feat: wire /admin/teams GET + POST /admin/teams/save for SPA team management #122
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!122
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "121-admin-teams-spa-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
Adds two endpoints to the admin router for the SPA draft-board UI in westside-app.
GET /admin/teamsreturns all teams and players in the shape the frontend expects, andPOST /admin/teams/saveaccepts bulk player-to-team assignments including creating new teams from temporary IDs.Changes
src/basketball_api/routes/admin.py-- AddedGET /admin/teamsendpoint returning{ teams, players }with auto-resolved tenant, andPOST /admin/teams/saveendpoint accepting{ assignments: [{ player_id, team_id }] }with support for existing team assignment (int), new team creation (string starting witht), and unassignment (null)tests/test_admin_teams.py-- 17 new tests covering GET response shape, player/team data, empty state, coach presence/absence, POST assign/unassign/create/dedup/mixed operations, error handling, and auth enforcementsrc/basketball_api/routes/register.py-- Fixed pre-existing E501 lint violations (line length > 100)src/basketball_api/services/email.py-- Fixed pre-existing E501 lint violations in HTML template stringsTest Plan
pytest tests/test_admin_teams.py -vtest_players.py::test_patch_ignores_read_only_fieldsunrelated to this PR)ruff check .andruff format .cleanReview Checklist
westside-app/src/routes/admin/teams/+page.svelte{ teams: [{ id, name, coaches }], players: [{ id, name, division, position, team_id, team, height, photo_url, parent_name }] }Related
Wire two endpoints consumed by the westside-app team management page: - GET /admin/teams returns { teams, players } with auto-resolved tenant - POST /admin/teams/save accepts bulk assignments, creates temp teams, and handles assign/unassign/create in a single transaction Also fixes pre-existing E501 lint violations in register.py and email.py that were blocking the commit hook. Closes #121 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>Self-review complete. No issues found.
Verified:
PR #122 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Pydantic / pytest
Endpoints added:
GET /admin/teams-- returns all teams and players for the SPA draft-board UIPOST /admin/teams/save-- bulk save player-to-team assignments with temp team creationPydantic models: Well-structured.
TeamCoachItem,TeamItem,TeamPlayerItem,TeamsResponse,PlayerAssignment,TeamsSaveRequest,TeamsSaveResponse-- all use PEP 484 type hints and follow existing codebase patterns.Auth: Both endpoints use
require_admindependency (Keycloak JWT admin role check), consistent with all other admin endpoints. No DRY violation -- reuses the module-levelrequire_admin = require_role("admin")pattern.SQLAlchemy patterns:
joinedloadonTeam.coach,Player.parent,Player.team-- correct, avoids N+1.db.flush()for temp team ID resolution before commit -- correct pattern.db.commit()at the end of POST -- good transactional behavior.Tenant resolution: Uses
db.query(Tenant).first()rather than theDEFAULT_TENANT_SLUGfilter used by the email endpoints (lines 391, 431, 507). This is a deliberate "auto-resolve" for the single-tenant case, documented in the docstring. Acceptable given the single-tenant constraint, though it is a minor inconsistency.Input validation:
PlayerAssignment.team_idtyped asint | str | None-- Pydantic validates the union at the schema level.startswith("t")for temp IDs, elseint()cast with properValueError/TypeErrorhandling and 422 response.Potential concern -- cross-tenant authorization in POST: The
GET /admin/teamsendpoint correctly filters players byPlayer.tenant_id == tenant.id. However, thePOST /admin/teams/saveendpoint queriesPlayerby ID alone (Player.id == assignment.player_id) without filtering bytenant_id. In a single-tenant deployment this is fine. In a future multi-tenant scenario, an admin could theoretically reassign a player from another tenant. This is a nit given the current single-tenant reality, but worth noting.Lint fixes: The changes to
register.pyandemail.pyare legitimate E501 lint fixes (line length). Theemail.pychange also refactors an inline conditional into a variable (creds_html) for readability. All non-functional, formatting-only.BLOCKERS
None.
NITS
Tenant lookup inconsistency:
GET /admin/teamsandPOST /admin/teams/saveuseTenant.first()while other admin endpoints useTenant.slug == DEFAULT_TENANT_SLUG. Both work for single-tenant but the pattern divergence could confuse future contributors. Consider standardizing on one approach.Cross-tenant guard on POST:
POST /admin/teams/savedoes not filterPlayer.idbytenant_id. Not a risk today (single tenant) but adding.filter(Player.tenant_id == tenant.id)would be defensive.Temp team naming: New teams created from temp IDs get the raw temp ID as their name (e.g.,
t1710878400). The comment says "name can be renamed later" which is fine, but this means the team name is a timestamp string until manually renamed. Acceptable for draft-board workflow.PR body Related section: References
Closes #121but does not reference the plan slugplan-wkq. Per SOP, the Related section should include the plan slug.SOP COMPLIANCE
121-admin-teams-spa-endpoints(references #121)plan-wkqtests/test_admin_teams.pyregister.pyandemail.pyare pre-existing violations, documented as suchPROCESS OBSERVATIONS
admin_client,sample_data) follows existing codebase conventions fromconftest.py.VERDICT: APPROVED
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.