diff --git a/src/main/java/com/mango/amango/domain/image/ImageUtil.java b/src/main/java/com/mango/amango/domain/image/ImageUtil.java new file mode 100644 index 0000000..c18ed43 --- /dev/null +++ b/src/main/java/com/mango/amango/domain/image/ImageUtil.java @@ -0,0 +1,65 @@ +package com.mango.amango.domain.image; + +import com.mango.amango.global.exception.CustomErrorCode; +import com.mango.amango.global.exception.CustomException; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.UUID; + +@Component +public class ImageUtil { + + public static String storeImage(MultipartFile file) { + if (file.isEmpty()) { + return null; + } + + makeDir(); + + String originalFilename = file.getOriginalFilename(); + String storeFilename = createStoreFilename(originalFilename); + try { + file.transferTo(new File(getFullPath(storeFilename))); + } catch (IOException e) { + throw new CustomException(CustomErrorCode.FILE_PROCESSING_ERROR); + } + + return storeFilename; + } + + public static String getFullPath(String fileName) { + if (fileName.isEmpty()) { + return null; + } + + return Paths.get(getFilePath(), fileName).toString(); + } + + private static String getFilePath() { + return Paths.get(System.getProperty("user.dir"), "src", "main", "resources", "static", "images").toString(); + } + + private static void makeDir() { + File file = new File(getFilePath()); + if(!file.exists() && !file.mkdirs()) { + throw new CustomException(CustomErrorCode.FILE_CREATE_ERROR); + } + } + + private static String createStoreFilename(String filename) { + String ext = extractExt(filename); + String uuid = UUID.randomUUID().toString(); + return uuid + "." + ext; + } + + private static String extractExt(String filename) { + int pos = filename.lastIndexOf("."); + return filename.substring(pos + 1); + } + + +} diff --git a/src/main/java/com/mango/amango/domain/image/Image.java b/src/main/java/com/mango/amango/domain/image/entity/Image.java similarity index 79% rename from src/main/java/com/mango/amango/domain/image/Image.java rename to src/main/java/com/mango/amango/domain/image/entity/Image.java index 89c8fac..fc2b61a 100644 --- a/src/main/java/com/mango/amango/domain/image/Image.java +++ b/src/main/java/com/mango/amango/domain/image/entity/Image.java @@ -1,6 +1,6 @@ -package com.mango.amango.domain.image; +package com.mango.amango.domain.image.entity; -import com.mango.amango.domain.product.Product; +import com.mango.amango.domain.product.entity.Product; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/mango/amango/domain/image/repository/ImageRepository.java b/src/main/java/com/mango/amango/domain/image/repository/ImageRepository.java new file mode 100644 index 0000000..696379b --- /dev/null +++ b/src/main/java/com/mango/amango/domain/image/repository/ImageRepository.java @@ -0,0 +1,7 @@ +package com.mango.amango.domain.image.repository; + +import com.mango.amango.domain.image.entity.Image; +import org.springframework.data.repository.CrudRepository; + +public interface ImageRepository extends CrudRepository { +} diff --git a/src/main/java/com/mango/amango/domain/image/service/ImageService.java b/src/main/java/com/mango/amango/domain/image/service/ImageService.java new file mode 100644 index 0000000..e400d73 --- /dev/null +++ b/src/main/java/com/mango/amango/domain/image/service/ImageService.java @@ -0,0 +1,31 @@ +package com.mango.amango.domain.image.service; + +import com.mango.amango.domain.image.entity.Image; +import com.mango.amango.domain.image.repository.ImageRepository; +import com.mango.amango.domain.product.entity.Product; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import static com.mango.amango.domain.image.ImageUtil.*; + +@Service +@Transactional +@AllArgsConstructor +public class ImageService { + + private final ImageRepository imageRepository; + + public void saveImage(Product product, List files) { + for (MultipartFile file : files) { + imageRepository.save(Image.builder() + .image(storeImage(file)) + .product(product) + .build() + ); + } + } +} diff --git a/src/main/java/com/mango/amango/domain/order/Order.java b/src/main/java/com/mango/amango/domain/order/Order.java index a6ca500..cf08378 100644 --- a/src/main/java/com/mango/amango/domain/order/Order.java +++ b/src/main/java/com/mango/amango/domain/order/Order.java @@ -1,6 +1,6 @@ package com.mango.amango.domain.order; -import com.mango.amango.domain.product.Product; +import com.mango.amango.domain.product.entity.Product; import com.mango.amango.domain.user.entity.User; import com.mango.amango.global.entity.BaseEntity; import jakarta.persistence.*; diff --git a/src/main/java/com/mango/amango/domain/product/controller/ProductController.java b/src/main/java/com/mango/amango/domain/product/controller/ProductController.java new file mode 100644 index 0000000..3b3357c --- /dev/null +++ b/src/main/java/com/mango/amango/domain/product/controller/ProductController.java @@ -0,0 +1,32 @@ +package com.mango.amango.domain.product.controller; + +import com.mango.amango.domain.product.entity.dto.request.CreateProductReq; +import com.mango.amango.domain.product.service.ProductService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Size; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import static org.springframework.http.HttpStatus.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/product") +public class ProductController { + + public final ProductService productService; + + @PostMapping + public ResponseEntity createProduct( + @Valid @RequestPart("request") CreateProductReq request, + @Size(max = 3, message = "이미지는 최대 3장까지 입니다.") @RequestPart("images") List images + ) { + productService.createProduct(request, images); + return ResponseEntity.status(CREATED).build(); + } + +} diff --git a/src/main/java/com/mango/amango/domain/product/Product.java b/src/main/java/com/mango/amango/domain/product/entity/Product.java similarity index 79% rename from src/main/java/com/mango/amango/domain/product/Product.java rename to src/main/java/com/mango/amango/domain/product/entity/Product.java index a857916..e90e09d 100644 --- a/src/main/java/com/mango/amango/domain/product/Product.java +++ b/src/main/java/com/mango/amango/domain/product/entity/Product.java @@ -1,4 +1,4 @@ -package com.mango.amango.domain.product; +package com.mango.amango.domain.product.entity; import com.mango.amango.domain.user.entity.User; import com.mango.amango.global.entity.BaseEntity; @@ -25,14 +25,16 @@ public class Product extends BaseEntity { private String description; - private String price; + private Long price; @ManyToOne private User user; - private Integer view; + @Builder.Default + private Integer view = 0; - private Integer likes; + @Builder.Default + private Integer likes = 0; private LocalDateTime expirTime; diff --git a/src/main/java/com/mango/amango/domain/product/entity/dto/request/CreateProductReq.java b/src/main/java/com/mango/amango/domain/product/entity/dto/request/CreateProductReq.java new file mode 100644 index 0000000..bc35376 --- /dev/null +++ b/src/main/java/com/mango/amango/domain/product/entity/dto/request/CreateProductReq.java @@ -0,0 +1,27 @@ +package com.mango.amango.domain.product.entity.dto.request; + +import com.mango.amango.domain.tag.entity.Category; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +public record CreateProductReq( + @NotBlank(message = "제목은 비어 있을 수 없습니다.") + String title, + + String description, + + Long price, + + @Size(min = 1, message = "태그는 최소 1개 이상 포함되어야 합니다.") + List tags, + + Long auctionPrice, + + @NotNull(message = "시간은 필수 입력 값 입니다.") + LocalDateTime expirTime +) { + +} diff --git a/src/main/java/com/mango/amango/domain/product/repository/ProductRepository.java b/src/main/java/com/mango/amango/domain/product/repository/ProductRepository.java new file mode 100644 index 0000000..f6a1b33 --- /dev/null +++ b/src/main/java/com/mango/amango/domain/product/repository/ProductRepository.java @@ -0,0 +1,8 @@ +package com.mango.amango.domain.product.repository; + +import com.mango.amango.domain.product.entity.Product; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; + +public interface ProductRepository extends JpaRepository { +} diff --git a/src/main/java/com/mango/amango/domain/product/service/ProductService.java b/src/main/java/com/mango/amango/domain/product/service/ProductService.java new file mode 100644 index 0000000..4369a76 --- /dev/null +++ b/src/main/java/com/mango/amango/domain/product/service/ProductService.java @@ -0,0 +1,42 @@ +package com.mango.amango.domain.product.service; + +import com.mango.amango.domain.image.service.ImageService; +import com.mango.amango.domain.product.entity.Product; +import com.mango.amango.domain.product.entity.dto.request.CreateProductReq; +import com.mango.amango.domain.product.repository.ProductRepository; +import com.mango.amango.domain.tag.service.TagService; +import com.mango.amango.domain.user.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class ProductService { + + private final ProductRepository productRepository; + private final UserService userService; + private final TagService tagService; + private final ImageService imageService; + + + public void createProduct(CreateProductReq request, List images) { + + Product product = Product.builder() + .title(request.title()) + .description(request.description()) + .price(request.price()) + .user(userService.getCurrentUser()) + .expirTime(request.expirTime()) + .auctionPrice(request.auctionPrice()) + .build(); + + productRepository.save(product); + tagService.saveTag(product, request.tags()); + imageService.saveImage(product, images); + } +} diff --git a/src/main/java/com/mango/amango/domain/tag/Category.java b/src/main/java/com/mango/amango/domain/tag/entity/Category.java similarity index 79% rename from src/main/java/com/mango/amango/domain/tag/Category.java rename to src/main/java/com/mango/amango/domain/tag/entity/Category.java index 8da38bf..4d0bde1 100644 --- a/src/main/java/com/mango/amango/domain/tag/Category.java +++ b/src/main/java/com/mango/amango/domain/tag/entity/Category.java @@ -1,4 +1,4 @@ -package com.mango.amango.domain.tag; +package com.mango.amango.domain.tag.entity; public enum Category { DISPOSABLES, diff --git a/src/main/java/com/mango/amango/domain/tag/Tag.java b/src/main/java/com/mango/amango/domain/tag/entity/Tag.java similarity index 81% rename from src/main/java/com/mango/amango/domain/tag/Tag.java rename to src/main/java/com/mango/amango/domain/tag/entity/Tag.java index 753c9a4..380488e 100644 --- a/src/main/java/com/mango/amango/domain/tag/Tag.java +++ b/src/main/java/com/mango/amango/domain/tag/entity/Tag.java @@ -1,6 +1,6 @@ -package com.mango.amango.domain.tag; +package com.mango.amango.domain.tag.entity; -import com.mango.amango.domain.product.Product; +import com.mango.amango.domain.product.entity.Product; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/mango/amango/domain/tag/repository/TagRepository.java b/src/main/java/com/mango/amango/domain/tag/repository/TagRepository.java new file mode 100644 index 0000000..be715ac --- /dev/null +++ b/src/main/java/com/mango/amango/domain/tag/repository/TagRepository.java @@ -0,0 +1,7 @@ +package com.mango.amango.domain.tag.repository; + +import com.mango.amango.domain.tag.entity.Tag; +import org.springframework.data.repository.CrudRepository; + +public interface TagRepository extends CrudRepository { +} diff --git a/src/main/java/com/mango/amango/domain/tag/service/TagService.java b/src/main/java/com/mango/amango/domain/tag/service/TagService.java new file mode 100644 index 0000000..244cbe9 --- /dev/null +++ b/src/main/java/com/mango/amango/domain/tag/service/TagService.java @@ -0,0 +1,28 @@ +package com.mango.amango.domain.tag.service; + +import com.mango.amango.domain.product.entity.Product; +import com.mango.amango.domain.tag.entity.Category; +import com.mango.amango.domain.tag.entity.Tag; +import com.mango.amango.domain.tag.repository.TagRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class TagService { + + private final TagRepository tagRepository; + + public void saveTag(Product product, List categories) { + categories.forEach(category -> { + tagRepository.save(Tag.builder() + .category(category) + .product(product) + .build()); + }); + } +} diff --git a/src/main/java/com/mango/amango/global/exception/CustomErrorCode.java b/src/main/java/com/mango/amango/global/exception/CustomErrorCode.java index 02b1afe..e04bead 100644 --- a/src/main/java/com/mango/amango/global/exception/CustomErrorCode.java +++ b/src/main/java/com/mango/amango/global/exception/CustomErrorCode.java @@ -13,7 +13,8 @@ public enum CustomErrorCode { USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "존재하지 않는 유저 입니다."), REFRESH_TOKEN_NOT_FOUND(HttpStatus.BAD_REQUEST, "Refresh 토큰을 찾을 수 없습니다."), VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "유효하지 않는 요청 형식입니다."), - + FILE_CREATE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "파일 경로를 찾을 수 없습니다."), + FILE_PROCESSING_ERROR(HttpStatus.BAD_REQUEST, "처리 할 수 없는 파일입니다."), MAIL_DELIVERY_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "매일 전송을 실패하였습니다.") ; diff --git a/src/main/java/com/mango/amango/global/exception/handler/CustomExceptionHandler.java b/src/main/java/com/mango/amango/global/exception/handler/CustomExceptionHandler.java index 76d7938..0d24810 100644 --- a/src/main/java/com/mango/amango/global/exception/handler/CustomExceptionHandler.java +++ b/src/main/java/com/mango/amango/global/exception/handler/CustomExceptionHandler.java @@ -5,12 +5,16 @@ import com.mango.amango.global.exception.CustomErrorRes; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.MessageSourceResolvable; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.HandlerMethodValidationException; + +import java.util.Objects; import static org.springframework.http.HttpStatus.*; @@ -37,18 +41,34 @@ public ResponseEntity handlerException( @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException e) { - CustomErrorRes response = CustomErrorRes.builder() - .status(CustomErrorCode.VALIDATION_FAILED) - .statusMessage(getErrorMessage(e)) - .build(); + String errorMessage = e.getBindingResult().getFieldErrors().stream() + .findFirst() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .orElse("유효성 검사에 실패했습니다."); - return ResponseEntity.status(BAD_REQUEST).body(response); + return getErrorResponse(errorMessage); } - private String getErrorMessage(MethodArgumentNotValidException e) { - return e.getBindingResult().getFieldErrors().stream() + @ExceptionHandler(HandlerMethodValidationException.class) + public ResponseEntity handlerMethodValidationException(HandlerMethodValidationException e) { + String errorMessage = e.getAllValidationResults() .stream() .findFirst() - .map(DefaultMessageSourceResolvable::getDefaultMessage) + .map(valueResult -> valueResult.getResolvableErrors().stream() + .map(MessageSourceResolvable::getDefaultMessage) + .filter(Objects::nonNull) + .findFirst() + .orElse("유효성 검사에 실패했습니다.")) .orElse("유효성 검사에 실패했습니다."); + + return getErrorResponse(errorMessage); + } + + private ResponseEntity getErrorResponse(String errorMessage) { + CustomErrorRes response = CustomErrorRes.builder() + .status(CustomErrorCode.VALIDATION_FAILED) + .statusMessage(errorMessage) + .build(); + + return ResponseEntity.status(BAD_REQUEST).body(response); } }