proteinea / src /app /api /upload /route.ts
Mahmoud Eljendy
feat: Antibody Studio — AI-native antibody design workspace by Proteinea
30cc31a
// POST /api/upload — accept multipart file upload, store via saveArtifact, return Artifact.
import { NextRequest, NextResponse } from "next/server";
import { saveArtifact, artifactId } from "@/server/storage";
import type { Artifact } from "@/lib/types";
export const dynamic = "force-dynamic";
const ALLOWED_EXTENSIONS = new Set([".fasta", ".pdb", ".csv", ".nwk"]);
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
function extFromName(name: string): string {
const idx = name.lastIndexOf(".");
return idx >= 0 ? name.slice(idx).toLowerCase() : "";
}
function mimeFromExt(ext: string): string {
const map: Record<string, string> = {
".fasta": "chemical/seq-aa-fasta",
".pdb": "chemical/x-pdb",
".csv": "text/csv",
".nwk": "text/x-newick",
};
return map[ext] || "application/octet-stream";
}
export async function POST(req: NextRequest): Promise<NextResponse> {
try {
const formData = await req.formData();
const file = formData.get("file");
const taskIdParam = formData.get("taskId");
if (!file || !(file instanceof File)) {
return NextResponse.json(
{ error: "Missing file in multipart body" },
{ status: 400 },
);
}
if (!taskIdParam || typeof taskIdParam !== "string") {
return NextResponse.json(
{ error: "Missing taskId in multipart body" },
{ status: 400 },
);
}
const ext = extFromName(file.name);
if (!ALLOWED_EXTENSIONS.has(ext)) {
return NextResponse.json(
{ error: `File extension "${ext}" not allowed. Accepted: ${Array.from(ALLOWED_EXTENSIONS).join(", ")}` },
{ status: 400 },
);
}
if (file.size > MAX_FILE_SIZE) {
return NextResponse.json(
{ error: `File too large (${(file.size / 1024 / 1024).toFixed(1)} MB). Max: 50 MB` },
{ status: 400 },
);
}
const id = artifactId();
const artifact: Artifact = {
id,
name: file.name,
bytes: file.size,
mime: mimeFromExt(ext),
url: `/api/artifacts/${id}`,
createdAt: Date.now(),
};
const buffer = Buffer.from(await file.arrayBuffer());
await saveArtifact(taskIdParam, artifact, buffer);
return NextResponse.json({ artifact }, { status: 201 });
} catch (err) {
const message = err instanceof Error ? err.message : "unknown error";
return NextResponse.json({ error: message }, { status: 500 });
}
}