Review: Public coaches endpoint — GET /public/coaches

review-430-2026-03-26 Review

review ready

Verdict: READY

Re-review of board item #430. Previous review (NEEDS_REFINEMENT) found three issues. All three have been addressed.

Template Completeness

  • [x] Type — Feature
  • [x] Lineage
  • [x] Repo
  • [x] User Story
  • [x] Context
  • [x] Security — Field Allowlist (bonus section, not required by template)
  • [x] File Targets
  • [x] Acceptance Criteria
  • [x] Test Expectations
  • [x] Constraints
  • [x] Checklist
  • [x] Related

Traceability

  • [x] story:WS-S26 label — Westside Season 26 public site
  • [x] arch:basketball-api label — basketball-api component
  • [x] Forgejo issue — forgejo_admin/basketball-api#177, open

File Targets

  • [x] src/basketball_api/routes/public.py — Does not exist yet, but issue #176 (board item #429, currently in_progress) creates it. Dependency is now tracked via depends:bb-176 label. Verified: no public.py or public_router references exist in the codebase yet.

Model Field Resolution

Previous review flagged bio, photo_url, and slug as missing from the Coach model. Resolution per comment #8098:
  • [x] bio — Alembic migration scoped in this ticket (ALTER TABLE coaches ADD COLUMN bio TEXT)
  • [x] photo_url — Alembic migration scoped in this ticket (ALTER TABLE coaches ADD COLUMN photo_url VARCHAR(500))
  • [x] is_public — Alembic migration scoped in this ticket (ALTER TABLE coaches ADD COLUMN is_public BOOLEAN NOT NULL DEFAULT true). Comment #8073 established this scope addition.
  • [x] slug — Documented as computed at runtime (name.lower().replace(" ", "-")), not stored. Acceptable.
Verified: no Alembic migration for these coach columns exists yet. The Player model has photo_url (line 182 of models.py) as prior art for the pattern.

Repo Placement

OK — issue filed on forgejo_admin/basketball-api, work belongs there. Single-repo change.

Dependencies

  • Blocking: Issue #176 (Public teams endpoint, board item #429) — now in_progress (was todo at previous review). Creates public.py and registers public router. Board item #430 has depends:bb-176 label. FIXED from previous review.
  • Downstream: Board item #431 (westside-app#98 "SvelteKit public site — layout shell + 8 pages") has depends:bb-176+bb-177, correctly documenting it waits on both public API endpoints.

Acceptance Criteria

All criteria are now testable:
  • [x] "GET /public/coaches returns coaches list, no auth required" — testable
  • [x] "Response schema is PublicCoachesResponse — allowlisted fields only" — testable
  • [x] "Each coach includes a URL-safe slug derived from name" — testable
  • [x] "photo_url points to MinIO path" — testable once migration lands and seed data populates the column
  • [x] "No sensitive data in response" — testable via negative assertion
  • [x] "Endpoint accessible without Bearer token" — testable
Note: The is_public filtering (only return coaches where is_public = true) is scoped in comment #8073 but not listed as an explicit acceptance criterion in the issue body. An executing agent will see the comment, but adding a criterion would make it unambiguous. Minor nit, not blocking.

Blast Radius

  • No existing public router pattern in basketball-api — this is new territory introduced by #176.
  • The role field on CoachRole is an enum (head_coach, assistant, director). The public response will serialize the enum value as-is. The executing agent should use .value for human-readable strings. Minor implementation detail, not a scope issue.
  • No other services consume coach data publicly. Only downstream consumer is westside-app's public site (board item #431).
  • The Alembic migration pattern has prior art: 002_add_player_profile_fields.py added photo_url to the players table.

Previous Findings Disposition

  • Missing dependency label — FIXED. depends:bb-176 now on board item #430.
  • Model field gaps — FIXED. Alembic migration for bio, photo_url, is_public explicitly scoped in comments #8073 and #8098.
  • Untestable acceptance criterion — FIXED. photo_url criterion is testable once migration lands and data is seeded.

Recommendation

No action needed. All three previous findings are resolved. Ticket is ready for execution.
Optional improvement (not blocking): Add "Only coaches with is_public = true are returned" as an explicit acceptance criterion in the issue body, since this behavior is currently only documented in comment #8073.