Maps¶
Purpose¶
Maps serves the detail sheet a map marker opens and the per-user
personalization that rides on it. Discovery emits the
viewport markers; Maps serves the location and event cards those markers
tap through to, plus the save / dismiss actions that shape a user's map
over time. It reads the canonical Location, Moment, and Event rows
directly.
Code lives in backend/internal/services/maps/.
HTTP surface¶
Routes are in backend/internal/services/maps/routes.go. The detail reads
are public with optional auth (share links resolve unauthenticated; authed
callers get viewer-relative fields like is_saved). The personalization
actions require JWT and mount under the caller's /map group.
| Method | Path | Auth | Handler | Description |
|---|---|---|---|---|
| GET | /map/location/:id |
optional | LocationDetail |
Full location card; is_saved set when authed |
| GET | /map/event/:id |
optional | EventDetail |
Full event card via the event domain |
| POST | /map/location/:id/dismiss |
JWT | DismissLocation |
Hide from my map, downshift affinity |
| POST | /map/location/:id/save |
JWT | SaveLocation |
Bookmark a place |
| DELETE | /map/location/:id/save |
JWT | UnsaveLocation |
Remove the bookmark |
Cards¶
LocationDetail returns MapLocationDetailResponse (types.go): the
place's identity and geo denorm, rating and save_count, opening hours,
moment highlights, and upcoming events. is_saved reflects the requester's
user_saves row when authenticated.
EventDetail does not build its own payload. It reuses the event domain
through the EventDetailProvider interface (deps.go), calling
event.GetEventDetail and returning event.EventDetailResponse so a map
event pin and the event screen render from the identical canonical shape.
Maps imports that response; it is not duplicated here.
Both handlers 404 on a non-UUID :id rather than binding-error, since the
path is a share-facing surface.
Personalization¶
Save, unsave, and dismiss adjust two things in one transaction: the bookmark row and a category-affinity score used to bias what the map surfaces. All three are idempotent so double-taps do not inflate counts.
| Action | Row effect | Affinity effect |
|---|---|---|
| Save | Upsert user_saves, save_count += 1 |
user_affinity.score += 0.05 (capped at 1.0) |
| Unsave | Delete user_saves, save_count -= 1 (floored at 0) |
score -= 0.03 (floored at 0) |
| Dismiss | Record dismissal | score -= 0.03 (floored at 0) |
Affinity is keyed (user_id, category) and upserted via
INSERT ... ON CONFLICT.
Key types¶
MapLocationDetailResponse,MapOpeningHours,MomentHighlightBrief,EventBrief,UserBriefinbackend/internal/services/maps/types.go. TheEventBrief/UserBriefbriefs are the Maps-local copies of the cross-cutting shapes.EventDetailResponse, owned by the event domain, imported.
Where to look¶
backend/internal/services/maps/handler.go: HTTP surfacebackend/internal/services/maps/service.go: card reads + save/dismiss/affinitybackend/internal/services/maps/deps.go:EventDetailProviderbackend/internal/services/maps/routes.go,types.go,wire.go- Discovery · Locations · Events