diff --git a/backend/src/main/java/shook/shook/auth/config/AuthConfig.java b/backend/src/main/java/shook/shook/auth/config/AuthConfig.java index 5459f1442..0f7a1c225 100644 --- a/backend/src/main/java/shook/shook/auth/config/AuthConfig.java +++ b/backend/src/main/java/shook/shook/auth/config/AuthConfig.java @@ -36,15 +36,18 @@ public void addInterceptors(final InterceptorRegistry registry) { private HandlerInterceptor loginCheckerInterceptor() { return new PathMatcherInterceptor(loginCheckerInterceptor) - .includePathPattern("/songs/high-liked/**", PathMethod.GET); + .includePathPattern("/songs/high-liked/**", PathMethod.GET) + .includePathPattern("/songs/*", PathMethod.GET); } private HandlerInterceptor tokenInterceptor() { return new PathMatcherInterceptor(tokenInterceptor) - .includePathPattern("/my-page", PathMethod.GET) + .includePathPattern("/my-page/**", PathMethod.GET) .includePathPattern("/songs/*/parts/*/likes", PathMethod.PUT) .includePathPattern("/voting-songs/*/parts", PathMethod.POST) .includePathPattern("/songs/*/parts/*/comments", PathMethod.POST) + .includePathPattern("/songs/*/member-parts", PathMethod.POST) + .includePathPattern("/member-parts/*", PathMethod.DELETE) .includePathPattern("/members/*", PathMethod.DELETE); } diff --git a/backend/src/main/java/shook/shook/auth/ui/AuthContext.java b/backend/src/main/java/shook/shook/auth/ui/AuthContext.java index f7d0cd5c4..7781a5868 100644 --- a/backend/src/main/java/shook/shook/auth/ui/AuthContext.java +++ b/backend/src/main/java/shook/shook/auth/ui/AuthContext.java @@ -18,7 +18,7 @@ public void setAuthenticatedMember(final Long memberId) { } public boolean isAnonymous() { - return this.authority == Authority.ANONYMOUS; + return authority.isAnonymous(); } public long getMemberId() { @@ -29,3 +29,4 @@ public Authority getAuthority() { return authority; } } + diff --git a/backend/src/main/java/shook/shook/auth/ui/interceptor/TokenInterceptor.java b/backend/src/main/java/shook/shook/auth/ui/interceptor/TokenInterceptor.java index 0f723b3b6..7c3b7e3a7 100644 --- a/backend/src/main/java/shook/shook/auth/ui/interceptor/TokenInterceptor.java +++ b/backend/src/main/java/shook/shook/auth/ui/interceptor/TokenInterceptor.java @@ -3,7 +3,6 @@ import io.jsonwebtoken.Claims; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.util.Map; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import shook.shook.auth.application.TokenProvider; @@ -12,6 +11,8 @@ import shook.shook.member.application.MemberService; import shook.shook.member.domain.Nickname; +import java.util.Map; + @Component public class TokenInterceptor implements HandlerInterceptor { @@ -20,9 +21,9 @@ public class TokenInterceptor implements HandlerInterceptor { private final MemberService memberService; public TokenInterceptor( - final TokenProvider tokenProvider, - final AuthContext authContext, - final MemberService memberService + final TokenProvider tokenProvider, + final AuthContext authContext, + final MemberService memberService ) { this.tokenProvider = tokenProvider; this.authContext = authContext; @@ -31,14 +32,14 @@ public TokenInterceptor( @Override public boolean preHandle( - final HttpServletRequest request, - final HttpServletResponse response, - final Object handler + final HttpServletRequest request, + final HttpServletResponse response, + final Object handler ) { final String token = TokenHeaderExtractor.extractToken(request) - .orElseThrow(() -> new AuthorizationException.AccessTokenNotFoundException( - Map.of("RequestURL", request.getRequestURL().toString()) - )); + .orElseThrow(() -> new AuthorizationException.AccessTokenNotFoundException( + Map.of("RequestURL", request.getRequestURL().toString()) + )); final Claims claims = tokenProvider.parseClaims(token); final Long memberId = claims.get("memberId", Long.class); final String nickname = claims.get("nickname", String.class); diff --git a/backend/src/main/java/shook/shook/globalexception/ErrorCode.java b/backend/src/main/java/shook/shook/globalexception/ErrorCode.java index 0b77761d5..832db7b6e 100644 --- a/backend/src/main/java/shook/shook/globalexception/ErrorCode.java +++ b/backend/src/main/java/shook/shook/globalexception/ErrorCode.java @@ -40,6 +40,7 @@ public enum ErrorCode { EMPTY_KILLING_PARTS(2011, "노래의 킬링파트는 비어있을 수 없습니다."), // 3000: 노래 + NOT_POSITIVE_SONG_LENGTH(3001, "노래 길이는 0보다 커야합니다."), SONG_NOT_EXIST(3002, "존재하지 않는 노래입니다."), EMPTY_SONG_TITLE(3003, "노래 제목은 비어있을 수 없습니다."), @@ -55,18 +56,20 @@ public enum ErrorCode { WRONG_GENRE_TYPE(3013, "잘못된 장르 타입입니다."), // 4000: 투표 + VOTING_PART_START_LESS_THAN_ZERO(4001, "파트의 시작 초는 0보다 작을 수 없습니다."), VOTING_PART_START_OVER_SONG_LENGTH(4002, "파트의 시작 초는 노래 길이를 초과할 수 없습니다."), VOTING_PART_END_OVER_SONG_LENGTH(4003, "파트의 끝 초는 노래 길이를 초과할 수 없습니다."), - INVALID_VOTING_PART_LENGTH(4004, "파트의 길이는 5, 10, 15초 중 하나여야합니다."), + INVALID_VOTING_PART_LENGTH(4004, "파트의 길이는 5초 이상, 15초 이하여야 합니다."), VOTING_PART_DUPLICATE_START_AND_LENGTH_EXCEPTION(4005, - "한 노래에 동일한 파트를 두 개 이상 등록할 수 없습니다."), + "한 노래에 동일한 파트를 두 개 이상 등록할 수 없습니다."), VOTING_SONG_PART_NOT_EXIST(4006, "투표 대상 파트가 존재하지 않습니다."), VOTING_SONG_PART_FOR_OTHER_SONG(4007, "해당 파트는 다른 노래의 파트입니다."), VOTING_SONG_NOT_EXIST(4008, "존재하지 않는 투표 노래입니다."), VOTE_FOR_OTHER_PART(4009, "해당 투표는 다른 파트에 대한 투표입니다."), DUPLICATE_VOTE_EXIST(4010, "중복된 투표입니다."), + // 5000: 사용자 EMPTY_EMAIL(5001, "이메일은 비어있을 수 없습니다."), @@ -80,7 +83,14 @@ public enum ErrorCode { REQUEST_BODY_VALIDATION_FAIL(10001, ""), WRONG_REQUEST_URL(10002, "URL의 pathVariable 은 비어있을 수 없습니다."), - INTERNAL_SERVER_ERROR(10003, "알 수 없는 서버 오류가 발생했습니다. 관리자에게 문의해주세요."); + INTERNAL_SERVER_ERROR(10003, "알 수 없는 서버 오류가 발생했습니다. 관리자에게 문의해주세요."), + + // 6000: 멤버 파트 + + NEGATIVE_START_SECOND(6001, "멤버 파트의 시작 초는 0보다 작을 수 없습니다."), + MEMBER_PART_END_OVER_SONG_LENGTH(6002, "파트의 끝 초는 노래 길이를 초과할 수 없습니다."), + MEMBER_PART_ALREADY_EXIST(6003, "멤버 파트가 이미 존재합니다."), + ; private final int code; private final String message; diff --git a/backend/src/main/java/shook/shook/globalexception/GlobalExceptionHandler.java b/backend/src/main/java/shook/shook/globalexception/GlobalExceptionHandler.java index 0779a4d19..5a7bb1162 100644 --- a/backend/src/main/java/shook/shook/globalexception/GlobalExceptionHandler.java +++ b/backend/src/main/java/shook/shook/globalexception/GlobalExceptionHandler.java @@ -13,6 +13,7 @@ import shook.shook.auth.exception.OAuthException; import shook.shook.auth.exception.TokenException; import shook.shook.member.exception.MemberException; +import shook.shook.member_part.exception.MemberPartException; import shook.shook.part.exception.PartException; import shook.shook.song.exception.SongException; import shook.shook.song.exception.killingpart.KillingPartCommentException; @@ -55,7 +56,8 @@ public ResponseEntity handleTokenException(final CustomException MemberException.class, VotingSongException.class, VotingSongPartException.PartNotExistException.class, - PartException.class + PartException.class, + MemberPartException.class }) public ResponseEntity handleGlobalBadRequestException(final CustomException e) { log.error(e.getErrorInfoLog()); diff --git a/backend/src/main/java/shook/shook/member/exception/MemberException.java b/backend/src/main/java/shook/shook/member/exception/MemberException.java index df8f8eeeb..c4bc8cffa 100644 --- a/backend/src/main/java/shook/shook/member/exception/MemberException.java +++ b/backend/src/main/java/shook/shook/member/exception/MemberException.java @@ -93,4 +93,15 @@ public MemberNotExistException(final Map inputValuesByProperty) super(ErrorCode.MEMBER_NOT_EXIST, inputValuesByProperty); } } + + public static class MemberPartAlreadyExistException extends MemberException { + + public MemberPartAlreadyExistException() { + super(ErrorCode.MEMBER_PART_ALREADY_EXIST); + } + + public MemberPartAlreadyExistException(final Map inputValuesByProperty) { + super(ErrorCode.MEMBER_PART_ALREADY_EXIST, inputValuesByProperty); + } + } } diff --git a/backend/src/main/java/shook/shook/member_part/application/MemberPartService.java b/backend/src/main/java/shook/shook/member_part/application/MemberPartService.java new file mode 100644 index 000000000..4e4588c4b --- /dev/null +++ b/backend/src/main/java/shook/shook/member_part/application/MemberPartService.java @@ -0,0 +1,71 @@ +package shook.shook.member_part.application; + +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import shook.shook.auth.exception.AuthorizationException; +import shook.shook.member.domain.Member; +import shook.shook.member.domain.repository.MemberRepository; +import shook.shook.member.exception.MemberException; +import shook.shook.member_part.application.dto.MemberPartRegisterRequest; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.member_part.domain.repository.MemberPartRepository; +import shook.shook.song.domain.Song; +import shook.shook.song.domain.repository.SongRepository; +import shook.shook.song.exception.SongException.SongNotExistException; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class MemberPartService { + + private final SongRepository songRepository; + private final MemberRepository memberRepository; + private final MemberPartRepository memberPartRepository; + + @Transactional + public void register(final Long songId, final Long memberId, final MemberPartRegisterRequest request) { + final Song song = getSong(songId); + final Member member = getMember(memberId); + validateAlreadyExistMemberPart(song, member); + + final MemberPart memberPart = MemberPart.forSave(request.getStartSecond(), request.getLength(), song, member); + memberPartRepository.save(memberPart); + } + + private void validateAlreadyExistMemberPart(final Song song, final Member member) { + final boolean existsMemberPart = memberPartRepository.existsByMemberAndSong(member, song); + + if (existsMemberPart) { + throw new MemberException.MemberPartAlreadyExistException( + Map.of("songId", String.valueOf(song.getId()), + "memberId", String.valueOf(member.getId())) + ); + } + } + + private Member getMember(final Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException.MemberNotExistException( + Map.of("memberId", String.valueOf(memberId)) + )); + } + + private Song getSong(final Long songId) { + return songRepository.findById(songId) + .orElseThrow(() -> new SongNotExistException( + Map.of("songId", String.valueOf(songId)) + )); + } + + @Transactional + public void delete(final Long memberId, final Long memberPartId) { + final MemberPart memberPart = memberPartRepository.findByMemberIdAndId(memberId, memberPartId) + .orElseThrow(() -> new AuthorizationException.UnauthenticatedException( + Map.of("memberId", String.valueOf(memberId), "memberPartId", String.valueOf(memberPartId)) + )); + + memberPartRepository.delete(memberPart); + } +} diff --git a/backend/src/main/java/shook/shook/member_part/application/dto/MemberPartRegisterRequest.java b/backend/src/main/java/shook/shook/member_part/application/dto/MemberPartRegisterRequest.java new file mode 100644 index 000000000..411097316 --- /dev/null +++ b/backend/src/main/java/shook/shook/member_part/application/dto/MemberPartRegisterRequest.java @@ -0,0 +1,27 @@ +package shook.shook.member_part.application.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Schema(description = "멤버 파트 등록 요청") +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor +@Getter +public class MemberPartRegisterRequest { + + @Schema(description = "멤버 파트 시작 초", example = "23") + @NotNull + @PositiveOrZero + private Integer startSecond; + + @Schema(description = "멤버 파트 길이", example = "10") + @NotNull + @Positive + private Integer length; +} diff --git a/backend/src/main/java/shook/shook/member_part/domain/MemberPart.java b/backend/src/main/java/shook/shook/member_part/domain/MemberPart.java new file mode 100644 index 000000000..d469c7c6f --- /dev/null +++ b/backend/src/main/java/shook/shook/member_part/domain/MemberPart.java @@ -0,0 +1,133 @@ +package shook.shook.member_part.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Map; +import java.util.Objects; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import shook.shook.member.domain.Member; +import shook.shook.member_part.exception.MemberPartException; +import shook.shook.part.domain.PartLength; +import shook.shook.song.domain.Song; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Table(name = "member_part") +@Entity +public class MemberPart { + + private static final int MINIMUM_START = 0; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, updatable = false) + private int startSecond; + + @Embedded + private PartLength length; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "song_id", foreignKey = @ForeignKey(name = "none"), updatable = false, nullable = false) + private Song song; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", foreignKey = @ForeignKey(name = "none"), updatable = false, nullable = false) + private Member member; + + @Column(nullable = false, updatable = false) + private LocalDateTime createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); + + @PrePersist + private void prePersist() { + createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); + } + + private MemberPart( + final Long id, + final int startSecond, + final PartLength length, + final Song song, + final Member member + ) { + this.id = id; + this.startSecond = startSecond; + this.length = length; + this.song = song; + this.member = member; + } + + public static MemberPart saved( + final Long id, + final int startSecond, + final PartLength length, + final Song song, + final Member member + ) { + return new MemberPart(id, startSecond, length, song, member); + } + + public static MemberPart forSave(final int startSecond, final int length, final Song song, final Member member) { + final PartLength partLength = new PartLength(length); + validateStartSecond(startSecond, partLength, song.getLength()); + return new MemberPart(null, startSecond, partLength, song, member); + } + + private static void validateStartSecond(final int startSecond, final PartLength length, final int songLength) { + if (startSecond < MINIMUM_START) { + throw new MemberPartException.MemberPartStartSecondNegativeException( + Map.of("startSecond", String.valueOf(startSecond)) + ); + } + if (length.getEndSecond(startSecond) > songLength) { + throw new MemberPartException.MemberPartEndOverSongLengthException( + Map.of("startSecond", String.valueOf(startSecond), + "EndSecond", String.valueOf(length.getEndSecond(startSecond)) + ) + ); + } + } + + public int getEndSecond() { + return length.getEndSecond(startSecond); + } + + public int getLength() { + return length.getValue(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final MemberPart part = (MemberPart) o; + if (Objects.isNull(part.id) || Objects.isNull(this.id)) { + return false; + } + return Objects.equals(id, part.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/backend/src/main/java/shook/shook/member_part/domain/repository/MemberPartRepository.java b/backend/src/main/java/shook/shook/member_part/domain/repository/MemberPartRepository.java new file mode 100644 index 000000000..5f4879322 --- /dev/null +++ b/backend/src/main/java/shook/shook/member_part/domain/repository/MemberPartRepository.java @@ -0,0 +1,30 @@ +package shook.shook.member_part.domain.repository; + +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import shook.shook.member.domain.Member; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.member_part.domain.repository.dto.SongMemberPartCreatedAtDto; +import shook.shook.song.domain.Song; + +public interface MemberPartRepository extends JpaRepository { + + Optional findByMemberIdAndId(final Long memberId, final Long memberPartId); + + List findByMemberAndSongIdIn(final Member member, final List songIds); + + @Query("SELECT s as song, mp as memberPart " + + "FROM MemberPart mp " + + "LEFT JOIN FETCH Song s ON mp.song = s " + + "WHERE mp.member.id = :memberId") + List findByMemberId( + @Param("memberId") final Long memberId + ); + + Optional findByMemberAndSong(final Member member, final Song song); + + boolean existsByMemberAndSong(final Member member, final Song song); +} diff --git a/backend/src/main/java/shook/shook/member_part/domain/repository/dto/SongMemberPartCreatedAtDto.java b/backend/src/main/java/shook/shook/member_part/domain/repository/dto/SongMemberPartCreatedAtDto.java new file mode 100644 index 000000000..afa8432fa --- /dev/null +++ b/backend/src/main/java/shook/shook/member_part/domain/repository/dto/SongMemberPartCreatedAtDto.java @@ -0,0 +1,11 @@ +package shook.shook.member_part.domain.repository.dto; + +import shook.shook.member_part.domain.MemberPart; +import shook.shook.song.domain.Song; + +public interface SongMemberPartCreatedAtDto { + + Song getSong(); + + MemberPart getMemberPart(); +} diff --git a/backend/src/main/java/shook/shook/member_part/exception/MemberPartException.java b/backend/src/main/java/shook/shook/member_part/exception/MemberPartException.java new file mode 100644 index 000000000..c72361b28 --- /dev/null +++ b/backend/src/main/java/shook/shook/member_part/exception/MemberPartException.java @@ -0,0 +1,41 @@ +package shook.shook.member_part.exception; + +import java.util.Map; +import shook.shook.globalexception.CustomException; +import shook.shook.globalexception.ErrorCode; + +public class MemberPartException extends CustomException { + + public MemberPartException(final ErrorCode errorCode) { + super(errorCode); + } + + public MemberPartException( + final ErrorCode errorCode, + final Map inputValuesByProperty + ) { + super(errorCode, inputValuesByProperty); + } + + public static class MemberPartStartSecondNegativeException extends MemberPartException { + + public MemberPartStartSecondNegativeException() { + super(ErrorCode.NEGATIVE_START_SECOND); + } + + public MemberPartStartSecondNegativeException(final Map inputValuesByProperty) { + super(ErrorCode.NEGATIVE_START_SECOND, inputValuesByProperty); + } + } + + public static class MemberPartEndOverSongLengthException extends MemberPartException { + + public MemberPartEndOverSongLengthException() { + super(ErrorCode.NEGATIVE_START_SECOND); + } + + public MemberPartEndOverSongLengthException(final Map inputValuesByProperty) { + super(ErrorCode.NEGATIVE_START_SECOND, inputValuesByProperty); + } + } +} diff --git a/backend/src/main/java/shook/shook/member_part/ui/MemberPartController.java b/backend/src/main/java/shook/shook/member_part/ui/MemberPartController.java new file mode 100644 index 000000000..5340618a5 --- /dev/null +++ b/backend/src/main/java/shook/shook/member_part/ui/MemberPartController.java @@ -0,0 +1,46 @@ +package shook.shook.member_part.ui; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +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; +import shook.shook.auth.ui.argumentresolver.Authenticated; +import shook.shook.auth.ui.argumentresolver.MemberInfo; +import shook.shook.member_part.application.MemberPartService; +import shook.shook.member_part.application.dto.MemberPartRegisterRequest; +import shook.shook.member_part.ui.openapi.MemberPartApi; + +@RequiredArgsConstructor +@RequestMapping +@RestController +public class MemberPartController implements MemberPartApi { + + private final MemberPartService memberPartService; + + @PostMapping("/songs/{song_id}/member-parts") + public ResponseEntity register( + @PathVariable(name = "song_id") final Long songId, + @Authenticated final MemberInfo memberInfo, + @Valid @RequestBody final MemberPartRegisterRequest request + ) { + memberPartService.register(songId, memberInfo.getMemberId(), request); + + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @DeleteMapping("/member-parts/{member_part_id}") + public ResponseEntity delete( + @PathVariable(name = "member_part_id") final Long memberPartId, + @Authenticated final MemberInfo memberInfo + ) { + memberPartService.delete(memberInfo.getMemberId(), memberPartId); + + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/shook/shook/member_part/ui/openapi/MemberPartApi.java b/backend/src/main/java/shook/shook/member_part/ui/openapi/MemberPartApi.java new file mode 100644 index 000000000..9aa64697e --- /dev/null +++ b/backend/src/main/java/shook/shook/member_part/ui/openapi/MemberPartApi.java @@ -0,0 +1,53 @@ +package shook.shook.member_part.ui.openapi; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import shook.shook.auth.ui.argumentresolver.Authenticated; +import shook.shook.auth.ui.argumentresolver.MemberInfo; +import shook.shook.member_part.application.dto.MemberPartRegisterRequest; + +@Tag(name = "Member Part", description = "멤버 파트 API") +public interface MemberPartApi { + + @Operation( + summary = "멤버 파트 등록", + description = "멤버 파트를 등록한다." + ) + @ApiResponse( + responseCode = "201", + description = "멤버 파트 등록 성공" + ) + @Parameters( + value = { + @Parameter( + name = "songId", + description = "노래 ID", + required = true + ), + @Parameter( + name = "memberId", + description = "회원 ID", + required = true, + hidden = true + ), + @Parameter( + name = "request", + description = "멤버 파트 등록 요청", + required = true + ) + } + ) + @PostMapping + ResponseEntity register( + @PathVariable(name = "song_id") final Long songId, + @Authenticated final MemberInfo memberInfo, + @Valid final MemberPartRegisterRequest request + ); +} diff --git a/backend/src/main/java/shook/shook/part/domain/PartLength.java b/backend/src/main/java/shook/shook/part/domain/PartLength.java index afcc9add1..263c826ca 100644 --- a/backend/src/main/java/shook/shook/part/domain/PartLength.java +++ b/backend/src/main/java/shook/shook/part/domain/PartLength.java @@ -1,34 +1,40 @@ package shook.shook.part.domain; -import java.util.Arrays; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; import java.util.Map; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; import shook.shook.part.exception.PartException; -public enum PartLength { - SHORT(5), - STANDARD(10), - LONG(15); +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@EqualsAndHashCode +@Embeddable +public class PartLength { - private final int value; + private static final int MINIMUM_LENGTH = 5; + private static final int MAXIMUM_LENGTH = 15; - PartLength(final int value) { + @Column(name = "length", nullable = false) + private int value; + + public PartLength(final int value) { + validate(value); this.value = value; } - public static PartLength findBySecond(final int second) { - return Arrays.stream(values()) - .filter(length -> length.value == second) - .findFirst() - .orElseThrow(() -> new PartException.InvalidLengthException( + public void validate(final int second) { + if (second < MINIMUM_LENGTH || second > MAXIMUM_LENGTH) { + throw new PartException.InvalidLengthException( Map.of("PartLength", String.valueOf(second)) - )); + ); + } } public int getEndSecond(final int start) { return start + this.value; } - - public int getValue() { - return value; - } } diff --git a/backend/src/main/java/shook/shook/song/application/MyPageService.java b/backend/src/main/java/shook/shook/song/application/MyPageService.java index 88466eb3e..c699b8bdd 100644 --- a/backend/src/main/java/shook/shook/song/application/MyPageService.java +++ b/backend/src/main/java/shook/shook/song/application/MyPageService.java @@ -10,7 +10,10 @@ import shook.shook.member.domain.repository.MemberRepository; import shook.shook.member.exception.MemberException; import shook.shook.member.exception.MemberException.MemberNotExistException; +import shook.shook.member_part.domain.repository.MemberPartRepository; +import shook.shook.member_part.domain.repository.dto.SongMemberPartCreatedAtDto; import shook.shook.song.application.dto.LikedKillingPartResponse; +import shook.shook.song.application.dto.MyPartsResponse; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; import shook.shook.song.domain.killingpart.repository.dto.SongKillingPartKillingPartLikeCreatedAtDto; @@ -21,6 +24,7 @@ public class MyPageService { private final KillingPartLikeRepository killingPartLikeRepository; private final MemberRepository memberRepository; + private final MemberPartRepository memberPartRepository; public List findLikedKillingPartByMemberId( final MemberInfo memberInfo @@ -46,4 +50,20 @@ public List findLikedKillingPartByMemberId( )) .toList(); } + + public List findMyPartByMemberId(final Long memberId) { + final List memberPartAndSongByMemberId = memberPartRepository.findByMemberId( + memberId); + + return memberPartAndSongByMemberId.stream() + .sorted(Comparator.comparing(songMemberPartCreatedAtDto -> + songMemberPartCreatedAtDto.getMemberPart().getCreatedAt(), + Comparator.reverseOrder() + )) + .map(memberPart -> MyPartsResponse.of( + memberPart.getSong(), + memberPart.getMemberPart() + )) + .toList(); + } } diff --git a/backend/src/main/java/shook/shook/song/application/SongDataExcelReader.java b/backend/src/main/java/shook/shook/song/application/SongDataExcelReader.java index 2283a8181..67dec6805 100644 --- a/backend/src/main/java/shook/shook/song/application/SongDataExcelReader.java +++ b/backend/src/main/java/shook/shook/song/application/SongDataExcelReader.java @@ -15,7 +15,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.Genre; import shook.shook.song.domain.KillingParts; import shook.shook.song.domain.Song; @@ -91,8 +90,7 @@ private Optional parseToSong(final Row currentRow) { final Optional killingParts = getKillingParts(cellIterator); return killingParts.map( - parts -> new Song(title, videoId, albumCoverUrl, singer, length, Genre.from(genre), - parts)); + parts -> new Song(title, videoId, albumCoverUrl, singer, length, Genre.from(genre), parts)); } private String getString(final Iterator iterator) { @@ -145,6 +143,6 @@ private Optional toKillingPart(final String killingPart) { } final int length = Integer.parseInt(songLength.split(songLengthSuffix)[0]); - return Optional.of(KillingPart.forSave(start, PartLength.findBySecond(length))); + return Optional.of(KillingPart.forSave(start, length)); } } diff --git a/backend/src/main/java/shook/shook/song/application/SongService.java b/backend/src/main/java/shook/shook/song/application/SongService.java index 1ae2b15fc..b4827e3af 100644 --- a/backend/src/main/java/shook/shook/song/application/SongService.java +++ b/backend/src/main/java/shook/shook/song/application/SongService.java @@ -1,7 +1,9 @@ package shook.shook.song.application; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -11,6 +13,8 @@ import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; import shook.shook.member.exception.MemberException; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.member_part.domain.repository.MemberPartRepository; import shook.shook.song.application.dto.SongResponse; import shook.shook.song.application.dto.SongSwipeResponse; import shook.shook.song.application.dto.SongWithKillingPartsRegisterRequest; @@ -37,6 +41,7 @@ public class SongService { private final KillingPartRepository killingPartRepository; private final KillingPartLikeRepository killingPartLikeRepository; private final MemberRepository memberRepository; + private final MemberPartRepository memberPartRepository; private final InMemorySongs inMemorySongs; private final SongDataExcelReader songDataExcelReader; @@ -88,7 +93,14 @@ private SongSwipeResponse convertToSongSwipeResponse( final Member member = findMemberById(memberInfo.getMemberId()); final List killingPartIds = killingPartLikeRepository.findLikedKillingPartIdsByMember(member); - return SongSwipeResponse.of(currentSong, beforeSongs, afterSongs, killingPartIds); + + final List allSongs = new ArrayList<>(beforeSongs); + allSongs.add(currentSong); + allSongs.addAll(afterSongs); + + final Map memberPartsGroupBySong = makeMemberPartsGroupBySongId(member, allSongs); + + return SongSwipeResponse.of(currentSong, beforeSongs, afterSongs, killingPartIds, memberPartsGroupBySong); } private Member findMemberById(final Long memberId) { @@ -125,12 +137,25 @@ private List convertToSongResponses( final Member member = findMemberById(memberInfo.getMemberId()); final List likedKillingPartIds = killingPartLikeRepository.findLikedKillingPartIdsByMember(member); + final Map memberPartsGroupBySongId = makeMemberPartsGroupBySongId(member, songs); return songs.stream() - .map(song -> SongResponse.of(song, likedKillingPartIds)) + .map(song -> SongResponse.of(song, likedKillingPartIds, + memberPartsGroupBySongId.getOrDefault(song.getId(), null))) .toList(); } + private Map makeMemberPartsGroupBySongId(final Member member, final List songs) { + final List songIds = songs.stream() + .map(Song::getId) + .collect(Collectors.toList()); + final List memberParts = memberPartRepository.findByMemberAndSongIdIn(member, songIds); + + return memberParts.stream() + .collect(Collectors.toMap(memberPart -> memberPart.getSong().getId(), + memberPart -> memberPart)); + } + public List findSongByIdForAfterSwipe( final Long songId, final MemberInfo memberInfo @@ -162,19 +187,10 @@ public SongSwipeResponse findSongsByGenreForSwipe( final Genre genre = Genre.findByName(genreName); final Song currentSong = inMemorySongs.getSongById(songId); final List prevSongs = inMemorySongs.getPrevLikedSongByGenre(currentSong, genre, - BEFORE_SONGS_COUNT); + BEFORE_SONGS_COUNT); final List nextSongs = inMemorySongs.getNextLikedSongByGenre(currentSong, genre, AFTER_SONGS_COUNT); - final Authority authority = memberInfo.getAuthority(); - - if (authority.isAnonymous()) { - return SongSwipeResponse.ofUnauthorizedUser(currentSong, prevSongs, nextSongs); - } - - final Member member = findMemberById(memberInfo.getMemberId()); - final List likedKillingPartIds = - killingPartLikeRepository.findLikedKillingPartIdsByMember(member); - return SongSwipeResponse.of(currentSong, prevSongs, nextSongs, likedKillingPartIds); + return convertToSongSwipeResponse(memberInfo, currentSong, prevSongs, nextSongs); } public List findPrevSongsByGenre( @@ -185,7 +201,7 @@ public List findPrevSongsByGenre( final Genre genre = Genre.findByName(genreName); final Song currentSong = inMemorySongs.getSongById(songId); final List prevSongs = inMemorySongs.getPrevLikedSongByGenre(currentSong, genre, - BEFORE_SONGS_COUNT); + BEFORE_SONGS_COUNT); return convertToSongResponses(memberInfo, prevSongs); } @@ -201,4 +217,21 @@ public List findNextSongsByGenre( return convertToSongResponses(memberInfo, nextSongs); } + + public SongResponse findSongById(final Long songId, final MemberInfo memberInfo) { + final Song song = inMemorySongs.getSongById(songId); + final Authority authority = memberInfo.getAuthority(); + + if (authority.isAnonymous()) { + return SongResponse.fromUnauthorizedUser(song); + } + + final Member member = findMemberById(memberInfo.getMemberId()); + final List likedKillingPartIds = + killingPartLikeRepository.findLikedKillingPartIdsByMember(member); + final MemberPart memberPart = memberPartRepository.findByMemberAndSong(member, song) + .orElse(null); + + return SongResponse.of(song, likedKillingPartIds, memberPart); + } } diff --git a/backend/src/main/java/shook/shook/song/application/dto/KillingPartRegisterRequest.java b/backend/src/main/java/shook/shook/song/application/dto/KillingPartRegisterRequest.java index 67eabe3d7..a5e00a1ea 100644 --- a/backend/src/main/java/shook/shook/song/application/dto/KillingPartRegisterRequest.java +++ b/backend/src/main/java/shook/shook/song/application/dto/KillingPartRegisterRequest.java @@ -8,7 +8,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.killingpart.KillingPart; @Schema(description = "킬링파트 등록 요청") @@ -28,9 +27,6 @@ public class KillingPartRegisterRequest { private Integer length; public KillingPart toKillingPart() { - return KillingPart.forSave( - startSecond, - PartLength.findBySecond(length) - ); + return KillingPart.forSave(startSecond, length); } } diff --git a/backend/src/main/java/shook/shook/song/application/dto/MemberPartResponse.java b/backend/src/main/java/shook/shook/song/application/dto/MemberPartResponse.java new file mode 100644 index 000000000..5a1869d01 --- /dev/null +++ b/backend/src/main/java/shook/shook/song/application/dto/MemberPartResponse.java @@ -0,0 +1,38 @@ +package shook.shook.song.application.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import shook.shook.member_part.domain.MemberPart; + +@Schema(description = "멤버 파트 응답") +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class MemberPartResponse { + + @Schema(description = "멤버 파트 id", example = "1") + private final Long id; + + @Schema(description = "멤버 파트 시작 초", example = "30") + private final int start; + + @Schema(description = "멤버 파트 끝 초", example = "40") + private final int end; + + @Schema(description = "멤버 파트 길이", example = "10") + private final int partLength; + + public static MemberPartResponse from(final MemberPart memberPart) { + if (memberPart == null) { + return null; + } + + return new MemberPartResponse( + memberPart.getId(), + memberPart.getStartSecond(), + memberPart.getEndSecond(), + memberPart.getLength() + ); + } +} diff --git a/backend/src/main/java/shook/shook/song/application/dto/MyPartsResponse.java b/backend/src/main/java/shook/shook/song/application/dto/MyPartsResponse.java new file mode 100644 index 000000000..4b5e0c079 --- /dev/null +++ b/backend/src/main/java/shook/shook/song/application/dto/MyPartsResponse.java @@ -0,0 +1,51 @@ +package shook.shook.song.application.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.song.domain.Song; + +@Schema(description = "내 파트 응답") +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class MyPartsResponse { + + @Schema(description = "노래 id", example = "1") + private final Long songId; + + @Schema(description = "노래 제목", example = "제목") + private final String title; + + @Schema(description = "노래 비디오 id", example = "4") + private final String songVideoId; + + @Schema(description = "가수 이름", example = "가수") + private final String singer; + + @Schema(description = "앨범 자켓 이미지 url", example = "https://image.com/album_cover.jpg") + private final String albumCoverUrl; + + @Schema(description = "내 파트 id", example = "1") + private final Long partId; + + @Schema(description = "내 파트 시작 초", example = "30") + private final int start; + + @Schema(description = "내 파트 끝 초", example = "40") + private final int end; + + public static MyPartsResponse of(final Song song, final MemberPart memberPart) { + return new MyPartsResponse( + song.getId(), + song.getTitle(), + song.getVideoId(), + song.getSinger(), + song.getAlbumCoverUrl(), + memberPart.getId(), + memberPart.getStartSecond(), + memberPart.getEndSecond() + ); + } +} diff --git a/backend/src/main/java/shook/shook/song/application/dto/SongResponse.java b/backend/src/main/java/shook/shook/song/application/dto/SongResponse.java index dc453c70a..434e8e648 100644 --- a/backend/src/main/java/shook/shook/song/application/dto/SongResponse.java +++ b/backend/src/main/java/shook/shook/song/application/dto/SongResponse.java @@ -7,6 +7,7 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; +import shook.shook.member_part.domain.MemberPart; import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; @@ -39,7 +40,11 @@ public class SongResponse { @Schema(description = "킬링파트 3개") private final List killingParts; - public static SongResponse of(final Song song, final List likedKillingPartIds) { + @Schema(description = "멤버 파트") + private final MemberPartResponse memberPart; + + public static SongResponse of(final Song song, final List likedKillingPartIds, + final MemberPart memberPart) { return new SongResponse( song.getId(), song.getTitle(), @@ -48,12 +53,13 @@ public static SongResponse of(final Song song, final List likedKillingPart song.getVideoId(), song.getAlbumCoverUrl(), song.getGenre().name(), - toKillingPartResponses(song, likedKillingPartIds) + toKillingPartResponses(song, likedKillingPartIds), + MemberPartResponse.from(memberPart) ); } public static SongResponse fromUnauthorizedUser(final Song song) { - return SongResponse.of(song, Collections.emptyList()); + return SongResponse.of(song, Collections.emptyList(), null); } private static List toKillingPartResponses(final Song song, @@ -62,16 +68,16 @@ private static List toKillingPartResponses(final Song song, return IntStream.range(0, songKillingParts.size()) .mapToObj(index -> - { - final KillingPart killingPart = songKillingParts.get(index); - final int killingPartRank = index + 1; - return KillingPartResponse.of( - song, - killingPart, - killingPartRank, - likedKillingPartIds.contains(killingPart.getId()) - ); - }) + { + final KillingPart killingPart = songKillingParts.get(index); + final int killingPartRank = index + 1; + return KillingPartResponse.of( + song, + killingPart, + killingPartRank, + likedKillingPartIds.contains(killingPart.getId()) + ); + }) .toList(); } } diff --git a/backend/src/main/java/shook/shook/song/application/dto/SongSwipeResponse.java b/backend/src/main/java/shook/shook/song/application/dto/SongSwipeResponse.java index d2a2f328a..48fbe532e 100644 --- a/backend/src/main/java/shook/shook/song/application/dto/SongSwipeResponse.java +++ b/backend/src/main/java/shook/shook/song/application/dto/SongSwipeResponse.java @@ -2,9 +2,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; +import java.util.Map; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; +import shook.shook.member_part.domain.MemberPart; import shook.shook.song.domain.Song; @Schema(description = "첫 스와이프 시, 현재 노래와 이전, 이후 노래 리스트 조회 응답") @@ -25,14 +27,16 @@ public static SongSwipeResponse of( final Song currentSong, final List prevSongs, final List nextSongs, - final List likedKillingPartIds + final List likedKillingPartIds, + final Map memberPartsById ) { - final SongResponse currentResponse = SongResponse.of(currentSong, likedKillingPartIds); + final SongResponse currentResponse = SongResponse.of(currentSong, likedKillingPartIds, + memberPartsById.getOrDefault(currentSong.getId(), null)); final List prevResponses = prevSongs.stream() - .map(song -> SongResponse.of(song, likedKillingPartIds)) + .map(song -> SongResponse.of(song, likedKillingPartIds, memberPartsById.getOrDefault(song.getId(), null))) .toList(); final List nextResponses = nextSongs.stream() - .map(song -> SongResponse.of(song, likedKillingPartIds)) + .map(song -> SongResponse.of(song, likedKillingPartIds, memberPartsById.getOrDefault(song.getId(), null))) .toList(); return new SongSwipeResponse(prevResponses, currentResponse, nextResponses); diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java index 1236e8b9c..0114edf86 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java @@ -3,8 +3,6 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; import jakarta.persistence.GeneratedValue; @@ -48,8 +46,7 @@ public class KillingPart { @Column(nullable = false, updatable = false) private int startSecond; - @Column(nullable = false, updatable = false) - @Enumerated(EnumType.STRING) + @Embedded private PartLength length; @ManyToOne(fetch = FetchType.LAZY) @@ -77,31 +74,31 @@ private void prePersist() { private KillingPart( final Long id, final int startSecond, - final PartLength length, + final int length, final Song song, final int likeCount ) { this.id = id; this.startSecond = startSecond; - this.length = length; + this.length = new PartLength(length); this.song = song; this.likeCount = likeCount; } - private KillingPart(final int startSecond, final PartLength length) { + private KillingPart(final int startSecond, final int length) { this(null, startSecond, length, null, 0); } public static KillingPart saved( final Long id, final int startSecond, - final PartLength length, + final int length, final Song song ) { return new KillingPart(id, startSecond, length, song, 0); } - public static KillingPart forSave(final int startSecond, final PartLength length) { + public static KillingPart forSave(final int startSecond, final int length) { return new KillingPart(startSecond, length); } diff --git a/backend/src/main/java/shook/shook/song/ui/MyPageController.java b/backend/src/main/java/shook/shook/song/ui/MyPageController.java index 3e1007e48..2a05984af 100644 --- a/backend/src/main/java/shook/shook/song/ui/MyPageController.java +++ b/backend/src/main/java/shook/shook/song/ui/MyPageController.java @@ -1,6 +1,5 @@ package shook.shook.song.ui; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -10,8 +9,11 @@ import shook.shook.auth.ui.argumentresolver.MemberInfo; import shook.shook.song.application.MyPageService; import shook.shook.song.application.dto.LikedKillingPartResponse; +import shook.shook.song.application.dto.MyPartsResponse; import shook.shook.song.ui.openapi.MyPageApi; +import java.util.List; + @RequiredArgsConstructor @RestController @RequestMapping("/my-page") @@ -19,10 +21,17 @@ public class MyPageController implements MyPageApi { private final MyPageService myPageService; - @GetMapping + @GetMapping("/like-parts") public ResponseEntity> getMemberLikedKillingParts( - @Authenticated final MemberInfo memberInfo + @Authenticated final MemberInfo memberInfo ) { return ResponseEntity.ok(myPageService.findLikedKillingPartByMemberId(memberInfo)); } + + @GetMapping("/my-parts") + public ResponseEntity> getMyParts( + @Authenticated final MemberInfo memberInfo + ) { + return ResponseEntity.ok(myPageService.findMyPartByMemberId(memberInfo.getMemberId())); + } } diff --git a/backend/src/main/java/shook/shook/song/ui/SongController.java b/backend/src/main/java/shook/shook/song/ui/SongController.java new file mode 100644 index 000000000..1164ea056 --- /dev/null +++ b/backend/src/main/java/shook/shook/song/ui/SongController.java @@ -0,0 +1,29 @@ +package shook.shook.song.ui; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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.RestController; +import shook.shook.auth.ui.argumentresolver.Authenticated; +import shook.shook.auth.ui.argumentresolver.MemberInfo; +import shook.shook.song.application.SongService; +import shook.shook.song.application.dto.SongResponse; +import shook.shook.song.ui.openapi.SongApi; + +@RequiredArgsConstructor +@RequestMapping("/songs") +@RestController +public class SongController implements SongApi { + + private final SongService songService; + + @GetMapping("/{song_id}") + public ResponseEntity findSong( + @PathVariable("song_id") final Long songId, + @Authenticated final MemberInfo memberInfo + ) { + return ResponseEntity.ok(songService.findSongById(songId, memberInfo)); + } +} diff --git a/backend/src/main/java/shook/shook/song/ui/openapi/MyPageApi.java b/backend/src/main/java/shook/shook/song/ui/openapi/MyPageApi.java index b1f622f1f..3906c2dd2 100644 --- a/backend/src/main/java/shook/shook/song/ui/openapi/MyPageApi.java +++ b/backend/src/main/java/shook/shook/song/ui/openapi/MyPageApi.java @@ -3,26 +3,41 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import shook.shook.auth.ui.argumentresolver.Authenticated; import shook.shook.auth.ui.argumentresolver.MemberInfo; import shook.shook.song.application.dto.LikedKillingPartResponse; +import shook.shook.song.application.dto.MyPartsResponse; + +import java.util.List; @Tag(name = "MyPage", description = "마이페이지 API") public interface MyPageApi { @Operation( - summary = "마이페이지 좋아요 리스트 조회", - description = "좋아요한 킬링파트 리스트를 전체 조회한다." + summary = "마이페이지 좋아요 리스트 조회", + description = "좋아요한 킬링파트 리스트를 전체 조회한다." ) @ApiResponse( - responseCode = "200", - description = "좋아요한 킬링파트 리스트 조회 성공" + responseCode = "200", + description = "좋아요한 킬링파트 리스트 조회 성공" ) @GetMapping ResponseEntity> getMemberLikedKillingParts( - @Authenticated final MemberInfo memberInfo + @Authenticated final MemberInfo memberInfo + ); + + @Operation( + summary = "마이페이지 내 마이파트 리스트 조회", + description = "마이 파트로 등록한 킬링파트 리스트를 전체 조회한다." + ) + @ApiResponse( + responseCode = "200", + description = "마이파트 리스트 조회 성공" + ) + @GetMapping + public ResponseEntity> getMyParts( + @Authenticated final MemberInfo memberInfo ); } diff --git a/backend/src/main/java/shook/shook/song/ui/openapi/SongApi.java b/backend/src/main/java/shook/shook/song/ui/openapi/SongApi.java new file mode 100644 index 000000000..a0eb2c870 --- /dev/null +++ b/backend/src/main/java/shook/shook/song/ui/openapi/SongApi.java @@ -0,0 +1,44 @@ +package shook.shook.song.ui.openapi; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import shook.shook.auth.ui.argumentresolver.Authenticated; +import shook.shook.auth.ui.argumentresolver.MemberInfo; +import shook.shook.song.application.dto.SongResponse; + +@Tag(name = "Song API", description = "노래 단일 조회 API") +public interface SongApi { + + @Operation( + summary = "노래 단일 조회", + description = "노래 id 과 액세스 토큰을 받아 노래 하나를 조회한다." + ) + @ApiResponse( + responseCode = "200", + description = "노래 단일 조회 성공" + ) + @Parameters({ + @Parameter( + name = "songId", + description = "노래 id", + required = true + ), + @Parameter( + name = "memberInfo", + description = "회원 정보", + required = true, + hidden = true + ) + }) + @GetMapping("/{song_id}") + ResponseEntity findSong( + @PathVariable("song_id") final Long songId, + @Authenticated final MemberInfo memberInfo + ); +} diff --git a/backend/src/main/java/shook/shook/voting_song/application/VotingSongPartService.java b/backend/src/main/java/shook/shook/voting_song/application/VotingSongPartService.java index 574509f12..3ab0a9765 100644 --- a/backend/src/main/java/shook/shook/voting_song/application/VotingSongPartService.java +++ b/backend/src/main/java/shook/shook/voting_song/application/VotingSongPartService.java @@ -39,7 +39,7 @@ public boolean registerAndReturnMemberPartDuplication(final MemberInfo memberInf final VotingSong votingSong = findVotingSongThrowIfNotExist(votingSongId); final int startSecond = request.getStartSecond(); - final PartLength partLength = PartLength.findBySecond(request.getLength()); + final PartLength partLength = new PartLength(request.getLength()); final Optional findVotingSongPart = votingSongPartRepository.findByVotingSongAndStartSecondAndLength(votingSong, startSecond, partLength); @@ -88,7 +88,7 @@ private void voteToExistPart(final Member member, Map.of( "VotingSongId", String.valueOf(votingSong.getId()), "StartSecond", String.valueOf(votingSongPart.getStartSecond()), - "PartLength", votingSongPart.getLength().name() + "PartLength", String.valueOf(votingSongPart.getLength()) ) )); diff --git a/backend/src/main/java/shook/shook/voting_song/domain/VotingSongPart.java b/backend/src/main/java/shook/shook/voting_song/domain/VotingSongPart.java index 3efdc5460..e64da20f0 100644 --- a/backend/src/main/java/shook/shook/voting_song/domain/VotingSongPart.java +++ b/backend/src/main/java/shook/shook/voting_song/domain/VotingSongPart.java @@ -80,10 +80,18 @@ public static VotingSongPart saved( final PartLength length, final VotingSong votingSong ) { - validateStartSecond(startSecond, length, votingSong.getLength()); return new VotingSongPart(id, startSecond, length, votingSong); } + public static VotingSongPart forSave( + final int startSecond, + final PartLength length, + final VotingSong votingSong + ) { + validateStartSecond(startSecond, length, votingSong.getLength()); + return new VotingSongPart(null, startSecond, length, votingSong); + } + private static void validateStartSecond( final int startSecond, final PartLength partLength, @@ -109,15 +117,6 @@ private static void validateStartSecond( } } - public static VotingSongPart forSave( - final int startSecond, - final PartLength length, - final VotingSong votingSong - ) { - validateStartSecond(startSecond, length, votingSong.getLength()); - return new VotingSongPart(null, startSecond, length, votingSong); - } - public void vote(final Vote vote) { validateVote(vote); this.votes.add(vote); diff --git a/backend/src/main/java/shook/shook/voting_song/domain/VotingSongParts.java b/backend/src/main/java/shook/shook/voting_song/domain/VotingSongParts.java index 7781cc7fd..a41bfa264 100644 --- a/backend/src/main/java/shook/shook/voting_song/domain/VotingSongParts.java +++ b/backend/src/main/java/shook/shook/voting_song/domain/VotingSongParts.java @@ -29,7 +29,7 @@ private void validatePart(final VotingSongPart newVotingSongPart) { throw new PartException.DuplicateStartAndLengthException( Map.of( "StartSecond", String.valueOf(newVotingSongPart.getStartSecond()), - "PartLength", newVotingSongPart.getLength().name() + "PartLength", String.valueOf(newVotingSongPart.getLength()) ) ); } diff --git a/backend/src/main/java/shook/shook/voting_song/ui/VotingSongController.java b/backend/src/main/java/shook/shook/voting_song/ui/VotingSongController.java index f966c1804..11d83ab66 100644 --- a/backend/src/main/java/shook/shook/voting_song/ui/VotingSongController.java +++ b/backend/src/main/java/shook/shook/voting_song/ui/VotingSongController.java @@ -12,6 +12,7 @@ import shook.shook.voting_song.application.dto.VotingSongSwipeResponse; import shook.shook.voting_song.ui.openapi.VotingSongApi; +@Deprecated @RequiredArgsConstructor @RequestMapping("/voting-songs") @RestController diff --git a/backend/src/main/resources/schema.sql b/backend/src/main/resources/schema.sql index c787967ca..d53bf75db 100644 --- a/backend/src/main/resources/schema.sql +++ b/backend/src/main/resources/schema.sql @@ -12,11 +12,12 @@ create table if not exists song create table if not exists killing_part ( - id bigint auto_increment, - start_second integer not null, - length varchar(255) not null check (length in ('SHORT', 'STANDARD', 'LONG')), - song_id bigint not null, - created_at timestamp(6) not null, + id bigint auto_increment, + start_second integer not null, + legacy_length varchar(255) not null check (legacy_length in ('SHORT', 'STANDARD', 'LONG')), + length integer not null, + song_id bigint not null, + created_at timestamp(6) not null, primary key (id) ); @@ -55,7 +56,7 @@ create table if not exists voting_song_part ( id bigint auto_increment, start_second integer not null, - length varchar(255) not null check (length in ('SHORT', 'STANDARD', 'LONG')), + length integer not null, voting_song_id bigint not null, created_at timestamp(6) not null, primary key (id) @@ -76,6 +77,17 @@ create table if not exists member primary key (id) ); +create table if not exists member_part +( + id bigint auto_increment, + start_second integer not null, + length integer not null, + song_id bigint not null, + member_id bigint not null, + created_at timestamp(6) not null, + primary key (id) +); + alter table killing_part add column like_count integer not null; alter table killing_part_comment @@ -94,3 +106,19 @@ alter table song 'FOLK_BLUES', 'POP', 'JAZZ', 'CLASSIC', 'J_POP', 'EDM', 'ETC')); alter table vote add column member_id bigint not null; +/* 배포 시 임시 열을 추가한 뒤에, rename 한다. +alter table killing_part + add column temp_length integer; +update killing_part +set temp_length = CASE + WHEN length = 'SHORT' THEN 5 + WHEN length = 'STANDARD' THEN 10 + WHEN length = 'LONG' THEN 15 + END; +alter table killing_part + modify temp_length integer not null; +alter table killing_part + change column length legacy_length varchar(255) null; +alter table killing_part + change column temp_length length integer not null; +*/ diff --git a/backend/src/main/resources/shook-security b/backend/src/main/resources/shook-security index e721f503b..692dffc9d 160000 --- a/backend/src/main/resources/shook-security +++ b/backend/src/main/resources/shook-security @@ -1 +1 @@ -Subproject commit e721f503bfdb5990582baa440869e3b936849ba6 +Subproject commit 692dffc9d85ae8f911040f6714948d28091488e0 diff --git a/backend/src/test/java/shook/shook/member_part/application/MemberPartServiceTest.java b/backend/src/test/java/shook/shook/member_part/application/MemberPartServiceTest.java new file mode 100644 index 000000000..0d6090e17 --- /dev/null +++ b/backend/src/test/java/shook/shook/member_part/application/MemberPartServiceTest.java @@ -0,0 +1,95 @@ +package shook.shook.member_part.application; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.jdbc.Sql; +import shook.shook.auth.exception.AuthorizationException; +import shook.shook.member.domain.Member; +import shook.shook.member.domain.repository.MemberRepository; +import shook.shook.member.exception.MemberException; +import shook.shook.member_part.application.dto.MemberPartRegisterRequest; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.member_part.domain.repository.MemberPartRepository; +import shook.shook.song.domain.Song; +import shook.shook.song.domain.repository.SongRepository; +import shook.shook.song.exception.SongException; +import shook.shook.support.UsingJpaTest; + +@Sql("classpath:/killingpart/initialize_killing_part_song.sql") +class MemberPartServiceTest extends UsingJpaTest { + + private MemberPartService memberPartService; + + @Autowired + private SongRepository songRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private MemberPartRepository memberPartRepository; + + @BeforeEach + void setUp() { + memberPartService = new MemberPartService(songRepository, memberRepository, memberPartRepository); + } + + @DisplayName("존재하지 않는 노래에 멤버 파트를 등록하면 예외가 발생한다.") + @Test + void register_failNotExistSong() { + // given + final MemberPartRegisterRequest request = new MemberPartRegisterRequest(5, 5); + final Long notExistSongId = 0L; + + // when + // then + assertThatThrownBy(() -> memberPartService.register(notExistSongId, 1L, request)) + .isInstanceOf(SongException.SongNotExistException.class); + } + + @DisplayName("존재하지 않는 멤버로 멤버 파트를 등록하면 예외가 발생한다.") + @Test + void register_failNotExist() { + // given + final MemberPartRegisterRequest request = new MemberPartRegisterRequest(5, 5); + final Long notExistMemberId = 0L; + + // when + // then + assertThatThrownBy(() -> memberPartService.register(1L, notExistMemberId, request)) + .isInstanceOf(MemberException.MemberNotExistException.class); + } + + @DisplayName("해당 노래에 이미 멤버 파트가 존재하면 예외가 발생한다.") + @Test + void register_failAlreadyExistMemberPart() { + // given + final long songId = 1L; + final long memberId = 1L; + memberPartService.register(songId, memberId, new MemberPartRegisterRequest(5, 5)); + + // when + // then + assertThatThrownBy(() -> memberPartService.register(songId, memberId, new MemberPartRegisterRequest(10, 10))) + .isInstanceOf(MemberException.MemberPartAlreadyExistException.class); + } + + @DisplayName("존재하지 않는 멤버로 멤버 파트를 삭제하면 예외가 발생한다.") + @Test + void delete_failUnauthenticatedMember() { + // given + final Song song = songRepository.findById(1L).get(); + final Member member = memberRepository.findById(1L).get(); + final MemberPart memberPart = memberPartRepository.save(MemberPart.forSave(20, 10, song, member)); + final Long notExistMemberId = 0L; + + // when + // then + assertThatThrownBy(() -> memberPartService.delete(notExistMemberId, memberPart.getId())) + .isInstanceOf(AuthorizationException.UnauthenticatedException.class); + } +} diff --git a/backend/src/test/java/shook/shook/member_part/domain/MemberPartTest.java b/backend/src/test/java/shook/shook/member_part/domain/MemberPartTest.java new file mode 100644 index 000000000..4b5cb8264 --- /dev/null +++ b/backend/src/test/java/shook/shook/member_part/domain/MemberPartTest.java @@ -0,0 +1,58 @@ +package shook.shook.member_part.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import shook.shook.member.domain.Member; +import shook.shook.member_part.exception.MemberPartException; +import shook.shook.song.domain.Genre; +import shook.shook.song.domain.KillingParts; +import shook.shook.song.domain.Song; +import shook.shook.song.domain.killingpart.KillingPart; + +class MemberPartTest { + + private static Song SONG; + private static Member MEMBER; + + @BeforeEach + void setUp() { + final KillingParts killingParts = new KillingParts( + List.of( + KillingPart.forSave(1, 10), + KillingPart.forSave(1, 10), + KillingPart.forSave(1, 10) + ) + ); + + SONG = new Song("title", "12345678901", "albumCover", "singer", 300, Genre.DANCE, killingParts); + MEMBER = new Member("shook@email.com", "shook"); + } + + @DisplayName("멤버 파트의 시작 시간이 음수이면 예외가 발생한다") + @Test + void create_failNegativeStartSecond() { + // given + final int negativeStartSecond = -1; + + // when + // then + assertThatThrownBy(() -> MemberPart.forSave(negativeStartSecond, 10, SONG, MEMBER)) + .isInstanceOf(MemberPartException.MemberPartStartSecondNegativeException.class); + } + + @DisplayName("멤버 파트의 끝 초가 노래 길이보다 길면 예외가 발생한다") + @Test + void create_failEndSecondOverSongLength() { + // given + final int longLength = 15; + + // when + // then + assertThatThrownBy(() -> MemberPart.forSave(340, longLength, SONG, MEMBER)) + .isInstanceOf(MemberPartException.MemberPartEndOverSongLengthException.class); + } +} diff --git a/backend/src/test/java/shook/shook/member_part/domain/repository/MemberPartRepositoryTest.java b/backend/src/test/java/shook/shook/member_part/domain/repository/MemberPartRepositoryTest.java new file mode 100644 index 000000000..1c8ed572b --- /dev/null +++ b/backend/src/test/java/shook/shook/member_part/domain/repository/MemberPartRepositoryTest.java @@ -0,0 +1,125 @@ +package shook.shook.member_part.domain.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.jdbc.Sql; +import shook.shook.member.domain.Member; +import shook.shook.member.domain.repository.MemberRepository; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.member_part.domain.repository.dto.SongMemberPartCreatedAtDto; +import shook.shook.song.domain.Genre; +import shook.shook.song.domain.KillingParts; +import shook.shook.song.domain.Song; +import shook.shook.song.domain.killingpart.KillingPart; +import shook.shook.song.domain.repository.SongRepository; +import shook.shook.support.UsingJpaTest; + +@Sql(scripts = "classpath:killingpart/initialize_killing_part_song.sql") +class MemberPartRepositoryTest extends UsingJpaTest { + + @Autowired + private MemberPartRepository memberPartRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private SongRepository songRepository; + + @DisplayName("멤버 아이디와 멤버 파트 아이디로 멤버 파트를 조회한다.") + @Test + void findByMemberIdAndId() { + // given + final Member member = createAndSaveMember("email", "name"); + final Song song = createNewSongWithKillingPartsAndSaveSong(); + final MemberPart memberPart = createAndSaveMemberPart(MemberPart.forSave(10, 5, song, member)); + saveAndClearEntityManager(); + + // when + final Optional optionalMember = memberPartRepository.findByMemberIdAndId(member.getId(), + memberPart.getId()); + final MemberPart savedMemberPart = optionalMember.get(); + + // then + assertThat(optionalMember).isPresent(); + assertThat(savedMemberPart.getMember().getId()).isEqualTo(member.getId()); + assertThat(savedMemberPart.getSong().getId()).isEqualTo(song.getId()); + } + + private Song createNewSongWithKillingPartsAndSaveSong() { + final KillingPart firstKillingPart = KillingPart.forSave(10, 5); + final KillingPart secondKillingPart = KillingPart.forSave(15, 5); + final KillingPart thirdKillingPart = KillingPart.forSave(20, 5); + + final Song song = new Song( + "제목", "비디오ID는 11글자", "이미지URL", "가수", 180, Genre.from("댄스"), + new KillingParts(List.of(firstKillingPart, secondKillingPart, thirdKillingPart))); + + return songRepository.save(song); + } + + private Member createAndSaveMember(final String email, final String name) { + final Member member = new Member(email, name); + return memberRepository.save(member); + } + + private MemberPart createAndSaveMemberPart(final MemberPart memberPart) { + return memberPartRepository.save(memberPart); + } + + @DisplayName("해당하는 song Id 리스트를 입력 받아 멤버 파트 리스트를 조회한다.") + @Test + void findBySongIdIn() { + // given + final Song firstSong = createNewSongWithKillingPartsAndSaveSong(); + final Song secondSong = createNewSongWithKillingPartsAndSaveSong(); + final Song thirdSong = createNewSongWithKillingPartsAndSaveSong(); + final Song fourthSong = createNewSongWithKillingPartsAndSaveSong(); + final Member member = createAndSaveMember("email", "name"); + + createAndSaveMemberPart(MemberPart.forSave(10, 5, firstSong, member)); + createAndSaveMemberPart(MemberPart.forSave(10, 5, secondSong, member)); + createAndSaveMemberPart(MemberPart.forSave(10, 5, thirdSong, member)); + + saveAndClearEntityManager(); + + // when + final List memberParts = memberPartRepository.findByMemberAndSongIdIn(member, + List.of(firstSong.getId(), + secondSong.getId(), + thirdSong.getId())); + + // then + assertThat(memberParts).hasSize(3); + assertThat(memberParts.stream() + .map(MemberPart::getSong) + .map(Song::getId) + .toList()).contains(firstSong.getId(), secondSong.getId(), thirdSong.getId()); + } + + @DisplayName("나의 파트를 조회한다.") + @Test + void find_myPart() { + // given + final Song savedSong = songRepository.findById(1L).get(); + + final Member member = new Member("shook@s.com", "shook1"); + final Member savedMember = memberRepository.save(member); + + final MemberPart memberPart = MemberPart.forSave(10, 15, savedSong, savedMember); + final MemberPart savedMemberPart = memberPartRepository.save(memberPart); + + // when + final List result = memberPartRepository.findByMemberId(savedMember.getId()); + + // then + assertThat(result.size()).isOne(); + assertThat(result.get(0).getSong()).isEqualTo(savedSong); + assertThat(result.get(0).getMemberPart()).isEqualTo(savedMemberPart); + } +} diff --git a/backend/src/test/java/shook/shook/member_part/ui/MemberPartControllerTest.java b/backend/src/test/java/shook/shook/member_part/ui/MemberPartControllerTest.java new file mode 100644 index 000000000..1bb8428fe --- /dev/null +++ b/backend/src/test/java/shook/shook/member_part/ui/MemberPartControllerTest.java @@ -0,0 +1,124 @@ +package shook.shook.member_part.ui; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.jdbc.Sql; +import shook.shook.auth.application.TokenProvider; +import shook.shook.member.domain.Member; +import shook.shook.member.domain.repository.MemberRepository; +import shook.shook.member_part.application.dto.MemberPartRegisterRequest; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.member_part.domain.repository.MemberPartRepository; +import shook.shook.song.domain.Song; +import shook.shook.song.domain.repository.SongRepository; + +@Sql("classpath:/killingpart/initialize_killing_part_song.sql") +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +class MemberPartControllerTest { + + @LocalServerPort + private int port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Autowired + private TokenProvider tokenProvider; + + @Autowired + private SongRepository songRepository; + + @Autowired + private MemberPartRepository memberPartRepository; + + @Autowired + private MemberRepository memberRepository; + + @DisplayName("멤버의 파트를 등록 성공 시 201 상태 코드를 반환한다.") + @Test + void registerMemberPart() { + // given + final Song song = songRepository.findById(1L).get(); + final Member member = memberRepository.findById(1L).get(); + final MemberPartRegisterRequest request = new MemberPartRegisterRequest(5, 10); + final String accessToken = tokenProvider.createAccessToken(member.getId(), member.getNickname()); + + // when + // then + RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .body(request) + .contentType(ContentType.JSON) + .when().log().all().post("/songs/{songId}/member-parts", song.getId()) + .then().statusCode(HttpStatus.CREATED.value()); + } + + @DisplayName("잘못된 정보로 파트를 등록 시 400 상태 코드를 반환한다.") + @CsvSource({"-1, 10", "190, 15", "5, 25"}) + @ParameterizedTest + void register_failBadRequest(final int startSecond, final int length) { + // given + final Song song = songRepository.findById(1L).get(); + final Member member = memberRepository.findById(1L).get(); + final MemberPartRegisterRequest request = new MemberPartRegisterRequest(startSecond, length); + final String accessToken = tokenProvider.createAccessToken(member.getId(), member.getNickname()); + + // when + // then + RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .body(request) + .contentType(ContentType.JSON) + .when().log().all().post("/songs/{songId}/member-parts", song.getId()) + .then().statusCode(HttpStatus.BAD_REQUEST.value()); + } + + @DisplayName("멤버 파트 삭제 성공 시 204 상태 코드를 반환한다.") + @Test + void delete() { + // given + final Song song = songRepository.findById(1L).get(); + final Member member = memberRepository.findById(1L).get(); + final String accessToken = tokenProvider.createAccessToken(member.getId(), member.getNickname()); + final MemberPart memberPart = memberPartRepository.save(MemberPart.forSave(5, 10, song, member)); + + // when + // then + RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .when().log().all().delete("/member-parts/{memberPartId}", memberPart.getId()) + .then().statusCode(HttpStatus.NO_CONTENT.value()); + } + + @DisplayName("자신의 파트가 아닌 파트를 삭제하려고 시도할 시 403 상태 코드를 반환한다.") + @Test + void delete_failUnauthorizedMember() { + // given + final Song song = songRepository.findById(1L).get(); + final Member member = memberRepository.findById(1L).get(); + final MemberPart memberPart = memberPartRepository.save(MemberPart.forSave(5, 10, song, member)); + final Member otherMember = memberRepository.save(new Member("other@email.com", "nickname")); + final String otherMemberAccessToken = tokenProvider.createAccessToken(otherMember.getId(), + otherMember.getNickname()); + + // when + // then + RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + otherMemberAccessToken) + .when().log().all().delete("/member-parts/{memberPartId}", memberPart.getId()) + .then().statusCode(HttpStatus.FORBIDDEN.value()); + } +} diff --git a/backend/src/test/java/shook/shook/part/domain/PartLengthTest.java b/backend/src/test/java/shook/shook/part/domain/PartLengthTest.java index 57d4a3fc8..a6a459b68 100644 --- a/backend/src/test/java/shook/shook/part/domain/PartLengthTest.java +++ b/backend/src/test/java/shook/shook/part/domain/PartLengthTest.java @@ -11,37 +11,27 @@ class PartLengthTest { - @DisplayName("초에 해당하는 PartLength 를 반환한다. (유효한 초일 때)") - @ParameterizedTest(name = "초가 {0}일 때 {1}를 반환한다.") - @CsvSource(value = {"5:SHORT", "10:STANDARD", "15:LONG"}, delimiter = ':') - void findBySecond_exist(final int second, final PartLength partLength) { - //given - //when - final PartLength expected = PartLength.findBySecond(second); - - //then - assertThat(expected).isEqualTo(partLength); - assertThat(expected.getValue()).isEqualTo(second); - } - @DisplayName("초에 해당하는 PartLength 를 반환한다. (유효하지 않은 초일 때 )") @ParameterizedTest(name = "초가 {0}일 때 예외를 던진다.") - @ValueSource(ints = {-1, 0, 4, 6, 9, 11, 14, 16}) + @ValueSource(ints = {-1, 0, 4, 16}) void findBySecond_invalidSecond(final int second) { //given //when //then - assertThatThrownBy(() -> PartLength.findBySecond(second)) + assertThatThrownBy(() -> new PartLength(second)) .isInstanceOf(PartException.InvalidLengthException.class); } @DisplayName("시작초를 받아 끝초를 반환한다. (유효한 초일 때)") @ParameterizedTest(name = "PartLength가 {0}일 때 시작이 {1}이면 {2}를 반환한다.") - @CsvSource(value = {"SHORT:1:6", "STANDARD:1:11", "LONG:1:16"}, delimiter = ':') - void getEndSecond(final PartLength partLength, final int start, final int expectedEnd) { + @CsvSource(value = {"5:1:6", "10:1:11", "15:1:16"}, delimiter = ':') + void getEndSecond(final int length, final int start, final int expectedEnd) { //given + final PartLength partLength = new PartLength(length); + //when final int endSecond = partLength.getEndSecond(start); + //then assertThat(expectedEnd).isEqualTo(endSecond); } diff --git a/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java b/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java index 30cbcbd1c..2a27104c1 100644 --- a/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java +++ b/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java @@ -1,8 +1,5 @@ package shook.shook.song.application; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Collections; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -11,6 +8,10 @@ import org.springframework.test.context.jdbc.Sql; import shook.shook.song.domain.InMemorySongs; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + @Sql(value = "classpath:/killingpart/initialize_killing_part_song.sql") @EnableScheduling @SpringBootTest diff --git a/backend/src/test/java/shook/shook/song/application/MyPageServiceTest.java b/backend/src/test/java/shook/shook/song/application/MyPageServiceTest.java index 244c1b921..2918afb21 100644 --- a/backend/src/test/java/shook/shook/song/application/MyPageServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/MyPageServiceTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -13,11 +14,16 @@ import shook.shook.auth.ui.argumentresolver.MemberInfo; import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.member_part.domain.repository.MemberPartRepository; import shook.shook.song.application.dto.LikedKillingPartResponse; +import shook.shook.song.application.dto.MyPartsResponse; +import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.KillingPartLike; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; import shook.shook.song.domain.killingpart.repository.KillingPartRepository; +import shook.shook.song.domain.repository.SongRepository; import shook.shook.support.UsingJpaTest; @Sql(scripts = "classpath:killingpart/initialize_killing_part_song.sql") @@ -32,11 +38,17 @@ class MyPageServiceTest extends UsingJpaTest { @Autowired private KillingPartLikeRepository killingPartLikeRepository; + @Autowired + private MemberPartRepository memberPartRepository; + + @Autowired + private SongRepository songRepository; + private MyPageService myPageService; @BeforeEach void setUp() { - myPageService = new MyPageService(killingPartLikeRepository, memberRepository); + myPageService = new MyPageService(killingPartLikeRepository, memberRepository, memberPartRepository); } @DisplayName("멤버가 좋아요한 모든 킬링파트에 대한 정보를 조회한다.") @@ -67,4 +79,36 @@ void findLikedKillingPartByMemberId() { } ); } + + @DisplayName("멤버가 노래마다 등록한 자신의 파트정보를 조회한다.") + @Test + void findMemberPartByMemberId() { + // given + final Member member = memberRepository.findById(1L).get(); + final Song song1 = songRepository.findById(1L).get(); + final Song song2 = songRepository.findById(2L).get(); + + final MemberPart memberPart1 = MemberPart.forSave(10, 15, song1, member); + final MemberPart memberPart2 = MemberPart.forSave(30, 10, song2, member); + memberPartRepository.save(memberPart1); + memberPartRepository.save(memberPart2); + + saveAndClearEntityManager(); + + // when + final List result = myPageService.findMyPartByMemberId(member.getId()); + + // then + final List expect = List.of( + MyPartsResponse.of(song2, memberPart2), + MyPartsResponse.of(song1, memberPart1)); + + assertAll( + () -> assertThat(result).hasSize(2), + () -> assertThat(result).usingRecursiveComparison() + .ignoringFieldsOfTypes(LocalDateTime.class) + .isEqualTo(expect) + ); + + } } diff --git a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java index ffb5a9159..dd74f0835 100644 --- a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java @@ -15,7 +15,10 @@ import shook.shook.auth.ui.argumentresolver.MemberInfo; import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.member_part.domain.repository.MemberPartRepository; import shook.shook.song.application.dto.KillingPartRegisterRequest; +import shook.shook.song.application.dto.MemberPartResponse; import shook.shook.song.application.dto.SongResponse; import shook.shook.song.application.dto.SongSwipeResponse; import shook.shook.song.application.dto.SongWithKillingPartsRegisterRequest; @@ -45,6 +48,9 @@ class SongServiceTest extends UsingJpaTest { @Autowired private MemberRepository memberRepository; + @Autowired + private MemberPartRepository memberPartRepository; + private final InMemorySongs inMemorySongs = new InMemorySongs(); private SongService songService; @@ -56,6 +62,7 @@ public void setUp() { killingPartRepository, likeRepository, memberRepository, + memberPartRepository, inMemorySongs, new SongDataExcelReader(" ", " ", " ") ); @@ -100,12 +107,13 @@ void findById_exist_login_member() { final Song song = registerNewSong("title"); addLikeToEachKillingParts(song, member); inMemorySongs.recreate(songRepository.findAllWithKillingParts()); + addMemberPartToSong(10, 5, song, member); //when saveAndClearEntityManager(); final SongSwipeResponse response = songService.findSongByIdForFirstSwipe(song.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); //then assertAll( @@ -113,24 +121,30 @@ void findById_exist_login_member() { () -> assertThat(response.getNextSongs()).isEmpty(), () -> assertThat(response.getCurrentSong().getKillingParts().get(0)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(0).getId()) + song.getLikeCountSortedKillingParts().get(0).getId()) .hasFieldOrPropertyWithValue("rank", 1) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getCurrentSong().getKillingParts().get(1)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(1).getId()) + song.getLikeCountSortedKillingParts().get(1).getId()) .hasFieldOrPropertyWithValue("rank", 2) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getCurrentSong().getKillingParts().get(2)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(2).getId()) + song.getLikeCountSortedKillingParts().get(2).getId()) .hasFieldOrPropertyWithValue("rank", 3) - .hasFieldOrPropertyWithValue("likeStatus", true) + .hasFieldOrPropertyWithValue("likeStatus", true), + () -> assertThat(response.getCurrentSong().getMemberPart().getId()).isNotNull() ); } + private MemberPart addMemberPartToSong(final int startSecond, final int length, final Song song, + final Member member) { + return memberPartRepository.save(MemberPart.forSave(startSecond, length, song, member)); + } + @DisplayName("로그인 되지 않은 사용자의 Id, 노래의 Id 로 존재하는 노래를 검색한다.") @Test void findById_exist_not_login_member() { @@ -142,7 +156,7 @@ void findById_exist_not_login_member() { saveAndClearEntityManager(); final SongSwipeResponse response = songService.findSongByIdForFirstSwipe(song.getId(), - new MemberInfo(0L, Authority.ANONYMOUS)); + new MemberInfo(0L, Authority.ANONYMOUS)); //then assertAll( @@ -150,21 +164,22 @@ void findById_exist_not_login_member() { () -> assertThat(response.getNextSongs()).isEmpty(), () -> assertThat(response.getCurrentSong().getKillingParts().get(0)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(0).getId()) + song.getLikeCountSortedKillingParts().get(0).getId()) .hasFieldOrPropertyWithValue("rank", 1) .hasFieldOrPropertyWithValue("likeStatus", false), () -> assertThat(response.getCurrentSong().getKillingParts().get(1)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(1).getId()) + song.getLikeCountSortedKillingParts().get(1).getId()) .hasFieldOrPropertyWithValue("rank", 2) .hasFieldOrPropertyWithValue("likeStatus", false), () -> assertThat(response.getCurrentSong().getKillingParts().get(2)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(2).getId()) + song.getLikeCountSortedKillingParts().get(2).getId()) .hasFieldOrPropertyWithValue("rank", 3) - .hasFieldOrPropertyWithValue("likeStatus", false) + .hasFieldOrPropertyWithValue("likeStatus", false), + () -> assertThat(response.getCurrentSong().getMemberPart()).isNull() ); } @@ -177,9 +192,9 @@ void findById_notExist() { //when //then assertThatThrownBy(() -> songService.findSongByIdForFirstSwipe( - 0L, - new MemberInfo(member.getId(), Authority.MEMBER) - ) + 0L, + new MemberInfo(member.getId(), Authority.MEMBER) + ) ).isInstanceOf(SongException.SongNotExistException.class); } @@ -268,16 +283,23 @@ void firstFindByMember() { final Member member = createAndSaveMember("first@naver.com", "first"); + // 4, 3, 5, 2, 1 addLikeToEachKillingParts(thirdSong, member); addLikeToEachKillingParts(fourthSong, member); inMemorySongs.recreate(songRepository.findAllWithKillingParts()); + // 1, 2, 3 노래에 memberPart 추가 + addMemberPartToSong(10, 5, firstSong, member); + addMemberPartToSong(10, 5, secondSong, member); + addMemberPartToSong(10, 5, thirdSong, member); + addMemberPartToSong(10, 5, fourthSong, member); + saveAndClearEntityManager(); // when final SongSwipeResponse result = songService.findSongByIdForFirstSwipe(fifthSong.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertAll( @@ -285,11 +307,22 @@ void firstFindByMember() { () -> assertThat(result.getPrevSongs()).hasSize(2), () -> assertThat(result.getNextSongs()).hasSize(2), () -> assertThat(result.getPrevSongs().stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(4L, 3L)), + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(4L, 3L)), () -> assertThat(result.getNextSongs().stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 1L)) + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 1L)), + () -> assertThat(result.getCurrentSong().getMemberPart()).isNull(), + () -> assertThat(result.getPrevSongs().stream() + .map(songResponse -> songResponse.getMemberPart().getId()) + .toList()) + .usingRecursiveComparison() + .isEqualTo(List.of(4L, 3L)), + () -> assertThat(result.getNextSongs().stream() + .map(songResponse -> songResponse.getMemberPart().getId()) + .toList()) + .usingRecursiveComparison() + .isEqualTo(List.of(2L, 1L)) ); } @@ -304,15 +337,15 @@ void firstFindByAnonymous() { // then assertThatThrownBy( () -> songService.findSongByIdForFirstSwipe(notExistSongId, - new MemberInfo(member.getId(), Authority.MEMBER))) + new MemberInfo(member.getId(), Authority.MEMBER))) .isInstanceOf(SongException.SongNotExistException.class); assertThatThrownBy( () -> songService.findSongByIdForBeforeSwipe(notExistSongId, - new MemberInfo(member.getId(), Authority.MEMBER))) + new MemberInfo(member.getId(), Authority.MEMBER))) .isInstanceOf(SongException.SongNotExistException.class); assertThatThrownBy( () -> songService.findSongByIdForAfterSwipe(notExistSongId, - new MemberInfo(member.getId(), Authority.MEMBER))) + new MemberInfo(member.getId(), Authority.MEMBER))) .isInstanceOf(SongException.SongNotExistException.class); } @@ -335,19 +368,29 @@ void findSongByIdForBeforeSwipe() { addLikeToEachKillingParts(firstSong, member2); inMemorySongs.recreate(songRepository.findAllWithKillingParts()); - // 정렬 순서: 2L, 4L, 1L, 5L, 3L + addMemberPartToSong(10, 5, firstSong, member); + addMemberPartToSong(10, 5, secondSong, member); + addMemberPartToSong(10, 5, standardSong, member); + addMemberPartToSong(10, 5, fourthSong, member); + addMemberPartToSong(10, 5, fifthSong, member); + + // 정렬 순서: 2L, 4L, 1L, 5L, 3L saveAndClearEntityManager(); // when final List beforeResponses = songService.findSongByIdForBeforeSwipe(standardSong.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertThat(beforeResponses.stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); + assertThat(beforeResponses.stream() + .map(SongResponse::getMemberPart) + .map(MemberPartResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); } @DisplayName("이후 노래를 1. 좋아요 순 내림차순, 2. id 내림차순으로 조회한다.") @@ -369,6 +412,12 @@ void findSongByIdForAfterSwipe() { addLikeToEachKillingParts(firstSong, member2); inMemorySongs.recreate(songRepository.findAllWithKillingParts()); + addMemberPartToSong(10, 5, firstSong, member); + addMemberPartToSong(10, 5, secondSong, member); + addMemberPartToSong(10, 5, thirdSong, member); + addMemberPartToSong(10, 5, standardSong, member); + addMemberPartToSong(10, 5, fifthSong, member); + // 정렬 순서: 2L, 4L, 1L, 5L, 3L saveAndClearEntityManager(); @@ -376,12 +425,16 @@ void findSongByIdForAfterSwipe() { // when final List afterResponses = songService.findSongByIdForAfterSwipe(standardSong.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertThat(afterResponses.stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); + assertThat(afterResponses.stream() + .map(SongResponse::getMemberPart) + .map(MemberPartResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); } } @@ -420,9 +473,52 @@ void findSongsByGenre() { assertAll( () -> assertThat(response).hasSize(5), () -> assertThat(response.stream() - .map(HighLikedSongResponse::getId).toList()) + .map(HighLikedSongResponse::getId).toList()) .containsExactly(2L, 1L, 3L, 5L, 4L) ); } } + + @DisplayName("노래 id 로 노래 하나를 조회한다.") + @Test + void findSongById() { + // given + final Song song = registerNewSong("title"); + final Member member = createAndSaveMember("email@email.com", "nickname"); + addLikeToEachKillingParts(song, member); + inMemorySongs.recreate(songRepository.findAllWithKillingParts()); + addMemberPartToSong(10, 5, song, member); + saveAndClearEntityManager(); + + // when + final SongResponse response = songService.findSongById(song.getId(), + new MemberInfo(member.getId(), Authority.MEMBER)); + + // then + assertAll( + () -> assertThat(response.getId()).isEqualTo(song.getId()), + () -> assertThat(response.getTitle()).isEqualTo(song.getTitle()), + () -> assertThat(response.getAlbumCoverUrl()).isEqualTo(song.getAlbumCoverUrl()), + () -> assertThat(response.getSinger()).isEqualTo(song.getSinger()), + () -> assertThat(response.getKillingParts()).hasSize(3), + () -> assertThat(response.getKillingParts().get(0)) + .hasFieldOrPropertyWithValue("id", + song.getLikeCountSortedKillingParts().get(0).getId()) + .hasFieldOrPropertyWithValue("rank", 1) + .hasFieldOrPropertyWithValue("likeStatus", true), + + () -> assertThat(response.getKillingParts().get(1)) + .hasFieldOrPropertyWithValue("id", + song.getLikeCountSortedKillingParts().get(1).getId()) + .hasFieldOrPropertyWithValue("rank", 2) + .hasFieldOrPropertyWithValue("likeStatus", true), + + () -> assertThat(response.getKillingParts().get(2)) + .hasFieldOrPropertyWithValue("id", + song.getLikeCountSortedKillingParts().get(2).getId()) + .hasFieldOrPropertyWithValue("rank", 3) + .hasFieldOrPropertyWithValue("likeStatus", true), + () -> assertThat(response.getMemberPart().getId()).isNotNull() + ); + } } diff --git a/backend/src/test/java/shook/shook/song/domain/SongTest.java b/backend/src/test/java/shook/shook/song/domain/SongTest.java index afa61816d..44f4f3e75 100644 --- a/backend/src/test/java/shook/shook/song/domain/SongTest.java +++ b/backend/src/test/java/shook/shook/song/domain/SongTest.java @@ -6,7 +6,6 @@ import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.exception.killingpart.KillingPartsException; @@ -26,15 +25,15 @@ void songCreate_nullKillingParts_fail() { @Test void getPartVideoUrl() { // given - final KillingPart killingPart1 = KillingPart.forSave(10, PartLength.STANDARD); - final KillingPart killingPart2 = KillingPart.forSave(20, PartLength.STANDARD); - final KillingPart killingPart3 = KillingPart.forSave(30, PartLength.STANDARD); + final KillingPart killingPart1 = KillingPart.forSave(10, 10); + final KillingPart killingPart2 = KillingPart.forSave(20, 10); + final KillingPart killingPart3 = KillingPart.forSave(30, 10); final KillingParts killingParts = new KillingParts( List.of(killingPart1, killingPart2, killingPart3) ); final Song song = new Song("title", "3rUPND6FG8A", "image_url", "singer", 230, - Genre.from("댄스"), - killingParts); + Genre.from("댄스"), + killingParts); // when final String killingPart1VideoUrl = song.getPartVideoUrl(killingPart1); diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartCommentTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartCommentTest.java index a17c89bb0..fdd056e40 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartCommentTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartCommentTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import shook.shook.member.domain.Member; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.Song; class KillingPartCommentTest { @@ -14,9 +13,9 @@ class KillingPartCommentTest { private static final Song EMPTY_SONG = null; private static final Member MEMBER = new Member("email@naver.com", "nickname"); private static final KillingPart FIRST_KILLING_PART = - KillingPart.saved(1L, 4, PartLength.SHORT, EMPTY_SONG); + KillingPart.saved(1L, 4, 5, EMPTY_SONG); private static final KillingPart SECOND_KILLING_PART = - KillingPart.saved(2L, 10, PartLength.SHORT, EMPTY_SONG); + KillingPart.saved(2L, 10, 5, EMPTY_SONG); @DisplayName("새로운 댓글을 생성한다.") @Test @@ -41,7 +40,7 @@ void create_exist() { void getContent() { //given final KillingPartComment killingPartComment = KillingPartComment.forSave(FIRST_KILLING_PART, - "댓글 내용", MEMBER); + "댓글 내용", MEMBER); //when final String content = killingPartComment.getContent(); @@ -55,7 +54,7 @@ void getContent() { void isBelongToOtherPart_samePart() { //given final KillingPartComment killingPartComment = KillingPartComment.forSave(FIRST_KILLING_PART, - "댓글 내용", MEMBER); + "댓글 내용", MEMBER); //when final boolean isBelongTo = killingPartComment.isBelongToOtherKillingPart( @@ -70,7 +69,7 @@ void isBelongToOtherPart_samePart() { void isBelongToOtherPart_otherPart() { //given final KillingPartComment partComment = KillingPartComment.forSave(FIRST_KILLING_PART, - "댓글 내용", MEMBER); + "댓글 내용", MEMBER); //when final boolean isBelongTo = partComment.isBelongToOtherKillingPart(SECOND_KILLING_PART); diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartCommentsTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartCommentsTest.java index 0fa5c5ae1..11867319d 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartCommentsTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartCommentsTest.java @@ -7,7 +7,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import shook.shook.member.domain.Member; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.Song; import shook.shook.song.exception.killingpart.KillingPartCommentException; @@ -20,7 +19,7 @@ class KillingPartCommentsTest { @Test void addComment_success() { //given - final KillingPart killingPart = KillingPart.saved(1L, 5, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 5, 5, EMPTY_SONG); final KillingPartComments comments = new KillingPartComments(); @@ -35,7 +34,7 @@ void addComment_success() { @Test void addComment_exist_fail() { //given - final KillingPart killingPart = KillingPart.saved(1L, 5, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 5, 5, EMPTY_SONG); final KillingPartComments partComments = new KillingPartComments(); //when @@ -52,13 +51,13 @@ void addComment_exist_fail() { @Test void getCommentsInRecentOrder() { //given - final KillingPart killingPart = KillingPart.saved(1L, 5, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 5, 5, EMPTY_SONG); final KillingPartComments comments = new KillingPartComments(); final KillingPartComment early = KillingPartComment.saved(1L, killingPart, "댓글입니다.", - MEMBER); + MEMBER); final KillingPartComment late = KillingPartComment.saved(2L, killingPart, "댓글이였습니다.", - MEMBER); + MEMBER); comments.addComment(late); comments.addComment(early); diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikeTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikeTest.java index 63b3692c7..1a21803d7 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikeTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikeTest.java @@ -5,15 +5,14 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import shook.shook.member.domain.Member; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.Song; class KillingPartLikeTest { private static final Song EMPTY_SONG = null; private static final Member MEMBER = new Member("email@naver.com", "nickname"); - private static final KillingPart KILLING_PART = KillingPart.saved(1L, 10, PartLength.SHORT, - EMPTY_SONG); + private static final KillingPart KILLING_PART = KillingPart.saved(1L, 10, 5, + EMPTY_SONG); @DisplayName("isDeleted 상태를 반대로 변경할 수 있다.") @Test diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java index f56a0dcf0..b4e688271 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java @@ -8,7 +8,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import shook.shook.member.domain.Member; -import shook.shook.part.domain.PartLength; class KillingPartLikesTest { @@ -19,7 +18,7 @@ class KillingPartLikesTest { @BeforeEach void setUp() { MEMBER = new Member("email@naver.com", "nickname"); - KILLING_PART = KillingPart.forSave(10, PartLength.STANDARD); + KILLING_PART = KillingPart.forSave(10, 10); LIKES = new KillingPartLikes(); } diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartTest.java index 1b9b75913..f8d784127 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartTest.java @@ -8,7 +8,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import shook.shook.member.domain.Member; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.Genre; import shook.shook.song.domain.KillingParts; import shook.shook.song.domain.Song; @@ -26,8 +25,8 @@ class KillingPartTest { @Test void equals_true() { //given - final KillingPart firstPart = KillingPart.saved(1L, 4, PartLength.SHORT, EMPTY_SONG); - final KillingPart secondPart = KillingPart.saved(1L, 14, PartLength.SHORT, EMPTY_SONG); + final KillingPart firstPart = KillingPart.saved(1L, 4, 5, EMPTY_SONG); + final KillingPart secondPart = KillingPart.saved(1L, 14, 5, EMPTY_SONG); //when final boolean equals = firstPart.equals(secondPart); @@ -44,8 +43,8 @@ class Equals { @Test void equals_false_nullId() { //given - final KillingPart firstPart = KillingPart.saved(null, 4, PartLength.SHORT, EMPTY_SONG); - final KillingPart secondPart = KillingPart.saved(1L, 14, PartLength.SHORT, EMPTY_SONG); + final KillingPart firstPart = KillingPart.saved(null, 4, 5, EMPTY_SONG); + final KillingPart secondPart = KillingPart.saved(1L, 14, 5, EMPTY_SONG); //when final boolean equals = firstPart.equals(secondPart); @@ -58,9 +57,9 @@ void equals_false_nullId() { @Test void equals_false_bothNullId() { //given - final KillingPart firstPart = KillingPart.saved(null, 4, PartLength.SHORT, EMPTY_SONG); - final KillingPart secondPart = KillingPart.saved(null, 14, PartLength.SHORT, - EMPTY_SONG); + final KillingPart firstPart = KillingPart.saved(null, 4, 5, EMPTY_SONG); + final KillingPart secondPart = KillingPart.saved(null, 14, 5, + EMPTY_SONG); //when final boolean equals = firstPart.equals(secondPart); @@ -74,7 +73,7 @@ void equals_false_bothNullId() { @Test void getStartAndEndUrlPathParameterOfKillingPart() { //given - final KillingPart killingPart = KillingPart.saved(1L, 5, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 5, 5, EMPTY_SONG); //when final String startAndEndUrlPathParameter = killingPart.getStartAndEndUrlPathParameter(); @@ -94,7 +93,7 @@ class AddComment { @Test void success() { //given - final KillingPart killingPart = KillingPart.saved(1L, 5, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 5, 5, EMPTY_SONG); //when killingPart.addComment(KillingPartComment.saved(1L, killingPart, "댓글 내용", MEMBER)); @@ -107,8 +106,8 @@ void success() { @Test void belongToOtherPart() { //given - final KillingPart firstPart = KillingPart.saved(1L, 5, PartLength.SHORT, EMPTY_SONG); - final KillingPart secondPart = KillingPart.saved(2L, 5, PartLength.SHORT, EMPTY_SONG); + final KillingPart firstPart = KillingPart.saved(1L, 5, 5, EMPTY_SONG); + final KillingPart secondPart = KillingPart.saved(2L, 5, 5, EMPTY_SONG); //when //then @@ -127,7 +126,7 @@ class SetSong { @Test void setSong_emptySong_fail() { // given - final KillingPart killingPart = KillingPart.forSave(0, PartLength.STANDARD); + final KillingPart killingPart = KillingPart.forSave(0, 10); // when, then assertThatThrownBy(() -> killingPart.setSong(EMPTY_SONG)) @@ -138,15 +137,16 @@ void setSong_emptySong_fail() { @Test void setSong_alreadyRegisteredToSong_fail() { // given - final KillingPart dummyKillingPart1 = KillingPart.forSave(0, PartLength.STANDARD); - final KillingPart dummyKillingPart2 = KillingPart.forSave(0, PartLength.SHORT); - final KillingPart dummyKillingPart3 = KillingPart.forSave(0, PartLength.LONG); + final KillingPart dummyKillingPart1 = KillingPart.forSave(0, 10); + final KillingPart dummyKillingPart2 = KillingPart.forSave(0, 5); + final KillingPart dummyKillingPart3 = KillingPart.forSave(0, 15); final Song song = new Song("title", "elevenVideo", "imageUrl", "singer", 10, - Genre.from("댄스"), - new KillingParts(List.of(dummyKillingPart1, dummyKillingPart2, dummyKillingPart3)) + Genre.from("댄스"), + new KillingParts( + List.of(dummyKillingPart1, dummyKillingPart2, dummyKillingPart3)) ); - final KillingPart killingPart = KillingPart.forSave(0, PartLength.STANDARD); + final KillingPart killingPart = KillingPart.forSave(0, 10); // when, then assertThatThrownBy(() -> killingPart.setSong(song)) @@ -163,7 +163,7 @@ class LikeCountKillingPart { void createKillingPart_likeCount_0() { // given // when - final KillingPart killingPart = KillingPart.saved(1L, 10, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 10, 5, EMPTY_SONG); // then assertThat(killingPart.getLikeCount()).isZero(); @@ -175,7 +175,7 @@ void createKillingPart_likeCount_0() { void likeCount_updateSuccess() { // given final Member member = new Member("email@naver.com", "nickname"); - final KillingPart killingPart = KillingPart.saved(1L, 10, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 10, 5, EMPTY_SONG); // when final KillingPartLike likeToAdd = new KillingPartLike(killingPart, member); @@ -191,7 +191,7 @@ void likeCount_updateSuccess() { void likeCount_updateFail() { // given final Member member = new Member("email@naver.com", "nickname"); - final KillingPart killingPart = KillingPart.saved(1L, 10, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 10, 5, EMPTY_SONG); final KillingPartLike like = new KillingPartLike(killingPart, member); killingPart.like(like); @@ -208,7 +208,7 @@ void likeCount_updateFail() { void likeCount_deleteSuccess() { // given final Member member = new Member("email@naver.com", "nickname"); - final KillingPart killingPart = KillingPart.saved(1L, 10, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 10, 5, EMPTY_SONG); final KillingPartLike like = new KillingPartLike(killingPart, member); killingPart.like(like); @@ -225,7 +225,7 @@ void likeCount_deleteSuccess() { void likeCount_deleteFail() { // given final Member member = new Member("email@naver.com", "nickname"); - final KillingPart killingPart = KillingPart.saved(1L, 10, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 10, 5, EMPTY_SONG); final KillingPartLike like = new KillingPartLike(killingPart, member); killingPart.like(like); @@ -245,7 +245,7 @@ void likeCount_deleteFail() { @Test void like_empty_fail() { // given - final KillingPart killingPart = KillingPart.saved(1L, 10, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 10, 5, EMPTY_SONG); // when, then assertThatThrownBy(() -> killingPart.like(null)) @@ -257,10 +257,10 @@ void like_empty_fail() { void like_belongsToOtherKillingPart_fail() { // given final Member member = new Member("email@naver.com", "name"); - final KillingPart killingPart = KillingPart.saved(1L, 10, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 10, 5, EMPTY_SONG); final KillingPartLike like = new KillingPartLike(killingPart, member); - final KillingPart other = KillingPart.saved(2L, 10, PartLength.SHORT, EMPTY_SONG); + final KillingPart other = KillingPart.saved(2L, 10, 5, EMPTY_SONG); // when, then assertThatThrownBy(() -> other.like(like)) @@ -272,7 +272,7 @@ void like_belongsToOtherKillingPart_fail() { void likeByMember() { // given final Member member = new Member("email@naver.com", "name"); - final KillingPart killingPart = KillingPart.saved(1L, 10, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart = KillingPart.saved(1L, 10, 5, EMPTY_SONG); final KillingPartLike like = new KillingPartLike(killingPart, member); killingPart.like(like); diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartsTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartsTest.java index 1538ddc76..0596f3eb4 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartsTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartsTest.java @@ -12,16 +12,15 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import shook.shook.member.domain.Member; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.KillingParts; import shook.shook.song.domain.Song; import shook.shook.song.exception.killingpart.KillingPartsException; class KillingPartsTest { - private static final KillingPart FIRST_PART = KillingPart.forSave(0, PartLength.SHORT); - private static final KillingPart SECOND_PART = KillingPart.forSave(5, PartLength.SHORT); - private static final KillingPart THIRD_PART = KillingPart.forSave(10, PartLength.SHORT); + private static final KillingPart FIRST_PART = KillingPart.forSave(0, 5); + private static final KillingPart SECOND_PART = KillingPart.forSave(5, 5); + private static final KillingPart THIRD_PART = KillingPart.forSave(10, 5); private static final Song EMPTY_SONG = null; @DisplayName("한 노래의 킬링파트는 총 3개로 구성된다.") @@ -41,7 +40,7 @@ void createKillingPart_fail(final int size) { // given final List killingPartsToSave = new ArrayList<>(); for (int i = 0; i < size; i++) { - killingPartsToSave.add(KillingPart.forSave(i, PartLength.STANDARD)); + killingPartsToSave.add(KillingPart.forSave(i, 10)); } // when, then @@ -66,7 +65,7 @@ void isFull() { // given final List killingPartsToSave = new ArrayList<>(); for (int i = 0; i < 3; i++) { - killingPartsToSave.add(KillingPart.forSave(i, PartLength.STANDARD)); + killingPartsToSave.add(KillingPart.forSave(i, 10)); } final KillingParts killingParts = new KillingParts(killingPartsToSave); @@ -82,15 +81,15 @@ void isFull() { void getKillingPartsSortedByLikeCount() { // given final Member member = new Member("email@naver.com", "nickname"); - final KillingPart killingPart1 = KillingPart.saved(1L, 15, PartLength.SHORT, EMPTY_SONG); - final KillingPart killingPart2 = KillingPart.saved(2L, 10, PartLength.SHORT, EMPTY_SONG); - final KillingPart killingPart3 = KillingPart.saved(3L, 20, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart1 = KillingPart.saved(1L, 15, 5, EMPTY_SONG); + final KillingPart killingPart2 = KillingPart.saved(2L, 10, 5, EMPTY_SONG); + final KillingPart killingPart3 = KillingPart.saved(3L, 20, 5, EMPTY_SONG); killingPart1.like(new KillingPartLike(killingPart1, member)); killingPart2.like(new KillingPartLike(killingPart2, member)); final KillingParts killingParts = new KillingParts(List.of(killingPart1, killingPart3, - killingPart2)); + killingPart2)); // when final List result = killingParts.getKillingPartsSortedByLikeCount(); @@ -108,15 +107,15 @@ void getKillingPartsSortedByLikeCount() { void getTotalLikeCount() { // given final Member member = new Member("email@naver.com", "nickname"); - final KillingPart killingPart1 = KillingPart.saved(1L, 15, PartLength.SHORT, EMPTY_SONG); - final KillingPart killingPart2 = KillingPart.saved(2L, 10, PartLength.SHORT, EMPTY_SONG); - final KillingPart killingPart3 = KillingPart.saved(3L, 20, PartLength.SHORT, EMPTY_SONG); + final KillingPart killingPart1 = KillingPart.saved(1L, 15, 5, EMPTY_SONG); + final KillingPart killingPart2 = KillingPart.saved(2L, 10, 5, EMPTY_SONG); + final KillingPart killingPart3 = KillingPart.saved(3L, 20, 5, EMPTY_SONG); killingPart1.like(new KillingPartLike(killingPart1, member)); killingPart2.like(new KillingPartLike(killingPart2, member)); final KillingParts killingParts = new KillingParts(List.of(killingPart1, killingPart3, - killingPart2)); + killingPart2)); // when final int totalLikeCount = killingParts.getKillingPartsTotalLikeCount(); diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java index c04d65933..f46284589 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.Genre; import shook.shook.song.domain.KillingParts; import shook.shook.song.domain.Song; @@ -33,9 +32,9 @@ class KillingPartRepositoryTest extends UsingJpaTest { @BeforeEach void setUp() { - FIRST_KILLING_PART = KillingPart.forSave(0, PartLength.SHORT); - SECOND_KILLING_PART = KillingPart.forSave(10, PartLength.SHORT); - THIRD_KILLING_PART = KillingPart.forSave(14, PartLength.STANDARD); + FIRST_KILLING_PART = KillingPart.forSave(0, 5); + SECOND_KILLING_PART = KillingPart.forSave(10, 5); + THIRD_KILLING_PART = KillingPart.forSave(14, 10); KILLING_PARTS = new KillingParts( List.of( FIRST_KILLING_PART, diff --git a/backend/src/test/java/shook/shook/song/domain/repository/SongRepositoryTest.java b/backend/src/test/java/shook/shook/song/domain/repository/SongRepositoryTest.java index 55d06b5f8..270162f15 100644 --- a/backend/src/test/java/shook/shook/song/domain/repository/SongRepositoryTest.java +++ b/backend/src/test/java/shook/shook/song/domain/repository/SongRepositoryTest.java @@ -12,7 +12,6 @@ import org.springframework.data.domain.PageRequest; import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; -import shook.shook.part.domain.PartLength; import shook.shook.song.domain.Genre; import shook.shook.song.domain.KillingParts; import shook.shook.song.domain.Song; @@ -38,9 +37,9 @@ class SongRepositoryTest extends UsingJpaTest { private MemberRepository memberRepository; private Song createNewSongWithKillingParts() { - final KillingPart firstKillingPart = KillingPart.forSave(10, PartLength.SHORT); - final KillingPart secondKillingPart = KillingPart.forSave(15, PartLength.SHORT); - final KillingPart thirdKillingPart = KillingPart.forSave(20, PartLength.SHORT); + final KillingPart firstKillingPart = KillingPart.forSave(10, 5); + final KillingPart secondKillingPart = KillingPart.forSave(15, 5); + final KillingPart thirdKillingPart = KillingPart.forSave(20, 5); return new Song( "제목", "비디오ID는 11글자", "이미지URL", "가수", 5, Genre.from("댄스"), @@ -195,7 +194,7 @@ void findSongsWithLessLikeCountThanSongWithId() { assertThat(songs).usingRecursiveComparison() .ignoringFieldsOfTypes(LocalDateTime.class) .isEqualTo(List.of(secondSong, thirdSong, fourthSong, fifthSong, sixthSong, seventhSong, - eighthSong, ninthSong, tenthSong, eleventhSong) + eighthSong, ninthSong, tenthSong, eleventhSong) ); } @@ -306,7 +305,7 @@ void findSongsWithMoreLikeCountThanSongWithId() { .ignoringFieldsOfTypes(LocalDateTime.class) .isEqualTo( List.of(seventhSong, eighthSong, ninthSong, tenthSong, eleventhSong, fourthSong, - fifthSong, firstSong, secondSong, thirdSong)); + fifthSong, firstSong, secondSong, thirdSong)); } @DisplayName("주어진 id보다 좋아요가 많은 노래 10개를 총 좋아요 오름차순, id 오름차순으로 조회한다. (데이터가 기준보다 부족할 때)") diff --git a/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java b/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java index bf5803e7c..114dcfe0b 100644 --- a/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java @@ -17,7 +17,12 @@ import org.springframework.http.HttpStatus; import org.springframework.test.context.jdbc.Sql; import shook.shook.auth.application.TokenProvider; +import shook.shook.member_part.application.MemberPartService; +import shook.shook.member_part.application.dto.MemberPartRegisterRequest; +import shook.shook.member_part.domain.MemberPart; +import shook.shook.member_part.domain.repository.MemberPartRepository; import shook.shook.song.application.dto.LikedKillingPartResponse; +import shook.shook.song.application.dto.MyPartsResponse; import shook.shook.song.application.killingpart.KillingPartLikeService; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; import shook.shook.song.domain.Song; @@ -50,9 +55,16 @@ void setUp() { @Autowired private KillingPartRepository killingPartRepository; + @Autowired + private MemberPartRepository memberPartRepository; + @Autowired private KillingPartLikeService killingPartLikeService; + @Autowired + private MemberPartService memberPartService; + + @DisplayName("멤버가 좋아요한 킬링파트를 최신순으로 정렬하여 반환한다.") @Nested class GetLikedKillingParts { @@ -62,7 +74,7 @@ class GetLikedKillingParts { void likedKillingPartExistWithOneDeletedLikeExist() { //given final String accessToken = tokenProvider.createAccessToken(SAVED_MEMBER_ID, - SAVED_MEMBER_NICKNAME); + SAVED_MEMBER_NICKNAME); final Song firstSong = songRepository.findById(1L).get(); final Song secondSong = songRepository.findById(2L).get(); @@ -117,7 +129,7 @@ void likedKillingPartExistWithOneDeletedLikeExist() { .header(HttpHeaders.AUTHORIZATION, TOKEN_PREFIX + accessToken) .contentType(ContentType.JSON) .when().log().all() - .get("/my-page") + .get("/my-page/like-parts") .then().log().all() .statusCode(HttpStatus.OK.value()) .extract().body().jsonPath().getList(".", LikedKillingPartResponse.class); @@ -130,7 +142,7 @@ void likedKillingPartExistWithOneDeletedLikeExist() { void notExist() { //given final String accessToken = tokenProvider.createAccessToken(SAVED_MEMBER_ID, - SAVED_MEMBER_NICKNAME); + SAVED_MEMBER_NICKNAME); //when //then @@ -138,12 +150,52 @@ void notExist() { .header(HttpHeaders.AUTHORIZATION, TOKEN_PREFIX + accessToken) .contentType(ContentType.JSON) .when().log().all() - .get("/my-page") + .get("/my-page/like-parts") .then().log().all() .statusCode(HttpStatus.OK.value()) .extract().body().jsonPath().getList(".", LikedKillingPartResponse.class); assertThat(response).isEmpty(); } + + @DisplayName("마이 파트를 등록한 경우 모든 마이 파트를 가져온다.") + @Test + void findAllMyPart() { + // given + final String accessToken = tokenProvider.createAccessToken(SAVED_MEMBER_ID, + SAVED_MEMBER_NICKNAME); + + final Song firstSong = songRepository.findById(1L).get(); + final Song secondSong = songRepository.findById(2L).get(); + final Song thirdSong = songRepository.findById(3L).get(); + + final MemberPartRegisterRequest memberPartRegisterRequest = new MemberPartRegisterRequest(5, 15); + + memberPartService.register(firstSong.getId(), SAVED_MEMBER_ID, memberPartRegisterRequest); + memberPartService.register(secondSong.getId(), SAVED_MEMBER_ID, memberPartRegisterRequest); + memberPartService.register(thirdSong.getId(), SAVED_MEMBER_ID, memberPartRegisterRequest); + + // when + // then + final MemberPart memberPart3 = memberPartRepository.findById(3L).get(); + final MemberPart memberPart2 = memberPartRepository.findById(2L).get(); + final MemberPart memberPart1 = memberPartRepository.findById(1L).get(); + + final List expected = List.of(MyPartsResponse.of(thirdSong, memberPart3), + MyPartsResponse.of(secondSong, memberPart2), + MyPartsResponse.of(firstSong, memberPart1)); + + final List response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, TOKEN_PREFIX + accessToken) + .contentType(ContentType.JSON) + .when().log().all() + .get("/my-page/my-parts") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract().body().jsonPath().getList(".", MyPartsResponse.class); + + assertThat(response).usingRecursiveComparison().isEqualTo(expected); + + } } } diff --git a/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java b/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java new file mode 100644 index 000000000..276805da1 --- /dev/null +++ b/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java @@ -0,0 +1,89 @@ +package shook.shook.song.ui; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.jdbc.Sql; +import shook.shook.auth.application.TokenProvider; +import shook.shook.member_part.application.MemberPartService; +import shook.shook.member_part.application.dto.MemberPartRegisterRequest; +import shook.shook.song.application.InMemorySongsScheduler; +import shook.shook.song.application.dto.SongResponse; +import shook.shook.song.application.killingpart.KillingPartLikeService; +import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; + +@Sql("classpath:/killingpart/initialize_killing_part_song.sql") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class SongControllerTest { + + private static final Long MEMBER_ID = 1L; + private static final Long FIRST_SONG_KILLING_PART_ID_1 = 1L; + private static final Long FIRST_SONG_KILLING_PART_ID_2 = 2L; + private static final Long FIRST_SONG_KILLING_PART_ID_3 = 3L; + private static final String MEMBER_NICKNAME = "nickname"; + + @Autowired + private TokenProvider tokenProvider; + + @Autowired + private InMemorySongsScheduler inMemorySongsScheduler; + + @Autowired + private KillingPartLikeService likeService; + + @Autowired + private MemberPartService memberPartService; + + @LocalServerPort + private int port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @DisplayName("노래 id 와 토큰을 입력 받아 노래 하나 조회 성공 시, 200 상태 코드를 반환한다.") + @Test + void findSongById() { + // given + final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, MEMBER_NICKNAME); + + final Long songId = 1L; + likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, + new KillingPartLikeRequest(true)); + likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, + new KillingPartLikeRequest(true)); + likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, + new KillingPartLikeRequest(true)); + likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_3, MEMBER_ID, + new KillingPartLikeRequest(true)); + inMemorySongsScheduler.recreateCachedSong(); + + memberPartService.register(songId, MEMBER_ID, new MemberPartRegisterRequest(0, 10)); + + // when + final SongResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .when().log().all() + .get("/songs/{song_id}", songId) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract() + .body().as(SongResponse.class); + + // then + assertThat(response.getKillingParts().get(0).getId()).isEqualTo(1L); + assertThat(response.getKillingParts().get(1).getId()).isEqualTo(2L); + assertThat(response.getKillingParts().get(2).getId()).isEqualTo(3L); + assertThat(response.getMemberPart().getStart()).isEqualTo(0); + assertThat(response.getMemberPart().getEnd()).isEqualTo(10); + } +} diff --git a/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java b/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java index 8adbbe9f2..5a6a165a2 100644 --- a/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java @@ -16,6 +16,8 @@ import org.springframework.http.HttpStatus; import org.springframework.test.context.jdbc.Sql; import shook.shook.auth.application.TokenProvider; +import shook.shook.member_part.application.MemberPartService; +import shook.shook.member_part.application.dto.MemberPartRegisterRequest; import shook.shook.song.application.InMemorySongsScheduler; import shook.shook.song.application.dto.SongResponse; import shook.shook.song.application.dto.SongSwipeResponse; @@ -46,6 +48,9 @@ void setUp() { @Autowired private KillingPartLikeService likeService; + @Autowired + private MemberPartService memberPartService; + @Autowired private InMemorySongsScheduler inMemorySongsScheduler; @@ -53,15 +58,15 @@ void setUp() { @Test void showSongById() { //given - final String accessToken = tokenProvider.createAccessToken(1L, "nickname"); + final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); final Long songId = 2L; likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); inMemorySongsScheduler.recreateCachedSong(); // 정렬 순서: 1L, 2L, 4L, 3L @@ -77,11 +82,11 @@ void showSongById() { //then assertThat(response.getPrevSongs().stream() - .map(SongResponse::getId).toList()) + .map(SongResponse::getId).toList()) .containsExactly(1L); assertThat(response.getCurrentSong().getId()).isEqualTo(songId); assertThat(response.getNextSongs().stream() - .map(SongResponse::getId).toList()) + .map(SongResponse::getId).toList()) .containsExactly(4L, 3L); } @@ -90,17 +95,20 @@ void showSongById() { void showSongsBeforeSongWithId() { // given final Long songId = 2L; + final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); + + memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L, 2L inMemorySongsScheduler.recreateCachedSong(); //when final List response = RestAssured.given().log().all() - .param("memberId", MEMBER_ID) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .when().log().all() .get("/songs/high-liked/{song_id}/prev", songId) .then().log().all() @@ -110,8 +118,11 @@ void showSongsBeforeSongWithId() { // then assertThat(response.stream() - .map(SongResponse::getId).toList()) + .map(SongResponse::getId).toList()) .containsExactly(1L, 4L, 3L); + assertThat(response.get(0).getMemberPart().getId()).isNotNull(); + assertThat(response.get(1).getMemberPart()).isNull(); + assertThat(response.get(2).getMemberPart()).isNull(); } @DisplayName("가장 좋아요가 많은 노래 id 로 이후 노래 정보를 조회할 때 200 상태코드, 이후 노래 리스트를 반환한다.") @@ -119,13 +130,15 @@ void showSongsBeforeSongWithId() { void showSongsAfterSongWithId() { // given final Long songId = 3L; + final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 3L, 2L, 1L inMemorySongsScheduler.recreateCachedSong(); //when final List response = RestAssured.given().log().all() - .param("memberId", MEMBER_ID) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .when().log().all() .get("/songs/high-liked/{song_id}/next", songId) .then().log().all() @@ -135,8 +148,10 @@ void showSongsAfterSongWithId() { // then assertThat(response.stream() - .map(SongResponse::getId).toList()) + .map(SongResponse::getId).toList()) .containsExactly(2L, 1L); + assertThat(response.get(0).getMemberPart()).isNull(); + assertThat(response.get(1).getMemberPart()).isNotNull(); } @DisplayName("장르 스와이프를 요청할 때, 장르별 노래 리스트를 조회할 수 있다.") @@ -147,6 +162,7 @@ class GenreSwipe { @Test void showSongsByGenre() { // given + final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); final String genre = "DANCE"; // 정렬 순서 4L, 3L, 1L @@ -155,7 +171,7 @@ void showSongsByGenre() { // when final List response = RestAssured.given().log().all() .queryParam("genre", genre) - .param("memberId", MEMBER_ID) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .when().log().all() .get("/songs/high-liked") .then().log().all() @@ -165,7 +181,7 @@ void showSongsByGenre() { // then assertThat(response.stream() - .map(HighLikedSongResponse::getId).toList()) + .map(HighLikedSongResponse::getId).toList()) .containsExactly(4L, 3L, 1L); } @@ -189,18 +205,22 @@ void showSongsByWrongGenre() { .body().jsonPath().getString("genre"); } - @DisplayName("장르 노래 정보를 처음으로 조회할 때, 가운데 장르 노래를 기준으로 조회한 경우 200 상태코드, 현재 노래, 이전 / 이후 장르 노래 리스트를 반환한다.") + @DisplayName("비회원이 장르 노래 정보를 처음으로 조회할 때, 가운데 장르 노래를 기준으로 조회한 경우 200 상태코드, 현재 노래, 이전 / 이후 장르 노래 리스트를 반환한다.") @Test void findSongsByGenreForSwipe() { //given final Long songId = 4L; final String genre = "DANCE"; + final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); + + memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L inMemorySongsScheduler.recreateCachedSong(); @@ -208,6 +228,7 @@ void findSongsByGenreForSwipe() { //when final SongSwipeResponse response = RestAssured.given().log().all() .queryParam("genre", genre) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .when().log().all() .get("/songs/high-liked/{songId}", songId) .then().log().all() @@ -217,12 +238,15 @@ void findSongsByGenreForSwipe() { //then assertThat(response.getPrevSongs().stream() - .map(SongResponse::getId).toList()) + .map(SongResponse::getId).toList()) .containsExactly(1L); assertThat(response.getCurrentSong().getId()).isEqualTo(songId); assertThat(response.getNextSongs().stream() - .map(SongResponse::getId).toList()) + .map(SongResponse::getId).toList()) .containsExactly(3L); + assertThat(response.getPrevSongs().get(0).getMemberPart()).isNotNull(); + assertThat(response.getCurrentSong().getMemberPart()).isNull(); + assertThat(response.getNextSongs().get(0).getMemberPart()).isNull(); } @DisplayName("가장 좋아요가 적은 장르 노래 id 로 이전 장르 노래 정보를 조회할 때 200 상태코드, 이전 장르 노래 리스트를 반환한다.") @@ -231,12 +255,14 @@ void showPrevSongsWithGenre() { //given final Long songId = 3L; final String genre = "DANCE"; + final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, - new KillingPartLikeRequest(true)); - likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); + + memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L inMemorySongsScheduler.recreateCachedSong(); @@ -244,6 +270,7 @@ void showPrevSongsWithGenre() { //when final List response = RestAssured.given().log().all() .queryParam("genre", genre) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .when().log().all() .get("/songs/high-liked/{songId}/prev", songId) .then().log().all() @@ -251,11 +278,12 @@ void showPrevSongsWithGenre() { .extract() .body().jsonPath().getList(".", SongResponse.class); - //then //then assertThat(response.stream() - .map(SongResponse::getId).toList()) + .map(SongResponse::getId).toList()) .containsExactly(1L, 4L); + assertThat(response.get(0).getMemberPart()).isNotNull(); + assertThat(response.get(1).getMemberPart()).isNull(); } @DisplayName("가장 좋아요가 많은 장르 노래 id 로 이후 장르 노래 정보를 조회할 때 200 상태코드, 이후 장르 노래 리스트를 반환한다.") @@ -264,12 +292,16 @@ void showNextSongsWithGenre() { //given final Long songId = 1L; final String genre = "DANCE"; + final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); + + memberPartService.register(4L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L inMemorySongsScheduler.recreateCachedSong(); @@ -277,6 +309,7 @@ void showNextSongsWithGenre() { //when final List response = RestAssured.given().log().all() .queryParam("genre", genre) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .when().log().all() .get("/songs/high-liked/{songId}/next", songId) .then().log().all() @@ -286,8 +319,10 @@ void showNextSongsWithGenre() { //then assertThat(response.stream() - .map(SongResponse::getId).toList()) + .map(SongResponse::getId).toList()) .containsExactly(4L, 3L); + assertThat(response.get(0).getMemberPart()).isNotNull(); + assertThat(response.get(1).getMemberPart()).isNull(); } } } diff --git a/backend/src/test/java/shook/shook/voting_song/application/VotingSongPartServiceTest.java b/backend/src/test/java/shook/shook/voting_song/application/VotingSongPartServiceTest.java index 6a6a9b55d..c223cb647 100644 --- a/backend/src/test/java/shook/shook/voting_song/application/VotingSongPartServiceTest.java +++ b/backend/src/test/java/shook/shook/voting_song/application/VotingSongPartServiceTest.java @@ -92,7 +92,7 @@ void notRegistered() { @Test void registered_membersSamePartExist() { //given - final VotingSongPart votingSongPart = VotingSongPart.forSave(1, PartLength.SHORT, SAVED_SONG); + final VotingSongPart votingSongPart = VotingSongPart.forSave(1, new PartLength(5), SAVED_SONG); addPart(SAVED_SONG, votingSongPart); final Vote vote = Vote.forSave(FIRST_MEMBER, votingSongPart); @@ -103,7 +103,7 @@ void registered_membersSamePartExist() { //when final MemberInfo anotherMemberInfo = new MemberInfo(FIRST_MEMBER.getId(), Authority.MEMBER); votingSongPartService.registerAndReturnMemberPartDuplication(anotherMemberInfo, SAVED_SONG.getId(), - request); + request); saveAndClearEntityManager(); //then @@ -117,7 +117,7 @@ void registered_membersSamePartExist() { @Test void registered() { //given - final VotingSongPart votingSongPart = VotingSongPart.forSave(1, PartLength.SHORT, SAVED_SONG); + final VotingSongPart votingSongPart = VotingSongPart.forSave(1, new PartLength(5), SAVED_SONG); addPart(SAVED_SONG, votingSongPart); final Vote vote = Vote.forSave(FIRST_MEMBER, votingSongPart); @@ -128,7 +128,7 @@ void registered() { //when final MemberInfo anotherMemberInfo = new MemberInfo(SECOND_MEMBER.getId(), Authority.MEMBER); votingSongPartService.registerAndReturnMemberPartDuplication(anotherMemberInfo, SAVED_SONG.getId(), - request); + request); saveAndClearEntityManager(); //then diff --git a/backend/src/test/java/shook/shook/voting_song/domain/VotingSongPartTest.java b/backend/src/test/java/shook/shook/voting_song/domain/VotingSongPartTest.java index 12acacc4e..fbd046553 100644 --- a/backend/src/test/java/shook/shook/voting_song/domain/VotingSongPartTest.java +++ b/backend/src/test/java/shook/shook/voting_song/domain/VotingSongPartTest.java @@ -21,9 +21,9 @@ class VotingSongPartTest { @Test void equals_true() { //given - final VotingSongPart firstPart = VotingSongPart.saved(1L, 4, PartLength.SHORT, votingSong); - final VotingSongPart secondPart = VotingSongPart.saved(1L, 14, PartLength.SHORT, - votingSong); + final VotingSongPart firstPart = VotingSongPart.saved(1L, 4, new PartLength(5), votingSong); + final VotingSongPart secondPart = VotingSongPart.saved(1L, 14, new PartLength(5), + votingSong); //when final boolean equals = firstPart.equals(secondPart); @@ -41,9 +41,9 @@ class Equals { void equals_false_nullId() { //given final VotingSongPart firstPart = - VotingSongPart.saved(null, 4, PartLength.SHORT, votingSong); + VotingSongPart.saved(null, 4, new PartLength(5), votingSong); final VotingSongPart secondPart = - VotingSongPart.saved(1L, 14, PartLength.SHORT, votingSong); + VotingSongPart.saved(1L, 14, new PartLength(5), votingSong); //when final boolean equals = firstPart.equals(secondPart); @@ -57,9 +57,9 @@ void equals_false_nullId() { void equals_false_bothNullId() { //given final VotingSongPart firstPart = - VotingSongPart.saved(null, 4, PartLength.SHORT, votingSong); + VotingSongPart.saved(null, 4, new PartLength(5), votingSong); final VotingSongPart secondPart = - VotingSongPart.saved(null, 14, PartLength.SHORT, votingSong); + VotingSongPart.saved(null, 14, new PartLength(5), votingSong); //when final boolean equals = firstPart.equals(secondPart); @@ -79,7 +79,7 @@ void create_success() { //given //when //then - assertDoesNotThrow(() -> VotingSongPart.forSave(14, PartLength.SHORT, votingSong)); + assertDoesNotThrow(() -> VotingSongPart.forSave(14, new PartLength(5), votingSong)); } @DisplayName("파트의 시작초가 0보다 작으면 예외가 발생한다.") @@ -88,7 +88,7 @@ void create_fail_startUnderZero() { //given //when //then - assertThatThrownBy(() -> VotingSongPart.forSave(-1, PartLength.SHORT, votingSong)) + assertThatThrownBy(() -> VotingSongPart.forSave(-1, new PartLength(5), votingSong)) .isInstanceOf(PartException.StartLessThanZeroException.class); } @@ -98,7 +98,7 @@ void create_fail_startOverSongLength() { //given //when //then - assertThatThrownBy(() -> VotingSongPart.forSave(30, PartLength.SHORT, votingSong)) + assertThatThrownBy(() -> VotingSongPart.forSave(30, new PartLength(5), votingSong)) .isInstanceOf(PartException.StartOverSongLengthException.class); } @@ -108,7 +108,7 @@ void create_fail_endOverSongLength() { //given //when //then - assertThatThrownBy(() -> VotingSongPart.forSave(29, PartLength.SHORT, votingSong)) + assertThatThrownBy(() -> VotingSongPart.forSave(29, new PartLength(5), votingSong)) .isInstanceOf(PartException.EndOverSongLengthException.class); } } @@ -121,7 +121,7 @@ class VoteToPart { @Test void vote_success_one() { //given - final VotingSongPart part = VotingSongPart.saved(1L, 14, PartLength.SHORT, votingSong); + final VotingSongPart part = VotingSongPart.saved(1L, 14, new PartLength(5), votingSong); final Vote vote = Vote.saved(1L, MEMBER, part); //when @@ -135,7 +135,7 @@ void vote_success_one() { @Test void vote_success_many() { //given - final VotingSongPart part = VotingSongPart.forSave(14, PartLength.SHORT, votingSong); + final VotingSongPart part = VotingSongPart.forSave(14, new PartLength(5), votingSong); final Vote firstVote = Vote.forSave(MEMBER, part); final Vote secondVote = Vote.forSave(MEMBER, part); @@ -151,10 +151,10 @@ void vote_success_many() { @Test void vote_fail_voteForOtherPart() { //given - final VotingSongPart firstPart = VotingSongPart.saved(1L, 14, PartLength.SHORT, - votingSong); - final VotingSongPart secondPart = VotingSongPart.saved(2L, 10, PartLength.SHORT, - votingSong); + final VotingSongPart firstPart = VotingSongPart.saved(1L, 14, new PartLength(5), + votingSong); + final VotingSongPart secondPart = VotingSongPart.saved(2L, 10, new PartLength(5), + votingSong); final Vote voteForSecondPart = Vote.forSave(MEMBER, secondPart); //when @@ -172,7 +172,7 @@ class GetVoteCount { @Test void getVoteCount_twoVoteDifferentId() { //given - final VotingSongPart part = VotingSongPart.forSave(14, PartLength.SHORT, votingSong); + final VotingSongPart part = VotingSongPart.forSave(14, new PartLength(5), votingSong); final Vote firstVote = Vote.saved(1L, MEMBER, part); final Vote secondVote = Vote.saved(2L, MEMBER, part); part.vote(firstVote); @@ -189,7 +189,7 @@ void getVoteCount_twoVoteDifferentId() { @Test void getVoteCount_towVoteSameId() { //given - final VotingSongPart part = VotingSongPart.forSave(14, PartLength.SHORT, votingSong); + final VotingSongPart part = VotingSongPart.forSave(14, new PartLength(5), votingSong); final Vote firstVote = Vote.saved(1L, MEMBER, part); final Vote secondVote = Vote.saved(1L, MEMBER, part); part.vote(firstVote); diff --git a/backend/src/test/java/shook/shook/voting_song/domain/VotingSongPartsTest.java b/backend/src/test/java/shook/shook/voting_song/domain/VotingSongPartsTest.java index 52e828f54..77dbd81eb 100644 --- a/backend/src/test/java/shook/shook/voting_song/domain/VotingSongPartsTest.java +++ b/backend/src/test/java/shook/shook/voting_song/domain/VotingSongPartsTest.java @@ -17,8 +17,8 @@ class VotingSongPartsTest { void create_fail_duplicatePartExist() { //given final VotingSong votingSong = new VotingSong("제목", "비디오ID는 11글자", "이미지URL", "가수", 30); - final VotingSongPart firstPart = VotingSongPart.saved(1L, 5, PartLength.SHORT, votingSong); - final VotingSongPart secondPart = VotingSongPart.forSave(5, PartLength.SHORT, votingSong); + final VotingSongPart firstPart = VotingSongPart.saved(1L, 5, new PartLength(5), votingSong); + final VotingSongPart secondPart = VotingSongPart.forSave(5, new PartLength(5), votingSong); final VotingSongParts votingSongParts = new VotingSongParts(); votingSongParts.addPart(firstPart); diff --git a/backend/src/test/java/shook/shook/voting_song/domain/VotingSongTest.java b/backend/src/test/java/shook/shook/voting_song/domain/VotingSongTest.java index a544c11a8..aa4930a06 100644 --- a/backend/src/test/java/shook/shook/voting_song/domain/VotingSongTest.java +++ b/backend/src/test/java/shook/shook/voting_song/domain/VotingSongTest.java @@ -15,7 +15,7 @@ class VotingSongTest { void addPart_valid() { //given final VotingSong votingSong = new VotingSong("노래제목", "비디오ID는 11글자", "이미지URL", "가수", 180); - final VotingSongPart votingSongPart = VotingSongPart.forSave(1, PartLength.STANDARD, votingSong); + final VotingSongPart votingSongPart = VotingSongPart.forSave(1, new PartLength(10), votingSong); //when votingSong.addPart(votingSongPart); @@ -30,7 +30,7 @@ void addPart_invalid() { //given final VotingSong firstSong = new VotingSong("노래제목", "비디오ID는 11글자", "이미지URL", "가수", 180); final VotingSong secondSong = new VotingSong("노래제목", "비디오ID는 11글자", "이미지URL", "가수", 180); - final VotingSongPart partInSecondSong = VotingSongPart.forSave(1, PartLength.STANDARD, secondSong); + final VotingSongPart partInSecondSong = VotingSongPart.forSave(1, new PartLength(10), secondSong); //when //then diff --git a/backend/src/test/java/shook/shook/voting_song/domain/repository/VoteRepositoryTest.java b/backend/src/test/java/shook/shook/voting_song/domain/repository/VoteRepositoryTest.java index ea100014e..2cec58111 100644 --- a/backend/src/test/java/shook/shook/voting_song/domain/repository/VoteRepositoryTest.java +++ b/backend/src/test/java/shook/shook/voting_song/domain/repository/VoteRepositoryTest.java @@ -37,7 +37,7 @@ void existsByMemberAndVotingSongPart() { final VotingSong votingSong = votingSongRepository.save( new VotingSong("제목1", "비디오ID는 11글자", "이미지URL", "가수", 20)); final VotingSongPart votingSongPart = votingSongPartRepository.save( - VotingSongPart.forSave(1, PartLength.SHORT, votingSong)); + VotingSongPart.forSave(1, new PartLength(5), votingSong)); voteRepository.save(Vote.forSave(member, votingSongPart)); //when diff --git a/backend/src/test/java/shook/shook/voting_song/domain/repository/VotingSongPartRepositoryTest.java b/backend/src/test/java/shook/shook/voting_song/domain/repository/VotingSongPartRepositoryTest.java index 3e7bbdeb6..b32148f85 100644 --- a/backend/src/test/java/shook/shook/voting_song/domain/repository/VotingSongPartRepositoryTest.java +++ b/backend/src/test/java/shook/shook/voting_song/domain/repository/VotingSongPartRepositoryTest.java @@ -35,7 +35,7 @@ void setUp() { @Test void save() { //given - final VotingSongPart votingSongPart = VotingSongPart.forSave(14, PartLength.SHORT, SAVED_SONG); + final VotingSongPart votingSongPart = VotingSongPart.forSave(14, new PartLength(5), SAVED_SONG); //when final VotingSongPart saved = votingSongPartRepository.save(votingSongPart); @@ -49,7 +49,7 @@ void save() { @Test void createdAt() { //given - final VotingSongPart votingSongPart = VotingSongPart.forSave(14, PartLength.SHORT, SAVED_SONG); + final VotingSongPart votingSongPart = VotingSongPart.forSave(14, new PartLength(5), SAVED_SONG); //when final LocalDateTime prev = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); @@ -65,8 +65,8 @@ void createdAt() { @Test void findAllBySong() { //given - final VotingSongPart firstPart = VotingSongPart.forSave(1, PartLength.SHORT, SAVED_SONG); - final VotingSongPart secondPart = VotingSongPart.forSave(5, PartLength.SHORT, SAVED_SONG); + final VotingSongPart firstPart = VotingSongPart.forSave(1, new PartLength(5), SAVED_SONG); + final VotingSongPart secondPart = VotingSongPart.forSave(5, new PartLength(5), SAVED_SONG); votingSongPartRepository.save(firstPart); votingSongPartRepository.save(secondPart); @@ -86,7 +86,7 @@ class findById { @Test void findOnePart() { // given - final VotingSongPart part = VotingSongPart.forSave(1, PartLength.SHORT, SAVED_SONG); + final VotingSongPart part = VotingSongPart.forSave(1, new PartLength(5), SAVED_SONG); votingSongPartRepository.save(part); // when @@ -111,7 +111,7 @@ void findNotExistedPart() { @Test void existsByVotingSongAndMemberAndStartSecondAndLength() { //given - final VotingSongPart part = VotingSongPart.forSave(1, PartLength.SHORT, SAVED_SONG); + final VotingSongPart part = VotingSongPart.forSave(1, new PartLength(5), SAVED_SONG); votingSongPartRepository.save(part); //when diff --git a/backend/src/test/resources/killingpart/initialize_killing_part_song.sql b/backend/src/test/resources/killingpart/initialize_killing_part_song.sql index 42e4f05be..88da34a14 100644 --- a/backend/src/test/resources/killingpart/initialize_killing_part_song.sql +++ b/backend/src/test/resources/killingpart/initialize_killing_part_song.sql @@ -3,6 +3,7 @@ drop table killing_part; drop table member; drop table killing_part_like; drop table killing_part_comment; +drop table member_part; create table if not exists song ( @@ -24,7 +25,7 @@ create table if not exists killing_part ( id bigint auto_increment, start_second integer not null, - length varchar(255) not null check (length in ('SHORT', 'STANDARD', 'LONG')), + length integer not null, song_id bigint not null, like_count integer not null default 0, created_at timestamp(6) not null, @@ -61,6 +62,17 @@ create table if not exists killing_part_comment primary key (id) ); +create table if not exists member_part +( + id bigint auto_increment, + start_second integer not null, + length integer not null, + song_id bigint not null, + member_id bigint not null, + created_at timestamp(6) not null, + primary key (id) +); + INSERT INTO song (title, singer, length, video_id, album_cover_url, created_at, genre) VALUES ('Super Shy', 'NewJeans', 200, 'ArmDp-zijuc', 'http://i.maniadb.com/images/album/999/999126_1_f.jpg', now(), 'DANCE'); @@ -75,25 +87,25 @@ VALUES ('Seven (feat. Latto) - Clean Ver.', '정국', 186, 'UUSbUBYqU_8', 'http://i.maniadb.com/images/album/1000/000246_1_f.jpg', now(), 'DANCE'); INSERT INTO killing_part (start_second, length, song_id, like_count, created_at) -VALUES (10, 'SHORT', 1, 0, now()); +VALUES (10, 5, 1, 0, now()); INSERT INTO killing_part (start_second, length, song_id, like_count, created_at) -VALUES (15, 'LONG', 1, 0, now()); +VALUES (15, 15, 1, 0, now()); INSERT INTO killing_part (start_second, length, song_id, like_count, created_at) -VALUES (20, 'STANDARD', 1, 0, now()); +VALUES (20, 10, 1, 0, now()); INSERT INTO killing_part (start_second, length, song_id, like_count, created_at) -VALUES (10, 'SHORT', 2, 0, now()); +VALUES (10, 5, 2, 0, now()); INSERT INTO killing_part (start_second, length, song_id, like_count, created_at) -VALUES (15, 'LONG', 2, 0, now()); +VALUES (15, 15, 2, 0, now()); INSERT INTO killing_part (start_second, length, song_id, like_count, created_at) -VALUES (20, 'STANDARD', 2, 0, now()); +VALUES (20, 10, 2, 0, now()); INSERT INTO killing_part (start_second, length, song_id, like_count, created_at) -VALUES (10, 'SHORT', 3, 0, now()); +VALUES (10, 5, 3, 0, now()); INSERT INTO killing_part (start_second, length, song_id, like_count, created_at) -VALUES (15, 'LONG', 3, 0, now()); +VALUES (15, 15, 3, 0, now()); INSERT INTO killing_part (start_second, length, song_id, like_count, created_at) -VALUES (20, 'STANDARD', 3, 0, now()); +VALUES (20, 10, 3, 0, now()); INSERT INTO member (email, nickname, created_at) VALUES ('email@naver.com', 'nickname', now()); diff --git a/backend/src/test/resources/schema-test.sql b/backend/src/test/resources/schema-test.sql index 2e6c81cbc..6fc289aee 100644 --- a/backend/src/test/resources/schema-test.sql +++ b/backend/src/test/resources/schema-test.sql @@ -6,6 +6,7 @@ drop table if exists voting_song_part; drop table if exists voting_song; drop table if exists vote; drop table if exists member; +drop table if exists member_part; create table if not exists song ( @@ -27,7 +28,7 @@ create table if not exists killing_part ( id bigint auto_increment, start_second integer not null, - length varchar(255) not null check (length in ('SHORT', 'STANDARD', 'LONG')), + length integer not null, song_id bigint not null, like_count integer not null default 0, created_at timestamp(6) not null, @@ -70,7 +71,7 @@ create table if not exists voting_song_part ( id bigint auto_increment, start_second integer not null, - length varchar(255) not null check (length in ('SHORT', 'STANDARD', 'LONG')), + length integer not null, voting_song_id bigint not null, created_at timestamp(6) not null, primary key (id) @@ -92,3 +93,14 @@ create table if not exists member created_at timestamp(6) not null, primary key (id) ); + +create table if not exists member_part +( + id bigint auto_increment, + start_second integer not null, + length integer not null, + song_id bigint not null, + member_id bigint not null, + created_at timestamp(6) not null, + primary key (id) +);