Skip to content

Conversation

@ygrishajev
Copy link
Contributor

@ygrishajev ygrishajev commented Dec 26, 2025

Change wallet balance reload check job management from tracking job IDs
in the database to using pg-boss singleton policy with bulk cleanup.

Key changes:

  • Change handler policy from "short" to "singleton" to allow multiple
      scheduled jobs while maintaining single active execution
  • Remove autoReloadJobId field from wallet-setting schema
  • Remove job cancellation from wallet-settings service delete operation
  • Add cancelCreatedBy method to JobQueueService for bulk cancellation
      of created jobs by singletonKey
  • Update handler to use withCleanup flag when scheduling next check
  • Simplify WalletReloadJobService by removing transaction wrapper and
      job ID tracking

This approach allows multiple scheduled jobs to exist while ensuring
only one active job runs at a time, with cleanup handled in bulk
rather than tracking individual job IDs.

Fixes race condition where REST handlers updating wallet settings could
mark an active job as cancelled even though it completes successfully,
causing state inconsistencies.

refs #1779

Summary by CodeRabbit

  • Bug Fixes

    • Improved wallet auto-reload job management by simplifying and streamlining the scheduling and cleanup logic.
    • Enhanced consistency in wallet settings data handling with complete object returns.
  • Improvements

    • Updated wallet reload job scheduling with more reliable singleton-based job handling and explicit cleanup controls.

✏️ Tip: You can customize this high-level summary in your review settings.

@ygrishajev ygrishajev requested a review from a team as a code owner December 26, 2025 20:48
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 26, 2025

📝 Walkthrough

Walkthrough

This PR removes the autoReloadJobId field from wallet settings and simplifies job scheduling by decoupling jobId tracking from the wallet setting record, replacing prior cancellation patterns with a new withCleanup flag and dedicated cancelCreatedByUserId method.

Changes

Cohort / File(s) Change Summary
Schema & Test Data
apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts, apps/api/test/seeders/wallet-setting.seeder.ts
Removed autoReloadJobId field from WalletSetting table and corresponding seeder output
Wallet Settings Service
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts, apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
Return types updated to include full WalletSettingOutput (no longer omitting autoReloadJobId); simplified getter/upsert logic; removed intermediate stripping and cancellation handling in deleteWalletSetting
Wallet Reload Job Service
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts, apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
Removed TxService dependency; scheduleForWalletSetting parameter changed from prevAction to withCleanup; added new cancelCreatedByUserId method; removed public cancel method; new comprehensive test suite covering scheduling and cleanup scenarios
Balance Reload Check Handler
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts, apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
Policy changed from "short" to "singleton"; ActionableWalletSetting type updated to exclude autoReloadJobId; scheduling calls updated from prevAction: "complete" to withCleanup: true
Job Queue Service
apps/api/src/core/services/job-queue/job-queue.service.ts
Added new cancelCreatedBy(query: { name; singletonKey }) method to cancel jobs by name and singleton key with logging

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • stalniy
  • baktun14

Poem

🐰 No jobId stored in wallets now,
Singleton jobs take a different bow,
Cleanup flags replace the old ways,
Simpler scheduling brightens our days! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: migrating to singleton policy and implementing bulk cleanup for wallet reload jobs.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b9ebd7 and 8f837b8.

📒 Files selected for processing (9)
  • apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts
  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts
  • apps/api/src/core/services/job-queue/job-queue.service.ts
  • apps/api/test/seeders/wallet-setting.seeder.ts
💤 Files with no reviewable changes (2)
  • apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
  • apps/api/test/seeders/wallet-setting.seeder.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
  • apps/api/src/core/services/job-queue/job-queue.service.ts
  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

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

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)

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

Use setup function instead of beforeEach in test files. The setup function must be at the bottom of the root describe block, should create an object under test and return it, accept a single parameter with inline type definition, avoid shared state, and not have a specified return type.

