Bug: GET /teams/{id} returns 422 — tenant_id not auto-resolved like admin routes #372

Closed
opened 2026-04-07 17:04:07 +00:00 by forgejo_admin · 3 comments

Type

Bug

Lineage

Standalone — discovered during live CRM monitoring session 2026-04-07. Marcus reported "CRM is down" while browsing on iPhone.

Repo

forgejo_admin/basketball-api

What Broke

All 9 endpoints in src/basketball_api/routes/teams.py require tenant_id as a mandatory query parameter, but the frontend never sends it. This causes 422 Unprocessable Entity on any direct team page access.

Admin routes (admin.py:680) auto-resolve via db.query(Tenant).first(). Schedule routes (schedule.py) use a _get_tenant helper with DEFAULT_TENANT_SLUG. The teams.py router is the only one that doesn't auto-resolve — it's an inconsistency, not a design choice.

Confirmed affected endpoints (all 9 in teams.py):

  • GET /teams (list)
  • GET /teams/overview
  • GET /teams/mine
  • GET /teams/{team_id} (detail) ← Marcus hit this one
  • POST /teams (create)
  • PATCH /teams/{team_id} (update)
  • DELETE /teams/{team_id}
  • POST /teams/{team_id}/players (assign)
  • DELETE /teams/{team_id}/players/{player_id} (unassign)

Repro Steps

  1. Log in as admin (Marcus) on iPhone Safari
  2. Navigate to admin dashboard — works (200)
  3. Tap any team card linking to /teams/{id}
  4. Observe: 422 error, page shows "Could not load team data"

Frontend call at westside-landing/src/routes/(app)/teams/[id]/+page.svelte:13:

team = await apiFetch(`/teams/${id}`);  // no tenant_id param

Expected Behavior

Team detail page loads with roster, coaches, and team info. All teams endpoints work without explicit tenant_id (auto-resolved like admin and schedule routes).

Environment

  • Cluster/namespace: prod / basketball-api
  • Service version/commit: basketball-api-6d89b9fc4f-ldjvc (running 16h)
  • Related alerts: none (no 422 alerting configured)

Acceptance Criteria

  • All 9 teams endpoints make tenant_id optional with auto-resolve fallback
  • Adopt schedule.py's _get_tenant helper pattern with DEFAULT_TENANT_SLUG
  • Existing callers that pass tenant_id explicitly still work (backwards compatible)
  • Tests updated for all 9 endpoints — both with and without tenant_id
  • No regressions in existing team CRUD operations

File Targets

  • src/basketball_api/routes/teams.py — make tenant_id optional on all 9 endpoints, add _get_tenant helper
  • tests/test_teams.py — add no-tenant_id test cases for each endpoint
  • westside-basketball — project this affects
  • forgejo_admin/westside-landing — frontend that calls these endpoints without tenant_id
  • schedule.py _get_tenant helper — reference implementation for the fix pattern
### Type Bug ### Lineage Standalone — discovered during live CRM monitoring session 2026-04-07. Marcus reported "CRM is down" while browsing on iPhone. ### Repo `forgejo_admin/basketball-api` ### What Broke All 9 endpoints in `src/basketball_api/routes/teams.py` require `tenant_id` as a mandatory query parameter, but the frontend never sends it. This causes **422 Unprocessable Entity** on any direct team page access. Admin routes (`admin.py:680`) auto-resolve via `db.query(Tenant).first()`. Schedule routes (`schedule.py`) use a `_get_tenant` helper with `DEFAULT_TENANT_SLUG`. The `teams.py` router is the only one that doesn't auto-resolve — it's an inconsistency, not a design choice. Confirmed affected endpoints (all 9 in teams.py): - `GET /teams` (list) - `GET /teams/overview` - `GET /teams/mine` - `GET /teams/{team_id}` (detail) ← Marcus hit this one - `POST /teams` (create) - `PATCH /teams/{team_id}` (update) - `DELETE /teams/{team_id}` - `POST /teams/{team_id}/players` (assign) - `DELETE /teams/{team_id}/players/{player_id}` (unassign) ### Repro Steps 1. Log in as admin (Marcus) on iPhone Safari 2. Navigate to admin dashboard — works (200) 3. Tap any team card linking to `/teams/{id}` 4. Observe: 422 error, page shows "Could not load team data" Frontend call at `westside-landing/src/routes/(app)/teams/[id]/+page.svelte:13`: ```js team = await apiFetch(`/teams/${id}`); // no tenant_id param ``` ### Expected Behavior Team detail page loads with roster, coaches, and team info. All teams endpoints work without explicit `tenant_id` (auto-resolved like admin and schedule routes). ### Environment - Cluster/namespace: prod / basketball-api - Service version/commit: `basketball-api-6d89b9fc4f-ldjvc` (running 16h) - Related alerts: none (no 422 alerting configured) ### Acceptance Criteria - [ ] All 9 teams endpoints make `tenant_id` optional with auto-resolve fallback - [ ] Adopt `schedule.py`'s `_get_tenant` helper pattern with `DEFAULT_TENANT_SLUG` - [ ] Existing callers that pass `tenant_id` explicitly still work (backwards compatible) - [ ] Tests updated for all 9 endpoints — both with and without `tenant_id` - [ ] No regressions in existing team CRUD operations ### File Targets - `src/basketball_api/routes/teams.py` — make `tenant_id` optional on all 9 endpoints, add `_get_tenant` helper - `tests/test_teams.py` — add no-tenant_id test cases for each endpoint ### Related - `westside-basketball` — project this affects - `forgejo_admin/westside-landing` — frontend that calls these endpoints without tenant_id - `schedule.py` `_get_tenant` helper — reference implementation for the fix pattern
Author
Owner

