feat(artists): GET /api/artists/{id}/profile — public artist profile
Implements the contract in docs#302 — row 2 of recoupable/chat#1963's PR matrix. Approved design: Artist Profile Page canvas.
What this adds
GET /api/artists/{id}/profile — the platform's first deliberately public read endpoint: no validateAuthContext, no credit gate, open CORS. One call returns { id, name, image, socials[], catalogs[] } for the shareable artist page.
| File | Role |
|---|---|
app/api/artists/[id]/profile/route.ts | GET + OPTIONS, no auth |
lib/artist/getArtistProfileHandler.ts | 404/500 shaping, UUID pre-check, cache header |
lib/artist/getArtistPublicProfile.ts | The allowlist composition |
lib/supabase/catalog_songs/countCatalogSongs.ts | Per-catalog song counts via parallel head-count queries (exact counts, zero row transfer) |
Three properties are load-bearing and each has a dedicated test:
- Allowlist, not blocklist. The response is built field-by-field; a DB row is never spread into it. The test seeds
account_infowithinstruction/knowledges/labelvalues markedPRIVATEand asserts the serialized response contains none of them. - Is-an-artist gate. An account qualifies iff it appears as
artist_idon at least one roster row (account_artist_ids) — personal and workspace accounts returnnull. Reuses the exact join (getAccountArtistIds) the authed roster list uses, so "artist" means the same thing everywhere. - One indistinguishable 404. Malformed id (rejected before any DB read), unknown id, and non-artist account all return the identical
{status:"error", message:"Artist not found"}— the endpoint cannot be used to probe which account ids exist.
Plus: Cache-Control: public, s-maxage=300, stale-while-revalidate=600 on 200s (public data, CDN-absorbable, crawler-safe), and a failed song count degrades to 0 rather than failing the profile.
Verification
TDD, red before green: all 3 new test files written first — RED (Cannot find module ×3, 0 tests ran) — then implemented to GREEN.
| Check | Result |
|---|---|
| New suites | 12/12 (profile composition, handler codes, counts) |
Full artist + catalog domains (lib/artist lib/artists lib/supabase/catalog_songs lib/supabase/account_catalogs lib/catalog) | 50 files / 312 tests passed |
eslint on all new files | clean |
tsc --noEmit | 202 errors, identical to the pre-existing baseline, zero in new files |
Not yet done: live preview verification (real artist id → documented shape with no auth header; non-artist account id and unknown id → identical 404s; instruction absent from a real response; cache header present). Flagging rather than implying it.
Merge order
docs#302 → this PR → chat page (row 3 of chat#1963). The chat PR's server fetch needs this on prod before it merges.
Summary by cubic
Adds GET /api/artists/{id}/profile, a public endpoint for shareable artist pages. Fixes empty catalog results by resolving catalogs through credited songs rather than catalog ownership.
- Unauthenticated with open CORS (OPTIONS included); 200s cache as "public, s-maxage=300, stale-while-revalidate=600".
- Malformed ids, unknown ids, and non-artist accounts all return the same 404 body: {status:"error", error:"Artist not found"}; 500s return {status:"error", error:"Internal server error"}.
- Response is an allowlist built field-by-field;
account_infoprivate fields are never serialized. - Artist gate: account must appear as
artist_idon a roster; otherwise 404. - Catalogs resolve via the songs graph (
song_artists→catalog_songs, ISRCs deduped and chunked); per-catalog song counts use parallel head-count queries; failures default to 0.
Rollout
- Merge docs PR
recoupable/docs#302(update the 404 example to{status,error}), deploy this endpoint, then merge the chat consumer (recoupable/chat#1963, row 3).
Summary by CodeRabbit
- New Features
- Added a public artist profile endpoint accessible without authentication.
- Profiles include artist names, images, social links, linked catalogs, and song counts.
- Added cross-origin access support for compatible clients.
- Bug Fixes
- Invalid, unknown, and non-artist profile requests now return consistent not-found responses.
- Unexpected errors return a standardized server-error response.
- Performance
- Successful profile responses now include caching guidance for faster repeat access.
- Large profile-related data requests are processed efficiently.