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
@@ -1,32 +1,35 @@
package com.somemore.domains.review.controller;

import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

import com.somemore.domains.review.dto.request.ReviewCreateRequestDto;
import com.somemore.domains.review.dto.request.ReviewUpdateRequestDto;
import com.somemore.domains.review.usecase.CreateReviewUseCase;
import com.somemore.domains.review.usecase.DeleteReviewUseCase;
import com.somemore.domains.review.usecase.UpdateReviewUseCase;
import com.somemore.global.auth.annotation.CurrentUser;
import com.somemore.global.common.response.ApiResponse;
import com.somemore.global.imageupload.dto.ImageUploadRequestDto;
import com.somemore.global.imageupload.usecase.ImageUploadUseCase;
import com.somemore.domains.review.dto.request.ReviewCreateRequestDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.UUID;

import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

@Tag(name = "Review Command API", description = "리뷰 생성 수정 삭제 API")
@RequiredArgsConstructor
@RequestMapping("/api")
@RestController
public class ReviewCommandApiController {

private final CreateReviewUseCase createReviewUseCase;
private final UpdateReviewUseCase updateReviewUseCase;
private final DeleteReviewUseCase deleteReviewUseCase;
private final ImageUploadUseCase imageUploadUseCase;

@Secured("ROLE_VOLUNTEER")
Expand All @@ -45,4 +48,47 @@ public ApiResponse<Long> createReview(
);
}

@Secured("ROLE_VOLUNTEER")
@Operation(summary = "리뷰 수정", description = "리뷰를 수정합니다.")
@PutMapping(value = "/review/{id}")
public ApiResponse<String> updateReview(
@CurrentUser UUID userId,
@PathVariable Long id,
@Valid @RequestBody ReviewUpdateRequestDto requestDto) {

updateReviewUseCase.updateReview(id, userId, requestDto);

return ApiResponse.ok(
200,
"리뷰 수정 성공"
);
}

@Secured("ROLE_VOLUNTEER")
@Operation(summary = "리뷰 이미지 수정", description = "리뷰 이미지를 수정합니다.")
@PutMapping(value = "/review/{id}", consumes = MULTIPART_FORM_DATA_VALUE)
public ApiResponse<String> updateReviewImage(
@CurrentUser UUID userId,
@PathVariable Long id,
@RequestPart(value = "img_file", required = false) MultipartFile image) {

String newImgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image));
updateReviewUseCase.updateReviewImageUrl(id, userId, newImgUrl);
return ApiResponse.ok(
200,
"리뷰 이미지 수정 성공"
);
}

