Files
EME_APP/eme-frontend/lib/services/api_client.dart
T
2026-07-23 13:38:20 +02:00

154 lines
4.2 KiB
Dart

import "dart:convert";
import "dart:typed_data";
import "package:http/http.dart" as http;
import "auth_service.dart";
/// Erreur levée par le client API (serveur injoignable ou réponse en échec).
class ApiException implements Exception {
const ApiException(this.message);
final String message;
@override
String toString() => message;
}
class ApiFile {
const ApiFile({
required this.bytes,
required this.fileName,
required this.contentType,
});
final Uint8List bytes;
final String fileName;
final String contentType;
}
/// Point d'accès unique au backend : centralise l'URL, l'authentification et
/// la gestion des erreurs.
class ApiClient {
ApiClient._();
static const String _baseUrl = "http://localhost:3000/api";
static Future<dynamic> get(String chemin) async {
final uri = Uri.parse("$_baseUrl$chemin");
late final http.Response reponse;
try {
final authHeaders = await AuthService.requestHeaders();
reponse = await http.get(uri, headers: authHeaders);
} catch (_) {
throw const ApiException(
"Impossible de joindre le serveur. Vérifiez que le backend est démarré.",
);
}
if (reponse.statusCode >= 200 && reponse.statusCode < 300) {
return jsonDecode(reponse.body);
}
throw ApiException(_messageErreur(reponse));
}
static Future<ApiFile> getFile(
String chemin, {
required String fallbackFileName,
}) async {
final uri = Uri.parse("$_baseUrl$chemin");
late final http.Response reponse;
try {
final authHeaders = await AuthService.requestHeaders();
reponse = await http.get(uri, headers: authHeaders);
} catch (_) {
throw const ApiException(
"Impossible de joindre le serveur. Vérifiez que le backend est démarré.",
);
}
if (reponse.statusCode < 200 || reponse.statusCode >= 300) {
throw ApiException(_messageErreur(reponse));
}
return ApiFile(
bytes: reponse.bodyBytes,
fileName: _fileName(reponse, fallbackFileName),
contentType:
reponse.headers["content-type"] ?? "application/octet-stream",
);
}
static Future<dynamic> post(String chemin, Map<String, dynamic> body) async {
final uri = Uri.parse("$_baseUrl$chemin");
late final http.Response reponse;
try {
final authHeaders = await AuthService.requestHeaders();
reponse = await http.post(
uri,
headers: {...authHeaders, "content-type": "application/json"},
body: jsonEncode(body),
);
} catch (_) {
throw const ApiException(
"Impossible de joindre le serveur. Vérifiez que le backend est démarré.",
);
}
if (reponse.statusCode >= 200 && reponse.statusCode < 300) {
return jsonDecode(reponse.body);
}
throw ApiException(_messageErreur(reponse));
}
static Future<dynamic> patch(String chemin, Map<String, dynamic> body) async {
final uri = Uri.parse("$_baseUrl$chemin");
late final http.Response reponse;
try {
final authHeaders = await AuthService.requestHeaders();
reponse = await http.patch(
uri,
headers: {...authHeaders, "content-type": "application/json"},
body: jsonEncode(body),
);
} catch (_) {
throw const ApiException(
"Impossible de joindre le serveur. Vérifiez que le backend est démarré.",
);
}
if (reponse.statusCode >= 200 && reponse.statusCode < 300) {
return jsonDecode(reponse.body);
}
throw ApiException(_messageErreur(reponse));
}
static String _messageErreur(http.Response reponse) {
try {
final corps = jsonDecode(reponse.body);
if (corps is Map && corps["error"] is Map) {
final message = (corps["error"] as Map)["message"];
if (message is String) {
return message;
}
}
} catch (_) {
// Corps non-JSON : on retombe sur le code HTTP.
}
return "Erreur ${reponse.statusCode}";
}
static String _fileName(http.Response reponse, String fallback) {
final disposition = reponse.headers["content-disposition"];
if (disposition == null) return fallback;
final match = RegExp(
"filename=\"?([^\";]+)\"?",
caseSensitive: false,
).firstMatch(disposition);
return match?.group(1) ?? fallback;
}
}