Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

부산대 BE_정지민 4주차 과제(3단계) #376

Merged
merged 37 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e81335a
feat: 지난주차 코드 가져옴
stopmin Jul 20, 2024
7222b87
feat: schema.sql 업데이트
stopmin Jul 20, 2024
64b3262
feat: Category 도메인 추가
stopmin Jul 20, 2024
e025268
feat: Category 도메인 추가
stopmin Jul 20, 2024
3365041
Merge remote-tracking branch 'origin/step1' into step1
stopmin Jul 20, 2024
10559a2
feat: 위시리스트 생성 로직 수정
stopmin Jul 20, 2024
1d878ac
feat: 로그인 토큰 앞에 Bearer
stopmin Jul 20, 2024
3d82091
feat: 유저 조회 API 생성
stopmin Jul 20, 2024
174320e
refactor: user 로그인 후 위시리스트에 상품을 추가할 수 있다
stopmin Jul 20, 2024
ae8326d
feat: 카테고리 생성/조회 로직 추가
stopmin Jul 20, 2024
68800a1
refactor: code style 통일
stopmin Jul 20, 2024
59854bb
feat: 연관관계 설정
stopmin Jul 20, 2024
6219d0c
feat: 카테고리 도메인 추가
stopmin Jul 20, 2024
ae6af7b
feat: 상품 추가 로직 카테고리 validate
stopmin Jul 20, 2024
368fbe4
docs: README.md
stopmin Jul 20, 2024
1a28ac0
refactor: jwt 의존성 버전업
stopmin Jul 20, 2024
9c063e9
refactor: jwt 의존성 버전업
stopmin Jul 20, 2024
3f2898b
feat: user Role 포함
stopmin Jul 20, 2024
b1d9737
feat: role 기반 인가
stopmin Jul 20, 2024
df876fc
feat: 카테고리 생성 관리자 인가
stopmin Jul 20, 2024
d0398bd
feat: Product Option 도메인 추가
stopmin Jul 20, 2024
886279d
feat: 가격은 제외
stopmin Jul 20, 2024
34a19ca
feat: 상품 옵션 추가 API 구현
stopmin Jul 20, 2024
558438f
feat: WishList 레포지토리 분리
stopmin Jul 20, 2024
dd0c592
feat: WishList 레포지토리 분리
stopmin Jul 20, 2024
0717c3b
feat: WishList 레포지토리 분리
stopmin Jul 20, 2024
765430a
feat: 상품 옵션 선택해서 추가
stopmin Jul 20, 2024
6bdedea
feat: kakao id는 여기 없는데 왜 추가한거지?
stopmin Jul 20, 2024
fd51d4e
feat: 구매 로직 구현
stopmin Jul 20, 2024
1e8c8a3
feat: 상품에 대한 개수 추가
stopmin Jul 20, 2024
5fef59d
Merge branch 'stopmin' into step3
stopmin Jul 22, 2024
d17a02a
feat: quentity type 수정
stopmin Jul 22, 2024
fbb2ed8
feat: 클래스단위 read only 해제
stopmin Jul 22, 2024
5d4ad4e
refactor: Category 생성자 수정
stopmin Jul 22, 2024
30239d4
refactor: ProductOption 생성자 수정
stopmin Jul 22, 2024
2c1f2eb
refactor: Product 생성자 수정
stopmin Jul 22, 2024
cffda01
refactor: WishList 생성자 수정
stopmin Jul 22, 2024
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
# spring-gift-enhancement
# spring-gift-enhancement

## 1단계 기능 요구사항

- 상품에는 항상 하나의 카테고리가 있어야 한다.
- 상품 카테고리는 수정할 수 있다.
- 관리자 화면에서 상품을 추가할 때 카테고리를 지정할 수 있다.
- 카테고리는 1차 카테고리만 있으며 2차 카테고리는 고려하지 않는다.
36 changes: 23 additions & 13 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.1'
id 'io.spring.dependency-management' version '1.1.5'
id 'io.spring.dependency-management' version '1.1.4'
}

group = 'camp.nextstep.edu'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
version = '1.0-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

//Jwt
implementation("io.jsonwebtoken:jjwt-api:0.12.3")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3")

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'

// Spring AOP
implementation 'org.springframework.boot:spring-boot-starter-aop'


testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

runtimeOnly 'com.h2database:h2'
}

