Commit Graph

38 Commits

Author SHA1 Message Date
Jonathan 381426082e feat(web): richer active-job rows on The Floor (source/format/tracks, stage, elapsed, error)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 22:05:07 +02:00
Jonathan 3ec1547e10 fix(web): artistPlays via user.getTop{Tracks,Albums} (getArtistTracks deprecated)
Last.fm returns error 27 "Deprecated" for user.getArtistTracks, so the per-artist
plays modal was 502ing for every artist. Pivot to filtering the user's supported
all-time top tracks + top albums (which carry real per-item play counts) to the
artist — exact personal counts, cheaper, and the clicked artists are the user's
top artists so their items cluster near the front. Adds topTracks(); route + modal
return shape unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 21:38:22 +02:00
Jonathan 5816a17952 feat(web): artistPlays lib + /api/lastfm/artist-plays (personal per-artist tally) 2026-07-13 21:20:47 +02:00
Jonathan ecdad88973 feat(web): Last.fm client lib + top-artists/albums + mb release-group routes 2026-07-13 19:01:04 +02:00
Jonathan 5eb4fbfd55 feat(web): Last.fm credentials (api key + username) + Settings tab 2026-07-13 18:53:58 +02:00
Jonathan bbe1616ed2 fix(web): harden config PATCH allowlists against prototype-chain keys
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:06:49 +02:00
Jonathan e008ee8d13 fix(web): remove inert global autoMonitorFuture checkbox from Monitor tab
The worker never reads the global monitor.autoMonitorFuture Config key;
auto-monitor-future is governed solely by the per-artist
WatchedArtist.autoMonitorFuture toggle on the Artists page. Removes the
misleading no-op checkbox and its DEFAULTS/test entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:08:05 +02:00
Jonathan 44c1b1a62a fix(web): wanted POST converges on P2002 unique race instead of 500
Two concurrent Wants for the same release-group both pass the
findUnique existence check as absent; the loser's create() then hits
the rgMbid unique constraint and threw an uncaught P2002, 500ing the
request. Wrap the create in try/catch and fall back to the same
update-and-return-200 path on P2002, converging instead of erroring.
Non-P2002 errors are rethrown unchanged.
2026-07-13 00:40:29 +02:00
Jonathan fd0bb6aaa0 feat(web): surface discover.listenBrainzUrl; swap maxSeeds->chunkSize in Discovery tab 2026-07-13 00:33:49 +02:00
Jonathan 011852d091 fix(web): close prototype-chain bypass in DELETE /api/config allowlist
FIELDS[field] walked the prototype chain, so field=__proto__ (or
constructor, hasOwnProperty, etc.) resolved to a truthy inherited value
whose .key was undefined. Prisma drops undefined filter values, so
deleteMany({ where: { key: undefined } }) collapsed to deleteMany({}),
wiping the entire Config table. Guard with an own-property check before
indexing FIELDS so only real allowlist entries pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:27:22 +02:00
Jonathan 789eb2c76a feat(web): DELETE /api/config?field= to clear a credential
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:23:21 +02:00
Jonathan 2a50c1f518 feat(web): monitor config API route
Implement GET and PATCH endpoints for monitor configuration, matching the existing discover config pattern. GET returns stored or default values; PATCH upserts known keys with allowlist filtering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:16:43 +02:00
Jonathan 606ea82b70 feat: real worker status, covers on Recently Pressed, library counter
- Worker stamps a throttled worker.heartbeat; GET /api/worker reports online
  from its updatedAt recency (DB-time); masthead shows a live listening/offline
  dot instead of a static decoration.
