update: add gestion erreur si dossier est existant

This commit is contained in:
Tom Boullay
2026-04-14 13:26:49 +02:00
parent c795082ca4
commit 2b3d02e489
4 changed files with 287 additions and 27 deletions
+120 -15
View File
@@ -15,6 +15,7 @@ const MODEL_EXTENSIONS = new Set(['.glb', '.gltf'])
const TEXTURE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp'])
const ALL_ALLOWED_EXTENSIONS = new Set([...MODEL_EXTENSIONS, ...TEXTURE_EXTENSIONS])
const REQUIRED_TEXTURES = ['roughness', 'normal', 'metalness', 'color', 'displace']
const VALID_DESTINATIONS = new Set(['farm', 'map', 'powergrid', 'workshop', 'general', 'environment'])
const TMP_DIR = join('/tmp', 'assets')
@@ -89,11 +90,12 @@ async function compressWithBlender(
function buildCommitMessage(
folderName: string,
destination: string,
modelFilename: string,
textureNames: string[],
compressed: boolean,
): string {
const title = `update: from upload-gltf add a new model -> ${folderName}`
const title = `update: upload-gltf add a new model -> ${destination}/${folderName}`
const foundTextures = new Set(
textureNames.map(t => t.toLowerCase().replace(/\.[^.]+$/, ''))
@@ -111,8 +113,7 @@ function buildCommitMessage(
title,
'',
'📦 Model',
`${modelFilename}${compressed ? ' (Draco)' : ''}`,
'',
`${modelFilename}${compressed ? ' (compressed)' : ''}`,
'🎨 Textures',
...textureLines,
]
@@ -133,12 +134,19 @@ interface ParsedFile {
async function parseMultiUpload(req: NextRequest): Promise<{
folderName: string
destination: string
files: ParsedFile[]
}> {
const formData = await req.formData()
const folderName = (formData.get('folderName') as string | null)?.trim() || 'assets'
const safeFolderName = sanitizeFilename(folderName).replace(/[^a-zA-Z0-9-_]/g, '-')
const rawDestination = (formData.get('destination') as string | null)?.trim() || 'general'
if (!VALID_DESTINATIONS.has(rawDestination)) {
throw new Error(`Destination invalide: "${rawDestination}"`)
}
const destination = rawDestination
const fileEntries = formData.getAll('files') as File[]
const fileTypes = formData.getAll('fileTypes') as string[]
const textureNames = formData.getAll('textureNames') as string[]
@@ -176,7 +184,7 @@ async function parseMultiUpload(req: NextRequest): Promise<{
parsed.push({ filename, buffer, isModel, textureName: texName || undefined })
}
return { folderName: safeFolderName, files: parsed }
return { folderName: safeFolderName, destination, files: parsed }
}
// ---------------------------------------------------------------------------
@@ -186,6 +194,7 @@ async function parseMultiUpload(req: NextRequest): Promise<{
async function pushAllToGitHub(
folderName: string,
files: { path: string; contentBase64: string }[],
deletePaths: string[],
commitMessage: string
): Promise<{ commitUrl: string }> {
const octokit = getOctokit()
@@ -219,17 +228,30 @@ async function pushAllToGitHub(
)
)
// 4. Create a single tree with all files
// 4. Create a single tree with all files (add new + delete orphans)
const newFilePaths = new Set(files.map(f => f.path))
const deleteEntries = deletePaths
.filter(p => !newFilePaths.has(p))
.map(p => ({
path: p,
mode: '100644' as const,
type: 'blob' as const,
sha: null,
}))
const { data: newTree } = await octokit.git.createTree({
owner,
repo,
base_tree: commit.tree.sha,
tree: files.map((f, i) => ({
path: f.path,
mode: '100644' as const,
type: 'blob' as const,
sha: blobResults[i].data.sha,
})),
tree: [
...files.map((f, i) => ({
path: f.path,
mode: '100644' as const,
type: 'blob' as const,
sha: blobResults[i].data.sha,
})),
...deleteEntries,
],
})
// 5. Create a single commit
@@ -252,6 +274,65 @@ async function pushAllToGitHub(
return { commitUrl: newCommit.html_url }
}
// ---------------------------------------------------------------------------
// GET handler — check if folder already exists on remote
// ---------------------------------------------------------------------------
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const destination = searchParams.get('destination')?.trim()
const folderName = searchParams.get('folderName')?.trim()
const secret = req.headers.get('x-upload-secret')
const expectedSecret = process.env.UPLOAD_SECRET_KEY
if (!expectedSecret || !secret || secret !== expectedSecret) {
return NextResponse.json({ success: false, error: 'Non autorise' }, { status: 401 })
}
if (!destination || !folderName) {
return NextResponse.json({ success: false, error: 'Parametres manquants' }, { status: 400 })
}
if (!VALID_DESTINATIONS.has(destination)) {
return NextResponse.json({ success: false, error: 'Destination invalide' }, { status: 400 })
}
const safeFolderName = sanitizeFilename(folderName).replace(/[^a-zA-Z0-9-_]/g, '-')
const folderPath = `public/models/${destination}/${safeFolderName}`
try {
const octokit = getOctokit()
const { owner, repo } = parseRepoUrl()
const branch = process.env.GIT_BRANCH ?? 'main'
const { data } = await octokit.repos.getContent({
owner,
repo,
path: folderPath,
ref: branch,
})
if (Array.isArray(data)) {
const existingFiles = data.map(f => f.name)
return NextResponse.json({
success: true,
exists: true,
path: folderPath,
files: existingFiles,
})
}
return NextResponse.json({ success: true, exists: false })
} catch (err: unknown) {
const status = (err as { status?: number })?.status
if (status === 404) {
return NextResponse.json({ success: true, exists: false })
}
const message = err instanceof Error ? err.message : 'Erreur inconnue'
return NextResponse.json({ success: false, error: message }, { status: 500 })
}
}
// ---------------------------------------------------------------------------
// POST handler
// ---------------------------------------------------------------------------
@@ -277,10 +358,11 @@ export async function POST(req: NextRequest) {
// --- Parse all files ---
let folderName: string
let destination: string
let parsedFiles: ParsedFile[]
try {
;({ folderName, files: parsedFiles } = await parseMultiUpload(req))
;({ folderName, destination, files: parsedFiles } = await parseMultiUpload(req))
} catch (err) {
const message = err instanceof Error ? err.message : 'Erreur inconnue'
return NextResponse.json({ success: false, error: message }, { status: 400 })
@@ -325,17 +407,40 @@ export async function POST(req: NextRequest) {
}
filesToPush.push({
path: `public/assets/${folderName}/${pf.filename}`,
path: `public/models/${destination}/${folderName}/${pf.filename}`,
contentBase64: content.toString('base64'),
})
}
// --- Build commit message ---
const commitMessage = buildCommitMessage(folderName, modelFilename, textureNames, compressed)
const commitMessage = buildCommitMessage(folderName, destination, modelFilename, textureNames, compressed)
// --- Detect existing files to clean up orphans ---
const folderPath = `public/models/${destination}/${folderName}`
let existingFilePaths: string[] = []
try {
const octokit = getOctokit()
const { owner, repo } = parseRepoUrl()
const branch = process.env.GIT_BRANCH ?? 'main'
const { data } = await octokit.repos.getContent({
owner,
repo,
path: folderPath,
ref: branch,
})
if (Array.isArray(data)) {
existingFilePaths = data.map(f => `${folderPath}/${f.name}`)
}
} catch {
// 404 = folder doesn't exist yet, no cleanup needed
}
// --- Push all in one commit ---
try {
const { commitUrl } = await pushAllToGitHub(folderName, filesToPush, commitMessage)
const { commitUrl } = await pushAllToGitHub(folderName, filesToPush, existingFilePaths, commitMessage)
return NextResponse.json({
success: true,