Feature: Seed Gmail OAuth token into oauth_tokens DB table for resilience #242

Open
opened 2026-03-29 06:47:36 +00:00 by forgejo_admin · 1 comment
Contributor

Type

Feature

Lineage

Discovered scope — identified during westside-landing #169 password reset investigation. The oauth_tokens DB table is empty, forcing fallback to file-based tokens that expire without self-renewal.

Repo

forgejo_admin/basketball-api

User Story

As a superadmin, I want Gmail OAuth tokens stored in the database so that the email service survives pod restarts without manual token rotation.

Context

The basketball-api email service uses Gmail OAuth. Currently:

  • oauth_tokens DB table has 0 rows
  • Code falls back to file-based token (gmail-westsidebasketball.json) mounted from k8s secret
  • File-based tokens cannot self-refresh after pod restarts — when the refresh token expires (7-day lifetime), email silently breaks
  • This caused the #169 password reset outage (token last refreshed March 21, refresh token expired March 28)

File Targets

  • src/basketball_api/services/email.py — Gmail OAuth token loading logic
  • src/basketball_api/models/oauth_tokens table model
  • Migration script to seed the token from the mounted file on startup

Test Expectations

  • Unit test: token loaded from DB when available
  • Unit test: fallback to file when DB empty
  • Integration test: token refresh writes back to DB

Constraints

  • Must not break existing email sending during migration
  • File-based token remains as fallback
  • No changes to k8s secret mounting (that stays as bootstrap source)

Acceptance Criteria

  • oauth_tokens table seeded with current Gmail token on startup or via migration
  • Token refresh logic uses DB store as primary, file as fallback
  • Pod restart does not break email sending
  • Monitoring/logging when token refresh fails

Checklist

  • PR opened
  • No unrelated changes
  • forgejo_admin/westside-landing#169 — password reset outage caused by this gap
  • project-westside-basketball
### Type Feature ### Lineage Discovered scope — identified during westside-landing #169 password reset investigation. The `oauth_tokens` DB table is empty, forcing fallback to file-based tokens that expire without self-renewal. ### Repo `forgejo_admin/basketball-api` ### User Story As a superadmin, I want Gmail OAuth tokens stored in the database so that the email service survives pod restarts without manual token rotation. ### Context The basketball-api email service uses Gmail OAuth. Currently: - `oauth_tokens` DB table has 0 rows - Code falls back to file-based token (`gmail-westsidebasketball.json`) mounted from k8s secret - File-based tokens cannot self-refresh after pod restarts — when the refresh token expires (7-day lifetime), email silently breaks - This caused the #169 password reset outage (token last refreshed March 21, refresh token expired March 28) ### File Targets - `src/basketball_api/services/email.py` — Gmail OAuth token loading logic - `src/basketball_api/models/` — `oauth_tokens` table model - Migration script to seed the token from the mounted file on startup ### Test Expectations - [ ] Unit test: token loaded from DB when available - [ ] Unit test: fallback to file when DB empty - [ ] Integration test: token refresh writes back to DB ### Constraints - Must not break existing email sending during migration - File-based token remains as fallback - No changes to k8s secret mounting (that stays as bootstrap source) ### Acceptance Criteria - [ ] `oauth_tokens` table seeded with current Gmail token on startup or via migration - [ ] Token refresh logic uses DB store as primary, file as fallback - [ ] Pod restart does not break email sending - [ ] Monitoring/logging when token refresh fails ### Checklist - [ ] PR opened - [ ] No unrelated changes ### Related - `forgejo_admin/westside-landing#169` — password reset outage caused by this gap - `project-westside-basketball`
Author
Contributor

Scope Review: APPROVED

Review note: review-657-2026-03-29

Scope is valid and issue is well-structured. All template sections present, traceability triangle complete (story:WS-S21, arch:basketball-api, Forgejo issue open).

Key finding: The code-level feature is already fully implemented by spike #130 (board item #229, done). The token_store.py module, OAuthToken model, migration 021, seed script, and 12 tests all exist. The remaining work is purely operational — seed the prod oauth_tokens table.

Recommendations (all [BODY]):

  • Fix file path: src/basketball_api/models/src/basketball_api/models.py (single file, not directory)
  • Reframe scope: code feature is done (spike #130). Remaining work = ops task (run seed script against prod). Consider changing Type to Task.
  • Add existing file targets: services/token_store.py, scripts/seed_oauth_token.py, tests/test_token_store.py
## Scope Review: APPROVED Review note: `review-657-2026-03-29` Scope is valid and issue is well-structured. All template sections present, traceability triangle complete (story:WS-S21, arch:basketball-api, Forgejo issue open). **Key finding**: The code-level feature is already fully implemented by spike #130 (board item #229, done). The `token_store.py` module, `OAuthToken` model, migration 021, seed script, and 12 tests all exist. The remaining work is purely operational — seed the prod `oauth_tokens` table. **Recommendations (all `[BODY]`):** - Fix file path: `src/basketball_api/models/` → `src/basketball_api/models.py` (single file, not directory) - Reframe scope: code feature is done (spike #130). Remaining work = ops task (run seed script against prod). Consider changing Type to Task. - Add existing file targets: `services/token_store.py`, `scripts/seed_oauth_token.py`, `tests/test_token_store.py`
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
ldraney/basketball-api#242
No description provided.