import { NextRequest, NextResponse } from 'next/server' import { validateUploadSecret } from '@/lib/auth' import { getRemoteFolder, pushAllToGit } 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' import { readStagingRequestBody, uploadErrorMessageResponse, uploadErrorResponse, uploadLockConflictResponse, } from '@/lib/upload-request' import { getErrorMessage } from '@/lib/guards' export const runtime = 'nodejs' export const dynamic = 'force-dynamic' async function cleanupCompletedStagingUpload(stagingId: string) { await cleanupStagingUpload(stagingId).catch((err) => { console.warn('[WARN] Git upload -> staging cleanup failed', { stagingId, error: getErrorMessage(err), }) }) } /** * POST /api/upload/git * Upload prepared files and push to the configured Git remote via Octokit. */ export async function POST(req: NextRequest) { const authError = validateUploadSecret(req) if (authError) return authError let folderName: string let stagingId: string try { stagingId = (await readStagingRequestBody(req)).stagingId const manifest = await readStagedManifest(stagingId) folderName = manifest.folderName } catch (err) { return uploadErrorResponse(err, 400) } if (!acquireUploadLock(folderName)) { return uploadLockConflictResponse() } try { const { filesToPush, modelFilename, compressed, deliveryMode, compressionError, assetSummaries, } = await ensurePreparedStagingAssets(stagingId) const folderPath = getModelFolderPath(folderName) const remote = await getRemoteFolder(folderPath) const remoteFileMap = new Map(remote.files.map((f) => [f.name.toLowerCase(), f.size])) const isReplace = remoteFileMap.size > 0 const { fileChanges, changedFilesToPush, deletedFileNames, deletePaths } = classifyFileChanges(filesToPush, remoteFileMap, folderPath) if (changedFilesToPush.length === 0 && deletePaths.length === 0) { await cleanupCompletedStagingUpload(stagingId) return NextResponse.json({ success: true, folderName, filesCount: 0, compressed, deliveryMode, compressionError: compressionError || undefined, message: 'Aucun fichier modifie — rien a envoyer.', }) } const commitMessage = buildCommitMessage( folderName, modelFilename, assetSummaries, isReplace, fileChanges, deletedFileNames, ) const { commitUrl } = await pushAllToGit(changedFilesToPush, deletePaths, commitMessage) await cleanupCompletedStagingUpload(stagingId) return NextResponse.json({ success: true, folderName, filesCount: changedFilesToPush.length, compressed, deliveryMode, compressionError: compressionError || undefined, message: `${changedFilesToPush.length} fichier(s) modifie(s) envoye(s) sur Git en un seul commit.`, commitUrl, }) } catch (err) { const message = getErrorMessage(err, 'Erreur Git inconnue') return uploadErrorMessageResponse(`Upload Git echoue: ${message}`, 500) } finally { releaseUploadLock(folderName) } }