@Secured("ROLE_VOLUNTEER")
@Operation(summary = "리뷰 삭제", description = "리뷰를 삭제합니다.")
@DeleteMapping(value = "/review/{id}")
public ApiResponse<String> createReview(
@CurrentUser UUID userId,
@PathVariable Long id
) {
deleteReviewUseCase.deleteReview(userId, id);
return ApiResponse.ok("리뷰 삭제 성공");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import com.somemore.domains.recruitboard.domain.VolunteerCategory;
import com.somemore.global.common.response.ApiResponse;
import com.somemore.domains.review.dto.condition.ReviewSearchCondition;
import com.somemore.domains.review.dto.response.ReviewResponseDto;
import com.somemore.domains.review.dto.response.ReviewWithNicknameResponseDto;
import com.somemore.domains.review.dto.response.ReviewDetailResponseDto;
import com.somemore.domains.review.dto.response.ReviewDetailWithNicknameResponseDto;
import com.somemore.domains.review.usecase.ReviewQueryUseCase;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -31,18 +31,18 @@ public class ReviewQueryApiController {

@Operation(summary = "리뷰 단건 조회", description = "리뷰 ID를 사용하여 단건 리뷰 조회")
@GetMapping("/review/{id}")
public ApiResponse<ReviewResponseDto> getById(@PathVariable Long id) {
public ApiResponse<ReviewDetailResponseDto> getById(@PathVariable Long id) {

return ApiResponse.ok(
200,
reviewQueryUseCase.getReviewById(id),
reviewQueryUseCase.getDetailById(id),
"리뷰 단건 조회 성공"
);
}

@Operation(summary = "기관별 리뷰 조회", description = "기관 ID를 사용하여 리뷰 조회")
@GetMapping("/reviews/center/{centerId}")
public ApiResponse<Page<ReviewWithNicknameResponseDto>> getReviewsByCenterId(
public ApiResponse<Page<ReviewDetailWithNicknameResponseDto>> getReviewsByCenterId(
@PathVariable UUID centerId,
@PageableDefault(sort = "created_at", direction = DESC) Pageable pageable,
@RequestParam(required = false) VolunteerCategory category
Expand All @@ -54,14 +54,14 @@ public ApiResponse<Page<ReviewWithNicknameResponseDto>> getReviewsByCenterId(

return ApiResponse.ok(
200,
reviewQueryUseCase.getReviewsByCenterId(centerId, condition),
reviewQueryUseCase.getDetailsWithNicknameByCenterId(centerId, condition),
"기관 리뷰 리스트 조회 성공"
);
}

@Operation(summary = "봉사자 리뷰 조회", description = "봉사자 ID를 사용하여 리뷰 조회")
@GetMapping("/reviews/volunteer/{volunteerId}")
public ApiResponse<Page<ReviewWithNicknameResponseDto>> getReviewsByVolunteerId(
public ApiResponse<Page<ReviewDetailWithNicknameResponseDto>> getReviewsByVolunteerId(
@PathVariable UUID volunteerId,
@PageableDefault(sort = "created_at", direction = DESC) Pageable pageable,
@RequestParam(required = false) VolunteerCategory category
Expand All @@ -73,7 +73,7 @@ public ApiResponse<Page<ReviewWithNicknameResponseDto>> getReviewsByVolunteerId(

return ApiResponse.ok(
200,
reviewQueryUseCase.getReviewsByVolunteerId(volunteerId, condition),
reviewQueryUseCase.getDetailsWithNicknameByVolunteerId(volunteerId, condition),
"유저 리뷰 리스트 조회 성공"
);
}
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/somemore/domains/review/domain/Review.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

import com.somemore.domains.review.dto.request.ReviewUpdateRequestDto;
import com.somemore.global.common.entity.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -50,4 +51,17 @@ public Review(Long volunteerApplyId, UUID volunteerId, String title,
this.content = content;
this.imgUrl = imgUrl;
}

public boolean isAuthor(UUID volunteerId) {
return this.volunteerId.equals(volunteerId);
}

public void updateWith(ReviewUpdateRequestDto dto) {
this.title = dto.title();
this.content = dto.content();
}

public void updateWith(String imgUrl) {
this.imgUrl = imgUrl;
}
Comment on lines +54 to +66
Copy link
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
@@ -1,21 +1,21 @@
package com.somemore.domains.review.dto.request;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.somemore.domains.volunteerapply.domain.VolunteerApply;
import com.somemore.domains.review.domain.Review;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
import lombok.Builder;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
import java.util.UUID;

@JsonNaming(SnakeCaseStrategy.class)
@Builder
public record ReviewCreateRequestDto(
@Schema(description = "봉사 모집글 아이디", example = "1")
@NotNull(message = "봉사 모집글 아이디는 필수 값입니다.")
Long recruitBoardId,
@Schema(description = "봉사 지원 아이디", example = "1")
@NotNull(message = "봉사 지원 아이디는 필수 값입니다.")
Long volunteerApplyId,
@Schema(description = "리뷰 제목", example = "내 인생 최고의 봉사 활동")
@NotBlank(message = "리뷰 제목은 필수 값입니다.")
String title,
Expand All @@ -24,9 +24,9 @@ public record ReviewCreateRequestDto(
String content
) {

public Review toEntity(VolunteerApply apply, UUID volunteerId, String imgUrl) {
public Review toEntity(UUID volunteerId, String imgUrl) {
return Review.builder()
.volunteerApplyId(apply.getId())
.volunteerApplyId(volunteerApplyId)
.volunteerId(volunteerId)
.title(title)
.content(content)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.somemore.domains.review.dto.request;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Builder;

@JsonNaming(SnakeCaseStrategy.class)
@Builder
public record ReviewUpdateRequestDto(
@Schema(description = "리뷰 제목", example = "내 인생 최고의 봉사 활동")
@NotBlank(message = "리뷰 제목은 필수 값입니다.")
String title,
@Schema(description = "리뷰 내용", example = "담당자님도 정말 친절하였고 정말 보람찬 봉사였어요 <br>")
@NotBlank(message = "리뷰 내용은 필수 값입니다.")
String content
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

@Builder
@JsonNaming(SnakeCaseStrategy.class)
@Schema(description = "리뷰 응답 DTO")
public record ReviewResponseDto(
@Schema(description = "리뷰 상세 조회 응답 DTO")
public record ReviewDetailResponseDto(
@Schema(description = "리뷰 ID", example = "123")
Long id,
@Schema(description = "봉사자(작성자) ID", example = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d")
Expand All @@ -28,8 +28,8 @@ public record ReviewResponseDto(
LocalDateTime updatedAt
) {

public static ReviewResponseDto from(Review review) {
return ReviewResponseDto.builder()
public static ReviewDetailResponseDto from(Review review) {
return ReviewDetailResponseDto.builder()
.id(review.getId())
.volunteerId(review.getVolunteerId())
.title(review.getTitle())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@Builder
@JsonNaming(SnakeCaseStrategy.class)
@Schema(description = "작성자 닉네임이 포함된 리뷰 응답 DTO")
public record ReviewWithNicknameResponseDto(
public record ReviewDetailWithNicknameResponseDto(
@Schema(description = "리뷰 ID", example = "123")
Long id,
@Schema(description = "봉사자(작성자) ID", example = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d")
Expand All @@ -30,8 +30,8 @@ public record ReviewWithNicknameResponseDto(
LocalDateTime updateAt
) {

public static ReviewWithNicknameResponseDto from(Review review, String volunteerNickname) {
return ReviewWithNicknameResponseDto.builder()
public static ReviewDetailWithNicknameResponseDto from(Review review, String volunteerNickname) {
return ReviewDetailWithNicknameResponseDto.builder()
.id(review.getId())
.volunteerId(review.getVolunteerId())
.volunteerNickname(volunteerNickname)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static com.somemore.global.exception.ExceptionMessage.REVIEW_RESTRICTED_TO_ATTENDED;

import com.somemore.domains.review.repository.ReviewRepository;
import com.somemore.domains.review.usecase.ReviewQueryUseCase;
import com.somemore.domains.volunteerapply.domain.VolunteerApply;
import com.somemore.domains.volunteerapply.usecase.VolunteerApplyQueryUseCase;
import com.somemore.global.exception.BadRequestException;
Expand All @@ -23,24 +24,24 @@
public class CreateReviewService implements CreateReviewUseCase {

private final ReviewRepository reviewRepository;
private final ReviewQueryUseCase reviewQueryUseCase;
private final VolunteerApplyQueryUseCase volunteerApplyQueryUseCase;

@Override
public Long createReview(ReviewCreateRequestDto requestDto, UUID volunteerId, String imgUrl) {
VolunteerApply apply = getVolunteerApply(requestDto.recruitBoardId(), volunteerId);
validateDuplicateReview(apply);
validateDuplicateReview(requestDto.volunteerApplyId());

VolunteerApply apply = volunteerApplyQueryUseCase.getById(requestDto.volunteerApplyId());
validateActivityCompletion(apply);

Review review = requestDto.toEntity(apply, volunteerId, imgUrl);
return reviewRepository.save(review).getId();
}
Review review = requestDto.toEntity(volunteerId, imgUrl);
reviewRepository.save(review);

private VolunteerApply getVolunteerApply(Long recruitBoardId, UUID volunteerId) {
return volunteerApplyQueryUseCase.getByRecruitIdAndVolunteerId(recruitBoardId, volunteerId);
return review.getId();
}

private void validateDuplicateReview(VolunteerApply apply) {
if (reviewRepository.existsByVolunteerApplyId(apply.getId())) {
private void validateDuplicateReview(Long volunteerApplyId) {
if (reviewQueryUseCase.existsByVolunteerApplyId(volunteerApplyId)) {
throw new DuplicateException(REVIEW_ALREADY_EXISTS);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.somemore.domains.review.service;

import com.somemore.domains.review.domain.Review;
import com.somemore.domains.review.service.validator.ReviewValidator;
import com.somemore.domains.review.usecase.DeleteReviewUseCase;
import com.somemore.domains.review.usecase.ReviewQueryUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.UUID;

@RequiredArgsConstructor
@Transactional
@Service
public class DeleteReviewService implements DeleteReviewUseCase {

private final ReviewQueryUseCase reviewQueryUseCase;
private final ReviewValidator reviewValidator;

@Override
public void deleteReview(UUID volunteerId, Long id) {
Review review = reviewQueryUseCase.getById(id);

reviewValidator.validateAuthor(review, volunteerId);
review.markAsDeleted();
}

}
Loading
Loading