Skip to content

updated project files - #517

Merged
ritik4ever merged 2 commits into
ritik4ever:mainfrom
Toyinoje:main
Jun 30, 2026
Merged

updated project files #517
ritik4ever merged 2 commits into
ritik4ever:mainfrom
Toyinoje:main

Conversation

@Toyinoje

@Toyinoje Toyinoje commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

changes made
closed #238

Pull Request

What Changed

A clear and concise description of what this PR changes and why.

Related Issues

  • Closes #
  • Fixes #

Testing Done

Describe the tests you ran and how to reproduce them.

Security Review

If this PR touches API endpoints, authentication, database queries, or contract code, check the applicable items from SECURITY_CHECKLIST.md. Paste relevant items below:

- [ ] A01-1: Public endpoints correctly excluded from auth
- [ ] A03-1: SQL uses parameterised statements
- [ ] A03-2: User input validated with Zod schema
- [ ] A05-1: CORS scoped to known origins
- [ ] A09-1: API requests logged with request ID
- [ ] Dependency changes reviewed for vulnerabilities

Checklist

  • Code follows the existing style and patterns of the project
  • Tests added or updated to cover the change
  • All tests pass locally (npm test / cargo test)
  • UI changes include screenshots or a screen recording
  • PR title is descriptive and references the issue number
  • Security checklist items reviewed (if applicable)

Screenshots (if applicable)

Add screenshots or recordings for visual changes.

Summary by CodeRabbit

  • Bug Fixes

    • Reconcile requests now return 200 when the pledge already exists, and 201 only for newly processed pledges.
    • Duplicate pledge data is cleaned up during database updates, helping keep campaign totals accurate.
    • Campaign progress now reflects the latest reconciled pledge data consistently.
  • Tests

    • Updated reconciliation and campaign tests to verify repeated requests stay idempotent and report existing pledges correctly.

@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

@Toyinoje 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 Jun 29, 2026

Copy link
Copy Markdown

@Toyinoje 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 Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ritik4ever, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0732832a-0f0f-4b55-ac72-8efcf6e2a91d

📥 Commits

Reviewing files that changed from the base of the PR and between 576770b and 03dcf18.

📒 Files selected for processing (6)
  • backend/src/index.ts
  • backend/src/services/__tests__/mutation.test.ts
  • backend/src/services/campaignStore.test.ts
  • backend/src/services/campaignStore.ts
  • backend/src/services/db.ts
  • backend/tests/integration.test.ts
📝 Walkthrough

Walkthrough

reconcileOnChainPledge is refactored to use INSERT OR IGNORE for deduplication and now returns a ReconcileOnChainPledgeResult object containing the campaign and an existing boolean. The reconcile HTTP endpoint returns 200 for duplicate calls and 201 for new ones. A DB migration removes existing duplicate pledge rows and recalculates campaign pledged amounts. Tests are updated to match the new return shape and HTTP status.

Changes

Pledge Reconcile Idempotency

Layer / File(s) Summary
ReconcileOnChainPledgeResult type and INSERT OR IGNORE deduplication
backend/src/services/campaignStore.ts
Exports ReconcileOnChainPledgeResult interface and getPledgeByTransactionHash; refactors reconcileOnChainPledge to insert via INSERT OR IGNORE, detect duplicate transaction hashes, validate campaign ownership on conflict, and return { campaign, existing }.
DB migration: deduplicate pledges and recalculate pledged_amount
backend/src/services/db.ts
Adds migration steps to delete duplicate pledges rows by transaction_hash (keeping minimum id), then recalculates campaigns.pledged_amount from non-refunded pledges.
Reconcile endpoint: conditional 200 vs 201
backend/src/index.ts
The POST handler captures the reconcileOnChainPledge result and returns 200 when result.existing is true, 201 otherwise; computes progress from result.campaign.
Test updates
backend/src/services/__tests__/mutation.test.ts, backend/src/services/campaignStore.test.ts, backend/tests/integration.test.ts
Idempotency tests assert on secondResult.campaign.pledgedAmount and secondResult.existing === true; integration test expects HTTP 200 for the second reconcile call.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • ritik4ever/stellar-goal-vault#456: Updates the same integration test for the pledge reconcile idempotency flow, directly overlapping with the HTTP status change from 201 to 200 for duplicate reconcile calls.

