From 1a7b96e8a6cda9a02537c38cb96af5158a72670a Mon Sep 17 00:00:00 2001 From: SlimusMinus Date: Tue, 6 Oct 2026 02:08:28 +0300 Subject: [PATCH] added EP rejected posts for admin --- .../controller/admin/PostAdminController.java | 15 +++++++++ .../service/admin/AdminPostService.java | 33 +++++++++++++++++-- .../refound/util/PostSpecification.java | 11 +++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/krylov/refound/controller/admin/PostAdminController.java b/src/main/java/com/krylov/refound/controller/admin/PostAdminController.java index 739f718..bcfe255 100644 --- a/src/main/java/com/krylov/refound/controller/admin/PostAdminController.java +++ b/src/main/java/com/krylov/refound/controller/admin/PostAdminController.java @@ -38,6 +38,7 @@ public class PostAdminController { private final AdminPostService adminPostService; + /** Основная лента: только ACTIVE и CLOSED. */ @GetMapping @PreAuthorize("hasRole('ADMIN')") public ResponseEntity> getPosts( @@ -51,6 +52,20 @@ public class PostAdminController { return ResponseEntity.ok(adminPostService.getPosts(search, type, status, pageable)); } + /** Отклонённые объявления: те же фильтры, но статус зафиксирован на REJECTED. */ + @GetMapping("/rejected") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity> getRejectedPosts( + @RequestParam(required = false) String search, + @RequestParam(required = false) String type, + @RequestParam(required = false) String status, + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) + Pageable pageable + ) { + log.info("getRejectedPosts search={} type={} status={} page={}", search, type, status, pageable.getPageNumber()); + return ResponseEntity.ok(adminPostService.getRejectedPosts(search, type, status, pageable)); + } + /** Multipart-поля те же, что у пользовательского PUT /api/v1/posts/{id}. */ @PutMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PreAuthorize("hasRole('ADMIN')") diff --git a/src/main/java/com/krylov/refound/service/admin/AdminPostService.java b/src/main/java/com/krylov/refound/service/admin/AdminPostService.java index 8685947..01a5cc9 100644 --- a/src/main/java/com/krylov/refound/service/admin/AdminPostService.java +++ b/src/main/java/com/krylov/refound/service/admin/AdminPostService.java @@ -23,9 +23,11 @@ import com.krylov.refound.service.redis.MapCacheService; import com.krylov.refound.service.redis.PostCacheService; import com.krylov.refound.util.PostSpecification; import java.util.Arrays; +import java.util.EnumSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -58,6 +60,12 @@ public class AdminPostService { private static final int MAX_PAGE_SIZE = 100; + /** Основная админская лента: опубликованные объявления, включая закрытые. */ + private static final Set FEED_STATUSES = EnumSet.of(PostStatus.ACTIVE, PostStatus.CLOSED); + + /** Отклонённые объявления живут отдельной лентой, в основной их не подмешиваем. */ + private static final Set REJECTED_STATUSES = EnumSet.of(PostStatus.REJECTED); + private final PostRepository postRepository; private final ImageRepository imageRepository; private final FavoriteRepository favoriteRepository; @@ -70,13 +78,34 @@ public class AdminPostService { private final ObjectMapper objectMapper; /** - * Лента объявлений: видны все статусы, включая MODERATION и REJECTED. + * Основная лента объявлений: только ACTIVE и CLOSED. MODERATION и REJECTED + * доступны через {@link #getRejectedPosts(String, String, String, Pageable)}. * Фильтры приходят строками и разбираются вручную — неверное значение даёт 400 вместо 500. */ @Transactional(readOnly = true) public Page getPosts(String search, String rawType, String rawStatus, Pageable pageable) { + return searchPosts(search, rawType, rawStatus, FEED_STATUSES, pageable); + } + + /** Отклонённые объявления: те же фильтры, но статус зафиксирован на REJECTED. */ + @Transactional(readOnly = true) + public Page getRejectedPosts(String search, String rawType, String rawStatus, + Pageable pageable) { + return searchPosts(search, rawType, rawStatus, REJECTED_STATUSES, pageable); + } + + private Page searchPosts(String search, String rawType, String rawStatus, + Set allowedStatuses, Pageable pageable) { + + PostStatus status = parseStatus(rawStatus); + if (status != null && !allowedStatuses.contains(status)) { + throw new ApiException(ErrorCode.VALIDATION_ERROR, + "Статус " + status + " недоступен в этой ленте, допустимы: " + allowedStatuses, + HttpStatus.BAD_REQUEST); + } + Page page = postRepository.findAll( - PostSpecification.adminSearch(search, parseType(rawType), parseStatus(rawStatus)), + PostSpecification.adminSearch(search, parseType(rawType), status, allowedStatuses), safePageable(pageable) ); diff --git a/src/main/java/com/krylov/refound/util/PostSpecification.java b/src/main/java/com/krylov/refound/util/PostSpecification.java index 2677ce9..d2d9b33 100644 --- a/src/main/java/com/krylov/refound/util/PostSpecification.java +++ b/src/main/java/com/krylov/refound/util/PostSpecification.java @@ -5,18 +5,25 @@ import com.krylov.refound.entity.User; import com.krylov.refound.enums.PostStatus; import com.krylov.refound.enums.PostType; import jakarta.persistence.criteria.Join; +import java.util.Set; import org.springframework.data.jpa.domain.Specification; import org.springframework.util.StringUtils; public class PostSpecification { /** - * Поиск для админской ленты: видимы все объявления, включая MODERATION и REJECTED. + * Поиск для админской ленты: видны только статусы из {@code allowedStatuses}, + * параметр {@code status} сужает выборку внутри них. {@code allowedStatuses == null} + * снимает ограничение по статусу (все объявления, включая MODERATION и REJECTED). * Совпадение ищется по названию, описанию, городу, району и данным владельца. */ - public static Specification adminSearch(String search, PostType type, PostStatus status) { + public static Specification adminSearch( + String search, PostType type, PostStatus status, Set allowedStatuses) { return Specification.allOf( (root, query, cb) -> type == null ? cb.conjunction() : cb.equal(root.get("type"), type), + (root, query, cb) -> allowedStatuses == null || allowedStatuses.isEmpty() + ? cb.conjunction() + : root.get("status").in(allowedStatuses), (root, query, cb) -> status == null ? cb.conjunction() : cb.equal(root.get("status"), status), (root, query, cb) -> { if (!StringUtils.hasText(search)) {