Files
gunshiz 8ddb5896b6
CI / Verify (push) Successful in 1m28s
CI / Build immutable images and deploy (push) Successful in 2m40s
feat : add audit logs
2026-09-27 16:03:53 +07:00

37 lines
1.6 KiB
TypeScript

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);
}
}