diff --git a/src/main/java/com/hongik/graduationproject/controller/VideoSummaryController.java b/src/main/java/com/hongik/graduationproject/controller/VideoSummaryController.java index 4c9e84b..bae65f7 100644 --- a/src/main/java/com/hongik/graduationproject/controller/VideoSummaryController.java +++ b/src/main/java/com/hongik/graduationproject/controller/VideoSummaryController.java @@ -56,7 +56,7 @@ public Response getSummaryByVideoSummaryId(@PathVariable(name = @AuthenticationPrincipal Long userId ) { log.info("summary requested videoSummaryId = {}", videoSummaryId); - return Response.createSuccess(videoSummaryService.getVideoSummaryById(videoSummaryId, userId)); + return Response.createSuccess(videoSummaryService.getSummaryByVideoSummaryId(videoSummaryId, userId)); } @Operation(summary = "영상 요약 목록 조회", description = "categoryId로 영상 요약 목록 조회를 위한 메소드") @@ -76,4 +76,33 @@ public List getAllVideoIdsBySearchWord(@Parameter(name = "searchWord", des log.info("검색어 '{}' 에 대한 모든 videoIds 조회 결과: {}", searchWord, videoIds); return Response.createSuccess(videoIds).getData(); } + + @Operation(summary = "숏폼 삭제", description = "사용자가 원하는 숏폼 삭제하는 메소드") + @ApiResponse(content = @Content(schema = @Schema(implementation = Response.class))) + @DeleteMapping("/summaries/delete/{videoSummaryId}") + @ResponseStatus(HttpStatus.OK) + public Response deleteVideoSummary(@PathVariable Long videoSummaryId) { + log.info("Deleting video summary with ID = {}", videoSummaryId); + videoSummaryService.deleteVideoSummary(videoSummaryId); + return Response.createSuccess("숏폼 삭제 완료"); + } + + @Operation(summary = "삭제된 숏폼 목록 조회", description = "삭제된 모든 숏폼의 목록 조회를 위한 api") + @ApiResponse(content = @Content(schema = @Schema(implementation = VideoSummaryListResponse.class))) + @GetMapping("/summaries/deleted") + @ResponseStatus(HttpStatus.OK) + public Response getAllDeletedVideoSummary() { + log.info("삭제된 숏폼 목록을 반환하는 중"); + return Response.createSuccess(videoSummaryService.getAllDeletedVideoSummary()); + } + + @Operation(summary = "삭제된 숏폼 복구", description = "삭제된 숏폼을 복구하는 메소드") + @ApiResponse(content = @Content(schema = @Schema(implementation = Response.class))) + @PatchMapping("/summaries/restore/{videoSummaryId}") + @ResponseStatus(HttpStatus.OK) + public Response restoreVideoSummary(@PathVariable Long videoSummaryId) { + log.info("Restoring deleted video summary with ID = {}", videoSummaryId); + videoSummaryService.restoreVideoSummary(videoSummaryId); + return Response.createSuccess("숏폼 복구 완료"); + } } \ No newline at end of file diff --git a/src/main/java/com/hongik/graduationproject/domain/entity/VideoSummary.java b/src/main/java/com/hongik/graduationproject/domain/entity/VideoSummary.java index bc6b041..4f8cc33 100644 --- a/src/main/java/com/hongik/graduationproject/domain/entity/VideoSummary.java +++ b/src/main/java/com/hongik/graduationproject/domain/entity/VideoSummary.java @@ -5,6 +5,7 @@ import com.hongik.graduationproject.enums.MainCategory; import com.hongik.graduationproject.enums.Platform; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.*; import java.util.List; @@ -33,6 +34,10 @@ public class VideoSummary extends BaseTimeEntity { private Platform platform; @Enumerated(EnumType.STRING) private MainCategory generatedMainCategory; + @Column(name = "is_deleted") + private boolean isDeleted = false; + @Column(name = "deleted_at") + private LocalDateTime deletedAt; public static VideoSummary of(VideoSummaryMessage videoSummaryMessage) { return VideoSummary @@ -60,4 +65,14 @@ private static String listToString(List keywords) { return ""; } } + + public void markAsDeleted() { + this.isDeleted = true; + this.deletedAt = LocalDateTime.now(); + } + + public void restore() { + this.isDeleted = false; + this.deletedAt = null; + } } diff --git a/src/main/java/com/hongik/graduationproject/exception/ErrorCode.java b/src/main/java/com/hongik/graduationproject/exception/ErrorCode.java index 37d1995..32272c0 100644 --- a/src/main/java/com/hongik/graduationproject/exception/ErrorCode.java +++ b/src/main/java/com/hongik/graduationproject/exception/ErrorCode.java @@ -17,6 +17,7 @@ public enum ErrorCode { CATEGORY_NOT_EXIST(HttpStatus.NOT_FOUND, "카테고리를 찾을 수 없습니다."), USER_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 가입한 사용자입니다."), SUBCATEGORY_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 서브 카테고리입니다."), + VIDEO_SUMMARY_ALREADY_DELETED(HttpStatus.BAD_REQUEST, "사용자가 이미 해당 영상을 삭제 요청했습니다."), // JWT 관련 에러 JWT_NOT_EXISTS(HttpStatus.UNAUTHORIZED, "요청에 JWT가 존재하지 않습니다."), diff --git a/src/main/java/com/hongik/graduationproject/repository/VideoSummaryCategoryRepository.java b/src/main/java/com/hongik/graduationproject/repository/VideoSummaryCategoryRepository.java index bd09552..11766eb 100644 --- a/src/main/java/com/hongik/graduationproject/repository/VideoSummaryCategoryRepository.java +++ b/src/main/java/com/hongik/graduationproject/repository/VideoSummaryCategoryRepository.java @@ -11,6 +11,8 @@ import java.util.List; public interface VideoSummaryCategoryRepository extends JpaRepository { + + @Query("SELECT vsc FROM VideoSummaryCategory vsc WHERE vsc.category = :category AND vsc.videoSummary.isDeleted = false") List findAllByCategory(Category category); @Query("select vsc " + @@ -25,4 +27,5 @@ public interface VideoSummaryCategoryRepository extends JpaRepository findByVideoCode(String videoCode); @Query("SELECT vs.id FROM VideoSummary vs " + - "WHERE vs.title LIKE CONCAT('%', :searchword, '%')" + "WHERE (vs.title LIKE CONCAT('%', :searchword, '%')" + "OR vs.description LIKE CONCAT('%', :searchword, '%')" + "OR vs.summary LIKE CONCAT('%', :searchword, '%')" - + "OR vs.keywords LIKE CONCAT('%', :searchword, '%')") + + "OR vs.keywords LIKE CONCAT('%', :searchword, '%'))" + + "AND vs.isDeleted = false" + ) List getAllVideoIdsBySearchWord(@Param("searchword") String searchWord); + + List findAllByIsDeletedTrue(); + + @Query("SELECT vs FROM VideoSummary vs WHERE vs.id = :videoSummaryId AND vs.isDeleted = true") + Optional findDeletedById(@Param("videoSummaryId") Long videoSummaryId); } \ No newline at end of file diff --git a/src/main/java/com/hongik/graduationproject/service/VideoSummaryService.java b/src/main/java/com/hongik/graduationproject/service/VideoSummaryService.java index bc73d67..b0af130 100644 --- a/src/main/java/com/hongik/graduationproject/service/VideoSummaryService.java +++ b/src/main/java/com/hongik/graduationproject/service/VideoSummaryService.java @@ -1,110 +1,141 @@ -package com.hongik.graduationproject.service; - -import com.hongik.graduationproject.domain.dto.video.*; -import com.hongik.graduationproject.domain.entity.Category; -import com.hongik.graduationproject.domain.entity.User; -import com.hongik.graduationproject.domain.entity.VideoSummary; -import com.hongik.graduationproject.domain.entity.VideoSummaryCategory; -import com.hongik.graduationproject.domain.entity.cache.VideoSummaryStatusCache; -import com.hongik.graduationproject.enums.Platform; -import com.hongik.graduationproject.exception.AppException; -import com.hongik.graduationproject.exception.ErrorCode; -import com.hongik.graduationproject.repository.*; -import com.hongik.graduationproject.util.UrlUtils; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Optional; - -import static com.hongik.graduationproject.enums.SummaryStatus.*; - -@Service -@RequiredArgsConstructor -public class VideoSummaryService { - private final MessageService messageService; - private final VideoSummaryRepository videoSummaryRepository; - private final VideoSummaryStatusCacheRepository summaryStatusCacheRepository; - private final CategoryRepository categoryRepository; - private final VideoSummaryCategoryRepository videoSummaryCategoryRepository; - private final UserRepository userRepository; - - public VideoSummaryInitiateResponse initiateSummarizing(VideoSummaryInitiateRequest summaryInitiateRequest, Long userId) { - Platform platform = UrlUtils.getVideoPlatform(summaryInitiateRequest.getUrl()); - String videoId = UrlUtils.getVideoId(summaryInitiateRequest.getUrl(), platform); - - String videoCode = platform.name() + '_' + videoId; - - if (checkDuplicateSummarizing(videoCode, userId)) { - throw new AppException(ErrorCode.ALREADY_REQUESTED_SUMMARIZING); - } - - Optional mayBeStatusCache = summaryStatusCacheRepository.findFirstByVideoCode(videoCode); - if (mayBeStatusCache.isPresent()) { - summaryStatusCacheRepository.save(VideoSummaryStatusCache.of(summaryInitiateRequest, userId, mayBeStatusCache.get())); - return new VideoSummaryInitiateResponse(videoCode); - } - - Optional mayBeVideoSummary = videoSummaryRepository.findByVideoCode(videoCode); - if (mayBeVideoSummary.isPresent()) { - VideoSummary videoSummary = mayBeVideoSummary.get(); - - summaryStatusCacheRepository.save(VideoSummaryStatusCache.of(summaryInitiateRequest, userId, videoSummary)); - return new VideoSummaryInitiateResponse(videoCode); - } - - messageService.sendVideoUrlToQueue(new VideoSummaryInitiateMessage(summaryInitiateRequest.getUrl(), videoCode, platform)); - - summaryStatusCacheRepository.save(VideoSummaryStatusCache.of(summaryInitiateRequest, userId, videoCode)); - return new VideoSummaryInitiateResponse(videoCode); - } - - private boolean checkDuplicateSummarizing(String videoCode, Long userId) { - return videoSummaryCategoryRepository.existsByVideoCodeAndUserId(videoCode, userId) || - summaryStatusCacheRepository.existsByVideoCodeAndUserId(videoCode, userId); - } - - // 무조건 중복허용이 안되는 로직 - public VideoSummaryDto getVideoSummaryById(Long videoSummaryId, Long userId) { - VideoSummary videoSummary = videoSummaryRepository.getReferenceById(videoSummaryId); - User user = userRepository.getReferenceById(userId); - VideoSummaryCategory videoSummaryCategory = videoSummaryCategoryRepository.findByVideoSummaryAndUser(videoSummary, user); - - return VideoSummaryDto.from(videoSummaryCategory); - } - - @Transactional - public VideoSummaryStatusResponse getStatus(String videoCode, Long userId) { - VideoSummaryStatusCache statusCache = summaryStatusCacheRepository.findByVideoCodeAndUserId(videoCode, userId) - .orElseThrow(() -> new AppException(ErrorCode.SUMMARIZING_STATUS_NOT_EXIST)); - - if (statusCache.getStatus().equals(COMPLETE.name())) { - Category category = categoryRepository.findDefaultCategoryByUserIdAndMainCategory(userId, statusCache.getGeneratedMainCategory()) - .orElseThrow(() -> new AppException(ErrorCode.CATEGORY_NOT_EXIST)); - VideoSummary videoSummary = videoSummaryRepository.getReferenceById(statusCache.getVideoSummaryId()); - - videoSummaryCategoryRepository.save(VideoSummaryCategory.builder() - .category(category) - .videoSummary(videoSummary) - .build()); - - summaryStatusCacheRepository.delete(statusCache); - - } - return VideoSummaryStatusResponse.from(statusCache); - } - - public VideoSummaryListResponse getAllSummariesByCategoryId(Long categoryId) { - Category category = categoryRepository.getReferenceById(categoryId); - List videoSummaryResponseList = videoSummaryCategoryRepository.findAllByCategory(category).stream() - .map(videoSummaryCategory -> new VideoSummaryResponse(videoSummaryCategory.getVideoSummary())) - .toList(); - return new VideoSummaryListResponse(videoSummaryResponseList); - } - - // 검색어를 포함하는 video id들을 조회하는 메서드 - public List getAllVideoIdsBySearchWord(String searchWord) { - return videoSummaryRepository.getAllVideoIdsBySearchWord(searchWord); - } -} +package com.hongik.graduationproject.service; + +import com.hongik.graduationproject.domain.dto.video.*; +import com.hongik.graduationproject.domain.entity.Category; +import com.hongik.graduationproject.domain.entity.User; +import com.hongik.graduationproject.domain.entity.VideoSummary; +import com.hongik.graduationproject.domain.entity.VideoSummaryCategory; +import com.hongik.graduationproject.domain.entity.cache.VideoSummaryStatusCache; +import com.hongik.graduationproject.enums.Platform; +import com.hongik.graduationproject.exception.AppException; +import com.hongik.graduationproject.exception.ErrorCode; +import com.hongik.graduationproject.repository.*; +import com.hongik.graduationproject.util.UrlUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +import static com.hongik.graduationproject.enums.SummaryStatus.*; + +@Service +@RequiredArgsConstructor +public class VideoSummaryService { + private final MessageService messageService; + private final VideoSummaryRepository videoSummaryRepository; + private final VideoSummaryStatusCacheRepository summaryStatusCacheRepository; + private final CategoryRepository categoryRepository; + private final VideoSummaryCategoryRepository videoSummaryCategoryRepository; + private final UserRepository userRepository; + + public VideoSummaryInitiateResponse initiateSummarizing(VideoSummaryInitiateRequest summaryInitiateRequest, Long userId) { + Platform platform = UrlUtils.getVideoPlatform(summaryInitiateRequest.getUrl()); + String videoId = UrlUtils.getVideoId(summaryInitiateRequest.getUrl(), platform); + + String videoCode = platform.name() + '_' + videoId; + + if (checkDuplicateSummarizing(videoCode, userId)) { + throw new AppException(ErrorCode.ALREADY_REQUESTED_SUMMARIZING); + } + + Optional mayBeStatusCache = summaryStatusCacheRepository.findFirstByVideoCode(videoCode); + if (mayBeStatusCache.isPresent()) { + summaryStatusCacheRepository.save(VideoSummaryStatusCache.of(summaryInitiateRequest, userId, mayBeStatusCache.get())); + return new VideoSummaryInitiateResponse(videoCode); + } + + Optional mayBeVideoSummary = videoSummaryRepository.findByVideoCode(videoCode); + if (mayBeVideoSummary.isPresent()) { + VideoSummary videoSummary = mayBeVideoSummary.get(); + + summaryStatusCacheRepository.save(VideoSummaryStatusCache.of(summaryInitiateRequest, userId, videoSummary)); + return new VideoSummaryInitiateResponse(videoCode); + } + + messageService.sendVideoUrlToQueue(new VideoSummaryInitiateMessage(summaryInitiateRequest.getUrl(), videoCode, platform)); + + summaryStatusCacheRepository.save(VideoSummaryStatusCache.of(summaryInitiateRequest, userId, videoCode)); + return new VideoSummaryInitiateResponse(videoCode); + } + + private boolean checkDuplicateSummarizing(String videoCode, Long userId) { + return videoSummaryCategoryRepository.existsByVideoCodeAndUserId(videoCode, userId) || + summaryStatusCacheRepository.existsByVideoCodeAndUserId(videoCode, userId); + } + + // 무조건 중복허용이 안되는 로직 + public VideoSummaryDto getSummaryByVideoSummaryId(Long videoSummaryId, Long userId) { + VideoSummary videoSummary = videoSummaryRepository.getReferenceById(videoSummaryId); + User user = userRepository.getReferenceById(userId); + VideoSummaryCategory videoSummaryCategory = videoSummaryCategoryRepository.findByVideoSummaryAndUser(videoSummary, user); + + return VideoSummaryDto.from(videoSummaryCategory); + } + + @Transactional + public VideoSummaryStatusResponse getStatus(String videoCode, Long userId) { + VideoSummaryStatusCache statusCache = summaryStatusCacheRepository.findByVideoCodeAndUserId(videoCode, userId) + .orElseThrow(() -> new AppException(ErrorCode.SUMMARIZING_STATUS_NOT_EXIST)); + + if (statusCache.getStatus().equals(COMPLETE.name())) { + Category category = categoryRepository.findDefaultCategoryByUserIdAndMainCategory(userId, statusCache.getGeneratedMainCategory()) + .orElseThrow(() -> new AppException(ErrorCode.CATEGORY_NOT_EXIST)); + VideoSummary videoSummary = videoSummaryRepository.getReferenceById(statusCache.getVideoSummaryId()); + + videoSummaryCategoryRepository.save(VideoSummaryCategory.builder() + .category(category) + .videoSummary(videoSummary) + .build()); + + summaryStatusCacheRepository.delete(statusCache); + + } + return VideoSummaryStatusResponse.from(statusCache); + } + + public VideoSummaryListResponse getAllSummariesByCategoryId(Long categoryId) { + Category category = categoryRepository.getReferenceById(categoryId); + List videoSummaryResponseList = videoSummaryCategoryRepository.findAllByCategory(category).stream() + .map(videoSummaryCategory -> new VideoSummaryResponse(videoSummaryCategory.getVideoSummary())) + .toList(); + return new VideoSummaryListResponse(videoSummaryResponseList); + } + + // 검색어를 포함하는 video id들을 조회하는 메서드 + public List getAllVideoIdsBySearchWord(String searchWord) { + return videoSummaryRepository.getAllVideoIdsBySearchWord(searchWord); + } + + @Transactional + public void deleteVideoSummary(Long videoSummaryId) { + VideoSummary videoSummary = videoSummaryRepository.findById(videoSummaryId) + .orElseThrow(() -> new AppException(ErrorCode.VIDEO_SUMMARY_NOT_FOUND)); + + if (videoSummary.isDeleted()) { + throw new AppException(ErrorCode.VIDEO_SUMMARY_ALREADY_DELETED); + } + + videoSummary.markAsDeleted(); + videoSummaryRepository.save(videoSummary); + } + + @Transactional + public VideoSummaryListResponse getAllDeletedVideoSummary(){ + List deletedSummaryList = videoSummaryRepository.findAllByIsDeletedTrue() + .stream() + .map(VideoSummaryResponse::new) + .toList(); + return new VideoSummaryListResponse(deletedSummaryList); + } + + @Transactional + public void restoreVideoSummary(Long videoSummaryId) { + VideoSummary videoSummary = videoSummaryRepository.findDeletedById(videoSummaryId) + .orElseThrow(() -> new AppException(ErrorCode.VIDEO_SUMMARY_NOT_FOUND)); + + videoSummary.restore(); + videoSummaryRepository.save(videoSummary); + } +} \ No newline at end of file