package com.walkguide.service; import com.walkguide.dto.request.SendNotificationRequest; import com.walkguide.dto.response.NotificationResponse; import com.walkguide.entity.GuardianNotification; import com.walkguide.enums.NotificationType; import com.walkguide.enums.PairingStatus; import com.walkguide.exception.PairingException; import com.walkguide.exception.ResourceNotFoundException; import com.walkguide.repository.GuardianNotificationRepository; import com.walkguide.repository.PairingRelationRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.Map; @Service @RequiredArgsConstructor public class NotificationService { private final GuardianNotificationRepository notifRepository; private final PairingRelationRepository pairingRelationRepository; private final FcmService fcmService; @Transactional public NotificationResponse sendNotification(Long guardianId, SendNotificationRequest req) { var pairing = pairingRelationRepository .findByGuardian_IdAndStatus(guardianId, PairingStatus.ACTIVE) .orElseThrow(() -> new PairingException("Tidak ada user yang dipair")); Long userId = pairing.getUser().getId(); NotificationType type = "VOICE_NOTE".equalsIgnoreCase(req.getNotifType()) ? NotificationType.VOICE_NOTE : NotificationType.TEXT; GuardianNotification notif = GuardianNotification.builder() .guardianId(guardianId) .userId(userId) .notifType(type) .content(req.getContent()) .voiceNoteUrl(req.getVoiceNoteUrl()) .voiceNoteDuration(req.getVoiceNoteDuration()) .build(); notif = notifRepository.save(notif); // FCM ke user String fcmToken = pairing.getUser().getFcmToken(); String fcmBody = type == NotificationType.TEXT ? req.getContent() : "Voice note from Guardian"; fcmService.sendToToken(fcmToken, "Pesan dari Guardian", fcmBody, Map.of("type", "NOTIFICATION", "notifId", String.valueOf(notif.getId()), "notifType", type.name(), "voiceNoteUrl", req.getVoiceNoteUrl() != null ? req.getVoiceNoteUrl() : "")); return toResponse(notif); } public Page getNotifications(Long userId, Pageable pageable) { return notifRepository.findByUserIdOrderByCreatedAtDesc(userId, pageable) .map(this::toResponse); } public long getUnreadCount(Long userId) { return notifRepository.countByUserIdAndIsReadFalse(userId); } @Transactional public void markAllRead(Long userId) { var unread = notifRepository.findByUserIdAndIsReadFalseOrderByCreatedAtAsc(userId); LocalDateTime now = LocalDateTime.now(); unread.forEach(n -> { n.setIsRead(true); n.setReadAt(now); }); notifRepository.saveAll(unread); } @Transactional public void markOneRead(Long notifId) { notifRepository.findById(notifId).ifPresent(n -> { n.setIsRead(true); n.setReadAt(LocalDateTime.now()); notifRepository.save(n); }); } private NotificationResponse toResponse(GuardianNotification n) { return NotificationResponse.builder() .id(n.getId()).notifType(n.getNotifType().name()).content(n.getContent()) .voiceNoteUrl(n.getVoiceNoteUrl()).voiceNoteDuration(n.getVoiceNoteDuration()) .isRead(n.getIsRead()).readAt(n.getReadAt()).createdAt(n.getCreatedAt()).build(); } }