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);
}
}