-
Notifications
You must be signed in to change notification settings - Fork 4
Feature/ #42 리뷰 생성, 삭제 #71
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
Changes from all commits
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,49 @@ | ||
| package com.homeaid.controller; | ||
|
|
||
| import com.homeaid.common.response.CommonApiResponse; | ||
| import com.homeaid.domain.Review; | ||
| import com.homeaid.domain.enumerate.UserRole; | ||
| import com.homeaid.dto.request.CustomerReviewRequestDto; | ||
| import com.homeaid.security.CustomUserDetails; | ||
| import com.homeaid.service.ReviewService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @RestController | ||
| @RequestMapping("/api/v1/review") | ||
| public class ReviewController { | ||
|
|
||
| private final ReviewService reviewService; | ||
|
|
||
| @Operation(summary = "리뷰 생성") | ||
| @PostMapping | ||
| public ResponseEntity<CommonApiResponse<Long>> createReview( | ||
| @AuthenticationPrincipal CustomUserDetails user, | ||
| @RequestBody CustomerReviewRequestDto customerReviewRequestDto) { | ||
|
|
||
| Review requestReview = CustomerReviewRequestDto.toEntity(customerReviewRequestDto, user.getUserRole(), user.getUserId()); | ||
|
|
||
| Review review = switch (user.getUserRole()) { | ||
| case CUSTOMER -> reviewService.createReviewByCustomer(requestReview); | ||
| case MANAGER -> reviewService.createReviewByManager(requestReview); | ||
| default -> throw new IllegalArgumentException("Unsupported role: " + user.getUserRole()); | ||
| }; | ||
| return ResponseEntity.ok(CommonApiResponse.success(review.getId())); | ||
| } | ||
|
|
||
| @Operation(summary = "리뷰 삭제") | ||
| @DeleteMapping("/{reviewId}") | ||
| public ResponseEntity<CommonApiResponse<Void>> delete( | ||
| @AuthenticationPrincipal CustomUserDetails userDetails, | ||
| @PathVariable Long reviewId) { | ||
|
|
||
| reviewService.deleteReview(reviewId, userDetails.getUserId()); | ||
| return ResponseEntity.ok(CommonApiResponse.success()); | ||
| } | ||
|
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package com.homeaid.domain; | ||
|
|
||
| import com.homeaid.domain.enumerate.UserRole; | ||
| import jakarta.persistence.*; | ||
| import lombok.*; | ||
| import org.springframework.data.annotation.CreatedDate; | ||
| import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @EntityListeners(AuditingEntityListener.class) | ||
| @Table(uniqueConstraints = @UniqueConstraint(columnNames = {"reservationId", "writerId"})) | ||
| public class Review { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| private Long writerId; | ||
|
|
||
| private Long targetId; | ||
|
|
||
| private int rating; //별점 | ||
|
|
||
| private String comment; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| private UserRole writerRole; | ||
|
|
||
| @CreatedDate | ||
| private LocalDateTime createdAt; | ||
|
|
||
| private Long reservationId; | ||
|
|
||
| @Builder | ||
| public Review(Long targetId, Long writerId, UserRole writerRole, String comment, int rating, Long reservationId) { | ||
| this.targetId = targetId; | ||
| this.writerId = writerId; | ||
| this.writerRole = writerRole; | ||
| this.comment = comment; | ||
| this.rating = rating; | ||
| this.reservationId = reservationId; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package com.homeaid.dto.request; | ||
|
|
||
|
|
||
| import com.homeaid.domain.Review; | ||
| import com.homeaid.domain.enumerate.UserRole; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class CustomerReviewRequestDto { | ||
|
|
||
| private Long targetId; | ||
|
|
||
| private Integer rating; | ||
|
Collaborator
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. 기업 답변에서 리뷰는 1~5점으로 점수를 매길 수 있다고 봤었는데 min,max 조건 있어야 할 것 같습니다. 그 외에도 코멘트는 최소 10이상 작성해야한다는 등.. valid 조건 있으면 좋을 것 같습니다! |
||
|
|
||
| private String comment; | ||
|
|
||
| private Long reservationId; | ||
|
|
||
| public static Review toEntity(CustomerReviewRequestDto customerReviewRequestDto, UserRole userRole, Long userId) { | ||
|
|
||
| return Review.builder() | ||
| .writerId(userId) | ||
| .targetId(customerReviewRequestDto.targetId) | ||
| .writerRole(userRole) | ||
| .comment(customerReviewRequestDto.comment) | ||
| .rating(customerReviewRequestDto.rating) | ||
| .reservationId(customerReviewRequestDto.reservationId) | ||
| .build(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.homeaid.exception; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum ReviewErrorCode implements BaseErrorCode { | ||
|
|
||
| // 400 BAD REQUEST | ||
| INVALID_REVIEW_CONTENT(HttpStatus.BAD_REQUEST, "INVALID_REVIEW_CONTENT", "리뷰 내용이 올바르지 않습니다."), | ||
| INVALID_REVIEW_SCORE(HttpStatus.BAD_REQUEST, "INVALID_REVIEW_SCORE", "리뷰 점수가 유효하지 않습니다."), | ||
|
|
||
| // 403 FORBIDDEN | ||
| UNAUTHORIZED_REVIEW_ACCESS(HttpStatus.FORBIDDEN, "UNAUTHORIZED_REVIEW_ACCESS", "리뷰에 대한 권한이 없습니다."), | ||
| UNAUTHORIZED_REVIEW_TARGET(HttpStatus.FORBIDDEN, "UNAUTHORIZED_REVIEW_TARGET", "예약건에 대한 리뷰 대상이 유효하지 않습니다."), | ||
|
|
||
| // 404 NOTFOUND | ||
| REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW_NOT_FOUND", "해당 리뷰가 존재하지 않습니다."), | ||
|
|
||
| // 409 CONFLICT | ||
| REVIEW_NOT_ALLOWED(HttpStatus.CONFLICT, "REVIEW_NOT_ALLOWED", "작업이 완료되지 않아 리뷰를 작성할 수 없습니다."), | ||
| DUPLICATE_REVIEW(HttpStatus.CONFLICT, "DUPLICATE_REVIEW", "이미 해당 예약에 대한 리뷰가 존재합니다."), | ||
| REVIEW_CANNOT_UPDATE(HttpStatus.CONFLICT, "REVIEW_CANNOT_UPDATE", "리뷰를 수정할 수 없습니다."), | ||
| REVIEW_CANNOT_DELETE(HttpStatus.CONFLICT, "REVIEW_CANNOT_DELETE", "리뷰를 삭제할 수 없습니다."); | ||
|
|
||
| private final HttpStatus status; | ||
| private final String code; | ||
| private final String message; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.homeaid.repository; | ||
|
|
||
|
|
||
| import com.homeaid.domain.Review; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| @Repository | ||
| public interface ReviewRepository extends JpaRepository<Review, Long> { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.homeaid.service; | ||
|
|
||
|
|
||
| import com.homeaid.domain.Review; | ||
|
|
||
| public interface ReviewService { | ||
| Review createReviewByCustomer(Review requestReview); | ||
|
|
||
| Review createReviewByManager(Review requestReview); | ||
|
|
||
| void deleteReview(Long reviewId, Long userId); | ||
| } |
|
Collaborator
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,97 @@ | ||
| package com.homeaid.service; | ||
|
|
||
| import com.homeaid.domain.Reservation; | ||
| import com.homeaid.domain.Review; | ||
| import com.homeaid.domain.enumerate.ReservationStatus; | ||
| import com.homeaid.exception.CustomException; | ||
| import com.homeaid.exception.MatchingErrorCode; | ||
| import com.homeaid.exception.ReservationErrorCode; | ||
| import com.homeaid.exception.ReviewErrorCode; | ||
| import com.homeaid.repository.MatchingRepository; | ||
| import com.homeaid.repository.ReservationRepository; | ||
| import com.homeaid.repository.ReviewRepository; | ||
| import jakarta.transaction.Transactional; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Service | ||
| public class ReviewServiceImpl implements ReviewService { | ||
| private final ReviewRepository reviewRepository; | ||
| private final ReservationRepository reservationRepository; | ||
| private final MatchingRepository matchingRepository; | ||
|
|
||
| @Transactional | ||
| @Override | ||
| public Review createReviewByCustomer(Review requestReview) { | ||
| Reservation validatedReservation = validateReview(requestReview); | ||
|
|
||
| //예약건의 고객아이디와 요청 고객의 아이디 검증 | ||
| if (!validatedReservation.getCustomerId().equals(requestReview.getWriterId())) { | ||
| throw new CustomException(ReviewErrorCode.UNAUTHORIZED_REVIEW_ACCESS); | ||
| } | ||
|
|
||
| //타켓 매니저 와 예약 건의 매니저 검증 | ||
| Long reservationManagerId = getFinalMatchingOfManagerId(validatedReservation.getFinalMatchingId()); | ||
| if (!reservationManagerId.equals(requestReview.getTargetId())) { | ||
| throw new CustomException(ReviewErrorCode.UNAUTHORIZED_REVIEW_TARGET); | ||
| } | ||
|
|
||
| //Todo 매니저 찜 기능 | ||
|
|
||
| return reviewRepository.save(requestReview); | ||
| } | ||
|
|
||
| @Transactional | ||
| @Override | ||
| public Review createReviewByManager(Review requestReview) { | ||
| Reservation validatedReservation = validateReview(requestReview); | ||
|
|
||
| //예약 건의 매니저와 와 요청자의 매니저 아이디 검증 | ||
| Long reservationManagerId = getFinalMatchingOfManagerId(validatedReservation.getFinalMatchingId()); | ||
| if (!reservationManagerId.equals(requestReview.getWriterId())) { | ||
| throw new CustomException(ReviewErrorCode.UNAUTHORIZED_REVIEW_ACCESS); | ||
| } | ||
|
|
||
| //예약 건의 고객아이디와 요청 받은 고객아이디 검증 | ||
| if (!validatedReservation.getCustomerId().equals(requestReview.getTargetId())) { | ||
| throw new CustomException(ReviewErrorCode.UNAUTHORIZED_REVIEW_TARGET); | ||
| } | ||
|
|
||
| return reviewRepository.save(requestReview); | ||
| } | ||
|
|
||
| public void deleteReview(Long reviewId, Long userId) { | ||
| Review review = reviewRepository.findById(reviewId).orElseThrow(() -> | ||
| new CustomException(ReviewErrorCode.REVIEW_NOT_FOUND)); | ||
|
|
||
| if (!review.getWriterId().equals(userId)) { | ||
| throw new CustomException(ReviewErrorCode.UNAUTHORIZED_REVIEW_ACCESS); | ||
| } | ||
|
|
||
| reservationRepository.deleteById(reviewId); | ||
|
Collaborator
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. 예약 Repository에서 deleteById 메서드를 사용하고 있습니다. |
||
| } | ||
|
|
||
|
|
||
| /** | ||
| * 요청 받은 예약이 유요한 예약이고 완료상태 검증 | ||
| * | ||
| * @param requestReview 요청한예약 아이디 | ||
| * @return 조회된 예약 | ||
| */ | ||
| private Reservation validateReview(Review requestReview) { | ||
| Reservation reservation = reservationRepository.findById(requestReview.getReservationId()).orElseThrow(() -> | ||
| new CustomException(ReservationErrorCode.RESERVATION_NOT_FOUND)); | ||
|
|
||
| if (reservation.getStatus() != ReservationStatus.COMPLETED) { | ||
| throw new CustomException(ReviewErrorCode.REVIEW_NOT_ALLOWED); | ||
| } | ||
| return reservation; | ||
| } | ||
|
|
||
| private Long getFinalMatchingOfManagerId(Long finalMatchingId) { | ||
| return matchingRepository.findById(finalMatchingId).orElseThrow( | ||
| () -> new CustomException(MatchingErrorCode.MATCHING_NOT_FOUND) | ||
| ).getManager().getId(); | ||
| } | ||
| } | ||
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.
URI 코드컨벤션 기준으로 네이밍 부탁드립니다! 복수형 ~