Review: Public teams endpoint — GET /public/teams
Verdict: READY
Template Completeness
- [x] Type — Feature
- [x] Lineage — "Enables dynamic teams page on westside-app public site"
- [x] Repo — forgejo_admin/basketball-api
- [x] User Story — well-formed As a/I want/So that
- [x] Context — explains why existing endpoints require auth, references playground data contract
- [x] File Targets — new file public.py, modify main.py, schemas in route file
- [x] Acceptance Criteria — 6 criteria, all testable
- [x] Test Expectations — 4 test cases covering auth, allowlist, empty teams, sort
- [x] Constraints — no auth, hardcode tenant_id=1, coach join documented
- [x] Checklist — present
- [x] Related — references playground, convention, westside-app #96
- [x] Security — Field Allowlist — CRITICAL section with explicit allow/deny lists (above and beyond template requirements)
Traceability
- [x] story:WS-S26 label — Westside Season 26 public site story
- [x] arch:basketball-api label — correct architecture component
- [x] Forgejo issue — forgejo_admin/basketball-api#176, open
File Targets
- [x]
src/basketball_api/routes/public.py— confirmed does NOT exist yet (new file, correct) - [x]
src/basketball_api/main.py— confirmed exists, router registration pattern at lines 67-88 is clear; adding public_router with prefix="/public" follows established convention - [x] Schemas in route file (not shared models) — ticket explicitly says "New schemas in the route file (not in shared models)" which is correct for isolation
- [x] Player model fields verified: name (L178), jersey_number (L213), position (L180), height (L179), current_school (L184) — all exist on the Player model
- [x] Team model fields verified: id, name, coach_id FK, coach relationship, players M2M — all present
- [x] Coach model verified: name field exists (L262), join via team.coach_id confirmed
Repo Placement
Correct. Issue filed on forgejo_admin/basketball-api, all file targets are in that repo. No cross-repo work required for this ticket.
Dependencies
- Board item #430 (Public coaches endpoint — GET /public/coaches, Forgejo #177) is a sibling ticket also creating a route in public.py. Both tickets specify public.py as a new file. Whoever goes second will modify instead of create. This is fine — no conflict, but the agent should check if the file exists before creating it.
- Board item #431 (SvelteKit public site — layout shell + 8 pages) has label depends:bb-176+bb-177, confirming this ticket is a prerequisite for the frontend. Dependency is correctly documented.
- No blockers found. This ticket is not blocked by anything in in_progress.
Acceptance Criteria
All 6 acceptance criteria are verifiable by an agent:
- "GET /public/teams returns teams with nested players, no auth required" — testable via unauthenticated HTTP call
- "Response schema is PublicTeamsResponse — allowlisted fields only" — testable by asserting response keys match allowlist
- "Teams sorted 17U to 16U to 15U, Elite to Select to Local" — testable by asserting order in response array
- "Teams with zero assigned players are excluded" — testable with fixture data
- "No sensitive data in response" — testable by asserting excluded keys are absent
- "Endpoint accessible without Bearer token" — testable (same as criterion 1)
Test expectations match acceptance criteria well. The test command is implied (pytest) and follows the existing pattern in tests/test_teams.py.
Minor note: The sort order "17U, 16U, 15U then Elite, Select, Local" references team name conventions, not the AgeGroup enum (which has U8-U18 values like "U16", "U18"). The agent will need to implement custom sort logic based on age_group enum descending + team name substring for tier. This is implementation detail, not a scope gap — the ticket correctly specifies the desired output order.
Blast Radius
- No existing /public routes exist in the codebase — this is greenfield. No collision risk.
- Auth is per-route via Depends(), not global middleware — no risk of accidentally protecting or unprotecting other routes.
- CORS middleware allows the westside frontend origin — no CORS issues for the public site consuming this endpoint.
- The Division enum (boys/girls) is unrelated to the "Elite/Select/Local" sort tier. No enum changes needed.
- No other services consume team data publicly — blast radius is contained to basketball-api + westside-app frontend.
Recommendation
No action needed. Scope is solid, all file targets verified, traceability complete, security allowlist is thorough, and acceptance criteria are testable. Ready for next_up.