Skip to content

fix(accounts): authenticate POST /api/accounts/artists (P0a)#771

Merged
sweetmantech merged 3 commits into
mainfrom
fix/authenticate-accounts-artists
Jul 9, 2026
Merged

fix(accounts): authenticate POST /api/accounts/artists (P0a)#771
sweetmantech merged 3 commits into
mainfrom
fix/authenticate-accounts-artists

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes P0a in recoupable/chat#1860.

POST /api/accounts/artists was completely unauthenticated: the handler resolved the target account purely from a caller-supplied email and wrote account_artist_ids rows, letting anyone link any account to any canonical artist (and that roster row doubles as act-as-artist connector authority).

Changes

  • app/api/accounts/artists/route.ts: now requires validateAuthContext (x-api-key or Bearer), matching sibling authenticated routes. Returns 401 with no/ambiguous credential.
  • lib/accounts/resolveAddArtistAccountId.ts (new): the target account defaults to the authenticated account. The legacy email body field is now optional and gated: it only works when checkAccountAccess(authAccountId, target) passes (self / managed artist / owned workspace / member org); otherwise 403.
  • lib/accounts/addArtistToAccountHandler.ts: takes a resolved { accountId, artistId }; no longer trusts email input for account resolution.
  • lib/accounts/validateAddArtistBody.ts: email is now optional; artistId still a required UUID.

Caller impact

Grepped the mono root (chat, marketing, docs, skills, cli) for accounts/artists: no code callers found in chat or marketing. The only external reference is docs/api-reference/accounts/add-artist.mdx (OpenAPI page), which will need a follow-up docs update: email is now optional, auth is required, and new 401/403 responses exist. Any out-of-tree caller sending only {email, artistId} without a credential will now get 401 (intended: that was the vulnerability).

Test plan (TDD, red first)

  • New failing tests written first, confirmed RED, then implementation to GREEN.
  • pnpm exec vitest run lib/accounts app/api/accounts: 14 files, 60 tests passed (12 new tests: route 401/400/derive-from-credential/override-denied; resolver default/self/access-granted/403/404; handler insert/dedupe/db-error).
  • pnpm exec tsc --noEmit: no errors in any touched file; pre-existing errors remain in unrelated test files (lib/trigger/__tests__, lib/admins/..., lib/accounts/__tests__/validateOverrideAccountId.test.ts) that also fail on main.

Preview verification pending — tracked in chat#1860.

🤖 Generated with Claude Code


Summary by cubic

Secure the roster-link endpoint: POST /api/accounts/artists now requires auth and links artists to the authenticated account by default. The optional email override is access-gated, fixing P0a in recoupable/chat#1860.

  • Bug Fixes

    • Require validateAuthContext; return 401 when missing/invalid.
    • Derive target account from the credential; gate optional email via checkAccountAccess (403 on denial, 404 if not found).
    • Preserve validation order (400 body-first), sanitize write failures to a generic 400, and keep idempotency (200 when already linked).
    • Extract addArtistToAccountHandler, validateAddArtistRequest, and linkArtistToAccount; route delegates to the handler. Added tests for route, handler, validator, resolver, and linking.
  • Migration

    • Callers must send valid auth; unauthenticated requests now return 401.
    • Body requires artistId; email is optional and access-gated. Update API docs for auth and new 401/403 responses.

Written for commit c2e3571. Summary will update on new commits.

Review in cubic

Require validateAuthContext on the roster-link endpoint; derive the
target account from the credential. The legacy email body field is now
an optional override gated by checkAccountAccess.

Fixes P0a in recoupable/chat#1860.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
api Ready Ready Preview Jul 9, 2026 4:31pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5d6cb209-d557-4034-9266-8e53412df278

📥 Commits

Reviewing files that changed from the base of the PR and between fc1e0d7 and c2e3571.