tasks.named('test') {
test {
useJUnitPlatform()
}
1 change: 1 addition & 0 deletions src/main/java/gift/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

44 changes: 44 additions & 0 deletions src/main/java/gift/payment/application/PaymentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package gift.payment.application;

import gift.product.domain.Product;
import gift.product.domain.ProductOption;
import gift.product.domain.WishList;
import gift.product.domain.WishListProduct;
import gift.product.exception.ProductException;
import gift.product.infra.ProductRepository;
import gift.product.infra.WishListRepository;
import gift.util.ErrorCode;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;

@Service
public class PaymentService {

private final WishListRepository wishListRepository;
private final ProductRepository productRepository;

public PaymentService(WishListRepository wishListRepository, ProductRepository productRepository) {
this.wishListRepository = wishListRepository;
this.productRepository = productRepository;
}

@Transactional
public void processPayment(Long userId, Long wishListId) {
WishList wishList = wishListRepository.findById(wishListId);

if (!Objects.equals(wishList.getUser().getId(), userId)) {
throw new ProductException(ErrorCode.NOT_USER_OWNED);
}

for (WishListProduct product : wishList.getWishListProducts()) {
Product wishListProduct = product.getProduct();
ProductOption productOption = product.getProductOption();
productOption.decreaseQuantity(product.getQuantity());

productRepository.save(wishListProduct);
}
wishListRepository.delete(wishList);
}
}
33 changes: 33 additions & 0 deletions src/main/java/gift/payment/controller/PaymentController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package gift.payment.presentation;

import gift.payment.application.PaymentService;
import gift.util.CommonResponse;
import gift.util.annotation.JwtAuthenticated;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/payment")
public class PaymentController {

private final PaymentService paymentService;

public PaymentController(PaymentService paymentService) {
this.paymentService = paymentService;
}

@PostMapping("/process/{wishListId}")
@JwtAuthenticated
public ResponseEntity<?> processPayment(@PathVariable Long wishListId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Long userId = Long.valueOf(authentication.getName());

paymentService.processPayment(userId, wishListId);
return ResponseEntity.ok(new CommonResponse<>(
null, "결제 완료", true
));
}

}
48 changes: 48 additions & 0 deletions src/main/java/gift/product/application/CategoryService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package gift.product.application;

import gift.product.domain.Category;
import gift.product.domain.CreateCategoryRequest;
import gift.product.infra.CategoryRepository;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class CategoryService {

private final CategoryRepository categoryRepository;

public CategoryService(CategoryRepository categoryRepository) {
this.categoryRepository = categoryRepository;
}

public Category findById(Long id) {
return categoryRepository.findById(id);
}

public void addCategory(CreateCategoryRequest request) {
if (categoryRepository.findByName(request.getName()) != null) {
throw new IllegalArgumentException("이미 존재하는 카테고리입니다.");
}
Category category = new Category(request.getName(), request.getDescription(), request.getImageUrl(), request.getColor());

categoryRepository.save(category);
}

public void deleteById(Long id) {
categoryRepository.deleteById(id);
}

public Category findByName(String name) {
return categoryRepository.findByName(name);
}

public List<Category> getCategory() {
return categoryRepository.findAll();
}

public Category getCategoryByName(String category) {
return Optional.ofNullable(categoryRepository.findByName(category)).orElseThrow(() -> new IllegalArgumentException("해당 이름의 카테고리가 존재하지 않습니다."));
}
}
90 changes: 90 additions & 0 deletions src/main/java/gift/product/application/ProductService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package gift.product.application;

import gift.product.domain.*;
import gift.product.exception.ProductException;
import gift.product.infra.ProductRepository;
import gift.util.ErrorCode;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true)
public class ProductService {

private final ProductRepository productRepository;
public CategoryService categoryService;

public ProductService(ProductRepository productRepository, CategoryService categoryService) {
this.productRepository = productRepository;
this.categoryService = categoryService;
}


private static final int MAX_PRODUCT_NAME_LENGTH = 15;
private static final String RESERVED_KEYWORD = "카카오";

@Transactional
public Long saveProduct(CreateProductRequestDTO createProductRequestDTO) {
Category category = categoryService.getCategoryByName(createProductRequestDTO.getCategory());
Product product = new Product(createProductRequestDTO.getName(), createProductRequestDTO.getPrice(),
createProductRequestDTO.getImageUrl(), category);
validateProduct(product);

return productRepository.save(product).getId();
}

@Transactional
public void addProductOption(Long id, CreateProductOptionRequestDTO createProductOptionRequestDTO) {
Product product = productRepository.findById(id);

product.addProductOption(createProductOptionRequestDTO.getName(), createProductOptionRequestDTO.getQuantity());

productRepository.save(product);
}

public void deleteProduct(Long id) {
productRepository.deleteById(id);
}

public void updateProduct(Long id, String name, Double price, String imageUrl) {
Product product = productRepository.findById(id);
product.setName(name);
product.setPrice(price);
product.setImageUrl(imageUrl);
productRepository.save(product);
}

private void validateProduct(Product product) {
validateName(product.getName());
validatePrice(product.getPrice());
}

private void validateName(String name) {
if (name == null || name.isEmpty()) {
throw new ProductException(ErrorCode.INVALID_NAME);
}
if (name.length() > MAX_PRODUCT_NAME_LENGTH) {
throw new ProductException(ErrorCode.NAME_TOO_LONG);
}

if (name.contains(RESERVED_KEYWORD)) {
throw new ProductException(ErrorCode.NAME_HAS_RESTRICTED_WORD);
}
}

private void validatePrice(Double price) {
if (price == null) {
throw new ProductException(ErrorCode.INVALID_PRICE);
}
if (price < 0) {
throw new ProductException(ErrorCode.NEGATIVE_PRICE);
}
}

public List<Product> getProduct() {
return productRepository.findAll();
}

}
92 changes: 92 additions & 0 deletions src/main/java/gift/product/application/WishListService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package gift.product.application;

