Skip to content

Commit

Permalink
[BE] feat: 내가 받은 리뷰 보기 기능 구현 (#109)
Browse files Browse the repository at this point in the history
* refactor: contains 작동을 위한 EqualsAndHashcode 추가

* fix: lazyInitialization 해결

* feat: 질문 레포지토리 생성

* feat: 내가 받은 리뷰 응답 생성

* refactor: 리뷰 항목과 질문의 연관관계 변경 및 답변 최대 글자수 DB에 반영

* refactor: 리뷰에 리뷰그룹 초기화 부분 추가

* feat: 내가 받은 리뷰 조회 기능 구현

* feat: 받은 리뷰가 없을 때의 응답 추가

* refactor: dto 설명 추가

* refactor: dto 설명 수정

* refactor: 인자 형식 수정, 개행 수정

* refactor: transactional 어노테이션 추가

* refactor: 내가 받은 리뷰 조회할 때Page객체 말고 List로 받아오도록 수정

* refactor: 미리보기 만드는 기능 도메인 안으로 이동

* test: 테스트 코드 개선

- 변수명 수정, save 여러개 대신 saveAll 사용 등

* refactor: 마지막으로 본 리뷰ID가 없는 로직에 대해 수정

- lastViewedReviewId를 입력하지 않으면 999같이 이상하게 큰 수를 넣어주는게 아니라, 가장 큰 값을 넣어주도록 수정

* docs: 스웨거 데코레이션 적용

* refactor: lastReviewId가 null 이어도 가장 최신 리뷰를 찾을 수 있도록 수정

* refactor: eqaulsAndHashCode 재정의

* refactor: eqaulsAndHashCode 재재정의

* refactor: API Docs 반영

---------

Co-authored-by: donghoony <[email protected]>
  • Loading branch information
nayonsoso and donghoony authored Jul 25, 2024
1 parent ee51a36 commit b4a068b
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 15 deletions.
3 changes: 2 additions & 1 deletion backend/src/main/java/reviewme/keyword/domain/Keywords.java
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);
}
}
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
10 changes: 7 additions & 3 deletions backend/src/main/java/reviewme/review/domain/ReviewContent.java
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
@JoinColumn(name = "question_id", nullable = false)
private Question question;

@Column(name = "answer", nullable = false)
@Column(name = "answer", nullable = false, length = MAX_ANSWER_LENGTH)
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;

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

@Schema(description = "키워드 아이디")
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 = "리뷰어 그룹 정보 응답")
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);
}
60 changes: 56 additions & 4 deletions backend/src/main/java/reviewme/review/service/ReviewService.java
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()
.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

0 comments on commit b4a068b

Please sign in to comment.