Skip to content

Commit

Permalink
API & Level Categories (#13)
Browse files Browse the repository at this point in the history
* Basic API Implementation

* Update Bunkum to 1.0.4

* API endpoints for getting levels

* Category system

Future-proofs some stuff for when we add LBP3 categories & more LBP2 categories, as well as API support

* Add more details to categories, add endpoint for getting categories

* Add skip/count parameters for level api

* Cleanup usages

* Add ByUserLevelCategory

* Allow game to use new category system

* Add level search category

* Match GameRoute instead of ApiRoute in LevelEndpoints

I don't know how I almost let that slip by.

* Mark workaround as FIXME
  • Loading branch information
jvyden authored Jan 25, 2023
1 parent f04b3e8 commit 4d55e38
Show file tree
Hide file tree
Showing 28 changed files with 313 additions and 128 deletions.
19 changes: 15 additions & 4 deletions Refresh.GameServer/Database/RealmDatabaseContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.UserData;
using Bunkum.HttpServer.Database;
using MongoDB.Bson;

namespace Refresh.GameServer.Database;

Expand Down Expand Up @@ -78,11 +79,20 @@ public GameUser CreateUser(string username)

[Pure]
[ContractAnnotation("null => null; notnull => canbenull")]
public GameUser? GetUser(string? username)
public GameUser? GetUserByUsername(string? username)
{
if (username == null) return null;
return this._realm.All<GameUser>().FirstOrDefault(u => u.Username == username);
}

[Pure]
[ContractAnnotation("null => null; notnull => canbenull")]
public GameUser? GetUserByUuid(string? uuid)
{
if (uuid == null) return null;
if(!ObjectId.TryParse(uuid, out ObjectId objectId)) return null;
return this._realm.All<GameUser>().FirstOrDefault(u => u.UserId == objectId);
}

public Token GenerateTokenForUser(GameUser user)
{
Expand Down Expand Up @@ -171,11 +181,12 @@ public IEnumerable<GameLevel> GetNewestLevels(int count, int skip) =>
.Skip(skip)
.Take(count);

// FIXME: to get this to work with new categories I removed the total number of results, this is terrible
[Pure]
public (IEnumerable<GameLevel> list, int count) SearchForLevels(int count, int skip, string query)
public IEnumerable<GameLevel> SearchForLevels(int count, int skip, string query)
{
string[] keywords = query.Split(' ');
if (keywords.Length == 0) return (Array.Empty<GameLevel>(), 0);
if (keywords.Length == 0) return Array.Empty<GameLevel>();

IQueryable<GameLevel> levels = this._realm.All<GameLevel>();

Expand All @@ -190,7 +201,7 @@ public IEnumerable<GameLevel> GetNewestLevels(int count, int skip) =>
);
}

return (levels.AsEnumerable().Skip(skip).Take(count), levels.Count());
return levels.AsEnumerable().Skip(skip).Take(count);
}

[Pure]
Expand Down
34 changes: 34 additions & 0 deletions Refresh.GameServer/Endpoints/Api/LevelApiEndpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Net;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Refresh.GameServer.Database;
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.Levels.Categories;
using Refresh.GameServer.Types.UserData;

namespace Refresh.GameServer.Endpoints.Api;

public class LevelApiEndpoints : EndpointGroup
{
[ApiEndpoint("levels/{route}")]
[Authentication(false)]
[NullStatusCode(HttpStatusCode.NotFound)]
public IEnumerable<GameLevel>? GetLevels(RequestContext context, RealmDatabaseContext database, GameUser? user, string route)
=> CategoryHandler.Categories
.FirstOrDefault(c => c.ApiRoute.StartsWith(route))?
.Fetch(context, database, user);

[ApiEndpoint("levels")]
[Authentication(false)]
public IEnumerable<LevelCategory> GetCategories(RequestContext context) => CategoryHandler.Categories;

[ApiEndpoint("level/id/{idStr}")]
[Authentication(false)]
public GameLevel? GetLevelById(RequestContext context, RealmDatabaseContext database, string idStr)
{
int.TryParse(idStr, out int id);
if (id == default) return null;

return database.GetLevelById(id);
}
}
19 changes: 19 additions & 0 deletions Refresh.GameServer/Endpoints/Api/UserApiEndpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Refresh.GameServer.Database;
using Refresh.GameServer.Types.UserData;

