diff --git a/.gitignore b/.gitignore index e65052be..596bb172 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ build/ !**/src/test/**/build/ *.yml .env +data.sql ### STS ### .apt_generated diff --git a/src/main/java/com/seeat/server/domain/best/application/dto/response/BestReviewListResponse.java b/src/main/java/com/seeat/server/domain/best/application/dto/response/BestReviewListResponse.java index 0a25e346..eed3d34d 100644 --- a/src/main/java/com/seeat/server/domain/best/application/dto/response/BestReviewListResponse.java +++ b/src/main/java/com/seeat/server/domain/best/application/dto/response/BestReviewListResponse.java @@ -1,9 +1,11 @@ package com.seeat.server.domain.best.application.dto.response; import com.seeat.server.domain.best.domain.entity.BestReviewSnapshot; -import com.seeat.server.domain.review.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; +import com.seeat.server.domain.theater.domain.entity.Auditorium; +import com.seeat.server.domain.theater.domain.entity.Seat; import com.seeat.server.domain.user.domain.entity.User; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @@ -98,7 +100,10 @@ public static List from(List snapsho } /// DB에서 직접 조회 정적 팩토리 메서드 - public static BestReviewListResponse from(Review review, List hashTags, Long heartCount) { + public static BestReviewListResponse from(Review review, List hashTags, Long heartCount, List seats) { + + /// 좌석에 따른 상영관 + Auditorium auditorium = seats.get(0).getAuditorium(); /// 해시태그들 정리 List hashtags = hashTags.stream() @@ -114,7 +119,7 @@ public static BestReviewListResponse from(Review review, List has .thumbnailUrl(review.getThumbnailUrl()) .hashtags(hashtags) .movieTitle(review.getMovieTitle()) - .theaterName(review.getSeat().getAuditorium().getTheater().getName()) + .theaterName(auditorium.getTheater().getName() + auditorium.getName()) .title(review.getTitle()) .content(review.getContent()) .userId(user.getId()) diff --git a/src/main/java/com/seeat/server/domain/review/application/dto/response/AuditoriumHashTagResponse.java b/src/main/java/com/seeat/server/domain/hashtag/application/dto/response/AuditoriumHashTagResponse.java similarity index 85% rename from src/main/java/com/seeat/server/domain/review/application/dto/response/AuditoriumHashTagResponse.java rename to src/main/java/com/seeat/server/domain/hashtag/application/dto/response/AuditoriumHashTagResponse.java index cbb15722..5adfabfe 100644 --- a/src/main/java/com/seeat/server/domain/review/application/dto/response/AuditoriumHashTagResponse.java +++ b/src/main/java/com/seeat/server/domain/hashtag/application/dto/response/AuditoriumHashTagResponse.java @@ -1,7 +1,6 @@ -package com.seeat.server.domain.review.application.dto.response; +package com.seeat.server.domain.hashtag.application.dto.response; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; -import com.seeat.server.domain.review.domain.repository.dto.ReviewHashTagWithCount; +import com.seeat.server.domain.hashtag.domain.repository.ReviewHashTagWithCount; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.util.List; diff --git a/src/main/java/com/seeat/server/domain/review/application/dto/response/HashTagResponse.java b/src/main/java/com/seeat/server/domain/hashtag/application/dto/response/HashTagResponse.java similarity index 90% rename from src/main/java/com/seeat/server/domain/review/application/dto/response/HashTagResponse.java rename to src/main/java/com/seeat/server/domain/hashtag/application/dto/response/HashTagResponse.java index c2e6e338..b0baeaf9 100644 --- a/src/main/java/com/seeat/server/domain/review/application/dto/response/HashTagResponse.java +++ b/src/main/java/com/seeat/server/domain/hashtag/application/dto/response/HashTagResponse.java @@ -1,6 +1,6 @@ -package com.seeat.server.domain.review.application.dto.response; +package com.seeat.server.domain.hashtag.application.dto.response; -import com.seeat.server.domain.review.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.util.List; diff --git a/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewHashTagResponse.java b/src/main/java/com/seeat/server/domain/hashtag/application/dto/response/ReviewHashTagResponse.java similarity index 89% rename from src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewHashTagResponse.java rename to src/main/java/com/seeat/server/domain/hashtag/application/dto/response/ReviewHashTagResponse.java index f15c3365..a164d2d6 100644 --- a/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewHashTagResponse.java +++ b/src/main/java/com/seeat/server/domain/hashtag/application/dto/response/ReviewHashTagResponse.java @@ -1,7 +1,7 @@ -package com.seeat.server.domain.review.application.dto.response; +package com.seeat.server.domain.hashtag.application.dto.response; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; diff --git a/src/main/java/com/seeat/server/domain/review/application/service/HashTagService.java b/src/main/java/com/seeat/server/domain/hashtag/application/service/HashTagService.java similarity index 51% rename from src/main/java/com/seeat/server/domain/review/application/service/HashTagService.java rename to src/main/java/com/seeat/server/domain/hashtag/application/service/HashTagService.java index d004e388..8202abc0 100644 --- a/src/main/java/com/seeat/server/domain/review/application/service/HashTagService.java +++ b/src/main/java/com/seeat/server/domain/hashtag/application/service/HashTagService.java @@ -1,21 +1,15 @@ -package com.seeat.server.domain.review.application.service; - -import com.seeat.server.domain.review.application.dto.response.HashTagResponse; -import com.seeat.server.domain.review.application.usecase.HashTagUseCase; -import com.seeat.server.domain.review.domain.entity.HashTag; -import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; -import com.seeat.server.domain.review.domain.repository.HashTagRepository; -import com.seeat.server.domain.review.domain.repository.ReviewHashTagRepository; +package com.seeat.server.domain.hashtag.application.service; + +import com.seeat.server.domain.hashtag.application.dto.response.HashTagResponse; +import com.seeat.server.domain.hashtag.application.usecase.HashTagUseCase; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; @Service @Transactional diff --git a/src/main/java/com/seeat/server/domain/review/application/service/ReviewHashTagService.java b/src/main/java/com/seeat/server/domain/hashtag/application/service/ReviewHashTagService.java similarity index 89% rename from src/main/java/com/seeat/server/domain/review/application/service/ReviewHashTagService.java rename to src/main/java/com/seeat/server/domain/hashtag/application/service/ReviewHashTagService.java index 13b2862c..455014ed 100644 --- a/src/main/java/com/seeat/server/domain/review/application/service/ReviewHashTagService.java +++ b/src/main/java/com/seeat/server/domain/hashtag/application/service/ReviewHashTagService.java @@ -1,14 +1,14 @@ -package com.seeat.server.domain.review.application.service; +package com.seeat.server.domain.hashtag.application.service; -import com.seeat.server.domain.review.application.dto.response.AuditoriumHashTagResponse; -import com.seeat.server.domain.review.application.usecase.ReviewHashTagUseCase; -import com.seeat.server.domain.review.domain.entity.HashTag; -import com.seeat.server.domain.review.domain.entity.HashTagType; +import com.seeat.server.domain.hashtag.application.dto.response.AuditoriumHashTagResponse; +import com.seeat.server.domain.hashtag.application.usecase.ReviewHashTagUseCase; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; -import com.seeat.server.domain.review.domain.repository.HashTagRepository; -import com.seeat.server.domain.review.domain.repository.ReviewHashTagRepository; -import com.seeat.server.domain.review.domain.repository.dto.ReviewHashTagWithCount; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.ReviewHashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.ReviewHashTagWithCount; import com.seeat.server.domain.theater.application.usecase.TheaterUseCase; import com.seeat.server.domain.theater.domain.entity.Auditorium; import com.seeat.server.global.response.ErrorCode; diff --git a/src/main/java/com/seeat/server/domain/review/application/usecase/HashTagUseCase.java b/src/main/java/com/seeat/server/domain/hashtag/application/usecase/HashTagUseCase.java similarity index 58% rename from src/main/java/com/seeat/server/domain/review/application/usecase/HashTagUseCase.java rename to src/main/java/com/seeat/server/domain/hashtag/application/usecase/HashTagUseCase.java index a0888c1f..1b6da57d 100644 --- a/src/main/java/com/seeat/server/domain/review/application/usecase/HashTagUseCase.java +++ b/src/main/java/com/seeat/server/domain/hashtag/application/usecase/HashTagUseCase.java @@ -1,7 +1,6 @@ -package com.seeat.server.domain.review.application.usecase; +package com.seeat.server.domain.hashtag.application.usecase; -import com.seeat.server.domain.review.application.dto.response.HashTagResponse; -import com.seeat.server.domain.review.domain.entity.Review; +import com.seeat.server.domain.hashtag.application.dto.response.HashTagResponse; import java.util.List; diff --git a/src/main/java/com/seeat/server/domain/review/application/usecase/ReviewHashTagUseCase.java b/src/main/java/com/seeat/server/domain/hashtag/application/usecase/ReviewHashTagUseCase.java similarity index 85% rename from src/main/java/com/seeat/server/domain/review/application/usecase/ReviewHashTagUseCase.java rename to src/main/java/com/seeat/server/domain/hashtag/application/usecase/ReviewHashTagUseCase.java index 458893fd..616fcefa 100644 --- a/src/main/java/com/seeat/server/domain/review/application/usecase/ReviewHashTagUseCase.java +++ b/src/main/java/com/seeat/server/domain/hashtag/application/usecase/ReviewHashTagUseCase.java @@ -1,8 +1,8 @@ -package com.seeat.server.domain.review.application.usecase; +package com.seeat.server.domain.hashtag.application.usecase; -import com.seeat.server.domain.review.application.dto.response.AuditoriumHashTagResponse; +import com.seeat.server.domain.hashtag.application.dto.response.AuditoriumHashTagResponse; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; import java.util.*; diff --git a/src/main/java/com/seeat/server/domain/review/domain/entity/HashTag.java b/src/main/java/com/seeat/server/domain/hashtag/domain/entity/HashTag.java similarity index 90% rename from src/main/java/com/seeat/server/domain/review/domain/entity/HashTag.java rename to src/main/java/com/seeat/server/domain/hashtag/domain/entity/HashTag.java index 887509b5..f95c70dc 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/entity/HashTag.java +++ b/src/main/java/com/seeat/server/domain/hashtag/domain/entity/HashTag.java @@ -1,4 +1,4 @@ -package com.seeat.server.domain.review.domain.entity; +package com.seeat.server.domain.hashtag.domain.entity; import com.seeat.server.domain.BaseEntity; import jakarta.persistence.*; diff --git a/src/main/java/com/seeat/server/domain/review/domain/entity/HashTagType.java b/src/main/java/com/seeat/server/domain/hashtag/domain/entity/HashTagType.java similarity index 81% rename from src/main/java/com/seeat/server/domain/review/domain/entity/HashTagType.java rename to src/main/java/com/seeat/server/domain/hashtag/domain/entity/HashTagType.java index 2a7bfe1c..9fd3e8b3 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/entity/HashTagType.java +++ b/src/main/java/com/seeat/server/domain/hashtag/domain/entity/HashTagType.java @@ -1,4 +1,4 @@ -package com.seeat.server.domain.review.domain.entity; +package com.seeat.server.domain.hashtag.domain.entity; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/seeat/server/domain/review/domain/entity/ReviewHashTag.java b/src/main/java/com/seeat/server/domain/hashtag/domain/entity/ReviewHashTag.java similarity index 87% rename from src/main/java/com/seeat/server/domain/review/domain/entity/ReviewHashTag.java rename to src/main/java/com/seeat/server/domain/hashtag/domain/entity/ReviewHashTag.java index c7fbaee0..1fcfc2bc 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/entity/ReviewHashTag.java +++ b/src/main/java/com/seeat/server/domain/hashtag/domain/entity/ReviewHashTag.java @@ -1,6 +1,7 @@ -package com.seeat.server.domain.review.domain.entity; +package com.seeat.server.domain.hashtag.domain.entity; import com.seeat.server.domain.BaseEntity; +import com.seeat.server.domain.review.domain.entity.Review; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/seeat/server/domain/review/domain/repository/HashTagRepository.java b/src/main/java/com/seeat/server/domain/hashtag/domain/repository/HashTagRepository.java similarity index 64% rename from src/main/java/com/seeat/server/domain/review/domain/repository/HashTagRepository.java rename to src/main/java/com/seeat/server/domain/hashtag/domain/repository/HashTagRepository.java index 91a37a56..1999a40f 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/repository/HashTagRepository.java +++ b/src/main/java/com/seeat/server/domain/hashtag/domain/repository/HashTagRepository.java @@ -1,6 +1,6 @@ -package com.seeat.server.domain.review.domain.repository; +package com.seeat.server.domain.hashtag.domain.repository; -import com.seeat.server.domain.review.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/seeat/server/domain/review/domain/repository/ReviewHashTagRepository.java b/src/main/java/com/seeat/server/domain/hashtag/domain/repository/ReviewHashTagRepository.java similarity index 64% rename from src/main/java/com/seeat/server/domain/review/domain/repository/ReviewHashTagRepository.java rename to src/main/java/com/seeat/server/domain/hashtag/domain/repository/ReviewHashTagRepository.java index 83666974..c5d5f7ac 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/repository/ReviewHashTagRepository.java +++ b/src/main/java/com/seeat/server/domain/hashtag/domain/repository/ReviewHashTagRepository.java @@ -1,8 +1,7 @@ -package com.seeat.server.domain.review.domain.repository; +package com.seeat.server.domain.hashtag.domain.repository; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; -import com.seeat.server.domain.review.domain.repository.dto.ReviewHashTagWithCount; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -34,11 +33,19 @@ public interface ReviewHashTagRepository extends JpaRepository */ - @Query("SELECT rh.hashTag.id as hashTagId, rh.hashTag.name as hashTagName, COUNT(rh) AS count " + - "FROM Review r LEFT JOIN ReviewHashTag rh ON rh.review.id = r.id " + - "WHERE r.seat.auditorium.id = :auditoriumId " + - "GROUP BY rh.hashTag.id, rh.hashTag.name " + - "ORDER BY COUNT(rh) DESC, rh.hashTag.id DESC") + @Query(""" + SELECT rh.hashTag.id as hashTagId, + rh.hashTag.name as hashTagName, + COUNT(DISTINCT r.id) AS count + FROM Auditorium a + LEFT JOIN Seat s ON s.auditorium = a + LEFT JOIN ReviewSeat rs ON rs.seat = s + LEFT JOIN Review r ON rs.review = r + LEFT JOIN ReviewHashTag rh ON rh.review = r + WHERE a.id = :auditoriumId + GROUP BY rh.hashTag.id, rh.hashTag.name + ORDER BY COUNT(DISTINCT r.id) DESC, rh.hashTag.id DESC + """) List findByAuditorium_Id(@Param("auditoriumId") String auditoriumId); diff --git a/src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewHashTagWithCount.java b/src/main/java/com/seeat/server/domain/hashtag/domain/repository/ReviewHashTagWithCount.java similarity index 66% rename from src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewHashTagWithCount.java rename to src/main/java/com/seeat/server/domain/hashtag/domain/repository/ReviewHashTagWithCount.java index 21f86449..03ad8f51 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewHashTagWithCount.java +++ b/src/main/java/com/seeat/server/domain/hashtag/domain/repository/ReviewHashTagWithCount.java @@ -1,4 +1,4 @@ -package com.seeat.server.domain.review.domain.repository.dto; +package com.seeat.server.domain.hashtag.domain.repository; public interface ReviewHashTagWithCount { diff --git a/src/main/java/com/seeat/server/domain/review/presentation/HashTagController.java b/src/main/java/com/seeat/server/domain/hashtag/presentation/HashTagController.java similarity index 75% rename from src/main/java/com/seeat/server/domain/review/presentation/HashTagController.java rename to src/main/java/com/seeat/server/domain/hashtag/presentation/HashTagController.java index 14c8d7a9..bd0c1b3e 100644 --- a/src/main/java/com/seeat/server/domain/review/presentation/HashTagController.java +++ b/src/main/java/com/seeat/server/domain/hashtag/presentation/HashTagController.java @@ -1,10 +1,10 @@ -package com.seeat.server.domain.review.presentation; +package com.seeat.server.domain.hashtag.presentation; -import com.seeat.server.domain.review.application.dto.response.AuditoriumHashTagResponse; -import com.seeat.server.domain.review.application.dto.response.HashTagResponse; -import com.seeat.server.domain.review.application.usecase.HashTagUseCase; -import com.seeat.server.domain.review.application.usecase.ReviewHashTagUseCase; -import com.seeat.server.domain.review.presentation.swagger.HashTagControllerSpec; +import com.seeat.server.domain.hashtag.application.dto.response.AuditoriumHashTagResponse; +import com.seeat.server.domain.hashtag.application.dto.response.HashTagResponse; +import com.seeat.server.domain.hashtag.application.usecase.HashTagUseCase; +import com.seeat.server.domain.hashtag.application.usecase.ReviewHashTagUseCase; +import com.seeat.server.domain.hashtag.presentation.swagger.HashTagControllerSpec; import com.seeat.server.global.response.ApiResponse; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/seeat/server/domain/review/presentation/swagger/HashTagControllerSpec.java b/src/main/java/com/seeat/server/domain/hashtag/presentation/swagger/HashTagControllerSpec.java similarity index 83% rename from src/main/java/com/seeat/server/domain/review/presentation/swagger/HashTagControllerSpec.java rename to src/main/java/com/seeat/server/domain/hashtag/presentation/swagger/HashTagControllerSpec.java index 3477010a..1a670293 100644 --- a/src/main/java/com/seeat/server/domain/review/presentation/swagger/HashTagControllerSpec.java +++ b/src/main/java/com/seeat/server/domain/hashtag/presentation/swagger/HashTagControllerSpec.java @@ -1,7 +1,7 @@ -package com.seeat.server.domain.review.presentation.swagger; +package com.seeat.server.domain.hashtag.presentation.swagger; -import com.seeat.server.domain.review.application.dto.response.AuditoriumHashTagResponse; -import com.seeat.server.domain.review.application.dto.response.HashTagResponse; +import com.seeat.server.domain.hashtag.application.dto.response.AuditoriumHashTagResponse; +import com.seeat.server.domain.hashtag.application.dto.response.HashTagResponse; import com.seeat.server.global.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; diff --git a/src/main/java/com/seeat/server/domain/image/application/service/ReviewImageService.java b/src/main/java/com/seeat/server/domain/image/application/service/ReviewImageService.java index 9f5777c2..f4fd24af 100644 --- a/src/main/java/com/seeat/server/domain/image/application/service/ReviewImageService.java +++ b/src/main/java/com/seeat/server/domain/image/application/service/ReviewImageService.java @@ -84,7 +84,7 @@ public List saveReviewImage(Review review, List imageUrls) throw public List getReviewImagesByReview(Review review) { /// 리뷰에 해당하는 이미지 주소 순서대로 가져오기 - List images = repository.findByReview(review); + List images = repository.findByReview_Id(review.getId()); /// 순서대로 정렬한 내용 출력 return images.stream() @@ -96,13 +96,13 @@ public List getReviewImagesByReview(Review review) { /// 삭제하기 /** * 리뷰에 존재하는 모든 이미지 삭제 - * @param review 리뷰 + * @param reviewId 리뷰 */ @Override - public void deleteReviewImage(Review review) throws IOException { + public void deleteReviewImage(Long reviewId) throws IOException { /// 리뷰에 해당하는 파일 이름 다 가져오기 - List images = repository.findByReview(review); + List images = repository.findByReview_Id(reviewId); /// 파일 이름만 추출하기 List imagesList = images.stream() diff --git a/src/main/java/com/seeat/server/domain/image/application/usecase/ReviewImageUseCase.java b/src/main/java/com/seeat/server/domain/image/application/usecase/ReviewImageUseCase.java index e61be519..428ee88e 100644 --- a/src/main/java/com/seeat/server/domain/image/application/usecase/ReviewImageUseCase.java +++ b/src/main/java/com/seeat/server/domain/image/application/usecase/ReviewImageUseCase.java @@ -21,7 +21,7 @@ public interface ReviewImageUseCase { List getReviewImagesByReview(Review review); /// 삭제 - void deleteReviewImage(Review review) throws IOException; + void deleteReviewImage(Long reviewId) throws IOException; diff --git a/src/main/java/com/seeat/server/domain/image/domain/repository/ReviewImageRepository.java b/src/main/java/com/seeat/server/domain/image/domain/repository/ReviewImageRepository.java index 78659f4e..2a873553 100644 --- a/src/main/java/com/seeat/server/domain/image/domain/repository/ReviewImageRepository.java +++ b/src/main/java/com/seeat/server/domain/image/domain/repository/ReviewImageRepository.java @@ -1,13 +1,12 @@ package com.seeat.server.domain.image.domain.repository; -import com.seeat.server.domain.review.domain.entity.Review; import com.seeat.server.domain.image.domain.entity.ReviewImage; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface ReviewImageRepository extends JpaRepository { - List findByReview(Review review); + List findByReview_Id(Long reviewId); void deleteAllByImageUrlIn(List imageUrls); } diff --git a/src/main/java/com/seeat/server/domain/manage/application/dto/response/FeedbackDetailResponse.java b/src/main/java/com/seeat/server/domain/manage/application/dto/response/FeedbackDetailResponse.java index 61fe2fe7..a371dbec 100644 --- a/src/main/java/com/seeat/server/domain/manage/application/dto/response/FeedbackDetailResponse.java +++ b/src/main/java/com/seeat/server/domain/manage/application/dto/response/FeedbackDetailResponse.java @@ -2,6 +2,8 @@ import com.seeat.server.domain.manage.domain.entity.Feedback; import com.seeat.server.domain.user.application.dto.response.UserResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.Builder; @@ -14,7 +16,10 @@ @Builder +@Schema(name = "[응답][피드백] 피드백 상세 조회 Response",description = "피드백 상세 조회에 대한 DTO 입니다.") public record FeedbackDetailResponse( + + @Schema(description = "피드백 내용", example = "메가박스 강남에 대한 정보가 틀린것 같아요!") String content, UserResponse user ) { diff --git a/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewDetailResponse.java b/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewDetailResponse.java index df32b6a3..be6632b1 100644 --- a/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewDetailResponse.java +++ b/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewDetailResponse.java @@ -1,7 +1,8 @@ package com.seeat.server.domain.review.application.dto.response; +import com.seeat.server.domain.hashtag.application.dto.response.ReviewHashTagResponse; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; import com.seeat.server.domain.image.domain.entity.ReviewImage; import com.seeat.server.domain.theater.domain.entity.Seat; import com.seeat.server.domain.user.application.dto.response.UserResponse; @@ -10,7 +11,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; -import java.time.LocalDateTime; import java.util.List; /** @@ -67,8 +67,8 @@ public static ReviewDetailResponse from( List hashTags, Long heartCount, List images, List seats ) { - /// 좌석 정보 - Seat seat = review.getSeat(); + /// 같은 상영관의 좌석이기에, + Seat seat = seats.get(0); return ReviewDetailResponse.builder() .movieTitle(review.getMovieTitle()) diff --git a/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewListResponse.java b/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewListResponse.java index 8cf36237..8e6c0c73 100644 --- a/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewListResponse.java +++ b/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewListResponse.java @@ -1,7 +1,8 @@ package com.seeat.server.domain.review.application.dto.response; +import com.seeat.server.domain.hashtag.application.dto.response.ReviewHashTagResponse; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; import com.seeat.server.domain.user.application.dto.response.UserResponse; import com.seeat.server.global.util.DateFormatUtil; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewSaveResponse.java b/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewSaveResponse.java new file mode 100644 index 00000000..adcd8db4 --- /dev/null +++ b/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewSaveResponse.java @@ -0,0 +1,25 @@ +package com.seeat.server.domain.review.application.dto.response; + +import com.seeat.server.domain.review.domain.entity.Review; +import lombok.Builder; +import java.util.List; + +@Builder +public record ReviewSaveResponse(Long reviewId) { + + + /// 정적 팩토리 메서드 + public static ReviewSaveResponse from(Review review) { + return ReviewSaveResponse.builder() + .reviewId(review.getId()) + .build(); + } + + /// 정적 팩토리 메서드 + public static List from(List reviews){ + return reviews.stream() + .map(ReviewSaveResponse::from) + .toList(); + } + +} diff --git a/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewSeatListResponse.java b/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewSeatListResponse.java index f6b22eb2..9c9ba1f1 100644 --- a/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewSeatListResponse.java +++ b/src/main/java/com/seeat/server/domain/review/application/dto/response/ReviewSeatListResponse.java @@ -1,6 +1,6 @@ package com.seeat.server.domain.review.application.dto.response; -import com.seeat.server.domain.review.domain.repository.dto.SeatReviewStats; +import com.seeat.server.domain.review.domain.repository.dto.ReviewSeatStats; import com.seeat.server.domain.theater.domain.entity.Seat; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @@ -38,7 +38,7 @@ public record ReviewSeatListResponse( ) { /// 정적 팩토리 메서드 - public static ReviewSeatListResponse from(SeatReviewStats stats, List reviews) { + public static ReviewSeatListResponse from(ReviewSeatStats stats, List reviews) { Seat seat = stats.getSeat(); diff --git a/src/main/java/com/seeat/server/domain/review/application/service/ReviewBestContentMediator.java b/src/main/java/com/seeat/server/domain/review/application/service/ReviewBestContentMediator.java index 399b9076..c128d449 100644 --- a/src/main/java/com/seeat/server/domain/review/application/service/ReviewBestContentMediator.java +++ b/src/main/java/com/seeat/server/domain/review/application/service/ReviewBestContentMediator.java @@ -1,10 +1,14 @@ package com.seeat.server.domain.review.application.service; import com.seeat.server.domain.best.application.dto.response.BestReviewListResponse; +import com.seeat.server.domain.hashtag.application.service.ReviewHashTagService; import com.seeat.server.domain.review.application.usecase.ReviewBestContentMediatorUseCase; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; +import com.seeat.server.domain.review.application.usecase.ReviewSeatUseCase; +import com.seeat.server.domain.review.domain.entity.Review; import com.seeat.server.domain.review.domain.repository.ReviewRepository; import com.seeat.server.domain.review.domain.repository.dto.ReviewWithLikeCount; +import com.seeat.server.domain.theater.domain.entity.Seat; import com.seeat.server.global.response.pageable.PageRequest; import com.seeat.server.global.response.pageable.SliceResponse; import lombok.RequiredArgsConstructor; @@ -29,6 +33,7 @@ public class ReviewBestContentMediator implements ReviewBestContentMediatorUseCa private final ReviewRepository repository; private final ReviewHashTagService hashTagService; + private final ReviewSeatUseCase seatService; /** * 베스트 리뷰 조회를 위해 사용되는 함수 * @param pageRequest 페이지 @@ -54,6 +59,11 @@ public SliceResponse getBestReviews(PageRequest pageRequ } + /** + * 리뷰의 Id를 바탕으로 BestReview DTO 변경 로직 + * @param reviewIds ID 추출 목록 + * @param reviews Page 처리를 한 리뷰 엔티티 + */ /** * 리뷰의 Id를 바탕으로 BestReview DTO 변경 로직 * @param reviewIds ID 추출 목록 @@ -61,23 +71,34 @@ public SliceResponse getBestReviews(PageRequest pageRequ */ private List getBestReviewListResponses(List reviewIds, Slice reviews) { - // 추출된 리뷰 ID로 해시태그 한 번에 조회 (IN 쿼리) + // 1. 해시태그 한 번에 조회 (IN 쿼리) List allHashTags = hashTagService.getReviewHashTagByReviews(reviewIds); - // 리뷰 ID를 바탕으로 해시태그 매핑 + // 2. 리뷰 ID별 해시태그 매핑 Map> mapping = allHashTags.stream() .collect(Collectors.groupingBy(ht -> ht.getReview().getId())); - // DTO 변환 + // 3. 리뷰 ID별 좌석들 한 번에 조회 + Map> reviewSeatMap = seatService.loadSeatsByReviewIds(reviewIds); + + // 4. DTO 변환 return reviews.stream() - .map(review -> BestReviewListResponse.from( - review.getReview(), - mapping.getOrDefault(review.getReview().getId(), List.of()), - review.getLikeCount()) - ) + .map(reviewWithLikeCount -> { + Review review = reviewWithLikeCount.getReview(); + Long reviewId = review.getId(); + List tags = mapping.getOrDefault(reviewId, List.of()); + List seats = reviewSeatMap.getOrDefault(reviewId, List.of()); + return BestReviewListResponse.from( + review, + tags, + reviewWithLikeCount.getLikeCount(), + seats + ); + }) .toList(); } + /** * 리뷰의 Id를 얻기 위한 공통 로직 * @param reviews ID를 추출할 리뷰 목록 diff --git a/src/main/java/com/seeat/server/domain/review/application/service/ReviewSeatService.java b/src/main/java/com/seeat/server/domain/review/application/service/ReviewSeatService.java new file mode 100644 index 00000000..8cc456f3 --- /dev/null +++ b/src/main/java/com/seeat/server/domain/review/application/service/ReviewSeatService.java @@ -0,0 +1,111 @@ +package com.seeat.server.domain.review.application.service; + +import com.seeat.server.domain.review.application.usecase.ReviewSeatUseCase; +import com.seeat.server.domain.review.domain.entity.Review; +import com.seeat.server.domain.review.domain.entity.ReviewSeat; +import com.seeat.server.domain.review.domain.repository.ReviewSeatRepository; +import com.seeat.server.domain.theater.domain.entity.Seat; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; + +/** + * 리뷰와 좌석 간의 다대다 관계를 처리하는 서비스 클래스입니다. + * - 리뷰와 좌석을 연결/해제 + * - 특정 리뷰에 연결된 좌석 조회 + * - 특정 좌석에 연결된 리뷰 조회를 지원합니다. + */ +@Service +@Transactional +@RequiredArgsConstructor +public class ReviewSeatService implements ReviewSeatUseCase { + + /// DB 의존성 + private final ReviewSeatRepository repository; + + /** + * 특정 좌석과 리뷰를 연결(매핑)합니다. + * + * @param seat 좌석 엔티티 + * @param review 리뷰 엔티티 + */ + @Override + public void connectSeats(Seat seat, Review review) { + + /// 객체 생성 + ReviewSeat reviewSeat = ReviewSeat.of(review, seat); + + /// DB에 저장 + repository.save(reviewSeat); + + } + + /** + * 주어진 리뷰에 연결된 모든 좌석 목록을 조회합니다. + * + * @param review 리뷰 엔티티 + * @return 좌석 엔티티 목록 + */ + @Override + public List loadSeatsByReview(Review review) { + + /// 해당 리뷰의 다대다 테이블에서 조회하기 + List seats = repository.findByReview(review); + + /// 좌석만 추출하기 + return seats.stream() + .map(ReviewSeat::getSeat) + .toList(); + } + + /** + * 주어진 좌석에 연결된 모든 리뷰 목록을 조회합니다. + * + * @param seat 좌석 엔티티 + * @return 리뷰 엔티티 목록 + */ + @Override + public List loadReviewsBySeat(Seat seat) { + /// 해당 리뷰의 다대다 테이블에서 조회하기 + List seats = repository.findBySeat(seat); + + /// 리뷰만 추출하기 + return seats.stream() + .map(ReviewSeat::getReview) + .toList(); + + } + + @Override + public Map> loadSeatsByReviewIds(List reviewIds) { + List result = repository.findByReview_IdIn(reviewIds); + + return result.stream() + .collect( + java.util.stream.Collectors.groupingBy( + rs -> rs.getReview().getId(), + java.util.stream.Collectors.mapping( + ReviewSeat::getSeat, + java.util.stream.Collectors.toList() + ) + ) + ); + } + + /** + * 특정 리뷰에 연결된 모든 좌석 매핑을 해제(삭제)합니다. + * + * @param reviewId 삭제할 리뷰 + */ + @Override + public void disconnectSeats(Long reviewId) { + + /// 해당 좌석에 존재하는 행 전부 삭제하기 + repository.deleteByReviewId(reviewId); + + } + +} diff --git a/src/main/java/com/seeat/server/domain/review/application/service/ReviewService.java b/src/main/java/com/seeat/server/domain/review/application/service/ReviewService.java index a4977504..6698792c 100644 --- a/src/main/java/com/seeat/server/domain/review/application/service/ReviewService.java +++ b/src/main/java/com/seeat/server/domain/review/application/service/ReviewService.java @@ -3,11 +3,13 @@ import com.seeat.server.domain.best.application.usecase.BestContentUseCase; import com.seeat.server.domain.image.application.usecase.ReviewImageUseCase; import com.seeat.server.domain.review.application.dto.request.ReviewSortType; +import com.seeat.server.domain.review.application.dto.response.ReviewSaveResponse; import com.seeat.server.domain.review.application.dto.response.ReviewSeatListResponse; -import com.seeat.server.domain.review.application.usecase.ReviewHashTagUseCase; +import com.seeat.server.domain.hashtag.application.usecase.ReviewHashTagUseCase; +import com.seeat.server.domain.review.application.usecase.ReviewSeatUseCase; import com.seeat.server.domain.review.application.usecase.ReviewUseCase; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; import com.seeat.server.domain.image.domain.entity.ReviewImage; import com.seeat.server.domain.review.domain.repository.ReviewRepository; import com.seeat.server.domain.review.application.dto.request.ReviewRequest; @@ -15,7 +17,7 @@ import com.seeat.server.domain.review.application.dto.response.ReviewDetailResponse; import com.seeat.server.domain.review.application.dto.response.ReviewListResponse; import com.seeat.server.domain.review.domain.repository.dto.ReviewWithLikeCount; -import com.seeat.server.domain.review.domain.repository.dto.SeatReviewStats; +import com.seeat.server.domain.review.domain.repository.dto.ReviewSeatStats; import com.seeat.server.domain.theater.application.usecase.SeatRatingUseCase; import com.seeat.server.domain.theater.application.usecase.TheaterUseCase; import com.seeat.server.domain.theater.domain.entity.Auditorium; @@ -55,6 +57,7 @@ public class ReviewService implements ReviewUseCase { private final UserUseCase userService; private final SeatRatingUseCase seatRatingService; private final BestContentUseCase bestContentService; + private final ReviewSeatUseCase reviewSeatService; // ======================== // 저장 함수 @@ -62,13 +65,12 @@ public class ReviewService implements ReviewUseCase { /** * 리뷰 저장을 위한 로직 * 테스트를 위해서 반환값이 존재하는 것입니다. + * * @param request 리뷰를 위한 DTO * @param userId 리뷰를 작성할 유저 id (@AuthenticationPrincipal) */ @Override - public List createReview(ReviewRequest request, Long userId) throws IOException { - - /// 여러 개의 좌석도 동시 기입 + public Review createReview(ReviewRequest request, Long userId) throws IOException { /// 좌석 예외 처리 List seats = theaterService.getSeat(request.getSeatIds()); @@ -76,34 +78,32 @@ public List createReview(ReviewRequest request, Long userId) throws IOEx /// 유저 예외처리 User user = userService.getUser(userId); - List savedReviews = new ArrayList<>(); + /// 리뷰 객체 생성 + Review review = Review.of(user, request.getMovieTitle(), request.getRating(), request.getContent(), request.getTitle()); - /// 한 명의 유저가 여러개의 리뷰를 동시에 저장할 시 - String groupId = UUID.randomUUID().toString(); - - for (Seat seat : seats) { - /// 리뷰 객체 생성 - Review review = Review.of(user, seat, request.getMovieTitle(), request.getRating(), request.getContent(), request.getTitle(), groupId); + /// DB 저장 + Review savedReview = repository.save(review); - /// DB 저장 - Review savedReview = repository.save(review); + /// 이미지 저장 + if (request.getImageUrls() != null && !request.getImageUrls().isEmpty()) { + String thumbnail = imageService.saveReviewImage(savedReview, request.getImageUrls()).get(0); + savedReview.changeThumbnailUrl(thumbnail); + } - /// 이미지 저장 - if (request.getImageUrls() != null && !request.getImageUrls().isEmpty()) { - String thumbnail = imageService.saveReviewImage(savedReview, request.getImageUrls()).get(0); - savedReview.changeThumbnailUrl(thumbnail); - } + /// 해시태그 저장 + hashTagService.createReviewHashTag(savedReview, request.getHashtags()); - /// 해시태그 저장 - hashTagService.createReviewHashTag(savedReview, request.getHashtags()); + /// 한 명의 유저가 여러개의 리뷰를 동시에 저장할 시 + for (Seat seat : seats) { /// 좌석 평점 업데이트 seatRatingService.saveSeatRating(savedReview, seat); - savedReviews.add(savedReview); + /// 여러개와 좌석을 매핑 + reviewSeatService.connectSeats(seat, savedReview); } - return savedReviews; + return savedReview; } // ======================== @@ -126,10 +126,7 @@ public ReviewDetailResponse loadReview(Long reviewId) { Review review = result.getReview(); /// 같이 작성된 좌석 조회 - List reviews = repository.findByGroupId(review.getGroupId()); - List seats = reviews.stream() - .map(Review::getSeat) - .toList(); + List seats = reviewSeatService.loadSeatsByReview(review); /// ReviewId를 바탕으로 작성한 해시태그 조회 List hashTags = hashTagService.getReviewHashTagByReview(review); @@ -154,7 +151,7 @@ public SliceResponse loadReviewsBySeatId( Seat seat = theaterService.getSeat(seatId); /// 좌석의 리뷰정보 조회 - SeatReviewStats stats = repository.findSeatReviewStats(seatId); + ReviewSeatStats stats = repository.findSeatReviewStats(seatId); /// Pageable 처리 Pageable pageable = getPageable(pageRequest); @@ -266,7 +263,7 @@ public void updateReview(Long reviewId, ReviewUpdateRequest request, Long userId if (request.getImages() != null && !request.getImages().isEmpty()) { /// 기존 이미지 삭제 - imageService.deleteReviewImage(review); + imageService.deleteReviewImage(reviewId); /// 새로운 이미지 추가 String thumbnail = imageService.saveReviewImage(review, request.getImages()).get(0); @@ -308,15 +305,18 @@ public void deleteReview(Long reviewId, Long userId) throws IOException { /// 해시태그 삭제 hashTagService.deleteReviewHashTagByReviewId(review.getId()); - /// DB 삭제 - repository.deleteById(reviewId); - /// 이미지 삭제 - imageService.deleteReviewImage(review); + imageService.deleteReviewImage(reviewId); + + /// 좌석 매핑도 삭제 + reviewSeatService.disconnectSeats(reviewId); /// 인기 게시글이라면, 캐싱 초기화 boolean checked = bestContentService.checkBestContentsByReviewId(reviewId); + /// DB 삭제 + repository.deleteById(reviewId); + if (checked) { /// 인기 게시글을 삭제 후, 다시 초기화 (리셋) bestContentService.resetBestContents(); diff --git a/src/main/java/com/seeat/server/domain/review/application/usecase/ReviewSeatUseCase.java b/src/main/java/com/seeat/server/domain/review/application/usecase/ReviewSeatUseCase.java new file mode 100644 index 00000000..35fb964e --- /dev/null +++ b/src/main/java/com/seeat/server/domain/review/application/usecase/ReviewSeatUseCase.java @@ -0,0 +1,43 @@ +package com.seeat.server.domain.review.application.usecase; + +import com.seeat.server.domain.review.domain.entity.Review; +import com.seeat.server.domain.theater.domain.entity.Seat; +import java.util.List; +import java.util.Map; + +/** + * 리뷰와 좌석 간의 다대다 관계를 처리하기 위한 UseCase 인터페이스입니다. + * 이 인터페이스는 리뷰와 좌석을 연결, 해제, 조회 등 다양한 비즈니스 로직을 정의하는 역할을 합니다. + * 실 구현 클래스에서는 리뷰-좌석 매핑에 대한 등록/삭제/조회 등 상세 행위를 구현할 수 있습니다. + */ +public interface ReviewSeatUseCase { + + /** + * 좌석과 리뷰를 연결하는 함수 + * @param seat 좌석 + * @param review 리뷰 + */ + void connectSeats(Seat seat, Review review); + + + /** + * 리뷰에 따른 좌석 목록 조회하기 + * @param review 리뷰 + */ + List loadSeatsByReview(Review review); + + /** + * 좌석에 따른 리뷰 조회하기 + * @param seat 좌석 + */ + List loadReviewsBySeat(Seat seat); + + Map> loadSeatsByReviewIds(List reviewIds); + + /** + * 리뷰를 해제하면 테이블에서 삭제하는 함수 + * @param reviewId 삭제하는 리뷰 + */ + void disconnectSeats(Long reviewId); + +} diff --git a/src/main/java/com/seeat/server/domain/review/application/usecase/ReviewUseCase.java b/src/main/java/com/seeat/server/domain/review/application/usecase/ReviewUseCase.java index b4f3c856..060bdde4 100644 --- a/src/main/java/com/seeat/server/domain/review/application/usecase/ReviewUseCase.java +++ b/src/main/java/com/seeat/server/domain/review/application/usecase/ReviewUseCase.java @@ -5,6 +5,7 @@ import com.seeat.server.domain.review.application.dto.request.ReviewUpdateRequest; import com.seeat.server.domain.review.application.dto.response.ReviewDetailResponse; import com.seeat.server.domain.review.application.dto.response.ReviewListResponse; +import com.seeat.server.domain.review.application.dto.response.ReviewSaveResponse; import com.seeat.server.domain.review.application.dto.response.ReviewSeatListResponse; import com.seeat.server.domain.review.domain.entity.Review; import com.seeat.server.global.response.pageable.PageRequest; @@ -12,7 +13,7 @@ import org.springframework.data.domain.Slice; import java.io.IOException; -import java.util.List; + /** * [리뷰 인터페이스] * - 로그인한 유저만 좌석 후기를 작성 및 열람할 수 있습니다. @@ -25,7 +26,7 @@ public interface ReviewUseCase { // 저장 함수 // ======================== - List createReview(ReviewRequest request, Long userId) throws IOException; + Review createReview(ReviewRequest request, Long userId) throws IOException; // ======================== // 조회 함수 diff --git a/src/main/java/com/seeat/server/domain/review/domain/entity/Review.java b/src/main/java/com/seeat/server/domain/review/domain/entity/Review.java index ae1e025b..bd1dbbc0 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/entity/Review.java +++ b/src/main/java/com/seeat/server/domain/review/domain/entity/Review.java @@ -1,7 +1,6 @@ package com.seeat.server.domain.review.domain.entity; import com.seeat.server.domain.BaseEntity; -import com.seeat.server.domain.theater.domain.entity.Seat; import com.seeat.server.domain.user.domain.entity.User; import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -11,8 +10,9 @@ /** * 영화 리뷰 엔티티 - * - 리뷰 작성자(User), 좌석(Seat)과 연관 + * - 리뷰 작성자(User) * - 영화 제목, 평점, 내용, 이미지 URL 저장 + * - 좌석은 중간테이블로 설정하여 처리한다. */ @Entity @@ -29,12 +29,6 @@ public class Review extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private User user; - @ManyToOne(fetch = FetchType.LAZY) - private Seat seat; - - /// 같이 입력된 리뷰를 확인하기 위한 식별자 - private String groupId; - private String title; private String movieTitle; @@ -46,17 +40,15 @@ public class Review extends BaseEntity { private String content; /// 정적 팩토리 메서드 - public static Review of(User user, Seat seat, String movieTitle, double rating, String content, String title, String groupId) { + public static Review of(User user, String movieTitle, double rating, String content, String title) { return Review.builder() .user(user) - .seat(seat) .title(title) .movieTitle(movieTitle) .thumbnailUrl("thumbnailUrl") .rating(rating) .content(content) - .groupId(groupId) .build(); } diff --git a/src/main/java/com/seeat/server/domain/review/domain/entity/ReviewSeat.java b/src/main/java/com/seeat/server/domain/review/domain/entity/ReviewSeat.java new file mode 100644 index 00000000..c7315462 --- /dev/null +++ b/src/main/java/com/seeat/server/domain/review/domain/entity/ReviewSeat.java @@ -0,0 +1,43 @@ +package com.seeat.server.domain.review.domain.entity; + +import com.seeat.server.domain.BaseEntity; +import com.seeat.server.domain.theater.domain.entity.Seat; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * 리뷰 및 좌석 다대다 엔티티 + * 하나의 리뷰가 여러 개의 좌석에서 작성될 수 있기에 중간 테이블로 활용한다. + */ + +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +public class ReviewSeat extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "seat_id") + private Seat seat; + + /// 정적 팩토리 메서드 + public static ReviewSeat of(Review review, Seat seat) { + return ReviewSeat.builder() + .review(review) + .seat(seat) + .build(); + } + +} diff --git a/src/main/java/com/seeat/server/domain/review/domain/repository/ReviewRepository.java b/src/main/java/com/seeat/server/domain/review/domain/repository/ReviewRepository.java index c454dc48..49c2f1dd 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/repository/ReviewRepository.java +++ b/src/main/java/com/seeat/server/domain/review/domain/repository/ReviewRepository.java @@ -1,10 +1,10 @@ package com.seeat.server.domain.review.domain.repository; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.custom.ReviewRepositoryCustom; +import com.seeat.server.domain.review.domain.repository.custom.ReviewRepositoryCustom; import com.seeat.server.domain.review.domain.repository.dto.ReviewLikeCount; import com.seeat.server.domain.review.domain.repository.dto.ReviewWithLikeCount; -import com.seeat.server.domain.review.domain.repository.dto.SeatReviewStats; +import com.seeat.server.domain.review.domain.repository.dto.ReviewSeatStats; import com.seeat.server.domain.search.application.dto.request.ReviewSearchCondition; import com.seeat.server.domain.user.domain.entity.User; import org.springframework.data.domain.Pageable; @@ -25,28 +25,34 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param seatId 조회할 좌석 ID */ @Query(""" - SELECT s AS seat, - COUNT(r) AS reviewCount, - AVG(r.rating) AS averageRating - FROM Seat s - LEFT JOIN Review r ON r.seat = s - WHERE s.id = :seatId - GROUP BY s - """) - SeatReviewStats findSeatReviewStats(@Param("seatId") String seatId); + SELECT s AS seat, + COUNT(rs.review) AS reviewCount, + AVG(r.rating) AS averageRating + FROM Seat s + LEFT JOIN ReviewSeat rs ON rs.seat = s + LEFT JOIN Review r ON rs.review = r + WHERE s.id = :seatId + GROUP BY s + """) + ReviewSeatStats findSeatReviewStats(@Param("seatId") String seatId); // ======================== - // 좌석별 함수 + // 좌석별 함수 (다대다 매핑 사용) // ======================== /** * 좌석별 최신순(기본, 생성일자 내림차순) 리뷰 조회 */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " - + "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " - + "WHERE r.seat.id = :seatId " - + "GROUP BY r " - + "ORDER BY r.createdAt DESC") + @Query(""" + SELECT r AS review, + COUNT(rl) AS likeCount + FROM ReviewSeat rs + JOIN Review r ON rs.review = r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE rs.seat.id = :seatId + GROUP BY r + ORDER BY r.createdAt DESC +""") Slice findBySeat_IdOrderByLatest(@Param("seatId") String seatId, Pageable pageable); /** @@ -55,11 +61,16 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param pageable 페이징 정보 * @return 리뷰와 좋아요 수 */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "WHERE r.seat.id = :seatId " + - "GROUP BY r " + - "ORDER BY COUNT(rl) DESC, r.createdAt DESC") + @Query(""" + SELECT r AS review, + COUNT(rl) AS likeCount + FROM ReviewSeat rs + JOIN Review r ON rs.review = r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE rs.seat.id = :seatId + GROUP BY r + ORDER BY COUNT(rl) DESC, r.createdAt DESC +""") Slice findBySeat_IdOrderByLikesDesc(@Param("seatId") String seatId, Pageable pageable); /** @@ -68,11 +79,16 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param pageable 페이징 정보 * @return 리뷰와 좋아요 수 */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "WHERE r.seat.id = :seatId " + - "GROUP BY r " + - "ORDER BY r.rating DESC, r.createdAt DESC") + @Query(""" + SELECT r AS review, + COUNT(rl) AS likeCount + FROM ReviewSeat rs + JOIN Review r ON rs.review = r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE rs.seat.id = :seatId + GROUP BY r + ORDER BY r.rating DESC, r.createdAt DESC +""") Slice findBySeat_IdOrderByRatingDesc(@Param("seatId") String seatId, Pageable pageable); /** @@ -81,25 +97,35 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param pageable 페이징 정보 * @return 리뷰와 좋아요 수 */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "WHERE r.seat.id = :seatId " + - "GROUP BY r " + - "ORDER BY r.rating ASC, r.createdAt DESC") + @Query(""" + SELECT r AS review, + COUNT(rl) AS likeCount + FROM ReviewSeat rs + JOIN Review r ON rs.review = r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE rs.seat.id = :seatId + GROUP BY r + ORDER BY r.rating ASC, r.createdAt DESC +""") Slice findBySeat_IdOrderByRatingAsc(@Param("seatId") String seatId, Pageable pageable); // ======================== - // 상영관별 함수 + // 상영관별 함수 (다대다 매핑 사용) // ======================== /** * 상영관별 최신순(기본, 생성일자 내림차순) 리뷰 조회 */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " - + "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " - + "WHERE r.seat.auditorium.id = :auditoriumId " - + "GROUP BY r " - + "ORDER BY r.createdAt DESC") + @Query(""" + SELECT r AS review, + COUNT(rl) AS likeCount + FROM ReviewSeat rs + JOIN Review r ON rs.review = r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE rs.seat.auditorium.id = :auditoriumId + GROUP BY r + ORDER BY r.createdAt DESC +""") Slice findByAuditorium_IdOrderByLatest(@Param("auditoriumId") String auditoriumId, Pageable pageable); /** @@ -108,11 +134,16 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param pageable 페이징 정보 * @return 리뷰와 좋아요 수 */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "WHERE r.seat.auditorium.id = :auditoriumId " + - "GROUP BY r " + - "ORDER BY COUNT(rl) DESC, r.createdAt DESC") + @Query(""" + SELECT r AS review, + COUNT(rl) AS likeCount + FROM ReviewSeat rs + JOIN Review r ON rs.review = r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE rs.seat.auditorium.id = :auditoriumId + GROUP BY r + ORDER BY COUNT(rl) DESC, r.createdAt DESC +""") Slice findByAuditorium_IdOrderByLikesDesc(@Param("auditoriumId") String auditoriumId, Pageable pageable); /** @@ -121,11 +152,16 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param pageable 페이징 정보 * @return 리뷰와 좋아요 수 */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "WHERE r.seat.auditorium.id = :auditoriumId " + - "GROUP BY r " + - "ORDER BY r.rating DESC, r.createdAt DESC") + @Query(""" + SELECT r AS review, + COUNT(rl) AS likeCount + FROM ReviewSeat rs + JOIN Review r ON rs.review = r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE rs.seat.auditorium.id = :auditoriumId + GROUP BY r + ORDER BY r.rating DESC, r.createdAt DESC +""") Slice findByAuditorium_IdOrderByRatingDesc(@Param("auditoriumId") String auditoriumId, Pageable pageable); /** @@ -134,11 +170,16 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param pageable 페이징 정보 * @return 리뷰와 좋아요 수 */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "WHERE r.seat.auditorium.id = :auditoriumId " + - "GROUP BY r " + - "ORDER BY r.rating ASC, r.createdAt DESC") + @Query(""" + SELECT r AS review, + COUNT(rl) AS likeCount + FROM ReviewSeat rs + JOIN Review r ON rs.review = r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE rs.seat.auditorium.id = :auditoriumId + GROUP BY r + ORDER BY r.rating ASC, r.createdAt DESC +""") Slice findByAuditorium_IdOrderByRatingAsc(@Param("auditoriumId") String auditoriumId, Pageable pageable); /** @@ -146,9 +187,11 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param auditoriumId 상영관 * @return boolean */ - @Query("SELECT COUNT(r) " + - "FROM Review r "+ - "WHERE r.seat.auditorium.id = :auditoriumId") + @Query(""" + SELECT COUNT(rs.review) + FROM ReviewSeat rs + WHERE rs.seat.auditorium.id = :auditoriumId + """) Long countByAuditoriumId(@Param("auditoriumId") String auditoriumId); @@ -157,18 +200,30 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param id 리뷰 ID * @return Optional */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "WHERE r.id = :id " + - "GROUP BY r ") + @Query(""" + SELECT r AS review, + COUNT(rl.user.id) AS likeCount + FROM Review r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE r.id = :id + GROUP BY r +""") Optional findReviewAndCountById(@Param("id") Long id); - @Query("SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "WHERE r.id IN :reviewIds " + - "GROUP BY r " + - "ORDER BY COUNT(rl) DESC, r.createdAt DESC") + /** + * 북마크에서 사용되는 IDS들에 포함되는 리뷰 모두 가져오기 + * @param reviewIds 리뷰 아이디들 + */ + @Query(""" + SELECT r AS review, + COUNT(rl.user.id) AS likeCount + FROM Review r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE r.id IN :reviewIds + GROUP BY r + ORDER BY COUNT(rl.user.id) DESC, r.createdAt DESC + """) List findByReviewIds(@Param("reviewIds") List reviewIds); /** @@ -178,10 +233,14 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param pageable 페이지네이션 정보 * @return 좋아요 수가 많은 순으로 정렬된 리뷰 목록 */ - @Query("SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "GROUP BY r "+ - "ORDER BY COUNT(rl) DESC, r.createdAt DESC") + @Query(""" + SELECT r AS review, + COUNT(rl.user.id) AS likeCount + FROM Review r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + GROUP BY r + ORDER BY COUNT(rl.user.id) DESC, r.createdAt DESC + """) Slice findBestReviews(Pageable pageable); @@ -191,12 +250,15 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @param pageable 페이징 * @return Slice */ - @Query( - "SELECT r AS review, COUNT(rl) AS likeCount " + - "FROM Review r LEFT JOIN ReviewLike rl ON rl.review.id = r.id " + - "WHERE r.user.id = :userId " + - "GROUP BY r " + - "ORDER BY COUNT(rl) DESC, r.createdAt DESC ") + @Query(""" + SELECT r AS review, + COUNT(rl.user.id) AS likeCount + FROM Review r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE r.user.id = :userId + GROUP BY r + ORDER BY COUNT(rl.user.id) DESC, r.createdAt DESC +""") Slice findMyReviews(@Param("userId") Long userId, Pageable pageable); @@ -215,24 +277,22 @@ public interface ReviewRepository extends JpaRepository, ReviewRep * @return Slice 응답 */ Slice searchReviewsWithFilters(ReviewSearchCondition condition, Pageable pageable); - + /** * 리뷰 개수와 하트 개수 조회 * * @param userId 유저 Id * @return ReviewLikeCountResponse 응답 */ - @Query(" SELECT new com.seeat.server.domain.review.domain.repository.dto.ReviewLikeCount(COUNT(DISTINCT r.id),COUNT(rl.id)) " + - "FROM Review r " + - "LEFT JOIN ReviewLike rl " + - "ON rl.review.id = r.id " + - "AND rl.user.id != :userId " + - "WHERE r.user.id = :userId") - ReviewLikeCount findReviewCountAndLikeCountByUserId(@Param("userId") Long userId); - - /** - * 같이 작성된 리뷰 조회 - * @param groupId 같이 작성된 그룹 ID - */ - List findByGroupId(String groupId); + @Query(""" + SELECT COUNT(DISTINCT r.id) AS reviewCount, + COUNT(rl.id) AS likeCount + FROM Review r + LEFT JOIN ReviewLike rl ON rl.review.id = r.id + WHERE r.user.id = :userId + """) + Optional findReviewAndLikeCountByUserId(@Param("userId") Long userId); + + + } diff --git a/src/main/java/com/seeat/server/domain/review/domain/repository/ReviewSeatRepository.java b/src/main/java/com/seeat/server/domain/review/domain/repository/ReviewSeatRepository.java new file mode 100644 index 00000000..a76753e9 --- /dev/null +++ b/src/main/java/com/seeat/server/domain/review/domain/repository/ReviewSeatRepository.java @@ -0,0 +1,33 @@ +package com.seeat.server.domain.review.domain.repository; + +import com.seeat.server.domain.review.domain.entity.Review; +import com.seeat.server.domain.review.domain.entity.ReviewSeat; +import com.seeat.server.domain.theater.domain.entity.Seat; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ReviewSeatRepository extends JpaRepository { + + /** + * 리뷰를 바탕으로 조회 + * @param review 리뷰 + */ + List findByReview(Review review); + + /** + * 좌석을 바탕으로 조회 + * @param seat 좌석 + */ + List findBySeat(Seat seat); + + /** + * 리뷰를 바탕으로 삭제 + * @param review 리뷰 + */ + void deleteByReview(Review review); + + List findByReview_IdIn(List reviewIds); + + void deleteByReviewId(Long reviewId); +} diff --git a/src/main/java/com/seeat/server/domain/review/domain/entity/custom/ReviewRepositoryCustom.java b/src/main/java/com/seeat/server/domain/review/domain/repository/custom/ReviewRepositoryCustom.java similarity index 90% rename from src/main/java/com/seeat/server/domain/review/domain/entity/custom/ReviewRepositoryCustom.java rename to src/main/java/com/seeat/server/domain/review/domain/repository/custom/ReviewRepositoryCustom.java index 552bf9aa..cbf9c28a 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/entity/custom/ReviewRepositoryCustom.java +++ b/src/main/java/com/seeat/server/domain/review/domain/repository/custom/ReviewRepositoryCustom.java @@ -1,24 +1,24 @@ -package com.seeat.server.domain.review.domain.entity.custom; - -import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.search.application.dto.request.ReviewSearchCondition; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; - -/** - * 커스텀 리뷰 레포지토리 인터페이스 - * - * 다양한 필터(검색어, 상영관, 해시태그)와 정렬 조건을 적용한 - * 리뷰 검색 기능을 위한 메서드를 선언합니다. - */ -public interface ReviewRepositoryCustom { - - /** - * 검색 조건에 따라 리뷰를 조회합니다. - * - * @param condition 검색 조건 DTO - * @param pageable 페이징 - * @return Slice 응답 - */ - Slice searchReviewsWithFilters(ReviewSearchCondition condition, Pageable pageable); -} +package com.seeat.server.domain.review.domain.repository.custom; + +import com.seeat.server.domain.review.domain.entity.Review; +import com.seeat.server.domain.search.application.dto.request.ReviewSearchCondition; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +/** + * 커스텀 리뷰 레포지토리 인터페이스 + * + * 다양한 필터(검색어, 상영관, 해시태그)와 정렬 조건을 적용한 + * 리뷰 검색 기능을 위한 메서드를 선언합니다. + */ +public interface ReviewRepositoryCustom { + + /** + * 검색 조건에 따라 리뷰를 조회합니다. + * + * @param condition 검색 조건 DTO + * @param pageable 페이징 + * @return Slice 응답 + */ + Slice searchReviewsWithFilters(ReviewSearchCondition condition, Pageable pageable); +} diff --git a/src/main/java/com/seeat/server/domain/review/domain/entity/custom/ReviewRepositoryImpl.java b/src/main/java/com/seeat/server/domain/review/domain/repository/custom/ReviewRepositoryImpl.java similarity index 94% rename from src/main/java/com/seeat/server/domain/review/domain/entity/custom/ReviewRepositoryImpl.java rename to src/main/java/com/seeat/server/domain/review/domain/repository/custom/ReviewRepositoryImpl.java index e4d8cf62..b35a7292 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/entity/custom/ReviewRepositoryImpl.java +++ b/src/main/java/com/seeat/server/domain/review/domain/repository/custom/ReviewRepositoryImpl.java @@ -1,111 +1,112 @@ -package com.seeat.server.domain.review.domain.entity.custom; - -import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.search.application.dto.request.ReviewSearchCondition; -import com.seeat.server.global.response.ErrorCode; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.persistence.TypedQuery; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; -import org.springframework.stereotype.Repository; -import org.springframework.util.StringUtils; - -import java.util.List; - -@Repository -@RequiredArgsConstructor -public class ReviewRepositoryImpl implements ReviewRepositoryCustom{ - - @PersistenceContext - private final EntityManager em; - - /** - * 리뷰 검색시 정렬 및 필터 조건에 따라 리뷰를 조회 - * - * @param condition 검색 조건 DTO - * @param pageable 페이징 - * @return Slice - */ - @Override - public Slice searchReviewsWithFilters(ReviewSearchCondition condition, Pageable pageable) { - - // 기본 Select review, seat, reviewLike - StringBuilder jpql = new StringBuilder( - "SELECT r FROM Review r " + - "JOIN r.seat s " + - "LEFT JOIN ReviewLike rl ON rl.review = r " - ); - - // 해시태그 필터가 있을 경우 - 해시태그 조인 추가 - boolean filterHashTags = condition.getHashTagIds() != null && !condition.getHashTagIds().isEmpty(); - if (filterHashTags) { - jpql.append("JOIN ReviewHashTag rht ON rht.review = r "); - } - - // 공통 WHERE 절 (영화 제목, 내용 검색) - jpql.append("WHERE (r.movieTitle LIKE :keyword OR r.content LIKE :keyword OR r.title LIKE :keyword) "); - - // 상영관 필터 - if (StringUtils.hasText(condition.getAuditoriumId())) { - jpql.append("AND s.auditorium.id = :auditoriumId "); - } - - // 해시태그 필터 - if (filterHashTags) { - jpql.append("AND rht.hashTag.id IN :hashTagIds "); - } - - // 좋아요 수 기준 정렬을 위해 그룹화 - jpql.append("GROUP BY r "); - - // 검색한 모든 해시태그가 포함된 리뷰만 필터링 - if (filterHashTags) { - jpql.append("HAVING COUNT(DISTINCT rht.hashTag.id) = :hashTagCount "); - } - - // 정렬 조건 분기 - switch (condition.getSort()) { - // 인기순 - case POPULAR -> jpql.append("ORDER BY COUNT(rl) DESC, r.createdAt DESC"); - // 평점순 - case RATING -> jpql.append("ORDER BY r.rating DESC"); - // 최신순 - case LATEST -> jpql.append("ORDER BY r.createdAt DESC"); - default -> throw new IllegalArgumentException(ErrorCode.INVALID_SORT_TYPE.getMessage()); - } - - // 동적 JPQL 쿼리 생성 - TypedQuery query = em.createQuery(jpql.toString(), Review.class); - query.setParameter("keyword", "%" + condition.getKeyword() + "%"); - - - // 상영관 파라미터 설정 - if (StringUtils.hasText(condition.getAuditoriumId())) { - query.setParameter("auditoriumId", condition.getAuditoriumId()); - } - - // 해시태그 파라미터 설정 - if (filterHashTags) { - query.setParameter("hashTagIds", condition.getHashTagIds()); - query.setParameter("hashTagCount", (long) condition.getHashTagIds().size()); - } - - // 페이징 처리 - query.setFirstResult((int) pageable.getOffset()); - query.setMaxResults(pageable.getPageSize() + 1); - - // 쿼리 실행 - List results = query.getResultList(); - boolean hasNext = results.size() > pageable.getPageSize(); - - // Slice 마지막 요소 제거 - if (hasNext) results.remove(results.size() - 1); - - // Slice 형태로 변환 - return new SliceImpl<>(results, pageable, hasNext); - } - -} +package com.seeat.server.domain.review.domain.repository.custom; + +import com.seeat.server.domain.review.domain.entity.Review; +import com.seeat.server.domain.search.application.dto.request.ReviewSearchCondition; +import com.seeat.server.global.response.ErrorCode; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.TypedQuery; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class ReviewRepositoryImpl implements ReviewRepositoryCustom{ + + @PersistenceContext + private final EntityManager em; + + /** + * 리뷰 검색시 정렬 및 필터 조건에 따라 리뷰를 조회 + * + * @param condition 검색 조건 DTO + * @param pageable 페이징 + * @return Slice + */ + @Override + public Slice searchReviewsWithFilters(ReviewSearchCondition condition, Pageable pageable) { + + // 기본 Select review, seat, reviewLike + StringBuilder jpql = new StringBuilder( + "SELECT r FROM Review r " + + "JOIN ReviewSeat rs ON rs.review = r " + + "JOIN Seat s ON s = rs.seat " + + "LEFT JOIN ReviewLike rl ON rl.review = r " + ); + + // 해시태그 필터가 있을 경우 - 해시태그 조인 추가 + boolean filterHashTags = condition.getHashTagIds() != null && !condition.getHashTagIds().isEmpty(); + if (filterHashTags) { + jpql.append("JOIN ReviewHashTag rht ON rht.review = r "); + } + + // 공통 WHERE 절 (영화 제목, 내용 검색) + jpql.append("WHERE (r.movieTitle LIKE :keyword OR r.content LIKE :keyword OR r.title LIKE :keyword) "); + + // 상영관 필터 + if (StringUtils.hasText(condition.getAuditoriumId())) { + jpql.append("AND s.auditorium.id = :auditoriumId "); + } + + // 해시태그 필터 + if (filterHashTags) { + jpql.append("AND rht.hashTag.id IN :hashTagIds "); + } + + // 좋아요 수 기준 정렬을 위해 그룹화 + jpql.append("GROUP BY r "); + + // 검색한 모든 해시태그가 포함된 리뷰만 필터링 + if (filterHashTags) { + jpql.append("HAVING COUNT(DISTINCT rht.hashTag.id) = :hashTagCount "); + } + + // 정렬 조건 분기 + switch (condition.getSort()) { + // 인기순 + case POPULAR -> jpql.append("ORDER BY COUNT(rl) DESC, r.createdAt DESC"); + // 평점순 + case RATING -> jpql.append("ORDER BY r.rating DESC"); + // 최신순 + case LATEST -> jpql.append("ORDER BY r.createdAt DESC"); + default -> throw new IllegalArgumentException(ErrorCode.INVALID_SORT_TYPE.getMessage()); + } + + // 동적 JPQL 쿼리 생성 + TypedQuery query = em.createQuery(jpql.toString(), Review.class); + query.setParameter("keyword", "%" + condition.getKeyword() + "%"); + + + // 상영관 파라미터 설정 + if (StringUtils.hasText(condition.getAuditoriumId())) { + query.setParameter("auditoriumId", condition.getAuditoriumId()); + } + + // 해시태그 파라미터 설정 + if (filterHashTags) { + query.setParameter("hashTagIds", condition.getHashTagIds()); + query.setParameter("hashTagCount", (long) condition.getHashTagIds().size()); + } + + // 페이징 처리 + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize() + 1); + + // 쿼리 실행 + List results = query.getResultList(); + boolean hasNext = results.size() > pageable.getPageSize(); + + // Slice 마지막 요소 제거 + if (hasNext) results.remove(results.size() - 1); + + // Slice 형태로 변환 + return new SliceImpl<>(results, pageable, hasNext); + } + +} diff --git a/src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewLikeCount.java b/src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewLikeCount.java index 52830294..7b40dd5c 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewLikeCount.java +++ b/src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewLikeCount.java @@ -1,8 +1,7 @@ package com.seeat.server.domain.review.domain.repository.dto; -public record ReviewLikeCount( - long reviewCount, - long likeCount -) { +public interface ReviewLikeCount{ + Long getReviewCount(); + Long getLikeCount(); } diff --git a/src/main/java/com/seeat/server/domain/review/domain/repository/dto/SeatReviewStats.java b/src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewSeatStats.java similarity index 85% rename from src/main/java/com/seeat/server/domain/review/domain/repository/dto/SeatReviewStats.java rename to src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewSeatStats.java index 13f0961f..b410683f 100644 --- a/src/main/java/com/seeat/server/domain/review/domain/repository/dto/SeatReviewStats.java +++ b/src/main/java/com/seeat/server/domain/review/domain/repository/dto/ReviewSeatStats.java @@ -2,7 +2,7 @@ import com.seeat.server.domain.theater.domain.entity.Seat; -public interface SeatReviewStats { +public interface ReviewSeatStats { Seat getSeat(); Long getReviewCount(); Double getAverageRating(); diff --git a/src/main/java/com/seeat/server/domain/review/presentation/ReviewController.java b/src/main/java/com/seeat/server/domain/review/presentation/ReviewController.java index 6bdb2aea..474d513e 100644 --- a/src/main/java/com/seeat/server/domain/review/presentation/ReviewController.java +++ b/src/main/java/com/seeat/server/domain/review/presentation/ReviewController.java @@ -2,11 +2,13 @@ import com.seeat.server.domain.review.application.dto.request.ReviewSortType; import com.seeat.server.domain.review.application.dto.request.ReviewUpdateRequest; +import com.seeat.server.domain.review.application.dto.response.ReviewSaveResponse; import com.seeat.server.domain.review.application.dto.response.ReviewSeatListResponse; import com.seeat.server.domain.review.application.usecase.ReviewUseCase; import com.seeat.server.domain.review.application.dto.request.ReviewRequest; import com.seeat.server.domain.review.application.dto.response.ReviewDetailResponse; import com.seeat.server.domain.review.application.dto.response.ReviewListResponse; +import com.seeat.server.domain.review.domain.entity.Review; import com.seeat.server.domain.review.presentation.swagger.ReviewControllerSpec; import com.seeat.server.domain.user.domain.entity.User; import com.seeat.server.global.response.ApiResponse; @@ -35,15 +37,18 @@ public class ReviewController implements ReviewControllerSpec { * @return 리뷰 작성 알림 */ @PostMapping() - public ApiResponse createReview( + public ApiResponse createReview( @RequestBody @Valid ReviewRequest request, @AuthenticationPrincipal User user) throws IOException { // 서비스 호출 - reviewService.createReview(request, user.getId()); + Review review = reviewService.createReview(request, user.getId()); + + // DTO 변환 + ReviewSaveResponse response = ReviewSaveResponse.from(review); // 결과 리턴 - return ApiResponse.created(); + return ApiResponse.created(response); } /** diff --git a/src/main/java/com/seeat/server/domain/review/presentation/ReviewSortTypeConverter.java b/src/main/java/com/seeat/server/domain/review/presentation/converter/ReviewSortTypeConverter.java similarity index 93% rename from src/main/java/com/seeat/server/domain/review/presentation/ReviewSortTypeConverter.java rename to src/main/java/com/seeat/server/domain/review/presentation/converter/ReviewSortTypeConverter.java index 3628e830..f6499d46 100644 --- a/src/main/java/com/seeat/server/domain/review/presentation/ReviewSortTypeConverter.java +++ b/src/main/java/com/seeat/server/domain/review/presentation/converter/ReviewSortTypeConverter.java @@ -1,4 +1,4 @@ -package com.seeat.server.domain.review.presentation; +package com.seeat.server.domain.review.presentation.converter; import com.seeat.server.domain.review.application.dto.request.ReviewSortType; import com.seeat.server.global.response.ErrorCode; diff --git a/src/main/java/com/seeat/server/domain/review/presentation/swagger/ReviewControllerSpec.java b/src/main/java/com/seeat/server/domain/review/presentation/swagger/ReviewControllerSpec.java index 30bf41a1..4fb56435 100644 --- a/src/main/java/com/seeat/server/domain/review/presentation/swagger/ReviewControllerSpec.java +++ b/src/main/java/com/seeat/server/domain/review/presentation/swagger/ReviewControllerSpec.java @@ -5,6 +5,7 @@ import com.seeat.server.domain.review.application.dto.request.ReviewUpdateRequest; import com.seeat.server.domain.review.application.dto.response.ReviewDetailResponse; import com.seeat.server.domain.review.application.dto.response.ReviewListResponse; +import com.seeat.server.domain.review.application.dto.response.ReviewSaveResponse; import com.seeat.server.domain.review.application.dto.response.ReviewSeatListResponse; import com.seeat.server.domain.user.domain.entity.User; import com.seeat.server.global.response.ApiResponse; @@ -31,7 +32,7 @@ public interface ReviewControllerSpec { description = "리뷰를 작성합니다." ) @PostMapping() - ApiResponse createReview( + ApiResponse createReview( @RequestBody @Valid ReviewRequest request, @Parameter(hidden = true) @AuthenticationPrincipal User user ) throws IOException; diff --git a/src/main/java/com/seeat/server/domain/search/application/dto/response/ReviewSearchResponse.java b/src/main/java/com/seeat/server/domain/search/application/dto/response/ReviewSearchResponse.java index 895ad481..588052c2 100644 --- a/src/main/java/com/seeat/server/domain/search/application/dto/response/ReviewSearchResponse.java +++ b/src/main/java/com/seeat/server/domain/search/application/dto/response/ReviewSearchResponse.java @@ -1,9 +1,7 @@ package com.seeat.server.domain.search.application.dto.response; -import com.seeat.server.domain.review.domain.entity.HashTag; import com.seeat.server.domain.review.domain.entity.Review; import com.seeat.server.domain.review.domain.entity.ReviewLike; -import com.seeat.server.domain.theater.application.dto.response.TheaterListResponse; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; diff --git a/src/main/java/com/seeat/server/domain/search/application/service/ReviewSearchService.java b/src/main/java/com/seeat/server/domain/search/application/service/ReviewSearchService.java index b53f2351..0c221d1d 100644 --- a/src/main/java/com/seeat/server/domain/search/application/service/ReviewSearchService.java +++ b/src/main/java/com/seeat/server/domain/search/application/service/ReviewSearchService.java @@ -1,6 +1,6 @@ package com.seeat.server.domain.search.application.service; -import com.seeat.server.domain.review.application.usecase.ReviewHashTagUseCase; +import com.seeat.server.domain.hashtag.application.usecase.ReviewHashTagUseCase; import com.seeat.server.domain.review.domain.entity.Review; import com.seeat.server.domain.review.domain.entity.ReviewLike; import com.seeat.server.domain.review.domain.repository.ReviewLikeRepository; diff --git a/src/main/java/com/seeat/server/domain/search/application/service/SearchService.java b/src/main/java/com/seeat/server/domain/search/application/service/SearchService.java index 3f3eb563..87a66625 100644 --- a/src/main/java/com/seeat/server/domain/search/application/service/SearchService.java +++ b/src/main/java/com/seeat/server/domain/search/application/service/SearchService.java @@ -1,10 +1,5 @@ package com.seeat.server.domain.search.application.service; -import com.seeat.server.domain.review.application.usecase.HashTagUseCase; -import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewLike; -import com.seeat.server.domain.review.domain.repository.ReviewLikeRepository; -import com.seeat.server.domain.review.domain.repository.ReviewRepository; import com.seeat.server.domain.search.application.dto.request.ReviewSearchCondition; import com.seeat.server.domain.search.application.dto.response.ReviewSearchResponse; import com.seeat.server.domain.search.application.dto.response.SearchResponse; @@ -12,30 +7,18 @@ import com.seeat.server.domain.search.application.usecase.ReviewSearchUseCase; import com.seeat.server.domain.search.application.usecase.SearchUseCase; import com.seeat.server.domain.search.domain.entity.Search; -import com.seeat.server.domain.search.domain.entity.SortType; import com.seeat.server.domain.search.domain.repository.SearchRepository; import com.seeat.server.domain.user.application.usecase.UserUseCase; import com.seeat.server.domain.user.domain.entity.User; -import com.seeat.server.domain.user.domain.entity.UserSearch; -import com.seeat.server.domain.user.domain.repository.UserSearchRepository; import com.seeat.server.global.response.ErrorCode; import com.seeat.server.global.response.pageable.PageRequest; -import com.seeat.server.global.response.pageable.PageUtil; import com.seeat.server.global.response.pageable.SliceResponse; -import com.seeat.server.global.service.RecentSearchRedisService; -import com.seeat.server.global.util.RedisKeyUtil; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; -import org.springframework.data.redis.core.ListOperations; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.NoSuchElementException; -import java.util.stream.Collectors; @Service @Transactional diff --git a/src/main/java/com/seeat/server/domain/search/domain/entity/SearchSnapshot.java b/src/main/java/com/seeat/server/domain/search/domain/entity/SearchSnapshot.java deleted file mode 100644 index 3ccccc67..00000000 --- a/src/main/java/com/seeat/server/domain/search/domain/entity/SearchSnapshot.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.seeat.server.domain.search.domain.entity; - -public class SearchSnapshot { -} diff --git a/src/main/java/com/seeat/server/domain/search/domain/repository/SearchSnapShotRepository.java b/src/main/java/com/seeat/server/domain/search/domain/repository/SearchSnapShotRepository.java deleted file mode 100644 index e14ac78d..00000000 --- a/src/main/java/com/seeat/server/domain/search/domain/repository/SearchSnapShotRepository.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.seeat.server.domain.search.domain.repository; - -public interface SearchSnapShotRepository { -} diff --git a/src/main/java/com/seeat/server/domain/theater/domain/repository/AuditoriumRepository.java b/src/main/java/com/seeat/server/domain/theater/domain/repository/AuditoriumRepository.java index 48ef8586..b939debc 100644 --- a/src/main/java/com/seeat/server/domain/theater/domain/repository/AuditoriumRepository.java +++ b/src/main/java/com/seeat/server/domain/theater/domain/repository/AuditoriumRepository.java @@ -29,18 +29,24 @@ public interface AuditoriumRepository extends JpaRepository group by a """) Optional findAuditoriumWithRating(@Param("auditoriumId") String auditoriumId); + + @Query(""" SELECT a AS auditorium, - COUNT(r) AS reviewCount, + COUNT(DISTINCT r.id) AS reviewCount, AVG(r.rating) AS avgRating, - (COUNT(r) * 0.3 + AVG(r.rating) * 0.7) AS score + (COUNT(DISTINCT r.id) * 0.3 + AVG(r.rating) * 0.7) AS score FROM Auditorium a - LEFT JOIN Review r ON r.seat.auditorium = a + LEFT JOIN Seat s ON s.auditorium = a + LEFT JOIN ReviewSeat rs ON rs.seat = s + LEFT JOIN Review r ON rs.review = r GROUP BY a.id - ORDER BY COUNT(r) * 0.3 + COALESCE(AVG(r.rating), 0) * 0.7 DESC + ORDER BY (COUNT(DISTINCT r.id) * 0.3 + COALESCE(AVG(r.rating), 0) * 0.7) DESC """) Slice findBestAuditoriums(Pageable pageable); + + Optional findByNameContainingIgnoreCaseAndTheater_Id(String name, String theater_id); } diff --git a/src/main/java/com/seeat/server/domain/theater/presentation/TheaterAuditoriumTypeConverter.java b/src/main/java/com/seeat/server/domain/theater/presentation/converter/TheaterAuditoriumTypeConverter.java similarity index 93% rename from src/main/java/com/seeat/server/domain/theater/presentation/TheaterAuditoriumTypeConverter.java rename to src/main/java/com/seeat/server/domain/theater/presentation/converter/TheaterAuditoriumTypeConverter.java index 172a2fdf..4209b90b 100644 --- a/src/main/java/com/seeat/server/domain/theater/presentation/TheaterAuditoriumTypeConverter.java +++ b/src/main/java/com/seeat/server/domain/theater/presentation/converter/TheaterAuditoriumTypeConverter.java @@ -1,4 +1,4 @@ -package com.seeat.server.domain.theater.presentation; +package com.seeat.server.domain.theater.presentation.converter; import com.seeat.server.domain.theater.domain.entity.AuditoriumType; import com.seeat.server.global.response.ErrorCode; diff --git a/src/main/java/com/seeat/server/domain/user/application/service/UserProfileService.java b/src/main/java/com/seeat/server/domain/user/application/service/UserProfileService.java index bda57081..e65b5fab 100644 --- a/src/main/java/com/seeat/server/domain/user/application/service/UserProfileService.java +++ b/src/main/java/com/seeat/server/domain/user/application/service/UserProfileService.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; +import java.util.Optional; import java.util.stream.Collectors; @Service @@ -56,10 +57,15 @@ public UserInfoResponse getUserInfo(Long userId){ // 상영관 n+1 방지 fetch join, 예외 처리 List auditorium = getAuditoriums(userId); + Long reviewCount = 0L; + Long likeCount = 0L; + // 리뷰, 좋아요 수 가져오기 - ReviewLikeCount count = reviewRepository.findReviewCountAndLikeCountByUserId(userId); - long reviewCount = count.reviewCount(); - long likeCount = count.likeCount(); + Optional count = reviewRepository.findReviewAndLikeCountByUserId(userId); + if (count.isPresent()){ + reviewCount = count.get().getReviewCount(); + likeCount = count.get().getLikeCount(); + } // 경험치 계산 double levelExp = calculateLevelExp(user.getGrade(), reviewCount, likeCount); diff --git a/src/main/java/com/seeat/server/global/response/ApiResponse.java b/src/main/java/com/seeat/server/global/response/ApiResponse.java index a72e3fc1..474bcf4e 100644 --- a/src/main/java/com/seeat/server/global/response/ApiResponse.java +++ b/src/main/java/com/seeat/server/global/response/ApiResponse.java @@ -30,6 +30,12 @@ public static ApiResponse created() { return new ApiResponse<>(HttpStatus.CREATED, true, HttpStatus.CREATED.value(), "성공적으로 생성되었습니다.", null, null); } + // 데이터와 함께 생성 성공 응답 (201 Created) + public static ApiResponse created(T data) { + return new ApiResponse<>(HttpStatus.CREATED, true, HttpStatus.CREATED.value(), "성공적으로 생성되었습니다.", data, null); + } + + // 수정 성공 응답 (204 No Content) public static ApiResponse updated() { return new ApiResponse<>(HttpStatus.NO_CONTENT, true, HttpStatus.NO_CONTENT.value(), "성공적으로 수정되었습니다.", null, null); diff --git a/src/test/java/com/seeat/server/domain/best/application/service/BestContentServiceIntTest.java b/src/test/java/com/seeat/server/domain/best/application/service/BestContentServiceIntTest.java index 50f1a4d4..c656ca53 100644 --- a/src/test/java/com/seeat/server/domain/best/application/service/BestContentServiceIntTest.java +++ b/src/test/java/com/seeat/server/domain/best/application/service/BestContentServiceIntTest.java @@ -6,10 +6,12 @@ import com.seeat.server.domain.review.application.usecase.ReviewLikeUseCase; import com.seeat.server.domain.review.application.usecase.ReviewUseCase; import com.seeat.server.domain.review.domain.HashTagFixtures; -import com.seeat.server.domain.review.domain.entity.HashTag; -import com.seeat.server.domain.review.domain.entity.HashTagType; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.repository.HashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; +import com.seeat.server.domain.review.domain.entity.ReviewSeat; +import com.seeat.server.domain.review.domain.repository.ReviewSeatRepository; import com.seeat.server.domain.theater.domain.AuditoriumFixtures; import com.seeat.server.domain.theater.domain.SeatFixtures; import com.seeat.server.domain.theater.domain.TheaterFixtures; @@ -74,6 +76,10 @@ class BestContentServiceIntTest { @Autowired private SeatRepository seatRepository; + @Autowired + private ReviewSeatRepository reviewSeatRepository; + + /// 기본 데이터 등록 private User user1; private User user2; @@ -130,10 +136,10 @@ public void load_best_reviews() throws Exception { /// 리뷰 저장 후 var request = getReviewRequest(3); - List review = reviewService.createReview(request, user1.getId()); + Review review = reviewService.createReview(request, user1.getId()); /// 유저1이 작성한 리뷰에만 좋아요 구현 - likeService.reviewLike(user2.getId(), review.get(0).getId()); + likeService.reviewLike(user2.getId(), review.getId()); /// 페이징 처리 PageRequest pageRequest = PageRequest.builder() @@ -148,7 +154,7 @@ public void load_best_reviews() throws Exception { //then assertNotNull(response); - Assertions.assertEquals(response.content().get(0).reviewId(), review.get(0).getId()); + Assertions.assertEquals(response.content().get(0).reviewId(), review.getId()); Assertions.assertEquals(response.content().size(), 4); Assertions.assertTrue( response.content().get(2).createdAt().isAfter(response.content().get(1).createdAt()) @@ -298,7 +304,7 @@ class LoadRedisCachingTest { public void load_redis_best_reviews() throws Exception { // given - List review = reviewService.createReview(getReviewRequest(5), user1.getId()); + Review review = reviewService.createReview(getReviewRequest(5), user1.getId()); sut.saveBestContents(); // Redis에 key가 존재해야 함 @@ -311,7 +317,7 @@ public void load_redis_best_reviews() throws Exception { // then assertNotNull(response); assertFalse(response.content().isEmpty()); - assertEquals(review.get(0).getId(), response.content().get(0).reviewId()); + assertEquals(review.getId(), response.content().get(0).reviewId()); } @@ -320,7 +326,7 @@ public void load_redis_best_reviews() throws Exception { public void load_db_best_reviews() throws Exception { // given - List review = reviewService.createReview(getReviewRequest(3), user1.getId()); + Review review = reviewService.createReview(getReviewRequest(3), user1.getId()); /// 1차 레디스 저장X 및 2차로 Redis에서 삭제 (강제로 캐시 없음 상태로 만들기) redisTemplate.delete(BEST_REVIEW_LIST_KEY); @@ -332,7 +338,7 @@ public void load_db_best_reviews() throws Exception { // then assertNotNull(response); assertFalse(response.content().isEmpty()); - assertEquals(review.get(0).getId(), response.content().get(0).reviewId()); + assertEquals(review.getId(), response.content().get(0).reviewId()); } @Test @@ -340,7 +346,7 @@ public void load_db_best_reviews() throws Exception { public void load_redis_best_auditoriums() throws Exception { // given - List review = reviewService.createReview(getReviewRequest(5), user1.getId()); + Review review = reviewService.createReview(getReviewRequest(5), user1.getId()); sut.saveBestContents(); // Redis에 key가 존재해야 함 @@ -353,17 +359,24 @@ public void load_redis_best_auditoriums() throws Exception { // then assertNotNull(response); assertFalse(response.content().isEmpty()); - assertEquals(review.get(0).getSeat().getAuditorium().getId(), response.content().get(0).auditoriumId()); + + // 좌석 정보(다대다 변경점!) + List seats = reviewSeatRepository.findByReview(review); + assertFalse(seats.isEmpty()); + String auditoriumId = seats.get(0).getSeat().getAuditorium().getId(); // 복수 좌석 중 아무거나 1개 + + assertEquals(auditoriumId, response.content().get(0).auditoriumId()); } + @Test @DisplayName("[happy] redis 인기 상영관 목록에 값이 없으면 DB에서 조회") public void load_db_best_auditoriums() throws Exception { // given - List review = reviewService.createReview(getReviewRequest(5), user1.getId()); + Review review = reviewService.createReview(getReviewRequest(5), user1.getId()); - /// 1차 레디스 저장X 및 2차로 Redis에서 삭제 (강제로 캐시 없음 상태로 만들기) + // 1차 레디스 저장X 및 2차로 Redis에서 삭제 (강제로 캐시 없음 상태로 만들기) redisTemplate.delete(BEST_AUDITORIUM_LIST_KEY); // when @@ -373,9 +386,15 @@ public void load_db_best_auditoriums() throws Exception { // then assertNotNull(response); assertFalse(response.content().isEmpty()); - assertEquals(review.get(0).getSeat().getAuditorium().getId(), response.content().get(0).auditoriumId()); + // 다대다 구조 대응: reviewSeatRepository에서 auditoriumId를 String으로 추출 + List seats = reviewSeatRepository.findByReview(review); + assertFalse(seats.isEmpty()); + String expectedAuditoriumId = String.valueOf(seats.get(0).getSeat().getAuditorium().getId()); + + assertEquals(expectedAuditoriumId, response.content().get(0).auditoriumId()); } + } @Nested @DisplayName("기존 데이터의 삭제 트랜잭션 테스트") @@ -386,25 +405,31 @@ class DeleteRedisCachingTest { public void delete_reviews_redis_best_reviews() throws Exception { //given - List review1 = reviewService.createReview(getReviewRequest(5), user1.getId()); - List review2 = reviewService.createReview(getReviewRequest(4), user2.getId()); + Review review1 = reviewService.createReview(getReviewRequest(5), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(4), user2.getId()); + sut.saveBestContents(); + Long review1Id = review1.getId(); + Long review2Id = review2.getId(); /// 기존에 존재하는지 체크 - boolean checked = sut.checkBestContentsByReviewId(review1.get(0).getId()); - Assert.assertTrue(checked); + boolean checked = sut.checkBestContentsByReviewId(review1Id); + assertTrue(checked); - /// 리뷰 삭제 - reviewService.deleteReview(review1.get(0).getId(), user1.getId()); + /// 리뷰 삭제 -> 다시 초기화가 되어야한다. + reviewService.deleteReview(review1Id, user1.getId()); //when /// 인기 리뷰 캐시를 다시 조회 - SliceResponse response = sut.loadBestReviews(PageRequest.builder().page(1).size(4).build()); + SliceResponse response = sut.loadBestReviews(PageRequest.builder() + .page(1) + .size(4) + .build()); //then /// 삭제한 리뷰 ID가 캐시에서 없어야 한다 - assertFalse(sut.checkBestContentsByReviewId(review1.get(0).getId())); - assertEquals(response.content().get(0).reviewId(), review2.get(0).getId()); + assertFalse(sut.checkBestContentsByReviewId(review1Id)); + assertEquals(response.content().get(0).reviewId(), review2Id); } @@ -416,15 +441,15 @@ public void delete_normal_reviews_redis_best_reviews() throws Exception { /// 25개 리뷰 생성 for (int i = 1; i <= 20; i++) { - List review = reviewService.createReview(getReviewRequest(5), user1.getId()); + Review review = reviewService.createReview(getReviewRequest(5), user1.getId()); // 20개는 좋아요 눌러 인기 리뷰로 만들기 - likeService.reviewLike(user2.getId(), review.get(0).getId()); + likeService.reviewLike(user2.getId(), review.getId()); } /// 삭제할 리뷰 저장 - List review21 = reviewService.createReview(getReviewRequest(1), user1.getId()); - Long deleteId = review21.get(0).getId(); + Review review21 = reviewService.createReview(getReviewRequest(1), user1.getId()); + Long deleteId = review21.getId(); /// 인기 리뷰 캐시 저장 sut.saveBestContents(); @@ -445,18 +470,6 @@ public void delete_normal_reviews_redis_best_reviews() throws Exception { .anyMatch(r -> r.reviewId() == deleteId)); } - - @Test - @DisplayName("[happy] 리뷰를 작성한 사용자가 삭제할 경우, redis 인기상영관의 후기 개수와 후기 평점이 변경되는지") - public void delete_reviews_redis_best_auditoriums() throws Exception { - - //given - - //when - - //then - } - } diff --git a/src/test/java/com/seeat/server/domain/bookmark/application/service/BookmarkServiceIntTest.java b/src/test/java/com/seeat/server/domain/bookmark/application/service/BookmarkServiceIntTest.java index 39255513..d0621557 100644 --- a/src/test/java/com/seeat/server/domain/bookmark/application/service/BookmarkServiceIntTest.java +++ b/src/test/java/com/seeat/server/domain/bookmark/application/service/BookmarkServiceIntTest.java @@ -50,38 +50,22 @@ class BookmarkServiceIntTest { @Autowired private UserRepository userRepository; - @Autowired - private AuditoriumRepository auditoriumRepository; - - @Autowired - private TheaterRepository theaterRepository; - - @Autowired - private SeatRepository seatRepository; - /// 기타 의존성 @Autowired private ReviewLikeUseCase likeService; - private Seat seat; private User user; - private Theater theater; - private Auditorium auditorium; @BeforeEach void setUp() { - theater = theaterRepository.save(TheaterFixtures.createTheater()); - auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - seat = seatRepository.save(SeatFixtures.createSeat(auditorium)); user = userRepository.save(UserFixtures.createUser()); } /** * 리뷰를 직접 생성하여 저장하고, 필요한 경우 해시태그 연관관계도 세팅한다. */ - private Review saveReview(User user, Seat seat, String content, int rating) { + private Review saveReview(User user, String content, int rating) { Review review = Review.builder() - .seat(seat) .user(user) .title("title") .content(content) @@ -101,7 +85,7 @@ class SaveBookmark { @DisplayName("[happy] 유저가 존재하는 리뷰를 바탕으로 북마크를 생성합니다") public void saveBookmark_user_happy() { //given - Review review = saveReview(user, seat, "test", 5); + Review review = saveReview(user, "test", 5); //when Bookmark bookmark = sut.createBookmark(review.getId(), user.getId()); @@ -127,7 +111,7 @@ public void createBookmark_review_throw_exception() { @DisplayName("[unhappy] 유저가 없는 경우 예외 체크") public void createBookmark_user_throw_exception() { //given - Review review = saveReview(user, seat, "test", 5); + Review review = saveReview(user, "test", 5); Long nonUserId = 9999L; @@ -146,7 +130,7 @@ class LoadBookmark { @DisplayName("[happy] 추가한 북마크가 존재한다면, 정상적으로 목록 조회") public void loadBookmark_user_happy() { //given - Review review = saveReview(user, seat, "test", 5); + Review review = saveReview(user, "test", 5); sut.createBookmark(review.getId(), user.getId()); @@ -183,7 +167,7 @@ class LoadReviewsByLike { public void happyLoad_Like(){ //given - Review review = saveReview(user, seat, "test", 5); + Review review = saveReview(user, "test", 5); PageRequest pageRequest = PageRequest.builder().page(1).size(10).build(); // 좋아요 추가 @@ -209,7 +193,7 @@ class DeleteBookmark { @DisplayName("[happy] 내가 추가한 북마크라면, 정상적으로 삭제") public void deleteBookmark_user_happy() { //given - Review review = saveReview(user, seat, "delete test", 4); + Review review = saveReview(user, "delete test", 4); Bookmark bookmark = sut.createBookmark(review.getId(), user.getId()); @@ -226,7 +210,7 @@ public void deleteBookmark_user_throw_exception() { //given User otherUser = userRepository.save(UserFixtures.createUser()); - Review review = saveReview(user, seat, "delete test", 4); + Review review = saveReview(user, "delete test", 4); Bookmark bookmark = sut.createBookmark(review.getId(), user.getId()); diff --git a/src/test/java/com/seeat/server/domain/review/application/service/ReviewBestContentMediatorIntTest.java b/src/test/java/com/seeat/server/domain/review/application/service/ReviewBestContentMediatorIntTest.java index 651b3319..aa02f576 100644 --- a/src/test/java/com/seeat/server/domain/review/application/service/ReviewBestContentMediatorIntTest.java +++ b/src/test/java/com/seeat/server/domain/review/application/service/ReviewBestContentMediatorIntTest.java @@ -1,11 +1,15 @@ package com.seeat.server.domain.review.application.service; import com.seeat.server.domain.best.application.dto.response.BestReviewListResponse; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; +import com.seeat.server.domain.review.application.dto.request.ReviewRequest; import com.seeat.server.domain.review.application.usecase.ReviewBestContentMediatorUseCase; import com.seeat.server.domain.review.application.usecase.ReviewLikeUseCase; +import com.seeat.server.domain.review.domain.HashTagFixtures; import com.seeat.server.domain.review.domain.ReviewFixtures; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.repository.HashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; import com.seeat.server.domain.review.domain.repository.ReviewRepository; import com.seeat.server.domain.theater.domain.AuditoriumFixtures; import com.seeat.server.domain.theater.domain.SeatFixtures; @@ -66,11 +70,19 @@ class ReviewBestContentMediatorIntTest { @Autowired private ReviewLikeUseCase likeService; + @Autowired + private ReviewService reviewService; + + private Seat seat1; private Seat seat2; private User user1; + private User user2; private Theater theater; private Auditorium auditorium; + private HashTag hashTag1; + private HashTag hashTag2; + private HashTag hashTag3; /** * 각 테스트 실행 전 공통으로 필요한 영화관, 상영관, 좌석, 유저 데이터를 준비합니다. @@ -82,6 +94,11 @@ void setUp() throws IOException { seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); seat2 = seatRepository.save(SeatFixtures.createSeat2(auditorium)); user1 = userRepository.save(UserFixtures.createUser()); + user2 = userRepository.save(UserFixtures.createUser()); + hashTag1 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.SOUND, "음향이 좋아요")); + hashTag2 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.COMPANION, "혼자 관람했어요")); + hashTag3 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.ENVIRONMENT, "좌석이 넓어요")); + } @Nested @@ -90,15 +107,19 @@ class LoadReviewsPopular { @Test @DisplayName("[happy] 인기순정렬_기본케이스") - public void loadPopular() { + public void loadPopular() throws IOException { //given PageRequest pageRequest = PageRequest.builder().page(1).size(8).build(); - Review review1 = repository.save(ReviewFixtures.createReview(user1, seat1)); - Review review2 = repository.save(ReviewFixtures.createReview(user1, seat2)); + + var request1 = getReviewRequest(List.of(seat1, seat2), 1); + Review review1 = reviewService.createReview(request1, user1.getId()); + + var request2 = getReviewRequest(List.of(seat1, seat2), 5); + Review review2 = reviewService.createReview(request1, user2.getId()); // 좋아요 추가 - likeService.reviewLike(user1.getId(), review1.getId()); + likeService.reviewLike(user2.getId(), review1.getId()); //when SliceResponse response = sut.getBestReviews(pageRequest); @@ -118,11 +139,15 @@ public void loadPopular() { @Test @DisplayName("[happy] 좋아요 수가 같은 경우 최신순 정렬") - void loadPopular_same_new() { + void loadPopular_same_new() throws IOException { // given PageRequest pageRequest = PageRequest.builder().page(1).size(10).build(); - Review older = repository.save(ReviewFixtures.createReview(user1, seat1)); // 먼저 저장, 좋아요 1개 - Review newer = repository.save(ReviewFixtures.createReview(user1, seat2)); // 나중 저장, 좋아요 1개 + var request1 = getReviewRequest(List.of(seat1, seat2), 1,"older"); + var request2 = getReviewRequest(List.of(seat1, seat2), 5, "newer"); + + Review older = reviewService.createReview(request1, user1.getId()); + Review newer = reviewService.createReview(request1, user2.getId()); + likeService.reviewLike(user1.getId(), older.getId()); likeService.reviewLike(user1.getId(), newer.getId()); @@ -139,11 +164,15 @@ void loadPopular_same_new() { @Test @DisplayName("[happy] 여러 유저가 복수 개 리뷰에 좋아요를 누른 경우 합산 집계 및 순위정렬의 정확성") - void multiple_user_like() { + void multiple_user_like() throws IOException { // given PageRequest pageRequest = PageRequest.builder().page(1).size(10).build(); - Review revA = repository.save(ReviewFixtures.createReview(user1, seat1)); - Review revB = repository.save(ReviewFixtures.createReview(user1, seat2)); + var request1 = getReviewRequest(List.of(seat1, seat2), 1); + var request2 = getReviewRequest(List.of(seat1, seat2), 5); + + Review revA = reviewService.createReview(request1, user1.getId()); + Review revB = reviewService.createReview(request1, user2.getId()); + User user2 = userRepository.save(UserFixtures.createUser()); User user3 = userRepository.save(UserFixtures.createUser()); @@ -183,4 +212,41 @@ void loadPopular_empty() { } + + private ReviewRequest getReviewRequest(List seats, double rating) { + + /// 좌석 번호 + List seatIds = seats.stream() + .map(Seat::getId) + .toList(); + + return ReviewRequest.builder() + .seatIds(seatIds) + .title("review title") + .content("test1") + .movieTitle("ReviewTestTitle1") + .imageUrls(null) + .rating(rating) + .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) + .build(); + } + + private ReviewRequest getReviewRequest(List seats, double rating, String content) { + + /// 좌석 번호 + List seatIds = seats.stream() + .map(Seat::getId) + .toList(); + + return ReviewRequest.builder() + .seatIds(seatIds) + .title("review title") + .content(content) + .movieTitle("ReviewTestTitle1") + .imageUrls(null) + .rating(rating) + .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) + .build(); + } + } diff --git a/src/test/java/com/seeat/server/domain/review/application/service/ReviewHashTagServiceIntTest.java b/src/test/java/com/seeat/server/domain/review/application/service/ReviewHashTagServiceIntTest.java index 1ded9e1d..21bcbed1 100644 --- a/src/test/java/com/seeat/server/domain/review/application/service/ReviewHashTagServiceIntTest.java +++ b/src/test/java/com/seeat/server/domain/review/application/service/ReviewHashTagServiceIntTest.java @@ -1,15 +1,16 @@ package com.seeat.server.domain.review.application.service; -import com.seeat.server.domain.review.application.dto.response.AuditoriumHashTagResponse; -import com.seeat.server.domain.review.application.usecase.ReviewHashTagUseCase; +import com.seeat.server.domain.hashtag.application.dto.response.AuditoriumHashTagResponse; +import com.seeat.server.domain.hashtag.application.usecase.ReviewHashTagUseCase; +import com.seeat.server.domain.review.application.dto.request.ReviewRequest; import com.seeat.server.domain.review.domain.HashTagFixtures; import com.seeat.server.domain.review.domain.ReviewFixtures; -import com.seeat.server.domain.review.domain.entity.HashTag; -import com.seeat.server.domain.review.domain.entity.HashTagType; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; -import com.seeat.server.domain.review.domain.repository.HashTagRepository; -import com.seeat.server.domain.review.domain.repository.ReviewHashTagRepository; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.ReviewHashTagRepository; import com.seeat.server.domain.review.domain.repository.ReviewRepository; import com.seeat.server.domain.theater.domain.AuditoriumFixtures; import com.seeat.server.domain.theater.domain.SeatFixtures; @@ -46,6 +47,9 @@ class ReviewHashTagServiceIntTest { @Autowired private ReviewHashTagRepository repository; + @Autowired + private ReviewService reviewService; + /// 기타 의존성 @Autowired private UserRepository userRepository; @@ -84,7 +88,7 @@ void setUp() { auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); seat = seatRepository.save(SeatFixtures.createSeat(auditorium)); user = userRepository.save(UserFixtures.createUser()); - review = reviewRepository.save(ReviewFixtures.createReview(user, seat)); + review = reviewRepository.save(ReviewFixtures.createReview(user)); hashTag1 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.SOUND, "음향이 좋아요")); hashTag2 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.SOUND, "실감나는 음향이에요")); hashTag3 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.ENVIRONMENT, "좌석이 넓어요")); @@ -102,7 +106,7 @@ class CreateReviewHashTag { public void create_happy() throws Exception { //given - Review review = reviewRepository.save(ReviewFixtures.createReview(user, seat, 1)); + Review review = reviewRepository.save(ReviewFixtures.createReview(user, 1)); //when /// 저장 대상 해시태그 구성 @@ -135,12 +139,8 @@ class FindReviewHashTag { public void find_happy() throws Exception { // given - Review saved1 = reviewRepository.save(ReviewFixtures.createReview(user, seat, 1)); - Review saved2 = reviewRepository.save(ReviewFixtures.createReview(user, seat, 2)); - - /// 저장 대상 해시태그 구성 - sut.createReviewHashTag(saved1, List.of(hashTag1.getId(), hashTag3.getId(), hashTag5.getId())); - sut.createReviewHashTag(saved2, List.of(hashTag2.getId(), hashTag4.getId(), hashTag5.getId())); + Review review1 = reviewService.createReview(getReviewRequest(seat, List.of(hashTag1.getId(), hashTag3.getId(), hashTag5.getId())), user.getId()); + Review review2 = reviewService.createReview(getReviewRequest(seat, List.of(hashTag2.getId(), hashTag4.getId(), hashTag5.getId())), user.getId()); // when List result = sut.loadReviewHashTagsByAuditoriumId(auditorium.getId()); @@ -162,6 +162,18 @@ public void find_happy() throws Exception { } } + private ReviewRequest getReviewRequest(Seat seat,List hashTags) { + return ReviewRequest.builder() + .seatIds(List.of(seat.getId())) + .title("review title") + .content("test1") + .movieTitle("ReviewTestTitle1") + .imageUrls(null) + .rating(5) + .hashtags(hashTags) + .build(); + } + } diff --git a/src/test/java/com/seeat/server/domain/review/application/service/ReviewLikeServiceIntTest.java b/src/test/java/com/seeat/server/domain/review/application/service/ReviewLikeServiceIntTest.java index d65bf86f..9c0ae4d0 100644 --- a/src/test/java/com/seeat/server/domain/review/application/service/ReviewLikeServiceIntTest.java +++ b/src/test/java/com/seeat/server/domain/review/application/service/ReviewLikeServiceIntTest.java @@ -54,19 +54,13 @@ class ReviewLikeServiceIntTest { @Autowired private ReviewRepository reviewRepository; - private Seat seat; private User user; - private Theater theater; - private Auditorium auditorium; private Review review; @BeforeEach() void setUp() { - theater = theaterRepository.save(TheaterFixtures.createTheater()); - auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - seat = seatRepository.save(SeatFixtures.createSeat(auditorium)); user = userRepository.save(UserFixtures.createUser()); - review = reviewRepository.save(ReviewFixtures.createReview(user, seat)); + review = reviewRepository.save(ReviewFixtures.createReview(user)); } @Nested diff --git a/src/test/java/com/seeat/server/domain/review/application/service/ReviewServiceIntTest.java b/src/test/java/com/seeat/server/domain/review/application/service/ReviewServiceIntTest.java index 558b01a0..50607308 100644 --- a/src/test/java/com/seeat/server/domain/review/application/service/ReviewServiceIntTest.java +++ b/src/test/java/com/seeat/server/domain/review/application/service/ReviewServiceIntTest.java @@ -1,5 +1,8 @@ package com.seeat.server.domain.review.application.service; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; import com.seeat.server.domain.image.domain.entity.ReviewImage; import com.seeat.server.domain.review.application.dto.request.ReviewSortType; import com.seeat.server.domain.review.application.dto.request.ReviewUpdateRequest; @@ -9,13 +12,14 @@ import com.seeat.server.domain.review.domain.HashTagFixtures; import com.seeat.server.domain.review.domain.ReviewFixtures; import com.seeat.server.domain.review.domain.entity.*; -import com.seeat.server.domain.review.domain.repository.HashTagRepository; -import com.seeat.server.domain.review.domain.repository.ReviewHashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.ReviewHashTagRepository; import com.seeat.server.domain.image.domain.repository.ReviewImageRepository; import com.seeat.server.domain.review.domain.repository.ReviewRepository; import com.seeat.server.domain.review.application.dto.request.ReviewRequest; import com.seeat.server.domain.review.application.dto.response.ReviewDetailResponse; import com.seeat.server.domain.review.application.dto.response.ReviewListResponse; +import com.seeat.server.domain.review.domain.repository.ReviewSeatRepository; import com.seeat.server.domain.theater.domain.AuditoriumFixtures; import com.seeat.server.domain.theater.domain.SeatFixtures; import com.seeat.server.domain.theater.domain.TheaterFixtures; @@ -35,18 +39,17 @@ import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; import java.io.IOException; -import java.io.InputStream; import java.util.List; import java.util.NoSuchElementException; +import java.util.stream.Collectors; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** @@ -83,6 +86,9 @@ class ReviewServiceIntTest { @Autowired private ReviewHashTagRepository reviewHashTagRepository; + @Autowired + private ReviewSeatRepository reviewSeatRepository; + /// 좋아요 의존성 추가 @Autowired private ReviewLikeUseCase likeService; @@ -129,15 +135,7 @@ class CreateReview { @DisplayName("[happy] 이미지 없이 로그인한 유저 리뷰 정상 생성") void createReviewByUser_happy() throws IOException { //given - var request = ReviewRequest.builder() - .seatIds(List.of(seat1.getId())) - .title("review title") - .content("test") - .movieTitle("ReviewTestTitle") - .imageUrls(null) - .rating(5) - .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) - .build(); + var request = getReviewRequest(List.of(seat1, seat2), 5); //when sut.createReview(request, user1.getId()); @@ -146,15 +144,25 @@ void createReviewByUser_happy() throws IOException { /// 리뷰 체크 List reviews = repository.findAll(); - // 1. 개수 검증 + // 개수 검증 Assertions.assertThat(reviews).hasSize(1); - // 2. 리뷰와 정보들이 일치하는지 검증 + // 리뷰와 정보들이 일치하는지 검증 Review review = reviews.get(0); + Assertions.assertThat(review.getContent()).isEqualTo(request.getContent()); Assertions.assertThat(review.getRating()).isEqualTo(request.getRating()); - Assertions.assertThat(review.getSeat().getId()).isEqualTo(seat1.getId()); - Assertions.assertThat(review.getSeat().getAuditorium().getTheater().getName()).isEqualTo("Test Theater"); + + /// 좌석이 실제로 저장되는거지 + List seats = reviewSeatRepository.findByReview(review); + List seatIds = seats.stream() + .map(seat -> seat.getSeat().getId()) + .toList(); + + /// 포함하는지 체크 + Assertions.assertThat(seatIds).hasSameElementsAs(List.of(seat1.getId(), seat2.getId())); + + Assertions.assertThat(seats.get(0).getSeat().getAuditorium().getTheater().getName()).isEqualTo("Test Theater"); /// 해시태그 체크 List reviewHashTags = reviewHashTagRepository.findByReview(review); @@ -173,43 +181,44 @@ void createReviewByUser_happy() throws IOException { @DisplayName("[happy] 이미지 없이 로그인한 유저 리뷰 좌석 2개에 동시 생성 정상 생성") void createReviewByUser_happy_multiple_seat() throws IOException { // given - var request = ReviewRequest.builder() - .seatIds(List.of(seat1.getId(), seat2.getId())) - .title("review title") - .content("test") - .movieTitle("ReviewTestTitle") - .imageUrls(null) - .rating(5) - .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) - .build(); + var request = getReviewRequest(List.of(seat1, seat2), 5); // when - sut.createReview(request, user1.getId()); + Review review1 = sut.createReview(request, user1.getId()); // then - List reviews = repository.findAll(); - Assertions.assertThat(reviews).hasSize(2); + Review savedReview = repository.findById(review1.getId()).get(); + + Assertions.assertThat(savedReview).isNotNull(); + + // 좌석 조회(다대다) + List seats = reviewSeatRepository.findByReview(savedReview); + List seatIds = seats.stream() + .map(rs -> rs.getSeat().getId()) + .toList(); - for (Review review : reviews) { - Assertions.assertThat(review.getContent()).isEqualTo(request.getContent()); - Assertions.assertThat(review.getRating()).isEqualTo(request.getRating()); - Assertions.assertThat(List.of(seat1.getId(), seat2.getId())).contains(review.getSeat().getId()); - Assertions.assertThat(review.getSeat().getAuditorium().getTheater().getName()) - .isEqualTo("Test Theater"); + Assertions.assertThat(savedReview.getContent()).isEqualTo(request.getContent()); + Assertions.assertThat(savedReview.getRating()).isEqualTo(request.getRating()); + Assertions.assertThat(seatIds).hasSameElementsAs(List.of(seat1.getId(), seat2.getId())); - List reviewHashTags = reviewHashTagRepository.findByReview(review); - Assertions.assertThat(reviewHashTags).hasSize(3); + // 좌석 중 하나의 극장명으로 검증 (보통 동일 극장) + String theaterName = seats.get(0).getSeat().getAuditorium().getTheater().getName(); + Assertions.assertThat(theaterName).isEqualTo("Test Theater"); - List actualHashTagIds = reviewHashTags.stream() - .map(rht -> rht.getHashTag().getId()) - .toList(); + // 해시태그 체크 + List reviewHashTags = reviewHashTagRepository.findByReview(savedReview); + Assertions.assertThat(reviewHashTags).hasSize(3); - Assertions.assertThat(actualHashTagIds) - .containsExactlyInAnyOrderElementsOf(request.getHashtags()); - } + List actualHashTagIds = reviewHashTags.stream() + .map(rht -> rht.getHashTag().getId()) + .toList(); + + Assertions.assertThat(actualHashTagIds) + .containsExactlyInAnyOrderElementsOf(request.getHashtags()); } + /** * 정상적인 유저가 이미지 한 장을 통해 리뷰를 생성하는 경우를 검증합니다. */ @@ -231,7 +240,7 @@ void createReviewByUser_happy_with_imageUrls() throws IOException { sut.createReview(request, user1.getId()); //then - /// 리뷰 체크 + // 리뷰 체크 List reviews = repository.findAll(); // 1. 개수 검증 @@ -241,15 +250,17 @@ void createReviewByUser_happy_with_imageUrls() throws IOException { Review review = reviews.get(0); Assertions.assertThat(review.getContent()).isEqualTo(request.getContent()); Assertions.assertThat(review.getRating()).isEqualTo(request.getRating()); - Assertions.assertThat(review.getSeat().getId()).isEqualTo(seat1.getId()); - Assertions.assertThat(review.getSeat().getAuditorium().getTheater().getName()).isEqualTo("Test Theater"); - /// 해시태그 체크 + // 좌석 다대다 검증 + List reviewSeats = reviewSeatRepository.findByReview(review); + Assertions.assertThat(reviewSeats).hasSize(1); + Assertions.assertThat(reviewSeats.get(0).getSeat().getId()).isEqualTo(seat1.getId()); + Assertions.assertThat(reviewSeats.get(0).getSeat().getAuditorium().getTheater().getName()).isEqualTo("Test Theater"); + + // 해시태그 체크 List reviewHashTags = reviewHashTagRepository.findByReview(review); - // 1. 개수 검증 Assertions.assertThat(reviewHashTags).hasSize(3); - // 2. 실제 연결된 해시태그 ID와 요청한 해시태그 ID가 일치하는지 검증 List actualHashTagIds = reviewHashTags.stream() .map(rht -> rht.getHashTag().getId()) .toList(); @@ -258,9 +269,10 @@ void createReviewByUser_happy_with_imageUrls() throws IOException { // 이미지 검증 String thumbnailUrl = review.getThumbnailUrl(); - Assertions.assertThat(thumbnailUrl.contains("file1.jpg")); + Assertions.assertThat(thumbnailUrl).contains("file1.jpg"); } + /** * 정상적인 유저가 이미지 3 장을 통해 리뷰를 생성하는 경우를 검증합니다. */ @@ -282,7 +294,7 @@ void createReviewByUser_happy_with_imageUrls_3() throws IOException { sut.createReview(request, user1.getId()); //then - /// 리뷰 체크 + // 리뷰 체크 List reviews = repository.findAll(); // 1. 개수 검증 @@ -292,26 +304,26 @@ void createReviewByUser_happy_with_imageUrls_3() throws IOException { Review review = reviews.get(0); Assertions.assertThat(review.getContent()).isEqualTo(request.getContent()); Assertions.assertThat(review.getRating()).isEqualTo(request.getRating()); - Assertions.assertThat(review.getSeat().getId()).isEqualTo(seat1.getId()); - Assertions.assertThat(review.getSeat().getAuditorium().getTheater().getName()).isEqualTo("Test Theater"); - /// 해시태그 체크 + // 좌석·극장 정보의 다대다 검증 + List reviewSeats = reviewSeatRepository.findByReview(review); + Assertions.assertThat(reviewSeats).hasSize(1); + Assertions.assertThat(reviewSeats.get(0).getSeat().getId()).isEqualTo(seat1.getId()); + Assertions.assertThat(reviewSeats.get(0).getSeat().getAuditorium().getTheater().getName()).isEqualTo("Test Theater"); + + // 해시태그 체크 List reviewHashTags = reviewHashTagRepository.findByReview(review); - // 1. 개수 검증 Assertions.assertThat(reviewHashTags).hasSize(3); - - // 2. 실제 연결된 해시태그 ID와 요청한 해시태그 ID가 일치하는지 검증 List actualHashTagIds = reviewHashTags.stream() .map(rht -> rht.getHashTag().getId()) .toList(); Assertions.assertThat(actualHashTagIds) .containsExactlyInAnyOrderElementsOf(request.getHashtags()); - //3. 미지 검증 - List reviewImages = imageRepository.findByReview(review); + // 이미지 검증 + List reviewImages = imageRepository.findByReview_Id(review.getId()); Assertions.assertThat(reviewImages).hasSize(3); - // 파일명 포함 여부 확인 List storedFileNames = reviewImages.stream() .map(ReviewImage::getImageUrl) .toList(); @@ -323,9 +335,9 @@ void createReviewByUser_happy_with_imageUrls_3() throws IOException { // 썸네일 검사 assertThat(review.getThumbnailUrl()).isEqualTo(storedFileNames.stream().findFirst().get()); - } + /** * 정상적인 유저가 이미지 6 장을 통해 에러가 발생합니다.. */ @@ -409,18 +421,29 @@ class LoadReview { */ @Test @DisplayName("[happy] 리뷰 상세 조회") - void loadReview_happy() { + void loadReview_happy() throws IOException { //given - Review review = repository.save(ReviewFixtures.createReview(user1, seat1)); + var request = getReviewRequest(List.of(seat1, seat2), 5); + + Review review1 = sut.createReview(request, user1.getId()); //when - ReviewDetailResponse response = sut.loadReview(review.getId()); + ReviewDetailResponse response = sut.loadReview(review1.getId()); //then Assertions.assertThat(response).isNotNull(); - Assertions.assertThat(response.content()).isEqualTo(review.getContent()); - Assertions.assertThat(response.rating()).isEqualTo(review.getRating()); - Assertions.assertThat(response.seatInfo().get(0)).isEqualTo(ReviewSeatInfoResponse.from(seat1)); + Assertions.assertThat(response.title()).isEqualTo(review1.getTitle()); + Assertions.assertThat(response.content()).isEqualTo(review1.getContent()); + Assertions.assertThat(response.rating()).isEqualTo(review1.getRating()); + List infoResponses = response.seatInfo(); + + List seatIds = infoResponses.stream() + .map(ReviewSeatInfoResponse::seatId) + .toList(); + + /// 포함하는지 체크 + Assertions.assertThat(seatIds).hasSameElementsAs(List.of(seat1.getId(), seat2.getId())); + } /** @@ -430,7 +453,7 @@ void loadReview_happy() { @DisplayName("[unhappy] 존재하지 않는 리뷰 조회") void loadReview_unhappy_not_id() { //given - Review fakeReview = ReviewFixtures.fakeReview(user1, seat1); + Review fakeReview = ReviewFixtures.fakeReview(user1); // when & then Assertions.assertThatThrownBy(() -> sut.loadReview(fakeReview.getId())) @@ -443,13 +466,17 @@ void loadReview_unhappy_not_id() { */ @Test @DisplayName("[happy] 좌석 기반_리뷰 목록 조회") - void loadReviews_happy_seat() { + void loadReviews_happy_seat() throws IOException { //given String firstReview = "첫번째 리뷰입니다."; String secondReview = "두번째 리뷰입니다."; + //given + var request1 = getReviewRequest(List.of(seat1, seat2), 5, firstReview); + var request2 = getReviewRequest(List.of(seat1, seat2), 5, secondReview); + + Review review1 = sut.createReview(request1, user1.getId()); + Review review2 = sut.createReview(request2, user2.getId()); - Review review1 = repository.save(ReviewFixtures.createReview(user1, seat1, 5, firstReview)); - Review review2 = repository.save(ReviewFixtures.createReview(user1, seat1, 3, secondReview)); var pageRequest = PageRequest.builder().page(1).size(10).build(); //when @@ -457,6 +484,7 @@ void loadReviews_happy_seat() { //then List responses = response.content(); + /// 싱글톤 리스트이기에 어차피 1개 ReviewSeatListResponse seatListResponse = responses.get(0); @@ -471,17 +499,23 @@ void loadReviews_happy_seat() { @Test @DisplayName("[happy] 상영관 기반_리뷰 목록 조회") - void loadReviews_happy_theater() { + void loadReviews_happy_theater() throws IOException { // given String firstReview = "첫번째 리뷰입니다."; String secondReview = "두번째 리뷰입니다."; String thirdReview = "세번째 리뷰입니다."; String fourthReview = "네번째 리뷰입니다."; - Review review1 = repository.save(ReviewFixtures.createReview(user1, seat1, 1, firstReview)); - Review review2 = repository.save(ReviewFixtures.createReview(user1, seat1, 2, secondReview)); - Review review3 = repository.save(ReviewFixtures.createReview(user1, seat1, 3, thirdReview)); - Review review4 = repository.save(ReviewFixtures.createReview(user1, seat1, 4, fourthReview)); + var request1 = getReviewRequest(List.of(seat1), 1, firstReview); + var request2 = getReviewRequest(List.of(seat1), 2, secondReview); + var request3 = getReviewRequest(List.of(seat1, seat2), 3, thirdReview); + var request4 = getReviewRequest(List.of(seat2), 4, fourthReview); + + Review review1 = sut.createReview(request1, user1.getId()); + Review review2 = sut.createReview(request2, user2.getId()); + Review review3 = sut.createReview(request3, user1.getId()); + Review review4 = sut.createReview(request4, user2.getId()); + var pageRequest = PageRequest.builder().page(1).size(10).build(); // when @@ -508,24 +542,16 @@ class LoadReviewsByLike { public void happyLoad_Detail() throws IOException { //given - var request = ReviewRequest.builder() - .seatIds(List.of(seat1.getId())) - .title("review title") - .content("test") - .movieTitle("ReviewTestTitle") - .imageUrls(null) - .rating(5) - .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) - .build(); + var request = getReviewRequest(List.of(seat1), 1); //when - List sutReview = sut.createReview(request, user1.getId()); + Review review = sut.createReview(request, user1.getId()); - likeService.reviewLike(user1.getId(), sutReview.get(0).getId()); - likeService.reviewLike(user2.getId(), sutReview.get(0).getId()); + likeService.reviewLike(user1.getId(), review.getId()); + likeService.reviewLike(user2.getId(), review.getId()); //when - ReviewDetailResponse response = sut.loadReview(sutReview.get(0).getId()); + ReviewDetailResponse response = sut.loadReview(review.getId()); //then Assertions.assertThat(response).isNotNull(); @@ -560,12 +586,12 @@ public void happyLoad_List() throws IOException { .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) .build(); - List sutReview1 = sut.createReview(request1, user1.getId()); - List sutReview2 = sut.createReview(request2, user1.getId()); + Review review1 = sut.createReview(request1, user1.getId()); + Review review2 = sut.createReview(request2, user1.getId()); // 1에게만 좋아요 누르기 - likeService.reviewLike(user1.getId(), sutReview1.get(0).getId()); - likeService.reviewLike(user2.getId(), sutReview1.get(0).getId()); + likeService.reviewLike(user1.getId(), review1.getId()); + likeService.reviewLike(user2.getId(), review1.getId()); // when SliceResponse response = sut.loadReviewsByAuditoriumId(auditorium.getId(), pageRequest, ReviewSortType.RATING_DESC); @@ -593,8 +619,7 @@ void update_happy() throws IOException { var request = getReviewRequest(seat1, 4); /// 생성 - List reviews = sut.createReview(request, user1.getId()); - Review review = reviews.get(0); + Review review = sut.createReview(request, user1.getId()); /// 수정용 요청 var newRequest = ReviewUpdateRequest. @@ -616,7 +641,6 @@ void update_happy() throws IOException { /// 기존내용 변경 여부 Assertions.assertThat(savedReview.getMovieTitle()).isEqualTo(request.getMovieTitle()); - Assertions.assertThat(savedReview.getSeat()).isEqualTo(seat1); } @@ -629,8 +653,7 @@ void update_happy_imageUrls() throws IOException { var request = getReviewRequest(seat1, 4); /// 생성 - List reviews = sut.createReview(request, user1.getId()); - Review review = reviews.get(0); + Review review = sut.createReview(request, user1.getId()); /// 수정용 요청 var newRequest = ReviewUpdateRequest. @@ -660,7 +683,6 @@ void update_happy_imageUrls() throws IOException { /// 기존내용 변경 여부 Assertions.assertThat(savedReview.getMovieTitle()).isEqualTo(request.getMovieTitle()); - Assertions.assertThat(savedReview.getSeat()).isEqualTo(seat1); } @@ -674,8 +696,7 @@ void update_throws_not_users() throws IOException { var request = getReviewRequest(seat1, 4); /// 생성 - List reviews = sut.createReview(request, user1.getId()); - Review review = reviews.get(0); + Review review = sut.createReview(request, user1.getId()); /// 수정용 요청 var newRequest = ReviewUpdateRequest. @@ -700,8 +721,7 @@ void update_throws_not_my_reviews() throws IOException { var request = getReviewRequest(seat1, 4); /// 생성 - List reviews = sut.createReview(request, user1.getId()); - Review review = reviews.get(0); + Review review = sut.createReview(request, user1.getId()); /// 수정용 요청 var newRequest = ReviewUpdateRequest. @@ -725,26 +745,20 @@ class DeleteReview { @DisplayName("[happy] 리뷰의 작성자는 정상적으로 삭제할 수 있습니다.") void happyDelete() throws IOException { - /// 요청 - var request = getReviewRequest(seat1, 4); - /// 생성 - List reviews = sut.createReview(request, user1.getId()); - Review review = reviews.get(0); + Review review = repository.save(ReviewFixtures.createReview(user1)); + Long reviewId = review.getId(); // when sut.deleteReview(review.getId(), user1.getId()); // then - /// DB에 존재하는지 체크 - assertTrue(repository.findById(review.getId()).isEmpty()); - /// 해시태그 존재하는지 체크 - List hashTags = reviewHashTagRepository.findByReview_Id(review.getId()); + List hashTags = reviewHashTagRepository.findByReview_Id(reviewId); Assertions.assertThat(hashTags).isEmpty(); /// 이미지 존재하는지 체크 - List images = imageRepository.findByReview(review); + List images = imageRepository.findByReview_Id(reviewId); Assertions.assertThat(images).isEmpty(); } @@ -758,8 +772,7 @@ void delete_throws_not_users() throws IOException { var request = getReviewRequest(seat1, 4); /// 생성 - List reviews = sut.createReview(request, user1.getId()); - Review review = reviews.get(0); + Review review = sut.createReview(request, user1.getId()); // when & then assertThatThrownBy(() -> sut.deleteReview(review.getId(), attackedUserId)) @@ -778,8 +791,7 @@ void delete_throws_not_my_reviews() throws IOException { var request = getReviewRequest(seat1, 4); /// 생성 - List reviews = sut.createReview(request, user1.getId()); - Review review = reviews.get(0); + Review review = sut.createReview(request, user1.getId()); // when & then assertThatThrownBy(() -> sut.deleteReview(review.getId(), attackedUserId)) @@ -795,11 +807,12 @@ class LoadReviewsBySeatIdSortTest { @Test @DisplayName("[happy] 최신순(LATEST) 정렬") - void loadReviewsBySeat_LATEST() { + void loadReviewsBySeat_LATEST() throws IOException { //given - Review r1 = repository.save(ReviewFixtures.createReview(user1, seat1, 1, "first review")); // 나중에 저장 - Review r2 = repository.save(ReviewFixtures.createReview(user1, seat1, 4, "second review")); - Review r3 = repository.save(ReviewFixtures.createReview(user1, seat1, 5, "third review")); // 제일 먼저 저장 + sut.createReview(getReviewRequest(seat1, 1, "first review"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 4, "second review"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 5, "third review"), user1.getId()); + var pageRequest = PageRequest.builder().page(1).size(10).build(); //when @@ -814,11 +827,12 @@ void loadReviewsBySeat_LATEST() { @Test @DisplayName("[happy] 평점 내림차순(RATING_DESC) 정렬") - void loadReviewsBySeat_RATING_DESC() { + void loadReviewsBySeat_RATING_DESC() throws IOException { //given - repository.save(ReviewFixtures.createReview(user1, seat1, 3, "review 3")); - repository.save(ReviewFixtures.createReview(user1, seat1, 5, "review 5")); - repository.save(ReviewFixtures.createReview(user1, seat1, 1, "review 1")); + sut.createReview(getReviewRequest(seat1, 3, "review 3"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 5, "review 5"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 1, "review 1"), user1.getId()); + var pageRequest = PageRequest.builder().page(1).size(10).build(); //when @@ -833,11 +847,11 @@ void loadReviewsBySeat_RATING_DESC() { @Test @DisplayName("[happy] 평점 오름차순(RATING_ASC) 정렬") - void loadReviewsBySeat_RATING_ASC() { + void loadReviewsBySeat_RATING_ASC() throws IOException { //given - repository.save(ReviewFixtures.createReview(user1, seat1, 2, "review 2")); - repository.save(ReviewFixtures.createReview(user1, seat1, 4, "review 4")); - repository.save(ReviewFixtures.createReview(user1, seat1, 3, "review 3")); + sut.createReview(getReviewRequest(seat1, 2, "review 2"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 4, "review 4"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 3, "review 3"), user1.getId()); var pageRequest = PageRequest.builder().page(1).size(10).build(); //when @@ -852,18 +866,18 @@ void loadReviewsBySeat_RATING_ASC() { @Test @DisplayName("[happy] 좋아요 내림차순(LIKES) 정렬") - void loadReviewsBySeat_LIKES() { + void loadReviewsBySeat_LIKES() throws IOException { //given - Review r1 = repository.save(ReviewFixtures.createReview(user1, seat1, 5, "review A")); // 좋아요 1 - Review r2 = repository.save(ReviewFixtures.createReview(user1, seat1, 4, "review B")); // 좋아요 3 - Review r3 = repository.save(ReviewFixtures.createReview(user1, seat1, 3, "review C")); // 좋아요 2 - likeService.reviewLike(user1.getId(), r2.getId()); - likeService.reviewLike(user2.getId(), r2.getId()); - likeService.reviewLike(user2.getId(), r3.getId()); - likeService.reviewLike(user1.getId(), r3.getId()); + Review review1 = sut.createReview(getReviewRequest(seat1, 5, "review A"), user1.getId()); + Review review2 = sut.createReview(getReviewRequest(seat1, 4, "review B"), user1.getId()); + Review review3 = sut.createReview(getReviewRequest(seat1, 3, "review C"), user1.getId()); - likeService.reviewLike(user1.getId(), r1.getId()); + likeService.reviewLike(user1.getId(), review2.getId()); + likeService.reviewLike(user2.getId(), review2.getId()); + likeService.reviewLike(user2.getId(), review3.getId()); + likeService.reviewLike(user1.getId(), review3.getId()); + likeService.reviewLike(user1.getId(), review1.getId()); // r2(2명), r3(2명), r1(1명) → r2, r3, r1(동점시 저장순 or id순) var pageRequest = PageRequest.builder().page(1).size(10).build(); @@ -885,14 +899,18 @@ class LoadReviewsByAuditoriumIdSortTest { @Test @DisplayName("[happy] 최신순(LATEST) 정렬") - void loadReviewsByAuditorium_LATEST() { - Review r1 = repository.save(ReviewFixtures.createReview(user1, seat1, 2, "oldest")); - Review r2 = repository.save(ReviewFixtures.createReview(user1, seat1, 3, "middle")); - Review r3 = repository.save(ReviewFixtures.createReview(user1, seat1, 1, "latest")); + void loadReviewsByAuditorium_LATEST() throws IOException { + + // given + sut.createReview(getReviewRequest(seat1, 2, "oldest"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 3, "middle"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 1, "latest"), user1.getId()); var pageRequest = PageRequest.builder().page(1).size(10).build(); + // when SliceResponse response = sut.loadReviewsByAuditoriumId(auditorium.getId(), pageRequest, ReviewSortType.LATEST); + // then List result = response.content(); Assertions.assertThat(result.get(0).content()).isEqualTo("latest"); Assertions.assertThat(result.get(1).content()).isEqualTo("middle"); @@ -901,30 +919,36 @@ void loadReviewsByAuditorium_LATEST() { @Test @DisplayName("[happy] 평점 내림차순(RATING_DESC) 정렬") - void loadReviewsByAuditorium_RATING_DESC() { - repository.save(ReviewFixtures.createReview(user1, seat1, 1, "r1")); - repository.save(ReviewFixtures.createReview(user1, seat1, 5, "r5")); - repository.save(ReviewFixtures.createReview(user1, seat1, 3, "r3")); + void loadReviewsByAuditorium_RATING_DESC() throws IOException { + // given + sut.createReview(getReviewRequest(seat1, 2, "r1"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 3, "r5"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 1, "r3"), user1.getId()); var pageRequest = PageRequest.builder().page(1).size(10).build(); + // when SliceResponse response = sut.loadReviewsByAuditoriumId(auditorium.getId(), pageRequest, ReviewSortType.RATING_DESC); + // then List result = response.content(); Assertions.assertThat(result.get(0).content()).isEqualTo("r5"); - Assertions.assertThat(result.get(1).content()).isEqualTo("r3"); - Assertions.assertThat(result.get(2).content()).isEqualTo("r1"); + Assertions.assertThat(result.get(1).content()).isEqualTo("r1"); + Assertions.assertThat(result.get(2).content()).isEqualTo("r3"); } @Test @DisplayName("[happy] 평점 오름차순(RATING_ASC) 정렬") - void loadReviewsByAuditorium_RATING_ASC() { - repository.save(ReviewFixtures.createReview(user1, seat1, 2, "r2")); - repository.save(ReviewFixtures.createReview(user1, seat1, 5, "r5")); - repository.save(ReviewFixtures.createReview(user1, seat1, 3, "r3")); + void loadReviewsByAuditorium_RATING_ASC() throws IOException { + // given + sut.createReview(getReviewRequest(seat1, 1, "r2"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 5, "r5"), user1.getId()); + sut.createReview(getReviewRequest(seat1, 3, "r3"), user1.getId()); var pageRequest = PageRequest.builder().page(1).size(10).build(); + // when SliceResponse response = sut.loadReviewsByAuditoriumId(auditorium.getId(), pageRequest, ReviewSortType.RATING_ASC); + // then List result = response.content(); Assertions.assertThat(result.get(0).content()).isEqualTo("r2"); Assertions.assertThat(result.get(1).content()).isEqualTo("r3"); @@ -933,18 +957,22 @@ void loadReviewsByAuditorium_RATING_ASC() { @Test @DisplayName("[happy] 좋아요 내림차순(LIKES) 정렬") - void loadReviewsByAuditorium_LIKES() { - Review r1 = repository.save(ReviewFixtures.createReview(user1, seat1, 2, "AAA")); - Review r2 = repository.save(ReviewFixtures.createReview(user1, seat1, 2, "BBB")); - Review r3 = repository.save(ReviewFixtures.createReview(user1, seat1, 2, "CCC")); - likeService.reviewLike(user1.getId(), r3.getId()); - likeService.reviewLike(user2.getId(), r3.getId()); - likeService.reviewLike(user1.getId(), r2.getId()); + void loadReviewsByAuditorium_LIKES() throws IOException { + // given + Review review = sut.createReview(getReviewRequest(seat1, 2, "AAA"), user1.getId()); + Review review1 = sut.createReview(getReviewRequest(seat1, 2, "BBB"), user1.getId()); + Review review2 = sut.createReview(getReviewRequest(seat1, 2, "CCC"), user1.getId()); + + likeService.reviewLike(user1.getId(), review2.getId()); + likeService.reviewLike(user2.getId(), review2.getId()); + likeService.reviewLike(user1.getId(), review1.getId()); var pageRequest = PageRequest.builder().page(1).size(10).build(); + // when SliceResponse response = sut.loadReviewsByAuditoriumId(auditorium.getId(), pageRequest, ReviewSortType.LIKES); + // then List result = response.content(); Assertions.assertThat(result.get(0).content()).isEqualTo("CCC"); Assertions.assertThat(result.get(0).heartCount()).isEqualTo(2L); @@ -957,6 +985,9 @@ void loadReviewsByAuditorium_LIKES() { + + + /// 요청DTO 생성 private ReviewRequest getReviewRequest(Seat seat, double rating) { return ReviewRequest.builder() .seatIds(List.of(seat.getId())) @@ -969,4 +1000,52 @@ private ReviewRequest getReviewRequest(Seat seat, double rating) { .build(); } + private ReviewRequest getReviewRequest(List seats, double rating) { + + /// 좌석 번호 + List seatIds = seats.stream() + .map(Seat::getId) + .toList(); + + return ReviewRequest.builder() + .seatIds(seatIds) + .title("review title") + .content("test1") + .movieTitle("ReviewTestTitle1") + .imageUrls(null) + .rating(rating) + .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) + .build(); + } + + private ReviewRequest getReviewRequest(List seats, double rating, String content) { + + /// 좌석 번호 + List seatIds = seats.stream() + .map(Seat::getId) + .toList(); + + return ReviewRequest.builder() + .seatIds(seatIds) + .title("review title") + .content(content) + .movieTitle("ReviewTestTitle1") + .imageUrls(null) + .rating(rating) + .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) + .build(); + } + + private ReviewRequest getReviewRequest(Seat seat, double rating, String content) { + return ReviewRequest.builder() + .seatIds(List.of(seat.getId())) + .title("test") + .content(content) + .movieTitle("ReviewTestTitle1") + .imageUrls(null) + .rating(rating) + .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) + .build(); + } + } diff --git a/src/test/java/com/seeat/server/domain/review/domain/HashTagFixtures.java b/src/test/java/com/seeat/server/domain/review/domain/HashTagFixtures.java index c1d523aa..00235919 100644 --- a/src/test/java/com/seeat/server/domain/review/domain/HashTagFixtures.java +++ b/src/test/java/com/seeat/server/domain/review/domain/HashTagFixtures.java @@ -1,7 +1,7 @@ package com.seeat.server.domain.review.domain; -import com.seeat.server.domain.review.domain.entity.HashTag; -import com.seeat.server.domain.review.domain.entity.HashTagType; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; public class HashTagFixtures { diff --git a/src/test/java/com/seeat/server/domain/review/domain/ReviewFixtures.java b/src/test/java/com/seeat/server/domain/review/domain/ReviewFixtures.java index 774e75ff..058c1459 100644 --- a/src/test/java/com/seeat/server/domain/review/domain/ReviewFixtures.java +++ b/src/test/java/com/seeat/server/domain/review/domain/ReviewFixtures.java @@ -11,7 +11,7 @@ public class ReviewFixtures { - public static Review fakeReview(User user, Seat seat) { + public static Review fakeReview(User user) { return Review.builder() .id(1L) .title("Title") @@ -19,54 +19,49 @@ public static Review fakeReview(User user, Seat seat) { .movieTitle("testTitle1") .rating(1) .user(user) - .seat(seat) .build(); } - public static Review createReview(User user, Seat seat) { + public static Review createReview(User user) { return Review.builder() .title("Title") .content("testContent1") .movieTitle("testTitle1") .rating(1) .user(user) - .seat(seat) .thumbnailUrl("thumbnailUrl") .build(); } - public static Review createReview(User user, Seat seat, int rating) { + public static Review createReview(User user, int rating) { return Review.builder() .title("Title") .content("testContent2") .movieTitle("testTitle2") .rating(rating) .user(user) - .seat(seat) .thumbnailUrl("thumbnailUrl") .build(); } - public static Review createReview(User user, Seat seat, int rating, String content) { + public static Review createReview(User user, int rating, String content) { return Review.builder() .title("Title") .content(content) .movieTitle("testTitle2") .rating(rating) .user(user) - .seat(seat) .thumbnailUrl("thumbnailUrl") .build(); } - public static Review createReview(User user, Seat seat, int rating, String content, String title) { + public static Review createReview(User user, int rating, String content, String title) { return Review.builder() .title(title) .content(content) .movieTitle(title) .rating(rating) .user(user) - .seat(seat) .thumbnailUrl("thumbnailUrl") .build(); } diff --git a/src/test/java/com/seeat/server/domain/review/domain/ReviewHashTagFixtures.java b/src/test/java/com/seeat/server/domain/review/domain/ReviewHashTagFixtures.java index 448c5315..aac49019 100644 --- a/src/test/java/com/seeat/server/domain/review/domain/ReviewHashTagFixtures.java +++ b/src/test/java/com/seeat/server/domain/review/domain/ReviewHashTagFixtures.java @@ -1,8 +1,8 @@ package com.seeat.server.domain.review.domain; -import com.seeat.server.domain.review.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewHashTag; +import com.seeat.server.domain.hashtag.domain.entity.ReviewHashTag; public class ReviewHashTagFixtures { public static ReviewHashTag createReviewHashTag(Review review, HashTag hashTag) { diff --git a/src/test/java/com/seeat/server/domain/search/application/service/ReviewSearchServiceTest.java b/src/test/java/com/seeat/server/domain/search/application/service/ReviewSearchServiceTest.java index 9693fbf1..e184efed 100644 --- a/src/test/java/com/seeat/server/domain/search/application/service/ReviewSearchServiceTest.java +++ b/src/test/java/com/seeat/server/domain/search/application/service/ReviewSearchServiceTest.java @@ -1,22 +1,20 @@ package com.seeat.server.domain.search.application.service; +import com.seeat.server.domain.review.application.dto.request.ReviewRequest; +import com.seeat.server.domain.review.application.service.ReviewService; import com.seeat.server.domain.review.domain.HashTagFixtures; -import com.seeat.server.domain.review.domain.ReviewFixtures; -import com.seeat.server.domain.review.domain.ReviewHashTagFixtures; import com.seeat.server.domain.review.domain.ReviewLikeFixtures; -import com.seeat.server.domain.review.domain.entity.HashTag; -import com.seeat.server.domain.review.domain.entity.HashTagType; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; import com.seeat.server.domain.review.domain.entity.Review; import com.seeat.server.domain.review.domain.entity.ReviewLike; -import com.seeat.server.domain.review.domain.repository.HashTagRepository; -import com.seeat.server.domain.review.domain.repository.ReviewHashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.ReviewHashTagRepository; import com.seeat.server.domain.review.domain.repository.ReviewLikeRepository; -import com.seeat.server.domain.review.domain.repository.ReviewRepository; import com.seeat.server.domain.search.application.dto.request.ReviewSearchCondition; import com.seeat.server.domain.search.application.dto.response.ReviewSearchResponse; import com.seeat.server.domain.search.application.usecase.ReviewSearchUseCase; import com.seeat.server.domain.search.domain.entity.SortType; -import com.seeat.server.domain.search.domain.repository.SearchRepository; import com.seeat.server.domain.theater.domain.AuditoriumFixtures; import com.seeat.server.domain.theater.domain.SeatFixtures; import com.seeat.server.domain.theater.domain.TheaterFixtures; @@ -32,6 +30,7 @@ import com.seeat.server.global.response.pageable.PageRequest; import com.seeat.server.global.response.pageable.SliceResponse; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -40,6 +39,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.util.List; @@ -63,9 +63,6 @@ public class ReviewSearchServiceTest { @Autowired private SeatRepository seatRepository; - @Autowired - private ReviewRepository reviewRepository; - @Autowired private ReviewLikeRepository reviewLikeRepository; @@ -75,24 +72,53 @@ public class ReviewSearchServiceTest { @Autowired private ReviewHashTagRepository reviewHashTagRepository; + /// 리뷰는 직접 작성해야한다. + @Autowired + private ReviewService reviewService; + + private Theater theater; + private Auditorium auditorium1; + private Auditorium auditorium2; + private Seat seat1; + private Seat seat2; + private Seat seat3; + private User user1; + private HashTag hashTag1; + private HashTag hashTag2; + private HashTag hashTag3; + private HashTag hashTag4; + private HashTag hashTag5; + private HashTag hashTag6; + + @BeforeEach + void setUp() throws IOException { + theater = theaterRepository.save(TheaterFixtures.createTheater()); + auditorium1 = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); + seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium1)); + seat2 = seatRepository.save(SeatFixtures.createSeat(auditorium1)); + seat3 = seatRepository.save(SeatFixtures.createSeat(auditorium2)); + user1 = userRepository.save(UserFixtures.createUser()); + hashTag1 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.SOUND, "해시태그 1")); + hashTag2 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.COMPANION, "해시태그 2")); + hashTag3 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.ENVIRONMENT, "해시태그 3")); + hashTag4 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.SOUND, "해시태그 4")); + hashTag5 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.COMPANION, "해시태그 5")); + hashTag6 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.ENVIRONMENT, "해시태그 6")); + } + @Nested @DisplayName("정렬 조건 리뷰 검색 테스트") class GetReviewListBySort { @Test @DisplayName("비회원 최신순 정렬 리뷰 검색 조회시 리스트 empty") - void sortByLatest_AsGuest_Empty() { + void sortByLatest_AsGuest_Empty() throws IOException { // given String guestToken = "guest-token-123"; String keyword = "hello"; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); - - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 3, "content3", "3")); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, "test-2"), user1.getId()); + Review review3 = reviewService.createReview(getReviewRequest(List.of(seat2), 3, "test-3"), user1.getId()); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) @@ -119,19 +145,13 @@ void sortByLatest_AsGuest_Empty() { @Test @DisplayName("비회원 최신순 정렬 리뷰 검색 조회") - void sortByLatest_AsGuest_Success() { + void sortByLatest_AsGuest_Success() throws IOException { // given String guestToken = "guest-token-123"; String keyword = "test"; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); - - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 3, "content3", "3")); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, "test-2"), user1.getId()); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) @@ -159,19 +179,13 @@ void sortByLatest_AsGuest_Success() { @Test @DisplayName("비회원 평점순 정렬 리뷰 검색 조회") - void sortByRating_AsGuest_Success() { + void sortByRating_AsGuest_Success() throws IOException { // given String guestToken = "guest-token-123"; String keyword = "test"; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); - - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 3, "content3", "3")); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, "test-2"), user1.getId()); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) @@ -199,23 +213,18 @@ void sortByRating_AsGuest_Success() { @Test @DisplayName("비회원 인기순 정렬 리뷰 검색 조회") - void sortByPopular_AsGuest_Success() { + void sortByPopular_AsGuest_Success() throws IOException { // given String guestToken = "guest-token-123"; String keyword = "test"; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, "test-2"), user1.getId()); + Review review3 = reviewService.createReview(getReviewRequest(List.of(seat2), 3, "test-3"), user1.getId()); - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 3, "content3", "3")); - - ReviewLike like1 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user, review1)); - ReviewLike like2 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user, review1)); - ReviewLike like3 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user, review2)); + ReviewLike like1 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user1, review1)); + ReviewLike like2 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user1, review1)); + ReviewLike like3 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user1, review2)); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) @@ -234,7 +243,7 @@ void sortByPopular_AsGuest_Success() { // then List responses = response.content(); - Assertions.assertThat(responses).hasSize(2); + Assertions.assertThat(responses).hasSize(3); // 각 리뷰의 리스트가 인기순으로 반환되는지 검증 Assertions.assertThat(responses.get(0).content()).isEqualTo(review1.getContent()); @@ -248,23 +257,17 @@ void sortByPopular_AsGuest_Success() { class getUserLike { @Test @DisplayName("비회원 인기순 정렬 리뷰 검색 조회시 좋아요 누름 여부 false 확인") - void likeStatusFalse_AsGuest() { + void likeStatusFalse_AsGuest() throws IOException { // given String guestToken = "guest-token-123"; String keyword = "test"; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); - - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 3, "content3", "3")); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, "test-2"), user1.getId()); - ReviewLike like1 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user, review1)); - ReviewLike like2 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user, review1)); - ReviewLike like3 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user, review2)); + ReviewLike like1 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user1, review1)); + ReviewLike like2 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user1, review1)); + ReviewLike like3 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user1, review2)); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) @@ -293,16 +296,10 @@ void likeStatusFalse_AsGuest() { @Test @DisplayName("회원 인기순 정렬 리뷰 검색 조회시 좋아요 누름 여부 true 확인") - void likeStatusTrue_AsUser() { + void likeStatusTrue_AsUser() throws IOException { // given - User user1 = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); - - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user1, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user1, seat1, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user1, seat1, 3, "content3", "3")); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, "test-2"), user1.getId()); ReviewLike like1 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user1, review1)); ReviewLike like3 = reviewLikeRepository.save(ReviewLikeFixtures.stub(user1, review2)); @@ -342,19 +339,14 @@ class GetReviewListByCondition { @Test @DisplayName("키워드 필터시 빈 키워드 입력시 리스트 empty") - void filterByKeyword_All() { + void filterByKeyword_All() throws IOException { // given String guestToken = "guest-token-123"; String keyword = ""; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); - - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 3, "content3", "3")); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, "test-2"), user1.getId()); + Review review3 = reviewService.createReview(getReviewRequest(List.of(seat2), 3, "test-3"), user1.getId()); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) @@ -383,25 +375,21 @@ void filterByKeyword_All() { @Test @DisplayName("상영관 필터시 특정 auditoriumId 리뷰만 조회") - void filterByAuditoriumId_Success() { + void filterByAuditoriumId_Success() throws IOException { // given String guestToken = "guest-token-123"; String keyword = "test"; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium1 = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Auditorium auditorium2 = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium1)); - Seat seat2 = seatRepository.save(SeatFixtures.createSeat(auditorium2)); + // seat1, seat2 는 auditorium1 소속 + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, "test-2"), user1.getId()); - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat2, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat2, 3, "content3", "3")); + // seat3 는 auditorium2 소속 (다른 상영관) + Review review3 = reviewService.createReview(getReviewRequest(List.of(seat3), 3, "test-3"), user1.getId()); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) - .auditoriumId(auditorium1.getId()) + .auditoriumId(auditorium1.getId()) // auditorium1 으로 필터링 .sort(SortType.LATEST) .build(); @@ -417,29 +405,33 @@ void filterByAuditoriumId_Success() { // then List responses = response.content(); - Assertions.assertThat(responses).hasSize(1); - // 각 리뷰의 리스트가 상영관 필터로 반환되는지 검증 - Assertions.assertThat(responses.get(0).content()).isEqualTo(review1.getContent()); + // review3 는 다른 상영관 리뷰이므로 포함되지 않아야 한다. + // 따라서 응답 리스트의 크기는 review1, review2 만큼 2개여야 한다. + Assertions.assertThat(responses).hasSize(2); + + // 포함된 리뷰가 filter 조건에 맞는지 확인 (예: auditorium1 소속 좌석 리뷰) + Assertions.assertThat(responses).extracting("content") + .containsExactlyInAnyOrder(review1.getContent(), review2.getContent()); + + // 혹은 첫번째 리뷰 내용이 review1 content인지 체크 + Assertions.assertThat(responses.get(0).content()).isIn(review1.getContent(), review2.getContent()); } + @Test @DisplayName("상영관 필터시 존재하지 않는 auditoriumId 입력") - void filterByAuditoriumId_NotExist() { + void filterByAuditoriumId_NotExist() throws IOException { // given String guestToken = "guest-token-123"; String keyword = "test"; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium1 = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Auditorium auditorium2 = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium1)); - Seat seat2 = seatRepository.save(SeatFixtures.createSeat(auditorium2)); + /// seat1, seat2 는 auditorium1 소속 + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, "test-2"), user1.getId()); - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat2, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat2, 3, "content3", "3")); + /// seat3 는 auditorium2 소속 (다른 상영관) + Review review3 = reviewService.createReview(getReviewRequest(List.of(seat3), 3, "test-3"), user1.getId()); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) @@ -467,31 +459,23 @@ void filterByAuditoriumId_NotExist() { @Test @DisplayName("해시태그 필터시 특정 해시태그 포함 리뷰만 조회") - void filterByHashTags_Success() { + void filterByHashTags_Success() throws IOException { // given String guestToken = "guest-token-123"; String keyword = "test"; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); + List tag1Ids = List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId()); + List tag2Ids = List.of(hashTag4.getId(), hashTag5.getId(), hashTag6.getId()); - HashTag hashTag1 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.COMPANION, "자녀와")); - HashTag hashTag2 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.COMPANION, "부모와")); - List hashTagIds = List.of(hashTag1.getId()); - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 3, "content3", "3")); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, tag1Ids), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, tag2Ids), user1.getId()); - reviewHashTagRepository.save(ReviewHashTagFixtures.createReviewHashTag(review1, hashTag1)); - reviewHashTagRepository.save(ReviewHashTagFixtures.createReviewHashTag(review3, hashTag1)); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) .sort(SortType.LATEST) - .hashTagIds(hashTagIds) + .hashTagIds(List.of(hashTag1.getId())) .build(); var pageRequest = PageRequest.builder().page(1).size(10).build(); @@ -514,31 +498,21 @@ void filterByHashTags_Success() { @Test @DisplayName("해시태그 필터시 존재하지 않는 해시태그 Id 입력") - void filterByHashTags_NotExist() { + void filterByHashTags_NotExist() throws IOException { // given String guestToken = "guest-token-123"; String keyword = "test"; - User user = userRepository.save(UserFixtures.createUser()); - Theater theater = theaterRepository.save(TheaterFixtures.createTheater()); - Auditorium auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); + List tag1Ids = List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId()); + List tag2Ids = List.of(hashTag4.getId(), hashTag5.getId(), hashTag6.getId()); - HashTag hashTag1 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.COMPANION, "자녀와")); - HashTag hashTag2 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.COMPANION, "부모와")); - List hashTagIds = List.of(hashTag2.getId()); - - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 4, "test content2", "test2")); - Review review3 = reviewRepository.save(ReviewFixtures.createReview(user, seat1, 3, "content3", "3")); - - reviewHashTagRepository.save(ReviewHashTagFixtures.createReviewHashTag(review1, hashTag1)); - reviewHashTagRepository.save(ReviewHashTagFixtures.createReviewHashTag(review3, hashTag1)); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, tag1Ids), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1), 4, tag2Ids), user1.getId()); ReviewSearchCondition condition = ReviewSearchCondition.builder() .keyword(keyword) .sort(SortType.LATEST) - .hashTagIds(hashTagIds) + .hashTagIds(List.of(9999L)) .build(); var pageRequest = PageRequest.builder().page(1).size(10).build(); @@ -560,4 +534,41 @@ void filterByHashTags_NotExist() { } } + + private ReviewRequest getReviewRequest(List seats, double rating, String content) { + + /// 좌석 번호 + List seatIds = seats.stream() + .map(Seat::getId) + .toList(); + + return ReviewRequest.builder() + .seatIds(seatIds) + .title("review title") + .content(content) + .movieTitle("ReviewTestTitle1") + .imageUrls(null) + .rating(rating) + .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) + .build(); + } + + private ReviewRequest getReviewRequest(List seats, double rating, List hashTagIds) { + + /// 좌석 번호 + List seatIds = seats.stream() + .map(Seat::getId) + .toList(); + + return ReviewRequest.builder() + .seatIds(seatIds) + .title("review title") + .content("content") + .movieTitle("ReviewTestTitle1") + .imageUrls(null) + .rating(rating) + .hashtags(hashTagIds) + .build(); + } + } diff --git a/src/test/java/com/seeat/server/domain/theater/application/service/SeatRatingServiceIntTest.java b/src/test/java/com/seeat/server/domain/theater/application/service/SeatRatingServiceIntTest.java index ba8b31cb..500353d2 100644 --- a/src/test/java/com/seeat/server/domain/theater/application/service/SeatRatingServiceIntTest.java +++ b/src/test/java/com/seeat/server/domain/theater/application/service/SeatRatingServiceIntTest.java @@ -3,9 +3,9 @@ import com.seeat.server.domain.review.application.dto.request.ReviewRequest; import com.seeat.server.domain.review.application.usecase.ReviewUseCase; import com.seeat.server.domain.review.domain.HashTagFixtures; -import com.seeat.server.domain.review.domain.entity.HashTag; -import com.seeat.server.domain.review.domain.entity.HashTagType; -import com.seeat.server.domain.review.domain.repository.HashTagRepository; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; import com.seeat.server.domain.theater.domain.AuditoriumFixtures; import com.seeat.server.domain.theater.domain.SeatFixtures; import com.seeat.server.domain.theater.domain.TheaterFixtures; diff --git a/src/test/java/com/seeat/server/domain/theater/application/service/TheaterServiceIntTest.java b/src/test/java/com/seeat/server/domain/theater/application/service/TheaterServiceIntTest.java index d16aa653..3baf9a4a 100644 --- a/src/test/java/com/seeat/server/domain/theater/application/service/TheaterServiceIntTest.java +++ b/src/test/java/com/seeat/server/domain/theater/application/service/TheaterServiceIntTest.java @@ -3,10 +3,10 @@ import com.seeat.server.domain.review.application.dto.request.ReviewRequest; import com.seeat.server.domain.review.application.usecase.ReviewUseCase; import com.seeat.server.domain.review.domain.HashTagFixtures; -import com.seeat.server.domain.review.domain.entity.HashTag; -import com.seeat.server.domain.review.domain.entity.HashTagType; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.repository.HashTagRepository; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; import com.seeat.server.domain.theater.application.dto.response.AuditoriumDetailResponse; import com.seeat.server.domain.theater.application.dto.response.SeatListResponse; import com.seeat.server.domain.theater.application.dto.response.TheaterListResponse; diff --git a/src/test/java/com/seeat/server/domain/user/application/service/UserProfileServiceTest.java b/src/test/java/com/seeat/server/domain/user/application/service/UserProfileServiceTest.java index 8a217c54..4d919a36 100644 --- a/src/test/java/com/seeat/server/domain/user/application/service/UserProfileServiceTest.java +++ b/src/test/java/com/seeat/server/domain/user/application/service/UserProfileServiceTest.java @@ -1,9 +1,13 @@ package com.seeat.server.domain.user.application.service; -import com.seeat.server.domain.review.domain.ReviewFixtures; +import com.seeat.server.domain.hashtag.domain.entity.HashTag; +import com.seeat.server.domain.hashtag.domain.entity.HashTagType; +import com.seeat.server.domain.hashtag.domain.repository.HashTagRepository; +import com.seeat.server.domain.review.application.dto.request.ReviewRequest; +import com.seeat.server.domain.review.application.service.ReviewService; +import com.seeat.server.domain.review.domain.HashTagFixtures; import com.seeat.server.domain.review.domain.ReviewLikeFixtures; import com.seeat.server.domain.review.domain.entity.Review; -import com.seeat.server.domain.review.domain.entity.ReviewLike; import com.seeat.server.domain.review.domain.repository.ReviewLikeRepository; import com.seeat.server.domain.review.domain.repository.ReviewRepository; import com.seeat.server.domain.theater.domain.AuditoriumFixtures; @@ -78,30 +82,54 @@ public class UserProfileServiceTest { @Autowired private ReviewLikeRepository reviewLikeRepository; + @Autowired + private HashTagRepository hashTagRepository; + + @Autowired + private ReviewService reviewService; + + private Theater theater; + private Auditorium auditorium; + private Seat seat1; + private Seat seat2; + private User user1; + private User user2; + private User user3; + private User user4; + private HashTag hashTag1; + private HashTag hashTag2; + private HashTag hashTag3; + + + @BeforeEach + void setUp() throws IOException { + theater = theaterRepository.save(TheaterFixtures.createTheater()); + auditorium = auditoriumRepository.save(AuditoriumFixtures.createAuditorium(theater)); + seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); + seat2 = seatRepository.save(SeatFixtures.createSeat(auditorium)); + user1 = repository.save(UserFixtures.createUser("user1@test.com")); + user2 = repository.save(UserFixtures.createUser("user2@test.com")); + user3 = repository.save(UserFixtures.createUser("user3@test.com")); + user4 = repository.save(UserFixtures.createUser("user4@test.com")); + hashTag1 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.SOUND, "해시태그 1")); + hashTag2 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.COMPANION, "해시태그 2")); + hashTag3 = hashTagRepository.save(HashTagFixtures.createHashTag(HashTagType.ENVIRONMENT, "해시태그 3")); + } @Nested @DisplayName("사용자 정보 조회 테스트") class getUserInfo{ + @Test @DisplayName("로그인한 유저 정보 정상 조회") - void getUserInfo_Success() { + void getUserInfo_Success() throws IOException { // given - User user1 = UserFixtures.createUser("user1@test.com"); - User user2 = repository.save(UserFixtures.createUser("user2@test.com")); - User user3 = repository.save(UserFixtures.createUser("user3@test.com")); - User user4 = repository.save(UserFixtures.createUser("user4@test.com")); - - Theater theater = TheaterFixtures.createTheater(); - repository.save(user1); - theaterRepository.save(theater); - Auditorium auditorium = AuditoriumFixtures.createAuditorium(theater, "aud1"); - auditoriumRepository.save(auditorium); userAuditoriumRepository.save(UserAuditorium.of(user1, auditorium)); - Seat seat1 = seatRepository.save(SeatFixtures.createSeat(auditorium)); - Review review1 = reviewRepository.save(ReviewFixtures.createReview(user1, seat1, 5, "test content1", "test1")); - Review review2 = reviewRepository.save(ReviewFixtures.createReview(user1, seat1, 4, "test content2", "test2")); + Review review1 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + Review review2 = reviewService.createReview(getReviewRequest(List.of(seat1, seat2), 5, "test-1"), user1.getId()); + /// 좋아요 reviewLikeRepository.saveAll(List.of( ReviewLikeFixtures.stub(user2, review1), ReviewLikeFixtures.stub(user3, review1), @@ -247,4 +275,22 @@ void getUserGradeList() { .containsExactly(UserGrade.BRONZE, UserGrade.SILVER, UserGrade.GOLD, UserGrade.PLATINUM); } + + private ReviewRequest getReviewRequest(List seats, double rating, String content) { + + /// 좌석 번호 + List seatIds = seats.stream() + .map(Seat::getId) + .toList(); + + return ReviewRequest.builder() + .seatIds(seatIds) + .title("review title") + .content(content) + .movieTitle("ReviewTestTitle1") + .imageUrls(null) + .rating(rating) + .hashtags(List.of(hashTag1.getId(), hashTag2.getId(), hashTag3.getId())) + .build(); + } }