feat: add GET /admin/players/{player_id} detail endpoint #251
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
ldraney/basketball-api!251
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "249-admin-player-detail"
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
Adds a comprehensive player detail endpoint
GET /admin/players/{player_id}for the CRM detail view. Returns all player data needed by the frontend: personal info, parent info, team names, registration/contract/jersey status.Changes
src/basketball_api/routes/admin.py-- AddedAdminPlayerDetailPydantic response model andadmin_player_detailendpoint. Placed after all literal/players/*routes to avoid path parameter conflicts with/players/incompleteand/players/bulk-visibility.tests/test_admin_spa.py-- AddedTestAdminPlayerDetailclass with 5 tests: happy path with team, player without team, 404 for nonexistent player, response field completeness, and 401 without auth.Test Plan
pytest tests/test_admin_spa.py -v-- all 13 tests pass (8 existing + 5 new)ruff formatandruff checkpass cleanReview Checklist
/players/*paths to avoid FastAPI path parameter conflictsRelated Notes
Related
PR #251 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / Pydantic
Endpoint correctness:
GET /admin/players/{player_id}is properly guarded withrequire_admindependency.playerisNone.joinedloadforPlayer.teams,Player.parent, andPlayer.registrationsprevents N+1 queries -- consistent with the existingadmin_list_playerspattern./players/*routes (/players,/players/incomplete,/players/bulk-visibility) are registered before the{player_id}parameterized route, avoiding FastAPI path-matching conflicts..valueaccess is correct fordivision,contract_status,jersey_option,jersey_size,jersey_order_status.date_of_birthandcontract_signed_atuse.isoformat()with properNoneguards.registration_typeis a plainstrcolumn (not an enum), so no.valuecall is needed -- correctly handled.Response model completeness (vs. acceptance criteria):
name,photo_url,date_of_birth,height,position,current_school,graduating_class,hometown,division,country-- all present.parent_name,parent_email-- present.team_names,status(derived from registration payment_status),contract_status,contract_signed_at-- present.jersey_option,jersey_size,jersey_number,jersey_order_status-- present.registration_type-- good addition for CRM context.Test quality (5 tests):
test_returns_player_detail-- happy path with team, asserts key fields including parent info, team_names, status derivation.test_returns_player_without_team-- edge case: player with no team assignment, assertsteam_names == []and pending status.test_404_for_nonexistent_player-- verifies 404 status and error detail message.test_response_fields-- schema completeness check for all 22 fields.test_401_without_auth-- auth guard verification using unauthenticatedclientfixture.Tests reuse the existing
populated_dbfixture consistently.BLOCKERS
None.
All blocker criteria pass:
player_idis typed asintby FastAPI path parameter).require_admindependency is reused, not duplicated).NITS
DRY: Status derivation logic is duplicated. The "derive status from registrations" block (lines ~1101-1111 in the new endpoint) is an exact copy of lines 328-340 in
admin_list_players. Same for thereg_typederivation. Consider extracting a helper like_derive_player_status(registrations) -> tuple[str, str | None]that both endpoints call. Not a blocker since this is business logic, not auth/security, but divergence risk increases with each new consumer.Redundant
latest_regcomputation. The new endpoint computeslatest_reg = max(player.registrations, key=lambda r: r.created_at)twice -- once for status and once forreg_type. These could share a single computation. This is also duplicated from the list endpoint's same pattern.parent_nameandparent_emailare non-optional strings. If a Player could theoretically exist without a Parent loaded (e.g., orphaned data), accessingplayer.parent.namewould raiseAttributeError. The model hasparent_idas non-nullable, so this should be safe in practice, but a defensive guard (or explicit documentation of the invariant) would be slightly more robust. Minor nit.SOP COMPLIANCE
249-admin-player-detailadmin.py+test_admin_spa.pymodified)feat: add GET /admin/players/{player_id} detail endpoint)PROCESS OBSERVATIONS
admin.py(Pydantic response model,require_admin,joinedload, manual field mapping). Good codebase coherence.VERDICT: APPROVED