From 7af82b03e80c43a8df2d8d0f4c17eaa029c0c137 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 4 Jul 2025 08:00:45 +0100 Subject: [PATCH 1/7] feat(api): register dashboard summary model --- lib/src/registry/model_registry.dart | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/src/registry/model_registry.dart b/lib/src/registry/model_registry.dart index c290c05..3d3dda4 100644 --- a/lib/src/registry/model_registry.dart +++ b/lib/src/registry/model_registry.dart @@ -313,6 +313,27 @@ final modelRegistry = >{ type: RequiredPermissionType.adminOnly, // Only administrators can delete ), ), + 'dashboard_summary': ModelConfig( + fromJson: DashboardSummary.fromJson, + getId: (summary) => summary.id, + getOwnerId: null, // Not a user-owned resource + // Permissions: Read-only for admins, all other actions unsupported. + getCollectionPermission: const ModelActionPermission( + type: RequiredPermissionType.unsupported, + ), + getItemPermission: const ModelActionPermission( + type: RequiredPermissionType.adminOnly, + ), + postPermission: const ModelActionPermission( + type: RequiredPermissionType.unsupported, + ), + putPermission: const ModelActionPermission( + type: RequiredPermissionType.unsupported, + ), + deletePermission: const ModelActionPermission( + type: RequiredPermissionType.unsupported, + ), + ), }; /// Type alias for the ModelRegistry map for easier provider usage. From e1187f5c6de6541a2c8c0d07c21b9d34d527be90 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 4 Jul 2025 08:04:15 +0100 Subject: [PATCH 2/7] feat(api): provide dashboard summary repository --- routes/_middleware.dart | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/routes/_middleware.dart b/routes/_middleware.dart index 838cf02..47015dc 100644 --- a/routes/_middleware.dart +++ b/routes/_middleware.dart @@ -148,6 +148,20 @@ HtDataRepository _createAppConfigRepository() { return HtDataRepository(dataClient: client); } +HtDataRepository _createDashboardSummaryRepository() { + print('Initializing DashboardSummary Repository...'); + final initialData = [ + DashboardSummary.fromJson(dashboardSummaryFixtureData), + ]; + final client = HtDataInMemory( + toJson: (i) => i.toJson(), + getId: (i) => i.id, + initialData: initialData, + ); + print('DashboardSummary Repository Initialized.'); + return HtDataRepository(dataClient: client); +} + /// Middleware to asynchronously load and provide the AppConfig. Middleware _appConfigProviderMiddleware() { return (handler) { @@ -174,6 +188,7 @@ Handler middleware(Handler handler) { final userContentPreferencesRepository = _createUserContentPreferencesRepository(); final appConfigRepository = _createAppConfigRepository(); + final dashboardSummaryRepository = _createDashboardSummaryRepository(); const uuid = Uuid(); @@ -286,6 +301,11 @@ Handler middleware(Handler handler) { ), ) .use(provider>((_) => appConfigRepository)) + .use( + provider>( + (_) => dashboardSummaryRepository, + ), + ) // ORDER: These MUST be provided BEFORE `authenticationProvider` and // any route handlers that perform authentication/authorization. // - `Uuid` is used by `AuthService` and `JwtAuthTokenService`. From 4a06426f5112a711086bd798e9fba9db4102c557 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 4 Jul 2025 08:06:38 +0100 Subject: [PATCH 3/7] feat(api): expose dashboard summary via item route --- routes/api/v1/data/[id].dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/routes/api/v1/data/[id].dart b/routes/api/v1/data/[id].dart index 65960f4..05ab5c1 100644 --- a/routes/api/v1/data/[id].dart +++ b/routes/api/v1/data/[id].dart @@ -122,6 +122,12 @@ Future _handleGet( id: id, userId: userIdForRepoCall, ); // userId should be null for AppConfig + case 'dashboard_summary': + final repo = context.read>(); + item = await repo.read( + id: id, + userId: userIdForRepoCall, + ); default: // This case should ideally be caught by middleware, but added for safety // Throw an exception to be caught by the errorHandler From 4133a82537a010467fa2c281edf579051aa7627d Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 4 Jul 2025 08:16:24 +0100 Subject: [PATCH 4/7] feat(api): create dashboard summary service structure --- .../services/dashboard_summary_service.dart | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 lib/src/services/dashboard_summary_service.dart diff --git a/lib/src/services/dashboard_summary_service.dart b/lib/src/services/dashboard_summary_service.dart new file mode 100644 index 0000000..bcfaec1 --- /dev/null +++ b/lib/src/services/dashboard_summary_service.dart @@ -0,0 +1,41 @@ +import 'package:ht_data_repository/ht_data_repository.dart'; +import 'package:ht_shared/ht_shared.dart'; + +/// {@template dashboard_summary_service} +/// A service responsible for calculating the dashboard summary data on demand. +/// +/// This service aggregates data from various repositories to provide a +/// real-time overview of key metrics in the system. +/// {@endtemplate} +class DashboardSummaryService { + /// {@macro dashboard_summary_service} + const DashboardSummaryService({ + required HtDataRepository userRepository, + required HtDataRepository headlineRepository, + required HtDataRepository categoryRepository, + required HtDataRepository sourceRepository, + }) : _userRepository = userRepository, + _headlineRepository = headlineRepository, + _categoryRepository = categoryRepository, + _sourceRepository = sourceRepository; + + final HtDataRepository _userRepository; + final HtDataRepository _headlineRepository; + final HtDataRepository _categoryRepository; + final HtDataRepository _sourceRepository; + + /// Calculates and returns the current dashboard summary. + /// + /// This method fetches all items from the required repositories to count them + /// and constructs a [DashboardSummary] object. + Future getSummary() async { + // The actual calculation logic will be implemented in a subsequent step. + // For now, this serves as a placeholder structure. + return DashboardSummary( + id: 'dashboard_summary', + headlineCount: 0, + categoryCount: 0, + sourceCount: 0, + ); + } +} \ No newline at end of file From be5b899b9c544ed21800e0957dece0f6df2e059d Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 4 Jul 2025 08:40:57 +0100 Subject: [PATCH 5/7] fix(api): align summary service with correct model structure --- .../services/dashboard_summary_service.dart | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/src/services/dashboard_summary_service.dart b/lib/src/services/dashboard_summary_service.dart index bcfaec1..ed9e128 100644 --- a/lib/src/services/dashboard_summary_service.dart +++ b/lib/src/services/dashboard_summary_service.dart @@ -10,16 +10,13 @@ import 'package:ht_shared/ht_shared.dart'; class DashboardSummaryService { /// {@macro dashboard_summary_service} const DashboardSummaryService({ - required HtDataRepository userRepository, required HtDataRepository headlineRepository, required HtDataRepository categoryRepository, required HtDataRepository sourceRepository, - }) : _userRepository = userRepository, - _headlineRepository = headlineRepository, + }) : _headlineRepository = headlineRepository, _categoryRepository = categoryRepository, _sourceRepository = sourceRepository; - final HtDataRepository _userRepository; final HtDataRepository _headlineRepository; final HtDataRepository _categoryRepository; final HtDataRepository _sourceRepository; @@ -29,13 +26,23 @@ class DashboardSummaryService { /// This method fetches all items from the required repositories to count them /// and constructs a [DashboardSummary] object. Future getSummary() async { - // The actual calculation logic will be implemented in a subsequent step. - // For now, this serves as a placeholder structure. + // Use Future.wait to fetch all counts in parallel for efficiency. + final results = await Future.wait([ + _headlineRepository.readAll(), + _categoryRepository.readAll(), + _sourceRepository.readAll(), + ]); + + // The results are PaginatedResponse objects. + final headlineResponse = results[0] as PaginatedResponse; + final categoryResponse = results[1] as PaginatedResponse; + final sourceResponse = results[2] as PaginatedResponse; + return DashboardSummary( - id: 'dashboard_summary', - headlineCount: 0, - categoryCount: 0, - sourceCount: 0, + id: 'dashboard_summary', // Fixed ID for the singleton summary + headlineCount: headlineResponse.items.length, + categoryCount: categoryResponse.items.length, + sourceCount: sourceResponse.items.length, ); } } \ No newline at end of file From b1278a0f3a4c8f3cd8623635fc3e448e2b84810c Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 4 Jul 2025 08:42:43 +0100 Subject: [PATCH 6/7] refactor(api): integrate dashboard summary service into middleware --- routes/_middleware.dart | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/routes/_middleware.dart b/routes/_middleware.dart index 47015dc..244a16d 100644 --- a/routes/_middleware.dart +++ b/routes/_middleware.dart @@ -5,6 +5,7 @@ import 'package:ht_api/src/registry/model_registry.dart'; import 'package:ht_api/src/services/auth_service.dart'; import 'package:ht_api/src/services/auth_token_service.dart'; import 'package:ht_api/src/services/default_user_preference_limit_service.dart'; +import 'package:ht_api/src/services/dashboard_summary_service.dart'; import 'package:ht_api/src/services/jwt_auth_token_service.dart'; import 'package:ht_api/src/services/token_blacklist_service.dart'; import 'package:ht_api/src/services/user_preference_limit_service.dart'; @@ -148,20 +149,6 @@ HtDataRepository _createAppConfigRepository() { return HtDataRepository(dataClient: client); } -HtDataRepository _createDashboardSummaryRepository() { - print('Initializing DashboardSummary Repository...'); - final initialData = [ - DashboardSummary.fromJson(dashboardSummaryFixtureData), - ]; - final client = HtDataInMemory( - toJson: (i) => i.toJson(), - getId: (i) => i.id, - initialData: initialData, - ); - print('DashboardSummary Repository Initialized.'); - return HtDataRepository(dataClient: client); -} - /// Middleware to asynchronously load and provide the AppConfig. Middleware _appConfigProviderMiddleware() { return (handler) { @@ -188,7 +175,14 @@ Handler middleware(Handler handler) { final userContentPreferencesRepository = _createUserContentPreferencesRepository(); final appConfigRepository = _createAppConfigRepository(); - final dashboardSummaryRepository = _createDashboardSummaryRepository(); + + // Instantiate the new DashboardSummaryService with its dependencies + final dashboardSummaryService = DashboardSummaryService( + headlineRepository: headlineRepository, + categoryRepository: categoryRepository, + sourceRepository: sourceRepository, + ); + print('[MiddlewareSetup] DashboardSummaryService instantiated.'); const uuid = Uuid(); @@ -301,11 +295,6 @@ Handler middleware(Handler handler) { ), ) .use(provider>((_) => appConfigRepository)) - .use( - provider>( - (_) => dashboardSummaryRepository, - ), - ) // ORDER: These MUST be provided BEFORE `authenticationProvider` and // any route handlers that perform authentication/authorization. // - `Uuid` is used by `AuthService` and `JwtAuthTokenService`. @@ -328,6 +317,7 @@ Handler middleware(Handler handler) { .use( provider((_) => authService), ) // Reads other services/repos + .use(provider((_) => dashboardSummaryService)) // --- 5. RBAC Service Provider --- // PURPOSE: Provides the PermissionService for authorization checks. // ORDER: Must be provided before any middleware or handlers that use it From 7973922fd2e00d6f5ea9d1ea3597d3d79c34f5ca Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 4 Jul 2025 08:45:07 +0100 Subject: [PATCH 7/7] refactor(api): use service for dynamic dashboard summary --- routes/api/v1/data/[id].dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/routes/api/v1/data/[id].dart b/routes/api/v1/data/[id].dart index 05ab5c1..e0d43ff 100644 --- a/routes/api/v1/data/[id].dart +++ b/routes/api/v1/data/[id].dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:dart_frog/dart_frog.dart'; import 'package:ht_api/src/rbac/permission_service.dart'; import 'package:ht_api/src/registry/model_registry.dart'; +import 'package:ht_api/src/services/dashboard_summary_service.dart'; import 'package:ht_api/src/services/user_preference_limit_service.dart'; // Import UserPreferenceLimitService import 'package:ht_data_repository/ht_data_repository.dart'; import 'package:ht_shared/ht_shared.dart'; @@ -123,11 +124,8 @@ Future _handleGet( userId: userIdForRepoCall, ); // userId should be null for AppConfig case 'dashboard_summary': - final repo = context.read>(); - item = await repo.read( - id: id, - userId: userIdForRepoCall, - ); + final service = context.read(); + item = await service.getSummary(); default: // This case should ideally be caught by middleware, but added for safety // Throw an exception to be caught by the errorHandler