From 98f00d5a4c6a23ef86d17fdfa36ec69f1e37fc65 Mon Sep 17 00:00:00 2001 From: regalijan Date: Thu, 19 Oct 2023 16:49:52 -0400 Subject: [PATCH] Create initial new infraction endpoint --- functions/api/infractions/new.ts | 139 +++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 functions/api/infractions/new.ts diff --git a/functions/api/infractions/new.ts b/functions/api/infractions/new.ts new file mode 100644 index 0000000..08bf741 --- /dev/null +++ b/functions/api/infractions/new.ts @@ -0,0 +1,139 @@ +import { GenerateUploadURL } from "../../gcloud.js"; + +const allowedFileTypes = [ + "image/gif", + "image/heic", + "image/heif", + "image/jpeg", + "image/png", + "video/mp4", + "video/webm", +]; + +export async function onRequestPost(context: RequestContext) { + if ( + !context.data.current_user?.permissions || + ![1 << 0, 1 << 2, 1 << 11].find( + (p) => context.data.current_user.permissions & p + ) + ) + return new Response('{"error":"Forbidden"}', { + headers: { + "content-type": "application/json", + }, + status: 403, + }); + + if ( + context.request.headers + .get("content-type") + ?.startsWith("multipart/form-data; boundary=") + ) + return new Response('{"error":"Invalid content type"}', { + headers: { + "content-type": "application/json", + }, + status: 400, + }); + + let body: FormData; + + try { + body = await context.request.formData(); + } catch { + return new Response('{"error":"Invalid form data"}', { + headers: { + "content-type": "application/json", + }, + status: 400, + }); + } + + if ( + ![ + "verbal", + "warn", + "mute", + "ban_temp", + "ban_perm", + "ban_unappealable", + ].includes(body.get("punishment") as string) + ) + return new Response('{"error":"Invalid punishment"}', { + headers: { + "content-type": "application/json", + }, + status: 400, + }); + + if (!(body.get("user") as string).match(/^\d{17,19}$/)) + return new Response('{"error":"Invalid user"}', { + headers: { + "content-type": "application/json", + }, + status: 400, + }); + + // @ts-expect-error + const files: File[] = body.keys().find((key) => key.match(/^files\[\d]$/)); + const urlPromises = []; + + for (const file of files) { + if (!allowedFileTypes.includes(file.type)) + return new Response(`{"error":"File ${file.name} is not valid"}`, { + headers: { + "content-type": "application/json", + }, + status: 415, + }); + + const attachmentKey = `${Date.now()}${Math.round( + Math.random() * 10000000 + ).toString()}/${file.name}`; + + urlPromises.push( + GenerateUploadURL( + context.env, + attachmentKey, + file.size, + (allowedFileTypes.find((t) => t === file.type) as string).split("/")[1] + ) + ); + } + + const settledURLPromises = await Promise.allSettled(urlPromises); + + if (settledURLPromises.find((p) => p.status === "rejected")) + return new Response('{"error":"Failed to process one or more files"}', { + headers: { + "content-type": "application/json", + }, + status: 500, + }); + + const infractionId = `${body.get("user")}${Date.now()}${ + context.request.headers.get("cf-ray")?.split("-")[0] + }`; + const uploadReqs = []; + + for (let i = 0; i < files.length; i++) { + uploadReqs.push( + fetch(settledURLPromises[i] as unknown as string, { + body: files[i], + method: "PUT", + }) + ); + } + + await context.env.DATA.put( + infractionId, + JSON.stringify({ + created_at: Date.now(), + moderator: context.data.current_user.id, + }) + ); + + return new Response(null, { + status: 204, + }); +}