**/*.spec.{ts,tsx}: Use <Subject>.name in the root describe suite description instead of hardcoded class/service name strings to enable automated refactoring tools to find all references
Use either a method name or a condition starting with 'when' for nested suite descriptions in tests
Use present simple, 3rd person singular for test descriptions without prepending 'should'

Files:

  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: ygrishajev
Repo: akash-network/console PR: 2422
File: apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts:124-128
Timestamp: 2025-12-29T13:20:43.626Z
Learning: In the wallet-settings service (apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts), auto-reload jobs are not eagerly canceled when autoReloadEnabled is disabled. Instead, jobs check the autoReloadEnabled flag at execution time and complete early if disabled. This design avoids concurrency issues that could occur with eager cancellation.
📚 Learning: 2025-12-29T13:20:43.626Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2422
File: apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts:124-128
Timestamp: 2025-12-29T13:20:43.626Z
Learning: In the wallet-settings service (apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts), auto-reload jobs are not eagerly canceled when autoReloadEnabled is disabled. Instead, jobs check the autoReloadEnabled flag at execution time and complete early if disabled. This design avoids concurrency issues that could occur with eager cancellation.

Applied to files:

  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.

Applied to files:

  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
🧬 Code graph analysis (1)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
  • WalletSettingOutput (19-22)
⏰ 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). (3)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (9)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (1)

18-212: LGTM! Test updates correctly reflect the schema changes.

The test file has been properly updated to align with the removal of autoReloadJobId from the wallet setting schema. Key improvements:

  • Tests now expect the full WalletSettingOutput objects instead of destructured subsets
  • Mock setup simplified by removing cancellation-related stubs
  • Test structure follows coding guidelines (setup function at bottom, proper describe/it naming)

The changes are consistent with the PR's objective to decouple job ID tracking from wallet settings.

apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (3)

24-24: Type definition properly updated.

The ActionableWalletSetting type correctly reflects the removal of autoReloadJobId from the wallet setting schema, maintaining only the essential id and userId fields needed for job scheduling.


41-41: Policy change to "singleton" enables concurrent scheduling while preventing duplicate execution.

The policy change from "short" to "singleton" is a key improvement that allows multiple jobs to be scheduled (e.g., when settings are updated) while ensuring only one active job runs at a time per user. This aligns with the PR's goal of addressing race conditions in job management.


208-218: withCleanup flag enables bulk cleanup of stale jobs.

The withCleanup: true option triggers cancellation of pending "created" jobs before scheduling the next check, preventing accumulation of stale jobs. This is a cleaner approach than tracking individual job IDs.

Based on learnings, the handler validates autoReloadEnabled at runtime (line 113), so disabled auto-reload jobs will exit early without needing eager cancellation.

apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts (1)

1-188: LGTM! Comprehensive test coverage for the new service behavior.

This new test file provides excellent coverage of the WalletReloadJobService:

  • Properly tests early returns when conditions aren't met
  • Validates singleton key formation pattern
  • Covers the new withCleanup behavior for bulk job cancellation
  • Tests various option combinations (withCleanup, startAfter)
  • Includes error handling for job creation failures

The test structure follows coding guidelines:

  • Uses WalletReloadJobService.name in root describe
  • Nested describes use method names
  • Test descriptions use present simple 3rd person
  • Setup function at the bottom of the describe block
  • Uses jest-mock-extended for mocks
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (4)

10-16: Simplified constructor removes transaction dependency.

The removal of TxService from the constructor addresses previous concerns about database connection occupation during external calls. This service no longer wraps operations in transactions, which is appropriate since it primarily delegates to the job queue service.


18-24: scheduleImmediate correctly guards against unnecessary job creation.

The method now properly checks both the existence of walletSetting and autoReloadEnabled before scheduling, preventing unnecessary job creation. This aligns with the design where jobs validate autoReloadEnabled at runtime rather than eagerly canceling.


26-48: scheduleForWalletSetting signature properly constrains input types.

The method signature correctly uses Pick<WalletSettingOutput, "id" | "userId"> to accept only the required fields, eliminating the dependency on autoReloadJobId. The implementation:

  • Properly handles the withCleanup option to cancel stale jobs
  • Uses singleton key pattern for job identification
  • Includes error handling with logging
  • Returns the created job ID for tracking

50-52: New cancelCreatedByUserId enables bulk job cleanup.

This method provides a clean interface for canceling all pending jobs for a user, using the same singleton key pattern as job creation. This supports the bulk cleanup approach that replaces individual job ID tracking.


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

@codecov
Copy link

codecov bot commented Dec 26, 2025

Codecov Report

❌ Patch coverage is 77.77778% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.01%. Comparing base (80857e0) to head (8f837b8).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...i/src/core/services/job-queue/job-queue.service.ts 0.00% 6 Missing ⚠️

❌ Your patch status has failed because the patch coverage (77.77%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2422      +/-   ##
==========================================
- Coverage   51.26%   51.01%   -0.25%     
==========================================
  Files        1069     1059      -10     
  Lines       29451    29089     -362     
  Branches     6522     6476      -46     
==========================================
- Hits        15099    14841     -258     
+ Misses      14116    14021      -95     
+ Partials      236      227       -9     
Flag Coverage Δ *Carryforward flag
api 80.21% <77.77%> (+0.16%) ⬆️
deploy-web 31.40% <ø> (ø) Carriedforward from 80857e0
log-collector ?
notifications 87.94% <ø> (ø) Carriedforward from 80857e0
provider-console 81.48% <ø> (ø) Carriedforward from 80857e0
provider-proxy 84.35% <ø> (ø) Carriedforward from 80857e0

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...el-schemas/wallet-setting/wallet-setting.schema.ts 77.77% <ø> (ø)
...eload-check/wallet-balance-reload-check.handler.ts 95.45% <100.00%> (ø)
...ces/wallet-reload-job/wallet-reload-job.service.ts 100.00% <100.00%> (+55.55%) ⬆️
...ervices/wallet-settings/wallet-settings.service.ts 96.55% <100.00%> (-0.42%) ⬇️
...i/src/core/services/job-queue/job-queue.service.ts 78.09% <0.00%> (-4.74%) ⬇️

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ygrishajev ygrishajev force-pushed the feature/billing branch 2 times, most recently from e106fc2 to 4c4d6f1 Compare December 29, 2025 13:12
@ygrishajev ygrishajev changed the title feat(billing): add row-level locking for wallet settings fix(billing): does not cancel auto reload on disable Dec 29, 2025
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

🧹 Nitpick comments (2)
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (1)

35-36: Update comment to reflect "none" action behavior.

The comment states "Try to cancel/complete the previous job if it exists," but with the addition of prevAction: "none", the previous job may be left untouched.

🔎 Suggested comment update
-      // Try to cancel/complete the previous job if it exists
-      // This may fail if the job is already in a terminal state, which is fine
+      // Handle the previous job based on prevAction: cancel it, complete it, or leave it (none)
+      // Cancel/complete may fail if the job is already in a terminal state, which is fine
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts (1)

103-103: Remove redundant startAfter: undefined parameter.

Explicitly passing startAfter: undefined is redundant since undefined is the default value when the parameter is omitted.

🔎 Proposed refactor
-      await service.scheduleForWalletSetting(walletSetting, { prevAction: "cancel", startAfter: undefined });
+      await service.scheduleForWalletSetting(walletSetting, { prevAction: "cancel" });
-      await service.scheduleForWalletSetting(walletSetting, { prevAction: "complete", startAfter: undefined });
+      await service.scheduleForWalletSetting(walletSetting, { prevAction: "complete" });

Also applies to: 128-128

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e106fc2 and 2f75d0e.

📒 Files selected for processing (4)
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts
  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

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

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)

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

Use setup function instead of beforeEach in test files. The setup function must be at the bottom of the root describe block, should create an object under test and return it, accept a single parameter with inline type definition, avoid shared state, and not have a specified return type.

**/*.spec.{ts,tsx}: Use <Subject>.name in the root describe suite description instead of hardcoded class/service name strings to enable automated refactoring tools to find all references
Use either a method name or a condition starting with 'when' for nested suite descriptions in tests
Use present simple, 3rd person singular for test descriptions without prepending 'should'

