Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#46 유튜브 플리 상세 조회 #55

Merged
merged 6 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ out/

### personal file
test.json
openapi3.json
openapi3.json
# api
external/
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class OAuthController {

private final OAuthService oauthService;

@GetMapping("/youtube/oauth/token")
@GetMapping("/oauth/youtube/token")
public BaseResponse<GoogleAccessToken> getToken(@RequestParam final String code) {
final var accessToken = oauthService.getAccessToken(code);
return BaseResponse.ok(accessToken);
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/play/pluv/oauth/google/GoogleApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.web.service.annotation.PostExchange;
import play.pluv.oauth.google.dto.GoogleIdTokenResponse;
import play.pluv.oauth.google.dto.GoogleOAuthResponse;
import play.pluv.oauth.google.dto.YoutubeMusicResponses;
import play.pluv.oauth.google.dto.YoutubePlayListResponses;

public interface GoogleApiClient {
Expand All @@ -21,4 +22,10 @@ public interface GoogleApiClient {

@GetExchange("https://www.googleapis.com/youtube/v3/playlists?part=snippet&mine=true&maxResults=50")
YoutubePlayListResponses getPlayList(@RequestHeader("Authorization") final String accessToken);

@GetExchange("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50")
YoutubeMusicResponses getPlayListItems(
@RequestHeader("Authorization") final String accessToken,
@RequestParam final String playlistId, @RequestParam(required = false) final String pageToken
);
}
17 changes: 16 additions & 1 deletion src/main/java/play/pluv/oauth/google/GoogleConnector.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

import static play.pluv.music.domain.MusicStreaming.YOUTUBE;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.service.GenericResponseService;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import play.pluv.music.domain.MusicStreaming;
import play.pluv.oauth.application.SocialLoginClient;
import play.pluv.oauth.domain.OAuthMemberInfo;
import play.pluv.oauth.google.dto.GoogleOAuthResponse;
import play.pluv.oauth.google.dto.YoutubeMusicResponses;
import play.pluv.playlist.application.PlayListConnector;
import play.pluv.playlist.domain.PlayList;
import play.pluv.playlist.domain.PlayListId;
Expand All @@ -27,6 +30,7 @@ public class GoogleConnector implements SocialLoginClient, PlayListConnector {

private final GoogleApiClient googleApiClient;
private final GoogleConfigProperty googleConfigProperty;
private final GenericResponseService responseBuilder;

@Override
public OAuthMemberInfo fetchMember(final String idToken) {
Expand All @@ -42,7 +46,18 @@ public List<PlayList> getPlayList(final String accessToken) {

@Override
public List<PlayListMusic> getMusics(final String playListId, final String accessToken) {
return List.of();
final List<PlayListMusic> result = new ArrayList<>();
String nextPageToken = null;

do {
YoutubeMusicResponses playListItem = googleApiClient.getPlayListItems(
CREATE_AUTH_HEADER.apply(accessToken), playListId, nextPageToken
);
result.addAll(playListItem.toPlayListMusics());
nextPageToken = playListItem.nextPageToken();
} while (nextPageToken != null);

return result;
}

@Override
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/play/pluv/oauth/google/dto/ThumbnailUrls.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package play.pluv.oauth.google.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

record ThumbnailUrls(
@JsonProperty("default")
Thumbnail thumbnail
) {

public String getUrl() {
return thumbnail.url;
}

private record Thumbnail(String url) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package play.pluv.oauth.google.dto;

import java.util.List;
import play.pluv.playlist.domain.PlayListMusic;

public record YoutubeMusicResponses(
String nextPageToken,
List<YoutubeMusicResponse> items
) {

public List<PlayListMusic> toPlayListMusics() {
return items.stream()
.map(YoutubeMusicResponse::toPlayListMusic)
.toList();
}

private record YoutubeMusicResponse(
String id,
YoutubeMusicDetail snippet
) {

private PlayListMusic toPlayListMusic() {
return PlayListMusic.builder()
.imageUrl(snippet.thumbnails().getUrl())
.title(snippet().title())
.artistNames(List.of())
.build();
}

private record YoutubeMusicDetail(
String title, ThumbnailUrls thumbnails
) {

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static play.pluv.music.domain.MusicStreaming.YOUTUBE;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import play.pluv.playlist.domain.PlayList;
import play.pluv.playlist.domain.PlayListId;
Expand All @@ -24,7 +23,7 @@ private record YoutubePlayListResponse(
private PlayList toPlayList() {
return PlayList.builder()
.name(snippet.title)
.thumbNailUrl(snippet.thumbnails.thumbnail.url)
.thumbNailUrl(snippet.thumbnails.getUrl())
.playListId(new PlayListId(id, YOUTUBE))
.build();
}
Expand All @@ -33,15 +32,6 @@ private record YoutubePlayListDetail(
String title, ThumbnailUrls thumbnails
) {

private record ThumbnailUrls(
@JsonProperty("default")
Thumbnail thumbnail
) {

private record Thumbnail(String url) {

}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.stereotype.Component;
import play.pluv.music.domain.MusicStreaming;
import play.pluv.playlist.domain.PlayList;
import play.pluv.playlist.domain.PlayListMusic;
import play.pluv.playlist.exception.PlayListException;

@Component
Expand All @@ -23,8 +24,14 @@ public PlayListConnectorComposite(final Set<PlayListConnector> playListConnector
.collect(toMap(PlayListConnector::supportedType, identity()));
}

public List<PlayList> getPlayList (final MusicStreaming serverType, final String authKey) {
return getClient(serverType).getPlayList(authKey);
public List<PlayList> getPlayList(final MusicStreaming serverType, final String accessToken) {
return getClient(serverType).getPlayList(accessToken);
}

public List<PlayListMusic> getMusics(
final MusicStreaming serverType, final String accessToken, final String playListId
) {
return getClient(serverType).getMusics(playListId, accessToken);
}

private PlayListConnector getClient(final MusicStreaming serverType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import play.pluv.music.domain.MusicStreaming;
import play.pluv.oauth.spotify.SpotifyConnector;
import play.pluv.playlist.domain.PlayList;
import play.pluv.playlist.domain.PlayListMusic;

Expand All @@ -13,7 +12,6 @@
public class PlayListService {

private final PlayListConnectorComposite playListConnectorComposite;
private final SpotifyConnector spotifyConnector;

public List<PlayList> getPlayLists(final String accessToken, final MusicStreaming source) {
return playListConnectorComposite.getPlayList(source, accessToken);
Expand All @@ -22,6 +20,6 @@ public List<PlayList> getPlayLists(final String accessToken, final MusicStreamin
public List<PlayListMusic> getPlayListMusics(
final String playListId, final String accessToken, final MusicStreaming source
) {
return spotifyConnector.getMusics(playListId, accessToken);
return playListConnectorComposite.getMusics(source, playListId, accessToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,13 @@ public BaseResponse<List<PlayListMusicResponse>> readSpotifyMusics(
final List<PlayListMusicResponse> response = PlayListMusicResponse.createList(musics);
return BaseResponse.ok(response);
}

@PostMapping("/youtube/{id}/read")
public BaseResponse<List<PlayListMusicResponse>> readYoutubeMusics(
@RequestBody final OAuthAccessToken accessToken, @PathVariable final String id
) {
final var musics = playListService.getPlayListMusics(id, accessToken.accessToken(), YOUTUBE);
final List<PlayListMusicResponse> response = PlayListMusicResponse.createList(musics);
return BaseResponse.ok(response);
}
}
8 changes: 7 additions & 1 deletion src/test/java/play/pluv/api/OAuthApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import static org.springframework.restdocs.payload.JsonFieldType.STRING;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
Expand All @@ -22,9 +24,13 @@ public class OAuthApiTest extends ApiTest {

when(oAuthService.getAccessToken(authCode)).thenReturn(accessToken);

mockMvc.perform(get("/youtube/oauth/token?code=" + authCode))
mockMvc.perform(get("/oauth/youtube/token")
.param("code", authCode))
.andExpect(status().isOk())
.andDo(document("get-google-accessToken",
queryParameters(
parameterWithName("code").description("google의 auth code")
),
responseFields(
fieldWithPath("code").type(NUMBER).description("상태 코드"),
fieldWithPath("msg").type(STRING).description("상태 코드에 해당하는 메시지"),
Expand Down
39 changes: 38 additions & 1 deletion src/test/java/play/pluv/api/PlayListApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public class PlayListApiTest extends ApiTest {
}

@Test
void 플레이리스트의_음악을_읽어서_반환해준다() throws Exception {
void 스포티파이_플레이리스트의_음악을_읽어서_반환해준다() throws Exception {
final List<PlayListMusic> playListMusics =
List.of(
new PlayListMusic(
Expand Down Expand Up @@ -148,4 +148,41 @@ public class PlayListApiTest extends ApiTest {
)
));
}

@Test
void 유튜브_플레이리스트의_음악을_읽어서_반환해준다() throws Exception {
final List<PlayListMusic> playListMusics =
List.of(
new PlayListMusic(
"좋은 날", List.of("아이유"), "KRA381001057",
"https://i.scdn.co/image/ab67616d00001e0215cf3110f19687b1a24943d1"
),
new PlayListMusic(
"ㅈㅣㅂ", List.of("hanroro"), "KRA381001234",
"https://i.scdn.co/image/ab67616d00001e0215cf3110f19687b1a22314"
)
);

final PlayListMusicReadRequest request = new PlayListMusicReadRequest("accessToken");
final String requestBody = objectMapper.writeValueAsString(request);

when(playListService.getPlayListMusics(any(), any(), any())).thenReturn(playListMusics);

mockMvc.perform(post("/playlist/youtube/{id}/read", "playListId")
.contentType(APPLICATION_JSON_VALUE)
.content(requestBody))
.andExpect(status().isOk())
.andDo(document("read-playList-musics",
pathParameters(
parameterWithName("id").description("플레이리스트의 식별자")
),
requestFields(
fieldWithPath("accessToken").type(STRING)
.description("플레이리스트 제공자의 oauth accessToken")
),
responseFields(
MUSIC_RESPONSE
)
));
}
}
Loading