v1.2.0 — FFLogs user-OAuth (archived + private reports via Gold tier)
- **`fflogs_user_auth` table** (single-row, `id=1`) stores the connected user's refresh token + cached access token + scope. Alembic migration `267acc60ac83`. - **`FFLogsClient` u…
Added — user-OAuth (authorization code) flow
fflogs_user_authtable (single-row,id=1) stores the connected user's refresh token + cached access token + scope. Alembic migration267acc60ac83.FFLogsClientuser-OAuth methods in ingest/fflogs.py:build_authorize_url(redirect_uri, state, scope),exchange_authorization_code(session, code, redirect_uri),refresh_user_token(session),graphql_user(session, query, variables),has_user_auth(session),user_auth_status(session). Refresh token rotates per FFLogs spec — we update the row on each refresh.FFLogsArchivedErrorsubclass ofFFLogsAPIError—graphql()raises this specifically when the response is the "report archived, use /user" paywall, so callers can distinguish it.graphql_with_archive_retry(session, query, variables)— convenience: try/client, fall back to/useronly onFFLogsArchivedErrorand only when a user is connected. Used by bothingest/events.py::ingest_events_for_report(watched-report path, T-101) andjobs/backfill_field.py::_ingest_fight_events(field backfill, T-201). Default path stays cheap; the fallback only fires when needed.- API endpoints in api/main.py:
GET /auth/fflogs/login— generates an OAuth state, redirects to FFLogs consent screen.GET /auth/fflogs/callback— receives code, exchanges for tokens, persists, redirects to/#fflogs-connected.GET /api/fflogs-auth/status— returns connection state (drives UI).DELETE /api/fflogs-auth/connection— disconnect (deletes the row).
- React UI — new FFLogsAuthStatus.jsx component mounted in the header: shows "FFLogs Gold: Connect" (link to
/auth/fflogs/login) when disconnected, "FFLogs Gold ✓" with a disconnect option when connected. Auto-clears the#fflogs-connectedhash after return. - New config:
FFLOGS_REDIRECT_URIenv var (defaulthttp://127.0.0.1:8800/auth/fflogs/callback). Must match what's registered on the FFLogs OAuth client config page. - 13 new unit tests in
tests/test_fflogs_user_oauth.pycovering: authorize URL shape, state randomness, code exchange + token persistence, malformed responses, refresh-token usage, refresh on expired token, archived-error subclass detection, archive-retry fallback to /user, propagation when no user auth, has_user_auth and status payloads. Test isolation viahttpx.MockTransport. 308 tests total (295 → 308).
Live AC — re-backfill of TOP + DSR after user connected
- Pre-OAuth (v1.1.1 deep paginated backfill on TOP + DSR): TOP 4 kills_w_events, DSR 1.
- Post-OAuth re-run (same
--encounters 1068 1065 --reports-per-encounter 25 --events-top-n 15): TOP 4 → 15, DSR 1 → 15. Zero errors in the run log. +11 archived reports retrieved for TOP (+199k events), +14 for DSR (+260k events). Total runtime 153s. - Both ultimates now have enough kill-with-events coverage (≥3) for T-104 cross-pull consensus →
fight_modelfor TOP and DSR is buildable.
Operator setup (one-time, you-side, not code)
- Open https://www.fflogs.com/api/clients/ → edit existing API client.
- Add to "Redirect URLs":
http://127.0.0.1:8800/auth/fflogs/callback. Save. - In the dashboard header, click "FFLogs Gold: Connect" → Approve on FFLogs → redirected back, status shows "FFLogs Gold ✓".
- Run any backfill / Poll-now / watched-report poll as normal; archived/private reports automatically retry via /user.