Files
gerbeur/api/routes/avatars.ts

72 lines
2.0 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,
serveUploadedFile,
validateImageUpload,
} from "../lib/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");
}
const data = new Uint8Array(await file.arrayBuffer());
const mime = validateImageUpload(data);
const filePath = `${AVATARS_DIR}/${authPayload.userId}`;
await Deno.mkdir(AVATARS_DIR, { recursive: true });
await Deno.writeFile(filePath, data);
try {
updateUserAvatar(authPayload.userId, mime);
} catch (err) {
// DB write failed — clean up the orphaned file
await Deno.remove(filePath).catch(() => {});
throw err;
}
updateClientAvatar(authPayload.userId, mime);
const { passwordHash: _, ...publicUser } = getUserById(authPayload.userId);
ctx.response.status = 200;
ctx.response.body = { success: true, data: publicUser };
});
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;