Files:

  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
🧬 Code graph analysis (2)
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (2)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
  • WalletSettingOutput (19-22)
apps/api/src/core/services/job-queue/job-queue.service.ts (1)
  • EnqueueOptions (303-303)
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts (3)
apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts (2)
  • userId (58-75)
  • userId (77-105)
apps/api/test/seeders/wallet-setting.seeder.ts (1)
  • generateWalletSetting (5-18)
apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
  • WalletBalanceReloadCheck (5-15)
⏰ 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). (3)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (2)

23-23: LGTM: Simplified validation logic.

The removal of the userId check is appropriate since userId is part of the required type signature Pick<WalletSettingOutput, "id" | "userId" | "autoReloadJobId"> (line 31).


45-49: LGTM: Proper handling of prevAction variants.

The logic correctly handles three distinct cases:

  • "cancel": cancels the previous job
  • "complete": completes the previous job
  • "none" (or undefined): takes no action on the previous job

This implementation aligns with the PR objective to avoid canceling jobs in certain scenarios.

apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts (1)

12-263: LGTM: Comprehensive test coverage following coding guidelines.

The test file demonstrates:

  • ✓ Proper use of jest-mock-extended for mocking
  • setup function at the bottom of the root describe block
  • WalletReloadJobService.name in the describe suite
  • ✓ Method names for nested suite descriptions
  • ✓ Present simple, 3rd person singular for test descriptions
  • ✓ Comprehensive coverage of all public methods and edge cases

