feat: /api/* library reads, search, and media endpoints #15
Reference in New Issue
Block a user
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ships Plans 2 and 3 of the web UI
/api/*rollout. Plan 2 delivers the paginated library reads and search; Plan 3 delivers the cover + stream byte-serving endpoints that Plan 2's DTOs already reference.Plan 3 — media endpoints
Spec:
docs/superpowers/specs/2026-04-21-web-ui-media-endpoints-design.mdPlan:
docs/superpowers/plans/2026-04-21-web-ui-media-endpoints.mdGET /api/albums/{id}/cover— cover art bytes; explicitcover_art_pathtakes precedence, else sidecar lookup (cover.{jpg,jpeg,png}thenfolder.{jpg,jpeg,png}) next to the first track's file.Content-Typefrom extension.GET /api/tracks/{id}/stream— raw audio bytes with Range /If-Modified-Since/ ETag viahttp.ServeContent.Content-Typefromtrack.file_format,Accept-Ranges: bytes.Both under
RequireUser. Vanished-file races are 404, not 500. MIME tables duplicated locally rather than shared withinternal/subsonic— deliberate per project direction (subsonic stays frozen).audioContentTypeintentionally diverges from subsonic (opus→audio/ogg, aac→audio/aac), documented inline.Plan 2 — library reads + search
Spec:
docs/superpowers/specs/2026-04-20-web-ui-library-reads-design.mdPlan:
docs/superpowers/plans/2026-04-20-web-ui-library-reads.mdGET /api/artists— paginated list,sort=alpha|newest, defaultlimit=50, clamped to[1,200]GET /api/artists/{id}— artist detail with nested album refsGET /api/albums/{id}— album detail with nested track refs (aggregatesduration_sec)GET /api/tracks/{id}— track detail with parent album + artist metadataGET /api/search?q=…— three-facet paginated search; each facet carries its owntotalPlan 2 highlights
{items, total, limit, offset}uniform across list and every search facetinternal/subsonic(packages stay independent — duplication is intentional)[], nevernull(make([]T, 0, len(x))and asserted in tests)limit/offsetsilently clamp; only non-numeric values return 400AlbumRef.duration_secis0in nested browse contexts, populated on direct album GET (per spec data flow)Test plan
internal/api/...test suite green (includes 20+ new tests for Plan 3 handlers + helpers)TestRoutesRegisteredInMountverifies all 7/api/*library/search/media routes wired underRequireUser<img src="/api/albums/{id}/cover">renders,<audio src="/api/tracks/{id}/stream">plays and seeksRange: bytes=0-99→ 206 withContent-Range, repeat withIf-Modified-Since→ 304🤖 Generated with Claude Code