Skip to content

Commit

Permalink
Feat/#496 MemberPart 엔티티 추가 및 enum PartLength을 class 로 변경 (#497)
Browse files Browse the repository at this point in the history
* refactor: 중복 코드 메서드 호출로 변경

* refactor: PartLength 가 5초 이상 15초 이하의 값을 가질 수 있도록 리팩터링

- PartLength 를 enum 에서 class 로 변경

* feat: MemberPart 엔티티 추가

* fix: legacy_length 를 nullable 하게 변경

* feat: MemberPart 등록 기능 추가

* feat: MemberPart 삭제 기능 추가

* feat: 마이페이지에서 멤버파트 조회하는 기능 추가

* refactor: 시큐리티 최신버전 동기화

* feat: 노래 응답에 MemberPart 추가

* docs: swagger 명세 추가

* feat: 마이페이지 내 마이파트 조회 swagger 추가

* test: 불필요한 코드 제거 및 필드 변수 사용

* config: dev 데이터 유지하도록 설정

* fix: schema.sql 스키마 변경

* config: init mode never 로 변경

* fix: voting_song_part length 타입 변경

* refactor: 패키지 이름 변경

* feat: 노래 단일 조회 API 추가

* fix: 사용되지 않는 API deprecate 설정

* style: 공백 추가

* refactor: 내 파트 응답 속성 변경

* refactor: 멤버 파트 응답 속성 이름 변경

* fix: 해당 노래에 MemberPart 가 이미 등록되어 있으면 에러 던지도록 수정

* refactor: 마이페이지 내 파트 응답에 노래 비디오 id 추가

* fix: 파트 응답 순서 수정

---------

Co-authored-by: seokhwan-an <[email protected]>
  • Loading branch information
Cyma-s and seokhwan-an authored Oct 19, 2023
1 parent 49b2472 commit 622afd4
Show file tree
Hide file tree
Showing 62 changed files with 1,756 additions and 317 deletions.
7 changes: 5 additions & 2 deletions backend/src/main/java/shook/shook/auth/config/AuthConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
3 changes: 2 additions & 1 deletion backend/src/main/java/shook/shook/auth/ui/AuthContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public void setAuthenticatedMember(final Long memberId) {
}

public boolean isAnonymous() {
return this.authority == Authority.ANONYMOUS;
return authority.isAnonymous();
}

public long getMemberId() {
Expand All @@ -29,3 +29,4 @@ public Authority getAuthority() {
return authority;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand All @@ -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;
Expand All @@ -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);
Expand Down
16 changes: 13 additions & 3 deletions backend/src/main/java/shook/shook/globalexception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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, "노래 제목은 비어있을 수 없습니다."),
Expand All @@ -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, "이메일은 비어있을 수 없습니다."),
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,7 +56,8 @@ public ResponseEntity<ErrorResponse> handleTokenException(final CustomException
MemberException.class,
VotingSongException.class,
VotingSongPartException.PartNotExistException.class,
PartException.class
PartException.class,
MemberPartException.class
})
public ResponseEntity<ErrorResponse> handleGlobalBadRequestException(final CustomException e) {
log.error(e.getErrorInfoLog());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,15 @@ public MemberNotExistException(final Map<String, String> 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<String, String> inputValuesByProperty) {
super(ErrorCode.MEMBER_PART_ALREADY_EXIST, inputValuesByProperty);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 622afd4

Please sign in to comment.