Files
gerbeur/api/routes/attachments.ts

69 lines
1.7 KiB
TypeScript

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;