import { requireAdmin } from "@/lib/auth/server"; import { uploadPublicMedia } from "@/lib/media/repository"; import { MAX_MEDIA_BYTES, mediaUploadSchema } from "@/lib/media/validation"; import { boundedBody, errorResponse, HttpError, requireSameOrigin, withUploadSlot } from "@/lib/security/http"; import { limitRequest } from "@/lib/security/rate-limit"; import { auditActor } from "@/lib/audit-log"; export async function POST(request: Request) { try { requireSameOrigin(request); const admin = await requireAdmin(); await limitRequest("media", admin.user.id, 20); if (!request.headers.get("content-type")?.toLowerCase().startsWith("multipart/form-data;")) { throw new HttpError(415, "expected-multipart"); } return await withUploadSlot(async () => { let form: FormData; try { form = await boundedBody(request, MAX_MEDIA_BYTES + 64 * 1024).formData(); } catch (cause) { if (cause instanceof HttpError) throw cause; throw new HttpError(400, "invalid-form"); } const file = form.get("file"); if (!(file instanceof File)) throw new HttpError(400, "missing-image"); if (file.size > MAX_MEDIA_BYTES) throw new HttpError(413, "image-too-large"); if (!mediaUploadSchema.safeParse({ fileName: file.name, mimeType: file.type, byteSize: file.size }).success) { throw new HttpError(415, "invalid-image"); } const result = await uploadPublicMedia(file, auditActor(admin.user)); return Response.json(result, { status: 201, headers: { "Cache-Control": "no-store" } }); }); } catch (cause) { return errorResponse(cause); } }