diff --git a/src/main/java/poomasi/domain/farm/controller/FarmFarmerController.java b/src/main/java/poomasi/domain/farm/controller/FarmFarmerController.java index eed59cd..1bf3a15 100644 --- a/src/main/java/poomasi/domain/farm/controller/FarmFarmerController.java +++ b/src/main/java/poomasi/domain/farm/controller/FarmFarmerController.java @@ -3,6 +3,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.*; import poomasi.domain.farm.dto.FarmRegisterRequest; import poomasi.domain.farm.dto.FarmUpdateRequest; @@ -17,8 +18,11 @@ public class FarmFarmerController { private final FarmScheduleService farmScheduleService; // TODO: 판매자만 접근가능하도록 인증/인가 annotation 추가 + @Secured("ROLE_FARMER") @PostMapping("") - public ResponseEntity registerFarm(@RequestBody FarmRegisterRequest request) { + public ResponseEntity registerFarm( + + @RequestBody FarmRegisterRequest request) { return ResponseEntity.ok(farmFarmerService.registerFarm(request)); } diff --git a/src/main/java/poomasi/domain/product/controller/ProductTagController.java b/src/main/java/poomasi/domain/product/controller/ProductTagController.java new file mode 100644 index 0000000..b49a0b5 --- /dev/null +++ b/src/main/java/poomasi/domain/product/controller/ProductTagController.java @@ -0,0 +1,33 @@ +package poomasi.domain.product.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import poomasi.domain.product.dto.ProductTagRequest; +import poomasi.domain.product.service.ProductTagService; + +@Controller +@RequiredArgsConstructor +public class ProductTagController { + + private final ProductTagService productTagService; + + @Secured("ROLE_ADMIN") + @PostMapping("/api/products/tag") + public ResponseEntity addTag(@RequestBody ProductTagRequest productTagRequest) { + productTagService.addTag(productTagRequest); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @Secured("ROLE_ADMIN") + @DeleteMapping("/api/products/tag") + public ResponseEntity deleteTag(@RequestBody ProductTagRequest productTagRequest) { + productTagService.deleteTag(productTagRequest); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/poomasi/domain/product/dto/ProductResponse.java b/src/main/java/poomasi/domain/product/dto/ProductResponse.java index 89eca75..6975a2a 100644 --- a/src/main/java/poomasi/domain/product/dto/ProductResponse.java +++ b/src/main/java/poomasi/domain/product/dto/ProductResponse.java @@ -1,7 +1,9 @@ package poomasi.domain.product.dto; +import java.util.List; import lombok.Builder; import poomasi.domain.product.entity.Product; +import poomasi.domain.product.entity.ProductTagEnum; @Builder public record ProductResponse( @@ -11,10 +13,13 @@ public record ProductResponse( Integer stock, String description, String imageUrl, - Long categoryId + Long categoryId, + List tags ) { public static ProductResponse fromEntity(Product product) { + List tags = product.getTags().stream().map(ProductTagEnum::getKoreanName).toList(); + return ProductResponse.builder() .id(product.getId()) .name(product.getName()) @@ -23,6 +28,7 @@ public static ProductResponse fromEntity(Product product) { .description(product.getDescription()) .imageUrl(product.getImageUrl()) .categoryId(product.getCategoryId()) + .tags(tags) .build(); } } diff --git a/src/main/java/poomasi/domain/product/dto/ProductTagRequest.java b/src/main/java/poomasi/domain/product/dto/ProductTagRequest.java new file mode 100644 index 0000000..647a990 --- /dev/null +++ b/src/main/java/poomasi/domain/product/dto/ProductTagRequest.java @@ -0,0 +1,8 @@ +package poomasi.domain.product.dto; + +public record ProductTagRequest( + Long productId, + String tagEnum +) { + +} diff --git a/src/main/java/poomasi/domain/product/entity/Product.java b/src/main/java/poomasi/domain/product/entity/Product.java index f39e5cb..3226ae5 100644 --- a/src/main/java/poomasi/domain/product/entity/Product.java +++ b/src/main/java/poomasi/domain/product/entity/Product.java @@ -1,7 +1,13 @@ package poomasi.domain.product.entity; import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -64,6 +70,12 @@ public class Product { @JoinColumn(name = "entityId") List reviewList = new ArrayList<>(); + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "product_tag", joinColumns = @JoinColumn(name = "product_id")) + @Column(name = "enum_value") + @Enumerated(EnumType.STRING) + List tags = new ArrayList<>(); + @Comment("평균 평점") private double averageRating = 0.0; diff --git a/src/main/java/poomasi/domain/product/entity/ProductTagEnum.java b/src/main/java/poomasi/domain/product/entity/ProductTagEnum.java new file mode 100644 index 0000000..2e1021a --- /dev/null +++ b/src/main/java/poomasi/domain/product/entity/ProductTagEnum.java @@ -0,0 +1,16 @@ +package poomasi.domain.product.entity; + +public enum ProductTagEnum { + ORGANIC("유기농"), + NonPesticide("무농약"); + + private final String value; + + private ProductTagEnum(String value) { + this.value = value; + } + + public String getKoreanName() { + return value; + } +} diff --git a/src/main/java/poomasi/domain/product/service/ProductService.java b/src/main/java/poomasi/domain/product/service/ProductService.java index ca7b482..79b73dc 100644 --- a/src/main/java/poomasi/domain/product/service/ProductService.java +++ b/src/main/java/poomasi/domain/product/service/ProductService.java @@ -1,7 +1,6 @@ package poomasi.domain.product.service; import java.util.List; - import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import poomasi.domain.product.dto.ProductResponse; diff --git a/src/main/java/poomasi/domain/product/service/ProductTagService.java b/src/main/java/poomasi/domain/product/service/ProductTagService.java new file mode 100644 index 0000000..87c384b --- /dev/null +++ b/src/main/java/poomasi/domain/product/service/ProductTagService.java @@ -0,0 +1,51 @@ +package poomasi.domain.product.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import poomasi.domain.product.dto.ProductTagRequest; +import poomasi.domain.product.entity.Product; +import poomasi.domain.product.entity.ProductTagEnum; +import poomasi.domain.product.repository.ProductRepository; +import poomasi.global.error.BusinessError; +import poomasi.global.error.BusinessException; + +@Service +@RequiredArgsConstructor +public class ProductTagService { + + private final ProductRepository productRepository; + + @Transactional + public void addTag(ProductTagRequest productTagRequest) { + Product product = CheckProduct(productTagRequest); + + ProductTagEnum productTagEnum = null; + try { + productTagEnum = ProductTagEnum.valueOf(productTagRequest.tagEnum()); + } catch (IllegalArgumentException e) { + throw new BusinessException(BusinessError.INVALID_TAG_NAME); + } + + product.getTags().add(productTagEnum); + } + + private Product CheckProduct(ProductTagRequest productTagRequest) { + return productRepository.findById(productTagRequest.productId()) + .orElseThrow(() -> new BusinessException(BusinessError.PRODUCT_NOT_FOUND)); + } + + @Transactional + public void deleteTag(ProductTagRequest productTagRequest) { + Product product = CheckProduct(productTagRequest); + + ProductTagEnum productTagEnum = null; + try { + productTagEnum = ProductTagEnum.valueOf(productTagRequest.tagEnum()); + } catch (IllegalArgumentException e) { + throw new BusinessException(BusinessError.INVALID_TAG_NAME); + } + + product.getTags().remove(productTagEnum); + } +} diff --git a/src/main/java/poomasi/domain/review/dto/ReviewResponse.java b/src/main/java/poomasi/domain/review/dto/ReviewResponse.java index dfd362b..fd5e630 100644 --- a/src/main/java/poomasi/domain/review/dto/ReviewResponse.java +++ b/src/main/java/poomasi/domain/review/dto/ReviewResponse.java @@ -4,7 +4,7 @@ public record ReviewResponse (Long id, - Long productId, + Long entityId, //Long reviewerId, Float rating, String content diff --git a/src/main/java/poomasi/global/error/BusinessError.java b/src/main/java/poomasi/global/error/BusinessError.java index d9ce696..e87a1c1 100644 --- a/src/main/java/poomasi/global/error/BusinessError.java +++ b/src/main/java/poomasi/global/error/BusinessError.java @@ -1,5 +1,6 @@ package poomasi.global.error; +import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties.Http; import org.springframework.http.HttpStatus; import lombok.AllArgsConstructor; @@ -48,6 +49,10 @@ public enum BusinessError { RESERVATION_ALREADY_CANCELED(HttpStatus.BAD_REQUEST, "이미 취소된 예약입니다."), RESERVATION_CANCELLATION_PERIOD_EXPIRED(HttpStatus.BAD_REQUEST, "예약 취소 기간이 지났습니다."), + //ProductTag + INVALID_TAG_NAME(HttpStatus.BAD_REQUEST, "존재하지 않는 태그명입니다."), + TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "태그가 존재하지 않습니다."), + // ETC START_DATE_SHOULD_BE_BEFORE_END_DATE(HttpStatus.BAD_REQUEST, "시작 날짜는 종료 날짜보다 이전이어야 합니다.");