import { readSong } from "@/lib/browse"; import { readSpec, writeSpec, validateSpec, operationsFor, type Spec } from "@/lib/spec"; export const dynamic = "force-dynamic"; // Read and write one song's spec sheet. // // The other writer is song/spec.mjs on the CLI, which is the whole point -- // the sheet is meant to be something both sides edit. So this re-reads on every // request and writes through withStateLock + writeJsonAtomic, exactly as the // clip state does for the same reason. export async function GET(request: Request) { const id = new URL(request.url).searchParams.get("song") ?? ""; const song = await readSong(id); if (!song) return Response.json({ error: "no such song" }, { status: 404 }); const spec = await readSpec(id); return Response.json( { spec, problems: await validateSpec(spec), operations: operationsFor(spec, song) }, { headers: { "cache-control": "no-store" } }, ); } export async function PUT(request: Request) { let body: { song?: string; spec?: Spec }; try { body = await request.json(); } catch { return Response.json({ error: "bad json" }, { status: 400 }); } const id = body.song ?? ""; const song = await readSong(id); if (!song) return Response.json({ error: "no such song" }, { status: 404 }); if (!body.spec || typeof body.spec !== "object") { return Response.json({ error: "spec is required" }, { status: 400 }); } // Problems are REPORTED, not refused. A half-finished sheet is a normal state // to save from -- you point the background at a file you are about to render, // and it does not exist yet. Refusing the write would mean the only way to // author one is to get it right in a single pass. const spec = await writeSpec(id, body.spec); return Response.json( { ok: true, spec, problems: await validateSpec(spec), operations: operationsFor(spec, song) }, { headers: { "cache-control": "no-store" } }, ); }