Phase 2a: Admin endpoint for coach invite link generation #23

Closed
opened 2026-03-10 00:36:26 +00:00 by forgejo_admin · 0 comments

Lineage

plan-2026-03-08-tryout-prep → Phase 2 → Phase 2a (coach invite endpoint)

Repo

forgejo_admin/basketball-api

User Story

As Marcus (via Lucas as admin),
I want to create a coach invite link by providing a coach's name and email,
So that I can text/send the link to coaches and they can onboard themselves.

Context

Phase 2 (PR #9) delivered the full coach onboarding flow — contractor agreement form, Stripe Connect Express setup, status page, token TTL. But it never delivered a way to create Coach records and generate invite URLs. The service layer has generate_invite_token() and generate_invite_expiry() ready to go — nothing calls them from a route.

Marcus needs to give Lucas a coach's name + email. Lucas hits the admin endpoint, gets back a URL, Marcus passes it to the coach. That's it.

The Coach model already has invite_token (String(100), unique) and invite_expires_at (DateTime) columns from migration 003. No new migration needed.

File Targets

Files to modify or create:

  • src/basketball_api/routes/admin.py — may already exist from Phase 3a PR #22 (has generate-tokens endpoint). Add POST /admin/invite-coach here.
  • src/basketball_api/main.py — register admin router if not already registered
  • tests/test_admin.py — tests for the new endpoint

Files NOT to touch:

  • src/basketball_api/routes/coach.py — onboarding flow is complete, don't modify
  • src/basketball_api/services/coach_onboarding.py — service functions already exist, just call them
  • alembic/versions/ — no migration needed, columns already exist

Acceptance Criteria

  • POST /admin/invite-coach accepts JSON {name, email, role, tenant_id} and returns {coach_id, invite_url, expires_at}
  • Endpoint is auth-gated with require_role("admin") from pal-e-auth
  • Invite URL format: {config.base_url}/coach/onboard?token={token}
  • Token generated via generate_invite_token(), expiry via generate_invite_expiry() (7-day TTL)
  • Idempotent: re-inviting same email+tenant regenerates token + TTL if coach hasn't signed yet
  • If coach already signed contractor agreement, return 409 Conflict (don't reset their onboarding)
  • Non-admin gets 403

Test Expectations

  • Happy path: POST with valid data → 201, returns coach_id + invite_url + expires_at
  • Idempotent: POST same email+tenant twice → second call regenerates token, returns new URL
  • Already signed: POST for coach who signed agreement → 409
  • Auth: request without admin role → 403
  • Run command: pytest tests/test_admin.py -k invite_coach

Constraints

  • Follow existing admin route patterns (check if admin.py already exists on main or in PR #22 branch)
  • Use existing service functions — generate_invite_token() and generate_invite_expiry() from services/coach_onboarding.py
  • Use config.base_url for building the full invite URL
  • Match existing code style (FastAPI dependency injection, SQLAlchemy session via get_db)

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • project-westside-basketball — project this affects
  • PR #9 — Phase 2 (coach onboarding flow)
  • Issue #19 / PR #22 — Phase 3a (may have admin.py with generate-tokens endpoint)
### Lineage `plan-2026-03-08-tryout-prep` → Phase 2 → Phase 2a (coach invite endpoint) ### Repo `forgejo_admin/basketball-api` ### User Story As Marcus (via Lucas as admin), I want to create a coach invite link by providing a coach's name and email, So that I can text/send the link to coaches and they can onboard themselves. ### Context Phase 2 (PR #9) delivered the full coach onboarding flow — contractor agreement form, Stripe Connect Express setup, status page, token TTL. But it never delivered a way to **create** Coach records and generate invite URLs. The service layer has `generate_invite_token()` and `generate_invite_expiry()` ready to go — nothing calls them from a route. Marcus needs to give Lucas a coach's name + email. Lucas hits the admin endpoint, gets back a URL, Marcus passes it to the coach. That's it. The Coach model already has `invite_token` (String(100), unique) and `invite_expires_at` (DateTime) columns from migration 003. No new migration needed. ### File Targets Files to modify or create: - `src/basketball_api/routes/admin.py` — may already exist from Phase 3a PR #22 (has `generate-tokens` endpoint). Add `POST /admin/invite-coach` here. - `src/basketball_api/main.py` — register admin router if not already registered - `tests/test_admin.py` — tests for the new endpoint Files NOT to touch: - `src/basketball_api/routes/coach.py` — onboarding flow is complete, don't modify - `src/basketball_api/services/coach_onboarding.py` — service functions already exist, just call them - `alembic/versions/` — no migration needed, columns already exist ### Acceptance Criteria - [ ] `POST /admin/invite-coach` accepts JSON `{name, email, role, tenant_id}` and returns `{coach_id, invite_url, expires_at}` - [ ] Endpoint is auth-gated with `require_role("admin")` from pal-e-auth - [ ] Invite URL format: `{config.base_url}/coach/onboard?token={token}` - [ ] Token generated via `generate_invite_token()`, expiry via `generate_invite_expiry()` (7-day TTL) - [ ] Idempotent: re-inviting same email+tenant regenerates token + TTL if coach hasn't signed yet - [ ] If coach already signed contractor agreement, return 409 Conflict (don't reset their onboarding) - [ ] Non-admin gets 403 ### Test Expectations - [ ] Happy path: POST with valid data → 201, returns coach_id + invite_url + expires_at - [ ] Idempotent: POST same email+tenant twice → second call regenerates token, returns new URL - [ ] Already signed: POST for coach who signed agreement → 409 - [ ] Auth: request without admin role → 403 - Run command: `pytest tests/test_admin.py -k invite_coach` ### Constraints - Follow existing admin route patterns (check if `admin.py` already exists on main or in PR #22 branch) - Use existing service functions — `generate_invite_token()` and `generate_invite_expiry()` from `services/coach_onboarding.py` - Use `config.base_url` for building the full invite URL - Match existing code style (FastAPI dependency injection, SQLAlchemy session via `get_db`) ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `project-westside-basketball` — project this affects - PR #9 — Phase 2 (coach onboarding flow) - Issue #19 / PR #22 — Phase 3a (may have `admin.py` with `generate-tokens` endpoint)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/basketball-api#23
No description provided.