Skip to content

feat(api): migrate GET /api/songs - #466

Merged
sweetmantech merged 6 commits into
testfrom
migration/songs-by-isrc
Apr 23, 2026
Merged

feat(api): migrate GET /api/songs#466
sweetmantech merged 6 commits into
testfrom
migration/songs-by-isrc

Conversation

@arpitgupta1214

@arpitgupta1214arpitgupta1214 commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator

Ports the legacy GET /api/songs Express route into mono api with validateAuthContext + Zod filters (isrc, artist_account_id); response shape is byte-identical to the legacy endpoint so existing callers need no adaptation.

Test plan

  • Unit tests: handler (200/400/401/500 with generic error), validator (auth/400/parse), supabase selector (filters, flatten, order)
  • pnpm test green (2184 passed)
  • pnpm lint:check clean
  • Preview smoke: 401 (no auth), 400 (bad uuid), 200 (isrc=<known> with x-api-key)

Summary by cubic

Adds a new GET /api/songs endpoint that returns songs with flattened artist accounts. Supports optional filters and auth, matching the legacy Express /songs response so existing callers (e.g., chat’s getSongsByIsrc) need no changes.

  • New Features

    • Filters: isrc and artist_account_id (UUID). Ordered by updated_at DESC.
    • Auth: x-api-key or Authorization: Bearer. No per-artist scope check.
    • Response: { status: "success", songs } with artists[] flattened from song_artists.accounts. Includes CORS preflight and headers.
    • Zod validation with consistent 400/401/500 responses without leaking internal errors.
  • Bug Fixes

    • artist_account_id filter now excludes non-matching songs via song_artists!inner (prevents empty song_artists rows).

Written for commit 3bc4656. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Introduced a new songs API endpoint enabling retrieval of songs with optional filtering by ISRC code or artist. Includes request validation, error handling, and cross-origin request support.

Port the legacy Recoup-Agent-APIs /songs endpoint into the mono api as
GET /api/songs (flat). Accepts optional isrc and artist_account_id filters,
orders by updated_at DESC, and returns { status, songs } with each song's
song_artists flattened into a top-level artists[] to preserve the wire shape
consumed by chat's getSongsByIsrc caller.
Auth policy: plain validateAuthContext (no checkAccountArtistAccess). Song
metadata is effectively DSP-public, so per-artist song lists are unions of
DSP-public data — scoping them wouldn't reduce exposure. Rationale
documented in the validator JSDoc so a future maintainer doesn't treat the
missing scope check as an oversight.
@vercel

vercelBot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
apiReadyReadyPreviewApr 22, 2026 0:04am

Request Review

@coderabbitai

coderabbitaiBot commented Apr 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@sweetmantech has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 25 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 9 minutes and 25 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e9462659-ad3d-4a7b-86cd-610da7b25a19

📥 Commits

Reviewing files that changed from the base of the PR and between f6bcb3a and 3bc4656.

⛔ Files ignored due to path filters (2)
  • lib/songs/__tests__/getSongsHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/songs/__tests__/validateGetSongsRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (1)
  • lib/supabase/songs/selectSongsWithArtists.ts
📝 Walkthrough

Walkthrough

This pull request introduces a new GET /api/songs API endpoint that fetches songs with associated artist information. The implementation includes request validation with optional ISRC and artist account filtering, database querying with related artist data, and comprehensive error handling with CORS headers.

Changes

Cohort / File(s)Summary
API Route
app/api/songs/route.ts
Establishes the Next.js API route with OPTIONS handler returning CORS headers and GET handler delegating to business logic.
Request Handling & Validation
lib/songs/getSongsHandler.ts, lib/songs/validateGetSongsRequest.ts
Implements request validation using Zod schema for optional isrc and artist_account_id query parameters; includes auth validation and returns standardized error responses on validation failure.
Database Query
lib/supabase/songs/selectSongsWithArtists.ts
Queries Supabase songs table with related song_artists and nested accounts; supports optional filtering and transforms the response to flatten artist data.

Sequence Diagram(s)

