summaryrefslogtreecommitdiff
path: root/player-android/lib/api
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-22 23:16:14 +0300
committerPaul Buetow <paul@buetow.org>2026-05-22 23:16:14 +0300
commit1c912d5feee218ef46a295c05652f2dc3bd4d1d5 (patch)
tree2358d0cffa9c58fabe162b24f6ecb0510898a41b /player-android/lib/api
parentc2944be8708f4bb9c687679b4ed63e1398b83f05 (diff)
Add migration for missing playback_progress.finished column (5f)
Existing databases created before the finished column was added to the base schema still satisfied CREATE TABLE IF NOT EXISTS and never got the column, causing GET /api/v1/media/{id} to fail with "SQL logic error: no such column: finished" and break media detail view. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'player-android/lib/api')
-rw-r--r--player-android/lib/api/dio_client.dart26
1 files changed, 22 insertions, 4 deletions
diff --git a/player-android/lib/api/dio_client.dart b/player-android/lib/api/dio_client.dart
index 35db589..c988cf0 100644
--- a/player-android/lib/api/dio_client.dart
+++ b/player-android/lib/api/dio_client.dart
@@ -1,6 +1,9 @@
+import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
+import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:go_router/go_router.dart';
// Storage key under which the bearer token is persisted across app restarts.
const _kTokenKey = 'bearer_token';
@@ -89,9 +92,15 @@ class _UnauthorizedInterceptor extends Interceptor {
// Purge the stale token so subsequent requests start unauthenticated.
await _storage.deleteToken();
- // Use the navigator key to redirect without needing a BuildContext.
- _navigatorKey.currentState
- ?.pushNamedAndRemoveUntil(_loginRoute, (_) => false);
+ // Redirect via go_router (the app's router) rather than the classic
+ // Navigator. pushNamedAndRemoveUntil would throw "Navigator.onGenerateRoute
+ // was null" because go_router does not register named routes on the
+ // underlying Navigator. Using the navigatorKey's currentContext lets us
+ // resolve the active GoRouter instance without a widget-tree BuildContext.
+ final ctx = _navigatorKey.currentContext;
+ if (ctx != null && ctx.mounted) {
+ GoRouter.of(ctx).go(_loginRoute);
+ }
}
handler.next(err);
}
@@ -138,9 +147,18 @@ class DioClient {
responseType: ResponseType.json,
);
+ // The server's /api/v1/auth/login sets an HttpOnly Set-Cookie (session=...).
+ // Browsers persist this automatically; on mobile we attach a CookieJar so
+ // Dio replays the cookie on subsequent requests. Without this, every call
+ // after login returns 401 because Dio discards cookies by default.
+ // In-memory is sufficient: logout clears it, and we persist the bearer
+ // token (for API-token auth) separately via flutter_secure_storage.
+ final cookieJar = CookieJar();
return Dio(options)
..interceptors.addAll([
- // Auth must run before the 401 handler so the token is attached first.
+ // Cookie manager runs first so the session cookie is replayed before
+ // _AuthInterceptor decides whether to add a Bearer fallback.
+ CookieManager(cookieJar),
_AuthInterceptor(storage),
_UnauthorizedInterceptor(
storage: storage,