From 9d2da5f508591f826e4a56b76abf33d120d54391 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Tue, 16 Jul 2024 17:32:51 +0900 Subject: [PATCH 01/19] =?UTF-8?q?init=20:=20step0=20-=203=EC=A3=BC?= =?UTF-8?q?=EC=B0=A8=20jpa=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 21 +++- .../java/gift/controller/AdminController.java | 90 ++++++++++++++++ .../gift/controller/MemberController.java | 56 ++++++++++ .../gift/controller/ProductController.java | 66 ++++++++++++ .../gift/controller/WishListController.java | 76 +++++++++++++ src/main/java/gift/domain/MemberDTO.java | 51 +++++++++ src/main/java/gift/domain/ProductDTO.java | 76 +++++++++++++ src/main/java/gift/domain/WishListDTO.java | 30 ++++++ src/main/java/gift/entity/MemberEntity.java | 50 +++++++++ src/main/java/gift/entity/ProductEntity.java | 60 +++++++++++ src/main/java/gift/entity/WishListEntity.java | 40 +++++++ .../gift/repository/MemberRepository.java | 11 ++ .../gift/repository/ProductRepository.java | 14 +++ .../gift/repository/WishListRepository.java | 12 +++ src/main/java/gift/service/JwtUtil.java | 86 +++++++++++++++ src/main/java/gift/service/MemberService.java | 45 ++++++++ .../gift/service/MemberServiceStatus.java | 8 ++ .../java/gift/service/ProductService.java | 102 ++++++++++++++++++ .../gift/service/ProductServiceStatus.java | 8 ++ .../java/gift/service/WishListService.java | 71 ++++++++++++ src/main/resources/application.properties | 18 +++- src/main/resources/data.sql | 3 + src/main/resources/templates/create.html | 62 +++++++++++ src/main/resources/templates/list.html | 96 +++++++++++++++++ src/main/resources/templates/update.html | 62 +++++++++++ .../repository/ProductRepositoryTest.java | 58 ++++++++++ 26 files changed, 1270 insertions(+), 2 deletions(-) create mode 100644 src/main/java/gift/controller/AdminController.java create mode 100644 src/main/java/gift/controller/MemberController.java create mode 100644 src/main/java/gift/controller/ProductController.java create mode 100644 src/main/java/gift/controller/WishListController.java create mode 100644 src/main/java/gift/domain/MemberDTO.java create mode 100644 src/main/java/gift/domain/ProductDTO.java create mode 100644 src/main/java/gift/domain/WishListDTO.java create mode 100644 src/main/java/gift/entity/MemberEntity.java create mode 100644 src/main/java/gift/entity/ProductEntity.java create mode 100644 src/main/java/gift/entity/WishListEntity.java create mode 100644 src/main/java/gift/repository/MemberRepository.java create mode 100644 src/main/java/gift/repository/ProductRepository.java create mode 100644 src/main/java/gift/repository/WishListRepository.java create mode 100644 src/main/java/gift/service/JwtUtil.java create mode 100644 src/main/java/gift/service/MemberService.java create mode 100644 src/main/java/gift/service/MemberServiceStatus.java create mode 100644 src/main/java/gift/service/ProductService.java create mode 100644 src/main/java/gift/service/ProductServiceStatus.java create mode 100644 src/main/java/gift/service/WishListService.java create mode 100644 src/main/resources/data.sql create mode 100644 src/main/resources/templates/create.html create mode 100644 src/main/resources/templates/list.html create mode 100644 src/main/resources/templates/update.html create mode 100644 src/test/java/gift/repository/ProductRepositoryTest.java diff --git a/build.gradle b/build.gradle index df7db9334..4c0637e25 100644 --- a/build.gradle +++ b/build.gradle @@ -18,12 +18,31 @@ repositories { } dependencies { + // MySQL + runtimeOnly 'com.mysql:mysql-connector-j' // MySQL 드라이버 의존성 추가 + testRuntimeOnly 'mysql:mysql-connector-java' + + // JPA + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // Spring boot + implementation('nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect') // thymeleaf + implementation 'org.springframework.boot:spring-boot-starter-validation' 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' + + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // h2 DB + runtimeOnly 'com.h2database:h2' } tasks.named('test') { diff --git a/src/main/java/gift/controller/AdminController.java b/src/main/java/gift/controller/AdminController.java new file mode 100644 index 000000000..7d71f74c5 --- /dev/null +++ b/src/main/java/gift/controller/AdminController.java @@ -0,0 +1,90 @@ +package gift.controller; + +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") +public class AdminController { + private final ProductService productService; + + @Autowired + public AdminController(ProductService productService) { + this.productService = productService; + } + + @GetMapping + public String listProducts(Model model) { + model.addAttribute("products", productService.getAllProducts()); + return "list"; + } + + @GetMapping("/add") + public String addProductForm(Model model) { + model.addAttribute("product", new ProductDTO()); // 빈 Product 객체를 생성하여 모델에 추가 + return "create"; + } + + @PostMapping("/add") + public ModelAndView addProduct(@ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + return new ModelAndView("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; + } + + @GetMapping("/update/{id}") + public String editProductForm(@PathVariable Long id, Model model) { + Optional productDTO = productService.getProduct(id); + if (productDTO == null) { + return "redirect:/admin/products"; + } + model.addAttribute("product", productDTO); + return "update"; + } + + @PostMapping("/update/{id}") + public ModelAndView editProduct(@PathVariable Long id, @ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + return new ModelAndView("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; + } + + @PostMapping("/delete/{id}") + public String deleteProduct(@PathVariable Long id) { + ProductServiceStatus response = productService.deleteProduct(id); + if (response == ProductServiceStatus.SUCCESS) { + return "redirect:/admin/products"; // 성공적으로 삭제 후 목록 페이지로 리다이렉트 + } + return "error"; // 실패 시 에러 페이지로 리다이렉트 + } +} diff --git a/src/main/java/gift/controller/MemberController.java b/src/main/java/gift/controller/MemberController.java new file mode 100644 index 000000000..5e06d90df --- /dev/null +++ b/src/main/java/gift/controller/MemberController.java @@ -0,0 +1,56 @@ +package gift.controller; + +import gift.entity.MemberEntity; +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; +import org.springframework.web.bind.annotation.*; + +import java.util.Collections; +import java.util.Map; + +@RestController +@RequestMapping("/members") +public class MemberController { + + private final MemberService memberService; + private final JwtUtil jwtUtil; + + @Autowired + public MemberController(MemberService memberService, JwtUtil jwtUtil) { + this.memberService = memberService; + this.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")); + } + + @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); + } + +} \ No newline at end of file diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java new file mode 100644 index 000000000..48922f571 --- /dev/null +++ b/src/main/java/gift/controller/ProductController.java @@ -0,0 +1,66 @@ +package gift.controller; + +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; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping(value = "/api/products") +public class ProductController { + private final ProductService productService; + + @Autowired + private ProductController(ProductService productService) { + this.productService = productService; + } + + // 상품 추가(Create) + @PostMapping + public ResponseEntity addProduct(@RequestBody ProductDTO productDTO) { + ProductServiceStatus response = productService.createProduct(productDTO); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } + + // 단일 상품 조회(Read) + @GetMapping("/{id}") + public ResponseEntity selectProduct(@PathVariable Long id) { + Optional productDTO = productService.getProduct(id); + return productDTO + .map(dto -> new ResponseEntity<>(dto, HttpStatus.OK)) + .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND)); + } + + // 전체 상품 조회(Read) + @GetMapping + public ResponseEntity> selectAllProducts( + @Min(value = 1, message = "페이지 번호는 1이상이어야 합니다.") + @RequestParam(value = "page", defaultValue = "0") int page, + + @Min(value = 1, message = "한 페이지당 1개 이상의 항목이 포함되어야 합니다.") + @RequestParam(value = "size", defaultValue = "10") int size) { + Pageable pageable = PageRequest.of(page, size); + Page products = productService.getAllProducts(pageable); + return new ResponseEntity<>(products, HttpStatus.OK); + } + + // 상품 수정(Update) + @PutMapping("/{id}") + public ProductServiceStatus updateProduct(@PathVariable Long id, @RequestBody ProductDTO productDTO) { + return productService.editProduct(id, productDTO); + } + + // 상품 삭제(Delete) + @DeleteMapping("/{id}") + public ProductServiceStatus deleteProduct(@PathVariable Long id) { + return productService.deleteProduct(id); + } +} \ No newline at end of file diff --git a/src/main/java/gift/controller/WishListController.java b/src/main/java/gift/controller/WishListController.java new file mode 100644 index 000000000..c1de8cfe3 --- /dev/null +++ b/src/main/java/gift/controller/WishListController.java @@ -0,0 +1,76 @@ +package gift.controller; + +import gift.domain.ProductDTO; +import gift.domain.WishListDTO; +import gift.service.WishListService; +import gift.service.JwtUtil; +import jakarta.validation.constraints.Min; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/wishlist") +public class WishListController { + private final WishListService wishListService; + private final JwtUtil jwtUtil; + + @Autowired + public WishListController(WishListService wishListService, JwtUtil jwtUtil) { + this.wishListService = wishListService; + this.jwtUtil = jwtUtil; + } + + // 토큰에서 userId 추출 + private Long getUserId(String token) { + return jwtUtil.extractUserId(token); + + } + + // 전체 위시리스트 조회 + @GetMapping + public ResponseEntity> getWishList( + @RequestHeader("Authorization") String token, + + @Min(value = 1, message = "페이지 번호는 1이상이어야 합니다.") + @RequestParam(defaultValue = "0") int page, + + @Min(value = 1, message = "한 페이지당 1개 이상의 항목이 포함되어야 합니다.") + @RequestParam(defaultValue = "10") int size) { + Long userId = getUserId(token); + Pageable pageable = PageRequest.of(page, size); + Page wishList = wishListService.readWishList(userId, pageable); + return ResponseEntity.ok(wishList); + } + + + // 위시리스트 추가 + @PostMapping("/add/{productId}") + public ResponseEntity addWishList(@RequestHeader("Authorization") String token, + @RequestBody ProductDTO product) throws Exception { + Long userId = getUserId(token); + wishListService.addProductToWishList(userId, product); + return new ResponseEntity<>("Product added to wishlist", HttpStatus.CREATED); + } + + // 위시리스트 특정 상품 삭제 + @DeleteMapping("/delete/{productId}") + public ResponseEntity removeProductFromWishList(@RequestHeader("Authorization") String token, + @PathVariable Long productId) { + Long userId = getUserId(token); + wishListService.removeProductFromWishList(userId, productId); + return new ResponseEntity<>("Product removed from wishlist", HttpStatus.OK); + } + + // 전체 위시리스트 삭제 + @DeleteMapping("/deleteAll") + public ResponseEntity removeAllWishList(@RequestHeader("Authorization") String token) { + Long userId = getUserId(token); + wishListService.removeWishList(userId); + return new ResponseEntity<>("Wishlist deleted", HttpStatus.OK); + } +} diff --git a/src/main/java/gift/domain/MemberDTO.java b/src/main/java/gift/domain/MemberDTO.java new file mode 100644 index 000000000..e08d701da --- /dev/null +++ b/src/main/java/gift/domain/MemberDTO.java @@ -0,0 +1,51 @@ +package gift.domain; + +/** + * Data Transfer Object for Member + */ +public class MemberDTO { + + private Long id; + private String email; + private String password; + + + /** + * 매개변수가 있는 생성자 + * + * @param id 멤버 고유의 ID + * @param email 멤버의 이메일 주소 + * @param password 멤버의 비밀번호 + */ + public MemberDTO(Long id, String email, String password) { + this.id = id; + this.email = email; + this.password = password; + } + + // Getters + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + // Setters + public void setId(Long id) { + this.id = id; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } +} \ No newline at end of file diff --git a/src/main/java/gift/domain/ProductDTO.java b/src/main/java/gift/domain/ProductDTO.java new file mode 100644 index 000000000..018af99cb --- /dev/null +++ b/src/main/java/gift/domain/ProductDTO.java @@ -0,0 +1,76 @@ +package gift.domain; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Size; + +public class ProductDTO { + // 필드 생성 + private Long id; + + @NotNull + @Size(min = 1, max = 15, message = "상품의 이름은 1자 15자 이내로 작성해주세요.") + @Pattern(regexp = "^[가-힣a-zA-Z0-9()\\[\\]+\\-\\/&_\\s]*$", message = "한글, 영문자, 숫자, 특수 기호 ()[],+-&/_ 만 입력 가능합니다.") + @Pattern(regexp = "^(?!.*카카오).*$", message = "'카카오'가 포함된 문구는 담당 MD와 협의 후 사용 바랍니다.") + private String name; + + + @PositiveOrZero(message = "가격은 0 이상의 숫자를 입력해 주세요.") + private int price; + + @Pattern(regexp = "^https?://.*$", message = "올바른 이미지 URL 형식으로 입력해 주세요") + private String imageUrl; + + public ProductDTO(){} + + /** + * id로 상품 객체 전체를 조회할 때 사용되는 생성자 + * + * @param id 상품 고유의 ID + * @param name 상품의 이름 + * @param price 상품의 가격 + * @param imageUrl 상품의 이미지 주소 + */ + public ProductDTO(Long id, String name, int price, String imageUrl){ + this.id = id; + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + + // Getter 메서드들 + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public int getPrice() { + return price; + } + + public String getImageUrl() { + return imageUrl; + } + + // Setter 메서드들 + public void setId(Long id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setPrice(int price) { + this.price = price; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } +} \ No newline at end of file diff --git a/src/main/java/gift/domain/WishListDTO.java b/src/main/java/gift/domain/WishListDTO.java new file mode 100644 index 000000000..e75227276 --- /dev/null +++ b/src/main/java/gift/domain/WishListDTO.java @@ -0,0 +1,30 @@ +package gift.domain; + +public class WishListDTO { + private Long userId; + private Long productId; + + public WishListDTO(){} + + /** + * 해당 생성자를 통해 WishListDTO 객체를 생성 + * + * @param userId 멤버의 고유 ID + * @param productId 상품의 고유 ID + */ + public WishListDTO(Long userId, Long productId) { + this.userId = userId; + this.productId = productId; + + } + + public Long getUserId() { + return userId; + } + + public Long getProductId() { + return productId; + } + +} + diff --git a/src/main/java/gift/entity/MemberEntity.java b/src/main/java/gift/entity/MemberEntity.java new file mode 100644 index 000000000..d4d375846 --- /dev/null +++ b/src/main/java/gift/entity/MemberEntity.java @@ -0,0 +1,50 @@ +package gift.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +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="members") +public class MemberEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String email; + + @Column(nullable = false) + private String password; + + @OneToMany(mappedBy = "userEntity") + private List wishListEntities; + + protected MemberEntity() {} + + public MemberEntity(String email, String password) { + this.email = email; + this.password = password; + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public List getWishListEntities() { + return wishListEntities; + } +} \ No newline at end of file diff --git a/src/main/java/gift/entity/ProductEntity.java b/src/main/java/gift/entity/ProductEntity.java new file mode 100644 index 000000000..e4758e920 --- /dev/null +++ b/src/main/java/gift/entity/ProductEntity.java @@ -0,0 +1,60 @@ +package gift.entity; + +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="products") +public class ProductEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private int price; + + @Column(nullable = false) + private String imageUrl; + + @OneToMany(mappedBy = "productEntity", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + private List wishListEntities; + + public ProductEntity(Long id, String name, int price, String imageUrl) {} + + public ProductEntity(String name, int price, String imageUrl) { + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public int getPrice() { + return price; + } + + public String getImageUrl(){ + return imageUrl; + } + + public List getWishListEntities() { + return wishListEntities; + } +} \ No newline at end of file diff --git a/src/main/java/gift/entity/WishListEntity.java b/src/main/java/gift/entity/WishListEntity.java new file mode 100644 index 000000000..58abd5322 --- /dev/null +++ b/src/main/java/gift/entity/WishListEntity.java @@ -0,0 +1,40 @@ +package gift.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + +@Entity +@Table(name="wishlist") +public class WishListEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "product_id", nullable = false) + private ProductEntity productEntity; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private MemberEntity userEntity; + + public WishListEntity() {} + + public WishListEntity(ProductEntity productEntity, MemberEntity userEntity) { + this.productEntity = productEntity; + this.userEntity = userEntity; + } + + public ProductEntity getProductEntity() { + return productEntity; + } + + public MemberEntity getUserEntity() { + return userEntity; + } +} diff --git a/src/main/java/gift/repository/MemberRepository.java b/src/main/java/gift/repository/MemberRepository.java new file mode 100644 index 000000000..ac1de7cc8 --- /dev/null +++ b/src/main/java/gift/repository/MemberRepository.java @@ -0,0 +1,11 @@ +package gift.repository; + +import gift.entity.MemberEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MemberRepository extends JpaRepository { + MemberEntity findByEmail(String email); + boolean existsByEmail(String email); +} diff --git a/src/main/java/gift/repository/ProductRepository.java b/src/main/java/gift/repository/ProductRepository.java new file mode 100644 index 000000000..8de4f04ac --- /dev/null +++ b/src/main/java/gift/repository/ProductRepository.java @@ -0,0 +1,14 @@ +package gift.repository; + +import gift.entity.ProductEntity; +import java.util.List; +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 ProductRepository extends JpaRepository { + List findByName(String name); + Page findAll(Pageable pageable); +} diff --git a/src/main/java/gift/repository/WishListRepository.java b/src/main/java/gift/repository/WishListRepository.java new file mode 100644 index 000000000..caea49257 --- /dev/null +++ b/src/main/java/gift/repository/WishListRepository.java @@ -0,0 +1,12 @@ +package gift.repository; + +import gift.entity.WishListEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + +public interface WishListRepository extends JpaRepository { + Optional findByUserEntity_IdAndProductEntity_Id(Long userId, Long productId); + Page findByUserEntity_Id(Long userId, Pageable pageable); +} diff --git a/src/main/java/gift/service/JwtUtil.java b/src/main/java/gift/service/JwtUtil.java new file mode 100644 index 000000000..b8a8a5b4c --- /dev/null +++ b/src/main/java/gift/service/JwtUtil.java @@ -0,0 +1,86 @@ +package gift.service; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.stereotype.Component; + +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Component +public class JwtUtil { + private final String secretKey = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + private final Key key = new SecretKeySpec(secretKey.getBytes(), SignatureAlgorithm.HS512.getJcaName()); + + // userId와 email을 주체로 하는 토큰 생성 + public String generateToken(String email, Long userId) { + Map claims = new HashMap<>(); + claims.put("email", email); + claims.put("userId", userId); + + return Jwts.builder() + .setClaims(claims) + .setSubject(email) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1 day + .signWith(key) + .compact(); + } + + // 토큰에서 userId 추출 + public Long extractUserId(String token) { + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + + return claims.get("userId", Long.class); + } catch (JwtException | IllegalArgumentException e) { + return null; + } + } + + // 토큰에서 email 추출 + public String extractEmail(String token) { + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + + return claims.get("email", String.class); + } catch (JwtException | IllegalArgumentException e) { + return null; + } + } + + public boolean validateToken(String token, String email) { + try { + Jws claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + + String tokenEmail = claims.getBody().get("email", String.class); + Date expiration = claims.getBody().getExpiration(); + + return email.equals(tokenEmail) && !isTokenExpired(expiration); + } catch (JwtException | IllegalArgumentException e) { + return false; // 토큰 검증 실패 + } + } + + // 토큰 만료 여부 확인 + private boolean isTokenExpired(Date expiration) { + return expiration.before(new Date()); + } +} diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java new file mode 100644 index 000000000..d8dc71124 --- /dev/null +++ b/src/main/java/gift/service/MemberService.java @@ -0,0 +1,45 @@ +package gift.service; + +import gift.entity.MemberEntity; +import gift.domain.MemberDTO; +import gift.repository.MemberRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class MemberService { + + private final MemberRepository memberRepository; + + @Autowired + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + public MemberEntity authenticateToken(MemberDTO memberDTO) { + MemberEntity foundMember = memberRepository.findByEmail(memberDTO.getEmail()); + + if (foundMember == null || !memberDTO.getPassword().equals(foundMember.getPassword())) { + return null; + } + + return foundMember; + } + + @Transactional + public MemberServiceStatus save(MemberDTO memberDTO) { + if (existsByEmail(memberDTO.getEmail())) { + return MemberServiceStatus.EMAIL_ALREADY_EXISTS; + } + + // 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 new file mode 100644 index 000000000..e90cac64c --- /dev/null +++ b/src/main/java/gift/service/MemberServiceStatus.java @@ -0,0 +1,8 @@ +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 new file mode 100644 index 000000000..ff5226d86 --- /dev/null +++ b/src/main/java/gift/service/ProductService.java @@ -0,0 +1,102 @@ +package gift.service; + +import gift.entity.ProductEntity; +import gift.domain.ProductDTO; +import gift.repository.ProductRepository; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +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() + ); + } + + public List getAllProducts() { + List response = productRepository.findAll(); + return response.stream() + .map(this::toProductDTO) + .collect(Collectors.toList()); + } + + public Page getAllProducts(Pageable pageable) { + Page productEntities = productRepository.findAll(pageable); + return productEntities.map(this::toProductDTO); + } + + // Read(단일 상품) - getProduct() + public Optional getProduct(Long id) { + return productRepository.findById(id) + .map(this::toProductDTO); + } + + // 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; + } + } + + // 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; + } + } + + @Transactional + public ProductServiceStatus deleteProduct(Long id) { + try { + if (productRepository.existsById(id)) { + productRepository.deleteById(id); + return ProductServiceStatus.SUCCESS; + } + return ProductServiceStatus.NOT_FOUND; + } catch (Exception e) { + return ProductServiceStatus.ERROR; + } + } +} \ No newline at end of file diff --git a/src/main/java/gift/service/ProductServiceStatus.java b/src/main/java/gift/service/ProductServiceStatus.java new file mode 100644 index 000000000..6ebd985a8 --- /dev/null +++ b/src/main/java/gift/service/ProductServiceStatus.java @@ -0,0 +1,8 @@ +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 new file mode 100644 index 000000000..0b498a693 --- /dev/null +++ b/src/main/java/gift/service/WishListService.java @@ -0,0 +1,71 @@ +package gift.service; + +import gift.domain.ProductDTO; +import gift.entity.MemberEntity; +import gift.entity.ProductEntity; +import gift.entity.WishListEntity; +import gift.domain.WishListDTO; +import gift.repository.WishListRepository; +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; +import org.springframework.stereotype.Service; + +import java.util.List; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class WishListService { + private final WishListRepository wishListRepository; + private final ProductRepository productRepository; + private final MemberRepository memberRepository; + + @Autowired + public WishListService(WishListRepository wishListRepository, + WishListRepository wishListRepository1, ProductRepository productRepository, MemberRepository memberRepository) { + this.wishListRepository = wishListRepository1; + this.productRepository = productRepository; + 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("유저가 존재하지 않습니다.")); + + ProductEntity productEntity = productRepository.findById(product.getId()) + .orElseThrow(() -> new Exception("상품이 존재하지 않습니다.")); + + return new WishListEntity(productEntity, memberEntity); + } + + public Page readWishList(Long userId, Pageable pageable) { + Page wishListEntities = wishListRepository.findByUserEntity_Id(userId, pageable); + return wishListEntities.map(this::toWishListDTO); + } + + @Transactional + public void addProductToWishList(Long userId, ProductDTO product) throws Exception { + wishListRepository.save(dtoToEntity(userId, product)); + } + + @Transactional + public void removeWishList(Long userId) { + List wishListEntities = wishListRepository.findByUserEntity_Id(userId,Pageable.unpaged()).getContent(); + wishListRepository.deleteAll(wishListEntities); + } + + @Transactional + public void removeProductFromWishList(Long userId, Long productId) { + Optional wishListEntityOpt = wishListRepository.findByUserEntity_IdAndProductEntity_Id(userId, productId); + wishListEntityOpt.ifPresent(wishListRepository::delete); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3d16b65f4..0ffe4b859 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,17 @@ -spring.application.name=spring-gift +spring.application.name=jpa +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console + +spring.datasource.initialization-mode=always +spring.jpa.defer-datasource-initialization=true + +# H2 ?? ?? +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MYSQL +spring.datasource.username=sa +spring.datasource.password=password + +# Hibernate ?? +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.show-sql=true \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 000000000..3cc005e5c --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,3 @@ +INSERT INTO products (name, price, image_url) VALUES ('아이스 카페 아메리카노 T', 4500, 'https://st.kakaocdn.net/product/gift/product/20231010111814_9a667f9eccc943648797925498bdd8a3.jpg'); +INSERT INTO products (name, price, image_url) VALUES ('카페 라떼', 5800, 'http://image2.jpg'); +INSERT INTO products (name, price, image_url) VALUES ('복숭아아이스티', 3000, 'http://image3.jpg'); diff --git a/src/main/resources/templates/create.html b/src/main/resources/templates/create.html new file mode 100644 index 000000000..88db4ec44 --- /dev/null +++ b/src/main/resources/templates/create.html @@ -0,0 +1,62 @@ + + + + + Add Product + + + +

상품 추가

+
+ Name:
+
+ + Price:
+
+ + Image URL:
+ +
+ +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/list.html b/src/main/resources/templates/list.html new file mode 100644 index 000000000..3a7ad4fde --- /dev/null +++ b/src/main/resources/templates/list.html @@ -0,0 +1,96 @@ + + + + + Product List + + + +

상품 목록

+ + + + + + + + + + + + + + + + + + + +
IDNamePriceImage URLActions
IDNamePriceProduct Image + + Edit + +
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/update.html b/src/main/resources/templates/update.html new file mode 100644 index 000000000..0b3033b63 --- /dev/null +++ b/src/main/resources/templates/update.html @@ -0,0 +1,62 @@ + + + + + Edit Product + + + +

상품 수정

+
+ Name:
+
+ + Price:
+
+ + Image URL:
+ +
+ +
+ + \ No newline at end of file diff --git a/src/test/java/gift/repository/ProductRepositoryTest.java b/src/test/java/gift/repository/ProductRepositoryTest.java new file mode 100644 index 000000000..bb136f7ad --- /dev/null +++ b/src/test/java/gift/repository/ProductRepositoryTest.java @@ -0,0 +1,58 @@ +package gift.repository; + +import gift.entity.ProductEntity; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) +class ProductRepositoryTest { + + @Autowired + private ProductRepository productRepository; + + @Test + void testSaveProduct() { + // given + ProductEntity productEntity = new ProductEntity("아이스 아메리카노", 1000, "http://test.com/image.jpg"); + + // when + ProductEntity savedProduct = productRepository.save(productEntity); + + // then + assertNotNull(savedProduct.getId()); + assertEquals(productEntity.getName(), savedProduct.getName()); + } + + @Test + void testFindProductById() { + // given + ProductEntity productEntity = new ProductEntity("아이스 아메리카노", 1000, "http://test.com/image.jpg"); + ProductEntity savedProduct = productRepository.save(productEntity); + + // when + ProductEntity foundProduct = productRepository.findById(savedProduct.getId()).orElse(null); + + // then + assertNotNull(foundProduct); + assertEquals(savedProduct.getId(), foundProduct.getId()); + } + + @Test + void testDeleteProduct() { + // given + ProductEntity productEntity = new ProductEntity("아이스 아메리카노", 1000, "http://test.com/image.jpg"); + ProductEntity savedProduct = productRepository.save(productEntity); + + // when + productRepository.deleteById(savedProduct.getId()); + ProductEntity foundProduct = productRepository.findById(savedProduct.getId()).orElse(null); + + // then + assertNull(foundProduct); + } +} From 380f7ad84991a7285742c303fc5c1f63ce448a99 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Thu, 18 Jul 2024 19:49:49 +0900 Subject: [PATCH 02/19] =?UTF-8?q?docs=20:=20README.md=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) 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": ""
+         }
+       ]
+    
+    
From 5cd354de47219ac68073ebb8866ebbc2637a4629 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Thu, 18 Jul 2024 19:50:14 +0900 Subject: [PATCH 03/19] =?UTF-8?q?feat=20:=20CategoryDTO=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/domain/CategoryDTO.java | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/gift/domain/CategoryDTO.java diff --git a/src/main/java/gift/domain/CategoryDTO.java b/src/main/java/gift/domain/CategoryDTO.java new file mode 100644 index 000000000..f0647f251 --- /dev/null +++ b/src/main/java/gift/domain/CategoryDTO.java @@ -0,0 +1,44 @@ +package gift.domain; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +public class CategoryDTO { + private Long id; + + @NotBlank(message = "카테고리 이름은 필수 입력 항목입니다.") + private String name; + private String color; + + @Pattern(regexp = "^https?://.*$", message = "올바른 이미지 URL 형식으로 입력해 주세요") + private String imageUrl; + 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; + } +} From 25a214ecd46f3a10f9448b88a19c4bfef53bf44d Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Thu, 18 Jul 2024 19:50:32 +0900 Subject: [PATCH 04/19] =?UTF-8?q?feat=20:=20CategoryController.class=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/CategoryController.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/main/java/gift/controller/CategoryController.java 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); + } + + +} From 22a925d78084fd9e59950af9bce479e16fad0941 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Thu, 18 Jul 2024 19:50:52 +0900 Subject: [PATCH 05/19] =?UTF-8?q?feat=20:=20CategoryEntity=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/entity/CategoryEntity.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/main/java/gift/entity/CategoryEntity.java diff --git a/src/main/java/gift/entity/CategoryEntity.java b/src/main/java/gift/entity/CategoryEntity.java new file mode 100644 index 000000000..432f6cba6 --- /dev/null +++ b/src/main/java/gift/entity/CategoryEntity.java @@ -0,0 +1,63 @@ +package gift.entity; + +import gift.domain.CategoryDTO; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "category") +public class CategoryEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String color; + + @Column(name = "image_url", nullable = false) + private String imageUrl; + + @Column(nullable = false) + private String description; + + public CategoryEntity() {} + + public CategoryEntity(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; + } + + public static CategoryDTO toDTO(CategoryEntity category) { + return new CategoryDTO(category.getId(), category.getName(), category.getColor(), category.getImageUrl(), category.getDescription()); + } +} From cf40b40fdc0477f24de5acccfe03e998ac0cd0e0 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Thu, 18 Jul 2024 19:51:10 +0900 Subject: [PATCH 06/19] =?UTF-8?q?feat=20:=20CategoryService=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/service/CategoryService.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/java/gift/service/CategoryService.java diff --git a/src/main/java/gift/service/CategoryService.java b/src/main/java/gift/service/CategoryService.java new file mode 100644 index 000000000..b66c66caf --- /dev/null +++ b/src/main/java/gift/service/CategoryService.java @@ -0,0 +1,70 @@ +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 = toEntity(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("카테고리를 찾을 수 없습니다")); + + CategoryEntity updatedCategory = new CategoryEntity( + category.getId(), + categoryDTO.getName(), + categoryDTO.getColor(), + categoryDTO.getImageUrl(), + categoryDTO.getDescription() + ); + + updatedCategory = categoryRepository.save(updatedCategory); + return updatedCategory.toDTO(updatedCategory); + } + + // 카테고리 삭제 + @Transactional + public void deleteCategory(Long id) { + categoryRepository.deleteById(id); + } + + public CategoryEntity toEntity(CategoryDTO categoryDTO) { + return new CategoryEntity(categoryDTO.getId(), categoryDTO.getName(), categoryDTO.getColor(), categoryDTO.getImageUrl(), categoryDTO.getDescription()); + } + +} \ No newline at end of file From 9f4dea63c57315c44104fa8d1db0378503d38b84 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Thu, 18 Jul 2024 19:51:27 +0900 Subject: [PATCH 07/19] =?UTF-8?q?feat=20:=20CategoryRepository=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/repository/CategoryRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/gift/repository/CategoryRepository.java 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 { + +} From 1041e7a29d82464a598c3e31b8d8f07ec38353f1 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Thu, 18 Jul 2024 19:51:50 +0900 Subject: [PATCH 08/19] =?UTF-8?q?refactor=20:=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=20=EC=9B=90=EC=B9=99=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/entity/WishListEntity.java | 5 +++++ src/main/java/gift/service/WishListService.java | 7 +------ 2 files changed, 6 insertions(+), 6 deletions(-) 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/service/WishListService.java b/src/main/java/gift/service/WishListService.java index 0b498a693..5acc8f83b 100644 --- a/src/main/java/gift/service/WishListService.java +++ b/src/main/java/gift/service/WishListService.java @@ -9,7 +9,6 @@ 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,10 +31,6 @@ 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("유저가 존재하지 않습니다.")); @@ -48,7 +43,7 @@ private WishListEntity dtoToEntity(Long userId, ProductDTO product) throws Excep 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 From c94e4cb5f5c8bfe2d465851cc42ade59d5b97963 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Thu, 18 Jul 2024 19:52:07 +0900 Subject: [PATCH 09/19] =?UTF-8?q?refactor=20:=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=20=EC=9B=90=EC=B9=99=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/entity/ProductEntity.java | 11 +++++++++++ src/main/java/gift/service/ProductService.java | 15 +++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/java/gift/entity/ProductEntity.java b/src/main/java/gift/entity/ProductEntity.java index e4758e920..93f43c99b 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; @@ -57,4 +58,14 @@ public String getImageUrl(){ public List getWishListEntities() { return wishListEntities; } + + public static ProductDTO toDTO(ProductEntity productEntity) { + return new ProductDTO( + productEntity.getId(), + productEntity.getName(), + productEntity.getPrice(), + productEntity.getImageUrl() + ); + } + } \ 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..99d5735e5 100644 --- a/src/main/java/gift/service/ProductService.java +++ b/src/main/java/gift/service/ProductService.java @@ -17,15 +17,6 @@ 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(), @@ -38,19 +29,19 @@ private ProductEntity toProductEntity(ProductDTO productDTO) { 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() From 840491da9bd198240bb53aad1f7e8c6820581b9a Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Thu, 18 Jul 2024 19:52:49 +0900 Subject: [PATCH 10/19] =?UTF-8?q?refactor=20:=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/MemberController.java | 22 ++++--------------- src/main/java/gift/service/MemberService.java | 2 +- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/main/java/gift/controller/MemberController.java b/src/main/java/gift/controller/MemberController.java index 5e06d90df..9cac71afb 100644 --- a/src/main/java/gift/controller/MemberController.java +++ b/src/main/java/gift/controller/MemberController.java @@ -29,28 +29,14 @@ 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")); + return new ResponseEntity<>(Collections.singletonMap("status", status.name()), 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/service/MemberService.java b/src/main/java/gift/service/MemberService.java index d8dc71124..cf93f278b 100644 --- a/src/main/java/gift/service/MemberService.java +++ b/src/main/java/gift/service/MemberService.java @@ -21,7 +21,7 @@ public MemberEntity authenticateToken(MemberDTO memberDTO) { MemberEntity foundMember = memberRepository.findByEmail(memberDTO.getEmail()); if (foundMember == null || !memberDTO.getPassword().equals(foundMember.getPassword())) { - return null; + throw new RuntimeException("Invalid email or password"); } return foundMember; From de233583c5184e7bb8c1884c81a9ed0c4c44506d Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Fri, 19 Jul 2024 03:01:07 +0900 Subject: [PATCH 11/19] =?UTF-8?q?refactor=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EB=82=B4=20@Transaction=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +-- src/test/java/gift/repository/ProductRepositoryTest.java | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) 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/test/java/gift/repository/ProductRepositoryTest.java b/src/test/java/gift/repository/ProductRepositoryTest.java index bb136f7ad..3a0d323a7 100644 --- a/src/test/java/gift/repository/ProductRepositoryTest.java +++ b/src/test/java/gift/repository/ProductRepositoryTest.java @@ -1,6 +1,7 @@ package gift.repository; 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 +9,9 @@ import static org.junit.jupiter.api.Assertions.*; +@Transactional @DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) +//@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) class ProductRepositoryTest { @Autowired From 5ca217cc23958e042562bb621cb6a37cd91d2933 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Fri, 19 Jul 2024 03:02:00 +0900 Subject: [PATCH 12/19] =?UTF-8?q?test=20:=20WishListRepositoryTest=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/WishListRepositoryTest.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/test/java/gift/repository/WishListRepositoryTest.java diff --git a/src/test/java/gift/repository/WishListRepositoryTest.java b/src/test/java/gift/repository/WishListRepositoryTest.java new file mode 100644 index 000000000..f47c0ea54 --- /dev/null +++ b/src/test/java/gift/repository/WishListRepositoryTest.java @@ -0,0 +1,62 @@ +package gift.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +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 MemberRepository memberRepository; + + @Autowired + private ProductRepository productRepository; + + private MemberEntity user; + private ProductEntity product; + private WishListEntity wishListEntity; + + @BeforeEach + void setUp() { + user = new MemberEntity("admin@gmail.com", "password"); + memberRepository.save(user); + + product = new ProductEntity("아이스티", 3000, "https://gift-s.kakaocdn.net/dn/gift/images/m640/dimm_theme.png"); + productRepository.save(product); + + 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 From a7157eba9e18c89c7d834decca2e9f1c927eb83b Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Fri, 19 Jul 2024 03:05:10 +0900 Subject: [PATCH 13/19] =?UTF-8?q?refactor=20:=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/entity/CategoryEntity.java | 2 +- src/main/java/gift/entity/MemberEntity.java | 2 +- src/main/java/gift/entity/ProductEntity.java | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/gift/entity/CategoryEntity.java b/src/main/java/gift/entity/CategoryEntity.java index 432f6cba6..fda1edd9f 100644 --- a/src/main/java/gift/entity/CategoryEntity.java +++ b/src/main/java/gift/entity/CategoryEntity.java @@ -18,7 +18,7 @@ public class CategoryEntity { @Column(nullable = false) private String name; - @Column(nullable = false) + @Column(nullable = false, length = 7) private String color; @Column(name = "image_url", nullable = false) 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 93f43c99b..534ff6516 100644 --- a/src/main/java/gift/entity/ProductEntity.java +++ b/src/main/java/gift/entity/ProductEntity.java @@ -19,7 +19,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) @@ -31,6 +31,8 @@ public class ProductEntity { @OneToMany(mappedBy = "productEntity", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List wishListEntities; + public ProductEntity() {} + public ProductEntity(Long id, String name, int price, String imageUrl) {} public ProductEntity(String name, int price, String imageUrl) { From dfeb282172b7c4812c103fd017c0546770326d0f Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Fri, 19 Jul 2024 14:23:16 +0900 Subject: [PATCH 14/19] =?UTF-8?q?refactor=20:=20validation=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/domain/CategoryDTO.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/gift/domain/CategoryDTO.java b/src/main/java/gift/domain/CategoryDTO.java index f0647f251..0031cf4d1 100644 --- a/src/main/java/gift/domain/CategoryDTO.java +++ b/src/main/java/gift/domain/CategoryDTO.java @@ -2,16 +2,21 @@ 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) { From 5e4a0a7749ffac198fb551e4a91e00905d96ff5a Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Fri, 19 Jul 2024 14:26:16 +0900 Subject: [PATCH 15/19] =?UTF-8?q?refactor=20:=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=B0=A9=EB=B2=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/MemberService.java | 8 +- .../gift/service/MemberServiceStatus.java | 8 -- .../java/gift/service/ProductService.java | 79 +++++++++---------- .../gift/service/ProductServiceStatus.java | 8 -- 4 files changed, 42 insertions(+), 61 deletions(-) delete mode 100644 src/main/java/gift/service/MemberServiceStatus.java delete mode 100644 src/main/java/gift/service/ProductServiceStatus.java diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java index cf93f278b..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())) { - throw new RuntimeException("Invalid email or password"); + 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 99d5735e5..6548f4686 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,14 +20,8 @@ public class ProductService { @Autowired private ProductRepository productRepository; - 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(); @@ -46,48 +43,48 @@ public Optional getProduct(Long id) { // 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("An error occurred while deleting the product.", 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 -} - From 67dfd4acebe97cfd5489878db476f979e5f389a8 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Fri, 19 Jul 2024 14:27:10 +0900 Subject: [PATCH 16/19] =?UTF-8?q?refactor=20:=20=EC=9E=98=EB=AA=BB?= =?UTF-8?q?=EB=90=9C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/ProductController.java | 17 +++++---- src/main/java/gift/entity/CategoryEntity.java | 37 ++++++++++++++++++- .../java/gift/service/CategoryService.java | 18 ++------- 3 files changed, 47 insertions(+), 25 deletions(-) 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/entity/CategoryEntity.java b/src/main/java/gift/entity/CategoryEntity.java index fda1edd9f..a26507cc7 100644 --- a/src/main/java/gift/entity/CategoryEntity.java +++ b/src/main/java/gift/entity/CategoryEntity.java @@ -1,12 +1,16 @@ 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") @@ -27,16 +31,27 @@ public class CategoryEntity { @Column(nullable = false) private String description; + @OneToMany(mappedBy = "category", fetch = FetchType.LAZY, + cascade = CascadeType.ALL, orphanRemoval = true) + private List productEntities; + public CategoryEntity() {} - public CategoryEntity(Long id, String name, String color, String imageUrl, String description) { - this.id = id; + 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; } @@ -57,7 +72,25 @@ 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/service/CategoryService.java b/src/main/java/gift/service/CategoryService.java index b66c66caf..80541326c 100644 --- a/src/main/java/gift/service/CategoryService.java +++ b/src/main/java/gift/service/CategoryService.java @@ -34,7 +34,7 @@ public CategoryDTO getCategory(Long id) { // 카테고리 생성 @Transactional public CategoryDTO createCategory(CategoryDTO categoryDTO) { - CategoryEntity category = toEntity(categoryDTO); + CategoryEntity category = new CategoryEntity(categoryDTO); CategoryEntity newCategory = categoryRepository.save(category); return CategoryEntity.toDTO(newCategory); } @@ -45,16 +45,8 @@ public CategoryDTO updateCategory(Long id, CategoryDTO categoryDTO) { CategoryEntity category = categoryRepository.findById(id) .orElseThrow(() -> new RuntimeException("카테고리를 찾을 수 없습니다")); - CategoryEntity updatedCategory = new CategoryEntity( - category.getId(), - categoryDTO.getName(), - categoryDTO.getColor(), - categoryDTO.getImageUrl(), - categoryDTO.getDescription() - ); - - updatedCategory = categoryRepository.save(updatedCategory); - return updatedCategory.toDTO(updatedCategory); + category.update(categoryDTO); + return category.toDTO(category); } // 카테고리 삭제 @@ -63,8 +55,4 @@ public void deleteCategory(Long id) { categoryRepository.deleteById(id); } - public CategoryEntity toEntity(CategoryDTO categoryDTO) { - return new CategoryEntity(categoryDTO.getId(), categoryDTO.getName(), categoryDTO.getColor(), categoryDTO.getImageUrl(), categoryDTO.getDescription()); - } - } \ No newline at end of file From d9de226c099d4a4ba3d4fcb9d79a65f76c8c7f0e Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Fri, 19 Jul 2024 14:27:45 +0900 Subject: [PATCH 17/19] =?UTF-8?q?refactor=20:=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=EC=97=90=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=82=AC=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/domain/ProductDTO.java | 12 ++++++++++-- src/main/java/gift/entity/ProductEntity.java | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) 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/ProductEntity.java b/src/main/java/gift/entity/ProductEntity.java index 534ff6516..55e7b7a03 100644 --- a/src/main/java/gift/entity/ProductEntity.java +++ b/src/main/java/gift/entity/ProductEntity.java @@ -8,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; @@ -31,14 +33,19 @@ public class ProductEntity { @OneToMany(mappedBy = "productEntity", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List wishListEntities; + @ManyToOne(targetEntity = CategoryEntity.class, fetch = FetchType.LAZY) + @JoinColumn(name = "category_id") + private CategoryEntity category; + public ProductEntity() {} - public ProductEntity(Long id, 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) { + 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() { @@ -61,12 +68,15 @@ 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.getImageUrl(), + productEntity.getCategory().getId() ); } From d7ff8631e171ba3d70e23c0e29a4518c9c62ef70 Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Fri, 19 Jul 2024 14:28:15 +0900 Subject: [PATCH 18/19] =?UTF-8?q?refactor=20:=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=B0=A9=EB=B2=95=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=88=98=EC=A0=95=EC=82=AC?= =?UTF-8?q?=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/controller/AdminController.java | 44 +++++-------------- .../gift/controller/MemberController.java | 5 +-- .../repository/ProductRepositoryTest.java | 7 +-- .../repository/WishListRepositoryTest.java | 13 ++++-- 4 files changed, 28 insertions(+), 41 deletions(-) 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/MemberController.java b/src/main/java/gift/controller/MemberController.java index 9cac71afb..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,8 +27,8 @@ public MemberController(MemberService memberService, JwtUtil jwtUtil) { @PostMapping("/register") public ResponseEntity registerUser(@RequestBody MemberDTO memberDTO) { - MemberServiceStatus status = memberService.save(memberDTO); - return new ResponseEntity<>(Collections.singletonMap("status", status.name()), HttpStatus.OK); + memberService.save(memberDTO); + return new ResponseEntity<>(Collections.singletonMap("status", "SUCCESS"), HttpStatus.OK); } @PostMapping("/login") diff --git a/src/test/java/gift/repository/ProductRepositoryTest.java b/src/test/java/gift/repository/ProductRepositoryTest.java index 3a0d323a7..bb994c965 100644 --- a/src/test/java/gift/repository/ProductRepositoryTest.java +++ b/src/test/java/gift/repository/ProductRepositoryTest.java @@ -1,5 +1,6 @@ package gift.repository; +import gift.entity.CategoryEntity; import gift.entity.ProductEntity; import jakarta.transaction.Transactional; import org.junit.jupiter.api.*; @@ -20,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); @@ -33,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 @@ -47,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 index f47c0ea54..a05225c74 100644 --- a/src/test/java/gift/repository/WishListRepositoryTest.java +++ b/src/test/java/gift/repository/WishListRepositoryTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import gift.entity.CategoryEntity; import gift.entity.MemberEntity; import gift.entity.ProductEntity; import gift.entity.WishListEntity; @@ -23,6 +24,9 @@ public class WishListRepositoryTest { @Autowired private WishListRepository wishListRepository; + @Autowired + private CategoryRepository categoryRepository; + @Autowired private MemberRepository memberRepository; @@ -35,12 +39,15 @@ public class WishListRepositoryTest { @BeforeEach void setUp() { - user = new MemberEntity("admin@gmail.com", "password"); - memberRepository.save(user); + 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"); + 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); } From 0e1c77767d2f61e9e128f2ec464d687aadc1a01b Mon Sep 17 00:00:00 2001 From: peacefullyquietly Date: Fri, 19 Jul 2024 16:26:38 +0900 Subject: [PATCH 19/19] =?UTF-8?q?refactor=20:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/service/ProductService.java | 2 +- src/main/java/gift/service/WishListService.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/gift/service/ProductService.java b/src/main/java/gift/service/ProductService.java index 6548f4686..cf79f3297 100644 --- a/src/main/java/gift/service/ProductService.java +++ b/src/main/java/gift/service/ProductService.java @@ -84,7 +84,7 @@ public void deleteProduct(Long id) { try { productRepository.deleteById(id); } catch (Exception e) { - throw new RuntimeException("An error occurred while deleting the product.", e); + throw new RuntimeException("상품 삭제 중 오류가 발생했습니다", e); } } } \ No newline at end of file diff --git a/src/main/java/gift/service/WishListService.java b/src/main/java/gift/service/WishListService.java index 5acc8f83b..311243857 100644 --- a/src/main/java/gift/service/WishListService.java +++ b/src/main/java/gift/service/WishListService.java @@ -6,6 +6,7 @@ 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; @@ -33,10 +34,10 @@ public WishListService(WishListRepository wishListRepository, 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); }