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 }); } }