Skip to content

feat(api): migrate GET /api/artists/pro#477

Merged
sweetmantech merged 14 commits into
testfrom
feat/migrate-get-api-artists-pro
Apr 24, 2026
Merged

feat(api): migrate GET /api/artists/pro#477
sweetmantech merged 14 commits into
testfrom
feat/migrate-get-api-artists-pro

Conversation

@arpitgupta1214

@arpitgupta1214 arpitgupta1214 commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

Admin-scoped migration of legacy GET /api/artists/pro into mono/api — returns the deduped list of artist IDs owned by pro-tier accounts (enterprise email domain or active Stripe subscription).

Test plan

  • 401 on request with no auth
  • 403 on request with a non-admin key/token
  • 200 + sorted artist-id set matches legacy api.recoupable.com/api/artists/pro (requires tasks service account to be flagged admin)
  • Unit tests: validator + handler (auth/empty/dedup/500)

Summary by cubic

Migrated GET /api/admins/artists/pro into mono/api as an admin-only endpoint that returns a deduped list of pro artist IDs from enterprise-domain emails and active subscriptions in the Supabase mirror. Sources resolve to account IDs, then map to unique artist IDs.

  • New Features

    • Next.js route with CORS preflight; 200 returns { status, artists }; generic 500 without leaking error details.
    • Pro set = enterprise domains ∪ active subscriptions; dedupes by account_id then artist_id; short-circuits on empty.
  • Refactors

    • Switched from Stripe to Supabase subscriptions mirror and introduced selectSubscriptions({ accountIds?, active? }).
    • Enterprise and subscriber sources now return account_ids; merged/deduped once in getProArtists, then a single account_artist_ids query; selectAccountEmails adds an optional domain filter (ilike %@<domain>%).

Written for commit 6b5057f. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added new API endpoint to retrieve pro-tier artists based on enterprise domain accounts and active Stripe subscriptions.
    • Endpoint requires admin authentication and supports CORS preflight requests.

Admin-scoped flat route returning the deduped list of artist IDs owned by
"pro" accounts (enterprise email domain or active Stripe subscription).
Ports Stripe client + getActiveSubscriptions inline until the subscription
PR lands; reuses validateAdminAuth for scope.
@vercel

vercel Bot commented Apr 23, 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 Apr 24, 2026 6:49pm

Request Review

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@arpitgupta1214 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 40 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 7 minutes and 40 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: b2bbb13f-9134-40bd-ac16-d1cf8508a60b

📥 Commits

Reviewing files that changed from the base of the PR and between 3e68114 and 6b5057f.

⛔ Files ignored due to path filters (2)
  • lib/artists/__tests__/getArtistsProHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/artists/__tests__/validateGetArtistsProRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (3)
  • app/api/admins/artists/pro/route.ts
  • lib/artists/getProArtists.ts
  • lib/supabase/subscriptions/selectSubscriptions.ts
📝 Walkthrough

Walkthrough

This PR introduces a new /api/artists/pro endpoint with admin authentication that retrieves pro-tier artists from two sources: enterprise domain accounts and active Stripe subscriptions. The feature merges, deduplicates account IDs, and returns associated artist identifiers with CORS support.

Changes

Cohort / File(s) Summary
API Route Handler
app/api/artists/pro/route.ts
New route handler exporting OPTIONS (CORS preflight) and GET handlers that forward requests to the getArtistsProHandler function.
Request Handler & Validation
lib/artists/getArtistsProHandler.ts, lib/artists/validateGetArtistsProRequest.ts
Request handler that validates admin auth, invokes getProArtists(), and returns success/error responses with CORS headers. Validation delegates to validateAdminAuth.
Pro Artists Business Logic
lib/artists/getProArtists.ts
Core function that concurrently fetches enterprise domain and active subscription account IDs, merges and deduplicates them, then retrieves associated artist IDs via selectAccountArtistIds.
Enterprise Configuration & Lookup
lib/enterprise/consts.ts, lib/enterprise/getEnterpriseAccountIds.ts
New ENTERPRISE_DOMAINS constant (ReadonlySet) and async function to resolve account IDs by querying account emails for each configured domain concurrently.
Supabase Data Access
lib/supabase/account_artist_ids/selectAccountArtistIds.ts, lib/supabase/subscriptions/selectActiveSubscriptionAccountIds.ts
New data access functions: selectAccountArtistIds queries artist IDs by account list; selectActiveSubscriptionAccountIds retrieves account IDs with active subscriptions.
Enhanced Email Filtering
lib/supabase/account_emails/selectAccountEmails.ts
Updated with optional domain parameter to filter accounts by email domain using ilike pattern matching (%@<domain>%).

Sequence Diagram

