Skip to content

feat(billing): implement charges csv export#1790

Merged
ygrishajev merged 43 commits intoakash-network:mainfrom
domhhv:feat/charges-csv-endpoint
Aug 27, 2025
Merged

feat(billing): implement charges csv export#1790
ygrishajev merged 43 commits intoakash-network:mainfrom
domhhv:feat/charges-csv-endpoint

Conversation

@domhhv
Copy link
Contributor

@domhhv domhhv commented Aug 6, 2025

Closes #1788

Summary by CodeRabbit

  • New Features
    • Streamed, timezone-aware Stripe transactions CSV export endpoint with date-based filename and user-friendly message when payments aren’t configured.
  • Refactor
    • Centralized date-range validation; date-range APIs now require both start/end dates. UI exposes an Export action, surfaces errorMessage strings, and downloads CSV as a Blob.
  • Tests
    • Added tests for CSV streaming (pagination, empty results, payment-method edge cases), schema validations, DOM download utility, and UI export flows.
  • Chores
    • Added CSV stringifier dependency and test seeders/types.

@domhhv domhhv requested a review from a team as a code owner August 6, 2025 17:14
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds a streaming CSV export for Stripe transactions: new API route and controller, a paginated streaming service producing CSV chunks, request schemas with timezone/date-range validation, HTTP SDK and UI wiring to download a Blob, seeders/tests, types, and config/dependency updates.

Changes

Cohort / File(s) Summary
API: Controller
apps/api/src/billing/controllers/stripe/stripe.controller.ts
Adds exportTransactionsCsvStream controller method that authorizes, asserts stripeCustomerId, and returns an AsyncIterable<string> from the service.
API: Routes
apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts
Adds GET /v1/stripe/transactions/export route using CustomerTransactionsCsvExportQuerySchema; converts Node Readable to Web ReadableStream and returns chunked text/csv with Content-Disposition filename.
API: Service & Types
apps/api/src/billing/services/stripe/stripe.service.ts, apps/api/src/types/transactions.ts
Adds exportTransactionsCsvStream generator with pagination, transformTransactionForCsv, sanitizeForCsv, timezone normalization, csv-stringify pipeline, and TransactionCsvRow type.
API: Schemas & Tests
apps/api/src/billing/http-schemas/stripe.schema.ts, apps/api/src/billing/http-schemas/stripe.schema.spec.ts
Centralizes dateRangeSchema/dateRangeCheck, adds CustomerTransactionsCsvExportQuerySchema (with timezone validation), and expands tests for date-range and timezone validation.
API: Tests & Seeders
apps/api/src/billing/services/stripe/stripe.service.spec.ts, apps/api/test/seeders/payment-method.seeder.ts, apps/api/test/seeders/stripe-transaction.seeder.ts
Adds tests for streaming export scenarios, introduces StripeTransactionSeeder, and makes generatePaymentMethod configurable (note: duplicate test block present).
API: Config & Dependency
apps/api/package.json, apps/api/tsconfig.json
Adds csv-stringify dependency and adds lib: ["esnext"] to apps/api/tsconfig.json.
HTTP SDK
packages/http-sdk/src/stripe/stripe.service.ts, packages/http-sdk/src/stripe/stripe.types.ts
Adds exportTransactionsCsv(params) returning a Blob from /v1/stripe/transactions/export and ExportTransactionsCsvParams type.
Web App: Billing Usage UI
apps/deploy-web/src/components/billing-usage/BillingContainer/*, apps/deploy-web/src/components/billing-usage/BillingView/*
Exposes onExport prop, calls SDK export to download Blob, replaces error with errorMessage, tightens dateRange types; updates tests.
Web App: Usage UI
apps/deploy-web/src/components/billing-usage/UsageContainer/*, apps/deploy-web/src/components/billing-usage/UsageView/*
Tightens dateRange and onDateRangeChange to require both from and to Date values; tests adjusted.
Web App: Utilities & Query Keys
apps/deploy-web/src/utils/domUtils.ts, apps/deploy-web/src/queries/queryKeys.ts, apps/deploy-web/src/utils/domUtils.spec.ts
Changes downloadCsv to accept a Blob and adds tests; adds getExportTransactionsCsvKey factory including timezone and optional dates.
UI Package
packages/ui/components/custom/date-range-picker.tsx
Changes props to require complete { from: Date; to: Date } and only calls onChange when both bounds exist.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Web_UI as Web UI
  participant SDK as HTTP SDK
  participant API as API Router
  participant Controller as StripeController
  participant Service as StripeService
  participant Stripe as Stripe API

  User->>Web_UI: Click "Export CSV"
  Web_UI->>SDK: exportTransactionsCsv({startDate,endDate,timezone})
  SDK->>API: GET /v1/stripe/transactions/export?... (responseType=blob)
  API->>Controller: route -> exportTransactionsCsvStream
  Controller->>Service: exportTransactionsCsvStream(customerId, options)
  par paging loop
    Service->>Stripe: list transactions (limit, starting_after, date range)
    Stripe-->>Service: page of transactions
    Service->>Service: transform rows -> csv-stringify -> yield chunks
  end
  Service-->>Controller: AsyncIterable<string> (CSV chunks)
  Controller-->>API: stream response (text/csv, chunked, Content-Disposition)
  API-->>SDK: HTTP response -> Blob
  SDK-->>Web_UI: Blob
  Web_UI->>User: trigger download
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Export full date range instead of current paginated view (#1788)
Provide dedicated export endpoint for full-range CSV (#1788)
Programmatically loop through all pages during export (#1788)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Tightening of dateRange and onDateRangeChange props across UI components (apps/deploy-web/src/components/..., packages/ui/components/custom/date-range-picker.tsx) These API surface/type tightenings change consumer contracts and are broader UI/type changes not required by the export endpoint objective.
Addition of lib: ["esnext"] to TypeScript config (apps/api/tsconfig.json) Tooling/tsconfig change unrelated to implementing the export endpoint functionality.
Duplicate test block added in apps/api/src/billing/services/stripe/stripe.service.spec.ts Duplicate test suites are a test hygiene issue and not required by the export feature objective.

Possibly related PRs

Suggested reviewers

  • baktun14
  • stalniy
  • ygrishajev

Poem

I nibble bytes and stitch a row,
From stripe of coin to CSV snow,
Pages loop — I hop and fetch each page,
Chunks unfurl like leaves upon a page,
Click export — the ledger freed at last. 🐇📥

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11198ed and 66f91ad.

📒 Files selected for processing (6)
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts (1 hunks)
  • apps/api/src/billing/http-schemas/stripe.schema.ts (2 hunks)
  • apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (3 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (2 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.ts (1 hunks)
  • apps/api/test/seeders/payment-method.seeder.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
  • apps/api/test/seeders/payment-method.seeder.ts
  • apps/api/src/billing/http-schemas/stripe.schema.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
  • apps/api/test/seeders/payment-method.seeder.ts
  • apps/api/src/billing/http-schemas/stripe.schema.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: baktun14
PR: akash-network/console#1725
File: apps/api/src/utils/constants.ts:5-5
Timestamp: 2025-07-24T17:00:52.361Z
Learning: In the Akash Network Console project, when cross-cutting concerns or broader refactoring issues are identified during PR review, the preferred approach is to create a separate GitHub issue to track the work rather than expanding the scope of the current PR. This maintains focus and allows for proper planning of architectural improvements.
📚 Learning: applies to **/*.spec.{ts,tsx} : `setup` function should accept a single parameter with inline type d...
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : `setup` function should accept a single parameter with inline type definition

