feat: expose contract_overrides on player profile PATCH endpoint #471
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!471
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "462-add-contract-overrides-to-profile-update"
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
One-line change: adds
contract_overrides: dict | NonetoPlayerProfileUpdateso admins can set per-player contract overrides via the existingPATCH /players/{id}endpoint.Changes
src/basketball_api/routes/players.py— addcontract_overridesfield toPlayerProfileUpdateschemaTest Plan
Review Checklist
Related Notes
Adds contract_overrides (JSONB) to PlayerProfileUpdate schema so admins can set per-player contract overrides via the existing PATCH /players/{id} endpoint. The column already exists on the model — this just makes it writable through the API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>PR #471 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / Pydantic
The change adds
contract_overrides: dict | None = NonetoPlayerProfileUpdate. The existingsetattrloop at line 218 will apply it to thePlayer.contract_overridesJSONB column (model line 281). Mechanically correct -- the field name matches the column, andmodel_dump(exclude_unset=True)ensures it is only applied when explicitly provided.Two concerns:
Untyped
dictaccepts arbitrary JSON. There is no Pydantic model or JSON schema constraining what goes intocontract_overrides. A caller can store any structure, including deeply nested or oversized payloads. A typed Pydantic model (even a permissive one with known keys) would be safer. This is a nit given the field is admin-facing, but worth noting.No field-level authorization. The PATCH endpoint (lines 200-219) allows any authenticated player-owner to update their own profile using ALL fields in
PlayerProfileUpdate. Before this PR, those fields were benign profile data (height, position, school, etc.). After this PR, a parent can setcontract_overrideson their own child's profile, modifying contract terms. Ifcontract_overridesis intended to be admin-only, a field-level guard is needed. If parents legitimately should be able to set this, then this is fine -- but the PR body says "admins can set per-player contract overrides," suggesting admin-only intent.BLOCKERS
No test coverage for the new field. The existing
TestUpdatePlayerProfileclass has tests for the other fields but zero tests forcontract_overrides. The PR's own Test Plan checkboxes are unchecked. Per BLOCKER criteria: new functionality with zero test coverage is a blocker. At minimum, need:contract_overridessets the column and persistscontract_overrides-- expected behavior (403 if admin-only, 200 if allowed)contract_overrides: nullclears the fieldAuthorization gap (conditional). If
contract_overridesis admin-only (as the PR body implies), then a non-admin player-owner can currently set it on their own profile via the sharedsetattrloop. This needs either a field-level guard in the endpoint or explicit confirmation that parent-set overrides are intentional. Marking as blocker because it affects contract integrity.NITS
dictis untyped. Consider a Pydantic model likeclass ContractOverrides(BaseModel): ...or at minimumdict[str, Any]with a size/depth validator to prevent abuse.contract_overridesis not included inPlayerProfileResponse, so the PATCH response will not echo back the value. The test plan item "Contract page renders merged config" cannot be verified via this endpoint's response alone.SOP COMPLIANCE
462-add-contract-overrides-to-profile-updatePROCESS OBSERVATIONS
Small, focused change with low change failure risk in isolation. The missing tests and authorization question elevate risk. One test file touch and one guard clause would close both gaps cleanly.
VERDICT: NOT APPROVED
Two blockers: (1) zero test coverage for the new field, (2) authorization gap needs resolution -- either add a field-level admin guard or explicitly document that parent-set overrides are intentional.
- Guard contract_overrides as admin-only in PATCH /players/{id} - Non-admins get 403 when trying to set contract_overrides - Add 2 tests: admin can set overrides, non-admin gets 403 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>PR #471 Re-Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / Pydantic
Schema addition (
PlayerProfileUpdate):contract_overrides: dict | None = None-- clean. The JSONB column already exists on the model (models.py:281, migration 032). The existingsetattrloop in the PATCH handler picks it up automatically with no further wiring.Authorization guard: The new
admin_only_fieldsset + intersection check at line 217-221 is correct. It fires after coach/owner checks and before thesetattrloop. The error message is specific ("Only admins can set contract_overrides") and returns 403. The pattern is extensible -- future admin-only fields just get added to the set.Role check:
"admin" not in user_rolesis consistent with the existing role checks in the same function (lines 204, 210). Roles are lowercased at line 201 ({r.lower() for r in user.roles}), so the string comparison is safe.Response model:
PlayerProfileResponseintentionally does not includecontract_overrides, so the field is write-only through this endpoint. Appropriate for an admin-facing override.BLOCKERS
None. Both blockers from the previous review are resolved:
Test coverage -- FIXED. Two tests added:
test_patch_admin_can_set_contract_overrides-- admin client sends realistic override payload, asserts 200.test_patch_non_admin_cannot_set_contract_overrides-- player client attempts to set overrides, asserts 403 with correct detail message.Authorization gap on contract_overrides -- FIXED. The
admin_only_fieldsguard at lines 217-221 prevents non-admin users from settingcontract_overrides. Guard fires before thesetattrloop.NITS
status_code == 200but does not verify the override was persisted (e.g., a follow-up GET or DB query). Low risk since thesetattr+db.commit()path is well-tested by other PATCH tests, but aresp.json()check or DB assertion would make the test self-documenting.dict | Noneis a loose type forcontract_overrides. A stricter Pydantic model (e.g.,ContractOverrideswith typed fields fortournaments,sections) would catch malformed payloads at the API boundary. Acceptable as-is since the contract rendering logic downstream validates structure, but worth considering as the feature matures.SOP COMPLIANCE
462-add-contract-overrides-to-profile-updatePROCESS OBSERVATIONS
Clean fix cycle. Previous review identified two concrete blockers, both addressed in a single follow-up commit. The admin-only guard pattern (
admin_only_fieldsset) is a good reusable pattern for future field-level authorization.VERDICT: APPROVED