refactor: consolidate upload helpers

This commit is contained in:
Tom Boullay
2026-04-27 17:20:54 +02:00
parent 43cf48cc7d
commit d049318a73
11 changed files with 38 additions and 38 deletions
+2 -1
View File
@@ -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) {
+2 -1
View File
@@ -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 {
+2 -6
View File
@@ -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])
} }
+1 -7
View File
@@ -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[]>([])
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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)) {
+7
View File
@@ -0,0 +1,7 @@
export function getModelFolderPath(folderName: string) {
return `public/models/${folderName}`
}
export function getModelAssetPath(folderName: string, filename: string) {
return `${getModelFolderPath(folderName)}/${filename}`
}
+3 -7
View File
@@ -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'),
}) })
} }
+5
View File
@@ -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 -7
View File
@@ -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'),
} }
}), }),