From 521a920762c05a9a02cb0d07077979eef3ceca49 Mon Sep 17 00:00:00 2001 From: PgmJun Date: Tue, 6 Feb 2024 18:14:29 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B8=B0=ED=94=84=ED=8A=B8=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20#44?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/gift/GiftController.java | 18 +++++++++++++-- .../gift/service/GiftCommandService.java | 23 +++++++++++++++++-- .../gift/service/GiftServiceUtils.java | 15 ++++++++++++ .../auth/admin/AdminAuthInterceptor.java | 5 +++- .../auth/resolver/MemberIdResolver.java | 4 +++- .../common/exception/error/ErrorCode.java | 2 ++ .../com/nice/petudio/domain/gift/Gift.java | 19 ++++++++++++--- .../gift/repository/GiftRepository.java | 2 +- .../gift/repository/GiftRepositoryCustom.java | 4 ++++ .../gift/repository/GiftRepositoryImpl.java | 12 ++++++++++ src/main/resources/sql/schema.sql | 11 +++++---- 11 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/nice/petudio/api/controller/gift/service/GiftServiceUtils.java diff --git a/src/main/java/com/nice/petudio/api/controller/gift/GiftController.java b/src/main/java/com/nice/petudio/api/controller/gift/GiftController.java index 2ffea4c..c21c1f5 100644 --- a/src/main/java/com/nice/petudio/api/controller/gift/GiftController.java +++ b/src/main/java/com/nice/petudio/api/controller/gift/GiftController.java @@ -6,9 +6,14 @@ import com.nice.petudio.api.controller.gift.service.GiftQueryService; import com.nice.petudio.api.dto.ApiResponse; import com.nice.petudio.common.auth.admin.Admin; +import com.nice.petudio.common.auth.auth.Auth; +import com.nice.petudio.common.auth.resolver.MemberId; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -18,9 +23,18 @@ public class GiftController { private final GiftCommandService giftCommandService; private final GiftQueryService giftQueryService; + @Operation(summary = "[관리자] 기프트 생성") @Admin @PostMapping("/gift/generate") - public ApiResponse generateGift() { - return ApiResponse.success(giftCommandService.generateGift()); + public ApiResponse generateGift(@MemberId final Long memberId) { + return ApiResponse.success(giftCommandService.generateGift(memberId)); + } + + @Operation(summary = "[인증] 기프트 사용") + @Auth + @DeleteMapping("/gift/use") + public ApiResponse useGift(@MemberId final Long memberId, @RequestParam final String giftCode) { + giftCommandService.useGift(memberId, giftCode); + return ApiResponse.success(); } } diff --git a/src/main/java/com/nice/petudio/api/controller/gift/service/GiftCommandService.java b/src/main/java/com/nice/petudio/api/controller/gift/service/GiftCommandService.java index c7dabaf..457cabd 100644 --- a/src/main/java/com/nice/petudio/api/controller/gift/service/GiftCommandService.java +++ b/src/main/java/com/nice/petudio/api/controller/gift/service/GiftCommandService.java @@ -1,24 +1,43 @@ package com.nice.petudio.api.controller.gift.service; import com.nice.petudio.api.controller.gift.dto.GiftGenerateResponse; +import com.nice.petudio.common.exception.error.ErrorCode; +import com.nice.petudio.common.exception.model.ValidationException; import com.nice.petudio.domain.gift.Gift; import com.nice.petudio.domain.gift.repository.GiftRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service +@Slf4j @Transactional @RequiredArgsConstructor public class GiftCommandService { private final GiftRepository giftRepository; private final GiftCodeGenerator giftCodeGenerator; - public GiftGenerateResponse generateGift() { + public GiftGenerateResponse generateGift(final Long memberId) { String giftCode = giftCodeGenerator.generate(); - Gift gift = Gift.newInstance(giftCode); + Gift gift = Gift.newInstance(memberId, giftCode); giftRepository.save(gift); return GiftGenerateResponse.from(giftCode); } + + public void useGift(final Long memberId, final String giftCode) { + Gift gift = GiftServiceUtils.findByGiftId(giftRepository, giftCode); + validateGiftIsNotUsed(gift); + + gift.use(memberId); + log.info(String.format("[기프트 사용] 기프트 (GIFT_ID: %d)가 회원 (MEMBER_ID: %d)에 의해 사용되었습니다.", gift.getId(), memberId)); + } + + private void validateGiftIsNotUsed(Gift gift) { + if(gift.isUsed()) { + throw new ValidationException(ErrorCode.ALREADY_USED_GIFT_EXCEPTION, + String.format("이미 사용된 기프트 (GIFT_ID: %d) 입니다.", gift.getId())); + } + } } diff --git a/src/main/java/com/nice/petudio/api/controller/gift/service/GiftServiceUtils.java b/src/main/java/com/nice/petudio/api/controller/gift/service/GiftServiceUtils.java new file mode 100644 index 0000000..ac144d7 --- /dev/null +++ b/src/main/java/com/nice/petudio/api/controller/gift/service/GiftServiceUtils.java @@ -0,0 +1,15 @@ +package com.nice.petudio.api.controller.gift.service; + +import com.nice.petudio.common.exception.error.ErrorCode; +import com.nice.petudio.common.exception.model.NotFoundException; +import com.nice.petudio.domain.gift.Gift; +import com.nice.petudio.domain.gift.repository.GiftRepository; + +public class GiftServiceUtils { + + public static Gift findByGiftId(GiftRepository giftRepository, final String giftCode) { + return giftRepository.findByGiftCode(giftCode) + .orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_GIFT_EXCEPTION, + String.format("해당하는 GiftCode (%s)를 가진 Gift가 존재하지 않습니다.", giftCode))); + } +} diff --git a/src/main/java/com/nice/petudio/common/auth/admin/AdminAuthInterceptor.java b/src/main/java/com/nice/petudio/common/auth/admin/AdminAuthInterceptor.java index 1a7384b..1b3e6f1 100644 --- a/src/main/java/com/nice/petudio/common/auth/admin/AdminAuthInterceptor.java +++ b/src/main/java/com/nice/petudio/common/auth/admin/AdminAuthInterceptor.java @@ -1,5 +1,7 @@ package com.nice.petudio.common.auth.admin; +import static com.nice.petudio.common.auth.auth.AuthInterceptor.MEMBER_ID; + import com.nice.petudio.domain.member.MemberRole; import com.nice.petudio.common.auth.handler.AuthCheckHandler; import jakarta.servlet.http.HttpServletRequest; @@ -27,7 +29,8 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons if (adminAuth.isEmpty()) { return true; } - authHandler.validateAuthority(request, List.of(MemberRole.ADMIN)); + Long memberId = authHandler.validateAuthority(request, List.of(MemberRole.ADMIN)); + request.setAttribute(MEMBER_ID, memberId); return true; } return true; diff --git a/src/main/java/com/nice/petudio/common/auth/resolver/MemberIdResolver.java b/src/main/java/com/nice/petudio/common/auth/resolver/MemberIdResolver.java index 380b77a..61340d8 100644 --- a/src/main/java/com/nice/petudio/common/auth/resolver/MemberIdResolver.java +++ b/src/main/java/com/nice/petudio/common/auth/resolver/MemberIdResolver.java @@ -2,6 +2,7 @@ import static com.nice.petudio.common.auth.auth.AuthInterceptor.MEMBER_ID; +import com.nice.petudio.common.auth.admin.Admin; import com.nice.petudio.common.auth.auth.Auth; import com.nice.petudio.common.exception.model.InternalServerException; import com.nice.petudio.common.exception.error.ErrorCode; @@ -26,7 +27,8 @@ public boolean supportsParameter(MethodParameter parameter) { public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { Optional auth = Optional.ofNullable(parameter.getMethodAnnotation(Auth.class)); - if (auth.isEmpty()) { + Optional admin = Optional.ofNullable(parameter.getMethodAnnotation(Admin.class)); + if (auth.isEmpty() && admin.isEmpty()) { throw new InternalServerException(ErrorCode.INTERNAL_SERVER_EXCEPTION, "@MemberId 애노테이션을 적용한 메서드에 Auth 관련 애노테이션이 존재하지 않습니다."); } diff --git a/src/main/java/com/nice/petudio/common/exception/error/ErrorCode.java b/src/main/java/com/nice/petudio/common/exception/error/ErrorCode.java index ba69c3c..05dc330 100644 --- a/src/main/java/com/nice/petudio/common/exception/error/ErrorCode.java +++ b/src/main/java/com/nice/petudio/common/exception/error/ErrorCode.java @@ -14,6 +14,7 @@ public enum ErrorCode { INVALID_JWT_TOKEN_EXCEPTION("V005", "존재하지 않거나 잘못된 JWT 토큰 형식입니다."), // JWT 토큰 값에 이상이 있을 경우 INVALID_OAUTH2_ACCESS_TOKEN_EXCEPTION("V006", "존재하지 않거나 잘못된 OAuth2 Access 토큰 입니다."), NO_RESOURCE_FOUND_EXCEPTION("V007", "존재하지 않는 API 주소입니다."), + ALREADY_USED_GIFT_EXCEPTION("V008", "이미 사용된 기프트 입니다."), // UnAuthorized Exception UNAUTHORIZED_JWT_EXCEPTION("U001", "JWT 토큰이 유효하지 않습니다. 다시 로그인 해주세요."), // 인증에 실패했을 경우 @@ -26,6 +27,7 @@ public enum ErrorCode { NOT_FOUND_MEMBER_INFO_EXCEPTION("N002", "탈퇴했거나 존재하지 않는 회원 정보입니다."), NOT_FOUND_CONCEPT_EXCEPTION("N003", "존재하지 않는 컨셉입니다."), NOT_FOUND_PET_INFO_EXCEPTION("N004", "존재하지 않는 애완동물 정보입니다."), + NOT_FOUND_GIFT_EXCEPTION("N005","존재하지 않는 기프트 입니다."), // Conflict Exception CONFLICT_EXCEPTION("C001", "이미 존재하는 데이터입니다."), diff --git a/src/main/java/com/nice/petudio/domain/gift/Gift.java b/src/main/java/com/nice/petudio/domain/gift/Gift.java index df4efbd..36beee5 100644 --- a/src/main/java/com/nice/petudio/domain/gift/Gift.java +++ b/src/main/java/com/nice/petudio/domain/gift/Gift.java @@ -27,8 +27,11 @@ public class Gift extends BaseEntity { @Column(name = "gift_id") private Long id; - @Column(name = "member_id") - private Long user_id; // 기프트 사용자 아이디 + @Column(name = "buyer_id") + private Long buyerId; // 기프트 구매자 아이디 + + @Column(name = "user_id") + private Long userId; // 기프트 사용자 아이디 @Column(name = "gift_code", length = 100, nullable = false) private String code; @@ -36,10 +39,20 @@ public class Gift extends BaseEntity { @Column(name = "is_used", nullable = false) private Boolean isUsed; - public static Gift newInstance(String code) { + public static Gift newInstance(Long memberId, String code) { return Gift.builder() .code(code) + .buyerId(memberId) .isUsed(false) .build(); } + + public void use(final Long userId) { + this.userId = userId; + isUsed = true; + } + + public boolean isUsed() { + return isUsed; + } } diff --git a/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepository.java b/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepository.java index 391ef33..56e7638 100644 --- a/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepository.java +++ b/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepository.java @@ -1,7 +1,7 @@ package com.nice.petudio.domain.gift.repository; -import com.nice.petudio.domain.concept.Concept; import com.nice.petudio.domain.gift.Gift; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface GiftRepository extends GiftRepositoryCustom, JpaRepository { diff --git a/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepositoryCustom.java b/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepositoryCustom.java index 88ee137..2f016bf 100644 --- a/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepositoryCustom.java +++ b/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepositoryCustom.java @@ -1,4 +1,8 @@ package com.nice.petudio.domain.gift.repository; +import com.nice.petudio.domain.gift.Gift; +import java.util.Optional; + public interface GiftRepositoryCustom { + Optional findByGiftCode(String giftCode); } diff --git a/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepositoryImpl.java b/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepositoryImpl.java index 7ee53ca..5cccd02 100644 --- a/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepositoryImpl.java +++ b/src/main/java/com/nice/petudio/domain/gift/repository/GiftRepositoryImpl.java @@ -1,9 +1,21 @@ package com.nice.petudio.domain.gift.repository; +import static com.nice.petudio.domain.gift.QGift.gift; + +import com.nice.petudio.domain.gift.Gift; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.Optional; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class GiftRepositoryImpl implements GiftRepositoryCustom { private final JPAQueryFactory queryFactory; + + @Override + public Optional findByGiftCode(String giftCode) { + return Optional.ofNullable(queryFactory + .selectFrom(gift) + .where(gift.code.eq(giftCode)) + .fetchOne()); + } } diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql index f0388ee..a2ecf44 100644 --- a/src/main/resources/sql/schema.sql +++ b/src/main/resources/sql/schema.sql @@ -95,9 +95,10 @@ CREATE TABLE `posts` CREATE TABLE `gifts` ( `gift_id` bigint AUTO_INCREMENT PRIMARY KEY, - `member_id` bigint NULL, - `gift_code` varchar(100) NOT NULL, - `is_used` boolean NOT NULL, - `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `modified_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + `buyer_id` bigint NOT NULL, + `user_id` bigint NULL, + `gift_code` varchar(32) NOT NULL, + `is_used` boolean NOT NULL, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );