brackt/app/routes/api.upload-team-logo.ts
2026-05-06 14:47:37 -07:00

60 lines
1.9 KiB
TypeScript

import { auth } from "~/lib/auth.server";
import { buildWebhookUrl, uploadAvatarImage } from "~/lib/cloudinary.server";
import { logger } from "~/lib/logger";
import { findTeamById } from "~/models/team";
import type { Route } from "./+types/api.upload-team-logo";
const MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
const ACCEPTED_IMAGE_TYPES = new Set(["image/jpeg", "image/png", "image/webp", "image/gif"]);
export async function action({ request }: Route.ActionArgs) {
const session = await auth.api.getSession({ headers: request.headers });
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
const formData = await request.formData();
const teamId = formData.get("teamId");
const file = formData.get("file");
if (typeof teamId !== "string" || !teamId) {
return Response.json({ error: "Team ID is required" }, { status: 400 });
}
const team = await findTeamById(teamId);
if (!team) {
return Response.json({ error: "Team not found" }, { status: 404 });
}
if (team.ownerId !== session.user.id) {
return Response.json({ error: "Forbidden" }, { status: 403 });
}
if (!(file instanceof File)) {
return Response.json({ error: "Image file is required" }, { status: 400 });
}
if (!ACCEPTED_IMAGE_TYPES.has(file.type)) {
return Response.json({ error: "Unsupported image type" }, { status: 400 });
}
if (file.size > MAX_UPLOAD_BYTES) {
return Response.json({ error: "Image must be under 5 MB" }, { status: 400 });
}
try {
const buffer = Buffer.from(await file.arrayBuffer());
await uploadAvatarImage({
buffer,
contentType: file.type,
target: "team",
entityId: team.id,
notificationUrl: buildWebhookUrl(request),
});
return Response.json({ status: "pending" });
} catch (error) {
logger.error("Team avatar upload failed:", error);
return Response.json({ error: "Upload failed" }, { status: 500 });
}
}