sequenceDiagram
participant Client
participant Route as API Route
participant Handler as getSongsHandler
participant Validator as validateGetSongsRequest
participant Auth as validateAuthContext
participant DB as selectSongsWithArtists
participant Supabase
Client->>Route: GET /api/songs?isrc=...
Route->>Handler: delegate request
Handler->>Validator: validate request
Validator->>Auth: check auth context
alt Auth fails
Auth-->>Validator: NextResponse (error)
Validator-->>Handler: NextResponse (error)
Handler-->>Route: NextResponse (error)
Route-->>Client: error response
else Auth succeeds
Auth-->>Validator: validated auth
Validator->>Validator: parse & validate query params
alt Params invalid
Validator-->>Handler: validationErrorResponse
Handler-->>Route: NextResponse (400)
Route-->>Client: validation error
else Params valid
Validator-->>Handler: GetSongsParams
Handler->>DB: selectSongsWithArtists(params)
DB->>Supabase: query songs with artists
Supabase-->>DB: song data + nested artists
DB->>DB: transform artist structure
DB-->>Handler: songs array
Handler->>Handler: format success response
Handler-->>Route: NextResponse (200)
Route-->>Client: { status: "success", songs }
end
end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested Reviewers

  • sweetmantech

Poem

🎵 A new songs endpoint takes its stage,
With validation guards and filters bright,
From auth to database, each layer plays its part,
CORS headers in the spotlight,
Data flows in graceful arcs of light! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Solid & Clean Code⚠️ WarningPull request violates SOLID & Clean Code principles: incorrect file naming convention (validateGetSongsRequest.ts should be validateGetSongsQuery.ts), SRP violation in selectSongsWithArtists function (43 lines mixing query building, filtering, execution, and transformation), missing type export, and validateGetSongsRequest exceeding guideline length.Rename validateGetSongsRequest.ts to validateGetSongsQuery.ts; extract data transformation logic from selectSongsWithArtists into separate utility; export SelectSongsWithArtistsParams type; consider breaking validateGetSongsQuery into focused functions.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch migration/songs-by-isrc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@arpitgupta1214

Copy link
Copy Markdown
CollaboratorAuthor

Preview smoke — api-git-migration-songs-by-isrc-recoupable-ad724970.vercel.app

CaseStatusBody
No auth401{"status":"error","error":"Exactly one of x-api-key or Authorization must be provided"}
artist_account_id=not-a-uuid (authed)400{"status":"error","missing_fields":["artist_account_id"],"error":"artist_account_id must be a valid UUID"}
isrc=USRC17607839 (authed, x-api-key)200{"status":"success","songs":[{"isrc":"USRC17607839","name":"Crazy Eyes","album":"Bigger Than Both Of Us",...,"artists":[…]}]}

recoup-api-git-migration-songs-by-isrc-recoupable-ad724970.vercel.app alias not registered for this preview (DEPLOYMENT_NOT_FOUND) — same pattern as other migration PRs; re-verify on that host after promotion to test.

@cubic-dev-aicubic-dev-aiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 8 files

Confidence score: 3/5

  • There is a concrete regression risk in lib/supabase/songs/selectSongsWithArtists.ts: filtering artist_account_id on an embedded relation without !inner can return songs that should be excluded, which may surface incorrect results to users.
  • The test-file size issue in lib/songs/__tests__/getSongsHandler.test.ts is low severity and mainly maintainability-focused, so it does not materially block merging on its own.
  • Given the medium-severity, high-confidence query behavior issue (6/10, confidence 9/10), this PR carries some user-impacting risk until the join/filter logic is tightened.
  • Pay close attention to lib/supabase/songs/selectSongsWithArtists.ts and lib/songs/__tests__/getSongsHandler.test.ts - fix relation filtering correctness first, then address test-file maintainability limits.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/supabase/songs/selectSongsWithArtists.ts">
