From fffd05492c1d0ecd18379d174fedf319dd127072 Mon Sep 17 00:00:00 2001 From: young Date: Thu, 16 Jul 2026 01:02:53 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=EC=98=81=EC=83=81=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20api=20=EA=B5=AC=ED=98=84=20(#1?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/controller/VideoController.java | 41 ++++++++++++++ .../video/dto/response/VideoResponse.java | 38 +++++++++++++ .../repository/VideoBookmarkRepository.java | 23 ++++++++ .../VideoProjectAccessRepository.java | 32 +++++++++++ .../video/repository/VideoRepository.java | 28 ++++++++++ .../domain/video/service/VideoService.java | 53 +++++++++++++++++++ 6 files changed, 215 insertions(+) create mode 100644 src/main/java/com/slatto/domain/video/controller/VideoController.java create mode 100644 src/main/java/com/slatto/domain/video/dto/response/VideoResponse.java create mode 100644 src/main/java/com/slatto/domain/video/repository/VideoBookmarkRepository.java create mode 100644 src/main/java/com/slatto/domain/video/repository/VideoProjectAccessRepository.java create mode 100644 src/main/java/com/slatto/domain/video/repository/VideoRepository.java create mode 100644 src/main/java/com/slatto/domain/video/service/VideoService.java diff --git a/src/main/java/com/slatto/domain/video/controller/VideoController.java b/src/main/java/com/slatto/domain/video/controller/VideoController.java new file mode 100644 index 00000000..a811d883 --- /dev/null +++ b/src/main/java/com/slatto/domain/video/controller/VideoController.java @@ -0,0 +1,41 @@ +package com.slatto.domain.video.controller; + +import com.slatto.domain.video.dto.response.VideoResponse.VideoListResDTO; +import com.slatto.domain.video.service.VideoService; +import com.slatto.global.response.ApiResponse; +import com.slatto.global.response.code.CommonSuccessCode; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/projects/{projectId}/videos") +public class VideoController { + + private final VideoService videoService; + + @GetMapping + @Operation(summary = "영상 목록 조회", description = "프로젝트 멤버가 프로젝트에 등록된 영상을 커서 방식으로 조회합니다.") + public ApiResponse getVideos( + @PathVariable @Positive Long projectId, + @RequestParam(required = false) @Positive Long cursor, + @RequestParam(required = false) @Min(1) @Max(100) Integer size + ) { + // TODO: 인증/인가 구현 후 JWT에서 memberId 추출하도록 변경 + Long memberId = 1L; + return ApiResponse.success( + CommonSuccessCode.OK, + videoService.getVideos(memberId, projectId, cursor, size) + ); + } +} diff --git a/src/main/java/com/slatto/domain/video/dto/response/VideoResponse.java b/src/main/java/com/slatto/domain/video/dto/response/VideoResponse.java new file mode 100644 index 00000000..0ac59b77 --- /dev/null +++ b/src/main/java/com/slatto/domain/video/dto/response/VideoResponse.java @@ -0,0 +1,38 @@ +package com.slatto.domain.video.dto.response; + +import com.slatto.domain.video.entity.Video; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDateTime; +import java.util.List; + +public class VideoResponse { + + @Schema(description = "영상 목록 조회 응답") + public record VideoListResDTO( + List items, + @Schema(description = "다음 페이지 조회 커서", example = "9", nullable = true) + Long nextCursor, + @Schema(description = "다음 목록 존재 여부", example = "true") + boolean hasNext + ) { + } + + public record VideoItemResDTO( + @Schema(example = "10") Long videoId, + @Schema(example = "프로젝트 명") String title, + @Schema(example = "https://img.youtube.com/vi/abc123/maxresdefault.jpg") String thumbnailUrl, + @Schema(example = "true") boolean bookmarked, + @Schema(example = "IN_PROGRESS") String progressStatus, + @Schema(example = "3") int unreadCommentCount, + LocalDateTime createdAt, + LocalDateTime updatedAt + ) { + public static VideoItemResDTO from(Video video, boolean bookmarked) { + return new VideoItemResDTO( + video.getId(), video.getTitle(), video.getThumbnailUrl(), bookmarked, + video.getProgressStatus(), 0, video.getCreatedAt(), video.getUpdatedAt() + ); + } + } +} diff --git a/src/main/java/com/slatto/domain/video/repository/VideoBookmarkRepository.java b/src/main/java/com/slatto/domain/video/repository/VideoBookmarkRepository.java new file mode 100644 index 00000000..fb52ed2a --- /dev/null +++ b/src/main/java/com/slatto/domain/video/repository/VideoBookmarkRepository.java @@ -0,0 +1,23 @@ +package com.slatto.domain.video.repository; + +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class VideoBookmarkRepository { + + private final ObjectProvider entityManagerProvider; + + public boolean existsByVideoIdAndUserId(Long videoId, Long userId) { + return entityManagerProvider.getObject().createQuery(""" + select count(bookmark) from VideoBookmark bookmark + where bookmark.video.id = :videoId and bookmark.user.id = :userId + """, Long.class) + .setParameter("videoId", videoId) + .setParameter("userId", userId) + .getSingleResult() > 0; + } +} diff --git a/src/main/java/com/slatto/domain/video/repository/VideoProjectAccessRepository.java b/src/main/java/com/slatto/domain/video/repository/VideoProjectAccessRepository.java new file mode 100644 index 00000000..b6a12873 --- /dev/null +++ b/src/main/java/com/slatto/domain/video/repository/VideoProjectAccessRepository.java @@ -0,0 +1,32 @@ +package com.slatto.domain.video.repository; + +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class VideoProjectAccessRepository { + + private final ObjectProvider entityManagerProvider; + + public boolean existsByMemberIdAndProjectId(Long memberId, Long projectId) { + return entityManagerProvider.getObject().createQuery(""" + select count(member) from ProjectMember member + where member.user.id = :memberId + and member.project.id = :projectId + and member.leftAt is null + """, Long.class) + .setParameter("memberId", memberId) + .setParameter("projectId", projectId) + .getSingleResult() > 0; + } + + public boolean projectExistsById(Long projectId) { + return entityManagerProvider.getObject().createQuery( + "select count(project) from Project project where project.id = :projectId", Long.class) + .setParameter("projectId", projectId) + .getSingleResult() > 0; + } +} diff --git a/src/main/java/com/slatto/domain/video/repository/VideoRepository.java b/src/main/java/com/slatto/domain/video/repository/VideoRepository.java new file mode 100644 index 00000000..6b610896 --- /dev/null +++ b/src/main/java/com/slatto/domain/video/repository/VideoRepository.java @@ -0,0 +1,28 @@ +package com.slatto.domain.video.repository; + +import com.slatto.domain.video.entity.Video; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class VideoRepository { + + private final ObjectProvider entityManagerProvider; + + public List