The tests thoroughly cover the new prevAction: "none" behavior and various scheduling scenarios.

stalniy
stalniy previously approved these changes Dec 29, 2025
baktun14
baktun14 previously approved these changes Dec 29, 2025
@ygrishajev ygrishajev dismissed stale reviews from baktun14 and stalniy via 314bf27 December 30, 2025 11:23
@ygrishajev ygrishajev force-pushed the feature/billing branch 2 times, most recently from 314bf27 to c30d6d4 Compare December 30, 2025 11:24
@ygrishajev ygrishajev changed the title fix(billing): does not cancel auto reload on disable refactor(billing): migrate wallet reload jobs to singleton policy with bulk cleanup Dec 30, 2025
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

🧹 Nitpick comments (1)
apps/api/src/core/services/job-queue/job-queue.service.ts (1)

123-146: Implementation looks correct, but consider schema name handling.

The raw SQL uses parameterized queries for name and singletonKey, which is good. However, the schema name is directly interpolated into the query string. While it comes from configuration (not user input), consider using pg-boss's internal escaping or quoting mechanism for the schema identifier to prevent issues if the schema name ever contains special characters.

🔎 Optional: Use identifier quoting for schema name
  async cancelCreatedBy(query: { name: string; singletonKey: string }): Promise<void> {
    const db = await this.pgBoss.getDb();
    const schema = this.coreConfig.get("POSTGRES_BACKGROUND_JOBS_SCHEMA");
+   const escapedSchema = `"${schema.replace(/"/g, '""')}"`;
    const result = (await db.executeSql(
      `
        WITH results as (
-         UPDATE ${schema}.job
+         UPDATE ${escapedSchema}.job
          SET completed_on = now(),
            state = 'cancelled'
          WHERE name = $1
            AND state = 'created'
            AND singleton_key = $2
          RETURNING id
        )
        SELECT id FROM results
      `,
      [query.name, query.singletonKey]
    )) as { rows: { id: string }[] };

    this.logger.info({
      event: "JOBS_CANCELLED",
      jobIds: result.rows.map(r => r.id)
    });
  }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f75d0e and c30d6d4.

📒 Files selected for processing (12)
  • apps/api/drizzle/0024_overjoyed_swarm.sql
  • apps/api/drizzle/meta/0024_snapshot.json
  • apps/api/drizzle/meta/_journal.json
  • apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts
  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts
  • apps/api/src/core/services/job-queue/job-queue.service.ts
  • apps/api/test/seeders/wallet-setting.seeder.ts
💤 Files with no reviewable changes (2)
  • apps/api/src/billing/model-schemas/wallet-setting/wallet-setting.schema.ts
  • apps/api/test/seeders/wallet-setting.seeder.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/api/drizzle/meta/0024_snapshot.json
  • apps/api/drizzle/0024_overjoyed_swarm.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

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

