From c0f20ea1bfbc8c23fe842ef27bdbe33141ccfe7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=EB=94=94?= <38103085+EunjiShin@users.noreply.github.com> Date: Fri, 16 Aug 2024 02:28:36 +0900 Subject: [PATCH] [Deploy] PROD Manual Deploy (#139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: local profile에서 loki 비활성화 (#136) * [BSVR-194] 리뷰 등록 어드민 API + 관련 작업들 (#137) * feat: controller 추가 * feat: 어드민 리뷰 등록 서비스 코드 추가 * feat: 어드민 리뷰 생성 로직 수정 * fix: member, blockRow에서 발생하는 N+1 수정 * refactor: image, keyword 처리 로직을 클래스로 분리 * refactor: 클래스명 컨벤션에 맞게 level usecase 수정 * perf: value별 레벨 조회에 캐시 적용 * feat: content NotBlank 처리 --- .../controller/ReadLevelController.java | 8 +- .../review/CreateReviewController.java | 23 ++++ .../dto/request/CreateAdminReviewRequest.java | 49 +++++++ .../src/main/resources/logback-spring.xml | 1 - .../spot/infrastructure/cache/CacheType.java | 1 + .../repository/row/BlockRowJpaRepository.java | 10 ++ .../row/BlockRowRepositoryImpl.java | 7 + .../repository/MemberJpaRepository.java | 3 + .../repository/MemberRepositoryImpl.java | 4 +- .../jpa/review/entity/ReviewEntity.java | 19 +-- .../jpa/seat/entity/SeatEntity.java | 1 + .../repository/SectionRepositoryImpl.java | 8 ++ .../port/in/block/ReadBlockRowUsecase.java | 2 + .../usecase/port/in/member/LevelUsecase.java | 11 -- .../port/in/member/ReadLevelUsecase.java | 10 -- .../in/member/level/ReadLevelUsecase.java | 16 +++ .../port/in/review/CreateReviewUsecase.java | 17 +++ .../port/in/section/SectionReadUsecase.java | 2 + .../port/out/block/BlockRowRepository.java | 2 + .../port/out/section/SectionRepository.java | 2 + .../service/block/ReadBlockRowService.java | 5 + ...evelService.java => ReadLevelService.java} | 23 +++- .../usecase/service/member/MemberService.java | 2 +- .../service/member/ReadLevelService.java | 27 ---- .../service/member/UpdateMemberService.java | 2 +- .../service/review/CreateReviewService.java | 121 ++++++++++-------- .../processor/ReviewImageProcessor.java | 36 ++++++ .../processor/ReviewKeywordProcessor.java | 58 +++++++++ .../service/section/SectionReadService.java | 5 + .../service/fake/FakeSectionRepository.java | 11 ++ 30 files changed, 364 insertions(+), 122 deletions(-) create mode 100644 application/src/main/java/org/depromeet/spot/application/review/dto/request/CreateAdminReviewRequest.java delete mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/LevelUsecase.java delete mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/ReadLevelUsecase.java create mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/level/ReadLevelUsecase.java rename usecase/src/main/java/org/depromeet/spot/usecase/service/level/{LevelService.java => ReadLevelService.java} (57%) delete mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/service/member/ReadLevelService.java create mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReviewImageProcessor.java create mode 100644 usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReviewKeywordProcessor.java diff --git a/application/src/main/java/org/depromeet/spot/application/member/controller/ReadLevelController.java b/application/src/main/java/org/depromeet/spot/application/member/controller/ReadLevelController.java index 5f91c597..7a9b9134 100644 --- a/application/src/main/java/org/depromeet/spot/application/member/controller/ReadLevelController.java +++ b/application/src/main/java/org/depromeet/spot/application/member/controller/ReadLevelController.java @@ -8,7 +8,7 @@ import org.depromeet.spot.application.member.dto.response.LevelUpDialogInfo; import org.depromeet.spot.application.member.dto.response.LevelUpTableResponse; import org.depromeet.spot.domain.member.Level; -import org.depromeet.spot.usecase.port.in.member.LevelUsecase; +import org.depromeet.spot.usecase.port.in.member.level.ReadLevelUsecase; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -26,20 +26,20 @@ @RequestMapping("/api/v1/levels") public class ReadLevelController { - private final LevelUsecase levelUsecase; + private final ReadLevelUsecase readLevelUsecase; @GetMapping("/info") @ResponseStatus(HttpStatus.OK) @Operation(summary = "레벨업 조건 테이블 조회 API") public List getLevelUpTable() { - return levelUsecase.findAllLevels().stream().map(LevelUpTableResponse::from).toList(); + return readLevelUsecase.findAllLevels().stream().map(LevelUpTableResponse::from).toList(); } @GetMapping("/up/info") @ResponseStatus(HttpStatus.OK) @Operation(summary = "레벨업 다이얼로그 조회 API") public LevelUpDialogInfo getLevelUpDialogInfo(@RequestParam @NotNull @Positive int nextLevel) { - Level level = levelUsecase.findLevelUpDialogInfo(nextLevel); + Level level = readLevelUsecase.findLevelUpDialogInfo(nextLevel); return LevelUpDialogInfo.from(level); } } 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 index 1459bd80..e8814a2d 100644 --- a/application/src/main/java/org/depromeet/spot/application/review/CreateReviewController.java +++ b/application/src/main/java/org/depromeet/spot/application/review/CreateReviewController.java @@ -1,10 +1,14 @@ package org.depromeet.spot.application.review; +import java.util.List; + import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; import org.depromeet.spot.application.common.annotation.CurrentMember; +import org.depromeet.spot.application.review.dto.request.CreateAdminReviewRequest; import org.depromeet.spot.application.review.dto.request.CreateReviewRequest; import org.depromeet.spot.application.review.dto.response.BaseReviewResponse; import org.depromeet.spot.usecase.port.in.review.CreateReviewUsecase; @@ -14,14 +18,18 @@ 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.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @RestController @Tag(name = "리뷰") @RequiredArgsConstructor @@ -43,4 +51,19 @@ public BaseReviewResponse create( createReviewUsecase.create(blockId, seatNumber, memberId, request.toCommand()); return BaseReviewResponse.from(result); } + + @CurrentMember + @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "[어드민] 특정 열에 신규 리뷰를 추가한다.") + @PostMapping(value = "/stadiums/{stadiumId}/blocks/{blockCode}/rows/{rowNumber}/reviews") + public void createAll( + @PathVariable @Positive @NotNull final long stadiumId, + @PathVariable @Positive @NotNull final String blockCode, + @PathVariable @Positive @NotNull final int rowNumber, + @Parameter(hidden = true) Long memberId, + @RequestPart @Valid CreateAdminReviewRequest data, + @RequestPart @Size(min = 1, max = 3) List images) { + createReviewUsecase.createAdmin( + stadiumId, blockCode, rowNumber, memberId, data.toCommand(images)); + } } diff --git a/application/src/main/java/org/depromeet/spot/application/review/dto/request/CreateAdminReviewRequest.java b/application/src/main/java/org/depromeet/spot/application/review/dto/request/CreateAdminReviewRequest.java new file mode 100644 index 00000000..7e70a6ac --- /dev/null +++ b/application/src/main/java/org/depromeet/spot/application/review/dto/request/CreateAdminReviewRequest.java @@ -0,0 +1,49 @@ +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 jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import org.depromeet.spot.common.exception.review.ReviewException.InvalidReviewDateTimeFormatException; +import org.depromeet.spot.common.exception.review.ReviewException.InvalidReviewKeywordsException; +import org.depromeet.spot.usecase.port.in.review.CreateReviewUsecase.CreateAdminReviewCommand; +import org.springframework.web.multipart.MultipartFile; + +public record CreateAdminReviewRequest( + Integer seatNumber, + List good, + List bad, + @NotBlank String content, + @NotNull String dateTime) { + + public CreateAdminReviewCommand toCommand(List images) { + checkHasKeyword(); + return CreateAdminReviewCommand.builder() + .bad(bad) + .good(good) + .images(images) + .content(content) + .seatNumber(seatNumber) + .dateTime(toLocalDateTime(dateTime)) + .build(); + } + + private void checkHasKeyword() { + if ((this.good == null || good.isEmpty()) && (bad == null || bad.isEmpty())) { + throw new InvalidReviewKeywordsException(); + } + } + + 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/application/src/main/resources/logback-spring.xml b/application/src/main/resources/logback-spring.xml index e43fc383..1cb79230 100644 --- a/application/src/main/resources/logback-spring.xml +++ b/application/src/main/resources/logback-spring.xml @@ -4,7 +4,6 @@ - diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/cache/CacheType.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/cache/CacheType.java index a8b7626d..61403a58 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/cache/CacheType.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/cache/CacheType.java @@ -9,6 +9,7 @@ public enum CacheType { SECTION_SEATS("sectionSeatsCache", 100L, 60 * 60 * 24L), BLOCK_SEATS("blockSeatsCache", 1_000L, 60 * 60 * 24L), LEVEL_INFO("levelsCache", 1L, 60 * 60 * 1L), + LEVEL_VALUE_INFO("levelValuesCache", 6L, 60 * 60 * 1L), ; private final String name; diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/block/repository/row/BlockRowJpaRepository.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/block/repository/row/BlockRowJpaRepository.java index a9843f0c..28c870a3 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/block/repository/row/BlockRowJpaRepository.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/block/repository/row/BlockRowJpaRepository.java @@ -13,6 +13,16 @@ public interface BlockRowJpaRepository extends JpaRepository findAllByBlock(Long blockId) { blockRowJpaRepository.findAllByBlockIdOrderByNumberAsc(blockId); return entities.stream().map(BlockRowEntity::toDomain).toList(); } + + @Override + public BlockRow findBy(long stadiumId, String blockCode, int rowNumber) { + BlockRowEntity entity = + blockRowJpaRepository.findByBlockAndNumber(stadiumId, blockCode, rowNumber); + return entity.toDomain(); + } } diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberJpaRepository.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberJpaRepository.java index d31faf4d..ecc0310f 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberJpaRepository.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberJpaRepository.java @@ -12,6 +12,9 @@ public interface MemberJpaRepository extends JpaRepository { Optional findByIdToken(String idToken); + @Query("select m from MemberEntity m " + "join fetch m.level l " + "where m.id = :id") + Optional findByIdWithLevel(@Param("id") Long id); + boolean existsByNickname(String nickname); @Modifying diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberRepositoryImpl.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberRepositoryImpl.java index fdffe3bc..c2165f65 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberRepositoryImpl.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberRepositoryImpl.java @@ -52,7 +52,9 @@ public boolean existsByNickname(String nickname) { @Override public Member findById(Long memberId) { MemberEntity entity = - memberJpaRepository.findById(memberId).orElseThrow(MemberNotFoundException::new); + memberJpaRepository + .findByIdWithLevel(memberId) + .orElseThrow(MemberNotFoundException::new); return entity.toDomain(); } diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/entity/ReviewEntity.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/entity/ReviewEntity.java index ce5c447e..63cb5646 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/entity/ReviewEntity.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/review/entity/ReviewEntity.java @@ -75,10 +75,7 @@ public class ReviewEntity extends BaseEntity { private BlockRowEntity row; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn( - name = "seat_id", - nullable = false, - foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + @JoinColumn(name = "seat_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) private SeatEntity seat; @Column(name = "date_time", nullable = false) @@ -96,6 +93,12 @@ public class ReviewEntity extends BaseEntity { private List keywords; public static ReviewEntity from(Review review) { + SeatEntity seatEntity; + if (review.getSeat() == null) { + seatEntity = null; + } else { + seatEntity = SeatEntity.withSeat(review.getSeat()); + } ReviewEntity entity = new ReviewEntity( MemberEntity.withMember(review.getMember()), @@ -103,7 +106,7 @@ public static ReviewEntity from(Review review) { SectionEntity.withSection(review.getSection()), BlockEntity.withBlock(review.getBlock()), BlockRowEntity.withBlockRow(review.getRow()), - SeatEntity.withSeat(review.getSeat()), + seatEntity, review.getDateTime(), review.getContent(), new ArrayList<>(), @@ -114,12 +117,12 @@ public static ReviewEntity from(Review review) { entity.images = review.getImages().stream() .map(image -> ReviewImageEntity.from(image, entity)) - .collect(Collectors.toList()); + .toList(); entity.keywords = review.getKeywords().stream() .map(keyword -> ReviewKeywordEntity.from(keyword, entity)) - .collect(Collectors.toList()); + .toList(); return entity; } @@ -133,7 +136,7 @@ public Review toDomain() { .section(this.section.toDomain()) .block(this.block.toDomain()) .row(this.row.toDomain()) - .seat(this.seat.toDomain()) + .seat((this.seat == null) ? null : this.seat.toDomain()) .dateTime(this.dateTime) .content(this.content) .build(); diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/seat/entity/SeatEntity.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/seat/entity/SeatEntity.java index f1c21ff2..672b6e75 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/seat/entity/SeatEntity.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/seat/entity/SeatEntity.java @@ -67,6 +67,7 @@ public static SeatEntity from(Seat seat) { } public static SeatEntity withSeat(Seat seat) { + if (seat == null) return null; return new SeatEntity(seat); } diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/section/repository/SectionRepositoryImpl.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/section/repository/SectionRepositoryImpl.java index 636f291d..8f1056a5 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/section/repository/SectionRepositoryImpl.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/section/repository/SectionRepositoryImpl.java @@ -2,6 +2,7 @@ import java.util.List; +import org.depromeet.spot.common.exception.section.SectionException.SectionNotFoundException; import org.depromeet.spot.domain.section.Section; import org.depromeet.spot.infrastructure.jpa.section.entity.SectionEntity; import org.depromeet.spot.usecase.port.out.section.SectionRepository; @@ -37,4 +38,11 @@ public void saveAll(List
sections) { public boolean existsInStadium(Long stadiumId, Long sectionId) { return sectionJpaRepository.existsByStadiumIdAndId(stadiumId, sectionId); } + + @Override + public Section findById(Long id) { + SectionEntity entity = + sectionJpaRepository.findById(id).orElseThrow(SectionNotFoundException::new); + return entity.toDomain(); + } } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/block/ReadBlockRowUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/block/ReadBlockRowUsecase.java index 9efd5c3c..6c3a19ca 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/block/ReadBlockRowUsecase.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/block/ReadBlockRowUsecase.java @@ -7,4 +7,6 @@ public interface ReadBlockRowUsecase { List findAllByBlock(Long blockId); + + BlockRow findBy(long stadiumId, String blockCode, int rowNumber); } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/LevelUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/LevelUsecase.java deleted file mode 100644 index e3dc7cd3..00000000 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/LevelUsecase.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.depromeet.spot.usecase.port.in.member; - -import java.util.List; - -import org.depromeet.spot.domain.member.Level; - -public interface LevelUsecase { - List findAllLevels(); - - Level findLevelUpDialogInfo(int nextLevel); -} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/ReadLevelUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/ReadLevelUsecase.java deleted file mode 100644 index 0593e1cf..00000000 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/ReadLevelUsecase.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.depromeet.spot.usecase.port.in.member; - -import org.depromeet.spot.domain.member.Level; - -public interface ReadLevelUsecase { - - Level findInitialLevel(); - - Level findByValue(int value); -} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/level/ReadLevelUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/level/ReadLevelUsecase.java new file mode 100644 index 00000000..cf9fb200 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/member/level/ReadLevelUsecase.java @@ -0,0 +1,16 @@ +package org.depromeet.spot.usecase.port.in.member.level; + +import java.util.List; + +import org.depromeet.spot.domain.member.Level; + +public interface ReadLevelUsecase { + + Level findInitialLevel(); + + Level findByValue(int value); + + List findAllLevels(); + + Level findLevelUpDialogInfo(int nextLevel); +} 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 index ce624ef6..067c20fa 100644 --- 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 @@ -6,6 +6,7 @@ import org.depromeet.spot.domain.member.Member; import org.depromeet.spot.domain.review.Review; import org.depromeet.spot.domain.seat.Seat; +import org.springframework.web.multipart.MultipartFile; import lombok.Builder; @@ -13,6 +14,13 @@ public interface CreateReviewUsecase { CreateReviewResult create( Long blockId, Integer seatNumber, Long memberId, CreateReviewCommand command); + void createAdmin( + long stadiumId, + String blockCode, + int rowNumber, + Long memberId, + CreateAdminReviewCommand command); + @Builder record CreateReviewCommand( List images, @@ -21,5 +29,14 @@ record CreateReviewCommand( String content, LocalDateTime dateTime) {} + @Builder + record CreateAdminReviewCommand( + List images, + List good, + List bad, + String content, + Integer seatNumber, + LocalDateTime dateTime) {} + record CreateReviewResult(Review review, Member member, Seat seat) {} } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/section/SectionReadUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/section/SectionReadUsecase.java index 8b1bcd12..e1c79474 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/section/SectionReadUsecase.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/section/SectionReadUsecase.java @@ -16,6 +16,8 @@ public interface SectionReadUsecase { void checkIsExistsInStadium(Long stadiumId, Long sectionId); + Section findById(Long id); + @Getter @AllArgsConstructor class StadiumSections { diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/block/BlockRowRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/block/BlockRowRepository.java index 355783b2..c07b6b4c 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/block/BlockRowRepository.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/block/BlockRowRepository.java @@ -9,4 +9,6 @@ public interface BlockRowRepository { void createAll(List rows); List findAllByBlock(Long blockId); + + BlockRow findBy(long stadiumId, String blockCode, int rowNumber); } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/section/SectionRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/section/SectionRepository.java index f7065b6e..67e2e87a 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/section/SectionRepository.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/section/SectionRepository.java @@ -13,4 +13,6 @@ public interface SectionRepository { void saveAll(List
sections); boolean existsInStadium(Long stadiumId, Long sectionId); + + Section findById(Long id); } 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 8331833f..5d374cec 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 @@ -21,4 +21,9 @@ public class ReadBlockRowService implements ReadBlockRowUsecase { public List findAllByBlock(Long blockId) { return blockRowRepository.findAllByBlock(blockId); } + + @Override + public BlockRow findBy(long stadiumId, String blockCode, int rowNumber) { + return blockRowRepository.findBy(stadiumId, blockCode, rowNumber); + } } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/level/LevelService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/level/ReadLevelService.java similarity index 57% rename from usecase/src/main/java/org/depromeet/spot/usecase/service/level/LevelService.java rename to usecase/src/main/java/org/depromeet/spot/usecase/service/level/ReadLevelService.java index 96a9c057..22d6828e 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/level/LevelService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/level/ReadLevelService.java @@ -3,8 +3,7 @@ import java.util.List; import org.depromeet.spot.domain.member.Level; -import org.depromeet.spot.usecase.port.in.member.LevelUsecase; -import org.depromeet.spot.usecase.port.in.member.ReadLevelUsecase; +import org.depromeet.spot.usecase.port.in.member.level.ReadLevelUsecase; import org.depromeet.spot.usecase.port.out.member.LevelRepository; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @@ -15,10 +14,11 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class LevelService implements LevelUsecase { +public class ReadLevelService implements ReadLevelUsecase { private final LevelRepository levelRepository; - private final ReadLevelUsecase readLevelUsecase; + + private static final int INITIAL_LEVEL = 0; @Override @Cacheable(cacheNames = {"levelsCache"}) @@ -28,6 +28,19 @@ public List findAllLevels() { @Override public Level findLevelUpDialogInfo(final int nextLevel) { - return readLevelUsecase.findByValue(nextLevel); + return findByValue(nextLevel); + } + + @Override + public Level findInitialLevel() { + return levelRepository.findByValue(INITIAL_LEVEL); + } + + @Override + @Cacheable( + cacheNames = {"levelValuesCache"}, + key = "#value") + public Level findByValue(final int value) { + return levelRepository.findByValue(value); } } 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 0565c31a..a33f48c7 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 @@ -8,8 +8,8 @@ import org.depromeet.spot.domain.member.Member; import org.depromeet.spot.domain.team.BaseballTeam; import org.depromeet.spot.usecase.port.in.member.MemberUsecase; -import org.depromeet.spot.usecase.port.in.member.ReadLevelUsecase; import org.depromeet.spot.usecase.port.in.member.ReadMemberUsecase; +import org.depromeet.spot.usecase.port.in.member.level.ReadLevelUsecase; import org.depromeet.spot.usecase.port.in.review.ReadReviewUsecase; import org.depromeet.spot.usecase.port.in.team.ReadBaseballTeamUsecase; import org.depromeet.spot.usecase.port.out.member.MemberRepository; diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/ReadLevelService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/member/ReadLevelService.java deleted file mode 100644 index 87a776e8..00000000 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/ReadLevelService.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.depromeet.spot.usecase.service.member; - -import org.depromeet.spot.domain.member.Level; -import org.depromeet.spot.usecase.port.in.member.ReadLevelUsecase; -import org.depromeet.spot.usecase.port.out.member.LevelRepository; -import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class ReadLevelService implements ReadLevelUsecase { - - private final LevelRepository levelRepository; - - private static final int INITIAL_LEVEL = 0; - - @Override - public Level findInitialLevel() { - return levelRepository.findByValue(INITIAL_LEVEL); - } - - @Override - public Level findByValue(final int value) { - return levelRepository.findByValue(value); - } -} 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 39c62502..d19e31c8 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 @@ -4,9 +4,9 @@ import org.depromeet.spot.domain.member.Level; import org.depromeet.spot.domain.member.Member; -import org.depromeet.spot.usecase.port.in.member.ReadLevelUsecase; 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.member.level.ReadLevelUsecase; import org.depromeet.spot.usecase.port.in.team.ReadBaseballTeamUsecase; import org.depromeet.spot.usecase.port.out.member.MemberRepository; import org.springframework.stereotype.Service; 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 index 6ac97c9f..db8b0e93 100644 --- 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 @@ -1,23 +1,27 @@ package org.depromeet.spot.usecase.service.review; -import java.util.HashMap; import java.util.List; import java.util.Map; +import org.depromeet.spot.domain.block.Block; +import org.depromeet.spot.domain.block.BlockRow; import org.depromeet.spot.domain.member.Member; import org.depromeet.spot.domain.review.Review; -import org.depromeet.spot.domain.review.image.ReviewImage; import org.depromeet.spot.domain.review.keyword.Keyword; -import org.depromeet.spot.domain.review.keyword.ReviewKeyword; import org.depromeet.spot.domain.seat.Seat; +import org.depromeet.spot.domain.section.Section; +import org.depromeet.spot.domain.stadium.Stadium; +import org.depromeet.spot.usecase.port.in.block.ReadBlockRowUsecase; 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.review.ReadReviewUsecase; +import org.depromeet.spot.usecase.port.in.section.SectionReadUsecase; +import org.depromeet.spot.usecase.port.in.stadium.StadiumReadUsecase; import org.depromeet.spot.usecase.port.out.member.MemberRepository; -import org.depromeet.spot.usecase.port.out.review.BlockTopKeywordRepository; -import org.depromeet.spot.usecase.port.out.review.KeywordRepository; import org.depromeet.spot.usecase.port.out.review.ReviewRepository; import org.depromeet.spot.usecase.port.out.seat.SeatRepository; +import org.depromeet.spot.usecase.service.review.processor.ReviewImageProcessor; +import org.depromeet.spot.usecase.service.review.processor.ReviewKeywordProcessor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,16 +36,18 @@ public class CreateReviewService implements CreateReviewUsecase { private final SeatRepository seatRepository; private final MemberRepository memberRepository; private final ReviewRepository reviewRepository; - private final KeywordRepository keywordRepository; - private final BlockTopKeywordRepository blockTopKeywordRepository; private final UpdateMemberUsecase updateMemberUsecase; private final ReadReviewUsecase readReviewUsecase; + private final StadiumReadUsecase stadiumReadUsecase; + private final SectionReadUsecase sectionReadUsecase; + private final ReadBlockRowUsecase readBlockRowUsecase; + private final ReviewImageProcessor reviewImageProcessor; + private final ReviewKeywordProcessor reviewKeywordProcessor; @Override @Transactional public CreateReviewResult create( Long blockId, Integer seatNumber, Long memberId, CreateReviewCommand command) { - // ToDo: orElseThrow not found exception 처리하기 Member member = memberRepository.findById(memberId); Seat seat = seatRepository.findByIdWith(blockId, seatNumber); @@ -49,14 +55,15 @@ public CreateReviewResult create( Review review = convertToDomain(seat, member, command); // review 도메인에 keyword와 image를 추가 - Map keywordMap = processKeywords(review, command.good(), command.bad()); - processImages(review, command.images()); + Map keywordMap = + reviewKeywordProcessor.processKeywords(review, command.good(), command.bad()); + reviewImageProcessor.processImages(review, command.images()); // 저장 및 blockTopKeyword에도 count 업데이트 Review savedReview = reviewRepository.save(review); // BlockTopKeyword 업데이트 및 생성 - updateBlockTopKeywords(savedReview); + reviewKeywordProcessor.updateBlockTopKeywords(savedReview); savedReview.setKeywordMap(keywordMap); @@ -66,6 +73,38 @@ public CreateReviewResult create( return new CreateReviewResult(savedReview, levelUpdateMember, seat); } + @Override + @Transactional + public void createAdmin( + long stadiumId, + String blockCode, + int rowNumber, + Long memberId, + CreateAdminReviewCommand command) { + Member member = memberRepository.findById(memberId); + BlockRow blockRow = readBlockRowUsecase.findBy(stadiumId, blockCode, rowNumber); + Block block = blockRow.getBlock(); + Seat seat = getSeat(block.getId(), command.seatNumber()); + + Review review = convertToDomain(member, blockRow, seat, command); + Review savedReview = reviewRepository.save(review); + + List imageUrls = reviewImageProcessor.getImageUrl(command.images()); + reviewImageProcessor.processImages(review, imageUrls); + + Map keywordMap = + reviewKeywordProcessor.processKeywords(review, command.good(), command.bad()); + reviewKeywordProcessor.updateBlockTopKeywords(savedReview); + savedReview.setKeywordMap(keywordMap); + + calculateMemberLevel(member); + } + + private Seat getSeat(long blockId, Integer seatNumber) { + if (seatNumber == null) return null; + else return seatRepository.findByIdWith(blockId, seatNumber); + } + private Review convertToDomain(Seat seat, Member member, CreateReviewCommand command) { return Review.builder() .member(member) @@ -79,50 +118,26 @@ private Review convertToDomain(Seat seat, Member member, CreateReviewCommand com .build(); } - public Member calculateMemberLevel(final Member member) { - final long memberReviewCnt = readReviewUsecase.countByMember(member.getId()); - return updateMemberUsecase.updateLevel(member, memberReviewCnt); - } - - private Map processKeywords( - Review review, List goodKeywords, List badKeywords) { - Map keywordMap = new HashMap<>(); - processKeywordList(review, goodKeywords, true, keywordMap); - processKeywordList(review, badKeywords, false, keywordMap); - return keywordMap; - } - - private void processKeywordList( - Review review, - List keywordContents, - boolean isPositive, - Map keywordMap) { - for (String content : keywordContents) { - Keyword keyword = - keywordRepository - .findByContent(content) - .orElseGet( - () -> - keywordRepository.save( - Keyword.create(null, content, isPositive))); - - ReviewKeyword reviewKeyword = ReviewKeyword.create(null, keyword.getId()); - review.addKeyword(reviewKeyword); - keywordMap.put(keyword.getId(), keyword); - } - } + private Review convertToDomain( + Member member, BlockRow blockRow, Seat seat, CreateAdminReviewCommand command) { + Block block = blockRow.getBlock(); + Stadium stadium = stadiumReadUsecase.findById(block.getStadiumId()); + Section section = sectionReadUsecase.findById(block.getSectionId()); - private void processImages(Review review, List imageUrls) { - for (String url : imageUrls) { - ReviewImage image = ReviewImage.create(null, review, url); - review.addImage(image); - } + return Review.builder() + .member(member) + .stadium(stadium) + .section(section) + .block(block) + .row(blockRow) + .seat(seat) + .dateTime(command.dateTime()) + .content(command.content()) + .build(); } - private void updateBlockTopKeywords(Review review) { - for (ReviewKeyword reviewKeyword : review.getKeywords()) { - blockTopKeywordRepository.updateKeywordCount( - review.getBlock().getId(), reviewKeyword.getKeywordId()); - } + public Member calculateMemberLevel(final Member member) { + final long memberReviewCnt = readReviewUsecase.countByMember(member.getId()); + return updateMemberUsecase.updateLevel(member, memberReviewCnt); } } diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReviewImageProcessor.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReviewImageProcessor.java new file mode 100644 index 00000000..faf0bb24 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReviewImageProcessor.java @@ -0,0 +1,36 @@ +package org.depromeet.spot.usecase.service.review.processor; + +import java.util.ArrayList; +import java.util.List; + +import org.depromeet.spot.domain.media.MediaProperty; +import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.domain.review.image.ReviewImage; +import org.depromeet.spot.usecase.port.out.media.ImageUploadPort; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ReviewImageProcessor { + + private final ImageUploadPort imageUploadPort; + + public List getImageUrl(List images) { + List urls = new ArrayList<>(); + for (MultipartFile image : images) { + String name = image.getName(); + urls.add(imageUploadPort.upload(name, image, MediaProperty.REVIEW)); + } + return urls; + } + + public void processImages(Review review, List imageUrls) { + for (String url : imageUrls) { + ReviewImage image = ReviewImage.create(null, review, url); + review.addImage(image); + } + } +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReviewKeywordProcessor.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReviewKeywordProcessor.java new file mode 100644 index 00000000..88827f9d --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/review/processor/ReviewKeywordProcessor.java @@ -0,0 +1,58 @@ +package org.depromeet.spot.usecase.service.review.processor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.depromeet.spot.domain.review.Review; +import org.depromeet.spot.domain.review.keyword.Keyword; +import org.depromeet.spot.domain.review.keyword.ReviewKeyword; +import org.depromeet.spot.usecase.port.out.review.BlockTopKeywordRepository; +import org.depromeet.spot.usecase.port.out.review.KeywordRepository; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ReviewKeywordProcessor { + + private final KeywordRepository keywordRepository; + private final BlockTopKeywordRepository blockTopKeywordRepository; + + public Map processKeywords( + Review review, List goodKeywords, List badKeywords) { + Map keywordMap = new HashMap<>(); + processKeywordList(review, goodKeywords, true, keywordMap); + processKeywordList(review, badKeywords, false, keywordMap); + return keywordMap; + } + + // FIXME: 쿼리 호출 부분 개선 필요 + private void processKeywordList( + Review review, + List keywordContents, + boolean isPositive, + Map keywordMap) { + for (String content : keywordContents) { + Keyword keyword = + keywordRepository + .findByContent(content) + .orElseGet( + () -> + keywordRepository.save( + Keyword.create(null, content, isPositive))); + + ReviewKeyword reviewKeyword = ReviewKeyword.create(null, keyword.getId()); + review.addKeyword(reviewKeyword); + keywordMap.put(keyword.getId(), keyword); + } + } + + public void updateBlockTopKeywords(Review review) { + for (ReviewKeyword reviewKeyword : review.getKeywords()) { + blockTopKeywordRepository.updateKeywordCount( + review.getBlock().getId(), reviewKeyword.getKeywordId()); + } + } +} 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 df9065c5..b6638b95 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 @@ -38,6 +38,11 @@ public void checkIsExistsInStadium(final Long stadiumId, final Long sectionId) { } } + @Override + public Section findById(final Long id) { + return sectionRepository.findById(id); + } + @Override public boolean existsInStadium(final Long stadiumId, final Long sectionId) { return sectionRepository.existsInStadium(stadiumId, sectionId); diff --git a/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeSectionRepository.java b/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeSectionRepository.java index 1f8da668..702f0145 100644 --- a/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeSectionRepository.java +++ b/usecase/src/test/java/org/depromeet/spot/usecase/service/fake/FakeSectionRepository.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.section.SectionException.SectionNotFoundException; import org.depromeet.spot.domain.section.Section; import org.depromeet.spot.usecase.port.out.section.SectionRepository; @@ -50,4 +52,13 @@ public boolean existsInStadium(Long stadiumId, Long sectionId) { .filter(section -> section.getStadiumId().equals(sectionId)) .anyMatch(section -> section.getId().equals(sectionId)); } + + @Override + public Section findById(Long id) { + return getById(id).orElseThrow(SectionNotFoundException::new); + } + + private Optional
getById(Long id) { + return data.stream().filter(section -> section.getId().equals(id)).findAny(); + } }