Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public static AnalysisResultResponseDTO from(
Analysis analysis,
AnalysisReport analysisReport,
JsonNode rawResult,
String recordingFileUrl
String recordingFileUrl,
String backingTrackAudioFileUrl
) {
Playing playing = analysis.getPlaying();
BackingTrack backingTrack = playing.getBackingTrack();
Expand All @@ -55,7 +56,7 @@ public static AnalysisResultResponseDTO from(
playing.getBpm(),
playing.getEndedAt(),
recordingFileUrl,
backingTrack != null ? backingTrack.getAudioFileUrl() : null,
backingTrackAudioFileUrl,
analysis.getStatus(),
analysis.getStartBar(),
analysis.getEndBar(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
import com.mr.domain.analysis.factory.AnalysisRequestFactory;
import com.mr.domain.analysis.repository.AnalysisReportRepository;
import com.mr.domain.analysis.repository.AnalysisRepository;
import com.mr.domain.backingtrack.entity.BackingTrack;
import com.mr.domain.playing.entity.Playing;
import com.mr.domain.playing.entity.enums.PlayingStatus;
import com.mr.domain.playing.exception.PlayingErrorStatus;
import com.mr.domain.playing.repository.PlayingRepository;
import com.mr.global.apipayload.exception.GeneralException;
import com.mr.global.file.s3.enums.S3FileType;
import com.mr.global.file.s3.service.S3FileService;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -109,14 +111,33 @@ public AnalysisResultResponseDTO getAnalysisResult(
String recordingFileUrl =
s3FileService.createPresignedDownload(
userId,
S3FileType.RECORDING,
analysis.getPlaying().getRecordingObjectKey()
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

BackingTrack backingTrack =
analysis.getPlaying().getBackingTrack();

String backingTrackAudioFileUrl = null;

if (backingTrack != null
&& backingTrack.getAudioObjectKey() != null
&& !backingTrack.getAudioObjectKey().isBlank()) {

backingTrackAudioFileUrl =
s3FileService.createPresignedDownload(
backingTrack.getUser().getUserId(),
S3FileType.BACKING_TRACK,
backingTrack.getAudioObjectKey()
);
}

return AnalysisResultResponseDTO.from(
analysis,
analysisReport,
rawResult,
recordingFileUrl
recordingFileUrl,
backingTrackAudioFileUrl
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import com.mr.domain.backingtrack.dto.req.BackingTrackListRequestDTO;
import com.mr.domain.backingtrack.dto.req.BackingTrackSaveRequestDTO;
import com.mr.domain.backingtrack.dto.req.BackingTrackUploadUrlRequest;
import com.mr.domain.backingtrack.dto.req.PlayCountIncreaseRequestDTO;
import com.mr.domain.backingtrack.dto.res.BackingTrackCreateResponseDTO;
import com.mr.domain.backingtrack.dto.res.BackingTrackDetailResponseDTO;
import com.mr.domain.backingtrack.dto.res.BackingTrackListResponseDTO;
import com.mr.domain.backingtrack.dto.res.BackingTrackRecommendedResponseDTO;
import com.mr.domain.backingtrack.dto.res.BackingTrackUpdateResponseDTO;
import com.mr.domain.backingtrack.dto.res.BackingTrackUploadUrlResponse;
import com.mr.domain.backingtrack.dto.res.PlayCountIncreaseResponseDTO;
import com.mr.domain.backingtrack.service.BackingTrackService;
import com.mr.global.apipayload.ApiResponse;
Expand Down Expand Up @@ -45,14 +47,37 @@ public class BackingTrackController {
@PostMapping
public ApiResponse<BackingTrackCreateResponseDTO.CreateResultDTO> createBackingTrack(
@AuthenticationPrincipal CustomUserDetails userDetails,
@Valid @RequestBody BackingTrackSaveRequestDTO.SaveDTO request
@Valid @RequestBody BackingTrackSaveRequestDTO.CreateDTO request
) {
BackingTrackCreateResponseDTO.CreateResultDTO result =
backingTrackService.createBackingTrack(userDetails.getUserId(), request);

return ApiResponse.onSuccess(result);
}

@Operation(
summary = "백킹트랙 오디오 파일 업로드 URL 발급",
description = """
백킹트랙 생성 시 첨부할 오디오 파일을 S3에 직접 업로드하기 위한
Presigned PUT URL을 발급합니다.

파일 업로드 후 반환된 objectKey를
백킹트랙 생성 API의 audioObjectKey 필드로 전달합니다.
"""
)
@PostMapping("/audio-upload-url")
public ApiResponse<BackingTrackUploadUrlResponse> createAudioUploadUrl(
@AuthenticationPrincipal CustomUserDetails userDetails,
@Valid @RequestBody BackingTrackUploadUrlRequest request
) {
return ApiResponse.onSuccess(
backingTrackService.createAudioUploadUrl(
userDetails.getUserId(),
request
)
);
}

@Operation(
summary = "백킹트랙 수정",
description = "트랙의 생성자가 본인일 때, 트랙의 정보를 수정"
Expand All @@ -61,7 +86,7 @@ public ApiResponse<BackingTrackCreateResponseDTO.CreateResultDTO> createBackingT
public ApiResponse<BackingTrackUpdateResponseDTO.UpdateResultDTO> updateBackingTrack(
@AuthenticationPrincipal CustomUserDetails userDetails,
@PathVariable @Min(value = 1, message = "BACKING_TRACK_400_23") Long backingTrackId,
@Valid @RequestBody BackingTrackSaveRequestDTO.SaveDTO request
@Valid @RequestBody BackingTrackSaveRequestDTO.UpdateDTO request
) {
Long userId = userDetails.getUserId();
BackingTrackUpdateResponseDTO.UpdateResultDTO result =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,61 +16,111 @@

public class BackingTrackSaveRequestDTO {

public record SaveDTO(
@NotBlank(message = "백킹트랙 제목은 필수입니다.")
@Size(max = 50, message = "백킹트랙 제목은 50자 이내여야 합니다.")
String title,

@NotBlank(message = "장르는 필수입니다.")
@Size(max = 50, message = "장르는 50자 이내여야 합니다.")
String genre,

@NotBlank(message = "Key 정보는 필수입니다.")
@Size(max = 20, message = "Key 정보는 20자 이내여야 합니다.")
String keySignature,

@NotNull(message = "조성은 필수입니다.")
ScaleType scaleType,

@NotBlank(message = "박자는 필수입니다.")
@Pattern(regexp = "^([1-9]|1[0-6])/(2|4|8|16)$", message = "음악적으로 유효한 박자 형식이어야 합니다. (예: 4/4, 3/4, 6/8)")
String timeSignature,

@NotNull(message = "BPM은 필수입니다.")
@Min(value = 50, message = "BPM은 50 이상이어야 합니다.")
@Max(value = 200, message = "BPM은 200 이하여야 합니다.")
Integer bpm,

@NotNull(message = "재생 시간은 필수입니다.")
@Min(value = 60, message = "재생 시간은 최소 60초(1분) 이상이어야 합니다.")
@Max(value = 600, message = "재생 시간은 최대 600초(10분) 이하여야 합니다.")
Integer playtimeSec,

@Size(max = 255, message = "오디오 파일 URL은 255자 이내여야 합니다.")
String audioFileUrl,

@NotNull(message = "공개 범위는 필수입니다.")
AccessLevel accessLevel,

@NotNull(message = "난이도는 필수입니다.")
Level level,

@Valid
@NotEmpty(message = "코드 진행 정보는 필수입니다.")
List<ChordProgressionDTO> chordProgression
) {}

public record ChordProgressionDTO(
@NotNull(message = "마디 번호는 필수입니다.")
@Min(value = 1, message = "마디 번호는 1 이상이어야 합니다.")
Integer measureNo,

@NotNull(message = "코드 순서는 필수입니다.")
@Min(value = 1, message = "코드 순서는 1 이상이어야 합니다.")
Integer sequenceNo,

@NotBlank(message = "코드명은 필수입니다.")
@Size(max = 30, message = "코드명은 30자 이내여야 합니다.")
String chordName
) {}
}
public record CreateDTO(
@NotBlank(message = "백킹트랙 제목은 필수입니다.")
@Size(max = 50, message = "백킹트랙 제목은 50자 이내여야 합니다.")
String title,

@NotBlank(message = "장르는 필수입니다.")
@Size(max = 50, message = "장르는 50자 이내여야 합니다.")
String genre,

@NotBlank(message = "Key 정보는 필수입니다.")
@Size(max = 20, message = "Key 정보는 20자 이내여야 합니다.")
String keySignature,

@NotNull(message = "조성은 필수입니다.")
ScaleType scaleType,

@NotBlank(message = "박자는 필수입니다.")
@Pattern(
regexp = "^([1-9]|1[0-6])/(2|4|8|16)$",
message = "음악적으로 유효한 박자 형식이어야 합니다. (예: 4/4, 3/4, 6/8)"
)
String timeSignature,

@NotNull(message = "BPM은 필수입니다.")
@Min(value = 50, message = "BPM은 50 이상이어야 합니다.")
@Max(value = 200, message = "BPM은 200 이하여야 합니다.")
Integer bpm,

@NotNull(message = "재생 시간은 필수입니다.")
@Min(value = 60, message = "재생 시간은 최소 60초(1분) 이상이어야 합니다.")
@Max(value = 600, message = "재생 시간은 최대 600초(10분) 이하여야 합니다.")
Integer playtimeSec,

@Size(max = 255, message = "오디오 파일 Object Key는 255자 이내여야 합니다.")
String audioObjectKey,

@NotNull(message = "공개 범위는 필수입니다.")
AccessLevel accessLevel,

@NotNull(message = "난이도는 필수입니다.")
Level level,

@Valid
@NotEmpty(message = "코드 진행 정보는 필수입니다.")
List<ChordProgressionDTO> chordProgression
) {
}

public record UpdateDTO(
@NotBlank(message = "백킹트랙 제목은 필수입니다.")
@Size(max = 50, message = "백킹트랙 제목은 50자 이내여야 합니다.")
String title,

@NotBlank(message = "장르는 필수입니다.")
@Size(max = 50, message = "장르는 50자 이내여야 합니다.")
String genre,

@NotBlank(message = "Key 정보는 필수입니다.")
@Size(max = 20, message = "Key 정보는 20자 이내여야 합니다.")
String keySignature,

@NotNull(message = "조성은 필수입니다.")
ScaleType scaleType,

@NotBlank(message = "박자는 필수입니다.")
@Pattern(
regexp = "^([1-9]|1[0-6])/(2|4|8|16)$",
message = "음악적으로 유효한 박자 형식이어야 합니다. (예: 4/4, 3/4, 6/8)"
)
String timeSignature,

@NotNull(message = "BPM은 필수입니다.")
@Min(value = 50, message = "BPM은 50 이상이어야 합니다.")
@Max(value = 200, message = "BPM은 200 이하여야 합니다.")
Integer bpm,

@NotNull(message = "재생 시간은 필수입니다.")
@Min(value = 60, message = "재생 시간은 최소 60초(1분) 이상이어야 합니다.")
@Max(value = 600, message = "재생 시간은 최대 600초(10분) 이하여야 합니다.")
Integer playtimeSec,

@NotNull(message = "공개 범위는 필수입니다.")
AccessLevel accessLevel,

@NotNull(message = "난이도는 필수입니다.")
Level level,

@Valid
@NotEmpty(message = "코드 진행 정보는 필수입니다.")
List<ChordProgressionDTO> chordProgression
) {
}

public record ChordProgressionDTO(
@NotNull(message = "마디 번호는 필수입니다.")
@Min(value = 1, message = "마디 번호는 1 이상이어야 합니다.")
Integer measureNo,

@NotNull(message = "코드 순서는 필수입니다.")
@Min(value = 1, message = "코드 순서는 1 이상이어야 합니다.")
Integer sequenceNo,

@NotBlank(message = "코드명은 필수입니다.")
@Size(max = 30, message = "코드명은 30자 이내여야 합니다.")
String chordName
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.mr.domain.backingtrack.dto.req;

import com.mr.global.file.s3.dto.FileUploadCommand;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

public record BackingTrackUploadUrlRequest(

@NotBlank(message = "파일명은 필수입니다.")
String fileName,

@NotBlank(message = "Content-Type은 필수입니다.")
String contentType,

@NotNull(message = "파일 크기는 필수입니다.")
@Positive(message = "파일 크기는 0보다 커야 합니다.")
Long fileSize
) {
public FileUploadCommand toCommand() {
return new FileUploadCommand(
fileName,
contentType,
fileSize
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.mr.domain.backingtrack.dto.res;

import com.mr.global.file.s3.dto.PresignedUrlUpload;

import java.time.Instant;
import java.util.Map;

public record BackingTrackUploadUrlResponse(
String objectKey,
String uploadUrl,
Instant expiresAt,
Map<String, String> requiredHeaders
) {
public static BackingTrackUploadUrlResponse from(
PresignedUrlUpload presignedUpload
) {
return new BackingTrackUploadUrlResponse(
presignedUpload.objectKey(),
presignedUpload.uploadUrl(),
presignedUpload.expiresAt(),
presignedUpload.requiredHeaders()
);
}
}
Loading
Loading