Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BSVR-118] 블록별 리뷰 조회 API 구현 #23

Merged
merged 27 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0adfcf7
refactor: 도메인에 builder 패턴 적용, status->deletedAt으로 타입과 이름 변경
pminsung12 Jul 13, 2024
c128167
refactor: review entity에 base entity 적용
pminsung12 Jul 13, 2024
1ac2fbb
fix: review entity 필드 수정, blockTopKeyword base entity 상속받도록 개선
pminsung12 Jul 13, 2024
709d2e6
feat: stadiumId 추가
pminsung12 Jul 13, 2024
e39e01b
fix: deletedAt 필드에 nullable false 삭제
pminsung12 Jul 13, 2024
b180a77
feat: 블록별 리뷰조회 요청 dto 구현
pminsung12 Jul 14, 2024
66b002e
feat: 블록별 리뷰조회 응답 dto 구현
pminsung12 Jul 14, 2024
ff72e1b
feat: dto 역할에 해당하는 도메인 구현
pminsung12 Jul 14, 2024
57a9601
feat: 리뷰 이미지 배열과 리뷰 키워드 배열 도메인에 추가
pminsung12 Jul 14, 2024
2f69b01
feat: queryfactory를 통한 리뷰 커스텀 리포지토리 구현
pminsung12 Jul 14, 2024
4789fbe
feat: 리뷰 리포지토리 인터페이스와 구현체 구현
pminsung12 Jul 14, 2024
81d40bd
feat: 리뷰 조회 유스케이스 인터페이스 구현
pminsung12 Jul 14, 2024
81ac814
feat: 리뷰 조회 유스케이스 인터페이스를 구현한 리뷰 조회 서비스 구현
pminsung12 Jul 14, 2024
e3b0dbc
feat: 리뷰 조회 컨트롤러 구현
pminsung12 Jul 14, 2024
ba5e533
feat: 테스트위한 리뷰쪽 더미데이터 삽입
pminsung12 Jul 14, 2024
e90bd50
feat: custom exceptiond을 통한 레포지토리에서 예외처리
pminsung12 Jul 14, 2024
d651526
test: 모킹을 통한 조회서비스 테스트
pminsung12 Jul 14, 2024
f9d25e4
refactor: 상위 5개 블록의 매개변수 5 상수화
pminsung12 Jul 15, 2024
effc1a9
refactor: 의미없는 코드 삭제
pminsung12 Jul 15, 2024
faca081
refactor: 메서드 반환형 int->Long으로 수정
pminsung12 Jul 15, 2024
9a1586b
test: 메서드 반환형 int->Long으로 수정
pminsung12 Jul 15, 2024
bd4024e
refactor: 리뷰 엔티티 필드명 오타 수정
pminsung12 Jul 15, 2024
f337af0
fix: totalCount 데이터 타입 Long으로 변경
pminsung12 Jul 15, 2024
dfea456
refactor: exception 처리 삭제 및 팩터리 메서드로 코드 개선
pminsung12 Jul 15, 2024
4ba4f51
Merge branch 'main' into feat/BSVR-118
pminsung12 Jul 15, 2024
070e154
fix: base entity 충돌 해결
pminsung12 Jul 15, 2024
ae42f0e
fix: splotless 적용
pminsung12 Jul 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.depromeet.spot.application.review;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;

import org.depromeet.spot.application.review.dto.response.ReviewListResponse;
import org.depromeet.spot.domain.review.ReviewListResult;
import org.depromeet.spot.usecase.port.in.review.ReviewReadUsecase;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

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;

