From 2a2dfcd9cb5418c0ca184fb72de49f0a3af00cab Mon Sep 17 00:00:00 2001 From: Jason Dean Lessenich Date: Wed, 28 Feb 2024 22:42:07 +0100 Subject: [PATCH] =?UTF-8?q?v0.6=20=E2=80=94=20Implemented=20Events=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implemented events * Cleaned up auth process * Bumped version & updated CHANGELOG.md * Added RequestEvent * Improved logging & added RequestEvent * dart format --- CHANGELOG.md | 6 ++ lib/restrr.dart | 5 ++ lib/src/cache/batch_cache_view.dart | 2 +- lib/src/events/event_handler.dart | 15 +++++ lib/src/events/ready_event.dart | 5 ++ lib/src/events/request_event.dart | 8 +++ lib/src/events/restrr_event.dart | 7 +++ lib/src/restrr_base.dart | 94 +++++++++++++++-------------- lib/src/service/api_service.dart | 60 +++++++++++------- lib/src/service/user_service.dart | 1 + pubspec.yaml | 2 +- 11 files changed, 137 insertions(+), 68 deletions(-) create mode 100644 lib/src/events/event_handler.dart create mode 100644 lib/src/events/ready_event.dart create mode 100644 lib/src/events/request_event.dart create mode 100644 lib/src/events/restrr_event.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 42463cc..f31ea86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.6 +- Added `RestrrBuilder#on` & `Restrr#on` +- Added `ReadyEvent` & `RequestEvent` +- Added `RestrrOptions#disableLogging` +- Further improved Logging + ## 0.5 - Added `Currency` - Added `Restrr#retrieveAllCurrencies` diff --git a/lib/restrr.dart b/lib/restrr.dart index 6f56c48..80d1db4 100644 --- a/lib/restrr.dart +++ b/lib/restrr.dart @@ -10,6 +10,11 @@ export 'src/entities/health_response.dart'; export 'src/entities/restrr_entity.dart'; export 'src/entities/user.dart'; +/* [ /src/events ] */ +export 'src/events/ready_event.dart'; +export 'src/events/request_event.dart'; +export 'src/events/restrr_event.dart'; + /* [ /src/requests ] */ export 'src/requests/route_definitions.dart'; diff --git a/lib/src/cache/batch_cache_view.dart b/lib/src/cache/batch_cache_view.dart index f93e4c4..83c2ddb 100644 --- a/lib/src/cache/batch_cache_view.dart +++ b/lib/src/cache/batch_cache_view.dart @@ -10,4 +10,4 @@ class RestrrEntityBatchCacheView { void clear() => _lastSnapshot = null; bool get hasSnapshot => _lastSnapshot != null; -} \ No newline at end of file +} diff --git a/lib/src/events/event_handler.dart b/lib/src/events/event_handler.dart new file mode 100644 index 0000000..8ca1319 --- /dev/null +++ b/lib/src/events/event_handler.dart @@ -0,0 +1,15 @@ +import 'package:restrr/src/events/restrr_event.dart'; + +class RestrrEventHandler { + final Map eventMap; + + const RestrrEventHandler(this.eventMap); + + void on(Function(T) callback) { + eventMap[T.runtimeType] = callback; + } + + void fire(T event) { + eventMap[T.runtimeType]?.call(event); + } +} diff --git a/lib/src/events/ready_event.dart b/lib/src/events/ready_event.dart new file mode 100644 index 0000000..bdcc681 --- /dev/null +++ b/lib/src/events/ready_event.dart @@ -0,0 +1,5 @@ +import 'package:restrr/src/events/restrr_event.dart'; + +class ReadyEvent extends RestrrEvent { + const ReadyEvent({required super.api}); +} diff --git a/lib/src/events/request_event.dart b/lib/src/events/request_event.dart new file mode 100644 index 0000000..b471789 --- /dev/null +++ b/lib/src/events/request_event.dart @@ -0,0 +1,8 @@ +import 'package:restrr/restrr.dart'; + +class RequestEvent extends RestrrEvent { + final String route; + final int? statusCode; + + const RequestEvent({required super.api, required this.route, this.statusCode}); +} diff --git a/lib/src/events/restrr_event.dart b/lib/src/events/restrr_event.dart new file mode 100644 index 0000000..0a6c28c --- /dev/null +++ b/lib/src/events/restrr_event.dart @@ -0,0 +1,7 @@ +import '../../restrr.dart'; + +abstract class RestrrEvent { + final Restrr api; + + const RestrrEvent({required this.api}); +} diff --git a/lib/src/restrr_base.dart b/lib/src/restrr_base.dart index a44cf83..a045988 100644 --- a/lib/src/restrr_base.dart +++ b/lib/src/restrr_base.dart @@ -1,5 +1,6 @@ import 'package:logging/logging.dart'; import 'package:restrr/src/cache/batch_cache_view.dart'; +import 'package:restrr/src/events/event_handler.dart'; import 'package:restrr/src/requests/route.dart'; import 'package:restrr/src/service/api_service.dart'; import 'package:restrr/src/service/currency_service.dart'; @@ -10,7 +11,8 @@ import 'cache/cache_view.dart'; class RestrrOptions { final bool isWeb; - const RestrrOptions({this.isWeb = false}); + final bool disableLogging; + const RestrrOptions({this.isWeb = false, this.disableLogging = false}); } enum RestrrInitType { login, register, savedSession } @@ -18,6 +20,8 @@ enum RestrrInitType { login, register, savedSession } /// A builder for creating a new [Restrr] instance. /// The [Restrr] instance is created by calling [create]. class RestrrBuilder { + final Map _eventMap = {}; + final RestrrInitType initType; final Uri uri; String? sessionId; @@ -37,64 +41,53 @@ class RestrrBuilder { RestrrBuilder.savedSession({required this.uri}) : initType = RestrrInitType.savedSession; + RestrrBuilder on(void Function(T) func) { + _eventMap[T.runtimeType] = func; + return this; + } + /// Creates a new session with the given [uri]. Future> create() async { - Restrr.log.info('Attempting to initialize a session (${initType.name}) with $uri'); // check if the URI is valid final RestResponse statusResponse = await Restrr.checkUri(uri, isWeb: options.isWeb); if (statusResponse.hasError) { Restrr.log.warning('Invalid financrr URI: $uri'); - return statusResponse.error == RestrrError.unknown - ? RestrrError.invalidUri.toRestResponse() - : statusResponse.error?.toRestResponse(statusCode: statusResponse.statusCode) ?? - RestrrError.invalidUri.toRestResponse(); + return (statusResponse.error == RestrrError.unknown + ? RestrrError.invalidUri + : statusResponse.error ?? RestrrError.invalidUri) + .toRestResponse(statusCode: statusResponse.statusCode); } - Restrr.log.info('Host: $uri, API v${statusResponse.data!.apiVersion}'); + + Restrr.log.config('Host: $uri, API v${statusResponse.data!.apiVersion}'); final RestrrImpl apiImpl = RestrrImpl._( - options: options, routeOptions: RouteOptions(hostUri: uri, apiVersion: statusResponse.data!.apiVersion)); - return switch (initType) { - RestrrInitType.register => - _handleRegistration(apiImpl, username!, password!, email: email, displayName: displayName), - RestrrInitType.login => _handleLogin(apiImpl, username!, password!), - RestrrInitType.savedSession => _handleSavedSession(apiImpl), + options: options, + routeOptions: RouteOptions(hostUri: uri, apiVersion: statusResponse.data!.apiVersion), + eventMap: _eventMap); + + // attempt to authenticate the user + final RestResponse apiResponse = await switch (initType) { + RestrrInitType.register => _handleAuthProcess(apiImpl, + authFunction: () => + apiImpl._userService.register(username!, password!, email: email, displayName: displayName)), + RestrrInitType.login => + _handleAuthProcess(apiImpl, authFunction: () => apiImpl._userService.login(username!, password!)), + RestrrInitType.savedSession => _handleAuthProcess(apiImpl, authFunction: () => apiImpl._userService.getSelf()), }; - } - - /// Logs in with the given [username] and [password]. - Future> _handleLogin(RestrrImpl apiImpl, String username, String password) async { - final RestResponse response = await apiImpl._userService.login(username, password); - if (!response.hasData) { - Restrr.log.warning('Invalid credentials for user $username'); - return RestrrError.invalidCredentials.toRestResponse(statusCode: response.statusCode); - } - apiImpl.selfUser = response.data!; - Restrr.log.info('Successfully logged in as ${apiImpl.selfUser.username}'); - return RestResponse(data: apiImpl, statusCode: response.statusCode); - } - /// Registers a new user and logs in. - Future> _handleRegistration(RestrrImpl apiImpl, String username, String password, - {String? email, String? displayName}) async { - final RestResponse response = - await apiImpl._userService.register(username, password, email: email, displayName: displayName); - if (response.hasError) { - Restrr.log.warning('Failed to register user $username'); - return response.error?.toRestResponse(statusCode: response.statusCode) ?? RestrrError.unknown.toRestResponse(); + // fire [ReadyEvent] if the API is ready + if (apiResponse.hasData) { + apiImpl.eventHandler.fire(ReadyEvent(api: apiImpl)); } - apiImpl.selfUser = response.data!; - Restrr.log.info('Successfully registered & logged in as ${apiImpl.selfUser.username}'); - return RestResponse(data: apiImpl, statusCode: response.statusCode); + return apiResponse; } - /// Attempts to refresh the session with still saved credentials. - Future> _handleSavedSession(RestrrImpl apiImpl) async { - final RestResponse response = await apiImpl._userService.getSelf(); + Future> _handleAuthProcess(RestrrImpl apiImpl, + {required Future> Function() authFunction}) async { + final RestResponse response = await authFunction(); if (response.hasError) { - Restrr.log.warning('Failed to refresh session'); return response.error?.toRestResponse(statusCode: response.statusCode) ?? RestrrError.unknown.toRestResponse(); } apiImpl.selfUser = response.data!; - Restrr.log.info('Successfully refreshed session for ${apiImpl.selfUser.username}'); return RestResponse(data: apiImpl, statusCode: response.statusCode); } } @@ -105,6 +98,8 @@ abstract class Restrr { /// Getter for the [EntityBuilder] of this [Restrr] instance. EntityBuilder get entityBuilder; + RestrrEventHandler get eventHandler; + RestrrOptions get options; RouteOptions get routeOptions; @@ -120,6 +115,8 @@ abstract class Restrr { routeOptions: RouteOptions(hostUri: uri)); } + void on(void Function(T) func); + /// Retrieves the currently authenticated user. Future retrieveSelf({bool forceRetrieve = false}); @@ -144,6 +141,9 @@ class RestrrImpl implements Restrr { @override final RouteOptions routeOptions; + @override + late final RestrrEventHandler eventHandler; + /* Services */ late final UserService _userService = UserService(api: this); @@ -156,7 +156,8 @@ class RestrrImpl implements Restrr { late final RestrrEntityBatchCacheView _currencyBatchCache = RestrrEntityBatchCacheView(); - RestrrImpl._({required this.options, required this.routeOptions}); + RestrrImpl._({required this.options, required this.routeOptions, required Map eventMap}) + : eventHandler = RestrrEventHandler(eventMap); @override late final EntityBuilder entityBuilder = EntityBuilder(api: this); @@ -164,6 +165,9 @@ class RestrrImpl implements Restrr { @override late final User selfUser; + @override + void on(void Function(T) func) => eventHandler.on(func); + @override Future retrieveSelf({bool forceRetrieve = false}) async { return _getOrRetrieveSingle( @@ -186,8 +190,8 @@ class RestrrImpl implements Restrr { @override Future?> retrieveAllCurrencies({bool forceRetrieve = false}) async { return _getOrRetrieveMulti( - batchCache: _currencyBatchCache, - retrieveFunction: (api) => api._currencyService.retrieveAllCurrencies(), + batchCache: _currencyBatchCache, + retrieveFunction: (api) => api._currencyService.retrieveAllCurrencies(), ); } diff --git a/lib/src/service/api_service.dart b/lib/src/service/api_service.dart index 919ad5a..d5fca15 100644 --- a/lib/src/service/api_service.dart +++ b/lib/src/service/api_service.dart @@ -1,4 +1,7 @@ +import 'dart:async'; + import 'package:dio/dio.dart'; +import 'package:logging/logging.dart'; import 'package:restrr/restrr.dart'; import '../requests/route.dart'; @@ -119,13 +122,14 @@ abstract class ApiService { dynamic body, String contentType = 'application/json'}) async { return RequestHandler.request( - route: route, - routeOptions: api.routeOptions, - isWeb: api.options.isWeb, - mapper: mapper, - errorMap: errorMap, - body: body, - contentType: contentType); + route: route, + routeOptions: api.routeOptions, + isWeb: api.options.isWeb, + mapper: mapper, + errorMap: errorMap, + body: body, + contentType: contentType) + .then((response) => _fireEvent(route, response)); } Future> noResponseRequest( @@ -134,12 +138,13 @@ abstract class ApiService { Map errorMap = const {}, String contentType = 'application/json'}) async { return RequestHandler.noResponseRequest( - route: route, - routeOptions: api.routeOptions, - isWeb: api.options.isWeb, - body: body, - errorMap: errorMap, - contentType: contentType); + route: route, + routeOptions: api.routeOptions, + isWeb: api.options.isWeb, + body: body, + errorMap: errorMap, + contentType: contentType) + .then((response) => _fireEvent(route, response)); } Future>> multiRequest( @@ -150,13 +155,26 @@ abstract class ApiService { dynamic body, String contentType = 'application/json'}) async { return RequestHandler.multiRequest( - route: route, - routeOptions: api.routeOptions, - isWeb: api.options.isWeb, - mapper: mapper, - errorMap: errorMap, - fullRequest: fullRequest, - body: body, - contentType: contentType); + route: route, + routeOptions: api.routeOptions, + isWeb: api.options.isWeb, + mapper: mapper, + errorMap: errorMap, + fullRequest: fullRequest, + body: body, + contentType: contentType) + .then((response) => _fireEvent(route, response)); + } + + Future> _fireEvent(CompiledRoute route, RestResponse response) async { + if (!api.options.disableLogging) { + Restrr.log.log( + response.statusCode != null && response.statusCode! >= 400 ? Level.WARNING : Level.INFO, + '[${DateTime.now().toIso8601String()}] ${route.baseRoute.method} ' + '${api.routeOptions.hostUri}${route.baseRoute.isVersioned ? '/api/v${api.routeOptions.apiVersion}' : ''}' + '${route.compiledRoute} => ${response.statusCode} (${response.hasData ? 'OK' : response.error?.name})'); + } + api.eventHandler.fire(RequestEvent(api: api, route: route.compiledRoute, statusCode: response.statusCode)); + return response; } } diff --git a/lib/src/service/user_service.dart b/lib/src/service/user_service.dart index b592ad9..97931a7 100644 --- a/lib/src/service/user_service.dart +++ b/lib/src/service/user_service.dart @@ -13,6 +13,7 @@ class UserService extends ApiService { }, mapper: (json) => api.entityBuilder.buildUser(json), errorMap: { + 404: RestrrError.invalidCredentials, 401: RestrrError.invalidCredentials, }); } diff --git a/pubspec.yaml b/pubspec.yaml index 401ab90..67dd986 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: restrr description: Dart package which allows to communicate with the financrr REST API. -version: 0.5.0 +version: 0.6.0 repository: https://github.com/financrr/restrr environment: