Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
63 changes: 53 additions & 10 deletions src/main/java/koreatech/in/controller/OwnerShopController.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
package koreatech.in.controller;

import io.swagger.annotations.*;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import koreatech.in.annotation.Auth;
import koreatech.in.annotation.ParamValid;
import koreatech.in.dto.EmptyResponse;
import koreatech.in.dto.ExceptionResponse;
import koreatech.in.dto.RequestDataInvalidResponse;
import koreatech.in.dto.normal.shop.request.CreateMenuCategoryRequest;
import koreatech.in.dto.normal.shop.request.CreateMenuRequest;
import koreatech.in.dto.normal.shop.request.CreateShopRequest;
import koreatech.in.dto.normal.shop.request.UpdateMenuRequest;
import koreatech.in.dto.normal.shop.request.UpdateShopRequest;
import koreatech.in.dto.normal.shop.response.*;
import koreatech.in.dto.normal.shop.response.AllMenuCategoriesOfShopResponse;
import koreatech.in.dto.normal.shop.response.AllMenusOfShopResponse;
import koreatech.in.dto.normal.shop.response.AllShopsOfOwnerResponse;
import koreatech.in.dto.normal.shop.response.MenuResponse;
import koreatech.in.dto.normal.shop.response.ShopResponse;
import koreatech.in.exception.BaseException;
import koreatech.in.exception.ExceptionInformation;
import koreatech.in.service.OwnerShopService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import koreatech.in.util.StringXssChecker;

