Skip to content

Commit a93ad50

Browse files
committed
feat(user): add create playlist endpoint
1 parent d0dcfaf commit a93ad50

File tree

4 files changed

+277
-0
lines changed

4 files changed

+277
-0
lines changed

src/main/java/se/michaelthelin/spotify/SpotifyApi.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,18 @@ public ChangePlaylistsDetailsRequest.Builder changePlaylistsDetails(String playl
12411241
.playlist_id(playlist_id);
12421242
}
12431243

1244+
/**
1245+
* Create a playlist for the current Spotify user.
1246+
*
1247+
* @param name The name for the new playlist.
1248+
* @return A {@link CreatePlaylistRequest.Builder}.
1249+
*/
1250+
public CreatePlaylistRequest.Builder createPlaylist(String name) {
1251+
return new CreatePlaylistRequest.Builder(accessToken)
1252+
.setDefaults(httpManager, scheme, host, port)
1253+
.name(name);
1254+
}
1255+
12441256
/**
12451257
* Get a list of the playlists owned or followed by the current Spotify user.
12461258
*
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package se.michaelthelin.spotify.requests.data.playlists;
2+
3+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
4+
import org.apache.hc.core5.http.ContentType;
5+
import org.apache.hc.core5.http.ParseException;
6+
import se.michaelthelin.spotify.exceptions.SpotifyWebApiException;
7+
import se.michaelthelin.spotify.model_objects.specification.Playlist;
8+
import se.michaelthelin.spotify.requests.data.AbstractDataRequest;
9+
10+
import java.io.IOException;
11+
12+
/**
13+
* Create a playlist for the current Spotify user. (The playlist will be empty until you add tracks.)
14+
* Each user is generally limited to a maximum of 11000 playlists.
15+
*/
16+
@JsonDeserialize(builder = CreatePlaylistRequest.Builder.class)
17+
public class CreatePlaylistRequest extends AbstractDataRequest<Playlist> {
18+
19+
/**
20+
* The private {@link CreatePlaylistRequest} constructor.
21+
*
22+
* @param builder A {@link CreatePlaylistRequest.Builder}.
23+
*/
24+
private CreatePlaylistRequest(final Builder builder) {
25+
super(builder);
26+
}
27+
28+
/**
29+
* Create a new playlist.
30+
*
31+
* @return The newly created {@link Playlist}.
32+
* @throws IOException In case of networking issues.
33+
* @throws SpotifyWebApiException The Web API returned an error further specified in this exception's root cause.
34+
*/
35+
public Playlist execute() throws
36+
IOException,
37+
SpotifyWebApiException,
38+
ParseException {
39+
return new Playlist.JsonUtil().createModelObject(postJson());
40+
}
41+
42+
/**
43+
* Builder class for building a {@link CreatePlaylistRequest}.
44+
*/
45+
public static final class Builder extends AbstractDataRequest.Builder<Playlist, Builder> {
46+
47+
/**
48+
* Create a new {@link CreatePlaylistRequest.Builder}.
49+
* <p>
50+
* Creating a public playlist for a user requires authorization of the {@code playlist-modify-public}
51+
* scope; creating a private playlist requires the {@code playlist-modify-private} scope.
52+
*
53+
* @param accessToken Required. A valid access token from the Spotify Accounts service.
54+
* @see <a href="https://developer.spotify.com/documentation/web-api/concepts/scopes">Spotify: Using Scopes</a>
55+
*/
56+
public Builder(final String accessToken) {
57+
super(accessToken);
58+
}
59+
60+
/**
61+
* The playlist name setter.
62+
*
63+
* @param name Required. The name for the new playlist, for example {@code "Your Coolest Playlist"}.
64+
* This name does not need to be unique; a user may have several playlists with the same name.
65+
* @return A {@link CreatePlaylistRequest.Builder}.
66+
*/
67+
public Builder name(final String name) {
68+
assert (name != null);
69+
assert (!name.isEmpty());
70+
return setBodyParameter("name", name);
71+
}
72+
73+
/**
74+
* The public status setter.
75+
*
76+
* @param public_ Optional. Defaults to {@code true}. If {@code true} the playlist will be public, if
77+
* {@code false} it will be private. To be able to create private playlists, the user must
78+
* have granted the {@code playlist-modify-private} scope.
79+
* @return A {@link CreatePlaylistRequest.Builder}.
80+
*/
81+
public Builder public_(final Boolean public_) {
82+
return setBodyParameter("public", public_);
83+
}
84+
85+
/**
86+
* The collaborative state setter.
87+
*
88+
* @param collaborative Optional. Defaults to {@code false}. If {@code true} the playlist will be collaborative.
89+
* <b>Note:</b> To create a collaborative playlist you must also set {@code public} to
90+
* {@code false}. To create collaborative playlists you must have granted
91+
* {@code playlist-modify-private} and {@code playlist-modify-public} scopes.
92+
* @return A {@link CreatePlaylistRequest.Builder}.
93+
*/
94+
public Builder collaborative(final Boolean collaborative) {
95+
return setBodyParameter("collaborative", collaborative);
96+
}
97+
98+
/**
99+
* The playlist description setter.
100+
*
101+
* @param description Optional. Value for playlist description as displayed in Spotify Clients and in the Web API.
102+
* @return A {@link CreatePlaylistRequest.Builder}.
103+
*/
104+
public Builder description(final String description) {
105+
assert (description != null);
106+
assert (!description.isEmpty());
107+
return setBodyParameter("description", description);
108+
}
109+
110+
/**
111+
* The request build method.
112+
*
113+
* @return A custom {@link CreatePlaylistRequest}.
114+
*/
115+
@Override
116+
public CreatePlaylistRequest build() {
117+
setContentType(ContentType.APPLICATION_JSON);
118+
setPath("/v1/me/playlists");
119+
return new CreatePlaylistRequest(this);
120+
}
121+
122+
@Override
123+
protected Builder self() {
124+
return this;
125+
}
126+
}
127+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"collaborative": false,
3+
"description": "New playlist description",
4+
"external_urls": {
5+
"spotify": "https://open.spotify.com/playlist/3cEYpjA9oz9GiPac4AsH4n"
6+
},
7+
"href": "https://api.spotify.com/v1/playlists/3cEYpjA9oz9GiPac4AsH4n",
8+
"id": "3cEYpjA9oz9GiPac4AsH4n",
9+
"images": [],
10+
"name": "New Playlist",
11+
"owner": {
12+
"external_urls": {
13+
"spotify": "https://open.spotify.com/user/jmperezperez"
14+
},
15+
"href": "https://api.spotify.com/v1/users/jmperezperez",
16+
"id": "jmperezperez",
17+
"type": "user",
18+
"uri": "spotify:user:jmperezperez"
19+
},
20+
"public": false,
21+
"snapshot_id": "JbtmHBDBAkMzFjnFzSP0aeYCCMP1XSIY5VHZT_jUGrFTzNTa6tnPiSzeBMFIcH2",
22+
"items": {
23+
"href": "https://api.spotify.com/v1/playlists/3cEYpjA9oz9GiPac4AsH4n/tracks",
24+
"limit": 100,
25+
"next": null,
26+
"offset": 0,
27+
"previous": null,
28+
"total": 0,
29+
"items": []
30+
},
31+
"tracks": {
32+
"href": "https://api.spotify.com/v1/playlists/3cEYpjA9oz9GiPac4AsH4n/tracks",
33+
"limit": 100,
34+
"next": null,
35+
"offset": 0,
36+
"previous": null,
37+
"total": 0,
38+
"items": []
39+
},
40+
"type": "playlist",
41+
"uri": "spotify:playlist:3cEYpjA9oz9GiPac4AsH4n"
42+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package se.michaelthelin.spotify.requests.data.playlists;
2+
3+
import org.apache.hc.core5.http.ParseException;
4+
import org.junit.jupiter.api.Test;
5+
import se.michaelthelin.spotify.ITest;
6+
import se.michaelthelin.spotify.TestUtil;
7+
import se.michaelthelin.spotify.exceptions.SpotifyWebApiException;
8+
import se.michaelthelin.spotify.model_objects.specification.Playlist;
9+
import se.michaelthelin.spotify.requests.data.AbstractDataTest;
10+
11+
import java.io.IOException;
12+
import java.util.concurrent.ExecutionException;
13+
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
import static org.junit.jupiter.api.Assertions.assertFalse;
16+
import static org.junit.jupiter.api.Assertions.assertNotNull;
17+
import static se.michaelthelin.spotify.Assertions.assertHasBodyParameter;
18+
import static se.michaelthelin.spotify.Assertions.assertHasHeader;
19+
20+
public class CreatePlaylistRequestTest extends AbstractDataTest<Playlist> {
21+
private final CreatePlaylistRequest defaultRequest = ITest.SPOTIFY_API
22+
.createPlaylist(ITest.NAME)
23+
.setHttpManager(
24+
TestUtil.MockedHttpManager.returningJson(
25+
"requests/data/playlists/CreatePlaylistRequest.json"))
26+
.collaborative(ITest.COLLABORATIVE)
27+
.description(ITest.DESCRIPTION)
28+
.public_(ITest.PUBLIC)
29+
.build();
30+
31+
public CreatePlaylistRequestTest() throws Exception {
32+
}
33+
34+
@Test
35+
public void shouldComplyWithReference() {
36+
assertHasAuthorizationHeader(defaultRequest);
37+
assertHasHeader(defaultRequest, "Content-Type", "application/json");
38+
assertHasBodyParameter(
39+
defaultRequest,
40+
"name",
41+
ITest.NAME);
42+
assertHasBodyParameter(
43+
defaultRequest,
44+
"public",
45+
ITest.PUBLIC);
46+
assertHasBodyParameter(
47+
defaultRequest,
48+
"collaborative",
49+
ITest.COLLABORATIVE);
50+
assertHasBodyParameter(
51+
defaultRequest,
52+
"description",
53+
ITest.DESCRIPTION);
54+
assertEquals(
55+
"https://api.spotify.com:443/v1/me/playlists",
56+
defaultRequest.getUri().toString());
57+
}
58+
59+
@Test
60+
public void shouldReturnDefault_sync() throws IOException, SpotifyWebApiException, ParseException {
61+
shouldReturnDefault(defaultRequest.execute());
62+
}
63+
64+
@Test
65+
public void shouldReturnDefault_async() throws ExecutionException, InterruptedException {
66+
shouldReturnDefault(defaultRequest.executeAsync().get());
67+
}
68+
69+
public void shouldReturnDefault(final Playlist playlist) {
70+
assertFalse(
71+
playlist.getIsCollaborative());
72+
assertEquals(
73+
"New playlist description",
74+
playlist.getDescription());
75+
assertNotNull(
76+
playlist.getExternalUrls());
77+
assertEquals(
78+
"https://api.spotify.com/v1/playlists/3cEYpjA9oz9GiPac4AsH4n",
79+
playlist.getHref());
80+
assertEquals(
81+
"3cEYpjA9oz9GiPac4AsH4n",
82+
playlist.getId());
83+
assertEquals(
84+
"New Playlist",
85+
playlist.getName());
86+
assertNotNull(
87+
playlist.getOwner());
88+
assertFalse(
89+
playlist.getIsPublicAccess());
90+
assertEquals(
91+
"JbtmHBDBAkMzFjnFzSP0aeYCCMP1XSIY5VHZT_jUGrFTzNTa6tnPiSzeBMFIcH2",
92+
playlist.getSnapshotId());
93+
assertNotNull(
94+
playlist.getItems());
95+
}
96+
}

0 commit comments

Comments
 (0)