Skip to content
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

✨ [Feat]: 레크레이션 리뷰 목록 조회 API #42

Merged
merged 3 commits into from
Jan 23, 2024
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
Expand Up @@ -11,7 +11,9 @@
@Getter
@AllArgsConstructor
public enum SuccessStatus implements BaseCode {
_OK(HttpStatus.OK, "COMMON200", "성공입니다.");
_OK(HttpStatus.OK, "COMMON200", "성공입니다."),
_CREATED(HttpStatus.CREATED, "COMMON201", "요청 성공 및 리소스 생성됨");

private final HttpStatus httpStatus;
private final String code;
private final String message;
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/com/avab/avab/controller/RecreationController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,37 @@

import java.util.List;

import jakarta.validation.Valid;

import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.avab.avab.apiPayload.BaseResponse;
import com.avab.avab.apiPayload.code.status.SuccessStatus;
import com.avab.avab.converter.RecreationConverter;
import com.avab.avab.domain.Recreation;
import com.avab.avab.domain.RecreationReview;
import com.avab.avab.domain.User;
import com.avab.avab.domain.enums.Age;
import com.avab.avab.domain.enums.Gender;
import com.avab.avab.domain.enums.Keyword;
import com.avab.avab.domain.enums.Place;
import com.avab.avab.dto.reqeust.RecreationRequestDTO.PostRecreationReviewDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.DescriptionDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.FavoriteDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.PopularRecreationListDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.RecreationPreviewListDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.RecreationReviewCreatedDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.RecreationReviewPageDTO;
import com.avab.avab.security.handler.annotation.AuthUser;
import com.avab.avab.service.RecreationService;
import com.avab.avab.validation.annotation.ExistRecreation;
Expand Down Expand Up @@ -113,4 +121,33 @@ public BaseResponse<FavoriteDTO> toggleFavoriteRecreation(

return BaseResponse.onSuccess(RecreationConverter.toFavoriteDTO(isFavorite));
}

@Operation(summary = "레크레이션 리뷰 작성 API", description = "레크레이션에 리뷰를 작성합니다.")
@ApiResponses({@ApiResponse(responseCode = "COMMON201", description = "리뷰 생성 성공")})
@Parameter(name = "user", hidden = true)
@PostMapping("/{recreationId}/reviews")
@ResponseStatus(code = HttpStatus.CREATED)
public BaseResponse<RecreationReviewCreatedDTO> postRecreationReviewDTO(
@AuthUser User user,
@PathVariable("recreationId") @ExistRecreation Long recreationId,
@Valid @RequestBody PostRecreationReviewDTO request) {
RecreationReview review = recreationService.createReview(user, recreationId, request);

return BaseResponse.of(
SuccessStatus._CREATED, RecreationConverter.toRecreationReviewCreatedDTO(review));
}

@Operation(summary = "레크레이션 리뷰 목록 조회 API", description = "레크레이션 리뷰를 조회합니다.")
@ApiResponses({@ApiResponse(responseCode = "COMMON200", description = "리뷰 조회 성공")})
@GetMapping("/{recreationId}/reviews")
public BaseResponse<RecreationReviewPageDTO> getRecreationReviews(
@PathVariable("recreationId") @ExistRecreation Long recreationId,
@RequestParam(name = "page", defaultValue = "0", required = false) @ValidatePage
Integer page) {

Page<RecreationReview> reviewPage =
recreationService.getRecreationReviews(recreationId, page);

return BaseResponse.onSuccess(RecreationConverter.toRecreationReviewPageDTO(reviewPage));
}
}
50 changes: 50 additions & 0 deletions src/main/java/com/avab/avab/converter/RecreationConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,24 @@
import com.avab.avab.domain.RecreationHashtag;
import com.avab.avab.domain.RecreationKeyword;
import com.avab.avab.domain.RecreationPreparation;
import com.avab.avab.domain.RecreationReview;
import com.avab.avab.domain.RecreationWay;
import com.avab.avab.domain.User;
import com.avab.avab.domain.enums.Age;
import com.avab.avab.domain.enums.Gender;
import com.avab.avab.domain.enums.Keyword;
import com.avab.avab.domain.mapping.RecreationFavorite;
import com.avab.avab.domain.mapping.RecreationRecreationKeyword;
import com.avab.avab.dto.reqeust.RecreationRequestDTO.PostRecreationReviewDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.DescriptionDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.FavoriteDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.PopularRecreationListDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.RecreationPreviewDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.RecreationPreviewListDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.RecreationReviewCreatedDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.RecreationReviewDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.RecreationReviewDTO.AuthorDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.RecreationReviewPageDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.WayDTO;

