diff --git a/src/main/generated/com/gongspot/project/domain/review/entity/QReview.java b/src/main/generated/com/gongspot/project/domain/review/entity/QReview.java index 165a520..d486a02 100644 --- a/src/main/generated/com/gongspot/project/domain/review/entity/QReview.java +++ b/src/main/generated/com/gongspot/project/domain/review/entity/QReview.java @@ -40,6 +40,8 @@ public class QReview extends EntityPathBase { public final NumberPath id = createNumber("id", Long.class); + public final BooleanPath isDeleted = createBoolean("isDeleted"); + public final ListPath mediaList = this.createList("mediaList", com.gongspot.project.domain.media.entity.Media.class, com.gongspot.project.domain.media.entity.QMedia.class, PathInits.DIRECT2); public final ListPath> mood = this.>createList("mood", com.gongspot.project.common.enums.MoodEnum.class, EnumPath.class, PathInits.DIRECT2); diff --git a/src/main/java/com/gongspot/project/common/code/status/ErrorStatus.java b/src/main/java/com/gongspot/project/common/code/status/ErrorStatus.java index f263111..e8e2328 100644 --- a/src/main/java/com/gongspot/project/common/code/status/ErrorStatus.java +++ b/src/main/java/com/gongspot/project/common/code/status/ErrorStatus.java @@ -52,6 +52,7 @@ public enum ErrorStatus implements BaseErrorCode { REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW4001", "리뷰를 찾을 수 없습니다."), REVIEW_ALREADY_EXISTS(HttpStatus.CONFLICT, "REVIEW4002", "이미 해당 장소에 리뷰를 작성했습니다."), REVIEW_SAVE_FAIL(HttpStatus.BAD_REQUEST, "REVIEW4003", "리뷰 저장에 실패했습니다."), + UNAUTHORIZED_REVIEW_DELETION(HttpStatus.UNAUTHORIZED, "REVIEW4004", "본인의 리뷰만 삭제할 수 있습니다."), // Media Error MEDIA_NOT_FOUND(HttpStatus.NOT_FOUND, "MEDIA4001", "미디어를 찾을 수 없습니다."), diff --git a/src/main/java/com/gongspot/project/domain/place/controller/PlaceController.java b/src/main/java/com/gongspot/project/domain/place/controller/PlaceController.java index 346988e..3c9b9bc 100644 --- a/src/main/java/com/gongspot/project/domain/place/controller/PlaceController.java +++ b/src/main/java/com/gongspot/project/domain/place/controller/PlaceController.java @@ -52,7 +52,10 @@ public ApiResponse getReviewList( @PathVariable("placeId") Long placeId, @RequestParam(name = "page", defaultValue = "0") int page) { - ReviewResponseDTO.GetReviewListDTO result = reviewQueryService.getReviewList(placeId, page); + User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + Long userId = user.getId(); + + ReviewResponseDTO.GetReviewListDTO result = reviewQueryService.getReviewList(placeId, page, userId); return ApiResponse.onSuccess(result); } diff --git a/src/main/java/com/gongspot/project/domain/review/controller/ReviewController.java b/src/main/java/com/gongspot/project/domain/review/controller/ReviewController.java index 4535852..02b2af4 100644 --- a/src/main/java/com/gongspot/project/domain/review/controller/ReviewController.java +++ b/src/main/java/com/gongspot/project/domain/review/controller/ReviewController.java @@ -11,6 +11,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -40,4 +41,22 @@ public ApiResponse createReview( reviewCommandService.saveReview(userId, placeId, review, reviewPictures); return ApiResponse.onSuccess(); } + + @DeleteMapping("/admin/{reviewId}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + @Operation(summary = "리뷰 삭제 (관리자)", description = "관리자가 리뷰를 삭제합니다.") + public ApiResponse deleteReviewByAdmin(@PathVariable("reviewId") Long reviewId) { + reviewCommandService.deleteReviewByAdmin(reviewId); + return ApiResponse.onSuccess(); + } + + @PatchMapping("/{reviewId}") + @PreAuthorize("isAuthenticated()") + @Operation(summary = "내 리뷰 삭제", description = "본인의 리뷰를 삭제합니다.") + public ApiResponse deleteMyReview(@PathVariable("reviewId") Long reviewId) { + User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + Long userId = user.getId(); + reviewCommandService.softDeleteReview(userId, reviewId); + return ApiResponse.onSuccess(); + } } diff --git a/src/main/java/com/gongspot/project/domain/review/converter/ReviewConverter.java b/src/main/java/com/gongspot/project/domain/review/converter/ReviewConverter.java index b0dfc59..223b029 100644 --- a/src/main/java/com/gongspot/project/domain/review/converter/ReviewConverter.java +++ b/src/main/java/com/gongspot/project/domain/review/converter/ReviewConverter.java @@ -20,7 +20,7 @@ public class ReviewConverter { - public static ReviewResponseDTO.GetReviewDTO toGetReviewDTO(Review review, List reviewMediaList) { + public static ReviewResponseDTO.GetReviewDTO toGetReviewDTO(Review review, List reviewMediaList, Long currentUserId) { List imageUrls = new ArrayList<>(); if (reviewMediaList != null && !reviewMediaList.isEmpty()) { @@ -28,6 +28,7 @@ public static ReviewResponseDTO.GetReviewDTO toGetReviewDTO(Review review, List< .map(Media::getUrl) .collect(Collectors.toList()); } + Boolean isMyReview = review.getUser().getId().equals(currentUserId); return ReviewResponseDTO.GetReviewDTO.builder() .reviewId(review.getId()) @@ -38,6 +39,7 @@ public static ReviewResponseDTO.GetReviewDTO toGetReviewDTO(Review review, List< .rating(review.getRating()) .reviewImageUrl(imageUrls) .content(review.getContent()) + .isMyReview(isMyReview) .build(); } @@ -166,7 +168,8 @@ public static ReviewResponseDTO.GetReviewListDTO toGetReviewListDTO( Double averageRating, List categoryList, Map ratingCounts, - int totalReviewCount + int totalReviewCount, + Long currentUserId ) { Map> reviewMediaMap = mediaList.stream() @@ -174,7 +177,7 @@ public static ReviewResponseDTO.GetReviewListDTO toGetReviewListDTO( List reviewDTOs = reviews.stream() - .map(review -> toGetReviewDTO(review, reviewMediaMap.getOrDefault(review.getId(), new ArrayList<>()))) + .map(review -> toGetReviewDTO(review, reviewMediaMap.getOrDefault(review.getId(), new ArrayList<>()),currentUserId)) .collect(Collectors.toList()); return ReviewResponseDTO.GetReviewListDTO.builder() diff --git a/src/main/java/com/gongspot/project/domain/review/dto/ReviewResponseDTO.java b/src/main/java/com/gongspot/project/domain/review/dto/ReviewResponseDTO.java index a3d44d8..2ea73a8 100644 --- a/src/main/java/com/gongspot/project/domain/review/dto/ReviewResponseDTO.java +++ b/src/main/java/com/gongspot/project/domain/review/dto/ReviewResponseDTO.java @@ -18,6 +18,7 @@ public static class GetReviewDTO { Integer rating; List reviewImageUrl; String content; + Boolean isMyReview; } @Builder diff --git a/src/main/java/com/gongspot/project/domain/review/entity/Review.java b/src/main/java/com/gongspot/project/domain/review/entity/Review.java index e04137c..471c481 100644 --- a/src/main/java/com/gongspot/project/domain/review/entity/Review.java +++ b/src/main/java/com/gongspot/project/domain/review/entity/Review.java @@ -15,6 +15,7 @@ import java.util.List; @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @@ -58,6 +59,9 @@ public class Review extends BaseEntity { @Column(name = "content", length = 500) private String content; + @Column(name = "is_deleted") + private Boolean isDeleted = false; + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL) private List mediaList; diff --git a/src/main/java/com/gongspot/project/domain/review/service/ReviewCommandService.java b/src/main/java/com/gongspot/project/domain/review/service/ReviewCommandService.java index 4d4c99d..fb2a7f7 100644 --- a/src/main/java/com/gongspot/project/domain/review/service/ReviewCommandService.java +++ b/src/main/java/com/gongspot/project/domain/review/service/ReviewCommandService.java @@ -7,4 +7,6 @@ public interface ReviewCommandService { void saveReview(Long userId, Long placeId, ReviewRequestDTO.ReviewRegisterDTO reqDTO, List reviewPictures); + void deleteReviewByAdmin(Long reviewId); + void softDeleteReview(Long userId, Long reviewId); } diff --git a/src/main/java/com/gongspot/project/domain/review/service/ReviewCommandServiceImpl.java b/src/main/java/com/gongspot/project/domain/review/service/ReviewCommandServiceImpl.java index 2387e5a..cb09bf6 100644 --- a/src/main/java/com/gongspot/project/domain/review/service/ReviewCommandServiceImpl.java +++ b/src/main/java/com/gongspot/project/domain/review/service/ReviewCommandServiceImpl.java @@ -21,6 +21,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -85,4 +86,25 @@ public void saveReview(Long userId, Long placeId, ReviewRequestDTO.ReviewRegiste throw new BusinessException(ErrorStatus.REVIEW_SAVE_FAIL); } } + + @Transactional + public void deleteReviewByAdmin(Long reviewId) { + Review review = reviewRepository.findById(reviewId) + .orElseThrow(() -> new BusinessException(ErrorStatus.REVIEW_NOT_FOUND)); + + reviewRepository.delete(review); + } + + @Transactional + public void softDeleteReview(Long userId, Long reviewId) { + Review review = reviewRepository.findById(reviewId) + .orElseThrow(() -> new BusinessException(ErrorStatus.REVIEW_NOT_FOUND)); + + if (!review.getUser().getId().equals(userId)) { + throw new BusinessException(ErrorStatus.UNAUTHORIZED_REVIEW_DELETION); + } + + review.setIsDeleted(true); + reviewRepository.save(review); + } } diff --git a/src/main/java/com/gongspot/project/domain/review/service/ReviewQueryService.java b/src/main/java/com/gongspot/project/domain/review/service/ReviewQueryService.java index 1419bf7..0da2fa8 100644 --- a/src/main/java/com/gongspot/project/domain/review/service/ReviewQueryService.java +++ b/src/main/java/com/gongspot/project/domain/review/service/ReviewQueryService.java @@ -4,5 +4,5 @@ public interface ReviewQueryService { ReviewResponseDTO.CongestionListDTO getCongestionList(Long userId, Long placeId, int page); - ReviewResponseDTO.GetReviewListDTO getReviewList(Long placeId, int page); + ReviewResponseDTO.GetReviewListDTO getReviewList(Long placeId, int page,Long currentUserId); } \ No newline at end of file diff --git a/src/main/java/com/gongspot/project/domain/review/service/ReviewQueryServiceImpl.java b/src/main/java/com/gongspot/project/domain/review/service/ReviewQueryServiceImpl.java index a037383..2e93680 100644 --- a/src/main/java/com/gongspot/project/domain/review/service/ReviewQueryServiceImpl.java +++ b/src/main/java/com/gongspot/project/domain/review/service/ReviewQueryServiceImpl.java @@ -44,7 +44,7 @@ public ReviewResponseDTO.CongestionListDTO getCongestionList(Long userId, Long p } @Override - public ReviewResponseDTO.GetReviewListDTO getReviewList(Long placeId, int page) { + public ReviewResponseDTO.GetReviewListDTO getReviewList(Long placeId, int page, Long currentUserId) { Place place = placeRepository.findById(placeId) .orElseThrow(() -> new BusinessException(ErrorStatus.PLACE_NOT_FOUND)); @@ -73,7 +73,8 @@ public ReviewResponseDTO.GetReviewListDTO getReviewList(Long placeId, int page) averageRating, categoryList, ratingCounts, - allReviews.size() + allReviews.size(), + currentUserId ); }