Integration fonctionnalités V1 ( resas + gestion machines
This commit is contained in:
@@ -9,7 +9,8 @@ class ApiClient {
|
||||
ApiClient({
|
||||
required this.baseUrl,
|
||||
this.getAccessToken,
|
||||
this.onUnauthorized,
|
||||
this.onRefreshToken,
|
||||
this.onSessionExpired,
|
||||
}) : _dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
@@ -31,9 +32,27 @@ class ApiClient {
|
||||
handler.next(options);
|
||||
},
|
||||
onError: (error, handler) async {
|
||||
if (error.response?.statusCode == 401) {
|
||||
await onUnauthorized?.call();
|
||||
final response = error.response;
|
||||
final alreadyRetried = error.requestOptions.extra['auth_retried'] == true;
|
||||
|
||||
if (response?.statusCode == 401 && !alreadyRetried && onRefreshToken != null) {
|
||||
final newToken = await onRefreshToken!();
|
||||
if (newToken != null && newToken.isNotEmpty) {
|
||||
final request = error.requestOptions;
|
||||
request.extra['auth_retried'] = true;
|
||||
request.headers['Authorization'] = 'Bearer $newToken';
|
||||
|
||||
try {
|
||||
final retryResponse = await _dio.fetch(request);
|
||||
return handler.resolve(retryResponse);
|
||||
} on DioException catch (retryError) {
|
||||
return handler.next(retryError);
|
||||
}
|
||||
}
|
||||
|
||||
await onSessionExpired?.call();
|
||||
}
|
||||
|
||||
handler.next(error);
|
||||
},
|
||||
),
|
||||
@@ -42,7 +61,8 @@ class ApiClient {
|
||||
|
||||
final String baseUrl;
|
||||
final Future<String?> Function()? getAccessToken;
|
||||
final Future<void> Function()? onUnauthorized;
|
||||
final Future<String?> Function()? onRefreshToken;
|
||||
final Future<void> Function()? onSessionExpired;
|
||||
final Dio _dio;
|
||||
|
||||
Dio get dio => _dio;
|
||||
@@ -94,6 +114,7 @@ final apiClientProvider = Provider<ApiClient>((ref) {
|
||||
return ApiClient(
|
||||
baseUrl: kApiBaseUrl,
|
||||
getAccessToken: () async => ref.read(authProvider).accessToken,
|
||||
onUnauthorized: () async => authNotifier.logout(),
|
||||
onRefreshToken: () => authNotifier.refreshAccessToken(),
|
||||
onSessionExpired: () => authNotifier.sessionExpired(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/// Chemins des endpoints REST de l'API Laverie v1.
|
||||
abstract final class ApiEndpoints {
|
||||
// Santé / monitoring
|
||||
static const health = '/health';
|
||||
static const healthDb = '/health/db';
|
||||
|
||||
// Authentification utilisateur
|
||||
static const authRegister = '/auth/register';
|
||||
static const authLogin = '/auth/login';
|
||||
@@ -17,6 +21,7 @@ abstract final class ApiEndpoints {
|
||||
static const establishments = '/establishments';
|
||||
static String establishment(String uuid) => '/establishments/$uuid';
|
||||
static String machine(String uuid) => '/machines/$uuid';
|
||||
static const machineLookup = '/machines/lookup';
|
||||
static String machineAvailability(String uuid) => '/machines/$uuid/availability';
|
||||
static String machinePricing(String uuid) => '/machines/$uuid/pricing';
|
||||
|
||||
@@ -24,6 +29,7 @@ abstract final class ApiEndpoints {
|
||||
static const bookings = '/bookings';
|
||||
static String booking(String uuid) => '/bookings/$uuid';
|
||||
static String bookingCancel(String uuid) => '/bookings/$uuid/cancel';
|
||||
static String bookingMove(String uuid) => '/bookings/$uuid/move';
|
||||
|
||||
// Lavages
|
||||
static const washes = '/washes';
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
/// Helpers pour parser les réponses JSON de l'API Laverie (`success` + `data`).
|
||||
abstract final class ApiResponse { static Map<String, dynamic> payload(dynamic data) {
|
||||
if (data is Map<String, dynamic>) {
|
||||
if (data['data'] is Map<String, dynamic>) {
|
||||
return data['data'] as Map<String, dynamic>;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
throw StateError('Réponse API inattendue');
|
||||
}
|
||||
|
||||
static List<dynamic> list(dynamic data, String key) {
|
||||
final value = payload(data)[key];
|
||||
if (value is List<dynamic>) {
|
||||
return value;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
static Map<String, dynamic> object(dynamic data, String key) {
|
||||
final value = payload(data)[key];
|
||||
if (value is Map<String, dynamic>) {
|
||||
return value;
|
||||
}
|
||||
throw StateError('Champ API "$key" introuvable ou invalide');
|
||||
}
|
||||
|
||||
static String errorMessage(DioException error, {String fallback = 'Erreur réseau'}) {
|
||||
final response = error.response;
|
||||
if (response?.data is Map<String, dynamic>) {
|
||||
final data = response!.data as Map<String, dynamic>;
|
||||
final message = data['message'];
|
||||
if (message is String && message.isNotEmpty) {
|
||||
return _sanitize(message);
|
||||
}
|
||||
}
|
||||
|
||||
return switch (error.type) {
|
||||
DioExceptionType.connectionTimeout ||
|
||||
DioExceptionType.sendTimeout ||
|
||||
DioExceptionType.receiveTimeout =>
|
||||
'Délai d\'attente dépassé',
|
||||
DioExceptionType.connectionError =>
|
||||
'Connexion impossible',
|
||||
_ => fallback,
|
||||
};
|
||||
}
|
||||
|
||||
/// Masque les détails SQL techniques pour l'utilisateur.
|
||||
static String _sanitize(String message) {
|
||||
if (message.contains('SQLSTATE') || message.contains('SQL:') || message.contains('must be of type')) {
|
||||
return 'Erreur serveur — réessayez dans un instant';
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user