import gift.product.domain.Product;
import gift.product.domain.ProductOption;
import gift.product.domain.WishList;
import gift.product.domain.WishListProduct;
import gift.product.exception.ProductException;
import gift.product.infra.ProductRepository;
import gift.product.infra.WishListRepository;
import gift.user.application.UserService;
import gift.user.domain.User;
import gift.util.ErrorCode;
import jakarta.transaction.Transactional;

import java.time.LocalDateTime;
import java.util.Objects;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

@Service
public class WishListService {

private final WishListRepository wishListRepository;
private final ProductRepository productRepository;
private final UserService userService;

public WishListService(WishListRepository wishListRepository, ProductRepository productRepository,
UserService userService) {
this.wishListRepository = wishListRepository;
this.productRepository = productRepository;
this.userService = userService;
}

public WishList getWishListByUserId(Long userId) {
return wishListRepository.findByUserId(userId);
}

public Page<WishList> getProductsInWishList(Long userId, int page, int size, String sortBy, String direction) {
Sort sort = direction.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending()
: Sort.by(sortBy).descending();
Pageable pageable = PageRequest.of(page, size, sort);

if (wishListRepository.findByUserId(userId, pageable).isEmpty()) {
throw new ProductException(ErrorCode.WISHLIST_NOT_FOUND);
}
return wishListRepository.findByUserId(userId, pageable);
}

@Transactional
public void addProductToWishList(Long userId, Long wishlistId, Long productId, Long optionId, int quantity) {
WishList wishList = findById(wishlistId);
if (!Objects.equals(wishList.getUser().getId(), userId)) {
throw new ProductException(ErrorCode.NOT_USER_OWNED);
}

Product product = productRepository.findById(productId);
ProductOption productOption = productRepository.getProductWithOption(productId, optionId);

wishList.addWishListProduct(new WishListProduct(wishList, product, productOption, quantity));


wishListRepository.save(wishList);
}

public WishList findById(Long id) {
return wishListRepository.findById(id);
}

@Transactional
public void deleteProductFromWishList(Long userId, Long productId) {
WishList wishList = wishListRepository.findByUserId(userId);
if (wishList != null) {
wishList.removeWishListProduct(productId);
} else {
throw new ProductException(ErrorCode.WISHLIST_NOT_FOUND);
}
}

public void createWishList(Long userId) {
if (wishListRepository.findByUserId(userId) != null) {
throw new ProductException(ErrorCode.WISHLIST_ALREADY_EXISTS);
}
User user = userService.getUser(userId);

WishList wishList = new WishList(user, LocalDateTime.now());
wishListRepository.save(wishList);
}
}
Loading