Commit Graph

16 Commits

Author SHA1 Message Date
bvandeusen 47d2f61161 fix: drift audit batch 1 — six small mechanical wins
test-go / test (push) Successful in 32s
test-web / test (push) Successful in 42s
test-go / integration (push) Has been cancelled
Six findings from the 2026-06-02 multi-system drift audit (Scribe
parent task #552):

- **#553 (web)** Tailwind class fix: web admin playback-errors Delete
  confirm button was using `bg-action-danger`, an undefined token —
  swap to `bg-action-destructive` to match every other destructive
  button. Restored the Oxblood signal that distinguishes Delete from
  Cancel.

- **#555 (web)** Type the `source` field on `play_started` in the
  EventRequest discriminated union. Server's eventRequest accepts it;
  web's TS type was missing the slot, so a "drop extra properties"
  refactor could silently strip the source tag and break system-
  playlist rotation attribution.

- **#556 + #557 (server)** Coverage rollup whitelist was pinned to
  ('sidecar','embedded','mbcaa','theaudiodb'); migration 0020 added
  'deezer' and 'lastfm' as valid cover_art_source values but those
  never got wired in, so albums with art from those providers
  silently counted as MISSING in the admin Coverage dashboard. The
  rollup test was seeding only the pre-0020 sources, masking the
  gap in CI. Extend the query to include deezer + lastfm; seed the
  test with one row per valid source (regression-guards future
  additions).

- **#558 (web)** Auth gate was blocking /forgot-password and
  /reset-password/<token> — both are entered without a session by
  definition, so the email-link reset flow was bouncing signed-out
  users to /login. Add /forgot-password to the public set and a
  /reset-password/ prefix matcher. New tests assert both routes
  reach their pages without redirect.

- **#571 (server)** Library scanner was indexing only .mp3/.m4a/.flac
  /.ogg while the stream handler (media.go) had been extended to
  serve .opus, .aac, and .wav. A user with .opus files in their
  library never saw them in artist/album listings because the
  scanner skipped indexing — silent data loss. Aligned the scanner
  to match the media handler.

Scribe statuses updated to in_progress; flipping to done after the
push since these are mechanical and verified directly against the
cited file:lines.
2026-06-02 18:11:25 -04:00
bvandeusen c7549bbe48 perf(api): collapse N+1 in /api/artists/{id} + 1 round-trip in /api/albums/{id}
Two new sqlc queries replace three sequential per-album round trips
that were dominating detail-screen latency.

GetAlbumWithArtist: handleGetAlbum was doing GetAlbumByID then
GetArtistByID — separate round trips for one logical lookup. The new
query joins albums + artists with sqlc.embed and returns both in one
SELECT. Detail-page DB cost: 3 trips → 2.

ListAlbumsByArtistWithTrackCount: handleGetArtist was loading the
artist's album list, then issuing one CountTracksByAlbum per album to
populate track_count. On a 30-album artist that's 32 sequential
queries — each ~5ms over a local DB, ~30ms over a remote one. The
new query embeds the album row + a correlated count(*) subquery, so
every album's track count comes back in one SELECT regardless of
album count. Detail-page DB cost: 1 + N → 1 + 1.

Together these account for the bulk of cold-cache navigation latency
on the Flutter client. Combined with the existing SWR + nav
hydration on the client side, detail screens should render their
header instantly and the body within one round trip instead of
N+constant.
2026-05-11 18:13:27 -04:00
bvandeusen 9bf4b504b3 feat(server): GET /api/library/sync endpoint
Returns batched upserts + deletes since the supplied cursor. Empty cursor
returns full snapshot; subsequent calls pull deltas. Per-user entities
(likes, playlists) are scoped to the authed user. Composite-key entities
(likes, playlist_tracks) use stable string ids encoded by sync.EncodeLikeID
/ sync.EncodePlaylistTrackID.

Behavior:
  204 No Content - no changes since cursor
  200 OK         - JSON syncResponse {cursor, upserts, deletes}
  410 Gone       - cursor older than oldest log row; client must reset
  401 / 500      - standard envelope errors

Adds sqlc queries GetAlbumsByIDs, GetTracksByIDs, GetPlaylistsByIDs to
mirror the existing GetArtistsByIDs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:36:26 -04:00
bvandeusen 526a78b302 feat(db/m7-cover-sources): SQL queries for provider settings + recheck
- Extends GetAlbumCoverageRollup's with_art FILTER to include
  'theaudiodb' so F's coverage gauge surfaces TheAudioDB-found rows
  as with_art rather than silently undercounting.
- Modifies ListAlbumsMissingCover to take the current sources_version
  as a parameter; eligibility now includes stale-'none' rows.
- Adds SetAlbumCoverWithVersion (atomic write of path + source +
  version stamp) and the parallel SetArtistArtWithVersion +
  ClearArtistArtNone + ListArtistsMissingArt for the artist-art
  enricher.
- New coverart_settings.sql with ListProviderSettings,
  UpsertProviderSettings (boot reconciliation, preserves operator
  values on conflict), UpdateProviderSettings (admin PATCH; tri-
  state api_key handling), GetCurrentSourcesVersion,
  BumpSourcesVersion (RETURNING the new value).
2026-05-06 12:28:18 -04:00
bvandeusen ff5868bdab docs(db/m7-coverage-gauge): note CHECK-constraint sync + bucket invariant
Code-review polish on GetAlbumCoverageRollup's comment block:
- Records the invariant that with_art + pending + settled = total
  (and that pending_no_mbid is a subset of pending, not a fourth
  bucket).
- Notes that the IN ('sidecar','embedded','mbcaa') list must stay
  in sync with the cover_art_source CHECK constraint in migration
  0016 — without this note a future source addition could silently
  undercount with_art.
2026-05-06 09:52:10 -04:00
bvandeusen 98ec64c658 feat(db/m7-coverage-gauge): GetAlbumCoverageRollup query
FILTER-aggregate count over albums.cover_art_source and albums.mbid
returning total, with_art, pending, settled, pending_no_mbid in a
single round-trip. Sub-millisecond on realistic libraries; no index
needed for v1. pending_no_mbid is a subset of pending — the UI
tooltip uses it to show how many "pending" rows are blocked on
missing MBID and won't be moved by another scan.

Also bumps the Makefile sqlc pin from 1.27.0 to 1.31.1 to match the
version that produced all existing committed dbq/*.go files. The
prior pin was stale; running make generate against 1.27.0 silently
rolled the codegen back (cosmetic version headers on most files
plus a real codegen shape change in DeleteArtistIfEmpty).
2026-05-06 09:40:26 -04:00
bvandeusen 34615fffbd feat(server/m7-380): one-shot MBID backfill worker for existing libraries
Adds a boot-time goroutine that walks albums with NULL mbid, re-reads
tags from one track per album via dhowden/tag, and persists album + artist
MBIDs. Healed albums also get their cover_art_source='none' cleared so the
enricher's next batch retries the MBCAA fetch. Caps at 5000 albums per
boot to avoid stalling startup on huge libraries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:40:27 -04:00
bvandeusen 6c6c4bc1e4 feat(server/m7-379): heal existing artist+album mbid on rescan 2026-05-04 19:03:12 -04:00
bvandeusen 30b0edf97b feat(db): cascade-cleanup queries for M7 #372 track removal
DeleteTrack returns album_id + artist_id so the calling service can
chain the album-empty / artist-empty cascade. DeleteAlbumIfEmpty and
DeleteArtistIfEmpty are no-ops when other rows still reference the
parent — the service treats pgx.ErrNoRows as "not orphaned, skip".
2026-05-02 21:53:33 -04:00
bvandeusen f77245bc41 feat(requests): show ingest progress while requests are in flight
After Lidarr accepts a request the local reconciler imports albums and
tracks as they arrive — but until the request hit 'completed' the
operator had no way to see "is anything happening?" The /requests and
/admin/requests rows now surface a live progress line whenever the
request's matched entity has children in our library.

Backend:
- New CountAlbumsByArtist + CountTracksByArtist sqlc queries.
- requestView gains imported_album_count and imported_track_count.
- New fillProgress helper computes them from the matched entity:
  - kind=artist  → counts albums + tracks under matched_artist_id
  - kind=album   → counts tracks under matched_album_id
  - kind=track   → 1 once matched_track_id is set
  N+1 in the list endpoints; acceptable at admin scale.
- handleListRequests, handleGetRequest, and handleListAdminRequests
  populate the new fields on every response.

Frontend:
- LidarrRequest TS type extended with the two counters.
- Both the operator's /requests page and the admin /admin/requests
  page render an accent-colored line under the row meta when at
  least one counter is non-zero, e.g.:
    Artist:  "5 albums · 47 tracks ingested"
    Album:   "12 tracks ingested"
    Track:   "Track ingested"
- Updated test fixtures to include the new required fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:04:38 -04:00
bvandeusen b31723c5e2 feat(db): add M6a library-list + artist-tracks queries
Appends ListArtistsAlphaWithCovers, ListAlbumsAlphaWithArtist,
CountAlbums, and ListArtistTracksForUser queries; regenerates sqlc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 09:03:10 -04:00
bvandeusen 10c84a536e feat(db): add M6a home-section queries (recently added, most played, last played)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 08:56:53 -04:00
bvandeusen fb49a39492 feat(db): add paged + count queries for library reads 2026-04-21 00:01:15 -04:00
bvandeusen a498d20021 sqlc: add album listing/search queries for Subsonic browse (#295)
Adds ListAlbumsAlphaByName, ListAlbumsAlphaByArtist (with sqlc.embed
for the joined artist sort_name), ListAlbumsNewest, ListAlbumsRandom,
ListAlbumsByGenre, and SearchAlbums. Powers getAlbumList2 type filters
and the album facet of search3.
2026-04-19 19:02:03 +00:00
bvandeusen 918506168a M1/#293: add GetAlbumByArtistAndTitle for no-mbid dedupe 2026-04-19 15:15:35 +00:00
bvandeusen 58b29cc4bd M1/#292: album queries (upsert/get/list-by-artist) 2026-04-19 02:31:31 +00:00