feat(artists): GET /api/artists/{id}/profile — public artist profile

Merged
SW
2mo ago
+552-0

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.

FileRole
app/api/artists/[id]/profile/route.tsGET + OPTIONS, no auth
lib/artist/getArtistProfileHandler.ts404/500 shaping, UUID pre-check, cache header
lib/artist/getArtistPublicProfile.tsThe allowlist composition
lib/supabase/catalog_songs/countCatalogSongs.tsPer-catalog song counts via parallel head-count queries (exact counts, zero row transfer)

Three properties are load-bearing and each has a dedicated test:

  1. Allowlist, not blocklist. The response is built field-by-field; a DB row is never spread into it. The test seeds account_info with instruction/knowledges/label values marked PRIVATE and asserts the serialized response contains none of them.
  2. Is-an-artist gate. An account qualifies iff it appears as artist_id on at least one roster row (account_artist_ids) — personal and workspace accounts return null. Reuses the exact join (getAccountArtistIds) the authed roster list uses, so "artist" means the same thing everywhere.
  3. 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.

CheckResult
New suites12/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 filesclean
tsc --noEmit202 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_info private fields are never serialized.
  • Artist gate: account must appear as artist_id on 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.