From 236d00f2f9e01a3af9b39c2aadcfe7a740dae7a7 Mon Sep 17 00:00:00 2001 From: SlimusMinus Date: Mon, 22 Jun 2026 01:08:47 +0300 Subject: [PATCH] added chats with another users --- .../refound/config/WebSocketConfig.java | 12 +- .../refound/controller/ChatController.java | 56 ++++---- .../controller/ChatWebSocketController.java | 22 +++ .../refound/controller/MessageController.java | 41 ------ .../java/com/krylov/refound/dto/ChatDto.java | 16 +++ .../krylov/refound/dto/CreateChatRequest.java | 8 ++ .../com/krylov/refound/dto/MessageDto.java | 14 ++ .../refound/dto/SendMessageRequest.java | 9 ++ .../java/com/krylov/refound/entity/Chat.java | 74 +++++++++++ .../com/krylov/refound/entity/Message.java | 20 ++- .../refound/repository/ChatRepository.java | 16 +++ .../refound/repository/MessageRepository.java | 45 +------ .../refound/repository/UserRepository.java | 5 + .../krylov/refound/service/ChatService.java | 125 ++++++++++++++++++ .../refound/service/MessageService.java | 60 --------- .../krylov/refound/service/UserService.java | 5 + .../refound/util/WsHandshakeInterceptor.java | 37 ++++++ .../db/changelog/create/006-create-chats.yaml | 17 +++ .../create/007-recreate-messages.yaml | 28 ++++ 19 files changed, 433 insertions(+), 177 deletions(-) create mode 100644 src/main/java/com/krylov/refound/controller/ChatWebSocketController.java delete mode 100644 src/main/java/com/krylov/refound/controller/MessageController.java create mode 100644 src/main/java/com/krylov/refound/dto/ChatDto.java create mode 100644 src/main/java/com/krylov/refound/dto/CreateChatRequest.java create mode 100644 src/main/java/com/krylov/refound/dto/MessageDto.java create mode 100644 src/main/java/com/krylov/refound/dto/SendMessageRequest.java create mode 100644 src/main/java/com/krylov/refound/entity/Chat.java create mode 100644 src/main/java/com/krylov/refound/repository/ChatRepository.java create mode 100644 src/main/java/com/krylov/refound/service/ChatService.java create mode 100644 src/main/java/com/krylov/refound/util/WsHandshakeInterceptor.java create mode 100644 src/main/resources/db/changelog/create/006-create-chats.yaml create mode 100644 src/main/resources/db/changelog/create/007-recreate-messages.yaml diff --git a/src/main/java/com/krylov/refound/config/WebSocketConfig.java b/src/main/java/com/krylov/refound/config/WebSocketConfig.java index a96d764..8a3c03d 100644 --- a/src/main/java/com/krylov/refound/config/WebSocketConfig.java +++ b/src/main/java/com/krylov/refound/config/WebSocketConfig.java @@ -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(); } } diff --git a/src/main/java/com/krylov/refound/controller/ChatController.java b/src/main/java/com/krylov/refound/controller/ChatController.java index 3602bab..1365811 100644 --- a/src/main/java/com/krylov/refound/controller/ChatController.java +++ b/src/main/java/com/krylov/refound/controller/ChatController.java @@ -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) { + // Создать чат с пользователем или получить существующий + @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); + } - Optional sender = userRepository.findByLogin(principal.getName()); - Message saved = new Message(); - if(sender.isPresent()){ - saved = service.send(message.getReceiverId(), message.getContent()); - } + // Список всех чатов текущего пользователя + @GetMapping + public List getMyChats(Authentication auth) { + return chatService.getUserChats(extractUserId(auth)); + } - // отправка получателю - messagingTemplate.convertAndSend("/topic/messages/" + message.getReceiverId(), saved); + // История сообщений конкретного чата + @GetMapping("/{chatId}/messages") + public List 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); } } diff --git a/src/main/java/com/krylov/refound/controller/ChatWebSocketController.java b/src/main/java/com/krylov/refound/controller/ChatWebSocketController.java new file mode 100644 index 0000000..c7d0e97 --- /dev/null +++ b/src/main/java/com/krylov/refound/controller/ChatWebSocketController.java @@ -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 + } +} diff --git a/src/main/java/com/krylov/refound/controller/MessageController.java b/src/main/java/com/krylov/refound/controller/MessageController.java deleted file mode 100644 index 31f8bc1..0000000 --- a/src/main/java/com/krylov/refound/controller/MessageController.java +++ /dev/null @@ -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 chat(@PathVariable Long userId) { - return service.getChat(userId); - } - - @GetMapping("/dialogs") - public List getDialogs() { - return service.getDialogs(); - } - - @PostMapping("/read/{userId}") - public void markAsRead(@PathVariable Long userId) { - service.markAsRead(userId); - } -} diff --git a/src/main/java/com/krylov/refound/dto/ChatDto.java b/src/main/java/com/krylov/refound/dto/ChatDto.java new file mode 100644 index 0000000..cde0a1d --- /dev/null +++ b/src/main/java/com/krylov/refound/dto/ChatDto.java @@ -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; +} diff --git a/src/main/java/com/krylov/refound/dto/CreateChatRequest.java b/src/main/java/com/krylov/refound/dto/CreateChatRequest.java new file mode 100644 index 0000000..514e083 --- /dev/null +++ b/src/main/java/com/krylov/refound/dto/CreateChatRequest.java @@ -0,0 +1,8 @@ +package com.krylov.refound.dto; + +import lombok.Data; + +@Data +public class CreateChatRequest { + private Long postId; // опционально, с каким объявлением связан чат +} diff --git a/src/main/java/com/krylov/refound/dto/MessageDto.java b/src/main/java/com/krylov/refound/dto/MessageDto.java new file mode 100644 index 0000000..bf003cd --- /dev/null +++ b/src/main/java/com/krylov/refound/dto/MessageDto.java @@ -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; +} diff --git a/src/main/java/com/krylov/refound/dto/SendMessageRequest.java b/src/main/java/com/krylov/refound/dto/SendMessageRequest.java new file mode 100644 index 0000000..12410e2 --- /dev/null +++ b/src/main/java/com/krylov/refound/dto/SendMessageRequest.java @@ -0,0 +1,9 @@ +package com.krylov.refound.dto; + +import lombok.Data; + +@Data +public class SendMessageRequest { + private Long chatId; + private String content; +} diff --git a/src/main/java/com/krylov/refound/entity/Chat.java b/src/main/java/com/krylov/refound/entity/Chat.java new file mode 100644 index 0000000..d6c9e6e --- /dev/null +++ b/src/main/java/com/krylov/refound/entity/Chat.java @@ -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(); + } +} diff --git a/src/main/java/com/krylov/refound/entity/Message.java b/src/main/java/com/krylov/refound/entity/Message.java index a317b43..758ade6 100644 --- a/src/main/java/com/krylov/refound/entity/Message.java +++ b/src/main/java/com/krylov/refound/entity/Message.java @@ -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(); + } } diff --git a/src/main/java/com/krylov/refound/repository/ChatRepository.java b/src/main/java/com/krylov/refound/repository/ChatRepository.java new file mode 100644 index 0000000..013d306 --- /dev/null +++ b/src/main/java/com/krylov/refound/repository/ChatRepository.java @@ -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 { + + @Query("SELECT c FROM Chat c WHERE c.userOneId = :u1 AND c.userTwoId = :u2") + Optional 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 findAllByUserId(Long userId); // Changed from Optional to List +} diff --git a/src/main/java/com/krylov/refound/repository/MessageRepository.java b/src/main/java/com/krylov/refound/repository/MessageRepository.java index 51854d0..09ae746 100644 --- a/src/main/java/com/krylov/refound/repository/MessageRepository.java +++ b/src/main/java/com/krylov/refound/repository/MessageRepository.java @@ -9,48 +9,7 @@ import org.springframework.data.jpa.repository.Query; public interface MessageRepository extends JpaRepository { - @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 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 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 findByChatIdOrderByCreatedAtAsc(Long chatId); + long countByChatIdAndIsReadFalseAndSenderIdNot(Long chatId, Long currentUserId); } diff --git a/src/main/java/com/krylov/refound/repository/UserRepository.java b/src/main/java/com/krylov/refound/repository/UserRepository.java index fe423fb..fabc6f0 100644 --- a/src/main/java/com/krylov/refound/repository/UserRepository.java +++ b/src/main/java/com/krylov/refound/repository/UserRepository.java @@ -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 { Optional findByLogin(String email); Boolean existsByLogin(String login); + + @Query("SELECT u.id FROM User u WHERE u.login = :login") + Optional findUserByLogin(@Param("login") String login); } diff --git a/src/main/java/com/krylov/refound/service/ChatService.java b/src/main/java/com/krylov/refound/service/ChatService.java new file mode 100644 index 0000000..ea694b3 --- /dev/null +++ b/src/main/java/com/krylov/refound/service/ChatService.java @@ -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 getUserChats(Long userId) { + return chatRepository.findAllByUserId(userId).stream() + .map(c -> toDto(c, userId)) + .collect(Collectors.toList()); + } + + // История сообщений в чате + public List 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 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; + } +} diff --git a/src/main/java/com/krylov/refound/service/MessageService.java b/src/main/java/com/krylov/refound/service/MessageService.java index 9e6566d..ed4c64c 100644 --- a/src/main/java/com/krylov/refound/service/MessageService.java +++ b/src/main/java/com/krylov/refound/service/MessageService.java @@ -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 getChat(Long userId) { - User current = userService.getCurrentUser(); - return messageRepository.findChat(current.getId(), userId); - } - - public List getDialogs() { - Long userId = 1L; - List 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); - } } diff --git a/src/main/java/com/krylov/refound/service/UserService.java b/src/main/java/com/krylov/refound/service/UserService.java index 54913ac..6f04524 100644 --- a/src/main/java/com/krylov/refound/service/UserService.java +++ b/src/main/java/com/krylov/refound/service/UserService.java @@ -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")); + } } diff --git a/src/main/java/com/krylov/refound/util/WsHandshakeInterceptor.java b/src/main/java/com/krylov/refound/util/WsHandshakeInterceptor.java new file mode 100644 index 0000000..58aae13 --- /dev/null +++ b/src/main/java/com/krylov/refound/util/WsHandshakeInterceptor.java @@ -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 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) {} +} diff --git a/src/main/resources/db/changelog/create/006-create-chats.yaml b/src/main/resources/db/changelog/create/006-create-chats.yaml new file mode 100644 index 0000000..41e75dc --- /dev/null +++ b/src/main/resources/db/changelog/create/006-create-chats.yaml @@ -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 \ No newline at end of file diff --git a/src/main/resources/db/changelog/create/007-recreate-messages.yaml b/src/main/resources/db/changelog/create/007-recreate-messages.yaml new file mode 100644 index 0000000..d110927 --- /dev/null +++ b/src/main/resources/db/changelog/create/007-recreate-messages.yaml @@ -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 \ No newline at end of file