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

[BE] feat: 내가 받은 리뷰 보기 기능 구현 #109

Merged
merged 24 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f522fd7
refactor: contains 작동을 위한 EqualsAndHashcode 추가
nayonsoso Jul 25, 2024
85ddfd2
fix: lazyInitialization 해결
nayonsoso Jul 25, 2024
a0ab043
feat: 질문 레포지토리 생성
nayonsoso Jul 25, 2024
b08db88
feat: 내가 받은 리뷰 응답 생성
nayonsoso Jul 25, 2024
4f18dda
refactor: 리뷰 항목과 질문의 연관관계 변경 및 답변 최대 글자수 DB에 반영
nayonsoso Jul 25, 2024
65a7917
refactor: 리뷰에 리뷰그룹 초기화 부분 추가
nayonsoso Jul 25, 2024
a8f2528
feat: 내가 받은 리뷰 조회 기능 구현
nayonsoso Jul 25, 2024
022910c
feat: 받은 리뷰가 없을 때의 응답 추가
nayonsoso Jul 25, 2024
453d1f4
refactor: dto 설명 추가
nayonsoso Jul 25, 2024
92ea554
refactor: dto 설명 수정
nayonsoso Jul 25, 2024
f2e1a0d
refactor: 인자 형식 수정, 개행 수정
nayonsoso Jul 25, 2024
bf00a66
refactor: transactional 어노테이션 추가
nayonsoso Jul 25, 2024
aa87cbd
refactor: 내가 받은 리뷰 조회할 때Page객체 말고 List로 받아오도록 수정
nayonsoso Jul 25, 2024
4a491fd
refactor: 미리보기 만드는 기능 도메인 안으로 이동
nayonsoso Jul 25, 2024
6fcbc6d
test: 테스트 코드 개선
nayonsoso Jul 25, 2024
010db8e
refactor: 마지막으로 본 리뷰ID가 없는 로직에 대해 수정
nayonsoso Jul 25, 2024
3a229bb
docs: 스웨거 데코레이션 적용
nayonsoso Jul 25, 2024
412523e
refactor: lastReviewId가 null 이어도 가장 최신 리뷰를 찾을 수 있도록 수정
nayonsoso Jul 25, 2024
1499f37
Merge remote-tracking branch 'refs/remotes/origin/develop' into 107-g…
donghoony Jul 25, 2024
bdc847b
Merge remote-tracking branch 'refs/remotes/origin/develop' into 107-g…
donghoony Jul 25, 2024
af8cf09
Merge remote-tracking branch 'origin/107-get-my-received-review' into…
donghoony Jul 25, 2024
1fb6465
refactor: eqaulsAndHashCode 재정의
nayonsoso Jul 25, 2024
c8e3cbe
refactor: eqaulsAndHashCode 재재정의
nayonsoso Jul 25, 2024
065f752
refactor: API Docs 반영
donghoony Jul 25, 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
Expand Up @@ -3,6 +3,7 @@
import jakarta.persistence.CollectionTable;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import java.util.Collections;
import java.util.List;
Expand All @@ -21,7 +22,7 @@ public class Keywords {

private static final int MAX_KEYWORD_COUNT = 5;

@ElementCollection
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "review_keyword", joinColumns = @JoinColumn(name = "review_id"))
private Set<Long> keywordIds;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.util.Objects;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "github_id_reviewer_group")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(of = "id")
@Getter
public class GithubIdReviewerGroup {

Expand All @@ -35,4 +34,27 @@ public GithubIdReviewerGroup(GithubId githubId, ReviewerGroup reviewerGroup) {
this.githubId = githubId;
this.reviewerGroup = reviewerGroup;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof GithubIdReviewerGroup githubIdReviewerGroup)) {
return false;
}
if (id == null) {
return Objects.equals(githubId, githubIdReviewerGroup.githubId);
}

return Objects.equals(id, githubIdReviewerGroup.id);
}

@Override
public int hashCode() {
if (id == null) {
return Objects.hash(githubId);
}
return Objects.hash(id);
}
}
13 changes: 11 additions & 2 deletions backend/src/main/java/reviewme/review/controller/ReviewApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import reviewme.review.dto.response.ReviewCreationResponse;
import reviewme.review.dto.request.CreateReviewRequest;
import reviewme.review.dto.response.ReceivedReviewsResponse;
import reviewme.review.dto.response.ReviewCreationResponse;
import reviewme.review.dto.response.ReviewDetailResponse;

