From ac0e8c50f972c58bf38576d79fdfa6ec16e84ef4 Mon Sep 17 00:00:00 2001 From: hong-sile Date: Sun, 4 Aug 2024 16:27:11 +0900 Subject: [PATCH 1/6] =?UTF-8?q?chore:=20disable=ED=95=A0=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20gitignore=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 850a2ec..65da6ae 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,6 @@ out/ ### personal file test.json -openapi3.json \ No newline at end of file +openapi3.json +# api +external/ \ No newline at end of file From 6cb1b4cbbe128b5b16fe0e3d22019151979fa732 Mon Sep 17 00:00:00 2001 From: hong-sile Date: Sun, 4 Aug 2024 16:42:30 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20oauth=20api=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=EC=99=80=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=EC=A0=95(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/play/pluv/oauth/controller/OAuthController.java | 2 +- src/test/java/play/pluv/api/OAuthApiTest.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/play/pluv/oauth/controller/OAuthController.java b/src/main/java/play/pluv/oauth/controller/OAuthController.java index 01a1a98..5f00a69 100644 --- a/src/main/java/play/pluv/oauth/controller/OAuthController.java +++ b/src/main/java/play/pluv/oauth/controller/OAuthController.java @@ -14,7 +14,7 @@ public class OAuthController { private final OAuthService oauthService; - @GetMapping("/youtube/oauth/token") + @GetMapping("/oauth/youtube/token") public BaseResponse getToken(@RequestParam final String code) { final var accessToken = oauthService.getAccessToken(code); return BaseResponse.ok(accessToken); diff --git a/src/test/java/play/pluv/api/OAuthApiTest.java b/src/test/java/play/pluv/api/OAuthApiTest.java index b5ef20f..0865ab4 100644 --- a/src/test/java/play/pluv/api/OAuthApiTest.java +++ b/src/test/java/play/pluv/api/OAuthApiTest.java @@ -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; @@ -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("상태 코드에 해당하는 메시지"), From 3b8d06c59b0084968e2012399f43ad7e45f415ec Mon Sep 17 00:00:00 2001 From: hong-sile Date: Sun, 4 Aug 2024 17:30:03 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20thumbnail=20urls=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pluv/oauth/google/dto/ThumbnailUrls.java | 17 +++++++++++++++++ .../google/dto/YoutubePlayListResponses.java | 12 +----------- 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 src/main/java/play/pluv/oauth/google/dto/ThumbnailUrls.java diff --git a/src/main/java/play/pluv/oauth/google/dto/ThumbnailUrls.java b/src/main/java/play/pluv/oauth/google/dto/ThumbnailUrls.java new file mode 100644 index 0000000..65d0813 --- /dev/null +++ b/src/main/java/play/pluv/oauth/google/dto/ThumbnailUrls.java @@ -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) { + + } +} diff --git a/src/main/java/play/pluv/oauth/google/dto/YoutubePlayListResponses.java b/src/main/java/play/pluv/oauth/google/dto/YoutubePlayListResponses.java index 8ab160c..729b56f 100644 --- a/src/main/java/play/pluv/oauth/google/dto/YoutubePlayListResponses.java +++ b/src/main/java/play/pluv/oauth/google/dto/YoutubePlayListResponses.java @@ -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; @@ -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(); } @@ -33,15 +32,6 @@ private record YoutubePlayListDetail( String title, ThumbnailUrls thumbnails ) { - private record ThumbnailUrls( - @JsonProperty("default") - Thumbnail thumbnail - ) { - - private record Thumbnail(String url) { - - } - } } } } From 1c449be275ec45ab9f007460a46d15243494f8b5 Mon Sep 17 00:00:00 2001 From: hong-sile Date: Sun, 4 Aug 2024 17:30:17 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EC=9C=A0=ED=8A=9C=EB=B8=8C=20?= =?UTF-8?q?=EB=AE=A4=EC=A7=81=20=EC=9D=8C=EC=95=85=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80(#4?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pluv/oauth/google/GoogleApiClient.java | 7 ++++ .../pluv/oauth/google/GoogleConnector.java | 17 ++++++++- .../google/dto/YoutubeMusicResponses.java | 36 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/main/java/play/pluv/oauth/google/dto/YoutubeMusicResponses.java diff --git a/src/main/java/play/pluv/oauth/google/GoogleApiClient.java b/src/main/java/play/pluv/oauth/google/GoogleApiClient.java index 7ef4b22..ce2a92e 100644 --- a/src/main/java/play/pluv/oauth/google/GoogleApiClient.java +++ b/src/main/java/play/pluv/oauth/google/GoogleApiClient.java @@ -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 { @@ -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 + ); } diff --git a/src/main/java/play/pluv/oauth/google/GoogleConnector.java b/src/main/java/play/pluv/oauth/google/GoogleConnector.java index 10f97cf..a27db3f 100644 --- a/src/main/java/play/pluv/oauth/google/GoogleConnector.java +++ b/src/main/java/play/pluv/oauth/google/GoogleConnector.java @@ -2,9 +2,11 @@ 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; @@ -12,6 +14,7 @@ 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; @@ -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) { @@ -42,7 +46,18 @@ public List getPlayList(final String accessToken) { @Override public List getMusics(final String playListId, final String accessToken) { - return List.of(); + final List 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 diff --git a/src/main/java/play/pluv/oauth/google/dto/YoutubeMusicResponses.java b/src/main/java/play/pluv/oauth/google/dto/YoutubeMusicResponses.java new file mode 100644 index 0000000..0f20615 --- /dev/null +++ b/src/main/java/play/pluv/oauth/google/dto/YoutubeMusicResponses.java @@ -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 items +) { + + public List 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 + ) { + + } + } +} From 70da170384f3f7586324458e196528e01bf72cc4 Mon Sep 17 00:00:00 2001 From: hong-sile Date: Sun, 4 Aug 2024 17:35:40 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20Composite=EC=9D=84=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20PlayList=20=EC=9D=8C=EC=95=85=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/PlayListConnectorComposite.java | 11 +++++++++-- .../pluv/playlist/application/PlayListService.java | 4 +--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/play/pluv/playlist/application/PlayListConnectorComposite.java b/src/main/java/play/pluv/playlist/application/PlayListConnectorComposite.java index 59e6794..d4a8d4b 100644 --- a/src/main/java/play/pluv/playlist/application/PlayListConnectorComposite.java +++ b/src/main/java/play/pluv/playlist/application/PlayListConnectorComposite.java @@ -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 @@ -23,8 +24,14 @@ public PlayListConnectorComposite(final Set playListConnector .collect(toMap(PlayListConnector::supportedType, identity())); } - public List getPlayList (final MusicStreaming serverType, final String authKey) { - return getClient(serverType).getPlayList(authKey); + public List getPlayList(final MusicStreaming serverType, final String accessToken) { + return getClient(serverType).getPlayList(accessToken); + } + + public List getMusics( + final MusicStreaming serverType, final String accessToken, final String playListId + ) { + return getClient(serverType).getMusics(playListId, accessToken); } private PlayListConnector getClient(final MusicStreaming serverType) { diff --git a/src/main/java/play/pluv/playlist/application/PlayListService.java b/src/main/java/play/pluv/playlist/application/PlayListService.java index 2360770..81efbfb 100644 --- a/src/main/java/play/pluv/playlist/application/PlayListService.java +++ b/src/main/java/play/pluv/playlist/application/PlayListService.java @@ -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; @@ -13,7 +12,6 @@ public class PlayListService { private final PlayListConnectorComposite playListConnectorComposite; - private final SpotifyConnector spotifyConnector; public List getPlayLists(final String accessToken, final MusicStreaming source) { return playListConnectorComposite.getPlayList(source, accessToken); @@ -22,6 +20,6 @@ public List getPlayLists(final String accessToken, final MusicStreamin public List getPlayListMusics( final String playListId, final String accessToken, final MusicStreaming source ) { - return spotifyConnector.getMusics(playListId, accessToken); + return playListConnectorComposite.getMusics(source, playListId, accessToken); } } From a60b0b4294469b5575a1e0f887b0ff91ac69a2ce Mon Sep 17 00:00:00 2001 From: hong-sile Date: Sun, 4 Aug 2024 17:36:58 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=EC=9C=A0=ED=8A=9C=EB=B8=8C=20?= =?UTF-8?q?=ED=94=8C=EB=A0=88=EC=9D=B4=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20api=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PlayListController.java | 9 +++++ .../java/play/pluv/api/PlayListApiTest.java | 39 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/java/play/pluv/playlist/controller/PlayListController.java b/src/main/java/play/pluv/playlist/controller/PlayListController.java index 78f5391..d7984cb 100644 --- a/src/main/java/play/pluv/playlist/controller/PlayListController.java +++ b/src/main/java/play/pluv/playlist/controller/PlayListController.java @@ -52,4 +52,13 @@ public BaseResponse> readSpotifyMusics( final List response = PlayListMusicResponse.createList(musics); return BaseResponse.ok(response); } + + @PostMapping("/youtube/{id}/read") + public BaseResponse> readYoutubeMusics( + @RequestBody final OAuthAccessToken accessToken, @PathVariable final String id + ) { + final var musics = playListService.getPlayListMusics(id, accessToken.accessToken(), YOUTUBE); + final List response = PlayListMusicResponse.createList(musics); + return BaseResponse.ok(response); + } } diff --git a/src/test/java/play/pluv/api/PlayListApiTest.java b/src/test/java/play/pluv/api/PlayListApiTest.java index 9c41551..e0b0e54 100644 --- a/src/test/java/play/pluv/api/PlayListApiTest.java +++ b/src/test/java/play/pluv/api/PlayListApiTest.java @@ -113,7 +113,7 @@ public class PlayListApiTest extends ApiTest { } @Test - void 플레이리스트의_음악을_읽어서_반환해준다() throws Exception { + void 스포티파이_플레이리스트의_음악을_읽어서_반환해준다() throws Exception { final List playListMusics = List.of( new PlayListMusic( @@ -148,4 +148,41 @@ public class PlayListApiTest extends ApiTest { ) )); } + + @Test + void 유튜브_플레이리스트의_음악을_읽어서_반환해준다() throws Exception { + final List 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 + ) + )); + } }