- /api/requests enriched with rgMbid; Recently Pressed shows cover thumbs.
- Library album count moved from the filter row into a section header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 17:58:23 +02:00
Jonathan c027945191 feat(web): cover art on Wanted + Discover album rows
Wanted API returns rgMbid/type/year; Wanted rows show a cover thumb and open
the album modal. Discover suggested-album rows get a cover thumb too. Completes
cover art across the app (artist detail, Library, modals, Wanted, Discover).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:37:02 +02:00
Jonathan 4f6c5995f8 feat(web): Library page — owned-albums grid with cover art
New /library: GET /api/library lists LibraryItems enriched with rgMbid + year
from the matching MonitoredRelease. Client renders a cover-art grid with a live
filter; clicking an album opens the AlbumModal (tracklist). Added Library to the
nav. The album-only overview to see everything downloaded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:24:16 +02:00
Jonathan d05d092d53 feat(web): artist detail — category tabs, cover thumbs, album modal
Discography now has category tabs (All/Albums/Singles/EPs/Live/Comps/Other
with counts, defaults to Albums), cover-art thumbnails, and clicking an album
opens the AlbumModal with its tracklist. Detail GET gains ?all=true so the
client filters by tab (studio-only default still governs the artists-list
counts). Replaces the old flat date-sorted list + show-all toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:20:06 +02:00
Jonathan 33a1ed9cc4 fix(artists-api): create artist + discography atomically
Wrap the WatchedArtist.create + MonitoredRelease.createMany in a
$transaction so a mid-write failure can't leave a followed artist with
no releases (a state the 409 duplicate-follow guard would then wedge).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:42:42 +02:00
Jonathan 9f2b115721 feat(preview): POST /api/discover/want (want a release-group by metadata)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 01:42:49 +02:00
Jonathan 46164c9eab feat(preview): GET /api/discover/preview/[mbid] annotated discography
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 01:39:35 +02:00
Jonathan f3cb306a19 feat(preview): GET /api/mb/release-groups/[rgMbid]/tracks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 01:36:28 +02:00
Jonathan 10262f053a feat(discovery): GET/PATCH /api/discover/config
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:41:13 +02:00
Jonathan c7aeab5176 feat(discovery): POST /api/discover/[id] follow/want/dismiss actions
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:38:06 +02:00
Jonathan bf868829d8 feat(discovery): GET /api/discover/search interactive seed search
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:34:28 +02:00
Jonathan be59862dfc feat(discovery): POST/GET /api/discover/run trigger + status
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:32:01 +02:00
Jonathan f750b815d2 feat(discovery): GET /api/discover feed
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:29:28 +02:00
Jonathan 560fdaa4b0 feat: filter artist discography + counts to core releases unless showAllTypes 2026-07-11 18:05:46 +02:00
Jonathan 7cb99361a5 feat: /api/scan — request a library scan + read its result
Type the POST request param (_request: Request) to match the test's
call signature and keep tsc --noEmit clean, per Next.js route handler
convention already used elsewhere in the codebase.
2026-07-11 17:11:42 +02:00
Jonathan 9f895cfc10 fix: search-now dedupes against an active job (match worker enqueue invariant)
The worker's enqueue_due refuses to enqueue when a non-terminal job
already exists for a release. The search-now route enqueued
unconditionally, so two clicks (or a click racing the worker sweep)
could create two concurrent pipelines for the same album, both
importing and colliding on LibraryItem's @@unique([artist,album]).

Mirror the worker's guard: no-op if a job in a non-terminal state
(anything other than imported/needs_attention) already exists for the
release.
2026-07-11 14:19:31 +02:00
Jonathan cd652c7285 feat: wanted list API — list + standalone add 2026-07-11 14:04:35 +02:00
Jonathan 6615ecf3a7 feat: releases API — monitor toggle + search-now enqueue 2026-07-11 14:00:49 +02:00
Jonathan f5ceea8fde fix: narrow artist PATCH/DELETE 404 catch to Prisma P2025 (rethrow real errors) 2026-07-11 13:58:40 +02:00
Jonathan 1e39c74b16 feat: artist item API — detail, auto-monitor toggle, unfollow 2026-07-11 13:55:36 +02:00
Jonathan 7f0ac68657 feat: artists API — follow (persist discography) + list 2026-07-11 13:51:14 +02:00
Jonathan 8e97796bad feat: MusicBrainz proxy API routes (artist search + discography browse)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 13:45:24 +02:00
Jonathan 00b3dddca6 feat: Qobuz auth-token (user_id + token) login as a reliable alternative to email/password
Qobuz's email/password API login returns 401 even with valid credentials
(a known Qobuz limitation, unaffected by streamrip version). Add settings
fields for a Qobuz user ID + auth token (token stored encrypted); when both
are present StreamripClient uses use_auth_token=True, else falls back to
email/password.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 00:46:17 +02:00
Jonathan 1b11fa37bd feat: add config API with encrypted secret storage 2026-07-10 19:54:27 +02:00
Jonathan fd9969ae17 test: cover 400 validation branches in request API
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 16:51:24 +02:00
Jonathan 15bb68bc49 feat: add request create/list API 2026-07-10 16:47:54 +02:00