init
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
|
||||
// File Storage
|
||||
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ktds.hi.review;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
/**
|
||||
* 리뷰 관리 서비스 메인 애플리케이션 클래스
|
||||
* 리뷰 작성, 조회, 삭제, 반응, 댓글 기능을 제공
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.ktds.hi.review", "com.ktds.hi.common"})
|
||||
@EnableJpaAuditing
|
||||
public class ReviewServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ReviewServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ktds.hi.review.biz.domain;
|
||||
|
||||
/**
|
||||
* 반응 유형 열거형
|
||||
* 리뷰 반응의 종류를 정의
|
||||
*/
|
||||
public enum ReactionType {
|
||||
LIKE("좋아요"),
|
||||
DISLIKE("싫어요");
|
||||
|
||||
private final String description;
|
||||
|
||||
ReactionType(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.ktds.hi.review.biz.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리뷰 도메인 클래스
|
||||
* 리뷰의 기본 정보를 담는 도메인 객체
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Review {
|
||||
|
||||
private Long id;
|
||||
private Long storeId;
|
||||
private Long memberId;
|
||||
private String memberNickname;
|
||||
private Integer rating;
|
||||
private String content;
|
||||
private List<String> imageUrls;
|
||||
private ReviewStatus status;
|
||||
private Integer likeCount;
|
||||
private Integer dislikeCount;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 리뷰 내용 수정
|
||||
*/
|
||||
public Review updateContent(String newContent, Integer newRating) {
|
||||
return Review.builder()
|
||||
.id(this.id)
|
||||
.storeId(this.storeId)
|
||||
.memberId(this.memberId)
|
||||
.memberNickname(this.memberNickname)
|
||||
.rating(newRating)
|
||||
.content(newContent)
|
||||
.imageUrls(this.imageUrls)
|
||||
.status(this.status)
|
||||
.likeCount(this.likeCount)
|
||||
.dislikeCount(this.dislikeCount)
|
||||
.createdAt(this.createdAt)
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 리뷰 상태 변경
|
||||
*/
|
||||
public Review updateStatus(ReviewStatus newStatus) {
|
||||
return Review.builder()
|
||||
.id(this.id)
|
||||
.storeId(this.storeId)
|
||||
.memberId(this.memberId)
|
||||
.memberNickname(this.memberNickname)
|
||||
.rating(this.rating)
|
||||
.content(this.content)
|
||||
.imageUrls(this.imageUrls)
|
||||
.status(newStatus)
|
||||
.likeCount(this.likeCount)
|
||||
.dislikeCount(this.dislikeCount)
|
||||
.createdAt(this.createdAt)
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 좋아요 수 업데이트
|
||||
*/
|
||||
public Review updateLikeCount(Integer likeCount, Integer dislikeCount) {
|
||||
return Review.builder()
|
||||
.id(this.id)
|
||||
.storeId(this.storeId)
|
||||
.memberId(this.memberId)
|
||||
.memberNickname(this.memberNickname)
|
||||
.rating(this.rating)
|
||||
.content(this.content)
|
||||
.imageUrls(this.imageUrls)
|
||||
.status(this.status)
|
||||
.likeCount(likeCount)
|
||||
.dislikeCount(dislikeCount)
|
||||
.createdAt(this.createdAt)
|
||||
.updatedAt(this.updatedAt)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.ktds.hi.review.biz.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 도메인 클래스
|
||||
* 리뷰에 대한 점주 댓글 정보를 담는 도메인 객체
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ReviewComment {
|
||||
|
||||
private Long id;
|
||||
private Long reviewId;
|
||||
private Long ownerId;
|
||||
private String ownerNickname;
|
||||
private String content;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 댓글 내용 수정
|
||||
*/
|
||||
public ReviewComment updateContent(String newContent) {
|
||||
return ReviewComment.builder()
|
||||
.id(this.id)
|
||||
.reviewId(this.reviewId)
|
||||
.ownerId(this.ownerId)
|
||||
.ownerNickname(this.ownerNickname)
|
||||
.content(newContent)
|
||||
.createdAt(this.createdAt)
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.ktds.hi.review.biz.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 리뷰 반응 도메인 클래스
|
||||
* 리뷰에 대한 좋아요/싫어요 반응 정보를 담는 도메인 객체
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ReviewReaction {
|
||||
|
||||
private Long id;
|
||||
private Long reviewId;
|
||||
private Long memberId;
|
||||
private ReactionType reactionType;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 반응 유형 변경
|
||||
*/
|
||||
public ReviewReaction updateReactionType(ReactionType newType) {
|
||||
return ReviewReaction.builder()
|
||||
.id(this.id)
|
||||
.reviewId(this.reviewId)
|
||||
.memberId(this.memberId)
|
||||
.reactionType(newType)
|
||||
.createdAt(this.createdAt)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ktds.hi.review.biz.domain;
|
||||
|
||||
/**
|
||||
* 리뷰 상태 열거형
|
||||
* 리뷰의 현재 상태를 정의
|
||||
*/
|
||||
public enum ReviewStatus {
|
||||
ACTIVE("활성"),
|
||||
DELETED("삭제됨"),
|
||||
HIDDEN("숨김"),
|
||||
REPORTED("신고됨");
|
||||
|
||||
private final String description;
|
||||
|
||||
ReviewStatus(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.ktds.hi.review.biz.service;
|
||||
|
||||
import com.ktds.hi.review.biz.usecase.in.ManageReviewCommentUseCase;
|
||||
import com.ktds.hi.review.biz.usecase.out.ReviewCommentRepository;
|
||||
import com.ktds.hi.review.biz.usecase.out.ReviewRepository;
|
||||
import com.ktds.hi.review.biz.domain.ReviewComment;
|
||||
import com.ktds.hi.review.biz.domain.Review;
|
||||
import com.ktds.hi.review.infra.dto.request.ReviewCommentRequest;
|
||||
import com.ktds.hi.review.infra.dto.response.ReviewCommentResponse;
|
||||
import com.ktds.hi.common.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 인터랙터 클래스
|
||||
* 리뷰 댓글 작성, 조회, 삭제 기능을 구현
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class ReviewCommentInteractor implements ManageReviewCommentUseCase {
|
||||
|
||||
private final ReviewCommentRepository commentRepository;
|
||||
private final ReviewRepository reviewRepository;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ReviewCommentResponse> getReviewComments(Long reviewId) {
|
||||
List<ReviewComment> comments = commentRepository.findCommentsByReviewId(reviewId);
|
||||
|
||||
return comments.stream()
|
||||
.map(comment -> ReviewCommentResponse.builder()
|
||||
.commentId(comment.getId())
|
||||
.ownerNickname(comment.getOwnerNickname())
|
||||
.content(comment.getContent())
|
||||
.createdAt(comment.getCreatedAt())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewCommentResponse createComment(Long reviewId, Long ownerId, ReviewCommentRequest request) {
|
||||
// 리뷰 존재 확인
|
||||
Review review = reviewRepository.findReviewById(reviewId)
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 리뷰입니다"));
|
||||
|
||||
// 댓글 생성
|
||||
ReviewComment comment = ReviewComment.builder()
|
||||
.reviewId(reviewId)
|
||||
.ownerId(ownerId)
|
||||
.ownerNickname("점주" + ownerId) // TODO: 점주 서비스에서 닉네임 조회
|
||||
.content(request.getContent())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
ReviewComment savedComment = commentRepository.saveComment(comment);
|
||||
|
||||
log.info("리뷰 댓글 생성 완료: commentId={}, reviewId={}, ownerId={}",
|
||||
savedComment.getId(), reviewId, ownerId);
|
||||
|
||||
return ReviewCommentResponse.builder()
|
||||
.commentId(savedComment.getId())
|
||||
.ownerNickname(savedComment.getOwnerNickname())
|
||||
.content(savedComment.getContent())
|
||||
.createdAt(savedComment.getCreatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteComment(Long commentId, Long ownerId) {
|
||||
ReviewComment comment = commentRepository.findCommentByIdAndOwnerId(commentId, ownerId)
|
||||
.orElseThrow(() -> new BusinessException("댓글을 찾을 수 없거나 권한이 없습니다"));
|
||||
|
||||
commentRepository.deleteComment(commentId);
|
||||
|
||||
log.info("리뷰 댓글 삭제 완료: commentId={}, ownerId={}", commentId, ownerId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.ktds.hi.review.biz.service;
|
||||
|
||||
import com.ktds.hi.review.biz.usecase.in.CreateReviewUseCase;
|
||||
import com.ktds.hi.review.biz.usecase.in.DeleteReviewUseCase;
|
||||
import com.ktds.hi.review.biz.usecase.in.GetReviewUseCase;
|
||||
import com.ktds.hi.review.biz.usecase.out.ReviewRepository;
|
||||
import com.ktds.hi.review.biz.domain.Review;
|
||||
import com.ktds.hi.review.biz.domain.ReviewStatus;
|
||||
import com.ktds.hi.review.infra.dto.request.ReviewCreateRequest;
|
||||
import com.ktds.hi.review.infra.dto.response.*;
|
||||
import com.ktds.hi.common.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 리뷰 인터랙터 클래스
|
||||
* 리뷰 생성, 조회, 삭제 기능을 구현
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class ReviewInteractor implements CreateReviewUseCase, DeleteReviewUseCase, GetReviewUseCase {
|
||||
|
||||
private final ReviewRepository reviewRepository;
|
||||
|
||||
@Override
|
||||
public ReviewCreateResponse createReview(Long memberId, ReviewCreateRequest request) {
|
||||
// 리뷰 생성
|
||||
Review review = Review.builder()
|
||||
.storeId(request.getStoreId())
|
||||
.memberId(memberId)
|
||||
.memberNickname("회원" + memberId) // TODO: 회원 서비스에서 닉네임 조회
|
||||
.rating(request.getRating())
|
||||
.content(request.getContent())
|
||||
.imageUrls(request.getImageUrls())
|
||||
.status(ReviewStatus.ACTIVE)
|
||||
.likeCount(0)
|
||||
.dislikeCount(0)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Review savedReview = reviewRepository.saveReview(review);
|
||||
|
||||
log.info("리뷰 생성 완료: reviewId={}, storeId={}, memberId={}",
|
||||
savedReview.getId(), savedReview.getStoreId(), savedReview.getMemberId());
|
||||
|
||||
return ReviewCreateResponse.builder()
|
||||
.reviewId(savedReview.getId())
|
||||
.message("리뷰가 성공적으로 등록되었습니다")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewDeleteResponse deleteReview(Long reviewId, Long memberId) {
|
||||
Review review = reviewRepository.findReviewByIdAndMemberId(reviewId, memberId)
|
||||
.orElseThrow(() -> new BusinessException("리뷰를 찾을 수 없거나 권한이 없습니다"));
|
||||
|
||||
Review deletedReview = review.updateStatus(ReviewStatus.DELETED);
|
||||
reviewRepository.saveReview(deletedReview);
|
||||
|
||||
log.info("리뷰 삭제 완료: reviewId={}, memberId={}", reviewId, memberId);
|
||||
|
||||
return ReviewDeleteResponse.builder()
|
||||
.success(true)
|
||||
.message("리뷰가 삭제되었습니다")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ReviewListResponse> getStoreReviews(Long storeId, Integer page, Integer size) {
|
||||
Pageable pageable = PageRequest.of(page != null ? page : 0, size != null ? size : 20);
|
||||
Page<Review> reviews = reviewRepository.findReviewsByStoreId(storeId, pageable);
|
||||
|
||||
return reviews.stream()
|
||||
.filter(review -> review.getStatus() == ReviewStatus.ACTIVE)
|
||||
.map(review -> ReviewListResponse.builder()
|
||||
.reviewId(review.getId())
|
||||
.memberNickname(review.getMemberNickname())
|
||||
.rating(review.getRating())
|
||||
.content(review.getContent())
|
||||
.imageUrls(review.getImageUrls())
|
||||
.likeCount(review.getLikeCount())
|
||||
.dislikeCount(review.getDislikeCount())
|
||||
.createdAt(review.getCreatedAt())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ReviewDetailResponse getReviewDetail(Long reviewId) {
|
||||
Review review = reviewRepository.findReviewById(reviewId)
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 리뷰입니다"));
|
||||
|
||||
if (review.getStatus() != ReviewStatus.ACTIVE) {
|
||||
throw new BusinessException("삭제되었거나 숨겨진 리뷰입니다");
|
||||
}
|
||||
|
||||
return ReviewDetailResponse.builder()
|
||||
.reviewId(review.getId())
|
||||
.storeId(review.getStoreId())
|
||||
.memberNickname(review.getMemberNickname())
|
||||
.rating(review.getRating())
|
||||
.content(review.getContent())
|
||||
.imageUrls(review.getImageUrls())
|
||||
.likeCount(review.getLikeCount())
|
||||
.dislikeCount(review.getDislikeCount())
|
||||
.createdAt(review.getCreatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ReviewListResponse> getMyReviews(Long memberId, Integer page, Integer size) {
|
||||
Pageable pageable = PageRequest.of(page != null ? page : 0, size != null ? size : 20);
|
||||
Page<Review> reviews = reviewRepository.findReviewsByMemberId(memberId, pageable);
|
||||
|
||||
return reviews.stream()
|
||||
.filter(review -> review.getStatus() == ReviewStatus.ACTIVE)
|
||||
.map(review -> ReviewListResponse.builder()
|
||||
.reviewId(review.getId())
|
||||
.memberNickname(review.getMemberNickname())
|
||||
.rating(review.getRating())
|
||||
.content(review.getContent())
|
||||
.imageUrls(review.getImageUrls())
|
||||
.likeCount(review.getLikeCount())
|
||||
.dislikeCount(review.getDislikeCount())
|
||||
.createdAt(review.getCreatedAt())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.ktds.hi.review.biz.service;
|
||||
|
||||
import com.ktds.hi.review.biz.usecase.in.ManageReviewReactionUseCase;
|
||||
import com.ktds.hi.review.biz.usecase.out.ReviewReactionRepository;
|
||||
import com.ktds.hi.review.biz.usecase.out.ReviewRepository;
|
||||
import com.ktds.hi.review.biz.domain.ReviewReaction;
|
||||
import com.ktds.hi.review.biz.domain.ReactionType;
|
||||
import com.ktds.hi.review.biz.domain.Review;
|
||||
import com.ktds.hi.review.infra.dto.response.ReviewReactionResponse;
|
||||
import com.ktds.hi.common.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 리뷰 반응 인터랙터 클래스
|
||||
* 리뷰 좋아요/싫어요 기능을 구현
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class ReviewReactionInteractor implements ManageReviewReactionUseCase {
|
||||
|
||||
private final ReviewReactionRepository reactionRepository;
|
||||
private final ReviewRepository reviewRepository;
|
||||
|
||||
@Override
|
||||
public ReviewReactionResponse addReaction(Long reviewId, Long memberId, ReactionType reactionType) {
|
||||
// 리뷰 존재 확인
|
||||
Review review = reviewRepository.findReviewById(reviewId)
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 리뷰입니다"));
|
||||
|
||||
// 기존 반응 확인
|
||||
Optional<ReviewReaction> existingReaction = reactionRepository
|
||||
.findReactionByReviewIdAndMemberId(reviewId, memberId);
|
||||
|
||||
if (existingReaction.isPresent()) {
|
||||
ReviewReaction reaction = existingReaction.get();
|
||||
if (reaction.getReactionType() == reactionType) {
|
||||
throw new BusinessException("이미 같은 반응을 등록했습니다");
|
||||
}
|
||||
|
||||
// 반응 유형 변경
|
||||
ReviewReaction updatedReaction = reaction.updateReactionType(reactionType);
|
||||
reactionRepository.saveReaction(updatedReaction);
|
||||
} else {
|
||||
// 새로운 반응 생성
|
||||
ReviewReaction newReaction = ReviewReaction.builder()
|
||||
.reviewId(reviewId)
|
||||
.memberId(memberId)
|
||||
.reactionType(reactionType)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
reactionRepository.saveReaction(newReaction);
|
||||
}
|
||||
|
||||
// 반응 개수 업데이트
|
||||
updateReactionCounts(reviewId);
|
||||
|
||||
log.info("리뷰 반응 추가: reviewId={}, memberId={}, type={}", reviewId, memberId, reactionType);
|
||||
|
||||
return ReviewReactionResponse.builder()
|
||||
.success(true)
|
||||
.message("반응이 등록되었습니다")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewReactionResponse removeReaction(Long reviewId, Long memberId) {
|
||||
ReviewReaction reaction = reactionRepository.findReactionByReviewIdAndMemberId(reviewId, memberId)
|
||||
.orElseThrow(() -> new BusinessException("등록된 반응이 없습니다"));
|
||||
|
||||
reactionRepository.deleteReaction(reaction.getId());
|
||||
|
||||
// 반응 개수 업데이트
|
||||
updateReactionCounts(reviewId);
|
||||
|
||||
log.info("리뷰 반응 제거: reviewId={}, memberId={}", reviewId, memberId);
|
||||
|
||||
return ReviewReactionResponse.builder()
|
||||
.success(true)
|
||||
.message("반응이 제거되었습니다")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 리뷰의 반응 개수 업데이트
|
||||
*/
|
||||
private void updateReactionCounts(Long reviewId) {
|
||||
Long likeCount = reactionRepository.countReactionsByReviewIdAndType(reviewId, ReactionType.LIKE);
|
||||
Long dislikeCount = reactionRepository.countReactionsByReviewIdAndType(reviewId, ReactionType.DISLIKE);
|
||||
|
||||
Review review = reviewRepository.findReviewById(reviewId)
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 리뷰입니다"));
|
||||
|
||||
Review updatedReview = review.updateLikeCount(likeCount.intValue(), dislikeCount.intValue());
|
||||
reviewRepository.saveReview(updatedReview);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ktds.hi.review.biz.usecase.in;
|
||||
|
||||
import com.ktds.hi.review.infra.dto.request.ReviewCreateRequest;
|
||||
import com.ktds.hi.review.infra.dto.response.ReviewCreateResponse;
|
||||
|
||||
/**
|
||||
* 리뷰 생성 유스케이스 인터페이스
|
||||
* 새로운 리뷰 작성 기능을 정의
|
||||
*/
|
||||
public interface CreateReviewUseCase {
|
||||
|
||||
/**
|
||||
* 리뷰 생성
|
||||
*/
|
||||
ReviewCreateResponse createReview(Long memberId, ReviewCreateRequest request);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ktds.hi.review.biz.usecase.in;
|
||||
|
||||
import com.ktds.hi.review.infra.dto.response.ReviewDeleteResponse;
|
||||
|
||||
/**
|
||||
* 리뷰 삭제 유스케이스 인터페이스
|
||||
* 리뷰 삭제 기능을 정의
|
||||
*/
|
||||
public interface DeleteReviewUseCase {
|
||||
|
||||
/**
|
||||
* 리뷰 삭제
|
||||
*/
|
||||
ReviewDeleteResponse deleteReview(Long reviewId, Long memberId);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ktds.hi.review.biz.usecase.in;
|
||||
|
||||
import com.ktds.hi.review.infra.dto.response.ReviewDetailResponse;
|
||||
import com.ktds.hi.review.infra.dto.response.ReviewListResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리뷰 조회 유스케이스 인터페이스
|
||||
* 리뷰 목록 및 상세 조회 기능을 정의
|
||||
*/
|
||||
public interface GetReviewUseCase {
|
||||
|
||||
/**
|
||||
* 매장 리뷰 목록 조회
|
||||
*/
|
||||
List<ReviewListResponse> getStoreReviews(Long storeId, Integer page, Integer size);
|
||||
|
||||
/**
|
||||
* 리뷰 상세 조회
|
||||
*/
|
||||
ReviewDetailResponse getReviewDetail(Long reviewId);
|
||||
|
||||
/**
|
||||
* 내가 작성한 리뷰 목록 조회
|
||||
*/
|
||||
List<ReviewListResponse> getMyReviews(Long memberId, Integer page, Integer size);
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.ktds.hi.review.biz.usecase.in;
|
||||
|
||||
import com.ktds.hi.review.infra.dto.request.ReviewCommentRequest;
|
||||
import com.ktds.hi.review.infra.dto.response.ReviewCommentResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 관리 유스케이스 인터페이스
|
||||
* 리뷰 댓글 작성, 조회, 삭제 기능을 정의
|
||||
*/
|
||||
public interface ManageReviewCommentUseCase {
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 목록 조회
|
||||
*/
|
||||
List<ReviewCommentResponse> getReviewComments(Long reviewId);
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 작성 (점주용)
|
||||
*/
|
||||
ReviewCommentResponse createComment(Long reviewId, Long ownerId, ReviewCommentRequest request);
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 삭제
|
||||
*/
|
||||
void deleteComment(Long commentId, Long ownerId);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.ktds.hi.review.biz.usecase.in;
|
||||
|
||||
import com.ktds.hi.review.biz.domain.ReactionType;
|
||||
import com.ktds.hi.review.infra.dto.response.ReviewReactionResponse;
|
||||
|
||||
/**
|
||||
* 리뷰 반응 관리 유스케이스 인터페이스
|
||||
* 리뷰 좋아요/싫어요 기능을 정의
|
||||
*/
|
||||
public interface ManageReviewReactionUseCase {
|
||||
|
||||
/**
|
||||
* 리뷰 반응 추가
|
||||
*/
|
||||
ReviewReactionResponse addReaction(Long reviewId, Long memberId, ReactionType reactionType);
|
||||
|
||||
/**
|
||||
* 리뷰 반응 제거
|
||||
*/
|
||||
ReviewReactionResponse removeReaction(Long reviewId, Long memberId);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.ktds.hi.review.biz.usecase.out;
|
||||
|
||||
import com.ktds.hi.review.biz.domain.ReviewComment;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 리포지토리 인터페이스
|
||||
* 리뷰 댓글 데이터 영속성 기능을 정의
|
||||
*/
|
||||
public interface ReviewCommentRepository {
|
||||
|
||||
/**
|
||||
* 댓글 저장
|
||||
*/
|
||||
ReviewComment saveComment(ReviewComment comment);
|
||||
|
||||
/**
|
||||
* 리뷰 ID로 댓글 목록 조회
|
||||
*/
|
||||
List<ReviewComment> findCommentsByReviewId(Long reviewId);
|
||||
|
||||
/**
|
||||
* 댓글 ID로 조회
|
||||
*/
|
||||
Optional<ReviewComment> findCommentById(Long commentId);
|
||||
|
||||
/**
|
||||
* 댓글 삭제
|
||||
*/
|
||||
void deleteComment(Long commentId);
|
||||
|
||||
/**
|
||||
* 댓글 ID와 소유자 ID로 댓글 조회
|
||||
*/
|
||||
Optional<ReviewComment> findCommentByIdAndOwnerId(Long commentId, Long ownerId);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ktds.hi.review.biz.usecase.out;
|
||||
|
||||
import com.ktds.hi.review.biz.domain.ReviewReaction;
|
||||
import com.ktds.hi.review.biz.domain.ReactionType;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 리뷰 반응 리포지토리 인터페이스
|
||||
* 리뷰 반응 데이터 영속성 기능을 정의
|
||||
*/
|
||||
public interface ReviewReactionRepository {
|
||||
|
||||
/**
|
||||
* 반응 저장
|
||||
*/
|
||||
ReviewReaction saveReaction(ReviewReaction reaction);
|
||||
|
||||
/**
|
||||
* 리뷰 ID와 회원 ID로 반응 조회
|
||||
*/
|
||||
Optional<ReviewReaction> findReactionByReviewIdAndMemberId(Long reviewId, Long memberId);
|
||||
|
||||
/**
|
||||
* 반응 삭제
|
||||
*/
|
||||
void deleteReaction(Long reactionId);
|
||||
|
||||
/**
|
||||
* 리뷰 ID와 반응 유형별 개수 조회
|
||||
*/
|
||||
Long countReactionsByReviewIdAndType(Long reviewId, ReactionType reactionType);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ktds.hi.review.biz.usecase.out;
|
||||
|
||||
import com.ktds.hi.review.biz.domain.Review;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 리뷰 리포지토리 인터페이스
|
||||
* 리뷰 데이터 영속성 기능을 정의
|
||||
*/
|
||||
public interface ReviewRepository {
|
||||
|
||||
/**
|
||||
* 리뷰 저장
|
||||
*/
|
||||
Review saveReview(Review review);
|
||||
|
||||
/**
|
||||
* 리뷰 ID로 조회
|
||||
*/
|
||||
Optional<Review> findReviewById(Long reviewId);
|
||||
|
||||
/**
|
||||
* 매장 ID로 리뷰 목록 조회
|
||||
*/
|
||||
Page<Review> findReviewsByStoreId(Long storeId, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 회원 ID로 리뷰 목록 조회
|
||||
*/
|
||||
Page<Review> findReviewsByMemberId(Long memberId, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 리뷰 삭제
|
||||
*/
|
||||
void deleteReview(Long reviewId);
|
||||
|
||||
/**
|
||||
* 리뷰 ID와 회원 ID로 리뷰 조회
|
||||
*/
|
||||
Optional<Review> findReviewByIdAndMemberId(Long reviewId, Long memberId);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.ktds.hi.review.infra.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
/**
|
||||
* 리뷰 서비스 설정 클래스
|
||||
*/
|
||||
@Configuration
|
||||
@EnableJpaRepositories(basePackages = "com.ktds.hi.review.infra.gateway.repository")
|
||||
public class ReviewConfig {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ktds.hi.review.infra.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Swagger 설정 클래스
|
||||
* API 문서화를 위한 OpenAPI 설정
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
return new OpenAPI()
|
||||
.addServersItem(new Server().url("/"))
|
||||
.info(new Info()
|
||||
.title("하이오더 리뷰 관리 서비스 API")
|
||||
.description("리뷰 작성, 조회, 삭제, 반응, 댓글 등 리뷰 관련 기능을 제공하는 API")
|
||||
.version("1.0.0"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.ktds.hi.review.infra.controller;
|
||||
|
||||
import com.ktds.hi.review.biz.usecase.in.ManageReviewCommentUseCase;
|
||||
import com.ktds.hi.review.infra.dto.request.ReviewCommentRequest;
|
||||
import com.ktds.hi.review.infra.dto.response.ReviewCommentResponse;
|
||||
import com.ktds.hi.common.dto.SuccessResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 관리 컨트롤러 클래스
|
||||
* 리뷰 댓글 작성, 조회, 삭제 API를 제공
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/reviews")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "리뷰 댓글 API", description = "리뷰 댓글 작성, 조회, 삭제 관련 API")
|
||||
public class ReviewCommentController {
|
||||
|
||||
private final ManageReviewCommentUseCase manageReviewCommentUseCase;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 목록 조회 API
|
||||
*/
|
||||
@GetMapping("/{reviewId}/comments")
|
||||
@Operation(summary = "리뷰 댓글 목록 조회", description = "특정 리뷰의 댓글 목록을 조회합니다.")
|
||||
public ResponseEntity<List<ReviewCommentResponse>> getReviewComments(@PathVariable Long reviewId) {
|
||||
List<ReviewCommentResponse> comments = manageReviewCommentUseCase.getReviewComments(reviewId);
|
||||
return ResponseEntity.ok(comments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 작성 API (점주용)
|
||||
*/
|
||||
@PostMapping("/{reviewId}/comments")
|
||||
@Operation(summary = "리뷰 댓글 작성", description = "점주가 리뷰에 댓글을 작성합니다.")
|
||||
public ResponseEntity<ReviewCommentResponse> createComment(Authentication authentication,
|
||||
@PathVariable Long reviewId,
|
||||
@Valid @RequestBody ReviewCommentRequest request) {
|
||||
Long ownerId = Long.valueOf(authentication.getName());
|
||||
ReviewCommentResponse response = manageReviewCommentUseCase.createComment(reviewId, ownerId, request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 삭제 API
|
||||
*/
|
||||
@DeleteMapping("/{reviewId}/comments/{commentId}")
|
||||
@Operation(summary = "리뷰 댓글 삭제", description = "작성한 리뷰 댓글을 삭제합니다.")
|
||||
public ResponseEntity<SuccessResponse> deleteComment(Authentication authentication,
|
||||
@PathVariable Long reviewId,
|
||||
@PathVariable Long commentId) {
|
||||
Long ownerId = Long.valueOf(authentication.getName());
|
||||
manageReviewCommentUseCase.deleteComment(commentId, ownerId);
|
||||
return ResponseEntity.ok(SuccessResponse.of("댓글이 삭제되었습니다"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.ktds.hi.review.infra.controller;
|
||||
|
||||
import com.ktds.hi.review.biz.usecase.in.*;
|
||||
import com.ktds.hi.review.biz.domain.ReactionType;
|
||||
import com.ktds.hi.review.infra.dto.request.ReviewCreateRequest;
|
||||
import com.ktds.hi.review.infra.dto.response.*;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리뷰 관리 컨트롤러 클래스
|
||||
* 리뷰 생성, 조회, 삭제 및 반응 관리 API를 제공
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/reviews")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "리뷰 관리 API", description = "리뷰 작성, 조회, 삭제 및 반응 관리 관련 API")
|
||||
public class ReviewController {
|
||||
|
||||
private final CreateReviewUseCase createReviewUseCase;
|
||||
private final DeleteReviewUseCase deleteReviewUseCase;
|
||||
private final GetReviewUseCase getReviewUseCase;
|
||||
private final ManageReviewReactionUseCase manageReviewReactionUseCase;
|
||||
|
||||
/**
|
||||
* 리뷰 작성 API
|
||||
*/
|
||||
@PostMapping
|
||||
@Operation(summary = "리뷰 작성", description = "새로운 리뷰를 작성합니다.")
|
||||
public ResponseEntity<ReviewCreateResponse> createReview(Authentication authentication,
|
||||
@Valid @RequestBody ReviewCreateRequest request) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
ReviewCreateResponse response = createReviewUseCase.createReview(memberId, request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 매장 리뷰 목록 조회 API
|
||||
*/
|
||||
@GetMapping("/stores/{storeId}")
|
||||
@Operation(summary = "매장 리뷰 목록 조회", description = "특정 매장의 리뷰 목록을 조회합니다.")
|
||||
public ResponseEntity<List<ReviewListResponse>> getStoreReviews(@PathVariable Long storeId,
|
||||
@RequestParam(defaultValue = "0") Integer page,
|
||||
@RequestParam(defaultValue = "20") Integer size) {
|
||||
List<ReviewListResponse> reviews = getReviewUseCase.getStoreReviews(storeId, page, size);
|
||||
return ResponseEntity.ok(reviews);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리뷰 상세 조회 API
|
||||
*/
|
||||
@GetMapping("/{reviewId}")
|
||||
@Operation(summary = "리뷰 상세 조회", description = "특정 리뷰의 상세 정보를 조회합니다.")
|
||||
public ResponseEntity<ReviewDetailResponse> getReviewDetail(@PathVariable Long reviewId) {
|
||||
ReviewDetailResponse review = getReviewUseCase.getReviewDetail(reviewId);
|
||||
return ResponseEntity.ok(review);
|
||||
}
|
||||
|
||||
/**
|
||||
* 내가 작성한 리뷰 목록 조회 API
|
||||
*/
|
||||
@GetMapping("/my")
|
||||
@Operation(summary = "내 리뷰 목록 조회", description = "현재 로그인한 회원이 작성한 리뷰 목록을 조회합니다.")
|
||||
public ResponseEntity<List<ReviewListResponse>> getMyReviews(Authentication authentication,
|
||||
@RequestParam(defaultValue = "0") Integer page,
|
||||
@RequestParam(defaultValue = "20") Integer size) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
List<ReviewListResponse> reviews = getReviewUseCase.getMyReviews(memberId, page, size);
|
||||
return ResponseEntity.ok(reviews);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리뷰 삭제 API
|
||||
*/
|
||||
@DeleteMapping("/{reviewId}")
|
||||
@Operation(summary = "리뷰 삭제", description = "작성한 리뷰를 삭제합니다.")
|
||||
public ResponseEntity<ReviewDeleteResponse> deleteReview(Authentication authentication,
|
||||
@PathVariable Long reviewId) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
ReviewDeleteResponse response = deleteReviewUseCase.deleteReview(reviewId, memberId);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리뷰 좋아요 API
|
||||
*/
|
||||
@PostMapping("/{reviewId}/like")
|
||||
@Operation(summary = "리뷰 좋아요", description = "리뷰에 좋아요를 등록합니다.")
|
||||
public ResponseEntity<ReviewReactionResponse> likeReview(Authentication authentication,
|
||||
@PathVariable Long reviewId) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
ReviewReactionResponse response = manageReviewReactionUseCase.addReaction(reviewId, memberId, ReactionType.LIKE);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리뷰 싫어요 API
|
||||
*/
|
||||
@PostMapping("/{reviewId}/dislike")
|
||||
@Operation(summary = "리뷰 싫어요", description = "리뷰에 싫어요를 등록합니다.")
|
||||
public ResponseEntity<ReviewReactionResponse> dislikeReview(Authentication authentication,
|
||||
@PathVariable Long reviewId) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
ReviewReactionResponse response = manageReviewReactionUseCase.addReaction(reviewId, memberId, ReactionType.DISLIKE);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리뷰 반응 제거 API
|
||||
*/
|
||||
@DeleteMapping("/{reviewId}/reaction")
|
||||
@Operation(summary = "리뷰 반응 제거", description = "리뷰에 등록한 반응을 제거합니다.")
|
||||
public ResponseEntity<ReviewReactionResponse> removeReaction(Authentication authentication,
|
||||
@PathVariable Long reviewId) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
ReviewReactionResponse response = manageReviewReactionUseCase.removeReaction(reviewId, memberId);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.ktds.hi.review.infra.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 요청 DTO
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "리뷰 댓글 요청")
|
||||
public class ReviewCommentRequest {
|
||||
|
||||
@NotBlank(message = "댓글 내용은 필수입니다")
|
||||
@Size(min = 5, max = 500, message = "댓글 내용은 5자 이상 500자 이하여야 합니다")
|
||||
@Schema(description = "댓글 내용", example = "소중한 리뷰 감사합니다. 더 좋은 서비스로 보답하겠습니다.")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 유효성 검증
|
||||
*/
|
||||
public void validate() {
|
||||
if (content == null || content.trim().length() < 5) {
|
||||
throw new IllegalArgumentException("댓글 내용은 5자 이상이어야 합니다");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.ktds.hi.review.infra.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리뷰 생성 요청 DTO
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "리뷰 생성 요청")
|
||||
public class ReviewCreateRequest {
|
||||
|
||||
@NotNull(message = "매장 ID는 필수입니다")
|
||||
@Schema(description = "매장 ID", example = "1")
|
||||
private Long storeId;
|
||||
|
||||
@NotNull(message = "평점은 필수입니다")
|
||||
@Min(value = 1, message = "평점은 1점 이상이어야 합니다")
|
||||
@Max(value = 5, message = "평점은 5점 이하여야 합니다")
|
||||
@Schema(description = "평점 (1-5)", example = "4")
|
||||
private Integer rating;
|
||||
|
||||
@NotBlank(message = "리뷰 내용은 필수입니다")
|
||||
@Size(min = 10, max = 1000, message = "리뷰 내용은 10자 이상 1000자 이하여야 합니다")
|
||||
@Schema(description = "리뷰 내용", example = "음식이 정말 맛있었습니다. 서비스도 친절하고 재방문 의사 있습니다.")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "이미지 URL 목록", example = "[\"https://example.com/image1.jpg\", \"https://example.com/image2.jpg\"]")
|
||||
private List<String> imageUrls;
|
||||
|
||||
/**
|
||||
* 유효성 검증
|
||||
*/
|
||||
public void validate() {
|
||||
if (storeId == null || storeId <= 0) {
|
||||
throw new IllegalArgumentException("유효한 매장 ID가 필요합니다");
|
||||
}
|
||||
if (rating == null || rating < 1 || rating > 5) {
|
||||
throw new IllegalArgumentException("평점은 1점에서 5점 사이여야 합니다");
|
||||
}
|
||||
if (content == null || content.trim().length() < 10) {
|
||||
throw new IllegalArgumentException("리뷰 내용은 10자 이상이어야 합니다");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.ktds.hi.review.infra.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "리뷰 댓글 응답")
|
||||
public class ReviewCommentResponse {
|
||||
|
||||
@Schema(description = "댓글 ID")
|
||||
private Long commentId;
|
||||
|
||||
@Schema(description = "점주 닉네임")
|
||||
private String ownerNickname;
|
||||
|
||||
@Schema(description = "댓글 내용")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "작성일시")
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ktds.hi.review.infra.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 리뷰 생성 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "리뷰 생성 응답")
|
||||
public class ReviewCreateResponse {
|
||||
|
||||
@Schema(description = "생성된 리뷰 ID")
|
||||
private Long reviewId;
|
||||
|
||||
@Schema(description = "응답 메시지")
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ktds.hi.review.infra.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 리뷰 삭제 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "리뷰 삭제 응답")
|
||||
public class ReviewDeleteResponse {
|
||||
|
||||
@Schema(description = "성공 여부")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "응답 메시지")
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ktds.hi.review.infra.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리뷰 상세 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "리뷰 상세 응답")
|
||||
public class ReviewDetailResponse {
|
||||
|
||||
@Schema(description = "리뷰 ID")
|
||||
private Long reviewId;
|
||||
|
||||
@Schema(description = "매장 ID")
|
||||
private Long storeId;
|
||||
|
||||
@Schema(description = "작성자 닉네임")
|
||||
private String memberNickname;
|
||||
|
||||
@Schema(description = "평점")
|
||||
private Integer rating;
|
||||
|
||||
@Schema(description = "리뷰 내용")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "이미지 URL 목록")
|
||||
private List<String> imageUrls;
|
||||
|
||||
@Schema(description = "좋아요 수")
|
||||
private Integer likeCount;
|
||||
|
||||
@Schema(description = "싫어요 수")
|
||||
private Integer dislikeCount;
|
||||
|
||||
@Schema(description = "작성일시")
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ktds.hi.review.infra.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리뷰 목록 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "리뷰 목록 응답")
|
||||
public class ReviewListResponse {
|
||||
|
||||
@Schema(description = "리뷰 ID")
|
||||
private Long reviewId;
|
||||
|
||||
@Schema(description = "작성자 닉네임")
|
||||
private String memberNickname;
|
||||
|
||||
@Schema(description = "평점")
|
||||
private Integer rating;
|
||||
|
||||
@Schema(description = "리뷰 내용")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "이미지 URL 목록")
|
||||
private List<String> imageUrls;
|
||||
|
||||
@Schema(description = "좋아요 수")
|
||||
private Integer likeCount;
|
||||
|
||||
@Schema(description = "싫어요 수")
|
||||
private Integer dislikeCount;
|
||||
|
||||
@Schema(description = "작성일시")
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.ktds.hi.review.infra.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 리뷰 반응 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "리뷰 반응 응답")
|
||||
public class ReviewReactionResponse {
|
||||
|
||||
@Schema(description = "성공 여부")
|
||||
private Boolean success;
|
||||
|
||||
@Schema(description = "응답 메시지")
|
||||
private String message;
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.ktds.hi.review.infra.gateway;
|
||||
|
||||
import com.ktds.hi.review.biz.usecase.out.ReviewCommentRepository;
|
||||
import com.ktds.hi.review.biz.domain.ReviewComment;
|
||||
import com.ktds.hi.review.infra.gateway.repository.ReviewCommentJpaRepository;
|
||||
import com.ktds.hi.review.infra.gateway.entity.ReviewCommentEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 리포지토리 어댑터 클래스
|
||||
* 도메인 리포지토리 인터페이스를 JPA 리포지토리에 연결
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ReviewCommentRepositoryAdapter implements ReviewCommentRepository {
|
||||
|
||||
private final ReviewCommentJpaRepository reviewCommentJpaRepository;
|
||||
|
||||
@Override
|
||||
public ReviewComment saveComment(ReviewComment comment) {
|
||||
ReviewCommentEntity entity = toEntity(comment);
|
||||
ReviewCommentEntity savedEntity = reviewCommentJpaRepository.save(entity);
|
||||
return toDomain(savedEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ReviewComment> findCommentsByReviewId(Long reviewId) {
|
||||
List<ReviewCommentEntity> entities = reviewCommentJpaRepository.findByReviewIdOrderByCreatedAtDesc(reviewId);
|
||||
return entities.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ReviewComment> findCommentById(Long commentId) {
|
||||
return reviewCommentJpaRepository.findById(commentId)
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteComment(Long commentId) {
|
||||
reviewCommentJpaRepository.deleteById(commentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ReviewComment> findCommentByIdAndOwnerId(Long commentId, Long ownerId) {
|
||||
return reviewCommentJpaRepository.findByIdAndOwnerId(commentId, ownerId)
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔티티를 도메인으로 변환
|
||||
*/
|
||||
private ReviewComment toDomain(ReviewCommentEntity entity) {
|
||||
return ReviewComment.builder()
|
||||
.id(entity.getId())
|
||||
.reviewId(entity.getReviewId())
|
||||
.ownerId(entity.getOwnerId())
|
||||
.ownerNickname(entity.getOwnerNickname())
|
||||
.content(entity.getContent())
|
||||
.createdAt(entity.getCreatedAt())
|
||||
.updatedAt(entity.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 도메인을 엔티티로 변환
|
||||
*/
|
||||
private ReviewCommentEntity toEntity(ReviewComment domain) {
|
||||
return ReviewCommentEntity.builder()
|
||||
.id(domain.getId())
|
||||
.reviewId(domain.getReviewId())
|
||||
.ownerId(domain.getOwnerId())
|
||||
.ownerNickname(domain.getOwnerNickname())
|
||||
.content(domain.getContent())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.ktds.hi.review.infra.gateway;
|
||||
|
||||
import com.ktds.hi.review.biz.usecase.out.ReviewReactionRepository;
|
||||
import com.ktds.hi.review.biz.domain.ReviewReaction;
|
||||
import com.ktds.hi.review.biz.domain.ReactionType;
|
||||
import com.ktds.hi.review.infra.gateway.repository.ReviewReactionJpaRepository;
|
||||
import com.ktds.hi.review.infra.gateway.entity.ReviewReactionEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 리뷰 반응 리포지토리 어댑터 클래스
|
||||
* 도메인 리포지토리 인터페이스를 JPA 리포지토리에 연결
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ReviewReactionRepositoryAdapter implements ReviewReactionRepository {
|
||||
|
||||
private final ReviewReactionJpaRepository reviewReactionJpaRepository;
|
||||
|
||||
@Override
|
||||
public ReviewReaction saveReaction(ReviewReaction reaction) {
|
||||
ReviewReactionEntity entity = toEntity(reaction);
|
||||
ReviewReactionEntity savedEntity = reviewReactionJpaRepository.save(entity);
|
||||
return toDomain(savedEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ReviewReaction> findReactionByReviewIdAndMemberId(Long reviewId, Long memberId) {
|
||||
return reviewReactionJpaRepository.findByReviewIdAndMemberId(reviewId, memberId)
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteReaction(Long reactionId) {
|
||||
reviewReactionJpaRepository.deleteById(reactionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countReactionsByReviewIdAndType(Long reviewId, ReactionType reactionType) {
|
||||
return reviewReactionJpaRepository.countByReviewIdAndReactionType(reviewId, reactionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔티티를 도메인으로 변환
|
||||
*/
|
||||
private ReviewReaction toDomain(ReviewReactionEntity entity) {
|
||||
return ReviewReaction.builder()
|
||||
.id(entity.getId())
|
||||
.reviewId(entity.getReviewId())
|
||||
.memberId(entity.getMemberId())
|
||||
.reactionType(entity.getReactionType())
|
||||
.createdAt(entity.getCreatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 도메인을 엔티티로 변환
|
||||
*/
|
||||
private ReviewReactionEntity toEntity(ReviewReaction domain) {
|
||||
return ReviewReactionEntity.builder()
|
||||
.id(domain.getId())
|
||||
.reviewId(domain.getReviewId())
|
||||
.memberId(domain.getMemberId())
|
||||
.reactionType(domain.getReactionType())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.ktds.hi.review.infra.gateway;
|
||||
|
||||
import com.ktds.hi.review.biz.usecase.out.ReviewRepository;
|
||||
import com.ktds.hi.review.biz.domain.Review;
|
||||
import com.ktds.hi.review.biz.domain.ReviewStatus;
|
||||
import com.ktds.hi.review.infra.gateway.repository.ReviewJpaRepository;
|
||||
import com.ktds.hi.review.infra.gateway.entity.ReviewEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 리뷰 리포지토리 어댑터 클래스
|
||||
* 도메인 리포지토리 인터페이스를 JPA 리포지토리에 연결
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ReviewRepositoryAdapter implements ReviewRepository {
|
||||
|
||||
private final ReviewJpaRepository reviewJpaRepository;
|
||||
|
||||
@Override
|
||||
public Review saveReview(Review review) {
|
||||
ReviewEntity entity = toEntity(review);
|
||||
ReviewEntity savedEntity = reviewJpaRepository.save(entity);
|
||||
return toDomain(savedEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Review> findReviewById(Long reviewId) {
|
||||
return reviewJpaRepository.findById(reviewId)
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Review> findReviewsByStoreId(Long storeId, Pageable pageable) {
|
||||
Page<ReviewEntity> entities = reviewJpaRepository.findByStoreIdAndStatus(storeId, ReviewStatus.ACTIVE, pageable);
|
||||
return entities.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Review> findReviewsByMemberId(Long memberId, Pageable pageable) {
|
||||
Page<ReviewEntity> entities = reviewJpaRepository.findByMemberIdAndStatus(memberId, ReviewStatus.ACTIVE, pageable);
|
||||
return entities.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteReview(Long reviewId) {
|
||||
reviewJpaRepository.deleteById(reviewId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Review> findReviewByIdAndMemberId(Long reviewId, Long memberId) {
|
||||
return reviewJpaRepository.findByIdAndMemberId(reviewId, memberId)
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔티티를 도메인으로 변환
|
||||
*/
|
||||
private Review toDomain(ReviewEntity entity) {
|
||||
return Review.builder()
|
||||
.id(entity.getId())
|
||||
.storeId(entity.getStoreId())
|
||||
.memberId(entity.getMemberId())
|
||||
.memberNickname(entity.getMemberNickname())
|
||||
.rating(entity.getRating())
|
||||
.content(entity.getContent())
|
||||
.imageUrls(entity.getImageUrls())
|
||||
.status(entity.getStatus())
|
||||
.likeCount(entity.getLikeCount())
|
||||
.dislikeCount(entity.getDislikeCount())
|
||||
.createdAt(entity.getCreatedAt())
|
||||
.updatedAt(entity.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 도메인을 엔티티로 변환
|
||||
*/
|
||||
private ReviewEntity toEntity(Review domain) {
|
||||
return ReviewEntity.builder()
|
||||
.id(domain.getId())
|
||||
.storeId(domain.getStoreId())
|
||||
.memberId(domain.getMemberId())
|
||||
.memberNickname(domain.getMemberNickname())
|
||||
.rating(domain.getRating())
|
||||
.content(domain.getContent())
|
||||
.imageUrls(domain.getImageUrls())
|
||||
.status(domain.getStatus())
|
||||
.likeCount(domain.getLikeCount())
|
||||
.dislikeCount(domain.getDislikeCount())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.ktds.hi.review.infra.gateway.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 엔티티 클래스
|
||||
* 데이터베이스 review_comments 테이블과 매핑되는 JPA 엔티티
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "review_comments")
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class ReviewCommentEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "review_id", nullable = false)
|
||||
private Long reviewId;
|
||||
|
||||
@Column(name = "owner_id", nullable = false)
|
||||
private Long ownerId;
|
||||
|
||||
@Column(name = "owner_nickname", nullable = false, length = 50)
|
||||
private String ownerNickname;
|
||||
|
||||
@Column(nullable = false, length = 500)
|
||||
private String content;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.ktds.hi.review.infra.gateway.entity;
|
||||
|
||||
import com.ktds.hi.review.biz.domain.ReviewStatus;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 리뷰 엔티티 클래스
|
||||
* 데이터베이스 reviews 테이블과 매핑되는 JPA 엔티티
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "reviews")
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class ReviewEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "store_id", nullable = false)
|
||||
private Long storeId;
|
||||
|
||||
@Column(name = "member_id", nullable = false)
|
||||
private Long memberId;
|
||||
|
||||
@Column(name = "member_nickname", nullable = false, length = 50)
|
||||
private String memberNickname;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Integer rating;
|
||||
|
||||
@Column(nullable = false, length = 1000)
|
||||
private String content;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "review_images",
|
||||
joinColumns = @JoinColumn(name = "review_id"))
|
||||
@Column(name = "image_url")
|
||||
private List<String> imageUrls;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private ReviewStatus status = ReviewStatus.ACTIVE;
|
||||
|
||||
@Column(name = "like_count")
|
||||
@Builder.Default
|
||||
private Integer likeCount = 0;
|
||||
|
||||
@Column(name = "dislike_count")
|
||||
@Builder.Default
|
||||
private Integer dislikeCount = 0;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.ktds.hi.review.infra.gateway.entity;
|
||||
|
||||
import com.ktds.hi.review.biz.domain.ReactionType;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 리뷰 반응 엔티티 클래스
|
||||
* 데이터베이스 review_reactions 테이블과 매핑되는 JPA 엔티티
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "review_reactions",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"review_id", "member_id"}))
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class ReviewReactionEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "review_id", nullable = false)
|
||||
private Long reviewId;
|
||||
|
||||
@Column(name = "member_id", nullable = false)
|
||||
private Long memberId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "reaction_type", nullable = false)
|
||||
private ReactionType reactionType;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.ktds.hi.review.infra.gateway.repository;
|
||||
|
||||
import com.ktds.hi.review.infra.gateway.entity.ReviewCommentEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 리뷰 댓글 JPA 리포지토리 인터페이스
|
||||
* 리뷰 댓글 데이터의 CRUD 작업을 담당
|
||||
*/
|
||||
@Repository
|
||||
public interface ReviewCommentJpaRepository extends JpaRepository<ReviewCommentEntity, Long> {
|
||||
|
||||
/**
|
||||
* 리뷰 ID로 댓글 목록 조회 (최신순)
|
||||
*/
|
||||
List<ReviewCommentEntity> findByReviewIdOrderByCreatedAtDesc(Long reviewId);
|
||||
|
||||
/**
|
||||
* 댓글 ID와 소유자 ID로 댓글 조회
|
||||
*/
|
||||
Optional<ReviewCommentEntity> findByIdAndOwnerId(Long id, Long ownerId);
|
||||
|
||||
/**
|
||||
* 소유자 ID로 댓글 목록 조회
|
||||
*/
|
||||
List<ReviewCommentEntity> findByOwnerIdOrderByCreatedAtDesc(Long ownerId);
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.ktds.hi.review.infra.gateway.repository;
|
||||
|
||||
import com.ktds.hi.review.biz.domain.ReviewStatus;
|
||||
import com.ktds.hi.review.infra.gateway.entity.ReviewEntity;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 리뷰 JPA 리포지토리 인터페이스
|
||||
* 리뷰 데이터의 CRUD 작업을 담당
|
||||
*/
|
||||
@Repository
|
||||
public interface ReviewJpaRepository extends JpaRepository<ReviewEntity, Long> {
|
||||
|
||||
/**
|
||||
* 매장 ID와 상태로 리뷰 목록 조회
|
||||
*/
|
||||
Page<ReviewEntity> findByStoreIdAndStatus(Long storeId, ReviewStatus status, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 회원 ID와 상태로 리뷰 목록 조회
|
||||
*/
|
||||
Page<ReviewEntity> findByMemberIdAndStatus(Long memberId, ReviewStatus status, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 리뷰 ID와 회원 ID로 리뷰 조회
|
||||
*/
|
||||
Optional<ReviewEntity> findByIdAndMemberId(Long id, Long memberId);
|
||||
|
||||
/**
|
||||
* 매장 ID와 회원 ID로 리뷰 존재 여부 확인
|
||||
*/
|
||||
boolean existsByStoreIdAndMemberId(Long storeId, Long memberId);
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.ktds.hi.review.infra.gateway.repository;
|
||||
|
||||
import com.ktds.hi.review.biz.domain.ReactionType;
|
||||
import com.ktds.hi.review.infra.gateway.entity.ReviewReactionEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 리뷰 반응 JPA 리포지토리 인터페이스
|
||||
* 리뷰 반응 데이터의 CRUD 작업을 담당
|
||||
*/
|
||||
@Repository
|
||||
public interface ReviewReactionJpaRepository extends JpaRepository<ReviewReactionEntity, Long> {
|
||||
|
||||
/**
|
||||
* 리뷰 ID와 회원 ID로 반응 조회
|
||||
*/
|
||||
Optional<ReviewReactionEntity> findByReviewIdAndMemberId(Long reviewId, Long memberId);
|
||||
|
||||
/**
|
||||
* 리뷰 ID와 반응 유형별 개수 조회
|
||||
*/
|
||||
Long countByReviewIdAndReactionType(Long reviewId, ReactionType reactionType);
|
||||
|
||||
/**
|
||||
* 회원 ID로 반응 목록 조회
|
||||
*/
|
||||
Long countByMemberId(Long memberId);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
server:
|
||||
port: ${REVIEW_SERVICE_PORT:8083}
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: review-service
|
||||
|
||||
datasource:
|
||||
url: ${REVIEW_DB_URL:jdbc:postgresql://localhost:5432/hiorder_review}
|
||||
username: ${REVIEW_DB_USERNAME:hiorder_user}
|
||||
password: ${REVIEW_DB_PASSWORD:hiorder_pass}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: ${JPA_DDL_AUTO:validate}
|
||||
show-sql: ${JPA_SHOW_SQL:false}
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: ${MAX_FILE_SIZE:10MB}
|
||||
max-request-size: ${MAX_REQUEST_SIZE:50MB}
|
||||
|
||||
file-storage:
|
||||
base-path: ${FILE_STORAGE_PATH:/var/hiorder/uploads}
|
||||
allowed-extensions: jpg,jpeg,png,gif,webp
|
||||
max-file-size: 10485760 # 10MB
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
Reference in New Issue
Block a user