sequenceDiagram
    participant Client
    participant Route as API Route
    participant Handler as getArtistsProHandler
    participant Validator as validateGetArtistsProRequest
    participant Auth as validateAdminAuth
    participant ProLogic as getProArtists
    participant EntDB as Enterprise DB
    participant SubDB as Subscriptions DB
    participant AccountDB as Account Mapping DB

    Client->>Route: GET /api/artists/pro
    Route->>Handler: forward request
    Handler->>Validator: validate request
    Validator->>Auth: enforce admin auth
    Auth-->>Validator: success or error response
    alt validation fails
        Validator-->>Handler: NextResponse (error)
        Handler-->>Route: NextResponse
        Route-->>Client: 401/403 error
    else validation succeeds
        Validator-->>Handler: empty object (valid)
        Handler->>ProLogic: getProArtists()
        par fetch concurrently
            ProLogic->>EntDB: getEnterpriseAccountIds()
            EntDB-->>ProLogic: enterprise account IDs
        and
            ProLogic->>SubDB: selectActiveSubscriptionAccountIds()
            SubDB-->>ProLogic: subscription account IDs
        end
        ProLogic->>ProLogic: merge & deduplicate
        ProLogic->>AccountDB: selectAccountArtistIds(merged IDs)
        AccountDB-->>ProLogic: artist IDs
        ProLogic-->>Handler: artist_id[]
        Handler-->>Route: 200 + artists JSON
        Route-->>Client: { status: 200, artists: [...] }
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested reviewers

  • sweetmantech

Poem

🎨 Pro artists now shine bright,
Enterprise and Stripe in flight,
Domains and subscriptions blend,
Admin gates defend the end,
Clean architecture takes the crown! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Solid & Clean Code ✅ Passed The file exports multiple HTTP method handlers following Next.js framework conventions, which is a codebase-wide pattern rather than a violation of SRP.

✏️ 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 feat/migrate-get-api-artists-pro

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.

@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.

5 issues found across 16 files

Confidence score: 2/5

  • Several high-confidence issues in lib/stripe/getActiveSubscriptions.ts and Supabase selectors convert real backend failures into [], which can silently change error cases into “no data” responses.
  • The most severe risk is user-facing correctness: /api/artists/pro may return 200 with incomplete or missing pro artist data when queries fail, rather than triggering proper error handling.
  • Given multiple 7–8/10 severity findings with 8–9/10 confidence, this is a clear regression risk and not just housekeeping.
  • Pay close attention to lib/stripe/getActiveSubscriptions.ts, lib/supabase/account_emails/selectAccountEmailsByDomain.ts, lib/supabase/account_artist_ids/selectAccountArtistIds.ts, lib/supabase/account_emails/selectAccountEmailsByAccountIds.ts, app/api/artists/pro/route.ts - error swallowing and API correctness need to be fixed 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/supabase/account_emails/selectAccountEmailsByDomain.ts">

<violation number="1" location="lib/supabase/account_emails/selectAccountEmailsByDomain.ts:17">
P1: Do not swallow Supabase query failures as `[]`; this hides backend errors and can return incorrect 200 responses with missing pro artists.</violation>
</file>

<file name="lib/supabase/account_artist_ids/selectAccountArtistIds.ts">

<violation number="1" location="lib/supabase/account_artist_ids/selectAccountArtistIds.ts:15">
P1: Database errors are being swallowed and converted to an empty result, which can silently produce incorrect responses instead of triggering proper error handling.</violation>
</file>

<file name="lib/stripe/getActiveSubscriptions.ts">

<violation number="1" location="lib/stripe/getActiveSubscriptions.ts:27">
P1: Do not return `[]` on Stripe errors here; it masks real failures as empty data and can produce incorrect results.</violation>
</file>

<file name="lib/supabase/account_emails/selectAccountEmailsByAccountIds.ts">

<violation number="1" location="lib/supabase/account_emails/selectAccountEmailsByAccountIds.ts:16">
P1: Do not return an empty array on Supabase query failure here; it masks backend errors and can make `/api/artists/pro` return a successful but incomplete result.

