Skip to content

Commit

Permalink
Merge pull request #6 from f-lab-edu/feature/4
Browse files Browse the repository at this point in the history
[#4] �Controller 및 테스트 코드 작성
  • Loading branch information
sssukho authored Nov 27, 2024
2 parents c90507d + 39173fa commit adb5b83
Show file tree
Hide file tree
Showing 47 changed files with 1,787 additions and 110 deletions.
38 changes: 35 additions & 3 deletions bp-app-api/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
plugins {
id 'org.springframework.boot' version '3.3.5'
id 'io.spring.dependency-management' version '1.1.6'
id 'io.freefair.lombok' version '8.11'
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'

implementation project(':bp-domain-rdb')
implementation project(':bp-kafka-event-publisher')
implementation project(':bp-dto')
implementation project(':bp-utils')
implementation project(':bp-core-web')
}

implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'org.springframework.boot:spring-boot-starter-validation'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.junit.jupiter:junit-jupiter-api'

testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

test {
useJUnitPlatform()
doFirst {
// build 시에는 integration-test 를 제외하고 test 를 수행한다.
// 개별 테스트 수행 시에는 integration-test 도 포함하여 수행한다.
if (filter.commandLineIncludePatterns.empty) {
useJUnitPlatform {
excludeTags("integration-test")
}
}
}

testLogging {
events 'PASSED', 'FAILED', 'SKIPPED' // 테스트 이벤트 로그
showStandardStreams = false // 표준 출력 및 표준 에러 표시
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.beautify_project.bp_app_api.controller;

import com.beautify_project.bp_app_api.dto.common.ResponseMessage;
import com.beautify_project.bp_app_api.dto.review.FindReviewListRequestParameters;
import com.beautify_project.bp_app_api.enumeration.OrderType;
import com.beautify_project.bp_app_api.enumeration.ReviewSortBy;
import com.beautify_project.bp_app_api.service.ReviewService;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class ReviewController {

private final ReviewService reviewService;

/**
* 리뷰 상세 조회
*
* @param reviewId 조회 대상 리뷰 아이디
* @return ResponseMessage 형태의 ResponseBody
*/
@GetMapping("/v1/reviews/{id}")
@ResponseStatus(code = HttpStatus.OK)
public ResponseMessage findReview(
@PathVariable(value = "id") @NotBlank @NotNull final String reviewId)
throws RuntimeException {
return reviewService.findReview(reviewId);
}

/**
* 샵에 속한 리뷰 목록 조회
*
* @param shopId 조회 대상 샵 아이디
* @param page 페이징 시 페이지 수
* @param count 페이징 시 한 페이지에 출력할 결과 개수
* @param order 리뷰 등록일자 기준 오름 차순 / 내림 차순
* @return ResponseMessage 형태의 ResponseBody
*/
@GetMapping("/v1/reviews/shops/{id}")
@ResponseStatus(code = HttpStatus.OK)
public ResponseMessage findReviewList(@PathVariable(value = "id") @NotBlank final String shopId,
@RequestParam(name = "sort", required = false, defaultValue = "registeredDate") final String sort,
@RequestParam(name = "page", required = false, defaultValue = "0") final int page,
@RequestParam(name = "count", required = false, defaultValue = "10") final int count,
@RequestParam(name = "order", required = false, defaultValue = "asc") final String order)
throws RuntimeException {
return reviewService.findReviewList(
FindReviewListRequestParameters.builder().shopId(shopId).sortBy(ReviewSortBy.from(sort))
.page(page).count(count).orderType(OrderType.from(order)).build());
}

/**
* 리뷰 삭제
*
* @param reviewId 삭제 대상 리뷰 아이디
*/
@DeleteMapping("/v1/reviews/{id}")
@ResponseStatus(code = HttpStatus.NO_CONTENT)
public void deleteReview(@PathVariable(value = "id") @NotBlank final String reviewId)
throws RuntimeException {
reviewService.deleteReview(reviewId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.beautify_project.bp_app_api.controller;

import com.beautify_project.bp_app_api.dto.common.ResponseMessage;
import com.beautify_project.bp_app_api.dto.shop.ImageFiles;
import com.beautify_project.bp_app_api.dto.shop.ShopFindListRequestParameters;
import com.beautify_project.bp_app_api.dto.shop.ShopRegistrationRequest;
import com.beautify_project.bp_app_api.enumeration.OrderType;
import com.beautify_project.bp_app_api.enumeration.ShopSearchType;
import com.beautify_project.bp_app_api.service.ShopService;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequiredArgsConstructor
public class ShopController {

private final ShopService shopService;

@PostMapping("/v1/shops")
@ResponseStatus(code = HttpStatus.OK)
ResponseMessage registerShop(
@RequestPart(value = "images", required = false) final List<MultipartFile> imageFiles,
@Valid @RequestPart(value = "shopRegistrationInfo") final ShopRegistrationRequest shopRegistrationRequest) {

return shopService.registerShop(new ImageFiles(imageFiles), shopRegistrationRequest);
}

@GetMapping("/v1/shops")
@ResponseStatus(code = HttpStatus.OK)
ResponseMessage findShopList(@RequestParam(name = "type") final String searchType,
@RequestParam(name = "page", required = false, defaultValue = "0") final Integer page,
@RequestParam(name = "count", required = false, defaultValue = "10") final Integer count,
@RequestParam(name = "order", required = false, defaultValue = "asc") final String order)
throws RuntimeException {

return shopService.findShopList(
new ShopFindListRequestParameters(ShopSearchType.from(searchType), page, count,
OrderType.from(order)));
}

// TODO: 샵 상세 조회 구현
// TODO: 샵 수정 구현
// TODO: 샵 삭제 구현
// TODO: 샵 좋아요 구현
// TODO: 샵 좋아요 취소 구현
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.beautify_project.bp_app_api.dto.common;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum ErrorCode {

BR001(HttpStatus.BAD_REQUEST, "BR001", "요청 파라미터가 잘못되었습니다."),
BR002(HttpStatus.BAD_REQUEST, "BR002", "본문 형식이 맞지 않습니다."),

UA001(HttpStatus.UNAUTHORIZED, "UA001", "접근 토큰이 존재하지 않습니다."),
UA002(HttpStatus.UNAUTHORIZED, "UA002", "접근 토큰이 만료되었습니다. "),

FB001(HttpStatus.FORBIDDEN, "FB001", "해당 API 사용 권한이 없습니다."),

NF001(HttpStatus.NOT_FOUND, "NF001", "요청 URL이 잘못되었습니다."),

SH001(HttpStatus.NOT_FOUND, "SH001", "등록되지 않은 미용 시술소입니다."),

IS001(HttpStatus.INTERNAL_SERVER_ERROR, "IS001", "시스템 에러가 발생하였습니다. 관리자에게 문읜해주세요.")
;

private final HttpStatus httpStatus;
private final String errorCode;
private final String errorMessage;

ErrorCode(final HttpStatus httpStatus, final String errorCode, final String errorMessage) {
this.httpStatus = httpStatus;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.beautify_project.bp_app_api.dto.common;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.http.HttpStatus;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@ToString
public class ErrorResponseMessage {

@JsonIgnore
private HttpStatus httpStatus;
@JsonInclude(Include.NON_NULL)
private String errorCode;
@JsonInclude(Include.NON_NULL)
private String errorMessage;

private ErrorResponseMessage(final HttpStatus httpStatus, final String errorCode,
final String errorMessage) {
this.httpStatus = httpStatus;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}

public static ErrorResponseMessage createCustomErrorMessage(final ErrorCode errorCode, final String message) {
return new ErrorResponseMessage(errorCode.getHttpStatus(), errorCode.getErrorCode(), message);
}

public static ErrorResponseMessage createErrorMessage(final ErrorCode errorCode) {
return new ErrorResponseMessage(errorCode.getHttpStatus(), errorCode.getErrorCode(),
errorCode.getErrorMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.beautify_project.bp_app_api.dto.common;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@ToString
public class ResponseMessage {

private Object returnValue;

private ResponseMessage(final Object returnValue) {
this.returnValue = returnValue;
}

public static ResponseMessage createResponseMessage(final Object responseBody) {
return new ResponseMessage(responseBody);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.beautify_project.bp_app_api.dto.review;

import com.beautify_project.bp_app_api.enumeration.OrderType;
import com.beautify_project.bp_app_api.enumeration.ReviewSortBy;
import com.beautify_project.bp_app_api.exception.ParameterOutOfRangeException;
import lombok.Builder;
import org.springframework.web.bind.MethodArgumentNotValidException;

@Builder
public record FindReviewListRequestParameters(
String shopId,
ReviewSortBy sortBy,
Integer page,
Integer count,
OrderType orderType) {

public FindReviewListRequestParameters {
validateCount(count);
}

private void validateCount(Integer count) {
if (count > 100 || count < 0) {
throw new ParameterOutOfRangeException("count", String.valueOf(count));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.beautify_project.bp_app_api.dto.review;

import lombok.Builder;

@Builder
public record FindReviewResult(
String id,
String rate,
Long registeredDate,
Member member,
Operation operation
){

public record Member (String id, String name) {}

public record Operation (String id, String name, Long date) {}

@Override
public String toString() {
return "FindReviewResponse{" +
"id='" + id + '\'' +
", rate='" + rate + '\'' +
", registeredDate=" + registeredDate +
", member=" + member +
", operation=" + operation +
'}';
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.beautify_project.bp_app_api.dto.shop;

import java.util.ArrayList;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;

public record ImageFiles(List<MultipartFile> files) {

public ImageFiles {
createEmptyListIfNull(files);
}

private void createEmptyListIfNull(List<MultipartFile> files) {
if (files == null || files.isEmpty()) {
files = new ArrayList<>();
}
}

public boolean isEmpty() {
return files == null || files.isEmpty();
}

public int size() {
return files.size();
}

public MultipartFile get(final int index) {
return files().get(index);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.beautify_project.bp_app_api.dto.shop;


import com.beautify_project.bp_app_api.enumeration.OrderType;
import com.beautify_project.bp_app_api.enumeration.ShopSearchType;
import jakarta.validation.constraints.Size;

public record ShopFindListRequestParameters(
ShopSearchType searchType,
@Size(min = 0)
Integer page,
@Size(max = 100)
Integer count,
OrderType orderType) {
}
Loading

0 comments on commit adb5b83

Please sign in to comment.