Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Product crud #45

Merged
merged 21 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
000fca6
feat: Product CRUD 기능 추가
wintiger98 Apr 4, 2024
eb53246
Merge branch 'main' of https://github.com/SSapingMall/backend into pr…
wintiger98 Apr 7, 2024
e556ae5
Refactor : Controller와 Service 레이어간 관심사 분리
wintiger98 Apr 7, 2024
ec32a2d
Test : ProductService Test 코드 작성
wintiger98 Apr 7, 2024
3575602
Feat : 사용자 정의 예외 처리(Product Not found exception) 추가
wintiger98 Apr 7, 2024
24db68f
Merge branch 'main' of https://github.com/SSapingMall/backend into pr…
wintiger98 Apr 10, 2024
ab0dc78
🩹 Fix : 컨벤션 규칙 적용 및 어노테이션 사용 추가
wintiger98 Apr 10, 2024
7b2ecc3
:zap: [Refactor] 성능개선을 위해 ModelMapper -> MapStruct 방식으로 변경
wintiger98 Apr 14, 2024
e4494f8
Merge branch 'main' of https://github.com/SSapingMall/backend into pr…
wintiger98 Apr 23, 2024
8bc1cb1
Merge branch 'main' of https://github.com/SSapingMall/backend into pr…
wintiger98 Apr 23, 2024
9a47e92
:sparkles: ddl-auto 수정
wintiger98 Apr 23, 2024
fd09bfa
:sparkles: 날짜 관련 필드 자동 채우기를 위한 어노테이션 추가
wintiger98 Apr 23, 2024
138e9f7
:sparkles: 날짜 관련 필드 자동 채우기를 위한 어노테이션 추가
wintiger98 Apr 23, 2024
b9c540c
:art: 안 쓰는 어노테이션 삭제
wintiger98 Apr 23, 2024
c5b546b
:art: controller 리턴 방식 팀원과 통일
wintiger98 Apr 23, 2024
6e92bf0
:art: int -> Long 형 변환
wintiger98 Apr 23, 2024
fc165be
:art: 안 쓰는 속성 제거
wintiger98 Apr 23, 2024
da6af3f
:sparkles: GlobalHandler에 UserNotFound Exception 추가
wintiger98 Apr 23, 2024
585a845
:sparkles: insert, update 안 되는 이슈 해결
wintiger98 Apr 23, 2024
312d0b9
:sparkles: insert, update 안 되는 이슈 해결로 인한 테스트 수정
wintiger98 Apr 23, 2024
8d6a8e2
:art: 안 쓰는 커밋 제거
wintiger98 Apr 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions spring-boot-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ dependencies {
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'

// for domain -> dto auto change
implementation 'org.modelmapper:modelmapper:2.3.8'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.ssafy.springbootapi.domain.product.api;

import java.util.List;

import com.ssafy.springbootapi.domain.product.application.ProductService;
import com.ssafy.springbootapi.domain.product.dto.ProductInput;
import com.ssafy.springbootapi.domain.product.dto.ProductListOutput;
import com.ssafy.springbootapi.domain.product.dto.ProductOutput;
import com.ssafy.springbootapi.domain.product.dto.ProductUpdate;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@Tag(name = "Product", description = "Product 관련 API 입니다.")
@RestController
@RequiredArgsConstructor
//@CrossOrigin // spring security 디펜던시 삭제로 인한 주석처리
@RequestMapping("/api/v1/products")
public class ProductController {
private final ProductService productService;

@Operation(summary = "모든 제품 조회")
@GetMapping("")
public ResponseEntity<List<ProductListOutput>> getAllProducts() {
List<ProductListOutput> products = productService.getAllProducts();
return new ResponseEntity<>(products, HttpStatus.OK);
}

@Operation(summary = "제품 ID로 조회")
@GetMapping("/{id}")
public ResponseEntity<ProductOutput> getProductById(@PathVariable Long id) {
ProductOutput product = productService.getProductById(id);
return new ResponseEntity<>(product, HttpStatus.OK);
}

@Operation(summary = "새 제품 추가")
@PostMapping("")
public ResponseEntity<ProductOutput> createProduct(@RequestBody ProductInput productInput) {
// 서비스 레이어를 통해 비즈니스 로직 처리
ProductOutput newProduct = productService.insertProduct(productInput);

// 생성된 Product 객체 반환
return new ResponseEntity<>(newProduct, HttpStatus.CREATED);
}

@Operation(summary = "제품 정보 업데이트")
@PatchMapping("/{id}")
public ResponseEntity<ProductOutput> updateProduct(@PathVariable Long id, @RequestBody ProductUpdate productDetails) {
// 서비스 레이어를 통해 비즈니스 로직 처리
ProductOutput updatedProduct = productService.updateProduct(id, productDetails);

// 업데이트된 Product 객체 반환
return new ResponseEntity<>(updatedProduct, HttpStatus.OK);
}
@Operation(summary = "제품 삭제")
@DeleteMapping("/{id}")
public ResponseEntity<ProductOutput> deleteProduct(@PathVariable Long id) {
ProductOutput product = productService.removeProduct(id);
return new ResponseEntity<>(product, HttpStatus.NO_CONTENT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.ssafy.springbootapi.domain.product.application;

import java.util.List;
import java.util.stream.Collectors;

import com.ssafy.springbootapi.domain.product.dao.ProductRepository;
import com.ssafy.springbootapi.domain.product.domain.Product;
import com.ssafy.springbootapi.domain.product.domain.ProductMapper;
import com.ssafy.springbootapi.domain.product.dto.ProductInput;
import com.ssafy.springbootapi.domain.product.dto.ProductListOutput;
import com.ssafy.springbootapi.domain.product.dto.ProductOutput;
import com.ssafy.springbootapi.domain.product.dto.ProductUpdate;
import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
private final ProductMapper productMapper;

/**
* Product 전체 조회
* @return List(ProductListOutput)
*/
public List<ProductListOutput> getAllProducts() {
List<Product> products = productRepository.findAll();
return products.stream()
.map(productMapper::toProductListOutput)
.collect(Collectors.toList());
}

/**
* Product ID로 조회
* @param id : 조회할 상품 id
* @return ProductOutput(DTO)
*/
public ProductOutput getProductById(Long id) {
Product product = findProductByIdOrThrow(id);
return productMapper.toProductOutput(product);
}

/**
* Product 삽입 메서드
* @param productInput : Product 생성 데이터
* @return ProductOutput(DTO)
*/
public ProductOutput insertProduct(ProductInput productInput) {
Product product = productMapper.toEntity(productInput);
return productMapper.toProductOutput(productRepository.save(product));
}

/**
* Product 삭제 메서드
* @param id : 삭제할 product id
* @return ProductOutput(DTO)
*/
public ProductOutput removeProduct(Long id) {
Product toRemove = findProductByIdOrThrow(id);
productRepository.delete(toRemove);
return productMapper.toProductOutput(toRemove);
}

/**
* Product 수정 메서드
* @param id : 수정할 product id
* @param productUpdate : 수정할 product 데이터
* @return ProductOutput(DTO)
*/
public ProductOutput updateProduct(Long id, ProductUpdate productUpdate) {
Product productToUpdate = findProductByIdOrThrow(id);
productMapper.updateProduct(productUpdate, productToUpdate);
return productMapper.toProductOutput(productRepository.save(productToUpdate));
}

/**
* id로 product 찾기 또는 없을 경우 Exception 발생
* @param id : product id
* @return Product(Entity)
*/
private Product findProductByIdOrThrow(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new NotFoundProductException("Product not found with id: " + id));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.ssafy.springbootapi.domain.product.dao;

import com.ssafy.springbootapi.domain.product.domain.Product;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.ssafy.springbootapi.domain.product.domain;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDateTime;

@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
@Table(name = "product")
@AllArgsConstructor
@NoArgsConstructor
@Getter
//@Setter entity에 setter두는건 안 좋다고 해서 삭제
@Builder
public class Product {
@Id
@GeneratedValue
@Column(name = "id", unique = true)
private Long id;
@Column(name = "image_url")
private String imageUrl;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "price", nullable = false)
private int price;
private String description;
private int category;
private int stock;
// @ManyToOne
// @JoinColumn(name = "user_id")
// private User user;
@Column(name = "user_id")
private int userId;
// test code를 위해 추가
public void updateInfo(int category, int stock, String imageUrl) {
this.category = category;
this.stock = stock;
this.imageUrl = imageUrl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.ssafy.springbootapi.domain.product.domain;

import com.ssafy.springbootapi.domain.product.dto.ProductInput;
import com.ssafy.springbootapi.domain.product.dto.ProductListOutput;
import com.ssafy.springbootapi.domain.product.dto.ProductOutput;
import com.ssafy.springbootapi.domain.product.dto.ProductUpdate;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;

@Mapper(componentModel = "spring")
public interface ProductMapper {
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateProduct(ProductUpdate dto, @MappingTarget Product entity);

Product toEntity(ProductUpdate dto);
Product toEntity(ProductInput dto);

ProductOutput toProductOutput(Product entity);
ProductListOutput toProductListOutput(Product entity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ssafy.springbootapi.domain.product.dto;

import lombok.*;
import lombok.experimental.SuperBuilder;

@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@SuperBuilder
public class ProductBase {
private String imageUrl;
private String name;
private int price;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ssafy.springbootapi.domain.product.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.*;
import lombok.experimental.SuperBuilder;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@SuperBuilder
public class ProductInput extends ProductBase {
private String description;
private int category;
private int stock;
@NotBlank(message = "user id is required")
private int user_id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.ssafy.springbootapi.domain.product.dto;

import lombok.*;
import lombok.experimental.SuperBuilder;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@SuperBuilder
public class ProductListOutput extends ProductBase{
private Long id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ssafy.springbootapi.domain.product.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@SuperBuilder
public class ProductOutput extends ProductListOutput{
private String description;
private int category;
private int stock;
private int user_id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ssafy.springbootapi.domain.product.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@SuperBuilder
public class ProductUpdate extends ProductBase {
private String description;
private int category;
private int stock;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.ssafy.springbootapi.domain.product.exception;

public class NotFoundProductException extends RuntimeException{
public NotFoundProductException(String message) {
super(message);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.ssafy.springbootapi.global.error;

// import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException;
import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
Expand All @@ -9,8 +9,8 @@
@ControllerAdvice
public class GlobalExceptionHandler { // Domain별 Exception 핸들러

// @ExceptionHandler(NotFoundProductException.class)
// public ResponseEntity<?> handleNotFoundProductException(NotFoundProductException ex) {
// return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
// }
@ExceptionHandler(NotFoundProductException.class)
public ResponseEntity<?> handleNotFoundProductException(NotFoundProductException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
Loading