NotificationService.java

package com.wavii.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wavii.model.AppNotification;
import com.wavii.model.User;
import com.wavii.repository.AppNotificationRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class NotificationService {

    private final AppNotificationRepository notificationRepository;
    private final ObjectMapper objectMapper;

    /**
     * Crea y guarda una nueva notificación para un usuario.
     * 
     * @param recipient Usuario destinatario.
     * @param type Tipo de notificación (ej. "class_request").
     * @param title Título de la notificación.
     * @param body Cuerpo o mensaje de la notificación.
     * @param data Datos adicionales en formato mapa para adjuntar como JSON.
     * @return La notificación creada.
     */
    @Transactional
    public AppNotification create(User recipient, String type, String title, String body, Map<String, Object> data) {
        return notificationRepository.save(AppNotification.builder()
                .recipient(recipient)
                .type(type)
                .title(title)
                .body(body)
                .dataJson(writeJson(data))
                .build());
    }

    /**
     * Lista todas las notificaciones de un usuario, ordenadas por fecha descendente.
     * 
     * @param recipient Usuario que consulta sus notificaciones.
     * @return Lista de notificaciones en formato mapa.
     */
    @Transactional(readOnly = true)
    public List<Map<String, Object>> list(User recipient) {
        return notificationRepository.findByRecipientOrderByCreatedAtDesc(recipient).stream()
                .map(this::toMap)
                .toList();
    }

    /**
     * Marca una notificación específica como leída.
     * 
     * @param recipient Usuario dueño de la notificación.
     * @param notificationId ID de la notificación.
     * @return La notificación actualizada.
     */
    @Transactional
    public Map<String, Object> markRead(User recipient, UUID notificationId) {
        AppNotification notification = notificationRepository.findById(notificationId)
                .orElseThrow(() -> new IllegalArgumentException("Notificacion no encontrada"));
        if (!notification.getRecipient().getId().equals(recipient.getId())) {
            throw new IllegalArgumentException("No puedes modificar esta notificacion");
        }
        notification.setRead(true);
        return toMap(notificationRepository.save(notification));
    }

    /**
     * Obtiene un resumen de las notificaciones, como el conteo de no leídas.
     * 
     * @param recipient Usuario destinatario.
     * @return Mapa con el conteo de no leídas.
     */
    @Transactional(readOnly = true)
    public Map<String, Object> summary(User recipient) {
        return Map.of("unreadCount", notificationRepository.countByRecipientAndReadFalse(recipient));
    }

    /**
     * Elimina todas las notificaciones de un usuario.
     * 
     * @param recipient Usuario destinatario.
     * @return Mapa con el número de notificaciones eliminadas.
     */
    @Transactional
    public Map<String, Object> clearAll(User recipient) {
        long removed = notificationRepository.deleteByRecipient(recipient);
        return Map.of("removed", removed);
    }

    /**
     * Marca todas las notificaciones de un usuario como leídas.
     * 
     * @param recipient Usuario destinatario.
     * @return Mapa con el número de notificaciones actualizadas.
     */
    @Transactional
    public Map<String, Object> markAllRead(User recipient) {
        int updated = notificationRepository.markAllReadByRecipient(recipient);
        return Map.of("updated", updated);
    }

    private Map<String, Object> toMap(AppNotification notification) {
        return Map.of(
                "id", notification.getId().toString(),
                "type", notification.getType(),
                "title", notification.getTitle(),
                "body", notification.getBody(),
                "data", readJson(notification.getDataJson()),
                "read", notification.isRead(),
                "createdAt", notification.getCreatedAt().toString()
        );
    }

    private String writeJson(Map<String, Object> data) {
        if (data == null || data.isEmpty()) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(data);
        } catch (JsonProcessingException e) {
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> readJson(String dataJson) {
        if (dataJson == null || dataJson.isBlank()) {
            return Map.of();
        }
        try {
            return objectMapper.readValue(dataJson, Map.class);
        } catch (JsonProcessingException e) {
            return Map.of();
        }
    }
}