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. diff --git a/lib/src/services/dashboard_summary_service.dart b/lib/src/services/dashboard_summary_service.dart new file mode 100644 index 0000000..ed9e128 --- /dev/null +++ b/lib/src/services/dashboard_summary_service.dart @@ -0,0 +1,48 @@ +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 headlineRepository, + required HtDataRepository categoryRepository, + required HtDataRepository sourceRepository, + }) : _headlineRepository = headlineRepository, + _categoryRepository = categoryRepository, + _sourceRepository = sourceRepository; + + 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 { + // 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', // Fixed ID for the singleton summary + headlineCount: headlineResponse.items.length, + categoryCount: categoryResponse.items.length, + sourceCount: sourceResponse.items.length, + ); + } +} \ No newline at end of file diff --git a/routes/_middleware.dart b/routes/_middleware.dart index 838cf02..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'; @@ -175,6 +176,14 @@ Handler middleware(Handler handler) { _createUserContentPreferencesRepository(); final appConfigRepository = _createAppConfigRepository(); + // Instantiate the new DashboardSummaryService with its dependencies + final dashboardSummaryService = DashboardSummaryService( + headlineRepository: headlineRepository, + categoryRepository: categoryRepository, + sourceRepository: sourceRepository, + ); + print('[MiddlewareSetup] DashboardSummaryService instantiated.'); + const uuid = Uuid(); // --- Auth Dependencies --- @@ -308,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 diff --git a/routes/api/v1/data/[id].dart b/routes/api/v1/data/[id].dart index 65960f4..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'; @@ -122,6 +123,9 @@ Future _handleGet( id: id, userId: userIdForRepoCall, ); // userId should be null for AppConfig + case 'dashboard_summary': + 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