@RestController
@Tag(name = "리뷰")
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class ReviewReadController {

private final ReviewReadUsecase reviewReadUsecase;

@ResponseStatus(HttpStatus.OK)
@GetMapping("/stadiums/{stadiumId}/blocks/{blockId}/reviews")
@Operation(summary = "특정 야구장의 특정 블록에 대한 리뷰 목록을 조회한다.")
public ReviewListResponse findReviewsByBlockId(
@PathVariable("stadiumId")
@NotNull
@Positive
@Parameter(description = "야구장 PK", required = true)
Long stadiumId,
@PathVariable("blockId")
@NotNull
@Positive
@Parameter(description = "블록 PK", required = true)
Long blockId,
@RequestParam(required = false) @Parameter(description = "열 ID (필터링)") Long rowId,
@RequestParam(required = false) @Parameter(description = "좌석 번호 (필터링)") Long seatNumber,
@RequestParam(defaultValue = "0")
@PositiveOrZero
@Parameter(description = "시작 위치 (기본값: 0)")
int offset,
Comment on lines +43 to +46
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요건 단순 질문인데, offset = null로 들어오면

  1. defaultValue가 0으로 세팅되는거야
  2. @PositiveOrZero에 걸려서 exception이 발생하는거야 ?.?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null로 들어오면 0으로 세팅돼!

  1. positiveOrZero로 0이상의 값만 받도록 -> 음수면 exception발생!
  2. 요청에 null로 들어오면 디폴트 0으로 세팅

@RequestParam(defaultValue = "10")
@Positive
@Max(50)
@Parameter(description = "조회할 리뷰 수 (기본값: 10, 최대: 50)")
int limit) {

ReviewListResult result =
reviewReadUsecase.findReviewsByBlockId(
stadiumId, blockId, rowId, seatNumber, offset, limit);
return ReviewListResponse.from(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.depromeet.spot.application.review.dto.request;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.PositiveOrZero;

import io.swagger.v3.oas.annotations.Parameter;

public record ReviewBlockRequest(
@Parameter(description = "열 ID (필터링)") Long rowId,
@Parameter(description = "좌석 번호 (필터링)") Long seatNumber,
@PositiveOrZero @Parameter(description = "시작 위치 (기본값: 0)") int offset,
@Max(50) @Parameter(description = "조회할 리뷰 수 (기본값: 10, 최대: 50)") int limit) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.depromeet.spot.application.review.dto.response;

import java.util.List;
import java.util.stream.Collectors;

import org.depromeet.spot.domain.review.ReviewListResult;

public record ReviewListResponse(
List<KeywordCountResponse> keywords,
List<ReviewResponse> reviews,
Long totalCount,
int filteredCount,
int offset,
int limit,
boolean hasMore,
FilterInfo filter) {
public static ReviewListResponse from(ReviewListResult result) {
List<ReviewResponse> reviewResponses =
result.reviews().stream().map(ReviewResponse::from).collect(Collectors.toList());

List<KeywordCountResponse> keywordResponses =
result.topKeywords().stream()
.map(kc -> new KeywordCountResponse(kc.content(), kc.count()))
.collect(Collectors.toList());

boolean hasMore = (result.offset() + result.reviews().size()) < result.totalCount();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JPA에서 제공하는 Page에 hasNext()가 있는데, 혹시 hasMore을 직접 계산한 이유가 있을까?

FilterInfo filter = new FilterInfo(null, null); // Assuming no filter info for now
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필터에는 항상 null 값이 들어가는 것 같은데, 이번 티켓에 포함된 이유가 있을까?!
어떤 역할을 하는 친구인지 궁금해요


return new ReviewListResponse(
keywordResponses,
reviewResponses,
result.totalCount(),
result.reviews().size(),
result.offset(),
result.limit(),
hasMore,
filter);
}

record KeywordCountResponse(String content, Long count) {}

record FilterInfo(Long rowId, Integer seatNumber) {}
}
Comment on lines +40 to +43
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

얘들은 딱히 밖에서 사용할 것 같지 않아서 중첩 클래스?레코드?로 넣어놨어.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.depromeet.spot.application.review.dto.response;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

import org.depromeet.spot.domain.review.Review;
import org.depromeet.spot.domain.review.ReviewImage;
import org.depromeet.spot.domain.review.ReviewKeyword;

public record ReviewResponse(
Long id,
Long userId,
Long blockId,
Long seatId,
Long rowId,
Long seatNumber,
LocalDateTime date,
String content,
LocalDateTime createdAt,
LocalDateTime updatedAt,
List<ImageResponse> images,
List<KeywordResponse> keywords) {
public static ReviewResponse from(Review review) {
return new ReviewResponse(
review.getId(),
review.getUserId(),
review.getBlockId(),
review.getSeatId(),
review.getRowId(),
review.getSeatNumber(),
review.getDateTime(),
review.getContent(),
review.getCreatedAt(),
review.getUpdatedAt(),
mapToImageResponses(review.getImages()),
mapToKeywordResponses(review.getKeywords()));
}

private static List<ImageResponse> mapToImageResponses(List<ReviewImage> images) {
return images.stream()
.map(image -> new ImageResponse(image.getId(), image.getUrl()))
.collect(Collectors.toList());
}

private static List<KeywordResponse> mapToKeywordResponses(List<ReviewKeyword> keywords) {
return keywords.stream()
.map(
keyword ->
new KeywordResponse(
keyword.getId(),
keyword.getKeywordId(),
keyword.getIsPositive()))
.collect(Collectors.toList());
}

record ImageResponse(Long id, String url) {}

record KeywordResponse(Long id, Long keywordId, Boolean isPositive) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.depromeet.spot.common.exception.review;

import org.depromeet.spot.common.exception.ErrorCode;
import org.springframework.http.HttpStatus;

import lombok.Getter;

@Getter
public enum ReviewErrorCode implements ErrorCode {
REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "RV001", "요청한 리뷰를 찾을 수 없습니다."),
INVALID_REVIEW_DATA(HttpStatus.BAD_REQUEST, "RV002", "유효하지 않은 리뷰 데이터입니다.");

private final HttpStatus status;
private final String code;
private String message;

ReviewErrorCode(HttpStatus status, String code, String message) {
this.status = status;
this.code = code;
this.message = message;
}

public ReviewErrorCode appended(Object o) {
message = message + " {" + o.toString() + "}";
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.depromeet.spot.common.exception.review;

import org.depromeet.spot.common.exception.BusinessException;

public abstract class ReviewException extends BusinessException {
protected ReviewException(ReviewErrorCode errorCode) {
super(errorCode);
}

public static class ReviewNotFoundException extends ReviewException {
public ReviewNotFoundException() {
super(ReviewErrorCode.REVIEW_NOT_FOUND);
}

public ReviewNotFoundException(String s) {
super(ReviewErrorCode.REVIEW_NOT_FOUND.appended(s));
}
}

public static class InvalidReviewDataException extends ReviewException {
public InvalidReviewDataException() {
super(ReviewErrorCode.INVALID_REVIEW_DATA);
}

public InvalidReviewDataException(String str) {
super(ReviewErrorCode.INVALID_REVIEW_DATA.appended(str));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package org.depromeet.spot.domain.block;

import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
public class BlockTopKeyword {
private final Long id;
private final Long blockId;
private final Long keywordId;
private final Long count;

public BlockTopKeyword(Long id, Long blockId, Long keywordId, Long count) {
this.id = id;
this.blockId = blockId;
this.keywordId = keywordId;
this.count = count;
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package org.depromeet.spot.domain.review;

import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
public class Keyword {

private final Long id;
private final String content;

public Keyword(Long id, String content) {
this.id = id;
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.depromeet.spot.domain.review;

public record KeywordCount(String content, Long count) {}
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
package org.depromeet.spot.domain.review;

import java.time.LocalDateTime;
import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
@AllArgsConstructor
public class Review {

private final Long id;
private final Long userId;
private final Long stadiumId;
private final Long blockId;
private final Long seatId;
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 String status;
private final LocalDateTime deletedAt;
private final List<ReviewImage> images;
private final List<ReviewKeyword> keywords;
Comment on lines +26 to +27
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

도메인 상에서 image와 keyword는 배열로 관리해줄 수 있도록 필드를 추가했어

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,16 @@

import java.time.LocalDateTime;

import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
public class ReviewImage {

private final Long id;
private final Long reviewId;
private final String url;
private final LocalDateTime createdAt;
private final String status;

public ReviewImage(Long id, Long reviewId, String url, LocalDateTime createdAt, String status) {
this.id = id;
this.reviewId = reviewId;
this.url = url;
this.createdAt = createdAt;
this.status = status;
}
private final LocalDateTime deletedAt;
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
package org.depromeet.spot.domain.review;

import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
public class ReviewKeyword {

private final Long id;
private final Long reviewId;
private final Long keywordId;
private final Boolean isPositive;

public ReviewKeyword(Long id, Long reviewId, Long keywordId, Boolean isPositive) {
this.id = id;
this.reviewId = reviewId;
this.keywordId = keywordId;
this.isPositive = isPositive;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.depromeet.spot.domain.review;

import java.util.List;

public record ReviewListResult(
List<Review> reviews,
List<KeywordCount> topKeywords,
Long totalCount,
int offset,
int limit) {}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public static BlockTopKeywordEntity from(BlockTopKeyword blockTopKeyword) {
}

public BlockTopKeyword toDomain() {
return new BlockTopKeyword(this.getId(), blockId, keywordId, count);
return BlockTopKeyword.builder()
.id(this.getId())
.blockId(blockId)
.keywordId(keywordId)
.count(count)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ public static KeywordEntity from(Keyword keyword) {
}

public Keyword toDomain() {
return new Keyword(this.getId(), content);
return Keyword.builder().id(this.getId()).content(content).build();
}
}
Loading
Loading