From e81335ade4d067e1cd6a2271d83f2de48c35356d Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 16:03:14 +0900 Subject: [PATCH 01/35] =?UTF-8?q?feat:=20=EC=A7=80=EB=82=9C=EC=A3=BC?= =?UTF-8?q?=EC=B0=A8=20=EC=BD=94=EB=93=9C=20=EA=B0=80=EC=A0=B8=EC=98=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: 지난주차 코드 가져옴 --- README.md | 10 +- build.gradle | 36 +++-- src/main/java/gift/Application.java | 1 + .../product/application/ProductService.java | 82 +++++++++++ .../product/application/WishListService.java | 69 +++++++++ .../gift/product/config/SecurityConfig.java | 39 +++++ .../domain/CreateProductRequestDTO.java | 31 ++++ .../java/gift/product/domain/Product.java | 79 ++++++++++ .../java/gift/product/domain/WishList.java | 86 +++++++++++ .../gift/product/domain/WishListProduct.java | 60 ++++++++ .../product/exception/ProductException.java | 13 ++ .../gift/product/infra/ProductRepository.java | 17 +++ .../gift/product/infra/ProductRowMapper.java | 21 +++ .../product/infra/WishListRepository.java | 17 +++ .../presentation/ProductManageController.java | 48 ++++++ .../presentation/WishListController.java | 54 +++++++ .../gift/user/application/UserService.java | 61 ++++++++ src/main/java/gift/user/domain/User.java | 75 ++++++++++ .../gift/user/domain/UserRegisterRequest.java | 23 +++ .../gift/user/exception/UserException.java | 11 ++ .../java/gift/user/infra/UserRepository.java | 20 +++ .../user/presentation/UserController.java | 56 +++++++ src/main/java/gift/util/CommonResponse.java | 38 +++++ src/main/java/gift/util/CustomException.java | 22 +++ src/main/java/gift/util/ErrorCode.java | 41 ++++++ src/main/java/gift/util/ErrorResponse.java | 28 ++++ .../gift/util/GlobalExceptionHandler.java | 40 +++++ src/main/java/gift/util/JwtAspect.java | 32 ++++ src/main/java/gift/util/JwtAuthenticated.java | 13 ++ src/main/java/gift/util/JwtUtil.java | 68 +++++++++ src/main/resources/application-secret.yml | 2 + src/main/resources/application.properties | 1 + src/main/resources/application.yml | 32 ++++ src/main/resources/db/data.sql | 6 + src/main/resources/db/schema.sql | 30 ++++ src/main/resources/templates/wishlist.html | 139 ++++++++++++++++++ 36 files changed, 1387 insertions(+), 14 deletions(-) create mode 100644 src/main/java/gift/product/application/ProductService.java create mode 100644 src/main/java/gift/product/application/WishListService.java create mode 100644 src/main/java/gift/product/config/SecurityConfig.java create mode 100644 src/main/java/gift/product/domain/CreateProductRequestDTO.java create mode 100644 src/main/java/gift/product/domain/Product.java create mode 100644 src/main/java/gift/product/domain/WishList.java create mode 100644 src/main/java/gift/product/domain/WishListProduct.java create mode 100644 src/main/java/gift/product/exception/ProductException.java create mode 100644 src/main/java/gift/product/infra/ProductRepository.java create mode 100644 src/main/java/gift/product/infra/ProductRowMapper.java create mode 100644 src/main/java/gift/product/infra/WishListRepository.java create mode 100644 src/main/java/gift/product/presentation/ProductManageController.java create mode 100644 src/main/java/gift/product/presentation/WishListController.java create mode 100644 src/main/java/gift/user/application/UserService.java create mode 100644 src/main/java/gift/user/domain/User.java create mode 100644 src/main/java/gift/user/domain/UserRegisterRequest.java create mode 100644 src/main/java/gift/user/exception/UserException.java create mode 100644 src/main/java/gift/user/infra/UserRepository.java create mode 100644 src/main/java/gift/user/presentation/UserController.java create mode 100644 src/main/java/gift/util/CommonResponse.java create mode 100644 src/main/java/gift/util/CustomException.java create mode 100644 src/main/java/gift/util/ErrorCode.java create mode 100644 src/main/java/gift/util/ErrorResponse.java create mode 100644 src/main/java/gift/util/GlobalExceptionHandler.java create mode 100644 src/main/java/gift/util/JwtAspect.java create mode 100644 src/main/java/gift/util/JwtAuthenticated.java create mode 100644 src/main/java/gift/util/JwtUtil.java create mode 100644 src/main/resources/application-secret.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/db/data.sql create mode 100644 src/main/resources/db/schema.sql create mode 100644 src/main/resources/templates/wishlist.html diff --git a/README.md b/README.md index 75f82d8fa..d1c92abd2 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# spring-gift-enhancement \ No newline at end of file +# spring-gift-jpa + +## 1단계: 엔티티 매핑 + +- [x] 지금까지 작성한 JdbcTemplate 기반 코드를 JPA로 리팩터링하고 실제 도메인 모델을 어떻게 구성하고 객체와 테이블을 어떻게 매핑해야 하는지 알아본다. + +## 2단계: 연관관계 매핑 + +- 객체의 참조와 테이블의 외래 키를 매핑해서 객체에서는 참조를 사용하고 테이블에서는 외래 키를 사용할 수 있도록 한다. diff --git a/build.gradle b/build.gradle index df7db9334..0800a03e7 100644 --- a/build.gradle +++ b/build.gradle @@ -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.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // 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() } diff --git a/src/main/java/gift/Application.java b/src/main/java/gift/Application.java index 61603cca0..de7ef5159 100644 --- a/src/main/java/gift/Application.java +++ b/src/main/java/gift/Application.java @@ -9,3 +9,4 @@ public static void main(String[] args) { SpringApplication.run(Application.class, args); } } + diff --git a/src/main/java/gift/product/application/ProductService.java b/src/main/java/gift/product/application/ProductService.java new file mode 100644 index 000000000..50434f8a7 --- /dev/null +++ b/src/main/java/gift/product/application/ProductService.java @@ -0,0 +1,82 @@ +package gift.product.application; + +import gift.product.domain.CreateProductRequestDTO; +import gift.product.domain.Product; +import gift.product.exception.ProductException; +import gift.product.infra.ProductRepository; +import gift.util.ErrorCode; +import java.util.List; +import java.util.Optional; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class ProductService { + + private final ProductRepository productRepository; + + public ProductService(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + private static final int MAX_PRODUCT_NAME_LENGTH = 15; + private static final String RESERVED_KEYWORD = "카카오"; + + @Transactional + public Long saveProduct(CreateProductRequestDTO createProductRequestDTO) { + Product product = new Product(createProductRequestDTO.getName(), createProductRequestDTO.getPrice(), + createProductRequestDTO.getImageUrl()); + validateProduct(product); + return productRepository.save(product).getId(); + } + + public void deleteProduct(Long id) { + productRepository.deleteById(id); + } + + public void updateProduct(Long id, String name, Double price, String imageUrl) { + Product product = productRepository.findById(id) + .orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_NOT_FOUND)); + 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 Optional getProductByName(Long id) { + return productRepository.findById(id); + } + + public List getProduct() { + return productRepository.findAll(); + } + +} diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java new file mode 100644 index 000000000..9322585b2 --- /dev/null +++ b/src/main/java/gift/product/application/WishListService.java @@ -0,0 +1,69 @@ +package gift.product.application; + +import gift.product.domain.Product; +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 jakarta.transaction.Transactional; +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; + + public WishListService(WishListRepository wishListRepository, ProductRepository productRepository) { + this.wishListRepository = wishListRepository; + this.productRepository = productRepository; + } + + public WishList getWishListByUserId(Long userId) { + return wishListRepository.findByUserId(userId); + } + + + public Page 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 productId) { + WishList wishList = wishListRepository.findByUserId(userId); + Product product = productRepository.findById(productId).orElseThrow(); + + if (wishList == null) { + wishList = new WishList(); + wishList = wishListRepository.save(wishList); + } + product.setWishList(wishList); + + WishListProduct wishListProduct = new WishListProduct(product, wishList); + wishList.addWishListProduct(wishListProduct); + + wishListRepository.save(wishList); + } + + @Transactional + public void deleteProductFromWishList(Long userId, Long productId) { + WishList wishList = wishListRepository.findByUserId(userId); + if (wishList != null) { + wishList.removeWishListProduct(productId); + } + } +} diff --git a/src/main/java/gift/product/config/SecurityConfig.java b/src/main/java/gift/product/config/SecurityConfig.java new file mode 100644 index 000000000..01f23f5ff --- /dev/null +++ b/src/main/java/gift/product/config/SecurityConfig.java @@ -0,0 +1,39 @@ +package gift.product.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) // CSRF 보호 비활성화 + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/api/register", "/api/login").permitAll() + .requestMatchers("/api/**").permitAll() + .requestMatchers("/h2-console/**").permitAll() // H2 콘솔 접근 허용 + .anyRequest().authenticated() + ) + .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) // H2 콘솔을 위한 설정 + .formLogin(AbstractHttpConfigurer::disable) // 기본 폼 로그인 비활성화 + .httpBasic(AbstractHttpConfigurer::disable); // HTTP Basic 인증 비활성화 + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/gift/product/domain/CreateProductRequestDTO.java b/src/main/java/gift/product/domain/CreateProductRequestDTO.java new file mode 100644 index 000000000..bab417532 --- /dev/null +++ b/src/main/java/gift/product/domain/CreateProductRequestDTO.java @@ -0,0 +1,31 @@ +package gift.product.domain; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +public class CreateProductRequestDTO { + + private static final int MAX_INPUT_LENGTH = 255; + + @NotBlank(message = "이름은 필수 입력 값입니다") + private String name; + + @NotNull(message = "가격은 필수 입력 값입니다.") + private Double price; + + @Size(max = MAX_INPUT_LENGTH, message = "이미지 URL은 255자를 넘을 수 없습니다.") + private String imageUrl; + + public String getName() { + return name; + } + + public Double getPrice() { + return price; + } + + public String getImageUrl() { + return imageUrl; + } +} diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java new file mode 100644 index 000000000..f8caf4e12 --- /dev/null +++ b/src/main/java/gift/product/domain/Product.java @@ -0,0 +1,79 @@ +package gift.product.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; + +@Entity(name = "product") +public class Product { + + @Id + @Column(name = "product_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + private Double price; + private String imageUrl; + + @ManyToOne(fetch = FetchType.LAZY) + private WishList wishList; + + public Product(String name, Double price, String imageUrl) { + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + public Product(Long id, String name, Double price, String imageUrl) { + this.id = id; + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + public Product() { + this(null, null, null, null); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public void setWishList(WishList wishList) { + this.wishList = wishList; + } + +} + diff --git a/src/main/java/gift/product/domain/WishList.java b/src/main/java/gift/product/domain/WishList.java new file mode 100644 index 000000000..3b9fd0301 --- /dev/null +++ b/src/main/java/gift/product/domain/WishList.java @@ -0,0 +1,86 @@ +package gift.product.domain; + +import gift.user.domain.User; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.time.LocalDateTime; +import java.util.ArrayList; + +@Entity(name = "wishlist") +public class WishList { + + @Id + @Column(name = "wishlist_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @OneToMany(mappedBy = "wishList", cascade = CascadeType.ALL, orphanRemoval = true) + private ArrayList wishListProducts = new ArrayList<>(); + + private LocalDateTime createdAt; + + public WishList() { + } + + public WishList(User user, ArrayList wishListProducts, LocalDateTime createdAt) { + this.user = user; + this.wishListProducts = wishListProducts; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + user.getWishLists().add(this); + } + + public ArrayList getWishListProducts() { + return wishListProducts; + } + + public void addWishListProduct(WishListProduct wishListProduct) { + wishListProducts.add(wishListProduct); + wishListProduct.setWishList(this); + } + + public void removeWishListProduct(Long wishListProductId) { + for (WishListProduct wishListProduct : wishListProducts) { + if (wishListProduct.getId().equals(wishListProductId)) { + wishListProducts.remove(wishListProduct); + wishListProduct.setWishList(null); + break; + } + } + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } +} diff --git a/src/main/java/gift/product/domain/WishListProduct.java b/src/main/java/gift/product/domain/WishListProduct.java new file mode 100644 index 000000000..bffd8e86a --- /dev/null +++ b/src/main/java/gift/product/domain/WishListProduct.java @@ -0,0 +1,60 @@ +package gift.product.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +@Entity(name = "wishlist_product") +public class WishListProduct { + + @Id + @Column(name = "wishlist_product_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id") + private Product product; + + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "wishlist_id") + private WishList wishList; + + public WishListProduct() { + } + + public WishListProduct(Product product, WishList wishList) { + this.product = product; + this.wishList = wishList; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public WishList getWishList() { + return wishList; + } + + public void setWishList(WishList wishList) { + this.wishList = wishList; + } +} diff --git a/src/main/java/gift/product/exception/ProductException.java b/src/main/java/gift/product/exception/ProductException.java new file mode 100644 index 000000000..1af8dbc14 --- /dev/null +++ b/src/main/java/gift/product/exception/ProductException.java @@ -0,0 +1,13 @@ +package gift.product.exception; + +import gift.util.CustomException; +import gift.util.ErrorCode; + +public class ProductException extends CustomException { + + public ProductException(ErrorCode errorCode) { + super(errorCode); + } +} + + diff --git a/src/main/java/gift/product/infra/ProductRepository.java b/src/main/java/gift/product/infra/ProductRepository.java new file mode 100644 index 000000000..4ddfe9c24 --- /dev/null +++ b/src/main/java/gift/product/infra/ProductRepository.java @@ -0,0 +1,17 @@ +package gift.product.infra; + +import gift.product.domain.Product; +import java.sql.Statement; +import java.sql.PreparedStatement; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductRepository extends JpaRepository { + +} diff --git a/src/main/java/gift/product/infra/ProductRowMapper.java b/src/main/java/gift/product/infra/ProductRowMapper.java new file mode 100644 index 000000000..b750079cc --- /dev/null +++ b/src/main/java/gift/product/infra/ProductRowMapper.java @@ -0,0 +1,21 @@ +package gift.product.infra; + +import gift.product.domain.Product; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ProductRowMapper implements RowMapper { + + @Override + public Product mapRow(ResultSet rs, int rowNum) throws SQLException { + return new Product( + rs.getLong("id"), + rs.getString("name"), + rs.getDouble("price"), + rs.getString("imageUrl") + + ); + } +} diff --git a/src/main/java/gift/product/infra/WishListRepository.java b/src/main/java/gift/product/infra/WishListRepository.java new file mode 100644 index 000000000..288cf32bd --- /dev/null +++ b/src/main/java/gift/product/infra/WishListRepository.java @@ -0,0 +1,17 @@ +package gift.product.infra; + +import gift.product.domain.WishList; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface WishListRepository extends JpaRepository { + + WishList findByUserId(Long userId); + + + Page findByUserId(Long userId, Pageable pageable); + +} diff --git a/src/main/java/gift/product/presentation/ProductManageController.java b/src/main/java/gift/product/presentation/ProductManageController.java new file mode 100644 index 000000000..32ee82fd8 --- /dev/null +++ b/src/main/java/gift/product/presentation/ProductManageController.java @@ -0,0 +1,48 @@ +package gift.product.presentation; + +import gift.product.application.ProductService; +import gift.product.domain.CreateProductRequestDTO; +import gift.product.domain.Product; +import gift.util.CommonResponse; +import jakarta.validation.Valid; +import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +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; + +@RestController +@RequestMapping("/api/product") +public class ProductManageController { + + private final ProductService productService; + + public ProductManageController(ProductService productService) { + this.productService = productService; + } + + @GetMapping("") + public ResponseEntity>> getProducts() { + List productList = productService.getProduct(); + return ResponseEntity.ok(new CommonResponse<>(productList, "상품 조회가 정상적으로 완료되었습니다", true)); + } + + @PostMapping("") + public ResponseEntity> addProduct( + @Valid @RequestBody CreateProductRequestDTO createProductRequestDTO) { + Long productId = productService.saveProduct(createProductRequestDTO); + return ResponseEntity.ok(new CommonResponse<>(productId, "상품이 정상적으로 추가 되었습니다", true)); + } + + @DeleteMapping("/delete/{id}") + public ResponseEntity> deleteProduct(@PathVariable Long id) { + productService.deleteProduct(id); + return ResponseEntity.ok(new CommonResponse<>(null, "상품이 정상적으로 삭제 되었습니다", true)); + } + + +} diff --git a/src/main/java/gift/product/presentation/WishListController.java b/src/main/java/gift/product/presentation/WishListController.java new file mode 100644 index 000000000..97645f267 --- /dev/null +++ b/src/main/java/gift/product/presentation/WishListController.java @@ -0,0 +1,54 @@ +package gift.product.presentation; + + +import gift.product.application.WishListService; +import gift.product.domain.WishList; +import gift.product.exception.ProductException; +import gift.util.CommonResponse; +import gift.util.ErrorCode; +import gift.util.JwtAuthenticated; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/wishlist") +public class WishListController { + + private final WishListService wishListService; + + public WishListController(WishListService wishListService) { + this.wishListService = wishListService; + } + + @JwtAuthenticated + @GetMapping("/{userId}") + public ResponseEntity getWishList(@PathVariable Long userId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "id") String sortBy, + @RequestParam(defaultValue = "asc") String direction) { + Page products = wishListService.getProductsInWishList(userId, page, size, sortBy, direction); + return ResponseEntity.ok(new CommonResponse<>(products, "위시리스트 조회 성공", true)); + } + + @JwtAuthenticated + @PostMapping("/{userId}/add/{productId}") + public ResponseEntity addProductToWishList(@PathVariable Long userId, @PathVariable Long productId) { + wishListService.addProductToWishList(userId, productId); + return ResponseEntity.ok(new CommonResponse<>(null, "위시리스트에 제품이 추가되었습니다", true)); + } + + @JwtAuthenticated + @DeleteMapping("/{userId}/delete/{productId}") + public ResponseEntity deleteProductFromWishList(@PathVariable Long userId, @PathVariable Long productId) { + wishListService.deleteProductFromWishList(userId, productId); + return ResponseEntity.ok(new CommonResponse<>(null, "위시리스트에서 제품이 삭제되었습니다", true)); + } +} diff --git a/src/main/java/gift/user/application/UserService.java b/src/main/java/gift/user/application/UserService.java new file mode 100644 index 000000000..7c32486d1 --- /dev/null +++ b/src/main/java/gift/user/application/UserService.java @@ -0,0 +1,61 @@ +package gift.user.application; + + +import gift.user.domain.User; +import gift.user.domain.UserRegisterRequest; +import gift.user.exception.UserException; +import gift.user.infra.UserRepository; +import gift.util.ErrorCode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class UserService { + + private final UserRepository userRepository; + + private final PasswordEncoder passwordEncoder; + + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + + @Transactional + public User registerUser(UserRegisterRequest request) { + String encodedPassword = passwordEncoder.encode(request.getPassword()); + // 중복회원 검증 + validateDuplicateUser(request.getEmail()); + + User user = new User(); + user.setEmail(request.getEmail()); + user.setPassword(encodedPassword); + return userRepository.save(user); + } + + private void validateDuplicateUser(String email) { + userRepository.findByEmail(email).ifPresent( + user -> { + throw new UserException(ErrorCode.DUPLICATE_USER); + } + ); + } + + public User authenticateUser(String email, String password) { + User user = userRepository.findByEmail(email).orElseThrow( + () -> new UserException(ErrorCode.USER_NOT_FOUND) + ); + System.out.println("user.getPassword() = " + user.getPassword()); + if (passwordEncoder.matches(password, user.getPassword())) { + return user; + } + return null; + } + + +} + + diff --git a/src/main/java/gift/user/domain/User.java b/src/main/java/gift/user/domain/User.java new file mode 100644 index 000000000..987618ef7 --- /dev/null +++ b/src/main/java/gift/user/domain/User.java @@ -0,0 +1,75 @@ +package gift.user.domain; + +import gift.product.domain.WishList; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +@Entity(name = "users") +public class User { + + @Id + @Column(name = "user_id") + @GeneratedValue(strategy = jakarta.persistence.GenerationType.IDENTITY) + private Long id; + + private String name; + private String email; + + private String password; + + @OneToMany(mappedBy = "user") + private List wishLists = new ArrayList<>(); + + public User() { + } + + public User(String name, String email) { + this.name = name; + this.email = email; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public void setName(String name) { + this.name = name; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public List getWishLists() { + return wishLists; + } + + public void setWishLists(List wishLists) { + this.wishLists = wishLists; + } +} diff --git a/src/main/java/gift/user/domain/UserRegisterRequest.java b/src/main/java/gift/user/domain/UserRegisterRequest.java new file mode 100644 index 000000000..279e2ff7f --- /dev/null +++ b/src/main/java/gift/user/domain/UserRegisterRequest.java @@ -0,0 +1,23 @@ +package gift.user.domain; + +public class UserRegisterRequest { + + private String email; + private String password; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/gift/user/exception/UserException.java b/src/main/java/gift/user/exception/UserException.java new file mode 100644 index 000000000..53bc375c5 --- /dev/null +++ b/src/main/java/gift/user/exception/UserException.java @@ -0,0 +1,11 @@ +package gift.user.exception; + +import gift.util.CustomException; +import gift.util.ErrorCode; + +public class UserException extends CustomException { + + public UserException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/gift/user/infra/UserRepository.java b/src/main/java/gift/user/infra/UserRepository.java new file mode 100644 index 000000000..3dc578be8 --- /dev/null +++ b/src/main/java/gift/user/infra/UserRepository.java @@ -0,0 +1,20 @@ +package gift.user.infra; + +import gift.user.domain.User; +import javax.swing.text.html.Option; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Optional; + + +@Repository +public interface UserRepository extends JpaRepository { + + Optional findByEmail(String email); +} diff --git a/src/main/java/gift/user/presentation/UserController.java b/src/main/java/gift/user/presentation/UserController.java new file mode 100644 index 000000000..a65d1210d --- /dev/null +++ b/src/main/java/gift/user/presentation/UserController.java @@ -0,0 +1,56 @@ +package gift.user.presentation; + +import gift.user.application.UserService; +import gift.user.domain.User; +import gift.user.domain.UserRegisterRequest; +import gift.user.exception.UserException; +import gift.util.CommonResponse; +import gift.util.ErrorCode; +import gift.util.JwtUtil; +import org.springframework.beans.factory.annotation.Autowired; +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("/api") +public class UserController { + + private final UserService userService; + + private final JwtUtil jwtUtil; + + public UserController(UserService userService, JwtUtil jwtUtil) { + this.userService = userService; + this.jwtUtil = jwtUtil; + } + + @PostMapping("/register") + public ResponseEntity registerUser(@RequestBody UserRegisterRequest user) { + User registeredUser = userService.registerUser(user); + return ResponseEntity.ok(new CommonResponse<>(null, "유저 등록이 정상적으로 완료되었습니다", true)); + } + + @PostMapping("/login") + public ResponseEntity createAuthenticationToken(@RequestBody User user) { + String token = jwtUtil.generateToken(user.getEmail()); + return ResponseEntity.ok( + new CommonResponse<>(new AuthenticationResponse(token), "로그인이 정상적으로 완료되었습니다", true)); + + } + + public static class AuthenticationResponse { + + private final String jwt; + + public AuthenticationResponse(String jwt) { + this.jwt = jwt; + } + + public String getJwt() { + return jwt; + } + } +} diff --git a/src/main/java/gift/util/CommonResponse.java b/src/main/java/gift/util/CommonResponse.java new file mode 100644 index 000000000..e9b85cd7a --- /dev/null +++ b/src/main/java/gift/util/CommonResponse.java @@ -0,0 +1,38 @@ +package gift.util; + +public class CommonResponse { + + private T data; + private String message; + private Boolean result; + + public CommonResponse(T data, String message, Boolean result) { + this.data = data; + this.message = message; + this.result = result; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Boolean getStatus() { + return result; + } + + public void setStatus(Boolean result) { + this.result = result; + } +} diff --git a/src/main/java/gift/util/CustomException.java b/src/main/java/gift/util/CustomException.java new file mode 100644 index 000000000..2e6a9f74a --- /dev/null +++ b/src/main/java/gift/util/CustomException.java @@ -0,0 +1,22 @@ +package gift.util; + + +import org.springframework.http.HttpStatus; + +public class CustomException extends RuntimeException { + + private final ErrorCode errorCode; + + public CustomException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; + } + + public HttpStatus getStatus() { + return errorCode.getStatus(); + } +} diff --git a/src/main/java/gift/util/ErrorCode.java b/src/main/java/gift/util/ErrorCode.java new file mode 100644 index 000000000..23b6de16f --- /dev/null +++ b/src/main/java/gift/util/ErrorCode.java @@ -0,0 +1,41 @@ +package gift.util; + +import org.springframework.http.HttpStatus; + +public enum ErrorCode { + // Product ErrorMessage + INVALID_NAME("상품의 이름은 필수입니다.", HttpStatus.BAD_REQUEST), + NAME_HAS_RESTRICTED_WORD("상품의 이름에 금지어가 포함되어 있습니다.", HttpStatus.BAD_REQUEST), + NAME_TOO_LONG("상품의 이름은 최대 15자입니다.", HttpStatus.BAD_REQUEST), + INVALID_PRICE("상품의 가격은 필수입니다.", HttpStatus.BAD_REQUEST), + NEGATIVE_PRICE("상품의 가격은 0보다 커야합니다.", HttpStatus.BAD_REQUEST), + PRODUCT_NOT_FOUND("해당 상품을 찾을 수 없습니다.", HttpStatus.BAD_REQUEST), + + // User ErrorMessage + DUPLICATE_USER("이미 존재하는 회원입니다.", HttpStatus.BAD_REQUEST), + + // User ErrorMessage + LOGIN_FAILED("로그인에 실패하였습니다.", HttpStatus.UNAUTHORIZED), + + USER_NOT_FOUND("유저를 찾을 수 없습니다.", HttpStatus.UNAUTHORIZED), + + + // Wishlist ErrorMessage + WISHLIST_NOT_FOUND("해당 위시리스트를 찾을 수 없습니다.", HttpStatus.BAD_REQUEST); + + private final String message; + private final HttpStatus status; + + ErrorCode(String message, HttpStatus status) { + this.message = message; + this.status = status; + } + + public String getMessage() { + return message; + } + + public HttpStatus getStatus() { + return status; + } +} diff --git a/src/main/java/gift/util/ErrorResponse.java b/src/main/java/gift/util/ErrorResponse.java new file mode 100644 index 000000000..b3adc7bca --- /dev/null +++ b/src/main/java/gift/util/ErrorResponse.java @@ -0,0 +1,28 @@ +package gift.util; + +public class ErrorResponse { + + private String message; + private String details; + + public ErrorResponse(String message, String details) { + this.message = message; + this.details = details; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } +} diff --git a/src/main/java/gift/util/GlobalExceptionHandler.java b/src/main/java/gift/util/GlobalExceptionHandler.java new file mode 100644 index 000000000..2e72e2c34 --- /dev/null +++ b/src/main/java/gift/util/GlobalExceptionHandler.java @@ -0,0 +1,40 @@ +package gift.util; + +import gift.product.exception.ProductException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ProductException.class) + public ResponseEntity> handleProductException(ProductException ex) { + ErrorResponse errorResponse = new ErrorResponse(ex.getErrorCode().getMessage(), ex.getMessage()); + CommonResponse commonResponse = new CommonResponse<>( + errorResponse.getMessage(), + ex.getMessage(), + false + ); + return ResponseEntity.badRequest().body(commonResponse); + } + + @ExceptionHandler(CustomException.class) + public ResponseEntity> handleCustomException(CustomException ex) { + ErrorResponse errorResponse = new ErrorResponse(ex.getErrorCode().getMessage(), ex.getMessage()); + CommonResponse commonResponse = new CommonResponse<>( + errorResponse.getMessage(), + ex.getMessage(), + false + ); + return ResponseEntity.status(ex.getErrorCode().getStatus()).body(commonResponse); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGenericException(Exception ex) { + ErrorResponse errorResponse = new ErrorResponse("Internal server error", ex.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new CommonResponse<>(errorResponse.getMessage(), ex.getMessage(), false)); + } +} diff --git a/src/main/java/gift/util/JwtAspect.java b/src/main/java/gift/util/JwtAspect.java new file mode 100644 index 000000000..f0042f421 --- /dev/null +++ b/src/main/java/gift/util/JwtAspect.java @@ -0,0 +1,32 @@ +package gift.util; + +import jakarta.servlet.http.HttpServletRequest; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class JwtAspect { + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private HttpServletRequest request; + + @Before("@annotation(gift.util.JwtAuthenticated)") + public void authenticate() { + String token = request.getHeader("Authorization"); + if (token == null) { + throw new RuntimeException("JWT Token is missing"); + } + + String email = jwtUtil.extractUsername(token); + + if (!jwtUtil.validateToken(token, email)) { + throw new RuntimeException("JWT Token is not valid"); + } + } +} diff --git a/src/main/java/gift/util/JwtAuthenticated.java b/src/main/java/gift/util/JwtAuthenticated.java new file mode 100644 index 000000000..561dce130 --- /dev/null +++ b/src/main/java/gift/util/JwtAuthenticated.java @@ -0,0 +1,13 @@ +package gift.util; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface JwtAuthenticated { + +} diff --git a/src/main/java/gift/util/JwtUtil.java b/src/main/java/gift/util/JwtUtil.java new file mode 100644 index 000000000..a8c6b2af0 --- /dev/null +++ b/src/main/java/gift/util/JwtUtil.java @@ -0,0 +1,68 @@ +package gift.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtUtil { + + @Value("${jwt.secret}") + private String JWT_SECRET; + private static final long JWT_EXPIRATION = 1000 * 60 * 60 * 10; + + public String generateToken(String email) { + Map claims = new HashMap<>(); + return createToken(claims, email); + } + + private String createToken(Map claims, String subject) { + LocalDateTime now = LocalDateTime.now(); + Date issuedAt = Date.from(now.atZone(ZoneId.systemDefault()).toInstant()); + Date expiryDate = Date.from( + now.plus(Duration.ofMillis(JWT_EXPIRATION)).atZone(ZoneId.systemDefault()).toInstant()); + + return Jwts.builder() + .setClaims(claims) + .setSubject(subject) + .setIssuedAt(issuedAt) + .setExpiration(expiryDate) + .signWith(SignatureAlgorithm.HS256, JWT_SECRET) + .compact(); + } + + public Boolean validateToken(String token, String email) { + final String username = extractUsername(token); + return (username.equals(email) && !isTokenExpired(token)); + } + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(String token) { + return Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token).getBody(); + } + + private Boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + private Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } +} diff --git a/src/main/resources/application-secret.yml b/src/main/resources/application-secret.yml new file mode 100644 index 000000000..cfcb233df --- /dev/null +++ b/src/main/resources/application-secret.yml @@ -0,0 +1,2 @@ +jwt: + secret: sk1234567890123456789012345678901234567890123456789012345678901234 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3d16b65f4..3f395fbe9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,2 @@ spring.application.name=spring-gift +spring.config.import=optional:classpath:application-secret.yml diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 000000000..5f999cd23 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,32 @@ +spring: + application: + name: spring-gift + thymeleaf: + prefix: classpath:/templates/ + suffix: .html + cache: false + h2: + console: + enabled: true + path: /h2-console + datasource: + url: jdbc:h2:mem:gift + driver-class-name: org.h2.Driver + username: sa + password: + sql: + init: + mode: always + schema-locations: classpath:/db/schema.sql + + jpa: + properties: + hibernate: + format_sql: true + show_sql: true + +logging: + level: + org: + springframework: + web: DEBUG diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql new file mode 100644 index 000000000..54679b323 --- /dev/null +++ b/src/main/resources/db/data.sql @@ -0,0 +1,6 @@ +INSERT INTO Product (id, name, price, imageUrl) +VALUES (1, 'Product 1', 10.0, 'http://example.com/image1.jpg'); +INSERT INTO Product (id, name, price, imageUrl) +VALUES (2, 'Product 2', 20.0, 'http://example.com/image2.jpg'); +INSERT INTO Product (id, name, price, imageUrl) +VALUES (3, 'Product 3', 30.0, 'http://example.com/image3.jpg'); diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql new file mode 100644 index 000000000..2167ecb62 --- /dev/null +++ b/src/main/resources/db/schema.sql @@ -0,0 +1,30 @@ +CREATE TABLE Product +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + price DOUBLE NOT NULL, + imageUrl VARCHAR(255) NOT NULL +); + +CREATE TABLE Users +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL +); + +CREATE TABLE WishList +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NOT NULL, + FOREIGN KEY (user_id) REFERENCES Users (id) +); + +CREATE TABLE WishList_Product +( + wishlist_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + PRIMARY KEY (wishlist_id, product_id), + FOREIGN KEY (wishlist_id) REFERENCES WishList (id), + FOREIGN KEY (product_id) REFERENCES Product (id) +); diff --git a/src/main/resources/templates/wishlist.html b/src/main/resources/templates/wishlist.html new file mode 100644 index 000000000..e6fe9ab19 --- /dev/null +++ b/src/main/resources/templates/wishlist.html @@ -0,0 +1,139 @@ + + + + + Manage WishList + + + + +
+

