Add player profile endpoints (GET + PATCH) — Phase 11a #89

Closed
opened 2026-03-16 00:53:03 +00:00 by forgejo_admin · 0 comments

Lineage

plan-2026-03-08-tryout-prep → Phase 11 → Phase 11a

Repo

forgejo_admin/basketball-api

User Story

As a player/parent, coach, or admin
I want API endpoints to view and update individual player profiles
So that the frontend can render a player profile page with role-based edit permissions

Context

Phase 11 adds player profile pages as the central entity in the westside app. The roster endpoints exist but only return flat lists — there's no way to GET a single player's full profile or PATCH their editable fields. The frontend needs these endpoints before the profile page can be built.

Permission model decided by Lucas:

  • Players (matched by parent email in JWT) can update their own profile
  • Admins can update any player's profile
  • Coaches can view any player's profile (read-only)
  • All authenticated users can GET a player profile

Auth architecture: Keycloak OIDC JWT validation is already implemented (auth.py). get_current_user() returns a User dataclass with sub, email, username, roles. require_role() factory checks role membership. Player ownership check: match player.parent.email against current_user.email.

Existing infrastructure:

  • Player model has all needed fields: name, height, position, graduating_class, photo_url, current_school, target_schools, hometown, team_id, subscription_status, etc.
  • Photo upload endpoint already exists at POST /upload/photo (returns photo_url)
  • Team relationship exists on Player model (player.team with coach info)
  • Tenant slug resolution exists (_get_tenant_or_404 in teams.py)

File Targets

Files to create:

  • src/basketball_api/routes/players.py — New router with GET and PATCH endpoints
  • tests/test_players.py — Tests for both endpoints + permission scenarios

Files to modify:

  • src/basketball_api/main.py — Register the new players router

