import { NextRequest, NextResponse } from 'next/server' import { validateUploadSecret } from '@/lib/auth' import { getRemoteFolder, pushAllToGitHub } from '@/lib/github' import { buildCommitMessage } from '@/lib/commit-message' import { classifyFileChanges } from '@/lib/diff-files' import { getModelFolderPath } from '@/lib/model-paths' import { cleanupStagingUpload, ensurePreparedStagingAssets, readStagedManifest } from '@/lib/upload-staging' import { acquireUploadLock, releaseUploadLock } from '@/lib/upload-lock' export const runtime = 'nodejs' export const dynamic = 'force-dynamic' /** * POST /api/upload/git * Upload prepared files and push to GitHub via Octokit. */ export async function POST(req: NextRequest) { // --- Auth --- const authError = validateUploadSecret(req) if (authError) return authError let folderName: string let stagingId: string try { const body = await req.json() stagingId = body.stagingId if (!stagingId || typeof stagingId !== 'string') { throw new Error('stagingId manquant') } const manifest = await readStagedManifest(stagingId) folderName = manifest.folderName } catch (err) { const message = err instanceof Error ? err.message : 'Erreur inconnue' return NextResponse.json({ success: false, error: message }, { status: 400 }) } if (!acquireUploadLock(folderName)) { return NextResponse.json( { success: false, error: 'Un upload est deja en cours pour ce dossier. Patientez quelques secondes.' }, { status: 409 }, ) } try { // --- Process files (preserve model + buffers, compress textures for Git) --- const { filesToPush, modelFilename, compressed, compressionError, assetSummaries, } = await ensurePreparedStagingAssets(stagingId) // --- Detect existing files and classify changes --- const folderPath = getModelFolderPath(folderName) let remoteFileMap: Map try { const remote = await getRemoteFolder(folderPath) remoteFileMap = new Map(remote.files.map((f) => [f.name.toLowerCase(), f.size])) } catch { remoteFileMap = new Map() } const isReplace = remoteFileMap.size > 0 const { fileChanges, changedFilesToPush, deletedFileNames, deletePaths } = classifyFileChanges(filesToPush, remoteFileMap, folderPath) // If nothing changed, don't create an empty commit if (changedFilesToPush.length === 0 && deletePaths.length === 0) { await cleanupStagingUpload(stagingId).catch(() => {}) return NextResponse.json({ success: true, folderName, filesCount: 0, compressed, compressionError: compressionError || undefined, message: 'Aucun fichier modifie — rien a envoyer.', }) } // --- Build commit message --- const commitMessage = buildCommitMessage( folderName, modelFilename, assetSummaries, isReplace, fileChanges, deletedFileNames, ) // --- Push all in one commit --- try { const { commitUrl } = await pushAllToGitHub(changedFilesToPush, deletePaths, commitMessage) await cleanupStagingUpload(stagingId).catch(() => {}) return NextResponse.json({ success: true, folderName, filesCount: changedFilesToPush.length, compressed, compressionError: compressionError || undefined, message: `${changedFilesToPush.length} fichier(s) modifie(s) envoye(s) sur GitHub en un seul commit.`, commitUrl, }) } catch (err) { const message = err instanceof Error ? err.message : 'Erreur GitHub inconnue' return NextResponse.json( { success: false, error: `Push GitHub echoue: ${message}` }, { status: 500 }, ) } } finally { releaseUploadLock(folderName) } }