⛔ Files ignored due to path filters (5)
  • app/api/accounts/artists/__tests__/route.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by app/**
  • lib/accounts/__tests__/addArtistToAccountHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/accounts/__tests__/linkArtistToAccount.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/accounts/__tests__/resolveAddArtistAccountId.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/accounts/__tests__/validateAddArtistRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (6)
  • app/api/accounts/artists/route.ts
  • lib/accounts/addArtistToAccountHandler.ts
  • lib/accounts/linkArtistToAccount.ts
  • lib/accounts/resolveAddArtistAccountId.ts
  • lib/accounts/validateAddArtistBody.ts
  • lib/accounts/validateAddArtistRequest.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/authenticate-accounts-artists

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.

@cubic-dev-ai cubic-dev-ai Bot 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.

3 issues found across 7 files

Confidence score: 3/5

  • lib/accounts/resolveAddArtistAccountId.ts currently returns different outcomes for unknown vs inaccessible emails (404 vs 403), which lets authenticated callers infer whether an account/email exists; merging as-is keeps an account-enumeration side channel in place — return a single generic denial response for both cases before merging.
  • lib/accounts/__tests__/addArtistToAccountHandler.test.ts highlights that handler errors can surface raw exception text (e.g., "boom") in JSON, so internal messages could leak to clients and create avoidable information disclosure risk — sanitize error responses to a generic message and update the 400-path assertion to verify body content.
  • app/api/accounts/artists/__tests__/route.test.ts is missing coverage for the allowed email override path, so a key authorization/lookup flow can regress without test detection — add the accepted-path test to lock expected behavior before merge.
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/accounts/resolveAddArtistAccountId.ts">

<violation number="1" location="lib/accounts/resolveAddArtistAccountId.ts:27">
P2: The email override still exposes account existence to any authenticated caller: unknown emails return the resolver's 404, while existing inaccessible emails return 403. Consider returning the same generic denial response for unresolved and inaccessible emails so this endpoint cannot be used for account/email enumeration.</violation>
</file>

<file name="lib/accounts/__tests__/addArtistToAccountHandler.test.ts">

<violation number="1" location="lib/accounts/__tests__/addArtistToAccountHandler.test.ts:58">
P2: The 400-error test asserts `res.status` but doesn't verify the response body. The handler leaks raw error messages — `new Error("boom")` becomes `{ message: "boom" }` in the JSON response. As reported in team feedback, exception text (stack traces, DB errors, etc.) should never appear in API responses; the server should log the full error and return a hardcoded message.</violation>
</file>

<file name="app/api/accounts/artists/__tests__/route.test.ts">

<violation number="1" location="app/api/accounts/artists/__tests__/route.test.ts:80">
P2: Missing test for the email override accepted path. The test suite covers the `email` denied case (403) and the no-email case (resolver returns same accountId), but not the `email` accepted case where `resolveAddArtistAccountId` returns a *different* accountId than the auth context. Adding this would verify that the route correctly passes through the resolver's output to the handler rather than inadvertently using the auth accountId.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client
    participant Route as POST /api/accounts/artists
    participant Auth as validateAuthContext
    participant Resolver as resolveAddArtistAccountId
    participant Access as checkAccountAccess
    participant DB as Database (Supabase)
    participant Handler as addArtistToAccountHandler

    Note over Client,Handler: NEW: Authenticated flow for linking artist to account

    Client->>Route: POST { artistId, ?email }
    Route->>Route: validateAddArtistBody()
    alt Body invalid (missing or bad artistId)
        Route-->>Client: 400 Bad Request
    end

    Route->>Auth: validateAuthContext(req)
    alt No/ambiguous credential
        Auth-->>Route: 401 NextResponse
        Route-->>Client: 401 Unauthorized
    else Credential valid
        Auth-->>Route: { accountId, authToken }
    end

    Route->>Resolver: resolveAddArtistAccountId(accountId, email)
    alt No email given
        Resolver-->>Route: authenticatedAccountId (default)
    else Email provided
        Resolver->>DB: resolveAccountIdByEmail(email)
        alt Email not found
            DB-->>Resolver: 404 NextResponse
            Resolver-->>Route: 404
            Route-->>Client: 404 Not Found
        else Email found
            DB-->>Resolver: targetAccountId
            alt targetAccountId == authenticatedAccountId
                Resolver-->>Route: targetAccountId (self)
            else Different account
                Resolver->>Access: checkAccountAccess(authId, targetId)
                alt Access granted
                    Access-->>Resolver: { hasAccess: true }
                    Resolver-->>Route: targetAccountId
                else Access denied
                    Access-->>Resolver: { hasAccess: false }
                    Resolver-->>Route: 403 NextResponse
                    Route-->>Client: 403 Forbidden
                end
            end
        end
    end

    Route->>Handler: addArtistToAccountHandler({ accountId, artistId })
    Handler->>DB: getAccountArtistIds([accountId])
    alt Artist already linked
        DB-->>Handler: existing link
        Handler-->>Route: 200 (idempotent)
    else Artist not yet linked
        DB-->>Handler: no duplicate
        Handler->>DB: insertAccountArtistId(accountId, artistId)
        alt Insert succeeds
            DB-->>Handler: ok
            Handler-->>Route: 200 Success
        else DB error
            DB-->>Handler: error
            Handler-->>Route: 400 Bad Request
        end
    end
    Route-->>Client: JSON response
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


const targetAccountId = await resolveAccountIdByEmail(email);
if (targetAccountId instanceof NextResponse) {
return targetAccountId;

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 email override still exposes account existence to any authenticated caller: unknown emails return the resolver's 404, while existing inaccessible emails return 403. Consider returning the same generic denial response for unresolved and inaccessible emails so this endpoint cannot be used for account/email enumeration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/accounts/resolveAddArtistAccountId.ts, line 27:

<comment>The email override still exposes account existence to any authenticated caller: unknown emails return the resolver's 404, while existing inaccessible emails return 403. Consider returning the same generic denial response for unresolved and inaccessible emails so this endpoint cannot be used for account/email enumeration.</comment>

<file context>
@@ -0,0 +1,43 @@
+
+  const targetAccountId = await resolveAccountIdByEmail(email);
+  if (targetAccountId instanceof NextResponse) {
+    return targetAccountId;
+  }
+
</file context>


const res = await addArtistToAccountHandler({ accountId: ACCOUNT_ID, artistId: ARTIST_ID });

expect(res.status).toBe(400);

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 400-error test asserts res.status but doesn't verify the response body. The handler leaks raw error messages — new Error("boom") becomes { message: "boom" } in the JSON response. As reported in team feedback, exception text (stack traces, DB errors, etc.) should never appear in API responses; the server should log the full error and return a hardcoded message.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/accounts/__tests__/addArtistToAccountHandler.test.ts, line 58:

<comment>The 400-error test asserts `res.status` but doesn't verify the response body. The handler leaks raw error messages — `new Error("boom")` becomes `{ message: "boom" }` in the JSON response. As reported in team feedback, exception text (stack traces, DB errors, etc.) should never appear in API responses; the server should log the full error and return a hardcoded message.</comment>

<file context>
@@ -0,0 +1,60 @@
+
+    const res = await addArtistToAccountHandler({ accountId: ACCOUNT_ID, artistId: ARTIST_ID });
+
+    expect(res.status).toBe(400);
+  });
+});
</file context>

});
});

it("returns the resolver error when email override is denied", async () => {

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: Missing test for the email override accepted path. The test suite covers the email denied case (403) and the no-email case (resolver returns same accountId), but not the email accepted case where resolveAddArtistAccountId returns a different accountId than the auth context. Adding this would verify that the route correctly passes through the resolver's output to the handler rather than inadvertently using the auth accountId.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/accounts/artists/__tests__/route.test.ts, line 80:

<comment>Missing test for the email override accepted path. The test suite covers the `email` denied case (403) and the no-email case (resolver returns same accountId), but not the `email` accepted case where `resolveAddArtistAccountId` returns a *different* accountId than the auth context. Adding this would verify that the route correctly passes through the resolver's output to the handler rather than inadvertently using the auth accountId.</comment>

<file context>
@@ -0,0 +1,95 @@
+    });
+  });
+
+  it("returns the resolver error when email override is denied", async () => {
+    vi.mocked(validateAuthContext).mockResolvedValue({
+      accountId: AUTH_ACCOUNT_ID,
</file context>

@sweetmantech

Copy link
Copy Markdown
Contributor Author

Preview verification — P0a: authenticate POST /api/accounts/artists

Preview: https://api-pk1fj8a32-recoup.vercel.app (deployment 5367846029, built from 39d4e36 — the PR head)
When: 2026-07-09 16:20 UTC
Callers: non-staff Privy token for account …ocnxy81t (zero orgs; rosters exactly one artist, Lady Gaga a9972289…). Happy-path add uses that already-rostered artist so the write stays idempotent (no row created — confirmed in row 7).

The fix

Before this PR the route never called validateAuthContextanyone could link any account to any canonical artist with just an email and no credential (and under the flat model that roster row was act-as-artist authority). Row 1 confirms that door is now closed.

Results

# Path Request Documented Actual Verdict
1 Auth gate (the fix) no credential, valid body {artistId} 401 HTTP 401 {"error":"Exactly one of x-api-key or Authorization must be provided"} unauthenticated write closed
2 Auth gate bad Bearer token 401 HTTP 401 {"message":"Failed to verify authentication token"}
3 Validation no auth, {} (missing artistId) 400 HTTP 400 missing_fields:["artistId"] ✅ (body validation precedes auth)
4 Validation {artistId:"not-a-uuid"} 400 HTTP 400 "artistId must be a valid UUID"
5 Happy path authed, {artistId:<already-rostered>} 200 HTTP 200 {"success":true}
6 Email override, not found authed, {artistId, email:<nonexistent>} 404 HTTP 404 {"error":"No account found for the provided email"}
7 No mutation GET /api/artists after row 5 roster unchanged roster count: 1 → ['Lady Gaga'] ✅ idempotent, no row added
8 Email override denied non-staff caller, {artistId, email:<foreign account>} 403 HTTP 403 {"error":"Access denied to the account for the provided email"} ✅ override gated by checkAccountAccess

Notes

  • Check ordering: body validation (400) runs before auth (401) — a malformed body 400s even unauthenticated (rows 3–4). Auth-then-resolve for valid bodies (rows 1, 5, 6, 8). Consistent with the diff.
  • The email override survives only as an access-gated path: self / accessible account → resolves (row 5-style), foreign account → 403 (row 8), unknown email → 404 (row 6). Derives the target from the credential otherwise — never from unauthenticated email.
  • Per the issue, this needs a follow-up docs update (add-artist.mdx: auth now required; document 401/403/404). Not in this PR's scope.

Verdict

The unauthenticated roster-write hole is closed (401), input validation and the access-gated email override behave as documented (400/403/404), and the happy path is idempotent with no unintended mutation. No regressions observed.

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

SRP

  • actual: handler code written in route.ts
  • required: handler + validator functions to match other endpoint architecture.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in c2e35716. Extracted the orchestration to match the connectors endpoint architecture:

  • route.ts → one-line delegate: return addArtistToAccountHandler(req)
  • addArtistToAccountHandler(request) → thin request handler
  • validateAddArtistRequest(request) → validator (body + auth + target-account resolution → {accountId, artistId} or error)
  • linkArtistToAccount({accountId, artistId}) → business/DB step

Behavior preserved (400 body-first → 401 → 403/404 → link). 60 accounts tests green; tsc + lint + prettier clean on touched files.

@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

… validator (SRP)

Route was carrying orchestration (parse, body validation, auth, account
resolution) inline. Mirror the connectors endpoint architecture:

- route.ts -> one-line delegate to addArtistToAccountHandler(request)
- addArtistToAccountHandler(request): thin request handler
- validateAddArtistRequest(request): bundles body + auth + target-account
  resolution, returns { accountId, artistId } or a NextResponse error
- linkArtistToAccount({ accountId, artistId }): the business/DB step

Behavior preserved (400 body-first, then 401, then 403/404, then link).
Also sanitize the link failure path to a generic 400 message + server log
(no raw exception text in the response). 60 accounts tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@sweetmantech

Copy link
Copy Markdown
Contributor Author

SRP refactor applied + re-verified live

Addressed the review note (handler code inline in route.ts). Refactored to match the connectors endpoint architecture in c2e35716:

Layer File Role
Route app/api/accounts/artists/route.ts one line: return addArtistToAccountHandler(req)
Request handler lib/accounts/addArtistToAccountHandler.ts calls the validator, then links
Validator lib/accounts/validateAddArtistRequest.ts body + auth + target-account resolution → {accountId, artistId} | NextResponse
Business/DB lib/accounts/linkArtistToAccount.ts idempotent insert into account_artist_ids

Mirrors authorizeConnectorHandlervalidateAuthorizeConnectorRequestauthorizeConnector. Also sanitized the link-failure path to a generic 400 message + server-side log (no raw exception text in the response — addresses the cubic P2 on addArtistToAccountHandler.test.ts:58), and added the email-override accepted-path test the cubic P2 on route.test.ts:80 asked for (now in validateAddArtistRequest.test.ts).

Tests: 60 accounts tests green (pnpm exec vitest run lib/accounts app/api/accounts/artists); tsc --noEmit, eslint, and prettier all clean on the touched files.

Re-verified live against the refactor preview https://api-iryovt5rp-recoup.vercel.app (deployment 5379383694, built from c2e35716) — behavior unchanged from the prior run:

# Path Actual
1 no credential 401
2 bad Bearer 401
3 missing artistId 400
4 non-UUID artistId 400
5 happy path (already-rostered, idempotent) 200 {"success":true}
6 email override, unknown email 404
8 email override, foreign account 403
7 roster after row 5 unchanged (1 → ['Lady Gaga'])

Remaining cubic P2 (not changed) — email enumeration (404 vs 403)

cubic flagged that unknown email → 404 while inaccessible email → 403 lets an authenticated caller probe account existence. Left as-is deliberately: collapsing both to one generic denial is a behavior change to the documented contract (and would need the docs update to match). Flagging for a decision — happy to fold it in here or split to a follow-up.

🤖 Generated with Claude Code

@cubic-dev-ai cubic-dev-ai Bot 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 (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/accounts/linkArtistToAccount.ts">

<violation number="1" location="lib/accounts/linkArtistToAccount.ts:24">
P3: This existence check loads the account's full artist roster and nested artist/social details just to check one `(accountId, artistId)` pair. Consider using the existing `selectAccountArtistId(accountId, artistId)` helper or an equally narrow query to avoid unnecessary DB and network work on large rosters.</violation>

<violation number="2" location="lib/accounts/linkArtistToAccount.ts:32">
P2: Concurrent duplicate requests can break the endpoint's idempotency because both calls can observe no existing row before either insert commits. Prefer a DB-level upsert/on-conflict ignore or duplicate-key handling so repeated requests return success atomically.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}

// Add artist to account
await insertAccountArtistId(accountId, artistId);

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: Concurrent duplicate requests can break the endpoint's idempotency because both calls can observe no existing row before either insert commits. Prefer a DB-level upsert/on-conflict ignore or duplicate-key handling so repeated requests return success atomically.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/accounts/linkArtistToAccount.ts, line 32:

<comment>Concurrent duplicate requests can break the endpoint's idempotency because both calls can observe no existing row before either insert commits. Prefer a DB-level upsert/on-conflict ignore or duplicate-key handling so repeated requests return success atomically.</comment>

<file context>
@@ -0,0 +1,42 @@
+    }
+
+    // Add artist to account
+    await insertAccountArtistId(accountId, artistId);
+
+    return NextResponse.json({ success: true }, { status: 200, headers: getCorsHeaders() });
</file context>

}: AddArtistParams): Promise<NextResponse> {
try {
// Check if artist is already associated with account
const existingArtists = await getAccountArtistIds({ accountIds: [accountId] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This existence check loads the account's full artist roster and nested artist/social details just to check one (accountId, artistId) pair. Consider using the existing selectAccountArtistId(accountId, artistId) helper or an equally narrow query to avoid unnecessary DB and network work on large rosters.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/accounts/linkArtistToAccount.ts, line 24:

<comment>This existence check loads the account's full artist roster and nested artist/social details just to check one `(accountId, artistId)` pair. Consider using the existing `selectAccountArtistId(accountId, artistId)` helper or an equally narrow query to avoid unnecessary DB and network work on large rosters.</comment>

<file context>
@@ -0,0 +1,42 @@
+}: AddArtistParams): Promise<NextResponse> {
+  try {
+    // Check if artist is already associated with account
+    const existingArtists = await getAccountArtistIds({ accountIds: [accountId] });
+    const alreadyExists = existingArtists.some(a => a.artist_id === artistId);
+
</file context>

@sweetmantech sweetmantech merged commit 69aa286 into main Jul 9, 2026
6 checks passed
Sign up for free to 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.

1 participant