@Auth(role = Auth.Role.OWNER, authority = Auth.Authority.SHOP)
@Api(tags = "(Normal) Owner Shop", description = "상점 (점주 전용)")
Expand All @@ -31,6 +50,30 @@ public class OwnerShopController {

// =============================================== 상점 =================================================

@ApiOperation(value = "상점 생성", notes = "- 사장님 권한만 허용", authorizations = {@Authorization("Authorization")})
@ApiResponses({
@ApiResponse(code = 401, message = "- 잘못된 접근일 때 (code: 100001) \n" +
"- 액세스 토큰이 만료되었을 때 (code: 100004) \n" +
"- 액세스 토큰이 변경되었을 때 (code: 100005)", response = ExceptionResponse.class),
@ApiResponse(code = 403, message = "- 권한이 없을 때 (code: 100003)", response = ExceptionResponse.class)
})
@ParamValid
@ResponseStatus(HttpStatus.CREATED)
@RequestMapping(value = "", method = RequestMethod.POST)
Copy link
Contributor

Choose a reason for hiding this comment

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

PUT /admin/shops 에서 s가 꼭 붙어야 할까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

메서드 단의 RequestMapping에서 클래스 단의 매핑값을 지우는 방법은 찾지 못해서 이렇게 작성했습니다..

public @ResponseBody
ResponseEntity<EmptyResponse> createShop(@ApiParam(name = "상점 정보 JSON", required = true) @RequestBody @Valid CreateShopRequest request, BindingResult bindingResult) {
try {
request = StringXssChecker.xssCheck(request, request.getClass().newInstance());
} catch (Exception exception) {
throw new BaseException(ExceptionInformation.REQUEST_DATA_INVALID);
}

request.checkDataConstraintViolation(); // javax validation으로 판단할 수 없는 제약조건 검사

ownerShopService.createShop(request);
return new ResponseEntity<>(HttpStatus.CREATED);
}

@ApiOperation(value = "상점 조회", notes = "- 사장님 권한만 허용\n- 인증 정보에 대한 신원이 해당 상점의 점주가 아니라면 403(Forbidden) 응답", authorizations = {@Authorization("Authorization")})
@ApiResponses({
@ApiResponse(code = 401, message = "- 잘못된 접근일 때 (code: 100001) \n" +
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/koreatech/in/domain/Shop/Shop.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package koreatech.in.domain.Shop;

import java.util.Date;
import java.util.Objects;

import koreatech.in.dto.admin.shop.request.UpdateShopRequest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.*;

@Getter @Builder
@NoArgsConstructor
@AllArgsConstructor
Expand Down Expand Up @@ -79,6 +80,11 @@ public void update(koreatech.in.dto.normal.shop.request.UpdateShopRequest reques
this.chosung = this.internal_name.substring(0, 1);
}

public void nameUpdate() {
this.internal_name = this.name.replace(" ", "").toLowerCase();
this.chosung = this.internal_name.substring(0, 1);
}

public boolean hasSameOwnerId(Integer ownerId) {
if (this.owner_id == null) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import static koreatech.in.exception.ExceptionInformation.*;

@Getter @Setter
@ApiModel("AdminCreateShopRequest")
public class CreateShopRequest {
@Size(min = 1, max = 15, message = "가게명의 길이는 1자 이상 15자 이하입니다.")
@NotNull(message = "가게명은 필수입니다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package koreatech.in.dto.normal.shop.request;

import static koreatech.in.exception.ExceptionInformation.DUPLICATE_DAY_OF_WEEK_INFORMATION_EXISTS;
import static koreatech.in.exception.ExceptionInformation.LENGTH_OF_OPENS_MUST_BE_7;
import static koreatech.in.exception.ExceptionInformation.TIME_INFORMATION_IS_REQUIRED_UNLESS_CLOSED;

import java.time.DayOfWeek;
import java.util.ArrayList;
import java.util.List;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import koreatech.in.exception.BaseException;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
@ApiModel("CreateShopRequest")
public class CreateShopRequest {
@Size(min = 1, max = 15, message = "가게명의 길이는 1자 이상 15자 이하입니다.")
@NotNull(message = "가게명은 필수입니다.")
@ApiModelProperty(notes = "가게명 \n" +
"- not null \n" +
"- 1자 이상 15자 이하", example = "써니 숯불 도시락", required = true)
private String name;

@Pattern(regexp = "^[0-9]{3}-[0-9]{3,4}-[0-9]{4}$", message = "전화번호의 형식이 올바르지 않습니다.")
@NotNull(message = "전화번호는 필수입니다.")
@ApiModelProperty(notes = "전화번호 \n" +
"- not null \n" +
"- 정규식 `^[0-9]{3}-[0-9]{3,4}-[0-9]{4}$`을 만족해야함", example = "041-123-4567", required = true)
private String phone;

@Valid
@NotNull(message = "운영 시간 정보는 필수입니다.")
@ApiModelProperty(notes = "요일별 운영 시간과 휴무 여부 \n" +
"- not null \n" +
"- 리스트의 길이는 7(1주일 요일 개수) 이어야 함 \n" +
"- day_of_week은 각각 `MONDAY`, `TUESDAY`, `WEDNESDAY`, `THURSDAY`, `FRIDAY`, `SATURDAY`, `SUNDAY` 이어야 함", required = true)
private List<Open> open = new ArrayList<>();

@Size(min = 1, max = 100, message = "주소의 길이는 1자 이상 100자 이하입니다.")
@NotNull(message = "주소는 필수입니다.")
@ApiModelProperty(notes = "주소 \n" +
"- not null \n" +
"- 1자 이상 100자 이하", example = "충청남도 천안시 동남구 병천면 충절로 1600", required = true)
private String address;

@PositiveOrZero(message = "배달 금액은 0원 이상이어야 합니다.")
@ApiModelProperty(notes = "배달 금액 \n" +
"- 0 이상 2147483647 이하 \n" +
"- null일 경우 0으로 저장됨", example = "1000")
private Integer delivery_price = 0;

@Size(min = 1, max = 50, message = "기타정보의 길이는 1자 이상 50자 이하입니다.")
@ApiModelProperty(notes = "기타정보 \n" +
"- 1자 이상 50자 이하", example = "이번주 전 메뉴 10% 할인 이벤트합니다.")
private String description;

@NotNull(message = "배달 가능 여부는 필수입니다.")
@ApiModelProperty(notes = "배달 가능 여부 \n" +
"- not null", example = "false", required = true)
private Boolean delivery;

@NotNull(message = "카드 가능 여부는 필수입니다.")
@ApiModelProperty(notes = "카드 가능 여부 \n" +
"- not null", example = "true", required = true)
private Boolean pay_card;

@NotNull(message = "계좌 이체 가능 여부는 필수입니다.")
@ApiModelProperty(notes = "계좌 이체 가능 여부 \n" +
"- not null", example = "true", required = true)
private Boolean pay_bank;

@NotEmpty(message = "소속시킬 상점 카테고리는 최소 1개 선택하여야합니다.")
@ApiModelProperty(notes = "상점 카테고리 고유 id 리스트 \n" +
"- not null \n" +
"- 최소 1개 \n" +
"- 예시 : [1, 4]", allowableValues = "1, 2, 3, 4, 5", required = true)
private List<Integer> category_ids = new ArrayList<>();

@Size(max = 10, message = "상점 이미지 개수 제한은 최대 10개입니다.")
@ApiModelProperty(notes = "이미지 URL 리스트 \n" +
"- 최대 10개")
private List<String> image_urls = new ArrayList<>();

@Getter @Setter
@ApiModel("Open_7")
public static class Open {
@NotNull(message = "운영 시간의 요일 정보는 필수입니다.")
@ApiModelProperty(notes = "요일 \n" +
"- not null \n" +
"- `MONDAY`, `TUESDAY`, `WEDNESDAY`, `THURSDAY`, `FRIDAY`, `SATURDAY`, `SUNDAY` 중 택1 (중복되면 안됨)", example = "MONDAY", required = true)
private DayOfWeek day_of_week;

@NotNull(message = "운영 시간의 휴무 여부는 필수입니다.")
@ApiModelProperty(notes = "휴무 여부 \n" +
"- not null", example = "false", required = true)
private Boolean closed;

@Pattern(regexp = "^([01][0-9]|2[0-3]):([0-5][0-9])$", message = "운영 시간의 여는 시간 정보 형식이 올바르지 않습니다.")
@ApiModelProperty(notes = "여는 시간 \n" +
"- closed가 false일 때 \n" +
" - not null \n" +
" - 정규식 `^([01][0-9]|2[0-3]):([0-5][0-9])$`을 만족해야 함 \n" +
"- closed가 true일 때 \n" +
" - 어떤 값이 요청되던 null로 저장됨", example = "10:00")
private String open_time;

@Pattern(regexp = "^([01][0-9]|2[0-3]):([0-5][0-9])$", message = "운영 시간의 닫는 시간 정보 형식이 올바르지 않습니다.")
@ApiModelProperty(notes = "닫는 시간 \n" +
"- closed가 false일때 \n" +
" - not null \n" +
" - 정규식 `^([01][0-9]|2[0-3]):([0-5][0-9])$`을 만족해야 함 \n" +
"- closed가 true일 때 \n" +
" - 어떤 값이 요청되던 null로 저장됨", example = "22:30")
private String close_time;
}

public void checkDataConstraintViolation() {
checkOpenConstraintViolation();
}

private void checkOpenConstraintViolation() {
if (this.open.size() != 7) {
throw new BaseException(LENGTH_OF_OPENS_MUST_BE_7);
}

boolean hasEmptyTimeInformation = this.open.stream()
.filter(open -> !open.getClosed())
.anyMatch(open -> open.getOpen_time() == null || open.getClose_time() == null);

if (hasEmptyTimeInformation) {
throw new BaseException(TIME_INFORMATION_IS_REQUIRED_UNLESS_CLOSED);
}

boolean hasDuplicateDayOfWeek = this.open.stream()
.map(Open::getDay_of_week)
.distinct()
.count() != 7;

if (hasDuplicateDayOfWeek) {
throw new BaseException(DUPLICATE_DAY_OF_WEEK_INFORMATION_EXISTS);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package koreatech.in.mapstruct.normal.shop;

import koreatech.in.domain.Shop.Shop;
import koreatech.in.domain.Shop.ShopProfile;
import koreatech.in.dto.normal.shop.request.CreateShopRequest;
import koreatech.in.dto.normal.shop.response.AllShopsResponse;
import koreatech.in.dto.normal.shop.response.ShopResponse;
import org.mapstruct.Mapper;
Expand All @@ -15,4 +17,7 @@ public interface ShopConverter {

@Mapping(target = "category_ids", expression = "java(shopProfile.getShopCategoryIds())")
AllShopsResponse.Shop toAllShopsResponse$Shop(ShopProfile shopProfile);

@Mapping(source = "ownerId", target = "owner_id")
Shop toShop(CreateShopRequest request, Integer ownerId);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package koreatech.in.mapstruct.normal.shop;

import koreatech.in.domain.Shop.ShopOpen;
import koreatech.in.dto.normal.shop.request.CreateShopRequest;
import koreatech.in.dto.normal.shop.request.UpdateShopRequest;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
Expand All @@ -11,5 +12,8 @@ public interface ShopOpenConverter {
ShopOpenConverter INSTANCE = Mappers.getMapper(ShopOpenConverter.class);

@Mapping(source = "shopId", target = "shop_id")
ShopOpen toShopOpen(UpdateShopRequest.Open open, Integer shopId);
ShopOpen toShopOpenForCreate(CreateShopRequest.Open open, Integer shopId);

@Mapping(source = "shopId", target = "shop_id")
ShopOpen toShopOpenForUpdate(UpdateShopRequest.Open open, Integer shopId);
}
4 changes: 4 additions & 0 deletions src/main/java/koreatech/in/repository/ShopMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ public interface ShopMapper {

List<Shop> getShopByOwnerId(@Param("ownerId") Integer ownerId);

void createShop(@Param("shop") Shop shop);

void updateShop(@Param("shop") Shop shop);

void createShopOpens(@Param("shopOpens") List<ShopOpen> shopOpens);

void updateShopOpens(@Param("shopOpens") List<ShopOpen> shopOpens);

ShopCategory getShopCategoryById(@Param("id") Integer id);
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/koreatech/in/service/OwnerShopService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@

import koreatech.in.dto.normal.shop.request.CreateMenuCategoryRequest;
import koreatech.in.dto.normal.shop.request.CreateMenuRequest;
import koreatech.in.dto.normal.shop.request.CreateShopRequest;
import koreatech.in.dto.normal.shop.request.UpdateMenuRequest;
import koreatech.in.dto.normal.shop.request.UpdateShopRequest;
import koreatech.in.dto.normal.shop.response.*;
import koreatech.in.dto.normal.shop.response.AllMenuCategoriesOfShopResponse;
import koreatech.in.dto.normal.shop.response.AllMenusOfShopResponse;
import koreatech.in.dto.normal.shop.response.AllShopsOfOwnerResponse;
import koreatech.in.dto.normal.shop.response.MenuResponse;
import koreatech.in.dto.normal.shop.response.ShopResponse;

public interface OwnerShopService {
ShopResponse getShop(Integer shopId);

AllShopsOfOwnerResponse getAllShopsOfOwner();

void createShop(CreateShopRequest request);

void updateShop(Integer shopId, UpdateShopRequest request);

void createMenuCategory(Integer shopId, CreateMenuCategoryRequest request);
Expand Down
Loading