From 507314c6c82893ac07ba8a2d365b09b372931541 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 20 Nov 2025 22:37:24 +0100 Subject: [PATCH 1/2] feat(push-notification): persist in-app notifications before sending push - Add in-app notification repository to push notification service dependencies - Implement logic to create and persist in-app notifications for targeted users - Ensure in-app notifications are saved before sending push notifications --- lib/src/config/app_dependencies.dart | 1 + .../services/push_notification_service.dart | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/lib/src/config/app_dependencies.dart b/lib/src/config/app_dependencies.dart index 91f3351..de78c86 100644 --- a/lib/src/config/app_dependencies.dart +++ b/lib/src/config/app_dependencies.dart @@ -385,6 +385,7 @@ class AppDependencies { pushNotificationDeviceRepository: pushNotificationDeviceRepository, userContentPreferencesRepository: userContentPreferencesRepository, remoteConfigRepository: remoteConfigRepository, + inAppNotificationRepository: inAppNotificationRepository, firebaseClient: firebasePushNotificationClient, oneSignalClient: oneSignalPushNotificationClient, log: Logger('DefaultPushNotificationService'), diff --git a/lib/src/services/push_notification_service.dart b/lib/src/services/push_notification_service.dart index 78ff93a..bbdb4fc 100644 --- a/lib/src/services/push_notification_service.dart +++ b/lib/src/services/push_notification_service.dart @@ -4,6 +4,7 @@ import 'package:core/core.dart'; import 'package:data_repository/data_repository.dart'; import 'package:flutter_news_app_api_server_full_source_code/src/services/push_notification_client.dart'; import 'package:logging/logging.dart'; +import 'package:mongo_dart/mongo_dart.dart'; /// An abstract interface for the push notification service. /// @@ -35,12 +36,14 @@ class DefaultPushNotificationService implements IPushNotificationService { required DataRepository userContentPreferencesRepository, required DataRepository remoteConfigRepository, + required DataRepository inAppNotificationRepository, required IPushNotificationClient? firebaseClient, required IPushNotificationClient? oneSignalClient, required Logger log, }) : _pushNotificationDeviceRepository = pushNotificationDeviceRepository, _userContentPreferencesRepository = userContentPreferencesRepository, _remoteConfigRepository = remoteConfigRepository, + _inAppNotificationRepository = inAppNotificationRepository, _firebaseClient = firebaseClient, _oneSignalClient = oneSignalClient, _log = log; @@ -50,6 +53,7 @@ class DefaultPushNotificationService implements IPushNotificationService { final DataRepository _userContentPreferencesRepository; final DataRepository _remoteConfigRepository; + final DataRepository _inAppNotificationRepository; final IPushNotificationClient? _firebaseClient; final IPushNotificationClient? _oneSignalClient; final Logger _log; @@ -188,6 +192,35 @@ class DefaultPushNotificationService implements IPushNotificationService { 'Found ${tokens.length} devices to target via $primaryProvider.', ); + // Before sending the push, persist the InAppNotification record for + // each targeted user. This ensures the notification is available in + // their inbox immediately. + try { + final notificationCreationFutures = userIds.map((userId) { + final notification = InAppNotification( + id: ObjectId().oid, + userId: userId, + payload: PushNotificationPayload( + title: headline.title, + body: headline.excerpt, + imageUrl: headline.imageUrl, + data: { + 'headlineId': headline.id, + 'contentType': 'headline', + 'notificationType': + PushNotificationSubscriptionDeliveryType.breakingOnly.name, + }, + ), + createdAt: DateTime.now(), + ); + return _inAppNotificationRepository.create(item: notification); + }); + await Future.wait(notificationCreationFutures); + _log.info('Persisted ${userIds.length} in-app notifications.'); + } catch (e, s) { + _log.severe('Failed to persist in-app notifications.', e, s); + } + // 7. Construct the notification payload. final payload = PushNotificationPayload( title: headline.title, From 094e1e22ebb8fe0ea6e6ebdc93d786f5175277e5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Thu, 20 Nov 2025 22:42:25 +0100 Subject: [PATCH 2/2] docs(README): update notifications feature description - Enhance description of the notification engine's capabilities - Clarify integration of push notifications with in-app notification center - Improve wording for user notification streams and delivery mechanisms --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e2b33a..7aaf2bd 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,8 @@ The API automatically validates the structure of all incoming data, ensuring tha --- ### 📲 Dynamic & Personalized Notifications -A complete, multi-provider notification engine that empowers you to engage users with timely, relevant, and personalized alerts. -- **Editorial-Driven Alerts:** Any piece of content can be designated as "breaking news" from the content dashboard, triggering immediate, high-priority alerts to subscribed users. +A complete, multi-provider notification engine that empowers you to engage users with timely and relevant alerts, seamlessly integrated into their app experience. +- **Editorial-Driven Alerts:** Any piece of content can be designated as "breaking news" from the content dashboard, triggering immediate, high-priority push notifications that are also captured in each user's in-app notification center. - **User-Crafted Notification Streams:** Users can create and save persistent **Saved Headline Filters** based on any combination of content filters (such as topics, sources, or regions). They can then subscribe to notifications for that filter, receiving alerts only for the news they care about. - **Flexible Delivery Mechanisms:** The system is architected to support multiple notification types for each subscription, from immediate alerts to scheduled daily or weekly digests. - **Provider Agnostic:** The engine is built to be provider-agnostic, with out-of-the-box support for Firebase (FCM) and OneSignal. The active provider can be switched remotely without any code changes.