From 97f28b9eca07c67349965ffb2afa116c4ba78267 Mon Sep 17 00:00:00 2001 From: SlimusMinus Date: Mon, 6 Jul 2026 00:47:07 +0300 Subject: [PATCH] added filter on title and description on post --- .../refound/controller/ChatController.java | 14 +++++ .../refound/controller/PostController.java | 10 +++- .../refound/repository/MessageRepository.java | 4 ++ .../krylov/refound/service/ChatService.java | 25 +++++++- .../krylov/refound/service/PostService.java | 40 +++++++------ .../refound/util/PostSpecification.java | 60 +++++-------------- 6 files changed, 84 insertions(+), 69 deletions(-) diff --git a/src/main/java/com/krylov/refound/controller/ChatController.java b/src/main/java/com/krylov/refound/controller/ChatController.java index 1365811..1053682 100644 --- a/src/main/java/com/krylov/refound/controller/ChatController.java +++ b/src/main/java/com/krylov/refound/controller/ChatController.java @@ -5,6 +5,7 @@ import com.krylov.refound.service.ChatService; import com.krylov.refound.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -42,4 +43,17 @@ public class ChatController { return userService.getUserIdByLogin(login); } + @DeleteMapping("/{chatId}") + public void deleteChat(@PathVariable Long chatId, Authentication auth) { + Long currentUserId = extractUserId(auth); + chatService.deleteChat(chatId); + } + + @PostMapping("/api/v1/chats/{chatId}/read") + public void markAsRead(@PathVariable Long chatId) { + Long userId = (Long) SecurityContextHolder.getContext() + .getAuthentication().getPrincipal(); // или твой способ + chatService.markAsRead(chatId, userId); + } + } diff --git a/src/main/java/com/krylov/refound/controller/PostController.java b/src/main/java/com/krylov/refound/controller/PostController.java index 4edf1dc..b7e2e2c 100644 --- a/src/main/java/com/krylov/refound/controller/PostController.java +++ b/src/main/java/com/krylov/refound/controller/PostController.java @@ -11,6 +11,10 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.DeleteMapping; @@ -44,8 +48,10 @@ public class PostController { } @GetMapping - public List getFeed() { - return service.getFeed(); + public Page getFeed( + @RequestParam(required = false) String search, + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { + return service.getFeed(search, pageable); } @GetMapping("/{id}") diff --git a/src/main/java/com/krylov/refound/repository/MessageRepository.java b/src/main/java/com/krylov/refound/repository/MessageRepository.java index 09ae746..9463513 100644 --- a/src/main/java/com/krylov/refound/repository/MessageRepository.java +++ b/src/main/java/com/krylov/refound/repository/MessageRepository.java @@ -6,10 +6,14 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface MessageRepository extends JpaRepository { List findByChatIdOrderByCreatedAtAsc(Long chatId); long countByChatIdAndIsReadFalseAndSenderIdNot(Long chatId, Long currentUserId); + @Modifying + @Query("UPDATE Message m SET m.isRead = true WHERE m.chatId = :chatId AND m.senderId != :userId") + int markAsReadByChatIdAndRecipient(@Param("chatId") Long chatId, @Param("userId") Long userId); } diff --git a/src/main/java/com/krylov/refound/service/ChatService.java b/src/main/java/com/krylov/refound/service/ChatService.java index ea694b3..671bb38 100644 --- a/src/main/java/com/krylov/refound/service/ChatService.java +++ b/src/main/java/com/krylov/refound/service/ChatService.java @@ -8,6 +8,7 @@ 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.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.messaging.simp.SimpMessagingTemplate; @@ -64,7 +65,6 @@ public class ChatService { Chat chat = chatRepository.findById(chatId) .orElseThrow(() -> new IllegalArgumentException("Чат не найден")); - // Проверка что отправитель - участник чата if (!chat.getUserOneId().equals(senderId) && !chat.getUserTwoId().equals(senderId)) { throw new SecurityException("Вы не участник этого чата"); } @@ -77,9 +77,21 @@ public class ChatService { MessageDto dto = toMessageDto(message); - // Отправляем сообщение через WebSocket в топик конкретного чата messagingTemplate.convertAndSend("/topic/chat/" + chatId, dto); + // Уведомляем получателя об непрочитанных + Long recipientId = chat.getUserOneId().equals(senderId) + ? chat.getUserTwoId() + : chat.getUserOneId(); + + long unreadCount = messageRepository + .countByChatIdAndIsReadFalseAndSenderIdNot(chatId, recipientId); + + messagingTemplate.convertAndSend( + "/topic/user/" + recipientId + "/unread", + Map.of("chatId", chatId, "unreadCount", unreadCount) + ); + return dto; } @@ -122,4 +134,13 @@ public class ChatService { dto.setCreatedAt(m.getCreatedAt()); return dto; } + + public void deleteChat(Long chatId) { + chatRepository.deleteById(chatId); + } + + @Transactional + public void markAsRead(Long chatId, Long userId) { + messageRepository.markAsReadByChatIdAndRecipient(chatId, userId); + } } diff --git a/src/main/java/com/krylov/refound/service/PostService.java b/src/main/java/com/krylov/refound/service/PostService.java index 7e6f782..bb5a8bb 100644 --- a/src/main/java/com/krylov/refound/service/PostService.java +++ b/src/main/java/com/krylov/refound/service/PostService.java @@ -13,6 +13,7 @@ import com.krylov.refound.mapper.PostMapper; import com.krylov.refound.repository.FavoriteRepository; import com.krylov.refound.repository.ImageRepository; import com.krylov.refound.repository.PostRepository; +import com.krylov.refound.util.PostSpecification; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -28,6 +29,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -83,22 +87,24 @@ public class PostService { return request; } - public List getFeed() { - List posts = repository.findAll(); + public Page getFeed(String search, Pageable pageable) { + Specification spec = PostSpecification.hasSearch(search); + Page postsPage = repository.findAll(spec, pageable); - // Get all post IDs - List postIds = posts.stream() + List postIds = postsPage.getContent().stream() .map(Post::getId) .toList(); - // Count likes for each post + if (postIds.isEmpty()) { + return Page.empty(pageable); + } + Map likeCounts = favoriteRepository.countLikesByPostIds(postIds).stream() .collect(Collectors.toMap( - row -> (Long) row[0], // post ID - row -> (Long) row[1] // like count + row -> (Long) row[0], + row -> (Long) row[1] )); - // Get current user's favorites if authenticated Set userFavoriteIds = new HashSet<>(); try { User currentUser = userService.getCurrentUser(); @@ -109,18 +115,14 @@ public class PostService { // User not authenticated - that's OK } - // Set like counts and favorite status final Set finalUserFavoriteIds = userFavoriteIds; - return posts.stream() - .map(post -> { - PostResponse response = mapper.toResponse(post); - response.setLikeCount(likeCounts.getOrDefault(post.getId(), 0L)); - response.setFavorite(finalUserFavoriteIds.contains(post.getId())); - response.setUserRole(post.getUser().getRole()); - return response; - }) - .toList(); - + return postsPage.map(post -> { + PostResponse response = mapper.toResponse(post); + response.setLikeCount(likeCounts.getOrDefault(post.getId(), 0L)); + response.setFavorite(finalUserFavoriteIds.contains(post.getId())); + response.setUserRole(post.getUser().getRole()); + return response; + }); } public List getById(Long id) { diff --git a/src/main/java/com/krylov/refound/util/PostSpecification.java b/src/main/java/com/krylov/refound/util/PostSpecification.java index e97ee56..c2f99eb 100644 --- a/src/main/java/com/krylov/refound/util/PostSpecification.java +++ b/src/main/java/com/krylov/refound/util/PostSpecification.java @@ -1,58 +1,26 @@ package com.krylov.refound.util; -import com.krylov.refound.dto.PostFilter; import com.krylov.refound.entity.Post; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Predicate; -import java.util.ArrayList; -import java.util.List; import org.springframework.data.jpa.domain.Specification; public class PostSpecification { - public static Specification withFilter(PostFilter filter) { + public static Specification hasSearch(String search) { return (root, query, cb) -> { - - List predicates = new ArrayList<>(); - - - - if (filter.getType() != null) { - predicates.add(cb.equal(root.get("type"), filter.getType())); + if (search == null || search.isBlank()) { + return cb.conjunction(); } + String pattern = "%" + search.toLowerCase() + "%"; + return cb.or( + cb.like(cb.lower(root.get("title")), pattern), + cb.like(cb.lower(root.get("description")), pattern) + ); + }; + } - if (filter.getCity() != null) { - predicates.add(cb.equal(root.get("city"), filter.getCity())); - } - - if (filter.getCategory() != null) { - predicates.add(cb.equal(root.get("category"), filter.getCategory())); - } - - if (filter.getDistrict() != null) { - predicates.add(cb.equal(root.get("district"), filter.getDistrict())); - } - if (filter.getSearch() != null && !filter.getSearch().isBlank()) { - String search = "%" + filter.getSearch().toLowerCase() + "%"; - - predicates.add(cb.or( - cb.like(cb.lower(root.get("title")), search), - cb.like(cb.lower(root.get("description")), search) - )); - } - if (filter.getLat() != null && filter.getLng() != null && filter.getRadius() != null) { - - Expression distance = cb.function( - "6371 * acos(cos(radians(?1)) * cos(radians(latitude)) * " + - "cos(radians(longitude) - radians(?2)) + sin(radians(?1)) * sin(radians(latitude)))", - Double.class, - cb.literal(filter.getLat()), - cb.literal(filter.getLng()) - ); - - predicates.add(cb.lessThanOrEqualTo(distance, filter.getRadius())); - } - - return cb.and(predicates.toArray(new Predicate[0])); + public static Specification hasType(String type) { + return (root, query, cb) -> { + if (type == null || type.isBlank()) return cb.conjunction(); + return cb.equal(cb.lower(root.get("type")), type.toLowerCase()); }; } }