diff --git a/application/src/main/java/org/depromeet/spot/application/review/CreateReviewController.java b/application/src/main/java/org/depromeet/spot/application/review/CreateReviewController.java new file mode 100644 index 00000000..9fbfc6d7 --- /dev/null +++ b/application/src/main/java/org/depromeet/spot/application/review/CreateReviewController.java @@ -0,0 +1,41 @@ +package org.depromeet.spot.application.review; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +import org.depromeet.spot.application.review.dto.request.CreateReviewRequest; +import org.depromeet.spot.application.review.dto.response.ReviewResponse; +import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.usecase.port.in.review.CreateReviewUsecase; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@RestController +@Tag(name = "리뷰") +@RequiredArgsConstructor +@RequestMapping("/api/v1") +public class CreateReviewController { + + private final CreateReviewUsecase createReviewUsecase; + + @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "특정 좌석에 신규 리뷰를 추가한다.") + @PostMapping("/seats/{seatId}/members/{memberId}/reviews") + public ReviewResponse create( + @PathVariable @Positive @NotNull final Long seatId, + @PathVariable @Positive @NotNull final Long memberId, + @RequestBody @Valid @NotNull CreateReviewRequest request) { + Review review = createReviewUsecase.create(seatId, memberId, request.toCommand()); + return ReviewResponse.from(review); + } +} diff --git a/application/src/main/java/org/depromeet/spot/application/review/dto/request/CreateReviewRequest.java b/application/src/main/java/org/depromeet/spot/application/review/dto/request/CreateReviewRequest.java new file mode 100644 index 00000000..2edf4c75 --- /dev/null +++ b/application/src/main/java/org/depromeet/spot/application/review/dto/request/CreateReviewRequest.java @@ -0,0 +1,32 @@ +package org.depromeet.spot.application.review.dto.request; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.List; + +import org.depromeet.spot.common.exception.review.ReviewException.InvalidReviewDateTimeFormatException; +import org.depromeet.spot.usecase.port.in.review.CreateReviewUsecase.CreateReviewCommand; + +public record CreateReviewRequest( + List images, List good, List bad, String content, String dateTime) { + + public CreateReviewCommand toCommand() { + return CreateReviewCommand.builder() + .images(images) + .good(good) + .bad(bad) + .content(content) + .dateTime(toLocalDateTime(dateTime)) + .build(); + } + + private LocalDateTime toLocalDateTime(String dateTimeStr) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + try { + return LocalDateTime.parse(dateTimeStr, formatter); + } catch (DateTimeParseException e) { + throw new InvalidReviewDateTimeFormatException(); + } + } +} diff --git a/common/src/main/java/org/depromeet/spot/common/exception/review/ReviewErrorCode.java b/common/src/main/java/org/depromeet/spot/common/exception/review/ReviewErrorCode.java index f0a44f03..4e12bf4c 100644 --- a/common/src/main/java/org/depromeet/spot/common/exception/review/ReviewErrorCode.java +++ b/common/src/main/java/org/depromeet/spot/common/exception/review/ReviewErrorCode.java @@ -8,7 +8,9 @@ @Getter public enum ReviewErrorCode implements ErrorCode { REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "RV001", "요청한 리뷰를 찾을 수 없습니다."), - INVALID_REVIEW_DATA(HttpStatus.BAD_REQUEST, "RV002", "유효하지 않은 리뷰 데이터입니다."); + INVALID_REVIEW_DATA(HttpStatus.BAD_REQUEST, "RV002", "유효하지 않은 리뷰 데이터입니다."), + INVALID_REVIEW_DATETIME_FORMAT( + HttpStatus.BAD_REQUEST, "RV003", "리뷰 작성일시는 yyyy-MM-dd HH:mm 포맷이어야 합니다."); private final HttpStatus status; private final String code; diff --git a/common/src/main/java/org/depromeet/spot/common/exception/review/ReviewException.java b/common/src/main/java/org/depromeet/spot/common/exception/review/ReviewException.java index a766d2c2..e15f9cfc 100644 --- a/common/src/main/java/org/depromeet/spot/common/exception/review/ReviewException.java +++ b/common/src/main/java/org/depromeet/spot/common/exception/review/ReviewException.java @@ -26,4 +26,14 @@ public InvalidReviewDataException(String str) { super(ReviewErrorCode.INVALID_REVIEW_DATA.appended(str)); } } + + public static class InvalidReviewDateTimeFormatException extends ReviewException { + public InvalidReviewDateTimeFormatException() { + super(ReviewErrorCode.INVALID_REVIEW_DATETIME_FORMAT); + } + + public InvalidReviewDateTimeFormatException(String str) { + super(ReviewErrorCode.INVALID_REVIEW_DATETIME_FORMAT.appended(str)); + } + } } diff --git a/common/src/main/java/org/depromeet/spot/common/exception/seat/SeatErrorCode.java b/common/src/main/java/org/depromeet/spot/common/exception/seat/SeatErrorCode.java new file mode 100644 index 00000000..e7604689 --- /dev/null +++ b/common/src/main/java/org/depromeet/spot/common/exception/seat/SeatErrorCode.java @@ -0,0 +1,27 @@ +package org.depromeet.spot.common.exception.seat; + +import org.depromeet.spot.common.exception.ErrorCode; +import org.springframework.http.HttpStatus; + +import lombok.Getter; + +@Getter +public enum SeatErrorCode implements ErrorCode { + SEAT_NOT_FOUND(HttpStatus.NOT_FOUND, "SEAT001", "요청 좌석이 존재하지 않습니다."), + ; + + private final HttpStatus status; + private final String code; + private String message; + + SeatErrorCode(HttpStatus status, String code, String message) { + this.status = status; + this.code = code; + this.message = message; + } + + public SeatErrorCode appended(Object o) { + message = message + " {" + o.toString() + "}"; + return this; + } +} diff --git a/common/src/main/java/org/depromeet/spot/common/exception/seat/SeatException.java b/common/src/main/java/org/depromeet/spot/common/exception/seat/SeatException.java new file mode 100644 index 00000000..7d1f47b7 --- /dev/null +++ b/common/src/main/java/org/depromeet/spot/common/exception/seat/SeatException.java @@ -0,0 +1,20 @@ +package org.depromeet.spot.common.exception.seat; + +import org.depromeet.spot.common.exception.BusinessException; + +public abstract class SeatException extends BusinessException { + + protected SeatException(SeatErrorCode errorCode) { + super(errorCode); + } + + public static class SeatNotFoundException extends SeatException { + public SeatNotFoundException() { + super(SeatErrorCode.SEAT_NOT_FOUND); + } + + public SeatNotFoundException(Object obj) { + super(SeatErrorCode.SEAT_NOT_FOUND.appended(obj)); + } + } +} diff --git a/domain/src/main/java/org/depromeet/spot/domain/member/Member.java b/domain/src/main/java/org/depromeet/spot/domain/member/Member.java index 52e5e8ef..5ba7eebe 100644 --- a/domain/src/main/java/org/depromeet/spot/domain/member/Member.java +++ b/domain/src/main/java/org/depromeet/spot/domain/member/Member.java @@ -28,6 +28,43 @@ public class Member { private final LocalDateTime createdAt; private final LocalDateTime deletedAt; + public int calculateLevel(long reviewCnt) { + if (reviewCnt <= 2) { + return 1; + } + if (2 < reviewCnt && reviewCnt <= 6) { + return 2; + } + if (6 < reviewCnt && reviewCnt <= 11) { + return 3; + } + if (11 < reviewCnt && reviewCnt <= 20) { + return 4; + } + if (20 < reviewCnt && reviewCnt <= 35) { + return 5; + } + return 6; + } + + public Member updateLevel(int newLevel) { + return Member.builder() + .id(id) + .email(email) + .name(name) + .nickname(nickname) + .phoneNumber(phoneNumber) + .level(newLevel) + .profileImage(profileImage) + .snsProvider(snsProvider) + .idToken(idToken) + .teamId(teamId) + .role(role) + .createdAt(createdAt) + .deletedAt(deletedAt) + .build(); + } + public Member updateProfile(String newProfileImage, String newNickname, Long newTeamId) { return Member.builder() .id(id) diff --git a/domain/src/main/java/org/depromeet/spot/domain/review/Review.java b/domain/src/main/java/org/depromeet/spot/domain/review/Review.java index b96e33d8..a5b99f85 100644 --- a/domain/src/main/java/org/depromeet/spot/domain/review/Review.java +++ b/domain/src/main/java/org/depromeet/spot/domain/review/Review.java @@ -3,11 +3,13 @@ import java.time.LocalDateTime; import java.util.List; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -@Builder @Getter +@Builder +@AllArgsConstructor public class Review { private final Long id; @@ -18,11 +20,30 @@ public class Review { private final Long rowId; private final Long seatNumber; - private final LocalDateTime dateTime; // 시간은 미표기 + private final LocalDateTime dateTime; private final String content; private final LocalDateTime createdAt; private final LocalDateTime updatedAt; private final LocalDateTime deletedAt; private final List images; private final List keywords; + + public Review addImagesAndKeywords( + List newImages, List newKeywords) { + return new Review( + id, + userId, + stadiumId, + blockId, + seatId, + rowId, + seatNumber, + dateTime, + content, + createdAt, + updatedAt, + deletedAt, + newImages, + newKeywords); + } } diff --git a/domain/src/main/java/org/depromeet/spot/domain/review/ReviewImage.java b/domain/src/main/java/org/depromeet/spot/domain/review/ReviewImage.java index a2034140..95787ba9 100644 --- a/domain/src/main/java/org/depromeet/spot/domain/review/ReviewImage.java +++ b/domain/src/main/java/org/depromeet/spot/domain/review/ReviewImage.java @@ -5,8 +5,8 @@ import lombok.Builder; import lombok.Getter; -@Builder @Getter +@Builder public class ReviewImage { private final Long id; @@ -14,4 +14,8 @@ public class ReviewImage { private final String url; private final LocalDateTime createdAt; private final LocalDateTime deletedAt; + + public static ReviewImage of(Long reviewId, String url) { + return ReviewImage.builder().reviewId(reviewId).url(url).build(); + } } diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberJpaRepository.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberJpaRepository.java index 2333d318..ea1d5db4 100644 --- a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberJpaRepository.java +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberJpaRepository.java @@ -25,4 +25,8 @@ void updateProfile( @Param("profileImage") String profileImage, @Param("teamId") Long teamId, @Param("nickname") String nickname); + + @Modifying + @Query("update MemberEntity m set m.level = :level where m.id = :memberId") + void updateLevel(@Param("memberId") Long memberId, @Param("level") int level); } diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberRepositoryImpl.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberRepositoryImpl.java index c70e82bd..c39f2e81 100644 --- a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberRepositoryImpl.java +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberRepositoryImpl.java @@ -23,12 +23,18 @@ public Member save(Member member) { } @Override - public Member update(Member member) { + public Member updateProfile(Member member) { memberJpaRepository.updateProfile( member.getId(), member.getProfileImage(), member.getTeamId(), member.getNickname()); return member; } + @Override + public Member updateLevel(Member member) { + memberJpaRepository.updateLevel(member.getId(), member.getLevel()); + return member; + } + @Override public Optional findByIdToken(String idToken) { return memberJpaRepository.findByIdToken(idToken).map(MemberEntity::toDomain); diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/entity/ReviewEntity.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/entity/ReviewEntity.java index ea81c209..7f2f5d93 100644 --- a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/entity/ReviewEntity.java +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/entity/ReviewEntity.java @@ -34,6 +34,9 @@ public class ReviewEntity extends BaseEntity { @Column(name = "row_id", nullable = false) private Long rowId; + @Column(name = "seat_id", nullable = false) + private Long seatId; + @Column(name = "seat_number", nullable = false) private Long seatNumber; @@ -56,6 +59,7 @@ public static Review createReviewWithDetails( .stadiumId(entity.getStadiumId()) .blockId(entity.getBlockId()) .rowId(entity.getRowId()) + .seatId(entity.getSeatId()) .seatNumber(entity.getSeatNumber()) .dateTime(entity.getDateTime()) .content(entity.getContent()) @@ -79,6 +83,7 @@ public static ReviewEntity from(Review review) { review.getStadiumId(), review.getBlockId(), review.getRowId(), + review.getSeatId(), review.getSeatNumber(), review.getDateTime(), review.getContent(), @@ -92,6 +97,7 @@ public Review toDomain() { .stadiumId(stadiumId) .blockId(blockId) .rowId(rowId) + .seatId(seatId) .seatNumber(seatNumber) .dateTime(dateTime) .content(content) diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/entity/ReviewImageEntity.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/entity/ReviewImageEntity.java index 30789b82..5dcb7291 100644 --- a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/entity/ReviewImageEntity.java +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/entity/ReviewImageEntity.java @@ -1,7 +1,5 @@ package org.depromeet.spot.jpa.review.entity; -import java.time.LocalDateTime; - import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; @@ -24,12 +22,8 @@ public class ReviewImageEntity extends BaseEntity { @Column(name = "url", nullable = false, length = 255) private String url; - @Column(name = "deleted_at") - private LocalDateTime deletedAt; - public static ReviewImageEntity from(ReviewImage reviewImage) { - return new ReviewImageEntity( - reviewImage.getReviewId(), reviewImage.getUrl(), reviewImage.getDeletedAt()); + return new ReviewImageEntity(reviewImage.getReviewId(), reviewImage.getUrl()); } public ReviewImage toDomain() { @@ -38,7 +32,6 @@ public ReviewImage toDomain() { .reviewId(reviewId) .url(url) .createdAt(this.getCreatedAt()) - .deletedAt(deletedAt) .build(); } } diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/ReviewJpaRepository.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/ReviewJpaRepository.java new file mode 100644 index 00000000..438183ec --- /dev/null +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/ReviewJpaRepository.java @@ -0,0 +1,8 @@ +package org.depromeet.spot.jpa.review.repository; + +import org.depromeet.spot.jpa.review.entity.ReviewEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReviewJpaRepository extends JpaRepository { + long countByUserId(Long userId); +} diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/ReviewRepositoryImpl.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/ReviewRepositoryImpl.java index 1a89bd82..566dd8cd 100644 --- a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/ReviewRepositoryImpl.java +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/ReviewRepositoryImpl.java @@ -17,6 +17,7 @@ @RequiredArgsConstructor public class ReviewRepositoryImpl implements ReviewRepository { private final ReviewCustomRepository reviewCustomRepository; + private final ReviewJpaRepository reviewJpaRepository; @Override public List findByBlockId( @@ -46,11 +47,22 @@ public Long countByUserId(Long userId, Integer year, Integer month) { return reviewCustomRepository.countByUserIdWithFilters(userId, year, month); } + @Override + public long countByUserId(Long userId) { + return reviewJpaRepository.countByUserId(userId); + } + @Override public List findTopKeywordsByBlockId(Long stadiumId, Long blockId, int limit) { return reviewCustomRepository.findTopKeywordsByBlockId(stadiumId, blockId, limit); } + @Override + public Review save(Review review) { + ReviewEntity entity = reviewJpaRepository.save(ReviewEntity.from(review)); + return entity.toDomain(); + } + private Review fetchReviewDetails(ReviewEntity reviewEntity) { List images = reviewCustomRepository.findImagesByReviewIds(List.of(reviewEntity.getId())); diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/image/ReviewImageJpaRepository.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/image/ReviewImageJpaRepository.java new file mode 100644 index 00000000..11b0da50 --- /dev/null +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/image/ReviewImageJpaRepository.java @@ -0,0 +1,6 @@ +package org.depromeet.spot.jpa.review.repository.image; + +import org.depromeet.spot.jpa.review.entity.ReviewImageEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReviewImageJpaRepository extends JpaRepository {} diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/image/ReviewImageRepositoryImpl.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/image/ReviewImageRepositoryImpl.java new file mode 100644 index 00000000..b4d30c35 --- /dev/null +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/image/ReviewImageRepositoryImpl.java @@ -0,0 +1,25 @@ +package org.depromeet.spot.jpa.review.repository.image; + +import java.util.List; + +import org.depromeet.spot.domain.review.ReviewImage; +import org.depromeet.spot.jpa.review.entity.ReviewImageEntity; +import org.depromeet.spot.usecase.port.out.review.ReviewImageRepository; +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class ReviewImageRepositoryImpl implements ReviewImageRepository { + + private final ReviewImageJpaRepository reviewImageJpaRepository; + + @Override + public List saveAll(List images) { + List entities = + reviewImageJpaRepository.saveAll( + images.stream().map(ReviewImageEntity::from).toList()); + return entities.stream().map(ReviewImageEntity::toDomain).toList(); + } +} diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/seat/repository/SeatJpaRepository.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/seat/repository/SeatJpaRepository.java new file mode 100644 index 00000000..d6646ad7 --- /dev/null +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/seat/repository/SeatJpaRepository.java @@ -0,0 +1,6 @@ +package org.depromeet.spot.jpa.seat.repository; + +import org.depromeet.spot.jpa.seat.entity.SeatEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SeatJpaRepository extends JpaRepository {} diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/seat/repository/SeatRepositoryImpl.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/seat/repository/SeatRepositoryImpl.java index e37d6f8d..c316587d 100644 --- a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/seat/repository/SeatRepositoryImpl.java +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/seat/repository/SeatRepositoryImpl.java @@ -2,7 +2,9 @@ import java.util.List; +import org.depromeet.spot.common.exception.seat.SeatException.SeatNotFoundException; import org.depromeet.spot.domain.seat.Seat; +import org.depromeet.spot.jpa.seat.entity.SeatEntity; import org.depromeet.spot.usecase.port.out.seat.SeatRepository; import org.springframework.stereotype.Repository; @@ -13,9 +15,17 @@ public class SeatRepositoryImpl implements SeatRepository { private final SeatJdbcRepository seatJdbcRepository; + private final SeatJpaRepository seatJpaRepository; @Override public void saveAll(List seats) { seatJdbcRepository.createSeats(seats); } + + @Override + public Seat findById(Long seatId) { + SeatEntity entity = + seatJpaRepository.findById(seatId).orElseThrow(SeatNotFoundException::new); + return entity.toDomain(); + } } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/UpdateMemberUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/UpdateMemberUsecase.java index 4f3b8309..a83f096e 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/UpdateMemberUsecase.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/UpdateMemberUsecase.java @@ -8,6 +8,8 @@ public interface UpdateMemberUsecase { Member updateProfile(Long memberId, UpdateProfileCommand command); + void updateLevel(Member member, long reviewCnt); + @Builder record UpdateProfileCommand(String profileImage, String nickname, Long teamId) {} } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/CreateReviewUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/CreateReviewUsecase.java new file mode 100644 index 00000000..8b466e92 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/review/CreateReviewUsecase.java @@ -0,0 +1,21 @@ +package org.depromeet.spot.usecase.port.in.review; + +import java.time.LocalDateTime; +import java.util.List; + +import org.depromeet.spot.domain.review.Review; + +import lombok.Builder; + +public interface CreateReviewUsecase { + + Review create(Long seatId, Long memberId, CreateReviewCommand command); + + @Builder + record CreateReviewCommand( + List images, + List good, + List bad, + LocalDateTime dateTime, + String content) {} +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/seat/ReadSeatUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/seat/ReadSeatUsecase.java new file mode 100644 index 00000000..c83b8719 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/seat/ReadSeatUsecase.java @@ -0,0 +1,8 @@ +package org.depromeet.spot.usecase.port.in.seat; + +import org.depromeet.spot.domain.seat.Seat; + +public interface ReadSeatUsecase { + + Seat findById(Long seatId); +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/member/MemberRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/member/MemberRepository.java index 496f0300..4b8fecdc 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/member/MemberRepository.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/member/MemberRepository.java @@ -8,7 +8,9 @@ public interface MemberRepository { Member save(Member member); - Member update(Member member); + Member updateProfile(Member member); + + Member updateLevel(Member member); Optional findByIdToken(String idToken); diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewImageRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewImageRepository.java new file mode 100644 index 00000000..69f1f0e4 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewImageRepository.java @@ -0,0 +1,10 @@ +package org.depromeet.spot.usecase.port.out.review; + +import java.util.List; + +import org.depromeet.spot.domain.review.ReviewImage; + +public interface ReviewImageRepository { + + List saveAll(List images); +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewRepository.java index 2f10de23..79b7f9c5 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewRepository.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/review/ReviewRepository.java @@ -15,5 +15,9 @@ List findByBlockId( Long countByUserId(Long userId, Integer year, Integer month); + long countByUserId(Long userId); + List findTopKeywordsByBlockId(Long stadiumId, Long blockId, int limit); + + Review save(Review review); } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/seat/SeatRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/seat/SeatRepository.java index 1a46b6f1..6f53cdfe 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/seat/SeatRepository.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/seat/SeatRepository.java @@ -7,4 +7,6 @@ public interface SeatRepository { void saveAll(List seats); + + Seat findById(Long seatId); } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/block/BlockReadService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/block/BlockReadService.java index 3cb60650..6eb1f25d 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/block/BlockReadService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/block/BlockReadService.java @@ -16,6 +16,7 @@ import org.depromeet.spot.usecase.port.in.stadium.StadiumReadUsecase; import org.depromeet.spot.usecase.port.out.block.BlockRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -23,6 +24,7 @@ @Service @Builder @RequiredArgsConstructor +@Transactional(readOnly = true) public class BlockReadService implements BlockReadUsecase { private final BlockRepository blockRepository; diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/block/ReadBlockRowService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/block/ReadBlockRowService.java index bc9bd91c..8331833f 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/block/ReadBlockRowService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/block/ReadBlockRowService.java @@ -6,11 +6,13 @@ import org.depromeet.spot.usecase.port.in.block.ReadBlockRowUsecase; import org.depromeet.spot.usecase.port.out.block.BlockRowRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class ReadBlockRowService implements ReadBlockRowUsecase { private final BlockRowRepository blockRowRepository; diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/MemberService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/member/MemberService.java index 88c1a085..11bfbd71 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/MemberService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/member/MemberService.java @@ -9,10 +9,12 @@ import org.depromeet.spot.usecase.port.out.member.MemberRepository; import org.depromeet.spot.usecase.port.out.oauth.OauthRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; @Service +@Transactional @RequiredArgsConstructor public class MemberService implements MemberUsecase { diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/ReadMemberService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/member/ReadMemberService.java index b8e47a0d..4e39eead 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/ReadMemberService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/member/ReadMemberService.java @@ -4,11 +4,13 @@ import org.depromeet.spot.usecase.port.in.member.ReadMemberUsecase; import org.depromeet.spot.usecase.port.out.member.MemberRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class ReadMemberService implements ReadMemberUsecase { private final MemberRepository memberRepository; diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/UpdateMemberService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/member/UpdateMemberService.java index c8c22ec6..cef7f661 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/UpdateMemberService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/member/UpdateMemberService.java @@ -27,6 +27,13 @@ public Member updateProfile(final Long memberId, UpdateProfileCommand command) { readBaseballTeamUsecase.areAllTeamIdsExist(Set.of(command.teamId())); Member updateMember = member.updateProfile(command.profileImage(), command.nickname(), command.teamId()); - return memberRepository.update(updateMember); + return memberRepository.updateProfile(updateMember); + } + + @Override + public void updateLevel(Member member, long reviewCnt) { + final int newLevel = member.calculateLevel(reviewCnt); + Member updateMember = member.updateLevel(newLevel); + memberRepository.updateLevel(updateMember); } } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/CreateReviewService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/CreateReviewService.java new file mode 100644 index 00000000..a3d49e7f --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/CreateReviewService.java @@ -0,0 +1,69 @@ +package org.depromeet.spot.usecase.service.review; + +import java.util.ArrayList; +import java.util.List; + +import org.depromeet.spot.domain.member.Member; +import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.domain.review.ReviewImage; +import org.depromeet.spot.domain.review.ReviewKeyword; +import org.depromeet.spot.domain.seat.Seat; +import org.depromeet.spot.usecase.port.in.member.ReadMemberUsecase; +import org.depromeet.spot.usecase.port.in.member.UpdateMemberUsecase; +import org.depromeet.spot.usecase.port.in.review.CreateReviewUsecase; +import org.depromeet.spot.usecase.port.in.seat.ReadSeatUsecase; +import org.depromeet.spot.usecase.port.out.review.ReviewImageRepository; +import org.depromeet.spot.usecase.port.out.review.ReviewRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional +@RequiredArgsConstructor +public class CreateReviewService implements CreateReviewUsecase { + + private final ReviewRepository reviewRepository; + private final ReviewImageRepository reviewImageRepository; + private final ReadSeatUsecase readSeatUsecase; + private final ReadMemberUsecase readMemberUsecase; + private final UpdateMemberUsecase updateMemberUsecase; + + @Override + public Review create(final Long seatId, final Long memberId, CreateReviewCommand command) { + Seat seat = readSeatUsecase.findById(seatId); + Member member = readMemberUsecase.findById(memberId); + + Review review = reviewRepository.save(convertToDomain(seat, member, command)); + List images = + reviewImageRepository.saveAll( + command.images().stream() + .map(url -> ReviewImage.of(review.getId(), url)) + .toList()); + + // TODO: 리뷰 키워드 저장 + List keywords = new ArrayList<>(); + + calculateMemberLevel(member); + return review.addImagesAndKeywords(images, keywords); + } + + public void calculateMemberLevel(final Member member) { + final long memberReviewCnt = reviewRepository.countByUserId(member.getId()); + updateMemberUsecase.updateLevel(member, memberReviewCnt); + } + + private Review convertToDomain(Seat seat, Member member, CreateReviewCommand command) { + return Review.builder() + .userId(member.getId()) + .stadiumId(seat.getStadium().getId()) + .blockId(seat.getBlock().getId()) + .seatId(seat.getId()) + .rowId(seat.getRow().getId()) + .seatNumber(Long.valueOf(seat.getSeatNumber())) + .dateTime(command.dateTime()) + .content(command.content()) + .build(); + } +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReviewReadService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReviewReadService.java index 8926633b..ef3fd660 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReviewReadService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/ReviewReadService.java @@ -9,11 +9,13 @@ import org.depromeet.spot.usecase.port.in.review.ReviewReadUsecase; import org.depromeet.spot.usecase.port.out.review.ReviewRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class ReviewReadService implements ReviewReadUsecase { private final ReviewRepository reviewRepository; diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/seat/CreateSeatService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/seat/CreateSeatService.java index 089c8579..f9d13bf9 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/seat/CreateSeatService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/seat/CreateSeatService.java @@ -15,12 +15,14 @@ import org.depromeet.spot.usecase.port.out.seat.SeatRepository; import org.springframework.context.annotation.Description; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.Builder; import lombok.RequiredArgsConstructor; @Service @Builder +@Transactional @RequiredArgsConstructor public class CreateSeatService implements CreateSeatUsecase { diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/seat/ReadSeatService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/seat/ReadSeatService.java new file mode 100644 index 00000000..dcdb154d --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/seat/ReadSeatService.java @@ -0,0 +1,22 @@ +package org.depromeet.spot.usecase.service.seat; + +import org.depromeet.spot.domain.seat.Seat; +import org.depromeet.spot.usecase.port.in.seat.ReadSeatUsecase; +import org.depromeet.spot.usecase.port.out.seat.SeatRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ReadSeatService implements ReadSeatUsecase { + + private final SeatRepository seatRepository; + + @Override + public Seat findById(final Long seatId) { + return seatRepository.findById(seatId); + } +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/section/CreateSectionService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/section/CreateSectionService.java index fd1dfa20..c481b0ec 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/section/CreateSectionService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/section/CreateSectionService.java @@ -12,12 +12,14 @@ import org.depromeet.spot.usecase.port.in.stadium.StadiumReadUsecase; import org.depromeet.spot.usecase.port.out.section.SectionRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.Builder; import lombok.RequiredArgsConstructor; @Service @Builder +@Transactional @RequiredArgsConstructor public class CreateSectionService implements CreateSectionUsecase { diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/section/SectionReadService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/section/SectionReadService.java index 9a420792..df9065c5 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/section/SectionReadService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/section/SectionReadService.java @@ -9,6 +9,7 @@ import org.depromeet.spot.usecase.port.in.stadium.StadiumReadUsecase; import org.depromeet.spot.usecase.port.out.section.SectionRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -16,6 +17,7 @@ @Service @Builder @RequiredArgsConstructor +@Transactional(readOnly = true) public class SectionReadService implements SectionReadUsecase { private final StadiumReadUsecase stadiumReadUsecase; diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/stadium/CreateStadiumService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/stadium/CreateStadiumService.java index 5f0d5a33..91f3434f 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/stadium/CreateStadiumService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/stadium/CreateStadiumService.java @@ -6,10 +6,12 @@ import org.depromeet.spot.usecase.port.out.media.ImageUploadPort; import org.depromeet.spot.usecase.port.out.stadium.StadiumRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; @Service +@Transactional @RequiredArgsConstructor public class CreateStadiumService implements CreateStadiumUsecase { diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/stadium/StadiumReadService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/stadium/StadiumReadService.java index 82ca5cb3..93abdfd7 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/stadium/StadiumReadService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/stadium/StadiumReadService.java @@ -11,6 +11,7 @@ import org.depromeet.spot.usecase.port.in.team.ReadStadiumHomeTeamUsecase.HomeTeamInfo; import org.depromeet.spot.usecase.port.out.stadium.StadiumRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -18,6 +19,7 @@ @Service @Builder @RequiredArgsConstructor +@Transactional(readOnly = true) public class StadiumReadService implements StadiumReadUsecase { private final ReadStadiumHomeTeamUsecase readStadiumHomeTeamUsecase; diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/team/CreateBaseballTeamService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/team/CreateBaseballTeamService.java index 2c9414af..0b4a3909 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/team/CreateBaseballTeamService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/team/CreateBaseballTeamService.java @@ -7,12 +7,14 @@ import org.depromeet.spot.usecase.port.in.team.CreateBaseballTeamUsecase; import org.depromeet.spot.usecase.port.out.team.BaseballTeamRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.Builder; import lombok.RequiredArgsConstructor; @Service @Builder +@Transactional @RequiredArgsConstructor public class CreateBaseballTeamService implements CreateBaseballTeamUsecase { diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/team/CreateHomeTeamService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/team/CreateHomeTeamService.java index c47ef649..1810cf81 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/team/CreateHomeTeamService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/team/CreateHomeTeamService.java @@ -10,10 +10,12 @@ import org.depromeet.spot.usecase.port.in.team.ReadBaseballTeamUsecase; import org.depromeet.spot.usecase.port.out.team.HomeTeamRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; @Service +@Transactional @RequiredArgsConstructor public class CreateHomeTeamService implements CreateHomeTeamUsecase { diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/team/ReadBaseballTeamService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/team/ReadBaseballTeamService.java index 96bdabb4..96b72509 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/team/ReadBaseballTeamService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/team/ReadBaseballTeamService.java @@ -8,6 +8,7 @@ import org.depromeet.spot.usecase.port.in.team.ReadBaseballTeamUsecase; import org.depromeet.spot.usecase.port.out.team.BaseballTeamRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -15,6 +16,7 @@ @Service @Builder @RequiredArgsConstructor +@Transactional(readOnly = true) public class ReadBaseballTeamService implements ReadBaseballTeamUsecase { private final BaseballTeamRepository baseballTeamRepository; diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/team/ReadStadiumHomeTeamService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/team/ReadStadiumHomeTeamService.java index da12d596..b453d835 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/team/ReadStadiumHomeTeamService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/team/ReadStadiumHomeTeamService.java @@ -8,11 +8,13 @@ import org.depromeet.spot.usecase.port.in.team.ReadStadiumHomeTeamUsecase; import org.depromeet.spot.usecase.port.out.team.HomeTeamRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class ReadStadiumHomeTeamService implements ReadStadiumHomeTeamUsecase { private final HomeTeamRepository homeTeamRepository; diff --git a/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeReviewRepository.java b/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeReviewRepository.java index 55b107be..a58e9941 100644 --- a/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeReviewRepository.java +++ b/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeReviewRepository.java @@ -1,6 +1,7 @@ package org.depromeet.spot.usecase.service.fake; import java.util.*; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -12,6 +13,8 @@ public class FakeReviewRepository implements ReviewRepository { + private final AtomicLong autoGeneratedId = new AtomicLong(0); + private final List data = new ArrayList<>(); @Override @@ -97,8 +100,38 @@ public Long countByUserId(Long userId, Integer year, Integer month) { .count(); } - public void save(Review review) { - data.add(review); + @Override + public long countByUserId(Long userId) { + return data.stream().filter(review -> review.getUserId().equals(userId)).count(); + } + + @Override + public Review save(Review review) { + if (review.getId() == null || review.getId() == 0) { + Review newReview = + Review.builder() + .id(autoGeneratedId.incrementAndGet()) + .userId(review.getUserId()) + .stadiumId(review.getStadiumId()) + .blockId(review.getBlockId()) + .seatId(review.getSeatId()) + .rowId(review.getRowId()) + .seatNumber(review.getSeatNumber()) + .dateTime(review.getDateTime()) + .content(review.getContent()) + .createdAt(review.getCreatedAt()) + .updatedAt(review.getUpdatedAt()) + .deletedAt(review.getDeletedAt()) + .images(review.getImages()) + .keywords(review.getKeywords()) + .build(); + data.add(newReview); + return newReview; + } else { + data.removeIf(item -> Objects.equals(item.getId(), review.getId())); + data.add(review); + return review; + } } public void clear() { diff --git a/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeSeatRepository.java b/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeSeatRepository.java index 60a1900e..82506ece 100644 --- a/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeSeatRepository.java +++ b/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeSeatRepository.java @@ -4,8 +4,10 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; +import org.depromeet.spot.common.exception.seat.SeatException.SeatNotFoundException; import org.depromeet.spot.domain.seat.Seat; import org.depromeet.spot.usecase.port.out.seat.SeatRepository; @@ -19,6 +21,15 @@ public void saveAll(List seats) { seats.forEach(this::save); } + @Override + public Seat findById(Long seatId) { + return getById(seatId).orElseThrow(SeatNotFoundException::new); + } + + private Optional getById(Long id) { + return data.stream().filter(seat -> seat.getId().equals(id)).findAny(); + } + private Seat save(Seat seat) { if (seat.getId() == null || seat.getId() == 0) { Seat newSeat = diff --git a/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewReadServiceTest.java b/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewReadServiceTest.java index 37b90a9d..96e5eb24 100644 --- a/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewReadServiceTest.java +++ b/usecase/src/test/java/org/depromeet/spot/usecase/service/review/ReviewReadServiceTest.java @@ -97,7 +97,7 @@ void setUp() { for (int i = 0; i < 20; i++) { Review review = Review.builder() - .id((long) i) + .id((long) i + 1) .stadiumId(1L) .blockId(1L) .rowId(1L)