NotificationRetrievalService.java

package com.seebie.server.service;

import com.seebie.server.entity.Notification;
import com.seebie.server.repository.NotificationRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.time.ZoneId;
import java.util.List;

import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;

@Service
public class NotificationRetrievalService {

    private static final Logger LOG = LoggerFactory.getLogger(NotificationRetrievalService.class);

    private final NotificationRepository notificationRepo;

    public NotificationRetrievalService(NotificationRepository notificationRepo) {
        this.notificationRepo = notificationRepo;
    }

    /**
     * Separate out the finding of people to send notifications, from the email sending logic.
     * A @Transactional method must be called from a separate class from the Scheduled method anyway.
     *
     * This sets the last sent time of the Notification record,
     * that way another scan transaction (maybe by another node) that tries to read that record will ignore it.
     *
     * @return
     */
    @Transactional
    public List<NotificationRequired> getUsersToNotify(Instant lastNotifiedBefore, Instant lastLoggedBefore, Instant scanDate) {

        LOG.debug("Retrieving Notification records for users.");

        LOG.debug("Notifications will be sent to users who have not been notified since " + toLocale(lastNotifiedBefore)
                + "and have not logged their sleep since " + toLocale(lastLoggedBefore) + "."
                + "Last scan date for notified users is set to " + toLocale(scanDate) + ".");

        return notificationRepo.findNotificationsBy(lastNotifiedBefore, lastLoggedBefore).stream()
                .peek(notification -> LOG.info(createLogMessage(notification, scanDate)))
                .map(notification -> notification.withLastSent(scanDate))
                .map(Notification::getUser)
                .map(user -> new NotificationRequired(user.getEmail(), user.getPublicId()))
                .toList();
    }

    private String createLogMessage(Notification notification, Instant scanDate) {
        var user = notification.getUser().getPublicId();
        var lastSentDate = toLocale(notification.getLastSent());
        return "Updating notification record for " + user + " from " + lastSentDate + " to " + scanDate;
    }

    public static String toLocale(Instant instant) {
        return instant.atZone(ZoneId.systemDefault()).format(ISO_OFFSET_DATE_TIME);
    }

}