-
Notifications
You must be signed in to change notification settings - Fork 4
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
유저 상호 평가 api 추가 #515
유저 상호 평가 api 추가 #515
Changes from 14 commits
4191896
54a163b
f6b0ffc
bfaf87c
fb7c229
2854e7f
e398232
1b63216
8382a09
2b0196e
7f46005
9f4e982
a0da88b
024891a
5318223
762a2ab
c511d0d
f614eb0
99f067c
e5cd696
0a52c91
80e033e
ae7fbe2
5c79872
0956831
a06f221
03ef2f3
cfb2e05
88e905e
e05ba4c
7cf0cad
093ed4b
b4bfd1f
e01012a
9029205
92ae2d3
71ce00c
cc1a857
425fed3
93a5468
e6a5a8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package com.ddang.ddang.review.application; | ||
|
||
import com.ddang.ddang.auction.application.exception.AuctionNotFoundException; | ||
import com.ddang.ddang.auction.domain.Auction; | ||
import com.ddang.ddang.auction.infrastructure.persistence.JpaAuctionRepository; | ||
import com.ddang.ddang.review.application.dto.CreateReviewDto; | ||
import com.ddang.ddang.review.application.dto.ReadReviewDetailDto; | ||
import com.ddang.ddang.review.application.dto.ReadReviewDto; | ||
import com.ddang.ddang.review.application.exception.AlreadyReviewException; | ||
import com.ddang.ddang.review.application.exception.InvalidUserToReview; | ||
import com.ddang.ddang.review.domain.Review; | ||
import com.ddang.ddang.review.infrastructure.persistence.JpaReviewRepository; | ||
import com.ddang.ddang.user.application.exception.UserNotFoundException; | ||
import com.ddang.ddang.user.domain.User; | ||
import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
|
||
@Service | ||
@Transactional(readOnly = true) | ||
@RequiredArgsConstructor | ||
public class ReviewService { | ||
|
||
private final JpaReviewRepository reviewRepository; | ||
private final JpaAuctionRepository auctionRepository; | ||
private final JpaUserRepository userRepository; | ||
|
||
@Transactional | ||
public Long create(final CreateReviewDto reviewDto) { | ||
final Auction findAuction = auctionRepository.findById(reviewDto.auctionId()) | ||
.orElseThrow(() -> | ||
new AuctionNotFoundException("해당 경매를 찾을 수 없습니다.") | ||
); | ||
final User writer = userRepository.findById(reviewDto.writerId()) | ||
.orElseThrow(() -> new UserNotFoundException("작성자 정보를 찾을 수 없습니다.")); | ||
final User target = userRepository.findById(reviewDto.targetId()) | ||
.orElseThrow(() -> new UserNotFoundException("평가 상대의 정보를 찾을 수 없습니다.")); | ||
|
||
validateWriterCanReview(findAuction, writer); | ||
|
||
final Review review = reviewDto.toEntity(findAuction, writer, target); | ||
final Review persistReview = saveReviewAndUpdateReliability(review, target); | ||
|
||
return persistReview.getId(); | ||
} | ||
|
||
private void validateWriterCanReview(final Auction auction, final User writer) { | ||
if (!auction.isSellerOrWinner(writer, LocalDateTime.now())) { | ||
throw new InvalidUserToReview("경매의 판매자 또는 최종 낙찰자만 평가가 가능합니다."); | ||
} | ||
|
||
validateAlreadyReviewed(auction, writer); | ||
} | ||
|
||
private void validateAlreadyReviewed(final Auction auction, final User writer) { | ||
if (reviewRepository.existsByAuctionIdAndWriterId(auction.getId(), writer.getId())) { | ||
throw new AlreadyReviewException("이미 평가하였습니다."); | ||
} | ||
} | ||
|
||
private Review saveReviewAndUpdateReliability(final Review review, final User target) { | ||
final Review persistReview = reviewRepository.save(review); | ||
|
||
final List<Review> targetReviews = reviewRepository.findAllByTargetId(target.getId()); | ||
target.updateReliability(targetReviews); | ||
return persistReview; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 필수return을 윗 행과 개행 부탁드립니다! |
||
} | ||
|
||
public List<ReadReviewDto> readAllByTargetId(final Long targetId) { | ||
final List<Review> targetReviews = reviewRepository.findAllByTargetId(targetId); | ||
|
||
return targetReviews.stream() | ||
.map(ReadReviewDto::from) | ||
.toList(); | ||
} | ||
|
||
public ReadReviewDetailDto read(final Long writerId, final Long auctionId) { | ||
return reviewRepository.findByAuctionIdAndWriterId(auctionId, writerId) | ||
.map(ReadReviewDetailDto::from) | ||
.orElse(ReadReviewDetailDto.empty()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 칭찬칭찬해요!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!@#!@#!@#!@#2 orElse()에서 빈 값을 반환하도록 해서 깔끔하게 처리해주셨네요 |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.ddang.ddang.review.application.dto; | ||
|
||
import com.ddang.ddang.auction.domain.Auction; | ||
import com.ddang.ddang.review.domain.Review; | ||
import com.ddang.ddang.review.presentation.dto.request.CreateReviewRequest; | ||
import com.ddang.ddang.user.domain.User; | ||
|
||
public record CreateReviewDto(Long auctionId, Long writerId, Long targetId, String content, Double score) { | ||
|
||
public static CreateReviewDto of(final Long writerId, final CreateReviewRequest createReviewRequest) { | ||
return new CreateReviewDto( | ||
createReviewRequest.auctionId(), | ||
writerId, | ||
createReviewRequest.targetId(), | ||
createReviewRequest.content(), | ||
createReviewRequest.score() | ||
); | ||
} | ||
|
||
public Review toEntity(final Auction auction, final User writer, final User target) { | ||
return Review.builder() | ||
.auction(auction) | ||
.writer(writer) | ||
.target(target) | ||
.content(content) | ||
.score(score) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,18 @@ | ||||||||||
package com.ddang.ddang.review.application.dto; | ||||||||||
|
||||||||||
import com.ddang.ddang.review.domain.Review; | ||||||||||
|
||||||||||
import javax.annotation.Nullable; | ||||||||||
|
||||||||||
public record ReadReviewDetailDto(@Nullable Double score, @Nullable String content) { | ||||||||||
|
||||||||||
private static final Double EMPTY_SCORE = null; | ||||||||||
private static final String EMPTY_CONTENT = null; | ||||||||||
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 칭찬칭찬해요!!!!!!!!!!!!!!!!!!!!!!!!!!!!!@#!@!@!@#!@ 동일한 null인데 무슨 의미인지 써주셔서 명확해진 것 같습니다 |
||||||||||
|
||||||||||
public static ReadReviewDetailDto empty() { | ||||||||||
return new ReadReviewDetailDto(EMPTY_SCORE, EMPTY_CONTENT); | ||||||||||
} | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 선택
Suggested change
empty()의 결과는 두 값 모두 null로 변할 것 같지 않은데, 지금은 매번 새로운 인스턴스를 생성해서 반환하는 것으로 보입니다 |
||||||||||
public static ReadReviewDetailDto from(final Review review) { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 선택개행..? |
||||||||||
return new ReadReviewDetailDto(review.getScore(), review.getContent()); | ||||||||||
} | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.ddang.ddang.review.application.dto; | ||
|
||
import com.ddang.ddang.review.domain.Review; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
public record ReadReviewDto( | ||
Long id, | ||
ReadUserInReviewDto writer, | ||
String content, | ||
Double score, | ||
LocalDateTime createdTime | ||
) { | ||
|
||
public static ReadReviewDto from(final Review review) { | ||
return new ReadReviewDto( | ||
review.getId(), | ||
ReadUserInReviewDto.from(review.getWriter()), | ||
review.getContent(), | ||
review.getScore(), | ||
review.getCreatedTime()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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,16 @@ | ||
package com.ddang.ddang.review.application.dto; | ||
|
||
import com.ddang.ddang.user.domain.User; | ||
|
||
public record ReadUserInReviewDto(Long id, String name, Long profileImageId, double reliability, String oauthId) { | ||
|
||
public static ReadUserInReviewDto from(final User user) { | ||
return new ReadUserInReviewDto( | ||
user.getId(), | ||
user.getName(), | ||
user.getProfileImage().getId(), | ||
user.getReliability(), | ||
user.getOauthId() | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.ddang.ddang.review.application.exception; | ||
|
||
public class AlreadyReviewException extends IllegalArgumentException { | ||
|
||
public AlreadyReviewException(final String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.ddang.ddang.review.application.exception; | ||
|
||
public class InvalidUserToReview extends IllegalArgumentException { | ||
|
||
public InvalidUserToReview(final String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package com.ddang.ddang.review.domain; | ||
|
||
import com.ddang.ddang.auction.domain.Auction; | ||
import com.ddang.ddang.common.entity.BaseCreateTimeEntity; | ||
import com.ddang.ddang.user.domain.User; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.ForeignKey; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import lombok.Builder; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.ToString; | ||
|
||
@Entity | ||
@NoArgsConstructor | ||
@Getter | ||
@EqualsAndHashCode(of = "id", callSuper = false) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 질문
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 명시를 하는게 좋을 것 같습니다 자꾸 빌드할때마다 gradle이 경고 띄워서 살짝 느려지더라고요.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네 저번에 붙이기로 했어서 붙였습니다. 아마 이전 엔티티들은 따로 리팩토링을 진행하지 않아서 명시가 안되어있는 것 같아요. |
||
@ToString(of = {"id", "content", "score"}) | ||
public class Review extends BaseCreateTimeEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "auction_id", nullable = false, foreignKey = @ForeignKey(name = "fk_review_auction")) | ||
private Auction auction; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "writer_id", nullable = false, foreignKey = @ForeignKey(name = "fk_review_writer")) | ||
private User writer; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "target_id", nullable = false, foreignKey = @ForeignKey(name = "fk_review_target")) | ||
private User target; | ||
|
||
private String content; | ||
|
||
private Double score; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 질문Bid의 Price처럼 VO를 생성하지 않아도 괜찮을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 음 score도 따지고 보면 0.5 단위라는 정책이 존재하니까 VO를 만들면 좋겠네요. 그런데 이게 부동소수점이라 정확히 비교할 수 있을지 몰겠어요. |
||
|
||
@Builder | ||
public Review( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 질문 & 선택
IDE에서 확인해보니 외부에서 해당 생성자를 사용하는 부분이 없던데 특별한 이유가 없으시다면 private으로 바꾸면 될 것 같습니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 수정하겠습니다! |
||
final Auction auction, | ||
final User writer, | ||
final User target, | ||
final String content, | ||
final Double score | ||
) { | ||
this.auction = auction; | ||
this.writer = writer; | ||
this.target = target; | ||
this.content = content; | ||
this.score = score; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.ddang.ddang.review.infrastructure.persistence; | ||
|
||
import com.ddang.ddang.review.domain.Review; | ||
import org.springframework.data.jpa.repository.EntityGraph; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Query; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
public interface JpaReviewRepository extends JpaRepository<Review, Long> { | ||
|
||
boolean existsByAuctionIdAndWriterId(final Long auctionId, final Long writerId); | ||
|
||
@EntityGraph(attributePaths = {"writer", "target"}) | ||
@Query(""" | ||
SELECT r FROM Review r JOIN FETCH r.writer w JOIN FETCH r.target t | ||
WHERE t.id = :targetId | ||
ORDER BY r.id DESC | ||
""") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 질문 & 필수
일단 질문에 대한 답변이 필수다? 암튼 그런 느낌입니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. n+1 제거할 때 둘 다 실험해보고 싶어서 써놨다가 entity graph 부분을 안지웠네요.. 리뷰 감사합니다! |
||
List<Review> findAllByTargetId(final Long targetId); | ||
|
||
Optional<Review> findByAuctionIdAndWriterId(final Long auctionId, final Long writerId); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
칭찬
오!! 이 부분 선택사항으로 메서드 분리를 요청하려고 했었는데 이미 해주셨네요
역시 엔초