Skip to content

feat(api): migrate GET /api/subscriptions to /api/accounts/{id}/subscription#476

Closed
arpitgupta1214 wants to merge 4 commits into
testfrom
migrate/get-accounts-id-subscription
Closed

feat(api): migrate GET /api/subscriptions to /api/accounts/{id}/subscription#476
arpitgupta1214 wants to merge 4 commits into
testfrom
migrate/get-accounts-id-subscription

Conversation

@arpitgupta1214
Copy link
Copy Markdown
Collaborator

@arpitgupta1214 arpitgupta1214 commented Apr 23, 2026

Ports the Express /api/subscriptions?accountId= handler to a nested GET /api/accounts/{id}/subscription with validateAuthContext + canAccessAccount access check; seeds lib/stripe/*, lib/enterprise/*, and the subscriptions domain. Response body is byte-identical to the legacy endpoint (the subscription payload keeps camelCase since it's a raw Stripe.Subscription).

Test plan

  • pnpm test lib/subscriptions lib/stripe passes (17 tests)
  • Full suite pnpm test passes (2238 tests)
  • Preview: curl -H "x-api-key: $RECOUP_TEST_API_KEY" <preview>/api/accounts/<uuid>/subscription returns 200 with { isEnterprise: true } for an enterprise account and { subscription } for a paid account
  • 401 without auth; 403 on cross-account; 404 on unknown UUID; 400 on non-UUID

Summary by cubic

Migrated the legacy GET /api/subscriptions?accountId= to GET /api/accounts/{id}/subscription with proper auth and access checks. The response stays byte-identical, including camelCase on the Stripe subscription, and supports enterprise short-circuiting.

  • New Features

    • Next.js route with CORS OPTIONS preflight.
    • Validator checks UUID, auth, account existence, and access; returns 400/401/403/404 as needed.
    • Enterprise detection via allow-listed domains; returns { isEnterprise: true } without Stripe.
    • Pinned stripe API version; tests added for handler and validator.
  • Refactors

    • Replaced list+client filter with server-side subscriptions.search to scale past 100 active subs.
    • Centralized resolution in getAccountSubscription (returns enterprise | subscription | null); handler now just validates and formats the response.
    • Removed the extra subscription-details wrapper; helper owns emails lookup + enterprise check + Stripe search.
    • Dropped the redundant “no emails” 404 path; empty email lists now fall through to Stripe lookup.

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

…ription

Ports the Express /api/subscriptions?accountId= handler to a nested
/api/accounts/{id}/subscription route with validateAuthContext +
canAccessAccount access check. Seeds lib/stripe/* and lib/enterprise/*,
adds the subscriptions domain, and pins the Stripe SDK apiVersion to
match chat. Response body is byte-identical to the legacy endpoint.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 23, 2026

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

Project Deployment Actions Updated (UTC)
api Error Error Apr 23, 2026 10:50pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Warning

Rate limit exceeded

@arpitgupta1214 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 56 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 17 minutes and 56 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: 99db844b-2d7f-4297-99d4-ef460f1a18a0

📥 Commits

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

⛔ Files ignored due to path filters (4)
  • lib/subscriptions/__tests__/getSubscriptionsHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/subscriptions/__tests__/validateGetSubscriptionRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • package.json is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by none
📒 Files selected for processing (7)
  • app/api/accounts/[id]/subscription/route.ts
  • lib/consts.ts
  • lib/enterprise/isEnterprise.ts
  • lib/stripe/client.ts
  • lib/subscriptions/getAccountSubscription.ts
  • lib/subscriptions/getSubscriptionsHandler.ts
  • lib/subscriptions/validateGetSubscriptionRequest.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch migrate/get-accounts-id-subscription

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.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

8 issues found across 13 files

Confidence score: 3/5

  • There is a moderate merge risk: several high-confidence issues (6–7/10) are user-impacting, especially around subscription state resolution and error handling.
  • In lib/stripe/getActiveSubscriptions.ts, missing explicit status filtering plus returning [] on Stripe failures can misreport inactive/error states as "no active subscription," which can drive incorrect 404/empty responses.
  • In lib/subscriptions/getSubscriptionsHandler.ts and lib/stripe/getActiveSubscriptionDetails.ts, treating failures as "not found" (404 or null) can mask operational/query problems and make debugging and recovery harder.
  • Pay close attention to app/api/accounts/[id]/subscription/route.ts, lib/stripe/getActiveSubscriptions.ts, lib/subscriptions/getSubscriptionsHandler.ts, and lib/stripe/getActiveSubscriptionDetails.ts - routing/export contract and failure-path handling may misclassify real errors as missing subscriptions.
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:25">
P2: Custom agent: **Module should export a single primary function whose name matches the filename**

`client.ts` exports a default object instance instead of a primary function named to match the filename (`client`).</violation>
</file>

<file name="lib/subscriptions/__tests__/getSubscriptionsHandler.test.ts">

<violation number="1" location="lib/subscriptions/__tests__/getSubscriptionsHandler.test.ts:1">
P3: Custom agent: **Enforce Clear Code Style and Maintainability Practices**

New file exceeds the 100-line file-size limit required by Rule 3.</violation>
</file>

<file name="lib/subscriptions/__tests__/validateGetSubscriptionRequest.test.ts">

<violation number="1" location="lib/subscriptions/__tests__/validateGetSubscriptionRequest.test.ts:1">
P3: Custom agent: **Enforce Clear Code Style and Maintainability Practices**

File exceeds the repository’s 100-line maintainability cap.</violation>
</file>

<file name="app/api/accounts/[id]/subscription/route.ts">

<violation number="1" location="app/api/accounts/[id]/subscription/route.ts:10">
P1: Custom agent: **Module should export a single primary function whose name matches the filename**

Module exports multiple top-level functions and none matches the `route.ts` basename, violating the single-primary-export naming rule.</violation>
</file>

<file name="lib/subscriptions/getSubscriptionsHandler.ts">

<violation number="1" location="lib/subscriptions/getSubscriptionsHandler.ts:34">
P2: Do not return `404 Account not found` from an empty `account_emails` result; it can mask query failures and mislabel existing accounts.</violation>
</file>

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

<violation number="1" location="lib/stripe/getActiveSubscriptionDetails.ts:17">
P2: Do not convert exceptions to `null` here; it makes operational failures indistinguishable from "no active subscription" and can return an incorrect 404.</violation>
</file>

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

<violation number="1" location="lib/stripe/getActiveSubscriptions.ts:21">
P1: Add an explicit status filter; this query can return non-active subscriptions as active.</violation>

<violation number="2" location="lib/stripe/getActiveSubscriptions.ts:30">
P2: Do not return an empty array on Stripe errors; it masks failures as "no subscription".</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client
    participant API as Next.js Route Handler
    participant Auth as Auth & Permissions
    participant DB as Supabase DB
    participant Stripe as Stripe API

    Note over Client,Stripe: GET /api/accounts/{id}/subscription

    Client->>API: NEW: Request with Account ID and Auth Header
    API->>API: NEW: Validate ID is UUID

    API->>Auth: NEW: validateAuthContext(request)
    alt Unauthorized (401)
        Auth-->>Client: Return 401 Unauthorized
    else Authorized
        Auth-->>API: Return Requester accountId
    end

    API->>DB: NEW: selectAccounts(targetId)
    alt Not Found (404)
        DB-->>Client: Return 404 Account not found
    end
    DB-->>API: Account Record

    API->>Auth: NEW: canAccessAccount(requesterId, targetId)
    alt Access Denied (403)
        Auth-->>Client: Return 403 Forbidden
    end
    Auth-->>API: Access Granted

    API->>DB: NEW: selectAccountEmails(targetId)
    DB-->>API: List of emails

    API->>API: NEW: isEnterprise(emails) check via domain allow-list
    
    alt NEW: Enterprise Domain Found
        API-->>Client: 200 OK { isEnterprise: true }
    else NEW: Standard Account
        API->>Stripe: NEW: subscriptions.list({ limit: 100 })
        Stripe-->>API: Active subscriptions
        
        API->>API: NEW: Filter by metadata.accountId
        
        alt Subscription Found
            API-->>Client: 200 OK { subscription } (Stripe raw payload)
        else No Active Subscription
            API-->>Client: 404 Not Found { error: "No active subscription found" }
        end
    end

    Note over API,Stripe: Safe Error Handling: Stripe/DB errors return 500 without leaking details
Loading

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

*
* @returns A NextResponse with CORS headers.
*/
export async function OPTIONS() {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 23, 2026

Choose a reason for hiding this comment

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

P1: Custom agent: Module should export a single primary function whose name matches the filename

Module exports multiple top-level functions and none matches the route.ts basename, violating the single-primary-export naming rule.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/accounts/[id]/subscription/route.ts, line 10:

<comment>Module exports multiple top-level functions and none matches the `route.ts` basename, violating the single-primary-export naming rule.</comment>

<file context>
@@ -0,0 +1,34 @@
+ *
+ * @returns A NextResponse with CORS headers.
+ */
+export async function OPTIONS() {
+  return new NextResponse(null, {
+    status: 200,
</file context>
Fix with Cubic

Comment thread lib/stripe/getActiveSubscriptions.ts Outdated
Comment thread lib/stripe/client.ts
apiVersion: STRIPE_API_VERSION as Stripe.LatestApiVersion,
});

export default stripeClient;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 23, 2026

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

client.ts exports a default object instance instead of a primary function named to match the filename (client).

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

<comment>`client.ts` exports a default object instance instead of a primary function named to match the filename (`client`).</comment>

<file context>
@@ -0,0 +1,25 @@
+  apiVersion: STRIPE_API_VERSION as Stripe.LatestApiVersion,
+});
+
+export default stripeClient;
</file context>
Fix with Cubic

