import 'dart:convert'; import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import '../config.dart'; import '../models.dart'; import 'auth_service.dart'; /// Client de l'API GameManager (Bearer JWT pocket-id). class ApiService { ApiService(this._auth); final AuthService _auth; Future get baseUrl async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('api_base') ?? Config.defaultApiBase; } Future setBaseUrl(String url) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('api_base', url.trim().replaceAll(RegExp(r'/$'), '')); } Future> _headers() async { final token = await _auth.validToken(); return {'Authorization': 'Bearer ${token ?? ''}', 'Accept': 'application/json'}; } Future> servers() async { final base = await baseUrl; final res = await http.get(Uri.parse('$base/api/servers'), headers: await _headers()); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode != 200) throw ApiException('Erreur ${res.statusCode}'); final list = jsonDecode(res.body) as List; return list.map((e) => GameServer.fromJson(e as Map)).toList(); } Future server(String id) async { final base = await baseUrl; final res = await http.get(Uri.parse('$base/api/servers/$id'), headers: await _headers()); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode != 200) throw ApiException('Erreur ${res.statusCode}'); return GameServer.fromJson(jsonDecode(res.body) as Map); } Future> containers() async { final base = await baseUrl; final res = await http.get(Uri.parse('$base/api/containers'), headers: await _headers()); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode != 200) throw ApiException('Erreur ${res.statusCode}'); return (jsonDecode(res.body) as List) .map((e) => DockerContainer.fromJson(e as Map)) .toList(); } Future createServer(Map payload) async { final base = await baseUrl; final res = await http.post( Uri.parse('$base/api/servers'), headers: {...await _headers(), 'Content-Type': 'application/json'}, body: jsonEncode(payload), ); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode >= 400) { throw ApiException(_msg(res.body) ?? 'Création refusée (${res.statusCode})'); } return GameServer.fromJson(jsonDecode(res.body) as Map); } Future deleteServer(String id) async { final base = await baseUrl; final res = await http.delete(Uri.parse('$base/api/servers/$id'), headers: await _headers()); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode >= 400) { throw ApiException(_msg(res.body) ?? 'Suppression refusée (${res.statusCode})'); } } String? _msg(String body) { try { return (jsonDecode(body) as Map)['detail'] as String?; } catch (_) { return null; } } Future start(String id) => _action(id, 'start'); Future stop(String id) => _action(id, 'stop'); Future _action(String id, String action) async { final base = await baseUrl; final res = await http.post( Uri.parse('$base/api/servers/$id/$action'), headers: await _headers(), ); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode >= 400) throw ApiException('Action refusée (${res.statusCode})'); } Future iconUrl(String id) async => '${await baseUrl}/api/servers/$id/icon'; /// Stats d'utilisation du conteneur (CPU/RAM/réseau). `{running:false}` si éteint. Future> stats(String id) async { final base = await baseUrl; final res = await http.get(Uri.parse('$base/api/servers/$id/stats'), headers: await _headers()); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode != 200) throw ApiException('Erreur ${res.statusCode}'); return jsonDecode(res.body) as Map; } /// Image importée du serveur (bytes), null si aucune. Future imageBytes(String id) async { final base = await baseUrl; final res = await http.get(Uri.parse('$base/api/servers/$id/image'), headers: await _headers()); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode != 200) return null; return res.bodyBytes; } Future uploadImage(String id, String filePath) async { final base = await baseUrl; final token = await _auth.validToken(); final req = http.MultipartRequest('POST', Uri.parse('$base/api/servers/$id/image')) ..headers['Authorization'] = 'Bearer ${token ?? ''}' ..files.add(await http.MultipartFile.fromPath('file', filePath)); final res = await http.Response.fromStream(await req.send()); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode >= 400) throw ApiException('Envoi refusé (${res.statusCode})'); } Future deleteImage(String id) async { final base = await baseUrl; final res = await http.delete(Uri.parse('$base/api/servers/$id/image'), headers: await _headers()); if (res.statusCode == 401) throw UnauthorizedException(); if (res.statusCode >= 400) throw ApiException('Suppression refusée (${res.statusCode})'); } } class UnauthorizedException implements Exception {} class ApiException implements Exception { ApiException(this.message); final String message; @override String toString() => message; }