feat: add bulk visibility toggle for admin players #218
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!218
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "217-bulk-visibility"
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
PATCH /admin/players/bulk-visibilityendpoint that accepts a list of player IDs and anis_publicflag, bulk-updating visibility in a single atomic operation. Returns 404 with no changes if any player ID is missing.Changes
src/basketball_api/routes/admin.py— AddedBulkVisibilityRequest/BulkVisibilityResponsemodels andbulk_toggle_visibilityendpoint. Placed before/players/{player_id}/visibilityto avoid FastAPI path parameter conflicts.tests/test_bulk_visibility.py— 5 tests covering bulk update (3 players), set-false, empty list (400), nonexistent player (404 atomic), and non-admin rejection (403).Test Plan
python -m pytest tests/test_bulk_visibility.py -v— all 5 passpython -m pytest tests/test_player_visibility.py -v— all 7 existing tests still pass (no regressions)ruff formatandruff checkcleanReview Checklist
Related
Closes #217
Related Notes
N/A — no pal-e-docs notes for this change.
PR #218 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Pydantic
Files changed: 2 (204 additions, 0 deletions)
Route ordering -- Verified.
@router.patch("/players/bulk-visibility")is defined at line 922, before@router.patch("/players/{player_id}/visibility")at line 976. FastAPI path parameter conflicts are avoided.Admin auth -- Verified. Uses
user: User = Depends(require_admin)whererequire_admin = require_role("admin")is the module-level dependency shared by all admin endpoints. No DRY violation, no auth bypass path.Atomic behavior -- Verified. The flow is: (1) query players via
IN, (2) compare count to input length, (3) raise 404 if mismatch (before any mutation), (4) mutate and commit only if all players found. The 404 path never touchesis_public, so no rollback is needed -- changes simply never happen.Input validation -- Pydantic enforces
player_ids: list[int]andis_public: boolat the model level. Empty list is explicitly guarded with a 400. SQLAlchemy uses parameterized queries for theINclause. No SQL injection risk.Pydantic models --
BulkVisibilityRequestandBulkVisibilityResponseare clean, minimal, and correctly typed. Response includesupdatedcount, sortedplayer_ids, and the appliedis_publicvalue. Good.Logging -- Audit log includes player count, visibility value, and admin email. Appropriate for an admin mutation endpoint.
SQLAlchemy patterns --
.query().filter().all()is standard. Session commit is called once after all mutations. No N+1 issues.Test coverage (5 tests):
test_bulk_update_three_playersis_public=True, verifies response and DB persistencetest_bulk_update_set_falseis_public=Falsetest_empty_list_returns_400player_idslist rejectedtest_nonexistent_player_returns_404_no_changestest_non_admin_rejectedTest fixtures are well-structured:
admin_clientoverrides bothget_dbandrequire_admin,client(from conftest) overrides onlyget_dbso real auth rejects.BLOCKERS
None.
NITS
Duplicate player_ids edge case -- If the caller sends
player_ids: [1, 1, 1], the SQLINquery returns 1 row, butlen(payload.player_ids)is 3. This triggers a misleading 404 with"Players not found: []"(empty missing list sinceset(payload.player_ids) - found_idsis empty after dedup). Two options: (a) deduplicatepayload.player_idsat the top of the function (payload.player_ids = list(set(payload.player_ids))), or (b) add a Pydantic validator that rejects duplicates. Either way, a test for this case would be good. Non-blocking since real callers are unlikely to send duplicates, but the empty-missing-list 404 is confusing if they do.Consider
db.flush()instead ofdb.commit()for testability -- The current pattern works, but usingdb.flush()+ letting the request lifecycle handle commit would be more consistent with transactional patterns in larger apps. Very minor, not blocking.SOP COMPLIANCE
217-bulk-visibilityfollows{issue-number}-{kebab-case-purpose}conventionCloses #217presentPROCESS OBSERVATIONS
Clean, focused PR. Single endpoint + comprehensive tests. The existing single-player visibility endpoint (
/players/{player_id}/visibility) is untouched and its 7 tests are reported as still passing. Route ordering was considered and handled correctly. Good deployment frequency signal -- small, reviewable, mergeable.VERDICT: APPROVED