import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'types.dart'; import 'openapi_config.dart'; class CqrsApiClient { final ApiClientConfig config; late final http.Client _httpClient; CqrsApiClient({ required this.config, http.Client? httpClient, }) { _httpClient = httpClient ?? http.Client(); } String get baseUrl => config.baseUrl; Future> executeQuery({ required String endpoint, required Serializable query, required T Function(Map) fromJson, }) async { try { final url = Uri.parse('$baseUrl/api/query/$endpoint'); final headers = _buildHeaders(); final response = await _httpClient .post( url, headers: headers, body: jsonEncode(query.toJson()), ) .timeout(config.timeout); return _handleResponse(response, fromJson); } on TimeoutException { return Result.error(ApiError.timeout()); } catch (e, stackTrace) { return Result.error( ApiError.unknown( 'Failed to execute query: ${e.toString()}', exception: Exception(stackTrace.toString()), ), ); } } Future>> executePaginatedQuery({ required String endpoint, required Serializable query, required T Function(Map) itemFromJson, required int page, required int pageSize, List? filters, }) async { try { final url = Uri.parse( '$baseUrl/api/query/$endpoint?page=$page&pageSize=$pageSize', ); final headers = _buildHeaders(); final queryData = { ...query.toJson(), if (filters != null && filters.isNotEmpty) 'filters': filters.map((f) => f.toJson()).toList(), }; final response = await _httpClient .post( url, headers: headers, body: jsonEncode(queryData), ) .timeout(config.timeout); return _handlePaginatedResponse(response, itemFromJson, page, pageSize); } on TimeoutException { return Result.error(ApiError.timeout()); } catch (e, stackTrace) { return Result.error( ApiError.unknown( 'Failed to execute paginated query: ${e.toString()}', exception: Exception(stackTrace.toString()), ), ); } } Future> executeCommand({ required String endpoint, required Serializable command, }) async { try { final url = Uri.parse('$baseUrl/api/command/$endpoint'); final headers = _buildHeaders(); final response = await _httpClient .post( url, headers: headers, body: jsonEncode(command.toJson()), ) .timeout(config.timeout); if (response.statusCode >= 200 && response.statusCode < 300) { return Result.success(null); } else { return _handleErrorResponse(response); } } on TimeoutException { return Result.error(ApiError.timeout()); } catch (e, stackTrace) { return Result.error( ApiError.unknown( 'Failed to execute command: ${e.toString()}', exception: Exception(stackTrace.toString()), ), ); } } Future> executeCommandWithResult({ required String endpoint, required Serializable command, required T Function(Map) fromJson, }) async { try { final url = Uri.parse('$baseUrl/api/command/$endpoint'); final headers = _buildHeaders(); final response = await _httpClient .post( url, headers: headers, body: jsonEncode(command.toJson()), ) .timeout(config.timeout); return _handleResponse(response, fromJson); } on TimeoutException { return Result.error(ApiError.timeout()); } catch (e, stackTrace) { return Result.error( ApiError.unknown( 'Failed to execute command with result: ${e.toString()}', exception: Exception(stackTrace.toString()), ), ); } } Future> uploadFile({ required String endpoint, required String filePath, required String fieldName, Map? additionalFields, }) async { try { final url = Uri.parse('$baseUrl/api/command/$endpoint'); final request = http.MultipartRequest('POST', url) ..headers.addAll(_buildHeaders()) ..files.add(await http.MultipartFile.fromPath(fieldName, filePath)); if (additionalFields != null) { request.fields.addAll(additionalFields); } final response = await request.send().timeout(config.timeout); final responseBody = await response.stream.bytesToString(); if (response.statusCode >= 200 && response.statusCode < 300) { return Result.success(responseBody); } else { return _parseErrorFromString(responseBody, response.statusCode); } } on TimeoutException { return Result.error(ApiError.timeout()); } catch (e, stackTrace) { return Result.error( ApiError.unknown( 'Failed to upload file: ${e.toString()}', exception: Exception(stackTrace.toString()), ), ); } } Map _buildHeaders() { final headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', ...config.defaultHeaders, }; return headers; } Result _handleResponse( http.Response response, T Function(Map) fromJson, ) { try { if (response.statusCode >= 200 && response.statusCode < 300) { if (response.body.isEmpty) { return Result.success(null as T); } final data = jsonDecode(response.body) as Map; return Result.success(fromJson(data)); } else { return _handleErrorResponse(response); } } catch (e) { return Result.error( ApiError.unknown('Failed to parse response: ${e.toString()}'), ); } } Result> _handlePaginatedResponse( http.Response response, T Function(Map) itemFromJson, int page, int pageSize, ) { try { if (response.statusCode >= 200 && response.statusCode < 300) { final data = jsonDecode(response.body) as Map; final items = (data['items'] as List?) ?.map((item) => itemFromJson(item as Map)) .toList() ?? []; final totalCount = data['totalCount'] as int? ?? items.length; return Result.success( PaginatedResult( items: items, page: page, pageSize: pageSize, totalCount: totalCount, ), ); } else { return _handleErrorResponse(response); } } catch (e) { return Result.error( ApiError.unknown('Failed to parse paginated response: ${e.toString()}'), ); } } Result _handleErrorResponse(http.Response response) { try { final data = jsonDecode(response.body) as Map; final message = data['message'] as String? ?? 'An error occurred'; if (response.statusCode == 422) { final errors = data['errors'] as Map?; final details = errors?.map( (key, value) => MapEntry( key, (value as List?)?.map((e) => e.toString()).toList() ?? [], ), ); return Result.error( ApiError.validation(message, details), ); } return Result.error( ApiError.http(statusCode: response.statusCode, message: message), ); } catch (e) { return Result.error( ApiError.http( statusCode: response.statusCode, message: response.reasonPhrase ?? 'Unknown error', ), ); } } Result _parseErrorFromString(String body, int statusCode) { try { final data = jsonDecode(body) as Map; final message = data['message'] as String? ?? 'An error occurred'; return Result.error( ApiError.http(statusCode: statusCode, message: message), ); } catch (e) { return Result.error( ApiError.http(statusCode: statusCode, message: 'Unknown error'), ); } } void close() { _httpClient.close(); } }