export const prerender = false; import type { APIRoute } from 'astro'; import { writeFile, mkdir } from 'node:fs/promises'; import { join, extname, basename } from 'node:path'; const UPLOAD_ROOT = join(process.cwd(), 'data', 'uploads'); const IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.webp', '.svg', '.gif']); const DOCUMENT_EXT = new Set(['.pdf']); const MAX_SIZE: Record<'image' | 'document', number> = { image: 5 * 1024 * 1024, document: 8 * 1024 * 1024, }; function sanitize(filename: string): string { const name = basename(filename).replace(/[^a-zA-Z0-9._-]/g, '_'); return name.slice(-120); } export const POST: APIRoute = async ({ request }) => { const form = await request.formData(); const file = form.get('file'); const folder = (form.get('folder') as string | null) ?? ''; const kindRaw = (form.get('kind') as string | null) ?? 'image'; const kind: 'image' | 'document' = kindRaw === 'document' ? 'document' : 'image'; if (!(file instanceof File)) { return new Response(JSON.stringify({ error: 'no_file' }), { status: 400 }); } const ext = extname(file.name).toLowerCase(); const allowed = kind === 'document' ? DOCUMENT_EXT : IMAGE_EXT; if (!allowed.has(ext)) { return new Response(JSON.stringify({ error: 'invalid_extension' }), { status: 400 }); } if (file.size > MAX_SIZE[kind]) { return new Response(JSON.stringify({ error: 'file_too_large' }), { status: 400 }); } const safeFolder = folder.replace(/[^a-zA-Z0-9_-]/g, ''); const bucket = kind === 'document' ? 'assets' : 'img'; const targetDir = safeFolder ? join(UPLOAD_ROOT, bucket, safeFolder) : join(UPLOAD_ROOT, bucket); await mkdir(targetDir, { recursive: true }); const filename = sanitize(file.name); const buffer = Buffer.from(await file.arrayBuffer()); await writeFile(join(targetDir, filename), buffer); const url = `/api/files/${bucket}/${safeFolder ? safeFolder + '/' : ''}${filename}`; return new Response(JSON.stringify({ ok: true, url }), { headers: { 'Content-Type': 'application/json' }, }); };