From 2dbfcb543a87bdc43c232de54b3470abea7f1918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B1=84=EB=A6=B0=20=28Bryn=29?= <67696767+cofls6581@users.noreply.github.com> Date: Mon, 16 Jan 2023 20:07:54 +0900 Subject: [PATCH 1/8] Feature/93 create coupon compaign (#135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : enum final 키워드 추가 * chore : 주석 질문 답변 * feat : 쿠폰 도메인 새로운 이셉션 처리 적용 * feat : 쿠폰 도메인 에러코드 목록 API 생성 * feat : 쿠폰 캠페인 API 작성 * feat : 쿠폰 캠페인 ApplyTarget 칼럼 디폴트값 설정 * refactor : 유저 id 뽑는 부분 유즈케이스로 위치 변경 * refactor : request 값 validation 에러 메시지 한글로 변경 * refactor : 함수명 통일성 있게 변경 --- .../coupon/controller/CouponController.java | 32 ++++++++ .../reqeust/CreateCouponCampaignRequest.java | 75 +++++++++++++++++++ .../CreateCouponCampaignResponse.java | 27 +++++++ .../coupon/service/CreateCouponUseCase.java | 38 ++++++++++ .../example/controller/ExampleController.java | 7 ++ .../domains/coupon/domain/ApplyTarget.java | 2 +- .../domains/coupon/domain/CouponCampaign.java | 5 ++ .../domains/coupon/domain/DiscountType.java | 2 +- .../domains/coupon/domain/IssuedCoupon.java | 2 +- .../AlreadyExistCouponCampaignException.java | 12 +++ .../coupon/exception/CouponErrorCode.java | 36 +++++++++ .../repository/CouponCampaignRepository.java | 9 +++ .../CreateCouponCampaignDomainService.java | 22 ++++++ 13 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/coupon/controller/CouponController.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/reqeust/CreateCouponCampaignRequest.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/response/CreateCouponCampaignResponse.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/coupon/service/CreateCouponUseCase.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/exception/AlreadyExistCouponCampaignException.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/exception/CouponErrorCode.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/repository/CouponCampaignRepository.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/service/CreateCouponCampaignDomainService.java diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/coupon/controller/CouponController.java b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/controller/CouponController.java new file mode 100644 index 00000000..a8b15b9c --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/controller/CouponController.java @@ -0,0 +1,32 @@ +package band.gosrock.api.coupon.controller; + + +import band.gosrock.api.coupon.dto.reqeust.*; +import band.gosrock.api.coupon.dto.response.*; +import band.gosrock.api.coupon.service.CreateCouponUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +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; + +@SecurityRequirement(name = "access-token") +@Tag(name = "쿠폰 관련 컨트롤러") +@RestController +@RequestMapping("/v1/coupons") +@RequiredArgsConstructor +public class CouponController { + + private final CreateCouponUseCase createCouponUseCase; + + @Operation(summary = "쿠폰 캠페인 생성 API") + @PostMapping("/campaigns") + public CreateCouponCampaignResponse createCouponCampaign( + @RequestBody @Valid CreateCouponCampaignRequest createCouponCampaignRequest) { + return createCouponUseCase.execute(createCouponCampaignRequest); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/reqeust/CreateCouponCampaignRequest.java b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/reqeust/CreateCouponCampaignRequest.java new file mode 100644 index 00000000..8b6c12e2 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/reqeust/CreateCouponCampaignRequest.java @@ -0,0 +1,75 @@ +package band.gosrock.api.coupon.dto.reqeust; + + +import band.gosrock.domain.domains.coupon.domain.ApplyTarget; +import band.gosrock.domain.domains.coupon.domain.CouponCampaign; +import band.gosrock.domain.domains.coupon.domain.CouponStockInfo; +import band.gosrock.domain.domains.coupon.domain.DiscountType; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import javax.validation.constraints.Future; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.Nullable; + +@Getter +@RequiredArgsConstructor +public class CreateCouponCampaignRequest { + + @NotNull(message = "host Id를 입력해주세요.") + private Long hostId; + + @NotNull(message = "discountType을 입력해주세요.") + private DiscountType discountType; + + @Nullable + @Schema(nullable = true, defaultValue = "ALL") + private ApplyTarget applyTarget; + + @NotNull(message = "validTerm을 입력해주세요.") + @Positive(message = "validTerm은 양수여야합니다.(유효기간)") + private Long validTerm; + + // 쿠폰 발행 시작 시각 + @NotNull(message = "startAt을 입력해주세요.") + private LocalDateTime startAt; + + // 쿠폰 발행 마감 시각 + @NotNull(message = "endAt을 입력해주세요.") + @Future(message = "endAt은 값이 미래여야합니다.") + private LocalDateTime endAt; + + @NotNull(message = "issuedAmount을 입력해주세요.") + @Positive(message = "issuedAmount는 양수여야합니다.") + private Long issuedAmount; + + @NotNull(message = "discountAmount을 입력해주세요.") + @Positive(message = "discountAmount은 양수여야합니다.") + private Long discountAmount; + + @NotBlank(message = "couponCode를 입력해주세요.") + private String couponCode; + + public CouponCampaign toOnceEntity() { + CouponStockInfo couponStockInfo = + CouponStockInfo.builder() + .issuedAmount(issuedAmount) + .remainingAmount(issuedAmount) + .build(); + + return CouponCampaign.builder() + .hostId(hostId) + .discountType(discountType) + .applyTarget(applyTarget) + .validTerm(validTerm) + .startAt(startAt) + .endAt(endAt) + .couponStockInfo(couponStockInfo) + .discountAmount(discountAmount) + .couponCode(couponCode) + .build(); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/response/CreateCouponCampaignResponse.java b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/response/CreateCouponCampaignResponse.java new file mode 100644 index 00000000..44778e26 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/response/CreateCouponCampaignResponse.java @@ -0,0 +1,27 @@ +package band.gosrock.api.coupon.dto.response; + + +import band.gosrock.domain.domains.coupon.domain.CouponCampaign; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +@Builder +public record CreateCouponCampaignResponse( + @Schema(description = "쿠폰 캠페인 ID") Long couponCampaignId, + @Schema(description = "쿠폰 코드") String couponCode, + @Schema(description = "생성한 쿠폰 총 매수") Long issuedAmount, + @Schema(description = "쿠폰 생성한 호스트 ID") Long hostId) { + + /* + 나중에 디자인 나오고 더 필요한 리스폰스 값 있으면 추가할 예정입니다. + */ + + public static CreateCouponCampaignResponse of(CouponCampaign couponCampaign, Long hostId) { + return CreateCouponCampaignResponse.builder() + .couponCampaignId(couponCampaign.getId()) + .couponCode(couponCampaign.getCouponCode()) + .issuedAmount(couponCampaign.getCouponStockInfo().getIssuedAmount()) + .hostId(hostId) + .build(); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/coupon/service/CreateCouponUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/service/CreateCouponUseCase.java new file mode 100644 index 00000000..02ca2a56 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/service/CreateCouponUseCase.java @@ -0,0 +1,38 @@ +package band.gosrock.api.coupon.service; + + +import band.gosrock.api.config.security.SecurityUtils; +import band.gosrock.api.coupon.dto.reqeust.CreateCouponCampaignRequest; +import band.gosrock.api.coupon.dto.response.CreateCouponCampaignResponse; +import band.gosrock.common.annotation.UseCase; +import band.gosrock.domain.domains.coupon.domain.CouponCampaign; +import band.gosrock.domain.domains.coupon.repository.CouponCampaignRepository; +import band.gosrock.domain.domains.coupon.service.CreateCouponCampaignDomainService; +import band.gosrock.domain.domains.user.service.UserDomainService; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@UseCase +@RequiredArgsConstructor +public class CreateCouponUseCase { + + private final UserDomainService userDomainService; + private final CreateCouponCampaignDomainService createCouponCampaignDomainService; + private final CouponCampaignRepository couponCampaignRepository; + + @Transactional + public CreateCouponCampaignResponse execute( + CreateCouponCampaignRequest createCouponCampaignRequest) { + // 존재하는 유저인지 검증 + userDomainService.retrieveUser(SecurityUtils.getCurrentUserId()); + // 슈퍼 호스트인지 검증 + // TODO : 차후 호스트쪽 코드 구조 나오는 거 보고 넣을 예정 + // 이미 생성된 쿠폰 코드인지 검증 + createCouponCampaignDomainService.checkCouponCodeExists( + createCouponCampaignRequest.getCouponCode()); + // 쿠폰 생성 + CouponCampaign couponCampaign = + couponCampaignRepository.save(createCouponCampaignRequest.toOnceEntity()); + return CreateCouponCampaignResponse.of(couponCampaign, couponCampaign.getHostId()); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java b/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java index 97e2c18f..bf03744b 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java @@ -10,6 +10,7 @@ import band.gosrock.common.annotation.DevelopOnlyApi; import band.gosrock.common.exception.GlobalErrorCode; import band.gosrock.domain.domains.cart.exception.CartErrorCode; +import band.gosrock.domain.domains.coupon.exception.CouponErrorCode; import band.gosrock.domain.domains.issuedTicket.exception.IssuedTicketErrorCode; import band.gosrock.domain.domains.order.exception.OrderErrorCode; import band.gosrock.domain.domains.user.exception.UserErrorCode; @@ -108,4 +109,10 @@ public void getPaymentsCancelErrorCode() {} @Operation(summary = "토스 거래 조회 관련 에러 코드 나열") @ApiErrorCodeExample(TransactionGetErrorCode.class) public void getTransactionGetErrorCode() {} + + @GetMapping("/coupon") + @DevelopOnlyApi + @Operation(summary = "쿠폰 도메인 관련 에러 코드 나열") + @ApiErrorCodeExample(CouponErrorCode.class) + public void getCouponErrorCode() {} } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/ApplyTarget.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/ApplyTarget.java index 5f2dd883..caa2fa54 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/ApplyTarget.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/ApplyTarget.java @@ -9,5 +9,5 @@ public enum ApplyTarget { ALL("ALL"), SUB("SUB"); - private String value; + private final String value; } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/CouponCampaign.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/CouponCampaign.java index 86f4fefb..0c8ee0e5 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/CouponCampaign.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/CouponCampaign.java @@ -10,7 +10,10 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +@DynamicInsert @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Entity(name = "tbl_coupon_campaign") @@ -27,8 +30,10 @@ public class CouponCampaign extends BaseTimeEntity { private DiscountType discountType; @Enumerated(EnumType.STRING) + @ColumnDefault("'ALL'") private ApplyTarget applyTarget; + // 사용기한(일자) ex.10 -> 발급 이후 10일 동안 쿠폰 사용 가능 private Long validTerm; // 쿠폰 발행 시작 시각 diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/DiscountType.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/DiscountType.java index 793665f4..6430593c 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/DiscountType.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/DiscountType.java @@ -11,5 +11,5 @@ public enum DiscountType { AMOUNT("AMOUNT"), // 정률 할인 PERCENTAGE("PERCENTAGE"); - private String value; + private final String value; } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/IssuedCoupon.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/IssuedCoupon.java index 0bad05be..8fdf5655 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/IssuedCoupon.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/IssuedCoupon.java @@ -44,7 +44,7 @@ public IssuedCoupon(CouponCampaign couponCampaign, Long userId) { } public String getCouponName() { - // TODO : 쿠폰코드가 쿠폰이름 맞나? + // 쿠폰코드==쿠폰이름 return this.couponCampaign.getCouponCode(); } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/exception/AlreadyExistCouponCampaignException.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/exception/AlreadyExistCouponCampaignException.java new file mode 100644 index 00000000..e101fdf1 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/exception/AlreadyExistCouponCampaignException.java @@ -0,0 +1,12 @@ +package band.gosrock.domain.domains.coupon.exception; + + +import band.gosrock.common.exception.DuDoongCodeException; + +public class AlreadyExistCouponCampaignException extends DuDoongCodeException { + public static final DuDoongCodeException EXCEPTION = new AlreadyExistCouponCampaignException(); + + private AlreadyExistCouponCampaignException() { + super(CouponErrorCode.DUPLICATE_COUPON_CODE); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/exception/CouponErrorCode.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/exception/CouponErrorCode.java new file mode 100644 index 00000000..27686234 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/exception/CouponErrorCode.java @@ -0,0 +1,36 @@ +package band.gosrock.domain.domains.coupon.exception; + +import static band.gosrock.common.consts.DuDoongStatic.BAD_REQUEST; + +import band.gosrock.common.annotation.ExplainError; +import band.gosrock.common.dto.ErrorReason; +import band.gosrock.common.exception.BaseErrorCode; +import java.lang.reflect.Field; +import java.util.Objects; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum CouponErrorCode implements BaseErrorCode { + DUPLICATE_COUPON_CODE(BAD_REQUEST, "Coupon_400_1", "동일한 쿠폰 코드가 이미 존재합니다."); + private final Integer status; + private final String code; + private final String reason; + + @Override + public ErrorReason getErrorReason() { + return band.gosrock.common.dto.ErrorReason.builder() + .reason(reason) + .code(code) + .status(status) + .build(); + } + + @Override + public String getExplainError() throws NoSuchFieldException { + Field field = this.getClass().getField(this.name()); + ExplainError annotation = field.getAnnotation(ExplainError.class); + return Objects.nonNull(annotation) ? annotation.value() : this.getReason(); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/repository/CouponCampaignRepository.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/repository/CouponCampaignRepository.java new file mode 100644 index 00000000..9929260b --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/repository/CouponCampaignRepository.java @@ -0,0 +1,9 @@ +package band.gosrock.domain.domains.coupon.repository; + + +import band.gosrock.domain.domains.coupon.domain.CouponCampaign; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CouponCampaignRepository extends JpaRepository { + boolean existsByCouponCode(String couponCode); +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/service/CreateCouponCampaignDomainService.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/service/CreateCouponCampaignDomainService.java new file mode 100644 index 00000000..50c5161d --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/service/CreateCouponCampaignDomainService.java @@ -0,0 +1,22 @@ +package band.gosrock.domain.domains.coupon.service; + + +import band.gosrock.common.annotation.DomainService; +import band.gosrock.domain.domains.coupon.exception.AlreadyExistCouponCampaignException; +import band.gosrock.domain.domains.coupon.repository.CouponCampaignRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@DomainService +@RequiredArgsConstructor +public class CreateCouponCampaignDomainService { + + private final CouponCampaignRepository couponCampaignRepository; + + @Transactional(readOnly = true) + public void checkCouponCodeExists(String couponCode) { + if (couponCampaignRepository.existsByCouponCode(couponCode)) { + throw AlreadyExistCouponCampaignException.EXCEPTION; + } + } +} From e239a84c835358e7cb7632067037dbcd5f68381e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B1=84=EB=A6=B0=20=28Bryn=29?= <67696767+cofls6581@users.noreply.github.com> Date: Tue, 17 Jan 2023 01:02:27 +0900 Subject: [PATCH 2/8] =?UTF-8?q?refactor=20:=20=EC=BF=A0=ED=8F=B0=20?= =?UTF-8?q?=EC=BA=A0=ED=8E=98=EC=9D=B8=20=EC=83=9D=EC=84=B1=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20(#142)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor : DateTimePeriod vo 생성 후 기존 변수들과 교체 * refactor : CouponCampaignAdaptor 생성 * refactor : save 로직 adaptor로 감싸 도메인 서비스단에서 처리하도록 변경 * refactor : DateTimePeriod vo common 패키지로 이동 --- .../reqeust/CreateCouponCampaignRequest.java | 11 +++--- .../coupon/service/CreateCouponUseCase.java | 5 ++- .../domain/common/vo/DateTimePeriod.java | 34 +++++++++++++++++++ .../coupon/adaptor/CouponCampaignAdaptor.java | 23 +++++++++++++ .../domains/coupon/domain/CouponCampaign.java | 15 ++++---- .../CreateCouponCampaignDomainService.java | 15 ++++---- 6 files changed, 79 insertions(+), 24 deletions(-) create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/common/vo/DateTimePeriod.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/adaptor/CouponCampaignAdaptor.java diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/reqeust/CreateCouponCampaignRequest.java b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/reqeust/CreateCouponCampaignRequest.java index 8b6c12e2..4ff393fc 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/reqeust/CreateCouponCampaignRequest.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/dto/reqeust/CreateCouponCampaignRequest.java @@ -1,10 +1,8 @@ package band.gosrock.api.coupon.dto.reqeust; -import band.gosrock.domain.domains.coupon.domain.ApplyTarget; -import band.gosrock.domain.domains.coupon.domain.CouponCampaign; -import band.gosrock.domain.domains.coupon.domain.CouponStockInfo; -import band.gosrock.domain.domains.coupon.domain.DiscountType; +import band.gosrock.domain.common.vo.DateTimePeriod; +import band.gosrock.domain.domains.coupon.domain.*; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; import javax.validation.constraints.Future; @@ -59,14 +57,15 @@ public CouponCampaign toOnceEntity() { .issuedAmount(issuedAmount) .remainingAmount(issuedAmount) .build(); + DateTimePeriod dateTimePeriod = + DateTimePeriod.builder().startAt(startAt).endAt(endAt).build(); return CouponCampaign.builder() .hostId(hostId) .discountType(discountType) .applyTarget(applyTarget) .validTerm(validTerm) - .startAt(startAt) - .endAt(endAt) + .dateTimePeriod(dateTimePeriod) .couponStockInfo(couponStockInfo) .discountAmount(discountAmount) .couponCode(couponCode) diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/coupon/service/CreateCouponUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/service/CreateCouponUseCase.java index 02ca2a56..b558128c 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/coupon/service/CreateCouponUseCase.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/coupon/service/CreateCouponUseCase.java @@ -6,7 +6,6 @@ import band.gosrock.api.coupon.dto.response.CreateCouponCampaignResponse; import band.gosrock.common.annotation.UseCase; import band.gosrock.domain.domains.coupon.domain.CouponCampaign; -import band.gosrock.domain.domains.coupon.repository.CouponCampaignRepository; import band.gosrock.domain.domains.coupon.service.CreateCouponCampaignDomainService; import band.gosrock.domain.domains.user.service.UserDomainService; import lombok.RequiredArgsConstructor; @@ -18,7 +17,6 @@ public class CreateCouponUseCase { private final UserDomainService userDomainService; private final CreateCouponCampaignDomainService createCouponCampaignDomainService; - private final CouponCampaignRepository couponCampaignRepository; @Transactional public CreateCouponCampaignResponse execute( @@ -32,7 +30,8 @@ public CreateCouponCampaignResponse execute( createCouponCampaignRequest.getCouponCode()); // 쿠폰 생성 CouponCampaign couponCampaign = - couponCampaignRepository.save(createCouponCampaignRequest.toOnceEntity()); + createCouponCampaignDomainService.createCouponCampaign( + createCouponCampaignRequest.toOnceEntity()); return CreateCouponCampaignResponse.of(couponCampaign, couponCampaign.getHostId()); } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/vo/DateTimePeriod.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/vo/DateTimePeriod.java new file mode 100644 index 00000000..78db230d --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/vo/DateTimePeriod.java @@ -0,0 +1,34 @@ +package band.gosrock.domain.common.vo; + + +import java.time.LocalDateTime; +import javax.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DateTimePeriod { + // 쿠폰 발행 시작 시각 + private LocalDateTime startAt; + // 쿠폰 발행 마감 시각 + private LocalDateTime endAt; + + @Builder + public DateTimePeriod(LocalDateTime startAt, LocalDateTime endAt) { + this.startAt = startAt; + this.endAt = endAt; + } + + public static DateTimePeriod between(LocalDateTime startAt, LocalDateTime endAt) { + return new DateTimePeriod(startAt, endAt); + } + + public boolean contains(LocalDateTime datetime) { + return (datetime.isAfter(startAt) || datetime.equals(startAt)) + && (datetime.isBefore(endAt) || datetime.equals(endAt)); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/adaptor/CouponCampaignAdaptor.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/adaptor/CouponCampaignAdaptor.java new file mode 100644 index 00000000..a671a8f1 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/adaptor/CouponCampaignAdaptor.java @@ -0,0 +1,23 @@ +package band.gosrock.domain.domains.coupon.adaptor; + + +import band.gosrock.common.annotation.Adaptor; +import band.gosrock.domain.domains.coupon.domain.CouponCampaign; +import band.gosrock.domain.domains.coupon.exception.AlreadyExistCouponCampaignException; +import band.gosrock.domain.domains.coupon.repository.CouponCampaignRepository; +import lombok.RequiredArgsConstructor; + +@Adaptor +@RequiredArgsConstructor +public class CouponCampaignAdaptor { + private final CouponCampaignRepository couponCampaignRepository; + + public CouponCampaign save(CouponCampaign couponCampaign) { + return couponCampaignRepository.save(couponCampaign); + } + + public void existsByCouponCode(String couponCode) { + if (couponCampaignRepository.existsByCouponCode(couponCode)) + throw AlreadyExistCouponCampaignException.EXCEPTION; + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/CouponCampaign.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/CouponCampaign.java index 0c8ee0e5..c615bee3 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/CouponCampaign.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/domain/CouponCampaign.java @@ -2,7 +2,7 @@ import band.gosrock.domain.common.model.BaseTimeEntity; -import java.time.LocalDateTime; +import band.gosrock.domain.common.vo.DateTimePeriod; import java.util.ArrayList; import java.util.List; import javax.persistence.*; @@ -36,11 +36,10 @@ public class CouponCampaign extends BaseTimeEntity { // 사용기한(일자) ex.10 -> 발급 이후 10일 동안 쿠폰 사용 가능 private Long validTerm; - // 쿠폰 발행 시작 시각 - private LocalDateTime startAt; - // 쿠폰 발행 마감 시각 - private LocalDateTime endAt; + // 쿠폰 발행 가능 기간 + @Embedded private DateTimePeriod dateTimePeriod; + // 쿠폰 총 발행량 및 잔량 @Embedded private CouponStockInfo couponStockInfo; private Long discountAmount; @@ -56,8 +55,7 @@ public CouponCampaign( DiscountType discountType, ApplyTarget applyTarget, Long validTerm, - LocalDateTime startAt, - LocalDateTime endAt, + DateTimePeriod dateTimePeriod, CouponStockInfo couponStockInfo, Long discountAmount, String couponCode) { @@ -65,8 +63,7 @@ public CouponCampaign( this.discountType = discountType; this.applyTarget = applyTarget; this.validTerm = validTerm; - this.startAt = startAt; - this.endAt = endAt; + this.dateTimePeriod = dateTimePeriod; this.couponStockInfo = couponStockInfo; this.discountAmount = discountAmount; this.couponCode = couponCode; diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/service/CreateCouponCampaignDomainService.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/service/CreateCouponCampaignDomainService.java index 50c5161d..fa4f726c 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/service/CreateCouponCampaignDomainService.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/coupon/service/CreateCouponCampaignDomainService.java @@ -2,8 +2,8 @@ import band.gosrock.common.annotation.DomainService; -import band.gosrock.domain.domains.coupon.exception.AlreadyExistCouponCampaignException; -import band.gosrock.domain.domains.coupon.repository.CouponCampaignRepository; +import band.gosrock.domain.domains.coupon.adaptor.CouponCampaignAdaptor; +import band.gosrock.domain.domains.coupon.domain.CouponCampaign; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -11,12 +11,15 @@ @RequiredArgsConstructor public class CreateCouponCampaignDomainService { - private final CouponCampaignRepository couponCampaignRepository; + private final CouponCampaignAdaptor couponCampaignAdaptor; @Transactional(readOnly = true) public void checkCouponCodeExists(String couponCode) { - if (couponCampaignRepository.existsByCouponCode(couponCode)) { - throw AlreadyExistCouponCampaignException.EXCEPTION; - } + couponCampaignAdaptor.existsByCouponCode(couponCode); + } + + @Transactional + public CouponCampaign createCouponCampaign(CouponCampaign couponCampaign) { + return couponCampaignAdaptor.save(couponCampaign); } } From 58f9ce2251755a4bb3cf00771b054cd73288b922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EA=B2=BD=EB=AF=BC?= Date: Tue, 17 Jan 2023 19:41:02 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat=20:=20=ED=98=B8=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=83=9D=EC=84=B1/?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=B3=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 이벤트 조회 및 생성 초안 * feat: 호스트 생성 및 조회 기본 로직 * style : spotless apply * refactor : host 구조 변경 * refactor : 계층 별 분리 구조 적용 --- .../api/event/controller/EventController.java | 37 +++++++++++++++++ .../model/dto/request/CreateEventRequest.java | 17 ++++++++ .../model/dto/response/EventResponse.java | 18 ++++++++ .../api/event/service/CreateEventUseCase.java | 30 ++++++++++++++ .../api/event/service/ReadEventUseCase.java | 25 +++++++++++ .../example/controller/ExampleController.java | 14 +++++++ .../api/host/controller/HostController.java | 37 +++++++++++++++++ .../model/dto/request/CreateHostRequest.java | 20 +++++++++ .../dto/response/CreateHostResponse.java | 33 +++++++++++++++ .../host/model/dto/response/HostResponse.java | 33 +++++++++++++++ .../api/host/model/mapper/HostMapper.java | 3 ++ .../api/host/service/CreateHostUseCase.java | 41 +++++++++++++++++++ .../api/host/service/ReadHostUseCase.java | 29 +++++++++++++ .../domains/event/adaptor/EventAdaptor.java | 6 +++ .../event/repository/EventRepository.java | 5 ++- .../domains/event/service/EventService.java | 4 ++ .../domains/host/adaptor/HostAdaptor.java | 29 +++++++++++++ .../domain/domains/host/domain/Host.java | 20 ++++----- .../domain/domains/host/domain/HostUser.java | 8 ++++ .../domains/host/exception/HostErrorCode.java | 35 ++++++++++++++++ .../host/exception/HostNotFoundException.java | 13 ++++++ .../host/exception/NotSuperHostException.java | 12 ++++++ .../host/repository/HostRepository.java | 5 ++- .../domains/host/service/HostService.java | 37 ++++++++++++++++- 24 files changed, 497 insertions(+), 14 deletions(-) create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/event/model/dto/request/CreateEventRequest.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/event/model/dto/response/EventResponse.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/event/service/CreateEventUseCase.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/event/service/ReadEventUseCase.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/host/controller/HostController.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/request/CreateHostRequest.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/response/CreateHostResponse.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/response/HostResponse.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/host/model/mapper/HostMapper.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/host/service/CreateHostUseCase.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/host/service/ReadHostUseCase.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/adaptor/HostAdaptor.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/HostErrorCode.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/HostNotFoundException.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/NotSuperHostException.java diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java b/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java new file mode 100644 index 00000000..86d4fd00 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java @@ -0,0 +1,37 @@ +package band.gosrock.api.event.controller; + + +import band.gosrock.api.event.model.dto.request.CreateEventRequest; +import band.gosrock.api.event.model.dto.response.EventResponse; +import band.gosrock.api.event.service.CreateEventUseCase; +import band.gosrock.api.event.service.ReadEventUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@SecurityRequirement(name = "access-token") +@Tag(name = "이벤트(공연) 관련 컨트롤러") +@RestController +@RequestMapping("/v1/events") +@RequiredArgsConstructor +public class EventController { + + private final CreateEventUseCase createEventUseCase; + private final ReadEventUseCase readEventUseCase; + + @Operation(summary = "내가 관리하는 이벤트 리스트를 가져옵니다") + @GetMapping + public List getAllEvents() { + return readEventUseCase.execute(); + } + + @Operation(summary = "새로운 이벤트(공연)를 생성합니다") + @PostMapping + public EventResponse createEvent(@RequestBody @Valid CreateEventRequest createEventRequest) { + return createEventUseCase.execute(createEventRequest); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/event/model/dto/request/CreateEventRequest.java b/DuDoong-Api/src/main/java/band/gosrock/api/event/model/dto/request/CreateEventRequest.java new file mode 100644 index 00000000..acb7b56e --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/event/model/dto/request/CreateEventRequest.java @@ -0,0 +1,17 @@ +package band.gosrock.api.event.model.dto.request; + + +import band.gosrock.domain.domains.event.domain.Event; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class CreateEventRequest { + private String name; + + @Deprecated + public Event toEntity() { + return Event.builder().name(name).build(); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/event/model/dto/response/EventResponse.java b/DuDoong-Api/src/main/java/band/gosrock/api/event/model/dto/response/EventResponse.java new file mode 100644 index 00000000..5a878acd --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/event/model/dto/response/EventResponse.java @@ -0,0 +1,18 @@ +package band.gosrock.api.event.model.dto.response; + + +import band.gosrock.domain.domains.event.domain.Event; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class EventResponse { + @Schema(description = "공연 이름") + private final String name; + + public static EventResponse of(Event event) { + return EventResponse.builder().name(event.getName()).build(); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/event/service/CreateEventUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/event/service/CreateEventUseCase.java new file mode 100644 index 00000000..4ec3ac8d --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/event/service/CreateEventUseCase.java @@ -0,0 +1,30 @@ +package band.gosrock.api.event.service; + + +import band.gosrock.api.config.security.SecurityUtils; +import band.gosrock.api.event.model.dto.request.CreateEventRequest; +import band.gosrock.api.event.model.dto.response.EventResponse; +import band.gosrock.common.annotation.UseCase; +import band.gosrock.domain.domains.event.domain.Event; +import band.gosrock.domain.domains.event.service.EventService; +import band.gosrock.domain.domains.user.service.UserDomainService; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@UseCase +@RequiredArgsConstructor +public class CreateEventUseCase { + + private final EventService eventService; + private final UserDomainService userDomainService; + + @Transactional + public EventResponse execute(CreateEventRequest createEventRequest) { + // 존재하는 유저인지 검증 + userDomainService.retrieveUser(SecurityUtils.getCurrentUserId()); + // Todo:: 호스트 검증 + // 이벤트 생성 + Event event = eventService.createEvent(createEventRequest.toEntity()); + return EventResponse.of(event); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/event/service/ReadEventUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/event/service/ReadEventUseCase.java new file mode 100644 index 00000000..424c5a4d --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/event/service/ReadEventUseCase.java @@ -0,0 +1,25 @@ +package band.gosrock.api.event.service; + + +import band.gosrock.api.event.model.dto.response.EventResponse; +import band.gosrock.common.annotation.UseCase; +import band.gosrock.domain.domains.event.adaptor.EventAdaptor; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@UseCase +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ReadEventUseCase { + + private final EventAdaptor eventAdaptor; + + public List execute() { + // Todo:: hostId로 변경필요 + return eventAdaptor.findAllByHostId(0L).stream() + .map(EventResponse::of) + .collect(Collectors.toList()); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java b/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java index bf03744b..80ca6264 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java @@ -11,6 +11,8 @@ import band.gosrock.common.exception.GlobalErrorCode; import band.gosrock.domain.domains.cart.exception.CartErrorCode; import band.gosrock.domain.domains.coupon.exception.CouponErrorCode; +import band.gosrock.domain.domains.event.exception.EventErrorCode; +import band.gosrock.domain.domains.host.exception.HostErrorCode; import band.gosrock.domain.domains.issuedTicket.exception.IssuedTicketErrorCode; import band.gosrock.domain.domains.order.exception.OrderErrorCode; import band.gosrock.domain.domains.user.exception.UserErrorCode; @@ -115,4 +117,16 @@ public void getTransactionGetErrorCode() {} @Operation(summary = "쿠폰 도메인 관련 에러 코드 나열") @ApiErrorCodeExample(CouponErrorCode.class) public void getCouponErrorCode() {} + + @GetMapping("/host") + @DevelopOnlyApi + @Operation(summary = "호스트 도메인 관련 에러 코드 나열") + @ApiErrorCodeExample(HostErrorCode.class) + public void getHostErrorCode() {} + + @GetMapping("/event") + @DevelopOnlyApi + @Operation(summary = "이벤트 도메인 관련 에러 코드 나열") + @ApiErrorCodeExample(EventErrorCode.class) + public void getEventErrorCode() {} } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/host/controller/HostController.java b/DuDoong-Api/src/main/java/band/gosrock/api/host/controller/HostController.java new file mode 100644 index 00000000..b0477517 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/host/controller/HostController.java @@ -0,0 +1,37 @@ +package band.gosrock.api.host.controller; + + +import band.gosrock.api.host.model.dto.request.CreateHostRequest; +import band.gosrock.api.host.model.dto.response.HostResponse; +import band.gosrock.api.host.service.CreateHostUseCase; +import band.gosrock.api.host.service.ReadHostUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@SecurityRequirement(name = "access-token") +@Tag(name = "호스트 관련 컨트롤러") +@RestController +@RequestMapping("/v1/hosts") +@RequiredArgsConstructor +public class HostController { + + private final CreateHostUseCase createHostUseCase; + private final ReadHostUseCase readHostUseCase; + + @Operation(summary = "내가 속한 호스트 리스트를 가져옵니다") + @GetMapping + public List getAllHosts() { + return readHostUseCase.execute(); + } + + @Operation(summary = "새로운 이벤트(공연)를 생성합니다") + @PostMapping + public HostResponse createEvent(@RequestBody @Valid CreateHostRequest createEventRequest) { + return createHostUseCase.execute(createEventRequest); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/request/CreateHostRequest.java b/DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/request/CreateHostRequest.java new file mode 100644 index 00000000..8d5174ea --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/request/CreateHostRequest.java @@ -0,0 +1,20 @@ +package band.gosrock.api.host.model.dto.request; + + +import io.swagger.v3.oas.annotations.media.Schema; +import javax.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class CreateHostRequest { + @Schema(defaultValue = "gosrock@gsrk.com", description = "마스터 이메일") + @NotNull(message = "담당자 이메일을 입력하세요") + private final String contactEmail; + + @Schema(defaultValue = "010-1111-3333", description = "마스터 전화번호") + @NotNull(message = "담당자 번호를 입력하세요") + // todo:: 정규식 적용 + private final String contactNumber; +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/response/CreateHostResponse.java b/DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/response/CreateHostResponse.java new file mode 100644 index 00000000..addf4650 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/response/CreateHostResponse.java @@ -0,0 +1,33 @@ +package band.gosrock.api.host.model.dto.response; + + +import band.gosrock.domain.domains.host.domain.Host; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CreateHostResponse { + @Schema(description = "담당자 이메일") + private final String contactEmail; + + @Schema(description = "담당자 전화번호") + private final String contactNumber; + + @Schema(description = "마스터 유저의 고유 아이디") + private final Long masterUserId; + // private final HostUser masterUser; + + @Schema(description = "파트너쉽 여부") + private final boolean partner; + + public static CreateHostResponse of(Host host) { + return CreateHostResponse.builder() + .contactEmail(host.getContactEmail()) + .contactNumber(host.getContactNumber()) + .masterUserId(host.getMasterUserId()) // todo:: 마스터 정보 나오도록 + .partner(host.getPartner()) + .build(); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/response/HostResponse.java b/DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/response/HostResponse.java new file mode 100644 index 00000000..788efc2b --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/host/model/dto/response/HostResponse.java @@ -0,0 +1,33 @@ +package band.gosrock.api.host.model.dto.response; + + +import band.gosrock.domain.domains.host.domain.Host; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class HostResponse { + @Schema(description = "담당자 이메일") + private final String contactEmail; + + @Schema(description = "담당자 전화번호") + private final String contactNumber; + + @Schema(description = "마스터 유저의 고유 아이디") + private final Long masterUserId; + // private final HostUser masterUser; + + @Schema(description = "파트너쉽 여부") + private final boolean partner; + + public static HostResponse of(Host host) { + return HostResponse.builder() + .contactEmail(host.getContactEmail()) + .contactNumber(host.getContactNumber()) + .masterUserId(host.getMasterUserId()) // todo:: 마스터 정보 나오도록 + .partner(host.getPartner()) + .build(); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/host/model/mapper/HostMapper.java b/DuDoong-Api/src/main/java/band/gosrock/api/host/model/mapper/HostMapper.java new file mode 100644 index 00000000..47ec8414 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/host/model/mapper/HostMapper.java @@ -0,0 +1,3 @@ +package band.gosrock.api.host.model.mapper; + +public class HostMapper {} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/host/service/CreateHostUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/host/service/CreateHostUseCase.java new file mode 100644 index 00000000..0b44042c --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/host/service/CreateHostUseCase.java @@ -0,0 +1,41 @@ +package band.gosrock.api.host.service; + + +import band.gosrock.api.config.security.SecurityUtils; +import band.gosrock.api.host.model.dto.request.CreateHostRequest; +import band.gosrock.api.host.model.dto.response.HostResponse; +import band.gosrock.common.annotation.UseCase; +import band.gosrock.domain.domains.host.adaptor.HostAdaptor; +import band.gosrock.domain.domains.host.domain.Host; +import band.gosrock.domain.domains.host.service.HostService; +import band.gosrock.domain.domains.user.domain.User; +import band.gosrock.domain.domains.user.service.UserDomainService; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@UseCase +@RequiredArgsConstructor +public class CreateHostUseCase { + + private final UserDomainService userDomainService; + private final HostService hostService; + private final HostAdaptor hostAdaptor; + + @Transactional + public HostResponse execute(CreateHostRequest createHostRequest) { + final Long securityUserId = SecurityUtils.getCurrentUserId(); + // 존재하는 유저인지 검증 + final User user = userDomainService.retrieveUser(securityUserId); + + // 호스트 생성 + final Host host = + hostService.createHost( + Host.builder() + .contactEmail(createHostRequest.getContactEmail()) + .contactNumber(createHostRequest.getContactNumber()) + .masterUserId(securityUserId) + .build()); + // todo :: host 생성 레이어 찾기 + return HostResponse.of(hostService.addHostUser(host, securityUserId)); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/host/service/ReadHostUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/host/service/ReadHostUseCase.java new file mode 100644 index 00000000..6c881738 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/host/service/ReadHostUseCase.java @@ -0,0 +1,29 @@ +package band.gosrock.api.host.service; + + +import band.gosrock.api.config.security.SecurityUtils; +import band.gosrock.api.host.model.dto.response.HostResponse; +import band.gosrock.common.annotation.UseCase; +import band.gosrock.domain.domains.host.adaptor.HostAdaptor; +import band.gosrock.domain.domains.user.domain.User; +import band.gosrock.domain.domains.user.service.UserDomainService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@UseCase +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ReadHostUseCase { + private final UserDomainService userDomainService; + private final HostAdaptor hostAdaptor; + + public List execute() { + Long securityUserId = SecurityUtils.getCurrentUserId(); + User user = userDomainService.retrieveUser(securityUserId); + // Todo:: hostId로 변경필요 + return hostAdaptor.findAllByMasterUserId(securityUserId).stream() + .map(HostResponse::of) + .toList(); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/adaptor/EventAdaptor.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/adaptor/EventAdaptor.java index 6a0d2963..b3a8d249 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/adaptor/EventAdaptor.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/adaptor/EventAdaptor.java @@ -5,6 +5,7 @@ import band.gosrock.domain.domains.event.domain.Event; import band.gosrock.domain.domains.event.exception.EventNotFoundException; import band.gosrock.domain.domains.event.repository.EventRepository; +import java.util.List; import lombok.RequiredArgsConstructor; @Adaptor @@ -18,4 +19,9 @@ public Event findById(Long eventId) { .findById(eventId) .orElseThrow(() -> EventNotFoundException.EXCEPTION); } + + // Todo:: hostId 지정해서 뽑아오도록 변경하기 (임시) + public List findAllByHostId(Long hostId) { + return eventRepository.findAll(); + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/repository/EventRepository.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/repository/EventRepository.java index 3034994c..70780b06 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/repository/EventRepository.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/repository/EventRepository.java @@ -2,6 +2,9 @@ import band.gosrock.domain.domains.event.domain.Event; +import java.util.List; import org.springframework.data.repository.CrudRepository; -public interface EventRepository extends CrudRepository {} +public interface EventRepository extends CrudRepository { + List findAll(); +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/service/EventService.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/service/EventService.java index 0bae7cf6..2e21edbe 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/service/EventService.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/event/service/EventService.java @@ -16,6 +16,10 @@ public class EventService { private final EventRepository eventRepository; private final EventAdaptor eventAdaptor; + public Event createEvent(Event event) { + return eventRepository.save(event); + } + public void checkEventHost(Long hostId, Long eventId) { Event event = eventAdaptor.findById(eventId); if (!event.getHostId().equals(hostId)) { diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/adaptor/HostAdaptor.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/adaptor/HostAdaptor.java new file mode 100644 index 00000000..6dde3b1e --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/adaptor/HostAdaptor.java @@ -0,0 +1,29 @@ +package band.gosrock.domain.domains.host.adaptor; + + +import band.gosrock.common.annotation.Adaptor; +import band.gosrock.domain.domains.host.domain.Host; +import band.gosrock.domain.domains.host.exception.HostNotFoundException; +import band.gosrock.domain.domains.host.repository.HostRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; + +@Adaptor +@RequiredArgsConstructor +public class HostAdaptor { + private final HostRepository hostRepository; + + public Host findById(Long hostId) { + return hostRepository.findById(hostId).orElseThrow(() -> HostNotFoundException.EXCEPTION); + } + + public List findAllByMasterUserId(Long userId) { + return hostRepository.findAllByMasterUserId(userId); + } + + // public List findAllByUserId(Long userId) { + // return hostRepository.findAllByMasterUserId(userId); + //// .orElseThrow(() -> HostNotFoundException.EXCEPTION); + // } + +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/domain/Host.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/domain/Host.java index 0ec6ddf6..7c99bd22 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/domain/Host.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/domain/Host.java @@ -2,8 +2,8 @@ import band.gosrock.domain.common.model.BaseTimeEntity; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import javax.persistence.*; import lombok.AccessLevel; import lombok.Builder; @@ -35,19 +35,17 @@ public class Host extends BaseTimeEntity { // 단방향 oneToMany 매핑 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "host_user_id") - private List hostUsers = new ArrayList<>(); + private Set hostUsers = new HashSet<>(); + + public void addHostUsers(Set hostUserList) { + hostUsers.addAll(hostUserList); + } @Builder - public Host( - String contactEmail, - String contactNumber, - Long masterUserId, - Boolean partner, - List hostUsers) { + public Host(String contactEmail, String contactNumber, Long masterUserId) { this.contactEmail = contactEmail; this.contactNumber = contactNumber; this.masterUserId = masterUserId; - this.partner = partner; - // this.hostUsers = hostUsers; + this.partner = false; // 정책상 초기값 false 로 고정입니다 } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/domain/HostUser.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/domain/HostUser.java index 492eff50..ddc9fda8 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/domain/HostUser.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/domain/HostUser.java @@ -11,6 +11,12 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity(name = "tbl_host_user") +@Table( + uniqueConstraints = { + @UniqueConstraint( + name = "constraintName", + columnNames = {"host_id", "user_id"}) + }) public class HostUser extends BaseTimeEntity { @Id @@ -19,9 +25,11 @@ public class HostUser extends BaseTimeEntity { private Long id; // 소속 호스트 아이디 + @Column(name = "host_id") private Long hostId; // 소속 호스트를 관리중인 유저 아이디 + @Column(name = "user_id") private Long userId; // 유저의 권한 diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/HostErrorCode.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/HostErrorCode.java new file mode 100644 index 00000000..3fd56d42 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/HostErrorCode.java @@ -0,0 +1,35 @@ +package band.gosrock.domain.domains.host.exception; + +import static band.gosrock.common.consts.DuDoongStatic.BAD_REQUEST; +import static band.gosrock.common.consts.DuDoongStatic.NOT_FOUND; + +import band.gosrock.common.annotation.ExplainError; +import band.gosrock.common.dto.ErrorReason; +import band.gosrock.common.exception.BaseErrorCode; +import java.lang.reflect.Field; +import java.util.Objects; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum HostErrorCode implements BaseErrorCode { + HOST_NOT_FOUND(NOT_FOUND, "Host_404_1", "해당 호스트를 찾을 수 없습니다."), + NOT_SUPER_HOST(BAD_REQUEST, "HOST_400_1", "슈퍼 호스트 권한이 없는 유저입니다"); + + private Integer status; + private String code; + private String reason; + + @Override + public ErrorReason getErrorReason() { + return ErrorReason.builder().reason(reason).code(code).status(status).build(); + } + + @Override + public String getExplainError() throws NoSuchFieldException { + Field field = this.getClass().getField(this.name()); + ExplainError annotation = field.getAnnotation(ExplainError.class); + return Objects.nonNull(annotation) ? annotation.value() : this.getReason(); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/HostNotFoundException.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/HostNotFoundException.java new file mode 100644 index 00000000..5100291f --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/HostNotFoundException.java @@ -0,0 +1,13 @@ +package band.gosrock.domain.domains.host.exception; + + +import band.gosrock.common.exception.DuDoongCodeException; + +public class HostNotFoundException extends DuDoongCodeException { + + public static final DuDoongCodeException EXCEPTION = new HostNotFoundException(); + + private HostNotFoundException() { + super(HostErrorCode.HOST_NOT_FOUND); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/NotSuperHostException.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/NotSuperHostException.java new file mode 100644 index 00000000..e653887f --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/exception/NotSuperHostException.java @@ -0,0 +1,12 @@ +package band.gosrock.domain.domains.host.exception; + + +import band.gosrock.common.exception.DuDoongCodeException; + +public class NotSuperHostException extends DuDoongCodeException { + public static final DuDoongCodeException EXCEPTION = new NotSuperHostException(); + + private NotSuperHostException() { + super(HostErrorCode.NOT_SUPER_HOST); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/repository/HostRepository.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/repository/HostRepository.java index 2d89a869..dcd87ff2 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/repository/HostRepository.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/repository/HostRepository.java @@ -2,6 +2,9 @@ import band.gosrock.domain.domains.host.domain.Host; +import java.util.List; import org.springframework.data.repository.CrudRepository; -public interface HostRepository extends CrudRepository {} +public interface HostRepository extends CrudRepository { + List findAllByMasterUserId(Long userId); +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/service/HostService.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/service/HostService.java index d5ab1705..584314c9 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/service/HostService.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/host/service/HostService.java @@ -2,7 +2,13 @@ import band.gosrock.common.annotation.DomainService; +import band.gosrock.domain.domains.host.domain.Host; +import band.gosrock.domain.domains.host.domain.HostRole; +import band.gosrock.domain.domains.host.domain.HostUser; +import band.gosrock.domain.domains.host.exception.HostNotFoundException; +import band.gosrock.domain.domains.host.exception.NotSuperHostException; import band.gosrock.domain.domains.host.repository.HostRepository; +import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -10,5 +16,34 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class HostService { - private final HostRepository orderRepository; + private final HostRepository hostRepository; + + public Host createHost(Host host) { + return hostRepository.save(host); + } + + public Host addHostUser(Host host, HostUser hostUser) { + host.addHostUsers(Set.of(hostUser)); + return hostRepository.save(host); + } + + public Host addHostUser(Host host, Long userId) { + HostUser hostUser = + HostUser.builder() + .userId(userId) + .hostId(host.getId()) + .role(HostRole.SUPER_HOST) + .build(); + host.addHostUsers(Set.of(hostUser)); + return hostRepository.save(host); + } + + /** 해당 유저가 호스트의 마스터(담당자, 방장)인지 확인하는 검증 로직입니다 */ + public void checkSuperHost(Long hostId, Long userId) { + Host host = + hostRepository.findById(hostId).orElseThrow(() -> HostNotFoundException.EXCEPTION); + if (!host.getMasterUserId().equals(userId)) { + throw NotSuperHostException.EXCEPTION; + } + } } From cdecae38b7e82a2d8153a435425025cf7ba1c719 Mon Sep 17 00:00:00 2001 From: Minjoon Kim <59060780+sanbonai06@users.noreply.github.com> Date: Wed, 18 Jan 2023 15:14:30 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=EA=B0=9C=EB=B0=9C=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9A=A9=20=ED=8B=B0=EC=BC=93=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20API=20=EC=B6=94=EA=B0=80=20(#146)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(api) : 개발 및 테스트 용 티켓 발급 API 작성 * refactor(domain) : 발급 티켓 no 수정 * refactor(api) : 발급 티켓 리스트 가져오기 API 응답값에 옵션 응답 리스트 추가 * refactor(api) : 개발용 티켓 발급 API 구조 수정 * refactor(api) : 리뷰 수정사항 반영 * refactor(api) : ErrorReason import 수정 * chore: spotless 적용 --- .../example/controller/ExampleController.java | 9 ++- .../controller/IssuedTicketController.java | 12 ++++ .../dto/response/RetrieveIssuedTicketDTO.java | 9 +++ .../service/CreateIssuedTicketUseCase.java | 68 ++++++++++++++++++- .../common/vo/IssuedTicketOptionAnswerVo.java | 29 ++++++++ .../issuedTicket/domain/IssuedTicket.java | 28 +++++++- .../domain/IssuedTicketOptionAnswer.java | 9 ++- .../domain/IssuedTicketStatus.java | 7 +- .../request/CreateIssuedTicketForDevDTO.java | 20 ++++++ .../CreateIssuedTicketRequestDTOs.java | 2 +- ...a => CreateIssuedTicketRequestForDev.java} | 6 +- .../service/IssuedTicketDomainService.java | 2 + .../order/exception/OrderErrorCode.java | 6 +- .../ticket_item/adaptor/OptionAdaptor.java | 21 ++++++ .../adaptor/TicketItemAdaptor.java | 21 ++++++ .../exception/OptionNotFoundException.java | 13 ++++ .../exception/TicketItemErrorCode.java | 36 ++++++++++ .../TicketItemNotFoundException.java | 13 ++++ 18 files changed, 292 insertions(+), 19 deletions(-) create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/common/vo/IssuedTicketOptionAnswerVo.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketForDevDTO.java rename DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/{CreateIssuedTicketRequest.java => CreateIssuedTicketRequestForDev.java} (82%) create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/adaptor/OptionAdaptor.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/adaptor/TicketItemAdaptor.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/OptionNotFoundException.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/TicketItemErrorCode.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/TicketItemNotFoundException.java diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java b/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java index 80ca6264..785e158f 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/example/controller/ExampleController.java @@ -15,6 +15,7 @@ import band.gosrock.domain.domains.host.exception.HostErrorCode; import band.gosrock.domain.domains.issuedTicket.exception.IssuedTicketErrorCode; import band.gosrock.domain.domains.order.exception.OrderErrorCode; +import band.gosrock.domain.domains.ticket_item.exception.TicketItemErrorCode; import band.gosrock.domain.domains.user.exception.UserErrorCode; import band.gosrock.infrastructure.outer.api.oauth.exception.KakaoKauthErrorCode; import band.gosrock.infrastructure.outer.api.tossPayments.exception.PaymentsCancelErrorCode; @@ -78,7 +79,7 @@ public void getCartErrorCode() {} @GetMapping("/issuedTicket") @DevelopOnlyApi - @Operation(summary = "주문 도메인 관련 에러 코드 나열") + @Operation(summary = "티켓 발급 관련 에러 코드 나열") @ApiErrorCodeExample(IssuedTicketErrorCode.class) public void getIssuedTicketErrorCode() {} @@ -118,6 +119,12 @@ public void getTransactionGetErrorCode() {} @ApiErrorCodeExample(CouponErrorCode.class) public void getCouponErrorCode() {} + @GetMapping("/ticketItem") + @DevelopOnlyApi + @Operation(summary = "티켓 상품 관련 에러 코드 나열") + @ApiErrorCodeExample(TicketItemErrorCode.class) + public void getTicketItemErrorCode() {} + @GetMapping("/host") @DevelopOnlyApi @Operation(summary = "호스트 도메인 관련 에러 코드 나열") diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/controller/IssuedTicketController.java b/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/controller/IssuedTicketController.java index 32fb2bcb..14aacdb8 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/controller/IssuedTicketController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/controller/IssuedTicketController.java @@ -4,12 +4,16 @@ import band.gosrock.api.issuedTicket.dto.response.RetrieveIssuedTicketDetailResponse; import band.gosrock.api.issuedTicket.service.CreateIssuedTicketUseCase; import band.gosrock.api.issuedTicket.service.ReadIssuedTicketUseCase; +import band.gosrock.common.annotation.DevelopOnlyApi; +import band.gosrock.domain.domains.issuedTicket.dto.request.CreateIssuedTicketForDevDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; 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.RestController; @@ -28,4 +32,12 @@ public class IssuedTicketController { public RetrieveIssuedTicketDetailResponse getIssuedTicket(@PathVariable Long issuedTicketId) { return readIssuedTicketUseCase.execute(issuedTicketId); } + + @Operation(summary = "개발용 발급 티켓 생성 API 입니다.") + @DevelopOnlyApi + @PostMapping(value = "/develop") + public RetrieveIssuedTicketDetailResponse postIssuedTicket( + @RequestBody CreateIssuedTicketForDevDTO body) { + return createIssuedTicketUseCase.executeForDev(body); + } } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/dto/response/RetrieveIssuedTicketDTO.java b/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/dto/response/RetrieveIssuedTicketDTO.java index f6edff3f..6d026ad6 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/dto/response/RetrieveIssuedTicketDTO.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/dto/response/RetrieveIssuedTicketDTO.java @@ -2,9 +2,12 @@ import band.gosrock.domain.common.vo.IssuedTicketInfoVo; +import band.gosrock.domain.common.vo.IssuedTicketOptionAnswerVo; import band.gosrock.domain.common.vo.UserInfoVo; import band.gosrock.domain.domains.issuedTicket.domain.IssuedTicket; +import band.gosrock.domain.domains.issuedTicket.domain.IssuedTicketOptionAnswer; import band.gosrock.domain.domains.user.domain.User; +import java.util.List; import lombok.Builder; import lombok.Getter; @@ -16,10 +19,16 @@ public class RetrieveIssuedTicketDTO { private final UserInfoVo userInfo; + private final List issuedTicketOptionAnswers; + public static RetrieveIssuedTicketDTO of(IssuedTicket issuedTicket, User user) { return RetrieveIssuedTicketDTO.builder() .issuedTicketInfo(issuedTicket.toIssuedTicketInfoVo()) .userInfo(user.toUserInfoVo()) + .issuedTicketOptionAnswers( + issuedTicket.getIssuedTicketOptionAnswers().stream() + .map(IssuedTicketOptionAnswer::toIssuedTicketOptionAnswerVo) + .toList()) .build(); } } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/service/CreateIssuedTicketUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/service/CreateIssuedTicketUseCase.java index 3a27c9cb..ebfa8357 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/service/CreateIssuedTicketUseCase.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/issuedTicket/service/CreateIssuedTicketUseCase.java @@ -1,15 +1,79 @@ package band.gosrock.api.issuedTicket.service; +import band.gosrock.api.config.security.SecurityUtils; +import band.gosrock.api.issuedTicket.dto.response.RetrieveIssuedTicketDetailResponse; import band.gosrock.common.annotation.UseCase; -import band.gosrock.domain.domains.issuedTicket.repository.IssuedTicketRepository; +import band.gosrock.domain.common.vo.Money; +import band.gosrock.domain.domains.event.adaptor.EventAdaptor; +import band.gosrock.domain.domains.event.domain.Event; +import band.gosrock.domain.domains.issuedTicket.adaptor.IssuedTicketAdaptor; +import band.gosrock.domain.domains.issuedTicket.domain.IssuedTicket; +import band.gosrock.domain.domains.issuedTicket.domain.IssuedTicketOptionAnswer; +import band.gosrock.domain.domains.issuedTicket.dto.request.CreateIssuedTicketForDevDTO; import band.gosrock.domain.domains.issuedTicket.service.IssuedTicketDomainService; +import band.gosrock.domain.domains.ticket_item.adaptor.OptionAdaptor; +import band.gosrock.domain.domains.ticket_item.adaptor.TicketItemAdaptor; +import band.gosrock.domain.domains.ticket_item.domain.TicketItem; +import band.gosrock.domain.domains.user.adaptor.UserAdaptor; +import band.gosrock.domain.domains.user.domain.User; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; @UseCase @RequiredArgsConstructor public class CreateIssuedTicketUseCase { private final IssuedTicketDomainService issuedTicketDomainService; - private final IssuedTicketRepository issuedTicketRepository; + private final IssuedTicketAdaptor issuedTicketAdaptor; + private final UserAdaptor userAdaptor; + private final EventAdaptor eventAdaptor; + private final TicketItemAdaptor ticketItemAdaptor; + private final OptionAdaptor optionAdaptor; + + @Transactional + public RetrieveIssuedTicketDetailResponse executeForDev(CreateIssuedTicketForDevDTO body) { + Long currentUserId = SecurityUtils.getCurrentUserId(); + User user = getUser(currentUserId); + Event event = getEvent(body.getEventId()); + TicketItem ticketItem = getTicketItem(body.getTicketItemId()); + + List issuedTicketOptionAnswers = + body.getOptionAnswers().stream() + .map( + option -> + IssuedTicketOptionAnswer.builder() + .option(optionAdaptor.queryOption(option)) + .answer("test") + .build()) + .toList(); + + IssuedTicket issuedTicket = + issuedTicketAdaptor.save( + IssuedTicket.createForDev( + event, + user, + body.getOrderLineId(), + ticketItem, + Money.wons(body.getAmount()), + issuedTicketOptionAnswers)); + + return new RetrieveIssuedTicketDetailResponse( + issuedTicket.toIssuedTicketInfoVo(), + issuedTicket.getEvent().toEventInfoVo(), + issuedTicket.getUser().getProfile().getName()); + } + + private Event getEvent(Long eventId) { + return eventAdaptor.findById(eventId); + } + + private User getUser(Long userId) { + return userAdaptor.queryUser(userId); + } + + private TicketItem getTicketItem(Long ticketItemId) { + return ticketItemAdaptor.queryTicketItem(ticketItemId); + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/vo/IssuedTicketOptionAnswerVo.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/vo/IssuedTicketOptionAnswerVo.java new file mode 100644 index 00000000..32056b5c --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/vo/IssuedTicketOptionAnswerVo.java @@ -0,0 +1,29 @@ +package band.gosrock.domain.common.vo; + + +import band.gosrock.domain.domains.issuedTicket.domain.IssuedTicketOptionAnswer; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class IssuedTicketOptionAnswerVo { + + private final Long issuedTicketOptionAnswerId; + + private final String optionQuestion; + + private final String answer; + + private final Money additionalPrice; + + public static IssuedTicketOptionAnswerVo from( + IssuedTicketOptionAnswer issuedTicketOptionAnswer) { + return IssuedTicketOptionAnswerVo.builder() + .issuedTicketOptionAnswerId(issuedTicketOptionAnswer.getId()) + .optionQuestion(issuedTicketOptionAnswer.getOption().getOptionGroup().getName()) + .answer(issuedTicketOptionAnswer.getOption().getAnswer()) + .additionalPrice(issuedTicketOptionAnswer.getOption().getAdditionalPrice()) + .build(); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicket.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicket.java index 62447751..c37dbe31 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicket.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicket.java @@ -1,12 +1,13 @@ package band.gosrock.domain.domains.issuedTicket.domain; +import static band.gosrock.common.consts.DuDoongStatic.NO_START_NUMBER; import band.gosrock.domain.common.model.BaseTimeEntity; import band.gosrock.domain.common.vo.IssuedTicketInfoVo; import band.gosrock.domain.common.vo.Money; import band.gosrock.domain.domains.event.domain.Event; import band.gosrock.domain.domains.issuedTicket.dto.request.CreateIssuedTicketDTO; -import band.gosrock.domain.domains.issuedTicket.dto.request.CreateIssuedTicketRequest; +import band.gosrock.domain.domains.issuedTicket.dto.request.CreateIssuedTicketRequestForDev; import band.gosrock.domain.domains.issuedTicket.dto.response.CreateIssuedTicketResponse; import band.gosrock.domain.domains.ticket_item.domain.TicketItem; import band.gosrock.domain.domains.user.domain.User; @@ -120,7 +121,7 @@ public IssuedTicket( this.issuedTicketOptionAnswers.addAll(issuedTicketOptionAnswers); } - public static IssuedTicket create(CreateIssuedTicketRequest dto) { + public static IssuedTicket create(CreateIssuedTicketRequestForDev dto) { return IssuedTicket.builder() .event(dto.getEvent()) .user(dto.getUser()) @@ -132,6 +133,27 @@ public static IssuedTicket create(CreateIssuedTicketRequest dto) { .build(); } + public static IssuedTicket createForDev( + Event event, + User user, + Long orderLineId, + TicketItem ticketItem, + Money price, + List issuedTicketOptionAnswers) { + IssuedTicket createIssuedTicket = + IssuedTicket.builder() + .event(event) + .user(user) + .orderLineId(orderLineId) + .ticketItem(ticketItem) + .price(price) + .issuedTicketStatus(IssuedTicketStatus.ENTRANCE_INCOMPLETE) + .issuedTicketOptionAnswers(new ArrayList<>()) + .build(); + createIssuedTicket.getIssuedTicketOptionAnswers().addAll(issuedTicketOptionAnswers); + return createIssuedTicket; + } + @PrePersist public void createUUID() { this.uuid = UUID.randomUUID().toString(); @@ -139,7 +161,7 @@ public void createUUID() { @PostPersist public void createIssuedTicketNo() { - this.issuedTicketNo = "T" + this.id; + this.issuedTicketNo = "T" + Long.sum(NO_START_NUMBER, this.id); } public Money sumOptionPrice() { diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicketOptionAnswer.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicketOptionAnswer.java index 64a73341..b905a127 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicketOptionAnswer.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicketOptionAnswer.java @@ -2,6 +2,7 @@ import band.gosrock.domain.common.model.BaseTimeEntity; +import band.gosrock.domain.common.vo.IssuedTicketOptionAnswerVo; import band.gosrock.domain.domains.order.domain.OrderOptionAnswer; import band.gosrock.domain.domains.ticket_item.domain.Option; import javax.persistence.Column; @@ -13,12 +14,12 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter -@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity(name = "tbl_issued_ticket_option_answer") public class IssuedTicketOptionAnswer extends BaseTimeEntity { @@ -52,4 +53,8 @@ public static IssuedTicketOptionAnswer orderOptionAnswerToIssuedTicketOptionAnsw .answer(orderOptionAnswer.getAnswer()) .build(); } + + public IssuedTicketOptionAnswerVo toIssuedTicketOptionAnswerVo() { + return IssuedTicketOptionAnswerVo.from(this); + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicketStatus.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicketStatus.java index 3490ca44..83127f2c 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicketStatus.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/domain/IssuedTicketStatus.java @@ -1,6 +1,7 @@ package band.gosrock.domain.domains.issuedTicket.domain; +import com.fasterxml.jackson.annotation.JsonValue; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,9 +9,11 @@ @AllArgsConstructor public enum IssuedTicketStatus { // 입장 완료 - ENTRANCE_COMPLETED("ENTRANCE_COMPLETED"), + ENTRANCE_COMPLETED("ENTRANCE_COMPLETED", "입장 완료"), // 입장 미완료 - ENTRANCE_INCOMPLETE("ENTRANCE_INCOMPLETE"); + ENTRANCE_INCOMPLETE("ENTRANCE_INCOMPLETE", "입장 전"); private final String value; + + @JsonValue private final String kr; } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketForDevDTO.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketForDevDTO.java new file mode 100644 index 00000000..9a9a3a35 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketForDevDTO.java @@ -0,0 +1,20 @@ +package band.gosrock.domain.domains.issuedTicket.dto.request; + + +import java.util.List; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +@Getter +public class CreateIssuedTicketForDevDTO { + + @NotNull private Long eventId; + + @NotNull private Long orderLineId; + + @NotNull private Long ticketItemId; + + @NotNull private Long amount; + + @NotNull private List optionAnswers; +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequestDTOs.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequestDTOs.java index f16cc955..615d9c21 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequestDTOs.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequestDTOs.java @@ -9,5 +9,5 @@ @RequiredArgsConstructor public class CreateIssuedTicketRequestDTOs { - private List createIssuedTicketRequests; + private List createIssuedTicketRequestForDevs; } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequest.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequestForDev.java similarity index 82% rename from DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequest.java rename to DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequestForDev.java index 622a0655..f03b06da 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequest.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/dto/request/CreateIssuedTicketRequestForDev.java @@ -3,7 +3,7 @@ import band.gosrock.domain.common.vo.Money; import band.gosrock.domain.domains.event.domain.Event; -import band.gosrock.domain.domains.order.domain.OrderOptionAnswer; +import band.gosrock.domain.domains.issuedTicket.domain.IssuedTicketOptionAnswer; import band.gosrock.domain.domains.ticket_item.domain.TicketItem; import band.gosrock.domain.domains.user.domain.User; import java.util.List; @@ -12,7 +12,7 @@ @Getter @RequiredArgsConstructor -public class CreateIssuedTicketRequest { +public class CreateIssuedTicketRequestForDev { /* 발급 티켓의 이벤트 id @@ -42,5 +42,5 @@ public class CreateIssuedTicketRequest { /* 발급 티켓에 걸려오는 옵션들 CartOptionAnswer를 List로 받습니다. */ - private final List optionAnswers; + private final List issuedTicketOptionAnswers; } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/service/IssuedTicketDomainService.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/service/IssuedTicketDomainService.java index 556507cf..861b1e69 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/service/IssuedTicketDomainService.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/issuedTicket/service/IssuedTicketDomainService.java @@ -2,6 +2,7 @@ import band.gosrock.common.annotation.DomainService; +import band.gosrock.domain.domains.event.adaptor.EventAdaptor; import band.gosrock.domain.domains.issuedTicket.adaptor.IssuedTicketAdaptor; import band.gosrock.domain.domains.issuedTicket.adaptor.IssuedTicketOptionAnswerAdaptor; import band.gosrock.domain.domains.issuedTicket.domain.IssuedTicket; @@ -20,6 +21,7 @@ public class IssuedTicketDomainService { private final IssuedTicketRepository issuedTicketRepository; private final IssuedTicketAdaptor issuedTicketAdaptor; private final IssuedTicketOptionAnswerAdaptor issuedTicketOptionAnswerAdaptor; + private final EventAdaptor eventAdaptor; // Todo 둘 중 어떤 로직이 더 나은지 비교해주세요 // @Transactional diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/OrderErrorCode.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/OrderErrorCode.java index 38352e91..d31dd1c3 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/OrderErrorCode.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/exception/OrderErrorCode.java @@ -36,11 +36,7 @@ public enum OrderErrorCode implements BaseErrorCode { @Override public ErrorReason getErrorReason() { - return band.gosrock.common.dto.ErrorReason.builder() - .reason(reason) - .code(code) - .status(status) - .build(); + return ErrorReason.builder().reason(reason).code(code).status(status).build(); } @Override diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/adaptor/OptionAdaptor.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/adaptor/OptionAdaptor.java new file mode 100644 index 00000000..9f0a94b7 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/adaptor/OptionAdaptor.java @@ -0,0 +1,21 @@ +package band.gosrock.domain.domains.ticket_item.adaptor; + + +import band.gosrock.common.annotation.Adaptor; +import band.gosrock.domain.domains.ticket_item.domain.Option; +import band.gosrock.domain.domains.ticket_item.exception.OptionNotFoundException; +import band.gosrock.domain.domains.ticket_item.repository.OptionRepository; +import lombok.RequiredArgsConstructor; + +@Adaptor +@RequiredArgsConstructor +public class OptionAdaptor { + + private final OptionRepository optionRepository; + + public Option queryOption(Long optionId) { + return optionRepository + .findById(optionId) + .orElseThrow(() -> OptionNotFoundException.EXCEPTION); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/adaptor/TicketItemAdaptor.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/adaptor/TicketItemAdaptor.java new file mode 100644 index 00000000..7d2474f5 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/adaptor/TicketItemAdaptor.java @@ -0,0 +1,21 @@ +package band.gosrock.domain.domains.ticket_item.adaptor; + + +import band.gosrock.common.annotation.Adaptor; +import band.gosrock.domain.domains.ticket_item.domain.TicketItem; +import band.gosrock.domain.domains.ticket_item.exception.TicketItemNotFoundException; +import band.gosrock.domain.domains.ticket_item.repository.TicketItemRepository; +import lombok.RequiredArgsConstructor; + +@Adaptor +@RequiredArgsConstructor +public class TicketItemAdaptor { + + private final TicketItemRepository ticketItemRepository; + + public TicketItem queryTicketItem(Long ticketItemId) { + return ticketItemRepository + .findById(ticketItemId) + .orElseThrow(() -> TicketItemNotFoundException.EXCEPTION); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/OptionNotFoundException.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/OptionNotFoundException.java new file mode 100644 index 00000000..5a7fce96 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/OptionNotFoundException.java @@ -0,0 +1,13 @@ +package band.gosrock.domain.domains.ticket_item.exception; + + +import band.gosrock.common.exception.DuDoongCodeException; + +public class OptionNotFoundException extends DuDoongCodeException { + + public static final DuDoongCodeException EXCEPTION = new OptionNotFoundException(); + + private OptionNotFoundException() { + super(TicketItemErrorCode.OPTION_NOT_FOUND); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/TicketItemErrorCode.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/TicketItemErrorCode.java new file mode 100644 index 00000000..7580e481 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/TicketItemErrorCode.java @@ -0,0 +1,36 @@ +package band.gosrock.domain.domains.ticket_item.exception; + +import static band.gosrock.common.consts.DuDoongStatic.NOT_FOUND; + +import band.gosrock.common.annotation.ExplainError; +import band.gosrock.common.dto.ErrorReason; +import band.gosrock.common.exception.BaseErrorCode; +import java.lang.reflect.Field; +import java.util.Objects; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum TicketItemErrorCode implements BaseErrorCode { + @ExplainError("요청에서 보내준 티켓 상품 id 값이 올바르지 않을 때 발생하는 오류입니다.") + TICKET_ITEM_NOT_FOUND(NOT_FOUND, "Ticket_Item_404_1", "티켓 아이템을 찾을 수 없습니다."), + @ExplainError("요청에서 보내준 옵션 id 값이 올바르지 않을 때 발생하는 오류입니다.") + OPTION_NOT_FOUND(NOT_FOUND, "Option_404_1", "옵션을 찾을 수 없습니다."); + + private Integer status; + private String code; + private String reason; + + @Override + public ErrorReason getErrorReason() { + return ErrorReason.builder().reason(reason).code(code).status(status).build(); + } + + @Override + public String getExplainError() throws NoSuchFieldException { + Field field = this.getClass().getField(this.name()); + ExplainError annotation = field.getAnnotation(ExplainError.class); + return Objects.nonNull(annotation) ? annotation.value() : this.getReason(); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/TicketItemNotFoundException.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/TicketItemNotFoundException.java new file mode 100644 index 00000000..7a23200c --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/exception/TicketItemNotFoundException.java @@ -0,0 +1,13 @@ +package band.gosrock.domain.domains.ticket_item.exception; + + +import band.gosrock.common.exception.DuDoongCodeException; + +public class TicketItemNotFoundException extends DuDoongCodeException { + + public static final DuDoongCodeException EXCEPTION = new TicketItemNotFoundException(); + + private TicketItemNotFoundException() { + super(TicketItemErrorCode.TICKET_ITEM_NOT_FOUND); + } +} From 3cd5284b83c96041e90f8605a77b826ff3fbc998 Mon Sep 17 00:00:00 2001 From: Chan Jin Date: Wed, 18 Jan 2023 21:59:33 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor(cart)=20:=20=EC=9E=A5=EB=B0=94?= =?UTF-8?q?=EA=B5=AC=EB=8B=88=20=EC=83=9D=EC=84=B1=EC=8B=9C=201=EC=82=AC?= =?UTF-8?q?=EB=9E=8C=EB=8B=B9=201=EA=B0=9C=20=EC=A0=95=EC=B1=85=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20(#148)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor : CreateCartUseCase 에서 매퍼로 이동 * feat : cartPolicy 적용해보기 * feat : 최근 장바구니 조회 * feat : cart valid correctAnswer * refactor : redisson lock aop call transaction factory * style : spotless * refactor : adaptor ticketitem option 적용 * feat : 에러 문서화 * fix : test redisson --- .../api/cart/controller/CartController.java | 15 +- .../cart/docs/CreateCartExceptionDocs.java | 19 +++ .../model/dto/request/AddCartLineDto.java | 24 +-- .../dto/request/AddCartOptionAnswerDto.java | 13 +- .../model/dto/request/AddCartRequest.java | 8 - ...ateCartResponse.java => CartResponse.java} | 6 +- .../api/cart/model/mapper/CartMapper.java | 129 ++++++++++++++++ .../api/cart/service/CreateCartUseCase.java | 145 ++---------------- .../api/cart/service/ReadCartLineUseCase.java | 12 -- .../api/cart/service/ReadCartUseCase.java | 27 ++++ .../gosrock/common/annotation/Policy.java | 19 +++ .../aop/redissonLock/CallTransaction.java | 8 + .../redissonLock/CallTransactionFactory.java | 20 +++ ...n.java => RedissonCallNewTransaction.java} | 2 +- .../RedissonCallSameTransaction.java | 23 +++ .../common/aop/redissonLock/RedissonLock.java | 2 + .../aop/redissonLock/RedissonLockAop.java | 6 +- .../domains/cart/adaptor/CartAdaptor.java | 20 +++ .../domain/domains/cart/domain/Cart.java | 22 +++ .../domains/cart/domain/CartLineItem.java | 34 +++- .../domains/cart/exception/CartErrorCode.java | 6 +- .../CartInvalidItemKindPolicyException.java | 13 ++ .../CartInvalidOptionAnswerException.java | 13 ++ .../domains/cart/policy/CartPolicy.java | 5 + .../domains/cart/policy/CartPolicyImpl.java | 17 ++ .../cart/repository/CartCustomRepository.java | 9 ++ .../repository/CartCustomRepositoryImpl.java | 37 +++++ .../cart/repository/CartRepository.java | 6 +- .../cart/service/CartDomainService.java | 32 +++- .../cart/service/DoneOrderEventHandler.java | 29 ++++ .../ticket_item/domain/ItemOptionGroup.java | 42 +++++ .../domains/ticket_item/domain/Option.java | 4 + .../ticket_item/domain/TicketItem.java | 27 +++- .../aop/redissonLock/RedissonLockAopTest.java | 4 +- 34 files changed, 580 insertions(+), 218 deletions(-) create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/cart/docs/CreateCartExceptionDocs.java rename DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/response/{CreateCartResponse.java => CartResponse.java} (89%) create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/cart/model/mapper/CartMapper.java delete mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/cart/service/ReadCartLineUseCase.java create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/cart/service/ReadCartUseCase.java create mode 100644 DuDoong-Common/src/main/java/band/gosrock/common/annotation/Policy.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransaction.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransactionFactory.java rename DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/{RedissonCallTransaction.java => RedissonCallNewTransaction.java} (93%) create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallSameTransaction.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartInvalidItemKindPolicyException.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartInvalidOptionAnswerException.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/policy/CartPolicy.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/policy/CartPolicyImpl.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartCustomRepository.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartCustomRepositoryImpl.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/DoneOrderEventHandler.java create mode 100644 DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/ItemOptionGroup.java diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/controller/CartController.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/controller/CartController.java index 481b774a..a02ebdd2 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/cart/controller/CartController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/controller/CartController.java @@ -1,10 +1,12 @@ package band.gosrock.api.cart.controller; +import band.gosrock.api.cart.docs.CreateCartExceptionDocs; import band.gosrock.api.cart.model.dto.request.AddCartRequest; -import band.gosrock.api.cart.model.dto.response.CreateCartResponse; +import band.gosrock.api.cart.model.dto.response.CartResponse; import band.gosrock.api.cart.service.CreateCartUseCase; -import band.gosrock.api.cart.service.ReadCartLineUseCase; +import band.gosrock.api.cart.service.ReadCartUseCase; +import band.gosrock.common.annotation.ApiErrorExceptionsExample; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -23,7 +25,7 @@ public class CartController { private final CreateCartUseCase createCartUseCase; - private final ReadCartLineUseCase readOrderLineUseCase; + private final ReadCartUseCase readCartUseCase; // @Operation(summary = "상품 아이디에 답변을 해야하는 옵션이 있는지 확인합니다.(추후 아이템 도메인으로 이전?)") // @GetMapping("/check/answer") @@ -31,8 +33,9 @@ public class CartController { // createOrderLineUseCase.execute(ticketItemId); // } @Operation(summary = "상품을 장바구니에 담습니다. 상품에 답변해야하는 응답이 있다면, 응답도 보내주시면 됩니다.") + @ApiErrorExceptionsExample(CreateCartExceptionDocs.class) @PostMapping - public CreateCartResponse createCartLines(@RequestBody @Valid AddCartRequest addCartRequest) { + public CartResponse createCartLines(@RequestBody @Valid AddCartRequest addCartRequest) { return createCartUseCase.execute(addCartRequest); } @@ -62,7 +65,7 @@ public CreateCartResponse createCartLines(@RequestBody @Valid AddCartRequest add // } @Operation(summary = "사용자가 최근에 만들었던 장바구니를 불러옵니다. 없으면 data null (구현 안해도 됨)") @PostMapping("/recent") - public void createOrder() { - readOrderLineUseCase.getRecentOrderLine(); + public CartResponse getRecentMyCart() { + return readCartUseCase.execute(); } } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/docs/CreateCartExceptionDocs.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/docs/CreateCartExceptionDocs.java new file mode 100644 index 00000000..3bf355d6 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/docs/CreateCartExceptionDocs.java @@ -0,0 +1,19 @@ +package band.gosrock.api.cart.docs; + + +import band.gosrock.common.annotation.ExceptionDoc; +import band.gosrock.common.annotation.ExplainError; +import band.gosrock.common.exception.DuDoongCodeException; +import band.gosrock.common.interfaces.SwaggerExampleExceptions; +import band.gosrock.domain.domains.cart.exception.CartInvalidItemKindPolicyException; +import band.gosrock.domain.domains.cart.exception.CartInvalidOptionAnswerException; + +@ExceptionDoc +public class CreateCartExceptionDocs implements SwaggerExampleExceptions { + + @ExplainError("아이템에 대한 옵션에 대한 응답을 올바르게 안했을때 ( 한 옵션그룹에 한 응답, 옵션그룹에 응답안했을때)") + public DuDoongCodeException 응답_올바르게_안했을_때 = CartInvalidOptionAnswerException.EXCEPTION; + + @ExplainError("카트를 담을 때 한 아이템 종류 ( 한 아이디로만 담아주세요 ), 정책 위반입니다.") + public DuDoongCodeException 한_종류_아이템만_장바구니에 = CartInvalidItemKindPolicyException.EXCEPTION; +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartLineDto.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartLineDto.java index 2bf88e40..0c7c542b 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartLineDto.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartLineDto.java @@ -1,40 +1,20 @@ package band.gosrock.api.cart.model.dto.request; -import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import javax.validation.constraints.Min; import lombok.Getter; -import lombok.RequiredArgsConstructor; @Getter -@RequiredArgsConstructor public class AddCartLineDto { @Schema(description = "주문할 아이템 아이디", defaultValue = "1") - private final Long itemId; + private Long itemId; @Schema(description = "상품 수량", defaultValue = "1") @Min(1) - private final Long quantity; + private Long quantity; @Schema(description = "상품 관련 옵션에 대한 답변") private List options; - - @JsonIgnore - public List getOptionIds() { - return options.stream().map(AddCartOptionAnswerDto::getOptionId).toList(); - } - - // public CartLineItem toCartLineItem(Long userId) { - // List cartOptionAnswers = - // addCartOptionAnswerDtos.stream() - // .map(AddCartOptionAnswerDto::toCartOptionAnswer) - // .toList(); - // return CartLineItem.builder() - // .itemId(itemId) - // .quantity(quantity) - // .cartOptionAnswers(cartOptionAnswers) - // .build(); - // } } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartOptionAnswerDto.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartOptionAnswerDto.java index 994263ef..1f87b81f 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartOptionAnswerDto.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartOptionAnswerDto.java @@ -5,24 +5,15 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import lombok.Getter; -import lombok.RequiredArgsConstructor; @Getter -@RequiredArgsConstructor public class AddCartOptionAnswerDto { @Schema(description = "옵션 아이디") @NotNull - private final Long optionId; + private Long optionId; @Schema(description = "옵션 그룹에 대한 응답/ T/F면 예,아니오 ,서술형이면 서술응답", defaultValue = "네") @NotBlank - private final String answer; - - // public CartOptionAnswer toCartOptionAnswer(Option option, OptionGroup optionGroup) { - // return CartOptionAnswer.builder() - // .option(option) - // .optionGroup(optionGroup) - // .build(); - // } + private String answer; } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartRequest.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartRequest.java index 83907b0f..557c8eaf 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartRequest.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/request/AddCartRequest.java @@ -4,18 +4,10 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import lombok.Getter; -import lombok.RequiredArgsConstructor; @Getter -@RequiredArgsConstructor public class AddCartRequest { @Schema(description = "상품에 옵션이 있을시에 각기 답변마다 여러개를 보내주시면됩니다. 한번에 답변하기면 하나에 quantity 를 늘리면 됩니다.") private List items; - - // public List getCartLines(Long userId) { - // return addCartLineDtos.stream() - // .map(addCartLineDto -> addCartLineDto.toCartLineItem(userId)) - // .toList(); - // } } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/response/CreateCartResponse.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/response/CartResponse.java similarity index 89% rename from DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/response/CreateCartResponse.java rename to DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/response/CartResponse.java index 3e71ba23..755e97b1 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/response/CreateCartResponse.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/dto/response/CartResponse.java @@ -10,7 +10,7 @@ @Getter @Builder -public class CreateCartResponse { +public class CartResponse { @Schema(description = "장바구니명 입니다.", defaultValue = "") private final String title; // 내티켓 확인하기 @@ -29,8 +29,8 @@ public class CreateCartResponse { @Schema(description = "결제가 필요한지에 대한 여부를 결정합니다. 필요한 true면 결제창 띄우시면됩니다.", defaultValue = "true") private final Boolean isNeedPayment; - public static CreateCartResponse of(List cartItemResponses, Cart cart) { - return CreateCartResponse.builder() + public static CartResponse of(List cartItemResponses, Cart cart) { + return CartResponse.builder() .items(cartItemResponses) .totalPrice(cart.getTotalPrice()) .cartId(cart.getId()) diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/mapper/CartMapper.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/mapper/CartMapper.java new file mode 100644 index 00000000..67fe3320 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/model/mapper/CartMapper.java @@ -0,0 +1,129 @@ +package band.gosrock.api.cart.model.mapper; + + +import band.gosrock.api.cart.model.dto.request.AddCartLineDto; +import band.gosrock.api.cart.model.dto.request.AddCartOptionAnswerDto; +import band.gosrock.api.cart.model.dto.request.AddCartRequest; +import band.gosrock.api.cart.model.dto.response.CartItemResponse; +import band.gosrock.api.cart.model.dto.response.CartResponse; +import band.gosrock.common.annotation.Mapper; +import band.gosrock.domain.domains.cart.adaptor.CartAdaptor; +import band.gosrock.domain.domains.cart.domain.Cart; +import band.gosrock.domain.domains.cart.domain.CartLineItem; +import band.gosrock.domain.domains.cart.domain.CartOptionAnswer; +import band.gosrock.domain.domains.ticket_item.adaptor.OptionAdaptor; +import band.gosrock.domain.domains.ticket_item.adaptor.TicketItemAdaptor; +import band.gosrock.domain.domains.ticket_item.domain.TicketItem; +import band.gosrock.domain.domains.ticket_item.repository.OptionRepository; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.transaction.annotation.Transactional; + +@Mapper +@RequiredArgsConstructor +public class CartMapper { + private final TicketItemAdaptor ticketItemAdaptor; + private final OptionAdaptor optionAdaptor; + private final OptionRepository optionRepository; + + private final CartAdaptor cartAdaptor; + + @Transactional(readOnly = true) + public CartResponse toCartResponse(Long cartId) { + Cart cart = cartAdaptor.queryCart(cartId); + return getCartResponse(cart); + } + + @Transactional(readOnly = true) + public CartResponse toCartResponse(Cart cart) { + return getCartResponse(cart); + } + + private CartResponse getCartResponse(Cart cart) { + List newCartLineItems = cart.getCartLineItems(); + Long totalQuantity = cart.getTotalQuantity(); + + List cartItemResponses = + getCartItemResponses(newCartLineItems, totalQuantity); + + return CartResponse.of(cartItemResponses, cart); + } + + private List getCartItemResponses( + List newCartLineItems, Long totalQuantity) { + int startNum = 1; + List cartItemResponses = new ArrayList<>(); + for (CartLineItem cartLineItem : newCartLineItems) { + cartItemResponses.add( + CartItemResponse.of( + generateCartLineName(cartLineItem, startNum, totalQuantity), + cartLineItem)); + startNum += cartLineItem.getQuantity(); + } + return cartItemResponses; + } + + public Cart toEntity(AddCartRequest addCartRequest, Long currentUserId) { + List addCartLineDtos = addCartRequest.getItems(); + + List cartLineItems = + addCartLineDtos.stream() + .map( + addCartLineDto -> + CartLineItem.builder() + .ticketItem(getTicketItem(addCartLineDto)) + .cartOptionAnswers( + getCartOptionAnswers(addCartLineDto)) + .quantity(addCartLineDto.getQuantity()) + .build()) + .toList(); + return Cart.builder().cartLineItems(cartLineItems).userId(currentUserId).build(); + } + + @NotNull + private TicketItem getTicketItem(AddCartLineDto addCartLineDto) { + return ticketItemAdaptor.queryTicketItem(addCartLineDto.getItemId()); + } + + /** + * 일반티켓(1/3) - 4000원 과 같은 장바구니 상품의 응답을 반환합니다 카트라인 기준입니다. 카트라인에 3개의 상품이 같이 담기면 ( 옵션이 같은 상황 ) + * 일반티켓(1-3/3) 이런식으로 표현됩니다. + * + * @param cartLineItem + * @param startNum + * @param totalQuantity + * @return + */ + private String generateCartLineName( + CartLineItem cartLineItem, int startNum, Long totalQuantity) { + Long cartLineQuantity = cartLineItem.getQuantity(); + if (cartLineQuantity.equals(1L)) { + return String.format( + "%s (%s/%d) - %s", + cartLineItem.getTicketName(), + startNum, + totalQuantity, + cartLineItem.getTotalCartLinePrice().toString()); + } + int endNum = (int) (cartLineQuantity - 1 + startNum); + return String.format( + "%s (%s/%d) - %s", + cartLineItem.getTicketName(), + startNum + "-" + endNum, + totalQuantity, + cartLineItem.getTotalCartLinePrice().toString()); + } + + private List getCartOptionAnswers(AddCartLineDto addCartLineDto) { + return addCartLineDto.getOptions().stream().map(this::getCartOptionAnswer).toList(); + } + + private CartOptionAnswer getCartOptionAnswer(AddCartOptionAnswerDto addCartOptionAnswerDto) { + return CartOptionAnswer.builder() + .option(optionAdaptor.queryOption(addCartOptionAnswerDto.getOptionId())) + .answer(addCartOptionAnswerDto.getAnswer()) + .build(); + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/CreateCartUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/CreateCartUseCase.java index 679f3db9..3fd107bf 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/CreateCartUseCase.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/CreateCartUseCase.java @@ -1,27 +1,15 @@ package band.gosrock.api.cart.service; -import band.gosrock.api.cart.model.dto.request.AddCartLineDto; -import band.gosrock.api.cart.model.dto.request.AddCartOptionAnswerDto; import band.gosrock.api.cart.model.dto.request.AddCartRequest; -import band.gosrock.api.cart.model.dto.response.CartItemResponse; -import band.gosrock.api.cart.model.dto.response.CreateCartResponse; +import band.gosrock.api.cart.model.dto.response.CartResponse; +import band.gosrock.api.cart.model.mapper.CartMapper; import band.gosrock.api.config.security.SecurityUtils; import band.gosrock.common.annotation.UseCase; -import band.gosrock.domain.domains.cart.adaptor.CartAdaptor; import band.gosrock.domain.domains.cart.domain.Cart; -import band.gosrock.domain.domains.cart.domain.CartLineItem; -import band.gosrock.domain.domains.cart.domain.CartOptionAnswer; import band.gosrock.domain.domains.cart.service.CartDomainService; -import band.gosrock.domain.domains.event.repository.EventRepository; -import band.gosrock.domain.domains.ticket_item.domain.TicketItem; -import band.gosrock.domain.domains.ticket_item.repository.OptionGroupRepository; -import band.gosrock.domain.domains.ticket_item.repository.OptionRepository; -import band.gosrock.domain.domains.ticket_item.repository.TicketItemRepository; -import java.util.ArrayList; -import java.util.List; +import javax.persistence.EntityManager; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; import org.springframework.transaction.annotation.Transactional; @UseCase @@ -29,132 +17,17 @@ public class CreateCartUseCase { private final CartDomainService cartDomainService; - private final CartAdaptor cartAdaptor; + private final CartMapper cartMapper; - // 테스트 용 - private final OptionGroupRepository optionGroupRepository; - // 테스트 용 - private final OptionRepository optionRepository; - - private final EventRepository eventRepository; - - private final TicketItemRepository ticketItemRepository; + private final EntityManager entityManager; @Transactional - public CreateCartResponse execute(AddCartRequest addCartRequest) { + public CartResponse execute(AddCartRequest addCartRequest) { Long currentUserId = SecurityUtils.getCurrentUserId(); - // 테스트용 데이타 - // Event event = eventRepository.save(Event.builder().name("고스락 제 20회 공연").build()); - // Option optionYes = - // Option.builder().additionalPrice(Money.ZERO).answer("예").build(); - // Option optionNO = - // Option.builder().additionalPrice(Money.ZERO).answer("아니오").build(); - // OptionGroup build = OptionGroup.builder() - // .name("신한은행(110- )으로 3000원 입금 바랍니다.") - // .description("계좌이체 확인후 티켓을 발급해드립니다 ( 승인 결제 )") - // .type(OptionGroupType.TRUE_FALSE) - // .event(event) - // .options(List.of(optionNO,optionYes)) - // .build(); - // optionGroupRepository.save(build); - // TicketItem ticketItem = - // TicketItem.builder() - // .type(TicketType.FIRST_COME_FIRST_SERVED) - // .name("일반 티켓 선착순") - // .price(Money.wons(3000L)) - // .build(); - // ticketItemRepository.save(ticketItem); - // - // Long id = optionYes.getId(); - // OptionGroup optionGroup = optionYes.getOptionGroup(); - // optionGroup.getName(); - // 요청에서 옵션 아이디만 추출 - // List optionIds = - // addCartRequest.getAddCartLineDtos().stream() - // .flatMap(addCartLineDto -> addCartLineDto.getOptionIds().stream()) - // .distinct() - // .toList(); - // 캐싱 - // optionRepository.findAllById(optionIds); - // List itemId = addCartLineDtos.stream() - // .map(addCartLineDto -> addCartLineDto.getItemId()).toList(); - - List addCartLineDtos = addCartRequest.getItems(); - - List cartLineItems = - addCartLineDtos.stream() - .map( - addCartLineDto -> - CartLineItem.builder() - .ticketItem(getTicketItem(addCartLineDto)) - .cartOptionAnswers( - getCartOptionAnswers(addCartLineDto)) - .quantity(addCartLineDto.getQuantity()) - .build()) - .toList(); - - Cart newCart = - cartAdaptor.save( - Cart.builder().cartLineItems(cartLineItems).userId(currentUserId).build()); - List newCartLineItems = newCart.getCartLineItems(); - Long totalQuantity = newCart.getTotalQuantity(); - - int startNum = 1; - List cartItemResponses = new ArrayList<>(); - for (CartLineItem cartLineItem : newCartLineItems) { - cartItemResponses.add( - CartItemResponse.of( - generateCartLineName(cartLineItem, startNum, totalQuantity), - cartLineItem)); - startNum += cartLineItem.getQuantity(); - } - - return CreateCartResponse.of(cartItemResponses, newCart); - } - - @NotNull - private TicketItem getTicketItem(AddCartLineDto addCartLineDto) { - return ticketItemRepository.findById(addCartLineDto.getItemId()).get(); - } - - /** - * 일반티켓(1/3) - 4000원 과 같은 장바구니 상품의 응답을 반환합니다 카트라인 기준입니다. 카트라인에 3개의 상품이 같이 담기면 ( 옵션이 같은 상황 ) - * 일반티켓(1-3/3) 이런식으로 표현됩니다. - * - * @param cartLineItem - * @param startNum - * @param totalQuantity - * @return - */ - private String generateCartLineName( - CartLineItem cartLineItem, int startNum, Long totalQuantity) { - Long cartLineQuantity = cartLineItem.getQuantity(); - if (cartLineQuantity.equals(1L)) { - return String.format( - "%s (%s/%d) - %s", - cartLineItem.getTicketName(), - startNum, - totalQuantity, - cartLineItem.getTotalCartLinePrice().toString()); - } - int endNum = (int) (cartLineQuantity - 1 + startNum); - return String.format( - "%s (%s/%d) - %s", - cartLineItem.getTicketName(), - startNum + "-" + endNum, - totalQuantity, - cartLineItem.getTotalCartLinePrice().toString()); - } - - private List getCartOptionAnswers(AddCartLineDto addCartLineDto) { - return addCartLineDto.getOptions().stream().map(this::getCartOptionAnswer).toList(); - } - private CartOptionAnswer getCartOptionAnswer(AddCartOptionAnswerDto addCartOptionAnswerDto) { - return CartOptionAnswer.builder() - .option(optionRepository.findById(addCartOptionAnswerDto.getOptionId()).get()) - .answer(addCartOptionAnswerDto.getAnswer()) - .build(); + Cart cart = cartMapper.toEntity(addCartRequest, currentUserId); + Long cartId = cartDomainService.createCart(cart, currentUserId); + return cartMapper.toCartResponse(cartId); } } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/ReadCartLineUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/ReadCartLineUseCase.java deleted file mode 100644 index 98e0944e..00000000 --- a/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/ReadCartLineUseCase.java +++ /dev/null @@ -1,12 +0,0 @@ -package band.gosrock.api.cart.service; - - -import band.gosrock.common.annotation.UseCase; - -@UseCase -public class ReadCartLineUseCase { - - public void execute() {} - - public void getRecentOrderLine() {} -} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/ReadCartUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/ReadCartUseCase.java new file mode 100644 index 00000000..a634130b --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/service/ReadCartUseCase.java @@ -0,0 +1,27 @@ +package band.gosrock.api.cart.service; + + +import band.gosrock.api.cart.model.dto.response.CartResponse; +import band.gosrock.api.cart.model.mapper.CartMapper; +import band.gosrock.api.config.security.SecurityUtils; +import band.gosrock.common.annotation.UseCase; +import band.gosrock.domain.domains.cart.adaptor.CartAdaptor; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@UseCase +@RequiredArgsConstructor +public class ReadCartUseCase { + private final CartMapper cartMapper; + private final CartAdaptor cartAdaptor; + + /** 내가 지금 가지고 있는 장바구니를 리턴합니다. 에러 상황은 아니기때문에 없으면 null 리턴합니다. */ + @Transactional(readOnly = true) + public CartResponse execute() { + Long currentUserId = SecurityUtils.getCurrentUserId(); + return cartAdaptor + .findCartByUserId(currentUserId) + .map(cartMapper::toCartResponse) + .orElse(null); + } +} diff --git a/DuDoong-Common/src/main/java/band/gosrock/common/annotation/Policy.java b/DuDoong-Common/src/main/java/band/gosrock/common/annotation/Policy.java new file mode 100644 index 00000000..7fe9a103 --- /dev/null +++ b/DuDoong-Common/src/main/java/band/gosrock/common/annotation/Policy.java @@ -0,0 +1,19 @@ +package band.gosrock.common.annotation; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; +import org.springframework.stereotype.Component; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface Policy { + @AliasFor(annotation = Component.class) + String value() default ""; +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransaction.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransaction.java new file mode 100644 index 00000000..5d006463 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransaction.java @@ -0,0 +1,8 @@ +package band.gosrock.domain.common.aop.redissonLock; + + +import org.aspectj.lang.ProceedingJoinPoint; + +public interface CallTransaction { + Object proceed(final ProceedingJoinPoint joinPoint) throws Throwable; +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransactionFactory.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransactionFactory.java new file mode 100644 index 00000000..8466ae83 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransactionFactory.java @@ -0,0 +1,20 @@ +package band.gosrock.domain.common.aop.redissonLock; + + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class CallTransactionFactory { + + private final RedissonCallSameTransaction redissonCallSameTransaction; + private final RedissonCallNewTransaction redissonCallNewTransaction; + + public CallTransaction getCallTransaction(boolean needNew) { + if (needNew == true) { + return redissonCallNewTransaction; + } + return redissonCallSameTransaction; + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallTransaction.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallNewTransaction.java similarity index 93% rename from DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallTransaction.java rename to DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallNewTransaction.java index ab567e26..d4e9730d 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallTransaction.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallNewTransaction.java @@ -11,7 +11,7 @@ @Component @RequiredArgsConstructor @Slf4j -public class RedissonCallTransaction { +public class RedissonCallNewTransaction implements CallTransaction { // 다른 트랜젹선이 걸린서비스가 해당 조인포인트(메서드를) 호출하더라도 // 새로운 트랜잭션이 보장되어야합니다. (재고를 감소시키는 로직이므로) // leaseTime 보다 트랜잭션 타임아웃을 작게 설정 diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallSameTransaction.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallSameTransaction.java new file mode 100644 index 00000000..2f156fc9 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallSameTransaction.java @@ -0,0 +1,23 @@ +package band.gosrock.domain.common.aop.redissonLock; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class RedissonCallSameTransaction implements CallTransaction { + // 다른 트랜젹선이 걸린서비스가 해당 조인포인트(메서드를) 호출하더라도 + // 새로운 트랜잭션이 보장되어야합니다. (재고를 감소시키는 로직이므로) + // leaseTime 보다 트랜잭션 타임아웃을 작게 설정 + // leastTimeOut 발생전에 rollback 시키기 위함 + @Transactional(propagation = Propagation.REQUIRED, timeout = 9) + public Object proceed(final ProceedingJoinPoint joinPoint) throws Throwable { + return joinPoint.proceed(); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLock.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLock.java index dbde198b..b2805d2a 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLock.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLock.java @@ -19,6 +19,8 @@ Class paramClassType() default Object.class; + boolean newTransaction() default false; + // redisson default waitTime 이 30 s 임 long waitTime() default 10L; diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAop.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAop.java index 27d83cf5..32315d6d 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAop.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAop.java @@ -26,7 +26,7 @@ @Slf4j public class RedissonLockAop { private final RedissonClient redissonClient; - private final RedissonCallTransaction redissonCallTransaction; + private final CallTransactionFactory callTransactionFactory; @Around("@annotation(band.gosrock.domain.common.aop.redissonLock.RedissonLock)") public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable { @@ -55,7 +55,9 @@ public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable { if (!available) { throw NotAvailableRedissonLockException.EXCEPTION; } - return redissonCallTransaction.proceed(joinPoint); + return callTransactionFactory + .getCallTransaction(redissonLock.newTransaction()) + .proceed(joinPoint); } catch (DuDoongCodeException | DuDoongDynamicException | TransactionTimedOutException e) { throw e; } finally { diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/adaptor/CartAdaptor.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/adaptor/CartAdaptor.java index 7c5c08fa..80b8cb78 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/adaptor/CartAdaptor.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/adaptor/CartAdaptor.java @@ -5,6 +5,7 @@ import band.gosrock.domain.domains.cart.domain.Cart; import band.gosrock.domain.domains.cart.exception.CartNotFoundException; import band.gosrock.domain.domains.cart.repository.CartRepository; +import java.util.Optional; import lombok.RequiredArgsConstructor; @Adaptor @@ -25,4 +26,23 @@ public Cart queryCart(Long cartId, Long userId) { .findByIdAndUserId(cartId, userId) .orElseThrow(() -> CartNotFoundException.EXCEPTION); } + + public Optional findCartByUserId(Long userId) { + return cartRepository.findByUserId(userId); + } + + public Cart find(Long cartId) { + return cartRepository.find(cartId).orElseThrow(() -> CartNotFoundException.EXCEPTION); + } + + // public Cart upsert(Cart cart) { + // Objects.requireNonNull(cart.getUserId()); + // return cartRepository + // .findByUserId(cart.getUserId()) + // .orElseGet(() -> cartRepository.save(cart)); + // } + + public void deleteByUserId(Long userId) { + cartRepository.deleteByUserId(userId); + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/domain/Cart.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/domain/Cart.java index 35022929..c40a1efe 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/domain/Cart.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/domain/Cart.java @@ -3,8 +3,11 @@ import band.gosrock.domain.common.model.BaseTimeEntity; import band.gosrock.domain.common.vo.Money; +import band.gosrock.domain.domains.cart.policy.CartPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -50,6 +53,18 @@ public Cart(Long userId, List cartLineItems) { /** ---------------------------- 검증 메서드 ---------------------------------- */ + /** 카트에 담을 수 있는 아이템의 정책이 올바른지 확인합니다. ( 한 카트에 한 아이템 ) */ + public void validItemKindPolicy(Supplier supplier) { + Objects.requireNonNull(supplier); + CartPolicy cartPolicy = supplier.get(); + cartPolicy.itemKindAvailableQuantity(this.getCartLineItemKindIds().size()); + } + + /** 아이템에 요구하는 답변을 올바르게 했는지 확인합니다. */ + public void validCorrectAnswerToItems() { + this.cartLineItems.forEach(CartLineItem::validCorrectAnswer); + } + /** ---------------------------- 조회용 메서드 ---------------------------------- */ /** 결제가 필요한 오더인지 반환합니다. */ public Boolean isNeedPayment() { @@ -68,4 +83,11 @@ public Money getTotalPrice() { .map(CartLineItem::getTotalCartLinePrice) .reduce(Money.ZERO, Money::plus); } + + private List getCartLineItemKindIds() { + return this.cartLineItems.stream() + .map(cartLineItem -> cartLineItem.getTicketItem().getId()) + .distinct() + .toList(); + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/domain/CartLineItem.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/domain/CartLineItem.java index 6a67fe78..42b100d1 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/domain/CartLineItem.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/domain/CartLineItem.java @@ -4,6 +4,7 @@ import band.gosrock.domain.common.model.BaseTimeEntity; import band.gosrock.domain.common.vo.Money; import band.gosrock.domain.common.vo.OptionAnswerVo; +import band.gosrock.domain.domains.cart.exception.CartInvalidOptionAnswerException; import band.gosrock.domain.domains.ticket_item.domain.TicketItem; import band.gosrock.domain.domains.ticket_item.domain.TicketType; import java.util.ArrayList; @@ -34,8 +35,8 @@ public class CartLineItem extends BaseTimeEntity { private Long id; // 상품 - @JoinColumn(name = "ticket_item_id", updatable = false, nullable = false) @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "ticket_item_id", updatable = false, nullable = false) private TicketItem ticketItem; // 상품 수량 @@ -59,8 +60,34 @@ public CartLineItem( /** ---------------------------- 검증 메서드 ---------------------------------- */ + /** 아이템의 옵션 그룹 목록에 제대로 된 답변을 했는 지 확인 합니다. */ + public void validCorrectAnswer() { + // 답안 + List optionAnswersGroupSortedIds = getOptionAnswersGroupSortedIds(); + + // 질문지 + List itemOptionGroupSortedIds = getItemOptionGroupSortedIds(); + + if (!itemOptionGroupSortedIds.equals(optionAnswersGroupSortedIds)) { + throw CartInvalidOptionAnswerException.EXCEPTION; + } + } + /** ---------------------------- 조회용 메서드 ---------------------------------- */ + /** 응답답변의 옵션 그룹 아이디를 가져옵니다. */ + private List getOptionAnswersGroupSortedIds() { + return this.cartOptionAnswers.stream() + .map(cartOptionAnswer -> cartOptionAnswer.getOption().getOptionGroupId()) + .sorted() + .toList(); + } + + /** 아이템의 옵션 그룹 아이디를 가져옵니다. */ + private List getItemOptionGroupSortedIds() { + return this.ticketItem.getOptionGroupIds().stream().sorted().toList(); + } + /** 응답한 옵션들의 총 가격을 불러옵니다. */ public Money getTotalOptionsPrice() { return cartOptionAnswers.stream() @@ -93,4 +120,9 @@ public Money getItemPrice() { public Boolean isNeedPayment() { return ticketItem.isNeedPayment(); } + + /** 아이템이 옵션을 가지고 있는지 판별합니다. */ + private Boolean isItemHasOption() { + return ticketItem.hasOption(); + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartErrorCode.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartErrorCode.java index e0ea472f..f64094ac 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartErrorCode.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartErrorCode.java @@ -1,5 +1,6 @@ package band.gosrock.domain.domains.cart.exception; +import static band.gosrock.common.consts.DuDoongStatic.BAD_REQUEST; import static band.gosrock.common.consts.DuDoongStatic.NOT_FOUND; import band.gosrock.common.annotation.ExplainError; @@ -14,8 +15,11 @@ @AllArgsConstructor public enum CartErrorCode implements BaseErrorCode { @ExplainError("id로 카트를 찾을 때 못 찾으면 발생하는 오류") - CART_NOT_FOUND(NOT_FOUND, "Cart_404_1", "장바구니를 찾을 수 없습니다."); + CART_NOT_FOUND(NOT_FOUND, "Cart_404_1", "장바구니를 찾을 수 없습니다."), + @ExplainError("한 장바구니엔 관련된 한 아이템만 올수 있음") + CART_INVALID_ITEM_KIND_POLICY(BAD_REQUEST, "Cart_400_1", "장바구니에 아이템을 담는 정책을 위반하였습니다."), + CART_INVALID_OPTION_ANSWER(BAD_REQUEST, "Cart_400_2", "옵션을 잘못 응답 하였습니다."); private Integer status; private String code; private String reason; diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartInvalidItemKindPolicyException.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartInvalidItemKindPolicyException.java new file mode 100644 index 00000000..3dbc7a0e --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartInvalidItemKindPolicyException.java @@ -0,0 +1,13 @@ +package band.gosrock.domain.domains.cart.exception; + + +import band.gosrock.common.exception.DuDoongCodeException; + +public class CartInvalidItemKindPolicyException extends DuDoongCodeException { + + public static final DuDoongCodeException EXCEPTION = new CartInvalidItemKindPolicyException(); + + private CartInvalidItemKindPolicyException() { + super(CartErrorCode.CART_INVALID_ITEM_KIND_POLICY); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartInvalidOptionAnswerException.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartInvalidOptionAnswerException.java new file mode 100644 index 00000000..18be0d35 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/exception/CartInvalidOptionAnswerException.java @@ -0,0 +1,13 @@ +package band.gosrock.domain.domains.cart.exception; + + +import band.gosrock.common.exception.DuDoongCodeException; + +public class CartInvalidOptionAnswerException extends DuDoongCodeException { + + public static final DuDoongCodeException EXCEPTION = new CartInvalidOptionAnswerException(); + + private CartInvalidOptionAnswerException() { + super(CartErrorCode.CART_INVALID_OPTION_ANSWER); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/policy/CartPolicy.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/policy/CartPolicy.java new file mode 100644 index 00000000..7d0918e3 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/policy/CartPolicy.java @@ -0,0 +1,5 @@ +package band.gosrock.domain.domains.cart.policy; + +public interface CartPolicy { + void itemKindAvailableQuantity(int quantity); +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/policy/CartPolicyImpl.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/policy/CartPolicyImpl.java new file mode 100644 index 00000000..98fcdce1 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/policy/CartPolicyImpl.java @@ -0,0 +1,17 @@ +package band.gosrock.domain.domains.cart.policy; + + +import band.gosrock.common.annotation.Policy; +import band.gosrock.domain.domains.cart.exception.CartInvalidItemKindPolicyException; + +/** 디폴트 장바구니 관련 정책 */ +@Policy +public class CartPolicyImpl implements CartPolicy { + + /** 아이템 수량이 몇개 까지 가능한 종류인지. */ + public void itemKindAvailableQuantity(int quantity) { + if (quantity != 1) { + throw CartInvalidItemKindPolicyException.EXCEPTION; + } + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartCustomRepository.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartCustomRepository.java new file mode 100644 index 00000000..4b062ffe --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartCustomRepository.java @@ -0,0 +1,9 @@ +package band.gosrock.domain.domains.cart.repository; + + +import band.gosrock.domain.domains.cart.domain.Cart; +import java.util.Optional; + +public interface CartCustomRepository { + public Optional find(Long cartId); +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartCustomRepositoryImpl.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartCustomRepositoryImpl.java new file mode 100644 index 00000000..c721c699 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartCustomRepositoryImpl.java @@ -0,0 +1,37 @@ +package band.gosrock.domain.domains.cart.repository; + +import static band.gosrock.domain.domains.cart.domain.QCart.cart; +import static band.gosrock.domain.domains.cart.domain.QCartLineItem.cartLineItem; +import static band.gosrock.domain.domains.ticket_item.domain.QTicketItem.ticketItem; + +import band.gosrock.domain.domains.cart.domain.Cart; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.Optional; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class CartCustomRepositoryImpl implements CartCustomRepository { + + private final JPAQueryFactory queryFactory; + + public Optional find(Long cartId) { + Cart findCart = + queryFactory + .selectFrom(cart) + .leftJoin(cart.cartLineItems, cartLineItem) + .fetchJoin() + .leftJoin(cartLineItem.ticketItem, ticketItem) + .fetchJoin() + // .leftJoin(ticketItem.itemOptionGroups ,itemOptionGroup) + // .fetchJoin() + // .leftJoin(itemOptionGroup.optionGroup) + // .fetchJoin() + // .leftJoin(cartLineItem.cartOptionAnswers , cartOptionAnswer) + // .fetchJoin() + // .leftJoin(cartOptionAnswer.option , option) + // .fetchJoin() + .where(cart.id.eq(cartId)) + .fetchOne(); + return Optional.ofNullable(findCart); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartRepository.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartRepository.java index af46262d..e57642b7 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartRepository.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/repository/CartRepository.java @@ -5,6 +5,10 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -public interface CartRepository extends JpaRepository { +public interface CartRepository extends JpaRepository, CartCustomRepository { Optional findByIdAndUserId(Long id, Long userId); + + Optional findByUserId(Long userId); + + Optional deleteByUserId(Long userId); } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/CartDomainService.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/CartDomainService.java index aeefea31..700a17aa 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/CartDomainService.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/CartDomainService.java @@ -2,16 +2,36 @@ import band.gosrock.common.annotation.DomainService; +import band.gosrock.domain.common.aop.redissonLock.RedissonLock; +import band.gosrock.domain.domains.cart.adaptor.CartAdaptor; +import band.gosrock.domain.domains.cart.domain.Cart; +import band.gosrock.domain.domains.cart.policy.CartPolicy; +import band.gosrock.domain.domains.ticket_item.repository.OptionGroupRepository; +import band.gosrock.domain.domains.ticket_item.repository.OptionRepository; +import band.gosrock.domain.domains.ticket_item.repository.TicketItemRepository; import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; @DomainService @RequiredArgsConstructor +@Transactional(readOnly = true) public class CartDomainService { - // public Cart createCart(List cartLines, Long userId) { - // //TODO : 담을려는 상품의 재고가 남아있는지 확인 ? - // //TODO : 옵션이랑 연관관계 짓기... 가격 계산하기? - // return - // - // } + private final CartAdaptor cartAdaptor; + + private final CartPolicy cartPolicy; + + private final OptionRepository optionRepository; + private final OptionGroupRepository optionGroupRepository; + private final TicketItemRepository ticketItemRepository; + + // 한유저당 한 카트를 소유할 수 있는 제약조건을 가짐 + @RedissonLock(LockName = "카트생성", paramClassType = Cart.class, identifier = "userId") + public Long createCart(Cart cart, Long userId) { + cart.validItemKindPolicy(() -> cartPolicy); + cartAdaptor.deleteByUserId(userId); + Cart savedCart = cartAdaptor.save(cart); + savedCart.validCorrectAnswerToItems(); + return savedCart.getId(); + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/DoneOrderEventHandler.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/DoneOrderEventHandler.java new file mode 100644 index 00000000..993aa115 --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/DoneOrderEventHandler.java @@ -0,0 +1,29 @@ +package band.gosrock.domain.domains.cart.service; + + +import band.gosrock.domain.common.events.order.DoneOrderEvent; +import band.gosrock.domain.domains.cart.adaptor.CartAdaptor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +@Slf4j +public class DoneOrderEventHandler { + + private final CartAdaptor cartAdaptor; + + @Async + @TransactionalEventListener( + classes = DoneOrderEvent.class, + phase = TransactionPhase.AFTER_COMMIT) + public void handleDoneOrderEvent(DoneOrderEvent doneOrderEvent) { + log.info(doneOrderEvent.getUuid() + "주문 상태 완료, 장바구니를 제거합니다."); + Long userId = doneOrderEvent.getOrder().getUserId(); + cartAdaptor.deleteByUserId(userId); + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/ItemOptionGroup.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/ItemOptionGroup.java new file mode 100644 index 00000000..83a21dcf --- /dev/null +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/ItemOptionGroup.java @@ -0,0 +1,42 @@ +package band.gosrock.domain.domains.ticket_item.domain; + + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"item_id", "option_group_id"})}) +@Entity(name = "tbl_item_option_group") +public class ItemOptionGroup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "item_option_group_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id", updatable = false) + private TicketItem item; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "option_group_id", updatable = false) + private OptionGroup optionGroup; + + @Builder + public ItemOptionGroup(TicketItem item, OptionGroup optionGroup) { + this.item = item; + this.optionGroup = optionGroup; + } +} diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/Option.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/Option.java index 9f829d39..36f8e4a9 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/Option.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/Option.java @@ -34,4 +34,8 @@ public Option(String answer, Money additionalPrice) { public void setOptionGroup(OptionGroup optionGroup) { this.optionGroup = optionGroup; } + + public Long getOptionGroupId() { + return this.optionGroup.getId(); + } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/TicketItem.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/TicketItem.java index eefb88f4..a48e6ebb 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/TicketItem.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/ticket_item/domain/TicketItem.java @@ -55,14 +55,13 @@ public class TicketItem extends BaseTimeEntity { // 판매 종료 시간 private LocalDateTime saleEndAt; - @ManyToMany - @JoinTable(name = "tbl_ticket_item_option") - private List optionGroups = new ArrayList<>(); - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id", nullable = false) private Event event; + @OneToMany(mappedBy = "item") + private List itemOptionGroups = new ArrayList<>(); + @Builder public TicketItem( TicketType type, @@ -75,7 +74,7 @@ public TicketItem( Boolean isSellable, LocalDateTime saleStartAt, LocalDateTime saleEndAt, - List optionGroups) { + List itemOptionGroups) { this.type = type; this.name = name; this.description = description; @@ -86,7 +85,7 @@ public TicketItem( this.isSellable = isSellable; this.saleStartAt = saleStartAt; this.saleEndAt = saleEndAt; - this.optionGroups = optionGroups; + this.itemOptionGroups = itemOptionGroups; } public RefundInfoVo getRefundInfoVo() { @@ -96,4 +95,20 @@ public RefundInfoVo getRefundInfoVo() { public Boolean isNeedPayment() { return this.type.isNeedPayment(); } + + public Boolean hasOption() { + return !itemOptionGroups.isEmpty(); + } + + public List getOptionGroupIds() { + return itemOptionGroups.stream() + .map(itemOptionGroup -> itemOptionGroup.getOptionGroup().getId()) + .toList(); + } + + public void addOptionGroup(OptionGroup optionGroup) { + ItemOptionGroup itemOptionGroup = + ItemOptionGroup.builder().item(this).optionGroup(optionGroup).build(); + this.itemOptionGroups.add(itemOptionGroup); + } } diff --git a/DuDoong-Domain/src/test/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAopTest.java b/DuDoong-Domain/src/test/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAopTest.java index 2c243b4b..6bef9ac8 100644 --- a/DuDoong-Domain/src/test/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAopTest.java +++ b/DuDoong-Domain/src/test/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAopTest.java @@ -12,13 +12,13 @@ class RedissonLockAopTest { @Mock RedissonClient redissonClient; - @Mock RedissonCallTransaction redissonCallTransaction; + @Mock CallTransactionFactory callTransactionFactory; RedissonLockAop redissonLockAop; @BeforeEach public void beforeEach() { - redissonLockAop = new RedissonLockAop(redissonClient, redissonCallTransaction); + redissonLockAop = new RedissonLockAop(redissonClient, callTransactionFactory); } @Test From e0903d894efc34611d14e9bc51ebecb1e799547a Mon Sep 17 00:00:00 2001 From: Chan Jin Date: Thu, 19 Jan 2023 00:07:42 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix=20:=20utf-8=20=EC=84=A4=EC=A0=95=20(#15?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/config/security/AccessDeniedFilter.java | 11 +++++------ .../api/config/security/JwtExceptionFilter.java | 4 ++-- .../gosrock/common/exception/GlobalErrorCode.java | 3 ++- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/config/security/AccessDeniedFilter.java b/DuDoong-Api/src/main/java/band/gosrock/api/config/security/AccessDeniedFilter.java index b670a9a7..d7881df8 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/config/security/AccessDeniedFilter.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/config/security/AccessDeniedFilter.java @@ -5,6 +5,7 @@ import band.gosrock.common.dto.ErrorResponse; import band.gosrock.common.exception.BaseErrorCode; import band.gosrock.common.exception.DuDoongCodeException; +import band.gosrock.common.exception.GlobalErrorCode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import javax.servlet.FilterChain; @@ -36,14 +37,9 @@ protected void doFilterInternal( } catch (AccessDeniedException e) { ErrorResponse access_denied = new ErrorResponse( - 403, - "Access Denied", - "check if accessToken exist", + GlobalErrorCode.ACCESS_TOKEN_NOT_EXIST.getErrorReason(), request.getRequestURL().toString()); responseToClient(response, access_denied); - - } finally { - response.setContentType(MediaType.APPLICATION_JSON_VALUE); } } @@ -55,6 +51,9 @@ private ErrorResponse getErrorResponse(BaseErrorCode errorCode, String path) { private void responseToClient(HttpServletResponse response, ErrorResponse errorResponse) throws IOException { + response.setCharacterEncoding("UTF-8"); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(errorResponse.getStatus()); response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); } } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/config/security/JwtExceptionFilter.java b/DuDoong-Api/src/main/java/band/gosrock/api/config/security/JwtExceptionFilter.java index 4da31bdc..dc23652e 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/config/security/JwtExceptionFilter.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/config/security/JwtExceptionFilter.java @@ -31,8 +31,6 @@ protected void doFilterInternal( responseToClient( response, getErrorResponse(e.getErrorCode(), request.getRequestURL().toString())); - } finally { - response.setContentType(MediaType.APPLICATION_JSON_VALUE); } } @@ -43,6 +41,8 @@ private ErrorResponse getErrorResponse(BaseErrorCode errorCode, String path) { private void responseToClient(HttpServletResponse response, ErrorResponse errorResponse) throws IOException { + response.setCharacterEncoding("UTF-8"); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(errorResponse.getStatus()); response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); } diff --git a/DuDoong-Common/src/main/java/band/gosrock/common/exception/GlobalErrorCode.java b/DuDoong-Common/src/main/java/band/gosrock/common/exception/GlobalErrorCode.java index 2a67a543..6b52f951 100644 --- a/DuDoong-Common/src/main/java/band/gosrock/common/exception/GlobalErrorCode.java +++ b/DuDoong-Common/src/main/java/band/gosrock/common/exception/GlobalErrorCode.java @@ -31,7 +31,8 @@ public enum GlobalErrorCode implements BaseErrorCode { @ExplainError("refreshToken 만료시 발생하는 오류입니다.") REFRESH_TOKEN_EXPIRED(FORBIDDEN, "AUTH_403_1", "인증 시간이 만료되었습니다. 재 로그인 해주세요."), - + @ExplainError("헤더에 알맞은 형식으로 accessToken을 담지않았을 때 발생하는 오류") + ACCESS_TOKEN_NOT_EXIST(FORBIDDEN, "AUTH_403_2", "어세스토큰이 있는지 확인해 주세요."), @ExplainError("인증 토큰이 잘못됐을 때 발생하는 오류입니다.") INVALID_TOKEN(UNAUTHORIZED, "GLOBAL_401_1", "잘못된 토큰입니다. 재 로그인 해주세요"), From 84d6c444ae0cec32ba2e8d2ea30ad646553c7905 Mon Sep 17 00:00:00 2001 From: Chan Jin Date: Thu, 19 Jan 2023 00:07:53 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat=20:=20=20UserUtil=20class=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : add userUtil * style : spotless --- .../band/gosrock/api/common/UserUtils.java | 23 +++++++++++++++++++ .../model/dto/request/CreateOrderRequest.java | 6 ++--- .../api/order/service/CreateOrderUseCase.java | 10 ++++---- 3 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/common/UserUtils.java diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/common/UserUtils.java b/DuDoong-Api/src/main/java/band/gosrock/api/common/UserUtils.java new file mode 100644 index 00000000..40d67fff --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/common/UserUtils.java @@ -0,0 +1,23 @@ +package band.gosrock.api.common; + + +import band.gosrock.api.config.security.SecurityUtils; +import band.gosrock.domain.domains.user.adaptor.UserAdaptor; +import band.gosrock.domain.domains.user.domain.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class UserUtils { + private final UserAdaptor userAdaptor; + + public Long getCurrentUserId() { + return SecurityUtils.getCurrentUserId(); + } + + public User getCurrentUser() { + User user = userAdaptor.queryUser(getCurrentUserId()); + return user; + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/order/model/dto/request/CreateOrderRequest.java b/DuDoong-Api/src/main/java/band/gosrock/api/order/model/dto/request/CreateOrderRequest.java index 82224bdb..396b0ef2 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/order/model/dto/request/CreateOrderRequest.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/order/model/dto/request/CreateOrderRequest.java @@ -4,15 +4,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import javax.validation.constraints.NotNull; import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.springframework.lang.Nullable; @Getter -@RequiredArgsConstructor public class CreateOrderRequest { @Nullable @Schema(nullable = true, defaultValue = "null") - private final Long couponId; + private Long couponId; - @NotNull private final Long cartId; + @NotNull private Long cartId; } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/order/service/CreateOrderUseCase.java b/DuDoong-Api/src/main/java/band/gosrock/api/order/service/CreateOrderUseCase.java index e325f537..4f3ca70f 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/order/service/CreateOrderUseCase.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/order/service/CreateOrderUseCase.java @@ -1,13 +1,12 @@ package band.gosrock.api.order.service; -import band.gosrock.api.config.security.SecurityUtils; +import band.gosrock.api.common.UserUtils; import band.gosrock.api.order.model.dto.request.CreateOrderRequest; import band.gosrock.api.order.model.dto.response.CreateOrderResponse; import band.gosrock.common.annotation.UseCase; import band.gosrock.domain.domains.order.domain.Order; import band.gosrock.domain.domains.order.service.CartToOrderService; -import band.gosrock.domain.domains.user.adaptor.UserAdaptor; import band.gosrock.domain.domains.user.domain.User; import lombok.RequiredArgsConstructor; @@ -17,15 +16,14 @@ public class CreateOrderUseCase { private final CartToOrderService cartToOrderService; - private final UserAdaptor userAdaptor; + private final UserUtils userUtils; public CreateOrderResponse execute(CreateOrderRequest createOrderRequest) { - Long currentUserId = SecurityUtils.getCurrentUserId(); - User user = userAdaptor.queryUser(currentUserId); + User user = userUtils.getCurrentUser(); if (createOrderRequest.getCouponId() == null) { Order order = cartToOrderService.creatOrderWithOutCoupon( - createOrderRequest.getCartId(), currentUserId); + createOrderRequest.getCartId(), user.getId()); return CreateOrderResponse.from(order, user.getProfile()); } return null; From 4a9e041db297a823a8b137d239bf5b6b461b71f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=84=8E=E1=85=A1=E1=86=AB=E1=84=8C?= =?UTF-8?q?=E1=85=B5=E1=86=AB?= Date: Thu, 19 Jan 2023 01:51:48 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor=20:=20=EC=A3=BC=EB=AC=B8,=EC=B9=B4?= =?UTF-8?q?=ED=8A=B8=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=EC=A0=84=20=EC=A0=95=EC=83=81=EB=8F=99=EC=9E=91=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/cart/controller/CartController.java | 3 ++- .../model/dto/request/ConfirmOrderRequest.java | 6 ++---- .../aop/redissonLock/CallTransactionFactory.java | 8 ++++---- .../RedissonCallSameTransaction.java | 2 +- .../common/aop/redissonLock/RedissonLock.java | 2 +- .../common/aop/redissonLock/RedissonLockAop.java | 2 +- .../domains/cart/service/CartDomainService.java | 16 +++++----------- .../cart/service/DoneOrderEventHandler.java | 2 ++ .../domain/domains/order/domain/Order.java | 2 +- 9 files changed, 19 insertions(+), 24 deletions(-) diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/cart/controller/CartController.java b/DuDoong-Api/src/main/java/band/gosrock/api/cart/controller/CartController.java index a02ebdd2..a36d31a5 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/cart/controller/CartController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/cart/controller/CartController.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import javax.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -64,7 +65,7 @@ public CartResponse createCartLines(@RequestBody @Valid AddCartRequest addCartRe // createOrderLineUseCase.execute(ticketItemId); // } @Operation(summary = "사용자가 최근에 만들었던 장바구니를 불러옵니다. 없으면 data null (구현 안해도 됨)") - @PostMapping("/recent") + @GetMapping("/recent") public CartResponse getRecentMyCart() { return readCartUseCase.execute(); } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/order/model/dto/request/ConfirmOrderRequest.java b/DuDoong-Api/src/main/java/band/gosrock/api/order/model/dto/request/ConfirmOrderRequest.java index b10795d0..9e2040d0 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/order/model/dto/request/ConfirmOrderRequest.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/order/model/dto/request/ConfirmOrderRequest.java @@ -1,12 +1,10 @@ package band.gosrock.api.order.model.dto.request; -import lombok.Builder; import lombok.Getter; @Getter -@Builder public class ConfirmOrderRequest { - private final String paymentKey; - private final Long amount; + private String paymentKey; + private Long amount; } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransactionFactory.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransactionFactory.java index 8466ae83..217ec040 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransactionFactory.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/CallTransactionFactory.java @@ -11,10 +11,10 @@ public class CallTransactionFactory { private final RedissonCallSameTransaction redissonCallSameTransaction; private final RedissonCallNewTransaction redissonCallNewTransaction; - public CallTransaction getCallTransaction(boolean needNew) { - if (needNew == true) { - return redissonCallNewTransaction; + public CallTransaction getCallTransaction(boolean needSame) { + if (needSame) { + return redissonCallSameTransaction; } - return redissonCallSameTransaction; + return redissonCallNewTransaction; } } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallSameTransaction.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallSameTransaction.java index 2f156fc9..b5ea723b 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallSameTransaction.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonCallSameTransaction.java @@ -16,7 +16,7 @@ public class RedissonCallSameTransaction implements CallTransaction { // 새로운 트랜잭션이 보장되어야합니다. (재고를 감소시키는 로직이므로) // leaseTime 보다 트랜잭션 타임아웃을 작게 설정 // leastTimeOut 발생전에 rollback 시키기 위함 - @Transactional(propagation = Propagation.REQUIRED, timeout = 9) + @Transactional(propagation = Propagation.MANDATORY, timeout = 9) public Object proceed(final ProceedingJoinPoint joinPoint) throws Throwable { return joinPoint.proceed(); } diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLock.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLock.java index b2805d2a..722bf058 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLock.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLock.java @@ -19,7 +19,7 @@ Class paramClassType() default Object.class; - boolean newTransaction() default false; + boolean needSameTransaction() default false; // redisson default waitTime 이 30 s 임 long waitTime() default 10L; diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAop.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAop.java index 32315d6d..8c9a607e 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAop.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/common/aop/redissonLock/RedissonLockAop.java @@ -56,7 +56,7 @@ public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable { throw NotAvailableRedissonLockException.EXCEPTION; } return callTransactionFactory - .getCallTransaction(redissonLock.newTransaction()) + .getCallTransaction(redissonLock.needSameTransaction()) .proceed(joinPoint); } catch (DuDoongCodeException | DuDoongDynamicException | TransactionTimedOutException e) { throw e; diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/CartDomainService.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/CartDomainService.java index 700a17aa..22d5bbc7 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/CartDomainService.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/CartDomainService.java @@ -6,27 +6,21 @@ import band.gosrock.domain.domains.cart.adaptor.CartAdaptor; import band.gosrock.domain.domains.cart.domain.Cart; import band.gosrock.domain.domains.cart.policy.CartPolicy; -import band.gosrock.domain.domains.ticket_item.repository.OptionGroupRepository; -import band.gosrock.domain.domains.ticket_item.repository.OptionRepository; -import band.gosrock.domain.domains.ticket_item.repository.TicketItemRepository; import lombok.RequiredArgsConstructor; -import org.springframework.transaction.annotation.Transactional; @DomainService @RequiredArgsConstructor -@Transactional(readOnly = true) public class CartDomainService { private final CartAdaptor cartAdaptor; private final CartPolicy cartPolicy; - - private final OptionRepository optionRepository; - private final OptionGroupRepository optionGroupRepository; - private final TicketItemRepository ticketItemRepository; - // 한유저당 한 카트를 소유할 수 있는 제약조건을 가짐 - @RedissonLock(LockName = "카트생성", paramClassType = Cart.class, identifier = "userId") + @RedissonLock( + LockName = "카트생성", + paramClassType = Cart.class, + identifier = "userId", + needSameTransaction = true) public Long createCart(Cart cart, Long userId) { cart.validItemKindPolicy(() -> cartPolicy); cartAdaptor.deleteByUserId(userId); diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/DoneOrderEventHandler.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/DoneOrderEventHandler.java index 993aa115..77b4f3dc 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/DoneOrderEventHandler.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/cart/service/DoneOrderEventHandler.java @@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; @@ -21,6 +22,7 @@ public class DoneOrderEventHandler { @TransactionalEventListener( classes = DoneOrderEvent.class, phase = TransactionPhase.AFTER_COMMIT) + @Transactional public void handleDoneOrderEvent(DoneOrderEvent doneOrderEvent) { log.info(doneOrderEvent.getUuid() + "주문 상태 완료, 장바구니를 제거합니다."); Long userId = doneOrderEvent.getOrder().getUserId(); diff --git a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/Order.java b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/Order.java index e0ca52bb..d9e54d90 100644 --- a/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/Order.java +++ b/DuDoong-Domain/src/main/java/band/gosrock/domain/domains/order/domain/Order.java @@ -177,7 +177,7 @@ public void approve() { orderStatus.validCanApprove(); // TODO: 재고량 비교 필요? this.approvedAt = LocalDateTime.now(); - orderStatus = OrderStatus.APPROVED; + this.orderStatus = OrderStatus.APPROVED; Events.raise(DoneOrderEvent.of(this.uuid, this)); }