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/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/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..1a0c860f --- /dev/null +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/review/repository/ReviewJpaRepository.java @@ -0,0 +1,6 @@ +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 {} 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..a6430647 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( @@ -51,6 +52,12 @@ public List findTopKeywordsByBlockId(Long stadiumId, Long blockId, 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..4b1b0d8c 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(Long memberId, 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/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..29b02495 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 @@ -16,4 +16,6 @@ List findByBlockId( Long countByUserId(Long userId, Integer year, Integer month); 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..f848cce3 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 @@ -29,4 +29,9 @@ public Member updateProfile(final Long memberId, UpdateProfileCommand command) { member.updateProfile(command.profileImage(), command.nickname(), command.teamId()); return memberRepository.update(updateMember); } + + @Override + public void updateLevel(Long memberId, long reviewCnt) { + // TODO + } } 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..d10d1335 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/CreateReviewService.java @@ -0,0 +1,67 @@ +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 imageUrls = command.images(); + List images = + reviewImageRepository.saveAll( + imageUrls.stream() + .map(url -> ReviewImage.of(review.getId(), url)) + .toList()); + + // TODO: 리뷰 키워드 저장 + List keywords = new ArrayList<>(); + + // TODO: 리뷰 수를 이용해 레벨 조정 + + return review.addImagesAndKeywords(images, keywords); + } + + 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..3343cf92 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,33 @@ public Long countByUserId(Long userId, Integer year, Integer month) { .count(); } - public void save(Review review) { - data.add(review); + @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() {