<violation number="1" location="lib/supabase/songs/selectSongsWithArtists.ts:34">
P2: `artist_account_id` filtering is applied on an embedded relation without `!inner`, so non-matching songs can still be returned.</violation>
</file>
<file name="lib/songs/__tests__/getSongsHandler.test.ts">
<violation number="1" location="lib/songs/__tests__/getSongsHandler.test.ts:1">
P3: Custom agent: **Enforce Clear Code Style and Maintainability Practices**
Test file exceeds the 100-line file-size limit.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Client
participant API as Route Handler (Next.js)
participant Auth as validateAuthContext
participant Val as validateGetSongsRequest (Zod)
participant DB as selectSongsWithArtists (Supabase)
participant Supa as Supabase Database
Note over Client,Supa: NEW: GET /api/songs Flow
Client->>API: GET /api/songs?isrc=...&artist_account_id=...
API->>Val: validateGetSongsRequest(request)
Val->>Auth: NEW: validateAuthContext(request)
Note right of Auth: Checks x-api-key or Bearer token
alt Auth Failed
Auth-->>Val: 401 Unauthorized
Val-->>API: 401 Unauthorized
API-->>Client: 401 Unauthorized
else Auth Success
Auth-->>Val: AuthContext
end
Val->>Val: NEW: Parse & trim query params (Zod)
alt Invalid Parameters (e.g. malformed UUID)
Val-->>API: 400 Bad Request
API-->>Client: 400 Bad Request (missing_fields)
else Valid Parameters
Val-->>API: GetSongsParams
end
API->>DB: NEW: getSongsWithArtists(params)
DB->>Supa: select songs + song_artists + accounts!inner
alt Database Error
Supa-->>DB: DB Error
DB-->>API: throw Error
API->>API: internal logging
API-->>Client: 500 Internal Server Error (Obfuscated)
else Success
Supa-->>DB: Raw nested JSON
DB->>DB: NEW: Flatten song_artists.accounts -> artists[]
DB-->>API: { status: "success", songs }
end
API->>API: CHANGED: getCorsHeaders()
API-->>Client: 200 OK + JSON Body + CORS Headers
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment threadlib/supabase/songs/selectSongsWithArtists.ts Outdated
Comment threadlib/songs/__tests__/getSongsHandler.test.ts
…JSDoc
- Delete lib/songs/getSongsWithArtists.ts — envelope is a one-liner
built inline in the handler; YAGNI over a thin wrapper
- Switch .in(col, [val]) → .eq(col, val) in selectSongsWithArtists —
both filters are singular, so .in was dead plurality
- Trim JSDoc on handler/validator/supabase helper to minimal 'why'
comments only

@cubic-dev-aicubic-dev-aiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 5 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Recent migration supabase helpers (account_catalogs, account_socials)
ship without __tests__ — the coverage lives at the handler/validator
level. Follow that convention.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
lib/songs/validateGetSongsRequest.ts (2)

26-38: Minor: only the first validation issue is surfaced.

If both isrc and artist_account_id are invalid, the caller only ever sees one. That matches the preview behavior described in the PR, so this is intentional — just flagging in case you'd prefer to return all issues (e.g., missing_fields: error.issues.map(i => i.path).flat()). Feel free to ignore if parity with the legacy endpoint is the goal.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/songs/validateGetSongsRequest.ts` around lines 26 - 38, The current
validation in validateGetSongsRequest.ts only returns the first Zod issue (using
error.issues[0]/firstError); change it to surface all validation issues from
getSongsParamsSchema.safeParse by mapping error.issues into a consolidated
payload (e.g., aggregate messages and paths or a missing_fields array) and pass
that full array to validationErrorResponse instead of a single issue so callers
see every validation problem rather than just the first.

6-9: Adopt top-level z.uuid() for improved API and tree-shakability. Per Zod 4.1.x migration, z.string().uuid() is deprecated in favor of the top-level form. Note that z.uuid() enforces strict RFC 4122 compliance; if this endpoint accepts non-standard UUID formats, use z.guid() instead.