**/*.{ts,tsx,js}: Never use type any or cast to type any. Always define the proper TypeScript types.
Never use deprecated methods from libraries.
Don't add unnecessary comments to the code.

Files:

  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
  • apps/api/src/core/services/job-queue/job-queue.service.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/no-jest-mock.mdc)

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

Use setup function instead of beforeEach in test files. The setup function must be at the bottom of the root describe block, should create an object under test and return it, accept a single parameter with inline type definition, avoid shared state, and not have a specified return type.

**/*.spec.{ts,tsx}: Use <Subject>.name in the root describe suite description instead of hardcoded class/service name strings to enable automated refactoring tools to find all references
Use either a method name or a condition starting with 'when' for nested suite descriptions in tests
Use present simple, 3rd person singular for test descriptions without prepending 'should'

Files:

  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: ygrishajev
Repo: akash-network/console PR: 2422
File: apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts:124-128
Timestamp: 2025-12-29T13:20:36.482Z
Learning: In the wallet-settings service (apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts), auto-reload jobs are not eagerly canceled when autoReloadEnabled is disabled. Instead, jobs check the autoReloadEnabled flag at execution time and complete early if disabled. This design avoids concurrency issues that could occur with eager cancellation.
📚 Learning: 2025-12-29T13:20:36.482Z
Learnt from: ygrishajev
Repo: akash-network/console PR: 2422
File: apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts:124-128
Timestamp: 2025-12-29T13:20:36.482Z
Learning: In the wallet-settings service (apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts), auto-reload jobs are not eagerly canceled when autoReloadEnabled is disabled. Instead, jobs check the autoReloadEnabled flag at execution time and complete early if disabled. This design avoids concurrency issues that could occur with eager cancellation.

Applied to files:

  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts
  • apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts
  • apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.

Applied to files:

  • apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts
🧬 Code graph analysis (2)
apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts (2)
apps/api/test/seeders/wallet-setting.seeder.ts (1)
  • generateWalletSetting (5-17)
apps/api/src/billing/events/wallet-balance-reload-check.ts (1)
  • WalletBalanceReloadCheck (5-15)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (1)
apps/api/src/billing/repositories/wallet-settings/wallet-settings.repository.ts (1)
  • WalletSettingOutput (19-22)
⏰ 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 (17)
apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.spec.ts (2)

52-64: LGTM!

The test correctly verifies the updated scheduling API with withCleanup: true, aligning with the handler implementation changes.


185-194: LGTM!

Test assertion correctly updated to expect withCleanup: true option when scheduling the next check.

apps/api/drizzle/meta/_journal.json (1)

172-179: LGTM!

Migration journal entry correctly added for the auto_reload_job_id column removal with proper sequencing.

apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts (3)

24-24: LGTM!

Type correctly simplified to only require id and userId after removing autoReloadJobId from the schema.


41-41: Policy change to "singleton" is appropriate.

The singleton policy ensures only one job instance per singleton key can be active, which aligns with the cleanup-based approach where prior jobs are cancelled before scheduling new ones.


207-217: LGTM!

The withCleanup: true flag correctly triggers cancellation of previously created jobs before scheduling the next check, replacing the previous prevAction approach. Based on learnings, jobs check autoReloadEnabled at execution time, so this cleanup approach avoids concurrency issues.

apps/api/src/billing/services/wallet-settings/wallet-settings.service.spec.ts (4)

54-57: LGTM!

Test correctly expects the full updatedSetting object now that autoReloadJobId is removed from the schema.


82-87: LGTM!

Assertion correctly simplified to only expect id and userId in the wallet setting passed to scheduleForWalletSetting.


192-194: LGTM!

Mock setup correctly reflects that cancel is no longer needed, as cancellation now happens lazily at job execution time. Based on learnings, this design avoids concurrency issues.


201-201: LGTM!

Simplification is correct - publicSetting now equals walletSetting since autoReloadJobId was removed from the schema.

apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.spec.ts (4)

12-55: LGTM!

Tests for scheduleImmediate properly cover the early return scenarios (no wallet setting, auto-reload disabled) and the happy path. Test follows coding guidelines with proper mock usage and descriptive test names.


57-158: LGTM!

