-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat: Artist 도메인 생성 및 Song singer -> artist 변경 * feat: artist 테이블 생성 및 song, voting_song singer -> artist_id 변경 * config: shook-security 스냅샷 최신화 * feat: ArtistSynonym 엔티티 및 테이블 생성 * feat: 가수 이름, 동의어 검색 기능 구현 * refactor: 코드리뷰 반영 * feat: 상세 가수 페이지 반환 시, 노래별 가수 이름 response 추가 * refactor: 불필요한 주석, 개행 제거 * feat: Artist 도메인 생성 및 Song singer -> artist 변경 * feat: artist 테이블 생성 및 song, voting_song singer -> artist_id 변경 * feat: ArtistSynonym 엔티티 및 테이블 생성 * feat: 가수 이름, 동의어 검색 기능 구현 * refactor: 코드리뷰 반영 * feat: 상세 가수 페이지 반환 시, 노래별 가수 이름 response 추가 * refactor: 불필요한 주석, 개행 제거 * refactor: MemberPart 기능 통합 * fix: dev data.sql 아티스트 추가 * feat: 인메모리 아티스트 업데이트 API 추가 * refactor: 검색 API와 가수 API 분리 * fix: song, singer 검색 조건 수정 * data: artist, 동의어 데이터 추가
- Loading branch information
Showing
73 changed files
with
2,640 additions
and
596 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
backend/src/main/java/shook/shook/song/application/ArtistSearchService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package shook.shook.song.application; | ||
|
||
import java.util.Comparator; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Stream; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import shook.shook.song.application.dto.ArtistResponse; | ||
import shook.shook.song.application.dto.ArtistWithSongSearchResponse; | ||
import shook.shook.song.domain.Artist; | ||
import shook.shook.song.domain.InMemoryArtistSynonyms; | ||
import shook.shook.song.domain.InMemoryArtistSynonymsGenerator; | ||
import shook.shook.song.domain.Song; | ||
import shook.shook.song.domain.repository.ArtistRepository; | ||
import shook.shook.song.domain.repository.SongRepository; | ||
import shook.shook.song.domain.repository.dto.SongTotalLikeCountDto; | ||
import shook.shook.song.exception.ArtistException; | ||
|
||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
@Service | ||
public class ArtistSearchService { | ||
|
||
private static final int TOP_SONG_COUNT_OF_ARTIST = 3; | ||
|
||
private final InMemoryArtistSynonyms inMemoryArtistSynonyms; | ||
private final ArtistRepository artistRepository; | ||
private final SongRepository songRepository; | ||
private final InMemoryArtistSynonymsGenerator generator; | ||
|
||
public List<ArtistResponse> searchArtistsByKeyword(final String keyword) { | ||
final List<Artist> artists = findArtistsStartsWithKeyword(keyword); | ||
|
||
return artists.stream() | ||
.map(ArtistResponse::from) | ||
.toList(); | ||
} | ||
|
||
private List<Artist> findArtistsStartsWithKeyword(final String keyword) { | ||
final List<Artist> artistsFoundByName = inMemoryArtistSynonyms.findAllArtistsNameStartsWith( | ||
keyword); | ||
final List<Artist> artistsFoundBySynonym = inMemoryArtistSynonyms.findAllArtistsHavingSynonymStartsWith( | ||
keyword); | ||
|
||
return removeDuplicateArtistResultAndSortByName(artistsFoundByName, artistsFoundBySynonym); | ||
} | ||
|
||
private List<Artist> removeDuplicateArtistResultAndSortByName(final List<Artist> firstResult, | ||
final List<Artist> secondResult) { | ||
return Stream.concat(firstResult.stream(), secondResult.stream()) | ||
.distinct() | ||
.sorted(Comparator.comparing(Artist::getArtistName)) | ||
.toList(); | ||
} | ||
|
||
public List<ArtistWithSongSearchResponse> searchArtistsAndTopSongsByKeyword( | ||
final String keyword) { | ||
final List<Artist> artists = findArtistsStartsOrEndsWithKeyword(keyword); | ||
|
||
return artists.stream() | ||
.map(artist -> ArtistWithSongSearchResponse.of( | ||
artist, | ||
getSongsOfArtistSortedByLikeCount(artist).size(), | ||
getTopSongsOfArtist(artist)) | ||
) | ||
.toList(); | ||
} | ||
|
||
private List<Artist> findArtistsStartsOrEndsWithKeyword(final String keyword) { | ||
final List<Artist> artistsFoundByName = inMemoryArtistSynonyms.findAllArtistsNameStartsOrEndsWith( | ||
keyword); | ||
final List<Artist> artistsFoundBySynonym = inMemoryArtistSynonyms.findAllArtistsHavingSynonymStartsOrEndsWith( | ||
keyword); | ||
|
||
return removeDuplicateArtistResultAndSortByName(artistsFoundByName, artistsFoundBySynonym); | ||
} | ||
|
||
private List<Song> getTopSongsOfArtist(final Artist artist) { | ||
final List<Song> songs = getSongsOfArtistSortedByLikeCount(artist); | ||
if (songs.size() < TOP_SONG_COUNT_OF_ARTIST) { | ||
return songs; | ||
} | ||
|
||
return songs.subList(0, TOP_SONG_COUNT_OF_ARTIST); | ||
} | ||
|
||
private List<Song> getSongsOfArtistSortedByLikeCount(final Artist artist) { | ||
final List<SongTotalLikeCountDto> songsWithTotalLikeCount = songRepository.findAllSongsWithTotalLikeCountByArtist( | ||
artist); | ||
|
||
return songsWithTotalLikeCount.stream() | ||
.sorted(Comparator.comparing(SongTotalLikeCountDto::getTotalLikeCount, | ||
Comparator.reverseOrder()) | ||
.thenComparing(songWithTotalLikeCount -> songWithTotalLikeCount.getSong().getId(), | ||
Comparator.reverseOrder()) | ||
) | ||
.map(SongTotalLikeCountDto::getSong) | ||
.toList(); | ||
} | ||
|
||
public ArtistWithSongSearchResponse searchAllSongsByArtist(final long artistId) { | ||
final Artist artist = findArtistById(artistId); | ||
final List<Song> songs = getSongsOfArtistSortedByLikeCount(artist); | ||
|
||
return ArtistWithSongSearchResponse.of(artist, songs.size(), songs); | ||
} | ||
|
||
private Artist findArtistById(final long artistId) { | ||
return artistRepository.findById(artistId) | ||
.orElseThrow(() -> new ArtistException.NotExistException( | ||
Map.of("ArtistId", String.valueOf(artistId)) | ||
)); | ||
} | ||
|
||
public void updateArtistSynonymFromDatabase() { | ||
generator.initialize(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
backend/src/main/java/shook/shook/song/application/dto/ArtistResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
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.song.domain.Artist; | ||
|
||
@Schema(description = "아티스트를 통한 아티스트, 해당 아티스트의 노래 검색 결과") | ||
@AllArgsConstructor(access = AccessLevel.PRIVATE) | ||
@Getter | ||
public class ArtistResponse { | ||
|
||
@Schema(description = "아티스트 id", example = "1") | ||
private final Long id; | ||
|
||
@Schema(description = "가수 이름", example = "가수") | ||
private final String singer; | ||
|
||
@Schema(description = "가수 대표 이미지 url", example = "https://image.com/artist-profile.jpg") | ||
private final String profileImageUrl; | ||
|
||
public static ArtistResponse from(final Artist artist) { | ||
return new ArtistResponse( | ||
artist.getId(), | ||
artist.getArtistName(), | ||
artist.getProfileImageUrl() | ||
); | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
backend/src/main/java/shook/shook/song/application/dto/ArtistWithSongSearchResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package shook.shook.song.application.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import java.util.List; | ||
import lombok.AccessLevel; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import shook.shook.song.domain.Artist; | ||
import shook.shook.song.domain.Song; | ||
|
||
@Schema(description = "아티스트를 통한 아티스트, 해당 아티스트의 노래 검색 결과") | ||
@AllArgsConstructor(access = AccessLevel.PRIVATE) | ||
@Getter | ||
public class ArtistWithSongSearchResponse { | ||
|
||
@Schema(description = "아티스트 id", example = "1") | ||
private final Long id; | ||
|
||
@Schema(description = "가수 이름", example = "가수") | ||
private final String singer; | ||
|
||
@Schema(description = "가수 대표 이미지 url", example = "https://image.com/artist-profile.jpg") | ||
private final String profileImageUrl; | ||
|
||
@Schema(description = "가수 노래 총 개수", example = "10") | ||
private final int totalSongCount; | ||
|
||
@Schema(description = "아티스트의 노래 목록") | ||
private final List<SongSearchResponse> songs; | ||
|
||
public static ArtistWithSongSearchResponse of(final Artist artist, final int totalSongCount, | ||
final List<Song> songs) { | ||
return new ArtistWithSongSearchResponse( | ||
artist.getId(), | ||
artist.getArtistName(), | ||
artist.getProfileImageUrl(), | ||
totalSongCount, | ||
convertToSongSearchResponse(songs, artist.getArtistName()) | ||
); | ||
} | ||
|
||
private static List<SongSearchResponse> convertToSongSearchResponse(final List<Song> songs, | ||
final String singer) { | ||
return songs.stream() | ||
.map(song -> SongSearchResponse.from(song, singer)) | ||
.toList(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
backend/src/main/java/shook/shook/song/application/dto/SongSearchResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.song.domain.Song; | ||
|
||
@Schema(description = "검색 결과 (가수, 가수의 노래) 응답") | ||
@AllArgsConstructor(access = AccessLevel.PRIVATE) | ||
@Getter | ||
public class SongSearchResponse { | ||
|
||
@Schema(description = "노래 id", example = "1") | ||
private final Long id; | ||
|
||
@Schema(description = "노래 제목", example = "제목") | ||
private final String title; | ||
|
||
@Schema(description = "노래 앨범 커버 이미지 url", example = "https://image.com/album-cover.jpg") | ||
private final String albumCoverUrl; | ||
|
||
@Schema(description = "노래 비디오 길이", example = "247") | ||
private final int videoLength; | ||
|
||
@Schema(description = "가수 이름", example = "가수") | ||
private final String singer; | ||
|
||
public static SongSearchResponse from(final Song song, final String singer) { | ||
return new SongSearchResponse( | ||
song.getId(), | ||
song.getTitle(), | ||
song.getAlbumCoverUrl(), | ||
song.getLength(), | ||
singer | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.