Manage WishList

+
+ + +
+ + + + + + + + + + + + + + + + + + +
NamePriceImageUrl
NamePriceImageUrl + + + + +
+
+
+ Showing 5 out of 25 entries +
+ +
+
+ + + + + + + + + + From 7222b870370ff10ddf4ad585858862d698e0cfa0 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 16:39:28 +0900 Subject: [PATCH 02/35] =?UTF-8?q?feat:=20schema.sql=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20-=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20-=20product=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/db/schema.sql | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 2167ecb62..4d989cdb1 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -1,9 +1,21 @@ -CREATE TABLE Product +create table category ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - price DOUBLE NOT NULL, - imageUrl VARCHAR(255) NOT NULL + color varchar(7) not null, + id bigint not null auto_increment, + description varchar(255), + image_url varchar(255) not null, + name varchar(255) not null, + primary key (id) +); + +create table product +( + price integer not null, + category_id bigint not null, + id bigint not null auto_increment, + name varchar(15) not null, + image_url varchar(255) not null, + primary key (id) ); CREATE TABLE Users From 64b32623f75540ef376e97c83358545cf93b386d Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 16:47:39 +0900 Subject: [PATCH 03/35] =?UTF-8?q?feat:=20Category=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/product/domain/Category.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/main/java/gift/product/domain/Category.java diff --git a/src/main/java/gift/product/domain/Category.java b/src/main/java/gift/product/domain/Category.java new file mode 100644 index 000000000..4d5645ffa --- /dev/null +++ b/src/main/java/gift/product/domain/Category.java @@ -0,0 +1,38 @@ +package gift.product.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.util.Objects; + +@Entity(name = "category") +public class Category { + + @Id + @Column(name = "category_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + public Category() { + } + + public Category(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} From e0252684cb54dbb1ae5801f6537047177de54949 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 16:47:39 +0900 Subject: [PATCH 04/35] =?UTF-8?q?feat:=20Category=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: Category 도메인 추가 --- .../java/gift/product/domain/Category.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/gift/product/domain/Category.java diff --git a/src/main/java/gift/product/domain/Category.java b/src/main/java/gift/product/domain/Category.java new file mode 100644 index 000000000..2e1d8416f --- /dev/null +++ b/src/main/java/gift/product/domain/Category.java @@ -0,0 +1,37 @@ +package gift.product.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity(name = "category") +public class Category { + + @Id + @Column(name = "category_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + public Category() { + } + + public Category(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} From 10559a2ac4e2bce848f7ee4df6ae328689f88072 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:14:08 +0900 Subject: [PATCH 05/35] =?UTF-8?q?feat:=20=EC=9C=84=EC=8B=9C=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=EC=9C=84=EC=8B=9C=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/WishListService.java | 18 +++++++++++++++++- .../presentation/WishListController.java | 11 +++++++++-- src/main/java/gift/util/ErrorCode.java | 3 ++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index 9322585b2..3e81cb9be 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -6,6 +6,8 @@ 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 org.springframework.data.domain.Page; @@ -19,10 +21,13 @@ public class WishListService { private final WishListRepository wishListRepository; private final ProductRepository productRepository; + private final UserService userService; - public WishListService(WishListRepository wishListRepository, ProductRepository productRepository) { + public WishListService(WishListRepository wishListRepository, ProductRepository productRepository, + UserService userService) { this.wishListRepository = wishListRepository; this.productRepository = productRepository; + this.userService = userService; } public WishList getWishListByUserId(Long userId) { @@ -66,4 +71,15 @@ public void deleteProductFromWishList(Long userId, Long productId) { wishList.removeWishListProduct(productId); } } + + 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(); + wishList.setUser(user); + wishListRepository.save(wishList); + } } diff --git a/src/main/java/gift/product/presentation/WishListController.java b/src/main/java/gift/product/presentation/WishListController.java index 97645f267..d81688b92 100644 --- a/src/main/java/gift/product/presentation/WishListController.java +++ b/src/main/java/gift/product/presentation/WishListController.java @@ -38,17 +38,24 @@ public ResponseEntity getWishList(@PathVariable Long userId, return ResponseEntity.ok(new CommonResponse<>(products, "위시리스트 조회 성공", true)); } + @JwtAuthenticated + @PostMapping("/{userId}/create") + public ResponseEntity createWishList(@PathVariable Long userId) { + wishListService.createWishList(userId); + return ResponseEntity.ok(new CommonResponse<>(null, "위시리스트 생성 성공", true)); + } + @JwtAuthenticated @PostMapping("/{userId}/add/{productId}") public ResponseEntity addProductToWishList(@PathVariable Long userId, @PathVariable Long productId) { wishListService.addProductToWishList(userId, productId); - return ResponseEntity.ok(new CommonResponse<>(null, "위시리스트에 제품이 추가되었습니다", true)); + return ResponseEntity.ok(new CommonResponse<>(null, "제품 추가 성공", true)); } @JwtAuthenticated @DeleteMapping("/{userId}/delete/{productId}") public ResponseEntity deleteProductFromWishList(@PathVariable Long userId, @PathVariable Long productId) { wishListService.deleteProductFromWishList(userId, productId); - return ResponseEntity.ok(new CommonResponse<>(null, "위시리스트에서 제품이 삭제되었습니다", true)); + return ResponseEntity.ok(new CommonResponse<>(null, "제품 삭제 성공", true)); } } diff --git a/src/main/java/gift/util/ErrorCode.java b/src/main/java/gift/util/ErrorCode.java index 23b6de16f..d6df63ec4 100644 --- a/src/main/java/gift/util/ErrorCode.java +++ b/src/main/java/gift/util/ErrorCode.java @@ -21,7 +21,8 @@ public enum ErrorCode { // Wishlist ErrorMessage - WISHLIST_NOT_FOUND("해당 위시리스트를 찾을 수 없습니다.", HttpStatus.BAD_REQUEST); + WISHLIST_NOT_FOUND("해당 위시리스트를 찾을 수 없습니다.", HttpStatus.BAD_REQUEST), + WISHLIST_ALREADY_EXISTS("이미 위시리스트가 존재합니다.", HttpStatus.BAD_REQUEST); private final String message; private final HttpStatus status; From 1d878ac3b62a030f2da1bd0dde26324ff678c321 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:14:21 +0900 Subject: [PATCH 06/35] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=95=9E=EC=97=90=20Bearer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/util/JwtAspect.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/gift/util/JwtAspect.java b/src/main/java/gift/util/JwtAspect.java index f0042f421..c9696d8d2 100644 --- a/src/main/java/gift/util/JwtAspect.java +++ b/src/main/java/gift/util/JwtAspect.java @@ -18,11 +18,13 @@ public class JwtAspect { @Before("@annotation(gift.util.JwtAuthenticated)") public void authenticate() { - String token = request.getHeader("Authorization"); - if (token == null) { - throw new RuntimeException("JWT Token is missing"); + String authorizationHeader = request.getHeader("Authorization"); + if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) { + throw new RuntimeException("JWT Token is missing or does not start with Bearer"); } + String token = authorizationHeader.substring(7); // "Bearer " 이후의 토큰 부분만 추출 + String email = jwtUtil.extractUsername(token); if (!jwtUtil.validateToken(token, email)) { From 3d8209130abdcaa4406b4717ad0451239760fa51 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:14:30 +0900 Subject: [PATCH 07/35] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/user/application/UserService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/gift/user/application/UserService.java b/src/main/java/gift/user/application/UserService.java index 7c32486d1..d4094f1d5 100644 --- a/src/main/java/gift/user/application/UserService.java +++ b/src/main/java/gift/user/application/UserService.java @@ -19,11 +19,18 @@ public class UserService { private final PasswordEncoder passwordEncoder; + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; } + public User getUser(Long id) { + return userRepository.findById(id).orElseThrow( + () -> new UserException(ErrorCode.USER_NOT_FOUND) + ); + } + @Transactional public User registerUser(UserRegisterRequest request) { String encodedPassword = passwordEncoder.encode(request.getPassword()); From 174320ed9ad910e112f1a18abc5b62f084a936b2 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:19:45 +0900 Subject: [PATCH 08/35] =?UTF-8?q?refactor:=20user=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=9B=84=20=EC=9C=84=EC=8B=9C=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20=EC=83=81=ED=92=88=EC=9D=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/WishListService.java | 18 ++++--- .../java/gift/product/domain/Product.java | 35 +++++-------- .../java/gift/product/domain/WishList.java | 33 ++++-------- .../gift/product/domain/WishListProduct.java | 38 ++++++-------- .../gift/product/infra/ProductRowMapper.java | 21 -------- src/main/java/gift/user/domain/User.java | 17 +++--- src/main/resources/db/schema.sql | 52 ++++++++----------- 7 files changed, 75 insertions(+), 139 deletions(-) delete mode 100644 src/main/java/gift/product/infra/ProductRowMapper.java diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index 3e81cb9be..44d999b91 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -10,6 +10,8 @@ import gift.user.domain.User; import gift.util.ErrorCode; import jakarta.transaction.Transactional; +import java.time.LocalDateTime; +import java.util.ArrayList; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -34,7 +36,6 @@ public WishList getWishListByUserId(Long userId) { return wishListRepository.findByUserId(userId); } - public Page 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(); @@ -46,19 +47,19 @@ public Page getProductsInWishList(Long userId, int page, int size, Str return wishListRepository.findByUserId(userId, pageable); } - @Transactional public void addProductToWishList(Long userId, Long productId) { WishList wishList = wishListRepository.findByUserId(userId); - Product product = productRepository.findById(productId).orElseThrow(); + Product product = productRepository.findById(productId) + .orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_NOT_FOUND)); if (wishList == null) { - wishList = new WishList(); + User user = userService.getUser(userId); + wishList = new WishList(user, LocalDateTime.now()); wishList = wishListRepository.save(wishList); } - product.setWishList(wishList); - WishListProduct wishListProduct = new WishListProduct(product, wishList); + WishListProduct wishListProduct = new WishListProduct(wishList, product); wishList.addWishListProduct(wishListProduct); wishListRepository.save(wishList); @@ -69,6 +70,8 @@ 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); } } @@ -78,8 +81,7 @@ public void createWishList(Long userId) { } User user = userService.getUser(userId); - WishList wishList = new WishList(); - wishList.setUser(user); + WishList wishList = new WishList(user, LocalDateTime.now()); wishListRepository.save(wishList); } } diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java index f8caf4e12..6c7aa2a3a 100644 --- a/src/main/java/gift/product/domain/Product.java +++ b/src/main/java/gift/product/domain/Product.java @@ -1,12 +1,8 @@ package gift.product.domain; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; @Entity(name = "product") public class Product { @@ -15,30 +11,23 @@ public class Product { @Column(name = "product_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String name; private Double price; private String imageUrl; - @ManyToOne(fetch = FetchType.LAZY) - private WishList wishList; + @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) + private List wishListProducts = new ArrayList<>(); - public Product(String name, Double price, String imageUrl) { - this.name = name; - this.price = price; - this.imageUrl = imageUrl; + public Product() { } - public Product(Long id, String name, Double price, String imageUrl) { - this.id = id; + public Product(String name, Double price, String imageUrl) { this.name = name; this.price = price; this.imageUrl = imageUrl; } - public Product() { - this(null, null, null, null); - } - public Long getId() { return id; } @@ -71,9 +60,11 @@ public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } - public void setWishList(WishList wishList) { - this.wishList = wishList; + public List getWishListProducts() { + return wishListProducts; } + public void setWishListProducts(List wishListProducts) { + this.wishListProducts = wishListProducts; + } } - diff --git a/src/main/java/gift/product/domain/WishList.java b/src/main/java/gift/product/domain/WishList.java index 3b9fd0301..74d1c7969 100644 --- a/src/main/java/gift/product/domain/WishList.java +++ b/src/main/java/gift/product/domain/WishList.java @@ -1,19 +1,10 @@ package gift.product.domain; import gift.user.domain.User; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; +import jakarta.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.List; @Entity(name = "wishlist") public class WishList { @@ -22,24 +13,26 @@ public class WishList { @Column(name = "wishlist_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; @OneToMany(mappedBy = "wishList", cascade = CascadeType.ALL, orphanRemoval = true) - private ArrayList wishListProducts = new ArrayList<>(); + private List wishListProducts = new ArrayList<>(); private LocalDateTime createdAt; public WishList() { } - public WishList(User user, ArrayList wishListProducts, LocalDateTime createdAt) { + + public WishList(User user, LocalDateTime now) { this.user = user; - this.wishListProducts = wishListProducts; - this.createdAt = createdAt; + this.createdAt = now; } + public Long getId() { return id; } @@ -57,7 +50,7 @@ public void setUser(User user) { user.getWishLists().add(this); } - public ArrayList getWishListProducts() { + public List getWishListProducts() { return wishListProducts; } @@ -67,13 +60,7 @@ public void addWishListProduct(WishListProduct wishListProduct) { } public void removeWishListProduct(Long wishListProductId) { - for (WishListProduct wishListProduct : wishListProducts) { - if (wishListProduct.getId().equals(wishListProductId)) { - wishListProducts.remove(wishListProduct); - wishListProduct.setWishList(null); - break; - } - } + wishListProducts.removeIf(wishListProduct -> wishListProduct.getId().equals(wishListProductId)); } public LocalDateTime getCreatedAt() { diff --git a/src/main/java/gift/product/domain/WishListProduct.java b/src/main/java/gift/product/domain/WishListProduct.java index bffd8e86a..00198f06d 100644 --- a/src/main/java/gift/product/domain/WishListProduct.java +++ b/src/main/java/gift/product/domain/WishListProduct.java @@ -1,13 +1,6 @@ package gift.product.domain; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; @Entity(name = "wishlist_product") public class WishListProduct { @@ -17,21 +10,20 @@ public class WishListProduct { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "product_id") - private Product product; - - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "wishlist_id") private WishList wishList; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id") + private Product product; + public WishListProduct() { } - public WishListProduct(Product product, WishList wishList) { - this.product = product; + public WishListProduct(WishList wishList, Product product) { this.wishList = wishList; + this.product = product; } public Long getId() { @@ -42,14 +34,6 @@ public void setId(Long id) { this.id = id; } - public Product getProduct() { - return product; - } - - public void setProduct(Product product) { - this.product = product; - } - public WishList getWishList() { return wishList; } @@ -57,4 +41,12 @@ public WishList getWishList() { public void setWishList(WishList wishList) { this.wishList = wishList; } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } } diff --git a/src/main/java/gift/product/infra/ProductRowMapper.java b/src/main/java/gift/product/infra/ProductRowMapper.java deleted file mode 100644 index b750079cc..000000000 --- a/src/main/java/gift/product/infra/ProductRowMapper.java +++ /dev/null @@ -1,21 +0,0 @@ -package gift.product.infra; - -import gift.product.domain.Product; -import org.springframework.jdbc.core.RowMapper; - -import java.sql.ResultSet; -import java.sql.SQLException; - -public class ProductRowMapper implements RowMapper { - - @Override - public Product mapRow(ResultSet rs, int rowNum) throws SQLException { - return new Product( - rs.getLong("id"), - rs.getString("name"), - rs.getDouble("price"), - rs.getString("imageUrl") - - ); - } -} diff --git a/src/main/java/gift/user/domain/User.java b/src/main/java/gift/user/domain/User.java index 987618ef7..b43e83754 100644 --- a/src/main/java/gift/user/domain/User.java +++ b/src/main/java/gift/user/domain/User.java @@ -1,11 +1,7 @@ package gift.user.domain; import gift.product.domain.WishList; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; +import jakarta.persistence.*; import java.util.ArrayList; import java.util.List; @@ -19,10 +15,9 @@ public class User { private String name; private String email; - private String password; - @OneToMany(mappedBy = "user") + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List wishLists = new ArrayList<>(); public User() { @@ -45,14 +40,14 @@ public String getName() { return name; } - public String getEmail() { - return email; - } - public void setName(String name) { this.name = name; } + public String getEmail() { + return email; + } + public void setEmail(String email) { this.email = email; } diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 4d989cdb1..c8e54e06e 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -1,42 +1,32 @@ -create table category +CREATE TABLE users ( - color varchar(7) not null, - id bigint not null auto_increment, - description varchar(255), - image_url varchar(255) not null, - name varchar(255) not null, - primary key (id) -); - -create table product -( - price integer not null, - category_id bigint not null, - id bigint not null auto_increment, - name varchar(15) not null, - image_url varchar(255) not null, - primary key (id) + user_id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL ); -CREATE TABLE Users +CREATE TABLE wishlist ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - email VARCHAR(255) NOT NULL, - password VARCHAR(255) NOT NULL + wishlist_id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL, + FOREIGN KEY (user_id) REFERENCES users (user_id) ); -CREATE TABLE WishList +CREATE TABLE product ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - user_id BIGINT NOT NULL, - FOREIGN KEY (user_id) REFERENCES Users (id) + product_id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + price DOUBLE NOT NULL, + image_url VARCHAR(255) NOT NULL ); -CREATE TABLE WishList_Product +CREATE TABLE wishlist_product ( - wishlist_id BIGINT NOT NULL, - product_id BIGINT NOT NULL, - PRIMARY KEY (wishlist_id, product_id), - FOREIGN KEY (wishlist_id) REFERENCES WishList (id), - FOREIGN KEY (product_id) REFERENCES Product (id) + wishlist_product_id BIGINT AUTO_INCREMENT PRIMARY KEY, + wishlist_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + FOREIGN KEY (wishlist_id) REFERENCES wishlist (wishlist_id), + FOREIGN KEY (product_id) REFERENCES product (product_id) ); From ae8326daa2fb34b1c20934ddc3e25769ea28b063 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:34:04 +0900 Subject: [PATCH 09/35] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=83=9D=EC=84=B1/=EC=A1=B0=ED=9A=8C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80=20-=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=A1=B0=ED=9A=8C=20-=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/CategoryService.java | 41 +++++++++++++++++++ .../product/domain/CreateCategoryRequest.java | 26 ++++++++++++ .../product/infra/CategoryJpaRepository.java | 13 ++++++ .../product/infra/CategoryRepository.java | 36 ++++++++++++++++ .../presentation/CategoryController.java | 39 ++++++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 src/main/java/gift/product/application/CategoryService.java create mode 100644 src/main/java/gift/product/domain/CreateCategoryRequest.java create mode 100644 src/main/java/gift/product/infra/CategoryJpaRepository.java create mode 100644 src/main/java/gift/product/infra/CategoryRepository.java create mode 100644 src/main/java/gift/product/presentation/CategoryController.java diff --git a/src/main/java/gift/product/application/CategoryService.java b/src/main/java/gift/product/application/CategoryService.java new file mode 100644 index 000000000..224d3d179 --- /dev/null +++ b/src/main/java/gift/product/application/CategoryService.java @@ -0,0 +1,41 @@ +package gift.product.application; + +import gift.product.domain.Category; +import gift.product.domain.CreateCategoryRequest; +import gift.product.infra.CategoryRepository; +import org.springframework.stereotype.Service; + +@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 Category addCategory(CreateCategoryRequest request) { + if (categoryRepository.findByName(request.getName()) != null) { + throw new IllegalArgumentException("이미 존재하는 카테고리입니다."); + } + Category category = new Category(request.getName()); + + return categoryRepository.save(category); + } + + public void deleteById(Long id) { + categoryRepository.deleteById(id); + } + + public Category findByName(String name) { + return categoryRepository.findByName(name); + } + + public Object getCategory() { + return categoryRepository.findAll(); + } +} diff --git a/src/main/java/gift/product/domain/CreateCategoryRequest.java b/src/main/java/gift/product/domain/CreateCategoryRequest.java new file mode 100644 index 000000000..30861a8e0 --- /dev/null +++ b/src/main/java/gift/product/domain/CreateCategoryRequest.java @@ -0,0 +1,26 @@ +package gift.product.domain; + +public class CreateCategoryRequest { + + private String name; + private String description; + private String imageUrl; + + public CreateCategoryRequest(String name, String description, String imageUrl) { + this.name = name; + this.description = description; + this.imageUrl = imageUrl; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getImageUrl() { + return imageUrl; + } +} diff --git a/src/main/java/gift/product/infra/CategoryJpaRepository.java b/src/main/java/gift/product/infra/CategoryJpaRepository.java new file mode 100644 index 000000000..edb0f954d --- /dev/null +++ b/src/main/java/gift/product/infra/CategoryJpaRepository.java @@ -0,0 +1,13 @@ +package gift.product.infra; + +import gift.product.domain.Category; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CategoryJpaRepository extends JpaRepository { + + Optional findByName(String name); + +} diff --git a/src/main/java/gift/product/infra/CategoryRepository.java b/src/main/java/gift/product/infra/CategoryRepository.java new file mode 100644 index 000000000..49a36c3a1 --- /dev/null +++ b/src/main/java/gift/product/infra/CategoryRepository.java @@ -0,0 +1,36 @@ +package gift.product.infra; + +import gift.product.domain.Category; +import org.springframework.stereotype.Repository; + +@Repository +public class CategoryRepository { + + private final CategoryJpaRepository categoryJpaRepository; + + public CategoryRepository(CategoryJpaRepository categoryJpaRepository) { + this.categoryJpaRepository = categoryJpaRepository; + } + + public Category findById(Long id) { + return categoryJpaRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("해당 ID의 카테고리가 존재하지 않습니다.")); + } + + public Category save(Category category) { + return categoryJpaRepository.save(category); + } + + public void deleteById(Long id) { + categoryJpaRepository.deleteById(id); + } + + public Category findByName(String name) { + return categoryJpaRepository.findByName(name) + .orElseThrow(() -> new IllegalArgumentException("해당 이름의 카테고리가 존재하지 않습니다.")); + } + + public Object findAll() { + return categoryJpaRepository.findAll(); + } +} diff --git a/src/main/java/gift/product/presentation/CategoryController.java b/src/main/java/gift/product/presentation/CategoryController.java new file mode 100644 index 000000000..a3667ac83 --- /dev/null +++ b/src/main/java/gift/product/presentation/CategoryController.java @@ -0,0 +1,39 @@ +package gift.product.presentation; + +import gift.product.application.CategoryService; +import gift.product.domain.CreateCategoryRequest; +import gift.util.CommonResponse; +import org.springframework.http.ResponseEntity; +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.RestController; + +@RestController("/api/category") +public class CategoryController { + + private final CategoryService categoryService; + + public CategoryController(CategoryService categoryService) { + this.categoryService = categoryService; + } + + @GetMapping + public ResponseEntity getCategory() { + return ResponseEntity.ok(new CommonResponse<>( + categoryService.getCategory(), + "카테고리 조회 성공", + true + )); + } + + @PostMapping("/create") // TODO: 관리자만 접근 가능하도록 수정 + public ResponseEntity addCategory(@RequestBody CreateCategoryRequest request) { + categoryService.addCategory(request); + return ResponseEntity.ok(new CommonResponse<>( + null, + "카테고리 추가 성공", + true + )); + } +} From 68800a1997e37439bff900e69a284ee006a39c6a Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:39:06 +0900 Subject: [PATCH 10/35] =?UTF-8?q?refactor:=20code=20style=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC=20-=20intellij=20=EC=BD=94=EB=93=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=A0=81=EC=9A=A9=EC=9D=B4=20=EC=95=88?= =?UTF-8?q?=EB=90=98=EA=B3=A0=20=EC=9E=88=EC=97=88=EB=84=A4=EC=9A=94=20-?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4=20=ED=8C=8C=EC=9D=BC=20=EC=9D=BC=EA=B4=84?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9=EC=8B=9C=EC=BC=9C=EC=A4=AC=EC=8A=B5?= =?UTF-8?q?=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/ProductService.java | 6 ++++-- .../product/application/WishListService.java | 8 +++++--- .../gift/product/config/SecurityConfig.java | 20 +++++++++---------- .../java/gift/product/domain/Product.java | 1 + .../java/gift/product/domain/WishList.java | 1 + .../product/infra/CategoryJpaRepository.java | 2 ++ .../product/infra/CategoryRepository.java | 4 ++-- .../gift/product/infra/ProductRepository.java | 2 ++ .../presentation/ProductManageController.java | 4 +++- .../presentation/WishListController.java | 8 ++++---- .../gift/user/application/UserService.java | 10 +++++----- src/main/java/gift/user/domain/User.java | 1 + .../java/gift/user/infra/UserRepository.java | 2 ++ .../user/presentation/UserController.java | 2 +- src/main/java/gift/util/JwtUtil.java | 16 ++++++++------- 15 files changed, 52 insertions(+), 35 deletions(-) diff --git a/src/main/java/gift/product/application/ProductService.java b/src/main/java/gift/product/application/ProductService.java index 50434f8a7..185384548 100644 --- a/src/main/java/gift/product/application/ProductService.java +++ b/src/main/java/gift/product/application/ProductService.java @@ -5,8 +5,10 @@ import gift.product.exception.ProductException; import gift.product.infra.ProductRepository; import gift.util.ErrorCode; + import java.util.List; import java.util.Optional; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,7 +28,7 @@ public ProductService(ProductRepository productRepository) { @Transactional public Long saveProduct(CreateProductRequestDTO createProductRequestDTO) { Product product = new Product(createProductRequestDTO.getName(), createProductRequestDTO.getPrice(), - createProductRequestDTO.getImageUrl()); + createProductRequestDTO.getImageUrl()); validateProduct(product); return productRepository.save(product).getId(); } @@ -37,7 +39,7 @@ public void deleteProduct(Long id) { public void updateProduct(Long id, String name, Double price, String imageUrl) { Product product = productRepository.findById(id) - .orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_NOT_FOUND)); + .orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_NOT_FOUND)); product.setName(name); product.setPrice(price); product.setImageUrl(imageUrl); diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index 44d999b91..7b5629273 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -10,8 +10,10 @@ import gift.user.domain.User; import gift.util.ErrorCode; import jakarta.transaction.Transactional; + import java.time.LocalDateTime; import java.util.ArrayList; + import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -26,7 +28,7 @@ public class WishListService { private final UserService userService; public WishListService(WishListRepository wishListRepository, ProductRepository productRepository, - UserService userService) { + UserService userService) { this.wishListRepository = wishListRepository; this.productRepository = productRepository; this.userService = userService; @@ -38,7 +40,7 @@ public WishList getWishListByUserId(Long userId) { public Page 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(); + : Sort.by(sortBy).descending(); Pageable pageable = PageRequest.of(page, size, sort); if (wishListRepository.findByUserId(userId, pageable).isEmpty()) { @@ -51,7 +53,7 @@ public Page getProductsInWishList(Long userId, int page, int size, Str public void addProductToWishList(Long userId, Long productId) { WishList wishList = wishListRepository.findByUserId(userId); Product product = productRepository.findById(productId) - .orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_NOT_FOUND)); + .orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_NOT_FOUND)); if (wishList == null) { User user = userService.getUser(userId); diff --git a/src/main/java/gift/product/config/SecurityConfig.java b/src/main/java/gift/product/config/SecurityConfig.java index 01f23f5ff..92bb3e45b 100644 --- a/src/main/java/gift/product/config/SecurityConfig.java +++ b/src/main/java/gift/product/config/SecurityConfig.java @@ -18,16 +18,16 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(AbstractHttpConfigurer::disable) // CSRF 보호 비활성화 - .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/api/register", "/api/login").permitAll() - .requestMatchers("/api/**").permitAll() - .requestMatchers("/h2-console/**").permitAll() // H2 콘솔 접근 허용 - .anyRequest().authenticated() - ) - .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) // H2 콘솔을 위한 설정 - .formLogin(AbstractHttpConfigurer::disable) // 기본 폼 로그인 비활성화 - .httpBasic(AbstractHttpConfigurer::disable); // HTTP Basic 인증 비활성화 + .csrf(AbstractHttpConfigurer::disable) // CSRF 보호 비활성화 + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/api/register", "/api/login").permitAll() + .requestMatchers("/api/**").permitAll() + .requestMatchers("/h2-console/**").permitAll() // H2 콘솔 접근 허용 + .anyRequest().authenticated() + ) + .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) // H2 콘솔을 위한 설정 + .formLogin(AbstractHttpConfigurer::disable) // 기본 폼 로그인 비활성화 + .httpBasic(AbstractHttpConfigurer::disable); // HTTP Basic 인증 비활성화 return http.build(); } diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java index 6c7aa2a3a..1fc33fb68 100644 --- a/src/main/java/gift/product/domain/Product.java +++ b/src/main/java/gift/product/domain/Product.java @@ -1,6 +1,7 @@ package gift.product.domain; import jakarta.persistence.*; + import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/gift/product/domain/WishList.java b/src/main/java/gift/product/domain/WishList.java index 74d1c7969..74674914f 100644 --- a/src/main/java/gift/product/domain/WishList.java +++ b/src/main/java/gift/product/domain/WishList.java @@ -2,6 +2,7 @@ import gift.user.domain.User; import jakarta.persistence.*; + import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/gift/product/infra/CategoryJpaRepository.java b/src/main/java/gift/product/infra/CategoryJpaRepository.java index edb0f954d..d8070e6b8 100644 --- a/src/main/java/gift/product/infra/CategoryJpaRepository.java +++ b/src/main/java/gift/product/infra/CategoryJpaRepository.java @@ -1,7 +1,9 @@ package gift.product.infra; import gift.product.domain.Category; + import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/gift/product/infra/CategoryRepository.java b/src/main/java/gift/product/infra/CategoryRepository.java index 49a36c3a1..10b79196a 100644 --- a/src/main/java/gift/product/infra/CategoryRepository.java +++ b/src/main/java/gift/product/infra/CategoryRepository.java @@ -14,7 +14,7 @@ public CategoryRepository(CategoryJpaRepository categoryJpaRepository) { public Category findById(Long id) { return categoryJpaRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("해당 ID의 카테고리가 존재하지 않습니다.")); + .orElseThrow(() -> new IllegalArgumentException("해당 ID의 카테고리가 존재하지 않습니다.")); } public Category save(Category category) { @@ -27,7 +27,7 @@ public void deleteById(Long id) { public Category findByName(String name) { return categoryJpaRepository.findByName(name) - .orElseThrow(() -> new IllegalArgumentException("해당 이름의 카테고리가 존재하지 않습니다.")); + .orElseThrow(() -> new IllegalArgumentException("해당 이름의 카테고리가 존재하지 않습니다.")); } public Object findAll() { diff --git a/src/main/java/gift/product/infra/ProductRepository.java b/src/main/java/gift/product/infra/ProductRepository.java index 4ddfe9c24..44a42e741 100644 --- a/src/main/java/gift/product/infra/ProductRepository.java +++ b/src/main/java/gift/product/infra/ProductRepository.java @@ -1,9 +1,11 @@ package gift.product.infra; import gift.product.domain.Product; + import java.sql.Statement; import java.sql.PreparedStatement; import java.util.List; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/src/main/java/gift/product/presentation/ProductManageController.java b/src/main/java/gift/product/presentation/ProductManageController.java index 32ee82fd8..95156d019 100644 --- a/src/main/java/gift/product/presentation/ProductManageController.java +++ b/src/main/java/gift/product/presentation/ProductManageController.java @@ -5,7 +5,9 @@ import gift.product.domain.Product; import gift.util.CommonResponse; import jakarta.validation.Valid; + import java.util.List; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -33,7 +35,7 @@ public ResponseEntity>> getProducts() { @PostMapping("") public ResponseEntity> addProduct( - @Valid @RequestBody CreateProductRequestDTO createProductRequestDTO) { + @Valid @RequestBody CreateProductRequestDTO createProductRequestDTO) { Long productId = productService.saveProduct(createProductRequestDTO); return ResponseEntity.ok(new CommonResponse<>(productId, "상품이 정상적으로 추가 되었습니다", true)); } diff --git a/src/main/java/gift/product/presentation/WishListController.java b/src/main/java/gift/product/presentation/WishListController.java index d81688b92..a1ca7953c 100644 --- a/src/main/java/gift/product/presentation/WishListController.java +++ b/src/main/java/gift/product/presentation/WishListController.java @@ -30,10 +30,10 @@ public WishListController(WishListService wishListService) { @JwtAuthenticated @GetMapping("/{userId}") public ResponseEntity getWishList(@PathVariable Long userId, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(defaultValue = "id") String sortBy, - @RequestParam(defaultValue = "asc") String direction) { + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "id") String sortBy, + @RequestParam(defaultValue = "asc") String direction) { Page products = wishListService.getProductsInWishList(userId, page, size, sortBy, direction); return ResponseEntity.ok(new CommonResponse<>(products, "위시리스트 조회 성공", true)); } diff --git a/src/main/java/gift/user/application/UserService.java b/src/main/java/gift/user/application/UserService.java index d4094f1d5..b55d34100 100644 --- a/src/main/java/gift/user/application/UserService.java +++ b/src/main/java/gift/user/application/UserService.java @@ -27,7 +27,7 @@ public UserService(UserRepository userRepository, PasswordEncoder passwordEncode public User getUser(Long id) { return userRepository.findById(id).orElseThrow( - () -> new UserException(ErrorCode.USER_NOT_FOUND) + () -> new UserException(ErrorCode.USER_NOT_FOUND) ); } @@ -45,15 +45,15 @@ public User registerUser(UserRegisterRequest request) { private void validateDuplicateUser(String email) { userRepository.findByEmail(email).ifPresent( - user -> { - throw new UserException(ErrorCode.DUPLICATE_USER); - } + user -> { + throw new UserException(ErrorCode.DUPLICATE_USER); + } ); } public User authenticateUser(String email, String password) { User user = userRepository.findByEmail(email).orElseThrow( - () -> new UserException(ErrorCode.USER_NOT_FOUND) + () -> new UserException(ErrorCode.USER_NOT_FOUND) ); System.out.println("user.getPassword() = " + user.getPassword()); if (passwordEncoder.matches(password, user.getPassword())) { diff --git a/src/main/java/gift/user/domain/User.java b/src/main/java/gift/user/domain/User.java index b43e83754..c82cb8520 100644 --- a/src/main/java/gift/user/domain/User.java +++ b/src/main/java/gift/user/domain/User.java @@ -2,6 +2,7 @@ import gift.product.domain.WishList; import jakarta.persistence.*; + import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/gift/user/infra/UserRepository.java b/src/main/java/gift/user/infra/UserRepository.java index 3dc578be8..a4ed40700 100644 --- a/src/main/java/gift/user/infra/UserRepository.java +++ b/src/main/java/gift/user/infra/UserRepository.java @@ -1,7 +1,9 @@ package gift.user.infra; import gift.user.domain.User; + import javax.swing.text.html.Option; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/src/main/java/gift/user/presentation/UserController.java b/src/main/java/gift/user/presentation/UserController.java index a65d1210d..320302bce 100644 --- a/src/main/java/gift/user/presentation/UserController.java +++ b/src/main/java/gift/user/presentation/UserController.java @@ -37,7 +37,7 @@ public ResponseEntity registerUser(@RequestBody UserRegisterRequest user) { public ResponseEntity createAuthenticationToken(@RequestBody User user) { String token = jwtUtil.generateToken(user.getEmail()); return ResponseEntity.ok( - new CommonResponse<>(new AuthenticationResponse(token), "로그인이 정상적으로 완료되었습니다", true)); + new CommonResponse<>(new AuthenticationResponse(token), "로그인이 정상적으로 완료되었습니다", true)); } diff --git a/src/main/java/gift/util/JwtUtil.java b/src/main/java/gift/util/JwtUtil.java index a8c6b2af0..6813ea9f5 100644 --- a/src/main/java/gift/util/JwtUtil.java +++ b/src/main/java/gift/util/JwtUtil.java @@ -3,6 +3,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; + import java.time.Duration; import java.time.LocalDateTime; import java.time.ZoneId; @@ -10,6 +11,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; + import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -29,15 +31,15 @@ private String createToken(Map claims, String subject) { LocalDateTime now = LocalDateTime.now(); Date issuedAt = Date.from(now.atZone(ZoneId.systemDefault()).toInstant()); Date expiryDate = Date.from( - now.plus(Duration.ofMillis(JWT_EXPIRATION)).atZone(ZoneId.systemDefault()).toInstant()); + now.plus(Duration.ofMillis(JWT_EXPIRATION)).atZone(ZoneId.systemDefault()).toInstant()); return Jwts.builder() - .setClaims(claims) - .setSubject(subject) - .setIssuedAt(issuedAt) - .setExpiration(expiryDate) - .signWith(SignatureAlgorithm.HS256, JWT_SECRET) - .compact(); + .setClaims(claims) + .setSubject(subject) + .setIssuedAt(issuedAt) + .setExpiration(expiryDate) + .signWith(SignatureAlgorithm.HS256, JWT_SECRET) + .compact(); } public Boolean validateToken(String token, String email) { From 59854bb104e00458862bb82b6e45250efc52766b Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:46:48 +0900 Subject: [PATCH 11/35] =?UTF-8?q?feat:=20=EC=97=B0=EA=B4=80=EA=B4=80?= =?UTF-8?q?=EA=B3=84=20=EC=84=A4=EC=A0=95=20-=20Product=20&=20Category=20?= =?UTF-8?q?=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/product/domain/Product.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java index 1fc33fb68..a2ef3942f 100644 --- a/src/main/java/gift/product/domain/Product.java +++ b/src/main/java/gift/product/domain/Product.java @@ -20,6 +20,11 @@ public class Product { @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) private List wishListProducts = new ArrayList<>(); + @ManyToOne + @JoinColumn(name = "category_id") + private Category category; + + public Product() { } From 6219d0cd70f90c2f1b15a94b8ac921edef7e995a Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:47:37 +0900 Subject: [PATCH 12/35] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?-=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20-=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=8D=BC=ED=8B=B0=20=EC=B6=94=EA=B0=80=20(=EC=83=89?= =?UTF-8?q?=EC=83=81,=20=EC=9D=B4=EB=AF=B8=EC=A7=80,=20=EC=84=A4=EB=AA=85)?= =?UTF-8?q?=20-=20schema.sql=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/CategoryService.java | 2 +- .../java/gift/product/domain/Category.java | 25 ++++++++++++++----- .../product/domain/CreateCategoryRequest.java | 9 ++++++- .../domain/CreateProductRequestDTO.java | 7 ++++++ .../presentation/CategoryController.java | 3 ++- src/main/resources/db/schema.sql | 11 ++++++++ 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/main/java/gift/product/application/CategoryService.java b/src/main/java/gift/product/application/CategoryService.java index 224d3d179..0ac555c3e 100644 --- a/src/main/java/gift/product/application/CategoryService.java +++ b/src/main/java/gift/product/application/CategoryService.java @@ -22,7 +22,7 @@ public Category addCategory(CreateCategoryRequest request) { if (categoryRepository.findByName(request.getName()) != null) { throw new IllegalArgumentException("이미 존재하는 카테고리입니다."); } - Category category = new Category(request.getName()); + Category category = new Category(request.getName(), request.getDescription(), request.getImageUrl(), request.getColor()); return categoryRepository.save(category); } diff --git a/src/main/java/gift/product/domain/Category.java b/src/main/java/gift/product/domain/Category.java index 2e1d8416f..22e75e7f0 100644 --- a/src/main/java/gift/product/domain/Category.java +++ b/src/main/java/gift/product/domain/Category.java @@ -1,10 +1,9 @@ package gift.product.domain; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; @Entity(name = "category") public class Category { @@ -14,13 +13,27 @@ public class Category { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true) + private List products = new ArrayList<>(); + private String name; + private String description; + + private String imageUrl; + + private String color; + + public Category() { } - public Category(String name) { + public Category(String name, String description, String imageUrl, String color) { this.name = name; + this.description = description; + this.imageUrl = imageUrl; + this.color = color; } public Long getId() { diff --git a/src/main/java/gift/product/domain/CreateCategoryRequest.java b/src/main/java/gift/product/domain/CreateCategoryRequest.java index 30861a8e0..0494d3950 100644 --- a/src/main/java/gift/product/domain/CreateCategoryRequest.java +++ b/src/main/java/gift/product/domain/CreateCategoryRequest.java @@ -6,10 +6,13 @@ public class CreateCategoryRequest { private String description; private String imageUrl; - public CreateCategoryRequest(String name, String description, String imageUrl) { + private String color; + + public CreateCategoryRequest(String name, String description, String imageUrl, String color) { this.name = name; this.description = description; this.imageUrl = imageUrl; + this.color = color; } public String getName() { @@ -23,4 +26,8 @@ public String getDescription() { public String getImageUrl() { return imageUrl; } + + public String getColor() { + return color; + } } diff --git a/src/main/java/gift/product/domain/CreateProductRequestDTO.java b/src/main/java/gift/product/domain/CreateProductRequestDTO.java index bab417532..f95e92aa2 100644 --- a/src/main/java/gift/product/domain/CreateProductRequestDTO.java +++ b/src/main/java/gift/product/domain/CreateProductRequestDTO.java @@ -17,6 +17,9 @@ public class CreateProductRequestDTO { @Size(max = MAX_INPUT_LENGTH, message = "이미지 URL은 255자를 넘을 수 없습니다.") private String imageUrl; + @NotBlank(message = "색상은 필수 입력 값입니다.") + private String color; + public String getName() { return name; } @@ -28,4 +31,8 @@ public Double getPrice() { public String getImageUrl() { return imageUrl; } + + public String getColor() { + return color; + } } diff --git a/src/main/java/gift/product/presentation/CategoryController.java b/src/main/java/gift/product/presentation/CategoryController.java index a3667ac83..6ec822120 100644 --- a/src/main/java/gift/product/presentation/CategoryController.java +++ b/src/main/java/gift/product/presentation/CategoryController.java @@ -4,6 +4,7 @@ import gift.product.domain.CreateCategoryRequest; import gift.util.CommonResponse; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -28,7 +29,7 @@ public ResponseEntity getCategory() { } @PostMapping("/create") // TODO: 관리자만 접근 가능하도록 수정 - public ResponseEntity addCategory(@RequestBody CreateCategoryRequest request) { + public ResponseEntity addCategory(@RequestBody @Validated CreateCategoryRequest request) { categoryService.addCategory(request); return ResponseEntity.ok(new CommonResponse<>( null, diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index c8e54e06e..97f46e1c1 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -30,3 +30,14 @@ CREATE TABLE wishlist_product FOREIGN KEY (wishlist_id) REFERENCES wishlist (wishlist_id), FOREIGN KEY (product_id) REFERENCES product (product_id) ); + + +create table category +( + color varchar(7) not null, + id bigint not null auto_increment, + description varchar(255), + image_url varchar(255) not null, + name varchar(255) not null, + primary key (id) +); From ae6af7b6fa7f7511b2d0c80c9c4e2ae81eebaf58 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:53:22 +0900 Subject: [PATCH 13/35] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=A1=9C=EC=A7=81=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20validate=20-=20=EC=83=81=ED=92=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EC=8B=9C=20=ED=95=84=EC=88=98=EC=A0=81=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=83=81=ED=92=88=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80=20-=20validate=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/product/application/CategoryService.java | 13 ++++++++++--- .../gift/product/application/ProductService.java | 10 ++++++++-- .../product/domain/CreateProductRequestDTO.java | 9 +++++---- src/main/java/gift/product/domain/Product.java | 3 ++- .../java/gift/product/infra/CategoryRepository.java | 4 +++- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/main/java/gift/product/application/CategoryService.java b/src/main/java/gift/product/application/CategoryService.java index 0ac555c3e..708af38ae 100644 --- a/src/main/java/gift/product/application/CategoryService.java +++ b/src/main/java/gift/product/application/CategoryService.java @@ -5,6 +5,9 @@ import gift.product.infra.CategoryRepository; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Optional; + @Service public class CategoryService { @@ -18,13 +21,13 @@ public Category findById(Long id) { return categoryRepository.findById(id); } - public Category addCategory(CreateCategoryRequest request) { + 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()); - return categoryRepository.save(category); + categoryRepository.save(category); } public void deleteById(Long id) { @@ -35,7 +38,11 @@ public Category findByName(String name) { return categoryRepository.findByName(name); } - public Object getCategory() { + public List getCategory() { return categoryRepository.findAll(); } + + public Category getCategoryByName(String category) { + return Optional.ofNullable(categoryRepository.findByName(category)).orElseThrow(() -> new IllegalArgumentException("해당 이름의 카테고리가 존재하지 않습니다.")); + } } diff --git a/src/main/java/gift/product/application/ProductService.java b/src/main/java/gift/product/application/ProductService.java index 185384548..43078ef9d 100644 --- a/src/main/java/gift/product/application/ProductService.java +++ b/src/main/java/gift/product/application/ProductService.java @@ -1,5 +1,6 @@ package gift.product.application; +import gift.product.domain.Category; import gift.product.domain.CreateProductRequestDTO; import gift.product.domain.Product; import gift.product.exception.ProductException; @@ -17,19 +18,24 @@ public class ProductService { private final ProductRepository productRepository; + public CategoryService categoryService; - public ProductService(ProductRepository productRepository) { + 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()); + createProductRequestDTO.getImageUrl(), category); validateProduct(product); + return productRepository.save(product).getId(); } diff --git a/src/main/java/gift/product/domain/CreateProductRequestDTO.java b/src/main/java/gift/product/domain/CreateProductRequestDTO.java index f95e92aa2..c47ae1935 100644 --- a/src/main/java/gift/product/domain/CreateProductRequestDTO.java +++ b/src/main/java/gift/product/domain/CreateProductRequestDTO.java @@ -17,8 +17,9 @@ public class CreateProductRequestDTO { @Size(max = MAX_INPUT_LENGTH, message = "이미지 URL은 255자를 넘을 수 없습니다.") private String imageUrl; - @NotBlank(message = "색상은 필수 입력 값입니다.") - private String color; + @NotBlank(message = "카테고리는 필수 입력 값입니다.") + private String category; + public String getName() { return name; @@ -32,7 +33,7 @@ public String getImageUrl() { return imageUrl; } - public String getColor() { - return color; + public String getCategory() { + return category; } } diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java index a2ef3942f..62bbd25ea 100644 --- a/src/main/java/gift/product/domain/Product.java +++ b/src/main/java/gift/product/domain/Product.java @@ -28,10 +28,11 @@ public class Product { public Product() { } - public Product(String name, Double price, String imageUrl) { + public Product(String name, Double price, String imageUrl, Category category) { this.name = name; this.price = price; this.imageUrl = imageUrl; + this.category = category; } public Long getId() { diff --git a/src/main/java/gift/product/infra/CategoryRepository.java b/src/main/java/gift/product/infra/CategoryRepository.java index 10b79196a..fdcfc4d2a 100644 --- a/src/main/java/gift/product/infra/CategoryRepository.java +++ b/src/main/java/gift/product/infra/CategoryRepository.java @@ -3,6 +3,8 @@ import gift.product.domain.Category; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public class CategoryRepository { @@ -30,7 +32,7 @@ public Category findByName(String name) { .orElseThrow(() -> new IllegalArgumentException("해당 이름의 카테고리가 존재하지 않습니다.")); } - public Object findAll() { + public List findAll() { return categoryJpaRepository.findAll(); } } From 368fbe4407cf0603e252bbd386cb42997d48f62e Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 17:56:41 +0900 Subject: [PATCH 14/35] docs: README.md --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d1c92abd2..81fa9eb2f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -# spring-gift-jpa +# spring-gift-enhancement -## 1단계: 엔티티 매핑 +## 1단계 기능 요구사항 -- [x] 지금까지 작성한 JdbcTemplate 기반 코드를 JPA로 리팩터링하고 실제 도메인 모델을 어떻게 구성하고 객체와 테이블을 어떻게 매핑해야 하는지 알아본다. - -## 2단계: 연관관계 매핑 - -- 객체의 참조와 테이블의 외래 키를 매핑해서 객체에서는 참조를 사용하고 테이블에서는 외래 키를 사용할 수 있도록 한다. +- 상품에는 항상 하나의 카테고리가 있어야 한다. +- 상품 카테고리는 수정할 수 있다. +- 관리자 화면에서 상품을 추가할 때 카테고리를 지정할 수 있다. +- 카테고리는 1차 카테고리만 있으며 2차 카테고리는 고려하지 않는다. From 1a28ac091cfc009766804d4577b4eaf9e45e0b08 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 18:14:18 +0900 Subject: [PATCH 15/35] =?UTF-8?q?refactor:=20jwt=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EB=B2=84=EC=A0=84=EC=97=85=20-=200.11.5=20->=200.1?= =?UTF-8?q?2.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 0800a03e7..db579f14c 100644 --- a/build.gradle +++ b/build.gradle @@ -20,9 +20,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' //Jwt - implementation 'io.jsonwebtoken:jjwt-api:0.11.5' - implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' - implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + 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' From 9c063e967698ab340f3f6fe2c4638cb91073a954 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 18:14:22 +0900 Subject: [PATCH 16/35] =?UTF-8?q?refactor:=20jwt=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EB=B2=84=EC=A0=84=EC=97=85=20-=200.11.5=20->=200.1?= =?UTF-8?q?2.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/util/JwtUtil.java | 121 +++++++++++++++++++-------- 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/src/main/java/gift/util/JwtUtil.java b/src/main/java/gift/util/JwtUtil.java index 6813ea9f5..9ae3ded09 100644 --- a/src/main/java/gift/util/JwtUtil.java +++ b/src/main/java/gift/util/JwtUtil.java @@ -1,70 +1,125 @@ package gift.util; +import gift.user.domain.User; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; -import java.time.Duration; +import java.security.Key; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; +import java.util.Optional; +import javax.crypto.SecretKey; import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; +import org.springframework.context.annotation.Configuration; -@Component +@Configuration public class JwtUtil { - @Value("${jwt.secret}") - private String JWT_SECRET; - private static final long JWT_EXPIRATION = 1000 * 60 * 60 * 10; + @Value("${jwt.secret.key}") + private String jwtSecretKey; + private static final long ACCESS_TOKEN_VALIDITY_MINUTES = 60; + private static final long REFRESH_TOKEN_VALIDITY_DAYS = 30; - public String generateToken(String email) { - Map claims = new HashMap<>(); - return createToken(claims, email); + public TokenResponse generateTokenResponse(User user) { + return new TokenResponse + .Builder() + .accessToken(generateAccessToken(user)) + .refreshToken(generateRefreshToken(user)) + .build(); } - private String createToken(Map claims, String subject) { + public String generateAccessToken(User user) { + return generateToken(user, ACCESS_TOKEN_VALIDITY_MINUTES); + } + + public String generateRefreshToken(User user) { + return generateToken(user, REFRESH_TOKEN_VALIDITY_DAYS * 24 * 60); + } + + public String generateToken(User user, Long validityMinutes) { LocalDateTime now = LocalDateTime.now(); Date issuedAt = Date.from(now.atZone(ZoneId.systemDefault()).toInstant()); Date expiryDate = Date.from( - now.plus(Duration.ofMillis(JWT_EXPIRATION)).atZone(ZoneId.systemDefault()).toInstant()); + now.plusMinutes(validityMinutes).atZone(ZoneId.systemDefault()).toInstant()); + + Claims claims = Jwts.claims() + .subject(user.getId().toString()) + .add("id", user.getId()) + .add("name", user.getName()) + .add("role", user.getRole()) + .build(); + + Key key = generateKey(); return Jwts.builder() - .setClaims(claims) - .setSubject(subject) - .setIssuedAt(issuedAt) - .setExpiration(expiryDate) - .signWith(SignatureAlgorithm.HS256, JWT_SECRET) + .claims(claims) + .issuedAt(issuedAt) + .expiration(expiryDate) + .signWith(key, SignatureAlgorithm.HS256) .compact(); } - public Boolean validateToken(String token, String email) { - final String username = extractUsername(token); - return (username.equals(email) && !isTokenExpired(token)); + + public SecretKey generateKey() { + return Keys.hmacShaKeyFor(jwtSecretKey.getBytes()); } - public String extractUsername(String token) { - return extractClaim(token, Claims::getSubject); + + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(generateKey()).build().parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } } - public T extractClaim(String token, Function claimsResolver) { - final Claims claims = extractAllClaims(token); - return claimsResolver.apply(claims); + public Optional getUserId(String token) { + if (token == null || token.isEmpty()) { + return Optional.empty(); + } + Claims claims = Jwts.parser().setSigningKey(generateKey()).build().parseClaimsJws(token).getBody(); + Object idObj = claims.get("id"); + + if (idObj instanceof Integer) { + return Optional.of(((Integer) idObj).longValue()); + } else if (idObj instanceof Long) { + return Optional.of((Long) idObj); + } else { + return Optional.empty(); + } + } + + + public String getUsername(String token) { + return Jwts.parser().setSigningKey(generateKey()).build().parseClaimsJws(token).getBody().getSubject(); } - private Claims extractAllClaims(String token) { - return Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token).getBody(); + public Long getKakaoUserId(String token) { + Claims claims = Jwts.parser().setSigningKey(generateKey()).build().parseClaimsJws(token).getBody(); + return (Long) claims.get("kakaoUserId"); } - private Boolean isTokenExpired(String token) { - return extractExpiration(token).before(new Date()); + public String getRole(String token) { + Claims claims = Jwts.parser().setSigningKey(generateKey()).build().parseClaimsJws(token).getBody(); + return (String) claims.get("role"); } - private Date extractExpiration(String token) { - return extractClaim(token, Claims::getExpiration); + public String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; } + + public String resolveToken(String token) { + return token.substring(7); + } + } From 3f2898b207f18bc60ad50818e0aa60c9c28105d6 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 18:14:38 +0900 Subject: [PATCH 17/35] =?UTF-8?q?feat:=20user=20Role=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/user/domain/Role.java | 5 ++ src/main/java/gift/user/domain/User.java | 8 +++ .../user/presentation/UserController.java | 4 +- src/main/java/gift/util/TokenResponse.java | 50 +++++++++++++++++++ src/main/resources/db/schema.sql | 1 + 5 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 src/main/java/gift/user/domain/Role.java create mode 100644 src/main/java/gift/util/TokenResponse.java diff --git a/src/main/java/gift/user/domain/Role.java b/src/main/java/gift/user/domain/Role.java new file mode 100644 index 000000000..295c26d75 --- /dev/null +++ b/src/main/java/gift/user/domain/Role.java @@ -0,0 +1,5 @@ +package gift.user.domain; + +public enum Role { + ADMIN, USER +} diff --git a/src/main/java/gift/user/domain/User.java b/src/main/java/gift/user/domain/User.java index c82cb8520..6f819bd41 100644 --- a/src/main/java/gift/user/domain/User.java +++ b/src/main/java/gift/user/domain/User.java @@ -14,7 +14,11 @@ public class User { @GeneratedValue(strategy = jakarta.persistence.GenerationType.IDENTITY) private Long id; + @Enumerated(EnumType.STRING) + private Role role = Role.USER; + private String name; + private String email; private String password; @@ -65,6 +69,10 @@ public List getWishLists() { return wishLists; } + public Role getRole() { + return role; + } + public void setWishLists(List wishLists) { this.wishLists = wishLists; } diff --git a/src/main/java/gift/user/presentation/UserController.java b/src/main/java/gift/user/presentation/UserController.java index 320302bce..23e0a09d7 100644 --- a/src/main/java/gift/user/presentation/UserController.java +++ b/src/main/java/gift/user/presentation/UserController.java @@ -35,9 +35,9 @@ public ResponseEntity registerUser(@RequestBody UserRegisterRequest user) { @PostMapping("/login") public ResponseEntity createAuthenticationToken(@RequestBody User user) { - String token = jwtUtil.generateToken(user.getEmail()); + String tokenResponse = jwtUtil.generateToken(user, 60L); return ResponseEntity.ok( - new CommonResponse<>(new AuthenticationResponse(token), "로그인이 정상적으로 완료되었습니다", true)); + new CommonResponse<>(tokenResponse, "로그인이 정상적으로 완료되었습니다", true)); } diff --git a/src/main/java/gift/util/TokenResponse.java b/src/main/java/gift/util/TokenResponse.java new file mode 100644 index 000000000..41f3b2524 --- /dev/null +++ b/src/main/java/gift/util/TokenResponse.java @@ -0,0 +1,50 @@ +package gift.util; + +public class TokenResponse { + + private String accessToken; + private String refreshToken; + + private TokenResponse(Builder builder) { + this.accessToken = builder.accessToken; + this.refreshToken = builder.refreshToken; + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public static class Builder { + private String accessToken; + private String refreshToken; + + public Builder() { + } + + public Builder accessToken(String accessToken) { + this.accessToken = accessToken; + return this; + } + + public Builder refreshToken(String refreshToken) { + this.refreshToken = refreshToken; + return this; + } + + public TokenResponse build() { + return new TokenResponse(this); + } + } +} diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 97f46e1c1..bc890af22 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -1,6 +1,7 @@ CREATE TABLE users ( user_id BIGINT AUTO_INCREMENT PRIMARY KEY, + role VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL From b1d9737adbb0035894e0a92af283eb09492fbd8c Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 18:22:35 +0900 Subject: [PATCH 18/35] =?UTF-8?q?feat:=20role=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=9D=B8=EA=B0=80=20-=20=EC=83=81=ED=92=88/=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=83=9D=EC=84=B1=20=EC=9E=90=EC=B2=B4?= =?UTF-8?q?=EB=8A=94=20ADMIN=EB=A7=8C=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/ProductManageController.java | 3 ++ .../presentation/WishListController.java | 4 +-- .../gift/util/annotation/AdminAspect.java | 35 +++++++++++++++++++ .../util/annotation/AdminAuthenticated.java | 13 +++++++ .../gift/util/{ => annotation}/JwtAspect.java | 8 ++--- .../{ => annotation}/JwtAuthenticated.java | 2 +- 6 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 src/main/java/gift/util/annotation/AdminAspect.java create mode 100644 src/main/java/gift/util/annotation/AdminAuthenticated.java rename src/main/java/gift/util/{ => annotation}/JwtAspect.java (83%) rename src/main/java/gift/util/{ => annotation}/JwtAuthenticated.java (89%) diff --git a/src/main/java/gift/product/presentation/ProductManageController.java b/src/main/java/gift/product/presentation/ProductManageController.java index 95156d019..fbd94ba2c 100644 --- a/src/main/java/gift/product/presentation/ProductManageController.java +++ b/src/main/java/gift/product/presentation/ProductManageController.java @@ -4,6 +4,7 @@ import gift.product.domain.CreateProductRequestDTO; import gift.product.domain.Product; import gift.util.CommonResponse; +import gift.util.annotation.AdminAuthenticated; import jakarta.validation.Valid; import java.util.List; @@ -33,6 +34,7 @@ public ResponseEntity>> getProducts() { return ResponseEntity.ok(new CommonResponse<>(productList, "상품 조회가 정상적으로 완료되었습니다", true)); } + @AdminAuthenticated @PostMapping("") public ResponseEntity> addProduct( @Valid @RequestBody CreateProductRequestDTO createProductRequestDTO) { @@ -40,6 +42,7 @@ public ResponseEntity> addProduct( return ResponseEntity.ok(new CommonResponse<>(productId, "상품이 정상적으로 추가 되었습니다", true)); } + @AdminAuthenticated @DeleteMapping("/delete/{id}") public ResponseEntity> deleteProduct(@PathVariable Long id) { productService.deleteProduct(id); diff --git a/src/main/java/gift/product/presentation/WishListController.java b/src/main/java/gift/product/presentation/WishListController.java index a1ca7953c..fff5c97da 100644 --- a/src/main/java/gift/product/presentation/WishListController.java +++ b/src/main/java/gift/product/presentation/WishListController.java @@ -3,10 +3,8 @@ import gift.product.application.WishListService; import gift.product.domain.WishList; -import gift.product.exception.ProductException; import gift.util.CommonResponse; -import gift.util.ErrorCode; -import gift.util.JwtAuthenticated; +import gift.util.annotation.JwtAuthenticated; import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; diff --git a/src/main/java/gift/util/annotation/AdminAspect.java b/src/main/java/gift/util/annotation/AdminAspect.java new file mode 100644 index 000000000..8580733ce --- /dev/null +++ b/src/main/java/gift/util/annotation/AdminAspect.java @@ -0,0 +1,35 @@ +package gift.util.annotation; + +import gift.util.JwtUtil; +import jakarta.servlet.http.HttpServletRequest; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class AdminAspect { + + @Autowired + private HttpServletRequest request; + private final JwtUtil jwtUtil; + + public AdminAspect(HttpServletRequest request, JwtUtil jwtUtil) { + this.request = request; + this.jwtUtil = jwtUtil; + } + + @Before("@annotation(roleAuthenticated)") + public void authorize(AdminAuthenticated roleAuthenticated) { + String token = jwtUtil.resolveToken(request); + if (token == null || !jwtUtil.validateToken(token)) { + throw new RuntimeException("Access Token이 유효하지 않습니다."); + } + + String role = jwtUtil.getRole(token); + if (!role.equals(roleAuthenticated.role())) { + throw new RuntimeException("매니저 권한이 필요합니다."); + } + } +} diff --git a/src/main/java/gift/util/annotation/AdminAuthenticated.java b/src/main/java/gift/util/annotation/AdminAuthenticated.java new file mode 100644 index 000000000..da5a9f616 --- /dev/null +++ b/src/main/java/gift/util/annotation/AdminAuthenticated.java @@ -0,0 +1,13 @@ +package gift.util.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AdminAuthenticated { + + String role() default "ADMIN"; +} diff --git a/src/main/java/gift/util/JwtAspect.java b/src/main/java/gift/util/annotation/JwtAspect.java similarity index 83% rename from src/main/java/gift/util/JwtAspect.java rename to src/main/java/gift/util/annotation/JwtAspect.java index c9696d8d2..fd3b9ac93 100644 --- a/src/main/java/gift/util/JwtAspect.java +++ b/src/main/java/gift/util/annotation/JwtAspect.java @@ -1,5 +1,6 @@ -package gift.util; +package gift.util.annotation; +import gift.util.JwtUtil; import jakarta.servlet.http.HttpServletRequest; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @@ -16,7 +17,7 @@ public class JwtAspect { @Autowired private HttpServletRequest request; - @Before("@annotation(gift.util.JwtAuthenticated)") + @Before("@annotation(gift.util.annotation.JwtAuthenticated)") public void authenticate() { String authorizationHeader = request.getHeader("Authorization"); if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) { @@ -25,9 +26,8 @@ public void authenticate() { String token = authorizationHeader.substring(7); // "Bearer " 이후의 토큰 부분만 추출 - String email = jwtUtil.extractUsername(token); - if (!jwtUtil.validateToken(token, email)) { + if (!jwtUtil.validateToken(token)) { throw new RuntimeException("JWT Token is not valid"); } } diff --git a/src/main/java/gift/util/JwtAuthenticated.java b/src/main/java/gift/util/annotation/JwtAuthenticated.java similarity index 89% rename from src/main/java/gift/util/JwtAuthenticated.java rename to src/main/java/gift/util/annotation/JwtAuthenticated.java index 561dce130..b0122b25e 100644 --- a/src/main/java/gift/util/JwtAuthenticated.java +++ b/src/main/java/gift/util/annotation/JwtAuthenticated.java @@ -1,4 +1,4 @@ -package gift.util; +package gift.util.annotation; import java.lang.annotation.ElementType; From df876fc92549f7f9f38ca32d73f69362753e3fc9 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 18:23:09 +0900 Subject: [PATCH 19/35] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=83=9D=EC=84=B1=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B8=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/product/presentation/CategoryController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/gift/product/presentation/CategoryController.java b/src/main/java/gift/product/presentation/CategoryController.java index 6ec822120..83f609fb9 100644 --- a/src/main/java/gift/product/presentation/CategoryController.java +++ b/src/main/java/gift/product/presentation/CategoryController.java @@ -3,6 +3,7 @@ import gift.product.application.CategoryService; import gift.product.domain.CreateCategoryRequest; import gift.util.CommonResponse; +import gift.util.annotation.AdminAuthenticated; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -28,7 +29,8 @@ public ResponseEntity getCategory() { )); } - @PostMapping("/create") // TODO: 관리자만 접근 가능하도록 수정 + @AdminAuthenticated + @PostMapping("/create") public ResponseEntity addCategory(@RequestBody @Validated CreateCategoryRequest request) { categoryService.addCategory(request); return ResponseEntity.ok(new CommonResponse<>( From d0398bd79568350d48f654b0b35de0e9b8a0e93f Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 18:43:31 +0900 Subject: [PATCH 20/35] =?UTF-8?q?feat:=20Product=20Option=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/product/domain/Product.java | 4 ++ .../gift/product/domain/ProductOption.java | 19 ++++++++++ src/main/resources/application-secret.yml | 3 +- src/main/resources/db/schema.sql | 38 ++++++++++++------- 4 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 src/main/java/gift/product/domain/ProductOption.java diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java index 62bbd25ea..61f4f817e 100644 --- a/src/main/java/gift/product/domain/Product.java +++ b/src/main/java/gift/product/domain/Product.java @@ -24,6 +24,10 @@ public class Product { @JoinColumn(name = "category_id") private Category category; + @OneToMany + @JoinColumn(name = "product_id") + private List productOptions = new ArrayList<>(); + public Product() { } diff --git a/src/main/java/gift/product/domain/ProductOption.java b/src/main/java/gift/product/domain/ProductOption.java new file mode 100644 index 000000000..fd71db629 --- /dev/null +++ b/src/main/java/gift/product/domain/ProductOption.java @@ -0,0 +1,19 @@ +package gift.product.domain; + +import jakarta.persistence.*; + +@Entity(name = "product_option") +public class ProductOption { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + + private Long price; + + private Long quentity; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id") + private Product product; +} diff --git a/src/main/resources/application-secret.yml b/src/main/resources/application-secret.yml index cfcb233df..06f2c638f 100644 --- a/src/main/resources/application-secret.yml +++ b/src/main/resources/application-secret.yml @@ -1,2 +1,3 @@ jwt: - secret: sk1234567890123456789012345678901234567890123456789012345678901234 + secret: + key: sk1234567890123456789012345678901234567890123456789012345678901234 diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index bc890af22..3f250fb5a 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -15,12 +15,33 @@ CREATE TABLE wishlist FOREIGN KEY (user_id) REFERENCES users (user_id) ); +CREATE TABLE category +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + color VARCHAR(7) NOT NULL, + description VARCHAR(255), + image_url VARCHAR(255) NOT NULL +); + CREATE TABLE product ( - product_id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, + product_id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + price DOUBLE NOT NULL, + image_url VARCHAR(255) NOT NULL, + category_id BIGINT NOT NULL, + FOREIGN KEY (category_id) REFERENCES category (id) +); + +CREATE TABLE product_option +( + product_option_id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, price DOUBLE NOT NULL, - image_url VARCHAR(255) NOT NULL + product_id BIGINT NOT NULL, + quentity BIGINT NOT NULL, + FOREIGN KEY (product_id) REFERENCES product (product_id) ); CREATE TABLE wishlist_product @@ -31,14 +52,3 @@ CREATE TABLE wishlist_product FOREIGN KEY (wishlist_id) REFERENCES wishlist (wishlist_id), FOREIGN KEY (product_id) REFERENCES product (product_id) ); - - -create table category -( - color varchar(7) not null, - id bigint not null auto_increment, - description varchar(255), - image_url varchar(255) not null, - name varchar(255) not null, - primary key (id) -); From 886279dc1764f99d8af9b4d171593bd81fdae76d Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 18:43:43 +0900 Subject: [PATCH 21/35] =?UTF-8?q?feat:=20=EA=B0=80=EA=B2=A9=EC=9D=80=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/product/domain/ProductOption.java | 2 -- src/main/resources/db/schema.sql | 1 - 2 files changed, 3 deletions(-) diff --git a/src/main/java/gift/product/domain/ProductOption.java b/src/main/java/gift/product/domain/ProductOption.java index fd71db629..0a0d76f8f 100644 --- a/src/main/java/gift/product/domain/ProductOption.java +++ b/src/main/java/gift/product/domain/ProductOption.java @@ -9,8 +9,6 @@ public class ProductOption { private Long id; private String name; - private Long price; - private Long quentity; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 3f250fb5a..6d58c7cff 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -38,7 +38,6 @@ CREATE TABLE product_option ( product_option_id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, - price DOUBLE NOT NULL, product_id BIGINT NOT NULL, quentity BIGINT NOT NULL, FOREIGN KEY (product_id) REFERENCES product (product_id) From 34a19ca583bf47567713ba4164c37394be934d6a Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 18:58:39 +0900 Subject: [PATCH 22/35] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80=20API=20=EA=B5=AC=ED=98=84=20-?= =?UTF-8?q?=20=EA=B4=80=EB=A6=AC=EC=9E=90=EB=8A=94=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8B=A4=20-=20=EB=8F=99=EC=9D=BC?= =?UTF-8?q?=ED=95=9C=20=EC=9D=B4=EB=A6=84=EC=9D=98=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=EC=9D=80=20=EC=B6=94=EA=B0=80=ED=95=A0=20=EC=88=98=20=EC=97=86?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/ProductService.java | 32 +++++++----- .../product/application/WishListService.java | 7 ++- .../domain/CreateProductOptionRequestDTO.java | 17 +++++++ .../java/gift/product/domain/Product.java | 9 ++++ .../gift/product/domain/ProductOption.java | 13 +++++ .../product/infra/ProductJpaRepository.java | 12 +++++ .../infra/ProductOptionJpaRepository.java | 12 +++++ .../gift/product/infra/ProductRepository.java | 49 +++++++++++++++---- .../presentation/ProductManageController.java | 8 +++ src/main/java/gift/util/ErrorCode.java | 4 +- 10 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 src/main/java/gift/product/domain/CreateProductOptionRequestDTO.java create mode 100644 src/main/java/gift/product/infra/ProductJpaRepository.java create mode 100644 src/main/java/gift/product/infra/ProductOptionJpaRepository.java diff --git a/src/main/java/gift/product/application/ProductService.java b/src/main/java/gift/product/application/ProductService.java index 43078ef9d..73bd7e7a3 100644 --- a/src/main/java/gift/product/application/ProductService.java +++ b/src/main/java/gift/product/application/ProductService.java @@ -1,18 +1,14 @@ package gift.product.application; -import gift.product.domain.Category; -import gift.product.domain.CreateProductRequestDTO; -import gift.product.domain.Product; +import gift.product.domain.*; import gift.product.exception.ProductException; import gift.product.infra.ProductRepository; import gift.util.ErrorCode; - -import java.util.List; -import java.util.Optional; - import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @Transactional(readOnly = true) public class ProductService { @@ -39,13 +35,27 @@ public Long saveProduct(CreateProductRequestDTO createProductRequestDTO) { return productRepository.save(product).getId(); } + @Transactional + public void addProductOption(Long id, CreateProductOptionRequestDTO createProductOptionRequestDTO) { + Product product = productRepository.findById(id); + + productRepository.findProductOptionsByProductId(id).forEach(productOption -> { + if (productOption.getName().equals(createProductOptionRequestDTO.getName())) { + throw new ProductException(ErrorCode.DUPLICATED_OPTION_NAME); + } + }); + + ProductOption productOption = productRepository.saveProductOption(new ProductOption(createProductOptionRequestDTO.getName(), + createProductOptionRequestDTO.getQuantity(), product)); + 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) - .orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_NOT_FOUND)); + Product product = productRepository.findById(id); product.setName(name); product.setPrice(price); product.setImageUrl(imageUrl); @@ -79,10 +89,6 @@ private void validatePrice(Double price) { } } - public Optional getProductByName(Long id) { - return productRepository.findById(id); - } - public List getProduct() { return productRepository.findAll(); } diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index 7b5629273..f8b8d2199 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -4,7 +4,7 @@ import gift.product.domain.WishList; import gift.product.domain.WishListProduct; import gift.product.exception.ProductException; -import gift.product.infra.ProductRepository; +import gift.product.infra.ProductJpaRepository; import gift.product.infra.WishListRepository; import gift.user.application.UserService; import gift.user.domain.User; @@ -12,7 +12,6 @@ import jakarta.transaction.Transactional; import java.time.LocalDateTime; -import java.util.ArrayList; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -24,10 +23,10 @@ public class WishListService { private final WishListRepository wishListRepository; - private final ProductRepository productRepository; + private final ProductJpaRepository productRepository; private final UserService userService; - public WishListService(WishListRepository wishListRepository, ProductRepository productRepository, + public WishListService(WishListRepository wishListRepository, ProductJpaRepository productRepository, UserService userService) { this.wishListRepository = wishListRepository; this.productRepository = productRepository; diff --git a/src/main/java/gift/product/domain/CreateProductOptionRequestDTO.java b/src/main/java/gift/product/domain/CreateProductOptionRequestDTO.java new file mode 100644 index 000000000..75b64b107 --- /dev/null +++ b/src/main/java/gift/product/domain/CreateProductOptionRequestDTO.java @@ -0,0 +1,17 @@ +package gift.product.domain; + +public class CreateProductOptionRequestDTO { + + private static final int MAX_INPUT_LENGTH = 255; + + private String name; + private Long quantity; + + public String getName() { + return name; + } + + public Long getQuantity() { + return quantity; + } +} diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java index 61f4f817e..47ae734a5 100644 --- a/src/main/java/gift/product/domain/Product.java +++ b/src/main/java/gift/product/domain/Product.java @@ -78,4 +78,13 @@ public List getWishListProducts() { public void setWishListProducts(List wishListProducts) { this.wishListProducts = wishListProducts; } + + public void addProductOption(String name, Long quentity) { + ProductOption productOption = new ProductOption(name, quentity, this); + productOptions.add(productOption); + } + + public void addProductOption(ProductOption productOption) { + productOptions.add(productOption); + } } diff --git a/src/main/java/gift/product/domain/ProductOption.java b/src/main/java/gift/product/domain/ProductOption.java index 0a0d76f8f..6d1f011bb 100644 --- a/src/main/java/gift/product/domain/ProductOption.java +++ b/src/main/java/gift/product/domain/ProductOption.java @@ -14,4 +14,17 @@ public class ProductOption { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "product_id") private Product product; + + public ProductOption(String name, Long quentity, Product product) { + this.name = name; + this.quentity = quentity; + product.addProductOption(this); + } + + public ProductOption() { + } + + public String getName() { + return name; + } } diff --git a/src/main/java/gift/product/infra/ProductJpaRepository.java b/src/main/java/gift/product/infra/ProductJpaRepository.java new file mode 100644 index 000000000..419b6e99b --- /dev/null +++ b/src/main/java/gift/product/infra/ProductJpaRepository.java @@ -0,0 +1,12 @@ +package gift.product.infra; + +import gift.product.domain.Product; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface ProductJpaRepository extends JpaRepository { +} diff --git a/src/main/java/gift/product/infra/ProductOptionJpaRepository.java b/src/main/java/gift/product/infra/ProductOptionJpaRepository.java new file mode 100644 index 000000000..0bea7a14a --- /dev/null +++ b/src/main/java/gift/product/infra/ProductOptionJpaRepository.java @@ -0,0 +1,12 @@ +package gift.product.infra; + +import gift.product.domain.ProductOption; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ProductOptionJpaRepository extends JpaRepository { + List findByProductId(Long productId); +} diff --git a/src/main/java/gift/product/infra/ProductRepository.java b/src/main/java/gift/product/infra/ProductRepository.java index 44a42e741..dad92038e 100644 --- a/src/main/java/gift/product/infra/ProductRepository.java +++ b/src/main/java/gift/product/infra/ProductRepository.java @@ -1,19 +1,48 @@ package gift.product.infra; import gift.product.domain.Product; +import gift.product.domain.ProductOption; +import gift.product.exception.ProductException; +import gift.util.ErrorCode; +import org.springframework.stereotype.Repository; -import java.sql.Statement; -import java.sql.PreparedStatement; import java.util.List; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; +import java.util.Optional; @Repository -public interface ProductRepository extends JpaRepository { +public class ProductRepository { + private final ProductJpaRepository productJpaRepository; + private final ProductOptionJpaRepository productOptionJpaRepository; + + public ProductRepository(ProductJpaRepository productJpaRepository, ProductOptionJpaRepository productOptionJpaRepository) { + this.productJpaRepository = productJpaRepository; + this.productOptionJpaRepository = productOptionJpaRepository; + } + + + public Product save(Product product) { + return productJpaRepository.save(product); + } + + public Product findById(Long id) { + return productJpaRepository.findById(id).orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_NOT_FOUND)); + } + + + public void deleteById(Long id) { + productJpaRepository.deleteById(id); + } + + public List findAll() { + return productJpaRepository.findAll(); + } + + public List findProductOptionsByProductId(Long productId) { + return productOptionJpaRepository.findByProductId(productId); + } + + public ProductOption saveProductOption(ProductOption productOption) { + return productOptionJpaRepository.save(productOption); + } } diff --git a/src/main/java/gift/product/presentation/ProductManageController.java b/src/main/java/gift/product/presentation/ProductManageController.java index fbd94ba2c..33ae67a21 100644 --- a/src/main/java/gift/product/presentation/ProductManageController.java +++ b/src/main/java/gift/product/presentation/ProductManageController.java @@ -1,6 +1,7 @@ package gift.product.presentation; import gift.product.application.ProductService; +import gift.product.domain.CreateProductOptionRequestDTO; import gift.product.domain.CreateProductRequestDTO; import gift.product.domain.Product; import gift.util.CommonResponse; @@ -42,6 +43,13 @@ public ResponseEntity> addProduct( return ResponseEntity.ok(new CommonResponse<>(productId, "상품이 정상적으로 추가 되었습니다", true)); } + @AdminAuthenticated + @PostMapping("/{id}") + public ResponseEntity addProductOption(@PathVariable Long id, @RequestBody CreateProductOptionRequestDTO createProductOptionRequestDTO) { + productService.addProductOption(id, createProductOptionRequestDTO); + return ResponseEntity.ok(new CommonResponse<>(null, "상품 옵션이 정상적으로 추가 되었습니다", true)); + } + @AdminAuthenticated @DeleteMapping("/delete/{id}") public ResponseEntity> deleteProduct(@PathVariable Long id) { diff --git a/src/main/java/gift/util/ErrorCode.java b/src/main/java/gift/util/ErrorCode.java index d6df63ec4..50911ce51 100644 --- a/src/main/java/gift/util/ErrorCode.java +++ b/src/main/java/gift/util/ErrorCode.java @@ -22,7 +22,9 @@ public enum ErrorCode { // Wishlist ErrorMessage WISHLIST_NOT_FOUND("해당 위시리스트를 찾을 수 없습니다.", HttpStatus.BAD_REQUEST), - WISHLIST_ALREADY_EXISTS("이미 위시리스트가 존재합니다.", HttpStatus.BAD_REQUEST); + WISHLIST_ALREADY_EXISTS("이미 위시리스트가 존재합니다.", HttpStatus.BAD_REQUEST), + DUPLICATED_OPTION_NAME("이미 존재하는 옵션 이름입니다.", HttpStatus.BAD_REQUEST), + ; private final String message; private final HttpStatus status; From 558438f6f7b41f9417a1385a6ff99e2a1dac4ac4 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 19:06:07 +0900 Subject: [PATCH 23/35] =?UTF-8?q?feat:=20WishList=20=EB=A0=88=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=B6=84=EB=A6=AC=20-=20Jpa=20?= =?UTF-8?q?=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC/=EC=88=9C?= =?UTF-8?q?=EC=88=98=20Java=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/WishListService.java | 23 ++++++++----------- .../gift/product/domain/WishListProduct.java | 4 ++++ .../product/infra/WishLIstRepository.java | 2 ++ .../product/infra/WishListRepository.java | 19 +++++++++++---- 4 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 src/main/java/gift/product/infra/WishLIstRepository.java diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index f8b8d2199..2e2b6d9c5 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -1,11 +1,13 @@ 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.ProductJpaRepository; -import gift.product.infra.WishListRepository; +import gift.product.infra.ProductRepository; +import gift.product.infra.WishLIstRepository; +import gift.product.infra.WishListJpaRepository; import gift.user.application.UserService; import gift.user.domain.User; import gift.util.ErrorCode; @@ -22,11 +24,11 @@ @Service public class WishListService { - private final WishListRepository wishListRepository; - private final ProductJpaRepository productRepository; + private final WishLIstRepository wishListRepository; + private final ProductRepository productRepository; private final UserService userService; - public WishListService(WishListRepository wishListRepository, ProductJpaRepository productRepository, + public WishListService(WishLIstRepository wishListRepository, ProductRepository productRepository, UserService userService) { this.wishListRepository = wishListRepository; this.productRepository = productRepository; @@ -49,16 +51,11 @@ public Page getProductsInWishList(Long userId, int page, int size, Str } @Transactional - public void addProductToWishList(Long userId, Long productId) { + public void addProductToWishList(Long userId, Long productId, Long optionId) { WishList wishList = wishListRepository.findByUserId(userId); - Product product = productRepository.findById(productId) - .orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_NOT_FOUND)); + Product product = productRepository.findById(productId); + ProductOption productOption = productRepository.getProductWithOption(productId, optionId); - if (wishList == null) { - User user = userService.getUser(userId); - wishList = new WishList(user, LocalDateTime.now()); - wishList = wishListRepository.save(wishList); - } WishListProduct wishListProduct = new WishListProduct(wishList, product); wishList.addWishListProduct(wishListProduct); diff --git a/src/main/java/gift/product/domain/WishListProduct.java b/src/main/java/gift/product/domain/WishListProduct.java index 00198f06d..4ca19595f 100644 --- a/src/main/java/gift/product/domain/WishListProduct.java +++ b/src/main/java/gift/product/domain/WishListProduct.java @@ -18,6 +18,10 @@ public class WishListProduct { @JoinColumn(name = "product_id") private Product product; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_option_id") + private ProductOption productOption; + public WishListProduct() { } diff --git a/src/main/java/gift/product/infra/WishLIstRepository.java b/src/main/java/gift/product/infra/WishLIstRepository.java new file mode 100644 index 000000000..d2308d97f --- /dev/null +++ b/src/main/java/gift/product/infra/WishLIstRepository.java @@ -0,0 +1,2 @@ +package gift.product.infra;public class WishLIstRepository { +} diff --git a/src/main/java/gift/product/infra/WishListRepository.java b/src/main/java/gift/product/infra/WishListRepository.java index 288cf32bd..3e72cad38 100644 --- a/src/main/java/gift/product/infra/WishListRepository.java +++ b/src/main/java/gift/product/infra/WishListRepository.java @@ -3,15 +3,26 @@ import gift.product.domain.WishList; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface WishListRepository extends JpaRepository { +public class WishLIstRepository { + private final WishListJpaRepository wishListJpaRepository; - WishList findByUserId(Long userId); + public WishLIstRepository(WishListJpaRepository wishListJpaRepository) { + this.wishListJpaRepository = wishListJpaRepository; + } + public WishList findByUserId(Long userId) { + return wishListJpaRepository.findByUserId(userId) + .orElseThrow(() -> new IllegalArgumentException("해당 ID의 위시리스트가 존재하지 않습니다.")); + } - Page findByUserId(Long userId, Pageable pageable); + public Page findByUserId(Long userId, Pageable pageable) { + return wishListJpaRepository.findByUserId(userId, pageable); + } + public void save(WishList wishList) { + wishListJpaRepository.save(wishList); + } } From dd0c5928d51b09d10e62701a94c10464e6669f49 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 19:06:11 +0900 Subject: [PATCH 24/35] =?UTF-8?q?feat:=20WishList=20=EB=A0=88=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=B6=84=EB=A6=AC=20-=20Jpa=20?= =?UTF-8?q?=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC/=EC=88=9C?= =?UTF-8?q?=EC=88=98=20Java=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/infra/WishLIstRepository.java | 28 ++++++++++++++++++- .../product/infra/WishListRepository.java | 28 ------------------- 2 files changed, 27 insertions(+), 29 deletions(-) delete mode 100644 src/main/java/gift/product/infra/WishListRepository.java diff --git a/src/main/java/gift/product/infra/WishLIstRepository.java b/src/main/java/gift/product/infra/WishLIstRepository.java index d2308d97f..3e72cad38 100644 --- a/src/main/java/gift/product/infra/WishLIstRepository.java +++ b/src/main/java/gift/product/infra/WishLIstRepository.java @@ -1,2 +1,28 @@ -package gift.product.infra;public class WishLIstRepository { +package gift.product.infra; + +import gift.product.domain.WishList; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +public class WishLIstRepository { + private final WishListJpaRepository wishListJpaRepository; + + public WishLIstRepository(WishListJpaRepository wishListJpaRepository) { + this.wishListJpaRepository = wishListJpaRepository; + } + + public WishList findByUserId(Long userId) { + return wishListJpaRepository.findByUserId(userId) + .orElseThrow(() -> new IllegalArgumentException("해당 ID의 위시리스트가 존재하지 않습니다.")); + } + + public Page findByUserId(Long userId, Pageable pageable) { + return wishListJpaRepository.findByUserId(userId, pageable); + } + + public void save(WishList wishList) { + wishListJpaRepository.save(wishList); + } } diff --git a/src/main/java/gift/product/infra/WishListRepository.java b/src/main/java/gift/product/infra/WishListRepository.java deleted file mode 100644 index 3e72cad38..000000000 --- a/src/main/java/gift/product/infra/WishListRepository.java +++ /dev/null @@ -1,28 +0,0 @@ -package gift.product.infra; - -import gift.product.domain.WishList; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -@Repository -public class WishLIstRepository { - private final WishListJpaRepository wishListJpaRepository; - - public WishLIstRepository(WishListJpaRepository wishListJpaRepository) { - this.wishListJpaRepository = wishListJpaRepository; - } - - public WishList findByUserId(Long userId) { - return wishListJpaRepository.findByUserId(userId) - .orElseThrow(() -> new IllegalArgumentException("해당 ID의 위시리스트가 존재하지 않습니다.")); - } - - public Page findByUserId(Long userId, Pageable pageable) { - return wishListJpaRepository.findByUserId(userId, pageable); - } - - public void save(WishList wishList) { - wishListJpaRepository.save(wishList); - } -} From 0717c3bc3c23a652ed63b9ea4e26a385f2c520b9 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 19:06:27 +0900 Subject: [PATCH 25/35] =?UTF-8?q?feat:=20WishList=20=EB=A0=88=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=B6=84=EB=A6=AC=20-=20Jpa=20?= =?UTF-8?q?=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC/=EC=88=9C?= =?UTF-8?q?=EC=88=98=20Java=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/infra/WishListJpaRepository.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/gift/product/infra/WishListJpaRepository.java diff --git a/src/main/java/gift/product/infra/WishListJpaRepository.java b/src/main/java/gift/product/infra/WishListJpaRepository.java new file mode 100644 index 000000000..ff4aa7cc7 --- /dev/null +++ b/src/main/java/gift/product/infra/WishListJpaRepository.java @@ -0,0 +1,19 @@ +package gift.product.infra; + +import gift.product.domain.WishList; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface WishListJpaRepository extends JpaRepository { + + Optional findByUserId(Long userId); + + + Page findByUserId(Long userId, Pageable pageable); + +} From 765430a2b06e471282d54449f073a0bb8ca3e69e Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 19:15:54 +0900 Subject: [PATCH 26/35] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EC=84=A0=ED=83=9D=ED=95=B4=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20=ED=9A=8C=EC=9B=90=EC=9D=B4=20=EC=9C=84?= =?UTF-8?q?=EC=8B=9C=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=20=EC=83=81?= =?UTF-8?q?=ED=92=88=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=A0=20=EB=95=8C=20?= =?UTF-8?q?option=20id=EA=B9=8C=EC=A7=80=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/WishListService.java | 16 ++++++++++++---- .../gift/product/domain/WishListProduct.java | 3 ++- .../infra/ProductOptionJpaRepository.java | 3 +++ .../gift/product/infra/ProductRepository.java | 5 ++++- .../gift/product/infra/WishLIstRepository.java | 6 ++++++ .../product/presentation/WishListController.java | 13 +++++++++---- src/main/java/gift/util/ErrorCode.java | 3 ++- 7 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index 2e2b6d9c5..263def77f 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -14,6 +14,7 @@ import jakarta.transaction.Transactional; import java.time.LocalDateTime; +import java.util.Objects; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -51,18 +52,25 @@ public Page getProductsInWishList(Long userId, int page, int size, Str } @Transactional - public void addProductToWishList(Long userId, Long productId, Long optionId) { - WishList wishList = wishListRepository.findByUserId(userId); + public void addProductToWishList(Long userId, Long wishlistId, Long productId, Long optionId) { + 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)); - WishListProduct wishListProduct = new WishListProduct(wishList, product); - wishList.addWishListProduct(wishListProduct); wishListRepository.save(wishList); } + public WishList findById(Long id) { + return wishListRepository.findById(id).orElseThrow(() -> new ProductException(ErrorCode.WISHLIST_NOT_FOUND)); + } + @Transactional public void deleteProductFromWishList(Long userId, Long productId) { WishList wishList = wishListRepository.findByUserId(userId); diff --git a/src/main/java/gift/product/domain/WishListProduct.java b/src/main/java/gift/product/domain/WishListProduct.java index 4ca19595f..a78ffd3a6 100644 --- a/src/main/java/gift/product/domain/WishListProduct.java +++ b/src/main/java/gift/product/domain/WishListProduct.java @@ -25,9 +25,10 @@ public class WishListProduct { public WishListProduct() { } - public WishListProduct(WishList wishList, Product product) { + public WishListProduct(WishList wishList, Product product, ProductOption productOption) { this.wishList = wishList; this.product = product; + this.productOption = productOption; } public Long getId() { diff --git a/src/main/java/gift/product/infra/ProductOptionJpaRepository.java b/src/main/java/gift/product/infra/ProductOptionJpaRepository.java index 0bea7a14a..866cc8254 100644 --- a/src/main/java/gift/product/infra/ProductOptionJpaRepository.java +++ b/src/main/java/gift/product/infra/ProductOptionJpaRepository.java @@ -5,8 +5,11 @@ import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; @Repository public interface ProductOptionJpaRepository extends JpaRepository { List findByProductId(Long productId); + + Optional findByProductIdAndId(Long productId, Long optionId); } diff --git a/src/main/java/gift/product/infra/ProductRepository.java b/src/main/java/gift/product/infra/ProductRepository.java index dad92038e..031543996 100644 --- a/src/main/java/gift/product/infra/ProductRepository.java +++ b/src/main/java/gift/product/infra/ProductRepository.java @@ -7,7 +7,6 @@ import org.springframework.stereotype.Repository; import java.util.List; -import java.util.Optional; @Repository public class ProductRepository { @@ -45,4 +44,8 @@ public ProductOption saveProductOption(ProductOption productOption) { return productOptionJpaRepository.save(productOption); } + public ProductOption getProductWithOption(Long productId, Long optionId) { + return productOptionJpaRepository.findByProductIdAndId(productId, optionId) + .orElseThrow(() -> new ProductException(ErrorCode.PRODUCT_OPTION_NOT_FOUND)); + } } diff --git a/src/main/java/gift/product/infra/WishLIstRepository.java b/src/main/java/gift/product/infra/WishLIstRepository.java index 3e72cad38..fbfe71eee 100644 --- a/src/main/java/gift/product/infra/WishLIstRepository.java +++ b/src/main/java/gift/product/infra/WishLIstRepository.java @@ -5,6 +5,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public class WishLIstRepository { private final WishListJpaRepository wishListJpaRepository; @@ -25,4 +27,8 @@ public Page findByUserId(Long userId, Pageable pageable) { public void save(WishList wishList) { wishListJpaRepository.save(wishList); } + + public Optional findById(Long id) { + return wishListJpaRepository.findById(id); + } } diff --git a/src/main/java/gift/product/presentation/WishListController.java b/src/main/java/gift/product/presentation/WishListController.java index fff5c97da..4e77a8d6e 100644 --- a/src/main/java/gift/product/presentation/WishListController.java +++ b/src/main/java/gift/product/presentation/WishListController.java @@ -7,6 +7,8 @@ import gift.util.annotation.JwtAuthenticated; import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -44,10 +46,13 @@ public ResponseEntity createWishList(@PathVariable Long userId) { } @JwtAuthenticated - @PostMapping("/{userId}/add/{productId}") - public ResponseEntity addProductToWishList(@PathVariable Long userId, @PathVariable Long productId) { - wishListService.addProductToWishList(userId, productId); - return ResponseEntity.ok(new CommonResponse<>(null, "제품 추가 성공", true)); + @PostMapping("/{wishListId}/add/{productId}/{optionId}") + public ResponseEntity addProductToWishList(@PathVariable Long wishListId, @PathVariable Long productId, @PathVariable Long optionId) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Long userId = Long.valueOf(authentication.getName()); + wishListService.addProductToWishList(userId, wishListId, productId, optionId); + + return ResponseEntity.ok(new CommonResponse<>(null, "위시리스트에 제품 추가 성공", true)); } @JwtAuthenticated diff --git a/src/main/java/gift/util/ErrorCode.java b/src/main/java/gift/util/ErrorCode.java index 50911ce51..bccdb1815 100644 --- a/src/main/java/gift/util/ErrorCode.java +++ b/src/main/java/gift/util/ErrorCode.java @@ -24,7 +24,8 @@ public enum ErrorCode { WISHLIST_NOT_FOUND("해당 위시리스트를 찾을 수 없습니다.", HttpStatus.BAD_REQUEST), WISHLIST_ALREADY_EXISTS("이미 위시리스트가 존재합니다.", HttpStatus.BAD_REQUEST), DUPLICATED_OPTION_NAME("이미 존재하는 옵션 이름입니다.", HttpStatus.BAD_REQUEST), - ; + PRODUCT_OPTION_NOT_FOUND("해당 상품 옵션을 찾을 수 없습니다.", HttpStatus.BAD_REQUEST), + NOT_USER_OWNED("해당 유저의 소유가 아닙니다.", HttpStatus.BAD_REQUEST); private final String message; private final HttpStatus status; From 6bdedeac8c0dcc79733cd161307e16adfcf59bb7 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 19:24:52 +0900 Subject: [PATCH 27/35] =?UTF-8?q?feat:=20kakao=20id=EB=8A=94=20=EC=97=AC?= =?UTF-8?q?=EA=B8=B0=20=EC=97=86=EB=8A=94=EB=8D=B0=20=EC=99=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=9C=EA=B1=B0=EC=A7=80=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/util/JwtUtil.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/gift/util/JwtUtil.java b/src/main/java/gift/util/JwtUtil.java index 9ae3ded09..59217f6c4 100644 --- a/src/main/java/gift/util/JwtUtil.java +++ b/src/main/java/gift/util/JwtUtil.java @@ -100,11 +100,6 @@ public String getUsername(String token) { return Jwts.parser().setSigningKey(generateKey()).build().parseClaimsJws(token).getBody().getSubject(); } - public Long getKakaoUserId(String token) { - Claims claims = Jwts.parser().setSigningKey(generateKey()).build().parseClaimsJws(token).getBody(); - return (Long) claims.get("kakaoUserId"); - } - public String getRole(String token) { Claims claims = Jwts.parser().setSigningKey(generateKey()).build().parseClaimsJws(token).getBody(); return (String) claims.get("role"); From fd51d4ecb88e4f6ec4ec58f711a7490a9c4bf577 Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 19:49:12 +0900 Subject: [PATCH 28/35] =?UTF-8?q?feat:=20=EA=B5=AC=EB=A7=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84=20-=20=EA=B5=AC=EB=A7=A4?= =?UTF-8?q?=EB=A5=BC=20=EC=88=98=ED=96=89=ED=95=A0=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?stoke=20--?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/application/PaymentService.java | 44 +++++++++++++++++++ .../payment/controller/PaymentController.java | 33 ++++++++++++++ .../product/application/ProductService.java | 10 +---- .../product/application/WishListService.java | 9 ++-- .../java/gift/product/domain/Product.java | 9 +--- .../gift/product/domain/ProductOption.java | 19 ++++++++ .../java/gift/product/domain/WishList.java | 6 ++- .../gift/product/domain/WishListProduct.java | 8 ++++ ...epository.java => WishListRepository.java} | 12 +++-- 9 files changed, 124 insertions(+), 26 deletions(-) create mode 100644 src/main/java/gift/payment/application/PaymentService.java create mode 100644 src/main/java/gift/payment/controller/PaymentController.java rename src/main/java/gift/product/infra/{WishLIstRepository.java => WishListRepository.java} (69%) diff --git a/src/main/java/gift/payment/application/PaymentService.java b/src/main/java/gift/payment/application/PaymentService.java new file mode 100644 index 000000000..773ed5759 --- /dev/null +++ b/src/main/java/gift/payment/application/PaymentService.java @@ -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(1L);// 일단 1개식 구매한다고 생각하자구..!!! + + productRepository.save(wishListProduct); + } + wishListRepository.delete(wishList); + } +} diff --git a/src/main/java/gift/payment/controller/PaymentController.java b/src/main/java/gift/payment/controller/PaymentController.java new file mode 100644 index 000000000..9762be81c --- /dev/null +++ b/src/main/java/gift/payment/controller/PaymentController.java @@ -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 + )); + } + +} diff --git a/src/main/java/gift/product/application/ProductService.java b/src/main/java/gift/product/application/ProductService.java index 73bd7e7a3..b968a397e 100644 --- a/src/main/java/gift/product/application/ProductService.java +++ b/src/main/java/gift/product/application/ProductService.java @@ -39,14 +39,8 @@ public Long saveProduct(CreateProductRequestDTO createProductRequestDTO) { public void addProductOption(Long id, CreateProductOptionRequestDTO createProductOptionRequestDTO) { Product product = productRepository.findById(id); - productRepository.findProductOptionsByProductId(id).forEach(productOption -> { - if (productOption.getName().equals(createProductOptionRequestDTO.getName())) { - throw new ProductException(ErrorCode.DUPLICATED_OPTION_NAME); - } - }); - - ProductOption productOption = productRepository.saveProductOption(new ProductOption(createProductOptionRequestDTO.getName(), - createProductOptionRequestDTO.getQuantity(), product)); + product.addProductOption(createProductOptionRequestDTO.getName(), createProductOptionRequestDTO.getQuantity()); + productRepository.save(product); } diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index 263def77f..a3dabc305 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -6,8 +6,7 @@ import gift.product.domain.WishListProduct; import gift.product.exception.ProductException; import gift.product.infra.ProductRepository; -import gift.product.infra.WishLIstRepository; -import gift.product.infra.WishListJpaRepository; +import gift.product.infra.WishListRepository; import gift.user.application.UserService; import gift.user.domain.User; import gift.util.ErrorCode; @@ -25,11 +24,11 @@ @Service public class WishListService { - private final WishLIstRepository wishListRepository; + private final WishListRepository wishListRepository; private final ProductRepository productRepository; private final UserService userService; - public WishListService(WishLIstRepository wishListRepository, ProductRepository productRepository, + public WishListService(WishListRepository wishListRepository, ProductRepository productRepository, UserService userService) { this.wishListRepository = wishListRepository; this.productRepository = productRepository; @@ -68,7 +67,7 @@ public void addProductToWishList(Long userId, Long wishlistId, Long productId, L } public WishList findById(Long id) { - return wishListRepository.findById(id).orElseThrow(() -> new ProductException(ErrorCode.WISHLIST_NOT_FOUND)); + return wishListRepository.findById(id); } @Transactional diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java index 47ae734a5..7eecc112b 100644 --- a/src/main/java/gift/product/domain/Product.java +++ b/src/main/java/gift/product/domain/Product.java @@ -71,19 +71,12 @@ public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } - public List getWishListProducts() { - return wishListProducts; - } - - public void setWishListProducts(List wishListProducts) { - this.wishListProducts = wishListProducts; - } - public void addProductOption(String name, Long quentity) { ProductOption productOption = new ProductOption(name, quentity, this); productOptions.add(productOption); } + public void addProductOption(ProductOption productOption) { productOptions.add(productOption); } diff --git a/src/main/java/gift/product/domain/ProductOption.java b/src/main/java/gift/product/domain/ProductOption.java index 6d1f011bb..376472226 100644 --- a/src/main/java/gift/product/domain/ProductOption.java +++ b/src/main/java/gift/product/domain/ProductOption.java @@ -15,6 +15,10 @@ public class ProductOption { @JoinColumn(name = "product_id") private Product product; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "wishlist_product_id") + private WishListProduct wishListProduct; + public ProductOption(String name, Long quentity, Product product) { this.name = name; this.quentity = quentity; @@ -24,7 +28,22 @@ public ProductOption(String name, Long quentity, Product product) { public ProductOption() { } + public Long getId() { + return id; + } + + public Long getQuentity() { + return quentity; + } + public String getName() { return name; } + + public void decreaseQuantity(Long quentity) { + if (this.quentity < quentity) { + throw new IllegalArgumentException("재고가 부족합니다."); + } + this.quentity -= quentity; + } } diff --git a/src/main/java/gift/product/domain/WishList.java b/src/main/java/gift/product/domain/WishList.java index 74674914f..259ef1886 100644 --- a/src/main/java/gift/product/domain/WishList.java +++ b/src/main/java/gift/product/domain/WishList.java @@ -19,7 +19,7 @@ public class WishList { @JoinColumn(name = "user_id") private User user; - @OneToMany(mappedBy = "wishList", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "wishList") private List wishListProducts = new ArrayList<>(); private LocalDateTime createdAt; @@ -71,4 +71,8 @@ public LocalDateTime getCreatedAt() { public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + + public void clearProducts() { + wishListProducts.clear(); + } } diff --git a/src/main/java/gift/product/domain/WishListProduct.java b/src/main/java/gift/product/domain/WishListProduct.java index a78ffd3a6..406f30535 100644 --- a/src/main/java/gift/product/domain/WishListProduct.java +++ b/src/main/java/gift/product/domain/WishListProduct.java @@ -2,6 +2,9 @@ import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + @Entity(name = "wishlist_product") public class WishListProduct { @@ -54,4 +57,9 @@ public Product getProduct() { public void setProduct(Product product) { this.product = product; } + + + public ProductOption getProductOption() { + return productOption; + } } diff --git a/src/main/java/gift/product/infra/WishLIstRepository.java b/src/main/java/gift/product/infra/WishListRepository.java similarity index 69% rename from src/main/java/gift/product/infra/WishLIstRepository.java rename to src/main/java/gift/product/infra/WishListRepository.java index fbfe71eee..d212d6261 100644 --- a/src/main/java/gift/product/infra/WishLIstRepository.java +++ b/src/main/java/gift/product/infra/WishListRepository.java @@ -8,10 +8,10 @@ import java.util.Optional; @Repository -public class WishLIstRepository { +public class WishListRepository { private final WishListJpaRepository wishListJpaRepository; - public WishLIstRepository(WishListJpaRepository wishListJpaRepository) { + public WishListRepository(WishListJpaRepository wishListJpaRepository) { this.wishListJpaRepository = wishListJpaRepository; } @@ -28,7 +28,11 @@ public void save(WishList wishList) { wishListJpaRepository.save(wishList); } - public Optional findById(Long id) { - return wishListJpaRepository.findById(id); + public WishList findById(Long id) { + return wishListJpaRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 ID의 위시리스트가 존재하지 않습니다.")); + } + + public void delete(WishList wishList) { + wishListJpaRepository.delete(wishList); } } From 1e8c8a37f66194c90c6c54541c4101025522460f Mon Sep 17 00:00:00 2001 From: stopmin Date: Sat, 20 Jul 2024 19:58:54 +0900 Subject: [PATCH 29/35] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EA=B0=9C=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/payment/application/PaymentService.java | 2 +- .../gift/product/application/WishListService.java | 4 ++-- .../java/gift/product/domain/WishListProduct.java | 12 ++++++++---- .../product/presentation/WishListController.java | 6 +++--- src/main/resources/db/schema.sql | 1 + 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/gift/payment/application/PaymentService.java b/src/main/java/gift/payment/application/PaymentService.java index 773ed5759..de873f6e8 100644 --- a/src/main/java/gift/payment/application/PaymentService.java +++ b/src/main/java/gift/payment/application/PaymentService.java @@ -35,7 +35,7 @@ public void processPayment(Long userId, Long wishListId) { for (WishListProduct product : wishList.getWishListProducts()) { Product wishListProduct = product.getProduct(); ProductOption productOption = product.getProductOption(); - productOption.decreaseQuantity(1L);// 일단 1개식 구매한다고 생각하자구..!!! + productOption.decreaseQuantity(product.getQuantity()); productRepository.save(wishListProduct); } diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index a3dabc305..06310a00e 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -51,7 +51,7 @@ public Page getProductsInWishList(Long userId, int page, int size, Str } @Transactional - public void addProductToWishList(Long userId, Long wishlistId, Long productId, Long optionId) { + 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); @@ -60,7 +60,7 @@ public void addProductToWishList(Long userId, Long wishlistId, Long productId, L Product product = productRepository.findById(productId); ProductOption productOption = productRepository.getProductWithOption(productId, optionId); - wishList.addWishListProduct(new WishListProduct(wishList, product, productOption)); + wishList.addWishListProduct(new WishListProduct(wishList, product, productOption, quantity)); wishListRepository.save(wishList); diff --git a/src/main/java/gift/product/domain/WishListProduct.java b/src/main/java/gift/product/domain/WishListProduct.java index 406f30535..53c40cfde 100644 --- a/src/main/java/gift/product/domain/WishListProduct.java +++ b/src/main/java/gift/product/domain/WishListProduct.java @@ -2,9 +2,6 @@ import jakarta.persistence.*; -import java.util.ArrayList; -import java.util.List; - @Entity(name = "wishlist_product") public class WishListProduct { @@ -25,13 +22,16 @@ public class WishListProduct { @JoinColumn(name = "product_option_id") private ProductOption productOption; + private Long quantity; + public WishListProduct() { } - public WishListProduct(WishList wishList, Product product, ProductOption productOption) { + public WishListProduct(WishList wishList, Product product, ProductOption productOption, Long quantity) { this.wishList = wishList; this.product = product; this.productOption = productOption; + this.quantity = quantity; } public Long getId() { @@ -62,4 +62,8 @@ public void setProduct(Product product) { public ProductOption getProductOption() { return productOption; } + + public Long getQuantity() { + return quantity; + } } diff --git a/src/main/java/gift/product/presentation/WishListController.java b/src/main/java/gift/product/presentation/WishListController.java index 4e77a8d6e..c1aef60c8 100644 --- a/src/main/java/gift/product/presentation/WishListController.java +++ b/src/main/java/gift/product/presentation/WishListController.java @@ -46,11 +46,11 @@ public ResponseEntity createWishList(@PathVariable Long userId) { } @JwtAuthenticated - @PostMapping("/{wishListId}/add/{productId}/{optionId}") - public ResponseEntity addProductToWishList(@PathVariable Long wishListId, @PathVariable Long productId, @PathVariable Long optionId) { + @PostMapping("/{wishListId}/add/{productId}/{optionId}/{quentity}") + public ResponseEntity addProductToWishList(@PathVariable Long wishListId, @PathVariable Long productId, @PathVariable Long optionId, @PathVariable int quentity) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Long userId = Long.valueOf(authentication.getName()); - wishListService.addProductToWishList(userId, wishListId, productId, optionId); + wishListService.addProductToWishList(userId, wishListId, productId, optionId, quentity); return ResponseEntity.ok(new CommonResponse<>(null, "위시리스트에 제품 추가 성공", true)); } diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 6d58c7cff..f8cb18c54 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -48,6 +48,7 @@ CREATE TABLE wishlist_product wishlist_product_id BIGINT AUTO_INCREMENT PRIMARY KEY, wishlist_id BIGINT NOT NULL, product_id BIGINT NOT NULL, + quentity BIGINT NOT NULL, FOREIGN KEY (wishlist_id) REFERENCES wishlist (wishlist_id), FOREIGN KEY (product_id) REFERENCES product (product_id) ); From d17a02aa0f53107c869f0cb4af84ba18ef841498 Mon Sep 17 00:00:00 2001 From: stopmin Date: Mon, 22 Jul 2024 10:28:26 +0900 Subject: [PATCH 30/35] =?UTF-8?q?feat:=20quentity=20type=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20Long?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/product/application/WishListService.java | 2 +- src/main/java/gift/product/presentation/WishListController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index 06310a00e..c581b16e1 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -51,7 +51,7 @@ public Page getProductsInWishList(Long userId, int page, int size, Str } @Transactional - public void addProductToWishList(Long userId, Long wishlistId, Long productId, Long optionId, int quantity) { + public void addProductToWishList(Long userId, Long wishlistId, Long productId, Long optionId, Long quantity) { WishList wishList = findById(wishlistId); if (!Objects.equals(wishList.getUser().getId(), userId)) { throw new ProductException(ErrorCode.NOT_USER_OWNED); diff --git a/src/main/java/gift/product/presentation/WishListController.java b/src/main/java/gift/product/presentation/WishListController.java index c1aef60c8..1c2f76372 100644 --- a/src/main/java/gift/product/presentation/WishListController.java +++ b/src/main/java/gift/product/presentation/WishListController.java @@ -47,7 +47,7 @@ public ResponseEntity createWishList(@PathVariable Long userId) { @JwtAuthenticated @PostMapping("/{wishListId}/add/{productId}/{optionId}/{quentity}") - public ResponseEntity addProductToWishList(@PathVariable Long wishListId, @PathVariable Long productId, @PathVariable Long optionId, @PathVariable int quentity) { + public ResponseEntity addProductToWishList(@PathVariable Long wishListId, @PathVariable Long productId, @PathVariable Long optionId, @PathVariable Long quentity) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Long userId = Long.valueOf(authentication.getName()); wishListService.addProductToWishList(userId, wishListId, productId, optionId, quentity); From fbb2ed849f97ad75b2565352fe0ade417b5ad81d Mon Sep 17 00:00:00 2001 From: stopmin Date: Mon, 22 Jul 2024 10:30:24 +0900 Subject: [PATCH 31/35] =?UTF-8?q?feat:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20read=20only=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/product/application/ProductService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/gift/product/application/ProductService.java b/src/main/java/gift/product/application/ProductService.java index b968a397e..a7329e4b3 100644 --- a/src/main/java/gift/product/application/ProductService.java +++ b/src/main/java/gift/product/application/ProductService.java @@ -10,7 +10,6 @@ import java.util.List; @Service -@Transactional(readOnly = true) public class ProductService { private final ProductRepository productRepository; From 5d4ad4e84b4e5e59e9b7958d62bcc1ce7718a2a1 Mon Sep 17 00:00:00 2001 From: stopmin Date: Mon, 22 Jul 2024 15:33:06 +0900 Subject: [PATCH 32/35] =?UTF-8?q?refactor:=20Category=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=EC=A0=95=20-=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EB=B6=84=ED=95=B4=20x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/product/application/CategoryService.java | 2 +- src/main/java/gift/product/domain/Category.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/gift/product/application/CategoryService.java b/src/main/java/gift/product/application/CategoryService.java index 708af38ae..c62f5e6d7 100644 --- a/src/main/java/gift/product/application/CategoryService.java +++ b/src/main/java/gift/product/application/CategoryService.java @@ -25,7 +25,7 @@ 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()); + Category category = new Category(request); categoryRepository.save(category); } diff --git a/src/main/java/gift/product/domain/Category.java b/src/main/java/gift/product/domain/Category.java index 22e75e7f0..f1b205ccd 100644 --- a/src/main/java/gift/product/domain/Category.java +++ b/src/main/java/gift/product/domain/Category.java @@ -29,11 +29,11 @@ public class Category { public Category() { } - public Category(String name, String description, String imageUrl, String color) { - this.name = name; - this.description = description; - this.imageUrl = imageUrl; - this.color = color; + public Category(CreateCategoryRequest request) { + this.name = request.getName(); + this.description = request.getDescription(); + this.imageUrl = request.getImageUrl(); + this.color = request.getColor(); } public Long getId() { From 30239d4c4b551162d96c9d944f9bafb68fb94fd3 Mon Sep 17 00:00:00 2001 From: stopmin Date: Mon, 22 Jul 2024 15:34:30 +0900 Subject: [PATCH 33/35] =?UTF-8?q?refactor:=20ProductOption=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EC=88=98=EC=A0=95=20-=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EB=B6=84=ED=95=B4=20x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/product/application/ProductService.java | 2 +- src/main/java/gift/product/domain/Product.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/gift/product/application/ProductService.java b/src/main/java/gift/product/application/ProductService.java index a7329e4b3..874b5642f 100644 --- a/src/main/java/gift/product/application/ProductService.java +++ b/src/main/java/gift/product/application/ProductService.java @@ -38,7 +38,7 @@ public Long saveProduct(CreateProductRequestDTO createProductRequestDTO) { public void addProductOption(Long id, CreateProductOptionRequestDTO createProductOptionRequestDTO) { Product product = productRepository.findById(id); - product.addProductOption(createProductOptionRequestDTO.getName(), createProductOptionRequestDTO.getQuantity()); + product.addProductOption(createProductOptionRequestDTO); productRepository.save(product); } diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java index 7eecc112b..98d162bf1 100644 --- a/src/main/java/gift/product/domain/Product.java +++ b/src/main/java/gift/product/domain/Product.java @@ -71,8 +71,8 @@ public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } - public void addProductOption(String name, Long quentity) { - ProductOption productOption = new ProductOption(name, quentity, this); + public void addProductOption(CreateProductOptionRequestDTO createProductOptionRequestDTO) { + ProductOption productOption = new ProductOption(createProductOptionRequestDTO.getName(), createProductOptionRequestDTO.getQuantity(), this); productOptions.add(productOption); } From 2c1f2ebb2eacca9cac65f0c483cadd0e19fa1a0f Mon Sep 17 00:00:00 2001 From: stopmin Date: Mon, 22 Jul 2024 15:37:00 +0900 Subject: [PATCH 34/35] =?UTF-8?q?refactor:=20Product=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=EC=A0=95=20-=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EB=B6=84=ED=95=B4=20x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/product/application/ProductService.java | 3 +-- src/main/java/gift/product/domain/Product.java | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/gift/product/application/ProductService.java b/src/main/java/gift/product/application/ProductService.java index 874b5642f..7cd75c1ae 100644 --- a/src/main/java/gift/product/application/ProductService.java +++ b/src/main/java/gift/product/application/ProductService.java @@ -27,8 +27,7 @@ public ProductService(ProductRepository productRepository, CategoryService categ @Transactional public Long saveProduct(CreateProductRequestDTO createProductRequestDTO) { Category category = categoryService.getCategoryByName(createProductRequestDTO.getCategory()); - Product product = new Product(createProductRequestDTO.getName(), createProductRequestDTO.getPrice(), - createProductRequestDTO.getImageUrl(), category); + Product product = new Product(createProductRequestDTO, category); validateProduct(product); return productRepository.save(product).getId(); diff --git a/src/main/java/gift/product/domain/Product.java b/src/main/java/gift/product/domain/Product.java index 98d162bf1..533414a70 100644 --- a/src/main/java/gift/product/domain/Product.java +++ b/src/main/java/gift/product/domain/Product.java @@ -32,10 +32,10 @@ public class Product { public Product() { } - public Product(String name, Double price, String imageUrl, Category category) { - this.name = name; - this.price = price; - this.imageUrl = imageUrl; + public Product(CreateProductRequestDTO createProductRequestDTO, Category category) { + this.name = createProductRequestDTO.getName(); + this.price = createProductRequestDTO.getPrice(); + this.imageUrl = createProductRequestDTO.getImageUrl(); this.category = category; } From cffda01aa779cab20e0172779a40a0574a4fa0f1 Mon Sep 17 00:00:00 2001 From: stopmin Date: Mon, 22 Jul 2024 15:41:55 +0900 Subject: [PATCH 35/35] =?UTF-8?q?refactor:=20WishList=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=EC=A0=95=20-=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EB=B6=84=ED=95=B4=20x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/WishListService.java | 17 ++++----- .../product/domain/AddWishListRequest.java | 37 +++++++++++++++++++ .../presentation/WishListController.java | 4 +- 3 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 src/main/java/gift/product/domain/AddWishListRequest.java diff --git a/src/main/java/gift/product/application/WishListService.java b/src/main/java/gift/product/application/WishListService.java index c581b16e1..fc440f5a8 100644 --- a/src/main/java/gift/product/application/WishListService.java +++ b/src/main/java/gift/product/application/WishListService.java @@ -1,9 +1,6 @@ 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.domain.*; import gift.product.exception.ProductException; import gift.product.infra.ProductRepository; import gift.product.infra.WishListRepository; @@ -51,16 +48,16 @@ public Page getProductsInWishList(Long userId, int page, int size, Str } @Transactional - public void addProductToWishList(Long userId, Long wishlistId, Long productId, Long optionId, Long quantity) { - WishList wishList = findById(wishlistId); - if (!Objects.equals(wishList.getUser().getId(), userId)) { + public void addProductToWishList(AddWishListRequest request) { + WishList wishList = findById(request.getWishlistId()); + if (!Objects.equals(wishList.getUser().getId(), request.getUserId())) { throw new ProductException(ErrorCode.NOT_USER_OWNED); } - Product product = productRepository.findById(productId); - ProductOption productOption = productRepository.getProductWithOption(productId, optionId); + Product product = productRepository.findById(request.getProductId()); + ProductOption productOption = productRepository.getProductWithOption(request.getProductId(), request.getOptionId()); - wishList.addWishListProduct(new WishListProduct(wishList, product, productOption, quantity)); + wishList.addWishListProduct(new WishListProduct(wishList, product, productOption, request.getQuantity())); wishListRepository.save(wishList); diff --git a/src/main/java/gift/product/domain/AddWishListRequest.java b/src/main/java/gift/product/domain/AddWishListRequest.java new file mode 100644 index 000000000..e5ce3816f --- /dev/null +++ b/src/main/java/gift/product/domain/AddWishListRequest.java @@ -0,0 +1,37 @@ +package gift.product.domain; + +public class AddWishListRequest { + private Long userId; + private Long wishlistId; + private Long productId; + private Long optionId; + private Long quantity; + + public Long getUserId() { + return userId; + } + + public Long getWishlistId() { + return wishlistId; + } + + public Long getProductId() { + return productId; + } + + public Long getOptionId() { + return optionId; + } + + public Long getQuantity() { + return quantity; + } + + public AddWishListRequest(Long userId, Long wishlistId, Long productId, Long optionId, Long quantity) { + this.userId = userId; + this.wishlistId = wishlistId; + this.productId = productId; + this.optionId = optionId; + this.quantity = quantity; + } +} diff --git a/src/main/java/gift/product/presentation/WishListController.java b/src/main/java/gift/product/presentation/WishListController.java index 1c2f76372..6b85d4d1f 100644 --- a/src/main/java/gift/product/presentation/WishListController.java +++ b/src/main/java/gift/product/presentation/WishListController.java @@ -2,6 +2,7 @@ import gift.product.application.WishListService; +import gift.product.domain.AddWishListRequest; import gift.product.domain.WishList; import gift.util.CommonResponse; import gift.util.annotation.JwtAuthenticated; @@ -50,8 +51,7 @@ public ResponseEntity createWishList(@PathVariable Long userId) { public ResponseEntity addProductToWishList(@PathVariable Long wishListId, @PathVariable Long productId, @PathVariable Long optionId, @PathVariable Long quentity) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Long userId = Long.valueOf(authentication.getName()); - wishListService.addProductToWishList(userId, wishListId, productId, optionId, quentity); - + wishListService.addProductToWishList(new AddWishListRequest(userId, wishListId, productId, optionId, quentity)); return ResponseEntity.ok(new CommonResponse<>(null, "위시리스트에 제품 추가 성공", true)); }