namespace Refresh.GameServer.Endpoints.Api;

public class UserApiEndpoints : EndpointGroup
{
[ApiEndpoint("user/name/{username}")]
[Authentication(false)]
public GameUser? GetUserByName(RequestContext context, RealmDatabaseContext database, string username)
=> database.GetUserByUsername(username);

[ApiEndpoint("user/uuid/{uuid}")]
[Authentication(false)]
public GameUser? GetUserByUuid(RequestContext context, RealmDatabaseContext database, string uuid)
=> database.GetUserByUuid(uuid);
}
21 changes: 21 additions & 0 deletions Refresh.GameServer/Endpoints/ApiEndpointAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using JetBrains.Annotations;

namespace Refresh.GameServer.Endpoints;

[MeansImplicitUse]
public class ApiEndpointAttribute : EndpointAttribute
{
// v2, since maybe we want to add add v1 for backwards compatibility with project lighthouse?
// LegacyApiEndpointAttribute for lighthouse api
private const string BaseRoute = "/api/v2/";

public ApiEndpointAttribute(string route, Method method = Method.Get, ContentType contentType = ContentType.Json)
: base(BaseRoute + route, method, contentType)
{}

public ApiEndpointAttribute(string route, ContentType contentType, Method method = Method.Get)
: base(BaseRoute + route, contentType, method)
{}
}
3 changes: 1 addition & 2 deletions Refresh.GameServer/Endpoints/AutodiscoverEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using Newtonsoft.Json;
using Refresh.GameServer.Configuration;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Configuration;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Newtonsoft.Json;

namespace Refresh.GameServer.Endpoints;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
using System.Net;
using JetBrains.Annotations;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Refresh.GameServer.Database;
using Refresh.GameServer.Extensions;
using Refresh.GameServer.Types.Comments;
using Refresh.GameServer.Types.Lists;
using Refresh.GameServer.Types.UserData;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;

namespace Refresh.GameServer.Endpoints;
namespace Refresh.GameServer.Endpoints.Game;

