diff --git a/.gitignore b/.gitignore index f4106018..1f3d8456 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ db_dev.trace.db .DS_Store .env /src/main/generated/ +/k6 ### GA4 Service Account Key (민감 정보!) ### src/main/resources/ga4-service-account.json diff --git a/src/main/java/com/back/domain/funding/controller/FundingNewsController.java b/src/main/java/com/back/domain/funding/controller/FundingNewsController.java index 29ab0f29..ce671009 100644 --- a/src/main/java/com/back/domain/funding/controller/FundingNewsController.java +++ b/src/main/java/com/back/domain/funding/controller/FundingNewsController.java @@ -3,16 +3,24 @@ import com.back.domain.funding.dto.request.FundingNewsCreateRequest; import com.back.domain.funding.service.FundingNewsService; import com.back.global.rsData.RsData; +import com.back.global.s3.FileType; +import com.back.global.s3.S3Service; +import com.back.global.s3.UploadResultResponse; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @RestController @RequestMapping("/api/fundings") @@ -20,6 +28,7 @@ @Tag(name = "펀딩 새소식", description = "펀딩 새소식 API 컨트롤러") public class FundingNewsController{ private final FundingNewsService fundingNewsService; + private final S3Service s3Service; @PostMapping("/{id}/news") @PreAuthorize("isAuthenticated()") @@ -34,6 +43,34 @@ public ResponseEntity> addNews( .body(new RsData<>("201", "새소식이 등록되었습니다.", newsId)); } + @PostMapping(value = "/{id}/news/images", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PreAuthorize("hasAuthority('ROLE_ARTIST') or hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_ROOT')") + @Operation( + summary = "펀딩 새소식 이미지 업로드", + description = "펀딩 새소식에 사용할 이미지 업로드. 한 장만 업로드 가능" + ) + public ResponseEntity>> uploadNewsImage( + @PathVariable @Positive Long id, + @Parameter(description = "업로드 할 이미지 파일", required = true) + @RequestPart("file")MultipartFile file) { + List result = s3Service.uploadFile(file, "funding-news-images", FileType.ADDITIONAL); + return ResponseEntity.ok(RsData.of("200", "이미지 업로드 성공", result)); + } + + @DeleteMapping("/{id}/news/images") + @PreAuthorize("hasAuthority('ROLE_ARTIST') or hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_ROOT')") + @Operation( + summary = "펀딩 새소식 이미지 삭제", + description = "S3에 업로드된 이미지 삭제" + ) + public ResponseEntity> deleteNewsImages( + @PathVariable @Positive Long id, + @Parameter(description = "삭제할 파일의 s3Key", required = true) + @RequestParam String s3Key) { + s3Service.deleteFile(s3Key); + return ResponseEntity.ok(RsData.of("200", "이미지가 성공적으로 삭제되었습니다.", s3Key)); + } + @DeleteMapping("/{fundingId}/news/{newsId}") @PreAuthorize("isAuthenticated()") @Operation(summary = "펀딩 새소식 삭제", description = "펀딩 새소식을 삭제") diff --git a/src/main/java/com/back/domain/funding/dto/request/FundingNewsCreateRequest.java b/src/main/java/com/back/domain/funding/dto/request/FundingNewsCreateRequest.java index 821c7679..271f4dc0 100644 --- a/src/main/java/com/back/domain/funding/dto/request/FundingNewsCreateRequest.java +++ b/src/main/java/com/back/domain/funding/dto/request/FundingNewsCreateRequest.java @@ -5,5 +5,6 @@ public record FundingNewsCreateRequest( @NotBlank String title, @NotBlank String content, - String imageUrl + String imageUrl, + String s3Key ) {} \ No newline at end of file diff --git a/src/main/java/com/back/domain/funding/dto/response/FundingNewsItemDto.java b/src/main/java/com/back/domain/funding/dto/response/FundingNewsItemDto.java index 1a6463ec..73ec80ad 100644 --- a/src/main/java/com/back/domain/funding/dto/response/FundingNewsItemDto.java +++ b/src/main/java/com/back/domain/funding/dto/response/FundingNewsItemDto.java @@ -11,6 +11,7 @@ public record FundingNewsItemDto( String title, String content, String imageUrl, + String s3Key, @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createDate ) { @@ -21,6 +22,7 @@ public FundingNewsItemDto(FundingNews u) { u.getTitle(), u.getContent(), u.getImageUrl(), + u.getS3Key(), u.getCreateDate() ); } diff --git a/src/main/java/com/back/domain/funding/entity/FundingNews.java b/src/main/java/com/back/domain/funding/entity/FundingNews.java index 357ac97a..bf094cf5 100644 --- a/src/main/java/com/back/domain/funding/entity/FundingNews.java +++ b/src/main/java/com/back/domain/funding/entity/FundingNews.java @@ -31,6 +31,8 @@ public class FundingNews extends BaseEntity { private String imageUrl; + private String s3Key; + @Column(nullable = false) @Builder.Default private boolean deleted = false; @@ -38,4 +40,9 @@ public class FundingNews extends BaseEntity { public void delete() { this.deleted = true; } + + public void removeImage() { + this.imageUrl = null; + this.s3Key = null; + } } diff --git a/src/main/java/com/back/domain/funding/service/FundingNewsService.java b/src/main/java/com/back/domain/funding/service/FundingNewsService.java index fef5e403..753167f8 100644 --- a/src/main/java/com/back/domain/funding/service/FundingNewsService.java +++ b/src/main/java/com/back/domain/funding/service/FundingNewsService.java @@ -8,6 +8,8 @@ import com.back.domain.user.entity.User; import com.back.domain.user.repository.UserRepository; import com.back.global.exception.ServiceException; +import com.back.global.s3.S3Service; +import com.back.global.s3.S3ValidationService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,6 +20,8 @@ public class FundingNewsService { private final FundingNewsRepository fundingNewsRepository; private final FundingRepository fundingRepository; private final UserRepository userRepository; + private final S3Service s3Service; + private final S3ValidationService s3ValidationService; @Transactional public Long addFundingNews(Long fundingId, FundingNewsCreateRequest req, String userEmail) { @@ -31,12 +35,17 @@ public Long addFundingNews(Long fundingId, FundingNewsCreateRequest req, String throw new ServiceException("403", "권한이 없습니다."); } + if (req.s3Key() != null && !req.s3Key().isBlank()) { + s3ValidationService.validateFileExists(req.s3Key()); + } + FundingNews news = FundingNews.builder() .funding(funding) .artist(artist) .title(req.title()) .content(req.content()) .imageUrl(req.imageUrl()) + .s3Key(req.s3Key()) .build(); fundingNewsRepository.save(news); @@ -54,6 +63,9 @@ public void deleteFundingNews(Long fundingId ,Long newsId, String userEmail) { if (!funding.getUser().getId().equals(artist.getId())) { throw new ServiceException("403", "권한이 없습니다."); } + if (news.getS3Key() != null && !news.getS3Key().isBlank()){ + s3Service.deleteFile(news.getS3Key()); + } news.delete(); } } \ No newline at end of file