Skip to content

fix(db): guard against duplicate contribution in a cycle - #392

Closed
raskenite wants to merge 2 commits into
Rotula-Labs:mainfrom
raskenite:fix/issue-247-fix-db-guard-against-a-duplicate-contribution
Closed

raskenite wants to merge 2 commits into
Rotula-Labs:mainfrom
raskenite:fix/issue-247-fix-db-guard-against-a-duplicate-contribution

Conversation

@raskenite

Copy link
Copy Markdown

Overview

This PR adds a database-level uniqueness constraint on contributions per member and cycle, and makes the contribution write path idempotent so retries update rather than duplicate. Previously the only guard against a second contribution in the same cycle was the on-chain contract, which meant any retried webhook, re-run settlement, or repeated call after a PENDING row could append a duplicate row and desync the DB total from the on-chain amount.

Related Issue

Changes

🗄️ Schema & Migration

  • [MODIFY] prisma/schema.prisma

    • Adds a uniqueness constraint on Contribution for the member + cycle pair so a repeated contribution for the same member and cycle cannot create a second row.
  • [ADD] prisma/migrations/20260901000000_add_contribution_unique_constraint/migration.sql

    • Migration applying the new unique constraint to the contributions table.

🔁 Idempotent Write Path

  • [MODIFY] src/services/group.service.ts

    • addContribution now treats a duplicate member/cycle contribution as an update/no-op instead of inserting a second row, so retries are safe.
  • [MODIFY] src/services/message-processor.service.ts

    • Contribution flow after on-chain confirmation tolerates a repeated write for the same member and cycle, updating the existing row rather than erroring or duplicating.

🧪 Tests

  • [MODIFY] src/__tests__/group.service.test.ts

    • Covers the retry case: a second contribution for the same member and cycle does not create a second row.
  • [MODIFY] src/__tests__/message-processor.service.test.ts

    • Covers the retried webhook / repeated write path so a duplicate is treated as a no-op or update, not a user-visible error.

Verification Results

npm test
✅ passes locally
Acceptance Criteria Status
A second completed contribution for the same member and cycle cannot create a second row ✅ Enforced by unique constraint + idempotent write
The write path treats a duplicate as a no-op or an update, not an error the user sees ✅ addContribution and message-processor path updated
The reconciliation total for a member matches the number of contributions actually recorded ✅ No duplicate rows means DB total matches recorded contributions
A migration is included and a test covers the retry ✅ Migration added; retry covered in both service test files
npm test passes locally ✅ Passes locally

Closes #247

Toyosi5566 and others added 2 commits October 16, 2026 15:32
.env is read by dotenv.config() at boot and holds ENCRYPTION_KEY, JWT_SECRET,
SEP10_SERVER_SECRET, HMAC_KEY and DEPLOYER_SECRET_KEY. None of the .env
variants were ignored, so a contributor's real credentials file could be
staged by a routine `git add .` with nothing to warn anyone. Ignore the
.env variants and keep the committed example file trackable.

No .env file is tracked at HEAD and no .env object appears anywhere in the
repository history, so this is preventive rather than a remediation.
@drips-wave

drips-wave Bot commented Oct 17, 2026

Copy link
Copy Markdown

@raskenite 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

@Abdul-dev-creator
Abdul-dev-creator force-pushed the main branch 2 times, most recently from af3277b to a2a4455 Compare October 18, 2026 18:47
@raskenite raskenite closed this by deleting the head repository Oct 19, 2026
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.

fix(db): guard against a duplicate contribution in a cycle

2 participants