Skip to content

Commit

Permalink
Merge pull request #83 from akai-org/issue-58
Browse files Browse the repository at this point in the history
issue 58 - add get playlist songs endpoint
  • Loading branch information
mati2251 authored Mar 6, 2024
2 parents dd382f3 + 699df0a commit ab23c7a
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 42 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
.idea

# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build
Original file line number Diff line number Diff line change
Expand Up @@ -20,80 +20,95 @@ class PlaylistHandler(
fun getCurrentPlaylists(serverRequest: ServerRequest): Mono<ServerResponse> {
val limit = serverRequest.queryParam("limit").orElse("20").toInt()
val offset = serverRequest.queryParam("offset").orElse("0").toInt()
val body = spotifyPlaylistsService.getCurrentPlaylists(offset, limit).flatMap(PlaylistUtils.toPlaylists)
val body =
spotifyPlaylistsService
.getCurrentPlaylists(offset, limit)
.flatMap(PlaylistUtils.toPlaylists)
return ServerResponse.ok().body(body)
}

fun createPlaylist(serverRequest: ServerRequest): Mono<ServerResponse> {
val requestBody = serverRequest.bodyToMono(SpotifyCreatePlaylistRequestBody::class.java)
val userId = ReactiveSecurityContextHolder.getContext().map { it.authentication.principal.toString() }
val responseBody = Mono.zip(requestBody, userId).flatMap {
spotifyPlaylistsService.createPlaylist(it.t2, it.t1)
}.map {
PlaylistUtils.toPlaylist(it)
}
val userId =
ReactiveSecurityContextHolder.getContext().map {
it.authentication.principal.toString()
}
val responseBody =
Mono.zip(requestBody, userId)
.flatMap { spotifyPlaylistsService.createPlaylist(it.t2, it.t1) }
.map { PlaylistUtils.toPlaylist(it) }
return ServerResponse.ok().body(responseBody)
}

fun getCurrentPlaylistsByName(serverRequest: ServerRequest): Mono<ServerResponse> {
val name = serverRequest.queryParam("name").orElse("")

val body = spotifyPlaylistsService
.getCurrentPlaylists(limit = 999)
.map { it ->
SpotifyPlaylistsResponseBody(
it.total,
it.limit,
it.offset,
it.items.filter { it.name.matches(name.toSearchableRegex()) },
)
}
.flatMap(PlaylistUtils.toPlaylists)
val body =
spotifyPlaylistsService
.getCurrentPlaylists(limit = 999)
.map { it ->
SpotifyPlaylistsResponseBody(
it.total,
it.limit,
it.offset,
it.items.filter { it.name.matches(name.toSearchableRegex()) },
)
}
.flatMap(PlaylistUtils.toPlaylists)
return ServerResponse.ok().body(body)
}

fun getPlaylistDetails(serverRequest: ServerRequest): Mono<ServerResponse> {
val playlistId = serverRequest.pathVariable("playlist-id")
val playlistDetails = spotifyPlaylistsService.getPlaylist(playlistId).flatMap(PlaylistUtils.toPlaylistDetails)
val userDetails = playlistDetails.flatMap {
spotifyUserService.getExternalUserProfile(it.owner.id)
}
return playlistDetails.zipWith(userDetails)
.flatMap {
val picture = if (it.t2.images.isNotEmpty()) {
val playlistDetails =
spotifyPlaylistsService
.getPlaylist(playlistId)
.flatMap(PlaylistUtils.toPlaylistDetails)
val userDetails =
playlistDetails.flatMap { spotifyUserService.getExternalUserProfile(it.owner.id) }
return playlistDetails.zipWith(userDetails).flatMap {
val picture =
if (it.t2.images.isNotEmpty()) {
it.t2.images[0].url
} else {
null
}
it.t1.owner.picture = picture
ServerResponse.ok().body(Mono.just(it.t1))
}
it.t1.owner.picture = picture
ServerResponse.ok().body(Mono.just(it.t1))
}
}

fun updatePlaylistDetails(serverRequest: ServerRequest): Mono<ServerResponse> {
val playlistId = serverRequest.pathVariable("playlist-id")
val requestBody = serverRequest.bodyToMono(SpotifyCreatePlaylistRequestBody::class.java)
val responseBody = Mono.zip(requestBody, Mono.just(playlistId)).flatMap {
spotifyPlaylistsService.updatePlaylistDetails(it.t2, it.t1)
}.map {
PlaylistUtils.toPlaylist(it)
}
val responseBody =
Mono.zip(requestBody, Mono.just(playlistId))
.flatMap { spotifyPlaylistsService.updatePlaylistDetails(it.t2, it.t1) }
.map { PlaylistUtils.toPlaylist(it) }
return ServerResponse.ok().body(responseBody)
}

fun getPlaylistTracks(serverRequest: ServerRequest): Mono<ServerResponse> {
val playlistId = serverRequest.pathVariable("playlist-id")
val playlistTracks =
spotifyPlaylistsService
.getSpotifyPlaylistTracks(playlistId)
.flatMap(PlaylistUtils.toSpotifyPlaylistTracks)
return ServerResponse.ok().body(playlistTracks)
}
fun changePlaylistCover(serverRequest: ServerRequest): Mono<ServerResponse> {
val playlistId = serverRequest.pathVariable("playlist-id")
val requestBody = serverRequest.bodyToMono(String::class.java)

val operation = requestBody.flatMap { spotifyPlaylistsService.changePlaylistCover(playlistId, it) }
val operation =
requestBody.flatMap { spotifyPlaylistsService.changePlaylistCover(playlistId, it) }
return operation.then(ServerResponse.ok().build())
}
}

private fun String.toSearchableRegex(): Regex {
var rename = ".*"
for (ch in this)
rename += "[$ch].*"
for (ch in this) rename += "[$ch].*"

return Regex(rename, RegexOption.IGNORE_CASE)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package pl.akai.fillist.web.models

import kotlinx.serialization.Serializable
import pl.akai.fillist.web.spotifywrapper.models.Track

@Serializable
data class PlaylistTracks(
val href: String,
val limit: Int,
val next: String?,
val offset: Int,
val previous: String?,
val total: Int,
val items: List<Track>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class PlaylistRouter {
GET("/playlists/name", playlistHandler::getCurrentPlaylistsByName)
GET("/playlists/{playlist-id}/details", playlistHandler::getPlaylistDetails)
PUT("/playlists/{playlist-id}", playlistHandler::updatePlaylistDetails)
GET("/playlists/{playlist-id}/tracks", playlistHandler::getPlaylistTracks)
PUT("/playlists/{playlist-id}/cover", playlistHandler::changePlaylistCover)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyCreatePlaylistRequestBody
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyPlaylist
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyPlaylistTracks
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyPlaylistsResponseBody
import reactor.core.publisher.Mono

Expand Down Expand Up @@ -37,6 +38,11 @@ class SpotifyPlaylistsService @Autowired constructor(
.then(getPlaylist(playlistId))
}

fun getSpotifyPlaylistTracks(playlistId: String): Mono<SpotifyPlaylistTracks> {
return spotifyClient.get().uri("/playlists/$playlistId/tracks").retrieve()
.bodyToMono(SpotifyPlaylistTracks::class.java)
}

fun changePlaylistCover(playlistId: String, imageData: String): Mono<Void> {
return spotifyClient.put().uri("/playlists/$playlistId/images")
.bodyValue(imageData)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package pl.akai.fillist.web.spotifywrapper.playlists.models

import kotlinx.serialization.Serializable
import pl.akai.fillist.web.spotifywrapper.models.Track

@Serializable
data class SpotifyTrackWrapper(
val track: Track,
)

@Serializable
data class SpotifyPlaylistTracks(
val href: String,
val limit: Int,
val next: String?,
val offset: Int,
val previous: String?,
val total: Int,
val items: List<SpotifyTrackWrapper>,
)
17 changes: 17 additions & 0 deletions backend/src/main/kotlin/pl/akai/fillist/web/utils/PlaylistUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package pl.akai.fillist.web.utils

import pl.akai.fillist.web.models.Playlist
import pl.akai.fillist.web.models.PlaylistDetails
import pl.akai.fillist.web.models.PlaylistTracks
import pl.akai.fillist.web.models.PlaylistsResponseBody
import pl.akai.fillist.web.spotifywrapper.models.OwnerDetails
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyPlaylist
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyPlaylistTracks
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyPlaylistsResponseBody
import reactor.core.publisher.Mono

Expand Down Expand Up @@ -69,4 +71,19 @@ object PlaylistUtils {
}
return name
}

val toSpotifyPlaylistTracks: (SpotifyPlaylistTracks) -> Mono<PlaylistTracks> = { playlistTracks ->
Mono.just(playlistTracks)
.map {
PlaylistTracks(
href = playlistTracks.href,
limit = playlistTracks.limit,
next = playlistTracks.next,
offset = playlistTracks.offset,
previous = playlistTracks.previous,
total = playlistTracks.total,
items = playlistTracks.items.map { it.track },
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,11 @@ import pl.akai.fillist.configurations.SpotifyClientConfig
import pl.akai.fillist.configurations.WebTestClientConfig
import pl.akai.fillist.web.models.Playlist
import pl.akai.fillist.web.models.PlaylistDetails
import pl.akai.fillist.web.models.PlaylistTracks
import pl.akai.fillist.web.models.PlaylistsResponseBody
import pl.akai.fillist.web.spotifywrapper.models.ExternalUrls
import pl.akai.fillist.web.spotifywrapper.models.Image
import pl.akai.fillist.web.spotifywrapper.models.Owner
import pl.akai.fillist.web.spotifywrapper.models.*
import pl.akai.fillist.web.spotifywrapper.playlists.SpotifyPlaylistsService
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyCreatePlaylistRequestBody
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyPlaylist
import pl.akai.fillist.web.spotifywrapper.playlists.models.SpotifyPlaylistsResponseBody
import pl.akai.fillist.web.spotifywrapper.playlists.models.*
import reactor.core.publisher.Mono

@SpringBootTest()
Expand Down Expand Up @@ -199,6 +196,49 @@ class PlaylistsRouterTests {
}
}

@Test
fun getPlaylistTracks() {
`when`(playlistsService.getSpotifyPlaylistTracks(anyOrNull())).thenReturn(
Mono.just(
SpotifyPlaylistTracks(
href = "href",
limit = 1,
offset = 0,
total = 1,
next = "",
previous = "",
items = listOf(
SpotifyTrackWrapper(
track = Track(
id = "1WiIsyGhDQ0ZAD4vnEjOm3",
name = "name",
uri = "uri",
album = Album(
id = "id",
name = "name",
artists = listOf(),
href = "",
uri = "",
),
artists = listOf(),
),
),
),
),
),
)
webTestClient.get().uri("/playlists/1WiIsyGhDQ0ZAD4vnEjOm3/tracks").exchange()
.expectStatus().isOk.expectBody(PlaylistTracks::class.java).value {
assertNotNull(it.items)
assertEquals(it.items.size, 1)
assertEquals(it.items[0].id, "1WiIsyGhDQ0ZAD4vnEjOm3")
assertEquals(it.items[0].name, "name")
assertEquals(it.items[0].uri, "uri")
assertEquals(it.items[0].album.name, "name")
assertEquals(it.items[0].album.id, "id")
}
}

@Test
fun changePlaylistCover() {
val playlistId = "1B9WyPlzPbkyGMWcGlrgP7"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ class SpotifyPlaylistsTests {
// assertEquals(updatedPlaylistBody.public, updatedPlaylist.public)
}

@Test
fun getPlaylistTracks() {
val playlistId = "3cEYpjA9oz9GiPac4AsH4n"
val playlistTracks = spotifyPlaylistsService.getSpotifyPlaylistTracks(playlistId).block()!!
assertNotNull(playlistTracks.href)
assertNotNull(playlistTracks.limit)
assertNotNull(playlistTracks.items)
assertNotNull(playlistTracks.total)
}

@Test
fun changePlaylistCoverSuccess() {
val playlistId = "id"
Expand Down

0 comments on commit ab23c7a

Please sign in to comment.