(Based on your team's feedback about returning generic 500s while logging full internal errors.) [FEEDBACK_USED]</violation>
</file>

<file name="app/api/artists/pro/route.ts">

<violation number="1" location="app/api/artists/pro/route.ts:27">
P2: Custom agent: **Module should export a single primary function whose name matches the filename**

Module exports multiple top-level functions instead of a single primary export, violating the filename-to-export convention.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as Admin/Task Client
    participant Route as GET /api/artists/pro
    participant Auth as validateAdminAuth
    participant Service as getEnterpriseArtists
    participant Stripe as Stripe API
    participant DB as Supabase

    Note over Client,DB: NEW: Admin-scoped Pro Artist Migration Flow

    Client->>Route: Request with Admin Token
    
    Route->>Auth: NEW: validateAdminAuth(request)
    alt Unauthorized or Non-Admin
        Auth-->>Client: 401 Unauthorized / 403 Forbidden
    else Admin Authorized
        Auth-->>Route: Auth context (accountId, token)
        
        Route->>Service: NEW: getEnterpriseArtists()
        
        par Parallel Data Fetch
            Service->>DB: NEW: selectAccountEmailsByDomain(enterprise_domains)
            DB-->>Service: enterprise account list
        and
            Service->>Stripe: NEW: getActiveSubscriptions()
            Note right of Stripe: Capped at 100 active subs
            Stripe-->>Service: Stripe metadata (accountId)
            Service->>DB: NEW: selectAccountEmailsByAccountIds(stripe_account_ids)
            DB-->>Service: subscriber account list
        end

        Service->>Service: Deduplicate unique account_ids

        opt If pro account_ids exists
            Service->>DB: NEW: selectAccountArtistIds(account_ids)
            DB-->>Service: artist_id rows
        end

        Service-->>Route: Deduplicated string[] (artist_ids)
        
        alt Request Success
            Route-->>Client: 200 OK + { status: "success", artists }
        else Internal Exception
            Note over Route: NEW: Error masked to prevent DB leak
            Route-->>Client: 500 Internal Server Error
        end
    end

    Note over Route: NEW: Includes CORS headers via getCorsHeaders()
Loading

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

Comment thread lib/supabase/account_emails/selectAccountEmailsByDomain.ts Outdated
.select("artist_id")
.in("account_id", accountIds);

if (error) {

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

P1: Database errors are being swallowed and converted to an empty result, which can silently produce incorrect responses instead of triggering proper error handling.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/account_artist_ids/selectAccountArtistIds.ts, line 15:

<comment>Database errors are being swallowed and converted to an empty result, which can silently produce incorrect responses instead of triggering proper error handling.</comment>

<file context>
@@ -0,0 +1,21 @@
+    .select("artist_id")
+    .in("account_id", accountIds);
+
+  if (error) {
+    console.error("Error fetching account_artist_ids:", error);
+    return [];
</file context>
Fix with Cubic

Comment thread lib/stripe/getActiveSubscriptions.ts Outdated
Comment thread lib/supabase/account_emails/selectAccountEmailsByAccountIds.ts Outdated
@@ -0,0 +1,29 @@
import { NextRequest, NextResponse } from "next/server";

@cubic-dev-ai cubic-dev-ai Bot Apr 23, 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: Custom agent: Module should export a single primary function whose name matches the filename

Module exports multiple top-level functions instead of a single primary export, violating the filename-to-export convention.

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

<comment>Module exports multiple top-level functions instead of a single primary export, violating the filename-to-export convention.</comment>

<file context>
@@ -0,0 +1,29 @@
+ * @param request - The incoming request.
+ * @returns A NextResponse with `{ status, artists, [error] }`.
+ */
+export async function GET(request: NextRequest) {
+  return getArtistsProHandler(request);
+}
</file context>
Fix with Cubic

Next build on Vercel evaluates lib/stripe/client.ts while collecting
page data; throwing at module-load when STRIPE_SECRET_KEY is absent
from the preview env killed the build. Move the guard into a lazy
factory so build-time import is always safe — the first request
still surfaces a 500 via the handler's catch when the secret is
genuinely missing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@arpitgupta1214

Copy link
Copy Markdown
Collaborator Author

Preview smoke — HEAD 33b2c3a, preview api-git-feat-migrate-get-api-artists-pro-recoupable-ad724970.vercel.app

Probe Expected Actual Body
No auth 401 401 {"status":"error","error":"Exactly one of x-api-key or Authorization must be provided"}
Non-admin x-api-key 403 403 {"status":"error","message":"Forbidden"}
Admin x-api-key 200 with {status,artists} not smoked here — admin key not in CI env

Admin-scope note (intentional behavior change vs legacy)

Legacy https://api.recoupable.com/api/artists/pro is currently unauthenticated — it returns the full paying-customer list (386 artists right now) to any caller. The migration closes that leak by gating on validateAdminAuth (Recoup-org admin membership). Smoke output above confirms the gate rejects unauthenticated and non-admin credentials.

Legacy parity (200 set)

Legacy returns 386 deduped artist IDs. Handler parity is unit-covered (lib/artists/__tests__/getArtistsProHandler.test.tsit.each matrix of enterprise-only / subscription-only / both-deduped / neither paths, plus the 500-leak regression). Full 200 cross-check against legacy will be verified by whoever runs the admin-key smoke after merge to test.

Build fix

Initial deploy failed because lib/stripe/client.ts threw at module-load when STRIPE_SECRET_KEY was absent; Next evaluates that module during next build on Vercel. Moved the guard into a lazy factory (33b2c3a) — build-time import is now safe, missing secret surfaces as a 500 on first request.

@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 2 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/stripe/client.ts">

<violation number="1" location="lib/stripe/client.ts:22">
P3: Custom agent: **Module should export a single primary function whose name matches the filename**

Default-exported primary function name does not match the file basename.</violation>

<violation number="2" location="lib/stripe/client.ts:27">
P1: A missing Stripe secret now gets swallowed as an empty subscription set, causing `/api/artists/pro` to return incomplete "success" data instead of failing.</violation>
</file>

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

Comment thread lib/stripe/client.ts Outdated
Comment thread lib/stripe/client.ts Outdated
Add optional domain filter (ilike %@<domain>%) to the existing
selectAccountEmails helper and drop the per-filter wrappers. One
helper, three optional filters (emails, accountIds, domain).

@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.

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

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

Use Stripe SDK's async iterator so we're not capped at 100 rows — the
cursor paginates the full active set. The accountId filter param was
dead (no caller passed it); drop rather than carry; reintroduce via
Stripe Search API if a single-account lookup is ever needed.

@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.

0 issues found across 1 file (changes from recent commits).

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

Swap getActiveSubscriptions for an iterateActiveSubscriptions async
generator. Two wins:

1. status: 'active' is Stripe's native filter — no current_period_end
   vs server-clock comparison (timezone / skew hazards).
2. Yielded incrementally so callers fold results into a Set as they
   arrive; no hard in-memory cap on the active subscription count.

Caller now holds only the deduped accountId strings, not full
Subscription objects.

@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.

1 issue found across 1 file (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/stripe/getActiveSubscriptions.ts">

<violation number="1" location="lib/stripe/getActiveSubscriptions.ts:13">
P1: `autoPagingToArray({ limit: 10000 })` introduces a hard truncation at 10k subscriptions, which can silently omit active subscribers.</violation>
</file>

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

Comment thread lib/stripe/getActiveSubscriptions.ts Outdated

@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.

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

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

- lib/stripe/ now owns getActiveSubscriptions + getSubscriberAccountEmails
  (they query Stripe, not enterprise domains).
- lib/enterprise/ keeps only ENTERPRISE_DOMAINS and the domain email fan-out.
- The merge orchestrator moves to lib/artists/getProArtists.ts (enterprise ∪
  subscribers); renamed from getEnterpriseArtists since 'enterprise artists'
  understates what it returns.
- getActiveSubscriptions uses a manual starting_after cursor loop so there is
  no iterator and no hard row cap.

@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.

1 issue 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/stripe/getActiveSubscriptions.ts">

<violation number="1" location="lib/stripe/getActiveSubscriptions.ts:26">
P1: Do not swallow Stripe fetch errors here; rethrow after logging so the handler can return a proper 500 instead of silently returning incomplete results.</violation>
</file>

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

Comment thread lib/stripe/getActiveSubscriptions.ts Outdated
…ound-trip

The subscriber path fetched Stripe subs, extracted metadata.accountId,
round-tripped Supabase to get account_emails rows, then unpacked
account_id again — the email lookup was inert.

- lib/stripe/getSubscriberAccountIds reads metadata.accountId from
  Stripe directly; no Supabase call.
- lib/enterprise/getEnterpriseAccountIds projects the ilike result to
  account_ids at the source.
- getProArtists now merges two string arrays, dedupes once, then hits
  account_artist_ids.
getSubscriberAccountIds was a two-line .map/.filter wrapper. Drop it
and inline against the Stripe result — no useful indirection.
getActiveSubscriptions returned full Subscription objects only to have
every caller project down to metadata.accountId. Collapse that into
getActiveSubscriptionAccountIds — strings in, strings out.

@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.

1 issue found across 6 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/stripe/getActiveSubscriptionAccountIds.ts">

<violation number="1" location="lib/stripe/getActiveSubscriptionAccountIds.ts:29">
P2: On Stripe API errors, the function can return a partial ID list instead of the documented fail-safe empty array.</violation>
</file>

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

Comment thread lib/stripe/getActiveSubscriptionAccountIds.ts Outdated
Supabase has a subscriptions table mirroring billing-provider state
(stripe/lemon-squeezy/paddle) with account_id + active flag. Read from
that and skip Stripe entirely — no pagination, no secret-key env var,
no provider round-trip, and cross-provider correct.

- lib/supabase/subscriptions/selectActiveSubscriptionAccountIds.ts
- lib/stripe/ removed
- stripe package uninstalled

getProArtists now merges two Supabase queries.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (7)
lib/supabase/account_emails/selectAccountEmails.ts (1)

37-39: Escape ILIKE wildcards in domain for defense-in-depth.

Today ENTERPRISE_DOMAINS is a hard-coded set, so this is safe in practice. But selectAccountEmails is a generic helper — the first time a caller passes an unsanitized domain string, % or _ characters would silently widen the match (e.g., domain = "%" returns every email). Cheap to guard at the boundary:

🛡️ Proposed hardening
   if (hasDomain) {
-    query = query.ilike("email", `%@${domain}%`);
+    const escapedDomain = domain!.replace(/([\\%_])/g, "\\$1");
+    query = query.ilike("email", `%@${escapedDomain}%`);
   }

Also worth a one-line comment noting that the trailing % intentionally preserves the legacy user+tag@<domain>.anything match (so future readers don't "tighten" it to %@${domain} and break parity).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/supabase/account_emails/selectAccountEmails.ts` around lines 37 - 39,
selectAccountEmails currently passes the raw domain into query.ilike when
hasDomain is true, which lets '%' and '_' in the domain widen the match; before
calling query.ilike in the hasDomain branch, escape SQL ILIKE wildcard
characters in the domain (escape '%' -> '\%' and '_' -> '\_') and use the
escaped value in the pattern; keep the trailing '%' in the pattern intentionally
to preserve legacy matching of user+tag@<domain>.anything and add a one-line
comment by the query.ilike call explaining that the trailing '%' is deliberate
to maintain parity.
lib/artists/validateGetArtistsProRequest.ts (1)

10-18: Consider returning the auth context instead of {} for future-proofing.

Returning Record<string, never> discards the accountId/orgId/authToken that validateAdminAuth already resolved. If the handler ever needs to log which admin triggered the call (audit trail) or scope the pro-artist query, you'd have to re-authenticate. Mirroring the pattern used in validateGetArtistsRequest (which returns params on success) would keep the contract uniform across validators.

♻️ Proposed refactor
+import type { AuthContext } from "@/lib/auth/validateAuthContext";
+
 export async function validateGetArtistsProRequest(
   request: NextRequest,
-): Promise<NextResponse | Record<string, never>> {
+): Promise<NextResponse | AuthContext> {
   const auth = await validateAdminAuth(request);
   if (auth instanceof NextResponse) {
     return auth;
   }
-  return {};
+  return auth;
 }

(Adjust the imported type name to whatever validateAdminAuth actually returns.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/artists/validateGetArtistsProRequest.ts` around lines 10 - 18, The
validator validateGetArtistsProRequest currently discards the resolved auth
context and returns an empty object; change it to return the auth object from
validateAdminAuth on success so callers can access accountId/orgId/authToken for
logging or scoping. Update the function signature return type from
Promise<NextResponse | Record<string, never>> to Promise<NextResponse |
<AuthType>> (use the actual type returned by validateAdminAuth), and on success
return auth rather than {}—mirroring the pattern used by
validateGetArtistsRequest; keep the existing NextResponse short-circuit when
auth is an instance of NextResponse.
lib/supabase/account_artist_ids/selectAccountArtistIds.ts (1)

7-21: Add an explicit return type for the contract.

The signature currently leaks the inferred Supabase select shape. Per the repo guideline on typed Supabase returns, an explicit Promise<Pick<Tables<"account_artist_ids">, "artist_id">[]> (or a dedicated projection type) makes the contract obvious to callers like getProArtists and prevents a future select("*") change from silently broadening the return type.

♻️ Proposed refactor
+import type { Tables } from "@/types/database.types";
 import supabase from "@/lib/supabase/serverClient";

-export async function selectAccountArtistIds(accountIds: string[]) {
+export async function selectAccountArtistIds(
+  accountIds: string[],
+): Promise<Pick<Tables<"account_artist_ids">, "artist_id">[]> {
   if (accountIds.length === 0) return [];

As per coding guidelines: "Return typed results using Tables<'table_name'>" for Supabase operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/supabase/account_artist_ids/selectAccountArtistIds.ts` around lines 7 -
21, Add an explicit return type to selectAccountArtistIds so it returns
Promise<Pick<Tables<"account_artist_ids">, "artist_id">[]> instead of an
inferred Supabase shape; update the function signature (selectAccountArtistIds)
to use that type, ensure the supabase .select("artist_id") call is kept, and
cast or map the received data to that projection before returning (handle
null/error paths by returning an empty array typed to the same projection) so
callers like getProArtists receive a stable, narrow contract.
lib/artists/getArtistsProHandler.ts (2)

19-26: DRY: Prefer errorResponse() unless the artists: [] field is load-bearing.

Every other handler in the codebase (e.g., getSongsHandler) funnels 500s through @/lib/networking/errorResponse to keep the error envelope ({ status: "error", error }) and CORS headers consistent. This handler inlines the response and additionally adds artists: []. If clients rely on artists always being present on the response body, keep the inline form but call it out in a comment so the next refactor doesn't "normalize" it away. Otherwise, switching to errorResponse("Internal server error", 500) removes the duplication.

♻️ Proposed refactor (if artists: [] isn't required on error)
-import { NextRequest, NextResponse } from "next/server";
-import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
+import { NextRequest, NextResponse } from "next/server";
+import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
+import { errorResponse } from "@/lib/networking/errorResponse";
 import { getProArtists } from "@/lib/artists/getProArtists";
 import { validateGetArtistsProRequest } from "@/lib/artists/validateGetArtistsProRequest";
@@
   } catch (error) {
-    // Never leak error.message — it can surface DB hosts / stack hints.
     console.error("[ERROR] getArtistsProHandler:", error);
-    return NextResponse.json(
-      { status: "error", artists: [], error: "Internal server error" },
-      { status: 500, headers: getCorsHeaders() },
-    );
+    // Never leak error.message — it can surface DB hosts / stack hints.
+    return errorResponse("Internal server error", 500);
   }

As per coding guidelines: "Extract shared logic into reusable utilities following Don't Repeat Yourself (DRY) principle".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/artists/getArtistsProHandler.ts` around lines 19 - 26, In
getArtistsProHandler, avoid inlining the 500 response; if the artists: [] field
is not required by clients replace the inline NextResponse.json call with a call
to the shared errorResponse("Internal server error", 500) to preserve consistent
error envelope and CORS headers (use the existing errorResponse function from
`@/lib/networking/errorResponse`); if clients do need artists: [] to be present
keep the inline response but add a clear comment above the return indicating the
field is load-bearing so it isn't later removed during DRY refactors. Also
ensure you don't leak sensitive error details when logging.

6-13: Minor: consider a short-circuit for OPTIONS if the route ever shares this handler.

Not strictly needed today (the route file defines a dedicated OPTIONS handler per the PR description), but worth a one-line guard in case this handler is ever wired to multiple verbs. Purely defensive — feel free to ignore if the route contract is locked.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/artists/getArtistsProHandler.ts` around lines 6 - 13, Add a one-line
defensive short-circuit at the start of getArtistsProHandler to immediately
handle HTTP OPTIONS requests: check request.method === 'OPTIONS' and return an
empty 204 response before calling validateGetArtistsProRequest or getProArtists;
this keeps validateGetArtistsProRequest and the rest of the handler from running
for preflight checks if this handler ever gets wired to multiple verbs.
lib/enterprise/consts.ts (1)

5-12: Consider moving ENTERPRISE_DOMAINS to lib/const.ts.

Per a prior learning, shared constants in this repo live in lib/const.ts (alongside INBOUND_EMAIL_DOMAIN, OUTBOUND_EMAIL_DOMAIN, etc.). ENTERPRISE_DOMAINS is feature-scoped today, but it's likely to be referenced from billing, onboarding, or analytics paths later — centralizing now avoids a future "which file owns this list" split. If you'd rather keep it colocated with the enterprise feature, that's defensible too; just flagging the convention.

Also: small readability win — a trailing comment noting that rostrum.com vs rostrumrecords.com are distinct (not a duplicate/typo) will save the next reader a double-take.

Based on learnings: "All shared constants should be defined in lib/const.ts, including INBOUND_EMAIL_DOMAIN, OUTBOUND_EMAIL_DOMAIN, SUPABASE_STORAGE_BUCKET, wallet addresses, model names, and API keys".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/enterprise/consts.ts` around lines 5 - 12, Move the shared constant
ENTERPRISE_DOMAINS out of lib/enterprise/consts.ts into the central lib/const.ts
to follow the repo convention for shared constants; add an exported
ReadonlySet<string> named ENTERPRISE_DOMAINS in lib/const.ts, remove or
re-export the duplicate from lib/enterprise/consts.ts, and update any imports
that reference ENTERPRISE_DOMAINS to import from lib/const.ts instead; while
moving, append a trailing comment explaining that "rostrum.com" and
"rostrumrecords.com" are distinct entries (not a duplicate/typo) so future
readers aren't confused.
app/api/artists/pro/route.ts (1)

27-29: Consider await-ing the handler so this route owns its error boundary.

Returning the handler's promise directly is perfectly valid, but since getArtistsProHandler is the sole point of failure visible from this route, an await + try/catch here would let the route layer guarantee a JSON 500 with CORS headers even if the handler ever throws synchronously or rejects unexpectedly (e.g., a future refactor slips past its internal try/catch). Totally optional — the handler appears to already wrap its own body in try/catch per the PR summary, so this is belt-and-suspenders territory.

🧯 Optional defensive shape
-export async function GET(request: NextRequest) {
-  return getArtistsProHandler(request);
-}
+export async function GET(request: NextRequest) {
+  try {
+    return await getArtistsProHandler(request);
+  } catch (error) {
+    console.error("/api/artists/pro unhandled error:", error);
+    return NextResponse.json(
+      { status: "error", error: "Internal Server Error" },
+      { status: 500, headers: getCorsHeaders() },
+    );
+  }
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/artists/pro/route.ts` around lines 27 - 29, The GET route currently
returns the promise from getArtistsProHandler directly; change GET to await
getArtistsProHandler inside a try/catch so the route owns its error
boundary—call await getArtistsProHandler(request) and in catch return a JSON 500
response with appropriate CORS headers (same headers used elsewhere) so any
synchronous throw or rejection is handled at the route level; reference the GET
function and getArtistsProHandler to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/artists/pro/route.ts`:
- Around line 17-26: The JSDoc for the GET handler in
app/api/artists/pro/route.ts is stale (mentions "active Stripe subscriptions");
update the comment above the handler to remove Stripe references and instead
state that "pro" artists are determined from enterprise-domain emails or the
local Supabase subscriptions mirror (the code reading from the subscriptions
mirror determines active status). Keep the same shape of the comment (purpose,
params, returns) but correct the implementation source so future readers know to
look at the Supabase mirror logic used by this route.

---

Nitpick comments:
In `@app/api/artists/pro/route.ts`:
- Around line 27-29: The GET route currently returns the promise from
getArtistsProHandler directly; change GET to await getArtistsProHandler inside a
try/catch so the route owns its error boundary—call await
getArtistsProHandler(request) and in catch return a JSON 500 response with
appropriate CORS headers (same headers used elsewhere) so any synchronous throw
or rejection is handled at the route level; reference the GET function and
getArtistsProHandler to locate the change.

In `@lib/artists/getArtistsProHandler.ts`:
- Around line 19-26: In getArtistsProHandler, avoid inlining the 500 response;
if the artists: [] field is not required by clients replace the inline
NextResponse.json call with a call to the shared errorResponse("Internal server
error", 500) to preserve consistent error envelope and CORS headers (use the
existing errorResponse function from `@/lib/networking/errorResponse`); if clients
do need artists: [] to be present keep the inline response but add a clear
comment above the return indicating the field is load-bearing so it isn't later
removed during DRY refactors. Also ensure you don't leak sensitive error details
when logging.
- Around line 6-13: Add a one-line defensive short-circuit at the start of
getArtistsProHandler to immediately handle HTTP OPTIONS requests: check
request.method === 'OPTIONS' and return an empty 204 response before calling
validateGetArtistsProRequest or getProArtists; this keeps
validateGetArtistsProRequest and the rest of the handler from running for
preflight checks if this handler ever gets wired to multiple verbs.

In `@lib/artists/validateGetArtistsProRequest.ts`:
- Around line 10-18: The validator validateGetArtistsProRequest currently
discards the resolved auth context and returns an empty object; change it to
return the auth object from validateAdminAuth on success so callers can access
accountId/orgId/authToken for logging or scoping. Update the function signature
return type from Promise<NextResponse | Record<string, never>> to
Promise<NextResponse | <AuthType>> (use the actual type returned by
validateAdminAuth), and on success return auth rather than {}—mirroring the
pattern used by validateGetArtistsRequest; keep the existing NextResponse
short-circuit when auth is an instance of NextResponse.

In `@lib/enterprise/consts.ts`:
- Around line 5-12: Move the shared constant ENTERPRISE_DOMAINS out of
lib/enterprise/consts.ts into the central lib/const.ts to follow the repo
convention for shared constants; add an exported ReadonlySet<string> named
ENTERPRISE_DOMAINS in lib/const.ts, remove or re-export the duplicate from
lib/enterprise/consts.ts, and update any imports that reference
ENTERPRISE_DOMAINS to import from lib/const.ts instead; while moving, append a
trailing comment explaining that "rostrum.com" and "rostrumrecords.com" are
distinct entries (not a duplicate/typo) so future readers aren't confused.

In `@lib/supabase/account_artist_ids/selectAccountArtistIds.ts`:
- Around line 7-21: Add an explicit return type to selectAccountArtistIds so it
returns Promise<Pick<Tables<"account_artist_ids">, "artist_id">[]> instead of an
inferred Supabase shape; update the function signature (selectAccountArtistIds)
to use that type, ensure the supabase .select("artist_id") call is kept, and
cast or map the received data to that projection before returning (handle
null/error paths by returning an empty array typed to the same projection) so
callers like getProArtists receive a stable, narrow contract.

In `@lib/supabase/account_emails/selectAccountEmails.ts`:
- Around line 37-39: selectAccountEmails currently passes the raw domain into
query.ilike when hasDomain is true, which lets '%' and '_' in the domain widen
the match; before calling query.ilike in the hasDomain branch, escape SQL ILIKE
wildcard characters in the domain (escape '%' -> '\%' and '_' -> '\_') and use
the escaped value in the pattern; keep the trailing '%' in the pattern
intentionally to preserve legacy matching of user+tag@<domain>.anything and add
a one-line comment by the query.ilike call explaining that the trailing '%' is
deliberate to maintain parity.
🪄 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: 3210d8af-e73f-48ee-b0e0-eec60d81b72c

📥 Commits

Reviewing files that changed from the base of the PR and between 4c743ce and 3e68114.

⛔ Files ignored due to path filters (2)
  • lib/artists/__tests__/getArtistsProHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/artists/__tests__/validateGetArtistsProRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (9)
  • app/api/artists/pro/route.ts
  • lib/artists/getArtistsProHandler.ts
  • lib/artists/getProArtists.ts
  • lib/artists/validateGetArtistsProRequest.ts
  • lib/enterprise/consts.ts
  • lib/enterprise/getEnterpriseAccountIds.ts
  • lib/supabase/account_artist_ids/selectAccountArtistIds.ts
  • lib/supabase/account_emails/selectAccountEmails.ts
  • lib/supabase/subscriptions/selectActiveSubscriptionAccountIds.ts

Comment on lines +17 to +26
/**
* GET /api/artists/pro
*
* Returns a deduplicated list of artist IDs owned by "pro" accounts
* (enterprise-domain emails or active Stripe subscriptions). Admin-scoped —
* the response is the paying-customer list.
*
* @param request - The incoming request.
* @returns A NextResponse with `{ status, artists, [error] }`.
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stale JSDoc: "active Stripe subscriptions" no longer reflects the implementation.

Per the PR's commit history, later commits replaced the Stripe API calls with reads from a local Supabase subscriptions mirror. Keeping the Stripe reference in this doc comment will mislead future readers hunting down the source of truth for "active subscription" semantics — a small clean-code nit, but docs that drift from reality erode trust faster than bad code does.

📝 Suggested wording tweak
 /**
  * GET /api/artists/pro
  *
  * Returns a deduplicated list of artist IDs owned by "pro" accounts
- * (enterprise-domain emails or active Stripe subscriptions). Admin-scoped —
- * the response is the paying-customer list.
+ * (enterprise-domain emails or accounts with an active subscription in the
+ * local subscriptions mirror). Admin-scoped — the response is the
+ * paying-customer list.
  *
  * `@param` request - The incoming request.
  * `@returns` A NextResponse with `{ status, artists, [error] }`.
  */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* GET /api/artists/pro
*
* Returns a deduplicated list of artist IDs owned by "pro" accounts
* (enterprise-domain emails or active Stripe subscriptions). Admin-scoped
* the response is the paying-customer list.
*
* @param request - The incoming request.
* @returns A NextResponse with `{ status, artists, [error] }`.
*/
/**
* GET /api/artists/pro
*
* Returns a deduplicated list of artist IDs owned by "pro" accounts
* (enterprise-domain emails or accounts with an active subscription in the
* local subscriptions mirror). Admin-scoped the response is the
* paying-customer list.
*
* `@param` request - The incoming request.
* `@returns` A NextResponse with `{ status, artists, [error] }`.
*/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/artists/pro/route.ts` around lines 17 - 26, The JSDoc for the GET
handler in app/api/artists/pro/route.ts is stale (mentions "active Stripe
subscriptions"); update the comment above the handler to remove Stripe
references and instead state that "pro" artists are determined from
enterprise-domain emails or the local Supabase subscriptions mirror (the code
reading from the subscriptions mirror determines active status). Keep the same
shape of the comment (purpose, params, returns) but correct the implementation
source so future readers know to look at the Supabase mirror logic used by this
route.

@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 6 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/subscriptions/selectActiveSubscriptionAccountIds.ts">

<violation number="1" location="lib/supabase/subscriptions/selectActiveSubscriptionAccountIds.ts:12">
P1: This unpaginated `select("account_id")` can silently truncate active subscriptions when row count exceeds the Supabase API max rows, leading to incomplete pro-artist results.

(Based on your team's feedback about preferring DB-side pagination over raising query limits.) [FEEDBACK_USED]</violation>
</file>

<file name="lib/artists/getProArtists.ts">

<violation number="1" location="lib/artists/getProArtists.ts:15">
P2: This now includes non-Stripe active subscriptions because the new selector is provider-agnostic. That changes the endpoint’s Stripe-scoped pro-account logic and can return extra artist IDs.</violation>
</file>

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

Comment thread lib/supabase/subscriptions/selectActiveSubscriptionAccountIds.ts Outdated
Comment thread lib/artists/getProArtists.ts Outdated
export async function getProArtists(): Promise<string[]> {
const [enterpriseIds, subscriberIds] = await Promise.all([
getEnterpriseAccountIds(),
selectActiveSubscriptionAccountIds(),

@cubic-dev-ai cubic-dev-ai Bot Apr 24, 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: This now includes non-Stripe active subscriptions because the new selector is provider-agnostic. That changes the endpoint’s Stripe-scoped pro-account logic and can return extra artist IDs.

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

<comment>This now includes non-Stripe active subscriptions because the new selector is provider-agnostic. That changes the endpoint’s Stripe-scoped pro-account logic and can return extra artist IDs.</comment>

<file context>
@@ -12,7 +12,7 @@ import { selectAccountArtistIds } from "@/lib/supabase/account_artist_ids/select
   const [enterpriseIds, subscriberIds] = await Promise.all([
     getEnterpriseAccountIds(),
-    getActiveSubscriptionAccountIds(),
+    selectActiveSubscriptionAccountIds(),
   ]);
 
</file context>
Fix with Cubic

@cubic-dev-ai

cubic-dev-ai Bot commented Apr 24, 2026

Copy link
Copy Markdown

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

KISS - Make this more general

  • actual: lib/supabase/subscriptions/selectActiveSubscriptionAccountIds.ts
  • required: lib/supabase/subscriptions/selectSubscriptions.ts

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 6b5057f — replaced with generic selectSubscriptions({ accountIds?, active? }). Caller in getProArtists projects to account_ids.

Per review — replace selectActiveSubscriptionAccountIds with a generic
selectSubscriptions({ accountIds?, active? }) helper. Caller projects to
account_ids.
@sweetmantech

Copy link
Copy Markdown
Contributor

Preview smoke — HEAD 6b5057f, preview api-git-feat-migrate-get-api-artists-pro-recoupable-ad724970.vercel.app

Ran 8 probes against GET /api/admins/artists/pro on the preview deployment. Key findings: endpoint works end-to-end, returns 284 unique pro-artist UUIDs in ~1.1s, auth / method / CORS all behave correctly.

# Probe Expected Actual Body
1 No auth 401 ✅ 401 {"status":"error","error":"Exactly one of x-api-key or Authorization must be provided"}
2 x-api-key: recoup_sk_… 200 ✅ 200 {"status":"success","artists":[…284 UUIDs…]} (1.1s, 11108 B)
3 Authorization: Bearer recoup_sk_… 401 ✅ 401 {"status":"error","message":"Failed to verify authentication token"} — Bearer path treats value as a Privy JWT per convention; API keys go via x-api-key
4 Both headers present 401 ✅ 401 {"status":"error","error":"Exactly one of x-api-key or Authorization must be provided"}
5 Bogus API key 401 ✅ 401 {"status":"error","message":"Unauthorized"}
6 POST instead of GET 405 ✅ 405 (empty — Next.js default)
7 OPTIONS preflight 200 + CORS ✅ 200 Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, PATCH, Origin: *, Headers: Content-Type, Authorization, X-Requested-With, x-api-key
8 Dedup sanity on the 284-row response total == unique ✅ 284 = 284 no duplicates present

Observations

  • All 284 entries are well-formed UUIDs (spot-checked first 5 — standard v4 layout). Sample: e48d0737-dfb0-407e-85c5-a13ecd424af6.
  • No stale Stripe behavior leaked — the endpoint reads from the local subscriptions mirror (per the final commits) and returns a reasonable-size merged set (enterprise-domain accounts + active subscriptions).
  • Status line { status, artists, [error] } matches the JSDoc contract. No error field present on success.
  • Response time was ~1.1s for the full payload on the preview. Not a regression flag on its own, but worth noting it's not a sub-100ms lookup — if you expect this to be on a hot path, a quick trace would confirm whether the two-stage Supabase join (account_emails by domain + subscriptions → account_artist_ids) is cache-friendly or needs tightening.

Open-issue cross-check

All five cubic P1 findings (swallowed Supabase/Stripe errors as [], hard 10k cap, missing secret swallowed, partial Stripe IDs on error) have been addressed in the commit series I can see on the branch — and the smoke test doesn't surface any of the residual failure modes.

The two open semantic flags from review (provider-agnostic selector now potentially including non-Stripe active subs; stale JSDoc still mentioning "active Stripe subscriptions") aren't testable from the preview — they're worth a note from @arpitgupta1214 on what populates the subscriptions table today.

No blocking issues from my side.

@sweetmantech sweetmantech merged commit 73fa2ad into test Apr 24, 2026
5 checks passed
@sweetmantech sweetmantech deleted the feat/migrate-get-api-artists-pro branch April 24, 2026 19:22
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.

2 participants