brackt/app/routes/api.upload-sport-icon.ts

62 lines
1.9 KiB
TypeScript
Raw Normal View History

import { auth } from "~/lib/auth.server";
import { uploadSportIcon } from "~/lib/cloudinary.server";
import { logger } from "~/lib/logger";
import { isUserAdmin } from "~/models/user";
import type { Route } from "./+types/api.upload-sport-icon";
const MAX_UPLOAD_BYTES = 1024 * 1024;
function isSvgFile(file: File): boolean {
return file.type === "image/svg+xml" || file.name.toLowerCase().endsWith(".svg");
}
function looksLikeSvg(buffer: Buffer): boolean {
return buffer.toString("utf8", 0, Math.min(buffer.length, 1024)).includes("<svg");
}
export async function action({ request }: Route.ActionArgs) {
const session = await auth.api.getSession({ headers: request.headers });
const userId = session?.user.id ?? null;
if (!userId) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
if (!(await isUserAdmin(userId))) {
return Response.json({ error: "Forbidden" }, { status: 403 });
}
const formData = await request.formData();
const file = formData.get("file");
const slug = formData.get("slug");
if (!(file instanceof File)) {
return Response.json({ error: "SVG file is required" }, { status: 400 });
}
if (!isSvgFile(file)) {
return Response.json({ error: "Icon must be an SVG file" }, { status: 400 });
}
if (file.size > MAX_UPLOAD_BYTES) {
return Response.json({ error: "Icon must be under 1 MB" }, { status: 400 });
}
try {
const buffer = Buffer.from(await file.arrayBuffer());
if (!looksLikeSvg(buffer)) {
return Response.json({ error: "Icon must be a valid SVG file" }, { status: 400 });
}
const result = await uploadSportIcon({
buffer,
slug: typeof slug === "string" ? slug : null,
});
return Response.json({ iconUrl: result.secure_url });
} catch (error) {
logger.error("Sport icon upload failed:", error);
return Response.json({ error: "Upload failed" }, { status: 500 });
}
}