feat: add contract signing backend with status tracking and endpoints #98
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!98
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "97-feat-add-contract-signing-backend-status"
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
Changes
src/basketball_api/models.py: addedContractStatusenum (none/offered/signed) and four new fields on Player:contract_status,contract_signed_at,contract_signed_by,contract_signed_ipalembic/versions/010_add_contract_fields.py: migration adding the contractstatus enum type and columns to the players table (down_revision: 009)src/basketball_api/routes/players.py: addedGET /players/{id}/contractandPOST /players/{id}/contractwith ContractSignRequest/ContractStatusResponse schemassrc/basketball_api/routes/account.py: addedcontract_statusto AccountPlayerResponsesrc/basketball_api/routes/admin.py: addedcontract_statusto AdminPlayerItemtests/test_contract.py: 14 new tests covering all acceptance criteriaTest Plan
pytest tests/ -v)Review Checklist
Related
plan-wkq-- Phase 14 (Program Contract Flow)Add ContractStatus enum (none/offered/signed) and contract signing fields to the Player model, following the same digital signature pattern as Parent.waiver_signed and Coach.contractor_agreement_signed. Includes POST/GET /players/{id}/contract endpoints with parent/admin auth, and updates /account/players and /admin/players responses to include contract_status. 14 new tests, all 333 tests pass. Closes #97 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>Self-Review: Pass
Reviewed the full diff (6 files, +549/-3). No issues found.
Model & Migration
ContractStatusenum and Player fields follow the exact pattern established bySubscriptionStatus/Parent.waiver_signed_*/Coach.contractor_agreement_*checkfirst=True, downgrade cleans up in reverse orderEndpoints
/players/{id}/contract-- any authenticated user, matchesget_player_profilepattern/players/{id}/contract-- admin or parent (email match), matchesupdate_player_profilepermission modelRequest.client.host, same pattern ascoach.pyaccept_agreementRoute ordering
/{player_id}/contractdefined after/{player_id}-- FastAPI correctly matches literalcontractsegment before wildcard, no conflictAccount/Admin responses
.valueaccessor, consistent with existingsubscription_statuspatternTests
test_players.pyandtest_account.pyNo secrets, no unrelated changes.
PR #98 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy / Alembic / Pydantic -- Python/FastAPI domain checklist applies.
Migration (010_add_contract_fields.py)
checkfirst=True, column additions withserver_default, and clean downgrade that drops columns then the enum type.down_revision = "009",revision = "010".String(200)forcontract_signed_by,String(45)for IP (IPv6-safe),DateTimefor timestamp.Model (ContractStatus enum + Player fields)
SubscriptionStatuspattern. Placement in the file is consistent (betweenSubscriptionStatusandOnboardingStatus).waiver_signed_at/waiver_signed_ip, the Coach model hascontractor_agreement_signed_at/contractor_agreement_ip. This PR applies the same pattern to Player contracts. Consistent.Endpoints (GET/POST /players/{id}/contract)
_is_player_ownerhelper from the same file -- no duplicated auth logic.if "admin" not in user_roles: if not _is_player_owner(...)) matches the existingupdate_player_profileendpoint exactly.request.client.host if request.client else "unknown") matches the waiver signing implementation inregister.py:865-868.datetime.now(timezone.utc)-- correct.Account + Admin response changes
contract_status: str) to bothAccountPlayerResponseandAdminPlayerItem. Minimal surface area change, consistent with howsubscription_statuswas added.SQLAlchemy patterns
joinedloadfor relationship loading (via existing_get_player_or_404helper).db.commit()+db.refresh()after mutation.BLOCKERS
None.
signature_nameis a Pydanticstrfield (type-validated). SQL injection is not possible -- SQLAlchemy ORM uses parameterized queries throughout. No XSS risk since this is a JSON API (no HTML rendering of user input)._is_player_ownerhelper.NITS
signature_namehas no length constraint -- TheContractSignRequest.signature_namefield is a barestrwith nomin_lengthormax_length. The DB column isString(200). A name longer than 200 characters would cause a database-level truncation or error depending on the PostgresStringbehavior. Consider addingField(min_length=1, max_length=200)to match the column constraint and provide a clean Pydantic validation error instead of a DB error. This also prevents empty-string signatures.Duplicated
ContractStatusResponseconstruction -- The response object is built identically in bothget_contract_statusandsign_contract(lines 238-246 and 312-320). A small_contract_status_response(player)helper would eliminate the duplication, matching the existing_player_profile_responsepattern already in this file. Non-blocking since both occurrences are in the same file.GET /players/{id}/contract allows any authenticated user -- The GET endpoint has no ownership check: any authenticated user can query any player's contract status. This matches the existing
get_player_profileendpoint's behavior (also no ownership restriction on GET), so it is consistent. But worth flagging as a design choice to revisit if contract data becomes more sensitive.SOP COMPLIANCE
97-feat-add-contract-signing-backend-statusreferences issue #97)plan-wkqPhase 14) and parent issue (westside-app#39)tests/test_contract.py)PROCESS OBSERVATIONS
alembic downgrade 009is clean.*_signed_at,*_signed_ip,*_signed_by). Good consistency.VERDICT: APPROVED