feat: Admin endpoint for coach invite link generation #24
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!24
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/23-admin-invite-coach"
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
POST /admin/invite-coach, an auth-gated admin endpoint that creates a Coach record with an invite token and returns a full invite URLChanges
src/basketball_api/routes/admin.py(new): Admin router withPOST /invite-coachendpoint. Accepts{name, email, role, tenant_id}, creates/updates Coach record using existinggenerate_invite_token()andgenerate_invite_expiry(), builds invite URL fromsettings.base_urlsrc/basketball_api/main.py: Registers admin router at/adminprefixtests/test_admin.py(new): Five tests covering happy path (201 + persisted coach), idempotent regeneration, 409 for signed coaches, 403 for non-admin, and 401 for unauthenticated requestsTest Plan
coach_id,invite_url,expires_atReview Checklist
Related
plan-2026-03-08-tryout-prep— Phase 2aReview-fix round 1: Fixed B904 lint violation in
admin.py-- changed bareraise HTTPException(...)insideexcept ValueErrortoraise ... from excto properly chain the exception.PR #24 Review
BLOCKERS
1. PR will overwrite existing admin module and destroy the
generate-tokensendpointThe
mainbranch already containssrc/basketball_api/routes/admin.py(withPOST /admin/generate-tokensandrequire_admindependency) andtests/test_admin.py(with 4 tests for token generation). The PR diff shows both files asnew file(index0000000), meaning the PR branch was forked from a state ofmainbefore those files existed.If merged, this PR will replace the existing
admin.pyandtest_admin.pyentirely, deleting thegenerate-tokensendpoint and its tests. Forgejo reportsmergeable: true, which likely means the merge would succeed silently -- making this especially dangerous.Fix: Rebase the branch onto current
mainand add theinvite-coachendpoint alongside the existinggenerate-tokensendpoint in the sameadmin.pyfile. The test file similarly needs to be additive, not a replacement.2. Tests import
pal_e_auth.tokens.create_access_tokenandpal_e_auth.models.Role-- inconsistent with codebase test patternsThe existing test suite (
tests/test_admin.pyonmain,tests/conftest.py) usesMagicMock(spec=User)with FastAPI dependency overrides to simulate auth. The PR's tests instead importcreate_access_tokenandRolefrompal_e_authinternals to construct real JWTs. This pattern:pal_e_authinternal API surface (thetokensandmodelssubmodules)pal_e_authchanges its internal structureAfter rebasing, the new tests should follow the existing mock-based auth pattern (see
tests/test_admin.pyonmainfor the established approach usingrequire_admindependency override).NITS
rolefield default value:InviteCoachRequest.roledefaults to"assistant". This is reasonable but worth confirming it matches the desired business default. TheCoachmodel already defaults toassistantviaserver_default, so they are consistent.No email validation on
InviteCoachRequest.email: Theemailfield is typed asstr, notpydantic.EmailStr. Consider usingEmailStrfor basic format validation, consistent with how other endpoints might handle email input. Non-blocking.CoachRolevalidation is manual: The endpoint manually catchesValueErrorfromCoachRole(body.role)and returns 422. An alternative would be to type therolefield asCoachRoledirectly in the Pydantic model, letting FastAPI/Pydantic handle validation automatically. This is a style preference, not a correctness issue.Response
expires_atis a string, not a datetime:InviteCoachResponse.expires_atis typed asstrand populated via.isoformat(). Usingdatetimein the response model would let FastAPI serialize it consistently. Minor style point.SOP COMPLIANCE
feature/23-admin-invite-coachreferences issue #23)plan-2026-03-08-tryout-prep)routes/coach.pyis not touchedfeat:convention)generate_invite_token()andgenerate_invite_expiry()fromservices/coach_onboarding.pyconfig.base_urlfor URL building (viasettings.base_url)require_role("admin")frompal_e_authCode Quality (within the diff itself)
The endpoint logic is correct against the acceptance criteria:
POST /admin/invite-coachaccepts the specified JSON fields and returns the specified response shapecoach_id)require_role("admin"){base_url}/coach/onboard?token={token}secrets.token_urlsafe, no injection vectors)The code itself is well-structured. The blocker is purely about the rebase/merge conflict with existing code on
main.VERDICT: NOT APPROVED
The PR must be rebased onto current
mainto avoid destroying the existinggenerate-tokensadmin endpoint and its tests. Theinvite-coachendpoint code and logic are correct, but the branch is stale and would cause data loss on merge. After rebasing and adopting the existing mock-based test pattern, this should be ready for re-review.3154ecbe21f97cc6d99ePR #24 Review
Re-review after rebase onto current main.
BLOCKERS
None. Both previous blockers are resolved:
admin.pynow contains BOTHPOST /admin/generate-tokens(lines 32-77, from PR #22) and the newPOST /admin/invite-coach(lines 93-175). No overwrite.MagicMock(spec=User)+app.dependency_overrides(lines 43-58 oftest_admin.py), matching the existing test suite pattern. No real JWTs.NITS
Redundant auth tests:
test_returns_403_for_non_admin(line 378) andtest_returns_401_without_token(line 393) are functionally identical -- both use the unauthenticatedclientfixture, send the same request, and assertstatus_code in (401, 403). Consider collapsing to one test, or making them meaningfully different (e.g., one sends a non-admin JWT via acoach_clientfixture).secretsimport unused in new code:admin.pyimportssecrets(line 4) which is used bygenerate_tokensfrom PR #22, not byinvite_coach. Not a problem, just noting it's inherited context.VERIFICATION
admin.pyhas BOTH endpoints:generate_tokens(line 32) andinvite_coach(line 93) -- PASSMagicMock(spec=User)) -- PASSchanged_files: 2(admin.py + test_admin.py only) -- PASSgenerate_invite_token()andgenerate_invite_expiry()fromservices/coach_onboarding.py-- PASSconfig.base_urlfor URL building (_build_invite_url, line 178) -- PASSSOP COMPLIANCE
feature/23-admin-invite-coachreferences issue #23)plan-2026-03-08-tryout-prep)VERDICT: APPROVED