DirectMessageService.java

package com.wavii.service;

import com.wavii.dto.dm.DirectConversationDto;
import com.wavii.dto.dm.DirectMessageDto;
import com.wavii.model.DirectMessage;
import com.wavii.model.User;
import com.wavii.repository.DirectMessageRepository;
import com.wavii.repository.UserBlockRepository;
import com.wavii.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

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

@Service
@RequiredArgsConstructor
public class DirectMessageService {

    private final DirectMessageRepository directMessageRepository;
    private final UserRepository userRepository;
    private final UserBlockRepository userBlockRepository;
    private final NotificationService notificationService;

    @Transactional(readOnly = true)
    public List<DirectMessageDto> getConversation(User me, UUID userId) {
        User other = userRepository.findById(userId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Usuario no encontrado"));
        ensureNotBlocked(me, other);

        return directMessageRepository.findConversation(me, other)
                .stream()
                .map(DirectMessageDto::from)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<DirectConversationDto> getConversations(User me) {
        if (me == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Sesion no valida");
        }

        List<DirectMessage> messages = directMessageRepository.findRecentByUser(me.getId());
        Map<UUID, DirectConversationDto> summaries = new LinkedHashMap<>();

        for (DirectMessage message : messages) {
            User other = message.getSender().getId().equals(me.getId()) ? message.getReceiver() : message.getSender();
            if (summaries.containsKey(other.getId())) {
                continue;
            }
            if (isBlockedBetween(me.getId(), other.getId())) {
                continue;
            }
            summaries.put(
                    other.getId(),
                    new DirectConversationDto(
                            other.getId(),
                            other.getName(),
                            message.getContent(),
                            message.getCreatedAt().toString()
                    )
            );
        }

        return summaries.values().stream().toList();
    }

    @Transactional
    public DirectMessageDto sendMessage(User me, UUID userId, String rawContent) {
        if (me == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Sesion no valida");
        }
        if (me.getId().equals(userId)) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No puedes enviarte mensajes a ti mismo.");
        }

        User receiver = userRepository.findById(userId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Usuario no encontrado"));
        ensureNotBlocked(me, receiver);

        if (!receiver.isAcceptsMessages()) {
            throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Este usuario ha desactivado los mensajes directos.");
        }

        String content = rawContent == null ? "" : rawContent.trim();
        if (content.isBlank() || content.length() > 2000) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "El mensaje no puede estar vacio ni superar los 2000 caracteres.");
        }

        DirectMessage saved = directMessageRepository.save(DirectMessage.builder()
                .sender(me)
                .receiver(receiver)
                .content(content)
                .build());
        notificationService.create(
                receiver,
                "direct_message",
                "Nuevo mensaje de " + me.getName(),
                content.length() > 140 ? content.substring(0, 140) + "..." : content,
                Map.of(
                        "userId", me.getId().toString(),
                        "userName", me.getName()
                )
        );

        return DirectMessageDto.from(saved);
    }

    private void ensureNotBlocked(User me, User other) {
        if (me == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Sesion no valida");
        }
        if (isBlockedBetween(me.getId(), other.getId())) {
            throw new ResponseStatusException(HttpStatus.FORBIDDEN, "No puedes enviar mensajes a este usuario.");
        }
    }

    private boolean isBlockedBetween(UUID left, UUID right) {
        return userBlockRepository.existsByBlockerIdAndBlockedId(left, right)
                || userBlockRepository.existsByBlockerIdAndBlockedId(right, left);
    }
}