-
Notifications
You must be signed in to change notification settings - Fork 0
feat: store sent notifications to DB #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e49220d
385cfcb
3197265
4e22234
74e8aa7
a406f99
24cef81
2060d53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||
| /* | ||||||||||
| * © 2026 SAP SE or an SAP affiliate company and cds-feature-notifications contributors. | ||||||||||
| */ | ||||||||||
| package com.sap.cds.notifications.handlers; | ||||||||||
|
|
||||||||||
| import cds.gen.notificationproviderservice.NotificationProviderService_; | ||||||||||
| import cds.gen.notificationproviderservice.Notifications; | ||||||||||
| import cds.gen.notificationproviderservice.Notifications_; | ||||||||||
| import com.sap.cds.notifications.helpers.NotificationStorageService; | ||||||||||
| import com.sap.cds.services.Service; | ||||||||||
| import com.sap.cds.services.cds.CdsCreateEventContext; | ||||||||||
| import com.sap.cds.services.cds.CqnService; | ||||||||||
| import com.sap.cds.services.handler.EventHandler; | ||||||||||
| import com.sap.cds.services.handler.annotations.After; | ||||||||||
| import com.sap.cds.services.handler.annotations.ServiceName; | ||||||||||
| import java.time.Instant; | ||||||||||
| import java.util.List; | ||||||||||
| import java.util.Map; | ||||||||||
|
|
||||||||||
| @ServiceName(value = NotificationProviderService_.CDS_NAME, type = Service.class) | ||||||||||
| public class StoreNotificationsHandler implements EventHandler { | ||||||||||
|
|
||||||||||
| private final NotificationStorageService storageService; | ||||||||||
|
|
||||||||||
| public StoreNotificationsHandler(NotificationStorageService storageService) { | ||||||||||
| this.storageService = storageService; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| @After(event = CqnService.EVENT_CREATE, entity = Notifications_.CDS_NAME) | ||||||||||
| public void storeNotifications(CdsCreateEventContext context) { | ||||||||||
|
Comment on lines
+29
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can be simplified
Suggested change
|
||||||||||
| List<Notifications> results = context.getResult().listOf(Notifications.class); | ||||||||||
| List<Map<String, Object>> entries = context.getCqn().entries(); | ||||||||||
|
|
||||||||||
| if (results == null || results.isEmpty()) { | ||||||||||
| return; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| Instant sentAt = Instant.now(); | ||||||||||
|
|
||||||||||
| for (int i = 0; i < results.size(); i++) { | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possibly add a check here that results and entries have the same size. |
||||||||||
| Notifications result = results.get(i); | ||||||||||
| Notifications requestEntry = Notifications.of(entries.get(i)); | ||||||||||
| storageService.store(result.getId(), requestEntry, sentAt); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /* | ||
| * © 2026 SAP SE or an SAP affiliate company and cds-feature-notifications contributors. | ||
| */ | ||
| package com.sap.cds.notifications.handlers; | ||
|
|
||
| import cds.gen.notificationproviderservice.Notifications; | ||
| import com.sap.cds.notifications.helpers.NotificationStorageService; | ||
| import com.sap.cds.services.EventContext; | ||
| import com.sap.cds.services.cds.ApplicationService; | ||
| import com.sap.cds.services.handler.EventHandler; | ||
| import com.sap.cds.services.handler.annotations.After; | ||
| import com.sap.cds.services.handler.annotations.ServiceName; | ||
| import java.time.Instant; | ||
| import java.util.List; | ||
|
|
||
| @ServiceName(value = "*", type = ApplicationService.class) | ||
| public class StoreNotificationsLocalHandler implements EventHandler { | ||
|
|
||
| private final NotificationStorageService storageService; | ||
|
|
||
| public StoreNotificationsLocalHandler(NotificationStorageService storageService) { | ||
| this.storageService = storageService; | ||
| } | ||
|
|
||
| @After(event = "*") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be after every event, right? Is it possible to narrow that down? |
||
| public void storeNotifications(EventContext context) { | ||
| @SuppressWarnings("unchecked") | ||
| List<Notifications> sentNotifications = | ||
| (List<Notifications>) context.get(LocalHandler.SENT_NOTIFICATIONS_KEY); | ||
|
|
||
| if (sentNotifications == null || sentNotifications.isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| Instant sentAt = Instant.now(); | ||
|
|
||
| for (Notifications notification : sentNotifications) { | ||
| storageService.store(notification.getId(), notification, sentAt); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| /* | ||
| * © 2026 SAP SE or an SAP affiliate company and cds-feature-notifications contributors. | ||
| */ | ||
| package com.sap.cds.notifications.helpers; | ||
|
|
||
| import cds.gen.notificationproviderservice.Notifications; | ||
| import cds.gen.notificationproviderservice.Recipients; | ||
| import cds.gen.sap.cds.notifications.NotificationProperties; | ||
| import cds.gen.sap.cds.notifications.NotificationTargetParameters; | ||
| import com.sap.cds.ql.Insert; | ||
| import com.sap.cds.services.persistence.PersistenceService; | ||
| import java.time.Instant; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** Shared service for persisting notifications to the database. */ | ||
| public class NotificationStorageService { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class isn't a service (in cap terms) it is a normal pojo |
||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(NotificationStorageService.class); | ||
|
|
||
| private final PersistenceService persistenceService; | ||
|
|
||
| public NotificationStorageService(PersistenceService persistenceService) { | ||
| this.persistenceService = persistenceService; | ||
| } | ||
|
|
||
| public void store(String notificationId, Notifications request, Instant sentAt) { | ||
| if (notificationId == null) { | ||
| logger.warn("Skipping notification with null ID, cannot store to DB"); | ||
| return; | ||
| } | ||
|
|
||
| logger.info( | ||
| "Storing notification '{}' for {} recipient(s) to DB", | ||
| notificationId, | ||
| request.getRecipients().size()); | ||
|
|
||
| for (Recipients recipient : request.getRecipients()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question can |
||
| String recipientId = resolveRecipientId(recipient); | ||
| cds.gen.sap.cds.notifications.Notifications stored = | ||
| buildStoredNotification(notificationId, request, recipientId, sentAt); | ||
| persistenceService.run( | ||
| Insert.into(cds.gen.sap.cds.notifications.Notifications_.class).entry(stored)); | ||
| logger.debug("Stored notification '{}' for recipient '{}'", notificationId, recipientId); | ||
| } | ||
| } | ||
|
|
||
| private cds.gen.sap.cds.notifications.Notifications buildStoredNotification( | ||
| String notificationId, Notifications request, String recipientId, Instant sentAt) { | ||
| cds.gen.sap.cds.notifications.Notifications stored = | ||
| cds.gen.sap.cds.notifications.Notifications.create(); | ||
| stored.setId(notificationId); | ||
| stored.setRecipient(recipientId); | ||
| stored.setNotificationTypeKey(request.getNotificationTypeKey()); | ||
| stored.setNotificationTemplateKey(request.getNotificationTemplateKey()); | ||
| stored.setPriority(request.getPriority()); | ||
| stored.setNavigationTargetObject(request.getNavigationTargetObject()); | ||
| stored.setNavigationTargetAction(request.getNavigationTargetAction()); | ||
| stored.setSentAt(sentAt); | ||
|
|
||
| if (request.getProperties() != null) { | ||
| stored.setProperties(buildProperties(request)); | ||
| } | ||
|
|
||
| if (request.getTargetParameters() != null) { | ||
| stored.setTargetParameters(buildTargetParameters(request)); | ||
| } | ||
|
|
||
| return stored; | ||
| } | ||
|
|
||
| private List<NotificationProperties> buildProperties(Notifications notification) { | ||
| List<NotificationProperties> properties = new ArrayList<>(); | ||
| notification | ||
| .getProperties() | ||
| .forEach( | ||
| prop -> { | ||
| NotificationProperties p = NotificationProperties.create(); | ||
| p.setPropertyKey(prop.getKey()); | ||
| p.setPropertyValue(prop.getValue()); | ||
| properties.add(p); | ||
| }); | ||
| return properties; | ||
| } | ||
|
|
||
| private List<NotificationTargetParameters> buildTargetParameters(Notifications notification) { | ||
| List<NotificationTargetParameters> params = new ArrayList<>(); | ||
| notification | ||
| .getTargetParameters() | ||
| .forEach( | ||
| param -> { | ||
| NotificationTargetParameters p = NotificationTargetParameters.create(); | ||
| p.setParamKey(param.getKey()); | ||
| p.setParamValue(param.getValue()); | ||
| params.add(p); | ||
| }); | ||
| return params; | ||
| } | ||
|
|
||
| private String resolveRecipientId(Recipients recipient) { | ||
| if (recipient.getGlobalUserId() != null && !recipient.getGlobalUserId().isBlank()) { | ||
| return recipient.getGlobalUserId(); | ||
| } | ||
| return recipient.getRecipientId(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| namespace sap.cds.notifications; | ||
|
|
||
| @PersonalData.EntitySemantics : 'Other' | ||
| entity Notifications { | ||
| key ID : UUID not null; | ||
| @PersonalData.FieldSemantics : 'DataSubjectID' | ||
| key recipient : String(254) not null; | ||
| notificationTypeKey : String(128); | ||
| notificationTemplateKey : String; | ||
| priority : String(20); | ||
| navigationTargetObject : String(500); | ||
| navigationTargetAction : String(500); | ||
| sentAt : Timestamp; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention in the readme that this is UTC time. |
||
| properties : Composition of many NotificationProperties | ||
| on properties.notification = $self; | ||
| targetParameters : Composition of many NotificationTargetParameters | ||
| on targetParameters.notification = $self; | ||
| } | ||
|
|
||
| entity NotificationProperties { | ||
| key notification : Association to Notifications not null; | ||
| key propertyKey : String(128) not null; | ||
| @PersonalData.FieldSemantics : 'IsPotentiallyPersonal' | ||
| propertyValue : String; | ||
| } | ||
|
|
||
| entity NotificationTargetParameters { | ||
| key notification : Association to Notifications not null; | ||
| key paramKey : String(250) not null; | ||
| @PersonalData.FieldSemantics : 'IsPotentiallyPersonal' | ||
| paramValue : String; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| using from './NotificationProviderService.cds'; | ||
| using from './NotificationTypeProviderService.cds'; | ||
| using from './NotificationTemplateProviderService.cds'; | ||
| using from './NotificationTemplateProviderService.cds'; | ||
| using from './NotificationStorage'; |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cds.requires is the namespace for require service confiugration -> feature toggles are in cds.