feat: add idempotency keys to delivery creation and escrow funding (#145) - #155
Merged
Merged
Conversation
…wiftChainn#145) Closes SwiftChainn#145 ## What changed ### New files - src/middlewares/idempotency.ts requireIdempotencyKey Express middleware that enforces the Idempotency-Key header on protected POST endpoints. Intercepts res.json to capture response bodies and replays cached responses for duplicate requests. - src/services/idempotency.service.ts IdempotencyService with dual-store strategy: - Redis (primary) when REDIS_URL is configured; uses SET NX for atomic first-write protection against concurrent duplicates. - MongoDB (fallback) when Redis is absent; uses findOneAndUpdate with for the same atomicity guarantee. Exposes get / markProcessing / markCompleted / markFailed methods. - src/models/IdempotencyRecord.ts Mongoose model with a composite unique index on (key, endpoint) and a TTL index on expiresAt for automatic record expiry. - src/config/redis.ts Lazy ioredis singleton with exponential back-off retry and graceful degradation — when REDIS_URL is absent or Redis is unreachable the app continues using the MongoDB fallback store. ### Modified files - src/routes/delivery.routes.ts Added requireIdempotencyKey before the POST / handler so every delivery creation request must carry an Idempotency-Key header. - src/routes/escrow.routes.ts Added POST /fund endpoint protected by requireIdempotencyKey + validateRequest(fundEscrowBodySchema). Mounted escrow routes in src/routes/index.ts at /api/v1/escrow. - src/routes/index.ts Mounted escrow routes so /api/v1/escrow/* is now reachable. - src/controllers/escrow.controller.ts Added fund() handler that delegates to EscrowService.fund(). - src/services/escrow.service.ts Added fund() method as the HTTP-layer entry point for escrow funding; delegates to existing recordEscrowFunded() for write logic and re-fetches the persisted document for the response. - src/validators/escrowValidator.ts Added fundEscrowBodySchema (Zod v4) for request-body validation of the POST /fund endpoint. - src/models/Escrow.ts Fixed pre-existing duplicate-schema merge bug that caused 37 TypeScript errors; retained the EscrowLockStatus / transactions schema that the service layer depends on. - src/config/env.ts Added REDIS_URL (optional) and IDEMPOTENCY_TTL_SECONDS (default 86400 s) to envSchema and EnvConfig interface. - package.json / package-lock.json Added [email protected] runtime dependency. - .env.example Documented REDIS_URL and IDEMPOTENCY_TTL_SECONDS variables. ## Idempotency behaviour - Missing header -> 422 Unprocessable Entity - First request -> processed normally; response cached under key - Duplicate (complete) -> 200/201 replayed from cache, no DB writes - Duplicate (in-flight)-> 409 Conflict - Key TTL -> configurable via IDEMPOTENCY_TTL_SECONDS (default 24 h)
|
@mmotunrayo 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #145
What changed
New files
src/middlewares/idempotency.ts
requireIdempotencyKeyExpress middleware that enforces theIdempotency-Keyheader on protected POST endpoints. Interceptsres.jsonto capture response bodies and replays cached responses for duplicate requests.src/services/idempotency.service.ts
IdempotencyServicewith dual-store strategy:REDIS_URLis configured; usesSET NXfor atomic first-write protection against concurrent duplicates.findOneAndUpdatewith$setOnInsertfor the same atomicity guarantee.Exposes
get/markProcessing/markCompleted/markFailedmethods.src/models/IdempotencyRecord.ts
Mongoose model with a composite unique index on
(key, endpoint)and a TTL index onexpiresAtfor automatic record expiry.src/config/redis.ts
Lazy
ioredissingleton with exponential back-off retry and graceful degradation — whenREDIS_URLis absent or Redis is unreachable the app continues using the MongoDB fallback store.Modified files
src/routes/delivery.routes.ts
Added
requireIdempotencyKeybefore thePOST /handler so every delivery creation request must carry anIdempotency-Keyheader.src/routes/escrow.routes.ts
Added
POST /fundendpoint protected byrequireIdempotencyKey+validateRequest(fundEscrowBodySchema). Mounted escrow routes insrc/routes/index.tsat/api/v1/escrow.src/routes/index.ts
Mounted escrow routes so
/api/v1/escrow/*is now reachable.src/controllers/escrow.controller.ts
Added
fund()handler that delegates toEscrowService.fund().src/services/escrow.service.ts
Added
fund()method as the HTTP-layer entry point for escrow funding; delegates to existingrecordEscrowFunded()for write logic and re-fetches the persisted document for the response.src/validators/escrowValidator.ts
Added
fundEscrowBodySchema(Zod v4) for request-body validation of thePOST /fundendpoint.src/models/Escrow.ts
Fixed pre-existing duplicate-schema merge bug that caused 37 TypeScript errors; retained the
EscrowLockStatus/transactionsschema that the service layer depends on.src/config/env.ts
Added
REDIS_URL(optional) andIDEMPOTENCY_TTL_SECONDS(default 86400 s) toenvSchemaandEnvConfiginterface.package.json / package-lock.json
Added
[email protected]runtime dependency..env.example
Documented
REDIS_URLandIDEMPOTENCY_TTL_SECONDSvariables.Idempotency behaviour
Idempotency-Keyheader422 Unprocessable Entity200/201replayed from cache, no DB writes409 ConflictIDEMPOTENCY_TTL_SECONDS(default 24 h)