Poem

A bunny once pledged to the stars up high,
But sent the same hash twice — oh my!
Now INSERT OR IGNORE saves the day,
Returns a neat 200 "already done, hooray!"
No double counts, the vault stays true,
🐇✨ Idempotent hops, through and through!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR handles duplicates and existing pledges, but the summary does not show a UNIQUE transactionHash index being added. Add the UNIQUE transactionHash index or constraint in the pledges migration and verify it applies to existing data.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too generic to describe the idempotent pledge reconciliation changes. Use a concise title like 'Make pledge reconciliation idempotent by transaction hash'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Out of Scope Changes check ✅ Passed No clear unrelated changes are indicated; the edits all support idempotent pledge reconciliation and migration cleanup.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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: 3

🧹 Nitpick comments (1)
backend/src/services/campaignStore.ts (1)

847-882: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Handle only the transaction-hash conflict
INSERT OR IGNORE also hides NOT NULL violations on pledges (the FK still fails normally), so the changes === 0 path can misclassify bad pledge data as DB_CONFLICT. Use ON CONFLICT(transaction_hash) DO NOTHING or catch the unique-constraint error instead.

🤖 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 847 - 882, The pledge
insert logic in campaignStore’s transaction currently uses INSERT OR IGNORE,
which can mask non-transaction-hash validation issues and incorrectly fall
through to DB_CONFLICT. Update the insertedNewPledge flow to handle only the
transaction_hash uniqueness case in the db.transaction block, either by
switching the INSERT in that pledge insertion path to ON
CONFLICT(transaction_hash) DO NOTHING or by catching the unique-constraint error
explicitly, and keep the existing getPledgeByTransactionHash / duplicatePledge
campaignId conflict handling for true transaction-hash collisions.
🤖 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/services/db.ts`:
- Around line 163-185: The cleanup and campaign recalculation in the migration
logic are running as separate exec calls, so a failure can leave the database
partially migrated. Update the migration path in initDb()/migrate(db) so the
DELETE FROM pledges, UPDATE campaigns, and the unique-index creation all execute
inside a single transaction, using the existing database object and the
migration flow in db.ts to keep the whole change atomic.
- Around line 174-185: The migration in db.ts only deduplicates pledges and
updates campaigns, but leaves duplicate campaign_events rows with the same
blockchain_metadata.txHash intact. Update the migration to also clean duplicates
in campaign_events using the relevant txHash field, or explicitly document in
the migration and history flow that duplicate pledge history is intentionally
retained. Focus on the existing migration block around the campaigns update and
the campaign_events history path that feeds the endpoint.
- Around line 163-172: The deduplication query in the pledges cleanup is
preserving the wrong row because it always uses MIN(id), which can keep a
refunded legacy record instead of the active pledge. Update the deletion logic
in the database cleanup flow that runs the DELETE FROM pledges statement so the
survivor for each transaction_hash group is chosen from a non-refunded row when
any exist, and only falls back to a refunded row if all rows for that hash are
refunded. Make the change in the db service around the pledge deduplication
query so rebuilt pledged totals keep valid funds.

---

Nitpick comments:
In `@backend/src/services/campaignStore.ts`:
- Around line 847-882: The pledge insert logic in campaignStore’s transaction
currently uses INSERT OR IGNORE, which can mask non-transaction-hash validation
issues and incorrectly fall through to DB_CONFLICT. Update the insertedNewPledge
flow to handle only the transaction_hash uniqueness case in the db.transaction
block, either by switching the INSERT in that pledge insertion path to ON
CONFLICT(transaction_hash) DO NOTHING or by catching the unique-constraint error
explicitly, and keep the existing getPledgeByTransactionHash / duplicatePledge
campaignId conflict handling for true transaction-hash collisions.
🪄 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

Run ID: 89207b55-8e3d-4afc-9070-51ac013706a2

📥 Commits

Reviewing files that changed from the base of the PR and between f807502 and 576770b.

📒 Files selected for processing (6)
  • backend/src/index.ts
  • backend/src/services/__tests__/mutation.test.ts
  • backend/src/services/campaignStore.test.ts
  • backend/src/services/campaignStore.ts
  • backend/src/services/db.ts
  • backend/tests/integration.test.ts

Comment on lines +163 to +172
database.exec(`
DELETE FROM pledges
WHERE id NOT IN (
SELECT MIN(id)
FROM pledges
WHERE transaction_hash IS NOT NULL
GROUP BY transaction_hash
)
AND transaction_hash IS NOT NULL;
`);

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the migration and nearby transaction usage.
sed -n '130,220p' backend/src/services/db.ts

echo
echo '--- related transaction_hash usage ---'
rg -n "transaction_hash|txHash|pledged_amount|refund" backend/src -g '!**/node_modules/**'

Repository: ritik4ever/stellar-goal-vault

Length of output: 22122


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- db.ts around migration wrapper ---'
sed -n '1,140p' backend/src/services/db.ts
echo
sed -n '140,230p' backend/src/services/db.ts

echo
echo '--- campaignStore transaction_hash insert/update paths ---'
sed -n '700,780p' backend/src/services/campaignStore.ts
echo
sed -n '820,920p' backend/src/services/campaignStore.ts

Repository: ritik4ever/stellar-goal-vault

Length of output: 11861


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "getPledgeByTransactionHash|transactionHash already belongs|INSERT OR IGNORE INTO pledges|transaction_hash =" backend/src/services/campaignStore.ts
echo
sed -n '180,240p' backend/src/services/campaignStore.ts
echo
sed -n '780,860p' backend/src/services/campaignStore.ts

Repository: ritik4ever/stellar-goal-vault

Length of output: 5440


Keep the active pledge when deduplicating transaction_hash.
MIN(id) can preserve a refunded legacy row and delete the active pledge, which makes the pledged total rebuild drop valid funds. Pick a non-refunded row as the survivor when a hash group mixes refund states.

🤖 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 163 - 172, The deduplication query
in the pledges cleanup is preserving the wrong row because it always uses
MIN(id), which can keep a refunded legacy record instead of the active pledge.
Update the deletion logic in the database cleanup flow that runs the DELETE FROM
pledges statement so the survivor for each transaction_hash group is chosen from
a non-refunded row when any exist, and only falls back to a refunded row if all
rows for that hash are refunded. Make the change in the db service around the
pledge deduplication query so rebuilt pledged totals keep valid funds.

Comment on lines +163 to +185
database.exec(`
DELETE FROM pledges
WHERE id NOT IN (
SELECT MIN(id)
FROM pledges
WHERE transaction_hash IS NOT NULL
GROUP BY transaction_hash
)
AND transaction_hash IS NOT NULL;
`);

database.exec(`
UPDATE campaigns
SET pledged_amount = COALESCE(
(
SELECT SUM(amount)
FROM pledges
WHERE pledges.campaign_id = campaigns.id
AND refunded_at IS NULL
),
0
);
`);

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
# Inspect current migration transaction patterns without executing repository code.
rg -n -C4 'function migrate|database\.transaction|BEGIN|COMMIT|DELETE FROM pledges|CREATE UNIQUE INDEX IF NOT EXISTS idx_pledges_transaction_hash' backend/src/services/db.ts

Repository: ritik4ever/stellar-goal-vault

Length of output: 902


🏁 Script executed:

#!/bin/bash
# Inspect the migrate() function and its call sites for any surrounding transaction.
sed -n '1,260p' backend/src/services/db.ts
printf '\n--- call sites ---\n'
rg -n -C3 '\bmigrate\(' backend/src

Repository: ritik4ever/stellar-goal-vault

Length of output: 7242


Wrap the cleanup, recalculation, and unique-index creation in one migration transaction. initDb() calls migrate(db) directly, so these database.exec(...) steps run separately; a failure between them can leave the database half-migrated.

🤖 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 163 - 185, The cleanup and campaign
recalculation in the migration logic are running as separate exec calls, so a
failure can leave the database partially migrated. Update the migration path in
initDb()/migrate(db) so the DELETE FROM pledges, UPDATE campaigns, and the
unique-index creation all execute inside a single transaction, using the
existing database object and the migration flow in db.ts to keep the whole
change atomic.

Comment on lines +174 to +185
database.exec(`
UPDATE campaigns
SET pledged_amount = COALESCE(
(
SELECT SUM(amount)
FROM pledges
WHERE pledges.campaign_id = campaigns.id
AND refunded_at IS NULL
),
0
);
`);

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find event consumers/projections that may still observe duplicate pledged events.
rg -n -C4 "campaign_events|blockchain_metadata|txHash|event_type.*pledged|recordEvent" backend/src backend/tests

Repository: ritik4ever/stellar-goal-vault

Length of output: 50385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== eventHistory outline ==\n'
ast-grep outline backend/src/services/eventHistory.ts --view expanded || true

printf '\n== db outline ==\n'
ast-grep outline backend/src/services/db.ts --view expanded || true

printf '\n== eventIndexer outline ==\n'
ast-grep outline backend/src/services/eventIndexer.ts --view expanded || true

printf '\n== campaign history consumers ==\n'
rg -n -C3 "getCampaignHistory|getEventByTxHash|getEventsBySource|getEventsByLedger|campaign_events" backend/src backend/tests | head -n 250

Repository: ritik4ever/stellar-goal-vault

Length of output: 20337


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== backend/src/services/db.ts (160-220) ==\n'
sed -n '160,220p' backend/src/services/db.ts | cat -n

printf '\n== backend/src/services/eventIndexer.ts (110-145, 250-330) ==\n'
sed -n '110,145p' backend/src/services/eventIndexer.ts | cat -n
printf '\n---\n'
sed -n '250,330p' backend/src/services/eventIndexer.ts | cat -n

printf '\n== backend/src/services/eventHistory.ts (60-170) ==\n'
sed -n '60,170p' backend/src/services/eventHistory.ts | cat -n

printf '\n== campaignStore pledge/reconcile paths ==\n'
rg -n -C3 "recordEvent\\(|reconcileOnChainPledge|addPledge\\(|existing: true|getCampaignHistory\\(campaign.id\\).*pledged" backend/src/services/campaignStore.ts backend/src/services/campaignStore.test.ts backend/src/services/eventIndexer.ts

Repository: ritik4ever/stellar-goal-vault

Length of output: 21210


Deduplicate campaign_events too, or document that duplicate pledge history is retained
This migration only cleans pledges; any existing campaign_events rows with the same blockchain_metadata.txHash stay in place. Since the history endpoint reads campaign_events directly, duplicate pledge entries will still surface in user-facing history.

🤖 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 174 - 185, The migration in db.ts
only deduplicates pledges and updates campaigns, but leaves duplicate
campaign_events rows with the same blockchain_metadata.txHash intact. Update the
migration to also clean duplicates in campaign_events using the relevant txHash
field, or explicitly document in the migration and history flow that duplicate
pledge history is intentionally retained. Focus on the existing migration block
around the campaigns update and the campaign_events history path that feeds the
endpoint.

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.

Implement pledge idempotency using transaction hash deduplication

2 participants