Basketball-API client with Keycloak auth #5

Closed
opened 2026-03-28 19:15:10 +00:00 by forgejo_admin · 3 comments

Type

Feature

Lineage

Depends on forgejo_admin/westside-ai-assistant #4 (scaffold). Keycloak client provisioned by #1.

Repo

forgejo_admin/westside-ai-assistant

User Story

As Marcus (admin)
I want the AI to authenticate to basketball-api and call its endpoints
So that the AI can read and write program data on my behalf

Context

The AI assistant needs an HTTP client that authenticates to basketball-api via Keycloak client credentials flow. The client westside-ai-bot in the westside-basketball realm has admin role (provisioned by ticket #1). The client caches tokens and refreshes 30s before expiry. Each basketball-api operation gets a typed function. Tenant slug is hardcoded to "westside-kings-queens" (the only tenant).

File Targets

Files the agent should create:

  • app/basketball.py — async httpx client class with Keycloak token management and operation functions
  • tests/test_basketball.py — unit tests with mocked HTTP responses

Files the agent should modify:

  • requirements.txt — ensure httpx is listed (should already be from #4)

Files the agent should NOT touch:

  • basketball-api repo (consumed, not modified)
  • app/groupme.py — wiring happens in #6

Acceptance Criteria

  • Client authenticates via Keycloak client credentials POST to {KEYCLOAK_REALM_URL}/protocol/openid-connect/token
  • Token cached, auto-refreshed 30s before expiry
  • All functions include Bearer token in Authorization header
  • HTTP errors raised as exceptions with status code and body
  • Tenant slug hardcoded to "westside-kings-queens"

Endpoint Reference Table

Reads:

Function Method Path
get_dashboard GET /admin/dashboard
list_players GET /admin/players
get_player GET /players/{player_id}
list_teams GET /admin/teams
get_roster GET /tenants/westside-kings-queens/roster
get_subscriptions_overview GET /api/subscriptions/overview
list_subscriptions GET /api/subscriptions

Writes:

Function Method Path
update_player PUT /players/{player_id}
assign_player_to_team POST /teams/{team_id}/players
remove_player_from_team DELETE /teams/{team_id}/players/{player_id}
toggle_player_visibility PATCH /admin/players/{player_id}/visibility
create_team POST /api/teams
checkin_player POST /api/roster/westside-kings-queens/check-in/{player_id}
bulk_assign_tryout_numbers POST /tryouts/admin/westside-kings-queens/assign-numbers

Test Expectations

  • Unit test: token acquisition mocked, verify POST to correct Keycloak URL
  • Unit test: token refresh triggered when expiry < 30s
  • Unit test: each operation calls correct HTTP method + path (per table above)
  • Unit test: Bearer header present on all requests
  • Run command: pytest tests/test_basketball.py -v

Constraints

  • Use httpx.AsyncClient for all HTTP calls
  • basketball-api internal URL: http://basketball-api.basketball-api.svc.cluster.local:8000
  • Keycloak token URL: {KEYCLOAK_REALM_URL}/protocol/openid-connect/token
  • Tenant slug: hardcoded "westside-kings-queens" (single-tenant V1)
  • Note: bulk_assign_tryout_numbers is a BULK operation (assigns all unassigned players), not per-player
  • Do NOT modify basketball-api — this is a client only

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • project-westside-ai-assistant — parent project
  • arch-domain-westside-ai-assistant — A3: Basketball-API Client
### Type Feature ### Lineage Depends on forgejo_admin/westside-ai-assistant #4 (scaffold). Keycloak client provisioned by #1. ### Repo `forgejo_admin/westside-ai-assistant` ### User Story As Marcus (admin) I want the AI to authenticate to basketball-api and call its endpoints So that the AI can read and write program data on my behalf ### Context The AI assistant needs an HTTP client that authenticates to basketball-api via Keycloak client credentials flow. The client `westside-ai-bot` in the `westside-basketball` realm has admin role (provisioned by ticket #1). The client caches tokens and refreshes 30s before expiry. Each basketball-api operation gets a typed function. Tenant slug is hardcoded to "westside-kings-queens" (the only tenant). ### File Targets Files the agent should create: - `app/basketball.py` — async httpx client class with Keycloak token management and operation functions - `tests/test_basketball.py` — unit tests with mocked HTTP responses Files the agent should modify: - `requirements.txt` — ensure httpx is listed (should already be from #4) Files the agent should NOT touch: - basketball-api repo (consumed, not modified) - `app/groupme.py` — wiring happens in #6 ### Acceptance Criteria - [ ] Client authenticates via Keycloak client credentials POST to `{KEYCLOAK_REALM_URL}/protocol/openid-connect/token` - [ ] Token cached, auto-refreshed 30s before expiry - [ ] All functions include Bearer token in Authorization header - [ ] HTTP errors raised as exceptions with status code and body - [ ] Tenant slug hardcoded to "westside-kings-queens" ### Endpoint Reference Table **Reads:** | Function | Method | Path | |----------|--------|------| | get_dashboard | GET | /admin/dashboard | | list_players | GET | /admin/players | | get_player | GET | /players/{player_id} | | list_teams | GET | /admin/teams | | get_roster | GET | /tenants/westside-kings-queens/roster | | get_subscriptions_overview | GET | /api/subscriptions/overview | | list_subscriptions | GET | /api/subscriptions | **Writes:** | Function | Method | Path | |----------|--------|------| | update_player | PUT | /players/{player_id} | | assign_player_to_team | POST | /teams/{team_id}/players | | remove_player_from_team | DELETE | /teams/{team_id}/players/{player_id} | | toggle_player_visibility | PATCH | /admin/players/{player_id}/visibility | | create_team | POST | /api/teams | | checkin_player | POST | /api/roster/westside-kings-queens/check-in/{player_id} | | bulk_assign_tryout_numbers | POST | /tryouts/admin/westside-kings-queens/assign-numbers | ### Test Expectations - [ ] Unit test: token acquisition mocked, verify POST to correct Keycloak URL - [ ] Unit test: token refresh triggered when expiry < 30s - [ ] Unit test: each operation calls correct HTTP method + path (per table above) - [ ] Unit test: Bearer header present on all requests - Run command: `pytest tests/test_basketball.py -v` ### Constraints - Use httpx.AsyncClient for all HTTP calls - basketball-api internal URL: http://basketball-api.basketball-api.svc.cluster.local:8000 - Keycloak token URL: {KEYCLOAK_REALM_URL}/protocol/openid-connect/token - Tenant slug: hardcoded "westside-kings-queens" (single-tenant V1) - Note: bulk_assign_tryout_numbers is a BULK operation (assigns all unassigned players), not per-player - Do NOT modify basketball-api — this is a client only ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `project-westside-ai-assistant` — parent project - `arch-domain-westside-ai-assistant` — A3: Basketball-API Client
Author
Owner

Scope Review: NEEDS_REFINEMENT

Review note: review-597-2026-03-28
Ticket is well-structured but missing critical endpoint mapping details that would cause an agent to guess URLs incorrectly.

  • Undocumented dependency: No ticket covers provisioning the westside-ai-bot Keycloak client. Clarify whether #1 handles this or if a new ticket is needed.
  • Missing tenant resolution strategy: Multiple basketball-api endpoints require tenant_id or tenant_slug params. Issue body doesn't specify how the client resolves tenant identity.
  • Endpoint path table needed: Agent needs exact HTTP method + full URL path for each function. Three different route prefix patterns exist (/admin/, /api/, /tenants/, /tryouts/).
  • assign_tryout_number is bulk: Actual endpoint bulk-assigns all unassigned players. Rename to assign_tryout_numbers (plural) and note bulk semantics.
## Scope Review: NEEDS_REFINEMENT Review note: `review-597-2026-03-28` Ticket is well-structured but missing critical endpoint mapping details that would cause an agent to guess URLs incorrectly. - **Undocumented dependency**: No ticket covers provisioning the `westside-ai-bot` Keycloak client. Clarify whether #1 handles this or if a new ticket is needed. - **Missing tenant resolution strategy**: Multiple basketball-api endpoints require `tenant_id` or `tenant_slug` params. Issue body doesn't specify how the client resolves tenant identity. - **Endpoint path table needed**: Agent needs exact HTTP method + full URL path for each function. Three different route prefix patterns exist (`/admin/`, `/api/`, `/tenants/`, `/tryouts/`). - **`assign_tryout_number` is bulk**: Actual endpoint bulk-assigns all unassigned players. Rename to `assign_tryout_numbers` (plural) and note bulk semantics.
Author
Owner

Scope refinement (review-597-2026-03-28):

  1. Added tenant resolution strategy: hardcoded "westside-kings-queens" (single-tenant V1)
  2. Added full endpoint reference table with verified HTTP methods and paths
  3. Renamed assign_tryout_number to bulk_assign_tryout_numbers — the real endpoint is bulk, not per-player
  4. Clarified Keycloak client provisioned by #1
**Scope refinement (review-597-2026-03-28):** 1. Added tenant resolution strategy: hardcoded "westside-kings-queens" (single-tenant V1) 2. Added full endpoint reference table with verified HTTP methods and paths 3. Renamed `assign_tryout_number` to `bulk_assign_tryout_numbers` — the real endpoint is bulk, not per-player 4. Clarified Keycloak client provisioned by #1
Author
Owner

Scope Review: READY

Review note: review-597-2026-03-28-r2

Re-review after refinement. All 4 previous NEEDS_REFINEMENT recommendations resolved:

  • Tenant resolution strategy documented (hardcoded slug)
  • Endpoint reference table added (14 operations verified against basketball-api codebase)
  • bulk_assign_tryout_numbers renamed + BULK note added
  • Keycloak client provisioning linked to #1

Three non-blocking nits noted for agent awareness:

  • POST /teams/{team_id}/players and DELETE /teams/{team_id}/players/{player_id} require ?tenant_id= query param
  • Some paths use SPA alias mounts (/teams/, /players/) vs canonical (/api/teams/, /api/players/) — both work
  • assign_player_to_team actual body schema is {"player_ids": [...]} (list)

Ticket is ready for todo -> next_up promotion.

## Scope Review: READY Review note: `review-597-2026-03-28-r2` Re-review after refinement. All 4 previous NEEDS_REFINEMENT recommendations resolved: - Tenant resolution strategy documented (hardcoded slug) - Endpoint reference table added (14 operations verified against basketball-api codebase) - `bulk_assign_tryout_numbers` renamed + BULK note added - Keycloak client provisioning linked to #1 Three non-blocking nits noted for agent awareness: - `POST /teams/{team_id}/players` and `DELETE /teams/{team_id}/players/{player_id}` require `?tenant_id=` query param - Some paths use SPA alias mounts (`/teams/`, `/players/`) vs canonical (`/api/teams/`, `/api/players/`) — both work - `assign_player_to_team` actual body schema is `{"player_ids": [...]}` (list) Ticket is ready for `todo -> next_up` promotion.
Sign in to join this conversation.
No labels
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/westside-ai-assistant#5
No description provided.