Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions global/src/main/java/com/homeaid/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.homeaid.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
Expand Down Expand Up @@ -94,4 +92,13 @@ public GroupedOpenApi boardAPI() {
.build();

}

@Bean
public GroupedOpenApi reviewAPI() {
return GroupedOpenApi.builder()
.group("reviews")
.displayName("리뷰")
.pathsToMatch("/api/v1/review/**")
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.sql.SQLIntegrityConstraintViolationException;

@Slf4j
@RestControllerAdvice
Expand Down Expand Up @@ -50,5 +51,16 @@ public ResponseEntity<CommonApiResponse<Void>> handleParsingException(HttpMessag
.body(CommonApiResponse.fail("INVALID_JSON", "요청 형식이 잘못되었습니다."));
}

/**
*db 제약조건에 걸릴시
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public ResponseEntity<CommonApiResponse<Void>> handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) {
log.warn("[SQLIntegrityConstraintViolationException] {}", e.getMessage());

return ResponseEntity.badRequest()
.body(CommonApiResponse.fail("duplicate value", "이미 처리된 요청 입니다"));
}


}
6 changes: 6 additions & 0 deletions review/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,10 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6")
runtimeOnly("com.mysql:mysql-connector-j")

implementation(project(":user"))
implementation(project(":global"))
implementation(project(":reservation"))


}
49 changes: 49 additions & 0 deletions review/src/main/java/com/homeaid/controller/ReviewController.java
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")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

URI 코드컨벤션 기준으로 네이밍 부탁드립니다! 복수형 ~

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());
}


}
47 changes: 47 additions & 0 deletions review/src/main/java/com/homeaid/domain/Review.java
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;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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();
}
}
31 changes: 31 additions & 0 deletions review/src/main/java/com/homeaid/exception/ReviewErrorCode.java
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;
}
10 changes: 10 additions & 0 deletions review/src/main/java/com/homeaid/repository/ReviewRepository.java
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> {
}
12 changes: 12 additions & 0 deletions review/src/main/java/com/homeaid/service/ReviewService.java
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);
}
97 changes: 97 additions & 0 deletions review/src/main/java/com/homeaid/service/ReviewServiceImpl.java
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

예약 Repository에서 deleteById 메서드를 사용하고 있습니다.
리뷰 Repository로 수정이 필요해 보입니다!

}


/**
* 요청 받은 예약이 유요한 예약이고 완료상태 검증
*
* @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();
}
}