113 lines
3.1 KiB
Dart
113 lines
3.1 KiB
Dart
import 'package:dio/dio.dart';
|
|
|
|
import '../../core/logging/logger.dart';
|
|
import '../../core/network/api_problem.dart';
|
|
|
|
class ApiClient {
|
|
ApiClient({
|
|
required String baseUrl,
|
|
Future<String?> Function()? tokenProvider,
|
|
Future<void> Function()? onUnauthorized,
|
|
}) : _dio = Dio(
|
|
BaseOptions(
|
|
baseUrl: baseUrl,
|
|
connectTimeout: const Duration(seconds: 10),
|
|
receiveTimeout: const Duration(seconds: 15),
|
|
headers: const {'Content-Type': 'application/json'},
|
|
),
|
|
) {
|
|
if (tokenProvider != null) {
|
|
_dio.interceptors.add(
|
|
InterceptorsWrapper(
|
|
onRequest: (options, handler) async {
|
|
final token = await tokenProvider();
|
|
if (token != null && token.isNotEmpty) {
|
|
options.headers['Authorization'] = 'Bearer $token';
|
|
}
|
|
handler.next(options);
|
|
},
|
|
onError: (error, handler) async {
|
|
final status = error.response?.statusCode;
|
|
final authHeader =
|
|
error.requestOptions.headers['Authorization'] as String?;
|
|
final hasAuthHeader = authHeader != null && authHeader.isNotEmpty;
|
|
if (status == 401 && hasAuthHeader && onUnauthorized != null) {
|
|
await onUnauthorized();
|
|
}
|
|
handler.next(error);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
final Dio _dio;
|
|
final Logger _logger = getLogger('data.network.api_client');
|
|
|
|
Future<void> postNoContent(String path, {Map<String, dynamic>? data}) async {
|
|
try {
|
|
await _dio.post<void>(path, data: data);
|
|
} on DioException catch (error, stackTrace) {
|
|
_logger.error(
|
|
message: 'POST no-content failed',
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
);
|
|
throw _mapProblem(error);
|
|
}
|
|
}
|
|
|
|
Future<void> deleteNoContent(
|
|
String path, {
|
|
Map<String, dynamic>? data,
|
|
}) async {
|
|
try {
|
|
await _dio.delete<void>(path, data: data);
|
|
} on DioException catch (error, stackTrace) {
|
|
_logger.error(
|
|
message: 'DELETE no-content failed',
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
);
|
|
throw _mapProblem(error);
|
|
}
|
|
}
|
|
|
|
Future<Map<String, dynamic>> postJson(
|
|
String path, {
|
|
Map<String, dynamic>? data,
|
|
}) async {
|
|
try {
|
|
final response = await _dio.post<Map<String, dynamic>>(path, data: data);
|
|
return response.data ?? <String, dynamic>{};
|
|
} on DioException catch (error, stackTrace) {
|
|
_logger.error(
|
|
message: 'POST json failed',
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
);
|
|
throw _mapProblem(error);
|
|
}
|
|
}
|
|
|
|
ApiProblem _mapProblem(DioException error) {
|
|
final status = error.response?.statusCode ?? 500;
|
|
final data = error.response?.data;
|
|
|
|
if (data is Map<String, dynamic>) {
|
|
return ApiProblem(
|
|
status: status,
|
|
title: (data['title'] as String?) ?? 'Request failed',
|
|
detail: (data['detail'] as String?) ?? '',
|
|
code: data['code'] as String?,
|
|
);
|
|
}
|
|
|
|
return ApiProblem(
|
|
status: status,
|
|
title: 'Network error',
|
|
detail: error.message ?? 'Request failed',
|
|
);
|
|
}
|
|
}
|