feat(api/v2): ✨ Implement claim routes on a worker queue - #136
Open
kyanvde wants to merge 1 commit into
Open
Conversation
Fills in the claims section: the paginated and GeoJSON listings, a single claim by ID or by the ID its team uses, create, bulk import, update and delete. Reads are public, writes are scoped to the authenticated team, and every route exists bare and behind a :teamId prefix. Adds QueueService, which hands slow or external work to apps/worker over the BullMQ queue it already consumes. Reverse geocoding, building counts, Discord messages and build team webhooks are queued rather than awaited, so no request waits on a third party and no third party outage fails a write that already committed. Closes #61 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 28, 2026
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 #61.
Fills in the claims section, and introduces the queue this API uses to hand background work to
apps/worker.Routes
GET /claimsteamfilter; falls back to the token's team, then to every teamGET /:teamId/claimsGET /claims/:id?external=external=truereads the ID as the team's ownexternalIdPOST /claimsPOST /claims/importexternalIdPUT /claims/:id?external=DELETE /claims/:id?external=GET /claims.geojsonGET /:teamId/claims.geojsonGET /claims/imagesEach is registered bare and behind a
:teamIdprefix, with the prefix only ever allowed to name the team the token belongs to (@TeamScope).Worker queue
QueueService(common/queue/) adds BullMQ jobs to theEventQueuethatapps/workeralready consumes, so nothing slow or externally dependent runs inside a request:BUILDTEAM_WEBHOOK(CLAIM_CREATE/CLAIM_UPDATE),SEND_DISCORD_LOG,SYNC_CLAIM_OSMBUILDTEAM_WEBHOOK(CLAIM_UPDATE),SEND_DISCORD_LOG, plusSYNC_CLAIM_OSMonly when the outline changedBUILDTEAM_WEBHOOK(CLAIM_DELETE),SEND_DISCORD_LOGThis is the part that most changes how a claim write behaves compared with v1. v1 called Overpass and Nominatim inline and made the request fail with a 500 when the building count could not be fetched. Here only the geometry that can be derived locally (
area,size,center) is written in the request;buildings,osmNameandcityare left toSYNC_CLAIM_OSM, which is exactly what that task already computes. A create therefore answers immediately and those columns fill in shortly after.Job names and payload shapes live in
common/queue/jobs.tsand mirror the Zod schemas inapps/worker/src/tasks/— a change to either has to be made on both. The API only needs the queue name, so no worker code is imported.bullmqandioredisare pinned to the ranges the worker uses.Two deliberate failure choices, both because the row is already committed by the time a job is dispatched: without
REDIS_URLdispatching is a logged no-op (local dev and tests need no Redis), and a dispatch that fails is logged rather than thrown, so a queue outage never answers 500 for a write that succeeded.Other notes
.geojsonanswers raw GeoJSON, not the{ status, message, data }envelope, so the URL can be handed straight to a map client. That needed a small addition toResponseInterceptor— a@RawResponse()decorator, which nothing else uses.ownerandbuilderstake a whitelisted reference (id,ssoId,discordIdorminecraft) rather than v1's free-form object. v1 passed that object to Prisma as awhere, which let a caller query the user table on any column.externalIdtwice is a 400.externalIdbelonging to another team answers 404, not 403 — a 403 would confirm the ID exists. Same reasoning as the other sections.GET /claims/imagesis scoped to the authenticated team's claims. v1's equivalent was a global moderation list gated on a per-user permission, which v2's per-team token model has no equivalent for.Not included
DELETE /claims/:claimId/images/:imgId— the last of the two parenthesised routes in the issue. Deleting an image means deleting the S3 object, and the uploads/S3 service that does that is introduced by the showcases PR (#78, now merged intoapi/v2as of writing this branch's base). It is a small follow-up on top ofUploadsService.deleteIfUnreferenced; adding a second copy here would only have created a conflict. Everything else in the issue, bold and not, is implemented.Testing
yarn ws api-v2 test— 30 suites, 234 tests, all passing. 78 are new or rewritten:claims.service.spec.ts— listing, single lookup by ID and byexternalId, GeoJSON assembly, image scoping, user resolution, and which jobs each mutation queues (including that an update with no new outline does not queue an OSM sync).claims.routes.spec.ts— end to end through the real router:claims/imagesresolving ahead ofclaims/:id,claims/importahead of a claim ID, the.geojsonbody arriving unwrapped, and the:teamIdprefix rejecting another team.queue.service.spec.ts— job names, the no-Redis no-op, and that a dispatch failure is reported rather than thrown.util/area.spec.ts— coordinate parsing, ring closing, bounding-box centre and area.yarn ws api-v2 buildpasses, and the Swagger document builds with all ten claim paths registered.yarn ws api-v2 lintreports the 6 pre-existingunbound-methoderrors documented in CLAUDE.md and nothing new.🤖 Generated with Claude Code