import { NextResponse } from 'next/server'; import { withAuth, withPermission } from '@/lib/api/helpers'; import { errorResponse, ValidationError } from '@/lib/errors'; import { uploadFile } from '@/lib/services/files'; import { uploadFileSchema } from '@/lib/validators/files'; export const POST = withAuth( withPermission('files', 'upload', async (req, ctx) => { try { const formData = await req.formData(); const file = formData.get('file') as File | null; if (!file) { throw new ValidationError('No file provided'); } const buffer = Buffer.from(await file.arrayBuffer()); // A16: FormData.get returns null when the field is absent, not // undefined. Zod's .optional() accepts undefined but rejects null, // so the previous `as string | undefined` cast lied and uploads at // the hub root (no entity selected) 400'd. Coerce absent / empty // values to undefined before parse. const formStr = (key: string): string | undefined => { const v = formData.get(key); return typeof v === 'string' && v.length > 0 ? v : undefined; }; const metadata = uploadFileSchema.parse({ filename: formStr('filename') ?? file.name, clientId: formStr('clientId'), yachtId: formStr('yachtId'), companyId: formStr('companyId'), category: formStr('category'), entityType: formStr('entityType'), entityId: formStr('entityId'), folderId: formStr('folderId'), }); const result = await uploadFile( ctx.portId, ctx.portSlug, { buffer, originalName: file.name, mimeType: file.type, size: file.size, }, metadata, { userId: ctx.userId, portId: ctx.portId, ipAddress: ctx.ipAddress, userAgent: ctx.userAgent, }, ); return NextResponse.json({ data: result }, { status: 201 }); } catch (error) { return errorResponse(error); } }), );