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);
+ }
+}