public class RecreationConverter {
Expand Down Expand Up @@ -142,4 +148,48 @@ public static RecreationFavorite toRecreationFavorite(Recreation recreation, Use
public static FavoriteDTO toFavoriteDTO(Boolean isFavorite) {
return FavoriteDTO.builder().isFavorite(isFavorite).build();
}

public static RecreationReview toRecreationReview(
User user, Recreation recreation, PostRecreationReviewDTO request) {
return RecreationReview.builder()
.recreation(recreation)
.author(user)
.contents(request.getContents())
.stars(request.getStars())
.build();
}

public static RecreationReviewCreatedDTO toRecreationReviewCreatedDTO(RecreationReview review) {
return RecreationReviewCreatedDTO.builder().reviewId(review.getId()).build();
}

public static RecreationReviewPageDTO toRecreationReviewPageDTO(
Page<RecreationReview> reviewPage) {
return RecreationReviewPageDTO.builder()
.reviewList(
reviewPage.stream()
.map(RecreationConverter::toRecreationReviewDTO)
.toList())
.totalPages(reviewPage.getTotalPages())
.build();
}

public static RecreationReviewDTO toRecreationReviewDTO(RecreationReview review) {
User author = review.getAuthor();

return RecreationReviewDTO.builder()
.reviewId(review.getId())
.stars(review.getStars())
.author(
AuthorDTO.builder()
.userId(author.getId())
.username(author.getUsername())
.build())
.createdAt(review.getCreatedAt())
.updatedAt(review.getUpdatedAt())
.contents(review.getContents())
.goodCount(review.getGoodCount())
.badCount(review.getBadCount())
.build();
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/avab/avab/domain/RecreationReview.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;

import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import com.avab.avab.domain.common.BaseEntity;
import com.avab.avab.domain.mapping.RecreationReviewRecommendation;

Expand All @@ -28,6 +31,8 @@
@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicUpdate
@DynamicInsert
public class RecreationReview extends BaseEntity {

@Id
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/com/avab/avab/dto/reqeust/RecreationRequestDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.avab.avab.dto.reqeust;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import lombok.Getter;
import lombok.Setter;

public class RecreationRequestDTO {

@Getter
@Setter
public static class PostRecreationReviewDTO {

@Min(value = 0, message = "별점은 0점 이상이어야 합니다.")
@Max(value = 5, message = "별점은 5점 이하여야 합니다.")
@NotNull(message = "별점은 필수입니다.")
private Integer stars;

@Size(max = 300, message = "리뷰는 300자 이하여야 합니다.")
@NotEmpty(message = "리뷰 내용은 필수입니다.")
private String contents;
}
}
71 changes: 59 additions & 12 deletions src/main/java/com/avab/avab/dto/response/RecreationResponseDTO.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.avab.avab.dto.response;

import java.time.LocalDateTime;
import java.util.List;

import com.avab.avab.domain.enums.Age;
import com.avab.avab.domain.enums.Gender;
import com.avab.avab.domain.enums.Keyword;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -16,8 +18,8 @@ public class RecreationResponseDTO {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class PopularRecreationListDTO {

List<Keyword> keywordList;
Expand All @@ -33,8 +35,8 @@ public static class PopularRecreationListDTO {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class RecreationPreviewDTO {

Long id;
Expand All @@ -50,8 +52,8 @@ public static class RecreationPreviewDTO {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class RecreationPreviewListDTO {

List<RecreationPreviewDTO> recreationList;
Expand All @@ -60,8 +62,8 @@ public static class RecreationPreviewListDTO {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class DescriptionDTO {

Long recreationId;
Expand All @@ -77,8 +79,8 @@ public static class DescriptionDTO {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class WayDTO {

String contents;
Expand All @@ -87,10 +89,55 @@ public static class WayDTO {

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class FavoriteDTO {

Boolean isFavorite;
}

@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class RecreationReviewCreatedDTO {

Long reviewId;
}

@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class RecreationReviewDTO {

Long reviewId;
AuthorDTO author;
Integer stars;
LocalDateTime createdAt;
LocalDateTime updatedAt;
String contents;
Integer goodCount;
Integer badCount;

@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class AuthorDTO {

Long userId;
String username;
}
}

@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public static class RecreationReviewPageDTO {

List<RecreationReviewDTO> reviewList;
Integer totalPages;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.avab.avab.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import com.avab.avab.domain.RecreationReview;

public interface RecreationReviewRepository extends JpaRepository<RecreationReview, Long> {

Page<RecreationReview> findByRecreation_Id(Long recreationId, Pageable pageable);
}
6 changes: 5 additions & 1 deletion src/main/java/com/avab/avab/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
Expand Down Expand Up @@ -35,7 +36,7 @@ public class SecurityConfig {
"/v3/api-docs/**",
"/api/recreations/popular",
"/api/recreations/search",
"/api/recreations/{recreationId}"
"/api/recreations/{recreationId}",
};

@Bean
Expand Down Expand Up @@ -68,6 +69,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
authorize
.requestMatchers(securityAllowArray)
.permitAll()
.requestMatchers(
HttpMethod.GET, "/api/recreations/{recreationId}/reviews")
.permitAll()
.anyRequest()
.authenticated());

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/avab/avab/service/RecreationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import org.springframework.data.domain.Page;

import com.avab.avab.domain.Recreation;
import com.avab.avab.domain.RecreationReview;
import com.avab.avab.domain.User;
import com.avab.avab.domain.enums.Age;
import com.avab.avab.domain.enums.Gender;
import com.avab.avab.domain.enums.Keyword;
import com.avab.avab.domain.enums.Place;
import com.avab.avab.dto.reqeust.RecreationRequestDTO.PostRecreationReviewDTO;
import com.avab.avab.dto.response.RecreationResponseDTO.PopularRecreationListDTO;

public interface RecreationService {
Expand All @@ -30,4 +32,8 @@ Page<Recreation> searchRecreations(
Recreation getRecreationDescription(Long recreationId);

Boolean toggleFavoriteRecreation(Long recreationId, User user);

RecreationReview createReview(User user, Long recreationId, PostRecreationReviewDTO request);

Page<RecreationReview> getRecreationReviews(Long recreationId, Integer page);
}
Loading
Loading