feat: Team model + assignment endpoints (Phase 10a) #81

Closed
opened 2026-03-14 22:16:02 +00:00 by forgejo_admin · 0 comments

Lineage

plan-2026-03-08-tryout-prep → Phase 10 (Team Placement) → Phase 10a (Backend)

Repo

forgejo_admin/basketball-api

User Story

As an admin
I want to create teams, assign players to them, and assign coaches to teams
So that coaches see only their team's roster and players know which team they're on

Context

Tryouts are done (53 players, 45 ready). Auth is live (Keycloak IdP, Phase 5 COMPLETED). Now Marcus needs to assign players to teams before the season starts. Currently there is no Team model — Player and Coach exist but have no relationship to each other. This phase adds the Team entity as the bridge between players and coaches.

Key decisions from planning:

  • Teams have division (boys/girls) and age_group (U10, U12, U14)
  • One coach per team (nullable FK)
  • Players get a team_id FK (nullable — unassigned players are valid)
  • Admin-only write access for team CRUD and player assignment
  • Coach can view only their own team via email match to Keycloak identity
  • Multi-tenant: all queries scoped by tenant_id

File Targets

Files the agent should modify or create:

  • src/basketball_api/models.py — add Team model, add team_id FK to Player model
  • alembic/versions/xxx_add_team_model.py — new migration: Team table + Player.team_id FK
  • src/basketball_api/routes/teams.py — new route file: CRUD, assignment, overview, coach-filtered endpoints
  • src/basketball_api/main.py — register the teams router
  • tests/test_teams.py — new test file for all team endpoints

Files the agent should NOT touch:

  • src/basketball_api/routes/register.py — registration flow is separate
  • src/basketball_api/routes/webhooks.py — Stripe webhooks are separate
  • src/basketball_api/auth.py — auth is already working, use existing require_role() pattern

Acceptance Criteria

  • Team model exists: id, tenant_id (FK), name, division, age_group, coach_id (FK to coaches, nullable), created_at
  • Player model has team_id (FK to teams, nullable)
  • Alembic migration creates teams table and adds Player.team_id
  • POST /api/teams creates a team (admin only)
  • GET /api/teams lists all teams with player counts and coach name (admin only)
  • GET /api/teams/{team_id} returns team detail with full player roster (admin only)
  • PATCH /api/teams/{team_id} updates team fields (admin only)
  • DELETE /api/teams/{team_id} deletes team, unassigns players (admin only)
  • POST /api/teams/{team_id}/players assigns player(s) to team via {"player_ids": [...]} (admin only)
  • DELETE /api/teams/{team_id}/players/{player_id} unassigns player (admin only)
  • GET /api/teams/mine returns team(s) for authenticated coach (matches Keycloak email to Coach.email)
  • GET /api/teams/overview returns admin summary: all teams, player counts, unassigned count, coach info

Test Expectations

  • Unit test: create team, verify fields
  • Unit test: assign player to team, verify team_id set
  • Unit test: unassign player, verify team_id null
  • Unit test: delete team, verify players unassigned (not deleted)
  • Unit test: coach GET /api/teams/mine returns only their team
  • Unit test: non-admin cannot create/modify teams (403)
  • Unit test: overview endpoint returns correct counts
  • Run command: pytest tests/test_teams.py -v

Constraints

  • Follow existing auth pattern: require_role("admin") from src/basketball_api/auth.py
  • Follow existing route style (see src/basketball_api/routes/admin.py and roster.py)
  • Follow existing model style (see Player, Coach in models.py)
  • All queries must be scoped by tenant_id (multi-tenant)
  • Use SQLAlchemy relationships for Team↔Player and Team↔Coach
  • Latest migration is e09c9e678004 (add division column) — chain from that
  • Run ruff format before committing

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • westside-basketball — project
  • Phase 10b (westside-app draft board UI) is blocked by this
  • Phase 11 (Player Profiles), Phase 12 (Stats), Phase 13 (Recruiter) all depend on Phase 10
