diff --git a/src/main/java/farmSystem/closeUp/common/Result.java b/src/main/java/farmSystem/closeUp/common/Result.java index c6c35eb..4706085 100644 --- a/src/main/java/farmSystem/closeUp/common/Result.java +++ b/src/main/java/farmSystem/closeUp/common/Result.java @@ -42,6 +42,7 @@ public enum Result { NOTFOUND_RAFFLE(404, "해당 래플이 존재하지 않습니다."), RAFFLE_END(404, "래플 응모가 마감됐습니다."), NOT_ENOUGH_POINT(404, "응모할 포인트가 부족합니다"), + NOTFOUND_CATEGORY(404, "해당 카테고리가 존재하지 않습니다."), // 포인트 LESS_THAN_MINIMUM_POINT(400, "5000원 이상부터 충전이 가능합니다."), diff --git a/src/main/java/farmSystem/closeUp/config/S3Config.java b/src/main/java/farmSystem/closeUp/config/s3/S3Config.java similarity index 96% rename from src/main/java/farmSystem/closeUp/config/S3Config.java rename to src/main/java/farmSystem/closeUp/config/s3/S3Config.java index 5612aad..dceb9f9 100644 --- a/src/main/java/farmSystem/closeUp/config/S3Config.java +++ b/src/main/java/farmSystem/closeUp/config/s3/S3Config.java @@ -1,4 +1,4 @@ -package farmSystem.closeUp.config; +package farmSystem.closeUp.config.s3; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; diff --git a/src/main/java/farmSystem/closeUp/config/s3/S3Uploader.java b/src/main/java/farmSystem/closeUp/config/s3/S3Uploader.java new file mode 100644 index 0000000..9352715 --- /dev/null +++ b/src/main/java/farmSystem/closeUp/config/s3/S3Uploader.java @@ -0,0 +1,71 @@ +package farmSystem.closeUp.config.s3; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.PutObjectRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Optional; + +@Slf4j +@RequiredArgsConstructor +@Component +public class S3Uploader { + private final AmazonS3Client amazonS3Client; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + // MultipartFile을 전달받아 File로 전환한 후 S3에 업로드 + public String upload(MultipartFile multipartFile, String dirName) throws IOException { + File uploadFile = convert(multipartFile) + .orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File 전환 실패")); + return upload(uploadFile, dirName); + } + + private String upload(File uploadFile, String dirName) { + String fileName = dirName + "/" + uploadFile.getName(); + String uploadImageUrl = putS3(uploadFile, fileName); + + removeNewFile(uploadFile); // 로컬에 생성된 File 삭제 (MultipartFile -> File 전환 하며 로컬에 파일 생성됨) + + return uploadImageUrl; // 업로드된 파일의 S3 URL 주소 반환 + } + + private void removeNewFile(File targetFile) { + if(targetFile.delete()) { + log.info("파일이 삭제되었습니다."); + }else { + log.info("파일이 삭제되지 못했습니다."); + } + } + + + private String putS3(File uploadFile, String fileName) { + amazonS3Client.putObject( + new PutObjectRequest(bucket, fileName, uploadFile) + .withCannedAcl(CannedAccessControlList.PublicRead) // PublicRead 권한으로 업로드 됨 + ); + return amazonS3Client.getUrl(bucket, fileName).toString(); + } + + + private Optional convert(MultipartFile file) throws IOException { + File convertFile = new File(file.getOriginalFilename()); + if(convertFile.createNewFile()) { + try (FileOutputStream fos = new FileOutputStream(convertFile)) { + fos.write(file.getBytes()); + } + return Optional.of(convertFile); + } + return Optional.empty(); + } + +} diff --git a/src/main/java/farmSystem/closeUp/controller/RaffleProductController.java b/src/main/java/farmSystem/closeUp/controller/RaffleProductController.java index 211ed84..d1db27c 100644 --- a/src/main/java/farmSystem/closeUp/controller/RaffleProductController.java +++ b/src/main/java/farmSystem/closeUp/controller/RaffleProductController.java @@ -1,18 +1,20 @@ package farmSystem.closeUp.controller; import farmSystem.closeUp.common.CommonResponse; -import farmSystem.closeUp.dto.raffleProduct.response.GetRaffleProductApplyResponse; -import farmSystem.closeUp.dto.raffleProduct.response.GetRaffleProductResponse; -import farmSystem.closeUp.dto.raffleProduct.response.GetRaffleProductsResponse; -import farmSystem.closeUp.dto.raffleProduct.response.PostRaffleProductResponse; +import farmSystem.closeUp.dto.raffleProduct.request.PostCreateRaffleProductRequest; +import farmSystem.closeUp.dto.raffleProduct.response.*; import farmSystem.closeUp.service.RaffleProductService; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.naming.AuthenticationException; +import java.io.IOException; @RestController @Slf4j @@ -70,4 +72,10 @@ public CommonResponse getOrder(@PathVariable("raf public CommonResponse postRaffleProduct(@PathVariable("raffleProductId") Long raffleProductId){ return CommonResponse.success(raffleProductService.postRaffleProduct(raffleProductId)); } + + // 래플 생성하기 + @PostMapping(value = "/creator/raffle-products", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public CommonResponse postCreateRaffleProduct(@RequestParam(value="thumbnailImage") MultipartFile thumbnailImage, @Valid final PostCreateRaffleProductRequest postCreateRaffleProductRequest) throws IOException { + return CommonResponse.success(raffleProductService.postCreateRaffleProduct(thumbnailImage, postCreateRaffleProductRequest)); + } } diff --git a/src/main/java/farmSystem/closeUp/domain/RaffleProduct.java b/src/main/java/farmSystem/closeUp/domain/RaffleProduct.java index 38e36c6..6df5aec 100644 --- a/src/main/java/farmSystem/closeUp/domain/RaffleProduct.java +++ b/src/main/java/farmSystem/closeUp/domain/RaffleProduct.java @@ -46,6 +46,21 @@ public class RaffleProduct extends BaseEntity{ private Category category; @Builder + public RaffleProduct(Long raffleProductId, String title, LocalDate startDate, LocalDate endDate, String content, Long winnerCount, Long rafflePrice, String address, LocalDateTime winningDate, String thumbnailImageUrl, User creator, Category category) { + this.raffleProductId = raffleProductId; + this.title = title; + this.startDate = startDate; + this.endDate = endDate; + this.content = content; + this.winnerCount = winnerCount; + this.rafflePrice = rafflePrice; + this.address = address; + this.winningDate = winningDate; + this.thumbnailImageUrl = thumbnailImageUrl; + this.creator = creator; + this.category = category; + } + public RaffleProduct(Long raffleProductId, String title, LocalDate startDate, LocalDate endDate, String content, Long winnerCount, Long rafflePrice, String address, LocalDateTime winningDate, String thumbnailImageUrl) { this.raffleProductId = raffleProductId; this.title = title; @@ -57,5 +72,7 @@ public RaffleProduct(Long raffleProductId, String title, LocalDate startDate, Lo this.address = address; this.winningDate = winningDate; this.thumbnailImageUrl = thumbnailImageUrl; + this.creator = creator; + this.category = category; } } diff --git a/src/main/java/farmSystem/closeUp/dto/raffleProduct/request/PostCreateRaffleProductExtraRequest.java b/src/main/java/farmSystem/closeUp/dto/raffleProduct/request/PostCreateRaffleProductExtraRequest.java new file mode 100644 index 0000000..8da01d8 --- /dev/null +++ b/src/main/java/farmSystem/closeUp/dto/raffleProduct/request/PostCreateRaffleProductExtraRequest.java @@ -0,0 +1,14 @@ +package farmSystem.closeUp.dto.raffleProduct.request; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PostCreateRaffleProductExtraRequest { + private String file; +} diff --git a/src/main/java/farmSystem/closeUp/dto/raffleProduct/request/PostCreateRaffleProductRequest.java b/src/main/java/farmSystem/closeUp/dto/raffleProduct/request/PostCreateRaffleProductRequest.java new file mode 100644 index 0000000..7c3bb11 --- /dev/null +++ b/src/main/java/farmSystem/closeUp/dto/raffleProduct/request/PostCreateRaffleProductRequest.java @@ -0,0 +1,19 @@ +package farmSystem.closeUp.dto.raffleProduct.request; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Builder +@Getter +public class PostCreateRaffleProductRequest { + private String title; + private LocalDateTime startDate; + private LocalDateTime endDate; + private String content; + private Long winnerCount; + private Long rafflePrice; + private Long categoryId; + private PostCreateRaffleProductExtraRequest extraRequest; +} diff --git a/src/main/java/farmSystem/closeUp/dto/raffleProduct/response/PostCreateRaffleProductResponse.java b/src/main/java/farmSystem/closeUp/dto/raffleProduct/response/PostCreateRaffleProductResponse.java new file mode 100644 index 0000000..d25ff39 --- /dev/null +++ b/src/main/java/farmSystem/closeUp/dto/raffleProduct/response/PostCreateRaffleProductResponse.java @@ -0,0 +1,18 @@ +package farmSystem.closeUp.dto.raffleProduct.response; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +public class PostCreateRaffleProductResponse { + private LocalDateTime winnerDate; + + public static PostCreateRaffleProductResponse of(LocalDateTime winnerDate) { + return PostCreateRaffleProductResponse.builder() + .winnerDate(winnerDate) + .build(); + } +} diff --git a/src/main/java/farmSystem/closeUp/repository/category/CategoryRepository.java b/src/main/java/farmSystem/closeUp/repository/category/CategoryRepository.java new file mode 100644 index 0000000..d7834e5 --- /dev/null +++ b/src/main/java/farmSystem/closeUp/repository/category/CategoryRepository.java @@ -0,0 +1,10 @@ +package farmSystem.closeUp.repository.category; + +import farmSystem.closeUp.domain.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CategoryRepository extends JpaRepository { + Optional findByCategoryId(Long id); +} diff --git a/src/main/java/farmSystem/closeUp/service/RaffleProductService.java b/src/main/java/farmSystem/closeUp/service/RaffleProductService.java index 707e1ad..2e1ee80 100644 --- a/src/main/java/farmSystem/closeUp/service/RaffleProductService.java +++ b/src/main/java/farmSystem/closeUp/service/RaffleProductService.java @@ -2,27 +2,29 @@ import farmSystem.closeUp.common.CustomException; import farmSystem.closeUp.common.Result; +import farmSystem.closeUp.config.s3.S3Uploader; import farmSystem.closeUp.domain.*; -import farmSystem.closeUp.dto.raffleProduct.response.GetRaffleProductApplyResponse; -import farmSystem.closeUp.dto.raffleProduct.response.GetRaffleProductResponse; -import farmSystem.closeUp.dto.raffleProduct.response.GetRaffleProductsResponse; -import farmSystem.closeUp.dto.raffleProduct.response.PostRaffleProductResponse; +import farmSystem.closeUp.dto.raffleProduct.request.PostCreateRaffleProductRequest; +import farmSystem.closeUp.dto.raffleProduct.response.*; +import farmSystem.closeUp.repository.category.CategoryRepository; +import farmSystem.closeUp.repository.follow.FollowRepository; import farmSystem.closeUp.repository.pointHistory.PointHistoryRepository; import farmSystem.closeUp.repository.raffle.RaffleRepository; - -import farmSystem.closeUp.repository.follow.FollowRepository; import farmSystem.closeUp.repository.raffleProduct.RaffleProductRepository; import farmSystem.closeUp.repository.raffleProduct.RaffleProductRepositoryImpl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import javax.naming.AuthenticationException; +import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collections; @@ -33,6 +35,8 @@ @Slf4j @RequiredArgsConstructor public class RaffleProductService { + @Autowired + private S3Uploader s3Uploader; private final RaffleProductRepository raffleProductRepository; private final RaffleProductRepositoryImpl raffleProductRepositoryImpl; @@ -40,6 +44,9 @@ public class RaffleProductService { private final UserService userService; private final RaffleRepository raffleRepository; private final PointHistoryRepository pointHistoryRepository; + private final CategoryRepository categoryRepository; + + // 회원님이 팔로우하는 크리에이터 래플 목록 조회 @Transactional(readOnly = true) @@ -58,7 +65,7 @@ public Slice getFollowingRaffleProducts(Pageable page // 아무도 팔로우 안한 경우 빈 리스트 반환 if (followList.isEmpty()) { creatorIds = Collections.emptyList(); - }else { + } else { creatorIds = followList .stream() .map(follow -> follow.getCreator().getUserId()) @@ -71,29 +78,28 @@ public Slice getFollowingRaffleProducts(Pageable page try { Slice followingRaffleProducts = raffleProductRepositoryImpl.findFollowingRaffleProducts(creatorIds, pageable); return followingRaffleProducts; - } - catch (Exception e) { + } catch (Exception e) { throw new CustomException(Result.NOTFOUND_RAFFLE); } } // 전체 래플 목록 조회 @Transactional(readOnly = true) - public Slice getRaffleProducts(Pageable pageable){ + public Slice getRaffleProducts(Pageable pageable) { Sort sort = Sort.by(Sort.Direction.DESC, "createdAt"); PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort); try { Slice raffleProducts = raffleProductRepositoryImpl.findRaffleProducts(pageable); return raffleProducts; - }catch (Exception e) { + } catch (Exception e) { throw new CustomException(Result.NOTFOUND_RAFFLE); } } // 래플 상세 조회 @Transactional(readOnly = true) - public GetRaffleProductResponse getRaffleProduct(Long raffleProductId){ + public GetRaffleProductResponse getRaffleProduct(Long raffleProductId) { RaffleProduct raffleProduct = raffleProductRepository.findById(raffleProductId).orElseThrow(() -> new CustomException(Result.NOTFOUND_RAFFLE)); GetRaffleProductResponse getRaffleProductResponse = GetRaffleProductResponse. of(raffleProduct.getRaffleProductId(), raffleProduct.getTitle(), raffleProduct.getStartDate(), raffleProduct.getEndDate(), raffleProduct.getContent(), raffleProduct.getWinnerCount(), raffleProduct.getRafflePrice(), raffleProduct.getWinningDate(), raffleProduct.getThumbnailImageUrl(), raffleProduct.getCreator().getNickName(), raffleProduct.getCreator().getUserId()); @@ -133,7 +139,7 @@ public PostRaffleProductResponse postRaffleProduct(Long raffleProductId) { throw new CustomException(Result.RAFFLE_END); } - if (user.getPoint() - raffleProduct.getRafflePrice()<0){ + if (user.getPoint() - raffleProduct.getRafflePrice() < 0) { throw new CustomException(Result.NOT_ENOUGH_POINT); } @@ -169,4 +175,46 @@ public Slice getCreatorRaffleProducts(Long creatorId, return findRaffles; } + + @Transactional + public PostCreateRaffleProductResponse postCreateRaffleProduct(MultipartFile thumbnailImage, PostCreateRaffleProductRequest postCreateRaffleProductRequest) throws IOException { + // 현재 크리에이터 조회 + User user = userService.getCurrentUser(); + + // 카테고리 조회 + Category category = categoryRepository.findByCategoryId(postCreateRaffleProductRequest.getCategoryId()).orElseThrow(() -> new CustomException(Result.NOTFOUND_CATEGORY)); + + // 당첨자 발표 날짜 구하기 - 래플 종료 일 정오 12시 + LocalDateTime endDate = postCreateRaffleProductRequest.getEndDate(); + LocalDateTime winningDate = endDate.plusDays(0).withHour(12).withMinute(0).withSecond(0).withNano(0); + + // 섬네일 이미지 s3에 저장 + String thumbnailImageUrl = null; + if(!thumbnailImage.isEmpty()) { + String storedFileName = s3Uploader.upload(thumbnailImage, "raffle_thumbnail"); + thumbnailImageUrl = storedFileName; + } + + // 래플 상품 생성 + RaffleProduct raffleProduct = RaffleProduct.builder() + .title(postCreateRaffleProductRequest.getTitle()) + .startDate(postCreateRaffleProductRequest.getStartDate()) + .endDate(postCreateRaffleProductRequest.getEndDate()) + .winningDate(winningDate) + .content(postCreateRaffleProductRequest.getContent()) + .winnerCount(postCreateRaffleProductRequest.getWinnerCount()) + .rafflePrice(postCreateRaffleProductRequest.getRafflePrice()) + .thumbnailImageUrl(thumbnailImageUrl) + .creator(user) + .category(category) + .build(); + + // 무형인지 유형인지 판단 +// if (category.getParent().getCategoryId() == Long.valueOf(2)) { +// raffleProduct. +// } + + raffleProductRepository.save(raffleProduct); + return PostCreateRaffleProductResponse.of(winningDate); + } }