Comprehensive test coverage for scheduleForWalletSetting including:

  • Basic scheduling without cleanup
  • Cancellation flow with withCleanup: true
  • startAfter option handling
  • Combined options
  • Error handling when job creation fails

Tests correctly verify the new API contract.


160-172: LGTM!

Test correctly verifies cancelCreatedByUserId calls jobQueueService.cancelCreatedBy with the appropriate name and singleton key.


174-188: LGTM!

Setup function follows coding guidelines: positioned at the bottom of the describe block, creates the service under test, returns all necessary mocks, and avoids shared state.

apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts (3)

18-24: LGTM!

The scheduleImmediate method is correctly simplified. It only schedules when the wallet setting exists and autoReloadEnabled is true, which is the expected behavior.


26-48: LGTM!

The scheduleForWalletSetting implementation correctly:

  1. Optionally cancels previous jobs when withCleanup is true
  2. Creates a new job with a consistent singleton key
  3. Logs and throws on job creation failure

The singleton key format ${WalletBalanceReloadCheck.name}.${userId} is consistent with usage in the tests and handler.


50-52: LGTM!

The cancelCreatedByUserId helper provides a clean API for cancelling jobs by user ID, using the same singleton key pattern.

@ygrishajev
Copy link
Contributor Author

@coderabbitai can you construct a diagram how the feature would work now

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 30, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the main service files to understand the flow
echo "=== wallet-settings.service.ts ==="
cat apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts

echo -e "\n=== wallet-reload-job.service.ts ==="
cat apps/api/src/billing/services/wallet-reload-job/wallet-reload-job.service.ts

echo -e "\n=== wallet-balance-reload-check.handler.ts ==="
cat apps/api/src/billing/services/wallet-balance-reload-check/wallet-balance-reload-check.handler.ts

Length of output: 14814


@ygrishajev Here's a comprehensive diagram showing how the auto-reload feature works after your changes:

sequenceDiagram
    participant User
    participant WalletSettingService
    participant WalletReloadJobService
    participant JobQueueService
    participant WalletBalanceReloadCheckHandler
    participant StripeService
    
    Note over User,StripeService: Flow 1: Enabling Auto-Reload
    User->>WalletSettingService: upsertWalletSetting({autoReloadEnabled: true})
    WalletSettingService->>WalletSettingService: #validate (check payment method exists)
    WalletSettingService->>WalletSettingService: #arrangeSchedule(prev, next)
    alt prev.autoReloadEnabled == false && next.autoReloadEnabled == true
        WalletSettingService->>WalletReloadJobService: scheduleForWalletSetting(next)
        WalletReloadJobService->>JobQueueService: enqueue(WalletBalanceReloadCheck)
        Note right of JobQueueService: singletonKey: WalletBalanceReloadCheck.${userId}
        JobQueueService-->>WalletReloadJobService: jobId
        WalletReloadJobService-->>WalletSettingService: jobId
    end
    WalletSettingService-->>User: WalletSettingOutput (no autoReloadJobId)
    
    Note over User,StripeService: Flow 2: Job Execution & Recurring Checks
    JobQueueService->>WalletBalanceReloadCheckHandler: handle(payload, job)
    Note right of WalletBalanceReloadCheckHandler: policy: "singleton"
    WalletBalanceReloadCheckHandler->>WalletBalanceReloadCheckHandler: #collectResources(payload)
    WalletBalanceReloadCheckHandler->>WalletBalanceReloadCheckHandler: Check autoReloadEnabled at runtime
    alt autoReloadEnabled == false
        WalletBalanceReloadCheckHandler-->>JobQueueService: Exit early (validation error)
    else autoReloadEnabled == true
        WalletBalanceReloadCheckHandler->>WalletBalanceReloadCheckHandler: #tryToReload(resources)
        alt balance < threshold
            WalletBalanceReloadCheckHandler->>StripeService: createPaymentIntent(amount)
            StripeService-->>WalletBalanceReloadCheckHandler: Payment created
        else balance >= threshold
            Note right of WalletBalanceReloadCheckHandler: Skip reload (sufficient balance)
        end
        WalletBalanceReloadCheckHandler->>WalletBalanceReloadCheckHandler: #scheduleNextCheck(resources)
        WalletBalanceReloadCheckHandler->>WalletReloadJobService: scheduleForWalletSetting(walletSetting, {withCleanup: true, startAfter: +24h})
        alt withCleanup == true
            WalletReloadJobService->>WalletReloadJobService: cancelCreatedByUserId(userId)
            WalletReloadJobService->>JobQueueService: cancelCreatedBy({name, singletonKey})
            Note right of JobQueueService: Cancels "created" jobs<br/>matching singleton key
            JobQueueService-->>WalletReloadJobService: void
        end
        WalletReloadJobService->>JobQueueService: enqueue(WalletBalanceReloadCheck, {singletonKey, startAfter})
        JobQueueService-->>WalletReloadJobService: new jobId
        WalletReloadJobService-->>WalletBalanceReloadCheckHandler: new jobId
    end
    
    Note over User,StripeService: Flow 3: Disabling Auto-Reload
    User->>WalletSettingService: upsertWalletSetting({autoReloadEnabled: false})
    WalletSettingService->>WalletSettingService: Update setting in DB
    WalletSettingService-->>User: Updated setting
    Note over JobQueueService,WalletBalanceReloadCheckHandler: Existing jobs NOT eagerly canceled
    JobQueueService->>WalletBalanceReloadCheckHandler: handle(payload, job)
    WalletBalanceReloadCheckHandler->>WalletBalanceReloadCheckHandler: Check autoReloadEnabled (now false)
    WalletBalanceReloadCheckHandler-->>JobQueueService: Exit early, no more jobs scheduled
