From c2e59640cdb56a8c379f7c4063ce3f6c21a13b12 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 8 Aug 2025 17:03:48 +0100 Subject: [PATCH 1/6] feat(registry): implement data operation registry for CRUD functions - Create DataOperationRegistry class to centralize data handling functions - Define typedefs for various data operations (fetch, read, create, update, delete) - Implement registration methods for different data models - Add support for headline, topic, source, country, language, user, and other models - Enable dependency injection for DataRepository and DashboardSummaryService - Include ownership check middleware for user-related operations --- lib/src/registry/data_operation_registry.dart | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 lib/src/registry/data_operation_registry.dart diff --git a/lib/src/registry/data_operation_registry.dart b/lib/src/registry/data_operation_registry.dart new file mode 100644 index 0000000..cc55ed0 --- /dev/null +++ b/lib/src/registry/data_operation_registry.dart @@ -0,0 +1,221 @@ +import 'package:core/core.dart'; +import 'package:dart_frog/dart_frog.dart'; +import 'package:data_repository/data_repository.dart'; +import 'package:flutter_news_app_api_server_full_source_code/src/services/dashboard_summary_service.dart'; +import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/ownership_check_middleware.dart'; + +// --- Typedefs for Data Operations --- + +/// A function that fetches a single item by its ID. +typedef ItemFetcher = Future Function( + RequestContext context, + String id, +); + +/// A function that fetches a paginated list of items. +typedef AllItemsReader = Future> Function( + RequestContext context, + String? userId, + Map? filter, + List? sort, + PaginationOptions? pagination, +); + +/// A function that creates a new item. +typedef ItemCreator = Future Function( + RequestContext context, + dynamic item, + String? userId, +); + +/// A function that updates an existing item. +typedef ItemUpdater = Future Function( + RequestContext context, + String id, + dynamic item, + String? userId, +); + +/// A function that deletes an item by its ID. +typedef ItemDeleter = Future Function( + RequestContext context, + String id, + String? userId, +); + +/// {@template data_operation_registry} +/// A centralized registry for all data handling functions (CRUD operations). +/// +/// This class uses a map-based strategy to associate model names (strings) +/// with their corresponding data operation functions. This approach avoids +/// large, repetitive `switch` statements in the route handlers, making the +/// code more maintainable, scalable, and easier to read. +/// +/// By centralizing these mappings, we create a single source of truth for how +/// data operations are performed for each model, improving consistency across +/// the API. +/// {@endtemplate} +class DataOperationRegistry { + /// {@macro data_operation_registry} + DataOperationRegistry() { + _registerOperations(); + } + + // --- Private Maps to Hold Operation Functions --- + + final Map _itemFetchers = {}; + final Map _allItemsReaders = {}; + final Map _itemCreators = {}; + final Map _itemUpdaters = {}; + final Map _itemDeleters = {}; + + // --- Public Getters to Access Operation Maps --- + + /// Provides access to the map of item fetcher functions. + Map get itemFetchers => _itemFetchers; + + /// Provides access to the map of collection reader functions. + Map get allItemsReaders => _allItemsReaders; + + /// Provides access to the map of item creator functions. + Map get itemCreators => _itemCreators; + + /// Provides access to the map of item updater functions. + Map get itemUpdaters => _itemUpdaters; + + /// Provides access to the map of item deleter functions. + Map get itemDeleters => _itemDeleters; + + /// Populates the operation maps with their respective functions. + void _registerOperations() { + // --- Register Item Fetchers --- + _itemFetchers.addAll({ + 'headline': (c, id) => + c.read>().read(id: id, userId: null), + 'topic': (c, id) => + c.read>().read(id: id, userId: null), + 'source': (c, id) => + c.read>().read(id: id, userId: null), + 'country': (c, id) => + c.read>().read(id: id, userId: null), + 'language': (c, id) => + c.read>().read(id: id, userId: null), + 'user': (c, id) => + c.read>().read(id: id, userId: null), + 'user_app_settings': (c, id) => c + .read>() + .read(id: id, userId: null), + 'user_content_preferences': (c, id) => c + .read>() + .read(id: id, userId: null), + 'remote_config': (c, id) => + c.read>().read(id: id, userId: null), + 'dashboard_summary': (c, id) => + c.read().getSummary(), + }); + + // --- Register "Read All" Readers --- + _allItemsReaders.addAll({ + 'headline': (c, uid, f, s, p) => c + .read>() + .readAll(userId: uid, filter: f, sort: s, pagination: p), + 'topic': (c, uid, f, s, p) => c + .read>() + .readAll(userId: uid, filter: f, sort: s, pagination: p), + 'source': (c, uid, f, s, p) => c + .read>() + .readAll(userId: uid, filter: f, sort: s, pagination: p), + 'country': (c, uid, f, s, p) => c + .read>() + .readAll(userId: uid, filter: f, sort: s, pagination: p), + 'language': (c, uid, f, s, p) => c + .read>() + .readAll(userId: uid, filter: f, sort: s, pagination: p), + 'user': (c, uid, f, s, p) => c + .read>() + .readAll(userId: uid, filter: f, sort: s, pagination: p), + }); + + // --- Register Item Creators --- + _itemCreators.addAll({ + 'headline': (c, item, uid) => c + .read>() + .create(item: item as Headline, userId: uid), + 'topic': (c, item, uid) => c + .read>() + .create(item: item as Topic, userId: uid), + 'source': (c, item, uid) => c + .read>() + .create(item: item as Source, userId: uid), + 'country': (c, item, uid) => c + .read>() + .create(item: item as Country, userId: uid), + 'language': (c, item, uid) => c + .read>() + .create(item: item as Language, userId: uid), + 'remote_config': (c, item, uid) => c + .read>() + .create(item: item as RemoteConfig, userId: uid), + }); + + // --- Register Item Updaters --- + _itemUpdaters.addAll({ + 'headline': (c, id, item, uid) => c + .read>() + .update(id: id, item: item as Headline, userId: uid), + 'topic': (c, id, item, uid) => c + .read>() + .update(id: id, item: item as Topic, userId: uid), + 'source': (c, id, item, uid) => c + .read>() + .update(id: id, item: item as Source, userId: uid), + 'country': (c, id, item, uid) => c + .read>() + .update(id: id, item: item as Country, userId: uid), + 'language': (c, id, item, uid) => c + .read>() + .update(id: id, item: item as Language, userId: uid), + 'user': (c, id, item, uid) { + final repo = c.read>(); + final existingUser = c.read>().data as User; + final updatedUser = existingUser.copyWith( + feedActionStatus: (item as User).feedActionStatus, + ); + return repo.update(id: id, item: updatedUser, userId: uid); + }, + 'user_app_settings': (c, id, item, uid) => c + .read>() + .update(id: id, item: item as UserAppSettings, userId: uid), + 'user_content_preferences': (c, id, item, uid) => c + .read>() + .update(id: id, item: item as UserContentPreferences, userId: uid), + 'remote_config': (c, id, item, uid) => c + .read>() + .update(id: id, item: item as RemoteConfig, userId: uid), + }); + + // --- Register Item Deleters --- + _itemDeleters.addAll({ + 'headline': (c, id, uid) => + c.read>().delete(id: id, userId: uid), + 'topic': (c, id, uid) => + c.read>().delete(id: id, userId: uid), + 'source': (c, id, uid) => + c.read>().delete(id: id, userId: uid), + 'country': (c, id, uid) => + c.read>().delete(id: id, userId: uid), + 'language': (c, id, uid) => + c.read>().delete(id: id, userId: uid), + 'user': (c, id, uid) => + c.read>().delete(id: id, userId: uid), + 'user_app_settings': (c, id, uid) => c + .read>() + .delete(id: id, userId: uid), + 'user_content_preferences': (c, id, uid) => c + .read>() + .delete(id: id, userId: uid), + 'remote_config': (c, id, uid) => + c.read>().delete(id: id, userId: uid), + }); + } +} From 5da4e11c5850f96cd9b1bac96f9381cb7d065581 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 8 Aug 2025 17:04:20 +0100 Subject: [PATCH 2/6] refactor(middlewares): replace switch statement with registry in data fetcher - Introduce DataOperationRegistry for extensible data fetching - Remove direct dependency on DataRepository - Simplify _fetchItem function using registry lookup - Improve maintainability and extendability of data fetching logic --- .../middlewares/data_fetch_middleware.dart | 80 ++++--------------- 1 file changed, 15 insertions(+), 65 deletions(-) diff --git a/lib/src/middlewares/data_fetch_middleware.dart b/lib/src/middlewares/data_fetch_middleware.dart index bbb6c45..05cdd6e 100644 --- a/lib/src/middlewares/data_fetch_middleware.dart +++ b/lib/src/middlewares/data_fetch_middleware.dart @@ -1,8 +1,7 @@ import 'package:core/core.dart'; import 'package:dart_frog/dart_frog.dart'; -import 'package:data_repository/data_repository.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/ownership_check_middleware.dart'; -import 'package:flutter_news_app_api_server_full_source_code/src/services/dashboard_summary_service.dart'; +import 'package:flutter_news_app_api_server_full_source_code/src/registry/data_operation_registry.dart'; import 'package:logging/logging.dart'; final _log = Logger('DataFetchMiddleware'); @@ -50,10 +49,11 @@ Middleware dataFetchMiddleware() { } /// Helper function to fetch an item from the correct repository based on the -/// model name. +/// model name by using the [DataOperationRegistry]. /// -/// This function contains the switch statement that maps a `modelName` string -/// to a specific `DataRepository` call. +/// This function looks up the appropriate fetcher function from the registry +/// and invokes it. This avoids a large `switch` statement and makes the +/// system easily extensible. /// /// Throws [OperationFailedException] for unsupported model types. Future _fetchItem( @@ -61,68 +61,18 @@ Future _fetchItem( String modelName, String id, ) async { - // The `userId` is not needed here because this middleware's purpose is to - // fetch the item regardless of ownership. Ownership is checked in a - // subsequent middleware. We pass `null` for `userId` to ensure we are - // performing a global lookup for the item. - const String? userId = null; - try { - switch (modelName) { - case 'headline': - return await context.read>().read( - id: id, - userId: userId, - ); - case 'topic': - return await context - .read>() - .read(id: id, userId: userId); - case 'source': - return await context.read>().read( - id: id, - userId: userId, - ); - case 'country': - return await context.read>().read( - id: id, - userId: userId, - ); - case 'language': - return await context.read>().read( - id: id, - userId: userId, - ); - case 'user': - return await context - .read>() - .read(id: id, userId: userId); - case 'user_app_settings': - return await context.read>().read( - id: id, - userId: userId, - ); - case 'user_content_preferences': - return await context - .read>() - .read( - id: id, - userId: userId, - ); - case 'remote_config': - return await context.read>().read( - id: id, - userId: userId, - ); - case 'dashboard_summary': - // This is a special case that doesn't use a standard repository. - return await context.read().getSummary(); - default: - _log.warning('Unsupported model type "$modelName" for fetch operation.'); - throw OperationFailedException( - 'Unsupported model type "$modelName" for fetch operation.', - ); + final registry = context.read(); + final fetcher = registry.itemFetchers[modelName]; + + if (fetcher == null) { + _log.warning('Unsupported model type "$modelName" for fetch operation.'); + throw OperationFailedException( + 'Unsupported model type "$modelName" for fetch operation.', + ); } + + return await fetcher(context, id); } on NotFoundException { // The repository will throw this if the item doesn't exist. // We return null to let the main middleware handler throw a more From 50a0e2b208ca4d9b5afdd3d4e7db60c567a07c2d Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 8 Aug 2025 17:04:53 +0100 Subject: [PATCH 3/6] feat(backend): add data operation registry to middleware - Import DataOperationRegistry from registry package - Add DataOperationRegistry to the list of providers in middleware function --- routes/_middleware.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/routes/_middleware.dart b/routes/_middleware.dart index cf015bf..fc43b44 100644 --- a/routes/_middleware.dart +++ b/routes/_middleware.dart @@ -6,6 +6,7 @@ import 'package:flutter_news_app_api_server_full_source_code/src/config/app_depe import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/error_handler.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/models/request_id.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/rbac/permission_service.dart'; +import 'package:flutter_news_app_api_server_full_source_code/src/registry/data_operation_registry.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/registry/model_registry.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/services/auth_service.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/services/auth_token_service.dart'; @@ -84,6 +85,7 @@ Handler middleware(Handler handler) { // 2. Provide all dependencies to the inner handler. final deps = AppDependencies.instance; return handler + .use(provider((_) => DataOperationRegistry())) .use(provider((_) => modelRegistry)) .use( provider>( From 7456647680471ec0c7c482775f04b4eb69e8ccb6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 8 Aug 2025 17:06:47 +0100 Subject: [PATCH 4/6] refactor(api): register data operations for efficient processing - Introduce DataOperationRegistry for managing data operations - Replace switch-case statements with registered operations for update and delete - Improve scalability and maintainability of data operation handling --- routes/api/v1/data/[id]/index.dart | 133 +++++------------------------ 1 file changed, 19 insertions(+), 114 deletions(-) diff --git a/routes/api/v1/data/[id]/index.dart b/routes/api/v1/data/[id]/index.dart index baa47ce..401dd6d 100644 --- a/routes/api/v1/data/[id]/index.dart +++ b/routes/api/v1/data/[id]/index.dart @@ -6,6 +6,7 @@ import 'package:data_repository/data_repository.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/helpers/response_helper.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/ownership_check_middleware.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/rbac/permission_service.dart'; +import 'package:flutter_news_app_api_server_full_source_code/src/registry/data_operation_registry.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/registry/model_registry.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/services/user_preference_limit_service.dart'; import 'package:logging/logging.dart'; @@ -185,70 +186,16 @@ Future _updateItem( 'Executing _updateItem for model "$modelName", id "$id", userId: $userId.', ); try { - switch (modelName) { - case 'headline': - return await context.read>().update( - id: id, - item: itemToUpdate as Headline, - userId: userId, - ); - case 'topic': - return await context.read>().update( - id: id, - item: itemToUpdate as Topic, - userId: userId, - ); - case 'source': - return await context.read>().update( - id: id, - item: itemToUpdate as Source, - userId: userId, - ); - case 'country': - return await context.read>().update( - id: id, - item: itemToUpdate as Country, - userId: userId, - ); - case 'language': - return await context.read>().update( - id: id, - item: itemToUpdate as Language, - userId: userId, - ); - case 'user': - final repo = context.read>(); - final existingUser = context.read>().data as User; - final updatedUser = existingUser.copyWith( - feedActionStatus: (itemToUpdate as User).feedActionStatus, - ); - return await repo.update(id: id, item: updatedUser, userId: userId); - case 'user_app_settings': - return await context.read>().update( - id: id, - item: itemToUpdate as UserAppSettings, - userId: userId, - ); - case 'user_content_preferences': - return await context - .read>() - .update( - id: id, - item: itemToUpdate as UserContentPreferences, - userId: userId, - ); - case 'remote_config': - return await context.read>().update( - id: id, - item: itemToUpdate as RemoteConfig, - userId: userId, - ); - default: - _logger.warning('Unsupported model type "$modelName" for update operation.'); - throw OperationFailedException( - 'Unsupported model type "$modelName" for update operation.', - ); + final registry = context.read(); + final updater = registry.itemUpdaters[modelName]; + + if (updater == null) { + _logger.warning('Unsupported model type "$modelName" for update operation.'); + throw OperationFailedException( + 'Unsupported model type "$modelName" for update operation.', + ); } + return await updater(context, id, itemToUpdate, userId); } catch (e, s) { _logger.severe( 'Unhandled exception in _updateItem for model "$modelName", id "$id".', @@ -272,58 +219,16 @@ Future _deleteItem( 'Executing _deleteItem for model "$modelName", id "$id", userId: $userId.', ); try { - switch (modelName) { - case 'headline': - return await context.read>().delete( - id: id, - userId: userId, - ); - case 'topic': - return await context.read>().delete( - id: id, - userId: userId, - ); - case 'source': - return await context.read>().delete( - id: id, - userId: userId, - ); - case 'country': - return await context.read>().delete( - id: id, - userId: userId, - ); - case 'language': - return await context.read>().delete( - id: id, - userId: userId, - ); - case 'user': - return await context.read>().delete( - id: id, - userId: userId, - ); - case 'user_app_settings': - return await context.read>().delete( - id: id, - userId: userId, - ); - case 'user_content_preferences': - return await context.read>().delete( - id: id, - userId: userId, - ); - case 'remote_config': - return await context.read>().delete( - id: id, - userId: userId, - ); - default: - _logger.warning('Unsupported model type "$modelName" for delete operation.'); - throw OperationFailedException( - 'Unsupported model type "$modelName" for delete operation.', - ); + final registry = context.read(); + final deleter = registry.itemDeleters[modelName]; + + if (deleter == null) { + _logger.warning('Unsupported model type "$modelName" for delete operation.'); + throw OperationFailedException( + 'Unsupported model type "$modelName" for delete operation.', + ); } + return await deleter(context, id, userId); } catch (e, s) { _logger.severe( 'Unhandled exception in _deleteItem for model "$modelName", id "$id".', From 480f7e61df162a16b3525aa9ea09ce1cc4d0c8c3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 8 Aug 2025 17:07:08 +0100 Subject: [PATCH 5/6] refactor(data): implement data operation registry for create and read operations - Introduce DataOperationRegistry for managing data operations - Replace switch-case statements with registry lookups for create and readAll operations - Improve extensibility and maintainability of data operation handling --- routes/api/v1/data/index.dart | 103 +++++++--------------------------- 1 file changed, 19 insertions(+), 84 deletions(-) diff --git a/routes/api/v1/data/index.dart b/routes/api/v1/data/index.dart index 1824fcb..6745109 100644 --- a/routes/api/v1/data/index.dart +++ b/routes/api/v1/data/index.dart @@ -6,6 +6,7 @@ import 'package:dart_frog/dart_frog.dart'; import 'package:data_repository/data_repository.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/helpers/response_helper.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/rbac/permission_service.dart'; +import 'package:flutter_news_app_api_server_full_source_code/src/registry/data_operation_registry.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/registry/model_registry.dart'; import 'package:logging/logging.dart'; import 'package:mongo_dart/mongo_dart.dart'; @@ -165,55 +166,16 @@ Future> _readAllItems( 'Executing _readAllItems for model "$modelName", userId: $userId.', ); try { - switch (modelName) { - case 'headline': - return await context.read>().readAll( - userId: userId, - filter: filter, - sort: sort, - pagination: pagination, - ); - case 'topic': - return await context.read>().readAll( - userId: userId, - filter: filter, - sort: sort, - pagination: pagination, - ); - case 'source': - return await context.read>().readAll( - userId: userId, - filter: filter, - sort: sort, - pagination: pagination, - ); - case 'country': - return await context.read>().readAll( - userId: userId, - filter: filter, - sort: sort, - pagination: pagination, - ); - case 'language': - return await context.read>().readAll( - userId: userId, - filter: filter, - sort: sort, - pagination: pagination, - ); - case 'user': - return await context.read>().readAll( - userId: userId, - filter: filter, - sort: sort, - pagination: pagination, - ); - default: - _logger.warning('Unsupported model type "$modelName" for GET all.'); - throw OperationFailedException( - 'Unsupported model type "$modelName" for GET all.', - ); + final registry = context.read(); + final reader = registry.allItemsReaders[modelName]; + + if (reader == null) { + _logger.warning('Unsupported model type "$modelName" for GET all.'); + throw OperationFailedException( + 'Unsupported model type "$modelName" for GET all.', + ); } + return await reader(context, userId, filter, sort, pagination); } catch (e, s) { _logger.severe( 'Unhandled exception in _readAllItems for model "$modelName".', @@ -237,43 +199,16 @@ Future _createItem( 'Executing _createItem for model "$modelName", userId: $userId.', ); try { - switch (modelName) { - case 'headline': - return await context.read>().create( - item: itemToCreate as Headline, - userId: userId, - ); - case 'topic': - return await context.read>().create( - item: itemToCreate as Topic, - userId: userId, - ); - case 'source': - return await context.read>().create( - item: itemToCreate as Source, - userId: userId, - ); - case 'country': - return await context.read>().create( - item: itemToCreate as Country, - userId: userId, - ); - case 'language': - return await context.read>().create( - item: itemToCreate as Language, - userId: userId, - ); - case 'remote_config': - return await context.read>().create( - item: itemToCreate as RemoteConfig, - userId: userId, - ); - default: - _logger.warning('Unsupported model type "$modelName" for POST.'); - throw OperationFailedException( - 'Unsupported model type "$modelName" for POST.', - ); + final registry = context.read(); + final creator = registry.itemCreators[modelName]; + + if (creator == null) { + _logger.warning('Unsupported model type "$modelName" for POST.'); + throw OperationFailedException( + 'Unsupported model type "$modelName" for POST.', + ); } + return await creator(context, itemToCreate, userId); } catch (e, s) { _logger.severe( 'Unhandled exception in _createItem for model "$modelName".', From aae69578b2e2e5d4a3703cc2ab79c5a75f1444b6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 8 Aug 2025 17:12:45 +0100 Subject: [PATCH 6/6] style: format misc --- .../middlewares/data_fetch_middleware.dart | 4 +- lib/src/registry/data_operation_registry.dart | 159 ++++++++++-------- routes/_middleware.dart | 4 +- routes/api/v1/data/[id]/index.dart | 24 ++- routes/api/v1/data/index.dart | 22 +-- 5 files changed, 112 insertions(+), 101 deletions(-) diff --git a/lib/src/middlewares/data_fetch_middleware.dart b/lib/src/middlewares/data_fetch_middleware.dart index 05cdd6e..4c2c2a0 100644 --- a/lib/src/middlewares/data_fetch_middleware.dart +++ b/lib/src/middlewares/data_fetch_middleware.dart @@ -30,9 +30,7 @@ Middleware dataFetchMiddleware() { final item = await _fetchItem(context, modelName, id); if (item == null) { - _log.warning( - 'Item not found for model "$modelName", id "$id".', - ); + _log.warning('Item not found for model "$modelName", id "$id".'); throw NotFoundException( 'The requested item of type "$modelName" with id "$id" was not found.', ); diff --git a/lib/src/registry/data_operation_registry.dart b/lib/src/registry/data_operation_registry.dart index cc55ed0..afbb2d3 100644 --- a/lib/src/registry/data_operation_registry.dart +++ b/lib/src/registry/data_operation_registry.dart @@ -1,47 +1,45 @@ import 'package:core/core.dart'; import 'package:dart_frog/dart_frog.dart'; import 'package:data_repository/data_repository.dart'; -import 'package:flutter_news_app_api_server_full_source_code/src/services/dashboard_summary_service.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/ownership_check_middleware.dart'; +import 'package:flutter_news_app_api_server_full_source_code/src/services/dashboard_summary_service.dart'; // --- Typedefs for Data Operations --- /// A function that fetches a single item by its ID. -typedef ItemFetcher = Future Function( - RequestContext context, - String id, -); +typedef ItemFetcher = + Future Function(RequestContext context, String id); /// A function that fetches a paginated list of items. -typedef AllItemsReader = Future> Function( - RequestContext context, - String? userId, - Map? filter, - List? sort, - PaginationOptions? pagination, -); +typedef AllItemsReader = + Future> Function( + RequestContext context, + String? userId, + Map? filter, + List? sort, + PaginationOptions? pagination, + ); /// A function that creates a new item. -typedef ItemCreator = Future Function( - RequestContext context, - dynamic item, - String? userId, -); +typedef ItemCreator = + Future Function( + RequestContext context, + dynamic item, + String? userId, + ); /// A function that updates an existing item. -typedef ItemUpdater = Future Function( - RequestContext context, - String id, - dynamic item, - String? userId, -); +typedef ItemUpdater = + Future Function( + RequestContext context, + String id, + dynamic item, + String? userId, + ); /// A function that deletes an item by its ID. -typedef ItemDeleter = Future Function( - RequestContext context, - String id, - String? userId, -); +typedef ItemDeleter = + Future Function(RequestContext context, String id, String? userId); /// {@template data_operation_registry} /// A centralized registry for all data handling functions (CRUD operations). @@ -102,9 +100,8 @@ class DataOperationRegistry { c.read>().read(id: id, userId: null), 'user': (c, id) => c.read>().read(id: id, userId: null), - 'user_app_settings': (c, id) => c - .read>() - .read(id: id, userId: null), + 'user_app_settings': (c, id) => + c.read>().read(id: id, userId: null), 'user_content_preferences': (c, id) => c .read>() .read(id: id, userId: null), @@ -119,40 +116,57 @@ class DataOperationRegistry { 'headline': (c, uid, f, s, p) => c .read>() .readAll(userId: uid, filter: f, sort: s, pagination: p), - 'topic': (c, uid, f, s, p) => c - .read>() - .readAll(userId: uid, filter: f, sort: s, pagination: p), - 'source': (c, uid, f, s, p) => c - .read>() - .readAll(userId: uid, filter: f, sort: s, pagination: p), - 'country': (c, uid, f, s, p) => c - .read>() - .readAll(userId: uid, filter: f, sort: s, pagination: p), + 'topic': (c, uid, f, s, p) => c.read>().readAll( + userId: uid, + filter: f, + sort: s, + pagination: p, + ), + 'source': (c, uid, f, s, p) => c.read>().readAll( + userId: uid, + filter: f, + sort: s, + pagination: p, + ), + 'country': (c, uid, f, s, p) => c.read>().readAll( + userId: uid, + filter: f, + sort: s, + pagination: p, + ), 'language': (c, uid, f, s, p) => c .read>() .readAll(userId: uid, filter: f, sort: s, pagination: p), - 'user': (c, uid, f, s, p) => c - .read>() - .readAll(userId: uid, filter: f, sort: s, pagination: p), + 'user': (c, uid, f, s, p) => c.read>().readAll( + userId: uid, + filter: f, + sort: s, + pagination: p, + ), }); // --- Register Item Creators --- _itemCreators.addAll({ - 'headline': (c, item, uid) => c - .read>() - .create(item: item as Headline, userId: uid), - 'topic': (c, item, uid) => c - .read>() - .create(item: item as Topic, userId: uid), - 'source': (c, item, uid) => c - .read>() - .create(item: item as Source, userId: uid), - 'country': (c, item, uid) => c - .read>() - .create(item: item as Country, userId: uid), - 'language': (c, item, uid) => c - .read>() - .create(item: item as Language, userId: uid), + 'headline': (c, item, uid) => c.read>().create( + item: item as Headline, + userId: uid, + ), + 'topic': (c, item, uid) => c.read>().create( + item: item as Topic, + userId: uid, + ), + 'source': (c, item, uid) => c.read>().create( + item: item as Source, + userId: uid, + ), + 'country': (c, item, uid) => c.read>().create( + item: item as Country, + userId: uid, + ), + 'language': (c, item, uid) => c.read>().create( + item: item as Language, + userId: uid, + ), 'remote_config': (c, item, uid) => c .read>() .create(item: item as RemoteConfig, userId: uid), @@ -163,15 +177,21 @@ class DataOperationRegistry { 'headline': (c, id, item, uid) => c .read>() .update(id: id, item: item as Headline, userId: uid), - 'topic': (c, id, item, uid) => c - .read>() - .update(id: id, item: item as Topic, userId: uid), - 'source': (c, id, item, uid) => c - .read>() - .update(id: id, item: item as Source, userId: uid), - 'country': (c, id, item, uid) => c - .read>() - .update(id: id, item: item as Country, userId: uid), + 'topic': (c, id, item, uid) => c.read>().update( + id: id, + item: item as Topic, + userId: uid, + ), + 'source': (c, id, item, uid) => c.read>().update( + id: id, + item: item as Source, + userId: uid, + ), + 'country': (c, id, item, uid) => c.read>().update( + id: id, + item: item as Country, + userId: uid, + ), 'language': (c, id, item, uid) => c .read>() .update(id: id, item: item as Language, userId: uid), @@ -208,9 +228,8 @@ class DataOperationRegistry { c.read>().delete(id: id, userId: uid), 'user': (c, id, uid) => c.read>().delete(id: id, userId: uid), - 'user_app_settings': (c, id, uid) => c - .read>() - .delete(id: id, userId: uid), + 'user_app_settings': (c, id, uid) => + c.read>().delete(id: id, userId: uid), 'user_content_preferences': (c, id, uid) => c .read>() .delete(id: id, userId: uid), diff --git a/routes/_middleware.dart b/routes/_middleware.dart index fc43b44..38b0e44 100644 --- a/routes/_middleware.dart +++ b/routes/_middleware.dart @@ -85,7 +85,9 @@ Handler middleware(Handler handler) { // 2. Provide all dependencies to the inner handler. final deps = AppDependencies.instance; return handler - .use(provider((_) => DataOperationRegistry())) + .use( + provider((_) => DataOperationRegistry()), + ) .use(provider((_) => modelRegistry)) .use( provider>( diff --git a/routes/api/v1/data/[id]/index.dart b/routes/api/v1/data/[id]/index.dart index 401dd6d..9924660 100644 --- a/routes/api/v1/data/[id]/index.dart +++ b/routes/api/v1/data/[id]/index.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:core/core.dart'; import 'package:dart_frog/dart_frog.dart'; -import 'package:data_repository/data_repository.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/helpers/response_helper.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/ownership_check_middleware.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/rbac/permission_service.dart'; @@ -38,9 +37,7 @@ Future onRequest(RequestContext context, String id) async { /// response. Future _handleGet(RequestContext context, String id) async { final modelName = context.read(); - _logger.info( - 'Handling GET request for model "$modelName", id "$id".', - ); + _logger.info('Handling GET request for model "$modelName", id "$id".'); // The item is guaranteed to be present by the dataFetchMiddleware. final item = context.read>().data; @@ -60,12 +57,9 @@ Future _handlePut(RequestContext context, String id) async { final modelConfig = context.read>(); final authenticatedUser = context.read(); final permissionService = context.read(); - final userPreferenceLimitService = - context.read(); + final userPreferenceLimitService = context.read(); - _logger.info( - 'Handling PUT request for model "$modelName", id "$id".', - ); + _logger.info('Handling PUT request for model "$modelName", id "$id".'); final requestBody = await context.request.json() as Map?; if (requestBody == null) { @@ -142,9 +136,7 @@ Future _handleDelete(RequestContext context, String id) async { final authenticatedUser = context.read(); final permissionService = context.read(); - _logger.info( - 'Handling DELETE request for model "$modelName", id "$id".', - ); + _logger.info('Handling DELETE request for model "$modelName", id "$id".'); final userIdForRepoCall = _getUserIdForRepoCall( modelConfig: modelConfig, @@ -190,7 +182,9 @@ Future _updateItem( final updater = registry.itemUpdaters[modelName]; if (updater == null) { - _logger.warning('Unsupported model type "$modelName" for update operation.'); + _logger.warning( + 'Unsupported model type "$modelName" for update operation.', + ); throw OperationFailedException( 'Unsupported model type "$modelName" for update operation.', ); @@ -223,7 +217,9 @@ Future _deleteItem( final deleter = registry.itemDeleters[modelName]; if (deleter == null) { - _logger.warning('Unsupported model type "$modelName" for delete operation.'); + _logger.warning( + 'Unsupported model type "$modelName" for delete operation.', + ); throw OperationFailedException( 'Unsupported model type "$modelName" for delete operation.', ); diff --git a/routes/api/v1/data/index.dart b/routes/api/v1/data/index.dart index 6745109..0b8a679 100644 --- a/routes/api/v1/data/index.dart +++ b/routes/api/v1/data/index.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:core/core.dart'; import 'package:dart_frog/dart_frog.dart'; -import 'package:data_repository/data_repository.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/helpers/response_helper.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/rbac/permission_service.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/registry/data_operation_registry.dart'; @@ -33,10 +32,9 @@ Future _handleGet(RequestContext context) async { final authenticatedUser = context.read(); final params = context.request.uri.queryParameters; - _logger..info( - 'Handling GET collection request for model "$modelName".', - ) - ..finer('Query parameters: $params'); + _logger + ..info('Handling GET collection request for model "$modelName".') + ..finer('Query parameters: $params'); Map? filter; if (params.containsKey('filter')) { @@ -69,11 +67,11 @@ Future _handleGet(RequestContext context) async { final pagination = (params.containsKey('limit') || params.containsKey('cursor')) - ? PaginationOptions( - cursor: params['cursor'], - limit: int.tryParse(params['limit'] ?? ''), - ) - : null; + ? PaginationOptions( + cursor: params['cursor'], + limit: int.tryParse(params['limit'] ?? ''), + ) + : null; final userIdForRepoCall = (modelConfig.getOwnerId != null && @@ -105,9 +103,7 @@ Future _handlePost(RequestContext context) async { final modelConfig = context.read>(); final authenticatedUser = context.read(); - _logger.info( - 'Handling POST request for model "$modelName".', - ); + _logger.info('Handling POST request for model "$modelName".'); final requestBody = await context.request.json() as Map?; if (requestBody == null) {