added chats with another users
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package com.krylov.refound.config;
|
||||
|
||||
import com.krylov.refound.util.WsHandshakeInterceptor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
@@ -8,18 +10,22 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
@RequiredArgsConstructor
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
private final WsHandshakeInterceptor wsHandshakeInterceptor;
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||
config.enableSimpleBroker("/topic");
|
||||
config.setApplicationDestinationPrefixes("/app");
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.enableSimpleBroker("/topic"); // сервер → клиент
|
||||
registry.setApplicationDestinationPrefixes("/app"); // клиент → сервер
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/ws")
|
||||
.setAllowedOriginPatterns("*")
|
||||
.addInterceptors(wsHandshakeInterceptor)
|
||||
.withSockJS();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,45 @@
|
||||
package com.krylov.refound.controller;
|
||||
|
||||
import com.krylov.refound.dto.ChatMessage;
|
||||
import com.krylov.refound.entity.Message;
|
||||
import com.krylov.refound.entity.User;
|
||||
import com.krylov.refound.repository.UserRepository;
|
||||
import com.krylov.refound.service.MessageService;
|
||||
import java.security.Principal;
|
||||
import java.util.Optional;
|
||||
import com.krylov.refound.dto.*;
|
||||
import com.krylov.refound.service.ChatService;
|
||||
import com.krylov.refound.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/v1/chats")
|
||||
public class ChatController {
|
||||
private final MessageService service;
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final UserService userService;
|
||||
private final ChatService chatService;
|
||||
|
||||
@MessageMapping("/chat")
|
||||
public void send(@Payload ChatMessage message, Principal principal) {
|
||||
|
||||
Optional<User> sender = userRepository.findByLogin(principal.getName());
|
||||
Message saved = new Message();
|
||||
if(sender.isPresent()){
|
||||
saved = service.send(message.getReceiverId(), message.getContent());
|
||||
// Создать чат с пользователем или получить существующий
|
||||
@PostMapping("/with/{otherUserId}")
|
||||
public ChatDto createOrGetChat(@PathVariable Long otherUserId, @RequestParam(required = false) Long postId, Authentication auth) {
|
||||
Long currentUserId = extractUserId(auth); // см. ниже как достать id
|
||||
return chatService.getOrCreateChat(currentUserId, otherUserId, postId);
|
||||
}
|
||||
|
||||
// отправка получателю
|
||||
messagingTemplate.convertAndSend("/topic/messages/" + message.getReceiverId(), saved);
|
||||
// Список всех чатов текущего пользователя
|
||||
@GetMapping
|
||||
public List<ChatDto> getMyChats(Authentication auth) {
|
||||
return chatService.getUserChats(extractUserId(auth));
|
||||
}
|
||||
|
||||
// История сообщений конкретного чата
|
||||
@GetMapping("/{chatId}/messages")
|
||||
public List<MessageDto> getMessages(@PathVariable Long chatId) {
|
||||
return chatService.getMessages(chatId);
|
||||
}
|
||||
|
||||
private Long extractUserId(Authentication auth) {
|
||||
// Подставь свою логику получения userId из JWT/Authentication
|
||||
// Например если в токене login, а не id - подтяни через UserService
|
||||
String login = auth.getName();
|
||||
return userService.getUserIdByLogin(login);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.krylov.refound.controller;
|
||||
|
||||
import com.krylov.refound.dto.SendMessageRequest;
|
||||
import com.krylov.refound.service.ChatService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class ChatWebSocketController {
|
||||
private final ChatService chatService;
|
||||
|
||||
// Клиент шлёт сюда: /app/chat.send
|
||||
@MessageMapping("/chat.send")
|
||||
public void sendMessage(SendMessageRequest request, SimpMessageHeaderAccessor headerAccessor) {
|
||||
Long senderId = (Long) headerAccessor.getSessionAttributes().get("userId");
|
||||
chatService.sendMessage(senderId, request.getChatId(), request.getContent());
|
||||
// ChatService сам отправит сообщение всем подписчикам через messagingTemplate
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.krylov.refound.controller;
|
||||
|
||||
import com.krylov.refound.dto.DialogResponse;
|
||||
import com.krylov.refound.entity.Message;
|
||||
import com.krylov.refound.service.MessageService;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/messages")
|
||||
@RequiredArgsConstructor
|
||||
public class MessageController {
|
||||
private final MessageService service;
|
||||
|
||||
@PostMapping
|
||||
public void send(@RequestParam Long receiverId,
|
||||
@RequestParam String content) {
|
||||
service.send(receiverId, content);
|
||||
}
|
||||
|
||||
@GetMapping("/{userId}")
|
||||
public List<Message> chat(@PathVariable Long userId) {
|
||||
return service.getChat(userId);
|
||||
}
|
||||
|
||||
@GetMapping("/dialogs")
|
||||
public List<DialogResponse> getDialogs() {
|
||||
return service.getDialogs();
|
||||
}
|
||||
|
||||
@PostMapping("/read/{userId}")
|
||||
public void markAsRead(@PathVariable Long userId) {
|
||||
service.markAsRead(userId);
|
||||
}
|
||||
}
|
||||
16
src/main/java/com/krylov/refound/dto/ChatDto.java
Normal file
16
src/main/java/com/krylov/refound/dto/ChatDto.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.krylov.refound.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChatDto {
|
||||
private Long id;
|
||||
private Long otherUserId;
|
||||
private String otherUserName;
|
||||
private String otherUserAvatar;
|
||||
private Long postId;
|
||||
private String lastMessage;
|
||||
private LocalDateTime lastMessageAt;
|
||||
private long unreadCount;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.krylov.refound.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CreateChatRequest {
|
||||
private Long postId; // опционально, с каким объявлением связан чат
|
||||
}
|
||||
14
src/main/java/com/krylov/refound/dto/MessageDto.java
Normal file
14
src/main/java/com/krylov/refound/dto/MessageDto.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.krylov.refound.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class MessageDto {
|
||||
private Long id;
|
||||
private Long chatId;
|
||||
private Long senderId;
|
||||
private String content;
|
||||
private boolean isRead;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.krylov.refound.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SendMessageRequest {
|
||||
private Long chatId;
|
||||
private String content;
|
||||
}
|
||||
74
src/main/java/com/krylov/refound/entity/Chat.java
Normal file
74
src/main/java/com/krylov/refound/entity/Chat.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package com.krylov.refound.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
|
||||
@Entity
|
||||
@Table(name = "chats", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_one_id", "user_two_id"})})
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@RequiredArgsConstructor
|
||||
public class Chat {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "user_one_id", nullable = false)
|
||||
private Long userOneId; // всегда меньший id (для уникальности пары)
|
||||
|
||||
@Column(name = "user_two_id", nullable = false)
|
||||
private Long userTwoId; // всегда больший id
|
||||
|
||||
@Column(name = "post_id")
|
||||
private Long postId; // объявление, по поводу которого начат чат (опционально)
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
Class<?> oEffectiveClass =
|
||||
o instanceof HibernateProxy proxy ? proxy.getHibernateLazyInitializer()
|
||||
.getPersistentClass() : o.getClass();
|
||||
Class<?> thisEffectiveClass =
|
||||
this instanceof HibernateProxy proxy ? proxy.getHibernateLazyInitializer()
|
||||
.getPersistentClass() : this.getClass();
|
||||
if (thisEffectiveClass != oEffectiveClass) {
|
||||
return false;
|
||||
}
|
||||
Chat chat = (Chat) o;
|
||||
return getId() != null && Objects.equals(getId(), chat.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return this instanceof HibernateProxy proxy ? proxy.getHibernateLazyInitializer()
|
||||
.getPersistentClass().hashCode() : getClass().hashCode();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
@@ -20,16 +21,23 @@ public class Message {
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne
|
||||
private User sender;
|
||||
@Column(name = "chat_id", nullable = false)
|
||||
private Long chatId;
|
||||
|
||||
@ManyToOne
|
||||
private User receiver;
|
||||
@Column(name = "sender_id", nullable = false)
|
||||
private Long senderId;
|
||||
|
||||
@Column(length = 2000)
|
||||
@Column(nullable = false, columnDefinition = "TEXT")
|
||||
private String content;
|
||||
|
||||
@Column(name = "is_read", nullable = false)
|
||||
private boolean isRead = false;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
private boolean isRead;
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.krylov.refound.repository;
|
||||
|
||||
import com.krylov.refound.entity.Chat;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
public interface ChatRepository extends JpaRepository<Chat, Long> {
|
||||
|
||||
@Query("SELECT c FROM Chat c WHERE c.userOneId = :u1 AND c.userTwoId = :u2")
|
||||
Optional<Chat> findByUsers(Long u1, Long u2);
|
||||
|
||||
@Query("SELECT c FROM Chat c WHERE c.userOneId = :userId OR c.userTwoId = :userId ORDER BY c.createdAt DESC")
|
||||
List<Chat> findAllByUserId(Long userId); // Changed from Optional to List
|
||||
}
|
||||
@@ -9,48 +9,7 @@ import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
public interface MessageRepository extends JpaRepository<Message, Long> {
|
||||
|
||||
@Query("""
|
||||
SELECT m FROM Message m
|
||||
WHERE (m.sender.id = :user1 AND m.receiver.id = :user2)
|
||||
OR (m.sender.id = :user2 AND m.receiver.id = :user1)
|
||||
ORDER BY m.createdAt
|
||||
""")
|
||||
List<Message> findChat(Long user1, Long user2);
|
||||
|
||||
@Query("""
|
||||
SELECT m FROM Message m
|
||||
WHERE m.id IN (
|
||||
SELECT MAX(m2.id)
|
||||
FROM Message m2
|
||||
WHERE m2.sender.id = :userId OR m2.receiver.id = :userId
|
||||
GROUP BY
|
||||
CASE
|
||||
WHEN m2.sender.id = :userId THEN m2.receiver.id
|
||||
ELSE m2.sender.id
|
||||
END
|
||||
)
|
||||
ORDER BY m.createdAt DESC
|
||||
""")
|
||||
List<Message> findDialogs(Long userId);
|
||||
|
||||
// 🔥 количество непрочитанных от пользователя
|
||||
@Query("""
|
||||
SELECT COUNT(m) FROM Message m
|
||||
WHERE m.receiver.id = :userId
|
||||
AND m.sender.id = :otherUserId
|
||||
AND m.isRead = false
|
||||
""")
|
||||
int countUnread(Long userId, Long otherUserId);
|
||||
|
||||
// 🔥 пометить как прочитанные
|
||||
@Modifying
|
||||
@Query("""
|
||||
UPDATE Message m
|
||||
SET m.isRead = true
|
||||
WHERE m.receiver.id = :userId
|
||||
AND m.sender.id = :otherUserId
|
||||
AND m.isRead = false
|
||||
""")
|
||||
void markAsRead(Long userId, Long otherUserId);
|
||||
List<Message> findByChatIdOrderByCreatedAtAsc(Long chatId);
|
||||
long countByChatIdAndIsReadFalseAndSenderIdNot(Long chatId, Long currentUserId);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@ package com.krylov.refound.repository;
|
||||
import com.krylov.refound.entity.User;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
Optional<User> findByLogin(String email);
|
||||
|
||||
Boolean existsByLogin(String login);
|
||||
|
||||
@Query("SELECT u.id FROM User u WHERE u.login = :login")
|
||||
Optional<Long> findUserByLogin(@Param("login") String login);
|
||||
}
|
||||
|
||||
125
src/main/java/com/krylov/refound/service/ChatService.java
Normal file
125
src/main/java/com/krylov/refound/service/ChatService.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package com.krylov.refound.service;
|
||||
|
||||
import com.krylov.refound.dto.ChatDto;
|
||||
import com.krylov.refound.dto.MessageDto;
|
||||
import com.krylov.refound.entity.Chat;
|
||||
import com.krylov.refound.entity.Message;
|
||||
import com.krylov.refound.repository.ChatRepository;
|
||||
import com.krylov.refound.repository.MessageRepository;
|
||||
import com.krylov.refound.repository.UserRepository;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ChatService {
|
||||
private final ChatRepository chatRepository;
|
||||
private final MessageRepository messageRepository;
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
private final UserRepository userRepository; // твой существующий репозиторий юзеров
|
||||
|
||||
// Создать чат с пользователем (или вернуть существующий)
|
||||
@Transactional
|
||||
public ChatDto getOrCreateChat(Long currentUserId, Long otherUserId, Long postId) {
|
||||
if (currentUserId.equals(otherUserId)) {
|
||||
throw new IllegalArgumentException("Нельзя создать чат с самим собой");
|
||||
}
|
||||
|
||||
Long u1 = Math.min(currentUserId, otherUserId);
|
||||
Long u2 = Math.max(currentUserId, otherUserId);
|
||||
|
||||
Chat chat = chatRepository.findByUsers(u1, u2)
|
||||
.orElseGet(() -> {
|
||||
Chat newChat = new Chat();
|
||||
newChat.setUserOneId(u1);
|
||||
newChat.setUserTwoId(u2);
|
||||
newChat.setPostId(postId);
|
||||
return chatRepository.save(newChat);
|
||||
});
|
||||
|
||||
return toDto(chat, currentUserId);
|
||||
}
|
||||
|
||||
// Все чаты пользователя
|
||||
public List<ChatDto> getUserChats(Long userId) {
|
||||
return chatRepository.findAllByUserId(userId).stream()
|
||||
.map(c -> toDto(c, userId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// История сообщений в чате
|
||||
public List<MessageDto> getMessages(Long chatId) {
|
||||
return messageRepository.findByChatIdOrderByCreatedAtAsc(chatId).stream()
|
||||
.map(this::toMessageDto)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Отправка сообщения (вызывается из WebSocket контроллера)
|
||||
@Transactional
|
||||
public MessageDto sendMessage(Long senderId, Long chatId, String content) {
|
||||
Chat chat = chatRepository.findById(chatId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Чат не найден"));
|
||||
|
||||
// Проверка что отправитель - участник чата
|
||||
if (!chat.getUserOneId().equals(senderId) && !chat.getUserTwoId().equals(senderId)) {
|
||||
throw new SecurityException("Вы не участник этого чата");
|
||||
}
|
||||
|
||||
Message message = new Message();
|
||||
message.setChatId(chatId);
|
||||
message.setSenderId(senderId);
|
||||
message.setContent(content);
|
||||
message = messageRepository.save(message);
|
||||
|
||||
MessageDto dto = toMessageDto(message);
|
||||
|
||||
// Отправляем сообщение через WebSocket в топик конкретного чата
|
||||
messagingTemplate.convertAndSend("/topic/chat/" + chatId, dto);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
private ChatDto toDto(Chat chat, Long currentUserId) {
|
||||
Long otherUserId = chat.getUserOneId().equals(currentUserId)
|
||||
? chat.getUserTwoId() : chat.getUserOneId();
|
||||
|
||||
ChatDto dto = new ChatDto();
|
||||
dto.setId(chat.getId());
|
||||
dto.setOtherUserId(otherUserId);
|
||||
dto.setPostId(chat.getPostId());
|
||||
|
||||
// Подтягиваем имя/аватар собеседника из твоего UserRepository
|
||||
userRepository.findById(otherUserId).ifPresent(user -> {
|
||||
dto.setOtherUserName(user.getName() + " " + user.getLastName());
|
||||
dto.setOtherUserAvatar(user.getAvatarUrl());
|
||||
});
|
||||
|
||||
List<Message> messages = messageRepository.findByChatIdOrderByCreatedAtAsc(chat.getId());
|
||||
if (!messages.isEmpty()) {
|
||||
Message last = messages.getLast();
|
||||
dto.setLastMessage(last.getContent());
|
||||
dto.setLastMessageAt(last.getCreatedAt());
|
||||
}
|
||||
|
||||
dto.setUnreadCount(
|
||||
messageRepository.countByChatIdAndIsReadFalseAndSenderIdNot(chat.getId(), currentUserId)
|
||||
);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
private MessageDto toMessageDto(Message m) {
|
||||
MessageDto dto = new MessageDto();
|
||||
dto.setId(m.getId());
|
||||
dto.setChatId(m.getChatId());
|
||||
dto.setSenderId(m.getSenderId());
|
||||
dto.setContent(m.getContent());
|
||||
dto.setRead(m.isRead());
|
||||
dto.setCreatedAt(m.getCreatedAt());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -19,65 +19,5 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MessageService {
|
||||
private final MessageRepository messageRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final UserService userService;
|
||||
|
||||
public Message send(Long receiverId, String content) {
|
||||
|
||||
User sender = userService.getCurrentUser();
|
||||
|
||||
User receiver = userRepository.findById(receiverId)
|
||||
.orElseThrow(() -> new ApiException(ErrorCode.NOT_FOUND, "User not found", HttpStatus.NOT_FOUND));
|
||||
|
||||
Message message = new Message();
|
||||
message.setSender(sender);
|
||||
message.setReceiver(receiver);
|
||||
message.setContent(content);
|
||||
message.setCreatedAt(LocalDateTime.now());
|
||||
message.setRead(false);
|
||||
|
||||
return messageRepository.save(message);
|
||||
}
|
||||
|
||||
public List<Message> getChat(Long userId) {
|
||||
User current = userService.getCurrentUser();
|
||||
return messageRepository.findChat(current.getId(), userId);
|
||||
}
|
||||
|
||||
public List<DialogResponse> getDialogs() {
|
||||
Long userId = 1L;
|
||||
List<Message> dialogs = messageRepository.findDialogs(userId);
|
||||
|
||||
return dialogs.stream().map(m -> {
|
||||
|
||||
boolean isMeSender = m.getSender().getId().equals(userId);
|
||||
|
||||
Long otherUserId = isMeSender
|
||||
? m.getReceiver().getId()
|
||||
: m.getSender().getId();
|
||||
|
||||
String email = isMeSender
|
||||
? m.getReceiver().getLogin()
|
||||
: m.getSender().getLogin();
|
||||
|
||||
int unread = messageRepository.countUnread(userId, otherUserId);
|
||||
|
||||
return new DialogResponse(
|
||||
otherUserId,
|
||||
email,
|
||||
m.getContent(),
|
||||
m.getCreatedAt(),
|
||||
(long )unread
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void markAsRead(Long otherUserId) {
|
||||
|
||||
User current = userService.getCurrentUser();
|
||||
|
||||
messageRepository.markAsRead(current.getId(), otherUserId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,4 +77,9 @@ public class UserService {
|
||||
.avatar(user.getAvatarUrl())
|
||||
.build();
|
||||
}
|
||||
|
||||
public Long getUserIdByLogin(String login) {
|
||||
return repository.findUserByLogin(login)
|
||||
.orElseThrow(() -> new EntityNotFoundException("User not found"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.krylov.refound.util;
|
||||
|
||||
import com.krylov.refound.security.JwtService;
|
||||
import com.krylov.refound.service.UserService;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class WsHandshakeInterceptor implements HandshakeInterceptor {
|
||||
|
||||
private final JwtService jwtService;
|
||||
private final UserService userService;
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
|
||||
if (request instanceof ServletServerHttpRequest servletRequest) {
|
||||
String token = servletRequest.getServletRequest().getParameter("token");
|
||||
if (token != null && jwtService.isTokenValid(token)) {
|
||||
String login = jwtService.extractLogin(token);
|
||||
Long userId = userService.getUserIdByLogin(login);
|
||||
attributes.put("userId", userId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // отклонить подключение без валидного токена
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {}
|
||||
}
|
||||
17
src/main/resources/db/changelog/create/006-create-chats.yaml
Normal file
17
src/main/resources/db/changelog/create/006-create-chats.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 004-create-chats-messages
|
||||
author: you
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: chats
|
||||
columns:
|
||||
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
|
||||
- column: { name: user_one_id, type: BIGINT, constraints: { nullable: false } }
|
||||
- column: { name: user_two_id, type: BIGINT, constraints: { nullable: false } }
|
||||
- column: { name: post_id, type: BIGINT }
|
||||
- column: { name: created_at, type: TIMESTAMP, constraints: { nullable: false } }
|
||||
- addUniqueConstraint:
|
||||
tableName: chats
|
||||
columnNames: user_one_id, user_two_id
|
||||
constraintName: uq_chat_users
|
||||
@@ -0,0 +1,28 @@
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 005-drop-old-messages
|
||||
author: you
|
||||
changes:
|
||||
- dropTable:
|
||||
tableName: messages
|
||||
cascadeConstraints: true
|
||||
|
||||
- changeSet:
|
||||
id: 006-create-messages-new
|
||||
author: you
|
||||
changes:
|
||||
- createTable:
|
||||
tableName: messages
|
||||
columns:
|
||||
- column: { name: id, type: BIGSERIAL, constraints: { primaryKey: true } }
|
||||
- column: { name: chat_id, type: BIGINT, constraints: { nullable: false } }
|
||||
- column: { name: sender_id, type: BIGINT, constraints: { nullable: false } }
|
||||
- column: { name: content, type: TEXT, constraints: { nullable: false } }
|
||||
- column: { name: is_read, type: BOOLEAN, defaultValueBoolean: false }
|
||||
- column: { name: created_at, type: TIMESTAMP, constraints: { nullable: false } }
|
||||
- addForeignKeyConstraint:
|
||||
baseTableName: messages
|
||||
baseColumnNames: chat_id
|
||||
referencedTableName: chats
|
||||
referencedColumnNames: id
|
||||
constraintName: fk_messages_chat
|
||||
Reference in New Issue
Block a user