Skip to content

feat: implement graceful shutdown (#585) and backer dashboard (#598) - #752

Open
teefeh-07 wants to merge 1 commit into
ritik4ever:mainfrom
teefeh-07:feature/graceful-shutdown-and-backer-dashboard
Open

feat: implement graceful shutdown (#585) and backer dashboard (#598)#752
teefeh-07 wants to merge 1 commit into
ritik4ever:mainfrom
teefeh-07:feature/graceful-shutdown-and-backer-dashboard

Conversation

@teefeh-07

@teefeh-07 teefeh-07 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

This Pull Request introduces a comprehensive Backer Dashboard and implements graceful shutdown procedures for the backend, resolving issues #598 and #585.

Closes


Tasks and Fixes Made

🛡️ Graceful Shutdown (Backend)

Ensured that the server shuts down smoothly without dropping active requests or corrupting the database.

  • Improved Signal Handling: Updated backend/src/index.ts to listen for SIGTERM and SIGINT signals, immediately stopping the acceptance of new connections.
  • Request Draining: Configured a maximum 30-second grace period timer to allow in-flight requests to complete before force-quitting the server.
  • Database Safety: Exported a closeDb() function in backend/src/services/db.ts to explicitly checkpoint the WAL (Write-Ahead Logging) and cleanly close the SQLite connection before exiting.
  • Shutdown Logging: Added precise logging to capture the reason for the shutdown and track exactly how long it took to drain connections (drainDurationMs).

📊 Backer Dashboard (Frontend & Backend)

Created a dedicated dashboard allowing backers to track their pledges, view investments, and claim refunds.

  • New API Endpoint: Added a GET /api/users/:address/pledges endpoint in backend/src/index.ts to fetch a user's entire pledge history.
  • Data Aggregation: Implemented getUserPledges in backend/src/services/campaignStore.ts to perform the necessary SQL joins between campaigns and pledges tables to aggregate a user's total active investments and claimable refunds.
  • Dashboard Component: Developed BackerDashboard.tsx in frontend/src/pages/ to display:
    • High-level metrics for Total Pledged, Total Refunded, and Net Invested.
    • A dynamic, tabular view of all backed campaigns, their current status, and the user's specific pledge amounts.
  • Quick Refund Action: Integrated a "Quick Refund" button directly on the dashboard that becomes active for eligible failed campaigns, allowing one-click soroban refund transactions.
  • Navigation & Routing: Hooked up the new dashboard to the /my-pledges route in main.tsx and added a convenient "My Pledges" link to the global App.tsx navigation header (visible when a wallet is connected).

How to Test

  1. Test the Dashboard:

    • Run the frontend and connect your wallet.
    • Click "My Pledges" in the top navigation bar.
    • Verify that your backed campaigns appear accurately with correct metric totals.
    • For a failed campaign where you have active pledges, try clicking the "Quick Refund" button.
  2. Test Graceful Shutdown:

    • Start the backend server (npm start or npm run dev).
    • While the server is running, send a simulated long-running request (or mock one).
    • Send a SIGTERM (e.g., kill <pid>) or SIGINT (Ctrl+C).
    • Check the logs to verify that server_shutting_down is emitted, wait for the connection to drain, and ensure server_closed is logged with the drain duration before the process cleanly exits.

    Closes [FEATURE] Add backer dashboard with pledging activity #598
    Closes [FEATURE] Add graceful shutdown handler to backend #585

Summary by CodeRabbit

  • New Features
    • Added a My Pledges dashboard for connected wallets.
    • View total pledged, refunded, and net amounts across campaigns.
    • Review pledge history and campaign statuses in one place.
    • Added quick refund actions when refunds are available.
    • Added navigation to the new dashboard from the app header.
  • Bug Fixes
    • Improved server shutdown handling to safely close connections and the database.

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

@teefeh-07 is attempting to deploy a commit to the ritik4ever's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@teefeh-07 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The backend adds contributor pledge aggregation, an API endpoint, and SQLite-aware graceful shutdown. The frontend adds wallet-gated routing, pledge metrics, campaign summaries, and quick refunds through a new backer dashboard.

Changes

Backer dashboard

Layer / File(s) Summary
Pledge summaries and API endpoint
backend/src/services/campaignStore.ts, backend/src/index.ts
Adds UserPledgeSummary, aggregates contributor pledges by campaign, and exposes validated pledge-summary retrieval.
Dashboard routing and data wiring
frontend/src/types/campaign.ts, frontend/src/services/api.ts, frontend/src/main.tsx, frontend/src/App.tsx
Adds the frontend summary type, API wrapper, /my-pledges route, and wallet-conditional navigation link.
Dashboard metrics and refunds
frontend/src/pages/BackerDashboard.tsx
Fetches wallet pledges, calculates totals, renders campaign statuses, and handles eligible quick refunds.

Graceful shutdown

Layer / File(s) Summary
Shutdown lifecycle and database cleanup
backend/src/index.ts, backend/src/services/db.ts
Logs shutdown details, extends draining to 30 seconds, checkpoints and closes SQLite, and handles close failures.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BackerDashboard
  participant BackendAPI
  participant campaignStore
  BackerDashboard->>BackendAPI: Request wallet pledge summaries
  BackendAPI->>campaignStore: getUserPledges(address)
  campaignStore-->>BackendAPI: Campaign pledge summaries
  BackendAPI-->>BackerDashboard: Return summary data
Loading
sequenceDiagram
  participant ShutdownHandler
  participant HTTPServer
  participant SQLite
  ShutdownHandler->>HTTPServer: Stop accepting connections and drain requests
  HTTPServer-->>ShutdownHandler: server.close completes
  ShutdownHandler->>SQLite: WAL checkpoint and close connection
  SQLite-->>ShutdownHandler: Close result
Loading

Possibly related issues

  • ritik4ever/stellar-stream issue 732 — Requests a similar 30-second graceful shutdown flow with shutdown logging and SQLite cleanup.

Possibly related PRs

Suggested reviewers: ritik4ever

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: graceful shutdown and the backer dashboard.
Linked Issues check ✅ Passed The shutdown handler and backer dashboard requirements in #585 and #598 appear implemented, including wallet gating, refunds, WAL checkpointing, and graceful drain.
Out of Scope Changes check ✅ Passed All changes align with the shutdown and backer-dashboard objectives; no unrelated modifications are evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 2
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feature/graceful-shutdown-and-backer-dashboard
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

backend/src/index.ts

Parsing error: Cannot read file '/tsconfig.eslint.json'.

backend/src/services/campaignStore.ts

Parsing error: Cannot read file '/tsconfig.eslint.json'.

backend/src/services/db.ts

Parsing error: Cannot read file '/tsconfig.eslint.json'.

  • 5 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/src/index.ts (1)

869-878: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Forced-shutdown path skips the WAL checkpoint/DB close entirely.

When the grace-period timer fires (Line 871-878), the process calls process.exit(1) immediately without ever invoking closeDb(). This is precisely the path exercised when in-flight requests hang past 30s — i.e., the scenario most likely to trigger a forced exit — yet it bypasses the WAL checkpoint that the rest of this PR's objective calls out ("Checkpoints WAL and closes the SQLite connection via closeDb()"). SQLite's WAL mode tolerates ungraceful termination, but skipping the checkpoint here means the WAL file is never truncated on this path, which can leave WAL growth uncontrolled if forced shutdowns are frequent (e.g., during rolling restarts with slow drains).

Consider attempting a best-effort closeDb() call inside the timeout branch too (it should be fast since it's just a pragma + close):

🛡️ Suggested fix
     const gracePeriodTimer = setTimeout(() => {
       logError(
         new Error('Graceful shutdown timeout exceeded'),
         { event: 'graceful_shutdown_timeout', gracePeriodSeconds },
         config.logLevel,
       );
+      import('./services/db').then(({ closeDb }) => closeDb()).catch(() => {});
       process.exit(1);
     }, gracePeriodSeconds * 1000);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/index.ts` around lines 869 - 878, Update the graceful-shutdown
timeout callback to make a best-effort asynchronous call to closeDb() before
forcing process.exit(1), ensuring the WAL checkpoint and SQLite close are
attempted when the grace period expires. Preserve the existing timeout logging
and forced-exit behavior, and handle any closeDb failure without preventing the
exit.
🧹 Nitpick comments (3)
frontend/src/pages/BackerDashboard.tsx (2)

170-170: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the backend-computed canRefund flag instead of re-deriving it from status.

summary.campaign.progress.canRefund already encodes claimedAt === undefined && deadlineReached && pledgedAmount < targetAmount, which is exactly the condition being reconstructed here via status === 'failed'. Deriving eligibility independently duplicates business logic that could drift if calculateProgress's status/canRefund relationship ever changes.

♻️ Suggested fix
-                    const canRefund = summary.campaign.progress.status === 'failed' && summary.totalPledged > 0;
+                    const canRefund = summary.campaign.progress.canRefund && summary.totalPledged > 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/BackerDashboard.tsx` at line 170, Update the canRefund
assignment in BackerDashboard to use the backend-provided
summary.campaign.progress.canRefund value directly, removing the local status
and totalPledged derivation while preserving the existing refund flow.

9-9: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

ErrorBoundary is imported but never wraps any content.

App.tsx wraps comparable panels (e.g. CampaignDetailPanel, CampaignsTable) in ErrorBoundary to contain render errors. This dashboard imports ErrorBoundary but the pledges table/section isn't wrapped in it, so a render error here would crash the whole page instead of degrading gracefully.

Also applies to: 149-204

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/BackerDashboard.tsx` at line 9, Wrap the pledges
table/section rendered in BackerDashboard with the imported ErrorBoundary,
matching the existing panel-wrapping pattern used in App.tsx. Ensure the
relevant content in the render block around the pledges section is enclosed so
render errors are contained, and remove the import only if no content remains to
wrap.
backend/src/services/campaignStore.ts (1)

1220-1261: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

N+1 query pattern in getUserPledges.

For each distinct campaign the function issues a dedicated pledges query (Line 1236-1240) plus calculateProgress (Line 1234), which itself queries getActivePledgeCount per campaign. For a contributor with N backed campaigns this results in roughly 1 + 2N queries per dashboard load instead of a small constant number.

♻️ Suggested consolidation
-  const campaignRows = db.prepare(`
-    SELECT DISTINCT c.* 
-    FROM campaigns c
-    JOIN pledges p ON p.campaign_id = c.id
-    WHERE p.contributor = ? AND c.deleted_at IS NULL
-  `).all(contributor) as CampaignRow[];
-
-  const summaries: UserPledgeSummary[] = [];
-
-  for (const row of campaignRows) {
-    const campaign = rowToCampaign(row);
-    const progress = calculateProgress(campaign);
-
-    const pledgeRows = db.prepare(`
-      SELECT * FROM pledges 
-      WHERE campaign_id = ? AND contributor = ?
-      ORDER BY created_at DESC
-    `).all(campaign.id, contributor) as PledgeRow[];
-
-    const pledges: PledgeRecord[] = pledgeRows.map(rowToPledge);
+  const allPledgeRows = db.prepare(`
+    SELECT * FROM pledges WHERE contributor = ? ORDER BY created_at DESC
+  `).all(contributor) as PledgeRow[];
+
+  const pledgesByCampaign = new Map<string, PledgeRow[]>();
+  for (const row of allPledgeRows) {
+    const list = pledgesByCampaign.get(row.campaign_id) ?? [];
+    list.push(row);
+    pledgesByCampaign.set(row.campaign_id, list);
+  }
+
+  const campaignRows = db.prepare(`
+    SELECT * FROM campaigns WHERE id IN (${[...pledgesByCampaign.keys()].map(() => '?').join(',')}) AND deleted_at IS NULL
+  `).all(...pledgesByCampaign.keys()) as CampaignRow[];
+
+  const summaries: UserPledgeSummary[] = [];
+
+  for (const row of campaignRows) {
+    const campaign = rowToCampaign(row);
+    const progress = calculateProgress(campaign);
+    const pledges: PledgeRecord[] = (pledgesByCampaign.get(campaign.id) ?? []).map(rowToPledge);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/campaignStore.ts` around lines 1220 - 1261, Consolidate
the per-campaign work in getUserPledges to avoid the N+1 query pattern: fetch
the contributor’s campaign and pledge data in a bounded number of queries, and
derive each campaign’s progress from the already-loaded pledge data instead of
calling calculateProgress for every campaign. Preserve the existing totals,
refund classification, pledge ordering, and deleted-campaign filtering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/src/index.ts`:
- Around line 634-642: Update the `/api/users/:address/pledges` handler to
validate `address` with the existing `isValidStellarPublicKey` helper in
addition to the current type/presence check. Reject invalid keys with the
established 400 `AppError` response before calling `getUserPledges`.

In `@backend/src/services/campaignStore.ts`:
- Around line 1244-1253: The pledge totals in
backend/src/services/campaignStore.ts:1244-1253 must make totalPledged gross by
adding every pledge amount unconditionally, while totalRefunded only accumulates
pledges with refundedAt; preserve the resulting totalPledged - totalRefunded
calculation. No code change is needed in
frontend/src/pages/BackerDashboard.tsx:55-67; verify its net calculation and
per-campaign Total Pledged/Total Refunded columns remain correct with gross
totals.

In `@backend/src/services/db.ts`:
- Around line 56-60: Update closeDb() so wal_checkpoint(TRUNCATE) failures are
captured and surfaced after the database close operation completes. Ensure the
database is always closed, then rethrow or otherwise propagate the checkpoint
error so shutdown does not report success when checkpointing fails.

In `@frontend/src/pages/BackerDashboard.tsx`:
- Around line 99-104: Update the BackerDashboard wallet connection flow around
WalletWidget and reuse App.tsx’s config-driven network passphrase, such as
appConfig?.networkPassphrase ?? DEFAULT_NETWORK_PASSPHRASE, instead of the
hardcoded testnet value. Route the connection through the existing
handleConnectWallet behavior or equivalent logic so the promise is awaited and
failures update loading/error state and display the established toast feedback.

---

Outside diff comments:
In `@backend/src/index.ts`:
- Around line 869-878: Update the graceful-shutdown timeout callback to make a
best-effort asynchronous call to closeDb() before forcing process.exit(1),
ensuring the WAL checkpoint and SQLite close are attempted when the grace period
expires. Preserve the existing timeout logging and forced-exit behavior, and
handle any closeDb failure without preventing the exit.

---

Nitpick comments:
In `@backend/src/services/campaignStore.ts`:
- Around line 1220-1261: Consolidate the per-campaign work in getUserPledges to
avoid the N+1 query pattern: fetch the contributor’s campaign and pledge data in
a bounded number of queries, and derive each campaign’s progress from the
already-loaded pledge data instead of calling calculateProgress for every
campaign. Preserve the existing totals, refund classification, pledge ordering,
and deleted-campaign filtering.

In `@frontend/src/pages/BackerDashboard.tsx`:
- Line 170: Update the canRefund assignment in BackerDashboard to use the
backend-provided summary.campaign.progress.canRefund value directly, removing
the local status and totalPledged derivation while preserving the existing
refund flow.
- Line 9: Wrap the pledges table/section rendered in BackerDashboard with the
imported ErrorBoundary, matching the existing panel-wrapping pattern used in
App.tsx. Ensure the relevant content in the render block around the pledges
section is enclosed so render errors are contained, and remove the import only
if no content remains to wrap.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 74f37e08-9444-40a1-8a20-876ada08cf86

📥 Commits

Reviewing files that changed from the base of the PR and between 3e2dd95 and 8f5e554.

📒 Files selected for processing (8)
  • backend/src/index.ts
  • backend/src/services/campaignStore.ts
  • backend/src/services/db.ts
  • frontend/src/App.tsx
  • frontend/src/main.tsx
  • frontend/src/pages/BackerDashboard.tsx
  • frontend/src/services/api.ts
  • frontend/src/types/campaign.ts

Comment thread backend/src/index.ts
Comment on lines +634 to +642
app.get('/api/users/:address/pledges', (req: Request, res: Response) => {
const address = req.params.address;
if (!address || typeof address !== 'string') {
throw new AppError('Invalid address parameter.', 400, 'BAD_REQUEST');
}

const summaries = getUserPledges(address);
res.json({ data: summaries });
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether other routes/handlers validate Stellar address format
rg -nP -C3 '(isValidStellarAddress|StrKey\.isValidEd25519PublicKey|address.*regex|G\[A-Z2-7\]\{55\})' backend/src

Repository: ritik4ever/stellar-goal-vault

Length of output: 2394


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== stellarAddress.ts =="
cat -n backend/src/validation/stellarAddress.ts

echo
echo "== schemas.ts =="
cat -n backend/src/validation/schemas.ts

echo
echo "== openapi.ts =="
cat -n backend/src/openapi.ts

echo
echo "== usage of VALIDATORS/via import paths =="
rg -n "from ['\"](\.\./)?validation/(stellarAddress|schemas)|from ['\"]\.\./validation|STELLAR_ACCOUNT_REGEX|isValidStellarPublicKey|stellarAddressSchema" backend/src

Repository: ritik4ever/stellar-goal-vault

Length of output: 50385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== validateStellarAddress export status =="
if [ -f backend/src/services/checkpoints/checkpoints.ts ]; then
  rg -n -C3 "validateStellarAddress|isValidStellar" backend/src/services/checkpoints/checkpoints.ts
else
  echo "checkpoints.ts not found"
fi

echo
echo "== index route context =="
sed -n '600,660p;850,905p' backend/src/index.ts | cat -n

echo
echo "== stellarAddress schema exports/usages =="
rg -n "export (const|function|let)|isValidStellarPublicKey|stellarAccount|stellarAccountIdSchema" backend/src/validation backend/src -g '!index.ts' -g '!openapi.ts' -g '!schemas.ts' -g '!stellarAddress.ts'

Repository: ritik4ever/stellar-goal-vault

Length of output: 14173


Validate /api/users/:address/pledges with the Stellar public-key validator.

This endpoint only checks that address is a non-empty string before calling getUserPledges. Use the existing isValidStellarPublicKey validation logic here as well so malformed G... inputs fail fast with a consistent 400 response instead of reaching store/query logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/index.ts` around lines 634 - 642, Update the
`/api/users/:address/pledges` handler to validate `address` with the existing
`isValidStellarPublicKey` helper in addition to the current type/presence check.
Reject invalid keys with the established 400 `AppError` response before calling
`getUserPledges`.

Comment on lines +1244 to +1253
let totalPledged = 0;
let totalRefunded = 0;

for (const pledge of pledges) {
if (pledge.refundedAt) {
totalRefunded += pledge.amount;
} else {
totalPledged += pledge.amount;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Net Invested can go negative — totalPledged excludes refunded amounts, so the frontend double-subtracts refunds. getUserPledges accumulates totalPledged only for pledges without refundedAt, meaning refunded contributions are dropped from totalPledged entirely rather than counted then subtracted back out. The dashboard then computes net = totalPledged - totalRefunded, which subtracts refunds a second time. Concretely: a $50 pledge that gets fully refunded yields totalPledged=0, totalRefunded=50, so net = -50 — a backer who was fully refunded appears to have negative net investment instead of $0.

  • backend/src/services/campaignStore.ts#L1244-L1253: make totalPledged the gross amount (sum every pledge unconditionally), and only add to totalRefunded when refundedAt is set, so totalPledged - totalRefunded yields the correct currently-active amount:
     for (const pledge of pledges) {
-      if (pledge.refundedAt) {
-        totalRefunded += pledge.amount;
-      } else {
-        totalPledged += pledge.amount;
-      }
+      totalPledged += pledge.amount;
+      if (pledge.refundedAt) {
+        totalRefunded += pledge.amount;
+      }
     }
  • frontend/src/pages/BackerDashboard.tsx#L55-L67: no code change needed once totalPledged is gross; net = round(pledged - refunded) will then be correct. Verify the per-campaign table columns (Total Pledged / Total Refunded, Lines 181-182) still read sensibly with gross totalPledged.
📝 Committable suggestion

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

Suggested change
let totalPledged = 0;
let totalRefunded = 0;
for (const pledge of pledges) {
if (pledge.refundedAt) {
totalRefunded += pledge.amount;
} else {
totalPledged += pledge.amount;
}
}
let totalPledged = 0;
let totalRefunded = 0;
for (const pledge of pledges) {
totalPledged += pledge.amount;
if (pledge.refundedAt) {
totalRefunded += pledge.amount;
}
}
📍 Affects 2 files
  • backend/src/services/campaignStore.ts#L1244-L1253 (this comment)
  • frontend/src/pages/BackerDashboard.tsx#L55-L67
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/campaignStore.ts` around lines 1244 - 1253, The pledge
totals in backend/src/services/campaignStore.ts:1244-1253 must make totalPledged
gross by adding every pledge amount unconditionally, while totalRefunded only
accumulates pledges with refundedAt; preserve the resulting totalPledged -
totalRefunded calculation. No code change is needed in
frontend/src/pages/BackerDashboard.tsx:55-67; verify its net calculation and
per-campaign Total Pledged/Total Refunded columns remain correct with gross
totals.

Comment on lines +56 to +60
try {
db.pragma('wal_checkpoint(TRUNCATE)');
} catch {
// Ignore if checkpoint fails during shutdown
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "closeDb|wal_checkpoint|gracefulShutdown" backend --glob '*test*' --glob '*spec*'

Repository: ritik4ever/stellar-goal-vault

Length of output: 167


🏁 Script executed:

#!/bin/bash
set -e
echo "Files:"
git ls-files backend/src/services backend/src | sed -n '1,120p'
echo
echo "db.ts:"
cat -n backend/src/services/db.ts
echo
echo "index.ts shutdown references:"
rg -n "closeDb|process.exit|shutdown|logger|close" backend/src/index.ts backend/src || true

Repository: ritik4ever/stellar-goal-vault

Length of output: 12356


🏁 Script executed:

#!/bin/bash
set -e
echo "index.ts shutdown block:"
cat -n backend/src/index.ts | sed -n '855,896p'
echo
echo "db.wal.test.ts:"
cat -n backend/src/services/db.wal.test.ts
echo
echo "Tests mentioning db close/wal/no tests with those refs:"
rg -n "closeDb|wal_checkpoint|db_close_error|process.exit" backend --glob '*test*'

Repository: ritik4ever/stellar-goal-vault

Length of output: 3626


Propagate WAL checkpoint failures instead of swallowing them.

closeDb() currently returns normally when wal_checkpoint(TRUNCATE) throws, so graceful shutdown still calls process.exit(0) despite the requested checkpoint not completing. Close the database in all cases, then rethrow or otherwise surface checkpoint failures so shutdown failure is observable rather than suppressed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/db.ts` around lines 56 - 60, Update closeDb() so
wal_checkpoint(TRUNCATE) failures are captured and surfaced after the database
close operation completes. Ensure the database is always closed, then rethrow or
otherwise propagate the checkpoint error so shutdown does not report success
when checkpointing fails.

Comment on lines +99 to +104
<WalletWidget
status={freighter.status}
publicKey={freighter.publicKey}
error={freighter.error}
onConnect={() => freighter.connect("Test SDF Network ; September 2015")}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Hardcoded testnet passphrase diverges from the app-wide config-driven connect flow.

onConnect={() => freighter.connect("Test SDF Network ; September 2015")} hardcodes the testnet passphrase, while App.tsx's handleConnectWallet uses appConfig?.networkPassphrase ?? DEFAULT_NETWORK_PASSPHRASE fetched from the backend config. If the deployment is configured for a different network (e.g. mainnet), connecting from this dashboard will request the wrong network, causing wallet-connect mismatches or failures. The connect call also isn't awaited/error-handled the way handleConnectWallet is (no loading/error state, no toast on failure).

🛡️ Suggested fix
+import { getAppConfig } from "../services/api";
+import { DEFAULT_NETWORK_PASSPHRASE } from "../constants";
+...
+  const [appConfig, setAppConfig] = useState<AppConfig | null>(null);
+  useEffect(() => {
+    getAppConfig().then(setAppConfig).catch(() => {});
+  }, []);
+  async function handleConnectWallet() {
+    const networkPassphrase = appConfig?.networkPassphrase ?? DEFAULT_NETWORK_PASSPHRASE;
+    try {
+      await freighter.connect(networkPassphrase);
+    } catch (error) {
+      addToast(error instanceof Error ? error.message : "Failed to connect wallet", "error");
+    }
+  }
...
-              onConnect={() => freighter.connect("Test SDF Network ; September 2015")}
+              onConnect={() => { void handleConnectWallet(); }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/BackerDashboard.tsx` around lines 99 - 104, Update the
BackerDashboard wallet connection flow around WalletWidget and reuse App.tsx’s
config-driven network passphrase, such as appConfig?.networkPassphrase ??
DEFAULT_NETWORK_PASSPHRASE, instead of the hardcoded testnet value. Route the
connection through the existing handleConnectWallet behavior or equivalent logic
so the promise is awaited and failures update loading/error state and display
the established toast feedback.

@ritik4ever

Copy link
Copy Markdown
Owner

Hi @teefeh-07,

This PR could not be merged because it has merge conflicts with the target branch.

Please resolve the merge conflicts, push the updated changes, and the PR can be reviewed and merged.

Thank you!

1 similar comment
@ritik4ever

Copy link
Copy Markdown
Owner

Hi @teefeh-07,

This PR could not be merged because it has merge conflicts with the target branch.

Please resolve the merge conflicts, push the updated changes, and the PR can be reviewed and merged.

Thank you!

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.

[FEATURE] Add backer dashboard with pledging activity [FEATURE] Add graceful shutdown handler to backend

2 participants