Skip to content

feat(backend): idempotency key support for POST /credits/mint, /marketplace/purchase, /retirements#1

Open
Dev-Odun-oss wants to merge 1 commit into
mainfrom
feat/idempotency-key-support
Open

feat(backend): idempotency key support for POST /credits/mint, /marketplace/purchase, /retirements#1
Dev-Odun-oss wants to merge 1 commit into
mainfrom
feat/idempotency-key-support

Conversation

@Dev-Odun-oss

Copy link
Copy Markdown
Owner

Summary

Implements idempotency key deduplication for all three critical mutating endpoints as specified in issue Carbon-Ledger-stellar#539.

Changes

New files

  • backend/src/idempotency/idempotency.middleware.ts — NestJS middleware that validates Idempotency-Key (UUID v4), stores first-execution responses, and replays them on retry within 24 hours.
  • backend/src/idempotency/idempotency.module.ts — NestJS module.
  • backend/src/idempotency/idempotency.middleware.spec.ts — 12 integration tests.
  • backend/prisma/migrations/20260716000000_add_idempotency_record/migration.sql — Raw SQL migration.

Modified files

  • backend/prisma/schema.prisma — Added IdempotencyRecord model with composite unique index on (idempotencyKey, endpoint).
  • backend/src/app.module.ts — Registered module and applied middleware to the three routes.

Acceptance Criteria

Criterion Status
Idempotency-Key header accepted and validated on 3+ endpoints
Duplicate requests (same key) return cached response within 24 hours
Request hash stored with response in database
First execution saved, retry returns cached response
Tests verify idempotency across network failures, concurrency
Clear error responses if key format invalid ✅ HTTP 400 for invalid format, HTTP 422 for body mismatch

Testing

12 integration tests in idempotency.middleware.spec.ts covering:

  • Pass-through when no header
  • First execution stores response + txHash
  • Replay with Idempotent-Replayed: true header
  • Body mismatch → 422
  • Invalid UUID → 400
  • Expired record (>24 h) treated as new request
  • /marketplace/purchase and /retirements deduplication
  • Same key on different endpoints is allowed
  • Concurrent duplicate requests
  • Expired-record prune triggered on each request

Closes Carbon-Ledger-stellar#539

Implements Idempotency-Key header deduplication on:
  - POST /credits/mint
  - POST /marketplace/purchase
  - POST /retirements

Changes:
- backend/src/idempotency/idempotency.middleware.ts
    NestJS middleware that validates UUID-v4 Idempotency-Key header,
    stores first-execution responses in PostgreSQL, and replays them
    for retry attempts within a 24-hour window.
    Returns HTTP 400 for invalid key format, HTTP 422 when same key
    is reused with a different request body.
- backend/src/idempotency/idempotency.module.ts
    NestJS module wrapping the middleware.
- backend/src/idempotency/idempotency.middleware.spec.ts
    12 integration tests covering: first execution, replay, body
    mismatch, invalid format, expired TTL, endpoint scoping,
    concurrent requests, and prune scheduling.
- backend/prisma/schema.prisma
    Added IdempotencyRecord model with composite unique index on
    (idempotencyKey, endpoint) and createdAt index for TTL pruning.
- backend/prisma/migrations/20260716000000_add_idempotency_record/
    Raw SQL migration for the new table.
- backend/src/app.module.ts
    Registered IdempotencyModule and applied IdempotencyMiddleware
    to the three critical routes via configure().

Closes Carbon-Ledger-stellar#539
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.

[Backend / API] Implement idempotency keys for all financial state mutations

1 participant