diff --git a/README.md b/README.md index 75f82d8fa..557565400 100644 --- a/README.md +++ b/README.md @@ -1 +1,34 @@ -# spring-gift-enhancement \ No newline at end of file +# spring-gift-enhancement + +## Step1 +- 기능 요구사항 + - 상품 정보에 카테고리를 추가한다. 상품과 카테고리 모델 간의 관계를 고려하여 설계하고 구현한다. + - 상품에는 항상 하나의 카테고리가 있어야 한다. + - 상품 카테고리는 수정할 수 있다. + - 관리자 화면에서 상품을 추가할 때 카테고리를 지정할 수 있다. + - 카테고리는 1차 카테고리만 있으며 2차 카테고리는 고려하지 않는다. + - 카테고리의 예시는 아래와 같다. + - 교환권, 상품권, 뷰티, 패션, 식품, 리빙/도서, 레저/스포츠, 아티스트/캐릭터, 유아동/반려, 디지털/가전, 카카오프렌즈, 트렌드 선물, 백화점, ... + - 아래 예시와 같이 HTTP 메시지를 주고받도록 구현한다. + + - Request +
+       GET /api/categories HTTP/1.1 
+    
+ + - Response +
+    
+       HTTP/1.1 200 
+       Content-Type: application/json
+       [
+         {
+           "id": 91,
+           "name": "교환권",
+           "color": "#6c95d1",
+           "imageUrl": "https://gift-s.kakaocdn.net/dn/gift/images/m640/dimm_theme.png",
+           "description": ""
+         }
+       ]
+    
+    
diff --git a/build.gradle b/build.gradle index 4c0637e25..7f478a05e 100644 --- a/build.gradle +++ b/build.gradle @@ -19,8 +19,7 @@ repositories { dependencies { // MySQL - runtimeOnly 'com.mysql:mysql-connector-j' // MySQL 드라이버 의존성 추가 - testRuntimeOnly 'mysql:mysql-connector-java' + runtimeOnly 'mysql:mysql-connector-java:8.0.30' // MySQL 드라이버 의존성 추가 // JPA implementation 'org.springframework.boot:spring-boot-starter-data-jpa' diff --git a/src/main/java/gift/controller/AdminController.java b/src/main/java/gift/controller/AdminController.java index 7d71f74c5..a5c9e4267 100644 --- a/src/main/java/gift/controller/AdminController.java +++ b/src/main/java/gift/controller/AdminController.java @@ -2,16 +2,12 @@ import gift.domain.ProductDTO; import gift.service.ProductService; -import gift.service.ProductServiceStatus; import jakarta.validation.Valid; -import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/admin/products") @@ -36,25 +32,19 @@ public String addProductForm(Model model) { } @PostMapping("/add") - public ModelAndView addProduct(@ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) { + public String addProduct(@ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) { if (bindingResult.hasErrors()) { - return new ModelAndView("create"); + return "create"; } - ProductServiceStatus response = productService.createProduct(productDTO); - if (response == ProductServiceStatus.SUCCESS) { - return new ModelAndView("redirect:/admin/products"); - } - - ModelAndView mav = new ModelAndView("create"); - mav.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); - mav.addObject("error", "상품 추가 실패"); - return mav; + productService.createProduct(productDTO); + return "redirect:/admin/products"; } @GetMapping("/update/{id}") public String editProductForm(@PathVariable Long id, Model model) { - Optional productDTO = productService.getProduct(id); + var productDTO = productService.getProduct(id) + .orElse(null); // Optional에서 직접 null을 반환 if (productDTO == null) { return "redirect:/admin/products"; } @@ -63,28 +53,18 @@ public String editProductForm(@PathVariable Long id, Model model) { } @PostMapping("/update/{id}") - public ModelAndView editProduct(@PathVariable Long id, @ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) { + public String editProduct(@PathVariable Long id, @ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) { if (bindingResult.hasErrors()) { - return new ModelAndView("update"); + return "update"; } - ProductServiceStatus response = productService.editProduct(id, productDTO); - if (response == ProductServiceStatus.SUCCESS) { - return new ModelAndView("redirect:/admin/products"); - } - - ModelAndView mav = new ModelAndView("update"); - mav.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); - mav.addObject("error", "상품 수정 실패"); - return mav; + productService.editProduct(id, productDTO); + return "redirect:/admin/products"; } @PostMapping("/delete/{id}") public String deleteProduct(@PathVariable Long id) { - ProductServiceStatus response = productService.deleteProduct(id); - if (response == ProductServiceStatus.SUCCESS) { - return "redirect:/admin/products"; // 성공적으로 삭제 후 목록 페이지로 리다이렉트 - } - return "error"; // 실패 시 에러 페이지로 리다이렉트 + productService.deleteProduct(id); + return "redirect:/admin/products"; } } diff --git a/src/main/java/gift/controller/CategoryController.java b/src/main/java/gift/controller/CategoryController.java new file mode 100644 index 000000000..cf4614b45 --- /dev/null +++ b/src/main/java/gift/controller/CategoryController.java @@ -0,0 +1,60 @@ +package gift.controller; + +import gift.domain.CategoryDTO; +import gift.service.CategoryService; +import jakarta.validation.Valid; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/category") +public class CategoryController { + private final CategoryService categoryService; + + @Autowired + public CategoryController(CategoryService categoryService){ + this.categoryService = categoryService; + } + + @GetMapping + public ResponseEntity> getCategory() { + List response = categoryService.getCategories(); + return new ResponseEntity<>(response, HttpStatus.OK); + } + + @GetMapping("/{id}") + public ResponseEntity getCategoryById(@PathVariable Long id) throws Exception { + CategoryDTO response = categoryService.getCategory(id); + return new ResponseEntity<>(response, HttpStatus.OK); + } + + @PutMapping + public ResponseEntity addCategory(@Valid @RequestBody CategoryDTO category) { + CategoryDTO createdCategory = categoryService.createCategory(category); + return new ResponseEntity<>(createdCategory, HttpStatus.CREATED); + } + + @PostMapping("/{id}") + public ResponseEntity updateCategory(@PathVariable Long id, @Valid @RequestBody CategoryDTO category) { + CategoryDTO updatedCategory = categoryService.updateCategory(id, category); + return new ResponseEntity<>(updatedCategory, HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCategory(@PathVariable Long id) { + categoryService.deleteCategory(id); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + +} diff --git a/src/main/java/gift/controller/MemberController.java b/src/main/java/gift/controller/MemberController.java index 5e06d90df..9d77837d2 100644 --- a/src/main/java/gift/controller/MemberController.java +++ b/src/main/java/gift/controller/MemberController.java @@ -4,7 +4,6 @@ import gift.domain.MemberDTO; import gift.service.JwtUtil; import gift.service.MemberService; -import gift.service.MemberServiceStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -28,29 +27,15 @@ public MemberController(MemberService memberService, JwtUtil jwtUtil) { @PostMapping("/register") public ResponseEntity registerUser(@RequestBody MemberDTO memberDTO) { - MemberServiceStatus status = memberService.save(memberDTO); - - if (status == MemberServiceStatus.EMAIL_ALREADY_EXISTS) { - return ResponseEntity.status(HttpStatus.CONFLICT).body(Collections.singletonMap("error", "Email already exists")); - } - - return ResponseEntity.ok().body(Collections.singletonMap("message", "Success")); + memberService.save(memberDTO); + return new ResponseEntity<>(Collections.singletonMap("status", "SUCCESS"), HttpStatus.OK); } @PostMapping("/login") public ResponseEntity loginUser(@RequestBody MemberDTO memberDTO) { MemberEntity authenticatedMember = memberService.authenticateToken(memberDTO); - - if (authenticatedMember == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Collections.singletonMap("error", "Invalid email or password")); - } - - Long userId = authenticatedMember.getId(); - - // 토큰 생성 및 응답 - String token = jwtUtil.generateToken(authenticatedMember.getEmail(), userId); - Map responseBody = Collections.singletonMap("token", token); - return ResponseEntity.ok().body(responseBody); + String token = jwtUtil.generateToken(authenticatedMember.getEmail(), authenticatedMember.getId()); + Map response = Collections.singletonMap("token", token); + return new ResponseEntity<>(response, HttpStatus.OK); } - } \ No newline at end of file diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java index 48922f571..387b1684e 100644 --- a/src/main/java/gift/controller/ProductController.java +++ b/src/main/java/gift/controller/ProductController.java @@ -2,7 +2,6 @@ import gift.domain.ProductDTO; import gift.service.ProductService; -import gift.service.ProductServiceStatus; import jakarta.validation.constraints.Min; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; @@ -25,9 +24,9 @@ private ProductController(ProductService productService) { // 상품 추가(Create) @PostMapping - public ResponseEntity addProduct(@RequestBody ProductDTO productDTO) { - ProductServiceStatus response = productService.createProduct(productDTO); - return new ResponseEntity<>(response, HttpStatus.CREATED); + public ResponseEntity addProduct(@RequestBody ProductDTO productDTO) { + productService.createProduct(productDTO); + return new ResponseEntity<>(HttpStatus.CREATED); } // 단일 상품 조회(Read) @@ -54,13 +53,15 @@ public ResponseEntity> selectAllProducts( // 상품 수정(Update) @PutMapping("/{id}") - public ProductServiceStatus updateProduct(@PathVariable Long id, @RequestBody ProductDTO productDTO) { - return productService.editProduct(id, productDTO); + public ResponseEntity updateProduct(@PathVariable Long id, @RequestBody ProductDTO productDTO) { + productService.editProduct(id, productDTO); + return ResponseEntity.ok("상품이 추기 되었습니다."); } // 상품 삭제(Delete) @DeleteMapping("/{id}") - public ProductServiceStatus deleteProduct(@PathVariable Long id) { - return productService.deleteProduct(id); + public ResponseEntity deleteProduct(@PathVariable Long id) { + productService.deleteProduct(id); + return ResponseEntity.ok("상품이 삭제 되었습니다."); } } \ No newline at end of file diff --git a/src/main/java/gift/domain/CategoryDTO.java b/src/main/java/gift/domain/CategoryDTO.java new file mode 100644 index 000000000..0031cf4d1 --- /dev/null +++ b/src/main/java/gift/domain/CategoryDTO.java @@ -0,0 +1,49 @@ +package gift.domain; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +public class CategoryDTO { + private Long id; + + @NotBlank(message = "카테고리 이름은 필수 입력 항목입니다.") + private String name; + + @Pattern(regexp = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", message = "올바른 HEX 색상 코드로 입력해 주세요") + private String color; + + @Pattern(regexp = "^https?://.*$", message = "올바른 이미지 URL 형식으로 입력해 주세요") + private String imageUrl; + + @Size(max = 255, message = "설명은 최대 255글자 이하로 입력해 주세요") + private String description; + + public CategoryDTO(Long id, String name, String color, String imageUrl, String description) { + this.id = id; + this.name = name; + this.color = color; + this.imageUrl = imageUrl; + this.description = description; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getColor() { + return color; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/gift/domain/ProductDTO.java b/src/main/java/gift/domain/ProductDTO.java index 018af99cb..5704bb206 100644 --- a/src/main/java/gift/domain/ProductDTO.java +++ b/src/main/java/gift/domain/ProductDTO.java @@ -15,13 +15,15 @@ public class ProductDTO { @Pattern(regexp = "^(?!.*카카오).*$", message = "'카카오'가 포함된 문구는 담당 MD와 협의 후 사용 바랍니다.") private String name; - @PositiveOrZero(message = "가격은 0 이상의 숫자를 입력해 주세요.") private int price; @Pattern(regexp = "^https?://.*$", message = "올바른 이미지 URL 형식으로 입력해 주세요") private String imageUrl; + @NotNull(message = "카테고리는 필수 항목입니다.") + private Long categoryId; + public ProductDTO(){} /** @@ -31,12 +33,14 @@ public ProductDTO(){} * @param name 상품의 이름 * @param price 상품의 가격 * @param imageUrl 상품의 이미지 주소 + * @param categoryId 카테고리 고유의 ID */ - public ProductDTO(Long id, String name, int price, String imageUrl){ + public ProductDTO(Long id, String name, int price, String imageUrl, Long categoryId){ this.id = id; this.name = name; this.price = price; this.imageUrl = imageUrl; + this.categoryId = categoryId; } @@ -57,6 +61,8 @@ public String getImageUrl() { return imageUrl; } + public Long getCategoryId() {return categoryId;} + // Setter 메서드들 public void setId(Long id) { this.id = id; @@ -73,4 +79,6 @@ public void setPrice(int price) { public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } + + public void setCategoryId(Long categoryId) {this.categoryId = categoryId;} } \ No newline at end of file diff --git a/src/main/java/gift/entity/CategoryEntity.java b/src/main/java/gift/entity/CategoryEntity.java new file mode 100644 index 000000000..a26507cc7 --- /dev/null +++ b/src/main/java/gift/entity/CategoryEntity.java @@ -0,0 +1,96 @@ +package gift.entity; + +import gift.domain.CategoryDTO; +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.OneToMany; +import jakarta.persistence.Table; +import java.util.List; + +@Entity +@Table(name = "category") +public class CategoryEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false, length = 7) + private String color; + + @Column(name = "image_url", nullable = false) + private String imageUrl; + + @Column(nullable = false) + private String description; + + @OneToMany(mappedBy = "category", fetch = FetchType.LAZY, + cascade = CascadeType.ALL, orphanRemoval = true) + private List productEntities; + + public CategoryEntity() {} + + public CategoryEntity(String name, String color, String imageUrl, String description) { + this.name = name; + this.color = color; + this.imageUrl = imageUrl; + this.description = description; + } + + public CategoryEntity(CategoryDTO categoryDTO) { + this.id = categoryDTO.getId(); + this.name = categoryDTO.getName(); + this.color = categoryDTO.getColor(); + this.imageUrl = categoryDTO.getImageUrl(); + this.description = categoryDTO.getDescription(); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getColor() { + return color; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getDescription() { + return description; + } + + public void setId(Long id) {this.id = id;} + + public void setName(String name) {this.name = name;} + + public void setColor(String Color) {this.color = color;} + + public void setImageUrl(String imageUrl) {this.imageUrl = imageUrl;} + + public void setDescription(String description) {this.description = description;} + + public static CategoryDTO toDTO(CategoryEntity category) { + return new CategoryDTO(category.getId(), category.getName(), category.getColor(), category.getImageUrl(), category.getDescription()); + } + + public void update(CategoryDTO categoryDTO) { + this.setName(categoryDTO.getName()); + this.setColor(categoryDTO.getColor()); + this.setImageUrl(categoryDTO.getImageUrl()); + this.setDescription(categoryDTO.getDescription()); + } + +} diff --git a/src/main/java/gift/entity/MemberEntity.java b/src/main/java/gift/entity/MemberEntity.java index d4d375846..660619485 100644 --- a/src/main/java/gift/entity/MemberEntity.java +++ b/src/main/java/gift/entity/MemberEntity.java @@ -25,7 +25,7 @@ public class MemberEntity { @OneToMany(mappedBy = "userEntity") private List wishListEntities; - protected MemberEntity() {} + public MemberEntity() {} public MemberEntity(String email, String password) { this.email = email; diff --git a/src/main/java/gift/entity/ProductEntity.java b/src/main/java/gift/entity/ProductEntity.java index e4758e920..55e7b7a03 100644 --- a/src/main/java/gift/entity/ProductEntity.java +++ b/src/main/java/gift/entity/ProductEntity.java @@ -1,5 +1,6 @@ package gift.entity; +import gift.domain.ProductDTO; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -7,6 +8,8 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.List; @@ -18,7 +21,7 @@ public class ProductEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) + @Column(nullable = false, length = 15) private String name; @Column(nullable = false) @@ -30,12 +33,19 @@ public class ProductEntity { @OneToMany(mappedBy = "productEntity", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List wishListEntities; - public ProductEntity(Long id, String name, int price, String imageUrl) {} + @ManyToOne(targetEntity = CategoryEntity.class, fetch = FetchType.LAZY) + @JoinColumn(name = "category_id") + private CategoryEntity category; + + public ProductEntity() {} - public ProductEntity(String name, int price, String imageUrl) { + public ProductEntity(Long id, String name, int price, String imageUrl, CategoryEntity category) {} + + public ProductEntity(String name, int price, String imageUrl, CategoryEntity category) { this.name = name; this.price = price; this.imageUrl = imageUrl; + this.category = category; } public Long getId() { @@ -57,4 +67,17 @@ public String getImageUrl(){ public List getWishListEntities() { return wishListEntities; } + + public CategoryEntity getCategory() {return category;} + + public static ProductDTO toDTO(ProductEntity productEntity) { + return new ProductDTO( + productEntity.getId(), + productEntity.getName(), + productEntity.getPrice(), + productEntity.getImageUrl(), + productEntity.getCategory().getId() + ); + } + } \ No newline at end of file diff --git a/src/main/java/gift/entity/WishListEntity.java b/src/main/java/gift/entity/WishListEntity.java index 58abd5322..1e6c4b3e8 100644 --- a/src/main/java/gift/entity/WishListEntity.java +++ b/src/main/java/gift/entity/WishListEntity.java @@ -1,5 +1,6 @@ package gift.entity; +import gift.domain.WishListDTO; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -37,4 +38,8 @@ public ProductEntity getProductEntity() { public MemberEntity getUserEntity() { return userEntity; } + + public static WishListDTO toDTO(WishListEntity wishListEntity) { + return new WishListDTO(wishListEntity.getProductEntity().getId(), wishListEntity.getUserEntity().getId()); + } } diff --git a/src/main/java/gift/repository/CategoryRepository.java b/src/main/java/gift/repository/CategoryRepository.java new file mode 100644 index 000000000..118aa72fa --- /dev/null +++ b/src/main/java/gift/repository/CategoryRepository.java @@ -0,0 +1,10 @@ +package gift.repository; + +import gift.entity.CategoryEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CategoryRepository extends JpaRepository { + +} diff --git a/src/main/java/gift/service/CategoryService.java b/src/main/java/gift/service/CategoryService.java new file mode 100644 index 000000000..80541326c --- /dev/null +++ b/src/main/java/gift/service/CategoryService.java @@ -0,0 +1,58 @@ +package gift.service; + +import gift.domain.CategoryDTO; +import gift.entity.CategoryEntity; +import gift.repository.CategoryRepository; +import jakarta.transaction.Transactional; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class CategoryService { + private final CategoryRepository categoryRepository; + + @Autowired + public CategoryService(CategoryRepository categoryRepository) { + this.categoryRepository = categoryRepository; + } + + // 카테고리 전체 조회 + public List getCategories() { + List categories = categoryRepository.findAll(); + return categories.stream().map(CategoryEntity::toDTO).collect(Collectors.toList()); + } + + // 카테고리 단일 조회 + public CategoryDTO getCategory(Long id) { + CategoryEntity category = categoryRepository.findById(id) + .orElseThrow(() -> new RuntimeException("카테고리를 찾을 수 없습니다")); + return category.toDTO(category); + } + + // 카테고리 생성 + @Transactional + public CategoryDTO createCategory(CategoryDTO categoryDTO) { + CategoryEntity category = new CategoryEntity(categoryDTO); + CategoryEntity newCategory = categoryRepository.save(category); + return CategoryEntity.toDTO(newCategory); + } + + // 카테고리 수정 + @Transactional + public CategoryDTO updateCategory(Long id, CategoryDTO categoryDTO) { + CategoryEntity category = categoryRepository.findById(id) + .orElseThrow(() -> new RuntimeException("카테고리를 찾을 수 없습니다")); + + category.update(categoryDTO); + return category.toDTO(category); + } + + // 카테고리 삭제 + @Transactional + public void deleteCategory(Long id) { + categoryRepository.deleteById(id); + } + +} \ No newline at end of file diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java index d8dc71124..acee93144 100644 --- a/src/main/java/gift/service/MemberService.java +++ b/src/main/java/gift/service/MemberService.java @@ -21,24 +21,24 @@ public MemberEntity authenticateToken(MemberDTO memberDTO) { MemberEntity foundMember = memberRepository.findByEmail(memberDTO.getEmail()); if (foundMember == null || !memberDTO.getPassword().equals(foundMember.getPassword())) { - return null; + throw new IllegalArgumentException("로그인 정보가 올바르지 않습니다."); } return foundMember; } @Transactional - public MemberServiceStatus save(MemberDTO memberDTO) { + public void save(MemberDTO memberDTO) { if (existsByEmail(memberDTO.getEmail())) { - return MemberServiceStatus.EMAIL_ALREADY_EXISTS; + throw new IllegalArgumentException("이미 가입된 이메일입니다."); } // DTO to Entity MemberEntity memberEntity = new MemberEntity(memberDTO.getEmail(), memberDTO.getPassword()); memberRepository.save(memberEntity); - return MemberServiceStatus.SUCCESS; } + public boolean existsByEmail(String email) { return memberRepository.existsByEmail(email); } diff --git a/src/main/java/gift/service/MemberServiceStatus.java b/src/main/java/gift/service/MemberServiceStatus.java deleted file mode 100644 index e90cac64c..000000000 --- a/src/main/java/gift/service/MemberServiceStatus.java +++ /dev/null @@ -1,8 +0,0 @@ -package gift.service; - -public enum MemberServiceStatus { - SUCCESS, - EMAIL_ALREADY_EXISTS, - NOT_FOUND, - UNAUTHORIZED -} \ No newline at end of file diff --git a/src/main/java/gift/service/ProductService.java b/src/main/java/gift/service/ProductService.java index ff5226d86..cf79f3297 100644 --- a/src/main/java/gift/service/ProductService.java +++ b/src/main/java/gift/service/ProductService.java @@ -1,8 +1,11 @@ package gift.service; +import gift.entity.CategoryEntity; import gift.entity.ProductEntity; import gift.domain.ProductDTO; +import gift.repository.CategoryRepository; import gift.repository.ProductRepository; +import jakarta.persistence.EntityNotFoundException; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -17,86 +20,71 @@ public class ProductService { @Autowired private ProductRepository productRepository; - private ProductDTO toProductDTO(ProductEntity productEntity) { - return new ProductDTO( - productEntity.getId(), - productEntity.getName(), - productEntity.getPrice(), - productEntity.getImageUrl() - ); - } - - private ProductEntity toProductEntity(ProductDTO productDTO) { - return new ProductEntity( - productDTO.getId(), - productDTO.getName(), - productDTO.getPrice(), - productDTO.getImageUrl() - ); - } + @Autowired + private CategoryRepository categoryRepository; public List getAllProducts() { List response = productRepository.findAll(); return response.stream() - .map(this::toProductDTO) + .map(ProductEntity::toDTO) .collect(Collectors.toList()); } public Page getAllProducts(Pageable pageable) { Page productEntities = productRepository.findAll(pageable); - return productEntities.map(this::toProductDTO); + return productEntities.map(ProductEntity::toDTO); } // Read(단일 상품) - getProduct() public Optional getProduct(Long id) { return productRepository.findById(id) - .map(this::toProductDTO); + .map(ProductEntity::toDTO); } // Create(생성) - addProduct() @Transactional - public ProductServiceStatus createProduct(ProductDTO productDTO) { - try { - ProductEntity productEntity = toProductEntity(productDTO); - productRepository.save(productEntity); - return ProductServiceStatus.SUCCESS; - } catch (Exception e) { - return ProductServiceStatus.ERROR; - } + public void createProduct(ProductDTO productDTO) { + CategoryEntity category = categoryRepository.findById(productDTO.getCategoryId()) + .orElseThrow(() -> new EntityNotFoundException("해당 카테고리가 존재하지 않습니다.")); + + ProductEntity productEntity = new ProductEntity( + productDTO.getName(), + productDTO.getPrice(), + productDTO.getImageUrl(), + category + ); + productRepository.save(productEntity); } // Update(수정) - updateProduct() @Transactional - public ProductServiceStatus editProduct(Long id, ProductDTO productDTO) { - try { - Optional existingProductEntityOptional = productRepository.findById(id); - if (!existingProductEntityOptional.isPresent()) { - return ProductServiceStatus.NOT_FOUND; - } - ProductEntity existingProductEntity = existingProductEntityOptional.get(); - ProductEntity updatedProductEntity = new ProductEntity( - existingProductEntity.getId(), - productDTO.getName(), - productDTO.getPrice(), - productDTO.getImageUrl() - ); - productRepository.save(updatedProductEntity); - return ProductServiceStatus.SUCCESS; - } catch (Exception e) { - return ProductServiceStatus.ERROR; - } + public void editProduct(Long id, ProductDTO productDTO) { + ProductEntity existingProductEntity = productRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("해당 ID의 상품을 찾을 수 없습니다")); + + CategoryEntity categoryEntity = categoryRepository.findById(productDTO.getCategoryId()) + .orElseThrow(() -> new EntityNotFoundException("해당 ID의 카테고리를 찾을 수 없습니다.")); + + ProductEntity updatedProductEntity = new ProductEntity( + existingProductEntity.getId(), // 기존 ID 유지 + productDTO.getName(), + productDTO.getPrice(), + productDTO.getImageUrl(), + categoryEntity + ); + productRepository.save(updatedProductEntity); } @Transactional - public ProductServiceStatus deleteProduct(Long id) { + public void deleteProduct(Long id) { + if (!productRepository.existsById(id)) { + throw new EntityNotFoundException("해당 ID의 상품을 찾을 수 없습니다"); + } + try { - if (productRepository.existsById(id)) { - productRepository.deleteById(id); - return ProductServiceStatus.SUCCESS; - } - return ProductServiceStatus.NOT_FOUND; + productRepository.deleteById(id); } catch (Exception e) { - return ProductServiceStatus.ERROR; + throw new RuntimeException("상품 삭제 중 오류가 발생했습니다", e); } } } \ No newline at end of file diff --git a/src/main/java/gift/service/ProductServiceStatus.java b/src/main/java/gift/service/ProductServiceStatus.java deleted file mode 100644 index 6ebd985a8..000000000 --- a/src/main/java/gift/service/ProductServiceStatus.java +++ /dev/null @@ -1,8 +0,0 @@ -package gift.service; - -public enum ProductServiceStatus { - SUCCESS, - NOT_FOUND, - ERROR -} - diff --git a/src/main/java/gift/service/WishListService.java b/src/main/java/gift/service/WishListService.java index 0b498a693..311243857 100644 --- a/src/main/java/gift/service/WishListService.java +++ b/src/main/java/gift/service/WishListService.java @@ -6,10 +6,10 @@ import gift.entity.WishListEntity; import gift.domain.WishListDTO; import gift.repository.WishListRepository; +import jakarta.persistence.EntityNotFoundException; import java.util.Optional; import gift.repository.MemberRepository; import gift.repository.ProductRepository; -import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -32,23 +32,19 @@ public WishListService(WishListRepository wishListRepository, this.memberRepository = memberRepository; } - private WishListDTO toWishListDTO(WishListEntity wishListEntity) { - return new WishListDTO(wishListEntity.getProductEntity().getId(), wishListEntity.getUserEntity().getId()); - } - private WishListEntity dtoToEntity(Long userId, ProductDTO product) throws Exception { MemberEntity memberEntity = memberRepository.findById(userId) - .orElseThrow(() -> new Exception("유저가 존재하지 않습니다.")); + .orElseThrow(() -> new EntityNotFoundException("유저가 존재하지 않습니다.")); ProductEntity productEntity = productRepository.findById(product.getId()) - .orElseThrow(() -> new Exception("상품이 존재하지 않습니다.")); + .orElseThrow(() -> new EntityNotFoundException("상품이 존재하지 않습니다.")); return new WishListEntity(productEntity, memberEntity); } public Page readWishList(Long userId, Pageable pageable) { Page wishListEntities = wishListRepository.findByUserEntity_Id(userId, pageable); - return wishListEntities.map(this::toWishListDTO); + return wishListEntities.map(WishListEntity::toDTO); } @Transactional diff --git a/src/test/java/gift/repository/ProductRepositoryTest.java b/src/test/java/gift/repository/ProductRepositoryTest.java index bb136f7ad..bb994c965 100644 --- a/src/test/java/gift/repository/ProductRepositoryTest.java +++ b/src/test/java/gift/repository/ProductRepositoryTest.java @@ -1,6 +1,8 @@ package gift.repository; +import gift.entity.CategoryEntity; import gift.entity.ProductEntity; +import jakarta.transaction.Transactional; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -8,8 +10,9 @@ import static org.junit.jupiter.api.Assertions.*; +@Transactional @DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) +//@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) class ProductRepositoryTest { @Autowired @@ -18,7 +21,7 @@ class ProductRepositoryTest { @Test void testSaveProduct() { // given - ProductEntity productEntity = new ProductEntity("아이스 아메리카노", 1000, "http://test.com/image.jpg"); + ProductEntity productEntity = new ProductEntity("아이스 아메리카노", 1000, "http://test.com/image.jpg",new CategoryEntity()); // when ProductEntity savedProduct = productRepository.save(productEntity); @@ -31,7 +34,7 @@ void testSaveProduct() { @Test void testFindProductById() { // given - ProductEntity productEntity = new ProductEntity("아이스 아메리카노", 1000, "http://test.com/image.jpg"); + ProductEntity productEntity = new ProductEntity("아이스 아메리카노", 1000, "http://test.com/image.jpg",new CategoryEntity()); ProductEntity savedProduct = productRepository.save(productEntity); // when @@ -45,7 +48,7 @@ void testFindProductById() { @Test void testDeleteProduct() { // given - ProductEntity productEntity = new ProductEntity("아이스 아메리카노", 1000, "http://test.com/image.jpg"); + ProductEntity productEntity = new ProductEntity("아이스 아메리카노", 1000, "http://test.com/image.jpg",new CategoryEntity()); ProductEntity savedProduct = productRepository.save(productEntity); // when diff --git a/src/test/java/gift/repository/WishListRepositoryTest.java b/src/test/java/gift/repository/WishListRepositoryTest.java new file mode 100644 index 000000000..a05225c74 --- /dev/null +++ b/src/test/java/gift/repository/WishListRepositoryTest.java @@ -0,0 +1,69 @@ +package gift.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import gift.entity.CategoryEntity; +import gift.entity.MemberEntity; +import gift.entity.ProductEntity; +import gift.entity.WishListEntity; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; + +@Transactional +@DataJpaTest +public class WishListRepositoryTest { + + @Autowired + private WishListRepository wishListRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private ProductRepository productRepository; + + private MemberEntity user; + private ProductEntity product; + private WishListEntity wishListEntity; + + @BeforeEach + void setUp() { + CategoryEntity category = new CategoryEntity("Blue", "color", "https://example.com/image.png", "New Category"); + categoryRepository.save(category); + + product = new ProductEntity("아이스티", 3000, "https://gift-s.kakaocdn.net/dn/gift/images/m640/dimm_theme.png", category); + productRepository.save(product); + + user = new MemberEntity("admin@gmail.com", "password"); + memberRepository.save(user); + + wishListEntity = new WishListEntity(product, user); + wishListRepository.save(wishListEntity); + } + + @Test + void testFindByUserEntity_IdAndProductEntity_Id() { + Optional wishEntity = wishListRepository.findByUserEntity_IdAndProductEntity_Id(user.getId(), product.getId()); + assertThat(wishEntity).isPresent(); + assertThat(wishEntity.get().getUserEntity().getId()).isEqualTo(user.getId()); + assertThat(wishEntity.get().getProductEntity().getId()).isEqualTo(product.getId()); + } + + @Test + void testFindByUserEntity_Id() { + Pageable pageable = PageRequest.of(0, 10); + Page wishEntities = wishListRepository.findByUserEntity_Id(user.getId(), pageable); + assertThat(wishEntities).isNotEmpty(); + } +} \ No newline at end of file