Scope Review: NEEDS_REFINEMENT

Review note: review-877-2026-04-07

Ticket has a mismatched story label and missing arch note. Blast radius is wider than title suggests.

Issues found:

  • [LABEL] story:WS-S1 is about IaC deployment, not API behavior. Change to story:WS-S6 (admin team management) or another appropriate story.
  • [SCOPE] No architecture note arch-basketball-api exists in pal-e-docs. Create it.
  • [SCOPE] Title says "GET /teams/{id}" but ALL 9 routes in teams.py require explicit tenant_id query param. Clarify whether the fix covers 1 route or all 9.
  • [SCOPE] If all 9 routes, verify it fits within the 5-minute rule or decompose.
## Scope Review: NEEDS_REFINEMENT Review note: `review-877-2026-04-07` Ticket has a mismatched story label and missing arch note. Blast radius is wider than title suggests. **Issues found:** - **[LABEL]** story:WS-S1 is about IaC deployment, not API behavior. Change to story:WS-S6 (admin team management) or another appropriate story. - **[SCOPE]** No architecture note `arch-basketball-api` exists in pal-e-docs. Create it. - **[SCOPE]** Title says "GET /teams/{id}" but ALL 9 routes in `teams.py` require explicit `tenant_id` query param. Clarify whether the fix covers 1 route or all 9. - **[SCOPE]** If all 9 routes, verify it fits within the 5-minute rule or decompose.
Author
Owner

Scope refinement (post review-877-2026-04-07):

  • Expanded scope from 1 endpoint to all 9 routes in teams.py — codebase analysis confirmed all require explicit tenant_id
  • Added file targets: teams.py + test_teams.py
  • Referenced schedule.py's _get_tenant helper as the fix pattern
  • Fixed board label: WS-S1WS-S10 (admin manages coaches/teams)
**Scope refinement (post review-877-2026-04-07):** - Expanded scope from 1 endpoint to all 9 routes in `teams.py` — codebase analysis confirmed all require explicit `tenant_id` - Added file targets: `teams.py` + `test_teams.py` - Referenced `schedule.py`'s `_get_tenant` helper as the fix pattern - Fixed board label: `WS-S1` → `WS-S10` (admin manages coaches/teams)
Author
Owner

Scope Review: READY (re-review)

Review note: review-877-2026-04-07

All 4 refinement items from the previous NEEDS_REFINEMENT review have been addressed:

  • story label corrected to WS-S10 (verified in project-westside-basketball user stories)
  • Scope expanded to all routes in teams.py (8 query-param routes + 1 body-param route)
  • File targets specified: teams.py + test_teams.py
  • Fix pattern referenced: schedule.py _get_tenant helper using DEFAULT_TENANT_SLUG

Note for implementing agent: The scope says "all 9 routes" but create_team (POST "") takes tenant_id in the request body (TeamCreate model), not as a query param. The query-param fix applies to 8 routes. create_team is a judgment call -- body-based tenant_id for creation is reasonable to keep.

No decomposition needed -- mechanical transformation across 2 files, estimated 3-5 minutes.

## Scope Review: READY (re-review) Review note: `review-877-2026-04-07` All 4 refinement items from the previous NEEDS_REFINEMENT review have been addressed: - story label corrected to WS-S10 (verified in project-westside-basketball user stories) - Scope expanded to all routes in teams.py (8 query-param routes + 1 body-param route) - File targets specified: teams.py + test_teams.py - Fix pattern referenced: schedule.py `_get_tenant` helper using `DEFAULT_TENANT_SLUG` **Note for implementing agent:** The scope says "all 9 routes" but `create_team` (POST "") takes `tenant_id` in the request body (TeamCreate model), not as a query param. The query-param fix applies to 8 routes. `create_team` is a judgment call -- body-based tenant_id for creation is reasonable to keep. No decomposition needed -- mechanical transformation across 2 files, estimated 3-5 minutes.
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#372
No description provided.