Loading

Key Changes Illustrated:

  1. No Job ID Tracking: The autoReloadJobId field is removed from the database. Jobs are identified by their singleton key pattern: WalletBalanceReloadCheck.${userId}

  2. Singleton Pattern: Jobs use the "singleton" policy, preventing multiple concurrent jobs for the same user

  3. Runtime Flag Check: Jobs check autoReloadEnabled at execution time rather than being eagerly canceled. This aligns with the learning you provided earlier about avoiding concurrency issues

  4. Cleanup Mechanism: The withCleanup: true flag triggers cancelCreatedByUserId, which cancels any "created" (pending) jobs before scheduling the next one, preventing accumulation of stale jobs

  5. Passive Disable: When auto-reload is disabled, existing jobs naturally stop the cycle by detecting the flag change at runtime and exiting without scheduling the next check

This design is cleaner and avoids race conditions that could occur with eager cancellation while still preventing job accumulation through the cleanup mechanism.


🧠 Learnings used
Learnt from: ygrishajev
Repo: akash-network/console PR: 2422
File: apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts:124-128
Timestamp: 2025-12-29T13:20:36.482Z
Learning: In the wallet-settings service (apps/api/src/billing/services/wallet-settings/wallet-settings.service.ts), auto-reload jobs are not eagerly canceled when autoReloadEnabled is disabled. Instead, jobs check the autoReloadEnabled flag at execution time and complete early if disabled. This design avoids concurrency issues that could occur with eager cancellation.

baktun14
baktun14 previously approved these changes Dec 31, 2025
…h bulk cleanup

Change wallet balance reload check job management from tracking job IDs
in the database to using pg-boss singleton policy with bulk cleanup.

Key changes:
- Change handler policy from "short" to "singleton" to allow multiple
  scheduled jobs while maintaining single active execution
- Remove autoReloadJobId field from wallet-setting schema
- Remove job cancellation from wallet-settings service delete operation
- Add cancelCreatedBy method to JobQueueService for bulk cancellation
  of created jobs by singletonKey
- Update handler to use withCleanup flag when scheduling next check
- Simplify WalletReloadJobService by removing transaction wrapper and
  job ID tracking

This approach allows multiple scheduled jobs to exist while ensuring
only one active job runs at a time, with cleanup handled in bulk
rather than tracking individual job IDs.

Fixes race condition where REST handlers updating wallet settings could
mark an active job as cancelled even though it completes successfully,
causing state inconsistencies.

refs #1779
@ygrishajev ygrishajev merged commit 07e9be9 into main Jan 2, 2026
64 of 65 checks passed
@ygrishajev ygrishajev deleted the feature/billing branch January 2, 2026 09:56
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.

4 participants