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 : 웨이팅 생성 로직 구현 #26

Merged
merged 13 commits into from
Dec 31, 2023
Merged
11 changes: 11 additions & 0 deletions src/docs/asciidoc/api/album/waiting.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[[waiting-create]]
=== 웨이팅 등록

==== HTTP Request
include::{snippets}/waiting-create/http-request.adoc[]
include::{snippets}/waiting-create/request-fields.adoc[]


==== HTTP Response
include::{snippets}/waiting-create/http-response.adoc[]
include::{snippets}/waiting-create/response-fields.adoc[]
15 changes: 15 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ifndef::snippets[]
:snippets: ../../build/generated-snippets
endif::[]
= Catch Table REST API 문서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:

[[CatchTable-API]]
== Waiting API

include::api/album/waiting.adoc[]
4 changes: 2 additions & 2 deletions src/main/java/com/prgrms/catchtable/common/BaseEntity.java
Copy link
Collaborator

Choose a reason for hiding this comment

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

단순 변수명 변경이기에, 커밋 유형이 style이면 어떨까 싶습니다~

Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
public abstract class BaseEntity {

@CreatedDate
private LocalDateTime createDate;
private LocalDateTime createdAt;

@LastModifiedDate
private LocalDateTime updateDate;
private LocalDateTime updatedAt;

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@

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

@Getter
@RequiredArgsConstructor
public enum ErrorCode {
NOT_EXIST_MEMBER("존재하지 않는 아이디입니다."),
NOT_EXIST_MEMBER("존재하지 않는 회원입니다."),

ALREADY_PREOCCUPIED_RESERVATION_TIME("이미 타인에게 선점권이 있는 예약시간입니다."),
ALREADY_OCCUPIED_RESERVATION_TIME("이미 예약된 시간입니다."),
NOT_EXIST_SHOP("존재하지 않는 매장입니다."),
NOT_EXIST_TIME("존재하지 않는 예약 시간입니다.");
NOT_EXIST_TIME("존재하지 않는 예약 시간입니다."),

ALREADY_CANCELED_WAITING("이미 웨이팅을 취소하였습니다."),
EXISTING_MEMBER_WAITING("이미 회원이 웨이팅 중인 가게가 존재합니다."),
SHOP_NOT_RUNNING("가게가 영업시간이 아닙니다."),
INTERNAL_SERVER_ERROR("내부 서버 오류입니다.");
Copy link
Member

Choose a reason for hiding this comment

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

이 서버에러 메세지는 어떤 경우에 쓰일까요??

Copy link
Member Author

Choose a reason for hiding this comment

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

ExceptionHandler 구현 시 기타 Exception을 handling할 때 사용하려고 했습니다!


private final String message;
}
21 changes: 20 additions & 1 deletion src/main/java/com/prgrms/catchtable/shop/domain/Shop.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package com.prgrms.catchtable.shop.domain;

import static com.prgrms.catchtable.common.exception.ErrorCode.SHOP_NOT_RUNNING;
import static jakarta.persistence.EnumType.STRING;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

import com.prgrms.catchtable.common.BaseEntity;
import com.prgrms.catchtable.common.exception.custom.BadRequestCustomException;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import java.math.BigDecimal;
import java.time.LocalTime;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -42,12 +45,28 @@ public class Shop extends BaseEntity {
@Column(name = "capacity")
private int capacity;

@Column(name = "opening_time")
private LocalTime openingTime;

@Column(name = "closing_time")
private LocalTime closingTime;



@Builder
public Shop(String name, BigDecimal rating, Category category, Address address, int capacity) {
public Shop(String name, BigDecimal rating, Category category, Address address, int capacity, LocalTime openingTime, LocalTime closingTime) {
this.name = name;
this.rating = rating;
this.category = category;
this.address = address;
this.capacity = capacity;
this.openingTime = openingTime;
this.closingTime = closingTime;
}

public void validateIfShopOpened(LocalTime localTime) {
if (localTime.isBefore(openingTime)|| localTime.isAfter(closingTime)){
throw new BadRequestCustomException(SHOP_NOT_RUNNING);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.prgrms.catchtable.waiting.controller;

import com.prgrms.catchtable.waiting.dto.CreateWaitingRequest;
import com.prgrms.catchtable.waiting.dto.CreateWaitingResponse;
import com.prgrms.catchtable.waiting.service.WaitingService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
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.RestController;

@RequiredArgsConstructor
@RequestMapping("/waitings")
@RestController
public class WaitingController {

private final WaitingService waitingService;

@PostMapping("/{shopId}")
public ResponseEntity<CreateWaitingResponse> createWaiting(@PathVariable("shopId") Long shopId,
@RequestBody CreateWaitingRequest request) {
CreateWaitingResponse response = waitingService.createWaiting(shopId, request);
return ResponseEntity.ok(response);
}
}
23 changes: 18 additions & 5 deletions src/main/java/com/prgrms/catchtable/waiting/domain/Waiting.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.prgrms.catchtable.waiting.domain;

import static jakarta.persistence.ConstraintMode.NO_CONSTRAINT;
import static jakarta.persistence.EnumType.STRING;
import static jakarta.persistence.FetchType.LAZY;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;
Expand All @@ -8,8 +10,8 @@
import com.prgrms.catchtable.member.domain.Member;
import com.prgrms.catchtable.shop.domain.Shop;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
import jakarta.persistence.Enumerated;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
Expand All @@ -33,24 +35,35 @@ public class Waiting extends BaseEntity {
@Column(name = "waiting_number")
private int waitingNumber;

@Column(name = "waiting_order")
private int waitingOrder;

@Column(name = "people_count")
private int peopleCount;

@Column(name = "status")
@Enumerated(STRING)
private WaitingStatus status;

@Column(name = "delay_remaining_count")
private int delayRemainingCount;

@OneToOne(fetch = LAZY)
@JoinColumn(name = "member_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@JoinColumn(name = "member_id", foreignKey = @ForeignKey(NO_CONSTRAINT))
private Member member;

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "shop_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
@JoinColumn(name = "shop_id", foreignKey = @ForeignKey(NO_CONSTRAINT))
private Shop shop;

@Builder
public Waiting(int waitingNumber, int peopleCount) {
public Waiting(int waitingNumber, int waitingOrder, int peopleCount, Member member, Shop shop) {
this.waitingNumber = waitingNumber;
this.waitingOrder = waitingOrder;
this.peopleCount = peopleCount;
this.delayRemainingCount = 2;
this.member = member;
this.shop = shop;
status = WaitingStatus.PROGRESS;
delayRemainingCount = 2;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.prgrms.catchtable.waiting.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum WaitingStatus {
PROGRESS("웨이팅 진행 중"),
COMPLETED("웨이팅 입장"),
CANCELED("웨이팅 취소"),
NO_SHOW("노쇼");

private final String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.prgrms.catchtable.waiting.dto;

import jakarta.validation.constraints.Positive;
import lombok.Builder;

@Builder
public record CreateWaitingRequest(
@Positive(message = "인원은 1명 이상이어야 합니다.")
int peopleCount
){}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.prgrms.catchtable.waiting.dto;

import lombok.Builder;

@Builder
public record CreateWaitingResponse(
Long createdWaitingId,
Long shopId,
String shopName,
int peopleCount,
int waitingNumber,
int waitingOrder
Comment on lines +11 to +12
Copy link
Member

Choose a reason for hiding this comment

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

api 스펙 바뀐것은 pr 내용에도 넣어주시면 감사하겠습니다!
근데 이 둘이 각각 정확히 어떤 걸 의미하는건가요??

Copy link
Member Author

Choose a reason for hiding this comment

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

PR 수정했습니다~
waitingNumber는 웨이팅 고유 번호(불변)이고, waitingOrder는 웨이팅 순서(멤버 입장에 따라 1씩 차감)입니다!

){}
33 changes: 33 additions & 0 deletions src/main/java/com/prgrms/catchtable/waiting/dto/WaitingMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.prgrms.catchtable.waiting.dto;

import static lombok.AccessLevel.PRIVATE;

import com.prgrms.catchtable.member.domain.Member;
import com.prgrms.catchtable.shop.domain.Shop;
import com.prgrms.catchtable.waiting.domain.Waiting;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = PRIVATE)
public class WaitingMapper {
// dto -> entity
public static Waiting toWaiting(CreateWaitingRequest request, int waitingNumber, int waitingOrder, Member member, Shop shop){
return Waiting.builder()
.waitingNumber(waitingNumber)
.waitingOrder(waitingOrder)
.peopleCount(request.peopleCount())
.member(member)
.shop(shop).build();
}

// entity -> dto
public static CreateWaitingResponse toCreateWaitingResponse(Waiting waiting){
return CreateWaitingResponse.builder()
.createdWaitingId(waiting.getId())
.shopId(waiting.getShop().getId())
.shopName(waiting.getShop().getName())
.peopleCount(waiting.getPeopleCount())
.waitingNumber(waiting.getWaitingNumber())
.waitingOrder(waiting.getWaitingOrder())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.prgrms.catchtable.waiting.facade;

import static com.prgrms.catchtable.common.exception.ErrorCode.NOT_EXIST_MEMBER;
import static com.prgrms.catchtable.common.exception.ErrorCode.NOT_EXIST_SHOP;

import com.prgrms.catchtable.common.exception.custom.NotFoundCustomException;
import com.prgrms.catchtable.member.domain.Member;
import com.prgrms.catchtable.member.repository.MemberRepository;
import com.prgrms.catchtable.shop.domain.Shop;
import com.prgrms.catchtable.shop.repository.ShopRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class WaitingFacade {
private final MemberRepository memberRepository;
private final ShopRepository shopRepository;

public Member getMemberEntity(Long memberId){
return memberRepository.findById(memberId).orElseThrow(
()-> new NotFoundCustomException(NOT_EXIST_MEMBER)
);
}

public Shop getShopEntity(Long shopId){
return shopRepository.findById(shopId).orElseThrow(
() -> new NotFoundCustomException(NOT_EXIST_SHOP)
);
}
Copy link
Member

Choose a reason for hiding this comment

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

퍼사드 클래스인데 각 메소드에서 존재여부만 파악해서 여러 로직을 조립하는 과정이 없는데 퍼사드인 이유가 있을까요?? 즉, 퍼사드를 사용하는 측에서 shop의 존재여부를 알고 싶어 호출을 하면 여기선 하위 시스템들을 호출, 조립하여 응답을 하는게 아니라 여기서도 존재여부 결과만 리턴해주는 것 같습니다!
그리고 회원이 로그인을 통해 들어오는데 회원의 존재여부를 파악할 필요가 있을까요??

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package com.prgrms.catchtable.waiting.repository;

import com.prgrms.catchtable.member.domain.Member;
import com.prgrms.catchtable.shop.domain.Shop;
import com.prgrms.catchtable.waiting.domain.Waiting;
import com.prgrms.catchtable.waiting.domain.WaitingStatus;
import java.time.LocalDate;
import java.time.LocalDateTime;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface WaitingRepository extends JpaRepository<Waiting, Long> {

boolean existsByMember(Member member);
Long countByShopAndStatusAndCreatedAtBetween(Shop shop, WaitingStatus status, LocalDateTime start, LocalDateTime end);
Long countByShopAndCreatedAtBetween(Shop shop, LocalDateTime start, LocalDateTime end);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.prgrms.catchtable.waiting.service;

import static com.prgrms.catchtable.common.exception.ErrorCode.EXISTING_MEMBER_WAITING;
import static com.prgrms.catchtable.waiting.domain.WaitingStatus.PROGRESS;

import com.prgrms.catchtable.common.exception.custom.BadRequestCustomException;
import com.prgrms.catchtable.member.domain.Member;
import com.prgrms.catchtable.shop.domain.Shop;
import com.prgrms.catchtable.waiting.domain.Waiting;
import com.prgrms.catchtable.waiting.dto.CreateWaitingRequest;
import com.prgrms.catchtable.waiting.dto.CreateWaitingResponse;
import com.prgrms.catchtable.waiting.dto.WaitingMapper;
import com.prgrms.catchtable.waiting.facade.WaitingFacade;
import com.prgrms.catchtable.waiting.repository.WaitingRepository;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class WaitingService {
private final LocalDateTime START_DATE_TIME = LocalDateTime.of(LocalDate.now(), LocalTime.of(0,0,0));
private final LocalDateTime END_DATE_TIME = LocalDateTime.of(LocalDate.now(), LocalTime.of(23,59,59));
private final WaitingRepository waitingRepository;
private final WaitingFacade waitingFacade;
public CreateWaitingResponse createWaiting(Long shopId, CreateWaitingRequest request) {
// 연관 엔티티 조회
Member member = waitingFacade.getMemberEntity(1L);
Shop shop = waitingFacade.getShopEntity(shopId);
Copy link
Member

Choose a reason for hiding this comment

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

이 부분에서 퍼사드를 사용하는 부분이 같은 클래스내에서 로직을 메소드로 따로 빼는거랑 어떤 차이가 있는건지 잘 모르겠습니다!


// shop 영업 중인지 검증
shop.validateIfShopOpened(LocalTime.now());

// 기존 waiting이 있는지 검증
validateIfMemberWaitingExists(member);

// 대기 번호 생성
int waitingNumber = (waitingRepository.countByShopAndStatusAndCreatedAtBetween(shop,
PROGRESS, START_DATE_TIME, END_DATE_TIME)).intValue()+1;

// 대기 순서 생성
int waitingOrder = (waitingRepository.countByShopAndCreatedAtBetween(shop,
START_DATE_TIME, END_DATE_TIME)).intValue()+1;

// waiting 저장
Waiting waiting = WaitingMapper.toWaiting(request, waitingNumber, waitingOrder, member, shop);
Waiting savedWaiting = waitingRepository.save(waiting);

return WaitingMapper.toCreateWaitingResponse(savedWaiting);
}

private void validateIfMemberWaitingExists(Member member) {
if (waitingRepository.existsByMember(member)){
throw new BadRequestCustomException(EXISTING_MEMBER_WAITING);
}
}
}
Loading