Applied to files:

  • apps/api/test/seeders/payment-method.seeder.ts
📚 Learning: applies to **/*.spec.{ts,tsx} : don't use `jest.mock()` to mock dependencies in test files. instead,...
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-07-21T08:24:24.269Z
Learning: Applies to **/*.spec.{ts,tsx} : Don't use `jest.mock()` to mock dependencies in test files. Instead, use `jest-mock-extended` to create mocks and pass mocks as dependencies to the service under test.

Applied to files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
📚 Learning: applies to **/*.spec.{ts,tsx} : use `setup` function instead of `beforeeach` in test files...
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : Use `setup` function instead of `beforeEach` in test files

Applied to files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
📚 Learning: applies to **/*.spec.{ts,tsx} : `setup` function creates an object under test and returns it...
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : `setup` function creates an object under test and returns it

Applied to files:

  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
🧬 Code Graph Analysis (5)
apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (3)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • CustomerTransactionsCsvExportQuerySchema (180-186)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
  • exportTransactionsCsv (153-159)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
  • exportTransactionsCsv (416-440)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
apps/api/src/auth/services/auth.service.ts (1)
  • Protected (39-55)
apps/api/test/seeders/payment-method.seeder.ts (1)
apps/api/src/billing/services/stripe/stripe.service.ts (1)
  • PaymentMethod (515-544)
apps/api/src/billing/services/stripe/stripe.service.ts (2)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • Transaction (203-203)
apps/api/src/core/services/tx/tx.service.ts (1)
  • transaction (19-26)
apps/api/src/billing/services/stripe/stripe.service.spec.ts (1)
apps/api/test/seeders/payment-method.seeder.ts (1)
  • generatePaymentMethod (11-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
🔇 Additional comments (12)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)

131-186: LGTM! Excellent refactoring for code reuse.

The date range validation logic is well-structured and properly extracted for reuse. The 366-day limit is appropriate for preventing excessive data exports, and the validation correctly handles optional dates.

apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)

152-159: LGTM! Consistent with existing controller patterns.

The method follows established authorization and validation patterns in the controller. The error handling for missing stripeCustomerId is appropriate and user-friendly.

apps/api/test/seeders/payment-method.seeder.ts (1)

5-17: LGTM! Clean enhancement for test data customization.

The changes maintain backward compatibility while enabling more precise control over generated payment method data. The TypeScript typing is appropriate and the implementation follows good patterns.

apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (2)

54-75: LGTM! Proper OpenAPI documentation for CSV export.

The route definition correctly specifies the CSV content type and binary format in the OpenAPI schema. The request validation using the shared schema is appropriate.


102-116: LGTM! Correct implementation of CSV file download.

The route handler properly:

  • Extracts validated query parameters
  • Calls the controller method
  • Sets appropriate Content-Type and Content-Disposition headers
  • Generates a descriptive filename based on the date range

The filename generation is safe since the dates are validated to be ISO datetime strings by the schema.

apps/api/src/billing/services/stripe/stripe.service.spec.ts (5)

368-429: LGTM! Comprehensive test coverage for single page export.

The test properly mocks dependencies, verifies CSV format, and checks service method calls with correct parameters. The use of generatePaymentMethod with specific inputs is excellent for testing precise scenarios.


431-508: LGTM! Excellent pagination handling test.

The test properly verifies that multiple pages are fetched correctly and the startingAfter parameter is properly passed between calls. The verification of CSV content inclusion is appropriate.


540-556: LGTM! Good coverage for empty results scenario.

The test correctly handles the empty transaction case and verifies the appropriate message is returned.


558-587: LGTM! Important edge case coverage for null payment methods.

This test ensures the CSV export handles transactions with missing payment method data gracefully, which is a realistic scenario in production data.


1020-1077: LGTM! Proper test setup following coding guidelines.

The setup function is correctly positioned at the bottom of the describe block, uses jest-mock-extended for mocking, and returns the service under test with its dependencies. No shared state is used, which follows the guidelines.

apps/api/src/billing/services/stripe/stripe.service.ts (2)

416-440: LGTM! Well-implemented pagination with appropriate safety limits.

The method correctly handles pagination through all transactions while preventing resource exhaustion with the 50,000 transaction limit. The reuse of existing getCustomerTransactions method maintains consistency.


442-473: CSV formatting implementation is solid with proper escaping.

The method correctly:

  • Handles the empty transactions case gracefully
  • Converts Unix timestamps to ISO date format (YYYY-MM-DD)
  • Formats amounts from cents to dollars with proper decimal places
  • Escapes CSV fields by doubling internal quotes and wrapping in quotes
  • Handles optional fields safely with fallback to empty strings

The currency conversion to uppercase and amount formatting follow standard practices.

anilmurty
anilmurty previously approved these changes Aug 6, 2025
Copy link
Contributor

@baktun14 baktun14 left a comment

Choose a reason for hiding this comment

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

Looks good! Just need to address the potentially memory issues

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/api/src/billing/services/stripe/stripe.service.ts (1)

418-444: Solid, memory-safe streaming pipeline.

Using Readable.from(generator) piped into csv-stringify and yielding chunks keeps memory bounded and avoids buffering entire CSVs in RAM. Good job addressing the previous review’s streaming concern.

🧹 Nitpick comments (6)
apps/api/src/billing/services/stripe/stripe.service.ts (2)

470-483: Sentinel “no transactions” row could surprise downstream CSV consumers.

Emitting a synthetic row with a message in the id column can break parsers expecting a consistent schema. Consider emitting only the header (no rows) when empty, or add a separate footer comment line prefixed with “#”.

Here’s a minimal change to output only headers when empty:

-    if (!hasYieldedAny) {
-      yield {
-        id: "No transactions found for the specified date range",
-        date: "",
-        amount: "",
-        currency: "",
-        status: "",
-        paymentMethodType: "",
-        cardBrand: "",
-        cardLast4: "",
-        description: "",
-        receiptUrl: ""
-      };
-    }
+    // If no records, emit nothing further. csv-stringify already emitted headers when header: true.
+    if (!hasYieldedAny) return;

372-416: Optional: enforce a maximum date range or record cap to protect the service.

Even with streaming, extremely large ranges can cause long-lived requests and heavy Stripe pagination. Consider enforcing a maximum range (e.g., 90 days) or an overall record cap with a clear error.

Would you like me to add a guard in getCustomerTransactions and/or in the date-range schema to enforce a maximum window?

apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (4)

102-111: Minor: naming clarity.

