Skip to content

Commit d0cba58

Browse files
authored
Playlist API endpoints (#1039)
Adds API endpoints for basic playlist stuff (creating, updating, deleting, getting a playlist by ID, aswell as adding/removing levels and playlists). Categories and moderation endpoints will come in separate PRs. This needs a few tests before merging.
2 parents 0ac60b1 + a647e1d commit d0cba58

12 files changed

Lines changed: 753 additions & 19 deletions

File tree

Refresh.Database/GameDatabaseContext.Playlists.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,19 +108,19 @@ public GamePlaylist CreateRootPlaylist(GameUser user)
108108
public GamePlaylist? GetUserRootPlaylist(GameUser user)
109109
=> this.GamePlaylistsIncluded.FirstOrDefault(p => p.IsRoot && p.PublisherId == user.UserId);
110110

111-
public void UpdatePlaylist(GamePlaylist playlist, ISerializedCreatePlaylistInfo updateInfo)
111+
public GamePlaylist UpdatePlaylist(GamePlaylist playlist, ISerializedCreatePlaylistInfo updateInfo)
112112
{
113113
GameLocation location = updateInfo.Location ?? new GameLocation(playlist.LocationX, playlist.LocationY);
114114

115-
this.Write(() =>
116-
{
117-
playlist.Name = updateInfo.Name ?? playlist.Name;
118-
playlist.Description = updateInfo.Description ?? playlist.Description;
119-
playlist.IconHash = updateInfo.Icon ?? playlist.IconHash;
120-
playlist.LocationX = location.X;
121-
playlist.LocationY = location.Y;
122-
playlist.LastUpdateDate = this._time.Now;
123-
});
115+
playlist.Name = updateInfo.Name ?? playlist.Name;
116+
playlist.Description = updateInfo.Description ?? playlist.Description;
117+
playlist.IconHash = updateInfo.Icon ?? playlist.IconHash;
118+
playlist.LocationX = location.X;
119+
playlist.LocationY = location.Y;
120+
playlist.LastUpdateDate = this._time.Now;
121+
this.SaveChanges();
122+
123+
return playlist;
124124
}
125125

126126
public void DeletePlaylist(GamePlaylist playlist)

Refresh.Database/Query/ISerializedCreatePlaylistInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ namespace Refresh.Database.Query;
44

55
public interface ISerializedCreatePlaylistInfo
66
{
7-
string Name { get; }
8-
string Description { get; }
7+
string? Name { get; }
8+
string? Description { get; }
99
string? Icon { get; }
1010
GameLocation? Location { get; }
1111
}

Refresh.Interfaces.APIv3/Endpoints/ApiTypes/Errors/ApiNotFoundError.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,16 @@ public class ApiNotFoundError : ApiError
3939

4040
public const string VerifiedIpMissingErrorWhen = "The verified IP could not be found";
4141
public static readonly ApiNotFoundError VerifiedIpMissingError = new(VerifiedIpMissingErrorWhen);
42-
42+
43+
public const string PlaylistMissingErrorWhen = "The playlist could not be found";
44+
public static readonly ApiNotFoundError PlaylistMissingError = new(PlaylistMissingErrorWhen);
45+
46+
public const string ParentPlaylistMissingErrorWhen = "The parent playlist could not be found";
47+
public static readonly ApiNotFoundError ParentPlaylistMissingError = new(ParentPlaylistMissingErrorWhen);
48+
49+
public const string SubPlaylistMissingErrorWhen = "The sub-playlist could not be found";
50+
public static readonly ApiNotFoundError SubPlaylistMissingError = new(SubPlaylistMissingErrorWhen);
51+
4352
private ApiNotFoundError() : base("The requested resource was not found", NotFound)
4453
{}
4554

Refresh.Interfaces.APIv3/Endpoints/ApiTypes/Errors/ApiValidationError.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ public class ApiValidationError : ApiError
1717
public const string IpAddressParseErrorWhen = "The IP address could not be parsed by the server";
1818
public static readonly ApiValidationError IpAddressParseError = new(IpAddressParseErrorWhen);
1919

20+
public const string InvalidPlaylistIdWhen = "The playlist ID couldn't be parsed by the server";
21+
public static readonly ApiValidationError InvalidPlaylistId = new(InvalidPlaylistIdWhen);
22+
2023
public const string NoPhotoDeletionPermissionErrorWhen = "You do not have permission to delete someone else's photo";
2124
public static readonly ApiValidationError NoPhotoDeletionPermissionError = new(NoPhotoDeletionPermissionErrorWhen);
2225

@@ -52,6 +55,12 @@ public class ApiValidationError : ApiError
5255

5356
public const string BodyMustBeImageErrorWhen = "The asset must be a PNG/JPEG file";
5457
public static readonly ApiValidationError BodyMustBeImageError = new(BodyMustBeImageErrorWhen);
58+
59+
public const string IconMustBeImageErrorWhen = "The icon must be a PNG/JPEG file";
60+
public static readonly ApiValidationError IconMustBeImageError = new(IconMustBeImageErrorWhen);
61+
62+
public const string IconMissingErrorWhen = "The icon is missing from the server";
63+
public static readonly ApiValidationError IconMissingError = new(IconMissingErrorWhen);
5564

5665
public const string ResourceExistsErrorWhen = "The resource you are attempting to create already exists.";
5766
public static readonly ApiValidationError ResourceExistsError = new(ResourceExistsErrorWhen);
@@ -88,7 +97,13 @@ public class ApiValidationError : ApiError
8897

8998
public const string UsernameTakenErrorWhen = "This username is already taken!";
9099
public static readonly ApiValidationError UsernameTakenError = new(UsernameTakenErrorWhen);
91-
100+
101+
public const string NoPlaylistEditPermissionErrorWhen = "You do not have permission to update this playlist";
102+
public static readonly ApiValidationError NoPlaylistEditPermissionError = new(NoPlaylistEditPermissionErrorWhen);
103+
104+
public const string NoPlaylistDeletePermissionErrorWhen = "You do not have permission to delete this playlist";
105+
public static readonly ApiValidationError NoPlaylistDeletePermissionError = new(NoPlaylistDeletePermissionErrorWhen);
106+
92107
// TODO: Split off error messages which are actually 401 or anything else that isn't 400
93108
public ApiValidationError(string message) : base(message) {}
94109
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Refresh.Database.Models;
2+
using Refresh.Database.Query;
3+
4+
namespace Refresh.Interfaces.APIv3.Endpoints.DataTypes.Request;
5+
6+
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
7+
public class ApiPlaylistCreationRequest : ISerializedCreatePlaylistInfo
8+
{
9+
public string? Name { get; set; }
10+
public string? Icon { get; set; }
11+
public string? Description { get; set; }
12+
public GameLocation? Location { get; set; }
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Refresh.Core.Types.Data;
2+
using Refresh.Database.Models.Playlists;
3+
4+
namespace Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Playlists;
5+
6+
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
7+
public class ApiGamePlaylistOwnRelationsResponse
8+
{
9+
public bool IsHearted { get; set; }
10+
11+
public static ApiGamePlaylistOwnRelationsResponse? FromOld(GamePlaylist? old, DataContext dataContext)
12+
{
13+
if (old == null || dataContext.User == null) return null;
14+
15+
return new()
16+
{
17+
IsHearted = dataContext.Database.IsPlaylistFavouritedByUser(old, dataContext.User),
18+
};
19+
}
20+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Diagnostics;
2+
using Refresh.Core.Types.Data;
3+
using Refresh.Database.Models.Playlists;
4+
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Data;
5+
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Users;
6+
7+
namespace Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Playlists;
8+
9+
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
10+
public class ApiGamePlaylistResponse : IApiResponse, IDataConvertableFrom<ApiGamePlaylistResponse, GamePlaylist>
11+
{
12+
public required int PlaylistId { get; set; }
13+
public required ApiGameUserResponse? Publisher { get; set; }
14+
15+
public required string Name { get; set; }
16+
public required string IconHash { get; set; }
17+
public required string Description { get; set; }
18+
public required ApiGameLocationResponse Location { get; set; }
19+
20+
public required DateTimeOffset CreationDate { get; set; }
21+
public required DateTimeOffset UpdateDate { get; set; }
22+
23+
public ApiGamePlaylistStatisticsResponse? Statistics { get; set; }
24+
public ApiGamePlaylistOwnRelationsResponse? OwnRelations { get; set; }
25+
26+
public static ApiGamePlaylistResponse? FromOld(GamePlaylist? playlist, DataContext dataContext)
27+
{
28+
if (playlist == null) return null;
29+
30+
if (playlist.Statistics == null)
31+
dataContext.Database.RecalculatePlaylistStatistics(playlist);
32+
33+
Debug.Assert(playlist.Statistics != null);
34+
35+
return new()
36+
{
37+
PlaylistId = playlist.PlaylistId,
38+
Publisher = ApiGameUserResponse.FromOld(playlist.Publisher, dataContext),
39+
Name = playlist.Name,
40+
IconHash = dataContext.GetIconFromHash(playlist.IconHash),
41+
Description = playlist.Description,
42+
Location = ApiGameLocationResponse.FromLocation(playlist.LocationX, playlist.LocationY)!,
43+
CreationDate = playlist.CreationDate,
44+
UpdateDate = playlist.LastUpdateDate,
45+
Statistics = ApiGamePlaylistStatisticsResponse.FromOld(playlist.Statistics, dataContext),
46+
OwnRelations = ApiGamePlaylistOwnRelationsResponse.FromOld(playlist, dataContext),
47+
};
48+
}
49+
50+
public static IEnumerable<ApiGamePlaylistResponse> FromOldList(IEnumerable<GamePlaylist> oldList, DataContext dataContext)
51+
=> oldList.Select(old => FromOld(old, dataContext)).ToList()!;
52+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Refresh.Core.Types.Data;
2+
using Refresh.Database.Models.Statistics;
3+
4+
namespace Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Playlists;
5+
6+
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
7+
public class ApiGamePlaylistStatisticsResponse
8+
{
9+
public required int Levels { get; set; }
10+
public required int SubPlaylists { get; set; }
11+
public required int ParentPlaylists { get; set; }
12+
public required int Hearts { get; set; }
13+
14+
public static ApiGamePlaylistStatisticsResponse? FromOld(GamePlaylistStatistics? old, DataContext dataContext)
15+
{
16+
if (old == null) return null;
17+
18+
return new()
19+
{
20+
Levels = old.LevelCount,
21+
SubPlaylists = old.SubPlaylistCount,
22+
ParentPlaylists = old.ParentPlaylistCount,
23+
Hearts = old.FavouriteCount,
24+
};
25+
}
26+
}

0 commit comments

Comments
 (0)