-
Notifications
You must be signed in to change notification settings - Fork 2
[FEAT] MIDI 이벤트 저장 API 구현 #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cc825f4
366e478
63bb0b9
9421634
dbd8708
6f72d48
4b7b29d
b37a6c3
ab0857a
65fac67
93e7275
8142561
9cac136
c98d90c
7aac8a8
e94371c
f22c795
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.mr.domain.playing.constant; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public final class MidiEventConstants { | ||
|
|
||
| public static final int MAX_MIDI_EVENT_COUNT = 100_000; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.mr.domain.playing.controller; | ||
|
|
||
| import com.mr.domain.playing.dto.req.MidiEventSaveRequest; | ||
| import com.mr.domain.playing.dto.res.MidiEventSaveResponse; | ||
| import com.mr.domain.playing.service.PlayingService; | ||
| import com.mr.global.apipayload.ApiResponse; | ||
| import com.mr.global.security.principal.CustomUserDetails; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| 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; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/playings") | ||
| public class PlayingController { | ||
|
|
||
| private final PlayingService playingService; | ||
|
|
||
| @Operation( | ||
| summary = "MIDI 이벤트 저장", | ||
| description = "연주 세션에 대한 MIDI 이벤트를 저장하고 연주를 완료 상태로 변경합니다." | ||
| ) | ||
| @PostMapping("/{playingId}/midi-events") | ||
| public ApiResponse<MidiEventSaveResponse> saveMidiEvents( | ||
| @AuthenticationPrincipal CustomUserDetails userDetails, | ||
| @PathVariable Long playingId, | ||
| @Valid @RequestBody MidiEventSaveRequest request | ||
| ){ | ||
| Long userId = userDetails.getUserId(); | ||
|
|
||
| MidiEventSaveResponse response = playingService.saveMidiEvents( | ||
| userId, | ||
| playingId, | ||
| request | ||
| ); | ||
|
|
||
| return ApiResponse.onSuccess(response); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.mr.domain.playing.dto.req; | ||
|
|
||
| import com.mr.domain.playing.entity.enums.MidiType; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.NotEmpty; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import jakarta.validation.constraints.Max; | ||
| import jakarta.validation.constraints.Min; | ||
| import jakarta.validation.constraints.Size; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import static com.mr.domain.playing.constant.MidiEventConstants.MAX_MIDI_EVENT_COUNT; | ||
|
|
||
| public record MidiEventSaveRequest ( | ||
| @NotEmpty(message = "MIDI 이벤트 목록은 필수입니다") | ||
| @Size(max = MAX_MIDI_EVENT_COUNT, message = "MIDI 이벤트는 최대 100,000개까지 저장할 수 있습니다.") | ||
| List<@Valid MidiEventRequest> events | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 관련 문서 확인해 보니, Bean Validation 2.0부터 List<@Valid MidiEventRequest>처럼 컨테이너의 타입 인자에 @Valid를 적용하는 방식 권장 + @Valid List 방식도 기존 방식으로 계속 지원되고 있다고 나와서, 우선은 현재 작성한 List<@Valid MidiEventRequest> 형태를 유지해도 괜찮을 것 같다는 개인적인 의견입니다..! 참고 문서: Jakarta Bean Validation |
||
| ) { | ||
|
|
||
| public record MidiEventRequest ( | ||
|
|
||
| @NotNull(message = "MIDI 이벤트 순서 값은 필수입니다.") | ||
| @Min(value = 0, message = "MIDI 이벤트 순서 값은 0 이상이어야 합니다.") | ||
| Integer sequence, | ||
|
|
||
| @NotNull(message = "MIDI 이벤트 타입은 필수입니다.") | ||
| MidiType type, | ||
|
|
||
| @NotNull(message = "MIDI 피치 값은 필수입니다.") | ||
| @Min(value = 0, message = "MIDI 피치 값은 0 이상이어야 합니다.") | ||
| @Max(value = 127, message = "MIDI 피치 값은 127 이하여야 합니다.") | ||
| Integer pitch, | ||
|
|
||
| @NotNull(message = "MIDI 입력 강도 값은 필수입니다.") | ||
| @Min(value = 0, message = "MIDI 입력 강도 값은 0 이상이어야 합니다.") | ||
| @Max(value = 127, message = "MIDI 입력 강도 값은 127 이하여야 합니다.") | ||
| Integer velocity, | ||
|
|
||
| @NotNull(message = "MIDI 이벤트 발생 시간은 필수입니다.") | ||
| @Min(value = 0, message = "MIDI 이벤트 발생 시간은 0 이상이어야 합니다.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PR 참고 사항에는 10분을 초과하는 MIDI 이벤트는 저장 대상에서 제외한다고 되어 있는데, 현재 DTO와 서비스 로직에서는 timestampMs >= 0만 검증하고 전체 이벤트를 그대로 저장하는 것으로 보입니다! timestampMs > 600_000인 이벤트를 필터링하려는 의도인지, 요청 자체를 예외 처리하려는 의도인지 명확히 해야 할 것 같습니다...🥹🥹
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 최대 timestampMs는 요청 시점에 알 수 있는 고정값이 아니라 실제 연주 종료 시각을 기준으로 동적으로 결정되므로, DTO에서는 상한 검증을 두지 않았어요! (음수 값만 검증) 다만 연주 시간이 정확히 10분에 도달한 경우에 최대 10분 제한으로 인해 추가 500ms의 허용 오차가 적용되지 않는데, 해당 동작이 적절한지에 대해서는 팀원분들 의견도 궁금합니다ㅎㅎ
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 도메인에 대한 이해가 많이 부족하지만.... 저는 지금은 괜찮다구 생각해요!! |
||
| Long timestampMs | ||
| ) { | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.mr.domain.playing.dto.res; | ||
|
|
||
| public record MidiEventSaveResponse( | ||
| Long playingId, | ||
| int savedCount | ||
| ) { | ||
|
|
||
| public static MidiEventSaveResponse of(Long playingId, int savedCount) { | ||
| return new MidiEventSaveResponse(playingId, savedCount); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package com.mr.domain.playing.exception; | ||
|
|
||
| import com.mr.global.apipayload.code.BaseCode; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| public enum MidiEventErrorStatus implements BaseCode { | ||
|
|
||
| INVALID_PLAYING_ID(HttpStatus.BAD_REQUEST, "MIDI_400_01", "연주 ID가 올바르지 않습니다."), | ||
| EMPTY_MIDI_EVENTS(HttpStatus.BAD_REQUEST,"MIDI_400_02", "MIDI 이벤트 목록은 필수입니다."), | ||
| INVALID_MIDI_TYPE(HttpStatus.BAD_REQUEST, "MIDI_400_03", "MIDI 이벤트 타입이 올바르지 않습니다."), | ||
| INVALID_PITCH_RANGE(HttpStatus.BAD_REQUEST, "MIDI_400_04", "피치 값은 0~127 사이의 값이어야 합니다."), | ||
| INVALID_VELOCITY_RANGE(HttpStatus.BAD_REQUEST, "MIDI_400_05", "강도는 0~127 사이의 값이어야 합니다."), | ||
| INVALID_TIMESTAMP(HttpStatus.BAD_REQUEST, "MIDI_400_06", "MIDI 이벤트 타임스탬프는 0 이상이어야 합니다."), | ||
| INVALID_MIDI_EVENT(HttpStatus.BAD_REQUEST,"MIDI_400_07", "MIDI 이벤트 목록에 유효하지 않은 값이 포함되어 있습니다."), | ||
| INVALID_MIDI_SEQUENCE(HttpStatus.BAD_REQUEST, "MIDI_400_08", "MIDI 이벤트 순서(sequence) 값이 유효하지 않습니다."), | ||
| EXCEEDED_MIDI_EVENT_COUNT(HttpStatus.BAD_REQUEST, "MIDI_400_09", "MIDI 이벤트 개수가 허용 범위를 초과했습니다."), | ||
| DUPLICATE_MIDI_SEQUENCE(HttpStatus.BAD_REQUEST, "MIDI_400_10", "동일한 시간에 중복된 MIDI sequence 값이 존재합니다."), | ||
|
|
||
| PLAYING_NOT_IN_PROGRESS(HttpStatus.CONFLICT, "MIDI_409_01", "진행 중인 연주 세션에만 MIDI 이벤트를 저장할 수 있습니다."), | ||
| MIDI_SAVE_REQUEST_TOO_FREQUENT(HttpStatus.TOO_MANY_REQUESTS, "MIDI_429_01", "연주 완료 요청은 1분에 한 번만 가능합니다."), | ||
| ; | ||
|
|
||
| private final HttpStatus status; | ||
| private final String code; | ||
| private final String message; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.