Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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")
Expand All @@ -38,29 +42,31 @@ public class VideoController {

@PatchMapping("/{videoId}")
@Operation(summary = "영상 수정",
description = "프로젝트에 등록된 영상의 제목과 메모를 수정합니다.<br>" +
"수정하지 않을 부분은 null로 넣으면 됩니다.")
description = "프로젝트 멤버가 영상의 제목과 메모를 수정합니다. " +
"변경하지 않을 값은 생략하거나 null로 전달할 수 있으며, 제목과 메모 중 하나 이상은 입력해야 합니다.")
public ApiResponse<VideoUpdateResDTO> 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)
);
}

@DeleteMapping("/{videoId}")
@Operation(summary = "영상 삭제", description = "프로젝트에 등록된 영상을 삭제합니다.")
@Operation(summary = "영상 삭제", description = "프로젝트 멤버가 프로젝트에 등록된 영상을 삭제합니다.")
public ApiResponse<VideoDeleteResDTO> 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)
Expand All @@ -69,28 +75,38 @@ public ApiResponse<VideoDeleteResDTO> deleteVideo(

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "영상 등록", description = "프로젝트에 새로운 YouTube 영상을 등록합니다.")
@Operation(
summary = "영상 등록",
description = "프로젝트 멤버가 YouTube 영상을 등록합니다. YouTube Data API로 공개 여부와 재생 가능 여부를 확인하며, " +
"같은 프로젝트에 이미 등록된 영상이나 비공개·재생 불가능 영상은 등록할 수 없습니다."
)
public ApiResponse<VideoCreateResDTO> 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)
);
}

@GetMapping
@Operation(summary = "영상 목록 조회", description = "프로젝트 멤버가 프로젝트에 등록된 영상을 커서 방식으로 조회합니다.")
@Operation(
summary = "영상 목록 조회",
description = "프로젝트 멤버가 등록 영상을 최신순으로 조회합니다. 첫 요청에서는 cursor를 생략하고, " +
"다음 페이지는 이전 응답의 nextCursor를 전달합니다. size 기본값은 20이며 최대 100입니다."
)
public ApiResponse<VideoListResDTO> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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<YoutubeValidateResDTO> validateYoutubeUrl(
@AuthenticationPrincipal Long memberId,
@Valid @RequestBody YoutubeValidateReqDTO request
) {
// TODO: 인증/인가 구현 후 JWT에서 memberId 추출하도록 변경
Long memberId = 1L;
return ApiResponse.success(
CommonSuccessCode.OK,
videoService.validateYoutubeUrl(memberId, request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
}
Expand All @@ -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
) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -61,6 +62,7 @@ public static VideoCreateResDTO from(Video video) {

@Schema(description = "영상 목록 조회 응답")
public record VideoListResDTO(
@Schema(description = "조회된 영상 목록")
List<VideoItemResDTO> items,
@Schema(description = "다음 페이지 조회 커서", example = "9", nullable = true)
Long nextCursor,
Expand All @@ -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,
Expand Down
Loading