public class CommentEndpoints : EndpointGroup
{
[GameEndpoint("postUserComment/{username}", ContentType.Xml)]
public Response PostProfileComment(RequestContext context, RealmDatabaseContext database, string username, GameComment body, GameUser user)
{
GameUser? profile = database.GetUser(username);
GameUser? profile = database.GetUserByUsername(username);
if (profile == null) return new Response(HttpStatusCode.NotFound);

database.PostCommentToProfile(profile, user, body.Content);
Expand All @@ -27,7 +26,7 @@ public Response PostProfileComment(RequestContext context, RealmDatabaseContext
[NullStatusCode(HttpStatusCode.NotFound)]
public GameCommentList? GetProfileComments(RequestContext context, RealmDatabaseContext database, string username)
{
GameUser? profile = database.GetUser(username);
GameUser? profile = database.GetUserByUsername(username);
if (profile == null) return null;

(int skip, int count) = context.GetPageData();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using System.Net;
using System.Xml.Serialization;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using NPTicket;
using Refresh.GameServer.Authentication;
using Refresh.GameServer.Database;
using Refresh.GameServer.Types.UserData;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;

namespace Refresh.GameServer.Endpoints.Handshake;
namespace Refresh.GameServer.Endpoints.Game.Handshake;

public class AuthenticationEndpoints : EndpointGroup
{
Expand All @@ -28,7 +28,7 @@ public class AuthenticationEndpoints : EndpointGroup
return null;
}

GameUser? user = database.GetUser(ticket.Username);
GameUser? user = database.GetUserByUsername(ticket.Username);
user ??= database.CreateUser(ticket.Username);

Token token = database.GenerateTokenForUser(user);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using Refresh.GameServer.Configuration;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Refresh.GameServer.Configuration;

namespace Refresh.GameServer.Endpoints.Handshake;
namespace Refresh.GameServer.Endpoints.Game.Handshake;

public class LicenseEndpoints : EndpointGroup
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Xml.Serialization;
using Refresh.GameServer.Types;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Refresh.GameServer.Types;

namespace Refresh.GameServer.Endpoints.Handshake;
namespace Refresh.GameServer.Endpoints.Game.Handshake;

public class MetadataEndpoints : EndpointGroup
{
Expand Down
36 changes: 36 additions & 0 deletions Refresh.GameServer/Endpoints/Game/Levels/LevelEndpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Net;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Refresh.GameServer.Database;
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.Levels.Categories;
using Refresh.GameServer.Types.Lists;
using Refresh.GameServer.Types.UserData;

namespace Refresh.GameServer.Endpoints.Game.Levels;

public class LevelEndpoints : EndpointGroup
{
// FIXME: Workaround shitty routing - see https://github.com/LittleBigRefresh/Refresh/pull/13#discussion_r1086131790 for details
[GameEndpoint("slots", ContentType.Xml)]
public GameMinimalLevelList NewestLevels(RequestContext context, RealmDatabaseContext database, GameUser? user)
=> this.GetLevels(context, database, user, "newest");

[GameEndpoint("slots/{route}", ContentType.Xml)]
public GameMinimalLevelList GetLevels(RequestContext context, RealmDatabaseContext database, GameUser? user, string route) =>
new(CategoryHandler.Categories
.FirstOrDefault(c => c.GameRoute.StartsWith(route))?
.Fetch(context, database, user)?
.Select(GameMinimalLevel.FromGameLevel), database.GetTotalLevelCount()); // TODO: proper level count

[GameEndpoint("s/user/{idStr}", ContentType.Xml)]
[NullStatusCode(HttpStatusCode.NotFound)]
public GameLevel? LevelById(RequestContext context, RealmDatabaseContext database, string idStr)
{
int.TryParse(idStr, out int id);
if (id == default) return null;

return database.GetLevelById(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System.Net;
using Refresh.GameServer.Database;
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.UserData;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Refresh.GameServer.Database;
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.UserData;

namespace Refresh.GameServer.Endpoints.Levels;
namespace Refresh.GameServer.Endpoints.Game.Levels;

public class PublishEndpoints : EndpointGroup
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Xml.Serialization;
using Refresh.GameServer.Database;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Refresh.GameServer.Database;

namespace Refresh.GameServer.Endpoints;
namespace Refresh.GameServer.Endpoints.Game;

public class PresenceEndpoints : EndpointGroup
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using Refresh.GameServer.Types.Lists;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Refresh.GameServer.Types.Lists;

namespace Refresh.GameServer.Endpoints;
namespace Refresh.GameServer.Endpoints.Game;

public class ResourceEndpoints : EndpointGroup
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
using System.Net;
using System.Xml.Serialization;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Newtonsoft.Json;
using Refresh.GameServer.Database;
using Refresh.GameServer.Types.Lists;
using Refresh.GameServer.Types.UserData;
using Bunkum.HttpServer;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;

namespace Refresh.GameServer.Endpoints;
namespace Refresh.GameServer.Endpoints.Game;

public class UserEndpoints : EndpointGroup
{
[GameEndpoint("user/{name}", Method.Get, ContentType.Xml)]
public GameUser? GetUser(RequestContext context, RealmDatabaseContext database, string name)
{
GameUser? user = database.GetUser(name);
GameUser? user = database.GetUserByUsername(name);
return user;
}

Expand All @@ -29,7 +29,7 @@ public GameUserList GetMultipleUsers(RequestContext context, RealmDatabaseContex

foreach (string username in usernames)
{
GameUser? user = database.GetUser(username);
GameUser? user = database.GetUserByUsername(username);
if (user == null) continue;

user.PrepareForSerialization();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using JetBrains.Annotations;
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using JetBrains.Annotations;

namespace Refresh.GameServer;
namespace Refresh.GameServer.Endpoints;

[MeansImplicitUse]
public class GameEndpointAttribute : EndpointAttribute
Expand Down
Loading

0 comments on commit 4d55e38

Please sign in to comment.