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
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.onebridge.ouch.controller.review;

import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.onebridge.ouch.apiPayload.ApiResponse;
import com.onebridge.ouch.dto.review.ReviewRequest;
import com.onebridge.ouch.dto.review.ReviewResponse;
import com.onebridge.ouch.security.authorization.UserId;
import com.onebridge.ouch.service.review.ReviewService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@Tag(name = "리뷰 API", description = "리뷰 CRUD API")
@RestController
@RequestMapping("/reviews")
@RequiredArgsConstructor
public class ReviewController {

private final ReviewService reviewService;

@Operation(summary = "리뷰 작성 API", description = "리뷰 생성 API 입니다.")
@PostMapping
public ResponseEntity<ApiResponse<Void>> createReview(
@RequestBody @Validated ReviewRequest request,
@UserId Long userId
) {
reviewService.createReview(userId, request);
return ResponseEntity.ok(ApiResponse.successWithNoData());
}

@Operation(summary = "(테스트용)특정 리뷰 조회 API", description = "리뷰ID로 특정 리뷰를 조회하는 API입니다.")
@GetMapping("/{reviewId}")
public ResponseEntity<ReviewResponse> getReview(@PathVariable("reviewId") Long reviewId) {
ReviewResponse response = reviewService.getReview(reviewId);
return ResponseEntity.ok(response);
}

@Operation(summary = "(테스트용)모든 리뷰 조회 API", description = "모든 리뷰를 조회합니다.")
@GetMapping
public ResponseEntity<List<ReviewResponse>> getAllReviews() {
List<ReviewResponse> list = reviewService.getAllReviews();
return ResponseEntity.ok(list);
}

// 4) 특정 병원(ykiho) 리뷰 목록 조회
@Operation(summary = "특정 병원 리뷰 조회 API", description = "병원 Id를 활용하여 특정 병원의 모든 리뷰를 조회합니다.")
@GetMapping("/hospitals/{ykiho}")
public ResponseEntity<List<ReviewResponse>> getReviewsByHospital(@PathVariable("ykiho") String ykiho) {
List<ReviewResponse> list = reviewService.getReviewsByHospital(ykiho);
return ResponseEntity.ok(list);
}

@Operation(summary = "리뷰 수정 API", description = "리뷰 ID로 리뷰를 수정하는 API입니다.")
@PutMapping("/{reviewId}")
public ResponseEntity<ApiResponse<Void>> updateReview(
@PathVariable("reviewId") Long reviewId,
@RequestBody @Validated ReviewRequest request,
@UserId Long userId
) {
ReviewResponse updated = reviewService.updateReview(reviewId, userId, request);
return ResponseEntity.ok(ApiResponse.successWithNoData());
}

// 6) 리뷰 삭제 (JWT에서 추출한 userId로 작성자 검증)
@Operation(summary = "리뷰 삭제 API", description = "리뷰 ID로 리뷰를 삭제하는 API입니다.")
@DeleteMapping("/{reivewId}")
public ResponseEntity<Void> deleteReview(
@PathVariable("reivewId") Long reviewId,
@UserId Long userId
) {
reviewService.deleteReview(reviewId, userId);
return ResponseEntity.noContent().build();
}

// 7) 내가 쓴 리뷰 목록 조회
@Operation(summary = "내 리뷰 목록 조회 API", description = "내가 작성한 리뷰 목록을 조회하는 API입니다.")
@GetMapping("/me")
public ResponseEntity<List<ReviewResponse>> getMyReviews(
@UserId Long userId
) {
List<ReviewResponse> list = reviewService.getMyReviews(userId);
return ResponseEntity.ok(list);
}
}
1 change: 1 addition & 0 deletions src/main/java/com/onebridge/ouch/domain/Review.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/com/onebridge/ouch/dto/review/ReviewRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.onebridge.ouch.dto.review;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ReviewRequest {

@NotBlank(message = "hospitalYkiho는 필수입니다.")
private String hospitalYkiho; //   String 타입으로 변경

private String contents;

@NotNull(message = "score는 필수입니다.")
@Min(value = 1, message = "score는 최소 1점 이상이어야 합니다.")
@Max(value = 5, message = "score는 최대 5점 이하이어야 합니다.")
private Integer score;

private String imageUrl;
}
20 changes: 20 additions & 0 deletions src/main/java/com/onebridge/ouch/dto/review/ReviewResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.onebridge.ouch.dto.review;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ReviewResponse {

private Long id;
private String userNickname; // userId 대신 닉네임
private String hospitalYkiho; //   String 타입으로 변경
private String contents;
private Integer score;
private String imageUrl;
private String createdAt; // BaseEntity에 만든 createdAt 필드(예: ISO 포맷 문자열)
private String updatedAt; // BaseEntity에 만든 modifiedAt 필드
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.onebridge.ouch.repository.review;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.onebridge.ouch.domain.Review;

public interface ReviewRepository extends JpaRepository<Review, Long> {

// 특정 병원 리뷰 조회
List<Review> findAllByHospital_Ykiho(String hospitalYkiho);

// 특정 사용자(user.id) 기준으로 리뷰 조회
List<Review> findAllByUser_Id(Long userId);
}
123 changes: 123 additions & 0 deletions src/main/java/com/onebridge/ouch/service/review/ReviewService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.onebridge.ouch.service.review;

import com.onebridge.ouch.domain.Hospital;
import com.onebridge.ouch.domain.Review;
import com.onebridge.ouch.domain.User;
import com.onebridge.ouch.dto.review.ReviewRequest;
import com.onebridge.ouch.dto.review.ReviewResponse;
import com.onebridge.ouch.repository.hospital.HospitalRepository;
import com.onebridge.ouch.repository.review.ReviewRepository;
import com.onebridge.ouch.repository.user.UserRepository;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;

import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ReviewService {

private final ReviewRepository reviewRepository;
private final UserRepository userRepository;
private final HospitalRepository hospitalRepository;
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE;

@Transactional
public ReviewResponse createReview(Long userId, ReviewRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException("해당 userId를 찾을 수 없습니다: " + userId));

Hospital hospital = hospitalRepository.findById(request.getHospitalYkiho())
.orElseThrow(() -> new EntityNotFoundException("해당 hospitalYkiho를 찾을 수 없습니다: " + request.getHospitalYkiho()));

Review review = Review.builder()
.user(user)
.hospital(hospital)
.contents(request.getContents())
.score(request.getScore())
.imageUrl(request.getImageUrl())
.build();

Review saved = reviewRepository.save(review);
return toDto(saved);
}

@Transactional(readOnly = true)
public ReviewResponse getReview(Long reviewId) {
Review review = reviewRepository.findById(reviewId)
.orElseThrow(() -> new EntityNotFoundException("해당 reviewId를 찾을 수 없습니다: " + reviewId));
return toDto(review);
}

@Transactional(readOnly = true)
public List<ReviewResponse> getAllReviews() {
return reviewRepository.findAll().stream()
.map(this::toDto)
.collect(Collectors.toList());
}

@Transactional(readOnly = true)
public List<ReviewResponse> getReviewsByHospital(String hospitalYkiho) {
return reviewRepository.findAllByHospital_Ykiho(hospitalYkiho).stream()
.map(this::toDto)
.collect(Collectors.toList());
}

@Transactional
public ReviewResponse updateReview(Long reviewId, Long userId, ReviewRequest request) {
Review review = reviewRepository.findById(reviewId)
.orElseThrow(() -> new EntityNotFoundException("해당 reviewId를 찾을 수 없습니다: " + reviewId));

// 작성자만 수정할 수 있도록 간단 검증
if (!review.getUser().getId().equals(userId)) {
throw new IllegalArgumentException("작성자만 수정할 수 있습니다.");
}

review.setContents(request.getContents());
review.setScore(request.getScore());
review.setImageUrl(request.getImageUrl());

Review updated = reviewRepository.save(review);
return toDto(updated);
}

@Transactional
public void deleteReview(Long reviewId, Long userId) {
Review review = reviewRepository.findById(reviewId)
.orElseThrow(() -> new EntityNotFoundException("해당 reviewId를 찾을 수 없습니다: " + reviewId));

// 작성자만 삭제할 수 있도록 간단 검증
if (!review.getUser().getId().equals(userId)) {
throw new IllegalArgumentException("작성자만 삭제할 수 있습니다.");
}

reviewRepository.delete(review);
}

@Transactional(readOnly = true)
public List<ReviewResponse> getMyReviews(Long userId) {
List<Review> reviews = reviewRepository.findAllByUser_Id(userId);
return reviews.stream()
.map(this::toDto)
.collect(Collectors.toList());
}

private ReviewResponse toDto(Review review) {
return ReviewResponse.builder()
.id(review.getId())
.userNickname(review.getUser().getNickname())
.hospitalYkiho(review.getHospital().getYkiho())
.contents(review.getContents())
.score(review.getScore())
.imageUrl(review.getImageUrl())
.createdAt(review.getCreatedAt().format(dateFormatter))
.updatedAt(review.getUpdatedAt().format(dateFormatter))
.build();
}
}