Skip to content

Commit

Permalink
feat: 기프트 사용 기능 구현 #44
Browse files Browse the repository at this point in the history
  • Loading branch information
PgmJun committed Feb 6, 2024
1 parent 902cce2 commit 521a920
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,9 +23,18 @@ public class GiftController {
private final GiftCommandService giftCommandService;
private final GiftQueryService giftQueryService;

@Operation(summary = "[관리자] 기프트 생성")
@Admin
@PostMapping("/gift/generate")
public ApiResponse<GiftGenerateResponse> generateGift() {
return ApiResponse.success(giftCommandService.generateGift());
public ApiResponse<GiftGenerateResponse> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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()));
}
}
}
Original file line number Diff line number Diff line change
@@ -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)));
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,7 +27,8 @@ public boolean supportsParameter(MethodParameter parameter) {
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
Optional<Auth> auth = Optional.ofNullable(parameter.getMethodAnnotation(Auth.class));
if (auth.isEmpty()) {
Optional<Admin> admin = Optional.ofNullable(parameter.getMethodAnnotation(Admin.class));
if (auth.isEmpty() && admin.isEmpty()) {
throw new InternalServerException(ErrorCode.INTERNAL_SERVER_EXCEPTION,
"@MemberId 애노테이션을 적용한 메서드에 Auth 관련 애노테이션이 존재하지 않습니다.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 토큰이 유효하지 않습니다. 다시 로그인 해주세요."), // 인증에 실패했을 경우
Expand All @@ -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", "이미 존재하는 데이터입니다."),
Expand Down
19 changes: 16 additions & 3 deletions src/main/java/com/nice/petudio/domain/gift/Gift.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,32 @@ 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;

@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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Gift, Long> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Gift> findByGiftCode(String giftCode);
}
Original file line number Diff line number Diff line change
@@ -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<Gift> findByGiftCode(String giftCode) {
return Optional.ofNullable(queryFactory
.selectFrom(gift)
.where(gift.code.eq(giftCode))
.fetchOne());
}
}
11 changes: 6 additions & 5 deletions src/main/resources/sql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

0 comments on commit 521a920

Please sign in to comment.