♻️ Proposed refactor
 export const getSongsParamsSchema = z.object({
isrc: z.string().trim().min(1, "isrc cannot be empty").optional(),
- artist_account_id: z.string().uuid("artist_account_id must be a valid UUID").optional(),+ artist_account_id: z.uuid("artist_account_id must be a valid UUID").optional(),
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/songs/validateGetSongsRequest.ts` around lines 6 - 9, Replace the
deprecated z.string().uuid(...) usage in getSongsParamsSchema with the top-level
z.uuid() (or z.guid() if non-RFC-4122 UUIDs are expected) for artist_account_id;
update the artist_account_id schema entry to use z.uuid("artist_account_id must
be a valid UUID").optional() (or z.guid(...).optional()) so the schema uses the
new top-level validator and preserves the existing error message and
optionality.
lib/supabase/songs/selectSongsWithArtists.ts (1)

8-8: Optional: add an explicit return type.

A Promise<Array<{ isrc: string; name: string | null; album: string | null; notes: string | null; updated_at: string; artists: Array<{ id: string; name: string; timestamp: string } | null> }>> (or a named type) helps callers and keeps the public API of this module honest — right now the return type is fully inferred from a large select string.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/supabase/songs/selectSongsWithArtists.ts` at line 8, The function
selectSongsWithArtists currently relies on TypeScript to infer a complex return
type from the select string; explicitly declare its return type to improve
caller ergonomics and API stability — either add a named interface (e.g.,
SongsWithArtists or SelectSongsWithArtistsResult) describing the array item
shape (isrc, name, album, notes, updated_at, artists: Array<{ id, name,
timestamp } | null>) and change the signature of selectSongsWithArtists(params:
SelectSongsWithArtistsParams = {}): Promise<SongsWithArtists[]> or directly
annotate it as Promise<Array<{ isrc: string; name: string | null; album: string
| null; notes: string | null; updated_at: string; artists: Array<{ id: string;
name: string; timestamp } | null> }>> so callers see the exact contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/songs/validateGetSongsRequest.ts`:
- Around line 1-41: Rename the file to validateGetSongsQuery.ts and rename the
exported function validateGetSongsRequest to validateGetSongsQuery (keeping and
exporting getSongsParamsSchema and GetSongsParams), update any imports that pull
validateGetSongsRequest to import validateGetSongsQuery instead, and ensure the
signature and return type remain Promise<NextResponse | GetSongsParams> and the
schema export name getSongsParamsSchema is preserved so callers and types
continue to work.
---
Nitpick comments:
In `@lib/songs/validateGetSongsRequest.ts`:
- Around line 26-38: The current validation in validateGetSongsRequest.ts only
returns the first Zod issue (using error.issues[0]/firstError); change it to
surface all validation issues from getSongsParamsSchema.safeParse by mapping
error.issues into a consolidated payload (e.g., aggregate messages and paths or
a missing_fields array) and pass that full array to validationErrorResponse
instead of a single issue so callers see every validation problem rather than
just the first.
- Around line 6-9: Replace the deprecated z.string().uuid(...) usage in
getSongsParamsSchema with the top-level z.uuid() (or z.guid() if non-RFC-4122
UUIDs are expected) for artist_account_id; update the artist_account_id schema
entry to use z.uuid("artist_account_id must be a valid UUID").optional() (or
z.guid(...).optional()) so the schema uses the new top-level validator and
preserves the existing error message and optionality.
In `@lib/supabase/songs/selectSongsWithArtists.ts`:
- Line 8: The function selectSongsWithArtists currently relies on TypeScript to
infer a complex return type from the select string; explicitly declare its
return type to improve caller ergonomics and API stability — either add a named
interface (e.g., SongsWithArtists or SelectSongsWithArtistsResult) describing
the array item shape (isrc, name, album, notes, updated_at, artists: Array<{ id,
name, timestamp } | null>) and change the signature of
selectSongsWithArtists(params: SelectSongsWithArtistsParams = {}):
Promise<SongsWithArtists[]> or directly annotate it as Promise<Array<{ isrc:
string; name: string | null; album: string | null; notes: string | null;
updated_at: string; artists: Array<{ id: string; name: string; timestamp } |
null> }>> so callers see the exact contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 67ddb040-2782-4bc5-b174-a7875d5fde77

📥 Commits

Reviewing files that changed from the base of the PR and between 41a014a and f6bcb3a.

⛔ Files ignored due to path filters (2)
  • lib/songs/__tests__/getSongsHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/songs/__tests__/validateGetSongsRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (4)
  • app/api/songs/route.ts
  • lib/songs/getSongsHandler.ts
  • lib/songs/validateGetSongsRequest.ts
  • lib/supabase/songs/selectSongsWithArtists.ts

Comment on lines +1 to +41
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { validateAuthContext } from "@/lib/auth/validateAuthContext";
import { validationErrorResponse } from "@/lib/zod/validationErrorResponse";

export const getSongsParamsSchema = z.object({
isrc: z.string().trim().min(1, "isrc cannot be empty").optional(),
artist_account_id: z.string().uuid("artist_account_id must be a valid UUID").optional(),
});

export type GetSongsParams = z.infer<typeof getSongsParamsSchema>;

/**
* Auth-only; no `checkAccountArtistAccess` — song metadata is DSP-public
* (same data DSPs expose via ISRC lookup), so per-artist scoping would not
* meaningfully reduce exposure.
*/
export async function validateGetSongsRequest(
request: NextRequest,
): Promise<NextResponse | GetSongsParams> {
const authResult = await validateAuthContext(request);
if (authResult instanceof NextResponse) {
return authResult;
}

const { searchParams } = new URL(request.url);
const rawParams: Record<string, string> = {};
const isrc = searchParams.get("isrc");
const artistAccountId = searchParams.get("artist_account_id");
if (isrc !== null) rawParams.isrc = isrc;
if (artistAccountId !== null) rawParams.artist_account_id = artistAccountId;

const { data, error } = getSongsParamsSchema.safeParse(rawParams);

if (error) {
const firstError = error.issues[0];
return validationErrorResponse(firstError.message, firstError.path);
}

return data;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Rename to validateGetSongsQuery.ts to match the repo's validator naming convention.

This endpoint validates query parameters (isrc, artist_account_id), not a request body, so per the lib/**/validate*.ts guideline the file should be validateGetSongsQuery.ts with the exported function renamed accordingly. The route/handler imports will need to follow.

As per coding guidelines: "Create validate functions in validate<EndpointName>Body.ts or validate<EndpointName>Query.ts files that export both the schema and inferred TypeScript type."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/songs/validateGetSongsRequest.ts` around lines 1 - 41, Rename the file to
validateGetSongsQuery.ts and rename the exported function
validateGetSongsRequest to validateGetSongsQuery (keeping and exporting
getSongsParamsSchema and GetSongsParams), update any imports that pull
validateGetSongsRequest to import validateGetSongsQuery instead, and ensure the
signature and return type remain Promise<NextResponse | GetSongsParams> and the
schema export name getSongsParamsSchema is preserved so callers and types
continue to work.

- Add !inner on song_artists embed — .eq("song_artists.artist", …)
on a left-joined embed filtered the array but still returned
songs with empty song_artists. !inner excludes the parent row.
- Trim handler + validator tests under the 100-line repo limit
using it.each for the parametrized 200/400 cases.

@cubic-dev-aicubic-dev-aiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/supabase/songs/selectSongsWithArtists.ts">
<violation number="1" location="lib/supabase/songs/selectSongsWithArtists.ts:18">
P2: Using `song_artists!inner` unconditionally changes the endpoint to exclude songs that have no artist join rows, which can silently drop valid songs from unfiltered results.</violation>
</file>
<file name="lib/songs/__tests__/getSongsHandler.test.ts">
<violation number="1" location="lib/songs/__tests__/getSongsHandler.test.ts:33">
P2: The success-path test was weakened: it no longer asserts the response envelope or CORS headers, so API contract regressions can pass unnoticed.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

album,
notes,
updated_at,
song_artists!inner (

@cubic-dev-aicubic-dev-aiBotApr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using song_artists!inner unconditionally changes the endpoint to exclude songs that have no artist join rows, which can silently drop valid songs from unfiltered results.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/songs/selectSongsWithArtists.ts, line 18:
<comment>Using `song_artists!inner` unconditionally changes the endpoint to exclude songs that have no artist join rows, which can silently drop valid songs from unfiltered results.</comment>
<file context>
@@ -15,7 +15,7 @@ export async function selectSongsWithArtists(params: SelectSongsWithArtistsParam
notes,
updated_at,
- song_artists (
+ song_artists!inner (
artist,
accounts!inner (
</file context>
Suggested change
song_artists!inner(
song_artists(
Fix with Cubic

Comment on lines +33 to +37
])("200 with query %s", async (qs, expected) => {
const res = await getSongsHandler(makeReq(`https://x/api/songs?${qs}`));
expect(res.status).toBe(200);
expect(mockSelectSongsWithArtists).toHaveBeenCalledWith(expected);
});

@cubic-dev-aicubic-dev-aiBotApr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The success-path test was weakened: it no longer asserts the response envelope or CORS headers, so API contract regressions can pass unnoticed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/songs/__tests__/getSongsHandler.test.ts, line 33:
<comment>The success-path test was weakened: it no longer asserts the response envelope or CORS headers, so API contract regressions can pass unnoticed.</comment>
<file context>
@@ -9,106 +9,54 @@ const mockSelectSongsWithArtists = vi.fn();
+ ["isrc=USRC17607839", { isrc: "USRC17607839" }],
+ [`artist_account_id=${UUID}`, { artist_account_id: UUID }],
+ ["", {}],
+ ])("200 with query %s", async (qs, expected) => {
+ const res = await getSongsHandler(makeReq(`https://x/api/songs?${qs}`));
+ expect(res.status).toBe(200);
</file context>
Suggested change
])("200 with query %s",async(qs,expected)=>{
constres=awaitgetSongsHandler(makeReq(`https://x/api/songs?${qs}`));
expect(res.status).toBe(200);
expect(mockSelectSongsWithArtists).toHaveBeenCalledWith(expected);
});
])("200 with query %s",async(qs,expected)=>{
constres=awaitgetSongsHandler(makeReq(`https://x/api/songs?${qs}`));
expect(res.status).toBe(200);
expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
constbody=awaitres.json();
expect(body.status).toBe("success");
expect(Array.isArray(body.songs)).toBe(true);
expect(mockSelectSongsWithArtists).toHaveBeenCalledWith(expected);
});
Fix with Cubic

@arpitgupta1214arpitgupta1214 changed the title feat(songs): add GET /api/songs (auth + filters)feat(api): migrate GET /api/songsApr 22, 2026
@sweetmantech

sweetmantech commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Preview smoke test

Against preview https://api-git-migration-songs-by-isrc-recoupable-ad724970.vercel.app at commit db123f61.

#CaseExpectedGot
1No auth header401✅ 401 "Exactly one of x-api-key or Authorization must be provided"
2Empty isrc query param400✅ 400 "isrc cannot be empty" + missing_fields: ["isrc"]
3Bad UUID for artist_account_id400✅ 400 "artist_account_id must be a valid UUID" + missing_fields: ["artist_account_id"]
4Auth + non-existent ISRC200 empty✅ 200 { "status": "success", "songs": [] }
5Auth + random valid-UUID artist_account_id200 empty✅ 200 { "status": "success", "songs": [] }
6Auth + caller's own accountId as artist_account_id200✅ 200 { "status": "success", "songs": [] } (caller's account isn't an artist)
7Auth + real ISRC USRC17607839200, 1 song✅ 200, single row returned with full shape (see below)
8Auth + real artist_account_id for Daryl Hall & John Oates200, ≥1 song✅ 200, same row (artist has 1 song indexed)
9Auth + no filter200✅ 200, 1000 rows (Supabase default cap)

Populated row shape (from case 7)

{
"isrc": "USRC17607839",
"name": "Crazy Eyes",
"album": "Bigger Than Both Of Us",
"notes": "Track: Crazy Eyes\nArtist: Daryl Hall & John Oates\n...",
"updated_at": "2026-04-02T22:44:31.916527+00:00",
"artists": [
{
"id": "ac214945-993e-4f72-a6d8-27d544d054ef",
"name": "Daryl Hall & John Oates",
"timestamp": null
}
]
}

Findings

  • Zod validation fires at the right layer; missing_fields array + error string match the project convention.
  • Response shape is the flat { status: "success", songs: [...] } per CLAUDE.md. Artists are flattened from the song_artists join into a top-level artists array on each song.
  • Both filter paths (isrc eq, artist_account_id eq on the inner-joined song_artists table) hit the DB correctly and return identical rows for a known mapping.
  • Auth pathway works for x-api-key; behavior parallel to the catalogs migration (feat(api): migrate GET /api/accounts/{id}/catalogs #464).
  • No checkAccountArtistAccess — the validator comment explains the intent ("song metadata is DSP-public"); handler is auth-only, which matches the stated design.
  • ⚠️ Unfiltered call returns 1000 rows (Supabase default cap). Worth flagging if this endpoint ends up client-facing — pagination / required filter parameter may be worth adding in a follow-up.

🤖 Tested with Claude Code

@sweetmantech
sweetmantech merged commit f185fa5 into testApr 23, 2026
5 checks passed
This was referenced Apr 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@arpitgupta1214@sweetmantech