feat: add settings API service and IPTV settings provider with configurable options.

This commit is contained in:
Michael committed 2025-12-08 22:58:43 +01:00
1 parent 80d93469f8
commit 4f8f613f0a
3 files changed
+52 -37

No files matched your search

+8 -7
View File
@@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart'; import 'package:shelf_router/shelf_router.dart';
import '../database/database.dart'; import '../database/database.dart';
import '../models/user.dart';
class SettingsHandler { class SettingsHandler {
final AppDatabase db; final AppDatabase db;
@@ -14,12 +13,13 @@ class SettingsHandler {
// GET /api/settings // GET /api/settings
router.get('/', (Request req) { router.get('/', (Request req) {
final user = req.context['user'] as User?; // Get userId from context (set by auth middleware)
if (user == null) { final userId = req.context['userId'] as String?;
if (userId == null) {
return Response.forbidden(jsonEncode({'error': 'Authentication required'})); return Response.forbidden(jsonEncode({'error': 'Authentication required'}));
} }
final settingsJson = db.getUserSettings(user.id); final settingsJson = db.getUserSettings(userId);
// If no settings exist, return empty object or default // If no settings exist, return empty object or default
return Response.ok( return Response.ok(
@@ -30,8 +30,9 @@ class SettingsHandler {
// POST /api/settings // POST /api/settings
router.post('/', (Request req) async { router.post('/', (Request req) async {
final user = req.context['user'] as User?; // Get userId from context (set by auth middleware)
if (user == null) { final userId = req.context['userId'] as String?;
if (userId == null) {
return Response.forbidden(jsonEncode({'error': 'Authentication required'})); return Response.forbidden(jsonEncode({'error': 'Authentication required'}));
} }
@@ -44,7 +45,7 @@ class SettingsHandler {
return Response.badRequest(body: jsonEncode({'error': 'Invalid JSON'})); return Response.badRequest(body: jsonEncode({'error': 'Invalid JSON'}));
} }
db.updateUserSettings(user.id, body); db.updateUserSettings(userId, body);
return Response.ok( return Response.ok(
body, body,
+37 -28
View File
@@ -1,50 +1,59 @@
import 'dart:convert'; import '../api/api_client.dart';
import 'package:http/http.dart' as http;
import 'dart:html' as html;
import '../models/app_user.dart';
/// Service for user settings API
class SettingsApiService { class SettingsApiService {
final String _baseUrl = html.window.location.origin; final ApiClient _api = ApiClient();
/// Get settings from API
Future<Map<String, dynamic>?> getSettings() async { Future<Map<String, dynamic>?> getSettings() async {
final token = html.window.localStorage['auth_token']; // Restore token from localStorage before making the request
if (token == null) return null; _api.restoreToken();
if (_api.getStoredToken() == null) {
print('[SettingsAPI] getSettings: No auth token found');
return null;
}
try { try {
final response = await http.get( print('[SettingsAPI] getSettings: Fetching settings...');
Uri.parse('$_baseUrl/api/settings'), final response = await _api.get('/api/settings');
headers: {
'Authorization': 'Bearer $token', print('[SettingsAPI] getSettings: Response status ${response.statusCode}');
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) { if (response.statusCode == 200) {
if (response.body.isEmpty) return {}; if (response.data == null) return {};
return jsonDecode(response.body) as Map<String, dynamic>; final decoded = response.data as Map<String, dynamic>;
print('[SettingsAPI] getSettings: Loaded ${decoded.length} settings');
return decoded;
} }
print('[SettingsAPI] getSettings: Failed with data: ${response.data}');
return null; return null;
} catch (e) { } catch (e) {
print('[SettingsAPI] getSettings: Exception: $e');
return null; return null;
} }
} }
/// Save settings to API
Future<bool> saveSettings(Map<String, dynamic> settings) async { Future<bool> saveSettings(Map<String, dynamic> settings) async {
final token = html.window.localStorage['auth_token']; // Restore token from localStorage before making the request
if (token == null) return false; _api.restoreToken();
if (_api.getStoredToken() == null) {
print('[SettingsAPI] saveSettings: No auth token found');
return false;
}
try { try {
final response = await http.post( print('[SettingsAPI] saveSettings: Saving ${settings.length} settings...');
Uri.parse('$_baseUrl/api/settings'), final response = await _api.post('/api/settings', data: settings);
headers: {
'Authorization': 'Bearer $token', print('[SettingsAPI] saveSettings: Response status ${response.statusCode}');
'Content-Type': 'application/json', if (response.statusCode != 200) {
}, print('[SettingsAPI] saveSettings: Failed with data: ${response.data}');
body: jsonEncode(settings), }
);
return response.statusCode == 200; return response.statusCode == 200;
} catch (e) { } catch (e) {
print('[SettingsAPI] saveSettings: Exception: $e');
return false; return false;
} }
} }
@@ -295,7 +295,10 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
Future<void> _saveToApi() async { Future<void> _saveToApi() async {
final authState = _ref.read(authProvider); final authState = _ref.read(authProvider);
if (!authState.isAuthenticated) return; if (!authState.isAuthenticated) {
print('[SettingsProvider] _saveToApi: Not authenticated, skipping save');
return;
}
// Create map from state // Create map from state
final settingsMap = { final settingsMap = {
@@ -313,7 +316,9 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
_SettingsKeys.preferredAspectRatio: state.preferredAspectRatio, _SettingsKeys.preferredAspectRatio: state.preferredAspectRatio,
}; };
await _apiService.saveSettings(settingsMap); print('[SettingsProvider] _saveToApi: Saving settings...');
final success = await _apiService.saveSettings(settingsMap);
print('[SettingsProvider] _saveToApi: Save result: $success');
} }
// ===== Setters with Auto-Save ===== // ===== Setters with Auto-Save =====