Files NOT to touch:

  • src/basketball_api/routes/roster.py — Existing roster endpoints (don't break)
  • src/basketball_api/routes/tryouts.py — Existing tryout endpoints
  • src/basketball_api/models.py — Player model already has all needed fields
  • src/basketball_api/auth.py — Auth middleware already works

Acceptance Criteria

  • GET /api/players/{player_id} returns full player profile including: name, height, position, graduating_class, current_school, target_schools, hometown, photo_url, division, tryout_number, subscription_status, team (name + coach name if assigned), parent info (name, email, phone)
  • GET /api/players/{player_id} requires authentication (any role: admin, coach, or player)
  • GET /api/players/{player_id} returns 404 for non-existent player
  • PATCH /api/players/{player_id} allows updating: height, position, graduating_class, current_school, target_schools, hometown, photo_url
  • PATCH /api/players/{player_id} with admin role can update any player
  • PATCH /api/players/{player_id} with player role can only update their own profile (parent email match). Returns 403 otherwise.
  • PATCH /api/players/{player_id} with coach role returns 403 (read-only)
  • PATCH /api/players/{player_id} returns the updated player profile
  • Read-only fields (team_id, subscription_status, tryout_number, division) cannot be changed via PATCH

Test Expectations

  • Unit test: GET player by ID returns correct fields
  • Unit test: GET player includes team name and coach name when assigned
  • Unit test: GET player returns 404 for missing player
  • Unit test: GET player returns 401 without auth
  • Unit test: PATCH player updates editable fields
  • Unit test: PATCH player as admin updates any player
  • Unit test: PATCH player as player updates own profile (parent email match)
  • Unit test: PATCH player as player returns 403 for other player's profile
  • Unit test: PATCH player as coach returns 403
  • Unit test: PATCH player ignores read-only fields
  • Run command: cd ~/basketball-api && python -m pytest tests/test_players.py -v

Constraints

  • Follow existing route patterns — see routes/teams.py for style reference
  • Use Pydantic response models (match existing RosterResponse/PlayerInfo patterns)
  • Use get_current_user and require_role from auth.py
  • Player ownership check: player.parent.email == current_user.email (case-insensitive)
  • Route prefix: /api/players — register in main.py
  • Include team info in GET response via SQLAlchemy relationship (player.team)
  • Include parent info in GET response via relationship (player.parent)
  • Use async route handlers matching existing codebase pattern
  • Tenant scoping: players belong to a tenant. For now, don't require tenant_slug in URL — just validate the player exists. Cross-tenant access is OK for now since all users are in one tenant.

Checklist

  • PR opened
  • Tests pass (pytest tests/test_players.py -v)
  • Full test suite passes (pytest tests/ -v)
  • No unrelated changes
  • project-westside-basketball — project this affects
  • Phase 11b (frontend profile page) depends on this
### Lineage `plan-2026-03-08-tryout-prep` → Phase 11 → Phase 11a ### Repo `forgejo_admin/basketball-api` ### User Story As a player/parent, coach, or admin I want API endpoints to view and update individual player profiles So that the frontend can render a player profile page with role-based edit permissions ### Context Phase 11 adds player profile pages as the central entity in the westside app. The roster endpoints exist but only return flat lists — there's no way to GET a single player's full profile or PATCH their editable fields. The frontend needs these endpoints before the profile page can be built. **Permission model decided by Lucas:** - Players (matched by parent email in JWT) can update their own profile - Admins can update any player's profile - Coaches can view any player's profile (read-only) - All authenticated users can GET a player profile **Auth architecture:** Keycloak OIDC JWT validation is already implemented (`auth.py`). `get_current_user()` returns a User dataclass with `sub`, `email`, `username`, `roles`. `require_role()` factory checks role membership. Player ownership check: match `player.parent.email` against `current_user.email`. **Existing infrastructure:** - Player model has all needed fields: name, height, position, graduating_class, photo_url, current_school, target_schools, hometown, team_id, subscription_status, etc. - Photo upload endpoint already exists at `POST /upload/photo` (returns `photo_url`) - Team relationship exists on Player model (`player.team` with coach info) - Tenant slug resolution exists (`_get_tenant_or_404` in teams.py) ### File Targets Files to create: - `src/basketball_api/routes/players.py` — New router with GET and PATCH endpoints - `tests/test_players.py` — Tests for both endpoints + permission scenarios Files to modify: - `src/basketball_api/main.py` — Register the new players router Files NOT to touch: - `src/basketball_api/routes/roster.py` — Existing roster endpoints (don't break) - `src/basketball_api/routes/tryouts.py` — Existing tryout endpoints - `src/basketball_api/models.py` — Player model already has all needed fields - `src/basketball_api/auth.py` — Auth middleware already works ### Acceptance Criteria - [ ] `GET /api/players/{player_id}` returns full player profile including: name, height, position, graduating_class, current_school, target_schools, hometown, photo_url, division, tryout_number, subscription_status, team (name + coach name if assigned), parent info (name, email, phone) - [ ] `GET /api/players/{player_id}` requires authentication (any role: admin, coach, or player) - [ ] `GET /api/players/{player_id}` returns 404 for non-existent player - [ ] `PATCH /api/players/{player_id}` allows updating: height, position, graduating_class, current_school, target_schools, hometown, photo_url - [ ] `PATCH /api/players/{player_id}` with admin role can update any player - [ ] `PATCH /api/players/{player_id}` with player role can only update their own profile (parent email match). Returns 403 otherwise. - [ ] `PATCH /api/players/{player_id}` with coach role returns 403 (read-only) - [ ] `PATCH /api/players/{player_id}` returns the updated player profile - [ ] Read-only fields (team_id, subscription_status, tryout_number, division) cannot be changed via PATCH ### Test Expectations - [ ] Unit test: GET player by ID returns correct fields - [ ] Unit test: GET player includes team name and coach name when assigned - [ ] Unit test: GET player returns 404 for missing player - [ ] Unit test: GET player returns 401 without auth - [ ] Unit test: PATCH player updates editable fields - [ ] Unit test: PATCH player as admin updates any player - [ ] Unit test: PATCH player as player updates own profile (parent email match) - [ ] Unit test: PATCH player as player returns 403 for other player's profile - [ ] Unit test: PATCH player as coach returns 403 - [ ] Unit test: PATCH player ignores read-only fields - Run command: `cd ~/basketball-api && python -m pytest tests/test_players.py -v` ### Constraints - Follow existing route patterns — see `routes/teams.py` for style reference - Use Pydantic response models (match existing `RosterResponse`/`PlayerInfo` patterns) - Use `get_current_user` and `require_role` from `auth.py` - Player ownership check: `player.parent.email == current_user.email` (case-insensitive) - Route prefix: `/api/players` — register in `main.py` - Include team info in GET response via SQLAlchemy relationship (`player.team`) - Include parent info in GET response via relationship (`player.parent`) - Use `async` route handlers matching existing codebase pattern - Tenant scoping: players belong to a tenant. For now, don't require tenant_slug in URL — just validate the player exists. Cross-tenant access is OK for now since all users are in one tenant. ### Checklist - [ ] PR opened - [ ] Tests pass (`pytest tests/test_players.py -v`) - [ ] Full test suite passes (`pytest tests/ -v`) - [ ] No unrelated changes ### Related - `project-westside-basketball` — project this affects - Phase 11b (frontend profile page) depends on this
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/basketball-api#89
No description provided.