diff --git a/lib/services/api/api_exception.dart b/lib/services/api/api_exception.dart new file mode 100644 index 0000000..4bad6dc --- /dev/null +++ b/lib/services/api/api_exception.dart @@ -0,0 +1,31 @@ +part of 'api_service.dart'; + +class ApiException implements Exception { + const ApiException({ + required this.source, + required this.message, + required this.code, + }); + + static ApiException? fromHttpException(HttpException exception) { + if (exception.type != HttpExceptionType.response && + exception.response?.data is! Map) { + return null; + } + + final json = exception.response!.data as Map; + final source = json["errors"][0]["source"] as String; + final message = json["errors"][0]["detail"] as String; + final code = json["errors"][0]["code"] as String; + + return ApiException( + source: source, + message: message, + code: code, + ); + } + + final String source; + final String message; + final String code; +} diff --git a/lib/services/api/api_params.dart b/lib/services/api/api_params.dart new file mode 100644 index 0000000..a4101c6 --- /dev/null +++ b/lib/services/api/api_params.dart @@ -0,0 +1,5 @@ +part of 'api_service.dart'; + +abstract class ApiParams with Mappable { + // +} diff --git a/lib/services/api/api_service.dart b/lib/services/api/api_service.dart new file mode 100644 index 0000000..002a311 --- /dev/null +++ b/lib/services/api/api_service.dart @@ -0,0 +1,92 @@ +import 'package:survey/gen/configs.gen.dart'; +import 'package:survey/services/http/http_service.dart'; +import 'package:object_mapper/object_mapper.dart'; +import 'package:survey/services/locator/locator_service.dart'; + +part 'api_exception.dart'; + +part 'api_params.dart'; + +part 'api_service_register.dart'; + +abstract class ApiService { + Future call({ + required HttpMethod method, + String? baseUrl, + required String endPoint, + ApiParams? params, + bool requiresAuthentication = true, + String? token, + String? tokenType, + }); + + void configureGlobalBaseUrl(String? baseUrl); + + void configureGlobalToken(String? token, String? tokenType); +} + +class ApiServiceImpl implements ApiService { + static String? _baseUrl; + static String? _token; + static String _tokenType = "Bearer"; + final HttpService _httpService = locator.get(); + + @override + Future call({ + required HttpMethod method, + String? baseUrl, + required String endPoint, + ApiParams? params, + bool requiresAuthentication = true, + String? token, + String? tokenType, + }) async { + final String? finalBaseUrl = baseUrl ?? _baseUrl; + assert(finalBaseUrl != null); + + final Map headers = {}; + + // Append token + final String? finalToken = token ?? _token; + if (requiresAuthentication && finalToken != null) { + final finalTokenType = tokenType ?? _tokenType; + headers["authorization"] = "$finalTokenType $finalToken"; + } + + final url = finalBaseUrl! + endPoint; + try { + final response = await _httpService.request( + method: method, + data: params?.toJson(), + url: url, + headers: headers, + ) as Map; + return Mapper.fromJson( + response["data"]["attributes"] as Map) + .toObject(); + } on HttpException catch (e) { + throw ApiException.fromHttpException(e) ?? e; + } + } + + /// Configures a default base url when [call] with `baseUrl` is null. + /// + /// This value is stored in a static variable [_baseUrl] so it will be available in any + /// instance of [ApiServiceImpl], after set + @override + void configureGlobalBaseUrl(String? baseUrl) { + _baseUrl = baseUrl; + } + + /// Configures a default token & tokenType when [call] with token or tokenType is null. + /// + /// This value is stored in a static variable [_token], [_tokenType] so it will be available in any + /// instance of [ApiServiceImpl], after set + @override + void configureGlobalToken(String? token, String? tokenType) { + _token = token; + if (tokenType != null) { + _tokenType = tokenType; + } + } +} diff --git a/lib/services/api/api_service_register.dart b/lib/services/api/api_service_register.dart new file mode 100644 index 0000000..9fb2f6d --- /dev/null +++ b/lib/services/api/api_service_register.dart @@ -0,0 +1,9 @@ +part of 'api_service.dart'; + +class ApiServiceRegister { + final ApiService _apiService = locator.get(); + ApiServiceRegister() { + Mappable.factories = Configs.factories; + _apiService.configureGlobalBaseUrl(Configs.app.api.baseUrl); + } +} diff --git a/lib/services/locator/locator_service.dart b/lib/services/locator/locator_service.dart index 1f53b80..433f30f 100644 --- a/lib/services/locator/locator_service.dart +++ b/lib/services/locator/locator_service.dart @@ -1,4 +1,5 @@ import 'package:get_it/get_it.dart'; +import 'package:survey/services/api/api_service.dart'; import 'package:survey/services/http/http_service.dart'; part 'locator_service_register.dart'; diff --git a/lib/services/locator/locator_service_register.dart b/lib/services/locator/locator_service_register.dart index be39ec0..a95990b 100644 --- a/lib/services/locator/locator_service_register.dart +++ b/lib/services/locator/locator_service_register.dart @@ -3,5 +3,6 @@ part of 'locator_service.dart'; class LocatorServiceRegister { LocatorServiceRegister() { locator.registerFactory(() => HttpServiceImpl()); + locator.registerFactory(() => ApiServiceImpl()); } }