diff --git a/src/main/java/com/slatto/domain/video/controller/VideoController.java b/src/main/java/com/slatto/domain/video/controller/VideoController.java index 0afd8e1a..2bd7aa6f 100644 --- a/src/main/java/com/slatto/domain/video/controller/VideoController.java +++ b/src/main/java/com/slatto/domain/video/controller/VideoController.java @@ -10,12 +10,15 @@ import com.slatto.global.response.ApiResponse; import com.slatto.global.response.code.CommonSuccessCode; 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.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -29,6 +32,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; @Validated +@Tag(name = "Video", description = "프로젝트 영상 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/projects/{projectId}/videos") @@ -38,15 +42,16 @@ public class VideoController { @PatchMapping("/{videoId}") @Operation(summary = "영상 수정", - description = "프로젝트에 등록된 영상의 제목과 메모를 수정합니다.
" + - "수정하지 않을 부분은 null로 넣으면 됩니다.") + description = "프로젝트 멤버가 영상의 제목과 메모를 수정합니다. " + + "변경하지 않을 값은 생략하거나 null로 전달할 수 있으며, 제목과 메모 중 하나 이상은 입력해야 합니다.") public ApiResponse updateVideo( + @AuthenticationPrincipal Long memberId, + @Parameter(description = "프로젝트 ID", example = "10") @PathVariable @Positive Long projectId, + @Parameter(description = "영상 ID", example = "1") @PathVariable @Positive Long videoId, @Valid @RequestBody VideoUpdateReqDTO request ) { - // TODO: 인증/인가 구현 후 JWT에서 memberId 추출하도록 변경 - Long memberId = 1L; return ApiResponse.success( CommonSuccessCode.OK, videoService.updateVideo(memberId, projectId, videoId, request) @@ -54,13 +59,14 @@ public ApiResponse updateVideo( } @DeleteMapping("/{videoId}") - @Operation(summary = "영상 삭제", description = "프로젝트에 등록된 영상을 삭제합니다.") + @Operation(summary = "영상 삭제", description = "프로젝트 멤버가 프로젝트에 등록된 영상을 삭제합니다.") public ApiResponse deleteVideo( + @AuthenticationPrincipal Long memberId, + @Parameter(description = "프로젝트 ID", example = "10") @PathVariable @Positive Long projectId, + @Parameter(description = "영상 ID", example = "1") @PathVariable @Positive Long videoId ) { - // TODO: 인증/인가 구현 후 JWT에서 memberId 추출하도록 변경 - Long memberId = 1L; return ApiResponse.success( CommonSuccessCode.OK, videoService.deleteVideo(memberId, projectId, videoId) @@ -69,13 +75,17 @@ public ApiResponse deleteVideo( @PostMapping @ResponseStatus(HttpStatus.CREATED) - @Operation(summary = "영상 등록", description = "프로젝트에 새로운 YouTube 영상을 등록합니다.") + @Operation( + summary = "영상 등록", + description = "프로젝트 멤버가 YouTube 영상을 등록합니다. YouTube Data API로 공개 여부와 재생 가능 여부를 확인하며, " + + "같은 프로젝트에 이미 등록된 영상이나 비공개·재생 불가능 영상은 등록할 수 없습니다." + ) public ApiResponse createVideo( + @AuthenticationPrincipal Long memberId, + @Parameter(description = "프로젝트 ID", example = "10") @PathVariable @Positive Long projectId, @Valid @RequestBody VideoCreateReqDTO request ) { - // TODO: 인증/인가 구현 후 JWT에서 memberId 추출하도록 변경 - Long memberId = 1L; return ApiResponse.success( CommonSuccessCode.CREATED, videoService.createVideo(memberId, projectId, request) @@ -83,14 +93,20 @@ public ApiResponse createVideo( } @GetMapping - @Operation(summary = "영상 목록 조회", description = "프로젝트 멤버가 프로젝트에 등록된 영상을 커서 방식으로 조회합니다.") + @Operation( + summary = "영상 목록 조회", + description = "프로젝트 멤버가 등록 영상을 최신순으로 조회합니다. 첫 요청에서는 cursor를 생략하고, " + + "다음 페이지는 이전 응답의 nextCursor를 전달합니다. size 기본값은 20이며 최대 100입니다." + ) public ApiResponse getVideos( + @AuthenticationPrincipal Long memberId, + @Parameter(description = "프로젝트 ID", example = "10") @PathVariable @Positive Long projectId, + @Parameter(description = "이전 응답의 nextCursor. 첫 페이지에서는 생략합니다.", example = "9") @RequestParam(required = false) @Positive Long cursor, + @Parameter(description = "조회 개수. 생략 시 20, 최대 100입니다.", example = "20") @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/controller/YoutubeController.java b/src/main/java/com/slatto/domain/video/controller/YoutubeController.java index 9bee96be..7bfc8cbc 100644 --- a/src/main/java/com/slatto/domain/video/controller/YoutubeController.java +++ b/src/main/java/com/slatto/domain/video/controller/YoutubeController.java @@ -6,13 +6,16 @@ import com.slatto.global.response.ApiResponse; import com.slatto.global.response.code.CommonSuccessCode; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "YouTube", description = "YouTube 영상 URL 및 등록 가능 여부 검증 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/videos/youtube") @@ -21,12 +24,16 @@ public class YoutubeController { private final VideoService videoService; @PostMapping("/validate") - @Operation(summary = "YouTube URL 검증", description = "YouTube 영상 정보와 프로젝트 내 중복 등록 여부를 검증합니다.") + @Operation( + summary = "YouTube URL 검증", + description = "프로젝트 멤버가 등록 전에 YouTube URL을 검증합니다. URL에서 영상 ID를 추출한 뒤 " + + "프로젝트 내 중복 여부와 YouTube의 공개·재생 가능 상태를 확인하고 영상 정보를 반환합니다. " + + "이 API는 영상을 저장하지 않습니다." + ) public ApiResponse validateYoutubeUrl( + @AuthenticationPrincipal Long memberId, @Valid @RequestBody YoutubeValidateReqDTO request ) { - // TODO: 인증/인가 구현 후 JWT에서 memberId 추출하도록 변경 - Long memberId = 1L; return ApiResponse.success( CommonSuccessCode.OK, videoService.validateYoutubeUrl(memberId, request) diff --git a/src/main/java/com/slatto/domain/video/dto/request/VideoRequest.java b/src/main/java/com/slatto/domain/video/dto/request/VideoRequest.java index 7b3a3d95..469030d6 100644 --- a/src/main/java/com/slatto/domain/video/dto/request/VideoRequest.java +++ b/src/main/java/com/slatto/domain/video/dto/request/VideoRequest.java @@ -25,12 +25,12 @@ public record VideoUpdateReqDTO( public record YoutubeValidateReqDTO( @NotBlank(message = "YouTube URL은 필수입니다.") @Size(max = 500, message = "YouTube URL은 최대 500자까지 입력할 수 있습니다.") - @Schema(example = "https://www.youtube.com/watch?v=abc123") + @Schema(description = "검증할 YouTube 영상 URL", example = "https://www.youtube.com/watch?v=abc123") String youtubeUrl, @NotNull(message = "프로젝트 ID는 필수입니다.") @Positive(message = "프로젝트 ID는 양수여야 합니다.") - @Schema(example = "10") + @Schema(description = "영상 등록 여부를 확인할 프로젝트 ID", example = "10") Long projectId ) { } @@ -39,15 +39,15 @@ public record YoutubeValidateReqDTO( public record VideoCreateReqDTO( @NotBlank(message = "YouTube URL은 필수입니다.") @Size(max = 500, message = "YouTube URL은 최대 500자까지 입력할 수 있습니다.") - @Schema(example = "https://www.youtube.com/watch?v=abc123") + @Schema(description = "등록할 YouTube 영상 URL", example = "https://www.youtube.com/watch?v=abc123") String youtubeUrl, @NotBlank(message = "영상 제목은 필수입니다.") @Size(max = 255, message = "영상 제목은 최대 255자까지 입력할 수 있습니다.") - @Schema(example = "프로젝트 명") + @Schema(description = "프로젝트에 표시할 영상 제목", example = "촬영 콘셉트 참고 영상") String title, - @Schema(example = "영상에 관련된 메모", nullable = true) + @Schema(description = "영상 관련 메모", example = "오프닝 연출 참고", nullable = true) String memo ) { } 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 index 1f61baab..c185a9e3 100644 --- a/src/main/java/com/slatto/domain/video/dto/response/VideoResponse.java +++ b/src/main/java/com/slatto/domain/video/dto/response/VideoResponse.java @@ -31,20 +31,21 @@ public record VideoDeleteResDTO( @Schema(description = "YouTube URL 검증 응답") public record YoutubeValidateResDTO( - @Schema(example = "true") boolean valid, - @Schema(example = "abc123") String youtubeVideoId, - @Schema(example = "영상 제목") String title, - @Schema(example = "https://img.youtube.com/vi/abc123/maxresdefault.jpg") String thumbnailUrl, - @Schema(example = "1018") int durationSeconds, - @Schema(example = "true") boolean playable, - @Schema(example = "등록 가능한 영상입니다.") String message + @Schema(description = "프로젝트에 등록 가능한지 여부", example = "true") boolean valid, + @Schema(description = "URL에서 추출한 YouTube 영상 ID", example = "abc123") String youtubeVideoId, + @Schema(description = "YouTube에서 조회한 영상 제목", example = "영상 제목") String title, + @Schema(description = "YouTube 썸네일 URL", example = "https://img.youtube.com/vi/abc123/maxresdefault.jpg") + String thumbnailUrl, + @Schema(description = "영상 길이(초)", example = "1018") int durationSeconds, + @Schema(description = "외부 서비스에서 재생 가능한지 여부", example = "true") boolean playable, + @Schema(description = "검증 결과 안내 메시지", example = "등록 가능한 영상입니다.") String message ) { } @Schema(description = "영상 등록 응답") public record VideoCreateResDTO( @Schema(example = "1") Long videoId, - @Schema(example = "프로젝트 명") String title, + @Schema(example = "촬영 콘셉트 참고 영상") String title, @Schema(example = "https://img.youtube.com/vi/abc123/maxresdefault.jpg") String thumbnailUrl, @Schema(example = "1018") Integer durationSeconds, @Schema(example = "false") boolean bookmarked, @@ -61,6 +62,7 @@ public static VideoCreateResDTO from(Video video) { @Schema(description = "영상 목록 조회 응답") public record VideoListResDTO( + @Schema(description = "조회된 영상 목록") List items, @Schema(description = "다음 페이지 조회 커서", example = "9", nullable = true) Long nextCursor, @@ -69,9 +71,10 @@ public record VideoListResDTO( ) { } + @Schema(description = "영상 목록 항목") public record VideoItemResDTO( @Schema(example = "10") Long videoId, - @Schema(example = "프로젝트 명") String title, + @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,