feat: SPA API compatibility -- prefix fix + missing endpoints #96
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api!96
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "95-spa-api-compatibility-prefix-fix-missing-endpoints"
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?
Summary
Fix the
/apiprefix mismatch that prevents the SPA from reaching existing endpoints, and add the 6 missing endpoints the SPA needs for admin dashboard, parent dashboard, and coach views.Changes
src/basketball_api/main.py: Register players and teams routers at both/api/playersand/players(likewise/api/teamsand/teams) for SPA compatibility while preserving backward compat. Add new account and coaches_api routers.src/basketball_api/routes/account.py(new):GET /account/playersreturns the authenticated parent's players with status, team name, subscription info. Matches parent via Keycloak email.src/basketball_api/routes/admin.py: AddGET /admin/dashboard(aggregate stats: player count, team count, pending registrations, active subscriptions, revenue) andGET /admin/players(full player CRM list with team, parent, status, subscription details).src/basketball_api/routes/coaches_api.py(new):GET /coaches/me(coach dashboard with teams + players) andGET /coaches/{id}(coach profile with role-based access: admin sees all, coach sees self only).src/basketball_api/routes/players.py: AddPUT /{player_id}decorator alongside existingPATCHso the SPA can use either HTTP method for profile edits.tests/test_account.py(new): 7 tests for parent account endpoint.tests/test_admin_spa.py(new): 8 tests for admin dashboard and players list.tests/test_coaches_api.py(new): 13 tests for coach dashboard and profile endpoints.tests/test_spa_prefix.py(new): 7 tests verifying SPA prefix aliases and PUT endpoint.Test Plan
ruff formatandruff checkpass cleanplayer.name,player.status,player.team_name, etc.)/api/players/*and/api/teams/*still workReview Checklist
Related
plan-wkq, Phase 15 (Production Port -- SPA Rebuild)Fix prefix mismatch by registering players and teams routers at both /api/players and /players (likewise /api/teams and /teams) so the SPA can call endpoints without the /api prefix while preserving backward compatibility. Add missing endpoints the SPA needs: - GET /account/players (parent dashboard: list own players) - GET /admin/dashboard (admin stats: player/team counts, revenue) - GET /admin/players (admin CRM: all players with status/team/parent) - GET /coaches/me (coach dashboard: own teams and players) - GET /coaches/{id} (coach profile with role-based access) - PUT /players/{id} (alias for PATCH, SPA uses PUT for profile edit) All endpoints use Keycloak Bearer token auth with appropriate role checks. JSON responses match the field names the SPA components reference (player.name, player.status, player.team_name, etc.). Closes #95 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>Self-Review
Reviewed the full diff (9 files, +1467/-3 lines). No issues found.
Auth model: Correct -- account endpoints use
get_current_user(any auth), admin endpoints userequire_admin, coach profile enforces email-based self-access for non-admins.Prefix aliasing: Standard FastAPI pattern -- same router registered at
/api/playersand/players. Tests verify both paths.JSON contract: Field names match SPA component expectations (
player.name,player.status,player.team_name,player.subscription_status).Test coverage: 35 new tests covering all new endpoints, auth enforcement, empty states, and backward compatibility. All 319 tests pass.
Lint:
ruff formatandruff checkpass clean.PR #96 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Pydantic / Keycloak OIDC / Postgres. Reviewed for PEP compliance, OWASP awareness, SQLAlchemy patterns, FastAPI specifics, and ruff compliance.
Scope: 6 new endpoints (admin dashboard, admin players list, parent account players, coach dashboard, coach profile, PUT player alias) + SPA prefix aliasing for existing player/team routers. 4 new test files with 35 tests.
Auth model: Correctly uses existing
get_current_userandrequire_role("admin")dependencies fromauth.py. Coach profile endpoint (/coaches/{id}) implements proper role-based access: admin sees all, coach sees self only (email match), others get 403. This is well-structured. Theaccount.pyendpoints useget_current_user(any authenticated user) which is appropriate for parent-facing views since the data is scoped by email match.SQLAlchemy patterns: Relationship loading is done correctly with
joinedloadforPlayer.team,Player.parent,Player.registrations,Team.players,Team.coach. No N+1 query risks in the new endpoints. Session management follows existing patterns (dependency-injectedget_db).Pydantic schemas: All new endpoints use properly defined Pydantic response models with
model_config = {"from_attributes": True}. Type hints are consistent (usingstr | NonePEP 604 syntax).FastAPI patterns: Routers are mounted correctly in
main.py. The SPA alias approach (registering the same router at two prefixes) is a valid FastAPI pattern -- no code duplication for the routing layer. Dependency injection is properly used throughout.Test quality: 35 new tests across 4 files. Good coverage of happy paths, edge cases (unknown parent returns empty list, nonexistent coach returns 404), authorization boundaries (401 without auth, 403 for wrong role), and response shape validation. Tests follow the existing conftest patterns with DB session and dependency overrides.
BLOCKERS
None.
All new endpoints have test coverage. No unvalidated user input -- all data access is scoped by authenticated user identity (email match or role check). No secrets or credentials in code. Auth patterns are consistent with existing codebase (no DRY violation in auth/security paths -- auth is delegated to shared
get_current_userandrequire_roledependencies).NITS
DRY: status derivation logic duplicated (
account.py:74-83andadmin.py:273-279). Both files derive a player'sstatusstring from registrations using identical logic: find the latest registration bycreated_at, mappaidto"registered", else use the rawpayment_status.value, default to"pending"if no registrations. This should be extracted to a shared helper (e.g., inmodels.pyas aPlayermethod or a utility function) to prevent divergence when the business logic evolves.Hardcoded magic number in revenue calculation (
admin.py:227):total_revenue_cents = active_subscriptions * 20000. The constantMONTHLY_AMOUNT_CENTS = 20000already exists insubscriptions.py:34. The admin dashboard should import and use that constant rather than inlining the value. If the price changes, this would silently diverge.Revenue calculation is an approximation:
admin.py:226-227calculates revenue asactive_subscriptions * $200. This does not account for actual Stripe payment history, partial months, prorations, or failed payments. The comment acknowledges this (# Monthly revenue = active subscriptions * $200), but the field nametotal_revenue_centsimplies actual revenue. Consider renaming toestimated_monthly_revenue_centsor adding a docstring note that this is projected, not actual._build_team_responsehelper incoaches_api.pyis similar to the team-building logic inteams.py(_team_detail,_player_brief). Different schemas justify separate implementations for now, but if more team-view endpoints are added, consider converging these.PUT+PATCHstacking (players.py:148-149): Stacking@router.putand@router.patchon the same function works correctly in FastAPI. ThePUTsemantics are slightly misleading since the handler usesexclude_unset=True(partial update behavior), but the PR body and docstring document this intentionally ("SPA uses PUT, existing callers use PATCH"). Acceptable as a compatibility shim.SOP COMPLIANCE
95-spa-api-compatibility-prefix-fix-missing-endpointsmatches issue #95plan-wkq(Phase 15).envfiles, or credentials committedPROCESS OBSERVATIONS
pytest+ruff check+ruff format --check. Tests should pass in CI without issues since all new tests use the existing conftest DB fixtures.VERDICT: APPROVED
Fix: CORS middleware added (
7b43f08)QA approved the endpoints but missed a runtime blocker: the SPA at
westside-dev.tail5b443a.ts.netcalls the API cross-origin. WithoutCORSMiddleware, every browserfetchis blocked by CORS policy.Added:
CORSMiddlewareinmain.pywith allowed origins:https://westsidekingsandqueens.tail5b443a.ts.net(prod)https://westside-dev.tail5b443a.ts.net(dev)capacitor://localhost(mobile)http://localhost(local dev)All 319 tests still pass. Requesting fresh QA review.
PR #96 Review (Re-review)
Re-review after CORS fix commit (
7b43f08). Full fresh-context review below.DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Pydantic / Keycloak OIDC JWT auth
CORS Middleware (the focus of this re-review)
The CORSMiddleware config in
src/basketball_api/main.py(lines 48-59) is correct and well-scoped:allow_originsis an explicit allowlist -- not["*"]. Good.westsidekingsandqueens.tail5b443a.ts.net) and dev origin (westside-dev.tail5b443a.ts.net) are both HTTPS-only. Good.capacitor://localhostis correct for Capacitor mobile apps.http://localhost(no port) -- this will match thehttp://localhostorigin header exactly. Browsers send the origin ashttp://localhostonly when the page is served from port 80. If the SPA dev server runs on a different port (e.g., 5173), the browser sendshttp://localhost:5173, which would NOT match. This is actually more restrictive than it appears. If the intent is to allow local development, consider addinghttp://localhost:5173(Vite default) or making the list env-configurable. However, this is a nit, not a blocker -- production origins are correctly locked down.allow_credentials=Truewith explicit origins (not"*") is the correct pattern for cookie/JWT-bearing requests.allow_methods=["*"]andallow_headers=["*"]are standard for APIs consumed by known frontends.CORS verdict: Correct and complete for production use. The previous review's blocker is resolved.
Auth patterns
get_current_userorrequire_admindependency injection. No auth bypass paths.coaches_api.py(line 157-162) correctly normalizes to lowercase before comparison.SQLAlchemy patterns
joinedloadis used correctly to avoid N+1 queries in all new endpoints.get_db).Pydantic models
model_config = {"from_attributes": True}for ORM compatibility.PlayerProfileUpdateusesmodel_dump(exclude_unset=True)for partial updates -- correct pattern.setattr safety (players.py line 182-183)
setattr(player, field_name, value)iterates overbody.model_dump(exclude_unset=True). SincePlayerProfileUpdateonly defines safe string/None fields (height, position, graduating_class, current_school, target_schools, hometown, photo_url), this cannot overwrite sensitive model attributes. Safe.PUT + PATCH dual decorator (players.py lines 148-149)
@router.putand@router.patchon the same function is a valid FastAPI pattern. Both methods share the same handler and Pydantic model. Clean.BLOCKERS
None. All blockers from previous review cycles are resolved.
pydantic_settingswith env prefix.auth.pyviaget_current_userandrequire_role.NITS
DRY: Status derivation logic duplicated --
account.py(lines 74-83) andadmin.py(lines 273-279) contain identical logic for deriving player status from registrations (max(player.registrations, key=lambda r: r.created_at)then checkingpayment_status.value == "paid"). Extract to a shared helper, e.g.,_derive_player_status(player) -> strin a utils module or on the model itself.Hardcoded revenue calculation --
admin.pyline 227:total_revenue_cents = active_subscriptions * 20000hardcodes $200/month. TheRegistrationmodel already has anamount_centscolumn. Consider summing actual subscription amounts from the database for accuracy if pricing ever varies.DRY:
_make_clienttest helper duplicated -- The identical_make_client(db, mock_user)factory appears intest_account.py,test_coaches_api.py, andtest_players.py. Could be extracted toconftest.pyas a shared fixture factory.http://localhostCORS origin -- As noted above, this only matches port 80. If Vite dev server uses port 5173, local dev CORS preflight will fail. Considerhttp://localhost:5173or making the list env-configurable viaSettings.SOP COMPLIANCE
95)plan-wkqPROCESS OBSERVATIONS
/api/players+/players) maintains backward compatibility.7b43f08). Clean fix cycle.VERDICT: APPROVED