added EP rejected posts for admin
This commit is contained in:
@@ -38,6 +38,7 @@ public class PostAdminController {
|
|||||||
|
|
||||||
private final AdminPostService adminPostService;
|
private final AdminPostService adminPostService;
|
||||||
|
|
||||||
|
/** Основная лента: только ACTIVE и CLOSED. */
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
public ResponseEntity<Page<AdminPostResponse>> getPosts(
|
public ResponseEntity<Page<AdminPostResponse>> getPosts(
|
||||||
@@ -51,6 +52,20 @@ public class PostAdminController {
|
|||||||
return ResponseEntity.ok(adminPostService.getPosts(search, type, status, pageable));
|
return ResponseEntity.ok(adminPostService.getPosts(search, type, status, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Отклонённые объявления: те же фильтры, но статус зафиксирован на REJECTED. */
|
||||||
|
@GetMapping("/rejected")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<Page<AdminPostResponse>> 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}. */
|
/** Multipart-поля те же, что у пользовательского PUT /api/v1/posts/{id}. */
|
||||||
@PutMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
@PutMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ import com.krylov.refound.service.redis.MapCacheService;
|
|||||||
import com.krylov.refound.service.redis.PostCacheService;
|
import com.krylov.refound.service.redis.PostCacheService;
|
||||||
import com.krylov.refound.util.PostSpecification;
|
import com.krylov.refound.util.PostSpecification;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -58,6 +60,12 @@ public class AdminPostService {
|
|||||||
|
|
||||||
private static final int MAX_PAGE_SIZE = 100;
|
private static final int MAX_PAGE_SIZE = 100;
|
||||||
|
|
||||||
|
/** Основная админская лента: опубликованные объявления, включая закрытые. */
|
||||||
|
private static final Set<PostStatus> FEED_STATUSES = EnumSet.of(PostStatus.ACTIVE, PostStatus.CLOSED);
|
||||||
|
|
||||||
|
/** Отклонённые объявления живут отдельной лентой, в основной их не подмешиваем. */
|
||||||
|
private static final Set<PostStatus> REJECTED_STATUSES = EnumSet.of(PostStatus.REJECTED);
|
||||||
|
|
||||||
private final PostRepository postRepository;
|
private final PostRepository postRepository;
|
||||||
private final ImageRepository imageRepository;
|
private final ImageRepository imageRepository;
|
||||||
private final FavoriteRepository favoriteRepository;
|
private final FavoriteRepository favoriteRepository;
|
||||||
@@ -70,13 +78,34 @@ public class AdminPostService {
|
|||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Лента объявлений: видны все статусы, включая MODERATION и REJECTED.
|
* Основная лента объявлений: только ACTIVE и CLOSED. MODERATION и REJECTED
|
||||||
|
* доступны через {@link #getRejectedPosts(String, String, String, Pageable)}.
|
||||||
* Фильтры приходят строками и разбираются вручную — неверное значение даёт 400 вместо 500.
|
* Фильтры приходят строками и разбираются вручную — неверное значение даёт 400 вместо 500.
|
||||||
*/
|
*/
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Page<AdminPostResponse> getPosts(String search, String rawType, String rawStatus, Pageable pageable) {
|
public Page<AdminPostResponse> getPosts(String search, String rawType, String rawStatus, Pageable pageable) {
|
||||||
|
return searchPosts(search, rawType, rawStatus, FEED_STATUSES, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Отклонённые объявления: те же фильтры, но статус зафиксирован на REJECTED. */
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Page<AdminPostResponse> getRejectedPosts(String search, String rawType, String rawStatus,
|
||||||
|
Pageable pageable) {
|
||||||
|
return searchPosts(search, rawType, rawStatus, REJECTED_STATUSES, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Page<AdminPostResponse> searchPosts(String search, String rawType, String rawStatus,
|
||||||
|
Set<PostStatus> 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<Post> page = postRepository.findAll(
|
Page<Post> page = postRepository.findAll(
|
||||||
PostSpecification.adminSearch(search, parseType(rawType), parseStatus(rawStatus)),
|
PostSpecification.adminSearch(search, parseType(rawType), status, allowedStatuses),
|
||||||
safePageable(pageable)
|
safePageable(pageable)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -5,18 +5,25 @@ import com.krylov.refound.entity.User;
|
|||||||
import com.krylov.refound.enums.PostStatus;
|
import com.krylov.refound.enums.PostStatus;
|
||||||
import com.krylov.refound.enums.PostType;
|
import com.krylov.refound.enums.PostType;
|
||||||
import jakarta.persistence.criteria.Join;
|
import jakarta.persistence.criteria.Join;
|
||||||
|
import java.util.Set;
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
public class PostSpecification {
|
public class PostSpecification {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Поиск для админской ленты: видимы все объявления, включая MODERATION и REJECTED.
|
* Поиск для админской ленты: видны только статусы из {@code allowedStatuses},
|
||||||
|
* параметр {@code status} сужает выборку внутри них. {@code allowedStatuses == null}
|
||||||
|
* снимает ограничение по статусу (все объявления, включая MODERATION и REJECTED).
|
||||||
* Совпадение ищется по названию, описанию, городу, району и данным владельца.
|
* Совпадение ищется по названию, описанию, городу, району и данным владельца.
|
||||||
*/
|
*/
|
||||||
public static Specification<Post> adminSearch(String search, PostType type, PostStatus status) {
|
public static Specification<Post> adminSearch(
|
||||||
|
String search, PostType type, PostStatus status, Set<PostStatus> allowedStatuses) {
|
||||||
return Specification.allOf(
|
return Specification.allOf(
|
||||||
(root, query, cb) -> type == null ? cb.conjunction() : cb.equal(root.get("type"), type),
|
(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) -> status == null ? cb.conjunction() : cb.equal(root.get("status"), status),
|
||||||
(root, query, cb) -> {
|
(root, query, cb) -> {
|
||||||
if (!StringUtils.hasText(search)) {
|
if (!StringUtils.hasText(search)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user