### Lineage `plan-2026-03-08-tryout-prep` → Phase 10 (Team Placement) → Phase 10a (Backend) ### Repo `forgejo_admin/basketball-api` ### User Story As an admin I want to create teams, assign players to them, and assign coaches to teams So that coaches see only their team's roster and players know which team they're on ### Context Tryouts are done (53 players, 45 ready). Auth is live (Keycloak IdP, Phase 5 COMPLETED). Now Marcus needs to assign players to teams before the season starts. Currently there is no Team model — Player and Coach exist but have no relationship to each other. This phase adds the Team entity as the bridge between players and coaches. Key decisions from planning: - Teams have division (boys/girls) and age_group (U10, U12, U14) - One coach per team (nullable FK) - Players get a team_id FK (nullable — unassigned players are valid) - Admin-only write access for team CRUD and player assignment - Coach can view only their own team via email match to Keycloak identity - Multi-tenant: all queries scoped by tenant_id ### File Targets Files the agent should modify or create: - `src/basketball_api/models.py` — add Team model, add `team_id` FK to Player model - `alembic/versions/xxx_add_team_model.py` — new migration: Team table + Player.team_id FK - `src/basketball_api/routes/teams.py` — new route file: CRUD, assignment, overview, coach-filtered endpoints - `src/basketball_api/main.py` — register the teams router - `tests/test_teams.py` — new test file for all team endpoints Files the agent should NOT touch: - `src/basketball_api/routes/register.py` — registration flow is separate - `src/basketball_api/routes/webhooks.py` — Stripe webhooks are separate - `src/basketball_api/auth.py` — auth is already working, use existing `require_role()` pattern ### Acceptance Criteria - [ ] Team model exists: id, tenant_id (FK), name, division, age_group, coach_id (FK to coaches, nullable), created_at - [ ] Player model has team_id (FK to teams, nullable) - [ ] Alembic migration creates teams table and adds Player.team_id - [ ] `POST /api/teams` creates a team (admin only) - [ ] `GET /api/teams` lists all teams with player counts and coach name (admin only) - [ ] `GET /api/teams/{team_id}` returns team detail with full player roster (admin only) - [ ] `PATCH /api/teams/{team_id}` updates team fields (admin only) - [ ] `DELETE /api/teams/{team_id}` deletes team, unassigns players (admin only) - [ ] `POST /api/teams/{team_id}/players` assigns player(s) to team via `{"player_ids": [...]}` (admin only) - [ ] `DELETE /api/teams/{team_id}/players/{player_id}` unassigns player (admin only) - [ ] `GET /api/teams/mine` returns team(s) for authenticated coach (matches Keycloak email to Coach.email) - [ ] `GET /api/teams/overview` returns admin summary: all teams, player counts, unassigned count, coach info ### Test Expectations - [ ] Unit test: create team, verify fields - [ ] Unit test: assign player to team, verify team_id set - [ ] Unit test: unassign player, verify team_id null - [ ] Unit test: delete team, verify players unassigned (not deleted) - [ ] Unit test: coach GET /api/teams/mine returns only their team - [ ] Unit test: non-admin cannot create/modify teams (403) - [ ] Unit test: overview endpoint returns correct counts - Run command: `pytest tests/test_teams.py -v` ### Constraints - Follow existing auth pattern: `require_role("admin")` from `src/basketball_api/auth.py` - Follow existing route style (see `src/basketball_api/routes/admin.py` and `roster.py`) - Follow existing model style (see Player, Coach in `models.py`) - All queries must be scoped by `tenant_id` (multi-tenant) - Use SQLAlchemy relationships for Team↔Player and Team↔Coach - Latest migration is `e09c9e678004` (add division column) — chain from that - Run `ruff format` before committing ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `westside-basketball` — project - Phase 10b (westside-app draft board UI) is blocked by this - Phase 11 (Player Profiles), Phase 12 (Stats), Phase 13 (Recruiter) all depend on Phase 10
forgejo_admin 2026-03-14 22:34:01 +00:00
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#81
No description provided.