refactor: consolidate upload helpers
This commit is contained in:
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
|||||||
import { validateUploadSecret } from '@/lib/auth'
|
import { validateUploadSecret } from '@/lib/auth'
|
||||||
import { getRemoteFolder } from '@/lib/github'
|
import { getRemoteFolder } from '@/lib/github'
|
||||||
import { classifyFileChanges } from '@/lib/diff-files'
|
import { classifyFileChanges } from '@/lib/diff-files'
|
||||||
|
import { getModelFolderPath } from '@/lib/model-paths'
|
||||||
import { ensurePreparedStagingAssets } from '@/lib/upload-staging'
|
import { ensurePreparedStagingAssets } from '@/lib/upload-staging'
|
||||||
|
|
||||||
export const runtime = 'nodejs'
|
export const runtime = 'nodejs'
|
||||||
@@ -30,7 +31,7 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { folderName, filesToPush } = await ensurePreparedStagingAssets(stagingId)
|
const { folderName, filesToPush } = await ensurePreparedStagingAssets(stagingId)
|
||||||
const folderPath = `public/models/${folderName}`
|
const folderPath = getModelFolderPath(folderName)
|
||||||
const { exists, files } = await getRemoteFolder(folderPath)
|
const { exists, files } = await getRemoteFolder(folderPath)
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { validateUploadSecret } from '@/lib/auth'
|
|||||||
import { getRemoteFolder, pushAllToGitHub } from '@/lib/github'
|
import { getRemoteFolder, pushAllToGitHub } from '@/lib/github'
|
||||||
import { buildCommitMessage } from '@/lib/commit-message'
|
import { buildCommitMessage } from '@/lib/commit-message'
|
||||||
import { classifyFileChanges } from '@/lib/diff-files'
|
import { classifyFileChanges } from '@/lib/diff-files'
|
||||||
|
import { getModelFolderPath } from '@/lib/model-paths'
|
||||||
import { cleanupStagingUpload, ensurePreparedStagingAssets, readStagedManifest } from '@/lib/upload-staging'
|
import { cleanupStagingUpload, ensurePreparedStagingAssets, readStagedManifest } from '@/lib/upload-staging'
|
||||||
import { acquireUploadLock, releaseUploadLock } from '@/lib/upload-lock'
|
import { acquireUploadLock, releaseUploadLock } from '@/lib/upload-lock'
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ export async function POST(req: NextRequest) {
|
|||||||
} = await ensurePreparedStagingAssets(stagingId)
|
} = await ensurePreparedStagingAssets(stagingId)
|
||||||
|
|
||||||
// --- Detect existing files and classify changes ---
|
// --- Detect existing files and classify changes ---
|
||||||
const folderPath = `public/models/${folderName}`
|
const folderPath = getModelFolderPath(folderName)
|
||||||
let remoteFileMap: Map<string, number>
|
let remoteFileMap: Map<string, number>
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { FolderEntry } from '@/lib/client-types'
|
import type { FolderEntry } from '@/lib/client-types'
|
||||||
|
import { revokeEntryUrls } from '@/lib/client-object-urls'
|
||||||
import { useSecret } from '@/hooks/useSecret'
|
import { useSecret } from '@/hooks/useSecret'
|
||||||
import { useFolderEntries } from '@/hooks/useFolderEntries'
|
import { useFolderEntries } from '@/hooks/useFolderEntries'
|
||||||
import { useUploadOrchestrator } from '@/hooks/useUploadOrchestrator'
|
import { useUploadOrchestrator } from '@/hooks/useUploadOrchestrator'
|
||||||
@@ -60,12 +61,7 @@ export default function UploadZone() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleFolderSelected = (entry: FolderEntry) => {
|
const handleFolderSelected = (entry: FolderEntry) => {
|
||||||
entries.forEach((current) => {
|
entries.forEach(revokeEntryUrls)
|
||||||
const urls = new Set<string>()
|
|
||||||
if (current.modelUrl) urls.add(current.modelUrl)
|
|
||||||
Object.values(current.assetUrls || {}).forEach((url) => urls.add(url))
|
|
||||||
urls.forEach((url) => URL.revokeObjectURL(url))
|
|
||||||
})
|
|
||||||
setGlobalError(null)
|
setGlobalError(null)
|
||||||
setEntries([entry])
|
setEntries([entry])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useCallback } from 'react'
|
import { useState, useCallback } from 'react'
|
||||||
import type { FolderEntry } from '@/lib/client-types'
|
import type { FolderEntry } from '@/lib/client-types'
|
||||||
|
import { revokeEntryUrls } from '@/lib/client-object-urls'
|
||||||
function revokeEntryUrls(entry: FolderEntry) {
|
|
||||||
const urls = new Set<string>()
|
|
||||||
if (entry.modelUrl) urls.add(entry.modelUrl)
|
|
||||||
Object.values(entry.assetUrls || {}).forEach((url) => urls.add(url))
|
|
||||||
urls.forEach((url) => URL.revokeObjectURL(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFolderEntries() {
|
export function useFolderEntries() {
|
||||||
const [entries, setEntries] = useState<FolderEntry[]>([])
|
const [entries, setEntries] = useState<FolderEntry[]>([])
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { FolderEntry } from '@/lib/client-types'
|
||||||
|
|
||||||
|
export function revokeEntryUrls(entry: FolderEntry) {
|
||||||
|
const urls = new Set<string>()
|
||||||
|
|
||||||
|
if (entry.modelUrl) urls.add(entry.modelUrl)
|
||||||
|
Object.values(entry.assetUrls || {}).forEach((url) => urls.add(url))
|
||||||
|
urls.forEach((url) => URL.revokeObjectURL(url))
|
||||||
|
}
|
||||||
+1
-6
@@ -3,12 +3,7 @@
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
import { MODEL_EXTENSIONS } from './constants'
|
import { MODEL_EXTENSIONS } from './constants'
|
||||||
import type { FileChange } from './types'
|
import type { FileChange, PushFile } from './types'
|
||||||
|
|
||||||
interface PushFile {
|
|
||||||
path: string
|
|
||||||
contentBase64: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DiffResult {
|
export interface DiffResult {
|
||||||
/** Map of lowercase filename → change status (for commit message) */
|
/** Map of lowercase filename → change status (for commit message) */
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import { createHash } from 'crypto'
|
import { createHash } from 'crypto'
|
||||||
import { Octokit } from '@octokit/rest'
|
import { Octokit } from '@octokit/rest'
|
||||||
import { LFS_EXTENSIONS } from './constants'
|
import { LFS_EXTENSIONS } from './constants'
|
||||||
import type { RemoteFile } from './types'
|
import type { PushFile, RemoteFile } from './types'
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Octokit helpers
|
// Octokit helpers
|
||||||
@@ -237,7 +237,7 @@ export async function getRemoteFolder(
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export async function pushAllToGitHub(
|
export async function pushAllToGitHub(
|
||||||
files: { path: string; contentBase64: string }[],
|
files: PushFile[],
|
||||||
deletePaths: string[],
|
deletePaths: string[],
|
||||||
commitMessage: string,
|
commitMessage: string,
|
||||||
): Promise<{ commitUrl: string }> {
|
): Promise<{ commitUrl: string }> {
|
||||||
@@ -247,7 +247,7 @@ export async function pushAllToGitHub(
|
|||||||
|
|
||||||
// --- Separate LFS files from regular files ---
|
// --- Separate LFS files from regular files ---
|
||||||
const lfsFiles: { path: string; contentBase64: string; oid: string; size: number }[] = []
|
const lfsFiles: { path: string; contentBase64: string; oid: string; size: number }[] = []
|
||||||
const regularFiles: { path: string; contentBase64: string }[] = []
|
const regularFiles: PushFile[] = []
|
||||||
|
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
if (isLfsFile(f.path)) {
|
if (isLfsFile(f.path)) {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export function getModelFolderPath(folderName: string) {
|
||||||
|
return `public/models/${folderName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelAssetPath(folderName: string, filename: string) {
|
||||||
|
return `${getModelFolderPath(folderName)}/${filename}`
|
||||||
|
}
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import { compressTextureBuffer } from '@/lib/texture-compression'
|
import { compressTextureBuffer } from '@/lib/texture-compression'
|
||||||
import { classifyAssetCategory } from '@/lib/asset-classification'
|
import { classifyAssetCategory } from '@/lib/asset-classification'
|
||||||
import type { ParsedFile, PreparedAssetSummary } from '@/lib/types'
|
import { getModelAssetPath } from '@/lib/model-paths'
|
||||||
|
import type { ParsedFile, PreparedAssetSummary, PushFile } from '@/lib/types'
|
||||||
interface PushFile {
|
|
||||||
path: string
|
|
||||||
contentBase64: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PrepareGitAssetsParams {
|
interface PrepareGitAssetsParams {
|
||||||
folderName: string
|
folderName: string
|
||||||
@@ -61,7 +57,7 @@ export async function prepareGitAssets({
|
|||||||
}
|
}
|
||||||
|
|
||||||
filesToPush.push({
|
filesToPush.push({
|
||||||
path: `public/models/${folderName}/${pf.filename}`,
|
path: getModelAssetPath(folderName, pf.filename),
|
||||||
contentBase64: content.toString('base64'),
|
contentBase64: content.toString('base64'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export interface ParsedFile {
|
|||||||
isModel: boolean
|
isModel: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PushFile {
|
||||||
|
path: string
|
||||||
|
contentBase64: string
|
||||||
|
}
|
||||||
|
|
||||||
export type FileChange = 'new' | 'changed' | 'unchanged'
|
export type FileChange = 'new' | 'changed' | 'unchanged'
|
||||||
|
|
||||||
export interface FileDiff {
|
export interface FileDiff {
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { dirname, join } from 'path'
|
|||||||
import { mkdir, readdir, readFile, rm, writeFile } from 'fs/promises'
|
import { mkdir, readdir, readFile, rm, writeFile } from 'fs/promises'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import { TMP_DIR } from '@/lib/constants'
|
import { TMP_DIR } from '@/lib/constants'
|
||||||
|
import { getModelAssetPath } from '@/lib/model-paths'
|
||||||
import { prepareGitAssets } from '@/lib/prepare-git-assets'
|
import { prepareGitAssets } from '@/lib/prepare-git-assets'
|
||||||
import type { ParsedFile, PreparedAssetSummary } from '@/lib/types'
|
import type { ParsedFile, PreparedAssetSummary, PushFile } from '@/lib/types'
|
||||||
|
|
||||||
const STAGING_ROOT = join(TMP_DIR, 'staging')
|
const STAGING_ROOT = join(TMP_DIR, 'staging')
|
||||||
const STAGING_TTL_MS = 60 * 60 * 1000
|
const STAGING_TTL_MS = 60 * 60 * 1000
|
||||||
@@ -30,11 +31,6 @@ interface StagingManifest {
|
|||||||
prepared?: StagedPreparedData
|
prepared?: StagedPreparedData
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PushFile {
|
|
||||||
path: string
|
|
||||||
contentBase64: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PreparedStageAssetsResult {
|
interface PreparedStageAssetsResult {
|
||||||
folderName: string
|
folderName: string
|
||||||
filesToPush: PushFile[]
|
filesToPush: PushFile[]
|
||||||
@@ -148,7 +144,7 @@ async function buildPreparedPushFiles(stagingId: string, manifest: StagingManife
|
|||||||
manifest.originals.map(async (file) => {
|
manifest.originals.map(async (file) => {
|
||||||
const buffer = await readFile(join(preparedDir, file.filename))
|
const buffer = await readFile(join(preparedDir, file.filename))
|
||||||
return {
|
return {
|
||||||
path: `public/models/${manifest.folderName}/${file.filename}`,
|
path: getModelAssetPath(manifest.folderName, file.filename),
|
||||||
contentBase64: buffer.toString('base64'),
|
contentBase64: buffer.toString('base64'),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user