diff --git a/persistence-service-v1/persistence-service-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/model/notification/NotificationType.java b/persistence-service-v1/persistence-service-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/model/notification/NotificationType.java new file mode 100644 index 000000000..3c44bb8c1 --- /dev/null +++ b/persistence-service-v1/persistence-service-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/model/notification/NotificationType.java @@ -0,0 +1,11 @@ +package com.iqser.red.service.persistence.service.v1.api.model.notification; + +public enum NotificationType { + + ASSIGN_REVIEWER, ASSIGN_APPROVER, UNASSIGNED_FROM_FILE, + DOCUMENT_APPROVED, DOSSIER_OWNER_SET, DOSSIER_OWNER_REMOVED, + USER_BECOMES_DOSSIER_MEMBER, USER_REMOVED_AS_DOSSIER_MEMBER, + USER_PROMOTED_TO_APPROVER, USER_DEGRADED_TO_REVIEWER, + DOSSIER_OWNER_DELETED, DOWNLOAD_READY, DOSSIER_DELETED + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationEmailService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationEmailService.java new file mode 100644 index 000000000..a9d71ad3f --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationEmailService.java @@ -0,0 +1,45 @@ +package com.iqser.red.service.persistence.management.v1.processor.service.persistence; + +import com.iqser.red.service.persistence.management.v1.processor.entity.notification.NotificationEntity; +import com.iqser.red.service.persistence.service.v1.api.model.notification.EmailNotificationType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class NotificationEmailService { + + public void sendNotificationEmail(String userId, EmailNotificationType emailNotificationType, List notifications) { + + switch (emailNotificationType) { + case DAILY: + sendDailyEmail(userId, notifications); + case DAILY_SUMMARY: + sendDailySummaryEmail(userId, notifications); + case WEEKLY_SUMMARY: + sendWeeklySummaryEmail(userId, notifications); + } + + } + + private void sendWeeklySummaryEmail(String userId, List notifications) { + log.info("Should send weekly notification summary email for {}", userId); + // TODO + } + + private void sendDailySummaryEmail(String userId, List notifications) { + log.info("Should send daily notification summary email for {}", userId); + // TODO + } + + private void sendDailyEmail(String userId, List notifications) { + log.info("Should send daily notification email for {}", userId); + // TODO + } + + +} diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPersistenceService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPersistenceService.java index d84c1af93..d20f28203 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPersistenceService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPersistenceService.java @@ -4,6 +4,7 @@ import com.iqser.red.service.persistence.management.v1.processor.entity.notifica import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.NotificationRepository; import com.iqser.red.service.persistence.service.v1.api.model.audit.AddNotificationRequest; +import com.iqser.red.service.persistence.service.v1.api.model.notification.EmailNotificationType; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.beans.BeanUtils; @@ -20,7 +21,12 @@ public class NotificationPersistenceService { private final NotificationRepository notificationRepository; + private final NotificationPreferencesPersistenceService notificationPreferencesPersistenceService; + + private final NotificationEmailService notificationEmailService; + public boolean hasNewNotificationsSince(String userId, OffsetDateTime since) { + notificationPreferencesPersistenceService.initializePreferencesIfNotExists(userId); return notificationRepository.hasInAppNotificationForUser(userId, since.truncatedTo(ChronoUnit.MILLIS)) > 0; } @@ -30,6 +36,16 @@ public class NotificationPersistenceService { BeanUtils.copyProperties(addNotificationRequest, notification); notification.setCreationDate(OffsetDateTime.now()); + var notificationPreferences = notificationPreferencesPersistenceService.getOrCreateNotificationPreferences(addNotificationRequest.getUserId()); + if (!notificationPreferences.isInAppNotificationsEnabled() || notificationPreferences.getInAppNotifications().contains(notification.getNotificationType())) { + notification.setSoftDeleted(OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS)); + } + + if (notificationPreferences.isEmailNotificationsEnabled() && notificationPreferences.getEmailNotifications().contains(notification.getNotificationType()) + && notificationPreferences.getEmailNotificationType() == EmailNotificationType.DAILY) { + notificationEmailService.sendNotificationEmail(notification.getUserId(), EmailNotificationType.DAILY, List.of(notification)); + } + notificationRepository.save(notification); } @@ -66,12 +82,17 @@ public class NotificationPersistenceService { } - public List getNotifications(String userId, boolean includeSeen) { + public List getNotificationsForEmailSummary(String userId, OffsetDateTime from, OffsetDateTime to) { + return notificationRepository.findEmailNotificationsForUserId(userId, from, to); + } + + public List getNotifications(String userId, boolean includeSeen) { + notificationPreferencesPersistenceService.initializePreferencesIfNotExists(userId); if (includeSeen) { return notificationRepository.findAppNotificationsForUser(userId); } else { - return notificationRepository.findNotNotificationsSeenForUser(userId); + return notificationRepository.findUnseenNotificationsForUser(userId); } } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPreferencesPersistenceService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPreferencesPersistenceService.java index 92614de81..2d0cd242f 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPreferencesPersistenceService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/NotificationPreferencesPersistenceService.java @@ -1,14 +1,21 @@ package com.iqser.red.service.persistence.management.v1.processor.service.persistence; import com.iqser.red.service.persistence.management.v1.processor.entity.notification.NotificationPreferencesEntity; -import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.NotificationPreferencesRepository; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.NotificationRepository; import com.iqser.red.service.persistence.service.v1.api.model.notification.NotificationPreferences; +import com.iqser.red.service.persistence.service.v1.api.model.notification.NotificationType; import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import javax.transaction.Transactional; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -16,12 +23,27 @@ public class NotificationPreferencesPersistenceService { private final NotificationPreferencesRepository notificationPreferencesRepository; + private final NotificationRepository notificationRepository; + @Transactional - public void setNotificationPreference(String userId, NotificationPreferences notification) { + public void setNotificationPreference(String userId, NotificationPreferences newNotificationPreferences) { notificationPreferencesRepository.findById(userId) - .ifPresentOrElse(n -> BeanUtils.copyProperties(notification, n), () -> { + .ifPresentOrElse(oldNotificationPreferences -> { + BeanUtils.copyProperties(newNotificationPreferences, oldNotificationPreferences); + + // if we disabled the notification preferences just now + if (oldNotificationPreferences.isInAppNotificationsEnabled() && !newNotificationPreferences.isInAppNotificationsEnabled()) { + notificationRepository.deleteAllByUserId(userId, OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS)); + } + + var removedNotificationTypes = new ArrayList<>(oldNotificationPreferences.getInAppNotifications()); + removedNotificationTypes.removeAll(newNotificationPreferences.getInAppNotifications()); + + notificationRepository.deleteAllByUserIdAndNotificationTypeIn(userId, removedNotificationTypes, OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS)); + + }, () -> { NotificationPreferencesEntity notificationPreferencesEntity = new NotificationPreferencesEntity(); - BeanUtils.copyProperties(notification, notificationPreferencesEntity); + BeanUtils.copyProperties(newNotificationPreferences, notificationPreferencesEntity); notificationPreferencesEntity.setUserId(userId); notificationPreferencesRepository.save(notificationPreferencesEntity); }); @@ -33,9 +55,26 @@ public class NotificationPreferencesPersistenceService { } @Transactional - public NotificationPreferencesEntity getNotificationPreferences(String userId) { - return notificationPreferencesRepository.findById(userId).orElseThrow(() -> { - throw new NotFoundException("Notification preferences not found for userId: " + userId); + public NotificationPreferencesEntity getOrCreateNotificationPreferences(String userId) { + return notificationPreferencesRepository.findById(userId).orElseGet(() -> { + + var notificationPreference = new NotificationPreferencesEntity(); + notificationPreference.setUserId(userId); + notificationPreference.setEmailNotificationsEnabled(false); + notificationPreference.setInAppNotificationsEnabled(true); + notificationPreference.setInAppNotifications(Arrays.stream(NotificationType.values()).map(Enum::name).collect(Collectors.toList())); + return notificationPreferencesRepository.save(notificationPreference); }); } + + public void initializePreferencesIfNotExists(String userId) { + + if (!notificationPreferencesRepository.existsByUserId(userId)) { + getOrCreateNotificationPreferences(userId); + } + } + + public List findAll() { + return notificationPreferencesRepository.findAll(); + } } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationPreferencesRepository.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationPreferencesRepository.java index 206736d0d..a6e212b97 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationPreferencesRepository.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationPreferencesRepository.java @@ -4,4 +4,5 @@ import com.iqser.red.service.persistence.management.v1.processor.entity.notifica import org.springframework.data.jpa.repository.JpaRepository; public interface NotificationPreferencesRepository extends JpaRepository { + boolean existsByUserId(String userId); } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationRepository.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationRepository.java index fe4b7ca04..9b2e35d7b 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationRepository.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/NotificationRepository.java @@ -6,6 +6,7 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import java.time.OffsetDateTime; +import java.util.ArrayList; import java.util.List; public interface NotificationRepository extends JpaRepository { @@ -13,30 +14,24 @@ public interface NotificationRepository extends JpaRepository :since") int hasInAppNotificationForUser(String userId, OffsetDateTime since); - @Query("Select n from NotificationEntity n where " + + @Query("select n from NotificationEntity n where " + " ( " + - " (exists (select e from NotificationPreferencesEntity e where e.userId = :userId) and n.notificationType in ( select apn from NotificationPreferencesEntity p join p.inAppNotifications apn where p.userId = :userId )) " + - " or" + - " not exists (select e from NotificationPreferencesEntity e where e.userId = :userId) " + + " (exists (select e from NotificationPreferencesEntity e where e.userId = :userId) and n.notificationType in ( select apn from NotificationPreferencesEntity p join p.inAppNotifications apn where p.userId = :userId and p.inAppNotificationsEnabled = true )) " + " ) " + " and n.softDeleted is null and n.userId = :userId order by n.creationDate desc") List findAppNotificationsForUser(String userId); - @Query("Select n from NotificationEntity n where " + + @Query("select n from NotificationEntity n where " + " ( " + - " (exists (select e from NotificationPreferencesEntity e where e.userId = :userId) and n.notificationType in ( select apn from NotificationPreferencesEntity p join p.inAppNotifications apn where p.userId = :userId )) " + - " or" + - " not exists (select e from NotificationPreferencesEntity e where e.userId = :userId) " + + " (exists (select e from NotificationPreferencesEntity e where e.userId = :userId) and n.notificationType in ( select apn from NotificationPreferencesEntity p join p.inAppNotifications apn where p.userId = :userId and p.inAppNotificationsEnabled = true )) " + " ) " + - " and n.seenDate is null and n.softDeleted is null and n.userId = :userId order by n.creationDate desc") - List findNotNotificationsSeenForUser(String userId); + " and n.seenDate is null and n.softDeleted is null and n.userId = :userId order by n.creationDate desc ") + List findUnseenNotificationsForUser(String userId); @Modifying @Query("update NotificationEntity n set n.seenDate = :seenDate where n.id = :notificationId and n.userId = :userId") @@ -49,4 +44,21 @@ public interface NotificationRepository extends JpaRepository removedNotificationTypes, OffsetDateTime softDeleted); + + + @Query("select n from NotificationEntity n where " + + " ( " + + " (exists (select e from NotificationPreferencesEntity e where e.userId = :userId) and n.notificationType in ( select apn from NotificationPreferencesEntity p join p.emailNotifications apn where p.userId = :userId and p.emailNotificationsEnabled = true )) " + + " ) " + + " and n.userId = :userId and n.creationDate :from and n.creationDate <= :to order by n.creationDate desc") + List findEmailNotificationsForUserId(String userId, OffsetDateTime from, OffsetDateTime to); } diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/controller/NotificationPreferencesController.java b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/controller/NotificationPreferencesController.java index 567e7fdf2..a93842dc9 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/controller/NotificationPreferencesController.java +++ b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/controller/NotificationPreferencesController.java @@ -21,7 +21,7 @@ public class NotificationPreferencesController implements NotificationPreference @Override public NotificationPreferences getNotificationPreferences(String userId) { - return convert(notificationPreferencesPersistenceService.getNotificationPreferences(userId), NotificationPreferences.class); + return convert(notificationPreferencesPersistenceService.getOrCreateNotificationPreferences(userId), NotificationPreferences.class); } @Override diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/jobs/CreateJobsConfiguration.java b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/jobs/CreateJobsConfiguration.java index 0c2f42fec..5cff38134 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/jobs/CreateJobsConfiguration.java +++ b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/jobs/CreateJobsConfiguration.java @@ -3,6 +3,7 @@ package com.iqser.red.service.peristence.v1.server.jobs; import com.iqser.red.service.peristence.v1.server.service.job.AutomaticAnalysisJob; import com.iqser.red.service.peristence.v1.server.service.job.DeletedFilesCleanupJob; import com.iqser.red.service.peristence.v1.server.service.job.DownloadCleanupJob; +import com.iqser.red.service.peristence.v1.server.service.job.SendNotificationEmailJob; import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,6 +13,26 @@ import java.text.ParseException; @Configuration public class CreateJobsConfiguration { + @Bean + public JobDetail notificationEmailSummaryJob() { + return JobBuilder.newJob().ofType(SendNotificationEmailJob.class) + .storeDurably() + .withIdentity("SendNotificationEmailJob") + .withDescription("Send summary email with daily/weekly notifications") + .build(); + } + + @Bean + public Trigger notificationEmailSummaryJobTrigger() throws ParseException { + return TriggerBuilder.newTrigger().forJob(notificationEmailSummaryJob()) + .withIdentity("SendNotificationEmailJobTrigger") + .withDescription("Triggers SendNotificationEmailJob every night at 3 AM") + .withSchedule(CronScheduleBuilder.cronSchedule(new CronExpression("0 0 3 * * ?"))) + .build(); + } + + + @Bean public JobDetail automaticAnalysisJobDetail() { return JobBuilder.newJob().ofType(AutomaticAnalysisJob.class) diff --git a/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/service/job/SendNotificationEmailJob.java b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/service/job/SendNotificationEmailJob.java new file mode 100644 index 000000000..a27c6d7e3 --- /dev/null +++ b/persistence-service-v1/persistence-service-server-v1/src/main/java/com/iqser/red/service/peristence/v1/server/service/job/SendNotificationEmailJob.java @@ -0,0 +1,64 @@ +package com.iqser.red.service.peristence.v1.server.service.job; + +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationEmailService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPreferencesPersistenceService; +import com.iqser.red.service.persistence.service.v1.api.model.notification.EmailNotificationType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.springframework.stereotype.Service; + +import java.time.DayOfWeek; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; + +@Slf4j +@RequiredArgsConstructor +@Service +public class SendNotificationEmailJob implements Job { + + private final NotificationEmailService notificationEmailService; + private final NotificationPersistenceService notificationPersistenceService; + private final NotificationPreferencesPersistenceService notificationPreferencesPersistenceService; + + + @Override + public void execute(JobExecutionContext jobExecutionContext) { + + + var allConfiguredPreferences = notificationPreferencesPersistenceService.findAll(); + allConfiguredPreferences.forEach(preference -> { + if (preference.isEmailNotificationsEnabled()) { + + // summary emails only + if (preference.getEmailNotificationType() == EmailNotificationType.WEEKLY_SUMMARY || preference.getEmailNotificationType() == EmailNotificationType.DAILY_SUMMARY) { + + var now = OffsetDateTime.now().truncatedTo(ChronoUnit.HOURS); + + // Weekly summary is sent monday night + if (now.getDayOfWeek() == DayOfWeek.MONDAY && preference.getEmailNotificationType() == EmailNotificationType.WEEKLY_SUMMARY) { + var from = OffsetDateTime.now().minusDays(7).truncatedTo(ChronoUnit.HOURS).withHour(0); + var to = OffsetDateTime.now().truncatedTo(ChronoUnit.HOURS).withHour(0); + var notifications = notificationPersistenceService.getNotificationsForEmailSummary(preference.getUserId(), from, to); + + notificationEmailService.sendNotificationEmail(preference.getUserId(), EmailNotificationType.WEEKLY_SUMMARY, notifications); + } else { + var from = OffsetDateTime.now().minusDays(1).withHour(0).truncatedTo(ChronoUnit.HOURS); + var to = OffsetDateTime.now().minusDays(1).withHour(23).truncatedTo(ChronoUnit.HOURS); + var notifications = notificationPersistenceService.getNotificationsForEmailSummary(preference.getUserId(), from, to); + + notificationEmailService.sendNotificationEmail(preference.getUserId(), EmailNotificationType.DAILY_SUMMARY, notifications); + + } + + } + + } + }); + + } + + +}