Skip to content

Commit

Permalink
feat: 마이페이지를 위한 멤버의 좋아요한 킬링파트 조회 기능 완성
Browse files Browse the repository at this point in the history
  • Loading branch information
splitCoding committed Aug 17, 2023
1 parent ea2c4d5 commit 76d87f3
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ private HandlerInterceptor loginCheckerInterceptor() {

private HandlerInterceptor tokenInterceptor() {
return new PathMatcherInterceptor(tokenInterceptor)
.includePathPattern("/my-page", PathMethod.GET)
.includePathPattern("/songs/*/parts/*/likes", PathMethod.PUT)
.includePathPattern("/voting-songs/*/parts", PathMethod.POST)
.includePathPattern("/songs/*/parts/*/comments", PathMethod.POST);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package shook.shook.song.application;

import java.util.Comparator;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shook.shook.auth.ui.argumentresolver.MemberInfo;
import shook.shook.member.domain.Member;
import shook.shook.member.domain.repository.MemberRepository;
import shook.shook.member.exception.MemberException;
import shook.shook.member.exception.MemberException.MemberNotExistException;
import shook.shook.song.application.dto.LikedKillingPartResponse;
import shook.shook.song.domain.killingpart.KillingPart;
import shook.shook.song.domain.killingpart.KillingPartLike;
import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class KillingPartService {

private final KillingPartLikeRepository killingPartLikeRepository;
private final MemberRepository memberRepository;

public List<LikedKillingPartResponse> findLikedKillingPartByMemberId(
final MemberInfo memberInfo
) {
if (memberInfo.getAuthority().isAnonymous()) {
throw new MemberException.MemberNotExistException();
}

final Member member = memberRepository.findById(memberInfo.getMemberId())
.orElseThrow(MemberNotExistException::new);

final List<KillingPartLike> likes =
killingPartLikeRepository.findAllByMemberAndIsDeleted(member, false);

return likes.stream()
.sorted(Comparator.comparing(KillingPartLike::getUpdatedAt).reversed())
.map(killingPartLike -> {
final KillingPart killingPart = killingPartLike.getKillingPart();
return LikedKillingPartResponse.of(killingPart.getSong(), killingPart);
}).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package shook.shook.song.application.dto;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import shook.shook.song.domain.Song;
import shook.shook.song.domain.killingpart.KillingPart;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class LikedKillingPartResponse {

private final Long songId;
private final String title;
private final String singer;
private final String albumCoverUrl;
private final Long partId;
private final int start;
private final int end;

public static LikedKillingPartResponse of(final Song song, final KillingPart killingPart) {
return new LikedKillingPartResponse(
song.getId(),
song.getTitle(),
song.getSinger(),
song.getAlbumCoverUrl(),
killingPart.getId(),
killingPart.getStartSecond(),
killingPart.getEndSecond()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public class KillingPart {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "song_id", foreignKey = @ForeignKey(name = "none"), updatable = false, nullable = false)
@Getter(AccessLevel.NONE)
// @Getter(AccessLevel.NONE) 좋아요한 킬링파트 탐색 성능을 위해 사용
private Song song;

@Embedded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class KillingPartLike {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "killing_part_id", foreignKey = @ForeignKey(name = "none"), updatable = false, nullable = false)
@Getter(AccessLevel.NONE)
// @Getter(AccessLevel.NONE) 좋아요한 킬링파트 탐색 성능을 위해 사용
private KillingPart killingPart;

@ManyToOne
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package shook.shook.song.domain.killingpart.repository;

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
Expand All @@ -10,5 +11,8 @@
@Repository
public interface KillingPartLikeRepository extends JpaRepository<KillingPartLike, Long> {

Optional<KillingPartLike> findByKillingPartAndMember(final KillingPart killingPart, final Member member);
Optional<KillingPartLike> findByKillingPartAndMember(final KillingPart killingPart,
final Member member);

List<KillingPartLike> findAllByMemberAndIsDeleted(final Member member, final boolean isDeleted);
}
27 changes: 27 additions & 0 deletions backend/src/main/java/shook/shook/song/ui/MyPageController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package shook.shook.song.ui;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
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.KillingPartService;
import shook.shook.song.application.dto.LikedKillingPartResponse;

@RequiredArgsConstructor
@RestController
@RequestMapping("/my-page")
public class MyPageController {

private final KillingPartService killingPartService;

@GetMapping
public ResponseEntity<List<LikedKillingPartResponse>> getMemberLikedKillingParts(
@Authenticated MemberInfo memberInfo
) {
return ResponseEntity.ok(killingPartService.findLikedKillingPartByMemberId(memberInfo));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -85,4 +86,24 @@ void findByKillingPartAndMember_isDeleted() {
// then
assertThat(foundLike).isPresent();
}

@DisplayName("Member 와 isDeleted 로 KillingPartLike 를 조회한다.")
@Test
void findAllByMemberAndDeleted() {
// given
final KillingPartLike killingPartLike = new KillingPartLike(SAVED_KILLING_PART,
SAVED_MEMBER);

killingPartLikeRepository.save(killingPartLike);
saveAndClearEntityManager();

//when
final List<KillingPartLike> allByMemberAndDeleted =
killingPartLikeRepository.findAllByMemberAndIsDeleted(SAVED_MEMBER, true);

//then
assertThat(allByMemberAndDeleted).usingRecursiveComparison()
.comparingOnlyFields("id")
.isEqualTo(List.of(killingPartLike));
}
}
149 changes: 149 additions & 0 deletions backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package shook.shook.song.ui;

import static org.assertj.core.api.Assertions.assertThat;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
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.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.song.application.dto.LikedKillingPartResponse;
import shook.shook.song.application.killingpart.KillingPartLikeService;
import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest;
import shook.shook.song.domain.Song;
import shook.shook.song.domain.killingpart.KillingPart;
import shook.shook.song.domain.killingpart.repository.KillingPartRepository;
import shook.shook.song.domain.repository.SongRepository;

@Sql("classpath:/killingpart/initialize_killing_part_song.sql")
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyPageControllerTest {

@LocalServerPort
private int port;

@BeforeEach
void setUp() {
RestAssured.port = port;
}

private static final String TOKEN_PREFIX = "Bearer ";
private static final long SAVED_MEMBER_ID = 1L;
private static final String SAVED_MEMBER_NICKNAME = "nickname";

@Autowired
private TokenProvider tokenProvider;

@Autowired
private SongRepository songRepository;

@Autowired
private KillingPartRepository killingPartRepository;

@Autowired
private KillingPartLikeService killingPartLikeService;

@DisplayName("멤버가 좋아요한 킬링파트를 최신순으로 정렬하여 반환한다.")
@Nested
class GetLikedKillingParts {

@DisplayName("좋요한 킬링파트가 존재할 때. ( 취소한 좋아요가 존재할 때 )")
@Test
void likedKillingPartExistWithOneDeletedLikeExist() {
//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 List<KillingPart> firstSongKillingPart = killingPartRepository.findAllBySong(
firstSong);
final List<KillingPart> secondSongKillingPart = killingPartRepository.findAllBySong(
secondSong);
final List<KillingPart> thirdSongKillingPart = killingPartRepository.findAllBySong(
thirdSong);

final KillingPartLikeRequest likeCreateRequest = new KillingPartLikeRequest(true);
final KillingPartLikeRequest likeDeleteRequest = new KillingPartLikeRequest(false);

killingPartLikeService.updateLikeStatus(
firstSongKillingPart.get(0).getId(),
1L,
likeCreateRequest
);
killingPartLikeService.updateLikeStatus(
firstSongKillingPart.get(2).getId(),
1L,
likeCreateRequest
);
killingPartLikeService.updateLikeStatus(
firstSongKillingPart.get(2).getId(),
1L,
likeDeleteRequest
);
killingPartLikeService.updateLikeStatus(
secondSongKillingPart.get(0).getId(),
1L,
likeCreateRequest
);
killingPartLikeService.updateLikeStatus(
thirdSongKillingPart.get(0).getId(),
1L,
likeCreateRequest
);

//when
//then

final List<LikedKillingPartResponse> expected = List.of(
LikedKillingPartResponse.of(thirdSong, thirdSongKillingPart.get(0)),
LikedKillingPartResponse.of(secondSong, secondSongKillingPart.get(0)),
LikedKillingPartResponse.of(firstSong, firstSongKillingPart.get(0))
);

final List<LikedKillingPartResponse> response = RestAssured.given().log().all()
.header(HttpHeaders.AUTHORIZATION, TOKEN_PREFIX + accessToken)
.contentType(ContentType.JSON)
.when().log().all()
.get("/my-page")
.then().log().all()
.statusCode(HttpStatus.OK.value())
.extract().body().jsonPath().getList(".", LikedKillingPartResponse.class);

assertThat(response).usingRecursiveComparison().isEqualTo(expected);
}

@DisplayName("좋아요한 킬링파트가 없을 때")
@Test
void notExist() {
//given
final String accessToken = tokenProvider.createAccessToken(SAVED_MEMBER_ID,
SAVED_MEMBER_NICKNAME);

//when
//then
final List<LikedKillingPartResponse> response = RestAssured.given().log().all()
.header(HttpHeaders.AUTHORIZATION, TOKEN_PREFIX + accessToken)
.contentType(ContentType.JSON)
.when().log().all()
.get("/my-page")
.then().log().all()
.statusCode(HttpStatus.OK.value())
.extract().body().jsonPath().getList(".", LikedKillingPartResponse.class);

assertThat(response).isEmpty();
}
}
}

0 comments on commit 76d87f3

Please sign in to comment.