From 7681aa4c1f4363b18a6df4411b1b7c0a95230ff2 Mon Sep 17 00:00:00 2001 From: ssbg04 Date: Sun, 26 Jul 2026 14:10:54 +0800 Subject: [PATCH] Fix stale VPS URL and token expiration when switching servers --- frontend/lib/core/network/api_constants.dart | 25 ++++++++++++++++++- .../repositories/activity_repository.dart | 2 +- .../repositories/archive_repository.dart | 2 +- .../domain/repositories/auth_repository.dart | 2 +- .../repositories/backup_repository.dart | 3 +-- .../repositories/dashboard_repository.dart | 2 +- .../repositories/document_repository.dart | 2 +- .../repositories/notification_repository.dart | 2 +- .../domain/repositories/ocr_repository.dart | 3 +-- .../repositories/report_repository.dart | 2 +- .../domain/repositories/setup_repository.dart | 2 +- .../repositories/student_repository.dart | 2 +- .../domain/repositories/user_repository.dart | 2 +- .../lib/ui/providers/student_provider.dart | 2 +- .../documents/widgets/upload_ocr_modal.dart | 6 ++--- 15 files changed, 39 insertions(+), 20 deletions(-) diff --git a/frontend/lib/core/network/api_constants.dart b/frontend/lib/core/network/api_constants.dart index 187a7b3..8d5a308 100644 --- a/frontend/lib/core/network/api_constants.dart +++ b/frontend/lib/core/network/api_constants.dart @@ -1,3 +1,6 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + class ApiConstants { static const int port = 18484; static const String vpsUrl = 'http://198.252.107.197:$port/api'; @@ -12,6 +15,26 @@ class ApiConstants { static void setBaseUrl(String url) { // Strip trailing slash then append /api final clean = url.replaceAll(RegExp(r'/+$'), ''); - _baseUrl = clean.endsWith('/api') ? clean : '$clean/api'; + final newUrl = clean.endsWith('/api') ? clean : '$clean/api'; + if (_baseUrl != newUrl) { + _baseUrl = newUrl; + // Clear stored JWT token whenever server URL changes to prevent "token expired" on different server + const FlutterSecureStorage().delete(key: 'jwt_token'); + const FlutterSecureStorage().delete(key: 'remember_me'); + } + } + + /// Creates a Dio client that dynamically uses the active [baseUrl] on every request. + static Dio createDio([BaseOptions? options]) { + final dio = Dio(options ?? BaseOptions(baseUrl: _baseUrl)); + dio.interceptors.add( + InterceptorsWrapper( + onRequest: (options, handler) { + options.baseUrl = _baseUrl; + return handler.next(options); + }, + ), + ); + return dio; } } diff --git a/frontend/lib/domain/repositories/activity_repository.dart b/frontend/lib/domain/repositories/activity_repository.dart index 702a79a..7febb32 100644 --- a/frontend/lib/domain/repositories/activity_repository.dart +++ b/frontend/lib/domain/repositories/activity_repository.dart @@ -5,7 +5,7 @@ import '../entities/dashboard_models.dart'; /// Repository for paginated user history (admin-only). class ActivityRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future _getAuthOptions() async { diff --git a/frontend/lib/domain/repositories/archive_repository.dart b/frontend/lib/domain/repositories/archive_repository.dart index 8d6dc41..0137ebf 100644 --- a/frontend/lib/domain/repositories/archive_repository.dart +++ b/frontend/lib/domain/repositories/archive_repository.dart @@ -35,7 +35,7 @@ class ArchivePage { } class ArchiveRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future _getAuthOptions() async { diff --git a/frontend/lib/domain/repositories/auth_repository.dart b/frontend/lib/domain/repositories/auth_repository.dart index 70050ba..a4a6081 100644 --- a/frontend/lib/domain/repositories/auth_repository.dart +++ b/frontend/lib/domain/repositories/auth_repository.dart @@ -4,7 +4,7 @@ import '../../domain/entities/user_model.dart'; import '../../core/network/api_constants.dart'; class AuthRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); static const _tokenKey = 'jwt_token'; diff --git a/frontend/lib/domain/repositories/backup_repository.dart b/frontend/lib/domain/repositories/backup_repository.dart index bb48260..bc09f0e 100644 --- a/frontend/lib/domain/repositories/backup_repository.dart +++ b/frontend/lib/domain/repositories/backup_repository.dart @@ -4,9 +4,8 @@ import '../../core/network/api_constants.dart'; import 'dart:io'; class BackupRepository { - final Dio _dio = Dio( + final Dio _dio = ApiConstants.createDio( BaseOptions( - baseUrl: ApiConstants.baseUrl, connectTimeout: const Duration(seconds: 120), receiveTimeout: const Duration(seconds: 120), // Zipping might take time ), diff --git a/frontend/lib/domain/repositories/dashboard_repository.dart b/frontend/lib/domain/repositories/dashboard_repository.dart index 6c1e5d9..7111a77 100644 --- a/frontend/lib/domain/repositories/dashboard_repository.dart +++ b/frontend/lib/domain/repositories/dashboard_repository.dart @@ -4,7 +4,7 @@ import '../../core/network/api_constants.dart'; import '../entities/dashboard_models.dart'; class DashboardRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future _getAuthOptions() async { diff --git a/frontend/lib/domain/repositories/document_repository.dart b/frontend/lib/domain/repositories/document_repository.dart index 8b6582e..08ec451 100644 --- a/frontend/lib/domain/repositories/document_repository.dart +++ b/frontend/lib/domain/repositories/document_repository.dart @@ -195,7 +195,7 @@ class MissingRequirements { } class DocumentRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future _getAuthOptions() async { diff --git a/frontend/lib/domain/repositories/notification_repository.dart b/frontend/lib/domain/repositories/notification_repository.dart index 9a2b49c..d64eb5d 100644 --- a/frontend/lib/domain/repositories/notification_repository.dart +++ b/frontend/lib/domain/repositories/notification_repository.dart @@ -4,7 +4,7 @@ import '../../core/network/api_constants.dart'; import '../entities/notification_model.dart'; class NotificationRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future _getAuthOptions() async { diff --git a/frontend/lib/domain/repositories/ocr_repository.dart b/frontend/lib/domain/repositories/ocr_repository.dart index 2a8bbd5..5d8eed5 100644 --- a/frontend/lib/domain/repositories/ocr_repository.dart +++ b/frontend/lib/domain/repositories/ocr_repository.dart @@ -5,9 +5,8 @@ import '../../core/network/api_constants.dart'; import '../entities/ocr_result_model.dart'; class OcrRepository { - final Dio _dio = Dio( + final Dio _dio = ApiConstants.createDio( BaseOptions( - baseUrl: ApiConstants.baseUrl, connectTimeout: const Duration(seconds: 60), // OCR takes time receiveTimeout: const Duration(seconds: 60), ), diff --git a/frontend/lib/domain/repositories/report_repository.dart b/frontend/lib/domain/repositories/report_repository.dart index a509c0b..ef5a2e0 100644 --- a/frontend/lib/domain/repositories/report_repository.dart +++ b/frontend/lib/domain/repositories/report_repository.dart @@ -4,7 +4,7 @@ import '../../core/network/api_constants.dart'; import '../entities/report_models.dart'; class ReportRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future _authOptions() async { diff --git a/frontend/lib/domain/repositories/setup_repository.dart b/frontend/lib/domain/repositories/setup_repository.dart index 2cdd3b4..a04cb8a 100644 --- a/frontend/lib/domain/repositories/setup_repository.dart +++ b/frontend/lib/domain/repositories/setup_repository.dart @@ -4,7 +4,7 @@ import '../../core/network/api_constants.dart'; import '../entities/setup_models.dart'; class SetupRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future _getAuthOptions() async { diff --git a/frontend/lib/domain/repositories/student_repository.dart b/frontend/lib/domain/repositories/student_repository.dart index e65130f..7387165 100644 --- a/frontend/lib/domain/repositories/student_repository.dart +++ b/frontend/lib/domain/repositories/student_repository.dart @@ -36,7 +36,7 @@ class StudentPage { } class StudentRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); IO.Socket? _socket; diff --git a/frontend/lib/domain/repositories/user_repository.dart b/frontend/lib/domain/repositories/user_repository.dart index 57717f6..24686a7 100644 --- a/frontend/lib/domain/repositories/user_repository.dart +++ b/frontend/lib/domain/repositories/user_repository.dart @@ -4,7 +4,7 @@ import '../../core/network/api_constants.dart'; import '../entities/system_user.dart'; class UserRepository { - final Dio _dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final Dio _dio = ApiConstants.createDio(); final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future _getAuthOptions() async { diff --git a/frontend/lib/ui/providers/student_provider.dart b/frontend/lib/ui/providers/student_provider.dart index 809fb91..713310b 100644 --- a/frontend/lib/ui/providers/student_provider.dart +++ b/frontend/lib/ui/providers/student_provider.dart @@ -44,7 +44,7 @@ final academicYearsProvider = FutureProvider.autoDispose>(( ) async { final storage = const FlutterSecureStorage(); final token = await storage.read(key: 'jwt_token'); - final dio = Dio(BaseOptions(baseUrl: ApiConstants.baseUrl)); + final dio = ApiConstants.createDio(); try { final response = await dio.get( diff --git a/frontend/lib/ui/screens/documents/widgets/upload_ocr_modal.dart b/frontend/lib/ui/screens/documents/widgets/upload_ocr_modal.dart index 41a4e09..1353e5c 100644 --- a/frontend/lib/ui/screens/documents/widgets/upload_ocr_modal.dart +++ b/frontend/lib/ui/screens/documents/widgets/upload_ocr_modal.dart @@ -185,8 +185,7 @@ class _UploadOcrModalState extends ConsumerState { try { final storage = const FlutterSecureStorage(); final token = await storage.read(key: 'jwt_token'); - final dio = Dio(BaseOptions( - baseUrl: ApiConstants.baseUrl, + final dio = ApiConstants.createDio(BaseOptions( headers: {'Authorization': 'Bearer $token'}, )); @@ -396,8 +395,7 @@ class _UploadOcrModalState extends ConsumerState { try { final storage = const FlutterSecureStorage(); final token = await storage.read(key: 'jwt_token'); - final dio = Dio(BaseOptions( - baseUrl: ApiConstants.baseUrl, + final dio = ApiConstants.createDio(BaseOptions( headers: {'Authorization': 'Bearer $token'}, ));