Comment thread lib/subscriptions/getSubscriptionsHandler.ts Outdated
Comment thread lib/stripe/getActiveSubscriptionDetails.ts Outdated
Comment thread lib/stripe/getActiveSubscriptions.ts Outdated
@@ -0,0 +1,120 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 23, 2026

Choose a reason for hiding this comment

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

P3: Custom agent: Enforce Clear Code Style and Maintainability Practices

New file exceeds the 100-line file-size limit required by Rule 3.

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

<comment>New file exceeds the 100-line file-size limit required by Rule 3.</comment>

<file context>
@@ -0,0 +1,120 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { NextRequest, NextResponse } from "next/server";
+
</file context>
Fix with Cubic

@@ -0,0 +1,125 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 23, 2026

Choose a reason for hiding this comment

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

P3: Custom agent: Enforce Clear Code Style and Maintainability Practices

File exceeds the repository’s 100-line maintainability cap.

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

<comment>File exceeds the repository’s 100-line maintainability cap.</comment>

<file context>
@@ -0,0 +1,125 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { NextRequest, NextResponse } from "next/server";
+
</file context>
Fix with Cubic

…validator type

- getActiveSubscriptions (list 100 + client-side filter) -> getActiveSubscription
  using stripe.subscriptions.search with metadata+status filter (scales past 100)
