Board chips reserve future models; firmware uses searchable cards with detail pane and optional meta.json media/docs. Co-authored-by: Cursor <cursoragent@cursor.com>
44 lines
1.2 KiB
TypeScript
44 lines
1.2 KiB
TypeScript
import fs from "fs";
|
|
import path from "path";
|
|
import { NextResponse } from "next/server";
|
|
import { resolveMediaFile } from "@/lib/firmware";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
const MIME: Record<string, string> = {
|
|
".png": "image/png",
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".webp": "image/webp",
|
|
".gif": "image/gif",
|
|
".mp4": "video/mp4",
|
|
".webm": "video/webm",
|
|
".md": "text/markdown; charset=utf-8",
|
|
".txt": "text/plain; charset=utf-8",
|
|
".html": "text/html; charset=utf-8",
|
|
".pdf": "application/pdf",
|
|
};
|
|
|
|
type Params = {
|
|
params: Promise<{ project: string; variant: string; path: string[] }>;
|
|
};
|
|
|
|
export async function GET(_req: Request, { params }: Params) {
|
|
const { project, variant, path: parts } = await params;
|
|
const rel = parts.join("/");
|
|
const abs = resolveMediaFile(project, variant, rel);
|
|
if (!abs) {
|
|
return NextResponse.json({ error: "not found" }, { status: 404 });
|
|
}
|
|
|
|
const ext = path.extname(abs).toLowerCase();
|
|
const buf = fs.readFileSync(abs);
|
|
return new NextResponse(buf, {
|
|
headers: {
|
|
"Content-Type": MIME[ext] || "application/octet-stream",
|
|
"Content-Length": String(buf.length),
|
|
"Cache-Control": "public, max-age=3600",
|
|
},
|
|
});
|
|
}
|