Files
gerbeur/api/routes/avatars.ts

82 lines
2.1 KiB
TypeScript

import { Router } from "@oak/oak";
import { authMiddleware } from "../middleware/auth.ts";
import { getUserById, updateUserAvatar } from "../services/user-service.ts";
import { updateClientAvatar } from "../services/ws-service.ts";
import { APIErrorCode, APIException } from "../model/interfaces.ts";
import {
AVATARS_DIR,
detectImageMime,
MAX_IMAGE_SIZE,
serveUploadedFile,
} from "../utils/upload.ts";
const router = new Router();
router.post("/api/avatars/me", authMiddleware, async (ctx) => {
const authPayload = ctx.state.user;
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 field");
}
if (file.size > MAX_IMAGE_SIZE) {
throw new APIException(
APIErrorCode.BAD_REQUEST,
400,
"File too large (max 5 MB)",
);
}
const data = new Uint8Array(await file.arrayBuffer());
const mime = detectImageMime(data);
if (!mime) {
throw new APIException(
APIErrorCode.BAD_REQUEST,
400,
"File content is not a recognised image (JPEG, PNG, GIF, WebP)",
);
}
await Deno.mkdir(AVATARS_DIR, { recursive: true });
await Deno.writeFile(`${AVATARS_DIR}/${authPayload.userId}`, data);
updateUserAvatar(authPayload.userId, mime);
updateClientAvatar(authPayload.userId, mime);
const user = getUserById(authPayload.userId);
ctx.response.status = 200;
ctx.response.body = { success: true, data: user };
});
router.get("/api/avatars/:userId", async (ctx) => {
const { userId } = ctx.params;
let user;
try {
user = getUserById(userId);
} catch {
ctx.response.status = 404;
return;
}
if (!user.avatarMime) {
ctx.response.status = 404;
return;
}
await serveUploadedFile(ctx, `${AVATARS_DIR}/${userId}`, user.avatarMime);
});
export default router;