- Drop getActiveSubscriptionDetails wrapper, inline nullable handling
- Validator returns derived GetSubscriptionParams directly (drop extra interface)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 9 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/getActiveSubscription.ts">

<violation number="1" location="lib/stripe/getActiveSubscription.ts:22">
P1: Do not swallow Stripe errors as `null`; this turns upstream failures into false 404 responses.</violation>
</file>

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

Comment thread lib/stripe/getActiveSubscription.ts Outdated
…lper

Discriminated return (enterprise | subscription | null) collapses orchestration
out of the handler. Owns emails lookup + enterprise check + Stripe search.
Tests kept at handler + validator level only.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

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

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

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 5 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/subscriptions/getAccountSubscription.ts">

<violation number="1" location="lib/subscriptions/getAccountSubscription.ts:35">
P1: Do not return `null` from the catch block here; it causes backend failures to be reported as 404 "No active subscription found" instead of a 500 error.</violation>
</file>

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

return subscription ? { subscription } : null;
} catch (error) {
console.error("[ERROR] getAccountSubscription:", error);
return null;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 23, 2026

Choose a reason for hiding this comment

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

P1: Do not return null from the catch block here; it causes backend failures to be reported as 404 "No active subscription found" instead of a 500 error.

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

<comment>Do not return `null` from the catch block here; it causes backend failures to be reported as 404 "No active subscription found" instead of a 500 error.</comment>

<file context>
@@ -0,0 +1,37 @@
+    return subscription ? { subscription } : null;
+  } catch (error) {
+    console.error("[ERROR] getAccountSubscription:", error);
+    return null;
+  }
+}
</file context>
Fix with Cubic

@arpitgupta1214
Copy link
Copy Markdown
Collaborator Author

Closing — no monorepo caller hits this endpoint (chat's helper was dead code) and we're removing the endpoint from public docs (recoupable/docs#159) instead of migrating it.

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