Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions lib/src/config/app_dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ class AppDependencies {
pushNotificationDeviceRepository: pushNotificationDeviceRepository,
userContentPreferencesRepository: userContentPreferencesRepository,
remoteConfigRepository: remoteConfigRepository,
inAppNotificationRepository: inAppNotificationRepository,
firebaseClient: firebasePushNotificationClient,
oneSignalClient: oneSignalPushNotificationClient,
log: Logger('DefaultPushNotificationService'),
Expand Down
33 changes: 33 additions & 0 deletions lib/src/services/push_notification_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -35,12 +36,14 @@ class DefaultPushNotificationService implements IPushNotificationService {
required DataRepository<UserContentPreferences>
userContentPreferencesRepository,
required DataRepository<RemoteConfig> remoteConfigRepository,
required DataRepository<InAppNotification> inAppNotificationRepository,
required IPushNotificationClient? firebaseClient,
required IPushNotificationClient? oneSignalClient,
required Logger log,
}) : _pushNotificationDeviceRepository = pushNotificationDeviceRepository,
_userContentPreferencesRepository = userContentPreferencesRepository,
_remoteConfigRepository = remoteConfigRepository,
_inAppNotificationRepository = inAppNotificationRepository,
_firebaseClient = firebaseClient,
_oneSignalClient = oneSignalClient,
_log = log;
Expand All @@ -50,6 +53,7 @@ class DefaultPushNotificationService implements IPushNotificationService {
final DataRepository<UserContentPreferences>
_userContentPreferencesRepository;
final DataRepository<RemoteConfig> _remoteConfigRepository;
final DataRepository<InAppNotification> _inAppNotificationRepository;
final IPushNotificationClient? _firebaseClient;
final IPushNotificationClient? _oneSignalClient;
final Logger _log;
Expand Down Expand Up @@ -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,
Expand Down
Loading