csvStream is an AsyncIterable, not a Node.js stream. Consider renaming to csvChunks or csvIterable to avoid confusion with Node streams.

-  const csvStream = await container.resolve(StripeController).exportTransactionsCsvStream({
+  const csvChunks = await container.resolve(StripeController).exportTransactionsCsvStream({
     startDate,
     endDate
   });

And below:

-        for await (const chunk of csvStream) {
+        for await (const chunk of csvChunks) {
           controller.enqueue(new TextEncoder().encode(chunk));
         }

112-123: Avoid re-creating TextEncoder on every chunk.

Instantiate TextEncoder once per request to reduce allocations during streaming.

   const readableStream = new ReadableStream({
     async start(controller) {
+      const encoder = new TextEncoder();
       try {
-        for await (const chunk of csvStream) {
-          controller.enqueue(new TextEncoder().encode(chunk));
+        for await (const chunk of csvStream) {
+          controller.enqueue(encoder.encode(chunk));
         }
         controller.close();
       } catch (error) {
         controller.error(error);
       }
     }
   });

125-131: Drop Transfer-Encoding header and declare charset.

Servers set Transfer-Encoding automatically for streamed responses. Also, include charset for correctness.

   return new Response(readableStream, {
     headers: {
-      "Content-Type": "text/csv",
+      "Content-Type": "text/csv; charset=utf-8",
       "Content-Disposition": `attachment; filename="${filename}"`,
-      "Transfer-Encoding": "chunked"
     }
   });

54-75: Optional: add an OpenAPI example and proper schema for streaming text.

If your tooling supports it, prefer schema: { type: "string" } without format: "binary" for text/csv, and add an example to improve DX. Not blocking.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6144a99 and 08c4bd4.

⛔ Files ignored due to path filters (2)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • apps/api/package.json (1 hunks)
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts (1 hunks)
  • apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (3 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (2 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
  • apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts
🧬 Code Graph Analysis (3)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
apps/api/src/auth/services/auth.service.ts (1)
  • Protected (39-55)
apps/api/src/billing/services/stripe/stripe.service.ts (2)
apps/api/src/core/services/tx/tx.service.ts (1)
  • transaction (19-26)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • Transaction (203-203)
apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (1)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • CustomerTransactionsCsvExportQuerySchema (180-186)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: validate (apps/stats-web) / validate-unsafe
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
🔇 Additional comments (3)
apps/api/package.json (1)

74-74: Dependency addition looks good; aligns with streaming CSV use-case.

Adding csv-stringify is appropriate for the streaming pipeline introduced in the service. No concerns on the version constraint.

apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)

154-161: Controller delegation and auth check look correct.

Authorization is enforced via @Protected, and you assert the presence of stripeCustomerId before delegating to the service streaming method. Good separation of concerns.

apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (1)

54-75: OpenAPI route definition for CSV looks good.

Clear summary, correct method and path, and proper content type. This will document the export endpoint well.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/api/src/billing/services/stripe/stripe.service.ts (1)

500-516: Add an explicit return type to enforce the CSV row contract

The method shape matches TransactionCsvRow; add the explicit return type to lock this down at compile time.

Apply this diff:

-  private transformTransactionForCsv(transaction: Transaction) {
+  private transformTransactionForCsv(transaction: Transaction): TransactionCsvRow {
🧹 Nitpick comments (2)
apps/api/src/billing/services/stripe/stripe.service.ts (2)

442-445: Handle stream errors to avoid unhandled exceptions in the CSV pipeline

If csv-stringify or the source stream emits an error, this loop will throw without logging context. Wrap with try/catch and log (then rethrow) to aid observability.

Apply this diff:

-    for await (const chunk of csvStream) {
-      yield chunk.toString();
-    }
+    try {
+      for await (const chunk of csvStream) {
+        yield chunk.toString();
+      }
+    } catch (err) {
+      logger.error({
+        event: "CSV_STREAMING_ERROR",
+        error: err instanceof Error ? err.message : String(err)
+      });
+      throw err;
+    }

447-451: Nit: Prefer void for the third AsyncGenerator generic parameter

You aren’t sending values into the generator via next(), so using void communicates intent better than unknown.

Apply this diff:

-  ): AsyncGenerator<TransactionCsvRow, void, unknown> {
+  ): AsyncGenerator<TransactionCsvRow, void, void> {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 08c4bd4 and f7a3110.

📒 Files selected for processing (2)
  • apps/api/src/billing/services/stripe/stripe.service.ts (3 hunks)
  • apps/api/src/types/transactions.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/types/transactions.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/types/transactions.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
🧬 Code Graph Analysis (1)
apps/api/src/billing/services/stripe/stripe.service.ts (2)
apps/api/src/types/transactions.ts (1)
  • TransactionCsvRow (21-32)
apps/api/src/billing/http-schemas/stripe.schema.ts (1)
  • Transaction (203-203)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
🔇 Additional comments (5)
apps/api/src/types/transactions.ts (1)

21-32: LGTM: Well-scoped CSV row shape

The type is clear, minimal, and matches the export needs across the service and tests. Using strings here keeps the streaming pipeline straightforward.

apps/api/src/billing/services/stripe/stripe.service.ts (4)

419-446: Good streaming pipeline: memory-safe and backpressure-aware

Using Readable.from over the async generator and piping through csv-stringify keeps memory bounded and respects backpressure. This addresses prior concerns about large CSV exports.


490-498: CSV injection mitigation is correctly implemented

Prefixing =, +, -, @ with a single quote aligns with OWASP guidance for CSV/Excel injection. Good call applying this to user-controlled description.


474-487: Sentinel row is intentional — tests assert the message

rg shows the sentinel is emitted in the service and explicitly asserted in tests, so this is deliberate.

  • apps/api/src/billing/services/stripe/stripe.service.ts — sentinel yield with id: "No transactions found for the specified date range" (around lines 474–487)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts — test asserts: expect(fullCsv).toContain("No transactions found for the specified date range"); (line ~518)

Action: confirm product requirement — if you want header-only on empty results, remove the sentinel yield and update the test(s); otherwise keep as-is.


1-5: Keep importing Readable from "stream" to match the rest of apps/api

Search in apps/api found 1 import from "stream" and 0 from "node:stream", so changing this file alone would be inconsistent. Leave the import as-is:

  • apps/api/src/billing/services/stripe/stripe.service.ts — keep: import { Readable } from "stream";

If you want to standardize on node:stream, update all occurrences in a single PR instead of this file alone.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx (1)

1-112: Many getBy* calls remain across apps/deploy-web tests
The guideline to prefer queryBy* over getBy* in .spec.tsx files has only been applied to BillingView.spec.tsx. A search finds hundreds of remaining getBy*/getAllBy* usages under apps/deploy-web. To fully comply:

• Replace all presence assertions using screen.getBy* (and any destructured getBy*) with screen.queryBy*, e.g.:

  • expect(screen.getByText("…")).toBeInTheDocument()expect(screen.queryByText("…")).toBeInTheDocument()
    • For interactive queries (e.g. fireEvent.click/change), add non-null assertions:
  • fireEvent.click(screen.getByText("Next"))fireEvent.click(screen.queryByText("Next")!)
    • Automate via a codemod or scripted find-and-replace across all *.spec.tsx under apps/deploy-web.

Let’s roll this out project-wide rather than file-by-file.

🧹 Nitpick comments (5)
packages/http-sdk/src/stripe/stripe.types.ts (1)

69-71: Duplicate interface declaration: SetupIntentResponse

SetupIntentResponse is declared twice in this file (also at Line 7). While TypeScript will merge identical interface declarations, this duplication is unnecessary and can mislead future edits.

Consider removing one of the duplicate declarations to avoid confusion. Keep the one closest to related payment-method types for cohesion.

packages/http-sdk/src/stripe/stripe.service.ts (1)

67-80: Use shared type, add validation, and specify Accept header

A few non-breaking enhancements to keep this method consistent and DRY:

  • Swap the inline { startDate: Date; endDate: Date } for the shared ExportTransactionsCsvParams.
  • Add a lightweight guard to mirror getCustomerTransactions (throw if startDate >= endDate).
  • Explicitly set Accept: "text/csv" so the intent and content negotiation are clear.
  • No change to extractData—it’s available on ApiHttpService and appropriate for a raw Blob response.

Apply this diff in packages/http-sdk/src/stripe/stripe.service.ts:

@@ -67,14 +67,23 @@ export class StripeService extends ApiHttpService {
-  async exportTransactionsCsv(params: { startDate: Date; endDate: Date }): Promise<Blob> {
+  async exportTransactionsCsv(params: ExportTransactionsCsvParams): Promise<Blob> {
     if (params.startDate >= params.endDate) {
       throw new Error("startDate must be less than endDate");
     }

     const queryParams = new URLSearchParams({
       startDate: params.startDate.toISOString(),
       endDate: params.endDate.toISOString()
     });

     const url = `/v1/stripe/transactions/export?${queryParams}`;

     return this.extractData(
-      await this.get(url, { responseType: "blob" })
+      await this.get(url, {
+        responseType: "blob",
+        headers: { Accept: "text/csv" }
+      })
     );
   }

And at the top of the file:

import type { ExportTransactionsCsvParams } from "./stripe.types";
apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx (1)

63-67: Strengthen the export test by asserting the mutation is invoked with dates.

Currently, the test only checks that the onExport prop is called. Given the container now triggers a mutation when a valid date range exists, it’s helpful to assert that mutate is called with { startDate, endDate }.

You already control the mocked hook; capture its mutate and assert args:

-    const mockedUseExportTransactionsCsvMutation = jest.fn(() => ({
-      mutate: jest.fn(),
+    const mutateMock = jest.fn();
+    const mockedUseExportTransactionsCsvMutation = jest.fn(() => ({
+      mutate: mutateMock,
       isLoading: false,
       isError: false,
       error: null
     })) as unknown as jest.MockedFunction<typeof useExportTransactionsCsvMutation>;

Then extend the test:

-  it("calls onExport", async () => {
-    const { child, onExport } = await setup();
-    child.onExport();
-    expect(onExport).toHaveBeenCalled();
-  });
+  it("calls onExport and triggers export mutation with date range", async () => {
+    const { child, onExport } = await setup();
+    child.onExport();
+    expect(onExport).toHaveBeenCalled();
+    // also check mutation invocation:
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+    const { mutate } = (mockedUseExportTransactionsCsvMutation as unknown as jest.Mock).mock.results[0].value;
+    expect(mutate).toHaveBeenCalledWith(
+      expect.objectContaining({ startDate: expect.any(Date), endDate: expect.any(Date) })
+    );
+  });

Also applies to: 102-108

apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx (1)

126-132: Surface export errors to the UI by handling mutation failures.

Right now, export errors are silent. Consider setting error when the export mutation fails, and optionally clearing stale errors when a new export starts.

Proposed change:

   const exportCsv = () => {
     if (startDate && endDate) {
-      exportMutation.mutate({ startDate, endDate });
+      setError(null);
+      exportMutation.mutate(
+        { startDate, endDate },
+        {
+          onError: (e: Error) => setError(e)
+        }
+      );
     } else {
       setError(new Error("Please select a valid date range before exporting."));
     }
   };
apps/deploy-web/src/queries/usePaymentQueries.ts (1)

117-139: LGTM: export CSV mutation triggers a safe, revocable download.

The mutation is correctly typed, calls the service, and the onSuccess handler creates, clicks, and revokes the object URL.

Optional polish:

  • Consider a mutationKey (e.g., using a helper like QueryKeys.getExportTransactionsCsvKey(...) if available) to improve DevTools visibility.
  • If desired UX-wise, add an onError in callers (as suggested in the container) rather than here, to keep side-effects localized to the component that owns error UI.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f7a3110 and e509965.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • apps/api/package.json (1 hunks)
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx (6 hunks)
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx (5 hunks)
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx (3 hunks)
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.tsx (4 hunks)
  • apps/deploy-web/src/queries/queryKeys.ts (1 hunks)
  • apps/deploy-web/src/queries/usePaymentQueries.ts (2 hunks)
  • packages/http-sdk/src/stripe/stripe.service.ts (1 hunks)
  • packages/http-sdk/src/stripe/stripe.types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/package.json
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/deploy-web/src/queries/queryKeys.ts
  • packages/http-sdk/src/stripe/stripe.service.ts
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx
  • packages/http-sdk/src/stripe/stripe.types.ts
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.tsx
  • apps/deploy-web/src/queries/usePaymentQueries.ts
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/deploy-web/src/queries/queryKeys.ts
  • packages/http-sdk/src/stripe/stripe.service.ts
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx
  • packages/http-sdk/src/stripe/stripe.types.ts
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.tsx
  • apps/deploy-web/src/queries/usePaymentQueries.ts
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
apps/{deploy-web,provider-console}/**/*.spec.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/query-by-in-tests.mdc)

Use queryBy methods instead of getBy methods in test expectations in .spec.tsx files

Files:

  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
🧬 Code Graph Analysis (2)
apps/deploy-web/src/queries/usePaymentQueries.ts (2)
apps/deploy-web/src/context/ServicesProvider/ServicesProvider.tsx (1)
  • useServices (27-29)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • ExportTransactionsCsvParams (101-104)
apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx (1)
apps/deploy-web/src/queries/usePaymentQueries.ts (1)
  • useExportTransactionsCsvMutation (117-139)
🔇 Additional comments (13)
packages/http-sdk/src/stripe/stripe.types.ts (1)

101-104: Type addition is sensible and consistent

ExportTransactionsCsvParams with Date fields is a good fit for the client-side API and aligns with the rest of your date handling (ISO string conversion at call sites).

apps/deploy-web/src/queries/queryKeys.ts (1)

108-108: Cache key factory looks good

Key is stable (uses ISO strings) and specific to the date range. No issues.

apps/deploy-web/src/components/billing-usage/BillingView/BillingView.tsx (1)

51-52: Prop-driven export wiring LGTM

Shifting export into a parent-provided onExport keeps the view dumb and improves testability/composability. Button disablement when no data is also sensible.

Also applies to: 67-68, 174-177

apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx (1)

107-112: New onExport test is appropriate

Asserts the contract of the prop at the view layer. After switching to queryBy*, the intent remains clear.

apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx (5)

49-54: LGTM: pagination handler is correctly exercised.

The test cleanly verifies that the pagination callback is wired and invoked with the new state.


56-61: LGTM: date range handler is correctly exercised.

The test verifies wiring for date range changes; aligns with the container’s onDateRangeChange.


110-112: LGTM: dependency injection for hooks keeps tests isolated.

Passing both query and export mutation hooks via dependencies avoids jest.mock() and aligns with the repo’s testing guidelines.


128-133: LGTM: event forwarding pattern is consistent.

Forwarding to both the local spy and the child-provided prop maintains observability in tests while preserving intended behavior.


140-140: LGTM: setup returns the new onExport handle.

Returning onExport from setup matches the added behavior under test and keeps tests cohesive.

apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx (3)

6-12: LGTM: dependency wiring includes the new export mutation.

Importing and exposing useExportTransactionsCsvMutation in DEPENDENCIES keeps the component testable and aligns with the DI pattern already in use.


52-67: LGTM: consistent date-range flow into queries.

Renaming to startDate/endDate and passing through to usePaymentTransactionsQuery matches the new API and types in UsePaymentTransactionsOptions.


21-27: LGTM: onExport added to ChildrenProps and properly exposed.

The new onExport prop is included in the contract and wired to exportCsv, fitting the intended UX.

Also applies to: 134-149

apps/deploy-web/src/queries/usePaymentQueries.ts (1)

1-8: LGTM: imports expanded for export params.

Adding ExportTransactionsCsvParams aligns with the new hook signature and keeps types explicit.


const csvContent = [headers, ...rows].map(row => row.map(field => `"${String(field).replace(/"/g, '""')}"`).join(",")).join("\n");
private transformTransactionForCsv(transaction: Transaction) {
const date = new Date(transaction.created * 1000).toISOString().split("T")[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

question(non-blocking): is it expected for the api caller that this date will be in UTC?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is an important note. The user could call this endpoint from any part of the globe by picking the date range on the picker (in local timezone). The endpoint receives both startDate and endDate with timezone data. I should use it to properly filter the transactions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm now extracting the timezone offset from UTC inside *createTransactionGenerator(), passing it to transformTransactionForCsv() here and re-applying to the date it when returning a transaction item. This should provide expected dates in the report.

Transactions are already properly filtered on the Stripe side since I'm passing timestamps of start and end dates that I'm getting from Date.getTime(), which is UTC-based, inside getCustomerTransactions()

Copy link
Contributor

Choose a reason for hiding this comment

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

You could add timezone as a required parameter to controller. So, when frontend calls it, it will need to specify timezone. because all dates which are sent over the wire are in UTC. toISOString() converts date to ISO8601 in UTC timezone

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Required timezone on the query schema side with custom refinement + passed it down from the frontend 👍

@domhhv domhhv requested a review from stalniy August 19, 2025 15:05
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (1)

103-122: Nice switch to Node.js stream helpers; tweak headers and charset

Using Readable.from + Readable.toWeb is a solid simplification (addresses prior review). Minor header/encoding tweaks:

  • Add charset=utf-8 to Content-Type (CSV often contains non-ASCII).
  • You can omit Transfer-Encoding; the runtime will set it appropriately.

Apply this diff:

   const readableStream = Readable.toWeb(Readable.from(csvStream)) as ReadableStream;

   return new Response(readableStream, {
     headers: {
-      "Content-Type": "text/csv",
+      "Content-Type": "text/csv; charset=utf-8",
       "Content-Disposition": `attachment; filename="${filename}"`,
-      "Transfer-Encoding": "chunked"
+      // Let the runtime decide transfer-encoding for streaming
     }
   });
apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx (1)

9-9: Fix invalid typeof usage on type-only imports (will fail type-check).

typeof cannot reference symbols imported via import type. Use inline type queries with typeof import("...").symbol for your jest mocked function types.

-import type { useExportTransactionsCsvQuery, usePaymentTransactionsQuery } from "@src/queries";
+import type { useExportTransactionsCsvQuery, usePaymentTransactionsQuery } from "@src/queries";
+// Type aliases using inline type queries to work with consistent-type-imports
+type UsePaymentTransactionsQuery = typeof import("@src/queries").usePaymentTransactionsQuery;
+type UseExportTransactionsCsvQuery = typeof import("@src/queries").useExportTransactionsCsvQuery;
@@
-    })) as unknown as jest.MockedFunction<typeof usePaymentTransactionsQuery>;
+    })) as unknown as jest.MockedFunction<UsePaymentTransactionsQuery>;
@@
-    })) as unknown as jest.MockedFunction<typeof useExportTransactionsCsvQuery>;
+    })) as unknown as jest.MockedFunction<UseExportTransactionsCsvQuery>;

Also applies to: 94-99, 101-108

🧹 Nitpick comments (15)
apps/api/tsconfig.json (1)

9-9: Review TS lib configuration in apps/api/tsconfig.json

• No bleeding-edge ESNext-only APIs detected (per search for Object.groupBy, Array.toReversed, etc.).
• You do use Fetch API (fetch, Response, Headers) and Web-Streams (ReadableStream via Readable.toWeb), so narrowing lib will drop those type definitions unless you explicitly add a DOM-or-Web-Streams lib.
• apps/api/package.json doesn’t declare its own engines.node (falls back to root’s 22.14.0). Ensure your runtime supports native fetch and streams-to-web.

Optional refactor—if you want to tighten what TS exposes, you can switch to:

-    "lib": ["esnext"]
+    "lib": [
+      "ES2022",
+      "ESNext.AsyncIterable",
+      "DOM"
+    ]

This gives you modern ES2022 + async-iterable support plus web-API types for fetch/Response/ReadableStream.
If you genuinely need stage-3+ ESNext features, you may keep esnext—just verify your Node version or polyfills match the surfaced types.

packages/ui/components/custom/date-range-picker.tsx (1)

19-20: Prop type change is breaking; consider a union with DateRange and keep state in sync with prop

Switching date and onChange to strictly { from: Date; to: Date } is fine, but it’s a breaking change. If not all call-sites were updated, consider a transitional union, and also sync internal state when date changes.

Apply this diff to loosen date type:

 interface DateRangePickerProps {
   className?: string;
-  date?: { from: Date; to: Date };
+  date?: DateRange | { from: Date; to: Date } | null;
   onChange?: (date: { from: Date; to: Date }) => void;
   minDate?: Date | null;

Additionally, ensure prop updates reflect in internal state (outside this hunk):

// Sync internal state when `date` prop changes
React.useEffect(() => {
  if (date?.from && date?.to) {
    setSelectedRange({ from: date.from, to: date.to });
    setCalendarMonth(date.from);
  }
}, [date?.from, date?.to]);

Please confirm all usages were updated across the codebase to the new shape.

packages/http-sdk/src/stripe/stripe.service.ts (1)

68-81: Add client-side date range validation to mirror getCustomerTransactions

getCustomerTransactions validates startDate < endDate; exportTransactionsCsv should too for consistency and early feedback.

Apply this diff:

   async exportTransactionsCsv(params: ExportTransactionsCsvParams): Promise<Blob> {
+    if (params.startDate >= params.endDate) {
+      throw new Error("startDate must be less than endDate");
+    }
     const queryParams = new URLSearchParams({
       startDate: params.startDate.toISOString(),
       endDate: params.endDate.toISOString()
     });
apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx (3)

95-97: Avoid non-determinism from new Date() in tests

Using new Date() in expectations/setup can cause flakiness across time zones and execution times. Prefer fixed dates in test data.

Example:

-        to: new Date()
+        to: new Date("2025-06-30T00:00:00.000Z")

143-143: Stabilize DateRangePicker stub: avoid falling back to new Date()

Falling back to new Date() in the stubbed onChange introduces flakiness when date?.to/date?.from are absent. Use the existing bound’s value (which defaultProps guarantees) rather than the current time.

Apply this diff:

-              onChange={e => onChange?.({ from: new Date(e.target.value), to: date?.to || new Date() })}
+              onChange={e => onChange?.({ from: new Date(e.target.value), to: date?.to as Date })}
-              onChange={e => onChange?.({ from: date?.from || new Date(), to: new Date(e.target.value) })}
+              onChange={e => onChange?.({ from: date?.from as Date, to: new Date(e.target.value) })}

Also applies to: 151-151


21-24: Follow repo guideline: prefer queryBy over getBy in expectations**

Per project testing guidelines for apps/deploy-web, use queryBy* in expectations to avoid throwy selectors.

Example changes (apply similarly across this file):

- expect(screen.getByText("Error fetching billing data")).toBeInTheDocument();
- expect(screen.getByText("fail!")).toBeInTheDocument();
+ expect(screen.queryByText("Error fetching billing data")).toBeInTheDocument();
+ expect(screen.queryByText("fail!")).toBeInTheDocument();

Note: It’s fine to keep getBy* for actions (finding elements to click/type), but swap to queryBy* in assertions.

apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (2)

2-2: Prefer node:stream import path

Using Node’s scheme improves clarity and avoids tooling/bundler ambiguity.

Apply this diff:

-import { Readable } from "stream";
+import { Readable } from "node:stream";

55-76: OpenAPI response looks good; consider documenting validation error

The 200 text/csv response is defined correctly. Optionally, add a 400/422 response documenting date-range validation errors for better API UX.

Example:

   responses: {
       description: "CSV file with transaction data",
       content: { "text/csv": { schema: { type: "string", format: "binary" } } }
     },
+    400: { description: "Invalid date range" }
   }
apps/deploy-web/src/queries/usePaymentQueries.ts (1)

117-127: Avoid caching large Blob responses; annotate generics for useQuery

Since this is an on-demand export and returns a potentially large Blob, avoid caching it in React Query to save memory, and annotate the query’s types explicitly.

Apply this diff:

-export const useExportTransactionsCsvQuery = (options: ExportTransactionsCsvParams): UseQueryResult<Blob, Error> => {
+export const useExportTransactionsCsvQuery = (options: ExportTransactionsCsvParams): UseQueryResult<Blob, Error> => {
   const { stripe } = useServices();

-  return useQuery({
-    enabled: false,
+  return useQuery<Blob, Error>({
+    enabled: false,
+    // Avoid retaining blobs in cache; pick the key that matches your @tanstack/react-query version
+    // cacheTime (v4) | gcTime (v5)
+    cacheTime: 0 as unknown as number,
     queryKey: QueryKeys.getExportTransactionsCsvKey(options),
     queryFn: async (): Promise<Blob> => {
       return await stripe.exportTransactionsCsv(options);
     }
   });
 };

If you’re on React Query v5, replace cacheTime with gcTime. Otherwise keep cacheTime. This prevents holding Blob data after the component unmounts.

Please confirm the installed @tanstack/react-query major version so we can finalize cacheTime vs gcTime.

apps/deploy-web/src/components/billing-usage/BillingView/BillingView.tsx (2)

174-174: Remove redundant dateRange checks in button disabled predicate.

With the new strict type dateRange: { from: Date; to: Date }, from and to are always defined. The checks are dead code; simplify to rely on data presence.

-        <Button variant="secondary" onClick={onExport} className="h-12 gap-4" disabled={!data.length || !dateRange.from || !dateRange.to}>
+        <Button variant="secondary" onClick={onExport} className="h-12 gap-4" disabled={!data.length}>

50-57: Consider normalizing errorMessage type to a plain string for consistency.

BillingContainer always passes a string (default ""), while BillingView allows string | null. Unifying to string avoids unnecessary nullability and simplifies rendering logic.

-  errorMessage: string | null;
+  errorMessage: string;
apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx (2)

32-39: Rename test to reflect current assertion (it checks a message, not an error object).

-  it("passes error object if queryError is axios error", async () => {
+  it("passes errorMessage when queryError is axios error", async () => {

101-107: Remove unused/incorrect property from mocked query return.

useExportTransactionsCsvQuery is a query (not a mutation) and doesn’t expose mutate. Keeping only relevant keys clarifies intent and aligns with UseQueryResult.

-    const mockedUseExportTransactionsCsvQuery = jest.fn(() => ({
-      mutate: jest.fn(),
-      isLoading: false,
-      isError: false,
-      error: null,
-      refetch: jest.fn()
-    })) as unknown as jest.MockedFunction<typeof useExportTransactionsCsvQuery>;
+    const mockedUseExportTransactionsCsvQuery = jest.fn(() => ({
+      isLoading: false,
+      isError: false,
+      error: null,
+      refetch: jest.fn()
+    })) as unknown as jest.MockedFunction<UseExportTransactionsCsvQuery>;
apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx (2)

73-77: Clear stale errorMessage when queryError resolves.

Currently, once set, errorMessage may persist even after a successful fetch. Reset it when the error goes away.

   React.useEffect(() => {
     if (axios.isAxiosError(queryError)) {
       setErrorMessage(queryError.response?.data.message || "An error occurred while fetching payment transactions.");
+    } else if (!queryError) {
+      setErrorMessage("");
     }
   }, [queryError]);

134-140: Simplify exportCsv guard; startDate/endDate are guaranteed by type.

dateRange is always { from: Date; to: Date }, so the truthiness check is redundant.

-  const exportCsv = async () => {
-    if (startDate && endDate) {
-      await exportCsvQuery.refetch();
-    } else {
-      setErrorMessage("Please select a valid date range before exporting.");
-    }
-  };
+  const exportCsv = async () => {
+    await exportCsvQuery.refetch();
+  };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e509965 and 4ec4579.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • apps/api/package.json (1 hunks)
  • apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (3 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts (2 hunks)
  • apps/api/src/billing/services/stripe/stripe.service.ts (3 hunks)
  • apps/api/tsconfig.json (1 hunks)
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx (7 hunks)
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx (6 hunks)
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx (6 hunks)
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.tsx (5 hunks)
  • apps/deploy-web/src/queries/queryKeys.ts (1 hunks)
  • apps/deploy-web/src/queries/usePaymentQueries.ts (2 hunks)
  • packages/http-sdk/src/stripe/stripe.service.ts (2 hunks)
  • packages/ui/components/custom/date-range-picker.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/api/package.json
  • apps/api/src/billing/services/stripe/stripe.service.spec.ts
  • apps/deploy-web/src/queries/queryKeys.ts
  • apps/api/src/billing/services/stripe/stripe.service.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • packages/ui/components/custom/date-range-picker.tsx
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.tsx
  • apps/deploy-web/src/queries/usePaymentQueries.ts
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx
  • packages/http-sdk/src/stripe/stripe.service.ts
  • apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • packages/ui/components/custom/date-range-picker.tsx
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.tsx
  • apps/deploy-web/src/queries/usePaymentQueries.ts
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx
  • packages/http-sdk/src/stripe/stripe.service.ts
  • apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts
  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
apps/{deploy-web,provider-console}/**/*.spec.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/query-by-in-tests.mdc)

Use queryBy methods instead of getBy methods in test expectations in .spec.tsx files

Files:

  • apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
🧠 Learnings (2)
📚 Learning: 2025-07-21T08:24:27.953Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/query-by-in-tests.mdc:0-0
Timestamp: 2025-07-21T08:24:27.953Z
Learning: Applies to apps/{deploy-web,provider-console}/**/*.spec.tsx : Use `queryBy` methods instead of `getBy` methods in test expectations in `.spec.tsx` files

Applied to files:

  • apps/deploy-web/src/queries/usePaymentQueries.ts
  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
📚 Learning: 2025-07-21T08:24:24.269Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-07-21T08:24:24.269Z
Learning: Applies to **/*.spec.{ts,tsx} : Don't use `jest.mock()` to mock dependencies in test files. Instead, use `jest-mock-extended` to create mocks and pass mocks as dependencies to the service under test.

Applied to files:

  • apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx
🧬 Code Graph Analysis (5)
apps/deploy-web/src/queries/usePaymentQueries.ts (2)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • ExportTransactionsCsvParams (101-104)
apps/deploy-web/src/context/ServicesProvider/ServicesProvider.tsx (1)
  • useServices (27-29)
apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx (2)
apps/deploy-web/src/utils/dateUtils.ts (1)
  • createDateRange (46-58)
apps/deploy-web/src/utils/domUtils.ts (1)
  • downloadCsv (60-75)
packages/http-sdk/src/stripe/stripe.service.ts (1)
packages/http-sdk/src/stripe/stripe.types.ts (1)
  • ExportTransactionsCsvParams (101-104)
apps/api/src/billing/routes/stripe-transactions/stripe-transactions.router.ts (1)
packages/http-sdk/src/stripe/stripe.service.ts (1)
  • exportTransactionsCsv (68-81)
apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.spec.tsx (1)
apps/deploy-web/src/queries/usePaymentQueries.ts (1)
  • useExportTransactionsCsvQuery (117-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: validate / validate-app
  • GitHub Check: validate / validate-app
🔇 Additional comments (6)
apps/api/tsconfig.json (1)

8-8: NodeNext module setting looks appropriate for ESM in Node.

This aligns with modern Node ESM/TS interop and should work fine with service code and imports.

packages/ui/components/custom/date-range-picker.tsx (1)

187-197: Apply only when both ends are selected — good guard

Only emitting onChange when both from and to exist avoids partial ranges leaking upstream and matches the stricter prop typing.

packages/http-sdk/src/stripe/stripe.service.ts (1)

68-81: extractData correctly returns raw Blob for blob responses
The protected extractData method in HttpService is implemented as:

protected extractData<T>(response: AxiosResponse<T>): T {
  return response.data;
}

At runtime, when you call this.get(..., { responseType: "blob" }), response.data is a Blob, and extractData returns that Blob directly. No changes are required.

apps/deploy-web/src/components/billing-usage/BillingView/BillingView.spec.tsx (2)

108-113: LGTM: verifies export flow is wired through onExport

The test correctly ensures the "Export as CSV" control triggers the provided callback.


163-165: LGTM: props updated to new API (errorMessage, onExport)

Defaults align with the component’s new public API and keep tests isolated.

apps/deploy-web/src/components/billing-usage/BillingContainer/BillingContainer.tsx (1)

55-58: Nice wiring of the export flow with dependency injection and automatic CSV download.

  • Using a disabled query with refetch() is a clean trigger for one-off exports.
  • The DI pattern (D.useExportTransactionsCsvQuery) keeps the component testable.
  • Download side-effect is correctly isolated in a useEffect.

Also applies to: 79-81, 134-140, 148-149

@domhhv domhhv requested a review from stalniy August 25, 2025 18:45
async exportTransactionsCsvStream(options: { startDate: string; endDate: string; timezone: string }): Promise<AsyncIterable<string>> {
const { currentUser } = this.authService;

assert(currentUser.stripeCustomerId, 500, "Payment account not properly configured. Please contact support.");
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
assert(currentUser.stripeCustomerId, 500, "Payment account not properly configured. Please contact support.");
assert(currentUser.stripeCustomerId, 403, "Payments are not configured. Please start with a trial first");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This assertion was taken from this.getCustomerTransactions above, which is now replaced with gracefully returning empty data there. I'm wondering if something similar should be done here or if assertion is better suited still?

    if (!currentUser.stripeCustomerId) {
        return 'No transactions found';
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used this placeholder iterator:

    if (!currentUser.stripeCustomerId) {
      return (async function* () {
        yield "Payments are not configured. Please start with a trial first";
      })();
    }

stalniy
stalniy previously approved these changes Aug 26, 2025
ygrishajev
ygrishajev previously approved these changes Aug 26, 2025

if (!currentUser.stripeCustomerId) {
return (async function* () {
yield "Payments are not configured. Please start with a trial first";
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure i follow. this should be an error. basically this call is forbidden 403 as user gotta do other stuff. we can also use 428 Precondition Required

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ygrishajev I used this assertion that you proposed earlier:

assert(currentUser.stripeCustomerId, 403, "Payments are not configured. Please start with a trial first");

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)

158-158: Status/message consistency for missing billing setup

403 is acceptable here, but we’re inconsistent across endpoints (some return 500 with “contact support”, others return empty arrays). Either standardize on 403 for “action forbidden until billing is set up” or consider 428 Precondition Required as previously suggested. Also, the “Please start with a trial first” phrase may be misleading for users who actually need to set up billing, not a trial.

Option A (keep 403, clarify message):

-    assert(currentUser.stripeCustomerId, 403, "Payments are not configured. Please start with a trial first");
+    assert(currentUser.stripeCustomerId, 403, "Payments are not configured. Please set up billing to export transactions.");

Option B (switch to 428):

-    assert(currentUser.stripeCustomerId, 403, "Payments are not configured. Please start with a trial first");
+    assert(currentUser.stripeCustomerId, 428, "Billing account is required to export transactions.");
🧹 Nitpick comments (2)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (2)

155-161: Drop unnecessary async; return the AsyncIterable directly

The method doesn’t await anything. Returning the AsyncIterable directly avoids an extra Promise wrapper and simplifies the signature.

Apply this diff:

-  async exportTransactionsCsvStream(options: { startDate: string; endDate: string; timezone: string }): Promise<AsyncIterable<string>> {
+  exportTransactionsCsvStream(options: { startDate: string; endDate: string; timezone: string }): AsyncIterable<string> {
     const { currentUser } = this.authService;

     assert(currentUser.stripeCustomerId, 403, "Payments are not configured. Please start with a trial first");

     return this.stripe.exportTransactionsCsvStream(currentUser.stripeCustomerId, options);
   }

154-154: Consider throttling heavy exports

CSV export can be IO-heavy. If abuse or load spikes are a concern, consider adding the Semaphore decorator as you did for other actions. If rate limiting is handled elsewhere (e.g., at the router/gateway), feel free to ignore.

-  @Protected([{ action: "read", subject: "StripePayment" }])
+  @Semaphore()
+  @Protected([{ action: "read", subject: "StripePayment" }])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 941aeba and 8c4e8ab.

📒 Files selected for processing (1)
  • apps/api/src/billing/controllers/stripe/stripe.controller.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/billing/controllers/stripe/stripe.controller.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: domhhv
PR: akash-network/console#1790
File: apps/deploy-web/src/queries/queryKeys.ts:107-120
Timestamp: 2025-08-25T15:19:15.429Z
Learning: In the Akash Console billing system, timezone is only required for the CSV export functionality (getExportTransactionsCsvKey), not for regular transaction list queries (getPaymentTransactionsKey). The CSV export needs server-side timezone handling for proper date formatting in the exported file, while the regular transaction list handles dates differently.
🧬 Code graph analysis (1)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)
apps/api/src/auth/services/auth.service.ts (1)
  • Protected (39-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: validate (packages) / validate-unsafe
  • GitHub Check: validate (apps/provider-proxy) / validate-unsafe
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: test-build
  • GitHub Check: Validate local packages
🔇 Additional comments (1)
apps/api/src/billing/controllers/stripe/stripe.controller.ts (1)

154-161: Overall LGTM on route guard + delegation

Good use of the Protected decorator and clean delegation to StripeService for streaming. The controller stays thin, which is desirable here.

Comment on lines +155 to +161
async exportTransactionsCsvStream(options: { startDate: string; endDate: string; timezone: string }): Promise<AsyncIterable<string>> {
const { currentUser } = this.authService;

assert(currentUser.stripeCustomerId, 403, "Payments are not configured. Please start with a trial first");

return this.stripe.exportTransactionsCsvStream(currentUser.stripeCustomerId, options);
}
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 26, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Type the options via the request schema type and verify timezone validation

Per the retrieved learning, timezone is required only for the CSV export. If there’s a Zod schema/type (e.g., CustomerTransactionsCsvExportQuery or similar), prefer using that type here to avoid drift. Also verify the schema enforces a valid IANA timezone and that the service normalizes it.

Run this script to confirm the schema/type and headers:

If a concrete type exists, update the signature accordingly:

// Example if a type exists:
// exportTransactionsCsvStream(options: CustomerTransactionsCsvExportQuery): AsyncIterable<string>

Note: I used the retrieved learning about timezone being required for CSV export to guide this verification.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Locate CSV export query schema/type (name guesses; show context)
rg -n --type=ts -C3 'CustomerTransactionsCsvExport(Query|Request)(Schema|Type)?|TransactionsCsvExport' || true

# 2) Check that timezone is required and validated (IANA or normalization)
rg -n --type=ts -C2 '(timezone).*z\.string|timezone.*refine|IANA|normalize(Time|T)?Zone' || true

# 3) Confirm service method and any timezone normalization usage
rg -n --type=ts -C3 'exportTransactionsCsvStream\(' || true
rg -n --type=ts -C2 'normalize(Time|T)?Zone' || true

# 4) Ensure router sets CSV headers and filename
rg -n --type=ts -C2 'text/csv|Content-Disposition|attachment; filename' apps || true

Length of output: 19755


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show normalizeTimeZone method definition
rg -n --type=ts 'private normalizeTimeZone' -C10 apps/api/src/billing/services/stripe/stripe.service.ts || true

# Show exportTransactionsCsvStream implementation context
sed -n '400,450p' apps/api/src/billing/services/stripe/stripe.service.ts || true

Length of output: 2770


Use the Zod-inferred request schema type in your controller

To keep your controller signature in sync with the HTTP schema (and avoid type drift), replace the inline { startDate: string; endDate: string; timezone: string } type with the inferred type from CustomerTransactionsCsvExportQuerySchema. Your timezone validation is already handled by the schema’s .refine(...), and the service’s normalizeTimeZone(...) will fallback to UTC when needed.

• File: apps/api/src/billing/controllers/stripe/stripe.controller.ts (around line 155)
Replace:

async exportTransactionsCsvStream(
  options: { startDate: string; endDate: string; timezone: string }
): Promise<AsyncIterable<string>> {  }

With something like:

import { CustomerTransactionsCsvExportQuerySchema } from "@src/billing/http-schemas/stripe.schema";
import type { infer as ZodInfer } from "zod";

async exportTransactionsCsvStream(
  options: ZodInfer<typeof CustomerTransactionsCsvExportQuerySchema>
): Promise<AsyncIterable<string>> {  }

• This change ensures any updates to the export query schema (including IANA-timezone validation) flow through to the controller automatically.
• Timezone is validated by the Zod schema:

timezone: z
  .string()
  .refine(tz => Intl.supportedValuesOf("timeZone").includes(tz), {
    message: "Invalid IANA timezone",
  })

and normalized in the service:

private normalizeTimeZone(tz: string): string {
  return Intl.supportedValuesOf("timeZone").includes(tz) ? tz : "UTC";
}
🤖 Prompt for AI Agents
In apps/api/src/billing/controllers/stripe/stripe.controller.ts around lines
155-161, replace the inline options type { startDate: string; endDate: string;
timezone: string } with the Zod-inferred type from
CustomerTransactionsCsvExportQuerySchema so the controller signature stays in
sync with the HTTP schema; import the schema and use Zod's infer type (e.g.
import the schema from @src/billing/http-schemas/stripe.schema and use type
ZodInfer<typeof CustomerTransactionsCsvExportQuerySchema> for the options
parameter) so timezone validation comes from the schema and the service can
still normalize it as needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

@domhhv this is a valid comment

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

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.

Usage -csv only exports current view

5 participants

Comments