diff --git a/Refresh.Database/GameDatabaseContext.Relations.cs b/Refresh.Database/GameDatabaseContext.Relations.cs index 9723bd5b1..6b958cbdc 100644 --- a/Refresh.Database/GameDatabaseContext.Relations.cs +++ b/Refresh.Database/GameDatabaseContext.Relations.cs @@ -650,6 +650,9 @@ public int GetTotalUniquePlaysForLevel(GameLevel level, bool includingAuthor = t public int GetTotalCompletionsForLevel(GameLevel level) => this.GameScores.Count(s => s.Level == level); + + public int GetTotalCompletionsForLevelByUser(GameLevel level, GameUser user) => + this.GameScores.Count(s => s.LevelId == level.LevelId && s.PublisherId == user.UserId); #endregion diff --git a/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelOwnRelationsResponse.cs b/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelOwnRelationsResponse.cs new file mode 100644 index 000000000..ec74e0de3 --- /dev/null +++ b/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelOwnRelationsResponse.cs @@ -0,0 +1,36 @@ +using Refresh.Core.Types.Data; +using Refresh.Database.Models.Levels; + +namespace Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Levels; + +[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] +public class ApiGameLevelOwnRelationsResponse : IApiResponse +{ + public required bool IsHearted { get; set; } + public required bool IsQueued { get; set; } + public required int LevelRating { get; set; } + + /// + /// Returns the total amount of plays. Probably rename this in APIv4 for clarity. + /// + public required int MyPlaysCount { get; set; } + public required int CompletionCount { get; set; } + public required int PhotoCount { get; set; } + + public static ApiGameLevelOwnRelationsResponse? FromOld(GameLevel level, DataContext dataContext) + { + if (dataContext.User == null) + return null; + + return new() + { + // TODO: Probably cache these stats aswell + IsHearted = dataContext.Database.IsLevelFavouritedByUser(level, dataContext.User), + IsQueued = dataContext.Database.IsLevelQueuedByUser(level, dataContext.User), + LevelRating = (int?)dataContext.Database.GetRatingByUser(level, dataContext.User) ?? 0, + MyPlaysCount = dataContext.Database.GetTotalPlaysForLevelByUser(level, dataContext.User), + CompletionCount = dataContext.Database.GetTotalCompletionsForLevelByUser(level, dataContext.User), + PhotoCount = dataContext.Database.GetTotalPhotosInLevelByUser(level, dataContext.User) + }; + } +} diff --git a/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelRelationsResponse.cs b/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelRelationsResponse.cs deleted file mode 100644 index 2f16b6866..000000000 --- a/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelRelationsResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Refresh.Interfaces.APIv3.Endpoints.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; } -} \ No newline at end of file diff --git a/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelResponse.cs b/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelResponse.cs index 7ee20f267..8bf90e61e 100644 --- a/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelResponse.cs +++ b/Refresh.Interfaces.APIv3/Endpoints/DataTypes/Response/Levels/ApiGameLevelResponse.cs @@ -55,6 +55,7 @@ public class ApiGameLevelResponse : IApiResponse, IDataConvertableFrom Tags { get; set; } + public required ApiGameLevelOwnRelationsResponse? OwnRelations { get; set; } public static ApiGameLevelResponse? FromOld(GameLevel? level, DataContext dataContext) { @@ -102,6 +103,7 @@ public class ApiGameLevelResponse : IApiResponse, IDataConvertableFrom GetLevelRelationsOfUser(RequestContext context, GameDatabaseContext database, GameUser user, + public ApiResponse GetLevelRelationsOfUser(RequestContext context, DataContext dataContext, GameUser user, [DocSummary("The ID of the level")] int id) { - GameLevel? level = database.GetLevelById(id); + GameLevel? level = dataContext.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) - }; + return ApiGameLevelOwnRelationsResponse.FromOld(level, dataContext); } [ApiV3Endpoint("levels/id/{id}/heart", HttpMethods.Post)] diff --git a/RefreshTests.GameServer/Tests/ApiV3/LevelApiTests.cs b/RefreshTests.GameServer/Tests/ApiV3/LevelApiTests.cs index 231803427..3588e9f3c 100644 --- a/RefreshTests.GameServer/Tests/ApiV3/LevelApiTests.cs +++ b/RefreshTests.GameServer/Tests/ApiV3/LevelApiTests.cs @@ -143,4 +143,23 @@ public async Task CantDeleteLevelIfLevelInvalid() Assert.That(response.StatusCode, Is.EqualTo(NotFound)); Assert.That(context.Database.GetLevelById(id), Is.Not.Null); } -} \ No newline at end of file + + [Test] + public void OnlyIncludesOwnLevelRelationsWhenSignedIn() + { + using TestContext context = this.GetServer(); + GameUser me = context.CreateUser(); + GameLevel level = context.CreateLevel(me); + + // Try fetching the level without being signed in + ApiResponse? response = context.Http.GetData($"/api/v3/levels/id/{level.LevelId}"); + Assert.That(response?.Data, Is.Not.Null); + Assert.That(response!.Data!.OwnRelations, Is.Null); + + // Sign in and then get level again + using HttpClient client = context.GetAuthenticatedClient(TokenType.Api, me); + response = client.GetData($"/api/v3/levels/id/{level.LevelId}"); + Assert.That(response?.Data, Is.Not.Null); + Assert.That(response!.Data!.OwnRelations, Is.Not.Null); + } +}