diff --git a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java index afc9043..f6d76d3 100644 --- a/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java +++ b/src/main/java/com/helpmeCookies/global/utils/AwsS3FileUtils.java @@ -4,7 +4,7 @@ import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; -import com.helpmeCookies.product.dto.FileUploadResponse; +import com.helpmeCookies.product.dto.ImageUpload; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -14,7 +14,6 @@ import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -28,26 +27,21 @@ public class AwsS3FileUtils { private String bucket; //다중파일 업로드후 url 반환 - public List uploadMultiImages(List multipartFiles) { - List fileList = new ArrayList<>(); + public ImageUpload uploadMultiImages(MultipartFile multipartFile) { - multipartFiles.forEach(file -> { - String fileName = createFileName(file.getOriginalFilename()); //파일 이름 난수화 - ObjectMetadata objectMetadata = new ObjectMetadata(); - objectMetadata.setContentLength(file.getSize()); - objectMetadata.setContentType(file.getContentType()); + String fileName = createFileName(multipartFile.getOriginalFilename()); //파일 이름 난수화 + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(multipartFile.getSize()); + objectMetadata.setContentType(multipartFile.getContentType()); - try (InputStream inputStream = file.getInputStream()) { - amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) - .withCannedAcl(CannedAccessControlList.PublicRead)); - } catch (IOException e) { - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드 실패" + fileName); - } - - fileList.add(new FileUploadResponse(amazonS3.getUrl(bucket,fileName).toString(),fileName)); - }); + try (InputStream inputStream = multipartFile.getInputStream()) { + amazonS3.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + } catch (IOException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드 실패" + fileName); + } - return fileList; + return new ImageUpload(amazonS3.getUrl(bucket,fileName).toString()); } public String createFileName(String fileName) { diff --git a/src/main/java/com/helpmeCookies/product/controller/ProductController.java b/src/main/java/com/helpmeCookies/product/controller/ProductController.java index e575c09..59e4985 100644 --- a/src/main/java/com/helpmeCookies/product/controller/ProductController.java +++ b/src/main/java/com/helpmeCookies/product/controller/ProductController.java @@ -1,9 +1,9 @@ package com.helpmeCookies.product.controller; +import com.helpmeCookies.product.dto.ImageUpload; import static com.helpmeCookies.product.util.SortUtil.convertProductSort; import com.helpmeCookies.product.controller.docs.ProductApiDocs; -import com.helpmeCookies.product.dto.FileUploadResponse; import com.helpmeCookies.product.dto.ProductImageResponse; import com.helpmeCookies.product.dto.ProductPage; import com.helpmeCookies.product.dto.ProductRequest; @@ -18,7 +18,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.util.List; @RestController @@ -31,20 +30,22 @@ public class ProductController implements ProductApiDocs { @PostMapping public ResponseEntity saveProduct(@RequestBody ProductRequest productRequest) { - productService.save(productRequest); + Product product = productService.save(productRequest); + productImageService.saveImages(product.getId(),productRequest.imageUrls()); return ResponseEntity.ok().build(); } - @PostMapping("/{productId}/images") - public ResponseEntity uploadImages(@PathVariable("productId") Long productId, List files) throws IOException { - List responses = productImageService.uploadMultiFiles(productId,files); - return ResponseEntity.ok(new ProductImageResponse(responses.stream().map(FileUploadResponse::photoUrl).toList())); + @PostMapping("/images") + public ResponseEntity uploadImages(List files) { + List responses = productImageService.uploadMultiFiles(files); + return ResponseEntity.ok(new ProductImageResponse(responses.stream().map(ImageUpload::photoUrl).toList())); } @GetMapping("/{productId}") public ResponseEntity getProductInfo(@PathVariable("productId") Long productId) { Product product = productService.find(productId); - return ResponseEntity.ok(ProductResponse.from(product)); + List urls = productImageService.getImages(productId); + return ResponseEntity.ok(ProductResponse.from(product,urls)); } @PutMapping("/{productId}") @@ -55,8 +56,11 @@ public ResponseEntity editProductInfo(@PathVariable("productId") Long prod } @PutMapping("/{productId}/images") - public ResponseEntity editImages(@PathVariable("productId") Long productId, List files) throws IOException { + public ResponseEntity editImages(@PathVariable("productId") Long productId, List files) { productImageService.editImages(productId, files); + List images = productImageService.uploadMultiFiles(files).stream() + .map(ImageUpload::photoUrl).toList(); + productImageService.saveImages(productId,images); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java b/src/main/java/com/helpmeCookies/product/dto/ImageUpload.java similarity index 73% rename from src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java rename to src/main/java/com/helpmeCookies/product/dto/ImageUpload.java index ad03aff..fa847b3 100644 --- a/src/main/java/com/helpmeCookies/product/dto/FileUploadResponse.java +++ b/src/main/java/com/helpmeCookies/product/dto/ImageUpload.java @@ -2,15 +2,13 @@ import com.helpmeCookies.product.entity.ProductImage; -public record FileUploadResponse( - String photoUrl, - String uuid +public record ImageUpload( + String photoUrl ) { public ProductImage toEntity(Long productId) { return ProductImage.builder() .productId(productId) .photoUrl(photoUrl) - .uuid(uuid) .build(); } } diff --git a/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java b/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java index e1dfa2d..b0f73d5 100644 --- a/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java +++ b/src/main/java/com/helpmeCookies/product/dto/ProductRequest.java @@ -15,7 +15,9 @@ public record ProductRequest( String description, String preferredLocation, List hashTags, - Long artistInfo + Long artistInfoId, + List imageUrls + ) { public Product toEntity(ArtistInfo artistInfo) { return Product.builder() diff --git a/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java b/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java index 36b6e95..4e927e6 100644 --- a/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java +++ b/src/main/java/com/helpmeCookies/product/dto/ProductResponse.java @@ -14,28 +14,13 @@ public record ProductResponse( String description, String preferredLocation, List hashTags, - ArtistInfo artistInfo + ArtistInfo artistInfo, + List imageUrls ) { - public static class ArtistInfo { - private final Long artistId; - private final String name; - - public ArtistInfo(Long artistId, String name) { - this.artistId = artistId; - this.name = name; - } - - public Long getArtistId() { - return artistId; - } - - public String getName() { - return name; - } + public record ArtistInfo(Long artistId, String artistName) { } - public static ProductResponse from(Product product) { - //TODO artistInfo 코드 개발 이후 수정 예정 + public static ProductResponse from(Product product, List urls) { return new ProductResponse( product.getId(), product.getName(), @@ -45,9 +30,8 @@ public static ProductResponse from(Product product) { product.getDescription(), product.getPreferredLocation(), product.getHashTags(), - new ArtistInfo( - 1L, "임시" - ) + new ArtistInfo(product.getArtistInfo().getId(),product.getArtistInfo().getNickname()), + urls ); } } diff --git a/src/main/java/com/helpmeCookies/product/entity/ProductImage.java b/src/main/java/com/helpmeCookies/product/entity/ProductImage.java index 0a23f4f..ed6cf36 100644 --- a/src/main/java/com/helpmeCookies/product/entity/ProductImage.java +++ b/src/main/java/com/helpmeCookies/product/entity/ProductImage.java @@ -25,4 +25,9 @@ public ProductImage(String photoUrl, Long productId, String uuid) { this.productId = productId; this.uuid = uuid; } + + public String getPhotoUrl() { + return photoUrl; + } } + diff --git a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java index 0e34e78..f8de688 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductImageService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductImageService.java @@ -1,15 +1,16 @@ package com.helpmeCookies.product.service; import com.helpmeCookies.global.utils.AwsS3FileUtils; -import com.helpmeCookies.product.dto.FileUploadResponse; +import com.helpmeCookies.product.dto.ImageUpload; +import com.helpmeCookies.product.entity.ProductImage; import com.helpmeCookies.product.repository.ProductImageRepository; +import java.util.ArrayList; import com.helpmeCookies.product.repository.ProductRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.util.List; @Service @@ -20,22 +21,30 @@ public class ProductImageService { private final ProductRepository productRepository; @Transactional - public List uploadMultiFiles(Long productId, List files) throws IOException { - List uploadResponses = awsS3FileUtils.uploadMultiImages(files); - uploadResponses.forEach(response -> - productImageRepository.save(response.toEntity(productId))); - - var product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 id입니다")); - product.updateThumbnail(uploadResponses.getFirst().photoUrl()); + public List uploadMultiFiles(List files) { + List imageUploads = new ArrayList<>(); + for (MultipartFile multipartFile:files) { + imageUploads.add(awsS3FileUtils.uploadMultiImages(multipartFile)); + } + return imageUploads; + } - return uploadResponses; + @Transactional + public void saveImages(Long productId,List urls) { + //DTO 변환 + List files = urls.stream().map(ImageUpload::new).toList(); + files.forEach(image -> productImageRepository.save(image.toEntity(productId))); } @Transactional - public void editImages(Long productId, List files) throws IOException { + public void editImages(Long productId, List files) { //우선은 전부 삭제하고 다시 업로드 //추후에 개선 예정 + //TODO s3서버에서 기존 사진들을 제거하는 기능 productImageRepository.deleteAllByProductId(productId); - uploadMultiFiles(productId, files); + } + + public List getImages(Long productId) { + return productImageRepository.findAllByProductId(productId).stream().map(ProductImage::getPhotoUrl).toList(); } } diff --git a/src/main/java/com/helpmeCookies/product/service/ProductService.java b/src/main/java/com/helpmeCookies/product/service/ProductService.java index b0b156d..999e247 100644 --- a/src/main/java/com/helpmeCookies/product/service/ProductService.java +++ b/src/main/java/com/helpmeCookies/product/service/ProductService.java @@ -5,6 +5,8 @@ import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.product.repository.ProductImageRepository; import com.helpmeCookies.product.repository.ProductRepository; +import com.helpmeCookies.user.entity.ArtistInfo; +import com.helpmeCookies.user.repository.ArtistInfoRepository; import com.helpmeCookies.product.dto.ProductPage; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; @@ -16,6 +18,7 @@ public class ProductService { private final ProductRepository productRepository; private final ProductImageRepository productImageRepository; + private final ArtistInfoRepository artistInfoRepository; @Transactional(readOnly = true) public ProductPage.Paging getProductsByPage(String query, Pageable pageable) { @@ -24,8 +27,9 @@ public ProductPage.Paging getProductsByPage(String query, Pageable pageable) { } public Product save(ProductRequest productSaveRequest) { - //TODO ArtistInfo 코드 병합시 수정 예정 - Product product = productSaveRequest.toEntity(null); + ArtistInfo artistInfo = artistInfoRepository.findById(productSaveRequest.artistInfoId()) + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 작가 정보입니다.")); + Product product = productSaveRequest.toEntity(artistInfo); productRepository.save(product); return product; } @@ -37,7 +41,8 @@ public Product find(Long productId) { @Transactional public void edit(Long productId, ProductRequest productRequest) { Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 id입니다")); - //TODO ArtistInfo 코드 병합시 수정 예정 + ArtistInfo artistInfo = artistInfoRepository.findById(productRequest.artistInfoId()) + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 작가 정보입니다.")); product.update( productRequest.name(), Category.fromString(productRequest.category()), @@ -46,7 +51,7 @@ public void edit(Long productId, ProductRequest productRequest) { productRequest.description(), productRequest.preferredLocation(), productRequest.hashTags(), - null); + artistInfo); } public void delete(Long productId) { diff --git a/src/main/java/com/helpmeCookies/review/controller/ReviewController.java b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java new file mode 100644 index 0000000..9987947 --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/controller/ReviewController.java @@ -0,0 +1,45 @@ +package com.helpmeCookies.review.controller; + +import com.helpmeCookies.review.dto.ReviewRequest; +import com.helpmeCookies.review.dto.ReviewResponse; +import com.helpmeCookies.review.entity.Review; +import com.helpmeCookies.review.service.ReviewService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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("/v1/reviews") +@RequiredArgsConstructor +public class ReviewController { + + private final ReviewService reviewService; + + //TODO 감상평 삭제 + //TODO 해당 상품의 감상평 조회 + //TODO 내 감상평 조회 + + @PostMapping("/{productId}") + public ResponseEntity postReview(@RequestBody ReviewRequest request, @PathVariable Long productId) { + reviewService.saveReview(request, productId); + return ResponseEntity.ok().build(); + } + + @PutMapping("/{productId}/{reviewId}") + public ResponseEntity editReview(@RequestBody ReviewRequest request, @PathVariable Long productId, @PathVariable Long reviewId) { + reviewService.editReview(request, productId, reviewId); + return ResponseEntity.ok().build(); + } + + @GetMapping("/{reviewId}") + public ResponseEntity getReview(@PathVariable Long reviewId) { + Review response = reviewService.getReview(reviewId); + return ResponseEntity.ok(ReviewResponse.fromEntity(response)); + } +} diff --git a/src/main/java/com/helpmeCookies/review/dto/ReviewRequest.java b/src/main/java/com/helpmeCookies/review/dto/ReviewRequest.java new file mode 100644 index 0000000..17a2600 --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/dto/ReviewRequest.java @@ -0,0 +1,15 @@ +package com.helpmeCookies.review.dto; + +import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.review.entity.Review; +import com.helpmeCookies.user.entity.User; + +public record ReviewRequest(Long writerId, String content) { + public Review toEntity(User writer, Product product) { + return Review.builder() + .content(content) + .writer(writer) + .product(product) + .build(); + } +} diff --git a/src/main/java/com/helpmeCookies/review/dto/ReviewResponse.java b/src/main/java/com/helpmeCookies/review/dto/ReviewResponse.java new file mode 100644 index 0000000..044f00f --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/dto/ReviewResponse.java @@ -0,0 +1,11 @@ +package com.helpmeCookies.review.dto; + +import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.review.entity.Review; +import com.helpmeCookies.user.entity.User; + +public record ReviewResponse(Long id, String content,User writer, Product product) { + public static ReviewResponse fromEntity(Review review) { + return new ReviewResponse(review.getId(), review.getContent(),review.getWriter(), review.getProduct()); + } +} diff --git a/src/main/java/com/helpmeCookies/product/entity/Review.java b/src/main/java/com/helpmeCookies/review/entity/Review.java similarity index 52% rename from src/main/java/com/helpmeCookies/product/entity/Review.java rename to src/main/java/com/helpmeCookies/review/entity/Review.java index 854fdbb..1c9af76 100644 --- a/src/main/java/com/helpmeCookies/product/entity/Review.java +++ b/src/main/java/com/helpmeCookies/review/entity/Review.java @@ -1,5 +1,6 @@ -package com.helpmeCookies.product.entity; +package com.helpmeCookies.review.entity; +import com.helpmeCookies.product.entity.Product; import com.helpmeCookies.user.entity.User; import jakarta.persistence.Column; @@ -9,6 +10,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.Builder; @Entity public class Review { @@ -27,4 +29,34 @@ public class Review { @ManyToOne @JoinColumn(name = "product_id", nullable = false) private Product product; + + @Builder + public Review(Long id, String content, User writer, Product product) { + this.id = id; + this.content = content; + this.writer = writer; + this.product = product; + } + + public Review() {} + + public void updateContent(String content) { + this.content = content; + } + + public Long getId() { + return id; + } + + public String getContent() { + return content; + } + + public User getWriter() { + return writer; + } + + public Product getProduct() { + return product; + } } diff --git a/src/main/java/com/helpmeCookies/review/repository/ReviewRepository.java b/src/main/java/com/helpmeCookies/review/repository/ReviewRepository.java new file mode 100644 index 0000000..a64c2ff --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/repository/ReviewRepository.java @@ -0,0 +1,9 @@ +package com.helpmeCookies.review.repository; + +import com.helpmeCookies.review.entity.Review; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReviewRepository extends JpaRepository { +} diff --git a/src/main/java/com/helpmeCookies/review/service/ReviewService.java b/src/main/java/com/helpmeCookies/review/service/ReviewService.java new file mode 100644 index 0000000..a7f873a --- /dev/null +++ b/src/main/java/com/helpmeCookies/review/service/ReviewService.java @@ -0,0 +1,35 @@ +package com.helpmeCookies.review.service; + +import com.helpmeCookies.product.entity.Product; +import com.helpmeCookies.product.repository.ProductRepository; +import com.helpmeCookies.review.dto.ReviewRequest; +import com.helpmeCookies.review.entity.Review; +import com.helpmeCookies.review.repository.ReviewRepository; +import com.helpmeCookies.user.entity.User; +import com.helpmeCookies.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReviewService { + private final ReviewRepository reviewRepository; + private final UserRepository userRepository; + private final ProductRepository productRepository; + + public void saveReview(ReviewRequest request, Long productId) { + User writer = userRepository.findById(request.writerId()).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 writerID입니다.")); + Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 productID입니다.")); + + reviewRepository.save(request.toEntity(writer,product)); + } + + public void editReview(ReviewRequest request, Long productId, Long reviewId) { + Review review = reviewRepository.findById(reviewId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 reviewId 입니다.")); + review.updateContent(request.content()); + } + + public Review getReview(Long reviewId) { + return reviewRepository.findById(reviewId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 reviewId 입니다.")); + } + }