Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public class ApiExtendedGameUserResponse : ApiGameUserResponse, IApiResponse, ID
UnescapeXmlSequences = user.UnescapeXmlSequences,
FilesizeQuotaUsage = user.FilesizeQuotaUsage,
Statistics = ApiGameUserStatisticsResponse.FromOld(user, dataContext)!,
OwnRelations = ApiGameUserOwnRelationsResponse.FromOld(user, dataContext),
ActiveRoom = ApiGameRoomResponse.FromOld(dataContext.Match.RoomAccessor.GetRoomByUser(user), dataContext),
LevelVisibility = user.LevelVisibility,
ProfileVisibility = user.ProfileVisibility,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Refresh.Core.Types.Data;
using Refresh.Database.Models.Users;

namespace Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Users;

[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class ApiGameUserOwnRelationsResponse : IApiResponse
{
public required bool IsHearted { get; set; }

public static ApiGameUserOwnRelationsResponse? FromOld(GameUser user, DataContext dataContext)
{
if (dataContext.User == null)
return null;

return new()
{
IsHearted = dataContext.Database.IsUserFavouritedByUser(user, dataContext.User),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class ApiGameUserResponse : IApiResponse, IDataConvertableFrom<ApiGameUse
public required GameUserRole Role { get; set; }

public required ApiGameUserStatisticsResponse Statistics { get; set; }
public required ApiGameUserOwnRelationsResponse? OwnRelations { get; set; }
public required ApiGameRoomResponse? ActiveRoom { get; set; }

[ContractAnnotation("null => null; notnull => notnull")]
Expand All @@ -51,6 +52,7 @@ public class ApiGameUserResponse : IApiResponse, IDataConvertableFrom<ApiGameUse
LastLoginDate = user.LastLoginDate,
Role = user.Role,
Statistics = ApiGameUserStatisticsResponse.FromOld(user, dataContext)!,
OwnRelations = ApiGameUserOwnRelationsResponse.FromOld(user, dataContext),
ActiveRoom = ApiGameRoomResponse.FromOld(dataContext.Match.RoomAccessor.GetRoomByUser(user), dataContext),
};
}
Expand Down
8 changes: 5 additions & 3 deletions Refresh.Interfaces.APIv3/Endpoints/LevelApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,12 @@ public ApiOkResponse QueueLevel(RequestContext context, GameDatabaseContext data
GameLevel? level = database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

database.QueueLevel(level, user);
bool success = database.QueueLevel(level, user);

// Update pin progress for queueing a level through the API
database.IncrementUserPinProgress((long)ServerPins.QueueLevelOnWebsite, 1, user, false, TokenPlatform.Website);
// Only give pin if the level was queued without having already been queued.
// Won't protect against spam, but this way the pin objective is more accurately implemented.
if (success)
database.IncrementUserPinProgress((long)ServerPins.QueueLevelOnWebsite, 1, user, false, TokenPlatform.Website);

return new ApiOkResponse();
}
Expand Down
35 changes: 35 additions & 0 deletions Refresh.Interfaces.APIv3/Endpoints/UserApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using Refresh.Core.Services;
using Refresh.Core.Types.Data;
using Refresh.Database;
using Refresh.Database.Models.Authentication;
using Refresh.Database.Models.Pins;
using Refresh.Database.Models.Users;
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes;
using Refresh.Interfaces.APIv3.Endpoints.ApiTypes.Errors;
Expand Down Expand Up @@ -45,6 +47,39 @@ public ApiResponse<ApiGameUserResponse> GetUserByUuid(RequestContext context, Ga

return ApiGameUserResponse.FromOld(user, dataContext);
}

// TODO: Also allow specifying user by username
[ApiV3Endpoint("users/uuid/{uuid}/heart", HttpMethods.Post)]
[DocSummary("Hearts a user by their UUID")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
public ApiOkResponse HeartUserByUuid(RequestContext context, GameDatabaseContext database,
[DocSummary("The UUID of the user")] string uuid, DataContext dataContext, GameUser user)
{
GameUser? target = database.GetUserByUuid(uuid);
if(target == null) return ApiNotFoundError.UserMissingError;

bool success = database.FavouriteUser(target, user);

// Only give pin if the user was hearted without having already been hearted.
// Won't protect against spam, but this way the pin objective is more accurately implemented.
if (success)
database.IncrementUserPinProgress((long)ServerPins.HeartPlayerOnWebsite, 1, user, false, TokenPlatform.Website);

return new ApiOkResponse();
}

[ApiV3Endpoint("users/uuid/{uuid}/unheart", HttpMethods.Post)]
[DocSummary("Unhearts a user by their UUID")]
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
public ApiOkResponse UnheartUserByUuid(RequestContext context, GameDatabaseContext database,
[DocSummary("The UUID of the user")] string uuid, DataContext dataContext, GameUser user)
{
GameUser? target = database.GetUserByUuid(uuid);
if(target == null) return ApiNotFoundError.UserMissingError;

database.UnfavouriteUser(target, user);
return new ApiOkResponse();
}

[ApiV3Endpoint("users/me"), MinimumRole(GameUserRole.Restricted)]
[DocSummary("Returns your own user, provided you are authenticated")]
Expand Down
19 changes: 19 additions & 0 deletions RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,23 @@ public void GetsNewestUsers(bool showOnlineUsers)
index++;
}
}

[Test]
public void OnlyIncludesOwnUserRelationsWhenSignedIn()
{
using TestContext context = this.GetServer();
GameUser otherUser = context.CreateUser();
GameUser me = context.CreateUser();

// Try fetching the user without being signed in
ApiResponse<ApiGameUserResponse>? response = context.Http.GetData<ApiGameUserResponse>($"/api/v3/users/name/{otherUser.Username}");
Assert.That(response?.Data, Is.Not.Null);
Assert.That(response!.Data!.OwnRelations, Is.Null);

// Sign in and then get user again
using HttpClient client = context.GetAuthenticatedClient(TokenType.Api, me);
response = client.GetData<ApiGameUserResponse>($"/api/v3/users/name/{otherUser.Username}");
Assert.That(response?.Data, Is.Not.Null);
Assert.That(response!.Data!.OwnRelations, Is.Not.Null);
}
}