Add the list all places endpoint for the map home page #53
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/list-all-places-endpoint"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements task 144:
GET /api/places, every place the caller can see across every group they hold an accepted membership of (personal group included), each carrying its group id and name so the map can build its accordion without a request per group.PlaceRepository.ListForCallerAsync, joiningplaces→group_memberships→groups, filtered onstatus = 'accepted') — never fetches then filters.PlaceForCalleris the join projection, mirroringGroupForCaller.created_at, id, tested with several places sharing acreated_atacross two of the caller's groups.Authorisation: no
RequireGroupCapability/RequireGroupCapabilityForPlace— this route spans every group the caller belongs to, not one named by the route, so it doesn't fit either filter. TheWHEREclause scoping to the caller's own accepted memberships is the enforcement, the same shapeGET /api/groupsalready takes. Task 148's safety net (GroupScopedEndpointAuthorisationTests) does not cover this route — it enumerates routes under/api/groups/{groupId}and/api/places/, and this one matches neither pattern (mapped directly at exact/api/places, deliberately outside theplacesgroup, to avoid a trailing-slash routing artefact that would otherwise sweep it in as if it named one place).Pagination (ADR-0049): reuses the exact
PagedResponse<T>shape and page-size cap (50) every other list endpoint already uses, rather than a bigger bound or no bound at all. The map is expected to keep requesting pages until it has readTotalCountplaces, accumulating markers — never truncated, just possibly more than one round trip. Argued in full in the ADR, including why a bigger cap specific to this endpoint was rejected.Took ADR number 0049 — checked against
main(currently at PR #52) immediately before pushing; no open PRs at push time.Merged
origin/mainin before pushing to pick up PR #52 (delete place), which touched the same files; one test-file conflict, resolved by keeping both independent test methods.Verdict: mergeable
Verified the trailing-slash claim directly rather than taking it on trust: mapping
GetAllPlacesinside theplacesgroup (places.MapGet("", ...)) does produce route text/api/places/, which does tripGroupScopedEndpointAuthorisationTests'StartsWith($"{ByIdRoutePrefix}/")check — and since the route is genuinely not group-scoped, that flag is a false positive, not a real gap the test is right to catch. Reverted, then added a new group-scoped route under/api/places/...with no capability requirement to confirm the net still catches it — it does. The workaround is also recorded durably in code (the routing comment above theMapGetcall and inGetAllPlaces' own XML doc), not only in the PR description. No action needed here.Mutated
SelectPlacesForCaller/CountPlacesForCallerto dropgm.status = 'accepted'—ListForCallerAsync_APendingInvitation_ExcludesItsGroupsPlacesreddens as expected, confirming the scoping is load-bearing in the SQL, not incidental. Personal-group inclusion, removed-membership and cross-user exclusion are all covered at both repository and endpoint level. Ordering tie-break on(created_at, id)is tested across groups, not just within one.Merge conflict resolution in
PlaceRepositoryTests.cskept the full union of both branches' tests (ListForCallerAsync_*from this branch,DeleteAsync_*from #52) — nothing dropped.Entities stay off the wire:
PlaceForCaller→PlaceForCallerResponsemapping is explicit in the handler.ADR-0049's cap is argued honestly, including the consequence that a caller's map waits on as many requests as it takes to reach
TotalCount— it doesn't pin an exact ceiling on round trips, but the "tens to low hundreds per caller" estimate is a direct, reasonable inference from ADR-0007's existing app-wide numbers, not a new assumption, and the ADR names its own revisit trigger. Not blocking.Full solution builds clean and all tests pass under the pinned SDK (10.0.100).