Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,15 @@
- 배포 쉘 스크립트 작성
- CORS 설정 작성
- CORS 테스트 코드 작성
</details>

---
<details>
<summary><strong>3단계 - 포인트</strong></summary>

- Member에 포인트 추가하기
- 포인트 조회 기능 만들기
- 주문 시 포인트 차감 기능 만들기
- 주문 가격보다 포인트 부족하면 에러
- 관리자 화면에서 포인트 충전 가능하게 하기
</details>
40 changes: 40 additions & 0 deletions src/main/java/gift/controller/AdminPointController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package gift.controller;

import gift.annotation.LoginMember;
import gift.dto.pointDTO.PointRequestDTO;
import gift.exception.AuthorizationFailedException;
import gift.model.Member;
import gift.service.MemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
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;

@RestController
@RequestMapping("/admin/points")
@Tag(name = "포인트 관리 API", description = "포인트 관리를 위한 API")
public class AdminPointController {

private final MemberService memberService;

public AdminPointController(MemberService memberService) {
this.memberService = memberService;
}

@PostMapping
@Operation(summary = "포인트 충전", description = "관리자가 다른 사용자의 포인트를 충전시킵니다.")
public ResponseEntity<Void> chargePoints(
@Valid @RequestBody PointRequestDTO pointRequestDTO,
@LoginMember Member member) {
if (member == null || !"admin".equals(member.getRole())) {
throw new AuthorizationFailedException("관리자 권한이 없습니다.");
}
memberService.chargePoints(pointRequestDTO);
return ResponseEntity.ok().build();
}

}
4 changes: 4 additions & 0 deletions src/main/java/gift/controller/OrderController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import gift.dto.orderDTO.OrderRequestDTO;
import gift.dto.orderDTO.OrderResponseDTO;
import gift.exception.AuthorizationFailedException;
import gift.exception.InvalidInputValueException;
import gift.exception.NotFoundException;
import gift.exception.ServerErrorException;
import gift.model.Member;
import gift.service.KakaoService;
Expand Down Expand Up @@ -43,6 +45,8 @@ public ResponseEntity<OrderResponseDTO> createOrder(
OrderResponseDTO orderResponseDTO;
try {
orderResponseDTO = orderService.createOrder(orderRequestDTO, member.getEmail());
} catch(InvalidInputValueException | NotFoundException e) {
throw e;
} catch (Exception e) {
throw new ServerErrorException("서버 오류가 발생했습니다.");
}
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/gift/controller/PointController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package gift.controller;


import gift.annotation.LoginMember;
import gift.dto.pointDTO.PointResponseDTO;
import gift.exception.AuthorizationFailedException;
import gift.model.Member;
import gift.service.MemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/points")
@Tag(name = "포인트 조회 API", description = "포인트 조회를 위한 API")
public class PointController {

private final MemberService memberService;

public PointController(MemberService memberService) {
this.memberService = memberService;
}

@GetMapping
@Operation(summary = "포인트 조회", description = "멤버(사용자)의 포인트를 조회합니다.")
public ResponseEntity<PointResponseDTO> getPoints(@LoginMember Member member) {
if (member == null) {
throw new AuthorizationFailedException("인증되지 않은 사용자입니다.");
}
PointResponseDTO pointResponseDTO = memberService.getPoints(member.getEmail());
return ResponseEntity.ok(pointResponseDTO);
}

}
14 changes: 14 additions & 0 deletions src/main/java/gift/dto/pointDTO/PointRequestDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package gift.dto.pointDTO;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public record PointRequestDTO(
@NotNull(message = "이메일을 입력해야 합니다.")
String email,
@Min(value = 1, message = "포인트는 1 이상이어야 합니다.")
@NotNull(message = "포인트 입력은 필수입니다.")
Long points
) {

}
7 changes: 7 additions & 0 deletions src/main/java/gift/dto/pointDTO/PointResponseDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gift.dto.pointDTO;

public record PointResponseDTO(
Long points
) {

}
24 changes: 23 additions & 1 deletion src/main/java/gift/model/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,20 @@ public class Member {
@Column(name = "role")
private String role;

@Column(name = "points")
private Long points;

protected Member() {
}

public Member(Long id, String email, String password, String role) {
public Member(Long id, String email, String password, String role, Long points) {
validateEmail(email);
validatePassword(password);
this.id = id;
this.email = email;
this.password = password;
this.role = role;
this.points = points;
}

public Long getId() {
Expand All @@ -53,6 +57,24 @@ public String getRole() {
return role;
}

public Long getPoints() {
return points;
}

public void addPoints(Long points) {
if (points <= 0) {
throw new InvalidInputValueException("충전할 포인트는 0보다 커야 합니다.");
}
this.points += points;
}

public void subtractPoints(Long price) {
if (this.points < price) {
throw new InvalidInputValueException("포인트가 부족합니다.");
}
this.points -= price;
}

private void validateEmail(String email) {
if (email == null || email.trim().isEmpty()) {
throw new InvalidInputValueException("이메일을 입력하세요.");
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/gift/service/KakaoService.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public Member saveKakaoUser(String email) {
String password = generateRandomPassword();
MemberDTO memberDTO = new MemberDTO(name, email, password);
member = new Member(null, memberDTO.email(), memberDTO.password(),
"user");
"user", 0L);
memberRepository.save(member);
}
return member;
Expand Down
33 changes: 32 additions & 1 deletion src/main/java/gift/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import gift.dto.memberDTO.LoginRequestDTO;
import gift.dto.memberDTO.RegisterRequestDTO;
import gift.dto.pointDTO.PointRequestDTO;
import gift.dto.pointDTO.PointResponseDTO;
import gift.exception.InvalidInputValueException;
import gift.exception.NotFoundException;
import gift.model.Member;
import gift.repository.MemberRepository;
import gift.util.JwtUtil;
Expand All @@ -28,7 +31,7 @@ public String registerMember(RegisterRequestDTO registerRequestDTO) {
throw new InvalidInputValueException("이메일이 이미 존재합니다.");
}
Member member = new Member(null, registerRequestDTO.email(), registerRequestDTO.password(),
"user");
"user", 0L);
memberRepository.save(member);

return jwtUtil.generateToken(member.getEmail(), member.getPassword());
Expand All @@ -46,4 +49,32 @@ public String loginMember(LoginRequestDTO loginRequestDTO) {
public Member getMemberByEmail(String email) {
return memberRepository.findByEmail(email);
}

public PointResponseDTO getPoints(String email) {
Member member = memberRepository.findByEmail(email);
if (member == null) {
throw new NotFoundException("유효하지 않은 이메일입니다.");
}
return new PointResponseDTO(member.getPoints());
}

@Transactional
public void subtractPoints(PointRequestDTO pointRequestDTO) {
Member member = memberRepository.findByEmail(pointRequestDTO.email());
if (member == null) {
throw new NotFoundException("유효하지 않은 이메일입니다.");
}
member.subtractPoints(pointRequestDTO.points());
memberRepository.save(member);
}

@Transactional
public void chargePoints(PointRequestDTO pointRequestDTO) {
Member member = memberRepository.findByEmail(pointRequestDTO.email());
if (member == null) {
throw new NotFoundException("사용자를 찾을 수 없습니다.");
}
member.addPoints(pointRequestDTO.points());
memberRepository.save(member);
}
}
8 changes: 8 additions & 0 deletions src/main/java/gift/service/OrderService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import gift.dto.optionDTO.OptionResponseDTO;
import gift.dto.orderDTO.OrderRequestDTO;
import gift.dto.orderDTO.OrderResponseDTO;
import gift.dto.pointDTO.PointRequestDTO;
import gift.exception.InvalidInputValueException;
import gift.exception.NotFoundException;
import gift.exception.ServerErrorException;
Expand Down Expand Up @@ -48,6 +49,13 @@ public OrderResponseDTO createOrder(OrderRequestDTO orderRequestDTO, String emai
throw new InvalidInputValueException("잘못된 수량 입력입니다.");
}
Option option = optionService.toEntity(optionResponseDTO);
Long productPrice = Long.parseLong(option.getProduct().getPrice());
Long totalPrice = productPrice * orderRequestDTO.quantity();
if (member.getPoints() < totalPrice) {
throw new InvalidInputValueException("포인트가 부족합니다.");
}
PointRequestDTO pointRequestDTO = new PointRequestDTO(email, totalPrice);
memberService.subtractPoints(pointRequestDTO);
optionService.subtractQuantity(option.getId(), orderRequestDTO.quantity());
Order order = new Order(null, option, orderRequestDTO.quantity(), LocalDateTime.now(),
orderRequestDTO.message(), member);
Expand Down
12 changes: 6 additions & 6 deletions src/main/resources/db/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ INSERT INTO product (name, price, category_id, image_url)
VALUES ('예시1', '100', 1, '예시1 Image Url'),
('예시2', '200', 2, '예시2 Image Url');

INSERT INTO member (email, password, role)
VALUES ('admin@email.com', 'password', 'admin');
INSERT INTO member (email, password, role)
VALUES ('member@email.com', 'password', 'user');
INSERT INTO member (email, password, role)
VALUES ('kakaouser@email.com', 'password', 'kakaouser');
INSERT INTO member (email, password, role, points)
VALUES ('admin@email.com', 'password', 'admin', 10000);
INSERT INTO member (email, password, role, points)
VALUES ('member@email.com', 'password', 'user', 100);
INSERT INTO member (email, password, role, points)
VALUES ('kakaouser@email.com', 'password', 'kakaouser', 1000);

INSERT INTO options (name, quantity, product_id)
VALUES ('임시 옵션1', 1, 1),
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ CREATE TABLE member
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL
role VARCHAR(50) NOT NULL,
points BIGINT NOT NULL
);

CREATE TABLE options
Expand Down
12 changes: 6 additions & 6 deletions src/test/java/gift/model/MemberTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class MemberTest {

@Test
void testCreateValidMember() {
Member member = new Member(1L, "kbm@kbm.com", "mbk", "user");
Member member = new Member(1L, "kbm@kbm.com", "mbk", "user", 0L);
assertAll(
() -> assertThat(member.getId()).isNotNull(),
() -> assertThat(member.getEmail()).isEqualTo("kbm@kbm.com"),
Expand All @@ -22,7 +22,7 @@ void testCreateValidMember() {
@Test
void testCreateWithNullEmail() {
try {
Member nullEmailMember = new Member(1L, null, "mbk", "user");
Member nullEmailMember = new Member(1L, null, "mbk", "user", 0L);
} catch (InvalidInputValueException e) {
assertThat(e).isInstanceOf(InvalidInputValueException.class);
}
Expand All @@ -31,7 +31,7 @@ void testCreateWithNullEmail() {
@Test
void testCreateWithEmptyEmail() {
try {
Member emptyEmailMember = new Member(1L, "", "mbk", "user");
Member emptyEmailMember = new Member(1L, "", "mbk", "user", 0L);
} catch (InvalidInputValueException e) {
assertThat(e).isInstanceOf(InvalidInputValueException.class);
}
Expand All @@ -40,7 +40,7 @@ void testCreateWithEmptyEmail() {
@Test
void testCreateWithInvalidEmail() {
try {
Member invalidEmailMember = new Member(1L, "kbm", "mbk", "user");
Member invalidEmailMember = new Member(1L, "kbm", "mbk", "user", 0L);
} catch (InvalidInputValueException e) {
assertThat(e).isInstanceOf(InvalidInputValueException.class);
}
Expand All @@ -49,7 +49,7 @@ void testCreateWithInvalidEmail() {
@Test
void testCreateWithNullPassword() {
try {
Member nullPasswordMember = new Member(1L, "kbm@kbm.com", null, "user");
Member nullPasswordMember = new Member(1L, "kbm@kbm.com", null, "user", 0L);
} catch (InvalidInputValueException e) {
assertThat(e).isInstanceOf(InvalidInputValueException.class);
}
Expand All @@ -58,7 +58,7 @@ void testCreateWithNullPassword() {
@Test
void testCreateWithEmptyPassword() {
try {
Member emptyPasswordMember = new Member(1L, "kbm@kbm.com", "", "user");
Member emptyPasswordMember = new Member(1L, "kbm@kbm.com", "", "user", 0L);
} catch (InvalidInputValueException e) {
assertThat(e).isInstanceOf(InvalidInputValueException.class);
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/gift/model/OrderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class OrderTest {

@BeforeEach
void setUp() {
member = new Member(1L, "email@email.com", "password", "user");
member = new Member(1L, "email@email.com", "password", "user", 0L);
category = new Category(1L, "교환권", "#007700", "임시 이미지", "임시 설명");
product = new Product(1L, "상품", "100", category, "https://kakao");
option = new Option(1L, "임시 옵션", 10L, product);
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/gift/repository/MemberRepositoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class MemberRepositoryTest {

@BeforeEach
public void setUp() {
member = new Member(1L, "kbm@kbm.com", "mbk", "user");
member = new Member(1L, "kbm@kbm.com", "mbk", "user", 0L);
savedMember = memberRepository.save(member);
}

Expand Down
2 changes: 1 addition & 1 deletion src/test/java/gift/repository/OrderRepositoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class OrderRepositoryTest {
public void setUp() {
Category category = new Category(null, "사치품", "#007700", "임시 이미지", "임시 설명");
savedCategory = categoryRepository.save(category);
Member member = new Member(null, "email@email.com", "password", "user");
Member member = new Member(null, "email@email.com", "password", "user", 0L);
savedMember = memberRepository.save(member);
Product product = new Product(null, "상품", "100", savedCategory, "https://kakao");
savedProduct = productRepository.save(product);
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/gift/repository/WishlistRepositoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class WishlistRepositoryTest {
public void setUp() {
Category category = new Category(1L, "교환권", "#007700", "임시 이미지", "임시 설명");
savedCategory = categoryRepository.save(category);
Member member = new Member(4L, "kbm@kbm.com", "mbk", "user");
Member member = new Member(4L, "kbm@kbm.com", "mbk", "user", 0L);
savedMember = memberRepository.save(member);
Product product = new Product(1L, "상품", "100", savedCategory, "https://kakao");
savedProduct = productRepository.save(product);
Expand Down
Loading