Skip to content

fix: 7 backend bugs — webhook delete 404, message length, tips cache, webhook pagination, analytics 400, jarId validation, auth rate limit, delivery payload bound - #64

Merged
Abioladory123 merged 11 commits into
Novatip:mainfrom
slsgzs-cloud:fix-backend-bugs
Sep 8, 2026

Conversation

@slsgzs-cloud

@slsgzs-cloud slsgzs-cloud commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes 7 backend bugs:

#14 — claimSlug does not verify jarId matches slug

claimSlug writes whatever jarId the client sends, with no check that it matches the claimed slug. The contract's jar ID must be @${slug} for the indexer to resolve tips correctly.

Fix: Added validation that rejects with 400 if jarId !== '@${slug}'. Frontend already sends @${slug} so existing calls are unaffected.

#48 — Invalid analytics query parameters return 500 instead of 400

Zod's parse throws on invalid input, and the thrown ZodError carries no statusCode, so the global error handler falls through to 500.

Fix: Replaced parse with safeParse in all three analytics routes. Validation failures now return 400 with { error: error.flatten() }.

#49 — Deleting a webhook that does not exist returns 204

deleteWebhook used deleteMany filtered by id and creator, then the route returned 204 regardless. Deleting a non-existent id was indistinguishable from success.

Fix: deleteWebhook returns Promise<boolean> by checking count > 0. Route returns 404 when the webhook does not exist.

#50 — Auth challenge endpoint shares the global rate limit

POST /auth/challenge generates a 32-byte nonce and writes to Redis. Under the shared 100 req/min budget, a single caller can exhaust the allowance minting nonces, filling Redis with short-lived keys.

Fix: Added a dedicated sliding-window limiter (default 5 req/min per IP, configurable via AUTH_CHALLENGE_RATE_LIMIT). Existing cache headers preserved.

#51 — QR generation has no dedicated rate limit

Both QR routes are public and render images. The PNG route rasterises at 512 pixels — real CPU work. Only the global 100 req/min budget protects them.

Fix: Added a dedicated sliding-window limiter (default 10 req/min per IP, configurable via QR_RATE_LIMIT).

#52 — Tip messages stored without the contract's length bound

The contract caps tip messages at 280 bytes, but Tip.message had no length constraint.

Fix: Added @db.VarChar(280) to the message column in schema.prisma.

#53 — Recent tips endpoint is the only analytics read not cached

getTotals, getTimeSeries and getTopSupporters all use Redis with 30s TTL. getRecentTips went straight to the DB — the most frequently hit analytics query.

Fix: Added Redis caching with the same 30s TTL. Key includes creatorId and limit.

#54 — Webhook delivery records store the full payload with no size bound

Each delivery writes the complete payload as JSON. The response body is truncated to 1 KB, but the request payload is stored whole.

Fix: Added MAX_PAYLOAD_SIZE = 2_048 constant. Added boundPayload() that truncates message first, then strips amountRaw if still over the bound.

#55 — Webhook list endpoint has no pagination

listWebhooks ran an unbounded findMany.

Fix: Added limit (default 50, max 100) and offset query parameters with zod validation. Results ordered by createdAt desc.

@slsgzs-cloud slsgzs-cloud changed the title fix: webhook delete returns 404 + tip message length bound fix: webhook delete 404, message length bound, recent tips cache, webhook pagination Sep 8, 2026
@slsgzs-cloud slsgzs-cloud changed the title fix: webhook delete 404, message length bound, recent tips cache, webhook pagination fix: 7 backend bugs — webhook delete 404, message length, tips cache, webhook pagination, analytics 400, jarId validation, auth rate limit, delivery payload bound Sep 8, 2026
@Abioladory123
Abioladory123 merged commit 2eb8f9e into Novatip:main Sep 8, 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.

2 participants