import { readSong } from "@/lib/browse"; import { isTarget, readNotes, setNote, readTrimNotes, setTrimNote, NOTE_LIMIT } from "@/lib/notes"; import { TRIM_SETS } from "@/lib/trim"; export const dynamic = "force-dynamic"; // Notes on anything selectable. // // The target is validated for SHAPE but not for existence, deliberately. You // write a note about `cut:vertical` for a cut that has not been built yet -- // that is precisely when the note is worth having -- and about a clip from a // plan that is not the one currently selected. Requiring the thing to exist // first would rule out the most useful notes in the set. export async function GET(request: Request) { const url = new URL(request.url); const set = url.searchParams.get("set"); if (set) { if (!TRIM_SETS[set]) return Response.json({ error: "no such trim set" }, { status: 404 }); return Response.json( { notes: await readTrimNotes() }, { headers: { "cache-control": "no-store" } }, ); } const id = url.searchParams.get("song") ?? ""; const song = await readSong(id); if (!song) return Response.json({ error: "no such song" }, { status: 404 }); return Response.json({ notes: await readNotes(id) }, { headers: { "cache-control": "no-store" } }); } export async function POST(request: Request) { let body: { song?: string; set?: string; hook?: string; target?: string; note?: string }; try { body = await request.json(); } catch { return Response.json({ error: "bad json" }, { status: 400 }); } const note = typeof body.note === "string" ? body.note : ""; if (note.length > NOTE_LIMIT) { return Response.json({ error: `notes are capped at ${NOTE_LIMIT} characters` }, { status: 400 }); } // Trim-set notes are not song-scoped: a hook belongs to a catalogue shared // across songs, so it cannot live in one song's notes.json. if (body.set) { const set = TRIM_SETS[body.set]; if (!set) return Response.json({ error: "no such trim set" }, { status: 404 }); if (!body.hook || !/^[A-Za-z0-9._-]+$/.test(body.hook)) { return Response.json({ error: "hook is required" }, { status: 400 }); } const notes = await setTrimNote(set.id, body.hook, note); return Response.json({ ok: true, notes }, { headers: { "cache-control": "no-store" } }); } const id = body.song ?? ""; const target = body.target ?? ""; if (!isTarget(target)) return Response.json({ error: `bad target: ${target}` }, { status: 400 }); const song = await readSong(id); if (!song) return Response.json({ error: "no such song" }, { status: 404 }); const notes = await setNote(id, target, note); return Response.json( { ok: true, target, entry: notes[target] ?? null, notes }, { headers: { "cache-control": "no-store" } }, ); }