import { Router } from "@oak/oak"; import { authMiddleware } from "../middleware/auth.ts"; import { createAttachment, getAttachment, } from "../services/attachment-service.ts"; import { APIErrorCode, APIException } from "../model/interfaces.ts"; import { ATTACHMENTS_DIR, serveUploadedFile, validateImageUpload, } from "../lib/upload.ts"; const router = new Router(); router.post("/api/attachments", authMiddleware, async (ctx) => { const contentType = ctx.request.headers.get("content-type") ?? ""; if (!contentType.includes("multipart/form-data")) { throw new APIException( APIErrorCode.BAD_REQUEST, 400, "Expected multipart/form-data", ); } const body = await ctx.request.body.formData(); const file = body.get("file"); if (!(file instanceof File)) { throw new APIException(APIErrorCode.BAD_REQUEST, 400, "Missing file"); } const data = new Uint8Array(await file.arrayBuffer()); const mime = validateImageUpload(data); const attachmentId = crypto.randomUUID(); await Deno.mkdir(ATTACHMENTS_DIR, { recursive: true }); await Deno.writeFile(`${ATTACHMENTS_DIR}/${attachmentId}`, data); try { createAttachment(attachmentId, mime); } catch (err) { await Deno.remove(`${ATTACHMENTS_DIR}/${attachmentId}`).catch(() => {}); throw err; } ctx.response.status = 201; ctx.response.body = { success: true, data: { id: attachmentId } }; }); router.get("/api/attachments/:attachmentId", async (ctx) => { const { attachmentId } = ctx.params; let attachment; try { attachment = getAttachment(attachmentId); } catch { ctx.response.status = 404; return; } await serveUploadedFile( ctx, `${ATTACHMENTS_DIR}/${attachmentId}`, attachment.mime, ); }); export default router;