Skip to content

Commit

Permalink
Implement hearting and queueing levels through ApiV3 (#681)
Browse files Browse the repository at this point in the history
Implements hearting and queueing levels through ApiV3, aswell as getting
all hearted levels of a specific user, getting your own hearted and
queued levels, clearing your queue and including whether you have
hearted or queued a certain level, aswell as it's slot type (to
differentiate whether it is a user or a story/dlc level), in it's
ApiGameLevelResponse
  • Loading branch information
jvyden authored Nov 4, 2024
2 parents 2faa55c + 3696000 commit 0d8dda6
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 4 deletions.
4 changes: 2 additions & 2 deletions Refresh.GameServer/Database/GameDatabaseContext.Relations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public partial class GameDatabaseContext // Relations
{
#region Favouriting Levels
[Pure]
private bool IsLevelFavouritedByUser(GameLevel level, GameUser user) => this.FavouriteLevelRelations
public bool IsLevelFavouritedByUser(GameLevel level, GameUser user) => this.FavouriteLevelRelations
.FirstOrDefault(r => r.Level == level && r.User == user) != null;

[Pure]
Expand Down Expand Up @@ -143,7 +143,7 @@ public bool UnfavouriteUser(GameUser userToFavourite, GameUser userFavouriting)

#region Queueing
[Pure]
private bool IsLevelQueuedByUser(GameLevel level, GameUser user) => this.QueueLevelRelations
public bool IsLevelQueuedByUser(GameLevel level, GameUser user) => this.QueueLevelRelations
.FirstOrDefault(r => r.Level == level && r.User == user) != null;

[Pure]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response.Levels;

[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class ApiGameLevelRelationsResponse : IApiResponse
{
public required bool IsHearted { get; set; }
public required bool IsQueued { get; set; }
public required int MyPlaysCount { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class ApiGameLevelResponse : IApiResponse, IDataConvertableFrom<ApiGameLe
public required bool TeamPicked { get; set; }
public required DateTimeOffset? DateTeamPicked { get; set; }
public required GameLevelType LevelType { get; set; }
public required GameSlotType SlotType { get; set; }
public required bool IsLocked { get; set; }
public required bool IsSubLevel { get; set; }
public required bool IsCopyable { get; set; }
Expand All @@ -57,7 +58,7 @@ public class ApiGameLevelResponse : IApiResponse, IDataConvertableFrom<ApiGameLe
public static ApiGameLevelResponse? FromOld(GameLevel? level, DataContext dataContext)
{
if (level == null) return null;

return new ApiGameLevelResponse
{
IsAdventure = level.IsAdventure,
Expand Down Expand Up @@ -85,6 +86,7 @@ public class ApiGameLevelResponse : IApiResponse, IDataConvertableFrom<ApiGameLe
RootLevelHash = level.RootResource,
GameVersion = level.GameVersion,
LevelType = level.LevelType,
SlotType = level.SlotType,
IsCopyable = level.IsCopyable,
IsLocked = level.IsLocked,
IsSubLevel = level.IsSubLevel,
Expand Down
82 changes: 82 additions & 0 deletions Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.Levels.Categories;
using Refresh.GameServer.Types.UserData;
using Refresh.GameServer.Types.Roles;

namespace Refresh.GameServer.Endpoints.ApiV3;

Expand Down Expand Up @@ -52,6 +53,8 @@ public ApiListResponse<ApiLevelCategoryResponse> GetCategories(RequestContext co
[DocQueryParam("game", "Filters levels to a specific game version. Allowed values: lbp1-3, vita, psp, beta")]
[DocQueryParam("seed", "The random seed to use for randomization. Uses 0 if not specified.")]
[DocQueryParam("players", "Filters levels to those accommodating the specified number of players.")]
[DocQueryParam("username", "If set, certain categories like 'hearted' or 'byUser' will return the levels of " +
"the user with this username instead of your own. Optional.")]
public ApiListResponse<ApiGameLevelResponse> GetLevels(RequestContext context, GameDatabaseContext database,
MatchService matchService, CategoryService categories, GameUser? user, IDataStore dataStore,
[DocSummary("The name of the category you'd like to retrieve levels from. " +
Expand Down Expand Up @@ -174,4 +177,83 @@ public ApiOkResponse SetLevelAsOverrideByHash(RequestContext context, GameDataba

return new ApiOkResponse();
}


[ApiV3Endpoint("levels/id/{id}/relations"), MinimumRole(GameUserRole.Restricted)]
[DocSummary("Gets your relations to a level by it's ID")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)]
public ApiResponse<ApiGameLevelRelationsResponse> GetLevelRelationsOfUser(RequestContext context, GameDatabaseContext database, GameUser user,
[DocSummary("The ID of the level")] int id)
{
GameLevel? level = database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

return new ApiGameLevelRelationsResponse
{
IsHearted = database.IsLevelFavouritedByUser(level, user),
IsQueued = database.IsLevelQueuedByUser(level, user),
MyPlaysCount = database.GetTotalPlaysForLevelByUser(level, user)
};
}

[ApiV3Endpoint("levels/id/{id}/heart", HttpMethods.Post)]
[DocSummary("Adds a specific level by it's ID to your hearted levels")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)]
public ApiOkResponse FavouriteLevel(RequestContext context, GameDatabaseContext database, GameUser user,
[DocSummary("The ID of the level")] int id)
{
GameLevel? level = database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

database.FavouriteLevel(level, user);
return new ApiOkResponse();
}

[ApiV3Endpoint("levels/id/{id}/unheart", HttpMethods.Post)]
[DocSummary("Removes a specific level by it's ID from your hearted levels")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)]
public ApiOkResponse UnheartLevel(RequestContext context, GameDatabaseContext database, GameUser user,
[DocSummary("The ID of the level")] int id)
{
GameLevel? level = database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

database.UnfavouriteLevel(level, user);
return new ApiOkResponse();
}

[ApiV3Endpoint("levels/id/{id}/queue", HttpMethods.Post)]
[DocSummary("Adds a specific level by it's ID to your queue")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)]
public ApiOkResponse QueueLevel(RequestContext context, GameDatabaseContext database, GameUser user,
[DocSummary("The ID of the level")] int id)
{
GameLevel? level = database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

database.QueueLevel(level, user);
return new ApiOkResponse();
}

[ApiV3Endpoint("levels/id/{id}/dequeue", HttpMethods.Post)]
[DocSummary("Removes a specific level by it's ID from your queue")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)]
public ApiOkResponse DequeueLevel(RequestContext context, GameDatabaseContext database, GameUser user,
[DocSummary("The ID of the level")] int id)
{
GameLevel? level = database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

database.DequeueLevel(level, user);
return new ApiOkResponse();
}

[ApiV3Endpoint("levels/queued/clear", HttpMethods.Post)]
[DocSummary("Clears your level queue")]
public ApiOkResponse ClearQueuedLevels(RequestContext context, GameDatabaseContext database,
IDataStore dataStore, GameUser user, DataContext dataContext)
{
database.ClearQueue(user);
return new ApiOkResponse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal ByUserLevelCategory() : base("byUser", "by", true)
LevelFilterSettings levelFilterSettings, GameUser? user)
{
// Prefer username from query, but fallback to user passed into this category if it's missing
string? username = context.QueryString["u"];
string? username = context.QueryString["u"] ?? context.QueryString["username"];
if (username != null) user = dataContext.Database.GetUserByUsername(username);

if (user == null) return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ internal FavouriteSlotsByUserCategory() : base("hearted", "favouriteSlots", true
public override DatabaseList<GameLevel>? Fetch(RequestContext context, int skip, int count, DataContext dataContext,
LevelFilterSettings levelFilterSettings, GameUser? user)
{
// Prefer username from query, but fallback to user passed into this category if it's missing
string? username = context.QueryString["u"] ?? context.QueryString["username"];
if (username != null) user = dataContext.Database.GetUserByUsername(username);

if (user == null) return null;

return dataContext.Database.GetLevelsFavouritedByUser(user, count, skip, levelFilterSettings, dataContext.User);
Expand Down

0 comments on commit 0d8dda6

Please sign in to comment.