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
Conversation
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.
Summary
Fixes 7 backend bugs:
#14 — claimSlug does not verify jarId matches slug
claimSlugwrites whateverjarIdthe 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
parsethrows on invalid input, and the thrownZodErrorcarries nostatusCode, so the global error handler falls through to 500.Fix: Replaced
parsewithsafeParsein all three analytics routes. Validation failures now return 400 with{ error: error.flatten() }.#49 — Deleting a webhook that does not exist returns 204
deleteWebhookuseddeleteManyfiltered by id and creator, then the route returned 204 regardless. Deleting a non-existent id was indistinguishable from success.Fix:
deleteWebhookreturnsPromise<boolean>by checkingcount > 0. Route returns 404 when the webhook does not exist.#50 — Auth challenge endpoint shares the global rate limit
POST /auth/challengegenerates 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.messagehad no length constraint.Fix: Added
@db.VarChar(280)to themessagecolumn inschema.prisma.#53 — Recent tips endpoint is the only analytics read not cached
getTotals,getTimeSeriesandgetTopSupportersall use Redis with 30s TTL.getRecentTipswent 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_048constant. AddedboundPayload()that truncates message first, then strips amountRaw if still over the bound.#55 — Webhook list endpoint has no pagination
listWebhooksran an unboundedfindMany.Fix: Added
limit(default 50, max 100) andoffsetquery parameters with zod validation. Results ordered bycreatedAt desc.