@Tag(name = "리뷰 관리")
Expand All @@ -28,7 +29,15 @@ public interface ReviewApi {
ResponseEntity<ReviewDetailResponse> findReview(@PathVariable long id, @RequestParam long memberId);

@Operation(
summary = "리뷰 생성 시 필요한 정보 조회",
summary = "내가 받은 리뷰 조회",
description = "내가 받은 리뷰를 조회한다. (로그인을 구현하지 않은 지금 시점에서는 memberId로 로그인했다고 가정한다.)"
)
ResponseEntity<ReceivedReviewsResponse> findMyReceivedReview(@RequestParam long memberId,
@RequestParam(required = false) Long lastReviewId,
@RequestParam(defaultValue = "10") int size);

@Operation(
summary = "리뷰 작성 정보 조회",
description = "리뷰 생성 시 필요한 정보를 조회한다."
)
ResponseEntity<ReviewCreationResponse> findReviewCreationSetup(@RequestParam long reviewerGroupId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reviewme.review.dto.response.ReviewCreationResponse;
import reviewme.review.dto.request.CreateReviewRequest;
import reviewme.review.dto.response.ReceivedReviewsResponse;
import reviewme.review.dto.response.ReviewCreationResponse;
import reviewme.review.dto.response.ReviewDetailResponse;
import reviewme.review.service.ReviewService;

@RestController
@RequiredArgsConstructor
public class ReviewController implements ReviewApi{
public class ReviewController implements ReviewApi {

private final ReviewService reviewService;

Expand All @@ -39,4 +40,12 @@ public ResponseEntity<ReviewCreationResponse> findReviewCreationSetup(@RequestPa
ReviewCreationResponse response = reviewService.findReviewCreationSetup(reviewerGroupId);
return ResponseEntity.ok(response);
}

@GetMapping("/reviews")
public ResponseEntity<ReceivedReviewsResponse> findMyReceivedReview(@RequestParam long memberId,
@RequestParam(required = false) Long lastReviewId,
@RequestParam(defaultValue = "10") int size) {
ReceivedReviewsResponse myReceivedReview = reviewService.findMyReceivedReview(memberId, lastReviewId, size);
return ResponseEntity.ok(myReceivedReview);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

우리 디스코드에 공유한 API 문서에서는 요청 URL을 이렇게 받더라고요.
/reviews?revieweeId=?&lastReviewId=?&memberId=? (memberId 로 로그인했다고 가정)
그런데 지금은 '내가 받은 리뷰 조회' 이니까 revieweeId 랑 memberId가 같은 역할을 하는 중복 데이터라 생각해서 일단은 memberId 를 통해서만 조회하도록 했습니다. revieweeId는 무시하도록!

우선은 그냥 단순히 특정 회원의 리뷰만 조회한다고 생각하면 될까요?
(누구든지 memberId를 넣으면 다른 사람의 리뷰를 조회 가능!)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

그춍.. 사실 목표한 기능은 '내가 받은 리뷰 보기'이지만
로그인 기능이 없고, memberId 로 로그인한다고 가정하고 있는 상황이니깐요😓

Copy link
Contributor

Choose a reason for hiding this comment

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

다음 리팩터링 사항 논의 때 고려해보죠~

2 changes: 1 addition & 1 deletion backend/src/main/java/reviewme/review/domain/Review.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ public Review(Member reviewer, Member reviewee, ReviewerGroup reviewerGroup,
}
this.reviewer = reviewer;
this.reviewee = reviewee;
this.reviewerGroup = reviewerGroup;
this.reviewContents = new ArrayList<>();
this.keywords = new Keywords(keywords);
this.createdAt = createdAt;
reviewerGroup.addReview(this);
this.reviewerGroup = reviewerGroup;
this.isPublic = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
Expand All @@ -22,6 +21,7 @@ public class ReviewContent {

private static final int MIN_ANSWER_LENGTH = 20;
private static final int MAX_ANSWER_LENGTH = 1_000;
private static final int REVIEW_CONTENT_PREVIEW_MAX_LENGTH = 150;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -31,11 +31,11 @@ public class ReviewContent {
@JoinColumn(name = "review_id", nullable = false)
private Review review;

@OneToOne
@ManyToOne
Copy link
Contributor

Choose a reason for hiding this comment

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

요구사항대로 잘 되었네요 👍🏻

@JoinColumn(name = "question_id", nullable = false)
private Question question;

@Column(name = "answer", nullable = false)
@Column(name = "answer", nullable = false, length = MAX_ANSWER_LENGTH)
Copy link
Contributor

Choose a reason for hiding this comment

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

DB에 글자수 조건 반영 좋네요!
(이 코드 관련한 건 아니지만 새삼 다른 곳에도 했나 생각해봤는데 안되어있는 것 같아서, 이 부분도 맞춰줘야하겠다 싶어요. 아래 예시)

@Entity
public class ReviewerGroup {

    @Column(name = "description", nullable = false)
    private String description;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

헉스 그런데 제가 하면 혹시라도 conflict 날까봐 리팩터링 목록에 넣어두겠습니다!

#112

private String answer;

public ReviewContent(Review review, Question question, String answer) {
Expand All @@ -52,6 +52,10 @@ private void validateAnswerLength(String answer) {
}
}

public String getAnswerPreview() {
return answer.substring(0, REVIEW_CONTENT_PREVIEW_MAX_LENGTH);
}

public String getQuestion() {
return question.getContent();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package reviewme.review.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

Copy link
Contributor

Choose a reason for hiding this comment

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

위쪽에 어떤 응답인지 @Schema 달아주세요~!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

헉 속성에만 추가하고, dto 자체에는 안달아줬네요 😱
추가하고 푸쉬했습니다!

@Schema(description = "선택된 키워드 응답")
public record ReceivedReviewKeywordsResponse(

@Schema(description = "키워드 아이디")
Copy link
Contributor

Choose a reason for hiding this comment

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

다른 곳에서는 아이디를 "ID"로 표기했던 것 같은데, "아이디" 또는 "ID"로 통일해야 할 것 같아요.

Copy link
Contributor Author

@nayonsoso nayonsoso Jul 25, 2024

Choose a reason for hiding this comment

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

image
스크린샷 2024-07-25 오후 3 46 25

웁스 그렇네요!! 이슈 만들었습니다
#112

long id,

@Schema(description = "키워드 내용")
String content
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package reviewme.review.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import java.util.List;

@Schema(description = "리뷰 내용 응답")
public record ReceivedReviewResponse(

@Schema(description = "리뷰 아이디")
long id,

@Schema(description = "공개 여부")
boolean isPublic,

@Schema(description = "리뷰 작성일")
LocalDate createdAt,

@Schema(description = "응답 내용 미리보기")
String contentPreview,

@Schema(description = "리뷰어 그룹 정보")
ReceivedReviewReviewerGroupResponse reviewerGroup,

@Schema(description = "키워드")
List<ReceivedReviewKeywordsResponse> keywords
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package reviewme.review.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "리뷰어 그룹 정보 응답")
Copy link
Contributor

Choose a reason for hiding this comment

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

리뷰어 그룹 정보를 내보내는 DTO가 많기에 "리뷰 목록의 리뷰어 그룹 정보 응답"으로 컨텍스트를 추가해주는 건 어떨까요?
(사실 Swagger에서 이 정보를 어느정도 중요도있게 받아들이는지 잘 감이 안와서 한편으로는 제가 제안한 컨텍스트 추가가 과한가? 싶기도 합니다. 편하게 의견 알려주세요!)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

스웨거에서 볼 때 JSON 안에서 감싸져서 보여질 것이기 때문에 별도의 맥락 추가는 없어도 괜찮을 것 같아요 ㅎㅎ(귀찮은 것 아님)

public record ReceivedReviewReviewerGroupResponse(

@Schema(description = "리뷰어 그룹 아이디")
long id,

@Schema(description = "리뷰어 그룹 이름")
String name,

@Schema(description = "리뷰어 그룹 썸네일 이미지 URL")
String thumbnailUrl
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package reviewme.review.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;

@Schema(description = "내가 받은 리뷰 응답")
public record ReceivedReviewsResponse(

@Schema(description = "응답 개수")
long size,

@Schema(description = "마지막 리뷰 아이디")
long lastReviewId,

@Schema(description = "받은 리뷰 목록")
List<ReceivedReviewResponse> reviews
) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package reviewme.review.repository;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import reviewme.member.domain.Member;
import reviewme.member.domain.ReviewerGroup;
Expand All @@ -15,4 +17,15 @@ public interface ReviewRepository extends JpaRepository<Review, Long> {
default Review getReviewById(Long id) {
return findById(id).orElseThrow(ReviewNotFoundException::new);
}

@Query("""
SELECT r
FROM Review r
WHERE r.reviewee.id = :revieweeId
AND :lastViewedReviewId IS NULL OR r.id < :lastViewedReviewId
ORDER BY r.createdAt DESC
LIMIT :size
"""
)
List<Review> findLimitedReviewsWrittenForReviewee(long revieweeId, Long lastViewedReviewId, int size);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@
import reviewme.keyword.service.KeywordService;
import reviewme.member.domain.Member;
import reviewme.member.domain.ReviewerGroup;
import reviewme.member.dto.response.ReviewCreationReviewerGroupResponse;
import reviewme.member.repository.MemberRepository;
import reviewme.member.repository.ReviewerGroupRepository;
import reviewme.member.service.ReviewerGroupService;
import reviewme.review.domain.Question;
import reviewme.review.domain.Review;
import reviewme.review.domain.ReviewContent;
import reviewme.review.dto.response.ReviewCreationResponse;
import reviewme.review.dto.response.QuestionResponse;
import reviewme.member.dto.response.ReviewCreationReviewerGroupResponse;
import reviewme.review.dto.request.CreateReviewRequest;
import reviewme.review.dto.response.QuestionResponse;
import reviewme.review.dto.response.ReceivedReviewKeywordsResponse;
import reviewme.review.dto.response.ReceivedReviewResponse;
import reviewme.review.dto.response.ReceivedReviewReviewerGroupResponse;
import reviewme.review.dto.response.ReceivedReviewsResponse;
import reviewme.review.dto.response.ReviewCreationResponse;
import reviewme.review.dto.response.ReviewDetailResponse;
import reviewme.review.dto.response.ReviewDetailReviewContentResponse;
import reviewme.review.dto.response.ReviewDetailReviewerGroupResponse;
Expand Down Expand Up @@ -112,9 +116,57 @@ public ReviewDetailResponse findReview(long reviewId, long memberId) {

@Transactional(readOnly = true)
public ReviewCreationResponse findReviewCreationSetup(long reviewerGroupId) {
ReviewCreationReviewerGroupResponse reviewerGroup = reviewerGroupService.findReviewCreationReviewerGroup(reviewerGroupId);
ReviewCreationReviewerGroupResponse reviewerGroup = reviewerGroupService.findReviewCreationReviewerGroup(
reviewerGroupId);
List<QuestionResponse> questions = questionService.findAllQuestions();
List<KeywordResponse> keywords = keywordService.findAllKeywords();
return new ReviewCreationResponse(reviewerGroup, questions, keywords);
}

@Transactional(readOnly = true)
public ReceivedReviewsResponse findMyReceivedReview(long memberId, Long lastReviewId, int size) {
List<Review> reviews = reviewRepository.findLimitedReviewsWrittenForReviewee(memberId, lastReviewId, size);

if (reviews.isEmpty()) {
return new ReceivedReviewsResponse(0, 0, List.of());
}

return new ReceivedReviewsResponse(
reviews.size(),
reviews.get(reviews.size() - 1).getId(),
reviews.stream()
Copy link
Contributor

Choose a reason for hiding this comment

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

인덴트가 많아지니 따로 변수로 뽑아도 좋겠네요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

음 new XXXResponse 들이 다 map 안에서 사용되는 것들이라 분리한다면 함수로 분리해줘야 할 것 같네요!


일단 private 함수로 만들어주긴 했는데, 서비스 코드에서 dto 를 만들기 위해 필요한 함수가 많아져서 dto 안에 도메인 -> dto 함수를 만드는 것도 고려해볼 수 있는 시점 같아요

.map(this::createReceivedReviewResponse)
.toList());
}

private ReceivedReviewResponse createReceivedReviewResponse(Review review) {
return new ReceivedReviewResponse(
review.getId(),
review.isPublic(),
review.getCreatedAt().toLocalDate(),
createReviewContentPreview(review),
new ReceivedReviewReviewerGroupResponse(
review.getReviewerGroup().getId(),
review.getReviewerGroup().getGroupName(),
review.getReviewerGroup().getThumbnailUrl()
),
createKeywordResponse(review));
}

private String createReviewContentPreview(Review review) {
return reviewContentRepository.findAllByReviewId(review.getId())
.get(0)
.getAnswerPreview();
}

private List<ReceivedReviewKeywordsResponse> createKeywordResponse(Review review) {
return review.getKeywords().getKeywordIds()
.stream()
.map(keywordRepository::getKeywordById)
.map(keyword -> new ReceivedReviewKeywordsResponse(
keyword.getId(),
keyword.getContent()
))
.toList();
}
}
Loading