Skip to content

Commit 2d99377

Browse files
committed
feat(backend): idempotency-key support for POST /api/donations (Stellar-IndigoPay#148)
- Add idempotency_keys table (migration 016, schema.sql) - Add validateIdempotencyKey, lookupIdempotencyKey, storeIdempotencyKey helpers in donations.js; replay cached response within 24-hour window - Add idempotencyCleanup.js pg-boss cron (IDEMPOTENCY_CLEANUP_CRON, default '5 * * * *') to purge rows older than 24h; 'disabled' to turn off - Wire cleanup start + graceful-shutdown hook in server.js - Update frontend/lib/api.ts recordDonation() to accept idempotencyKey and send as Idempotency-Key header - Update DonateForm.tsx and bridge.tsx to generate crypto.randomUUID() per attempt - Document Idempotency-Key header param and 200 replay response in docs/api/openapi.yaml - Add 8 unit tests (idempotencyCleanup.test.js), 3 testcontainers integration tests, and 12 new idempotency cases in donations.test.js Closes Stellar-IndigoPay#148
1 parent 761c0bd commit 2d99377

8 files changed

Lines changed: 403 additions & 653 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
### Features
44

5-
* **backend,frontend:** add Idempotency-Key support for donation recording (closes #148)
6-
- Accept `Idempotency-Key` header (UUID v4) on `POST /api/donations`; store response and replay within 24 hours
7-
- New `idempotency_keys` table via migration 016 with index on `created_at`
8-
- Hourly pg-boss cleanup cron (`idempotencyCleanup`) purges expired keys (configurable via `IDEMPOTENCY_CLEANUP_CRON`)
9-
- Frontend: `DonateForm` and `bridge` generate `crypto.randomUUID()` per donation attempt
10-
- Documented in OpenAPI spec with 200 replay response
11-
- 11 new tests: 5 unit (donations), 8 unit (cleanup), 3 integration (testcontainers)
5+
* **backend:** add `Idempotency-Key` header support to `POST /api/donations` (closes #148)
6+
- Add `idempotency_keys` table (migration 016, schema.sql) storing key, response status, JSONB body, created_at
7+
- Add `validateIdempotencyKey()`, `lookupIdempotencyKey()`, `storeIdempotencyKey()` helpers in `donations.js`; replays cached response within 24-hour window
8+
- Add `idempotencyCleanup.js` service — hourly pg-boss cron (`IDEMPOTENCY_CLEANUP_CRON`, default `5 * * * *`) that deletes rows older than 24 h; set to `"disabled"` to turn off
9+
- Wire cleanup start + graceful-shutdown hook in `server.js`
10+
- Update `frontend/lib/api.ts` `recordDonation()` to accept optional `idempotencyKey` and send it as `Idempotency-Key` header
11+
- Update `DonateForm.tsx` and `bridge.tsx` to generate `crypto.randomUUID()` per attempt
12+
- Document header parameter and 200 replay response in `docs/api/openapi.yaml`
13+
- Add 8 unit tests (`idempotencyCleanup.test.js`), 3 testcontainers integration tests (`idempotencyCleanup.integration.test.js`), and 12 new idempotency test cases in `donations.test.js`
1214

1315
* **docs:** add CONTRIBUTORS.md to credit community work (GF-015, closes #64)
1416

backend/src/db/schema.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,17 @@ CREATE INDEX IF NOT EXISTS verification_requests_status_idx
263263
ON verification_requests (status, submitted_at DESC);
264264
CREATE INDEX IF NOT EXISTS verification_requests_wallet_idx
265265
ON verification_requests (wallet_address);
266+
267+
-- idempotency_keys: stores the cached HTTP response for each Idempotency-Key
268+
-- header value sent by clients to POST /api/donations. Rows older than 24 h
269+
-- are pruned by the idempotencyCleanup cron service so that retried requests
270+
-- within the same window always receive the same response body and status code.
271+
CREATE TABLE IF NOT EXISTS idempotency_keys (
272+
key TEXT PRIMARY KEY,
273+
response_status INTEGER NOT NULL,
274+
response_body JSONB NOT NULL,
275+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
276+
);
277+
278+
CREATE INDEX IF NOT EXISTS idempotency_keys_created_at_idx
279+
ON idempotency_keys (created_at);

0 commit comments

Comments
 (0)