From fe6f1fc09529d5286e68347ed320a945825ef810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 26 Jul 2026 03:23:06 +0000 Subject: [PATCH] music: document the full /api/music/* contract at the source of truth The proxy is an opaque catch-all, so the endpoint surface wasn't perceivable from the platform side. Add a contract header (all routes + params + SSE/response shapes) atop the sidecar fetch handler where the routes are defined, and point the proxy router at it. Co-Authored-By: Claude Opus 4.8 --- src/servers/api/music/router.ts | 8 ++++++-- src/servers/sidecar/music/index.ts | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/servers/api/music/router.ts b/src/servers/api/music/router.ts index 752a1974..c9966555 100644 --- a/src/servers/api/music/router.ts +++ b/src/servers/api/music/router.ts @@ -3,8 +3,12 @@ import { getMusicServerUrl } from './sidecar-server'; // Thin reverse-proxy for /api/music/*. Auth is handled upstream by userMiddleware (this router mounts // under the protected /api tree, so the media `?token=` path works). Everything else — path resolution, -// byte-range streaming, ffprobe duration — is done by the officer-music sidecar's audio server. We only -// forward the subpath + query + Range and stream the response back. +// byte-range streaming, ffprobe duration, indexing — is done by the officer-music sidecar's audio +// server. We only forward the subpath + query + Range and stream the response back. +// +// This is a catch-all, so it lists no routes: the full /api/music/* HTTP contract (stream, manifest, +// meta, cover, reindex, reindex/stream + their SSE/response shapes) is documented at the top of the +// sidecar's fetch handler — src/servers/sidecar/music/index.ts. export const musicRouter = createRouter(); diff --git a/src/servers/sidecar/music/index.ts b/src/servers/sidecar/music/index.ts index 969ddb9f..f053d6c4 100644 --- a/src/servers/sidecar/music/index.ts +++ b/src/servers/sidecar/music/index.ts @@ -21,6 +21,28 @@ const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); // resolution + streaming + ffprobe duration happens here); the platform API is just a thin proxy that // authenticates and forwards to us. The server listens on a random loopback port, reported to the API // on connect so it can route `/api/music/*` here. +// +// ───────────────────────────────────────────────────────────────────────────────────────────────── +// HTTP CONTRACT — the full `/api/music/*` surface (this fetch handler is the source of truth; the +// platform side is an opaque catch-all proxy). All routes are reached as `/api/music/`, authed +// upstream by userMiddleware (Bearer header or `?token=` for media). Data shapes are the exported +// `IndexStatus` / `IndexReport` / `IndexMeta` types in indexer.ts. +// +// GET /stream?path= audio with Range→206 (Content-Range/Length/Accept-Ranges) +// + `X-Audio-Duration` (seconds, ffprobe). 400/404/416. +// GET /manifest { version, generatedAt, albums: { "": { v, cover, tracks } } } +// GET /meta?path= album meta.json (IndexMeta). ETag: ; If-None-Match → 304. +// GET /cover?path= compressed cover.jpg. ETag: ; If-None-Match → 304. +// POST /reindex start an async build; returns IndexStatus (running: true). +// 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 +// `event: done` (IndexReport) and the stream closes. +// GET /health "ok". +// +// `` = album folder path relative to the Music root (e.g. "Albums/AC-DC/[1980] Back in Black"). +// `v` = per-album version stamp; unchanged `v` ⇒ nothing changed ⇒ the phone can skip re-downloading. +// ───────────────────────────────────────────────────────────────────────────────────────────────── const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`;