diff --git a/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteFactory.java b/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteFactory.java index 821b7353..d3f5a0b7 100644 --- a/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteFactory.java +++ b/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteFactory.java @@ -3,23 +3,21 @@ import org.springframework.stereotype.Component; import poomasi.domain.image.entity.ImageType; -import java.util.HashMap; -import java.util.Map; +import java.util.List; @Component public class ImageDeleteFactory { - private final Map handlerMap; + private final List deleteLinkers; - public ImageDeleteFactory( - ProductDeleteLinker productDeleteLinker, - MemberProfileDeleteLinker memberProfileDeleteLinker) { - this.handlerMap = new HashMap<>(); - handlerMap.put(ImageType.PRODUCT, productDeleteLinker); - handlerMap.put(ImageType.MEMBER_PROFILE, memberProfileDeleteLinker); + public ImageDeleteFactory(List deleteLinkers) { + this.deleteLinkers = deleteLinkers; } public ImageDeleteLinker getDeleteLinker(ImageType type) { - return handlerMap.get(type); + return deleteLinkers.stream() + .filter(linker -> linker.supports(type)) + .findFirst() + .orElse(null); } } diff --git a/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteLinker.java b/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteLinker.java index c5820c90..80f1c1cb 100644 --- a/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteLinker.java +++ b/src/main/java/poomasi/domain/image/deleteLinker/ImageDeleteLinker.java @@ -1,7 +1,9 @@ package poomasi.domain.image.deleteLinker; import poomasi.domain.image.entity.Image; +import poomasi.domain.image.entity.ImageType; public interface ImageDeleteLinker { + boolean supports(ImageType type); void handleImageDeletion(Image image); } \ No newline at end of file diff --git a/src/main/java/poomasi/domain/image/deleteLinker/MemberProfileDeleteLinker.java b/src/main/java/poomasi/domain/image/deleteLinker/MemberProfileDeleteLinker.java index 696bed0d..c5bf3f6f 100644 --- a/src/main/java/poomasi/domain/image/deleteLinker/MemberProfileDeleteLinker.java +++ b/src/main/java/poomasi/domain/image/deleteLinker/MemberProfileDeleteLinker.java @@ -2,6 +2,7 @@ import org.springframework.stereotype.Component; import poomasi.domain.image.entity.Image; +import poomasi.domain.image.entity.ImageType; import poomasi.domain.member._profile.entity.MemberProfile; import poomasi.domain.member._profile.service.MemberProfileService; @@ -14,6 +15,11 @@ public MemberProfileDeleteLinker(MemberProfileService memberProfileService) { this.memberProfileService = memberProfileService; } + @Override + public boolean supports(ImageType type) { + return type == ImageType.MEMBER_PROFILE; + } + @Override public void handleImageDeletion(Image image) { MemberProfile memberProfile = memberProfileService.getMemberProfileById(image.getReferenceId()); diff --git a/src/main/java/poomasi/domain/image/deleteLinker/ProductDeleteLinker.java b/src/main/java/poomasi/domain/image/deleteLinker/ProductDeleteLinker.java index 9453cbce..c396cda2 100644 --- a/src/main/java/poomasi/domain/image/deleteLinker/ProductDeleteLinker.java +++ b/src/main/java/poomasi/domain/image/deleteLinker/ProductDeleteLinker.java @@ -2,6 +2,7 @@ import org.springframework.stereotype.Component; import poomasi.domain.image.entity.Image; +import poomasi.domain.image.entity.ImageType; import poomasi.domain.product.entity.Product; import poomasi.domain.product.service.ProductService; @@ -14,6 +15,11 @@ public ProductDeleteLinker(ProductService productService) { this.productService = productService; } + @Override + public boolean supports(ImageType type) { + return type == ImageType.PRODUCT; + } + @Override public void handleImageDeletion(Image image) { Product product = productService.findProductById(image.getReferenceId()); diff --git a/src/main/java/poomasi/domain/image/deleteLinker/ProductIntroDeleteLinker.java b/src/main/java/poomasi/domain/image/deleteLinker/ProductIntroDeleteLinker.java new file mode 100644 index 00000000..7694fded --- /dev/null +++ b/src/main/java/poomasi/domain/image/deleteLinker/ProductIntroDeleteLinker.java @@ -0,0 +1,45 @@ +package poomasi.domain.image.deleteLinker; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import poomasi.domain.image.entity.Image; +import poomasi.domain.image.entity.ImageType; +import poomasi.domain.product._intro.entity.ProductIntro; +import poomasi.domain.product._intro.service.ProductIntroService; + +import java.util.Arrays; +import java.util.List; + +@Component +public class ProductIntroDeleteLinker implements ImageDeleteLinker { + + private final ProductIntroService productIntroService; + + public ProductIntroDeleteLinker(ProductIntroService productIntroService) { + this.productIntroService = productIntroService; + } + + @Override + public boolean supports(ImageType type) { + return type == ImageType.PRODUCT_INTRO; + } + + @Transactional + @Override + public void handleImageDeletion(Image image) { + ProductIntro productIntro = productIntroService.getIntroByIntroId(image.getReferenceId()); + + List images = Arrays.asList(productIntro.getMainImage(), productIntro.getSubImage1(), productIntro.getSubImage2(), productIntro.getSubImage3()); + for (int i = 0; i < images.size(); i++) { + if (images.get(i) == image) { + switch (i) { + case 0 -> productIntro.setMainImage(null); + case 1 -> productIntro.setSubImage1(null); + case 2 -> productIntro.setSubImage2(null); + case 3 -> productIntro.setSubImage3(null); + } + } + } + productIntroService.saveExistedProductIntro(productIntro); + } +} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/image/entity/Image.java b/src/main/java/poomasi/domain/image/entity/Image.java index 1565df79..ce1d442a 100644 --- a/src/main/java/poomasi/domain/image/entity/Image.java +++ b/src/main/java/poomasi/domain/image/entity/Image.java @@ -8,9 +8,7 @@ import java.time.LocalDateTime; @Entity -@Table(name = "image")/*, )uniqueConstraints = { - @UniqueConstraint(columnNames = {"type", "reference_id"}) -})*/ +@Table(name = "image") @Getter @Setter @NoArgsConstructor diff --git a/src/main/java/poomasi/domain/image/linker/ProductIntroImageLinker.java b/src/main/java/poomasi/domain/image/linker/ProductIntroImageLinker.java new file mode 100644 index 00000000..c5a2d086 --- /dev/null +++ b/src/main/java/poomasi/domain/image/linker/ProductIntroImageLinker.java @@ -0,0 +1,64 @@ +package poomasi.domain.image.linker; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import poomasi.domain.image.entity.Image; +import poomasi.domain.image.entity.ImageType; +import poomasi.domain.product._intro.entity.ProductIntro; +import poomasi.domain.product._intro.service.ProductIntroService; +import poomasi.global.error.BusinessException; + +import java.util.Arrays; +import java.util.List; + +import static poomasi.global.error.BusinessError.IMAGE_LIMIT_EXCEED; + +@Service +public class ProductIntroImageLinker implements ImageLinker { + + private final ProductIntroService productIntroService; + + public ProductIntroImageLinker(ProductIntroService productIntroService) { + this.productIntroService = productIntroService; + } + + @Override + public boolean supports(ImageType type) { + return type == ImageType.PRODUCT_INTRO; + } + + @Transactional + @Override + public void link(Long referenceId, Image savedImage) { + ProductIntro productIntro = productIntroService.getIntroByIntroId(referenceId); + addImageToProductIntro(productIntro, savedImage); + productIntroService.saveExistedProductIntro(productIntro); + } + + private void addImageToProductIntro(ProductIntro productIntro, Image savedImage) { + List images = Arrays.asList( + productIntro.getMainImage(), + productIntro.getSubImage1(), + productIntro.getSubImage2(), + productIntro.getSubImage3() + ); + + for (int i = 0; i < images.size(); i++) { + if (images.get(i) == null) { + setImageByIndex(productIntro, i, savedImage); + return; + } + } + + throw new BusinessException(IMAGE_LIMIT_EXCEED); + } + + private void setImageByIndex(ProductIntro productIntro, int index, Image image) { + switch (index) { + case 0 -> productIntro.setMainImage(image); + case 1 -> productIntro.setSubImage1(image); + case 2 -> productIntro.setSubImage2(image); + case 3 -> productIntro.setSubImage3(image); + } + } +} \ No newline at end of file diff --git a/src/main/java/poomasi/domain/image/service/ImageService.java b/src/main/java/poomasi/domain/image/service/ImageService.java index b0cabc50..ef22b310 100644 --- a/src/main/java/poomasi/domain/image/service/ImageService.java +++ b/src/main/java/poomasi/domain/image/service/ImageService.java @@ -30,7 +30,8 @@ public class ImageService { private static final int DEFAULT_IMAGE_LIMIT = 5; - private static final int IMAGE_ONE_LIMIT = 1; + private static final int MEMBER_PROFILE_IMAGE_LIMIT = 1; + private static final int PRODUCT_INTRO_IMAGE_LIMIT = 4; private final ImageRepository imageRepository; private final MemberService memberService; @@ -88,16 +89,23 @@ private Image recoverImageOrThrow(Image existingImage, ImageRequest imageRequest } private void validateImageLimit(ImageRequest imageRequest) { - int imageLimit = DEFAULT_IMAGE_LIMIT; - if (imageRequest.type() == ImageType.MEMBER_PROFILE || imageRequest.type() == ImageType.PRODUCT) { - imageLimit = IMAGE_ONE_LIMIT; // 멤버 프로필, 상품 이미지는 한 장으로 제한 - } + int imageLimit = determineImageLimit(imageRequest.type()); if (imageRepository.countByTypeAndReferenceIdAndDeletedAtIsNull(imageRequest.type(), imageRequest.referenceId()) >= imageLimit) { throw new BusinessException(IMAGE_LIMIT_EXCEED); } } + private int determineImageLimit(ImageType imageType) { + if (imageType == ImageType.MEMBER_PROFILE) { + return MEMBER_PROFILE_IMAGE_LIMIT; + } + if (imageType == ImageType.PRODUCT_INTRO) { + return PRODUCT_INTRO_IMAGE_LIMIT; + } + return DEFAULT_IMAGE_LIMIT; + } + // 여러 이미지 저장 @Transactional public List saveMultipleImages(Long memberId, List imageRequests) { diff --git a/src/main/java/poomasi/domain/image/validator/FarmOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/FarmOwnerValidator.java index 193aa1a0..6a36cea0 100644 --- a/src/main/java/poomasi/domain/image/validator/FarmOwnerValidator.java +++ b/src/main/java/poomasi/domain/image/validator/FarmOwnerValidator.java @@ -3,16 +3,21 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import poomasi.domain.farm.repository.FarmRepository; - +import poomasi.domain.image.entity.ImageType; @Component @RequiredArgsConstructor public class FarmOwnerValidator implements ImageOwnerValidator { private final FarmRepository farmRepository; + @Override + public boolean supports(ImageType type) { + return type == ImageType.FARM; + } + @Override public boolean validateOwner(Long memberId, Long referenceId) { - return farmRepository.findById(referenceId) + return farmRepository.findByIdAndDeletedAtIsNull(referenceId) .filter(farm -> farm.getOwnerId().equals(memberId)) .isPresent(); } diff --git a/src/main/java/poomasi/domain/image/validator/ImageOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/ImageOwnerValidator.java index 1faa853f..63de3180 100644 --- a/src/main/java/poomasi/domain/image/validator/ImageOwnerValidator.java +++ b/src/main/java/poomasi/domain/image/validator/ImageOwnerValidator.java @@ -1,5 +1,9 @@ package poomasi.domain.image.validator; +import poomasi.domain.image.entity.ImageType; + public interface ImageOwnerValidator { boolean validateOwner(Long memberId, Long referenceId); + boolean supports(ImageType type); + } \ No newline at end of file diff --git a/src/main/java/poomasi/domain/image/validator/ImageOwnerValidatorFactory.java b/src/main/java/poomasi/domain/image/validator/ImageOwnerValidatorFactory.java index 2de7c789..4e99c876 100644 --- a/src/main/java/poomasi/domain/image/validator/ImageOwnerValidatorFactory.java +++ b/src/main/java/poomasi/domain/image/validator/ImageOwnerValidatorFactory.java @@ -3,25 +3,21 @@ import org.springframework.stereotype.Component; import poomasi.domain.image.entity.ImageType; -import java.util.EnumMap; -import java.util.Map; +import java.util.List; @Component public class ImageOwnerValidatorFactory { - private final Map validators = new EnumMap<>(ImageType.class); - public ImageOwnerValidatorFactory(FarmOwnerValidator farmOwnerValidator, - ProductOwnerValidator productOwnerValidator, - ReviewOwnerValidator reviewOwnerValidator, - MemberProfileOwnerValidator memberProfileOwnerValidator) { - validators.put(ImageType.FARM, farmOwnerValidator); - validators.put(ImageType.PRODUCT, productOwnerValidator); - validators.put(ImageType.FARM_REVIEW, reviewOwnerValidator); - validators.put(ImageType.PRODUCT_REVIEW, reviewOwnerValidator); - validators.put(ImageType.MEMBER_PROFILE, memberProfileOwnerValidator); + private final List validators; + + public ImageOwnerValidatorFactory(List validators) { + this.validators = validators; } public ImageOwnerValidator getValidator(ImageType type) { - return validators.get(type); + return validators.stream() + .filter(validator -> validator.supports(type)) + .findFirst() + .orElse(null); } } diff --git a/src/main/java/poomasi/domain/image/validator/MemberProfileOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/MemberProfileOwnerValidator.java index f49c12ea..7b8f2622 100644 --- a/src/main/java/poomasi/domain/image/validator/MemberProfileOwnerValidator.java +++ b/src/main/java/poomasi/domain/image/validator/MemberProfileOwnerValidator.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import poomasi.domain.image.entity.ImageType; import poomasi.domain.member.repository.MemberRepository; @Component @@ -9,9 +10,14 @@ public class MemberProfileOwnerValidator implements ImageOwnerValidator{ private final MemberRepository memberRepository; + @Override + public boolean supports(ImageType type) { + return type == ImageType.MEMBER_PROFILE; + } + @Override public boolean validateOwner(Long memberId, Long referenceId) { - return memberRepository.findById(memberId) + return memberRepository.findByIdAndDeletedAtIsNull(memberId) .filter(member -> member.getMemberProfile().getId().equals(referenceId)) .isPresent(); } diff --git a/src/main/java/poomasi/domain/image/validator/ProductIntroOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/ProductIntroOwnerValidator.java new file mode 100644 index 00000000..dbc7b911 --- /dev/null +++ b/src/main/java/poomasi/domain/image/validator/ProductIntroOwnerValidator.java @@ -0,0 +1,24 @@ +package poomasi.domain.image.validator; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import poomasi.domain.image.entity.ImageType; +import poomasi.domain.product._intro.repository.ProductIntroRepository; + +@Component +@RequiredArgsConstructor +public class ProductIntroOwnerValidator implements ImageOwnerValidator{ + private final ProductIntroRepository productIntroRepository; + + @Override + public boolean supports(ImageType type) { + return type == ImageType.PRODUCT_INTRO; + } + + @Override + public boolean validateOwner(Long memberId, Long referenceId) { + return productIntroRepository.findById(referenceId) + .filter(productIntro -> productIntro.getFarmerId().equals(memberId)) + .isPresent(); + } +} diff --git a/src/main/java/poomasi/domain/image/validator/ProductOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/ProductOwnerValidator.java index 43175fa2..2d9d3776 100644 --- a/src/main/java/poomasi/domain/image/validator/ProductOwnerValidator.java +++ b/src/main/java/poomasi/domain/image/validator/ProductOwnerValidator.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import poomasi.domain.image.entity.ImageType; import poomasi.domain.product.repository.ProductRepository; @Component @@ -9,6 +10,11 @@ public class ProductOwnerValidator implements ImageOwnerValidator{ private final ProductRepository productRepository; + @Override + public boolean supports(ImageType type) { + return type == ImageType.PRODUCT; + } + @Override public boolean validateOwner(Long memberId, Long referenceId) { return productRepository.findById(referenceId) diff --git a/src/main/java/poomasi/domain/image/validator/ReviewOwnerValidator.java b/src/main/java/poomasi/domain/image/validator/ReviewOwnerValidator.java index 5f8d8d6b..ea9a006a 100644 --- a/src/main/java/poomasi/domain/image/validator/ReviewOwnerValidator.java +++ b/src/main/java/poomasi/domain/image/validator/ReviewOwnerValidator.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import poomasi.domain.image.entity.ImageType; import poomasi.domain.review.repository.ReviewRepository; @Component @@ -9,6 +10,11 @@ public class ReviewOwnerValidator implements ImageOwnerValidator{ private final ReviewRepository reviewRepository; + @Override + public boolean supports(ImageType type) { + return type == ImageType.FARM_REVIEW || type == ImageType.PRODUCT_REVIEW; + } + @Override public boolean validateOwner(Long memberId, Long referenceId) { return reviewRepository.findById(referenceId) diff --git a/src/main/java/poomasi/domain/member/_profile/repository/MemberProfileRepository.java b/src/main/java/poomasi/domain/member/_profile/repository/MemberProfileRepository.java index 433f671c..266b3791 100644 --- a/src/main/java/poomasi/domain/member/_profile/repository/MemberProfileRepository.java +++ b/src/main/java/poomasi/domain/member/_profile/repository/MemberProfileRepository.java @@ -4,6 +4,10 @@ import org.springframework.stereotype.Repository; import poomasi.domain.member._profile.entity.MemberProfile; +import java.util.Optional; + @Repository public interface MemberProfileRepository extends JpaRepository { + Optional findByIdAndDeletedAtIsNull(Long id); + } \ No newline at end of file diff --git a/src/main/java/poomasi/domain/member/_profile/service/MemberProfileService.java b/src/main/java/poomasi/domain/member/_profile/service/MemberProfileService.java index 3f88b3c4..0955c7c5 100644 --- a/src/main/java/poomasi/domain/member/_profile/service/MemberProfileService.java +++ b/src/main/java/poomasi/domain/member/_profile/service/MemberProfileService.java @@ -17,7 +17,7 @@ public class MemberProfileService { private final MemberProfileRepository memberProfileRepository; public MemberProfile getMemberProfileById(Long id){ - return memberProfileRepository.findById(id) + return memberProfileRepository.findByIdAndDeletedAtIsNull(id) .orElseThrow(() -> new BusinessException(MEMBER_PROFILE_NOT_FOUND)); } diff --git a/src/main/java/poomasi/domain/product/_intro/entity/ProductIntro.java b/src/main/java/poomasi/domain/product/_intro/entity/ProductIntro.java index 61acb28a..691c3489 100644 --- a/src/main/java/poomasi/domain/product/_intro/entity/ProductIntro.java +++ b/src/main/java/poomasi/domain/product/_intro/entity/ProductIntro.java @@ -31,21 +31,25 @@ public class ProductIntro { private String mainTitle; @OneToOne(cascade = CascadeType.ALL) + @Setter private Image mainImage; private String subTitle1; private String subDesc1; @OneToOne(cascade = CascadeType.ALL) + @Setter private Image subImage1; private String subTitle2; private String subDesc2; @OneToOne(cascade = CascadeType.ALL) + @Setter private Image subImage2; private String subTitle3; private String subDesc3; @OneToOne(cascade = CascadeType.ALL) + @Setter private Image subImage3; @Builder @@ -85,4 +89,8 @@ public void updateImage(Image mainImage, Image subImage1, Image subImage2, Image this.subImage2 = subImage2; this.subImage3 = subImage3; } + + public Long getFarmerId() { + return product.getFarmerId(); + } } diff --git a/src/main/java/poomasi/domain/product/_intro/service/ProductIntroService.java b/src/main/java/poomasi/domain/product/_intro/service/ProductIntroService.java index 84560baf..abbb61b6 100644 --- a/src/main/java/poomasi/domain/product/_intro/service/ProductIntroService.java +++ b/src/main/java/poomasi/domain/product/_intro/service/ProductIntroService.java @@ -3,14 +3,12 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import poomasi.domain.image.entity.Image; -import poomasi.domain.image.repository.ImageRepository; import poomasi.domain.member.entity.Member; import poomasi.domain.product._intro.dto.ProductIntroUpdateRequest; import poomasi.domain.product._intro.dto.ProductIntroResponse; +import poomasi.domain.product._intro.entity.ProductIntro; import poomasi.domain.product._intro.repository.ProductIntroRepository; import poomasi.domain.product.entity.Product; -import poomasi.domain.product.repository.ProductRepository; import poomasi.domain.product.service.ProductService; import poomasi.global.error.BusinessError; import poomasi.global.error.BusinessException; @@ -20,6 +18,7 @@ public class ProductIntroService { private final ProductService productService; + private final ProductIntroRepository productIntroRepository; public ProductIntroResponse getIntro(Long productId) { Product product = getProduct(productId); @@ -45,4 +44,13 @@ private Product getProduct(Long productId) { return productService.findProductById(productId); } + public ProductIntro getIntroByIntroId(Long productIntroId) { + return productIntroRepository.findById(productIntroId) + .orElseThrow(() -> new BusinessException(BusinessError.INTRO_NOT_FOUND)); + } + + @Transactional + public void saveExistedProductIntro(ProductIntro productIntro){ + productIntroRepository.save(productIntro); + } } diff --git a/src/main/java/poomasi/global/error/BusinessError.java b/src/main/java/poomasi/global/error/BusinessError.java index 3ba39e59..f639d67a 100644 --- a/src/main/java/poomasi/global/error/BusinessError.java +++ b/src/main/java/poomasi/global/error/BusinessError.java @@ -73,7 +73,7 @@ public enum BusinessError { START_DATE_SHOULD_BE_BEFORE_END_DATE(HttpStatus.BAD_REQUEST, "시작 날짜는 종료 날짜보다 이전이어야 합니다."), // Image - IMAGE_LIMIT_EXCEED(HttpStatus.BAD_REQUEST, "사진은 최대 5장까지 등록 가능합니다."), + IMAGE_LIMIT_EXCEED(HttpStatus.BAD_REQUEST, "이미지를 더 등록할 수 없습니다."), IMAGE_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 이미지가 존재합니다"), IMAGE_NOT_FOUND(HttpStatus.NOT_FOUND, "이미지를 찾을 수 없습니다."), IMAGE_OWNER_MISMATCH(HttpStatus.FORBIDDEN, "해당 이미지의 소유자가 아닙니다."),