music: POST /reindex?full=1 — on-demand full staged rebuild

The default POST /reindex is incremental (skips unchanged albums by version
stamp), so it can't backfill a meta-format change like the new track `disc`
field. Add ?full=1 to run reindexFull() instead — a from-scratch rebuild into a
fresh slot, atomically swapped in (safe, never disrupts the live index). Same
30-min per-request timeout applies. The nightly 3am run still does this
automatically; this is the on-demand trigger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 22:38:18 +00:00
co-authored by Claude Opus 4.8
parent 40080f7f1e
commit 8980cfe717
+11 -5
View File
@@ -7,6 +7,7 @@ import { startNightlyReindex, stopNightlyReindex } from './nightly-reindex';
import { startMusicWatcher, stopMusicWatcher } from './watcher';
import {
reindexNow,
reindexFull,
ensureCacheSetup,
MUSIC_ROOT,
getIndexStatus,
@@ -49,7 +50,9 @@ const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
// "<Type>" } }, Type ∈ Studio/Live/Compilation/Single/EP/…
// ETag: <v>; If-None-Match → 304. Source: each artist folder's
// _discography.md (normalized; the md itself is never modified).
// POST /reindex run the build to COMPLETION, then return the final IndexStatus.
// POST /reindex[?full=1] run the build to COMPLETION, then return the final IndexStatus.
// default = incremental (skips unchanged); ?full=1 = full staged
// rebuild + atomic swap (backfill a meta-format change).
// GET /reindex/status IndexStatus snapshot.
// GET /reindex/stream SSE. Triggers a build if idle (`?trigger=0` = watch-only).
// `event: progress` (IndexStatus) throttled ~200ms, then one
@@ -112,10 +115,13 @@ const server = Bun.serve({
// ── Index build ──
if (url.pathname === '/reindex') {
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
// Run the resync to completion, THEN respond — so the caller's manifest read right after is fresh.
// Incremental builds are near-instant (unchanged albums skip by version stamp). reindexNow joins
// an in-flight build rather than starting a second.
const result = await reindexNow();
// Run to completion, THEN respond — so the caller's manifest read right after is fresh. Both join an
// in-flight build rather than starting a second.
// default : incremental — near-instant, skips unchanged albums by version stamp.
// ?full=1 : full from-scratch rebuild into a fresh slot, swapped in atomically (staged + safe) —
// use to backfill a meta-format change (e.g. a new track field) across the WHOLE library.
const full = url.searchParams.get('full') === '1' || url.searchParams.get('full') === 'true';
const result = await (full ? reindexFull() : reindexNow());
return json(result);
}
if (url.pathname === '/reindex/status') return json(getIndexStatus());