From c2ae47fd3a037a93737c1c5f0dbd5245a855e1f1 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 10 Jun 2024 18:00:37 -0400 Subject: [PATCH 01/37] Ability to delete scores via the API --- .../GameDatabaseContext.Leaderboard.cs | 36 +++++++ .../Admin/AdminLeaderboardApiEndpoints.cs | 55 +++++++++++ .../ApiV3/Admin/AdminLevelApiEndpoints.cs | 2 +- .../ApiV3/LeaderboardApiEndpoints.cs | 4 +- .../Tests/Levels/ScoreModerationTests.cs | 96 +++++++++++++++++++ 5 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLeaderboardApiEndpoints.cs create mode 100644 RefreshTests.GameServer/Tests/Levels/ScoreModerationTests.cs diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs index 48299653..702eb125 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using MongoDB.Bson; using Refresh.GameServer.Authentication; +using Refresh.GameServer.Types.Activity; using Refresh.GameServer.Types.Levels; using Refresh.GameServer.Types.UserData; using Refresh.GameServer.Types.UserData.Leaderboard; @@ -109,4 +110,39 @@ public IEnumerable GetRankedScoresAroundScore(GameSubmittedScore if (id == null) return null; return this._realm.All().FirstOrDefault(u => u.ScoreId == id); } + + public void DeleteScore(GameSubmittedScore score) + { + IQueryable scoreEvents = this._realm.All() + .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); + + this._realm.Write(() => + { + this._realm.RemoveRange(scoreEvents); + this._realm.Remove(score); + }); + } + + public void DeleteScoresSetByUser(GameUser user) + { + IEnumerable scores = this._realm.All() + // FIXME: Realm (ahem, I mean the atlas device sdk *rolls eyes*) is a fucking joke. + // Realm doesn't support .Contains on IList. Yes, really. + // This means we are forced to iterate over EVERY SCORE. + // I can't wait for Postgres. + .AsEnumerable() + .Where(s => s.Players.Contains(user)); + + this._realm.Write(() => + { + foreach (GameSubmittedScore score in scores) + { + IQueryable scoreEvents = this._realm.All() + .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); + + this._realm.RemoveRange(scoreEvents); + this._realm.Remove(score); + } + }); + } } \ No newline at end of file diff --git a/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLeaderboardApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLeaderboardApiEndpoints.cs new file mode 100644 index 00000000..7b666f58 --- /dev/null +++ b/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLeaderboardApiEndpoints.cs @@ -0,0 +1,55 @@ +using AttribDoc.Attributes; +using Bunkum.Core; +using Bunkum.Core.Endpoints; +using Bunkum.Protocols.Http; +using Refresh.GameServer.Database; +using Refresh.GameServer.Endpoints.ApiV3.ApiTypes; +using Refresh.GameServer.Endpoints.ApiV3.ApiTypes.Errors; +using Refresh.GameServer.Types.Roles; +using Refresh.GameServer.Types.UserData; +using Refresh.GameServer.Types.UserData.Leaderboard; + +namespace Refresh.GameServer.Endpoints.ApiV3.Admin; + +public class AdminLeaderboardApiEndpoints : EndpointGroup +{ + [ApiV3Endpoint("admin/scores/{uuid}", HttpMethods.Delete), MinimumRole(GameUserRole.Admin)] + [DocSummary("Removes a score by the score's UUID.")] + [DocError(typeof(ApiNotFoundError), ApiNotFoundError.ScoreMissingErrorWhen)] + public ApiOkResponse DeleteScore(RequestContext context, GameDatabaseContext database, + [DocSummary("The UUID of the score")] string uuid) + { + GameSubmittedScore? score = database.GetScoreByUuid(uuid); + if (score == null) return ApiNotFoundError.Instance; + + database.DeleteScore(score); + + return new ApiOkResponse(); + } + + [ApiV3Endpoint("admin/users/uuid/{uuid}/scores", HttpMethods.Delete), MinimumRole(GameUserRole.Admin)] + [DocSummary("Deletes all scores set by a user. Gets user by their UUID.")] + [DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)] + public ApiOkResponse DeleteScoresSetByUuid(RequestContext context, GameDatabaseContext database, + [DocSummary("The UUID of the user")] string uuid) + { + GameUser? user = database.GetUserByUuid(uuid); + if (user == null) return ApiNotFoundError.UserMissingError; + + database.DeleteScoresSetByUser(user); + return new ApiOkResponse(); + } + + [ApiV3Endpoint("admin/users/name/{username}/scores", HttpMethods.Delete), MinimumRole(GameUserRole.Admin)] + [DocSummary("Deletes all scores set by a user. Gets user by their username.")] + [DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)] + public ApiOkResponse DeleteScoresSetByUsername(RequestContext context, GameDatabaseContext database, + [DocSummary("The username of the user")] string username) + { + GameUser? user = database.GetUserByUsername(username); + if (user == null) return ApiNotFoundError.UserMissingError; + + database.DeleteScoresSetByUser(user); + return new ApiOkResponse(); + } +} \ No newline at end of file diff --git a/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLevelApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLevelApiEndpoints.cs index ec255bf7..d650c26b 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLevelApiEndpoints.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLevelApiEndpoints.cs @@ -22,7 +22,7 @@ public class AdminLevelApiEndpoints : EndpointGroup [DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)] public ApiOkResponse AddTeamPickToLevel(RequestContext context, GameDatabaseContext database, GameUser user, int id) { - GameLevel? level = database.GetLevelById(id); + GameLevel? level = database.GetLevelById(id); if (level == null) return ApiNotFoundError.LevelMissingError; database.AddTeamPickToLevel(level); diff --git a/Refresh.GameServer/Endpoints/ApiV3/LeaderboardApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/LeaderboardApiEndpoints.cs index 8d534103..09560f29 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/LeaderboardApiEndpoints.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/LeaderboardApiEndpoints.cs @@ -46,8 +46,8 @@ public ApiListResponse GetTopScoresForLevel(RequestContext [DocSummary("Gets an individual score by a UUID")] [DocError(typeof(ApiNotFoundError), "The score could not be found")] public ApiResponse GetScoreByUuid(RequestContext context, GameDatabaseContext database, - IDataStore dataStore, - [DocSummary("The UUID of the score")] string uuid, DataContext dataContext) + DataContext dataContext, + [DocSummary("The UUID of the score")] string uuid) { GameSubmittedScore? score = database.GetScoreByUuid(uuid); if (score == null) return ApiNotFoundError.Instance; diff --git a/RefreshTests.GameServer/Tests/Levels/ScoreModerationTests.cs b/RefreshTests.GameServer/Tests/Levels/ScoreModerationTests.cs new file mode 100644 index 00000000..a3a6a738 --- /dev/null +++ b/RefreshTests.GameServer/Tests/Levels/ScoreModerationTests.cs @@ -0,0 +1,96 @@ +using MongoDB.Bson; +using Refresh.GameServer.Authentication; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Roles; +using Refresh.GameServer.Types.UserData; +using Refresh.GameServer.Types.UserData.Leaderboard; + +namespace RefreshTests.GameServer.Tests.Levels; + +public class ScoreModerationTests : GameServerTest +{ + [Test] + public void DeletesIndividualScore() + { + // Arrange + using TestContext context = this.GetServer(); + + GameUser user = context.CreateUser(); + GameLevel level = context.CreateLevel(user); + + GameSubmittedScore score = context.SubmitScore(1, 1, level, user, TokenGame.LittleBigPlanet2, TokenPlatform.PS3); + string uuid = score.ScoreId.ToString(); + + // Act + context.Database.DeleteScore(context.Database.GetScoreByUuid(uuid)!); + + // Assert + Assert.That(context.Database.GetScoreByUuid(uuid), Is.Null); + } + + [Test] + public async Task AnonymousCannotDeleteIndividualScoreViaApi() + { + // Arrange + using TestContext context = this.GetServer(); + + GameUser user = context.CreateUser(); + GameLevel level = context.CreateLevel(user); + + GameSubmittedScore score = context.SubmitScore(1, 1, level, user, TokenGame.LittleBigPlanet2, TokenPlatform.PS3); + string uuid = score.ScoreId.ToString(); + + // Act + HttpResponseMessage response = await context.Http.DeleteAsync($"/api/v3/admin/scores/{uuid}"); + + // Assert + Assert.That(response.StatusCode, Is.EqualTo(Forbidden)); + Assert.That(context.Database.GetScoreByUuid(uuid), Is.Not.Null); + } + + [Test] + public async Task DeletesIndividualScoreViaApi() + { + // Arrange + using TestContext context = this.GetServer(); + + GameUser user = context.CreateUser(); + GameLevel level = context.CreateLevel(user); + context.Database.SetUserRole(user, GameUserRole.Admin); + + GameSubmittedScore score = context.SubmitScore(1, 1, level, user, TokenGame.LittleBigPlanet2, TokenPlatform.PS3); + string uuid = score.ScoreId.ToString(); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Api, user); + + // Act + HttpResponseMessage response = await client.DeleteAsync($"/api/v3/admin/scores/{uuid}"); + + // Assert + Assert.That(response.StatusCode, Is.EqualTo(OK)); + Assert.That(context.Database.GetScoreByUuid(uuid), Is.Null); + } + + [Test] + public void DeletesAllScoresByUser() + { + // Arrange + using TestContext context = this.GetServer(); + + GameUser user = context.CreateUser(); + GameLevel level1 = context.CreateLevel(user); + GameLevel level2 = context.CreateLevel(user); + + GameSubmittedScore score1 = context.SubmitScore(1, 1, level1, user, TokenGame.LittleBigPlanet2, TokenPlatform.PS3); + GameSubmittedScore score2 = context.SubmitScore(1, 1, level2, user, TokenGame.LittleBigPlanet2, TokenPlatform.PS3); + string uuid1 = score1.ScoreId.ToString(); + string uuid2 = score2.ScoreId.ToString(); + + // Act + context.Database.DeleteScoresSetByUser(user); + + // Assert + Assert.That(context.Database.GetScoreByUuid(uuid1), Is.Null); + Assert.That(context.Database.GetScoreByUuid(uuid2), Is.Null); + } +} \ No newline at end of file From 61cc9b5fafb2a890f5d979bf6ca15f40fb8cec6d Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Fri, 14 Jun 2024 13:50:23 -0700 Subject: [PATCH 02/37] Assets: Make asset dependencies postgres friendly (#523) Co-authored-by: jvyden --- .../Database/GameDatabaseContext.Assets.cs | 21 +++++++++++++++++++ .../Database/GameDatabaseProvider.cs | 18 +++++++++++++++- .../Response/Data/ApiGameAssetResponse.cs | 2 +- .../Endpoints/ApiV3/ResourceApiEndpoints.cs | 2 +- .../Endpoints/Game/ResourceEndpoints.cs | 2 +- .../Extensions/GameAssetExtensions.cs | 7 +++++-- Refresh.GameServer/Importing/AssetImporter.cs | 15 ++++--------- Refresh.GameServer/Refresh.GameServer.csproj | 12 +++++------ .../Types/Assets/AssetDependencyRelation.cs | 11 ++++++++++ Refresh.GameServer/Types/Assets/GameAsset.cs | 2 -- 10 files changed, 67 insertions(+), 25 deletions(-) create mode 100644 Refresh.GameServer/Types/Assets/AssetDependencyRelation.cs diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs index c9ce7162..01e6eff0 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs @@ -30,6 +30,27 @@ public partial class GameDatabaseContext // AssetConfiguration return null; } + + public IEnumerable GetAssetDependencies(GameAsset asset) + => this._realm.All().Where(a => a.Dependent == asset.AssetHash) + .AsEnumerable() + .Select(a => a.Dependency); + + public void AddOrOverwriteAssetDependencyRelations(string dependent, IEnumerable dependencies) + { + this._realm.Write(() => + { + // delete all existing relations. ensures duplicates won't exist when reprocessing + this._realm.RemoveRange(this._realm.All().Where(a => a.Dependent == dependent)); + + foreach (string dependency in dependencies) + this._realm.Add(new AssetDependencyRelation + { + Dependent = dependent, + Dependency = dependency, + }); + }); + } public IEnumerable GetAssetsByType(GameAssetType type) => this._realm.All() diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index f4593d24..3029992a 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -34,7 +34,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 127; + protected override ulong SchemaVersion => 128; protected override string Filename => "refreshGameServer.realm"; @@ -67,6 +67,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) typeof(RequestStatistics), typeof(SequentialIdStorage), typeof(GameContest), + typeof(AssetDependencyRelation), //grief report items typeof(GameReport), typeof(InfoBubble), @@ -353,6 +354,21 @@ protected override void Migrate(Migration migration, ulong oldVersion) // and PSP is the only game to upload TGA files. newAsset.IsPSP = newAsset.AssetType == GameAssetType.Tga; } + + // In version 128 assets were moved from a list on the asset to a separate "relations" table + if (oldVersion < 128) + { + IList dependencies = oldAsset.Dependencies; + + foreach (string dependency in dependencies) + { + migration.NewRealm.Add(new AssetDependencyRelation + { + Dependent = oldAsset.AssetHash, + Dependency = dependency, + }); + } + } } // Remove all scores with a null level, as in version 92 we started tracking story leaderboards differently diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Data/ApiGameAssetResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Data/ApiGameAssetResponse.cs index 2bbe46f1..83098a85 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Data/ApiGameAssetResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Data/ApiGameAssetResponse.cs @@ -23,7 +23,7 @@ public class ApiGameAssetResponse : IApiResponse, IDataConvertableFrom UploadImageAsset(RequestContext context return new ApiValidationError($"The asset must be under 2MB. Your file was {body.Length:N0} bytes."); } - GameAsset? gameAsset = importer.ReadAndVerifyAsset(hash, body, TokenPlatform.Website); + GameAsset? gameAsset = importer.ReadAndVerifyAsset(hash, body, TokenPlatform.Website, database); if (gameAsset == null) return ApiValidationError.CannotReadAssetError; diff --git a/Refresh.GameServer/Endpoints/Game/ResourceEndpoints.cs b/Refresh.GameServer/Endpoints/Game/ResourceEndpoints.cs index 43c2e37d..f36343ec 100644 --- a/Refresh.GameServer/Endpoints/Game/ResourceEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/ResourceEndpoints.cs @@ -61,7 +61,7 @@ public Response UploadAsset(RequestContext context, string hash, string type, by return RequestEntityTooLarge; } - GameAsset? gameAsset = importer.ReadAndVerifyAsset(hash, body, token.TokenPlatform); + GameAsset? gameAsset = importer.ReadAndVerifyAsset(hash, body, token.TokenPlatform, database); if (gameAsset == null) return BadRequest; diff --git a/Refresh.GameServer/Extensions/GameAssetExtensions.cs b/Refresh.GameServer/Extensions/GameAssetExtensions.cs index ac50c770..fdfca554 100644 --- a/Refresh.GameServer/Extensions/GameAssetExtensions.cs +++ b/Refresh.GameServer/Extensions/GameAssetExtensions.cs @@ -8,10 +8,13 @@ public static class GameAssetExtensions public static void TraverseDependenciesRecursively(this GameAsset asset, GameDatabaseContext database, Action callback) { callback(asset.AssetHash, asset); - foreach (string internalAssetHash in asset.Dependencies) + foreach (string internalAssetHash in database.GetAssetDependencies(asset)) { GameAsset? internalAsset = database.GetAssetFromHash(internalAssetHash); - callback(internalAssetHash, internalAsset); + + // Only run this if this is null, since the next recursion will trigger its own callback + if(internalAsset == null) + callback(internalAssetHash, internalAsset); internalAsset?.TraverseDependenciesRecursively(database, callback); } diff --git a/Refresh.GameServer/Importing/AssetImporter.cs b/Refresh.GameServer/Importing/AssetImporter.cs index dd58d2ce..0dbcc3ae 100644 --- a/Refresh.GameServer/Importing/AssetImporter.cs +++ b/Refresh.GameServer/Importing/AssetImporter.cs @@ -38,7 +38,7 @@ public void ImportFromDataStore(GameDatabaseContext database, IDataStore dataSto byte[] data = dataStore.GetDataFromStore(path); - GameAsset? newAsset = this.ReadAndVerifyAsset(hash, data, isPsp ? TokenPlatform.PSP : null); + GameAsset? newAsset = this.ReadAndVerifyAsset(hash, data, isPsp ? TokenPlatform.PSP : null, database); if (newAsset == null) continue; GameAsset? oldAsset = database.GetAssetFromHash(hash); @@ -90,7 +90,7 @@ static char GetHexChar(int value) [Pure] - public GameAsset? ReadAndVerifyAsset(string hash, byte[] data, TokenPlatform? platform) + public GameAsset? ReadAndVerifyAsset(string hash, byte[] data, TokenPlatform? platform, GameDatabaseContext database) { string checkedHash = BytesToHexString(SHA1.HashData(data)); @@ -115,10 +115,8 @@ static char GetHexChar(int value) try { List dependencies = this.ParseDependencyTree(data); - foreach (string dependency in dependencies) - { - asset.Dependencies.Add(dependency); - } + + database.AddOrOverwriteAssetDependencyRelations(hash, dependencies); } catch (Exception e) { @@ -142,11 +140,6 @@ or GameAssetType.Mip { return false; } - - #if DEBUG - char typeChar = (char)data[3]; - if (typeChar != 'b') throw new Exception($"Asset type {type} is not binary (char was '{typeChar}')"); - #endif return true; } diff --git a/Refresh.GameServer/Refresh.GameServer.csproj b/Refresh.GameServer/Refresh.GameServer.csproj index 2e0f63bc..92636578 100644 --- a/Refresh.GameServer/Refresh.GameServer.csproj +++ b/Refresh.GameServer/Refresh.GameServer.csproj @@ -52,12 +52,12 @@ - - + + - - - + + + @@ -71,7 +71,7 @@ - + diff --git a/Refresh.GameServer/Types/Assets/AssetDependencyRelation.cs b/Refresh.GameServer/Types/Assets/AssetDependencyRelation.cs new file mode 100644 index 00000000..3b998ace --- /dev/null +++ b/Refresh.GameServer/Types/Assets/AssetDependencyRelation.cs @@ -0,0 +1,11 @@ +using Realms; + +namespace Refresh.GameServer.Types.Assets; + +#nullable disable + +public partial class AssetDependencyRelation : IRealmObject +{ + public string Dependent { get; set; } + public string Dependency { get; set; } +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Assets/GameAsset.cs b/Refresh.GameServer/Types/Assets/GameAsset.cs index 70f75ca9..5ad047d1 100644 --- a/Refresh.GameServer/Types/Assets/GameAsset.cs +++ b/Refresh.GameServer/Types/Assets/GameAsset.cs @@ -19,8 +19,6 @@ [Ignored] public GameAssetType AssetType // ReSharper disable once InconsistentNaming internal int _AssetType { get; set; } - public IList Dependencies { get; } = null!; - [Ignored] public AssetSafetyLevel SafetyLevel => AssetSafetyLevelExtensions.FromAssetType(this.AssetType); public string? AsMainlineIconHash { get; set; } From 880d8f2d2c511d660e79dc8b7fb7e771d4012473 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 20:51:45 +0000 Subject: [PATCH 03/37] Bump IronCompress from 1.5.1 to 1.5.2 Bumps [IronCompress](https://github.com/aloneguid/ironcompress) from 1.5.1 to 1.5.2. - [Release notes](https://github.com/aloneguid/ironcompress/releases) - [Commits](https://github.com/aloneguid/ironcompress/compare/1.5.1...1.5.2) --- updated-dependencies: - dependency-name: IronCompress dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Refresh.GameServer/Refresh.GameServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.GameServer/Refresh.GameServer.csproj b/Refresh.GameServer/Refresh.GameServer.csproj index 92636578..6695a339 100644 --- a/Refresh.GameServer/Refresh.GameServer.csproj +++ b/Refresh.GameServer/Refresh.GameServer.csproj @@ -67,7 +67,7 @@ - + From 05b1c75ce6d3b842cbca9687827e4d2c91cdd194 Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 14 Jun 2024 17:08:47 -0400 Subject: [PATCH 04/37] Shorten time frame required to be considered 'active' to 7 days --- Refresh.GameServer/Database/GameDatabaseContext.Users.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs index 80358c66..25c9aba0 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs @@ -190,8 +190,8 @@ public void UpdateUserData(GameUser user, ApiUpdateUserRequest data) [Pure] public int GetActiveUserCount() { - DateTimeOffset lastMonth = this._time.Now.Subtract(TimeSpan.FromDays(30)); - return this._realm.All().Count(u => u.LastLoginDate > lastMonth); + DateTimeOffset timeFrame = this._time.Now.Subtract(TimeSpan.FromDays(7)); + return this._realm.All().Count(u => u.LastLoginDate > timeFrame); } public void UpdateUserPins(GameUser user, UserPins pinsUpdate) From 61139bf6bd7134a7a8ba5db94d36d753bec30fd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 04:32:35 +0000 Subject: [PATCH 05/37] Bump Bunkum.AutoDiscover, Bunkum and Bunkum.Protocols.Http Bumps [Bunkum.AutoDiscover](https://github.com/PlanetBunkum/Bunkum), [Bunkum](https://github.com/PlanetBunkum/Bunkum) and [Bunkum.Protocols.Http](https://github.com/PlanetBunkum/Bunkum). These dependencies needed to be updated together. Updates `Bunkum.AutoDiscover` from 4.5.4 to 4.5.6 - [Commits](https://github.com/PlanetBunkum/Bunkum/compare/v4.5.4...v4.5.6) Updates `Bunkum` from 4.5.4 to 4.5.6 - [Commits](https://github.com/PlanetBunkum/Bunkum/compare/v4.5.4...v4.5.6) Updates `Bunkum.Protocols.Http` from 4.5.4 to 4.5.6 - [Commits](https://github.com/PlanetBunkum/Bunkum/compare/v4.5.4...v4.5.6) --- updated-dependencies: - dependency-name: Bunkum.AutoDiscover dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Bunkum dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Bunkum.Protocols.Http dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Refresh.GameServer/Refresh.GameServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.GameServer/Refresh.GameServer.csproj b/Refresh.GameServer/Refresh.GameServer.csproj index 6695a339..96b1f3b3 100644 --- a/Refresh.GameServer/Refresh.GameServer.csproj +++ b/Refresh.GameServer/Refresh.GameServer.csproj @@ -54,7 +54,7 @@ - + From 44c631c79332e51eac9d8f80136ac73df58b0929 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 04:32:57 +0000 Subject: [PATCH 06/37] Bump Bunkum from 4.5.4 to 4.5.6 Bumps [Bunkum](https://github.com/PlanetBunkum/Bunkum) from 4.5.4 to 4.5.6. - [Commits](https://github.com/PlanetBunkum/Bunkum/compare/v4.5.4...v4.5.6) --- updated-dependencies: - dependency-name: Bunkum dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Refresh.Common/Refresh.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.Common/Refresh.Common.csproj b/Refresh.Common/Refresh.Common.csproj index 487bbc5d..50c91b14 100644 --- a/Refresh.Common/Refresh.Common.csproj +++ b/Refresh.Common/Refresh.Common.csproj @@ -7,7 +7,7 @@ - + From 6b9e0b08e456b033e30953c4c5a038236c91ce19 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 17:56:08 -0400 Subject: [PATCH 07/37] Update Refresh.Common.csproj Signed-off-by: jvyden --- Refresh.Common/Refresh.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.Common/Refresh.Common.csproj b/Refresh.Common/Refresh.Common.csproj index 50c91b14..a2d94508 100644 --- a/Refresh.Common/Refresh.Common.csproj +++ b/Refresh.Common/Refresh.Common.csproj @@ -8,7 +8,7 @@ - + From 80fe667f82ca5f4f880a5f624cfb5fd0e9564426 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 18:29:30 -0400 Subject: [PATCH 08/37] Remove GameLocation from database models --- .../Database/GameDatabaseContext.Levels.cs | 1 - .../Database/GameDatabaseContext.Users.cs | 10 +++++---- .../Database/GameDatabaseProvider.cs | 21 ++++++++++++++++--- .../Response/Data/ApiGameLocationResponse.cs | 10 +++------ .../Response/Levels/ApiGameLevelResponse.cs | 3 ++- .../Users/ApiExtendedGameUserResponse.cs | 2 +- .../Response/Users/ApiGameUserResponse.cs | 2 +- .../DataTypes/Request/GameLevelRequest.cs | 3 ++- .../DataTypes/Response/GameLevelResponse.cs | 2 +- .../DataTypes/Response/GameUserResponse.cs | 2 +- Refresh.GameServer/Types/GameLocation.cs | 16 ++++++++++---- Refresh.GameServer/Types/Levels/GameLevel.cs | 4 +++- Refresh.GameServer/Types/UserData/GameUser.cs | 4 +++- RefreshTests.GameServer/TestContext.cs | 1 - 14 files changed, 53 insertions(+), 28 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs index 7a7d9df0..dc99e048 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs @@ -42,7 +42,6 @@ public GameLevel GetStoryLevelById(int id) { Title = $"Story level #{id}", Publisher = null, - Location = GameLocation.Zero, Source = GameLevelSource.Story, StoryId = id, }; diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs index 25c9aba0..c4297995 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs @@ -15,7 +15,6 @@ public partial class GameDatabaseContext // Users { private static readonly GameUser DeletedUser = new() { - Location = GameLocation.Zero, Username = "!DeletedUser", Description = "I'm a fake user that represents deleted users for levels.", FakeUser = true, @@ -31,7 +30,6 @@ public partial class GameDatabaseContext // Users if (username.StartsWith("!")) return new() { - Location = GameLocation.Zero, Username = username, Description = "I'm a fake user that represents a non existent publisher for re-published levels.", FakeUser = true, @@ -80,7 +78,10 @@ public void UpdateUserData(GameUser user, SerializedUpdateData data, TokenGame g user.Description = data.Description; if (data.Location != null) - user.Location = data.Location; + { + user.LocationX = data.Location.X; + user.LocationY = data.Location.Y; + } if (data.PlanetsHash != null) // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault @@ -286,7 +287,8 @@ public void DeleteUser(GameUser user) this._realm.Write(() => { user.Pins = new UserPins(); - user.Location = new GameLocation(); + user.LocationX = 0; + user.LocationY = 0; user.Description = deletedReason; user.EmailAddress = null; user.PasswordBcrypt = "deleted"; diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index 3029992a..5867f817 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -34,14 +34,13 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 128; + protected override ulong SchemaVersion => 129; protected override string Filename => "refreshGameServer.realm"; protected override List SchemaTypes { get; } = new() { typeof(GameUser), - typeof(GameLocation), typeof(UserPins), typeof(Token), typeof(GameLevel), @@ -113,7 +112,8 @@ protected override void Migrate(Migration migration, ulong oldVersion) if (oldVersion < 3) { newUser.Description = ""; - newUser.Location = new GameLocation { X = 0, Y = 0, }; + newUser.LocationX = 0; + newUser.LocationY = 0; } //In version 4, GameLocation went from TopLevel -> Embedded, and UserPins was added @@ -187,6 +187,14 @@ protected override void Migrate(Migration migration, ulong oldVersion) .Where(a => a.OriginalUploader?.UserId == newUser.UserId) .Sum(a => a.SizeInBytes); } + + // In version 129, we split locations from an embedded object out to two fields + if (oldVersion < 129) + { + // cast required because apparently they're stored as longs??? + newUser.LocationX = (int)oldUser.Location.X; + newUser.LocationY = (int)oldUser.Location.Y; + } } IQueryable? oldLevels = migration.OldRealm.DynamicApi.All("GameLevel"); @@ -247,6 +255,13 @@ protected override void Migrate(Migration migration, ulong oldVersion) { newLevel._Source = (int)GameLevelSource.User; } + + // In version 129, we split locations from an embedded object out to two fields + if (oldVersion < 129) + { + newLevel.LocationX = (int)oldLevel.Location.X; + newLevel.LocationY = (int)oldLevel.Location.Y; + } } // In version 22, tokens added expiry and types so just wipe them all diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Data/ApiGameLocationResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Data/ApiGameLocationResponse.cs index f983cad3..acb602d2 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Data/ApiGameLocationResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Data/ApiGameLocationResponse.cs @@ -8,16 +8,12 @@ public class ApiGameLocationResponse : IApiResponse { public required int X { get; set; } public required int Y { get; set; } - - [ContractAnnotation("null => null; notnull => notnull")] - public static ApiGameLocationResponse? FromGameLocation(GameLocation? location) + public static ApiGameLocationResponse? FromLocation(int x, int y) { - if (location == null) return null; - return new ApiGameLocationResponse { - X = location.X, - Y = location.Y, + X = x, + Y = y, }; } } \ No newline at end of file diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Levels/ApiGameLevelResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Levels/ApiGameLevelResponse.cs index 398f567a..d67292b9 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Levels/ApiGameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Levels/ApiGameLevelResponse.cs @@ -1,6 +1,7 @@ using Refresh.GameServer.Authentication; using Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response.Data; using Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response.Users; +using Refresh.GameServer.Types; using Refresh.GameServer.Types.Data; using Refresh.GameServer.Types.Levels; using Refresh.GameServer.Types.Reviews; @@ -62,7 +63,7 @@ public class ApiGameLevelResponse : IApiResponse, IDataConvertableFrom Title = this.Title, IconHash = this.IconHash, Description = this.Description, - Location = this.Location, + LocationX = this.Location.X, + LocationY = this.Location.Y, RootResource = this.RootResource, PublishDate = this.PublishDate, UpdateDate = this.UpdateDate, diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs index f82a748b..e43b83da 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs @@ -155,7 +155,7 @@ public static GameLevelResponse FromHash(string hash, DataContext dataContext) Title = old.Title, IconHash = old.IconHash, Description = old.Description, - Location = old.Location, + Location = new GameLocation(old.LocationX, old.LocationY), GameVersion = old.GameVersion.ToSerializedGame(), RootResource = old.RootResource, PublishDate = old.PublishDate, diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs index e301c816..b7526959 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs @@ -59,7 +59,7 @@ public class GameUserResponse : IDataConvertableFrom GameUserResponse response = new() { Description = old.Description, - Location = old.Location, + Location = new GameLocation(old.LocationX, old.LocationY), PlanetsHash = "0", Handle = SerializedUserHandle.FromUser(old, dataContext), diff --git a/Refresh.GameServer/Types/GameLocation.cs b/Refresh.GameServer/Types/GameLocation.cs index cbb70e29..1de4c8c5 100644 --- a/Refresh.GameServer/Types/GameLocation.cs +++ b/Refresh.GameServer/Types/GameLocation.cs @@ -4,7 +4,6 @@ namespace Refresh.GameServer.Types; [XmlType("location")] -[JsonObject(MemberSerialization.OptIn)] public partial class GameLocation : IEmbeddedObject { public static GameLocation Zero => new() @@ -12,7 +11,16 @@ public partial class GameLocation : IEmbeddedObject X = 0, Y = 0, }; - - [XmlElement("y")] [JsonProperty] public int X { get; set; } - [XmlElement("x")] [JsonProperty] public int Y { get; set; } + + public GameLocation() + {} + + public GameLocation(int x, int y) + { + this.X = x; + this.Y = y; + } + + [XmlElement("y")] public int X { get; set; } + [XmlElement("x")] public int Y { get; set; } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Levels/GameLevel.cs b/Refresh.GameServer/Types/Levels/GameLevel.cs index ff280dd3..f789ec9f 100644 --- a/Refresh.GameServer/Types/Levels/GameLevel.cs +++ b/Refresh.GameServer/Types/Levels/GameLevel.cs @@ -22,7 +22,9 @@ public partial class GameLevel : IRealmObject, ISequentialId public string IconHash { get; set; } = "0"; [Indexed(IndexType.FullText)] public string Description { get; set; } = ""; - public GameLocation Location { get; set; } = GameLocation.Zero; + + public int LocationX { get; set; } + public int LocationY { get; set; } public string RootResource { get; set; } = string.Empty; diff --git a/Refresh.GameServer/Types/UserData/GameUser.cs b/Refresh.GameServer/Types/UserData/GameUser.cs index 050730ed..cf1c7f67 100644 --- a/Refresh.GameServer/Types/UserData/GameUser.cs +++ b/Refresh.GameServer/Types/UserData/GameUser.cs @@ -55,7 +55,9 @@ public partial class GameUser : IRealmObject, IRateLimitUser public int FilesizeQuotaUsage { get; set; } public string Description { get; set; } = ""; - public GameLocation Location { get; set; } = GameLocation.Zero; + + public int LocationX { get; set; } + public int LocationY { get; set; } public DateTimeOffset JoinDate { get; set; } public UserPins Pins { get; set; } = new(); diff --git a/RefreshTests.GameServer/TestContext.cs b/RefreshTests.GameServer/TestContext.cs index fbaefb9b..38ad7fe3 100644 --- a/RefreshTests.GameServer/TestContext.cs +++ b/RefreshTests.GameServer/TestContext.cs @@ -119,7 +119,6 @@ public GameLevel CreateLevel(GameUser author, string title = "Level", TokenGame { Title = title, Publisher = author, - Location = GameLocation.Zero, Source = GameLevelSource.User, GameVersion = gameVersion, }; From 6d171b3bba208260e9572cf3cd55e873265a8978 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 18:31:04 -0400 Subject: [PATCH 09/37] Remove IEmbeddedObject marker from GameLocation --- Refresh.GameServer/Types/GameLocation.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Refresh.GameServer/Types/GameLocation.cs b/Refresh.GameServer/Types/GameLocation.cs index 1de4c8c5..24eb6ef2 100644 --- a/Refresh.GameServer/Types/GameLocation.cs +++ b/Refresh.GameServer/Types/GameLocation.cs @@ -1,10 +1,9 @@ using System.Xml.Serialization; -using Realms; namespace Refresh.GameServer.Types; [XmlType("location")] -public partial class GameLocation : IEmbeddedObject +public class GameLocation { public static GameLocation Zero => new() { From 9c689d18ab7219fe5254d826ffbc4971d4163fd2 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 18:45:46 -0400 Subject: [PATCH 10/37] Remove ability to store grief reports in Realm --- .../Database/GameDatabaseContext.Reports.cs | 16 - .../Database/GameDatabaseContext.Users.cs | 11 - .../Database/GameDatabaseProvider.cs | 14 +- .../DataTypes/Request/ApiUpdateUserRequest.cs | 3 +- .../Users/ApiExtendedGameUserResponse.cs | 4 +- .../Endpoints/Game/ReportingEndpoints.cs | 83 ++--- Refresh.GameServer/Services/CommandService.cs | 10 - Refresh.GameServer/Types/Report/GameReport.cs | 2 +- Refresh.GameServer/Types/Report/InfoBubble.cs | 2 +- Refresh.GameServer/Types/Report/Marqee.cs | 2 +- Refresh.GameServer/Types/Report/Player.cs | 2 +- Refresh.GameServer/Types/Report/Rect.cs | 2 +- .../Types/Report/ScreenElements.cs | 2 +- Refresh.GameServer/Types/Report/ScreenRect.cs | 2 +- Refresh.GameServer/Types/Report/Slot.cs | 2 +- Refresh.GameServer/Types/UserData/GameUser.cs | 6 +- .../Reporting/ReportingEndpointsTests.cs | 336 ------------------ 17 files changed, 45 insertions(+), 454 deletions(-) delete mode 100644 Refresh.GameServer/Database/GameDatabaseContext.Reports.cs delete mode 100644 RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Reports.cs b/Refresh.GameServer/Database/GameDatabaseContext.Reports.cs deleted file mode 100644 index e901b98a..00000000 --- a/Refresh.GameServer/Database/GameDatabaseContext.Reports.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Refresh.GameServer.Types.Report; - -namespace Refresh.GameServer.Database; - -public partial class GameDatabaseContext -{ - public void AddGriefReport(GameReport report) - { - this.AddSequentialObject(report, () => {}); - } - - public DatabaseList GetGriefReports(int count, int skip) - { - return new DatabaseList(this._realm.All(), skip, count); - } -} \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs index c4297995..b5610c2c 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs @@ -167,9 +167,6 @@ public void UpdateUserData(GameUser user, ApiUpdateUserRequest data) if (data.RpcnAuthenticationAllowed != null) user.RpcnAuthenticationAllowed = data.RpcnAuthenticationAllowed.Value; - - if (data.RedirectGriefReportsToPhotos != null) - user.RedirectGriefReportsToPhotos = data.RedirectGriefReportsToPhotos.Value; if (data.UnescapeXmlSequences != null) user.UnescapeXmlSequences = data.UnescapeXmlSequences.Value; @@ -338,14 +335,6 @@ public void SetUnescapeXmlSequences(GameUser user, bool value) user.UnescapeXmlSequences = value; }); } - - public void SetUserGriefReportRedirection(GameUser user, bool value) - { - this._realm.Write(() => - { - user.RedirectGriefReportsToPhotos = value; - }); - } public void ClearForceMatch(GameUser user) { diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index 5867f817..e329d912 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -34,7 +34,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 129; + protected override ulong SchemaVersion => 130; protected override string Filename => "refreshGameServer.realm"; @@ -67,15 +67,6 @@ protected GameDatabaseProvider(IDateTimeProvider time) typeof(SequentialIdStorage), typeof(GameContest), typeof(AssetDependencyRelation), - //grief report items - typeof(GameReport), - typeof(InfoBubble), - typeof(Marqee), - typeof(Player), - typeof(Rect), - typeof(ScreenElements), - typeof(ScreenRect), - typeof(Slot), typeof(GameReview), typeof(DisallowedUser), }; @@ -167,9 +158,6 @@ protected override void Migrate(Migration migration, ulong oldVersion) newUser.VitaPlanetsHash = "0"; } - // In version 94, we added an option to redirect grief reports to photos - if (oldVersion < 94) newUser.RedirectGriefReportsToPhotos = false; - // In version 100, we started enforcing lowercase email addresses if (oldVersion < 100) newUser.EmailAddress = oldUser.EmailAddress?.ToLowerInvariant(); diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiUpdateUserRequest.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiUpdateUserRequest.cs index 5986e043..38c29daf 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiUpdateUserRequest.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiUpdateUserRequest.cs @@ -11,8 +11,7 @@ public class ApiUpdateUserRequest public bool? PsnAuthenticationAllowed { get; set; } public bool? RpcnAuthenticationAllowed { get; set; } - - public bool? RedirectGriefReportsToPhotos { get; set; } + public bool? UnescapeXmlSequences { get; set; } public string? EmailAddress { get; set; } diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Users/ApiExtendedGameUserResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Users/ApiExtendedGameUserResponse.cs index d58fb560..93660198 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Users/ApiExtendedGameUserResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Users/ApiExtendedGameUserResponse.cs @@ -29,8 +29,7 @@ public class ApiExtendedGameUserResponse : IApiResponse, IDataConvertableFrom subjects = new(); - if (body.Players != null) - subjects.AddRange(body.Players.Select(player => new SerializedPhotoSubject - { - Username = player.Username, - DisplayName = player.Username, - // ReSharper disable PossibleLossOfFraction YES I KNOW THESE ARE INTEGERS - BoundsList = player.Rectangle == null - ? null : $"{(float)(player.Rectangle.Left - imageSize.Width / 2) / (imageSize.Width / 2)}," + - $"{(float)(player.Rectangle.Top - imageSize.Height / 2) / (imageSize.Height / 2)}," + - $"{(float)(player.Rectangle.Right - imageSize.Width / 2) / (imageSize.Width / 2)}," + - $"{(float)(player.Rectangle.Bottom - imageSize.Height / 2) / (imageSize.Height / 2)}", - })); + List subjects = new(); + if (body.Players != null) + subjects.AddRange(body.Players.Select(player => new SerializedPhotoSubject + { + Username = player.Username, + DisplayName = player.Username, + // ReSharper disable PossibleLossOfFraction YES I KNOW THESE ARE INTEGERS + BoundsList = player.Rectangle == null + ? null : $"{(float)(player.Rectangle.Left - imageSize.Width / 2) / (imageSize.Width / 2)}," + + $"{(float)(player.Rectangle.Top - imageSize.Height / 2) / (imageSize.Height / 2)}," + + $"{(float)(player.Rectangle.Right - imageSize.Width / 2) / (imageSize.Width / 2)}," + + $"{(float)(player.Rectangle.Bottom - imageSize.Height / 2) / (imageSize.Height / 2)}", + })); - string hash = body.JpegHash; + string hash = body.JpegHash; - database.UploadPhoto(new SerializedPhoto + database.UploadPhoto(new SerializedPhoto + { + Timestamp = time.TimestampSeconds, + AuthorName = user.Username, + SmallHash = hash, + MediumHash = hash, + LargeHash = hash, + PlanHash = "0", + //If the level id is 0 or we couldn't find the level null, dont fill out the `Level` field + Level = body.LevelId == 0 || level == null ? null : new SerializedPhotoLevel { - Timestamp = time.TimestampSeconds, - AuthorName = user.Username, - SmallHash = hash, - MediumHash = hash, - LargeHash = hash, - PlanHash = "0", - //If the level id is 0 or we couldn't find the level null, dont fill out the `Level` field - Level = body.LevelId == 0 || level == null ? null : new SerializedPhotoLevel - { - LevelId = level.LevelId, - Title = level.Title, - Type = level.Source switch { - GameLevelSource.User => "user", - GameLevelSource.Story => "developer", - _ => throw new ArgumentOutOfRangeException(), - }, + LevelId = level.LevelId, + Title = level.Title, + Type = level.Source switch { + GameLevelSource.User => "user", + GameLevelSource.Story => "developer", + _ => throw new ArgumentOutOfRangeException(), }, - PhotoSubjects = subjects, - }, user); + }, + PhotoSubjects = subjects, + }, user); - return OK; - } - - //If the level is specified but its invalid, set it to 0, to indicate the level is unknown - //This case is hit when someone makes a grief report from a non-existent level, which we allow - if (body.LevelId != 0 && level == null) - body.LevelId = 0; - - //Basic validation - if (body.Players is { Length: > 4 } || body.ScreenElements is { Player.Length: > 4 }) - //Return OK on PSP, since if we dont, it will error when trying to access the community moon and soft-lock the save file - return context.IsPSP() ? OK : BadRequest; - - database.AddGriefReport(body); - return OK; } } \ No newline at end of file diff --git a/Refresh.GameServer/Services/CommandService.cs b/Refresh.GameServer/Services/CommandService.cs index ad28897f..d4f9547d 100644 --- a/Refresh.GameServer/Services/CommandService.cs +++ b/Refresh.GameServer/Services/CommandService.cs @@ -105,16 +105,6 @@ public void HandleCommand(CommandInvocation command, GameDatabaseContext databas database.ClearForceMatch(user); break; } - case "griefphotoson": - { - database.SetUserGriefReportRedirection(user, true); - break; - } - case "griefphotosoff": - { - database.SetUserGriefReportRedirection(user, false); - break; - } case "unescapexmlon": { database.SetUnescapeXmlSequences(user, true); diff --git a/Refresh.GameServer/Types/Report/GameReport.cs b/Refresh.GameServer/Types/Report/GameReport.cs index ab80aceb..d5d67724 100644 --- a/Refresh.GameServer/Types/Report/GameReport.cs +++ b/Refresh.GameServer/Types/Report/GameReport.cs @@ -7,7 +7,7 @@ namespace Refresh.GameServer.Types.Report; #nullable disable [XmlRoot("griefReport")] -public partial class GameReport : IRealmObject, ISequentialId +public class GameReport { [XmlIgnore] private IList InternalInfoBubble { get; } diff --git a/Refresh.GameServer/Types/Report/InfoBubble.cs b/Refresh.GameServer/Types/Report/InfoBubble.cs index 65d191c3..3811e212 100644 --- a/Refresh.GameServer/Types/Report/InfoBubble.cs +++ b/Refresh.GameServer/Types/Report/InfoBubble.cs @@ -6,7 +6,7 @@ namespace Refresh.GameServer.Types.Report; #nullable disable [XmlRoot("infoBubble")] -public partial class InfoBubble : IEmbeddedObject +public class InfoBubble { [XmlElement("slot")] public Slot Slot { get; set; } diff --git a/Refresh.GameServer/Types/Report/Marqee.cs b/Refresh.GameServer/Types/Report/Marqee.cs index d7c28709..16a20974 100644 --- a/Refresh.GameServer/Types/Report/Marqee.cs +++ b/Refresh.GameServer/Types/Report/Marqee.cs @@ -6,7 +6,7 @@ namespace Refresh.GameServer.Types.Report; #nullable disable [XmlRoot("marqee")] -public partial class Marqee : IEmbeddedObject +public class Marqee { [XmlElement("rect")] public Rect Rect { get; set; } diff --git a/Refresh.GameServer/Types/Report/Player.cs b/Refresh.GameServer/Types/Report/Player.cs index c88d5730..06156245 100644 --- a/Refresh.GameServer/Types/Report/Player.cs +++ b/Refresh.GameServer/Types/Report/Player.cs @@ -6,7 +6,7 @@ namespace Refresh.GameServer.Types.Report; #nullable disable [XmlRoot("player")] -public partial class Player : IEmbeddedObject +public class Player { [XmlElement("id")] public string Username { get; set; } diff --git a/Refresh.GameServer/Types/Report/Rect.cs b/Refresh.GameServer/Types/Report/Rect.cs index 3f578edb..dbc93748 100644 --- a/Refresh.GameServer/Types/Report/Rect.cs +++ b/Refresh.GameServer/Types/Report/Rect.cs @@ -4,7 +4,7 @@ namespace Refresh.GameServer.Types.Report; [XmlRoot("rect")] -public partial class Rect : IEmbeddedObject +public class Rect { [XmlAttribute(AttributeName="t")] public long Top { get; set; } diff --git a/Refresh.GameServer/Types/Report/ScreenElements.cs b/Refresh.GameServer/Types/Report/ScreenElements.cs index cc18283d..aa07ed62 100644 --- a/Refresh.GameServer/Types/Report/ScreenElements.cs +++ b/Refresh.GameServer/Types/Report/ScreenElements.cs @@ -6,7 +6,7 @@ namespace Refresh.GameServer.Types.Report; #nullable disable [XmlRoot("screenElements")] -public partial class ScreenElements : IEmbeddedObject +public class ScreenElements { [XmlIgnore] private IList InternalSlot { get; } diff --git a/Refresh.GameServer/Types/Report/ScreenRect.cs b/Refresh.GameServer/Types/Report/ScreenRect.cs index 5ea6eeff..889fbf29 100644 --- a/Refresh.GameServer/Types/Report/ScreenRect.cs +++ b/Refresh.GameServer/Types/Report/ScreenRect.cs @@ -6,7 +6,7 @@ namespace Refresh.GameServer.Types.Report; #nullable disable [XmlRoot("screenRect")] -public partial class ScreenRect : IEmbeddedObject +public class ScreenRect { [XmlElement("rect")] public Rect Rect { get; set; } diff --git a/Refresh.GameServer/Types/Report/Slot.cs b/Refresh.GameServer/Types/Report/Slot.cs index 09559859..35865b89 100644 --- a/Refresh.GameServer/Types/Report/Slot.cs +++ b/Refresh.GameServer/Types/Report/Slot.cs @@ -6,7 +6,7 @@ namespace Refresh.GameServer.Types.Report; #nullable disable [XmlRoot("slot")] -public partial class Slot : IEmbeddedObject +public class Slot { [XmlElement("id")] public int Id { get; set; } diff --git a/Refresh.GameServer/Types/UserData/GameUser.cs b/Refresh.GameServer/Types/UserData/GameUser.cs index cf1c7f67..3cfe3736 100644 --- a/Refresh.GameServer/Types/UserData/GameUser.cs +++ b/Refresh.GameServer/Types/UserData/GameUser.cs @@ -106,11 +106,7 @@ public Visibility LevelVisibility get => (Visibility)this._LevelVisibility; set => this._LevelVisibility = (int)value; } - - /// - /// If `true`, turn all grief reports into photo uploads - /// - public bool RedirectGriefReportsToPhotos { get; set; } + /// /// If `true`, unescape XML tags sent to /filter /// diff --git a/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs b/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs deleted file mode 100644 index 2e08e5b9..00000000 --- a/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs +++ /dev/null @@ -1,336 +0,0 @@ -using System.Reflection; -using Refresh.GameServer.Authentication; -using Refresh.GameServer.Database; -using Refresh.GameServer.Resources; -using Refresh.GameServer.Types.Levels; -using Refresh.GameServer.Types.Photos; -using Refresh.GameServer.Types.Report; -using Refresh.GameServer.Types.UserData; -using RefreshTests.GameServer.Extensions; - -namespace RefreshTests.GameServer.Tests.Reporting; - -public class ReportingEndpointsTests : GameServerTest -{ - [Test] - public void UploadReport() - { - using TestContext context = this.GetServer(); - GameUser user = context.CreateUser(); - GameLevel level = context.CreateLevel(user); - - using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, user); - - GameReport report = new() - { - InfoBubble = new InfoBubble[] - {}, - Type = GriefReportType.Obscene, - Marqee = new Marqee - { - Rect = new Rect - { - Top = 10, - Left = 10, - Bottom = 20, - Right = 20, - }, - }, - LevelOwner = level.Publisher!.Username, - InitialStateHash = null, - LevelType = "user", - LevelId = level.LevelId, - Description = "This is a description, sent by LBP3", - GriefStateHash = null, - JpegHash = null, - Players = new Player[] - { - new() - { - Username = user.Username, - Rectangle = new Rect - { - Top = 10, - Left = 10, - Bottom = 20, - Right = 20, - }, - Reporter = true, - IngameNow = true, - PlayerNumber = 0, - Text = "Some text", - ScreenRect = new ScreenRect - { - Rect = new Rect - { - Top = 10, - Left = 10, - Bottom = 20, - Right = 20, - }, - }, - }, - }, - ScreenElements = new ScreenElements(), - }; - - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(report.AsXML())).Result; - Assert.That(response.StatusCode, Is.EqualTo(OK)); - - context.Database.Refresh(); - - DatabaseList griefReports = context.Database.GetGriefReports(10, 0); - Assert.That(griefReports.TotalItems, Is.EqualTo(1)); - List reports = griefReports.Items.ToList(); - Assert.That(reports[0].Description, Is.EqualTo(report.Description)); - Assert.That(reports[0].LevelId, Is.EqualTo(report.LevelId)); - } - - [Test] - public void CanUploadReportWithBadLevel() - { - using TestContext context = this.GetServer(); - GameUser user = context.CreateUser(); - - using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, user); - - GameReport report = new() - { - LevelId = int.MaxValue, - }; - - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(report.AsXML())).Result; - Assert.That(response.StatusCode, Is.EqualTo(OK)); - - context.Database.Refresh(); - - DatabaseList griefReports = context.Database.GetGriefReports(10, 0); - Assert.That(griefReports.TotalItems, Is.EqualTo(1)); - List reports = griefReports.Items.ToList(); - Assert.That(reports[0].Description, Is.EqualTo(report.Description)); - } - - [TestCase(true)] - [TestCase(false)] - public void CantUploadReportWithBadPlayerCount(bool psp) - { - using TestContext context = this.GetServer(); - GameUser user = context.CreateUser(); - - using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, user); - if(psp) client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT"); - - GameReport report = new() - { - LevelId = 0, - Players = new Player[] - { - new() - { - Username = "hee", - }, - new() - { - Username = "haw", - }, - new() - { - Username = "ham", - }, - new() - { - Username = "burg", - }, - new() - { - Username = "er", - }, - }, - }; - - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(report.AsXML())).Result; - Assert.That(response.StatusCode, Is.EqualTo(psp ? OK : BadRequest)); - } - - [TestCase(true)] - [TestCase(false)] - public void CantUploadReportWithBadScreenElementPlayerCount(bool psp) - { - using TestContext context = this.GetServer(); - GameUser user = context.CreateUser(); - - using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, user); - if(psp) client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT"); - - GameReport report = new() - { - LevelId = 0, - ScreenElements = new ScreenElements - { - Player = new Player[] - { - new() - { - Username = "hee", - }, - new() - { - Username = "haw", - }, - new() - { - Username = "ham", - }, - new() - { - Username = "burg", - }, - new() - { - Username = "er", - }, - }, - }, - }; - - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(report.AsXML())).Result; - Assert.That(response.StatusCode, Is.EqualTo(psp ? OK : BadRequest)); - } - - private const string TEST_ASSET_HASH = "0ec63b140374ba704a58fa0c743cb357683313dd"; - private static readonly byte[] TestAsset = ResourceHelper.ReadResource("RefreshTests.GameServer.Resources.1x1.png", Assembly.GetExecutingAssembly()); - - [TestCase(TokenGame.LittleBigPlanet1)] - [TestCase(TokenGame.LittleBigPlanet2)] - [TestCase(TokenGame.LittleBigPlanet3)] - [TestCase(TokenGame.LittleBigPlanetVita)] - [TestCase(TokenGame.LittleBigPlanetPSP)] - public void RedirectGriefReportToPhoto(TokenGame game) - { - using TestContext context = this.GetServer(); - GameUser user = context.CreateUser(); - GameLevel level = context.CreateLevel(user); - - //Enable the grief report re-direction - context.Database.SetUserGriefReportRedirection(user, true); - context.Database.Refresh(); - - using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, game, TokenPlatform.PS3, user); - - //Upload our """photo""" - HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; - Assert.That(message.StatusCode, Is.EqualTo(OK)); - - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport - { - Players = new Player[] - { - new() - { - Username = user.Username, - Rectangle = new Rect - { - Top = 10, - Left = 10, - Bottom = 20, - Right = 20, - }, - }, - }, - LevelId = level.LevelId, - JpegHash = TEST_ASSET_HASH, - }.AsXML())).Result; - Assert.That(response.StatusCode, Is.EqualTo(OK)); - - context.Database.Refresh(); - - DatabaseList photos = context.Database.GetPhotosByUser(user, 10, 0); - Assert.That(photos.TotalItems, Is.EqualTo(1)); - - GamePhoto photo = photos.Items.First(); - Assert.That(photo.Publisher.UserId, Is.EqualTo(user.UserId)); - } - - [TestCase(TokenGame.LittleBigPlanet1)] - [TestCase(TokenGame.LittleBigPlanet2)] - [TestCase(TokenGame.LittleBigPlanet3)] - [TestCase(TokenGame.LittleBigPlanetVita)] - [TestCase(TokenGame.LittleBigPlanetPSP)] - public void RedirectGriefReportToPhotoDeveloperLevel(TokenGame game) - { - using TestContext context = this.GetServer(); - GameUser user = context.CreateUser(); - GameLevel level = context.Database.GetStoryLevelById(100000); - - //Enable the grief report re-direction - context.Database.SetUserGriefReportRedirection(user, true); - context.Database.Refresh(); - - using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, game, TokenPlatform.PS3, user); - - //Upload our """photo""" - HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; - Assert.That(message.StatusCode, Is.EqualTo(OK)); - - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport - { - Players = new Player[] - { - new() - { - Username = user.Username, - Rectangle = new Rect - { - Top = 10, - Left = 10, - Bottom = 20, - Right = 20, - }, - }, - }, - LevelId = level.LevelId, - JpegHash = TEST_ASSET_HASH, - }.AsXML())).Result; - Assert.That(response.StatusCode, Is.EqualTo(OK)); - - context.Database.Refresh(); - - DatabaseList photos = context.Database.GetPhotosByUser(user, 10, 0); - Assert.That(photos.TotalItems, Is.EqualTo(1)); - - GamePhoto photo = photos.Items.First(); - Assert.That(photo.Publisher.UserId, Is.EqualTo(user.UserId)); - } - - [Test] - public void CantRedirectGriefReportToPhotoWhenWebsiteToken() - { - using TestContext context = this.GetServer(); - GameUser user = context.CreateUser(); - - //Enable the grief report re-direction - context.Database.SetUserGriefReportRedirection(user, true); - context.Database.Refresh(); - - using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.Website, TokenPlatform.PS3, user); - - HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport - { - Players = new Player[] - { - new() - { - Username = user.Username, - Rectangle = new Rect - { - Top = 10, - Left = 10, - Bottom = 20, - Right = 20, - }, - }, - }, - }.AsXML())).Result; - Assert.That(response.StatusCode, Is.EqualTo(BadRequest)); - } -} \ No newline at end of file From 828863b18ebb2ae086f389a3e0a984649bac4b38 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 19:03:10 -0400 Subject: [PATCH 11/37] Re-introduce and fix tests --- .../Endpoints/Game/ReportingEndpoints.cs | 10 + Refresh.GameServer/Types/Report/GameReport.cs | 4 +- .../Types/Report/ScreenElements.cs | 4 +- .../Reporting/ReportingEndpointsTests.cs | 349 ++++++++++++++++++ 4 files changed, 363 insertions(+), 4 deletions(-) create mode 100644 RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs diff --git a/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs b/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs index 2bf4c675..35ba1131 100644 --- a/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs @@ -57,6 +57,16 @@ public Response UploadReport(RequestContext context, GameDatabaseContext databas })); string hash = body.JpegHash; + + //If the level is specified but its invalid, set it to 0, to indicate the level is unknown + //This case is hit when someone makes a grief report from a non-existent level, which we allow + if (body.LevelId != 0 && level == null) + body.LevelId = 0; + + //Basic validation + if (body.Players is { Length: > 4 } || body.ScreenElements is { Player.Length: > 4 }) + //Return OK on PSP, since if we dont, it will error when trying to access the community moon and soft-lock the save file + return context.IsPSP() ? OK : BadRequest; database.UploadPhoto(new SerializedPhoto { diff --git a/Refresh.GameServer/Types/Report/GameReport.cs b/Refresh.GameServer/Types/Report/GameReport.cs index d5d67724..ad53a22f 100644 --- a/Refresh.GameServer/Types/Report/GameReport.cs +++ b/Refresh.GameServer/Types/Report/GameReport.cs @@ -10,7 +10,7 @@ namespace Refresh.GameServer.Types.Report; public class GameReport { [XmlIgnore] - private IList InternalInfoBubble { get; } + private List InternalInfoBubble { get; } = []; [Ignored] [XmlElement("infoBubble")] @@ -62,7 +62,7 @@ public InfoBubble[] InfoBubble public string JpegHash { get; set; } [XmlIgnore] - private IList InternalPlayers { get; } + private List InternalPlayers { get; } = []; [Ignored] [XmlElement("player")] diff --git a/Refresh.GameServer/Types/Report/ScreenElements.cs b/Refresh.GameServer/Types/Report/ScreenElements.cs index aa07ed62..aed5217c 100644 --- a/Refresh.GameServer/Types/Report/ScreenElements.cs +++ b/Refresh.GameServer/Types/Report/ScreenElements.cs @@ -9,7 +9,7 @@ namespace Refresh.GameServer.Types.Report; public class ScreenElements { [XmlIgnore] - private IList InternalSlot { get; } + private List InternalSlot { get; } = []; [XmlElement("slot")] public Slot[] Slot @@ -30,7 +30,7 @@ public Slot[] Slot } [XmlIgnore] - private IList InternalPlayer { get; } + private List InternalPlayer { get; } = []; [XmlElement("player")] public Player[] Player diff --git a/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs b/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs new file mode 100644 index 00000000..a4f78224 --- /dev/null +++ b/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs @@ -0,0 +1,349 @@ +using System.Reflection; +using Refresh.GameServer.Authentication; +using Refresh.GameServer.Database; +using Refresh.GameServer.Resources; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Photos; +using Refresh.GameServer.Types.Report; +using Refresh.GameServer.Types.UserData; +using RefreshTests.GameServer.Extensions; + +namespace RefreshTests.GameServer.Tests.Reporting; + +public class ReportingEndpointsTests : GameServerTest +{ + private const string TEST_ASSET_HASH = "0ec63b140374ba704a58fa0c743cb357683313dd"; + private static readonly byte[] TestAsset = ResourceHelper.ReadResource("RefreshTests.GameServer.Resources.1x1.png", Assembly.GetExecutingAssembly()); + + [Test] + public void UploadReport() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + GameLevel level = context.CreateLevel(user); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, user); + + //Upload our """photo""" + HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; + Assert.That(message.StatusCode, Is.EqualTo(OK)); + + GameReport report = new() + { + InfoBubble = new InfoBubble[] + {}, + Type = GriefReportType.Obscene, + Marqee = new Marqee + { + Rect = new Rect + { + Top = 10, + Left = 10, + Bottom = 20, + Right = 20, + }, + }, + LevelOwner = level.Publisher!.Username, + InitialStateHash = null, + LevelType = "user", + LevelId = level.LevelId, + Description = "This is a description, sent by LBP3", + GriefStateHash = null, + JpegHash = TEST_ASSET_HASH, + Players = new Player[] + { + new() + { + Username = user.Username, + Rectangle = new Rect + { + Top = 10, + Left = 10, + Bottom = 20, + Right = 20, + }, + Reporter = true, + IngameNow = true, + PlayerNumber = 0, + Text = "Some text", + ScreenRect = new ScreenRect + { + Rect = new Rect + { + Top = 10, + Left = 10, + Bottom = 20, + Right = 20, + }, + }, + }, + }, + ScreenElements = new ScreenElements(), + }; + + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(report.AsXML())).Result; + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + context.Database.Refresh(); + + DatabaseList photos = context.Database.GetRecentPhotos(10, 0); + Assert.That(photos.TotalItems, Is.EqualTo(1)); + Assert.That(photos.Items.First().LevelId, Is.EqualTo(report.LevelId)); + } + + [Test] + public void CanUploadReportWithBadLevel() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, user); + + //Upload our """photo""" + HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; + Assert.That(message.StatusCode, Is.EqualTo(OK)); + + GameReport report = new() + { + LevelId = int.MaxValue, + JpegHash = TEST_ASSET_HASH, + }; + + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(report.AsXML())).Result; + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + context.Database.Refresh(); + + DatabaseList photos = context.Database.GetRecentPhotos(10, 0); + Assert.That(photos.TotalItems, Is.EqualTo(1)); + } + + [TestCase(true)] + [TestCase(false)] + public void CantUploadReportWithBadPlayerCount(bool psp) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, user); + if(psp) client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT"); + + //Upload our """photo""" + HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; + Assert.That(message.StatusCode, Is.EqualTo(OK)); + + GameReport report = new() + { + LevelId = 0, + JpegHash = TEST_ASSET_HASH, + Players = new Player[] + { + new() + { + Username = "hee", + Rectangle = new Rect(), + }, + new() + { + Username = "haw", + Rectangle = new Rect(), + }, + new() + { + Username = "ham", + Rectangle = new Rect(), + }, + new() + { + Username = "burg", + Rectangle = new Rect(), + }, + new() + { + Username = "er", + Rectangle = new Rect(), + }, + }, + }; + + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(report.AsXML())).Result; + Assert.That(response.StatusCode, Is.EqualTo(psp ? OK : BadRequest)); + } + + [TestCase(true)] + [TestCase(false)] + public void CantUploadReportWithBadScreenElementPlayerCount(bool psp) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.LittleBigPlanet1, TokenPlatform.PS3, user); + if(psp) client.DefaultRequestHeaders.UserAgent.TryParseAdd("LBPPSP CLIENT"); + + //Upload our """photo""" + HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; + Assert.That(message.StatusCode, Is.EqualTo(OK)); + + GameReport report = new() + { + LevelId = 0, + JpegHash = TEST_ASSET_HASH, + ScreenElements = new ScreenElements + { + Player = new Player[] + { + new() + { + Username = "hee", + Rectangle = new Rect(), + }, + new() + { + Username = "haw", + Rectangle = new Rect(), + }, + new() + { + Username = "ham", + Rectangle = new Rect(), + }, + new() + { + Username = "burg", + Rectangle = new Rect(), + }, + new() + { + Username = "er", + Rectangle = new Rect(), + }, + }, + }, + }; + + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(report.AsXML())).Result; + Assert.That(response.StatusCode, Is.EqualTo(psp ? OK : BadRequest)); + } + + [TestCase(TokenGame.LittleBigPlanet1)] + [TestCase(TokenGame.LittleBigPlanet2)] + [TestCase(TokenGame.LittleBigPlanet3)] + [TestCase(TokenGame.LittleBigPlanetVita)] + [TestCase(TokenGame.LittleBigPlanetPSP)] + public void RedirectGriefReportToPhoto(TokenGame game) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + GameLevel level = context.CreateLevel(user); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, game, TokenPlatform.PS3, user); + + //Upload our """photo""" + HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; + Assert.That(message.StatusCode, Is.EqualTo(OK)); + + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport + { + Players = new Player[] + { + new() + { + Username = user.Username, + Rectangle = new Rect + { + Top = 10, + Left = 10, + Bottom = 20, + Right = 20, + }, + }, + }, + LevelId = level.LevelId, + JpegHash = TEST_ASSET_HASH, + }.AsXML())).Result; + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + context.Database.Refresh(); + + DatabaseList photos = context.Database.GetPhotosByUser(user, 10, 0); + Assert.That(photos.TotalItems, Is.EqualTo(1)); + + GamePhoto photo = photos.Items.First(); + Assert.That(photo.Publisher.UserId, Is.EqualTo(user.UserId)); + } + + [TestCase(TokenGame.LittleBigPlanet1)] + [TestCase(TokenGame.LittleBigPlanet2)] + [TestCase(TokenGame.LittleBigPlanet3)] + [TestCase(TokenGame.LittleBigPlanetVita)] + [TestCase(TokenGame.LittleBigPlanetPSP)] + public void RedirectGriefReportToPhotoDeveloperLevel(TokenGame game) + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + GameLevel level = context.Database.GetStoryLevelById(100000); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, game, TokenPlatform.PS3, user); + + //Upload our """photo""" + HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result; + Assert.That(message.StatusCode, Is.EqualTo(OK)); + + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport + { + Players = new Player[] + { + new() + { + Username = user.Username, + Rectangle = new Rect + { + Top = 10, + Left = 10, + Bottom = 20, + Right = 20, + }, + }, + }, + LevelId = level.LevelId, + JpegHash = TEST_ASSET_HASH, + }.AsXML())).Result; + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + context.Database.Refresh(); + + DatabaseList photos = context.Database.GetPhotosByUser(user, 10, 0); + Assert.That(photos.TotalItems, Is.EqualTo(1)); + + GamePhoto photo = photos.Items.First(); + Assert.That(photo.Publisher.UserId, Is.EqualTo(user.UserId)); + } + + [Test] + public void CantRedirectGriefReportToPhotoWhenWebsiteToken() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, TokenGame.Website, TokenPlatform.PS3, user); + + HttpResponseMessage response = client.PostAsync("/lbp/grief", new StringContent(new GameReport + { + Players = new Player[] + { + new() + { + Username = user.Username, + Rectangle = new Rect + { + Top = 10, + Left = 10, + Bottom = 20, + Right = 20, + }, + }, + }, + }.AsXML())).Result; + Assert.That(response.StatusCode, Is.EqualTo(BadRequest)); + } +} \ No newline at end of file From 8239740fcf7dcb8ebfb65dab0272650a845b4bce Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 19:22:30 -0400 Subject: [PATCH 12/37] Remove Refresh.Analyzers project --- Refresh.Analyzers/ActivityGenerator.cs | 173 -------------- Refresh.Analyzers/Refresh.Analyzers.csproj | 16 -- .../SyntaxReceivers/EnumNameReceiver.cs | 20 -- .../GameDatabaseContext.Activity.cs | 0 .../GameDatabaseContext.ActivityRead.cs | 58 +++++ .../GameDatabaseContext.ActivityWrite.cs | 214 ++++++++++++++++++ Refresh.GameServer/Refresh.GameServer.csproj | 4 - .../Refresh.GameServer.csproj.DotSettings | 2 + Refresh.sln | 8 - 9 files changed, 274 insertions(+), 221 deletions(-) delete mode 100644 Refresh.Analyzers/ActivityGenerator.cs delete mode 100644 Refresh.Analyzers/Refresh.Analyzers.csproj delete mode 100644 Refresh.Analyzers/SyntaxReceivers/EnumNameReceiver.cs rename Refresh.GameServer/Database/{ => Activity}/GameDatabaseContext.Activity.cs (100%) create mode 100644 Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs create mode 100644 Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs create mode 100644 Refresh.GameServer/Refresh.GameServer.csproj.DotSettings diff --git a/Refresh.Analyzers/ActivityGenerator.cs b/Refresh.Analyzers/ActivityGenerator.cs deleted file mode 100644 index 00a78f7a..00000000 --- a/Refresh.Analyzers/ActivityGenerator.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System.Text; -using JetBrains.Annotations; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using Refresh.Analyzers.SyntaxReceivers; - -namespace Refresh.Analyzers; - -[Generator] -public class ActivityGenerator : ISourceGenerator -{ - [LanguageInjection("csharp")] - private const string Header = - """ - // - using Refresh.GameServer.Types.Activity; - using Refresh.GameServer.Types.UserData; - using Refresh.GameServer.Types.UserData.Leaderboard; - using Refresh.GameServer.Types.Relations; - using Refresh.GameServer.Types.Levels; - """; - - public void Initialize(GeneratorInitializationContext context) - { - // no initialization code - } - - /// - /// Returns the type indicated by the input. - /// Example: Level_Upload resolves to GameLevel - /// - private static string GetTypeFromName(string input) - { - string typeName = input.Substring(0, input.IndexOf('_')); - - if (typeName != "RateLevelRelation") return "Game" + typeName; - return typeName; - } - - private static (string, string) GetIdFieldFromName(string name) - { - string idField; - string idFieldValue; - - name = name.Replace("Game", ""); - - if (name != "User" && name != "Score" && name != "SubmittedScore" && name != "RateLevelRelation") - { - idField = "StoredSequentialId"; - idFieldValue = idField + ".Value"; - } - else - { - idField = "StoredObjectId"; - idFieldValue = idField; - } - - return (idField, idFieldValue); - } - - private static void GenerateCreateEvents(GeneratorExecutionContext context, IEnumerable names) - { - string code = string.Empty; - foreach (string eventName in names) - { - string type = GetTypeFromName(eventName); - string typeParam = type.ToLower(); - string typeId = type.Replace("Game", "").Replace("Submitted", ""); - - (string idField, string _) = GetIdFieldFromName(type); - - string method = $@" - /// - /// Creates a new {eventName} event from a , and adds it to the event list. - /// - public Event Create{eventName.Replace("_", string.Empty)}Event(GameUser userFrom, {type} {typeParam}) - {{ - Event @event = new(); - @event.EventType = EventType.{eventName}; - @event.StoredDataType = EventDataType.{typeId}; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.{idField} = {typeParam}.{typeId}Id; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; - }} -"; - - code += method; - } - - - string sourceCode = $@"{Header} -namespace Refresh.GameServer.Database; - -public partial class GameDatabaseContext -{{ -{code} -}}"; - - context.AddSource("RealmDatabaseContext.Activity.CreateEvents.g.cs", SourceText.From(sourceCode, Encoding.UTF8)); - } - - private static void GenerateDataGetters(GeneratorExecutionContext context, IEnumerable names) - { - string code = string.Empty; - - foreach (string name in names) - { - string type = "Game" + name; - if (name == "RateLevelRelation") type = name; - - (string idField, string idFieldValue) = GetIdFieldFromName(name); - - string idAccess = name + "Id"; - if (name == "GameSubmittedScore" || type == "GameScore") - { - idAccess = "ScoreId"; - type = "GameSubmittedScore"; - } - - string method = $@" - public {type}? Get{name}FromEvent(Event @event) - {{ - if (@event.StoredDataType != EventDataType.{name}) - throw new InvalidOperationException(""Event does not store the correct data type (expected {name})""); - - if (@event.{idField} == null) - throw new InvalidOperationException(""Event was not created correctly, expected {idField} to not be null""); - - return this._realm.All<{type}>() - .FirstOrDefault(l => l.{idAccess} == @event.{idFieldValue}); - }} -"; - - code += method; - } - - string sourceCode = $@"{Header} -namespace Refresh.GameServer.Database; - -#nullable enable - -public partial class GameDatabaseContext -{{ -{code} -}}"; - - context.AddSource("GameDatabaseContext.Activity.DataGetters.g.cs", SourceText.From(sourceCode, Encoding.UTF8)); - } - - public void Execute(GeneratorExecutionContext context) - { - EnumNameReceiver syntaxReceiver = new(); - - foreach (SyntaxTree tree in context.Compilation.SyntaxTrees) - syntaxReceiver.OnVisitSyntaxNode(tree.GetRoot()); - - - foreach ((string className, List names) in syntaxReceiver.Enums) - switch (className) - { - case "EventType": - GenerateCreateEvents(context, names); - break; - case "EventDataType": - GenerateDataGetters(context, names); - break; - } - } -} \ No newline at end of file diff --git a/Refresh.Analyzers/Refresh.Analyzers.csproj b/Refresh.Analyzers/Refresh.Analyzers.csproj deleted file mode 100644 index eb2131fb..00000000 --- a/Refresh.Analyzers/Refresh.Analyzers.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netstandard2.1 - enable - enable - 11 - true - - - - - - - - diff --git a/Refresh.Analyzers/SyntaxReceivers/EnumNameReceiver.cs b/Refresh.Analyzers/SyntaxReceivers/EnumNameReceiver.cs deleted file mode 100644 index 23916808..00000000 --- a/Refresh.Analyzers/SyntaxReceivers/EnumNameReceiver.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Refresh.Analyzers.SyntaxReceivers; - -public class EnumNameReceiver : ISyntaxReceiver -{ - public Dictionary> Enums { get; } = new(); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - foreach (EnumDeclarationSyntax declaration in syntaxNode.DescendantNodes().OfType()) - { - string className = declaration.Identifier.Value!.ToString(); - List enumMembers = declaration.Members.Select(m => m.Identifier.ValueText).ToList(); - - this.Enums.TryAdd(className, enumMembers); - } - } -} \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Activity.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs similarity index 100% rename from Refresh.GameServer/Database/GameDatabaseContext.Activity.cs rename to Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs new file mode 100644 index 00000000..80ede1fc --- /dev/null +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs @@ -0,0 +1,58 @@ +using Refresh.GameServer.Types.Activity; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Relations; +using Refresh.GameServer.Types.UserData; +using Refresh.GameServer.Types.UserData.Leaderboard; + +namespace Refresh.GameServer.Database; + +public partial class GameDatabaseContext // ActivityRead +{ + public GameUser? GetUserFromEvent(Event @event) + { + if (@event.StoredDataType != EventDataType.User) + throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.User)})"); + + if (@event.StoredObjectId == null) + throw new InvalidOperationException("Event was not created correctly, expected StoredObjectId to not be null"); + + return this._realm.All() + .FirstOrDefault(l => l.UserId == @event.StoredObjectId); + } + + public GameLevel? GetLevelFromEvent(Event @event) + { + if (@event.StoredDataType != EventDataType.Level) + throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.Level)})"); + + if (@event.StoredSequentialId == null) + throw new InvalidOperationException("Event was not created correctly, expected StoredSequentialId to not be null"); + + return this._realm.All() + .FirstOrDefault(l => l.LevelId == @event.StoredSequentialId.Value); + } + + public GameSubmittedScore? GetScoreFromEvent(Event @event) + { + if (@event.StoredDataType != EventDataType.Score) + throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.Score)})"); + + if (@event.StoredObjectId == null) + throw new InvalidOperationException("Event was not created correctly, expected StoredObjectId to not be null"); + + return this._realm.All() + .FirstOrDefault(l => l.ScoreId == @event.StoredObjectId); + } + + public RateLevelRelation? GetRateLevelRelationFromEvent(Event @event) + { + if (@event.StoredDataType != EventDataType.RateLevelRelation) + throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.RateLevelRelation)})"); + + if (@event.StoredObjectId == null) + throw new InvalidOperationException("Event was not created correctly, expected StoredObjectId to not be null"); + + return this._realm.All() + .FirstOrDefault(l => l.RateLevelRelationId == @event.StoredObjectId); + } +} \ No newline at end of file diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs new file mode 100644 index 00000000..7e07d965 --- /dev/null +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs @@ -0,0 +1,214 @@ +using Refresh.GameServer.Types.Activity; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Relations; +using Refresh.GameServer.Types.UserData; +using Refresh.GameServer.Types.UserData.Leaderboard; + +namespace Refresh.GameServer.Database; + +public partial class GameDatabaseContext // ActivityWrite +{ + /// + /// Creates a new Level_Upload event from a , and adds it to the event list. + /// + public Event CreateLevelUploadEvent(GameUser userFrom, GameLevel gamelevel) + { + Event @event = new(); + @event.EventType = EventType.Level_Upload; + @event.StoredDataType = EventDataType.Level; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredSequentialId = gamelevel.LevelId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new Level_Favourite event from a , and adds it to the event list. + /// + public Event CreateLevelFavouriteEvent(GameUser userFrom, GameLevel gamelevel) + { + Event @event = new(); + @event.EventType = EventType.Level_Favourite; + @event.StoredDataType = EventDataType.Level; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredSequentialId = gamelevel.LevelId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new Level_Unfavourite event from a , and adds it to the event list. + /// + public Event CreateLevelUnfavouriteEvent(GameUser userFrom, GameLevel gamelevel) + { + Event @event = new(); + @event.EventType = EventType.Level_Unfavourite; + @event.StoredDataType = EventDataType.Level; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredSequentialId = gamelevel.LevelId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new User_Favourite event from a , and adds it to the event list. + /// + public Event CreateUserFavouriteEvent(GameUser userFrom, GameUser gameuser) + { + Event @event = new(); + @event.EventType = EventType.User_Favourite; + @event.StoredDataType = EventDataType.User; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredObjectId = gameuser.UserId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new User_Unfavourite event from a , and adds it to the event list. + /// + public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser gameuser) + { + Event @event = new(); + @event.EventType = EventType.User_Unfavourite; + @event.StoredDataType = EventDataType.User; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredObjectId = gameuser.UserId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new Level_Play event from a , and adds it to the event list. + /// + public Event CreateLevelPlayEvent(GameUser userFrom, GameLevel gamelevel) + { + Event @event = new(); + @event.EventType = EventType.Level_Play; + @event.StoredDataType = EventDataType.Level; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredSequentialId = gamelevel.LevelId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new Level_Tag event from a , and adds it to the event list. + /// + public Event CreateLevelTagEvent(GameUser userFrom, GameLevel gamelevel) + { + Event @event = new(); + @event.EventType = EventType.Level_Tag; + @event.StoredDataType = EventDataType.Level; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredSequentialId = gamelevel.LevelId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new Level_TeamPick event from a , and adds it to the event list. + /// + public Event CreateLevelTeamPickEvent(GameUser userFrom, GameLevel gamelevel) + { + Event @event = new(); + @event.EventType = EventType.Level_TeamPick; + @event.StoredDataType = EventDataType.Level; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredSequentialId = gamelevel.LevelId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new RateLevelRelation_Create event from a , and adds it to the event list. + /// + public Event CreateRateLevelRelationCreateEvent(GameUser userFrom, RateLevelRelation ratelevelrelation) + { + Event @event = new(); + @event.EventType = EventType.RateLevelRelation_Create; + @event.StoredDataType = EventDataType.RateLevelRelation; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredObjectId = ratelevelrelation.RateLevelRelationId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new Level_Review event from a , and adds it to the event list. + /// + public Event CreateLevelReviewEvent(GameUser userFrom, GameLevel gamelevel) + { + Event @event = new(); + @event.EventType = EventType.Level_Review; + @event.StoredDataType = EventDataType.Level; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredSequentialId = gamelevel.LevelId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new SubmittedScore_Create event from a , and adds it to the event list. + /// + public Event CreateSubmittedScoreCreateEvent(GameUser userFrom, GameSubmittedScore gamesubmittedscore) + { + Event @event = new(); + @event.EventType = EventType.SubmittedScore_Create; + @event.StoredDataType = EventDataType.Score; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredObjectId = gamesubmittedscore.ScoreId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } + + /// + /// Creates a new User_FirstLogin event from a , and adds it to the event list. + /// + public Event CreateUserFirstLoginEvent(GameUser userFrom, GameUser gameuser) + { + Event @event = new(); + @event.EventType = EventType.User_FirstLogin; + @event.StoredDataType = EventDataType.User; + @event.Timestamp = this._time.TimestampMilliseconds; + @event.User = userFrom; + + @event.StoredObjectId = gameuser.UserId; + + this._realm.Write(() => this._realm.Add(@event)); + return @event; + } +} \ No newline at end of file diff --git a/Refresh.GameServer/Refresh.GameServer.csproj b/Refresh.GameServer/Refresh.GameServer.csproj index 96b1f3b3..eb0d5d3a 100644 --- a/Refresh.GameServer/Refresh.GameServer.csproj +++ b/Refresh.GameServer/Refresh.GameServer.csproj @@ -78,10 +78,6 @@ - - false - Analyzer - diff --git a/Refresh.GameServer/Refresh.GameServer.csproj.DotSettings b/Refresh.GameServer/Refresh.GameServer.csproj.DotSettings new file mode 100644 index 00000000..84fed0cc --- /dev/null +++ b/Refresh.GameServer/Refresh.GameServer.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Refresh.sln b/Refresh.sln index a834dfa7..9fec4eec 100644 --- a/Refresh.sln +++ b/Refresh.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Refresh.GameServer", "Refresh.GameServer\Refresh.GameServer.csproj", "{622EB919-5FD2-45FE-B006-4EE5C7849FDF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Refresh.Analyzers", "Refresh.Analyzers\Refresh.Analyzers.csproj", "{4717B220-62C1-4CF9-9734-D9975C462159}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RefreshTests.GameServer", "RefreshTests.GameServer\RefreshTests.GameServer.csproj", "{8DC15128-D9C9-498B-AD3B-537C50966C91}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Refresh.Common", "Refresh.Common\Refresh.Common.csproj", "{A3D76B31-8732-4134-907B-F36773DF70D3}" @@ -27,12 +25,6 @@ Global {622EB919-5FD2-45FE-B006-4EE5C7849FDF}.DebugLocalBunkum|Any CPU.Build.0 = DebugLocalBunkum|Any CPU {622EB919-5FD2-45FE-B006-4EE5C7849FDF}.Debug|Any CPU.Build.0 = Debug|Any CPU {622EB919-5FD2-45FE-B006-4EE5C7849FDF}.Release|Any CPU.Build.0 = Release|Any CPU - {4717B220-62C1-4CF9-9734-D9975C462159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4717B220-62C1-4CF9-9734-D9975C462159}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4717B220-62C1-4CF9-9734-D9975C462159}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4717B220-62C1-4CF9-9734-D9975C462159}.Release|Any CPU.Build.0 = Release|Any CPU - {4717B220-62C1-4CF9-9734-D9975C462159}.DebugLocalBunkum|Any CPU.ActiveCfg = Debug|Any CPU - {4717B220-62C1-4CF9-9734-D9975C462159}.DebugLocalBunkum|Any CPU.Build.0 = Debug|Any CPU {8DC15128-D9C9-498B-AD3B-537C50966C91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8DC15128-D9C9-498B-AD3B-537C50966C91}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DC15128-D9C9-498B-AD3B-537C50966C91}.Release|Any CPU.ActiveCfg = Release|Any CPU From aad59d10cf63087c3c641fbec2b0d835042ebbff Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 19:24:26 -0400 Subject: [PATCH 13/37] Cleanup ActivityRead --- .../GameDatabaseContext.ActivityRead.cs | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs index 80ede1fc..a961a3f3 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Refresh.GameServer.Types.Activity; using Refresh.GameServer.Types.Levels; using Refresh.GameServer.Types.Relations; @@ -8,51 +9,44 @@ namespace Refresh.GameServer.Database; public partial class GameDatabaseContext // ActivityRead { - public GameUser? GetUserFromEvent(Event @event) + public GameUser? GetUserFromEvent(Event e) { - if (@event.StoredDataType != EventDataType.User) + if (e.StoredDataType != EventDataType.User) throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.User)})"); - if (@event.StoredObjectId == null) - throw new InvalidOperationException("Event was not created correctly, expected StoredObjectId to not be null"); + Debug.Assert(e.StoredObjectId != null); - return this._realm.All() - .FirstOrDefault(l => l.UserId == @event.StoredObjectId); + return this.GetUserByObjectId(e.StoredObjectId); } - public GameLevel? GetLevelFromEvent(Event @event) + public GameLevel? GetLevelFromEvent(Event e) { - if (@event.StoredDataType != EventDataType.Level) + if (e.StoredDataType != EventDataType.Level) throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.Level)})"); - if (@event.StoredSequentialId == null) - throw new InvalidOperationException("Event was not created correctly, expected StoredSequentialId to not be null"); - - return this._realm.All() - .FirstOrDefault(l => l.LevelId == @event.StoredSequentialId.Value); + Debug.Assert(e.StoredSequentialId != null); + + return this.GetLevelById(e.StoredSequentialId.Value); } - public GameSubmittedScore? GetScoreFromEvent(Event @event) + public GameSubmittedScore? GetScoreFromEvent(Event e) { - if (@event.StoredDataType != EventDataType.Score) + if (e.StoredDataType != EventDataType.Score) throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.Score)})"); - if (@event.StoredObjectId == null) - throw new InvalidOperationException("Event was not created correctly, expected StoredObjectId to not be null"); + Debug.Assert(e.StoredObjectId != null); - return this._realm.All() - .FirstOrDefault(l => l.ScoreId == @event.StoredObjectId); + return this.GetScoreByObjectId(e.StoredObjectId); } - public RateLevelRelation? GetRateLevelRelationFromEvent(Event @event) + public RateLevelRelation? GetRateLevelRelationFromEvent(Event e) { - if (@event.StoredDataType != EventDataType.RateLevelRelation) + if (e.StoredDataType != EventDataType.RateLevelRelation) throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.RateLevelRelation)})"); - if (@event.StoredObjectId == null) - throw new InvalidOperationException("Event was not created correctly, expected StoredObjectId to not be null"); + Debug.Assert(e.StoredObjectId != null); return this._realm.All() - .FirstOrDefault(l => l.RateLevelRelationId == @event.StoredObjectId); + .FirstOrDefault(l => l.RateLevelRelationId == e.StoredObjectId); } } \ No newline at end of file From 3065a475b08aeda53ca3a5460d51e599139208af Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 19:30:27 -0400 Subject: [PATCH 14/37] Refactor some events' naming, cleanup ActivityWrite --- .../GameDatabaseContext.ActivityWrite.cs | 282 +++++++++--------- .../GameDatabaseContext.Leaderboard.cs | 2 +- .../Types/Activity/ActivityPage.cs | 2 +- .../Types/Activity/EventType.cs | 6 +- .../Workers/DiscordIntegrationWorker.cs | 4 +- 5 files changed, 154 insertions(+), 142 deletions(-) diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs index 7e07d965..62d2e9e4 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs @@ -8,207 +8,219 @@ namespace Refresh.GameServer.Database; public partial class GameDatabaseContext // ActivityWrite { - /// + /// /// Creates a new Level_Upload event from a , and adds it to the event list. /// - public Event CreateLevelUploadEvent(GameUser userFrom, GameLevel gamelevel) + public Event CreateLevelUploadEvent(GameUser userFrom, GameLevel level) { - Event @event = new(); - @event.EventType = EventType.Level_Upload; - @event.StoredDataType = EventDataType.Level; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredSequentialId = gamelevel.LevelId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.Level_Upload, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// /// Creates a new Level_Favourite event from a , and adds it to the event list. /// - public Event CreateLevelFavouriteEvent(GameUser userFrom, GameLevel gamelevel) + public Event CreateLevelFavouriteEvent(GameUser userFrom, GameLevel level) { - Event @event = new(); - @event.EventType = EventType.Level_Favourite; - @event.StoredDataType = EventDataType.Level; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredSequentialId = gamelevel.LevelId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.Level_Favourite, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// /// Creates a new Level_Unfavourite event from a , and adds it to the event list. /// - public Event CreateLevelUnfavouriteEvent(GameUser userFrom, GameLevel gamelevel) + public Event CreateLevelUnfavouriteEvent(GameUser userFrom, GameLevel level) { - Event @event = new(); - @event.EventType = EventType.Level_Unfavourite; - @event.StoredDataType = EventDataType.Level; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredSequentialId = gamelevel.LevelId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.Level_Unfavourite, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// /// Creates a new User_Favourite event from a , and adds it to the event list. /// - public Event CreateUserFavouriteEvent(GameUser userFrom, GameUser gameuser) + public Event CreateUserFavouriteEvent(GameUser userFrom, GameUser user) { - Event @event = new(); - @event.EventType = EventType.User_Favourite; - @event.StoredDataType = EventDataType.User; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredObjectId = gameuser.UserId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.User_Favourite, + StoredDataType = EventDataType.User, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = user.UserId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// /// Creates a new User_Unfavourite event from a , and adds it to the event list. /// - public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser gameuser) + public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser user) { - Event @event = new(); - @event.EventType = EventType.User_Unfavourite; - @event.StoredDataType = EventDataType.User; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredObjectId = gameuser.UserId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.User_Unfavourite, + StoredDataType = EventDataType.User, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = user.UserId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// /// Creates a new Level_Play event from a , and adds it to the event list. /// - public Event CreateLevelPlayEvent(GameUser userFrom, GameLevel gamelevel) + public Event CreateLevelPlayEvent(GameUser userFrom, GameLevel level) { - Event @event = new(); - @event.EventType = EventType.Level_Play; - @event.StoredDataType = EventDataType.Level; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredSequentialId = gamelevel.LevelId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.Level_Play, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// /// Creates a new Level_Tag event from a , and adds it to the event list. /// - public Event CreateLevelTagEvent(GameUser userFrom, GameLevel gamelevel) + public Event CreateLevelTagEvent(GameUser userFrom, GameLevel level) { - Event @event = new(); - @event.EventType = EventType.Level_Tag; - @event.StoredDataType = EventDataType.Level; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredSequentialId = gamelevel.LevelId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.Level_Tag, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// /// Creates a new Level_TeamPick event from a , and adds it to the event list. /// - public Event CreateLevelTeamPickEvent(GameUser userFrom, GameLevel gamelevel) + public Event CreateLevelTeamPickEvent(GameUser userFrom, GameLevel level) { - Event @event = new(); - @event.EventType = EventType.Level_TeamPick; - @event.StoredDataType = EventDataType.Level; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredSequentialId = gamelevel.LevelId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.Level_TeamPick, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// - /// Creates a new RateLevelRelation_Create event from a , and adds it to the event list. + /// Creates a new Level_Rate event from a , and adds it to the event list. /// - public Event CreateRateLevelRelationCreateEvent(GameUser userFrom, RateLevelRelation ratelevelrelation) + public Event CreateRateLevelEvent(GameUser userFrom, RateLevelRelation relation) { - Event @event = new(); - @event.EventType = EventType.RateLevelRelation_Create; - @event.StoredDataType = EventDataType.RateLevelRelation; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredObjectId = ratelevelrelation.RateLevelRelationId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.Level_Rate, + StoredDataType = EventDataType.RateLevelRelation, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = relation.RateLevelRelationId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// /// Creates a new Level_Review event from a , and adds it to the event list. /// - public Event CreateLevelReviewEvent(GameUser userFrom, GameLevel gamelevel) + public Event CreateLevelReviewEvent(GameUser userFrom, GameLevel level) { - Event @event = new(); - @event.EventType = EventType.Level_Review; - @event.StoredDataType = EventDataType.Level; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredSequentialId = gamelevel.LevelId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.Level_Review, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// - /// Creates a new SubmittedScore_Create event from a , and adds it to the event list. + /// Creates a new Level_Score event from a , and adds it to the event list. /// - public Event CreateSubmittedScoreCreateEvent(GameUser userFrom, GameSubmittedScore gamesubmittedscore) + public Event CreateLevelScoreEvent(GameUser userFrom, GameSubmittedScore score) { - Event @event = new(); - @event.EventType = EventType.SubmittedScore_Create; - @event.StoredDataType = EventDataType.Score; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredObjectId = gamesubmittedscore.ScoreId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.Level_Score, + StoredDataType = EventDataType.Score, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = score.ScoreId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } /// /// Creates a new User_FirstLogin event from a , and adds it to the event list. /// - public Event CreateUserFirstLoginEvent(GameUser userFrom, GameUser gameuser) + public Event CreateUserFirstLoginEvent(GameUser userFrom, GameUser user) { - Event @event = new(); - @event.EventType = EventType.User_FirstLogin; - @event.StoredDataType = EventDataType.User; - @event.Timestamp = this._time.TimestampMilliseconds; - @event.User = userFrom; - - @event.StoredObjectId = gameuser.UserId; - - this._realm.Write(() => this._realm.Add(@event)); - return @event; + Event e = new() + { + EventType = EventType.User_FirstLogin, + StoredDataType = EventDataType.User, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = user.UserId, + }; + + this._realm.Write(() => this._realm.Add(e)); + return e; } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs index 702eb125..37a947fa 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs @@ -31,7 +31,7 @@ public GameSubmittedScore SubmitScore(SerializedScore score, GameUser user, Game this._realm.Add(newScore); }); - this.CreateSubmittedScoreCreateEvent(user, newScore); + this.CreateLevelScoreEvent(user, newScore); #region Notifications diff --git a/Refresh.GameServer/Types/Activity/ActivityPage.cs b/Refresh.GameServer/Types/Activity/ActivityPage.cs index ac2b85fd..251fe95b 100644 --- a/Refresh.GameServer/Types/Activity/ActivityPage.cs +++ b/Refresh.GameServer/Types/Activity/ActivityPage.cs @@ -259,7 +259,7 @@ private void GenerateLevelGroups(ActivityGroups groups) private void GenerateScoreGroups(ActivityGroups groups, IReadOnlyCollection scores) { - foreach (Event @event in this.Events.Where(e => e.EventType == EventType.SubmittedScore_Create)) + foreach (Event @event in this.Events.Where(e => e.EventType == EventType.Level_Score)) { GameSubmittedScore score = scores.First(u => u.ScoreId == @event.StoredObjectId); diff --git a/Refresh.GameServer/Types/Activity/EventType.cs b/Refresh.GameServer/Types/Activity/EventType.cs index f755b3a1..22dcf7b2 100644 --- a/Refresh.GameServer/Types/Activity/EventType.cs +++ b/Refresh.GameServer/Types/Activity/EventType.cs @@ -6,7 +6,7 @@ namespace Refresh.GameServer.Types.Activity; [JsonConverter(typeof(StringEnumConverter))] [SuppressMessage("ReSharper", "InconsistentNaming")] -public enum EventType +public enum EventType : byte { [XmlEnum("publish_level")] Level_Upload = 0, @@ -37,7 +37,7 @@ public enum EventType [XmlEnum("mm_pick_level")] Level_TeamPick = 13, [XmlEnum("dpad_rate_level")] - RateLevelRelation_Create = 14, + Level_Rate = 14, [XmlEnum("review_level")] Level_Review = 15, // [XmlEnum("comment_on_user")] @@ -49,7 +49,7 @@ public enum EventType // [XmlEnum("add_level_to_playlist")] // Playlist_AddLevel = 19, [XmlEnum("score")] - SubmittedScore_Create = 20, // FIXME: this name is shit + Level_Score = 20, // Custom events [XmlEnum("firstlogin")] diff --git a/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs b/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs index b788785c..527b5280 100644 --- a/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs +++ b/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs @@ -68,9 +68,9 @@ private string GetAssetUrl(string hash) EventType.Level_Play => null, EventType.Level_Tag => null, EventType.Level_TeamPick => $"team picked {levelLink}", - EventType.RateLevelRelation_Create => null, + EventType.Level_Rate => null, EventType.Level_Review => null, - EventType.SubmittedScore_Create => $"got {score!.Score:N0} points on {levelLink}", + EventType.Level_Score => $"got {score!.Score:N0} points on {levelLink}", EventType.User_FirstLogin => "logged in for the first time", _ => null, }; From 51d0f926d7849a1960929c685d9c6b1cbce67388 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 19:37:08 -0400 Subject: [PATCH 15/37] Fix cursed naming of EventType enum --- .../Activity/GameDatabaseContext.Activity.cs | 4 +- .../GameDatabaseContext.ActivityWrite.cs | 48 +++++++++---------- .../Types/Activity/ActivityPage.cs | 6 +-- .../Types/Activity/EventType.cs | 38 +++++++-------- .../Workers/DiscordIntegrationWorker.cs | 24 +++++----- 5 files changed, 59 insertions(+), 61 deletions(-) diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs index c407295e..7029c507 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs @@ -28,8 +28,8 @@ public DatabaseList GetUserRecentActivity(ActivityQueryParameters paramet userFriends.Contains(e.User.UserId) || userFriends.Contains(e.StoredObjectId) || this.GetLevelById(e.StoredSequentialId ?? int.MaxValue)?.Publisher?.UserId == parameters.User.UserId || - e.EventType == EventType.Level_TeamPick || - e.EventType == EventType.User_FirstLogin + e.EventType == EventType.LevelTeamPick || + e.EventType == EventType.UserFirstLogin ); } diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs index 62d2e9e4..6129eb2f 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs @@ -9,13 +9,13 @@ namespace Refresh.GameServer.Database; public partial class GameDatabaseContext // ActivityWrite { /// - /// Creates a new Level_Upload event from a , and adds it to the event list. + /// Creates a new LevelUpload event from a , and adds it to the event list. /// public Event CreateLevelUploadEvent(GameUser userFrom, GameLevel level) { Event e = new() { - EventType = EventType.Level_Upload, + EventType = EventType.LevelUpload, StoredDataType = EventDataType.Level, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -27,13 +27,13 @@ public Event CreateLevelUploadEvent(GameUser userFrom, GameLevel level) } /// - /// Creates a new Level_Favourite event from a , and adds it to the event list. + /// Creates a new LevelFavourite event from a , and adds it to the event list. /// public Event CreateLevelFavouriteEvent(GameUser userFrom, GameLevel level) { Event e = new() { - EventType = EventType.Level_Favourite, + EventType = EventType.LevelFavourite, StoredDataType = EventDataType.Level, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -45,13 +45,13 @@ public Event CreateLevelFavouriteEvent(GameUser userFrom, GameLevel level) } /// - /// Creates a new Level_Unfavourite event from a , and adds it to the event list. + /// Creates a new LevelUnfavourite event from a , and adds it to the event list. /// public Event CreateLevelUnfavouriteEvent(GameUser userFrom, GameLevel level) { Event e = new() { - EventType = EventType.Level_Unfavourite, + EventType = EventType.LevelUnfavourite, StoredDataType = EventDataType.Level, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -63,13 +63,13 @@ public Event CreateLevelUnfavouriteEvent(GameUser userFrom, GameLevel level) } /// - /// Creates a new User_Favourite event from a , and adds it to the event list. + /// Creates a new UserFavourite event from a , and adds it to the event list. /// public Event CreateUserFavouriteEvent(GameUser userFrom, GameUser user) { Event e = new() { - EventType = EventType.User_Favourite, + EventType = EventType.UserFavourite, StoredDataType = EventDataType.User, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -81,13 +81,13 @@ public Event CreateUserFavouriteEvent(GameUser userFrom, GameUser user) } /// - /// Creates a new User_Unfavourite event from a , and adds it to the event list. + /// Creates a new UserUnfavourite event from a , and adds it to the event list. /// public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser user) { Event e = new() { - EventType = EventType.User_Unfavourite, + EventType = EventType.UserUnfavourite, StoredDataType = EventDataType.User, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -99,13 +99,13 @@ public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser user) } /// - /// Creates a new Level_Play event from a , and adds it to the event list. + /// Creates a new LevelPlay event from a , and adds it to the event list. /// public Event CreateLevelPlayEvent(GameUser userFrom, GameLevel level) { Event e = new() { - EventType = EventType.Level_Play, + EventType = EventType.LevelPlay, StoredDataType = EventDataType.Level, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -117,13 +117,13 @@ public Event CreateLevelPlayEvent(GameUser userFrom, GameLevel level) } /// - /// Creates a new Level_Tag event from a , and adds it to the event list. + /// Creates a new LevelTag event from a , and adds it to the event list. /// public Event CreateLevelTagEvent(GameUser userFrom, GameLevel level) { Event e = new() { - EventType = EventType.Level_Tag, + EventType = EventType.LevelTag, StoredDataType = EventDataType.Level, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -135,13 +135,13 @@ public Event CreateLevelTagEvent(GameUser userFrom, GameLevel level) } /// - /// Creates a new Level_TeamPick event from a , and adds it to the event list. + /// Creates a new LevelTeamPick event from a , and adds it to the event list. /// public Event CreateLevelTeamPickEvent(GameUser userFrom, GameLevel level) { Event e = new() { - EventType = EventType.Level_TeamPick, + EventType = EventType.LevelTeamPick, StoredDataType = EventDataType.Level, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -153,13 +153,13 @@ public Event CreateLevelTeamPickEvent(GameUser userFrom, GameLevel level) } /// - /// Creates a new Level_Rate event from a , and adds it to the event list. + /// Creates a new LevelRate event from a , and adds it to the event list. /// public Event CreateRateLevelEvent(GameUser userFrom, RateLevelRelation relation) { Event e = new() { - EventType = EventType.Level_Rate, + EventType = EventType.LevelRate, StoredDataType = EventDataType.RateLevelRelation, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -171,13 +171,13 @@ public Event CreateRateLevelEvent(GameUser userFrom, RateLevelRelation relation) } /// - /// Creates a new Level_Review event from a , and adds it to the event list. + /// Creates a new LevelReview event from a , and adds it to the event list. /// public Event CreateLevelReviewEvent(GameUser userFrom, GameLevel level) { Event e = new() { - EventType = EventType.Level_Review, + EventType = EventType.LevelReview, StoredDataType = EventDataType.Level, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -189,13 +189,13 @@ public Event CreateLevelReviewEvent(GameUser userFrom, GameLevel level) } /// - /// Creates a new Level_Score event from a , and adds it to the event list. + /// Creates a new LevelScore event from a , and adds it to the event list. /// public Event CreateLevelScoreEvent(GameUser userFrom, GameSubmittedScore score) { Event e = new() { - EventType = EventType.Level_Score, + EventType = EventType.LevelScore, StoredDataType = EventDataType.Score, Timestamp = this._time.TimestampMilliseconds, User = userFrom, @@ -207,13 +207,13 @@ public Event CreateLevelScoreEvent(GameUser userFrom, GameSubmittedScore score) } /// - /// Creates a new User_FirstLogin event from a , and adds it to the event list. + /// Creates a new UserFirstLogin event from a , and adds it to the event list. /// public Event CreateUserFirstLoginEvent(GameUser userFrom, GameUser user) { Event e = new() { - EventType = EventType.User_FirstLogin, + EventType = EventType.UserFirstLogin, StoredDataType = EventDataType.User, Timestamp = this._time.TimestampMilliseconds, User = userFrom, diff --git a/Refresh.GameServer/Types/Activity/ActivityPage.cs b/Refresh.GameServer/Types/Activity/ActivityPage.cs index 251fe95b..c44cae4d 100644 --- a/Refresh.GameServer/Types/Activity/ActivityPage.cs +++ b/Refresh.GameServer/Types/Activity/ActivityPage.cs @@ -232,8 +232,8 @@ private void GenerateLevelGroups(ActivityGroups groups) // You will waste 30 seconds of your time if you don't. levelEvent = @event.EventType switch { - EventType.Level_Upload => SerializedLevelUploadEvent.FromSerializedLevelEvent(levelEvent), - EventType.Level_Play => SerializedLevelPlayEvent.FromSerializedLevelEvent(levelEvent), + EventType.LevelUpload => SerializedLevelUploadEvent.FromSerializedLevelEvent(levelEvent), + EventType.LevelPlay => SerializedLevelPlayEvent.FromSerializedLevelEvent(levelEvent), _ => levelEvent, }; @@ -259,7 +259,7 @@ private void GenerateLevelGroups(ActivityGroups groups) private void GenerateScoreGroups(ActivityGroups groups, IReadOnlyCollection scores) { - foreach (Event @event in this.Events.Where(e => e.EventType == EventType.Level_Score)) + foreach (Event @event in this.Events.Where(e => e.EventType == EventType.LevelScore)) { GameSubmittedScore score = scores.First(u => u.ScoreId == @event.StoredObjectId); diff --git a/Refresh.GameServer/Types/Activity/EventType.cs b/Refresh.GameServer/Types/Activity/EventType.cs index 22dcf7b2..175e4b07 100644 --- a/Refresh.GameServer/Types/Activity/EventType.cs +++ b/Refresh.GameServer/Types/Activity/EventType.cs @@ -1,57 +1,55 @@ -using System.Diagnostics.CodeAnalysis; using System.Xml.Serialization; using Newtonsoft.Json.Converters; namespace Refresh.GameServer.Types.Activity; [JsonConverter(typeof(StringEnumConverter))] -[SuppressMessage("ReSharper", "InconsistentNaming")] public enum EventType : byte { [XmlEnum("publish_level")] - Level_Upload = 0, + LevelUpload = 0, [XmlEnum("heart_level")] - Level_Favourite = 1, + LevelFavourite = 1, [XmlEnum("unheart_level")] - Level_Unfavourite = 2, + LevelUnfavourite = 2, [XmlEnum("heart_user")] - User_Favourite = 3, + UserFavourite = 3, [XmlEnum("unheart_user")] - User_Unfavourite = 4, + UserUnfavourite = 4, [XmlEnum("play_level")] - Level_Play = 5, + LevelPlay = 5, // [XmlEnum("rate_level")] // Level_Rate = 6, [XmlEnum("tag_level")] - Level_Tag = 7, + LevelTag = 7, // [XmlEnum("comment_on_level")] // PostLevelComment = 8, // [XmlEnum("delete_level_comment")] // DeleteLevelComment = 9, // [XmlEnum("upload_photo")] - // Photo_Upload = 10, + // PhotoUpload = 10, // [XmlEnum("unpublish_level")] - // Level_Unpublish = 11, + // LevelUnpublish = 11, // [XmlEnum("news_post")] - // News_Post = 12, + // NewsPost = 12, [XmlEnum("mm_pick_level")] - Level_TeamPick = 13, + LevelTeamPick = 13, [XmlEnum("dpad_rate_level")] - Level_Rate = 14, + LevelRate = 14, [XmlEnum("review_level")] - Level_Review = 15, + LevelReview = 15, // [XmlEnum("comment_on_user")] // PostUserComment = 16, // [XmlEnum("create_playlist")] - // Playlist_Create = 17, + // PlaylistCreate = 17, // [XmlEnum("heart_playlist")] - // Playlist_Favourite = 18, + // PlaylistFavourite = 18, // [XmlEnum("add_level_to_playlist")] - // Playlist_AddLevel = 19, + // PlaylistAddLevel = 19, [XmlEnum("score")] - Level_Score = 20, + LevelScore = 20, // Custom events [XmlEnum("firstlogin")] - User_FirstLogin = 127, + UserFirstLogin = 127, } \ No newline at end of file diff --git a/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs b/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs index 527b5280..ca1eab52 100644 --- a/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs +++ b/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs @@ -60,18 +60,18 @@ private string GetAssetUrl(string hash) string? description = @event.EventType switch { - EventType.Level_Upload => $"uploaded the level {levelLink}", - EventType.Level_Favourite => $"gave {levelLink} a heart", - EventType.Level_Unfavourite => null, - EventType.User_Favourite => $"hearted {userLink}", - EventType.User_Unfavourite => null, - EventType.Level_Play => null, - EventType.Level_Tag => null, - EventType.Level_TeamPick => $"team picked {levelLink}", - EventType.Level_Rate => null, - EventType.Level_Review => null, - EventType.Level_Score => $"got {score!.Score:N0} points on {levelLink}", - EventType.User_FirstLogin => "logged in for the first time", + EventType.LevelUpload => $"uploaded the level {levelLink}", + EventType.LevelFavourite => $"gave {levelLink} a heart", + EventType.LevelUnfavourite => null, + EventType.UserFavourite => $"hearted {userLink}", + EventType.UserUnfavourite => null, + EventType.LevelPlay => null, + EventType.LevelTag => null, + EventType.LevelTeamPick => $"team picked {levelLink}", + EventType.LevelRate => null, + EventType.LevelReview => null, + EventType.LevelScore => $"got {score!.Score:N0} points on {levelLink}", + EventType.UserFirstLogin => "logged in for the first time", _ => null, }; From ea64aa94004c4a901999cf2c5d019feb7c779f07 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 19:52:30 -0400 Subject: [PATCH 16/37] Add ability to fully delete users via CLI --- Refresh.GameServer/CommandLineManager.cs | 8 ++++++++ .../Database/GameDatabaseContext.Users.cs | 11 +++++++++++ Refresh.GameServer/RefreshGameServer.cs | 6 ++++++ 3 files changed, 25 insertions(+) diff --git a/Refresh.GameServer/CommandLineManager.cs b/Refresh.GameServer/CommandLineManager.cs index 817e4bfa..a0d2de65 100644 --- a/Refresh.GameServer/CommandLineManager.cs +++ b/Refresh.GameServer/CommandLineManager.cs @@ -58,6 +58,9 @@ private class Options [Option("rename-user", HelpText = "Changes a user's username. (old) username or Email option is required if this is set.")] public string? RenameUser { get; set; } + + [Option("fully-delete-user", HelpText = "Fully deletes a user, entirely removing the row and allowing people to register with that username once again. Not recommended.")] + public bool FullyDeleteUser { get; set; } } internal void StartWithArgs(string[] args) @@ -162,5 +165,10 @@ private void StartWithOptions(Options options) GameUser user = this.GetUserOrFail(options); this._server.RenameUser(user, options.RenameUser); } + else if (options.FullyDeleteUser) + { + GameUser user = this.GetUserOrFail(options); + this._server.FullyDeleteUser(user); + } } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs index b5610c2c..0f402488 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs @@ -318,6 +318,17 @@ public void DeleteUser(GameUser user) }); } + public void FullyDeleteUser(GameUser user) + { + // do an initial cleanup of everything before deleting the row + this.DeleteUser(user); + + this._realm.Write(() => + { + this._realm.Remove(user); + }); + } + public void ResetUserPlanets(GameUser user) { this._realm.Write(() => diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index a4647955..9d4484c6 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -237,6 +237,12 @@ public void RenameUser(GameUser user, string newUsername) using GameDatabaseContext context = this.GetContext(); context.RenameUser(user, newUsername); } + + public void FullyDeleteUser(GameUser user) + { + using GameDatabaseContext context = this.GetContext(); + context.FullyDeleteUser(user); + } public override void Dispose() { From d2e5592fe4bb1cd68b2d6176b623dc9416e35d9b Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 19:53:56 -0400 Subject: [PATCH 17/37] Also support soft-deletion through CLI --- Refresh.GameServer/CommandLineManager.cs | 8 ++++++++ Refresh.GameServer/RefreshGameServer.cs | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/Refresh.GameServer/CommandLineManager.cs b/Refresh.GameServer/CommandLineManager.cs index a0d2de65..4cb48fb0 100644 --- a/Refresh.GameServer/CommandLineManager.cs +++ b/Refresh.GameServer/CommandLineManager.cs @@ -59,6 +59,9 @@ private class Options [Option("rename-user", HelpText = "Changes a user's username. (old) username or Email option is required if this is set.")] public string? RenameUser { get; set; } + [Option("delete-user", HelpText = "Deletes a user's account, removing their data but keeping a record of their sign up.")] + public bool DeleteUser { get; set; } + [Option("fully-delete-user", HelpText = "Fully deletes a user, entirely removing the row and allowing people to register with that username once again. Not recommended.")] public bool FullyDeleteUser { get; set; } } @@ -165,6 +168,11 @@ private void StartWithOptions(Options options) GameUser user = this.GetUserOrFail(options); this._server.RenameUser(user, options.RenameUser); } + else if (options.DeleteUser) + { + GameUser user = this.GetUserOrFail(options); + this._server.DeleteUser(user); + } else if (options.FullyDeleteUser) { GameUser user = this.GetUserOrFail(options); diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index 9d4484c6..768bc8d7 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -238,6 +238,12 @@ public void RenameUser(GameUser user, string newUsername) context.RenameUser(user, newUsername); } + public void DeleteUser(GameUser user) + { + using GameDatabaseContext context = this.GetContext(); + context.DeleteUser(user); + } + public void FullyDeleteUser(GameUser user) { using GameDatabaseContext context = this.GetContext(); From 75c3545ed0aa3b5cef0d8473b807a2dd90ea496d Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 19:55:21 -0400 Subject: [PATCH 18/37] Report successes and exceptions on CLI --- Refresh.GameServer/CommandLineManager.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Refresh.GameServer/CommandLineManager.cs b/Refresh.GameServer/CommandLineManager.cs index 4cb48fb0..86e0ad71 100644 --- a/Refresh.GameServer/CommandLineManager.cs +++ b/Refresh.GameServer/CommandLineManager.cs @@ -68,8 +68,17 @@ private class Options internal void StartWithArgs(string[] args) { - Parser.Default.ParseArguments(args) - .WithParsed(this.StartWithOptions); + try + { + Parser.Default.ParseArguments(args) + .WithParsed(this.StartWithOptions); + } + catch (Exception e) + { + Fail($"An internal error occurred: {e}", 139); + } + + Console.WriteLine("The operation completed successfully."); } [DoesNotReturn] @@ -79,7 +88,7 @@ private static void Fail(string reason, int code = 1) Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(reason); - Console.WriteLine($"Failed with exit code {code}"); + Console.WriteLine($"The operation failed with exit code {code}"); Console.ForegroundColor = oldColor; Environment.Exit(code); From b899e4fc8d333ccdb4385be34987f66a6d5aadf0 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 20:18:34 -0400 Subject: [PATCH 19/37] Uncomment all event types --- .../Types/Activity/EventType.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Refresh.GameServer/Types/Activity/EventType.cs b/Refresh.GameServer/Types/Activity/EventType.cs index 175e4b07..9dab9594 100644 --- a/Refresh.GameServer/Types/Activity/EventType.cs +++ b/Refresh.GameServer/Types/Activity/EventType.cs @@ -18,34 +18,34 @@ public enum EventType : byte UserUnfavourite = 4, [XmlEnum("play_level")] LevelPlay = 5, - // [XmlEnum("rate_level")] - // Level_Rate = 6, + [XmlEnum("rate_level")] + LevelStarRate = 6, // as opposed to dpad rating. unused since we convert stars to dpad [XmlEnum("tag_level")] LevelTag = 7, - // [XmlEnum("comment_on_level")] - // PostLevelComment = 8, - // [XmlEnum("delete_level_comment")] - // DeleteLevelComment = 9, - // [XmlEnum("upload_photo")] - // PhotoUpload = 10, - // [XmlEnum("unpublish_level")] - // LevelUnpublish = 11, - // [XmlEnum("news_post")] - // NewsPost = 12, + [XmlEnum("comment_on_level")] + PostLevelComment = 8, + [XmlEnum("delete_level_comment")] + DeleteLevelComment = 9, + [XmlEnum("upload_photo")] + PhotoUpload = 10, + [XmlEnum("unpublish_level")] + LevelUnpublish = 11, + [XmlEnum("news_post")] + NewsPost = 12, [XmlEnum("mm_pick_level")] LevelTeamPick = 13, [XmlEnum("dpad_rate_level")] LevelRate = 14, [XmlEnum("review_level")] LevelReview = 15, - // [XmlEnum("comment_on_user")] - // PostUserComment = 16, - // [XmlEnum("create_playlist")] - // PlaylistCreate = 17, - // [XmlEnum("heart_playlist")] - // PlaylistFavourite = 18, - // [XmlEnum("add_level_to_playlist")] - // PlaylistAddLevel = 19, + [XmlEnum("comment_on_user")] + PostUserComment = 16, + [XmlEnum("create_playlist")] + PlaylistCreate = 17, + [XmlEnum("heart_playlist")] + PlaylistFavourite = 18, + [XmlEnum("add_level_to_playlist")] + PlaylistAddLevel = 19, [XmlEnum("score")] LevelScore = 20, From b3025463eea0b48fb70357843c7117f9ecf8f15e Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 21:20:50 -0400 Subject: [PATCH 20/37] Consolidate write calls into easily changeable function --- .../GameDatabaseContext.ActivityWrite.cs | 24 +++++++------- .../Database/GameDatabaseContext.Assets.cs | 12 +++---- .../Database/GameDatabaseContext.Comments.cs | 4 +-- .../Database/GameDatabaseContext.Contests.cs | 6 ++-- .../GameDatabaseContext.Leaderboard.cs | 6 ++-- .../Database/GameDatabaseContext.Levels.cs | 12 +++---- .../GameDatabaseContext.Notifications.cs | 10 +++--- .../Database/GameDatabaseContext.Photos.cs | 2 +- .../GameDatabaseContext.Registration.cs | 22 ++++++------- .../Database/GameDatabaseContext.Relations.cs | 26 +++++++-------- .../GameDatabaseContext.Statistics.cs | 6 ++-- .../Database/GameDatabaseContext.Tokens.cs | 16 +++++----- .../Database/GameDatabaseContext.Users.cs | 32 +++++++++---------- .../Database/GameDatabaseContext.cs | 11 +++++-- 14 files changed, 98 insertions(+), 91 deletions(-) diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs index 6129eb2f..26726509 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs @@ -22,7 +22,7 @@ public Event CreateLevelUploadEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -40,7 +40,7 @@ public Event CreateLevelFavouriteEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -58,7 +58,7 @@ public Event CreateLevelUnfavouriteEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -76,7 +76,7 @@ public Event CreateUserFavouriteEvent(GameUser userFrom, GameUser user) StoredObjectId = user.UserId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -94,7 +94,7 @@ public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser user) StoredObjectId = user.UserId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -112,7 +112,7 @@ public Event CreateLevelPlayEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -130,7 +130,7 @@ public Event CreateLevelTagEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -148,7 +148,7 @@ public Event CreateLevelTeamPickEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -166,7 +166,7 @@ public Event CreateRateLevelEvent(GameUser userFrom, RateLevelRelation relation) StoredObjectId = relation.RateLevelRelationId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -184,7 +184,7 @@ public Event CreateLevelReviewEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -202,7 +202,7 @@ public Event CreateLevelScoreEvent(GameUser userFrom, GameSubmittedScore score) StoredObjectId = score.ScoreId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } @@ -220,7 +220,7 @@ public Event CreateUserFirstLoginEvent(GameUser userFrom, GameUser user) StoredObjectId = user.UserId, }; - this._realm.Write(() => this._realm.Add(e)); + this.Write(() => this._realm.Add(e)); return e; } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs index 01e6eff0..414c51f8 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs @@ -38,7 +38,7 @@ public IEnumerable GetAssetDependencies(GameAsset asset) public void AddOrOverwriteAssetDependencyRelations(string dependent, IEnumerable dependencies) { - this._realm.Write(() => + this.Write(() => { // delete all existing relations. ensures duplicates won't exist when reprocessing this._realm.RemoveRange(this._realm.All().Where(a => a.Dependent == dependent)); @@ -57,31 +57,31 @@ public IEnumerable GetAssetsByType(GameAssetType type) => .Where(a => a._AssetType == (int)type); public void AddAssetToDatabase(GameAsset asset) => - this._realm.Write(() => + this.Write(() => { this._realm.Add(asset); }); public void AddOrUpdateAssetsInDatabase(IEnumerable assets) => - this._realm.Write(() => + this.Write(() => { this._realm.Add(assets, true); }); public void SetMainlineIconHash(GameAsset asset, string hash) => - this._realm.Write(() => + this.Write(() => { asset.AsMainlineIconHash = hash; }); public void SetMipIconHash(GameAsset asset, string hash) => - this._realm.Write(() => + this.Write(() => { asset.AsMipIconHash = hash; }); public void SetMainlinePhotoHash(GameAsset asset, string hash) => - this._realm.Write(() => + this.Write(() => { asset.AsMainlinePhotoHash = hash; }); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs b/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs index aec52d7d..db71b4e8 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs @@ -34,7 +34,7 @@ public IEnumerable GetProfileComments(GameUser profile, int count, public void DeleteProfileComment(GameComment comment, GameUser profile) { - this._realm.Write(() => + this.Write(() => { profile.ProfileComments.Remove(comment); }); @@ -65,7 +65,7 @@ public IEnumerable GetLevelComments(GameLevel level, int count, int public void DeleteLevelComment(GameComment comment, GameLevel level) { - this._realm.Write(() => + this.Write(() => { level.LevelComments.Remove(comment); }); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs b/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs index aeb63332..65c187aa 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs @@ -16,7 +16,7 @@ public void CreateContest(GameContest contest) { if (this.GetContestById(contest.ContestId) != null) throw new InvalidOperationException("Contest already exists."); - this._realm.Write(() => + this.Write(() => { contest.CreationDate = this._time.Now; this._realm.Add(contest); @@ -25,7 +25,7 @@ public void CreateContest(GameContest contest) public void DeleteContest(GameContest contest) { - this._realm.Write(() => + this.Write(() => { this._realm.Remove(contest); }); @@ -54,7 +54,7 @@ public IEnumerable GetAllContests() public GameContest UpdateContest(ApiContestRequest body, GameContest contest, GameUser? newOrganizer = null) { - this._realm.Write(() => + this.Write(() => { if (newOrganizer != null) contest.Organizer = newOrganizer; diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs index 37a947fa..79742f70 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs @@ -26,7 +26,7 @@ public GameSubmittedScore SubmitScore(SerializedScore score, GameUser user, Game Platform = platform, }; - this._realm.Write(() => + this.Write(() => { this._realm.Add(newScore); }); @@ -116,7 +116,7 @@ public void DeleteScore(GameSubmittedScore score) IQueryable scoreEvents = this._realm.All() .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); - this._realm.Write(() => + this.Write(() => { this._realm.RemoveRange(scoreEvents); this._realm.Remove(score); @@ -133,7 +133,7 @@ public void DeleteScoresSetByUser(GameUser user) .AsEnumerable() .Where(s => s.Players.Contains(user)); - this._realm.Write(() => + this.Write(() => { foreach (GameSubmittedScore score in scores) { diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs index dc99e048..111b62b7 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs @@ -81,7 +81,7 @@ public GameLevel GetStoryLevelById(int id) // Now newLevel is set up to replace oldLevel. // If information is lost here, then that's probably a bug. // Update the level's properties in the database - this._realm.Write(() => + this.Write(() => { PropertyInfo[] userProps = typeof(GameLevel).GetProperties(); foreach (PropertyInfo prop in userProps) @@ -96,7 +96,7 @@ public GameLevel GetStoryLevelById(int id) public GameLevel UpdateLevel(ApiEditLevelRequest body, GameLevel level) { - this._realm.Write(() => + this.Write(() => { PropertyInfo[] userProps = body.GetType().GetProperties(); foreach (PropertyInfo prop in userProps) @@ -120,7 +120,7 @@ public GameLevel UpdateLevel(ApiEditLevelRequest body, GameLevel level) public void DeleteLevel(GameLevel level) { - this._realm.Write(() => + this.Write(() => { IQueryable levelEvents = this._realm.All() .Where(e => e._StoredDataType == (int)EventDataType.Level && e.StoredSequentialId == level.LevelId); @@ -160,7 +160,7 @@ public void DeleteLevel(GameLevel level) }); //do in separate transaction in a vain attempt to fix Weirdness with favourite level relations having missing levels - this._realm.Write(() => + this.Write(() => { this._realm.Remove(level); }); @@ -397,7 +397,7 @@ public int GetTotalLevelsPublishedByUser(GameUser user, TokenGame game) private void SetLevelPickStatus(GameLevel level, bool status) { - this._realm.Write(() => + this.Write(() => { level.TeamPicked = status; }); @@ -408,7 +408,7 @@ private void SetLevelPickStatus(GameLevel level, bool status) public void SetLevelScores(Dictionary scoresToSet) { - this._realm.Write(() => + this.Write(() => { foreach ((GameLevel level, float score) in scoresToSet) { diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs index d0ad34d8..330f3e86 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs @@ -21,7 +21,7 @@ public void AddNotification(string title, string text, GameUser user, string? ic CreatedAt = this._time.Now, }; - this._realm.Write(() => + this.Write(() => { this._realm.Add(notification); }); @@ -59,7 +59,7 @@ public DatabaseList GetNotificationsByUser(GameUser user, int public void DeleteNotificationsByUser(GameUser user) { - this._realm.Write(() => + this.Write(() => { this._realm.RemoveRange(this._realm.All().Where(n => n.User == user)); }); @@ -67,7 +67,7 @@ public void DeleteNotificationsByUser(GameUser user) public void DeleteNotification(GameNotification notification) { - this._realm.Write(() => + this.Write(() => { this._realm.Remove(notification); }); @@ -87,7 +87,7 @@ public GameAnnouncement AddAnnouncement(string title, string text) CreatedAt = this._time.Now, }; - this._realm.Write(() => + this.Write(() => { this._realm.Add(announcement); }); @@ -97,7 +97,7 @@ public GameAnnouncement AddAnnouncement(string title, string text) public void DeleteAnnouncement(GameAnnouncement announcement) { - this._realm.Write(() => + this.Write(() => { this._realm.Remove(announcement); }); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs b/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs index ed4f66cb..24c739d8 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs @@ -54,7 +54,7 @@ public void UploadPhoto(SerializedPhoto photo, GameUser publisher) public void RemovePhoto(GamePhoto photo) { - this._realm.Write(() => + this.Write(() => { this._realm.Remove(photo); }); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs index b421220a..f20eb5ba 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs @@ -34,7 +34,7 @@ public GameUser CreateUser(string username, string emailAddress, bool skipChecks JoinDate = this._time.Now, }; - this._realm.Write(() => + this.Write(() => { this._realm.Add(user); }); @@ -45,7 +45,7 @@ public GameUser CreateUserFromQueuedRegistration(QueuedRegistration registration { QueuedRegistration cloned = (QueuedRegistration)registration.Clone(); - this._realm.Write(() => + this.Write(() => { this._realm.Remove(registration); }); @@ -55,7 +55,7 @@ public GameUser CreateUserFromQueuedRegistration(QueuedRegistration registration if (platform != null) { - this._realm.Write(() => + this.Write(() => { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (platform) @@ -107,7 +107,7 @@ public void AddRegistrationToQueue(string username, string emailAddress, string ExpiryDate = this._time.Now + TimeSpan.FromHours(1), }; - this._realm.Write(() => + this.Write(() => { this._realm.Add(registration); }); @@ -115,7 +115,7 @@ public void AddRegistrationToQueue(string username, string emailAddress, string public void RemoveRegistrationFromQueue(QueuedRegistration registration) { - this._realm.Write(() => + this.Write(() => { this._realm.Remove(registration); }); @@ -123,7 +123,7 @@ public void RemoveRegistrationFromQueue(QueuedRegistration registration) public void RemoveAllRegistrationsFromQueue() { - this._realm.Write(() => + this.Write(() => { this._realm.RemoveAll(); }); @@ -146,7 +146,7 @@ public DatabaseList GetAllVerificationCodes() public void VerifyUserEmail(GameUser user) { - this._realm.Write(() => + this.Write(() => { user.EmailAddressVerified = true; this._realm.RemoveRange(this._realm.All() @@ -186,7 +186,7 @@ public EmailVerificationCode CreateEmailVerificationCode(GameUser user) ExpiryDate = this._time.Now + TimeSpan.FromDays(1), }; - this._realm.Write(() => + this.Write(() => { this._realm.Add(verificationCode); }); @@ -196,7 +196,7 @@ public EmailVerificationCode CreateEmailVerificationCode(GameUser user) public void RemoveEmailVerificationCode(EmailVerificationCode code) { - this._realm.Write(() => + this.Write(() => { this._realm.Remove(code); }); @@ -207,7 +207,7 @@ public bool DisallowUser(string username) if (this._realm.Find(username) != null) return false; - this._realm.Write(() => + this.Write(() => { this._realm.Add(new DisallowedUser { @@ -224,7 +224,7 @@ public bool ReallowUser(string username) if (disallowedUser == null) return false; - this._realm.Write(() => + this.Write(() => { this._realm.Remove(disallowedUser); }); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 62bca927..e41c64ef 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -39,7 +39,7 @@ public bool FavouriteLevel(GameLevel level, GameUser user) Level = level, User = user, }; - this._realm.Write(() => this._realm.Add(relation)); + this.Write(() => this._realm.Add(relation)); this.CreateLevelFavouriteEvent(user, level); @@ -53,7 +53,7 @@ public bool UnfavouriteLevel(GameLevel level, GameUser user) if (relation == null) return false; - this._realm.Write(() => this._realm.Remove(relation)); + this.Write(() => this._realm.Remove(relation)); return true; } @@ -103,7 +103,7 @@ public bool FavouriteUser(GameUser userToFavourite, GameUser userFavouriting) UserFavouriting = userFavouriting, }; - this._realm.Write(() => this._realm.Add(relation)); + this.Write(() => this._realm.Add(relation)); this.CreateUserFavouriteEvent(userFavouriting, userToFavourite); @@ -127,7 +127,7 @@ public bool UnfavouriteUser(GameUser userToFavourite, GameUser userFavouriting) if (relation == null) return false; - this._realm.Write(() => this._realm.Remove(relation)); + this.Write(() => this._realm.Remove(relation)); return true; } @@ -162,7 +162,7 @@ public bool QueueLevel(GameLevel level, GameUser user) Level = level, User = user, }; - this._realm.Write(() => this._realm.Add(relation)); + this.Write(() => this._realm.Add(relation)); return true; } @@ -174,14 +174,14 @@ public bool DequeueLevel(GameLevel level, GameUser user) if (relation == null) return false; - this._realm.Write(() => this._realm.Remove(relation)); + this.Write(() => this._realm.Remove(relation)); return true; } public void ClearQueue(GameUser user) { - this._realm.Write(() => this._realm.RemoveRange(this._realm.All().Where(r => r.User == user))); + this.Write(() => this._realm.RemoveRange(this._realm.All().Where(r => r.User == user))); } #endregion @@ -220,11 +220,11 @@ public bool RateLevel(GameLevel level, GameUser user, RatingType type) Timestamp = this._time.Now, }; - this._realm.Write(() => this._realm.Add(rating)); + this.Write(() => this._realm.Add(rating)); return true; } - this._realm.Write(() => + this.Write(() => { rating.RatingType = type; rating.Timestamp = this._time.Now; @@ -243,7 +243,7 @@ public void AddReviewToLevel(GameReview review, GameLevel level) List toRemove = level.Reviews.Where(r => r.Publisher.UserId == review.Publisher.UserId).ToList(); if (toRemove.Count > 0) { - this._realm.Write(() => + this.Write(() => { foreach (GameReview reviewToDelete in toRemove) { @@ -301,7 +301,7 @@ public void PlayLevel(GameLevel level, GameUser user, int count) UniquePlayLevelRelation? uniqueRelation = this._realm.All() .FirstOrDefault(r => r.Level == level && r.User == user); - this._realm.Write(() => + this.Write(() => { this._realm.Add(relation); @@ -359,14 +359,14 @@ public bool RateComment(GameUser user, GameComment comment, RatingType ratingTyp RatingType = ratingType, }; - this._realm.Write(() => + this.Write(() => { this._realm.Add(relation); }); } else { - this._realm.Write(() => + this.Write(() => { relation.Timestamp = this._time.Now; relation.RatingType = ratingType; diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs b/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs index 77d20860..16aff170 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs @@ -10,7 +10,7 @@ public RequestStatistics GetRequestStatistics() if (statistics != null) return statistics; statistics = new RequestStatistics(); - this._realm.Write(() => + this.Write(() => { this._realm.Add(statistics); }); @@ -21,7 +21,7 @@ public RequestStatistics GetRequestStatistics() public void IncrementApiRequests(int count) { RequestStatistics statistics = this.GetRequestStatistics(); - this._realm.Write(() => { + this.Write(() => { statistics.TotalRequests += count; statistics.ApiRequests += count; }); @@ -30,7 +30,7 @@ public void IncrementApiRequests(int count) public void IncrementGameRequests(int count) { RequestStatistics statistics = this.GetRequestStatistics(); - this._realm.Write(() => { + this.Write(() => { statistics.TotalRequests += count; statistics.GameRequests += count; }); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs index f0b33ef8..8fb6b27a 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs @@ -56,7 +56,7 @@ public Token GenerateTokenForUser(GameUser user, TokenType type, TokenGame game, this.CreateUserFirstLoginEvent(user, user); } - this._realm.Write(() => + this.Write(() => { user.LastLoginDate = this._time.Now; this._realm.Add(token); @@ -91,7 +91,7 @@ public Token GenerateTokenForUser(GameUser user, TokenType type, TokenGame game, public void SetUserPassword(GameUser user, string? passwordBcrypt, bool shouldReset = false) { - this._realm.Write(() => + this.Write(() => { user.PasswordBcrypt = passwordBcrypt; user.ShouldResetPassword = shouldReset; @@ -112,7 +112,7 @@ public bool RevokeTokenByTokenData(string? tokenData, TokenType type) public void RevokeToken(Token token) { - this._realm.Write(() => + this.Write(() => { this._realm.Remove(token); }); @@ -120,7 +120,7 @@ public void RevokeToken(Token token) public void RevokeAllTokensForUser(GameUser user) { - this._realm.Write(() => + this.Write(() => { this._realm.RemoveRange(this._realm.All().Where(t => t.User == user)); }); @@ -128,7 +128,7 @@ public void RevokeAllTokensForUser(GameUser user) public void RevokeAllTokensForUser(GameUser user, TokenType type) { - this._realm.Write(() => + this.Write(() => { this._realm.RemoveRange(this._realm.All().Where(t => t.User == user && t._TokenType == (int)type)); }); @@ -147,7 +147,7 @@ public void AddIpVerificationRequest(GameUser user, string ipAddress) CreatedAt = this._time.Now, }; - this._realm.Write(() => + this.Write(() => { user.IpVerificationRequests.Add(request); }); @@ -155,7 +155,7 @@ public void AddIpVerificationRequest(GameUser user, string ipAddress) public void SetApprovedIp(GameUser user, string ipAddress) { - this._realm.Write(() => + this.Write(() => { user.CurrentVerifiedIp = ipAddress; user.IpVerificationRequests.Clear(); @@ -165,7 +165,7 @@ public void SetApprovedIp(GameUser user, string ipAddress) public void DenyIpVerificationRequest(GameUser user, string ipAddress) { IEnumerable requests = user.IpVerificationRequests.Where(r => r.IpAddress == ipAddress); - this._realm.Write(() => + this.Write(() => { foreach (GameIpVerificationRequest request in requests) { diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs index 0f402488..5493ead5 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs @@ -72,7 +72,7 @@ public DatabaseList GetUsers(int count, int skip) public void UpdateUserData(GameUser user, SerializedUpdateData data, TokenGame game) { - this._realm.Write(() => + this.Write(() => { if (data.Description != null) user.Description = data.Description; @@ -144,7 +144,7 @@ public void UpdateUserData(GameUser user, SerializedUpdateData data, TokenGame g public void UpdateUserData(GameUser user, ApiUpdateUserRequest data) { - this._realm.Write(() => + this.Write(() => { if (data.EmailAddress != null && data.EmailAddress != user.EmailAddress) { @@ -194,7 +194,7 @@ public int GetActiveUserCount() public void UpdateUserPins(GameUser user, UserPins pinsUpdate) { - this._realm.Write(() => { + this.Write(() => { user.Pins = new UserPins(); foreach (long pinsAward in pinsUpdate.Awards) user.Pins.Awards.Add(pinsAward); @@ -206,7 +206,7 @@ public void UpdateUserPins(GameUser user, UserPins pinsUpdate) public void SetUserRole(GameUser user, GameUserRole role) { if(role == GameUserRole.Banned) throw new InvalidOperationException($"Cannot ban a user with this method. Please use {nameof(this.BanUser)}()."); - this._realm.Write(() => + this.Write(() => { if (user.Role is GameUserRole.Banned or GameUserRole.Restricted) { @@ -220,7 +220,7 @@ public void SetUserRole(GameUser user, GameUserRole role) private void PunishUser(GameUser user, string reason, DateTimeOffset expiryDate, GameUserRole role) { - this._realm.Write(() => + this.Write(() => { user.Role = role; user.BanReason = reason; @@ -260,7 +260,7 @@ public void RenameUser(GameUser user, string newUsername) { string oldUsername = user.Username; - this._realm.Write(() => + this.Write(() => { user.Username = newUsername; }); @@ -281,7 +281,7 @@ public void DeleteUser(GameUser user) this.RevokeAllTokensForUser(user); this.DeleteNotificationsByUser(user); - this._realm.Write(() => + this.Write(() => { user.Pins = new UserPins(); user.LocationX = 0; @@ -323,7 +323,7 @@ public void FullyDeleteUser(GameUser user) // do an initial cleanup of everything before deleting the row this.DeleteUser(user); - this._realm.Write(() => + this.Write(() => { this._realm.Remove(user); }); @@ -331,7 +331,7 @@ public void FullyDeleteUser(GameUser user) public void ResetUserPlanets(GameUser user) { - this._realm.Write(() => + this.Write(() => { user.Lbp2PlanetsHash = "0"; user.Lbp3PlanetsHash = "0"; @@ -341,7 +341,7 @@ public void ResetUserPlanets(GameUser user) public void SetUnescapeXmlSequences(GameUser user, bool value) { - this._realm.Write(() => + this.Write(() => { user.UnescapeXmlSequences = value; }); @@ -349,7 +349,7 @@ public void SetUnescapeXmlSequences(GameUser user, bool value) public void ClearForceMatch(GameUser user) { - this._realm.Write(() => + this.Write(() => { user.ForceMatch = null; }); @@ -357,7 +357,7 @@ public void ClearForceMatch(GameUser user) public void SetForceMatch(GameUser user, GameUser target) { - this._realm.Write(() => + this.Write(() => { user.ForceMatch = target.ForceMatch; }); @@ -365,7 +365,7 @@ public void SetForceMatch(GameUser user, GameUser target) public void ForceUserTokenGame(Token token, TokenGame game) { - this._realm.Write(() => + this.Write(() => { token.TokenGame = game; }); @@ -373,7 +373,7 @@ public void ForceUserTokenGame(Token token, TokenGame game) public void ForceUserTokenPlatform(Token token, TokenPlatform platform) { - this._realm.Write(() => + this.Write(() => { token.TokenPlatform = platform; }); @@ -381,7 +381,7 @@ public void ForceUserTokenPlatform(Token token, TokenPlatform platform) public void IncrementUserFilesizeQuota(GameUser user, int amount) { - this._realm.Write(() => + this.Write(() => { user.FilesizeQuotaUsage += amount; }); @@ -389,7 +389,7 @@ public void IncrementUserFilesizeQuota(GameUser user, int amount) public void SetPrivacySettings(GameUser user, SerializedPrivacySettings settings) { - this._realm.Write(() => + this.Write(() => { if(settings.LevelVisibility != null) user.LevelVisibility = settings.LevelVisibility.Value; diff --git a/Refresh.GameServer/Database/GameDatabaseContext.cs b/Refresh.GameServer/Database/GameDatabaseContext.cs index fb493013..7091d853 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Realms; using Bunkum.RealmDatabase; using Refresh.GameServer.Time; @@ -48,7 +49,7 @@ private void AddSequentialObject(T obj, IList? list, Action? writtenCallba { lock (IdLock) { - this._realm.Write(() => + this.Write(() => { int newId = this.GetOrCreateSequentialId() + 1; @@ -63,7 +64,7 @@ private void AddSequentialObject(T obj, IList? list, Action? writtenCallba // We've already set a SequentialId so we can be outside the lock at this stage if (list != null) { - this._realm.Write(() => + this.Write(() => { list.Add(obj); writtenCallback?.Invoke(); @@ -76,4 +77,10 @@ private void AddSequentialObject(T obj, Action? writtenCallback) where T : IR private void AddSequentialObject(T obj) where T : IRealmObject, ISequentialId => this.AddSequentialObject(obj, null, null); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Write(Action callback) + { + this._realm.Write(callback); + } } \ No newline at end of file From b21edb6def3a5910134cc449853d1e6fdb595060 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 21:35:35 -0400 Subject: [PATCH 21/37] Replace calls to `this._realm.All` with properties closer to Entity Framework --- .../Activity/GameDatabaseContext.Activity.cs | 4 +- .../GameDatabaseContext.ActivityRead.cs | 2 +- .../Database/GameDatabaseContext.Assets.cs | 10 ++-- .../Database/GameDatabaseContext.Comments.cs | 2 +- .../Database/GameDatabaseContext.Contests.cs | 6 +-- .../GameDatabaseContext.Leaderboard.cs | 14 +++--- .../Database/GameDatabaseContext.Levels.cs | 46 +++++++++---------- .../GameDatabaseContext.Notifications.cs | 10 ++-- .../Database/GameDatabaseContext.Photos.cs | 18 ++++---- .../GameDatabaseContext.Registration.cs | 20 ++++---- .../Database/GameDatabaseContext.Relations.cs | 44 +++++++++--------- .../GameDatabaseContext.Statistics.cs | 2 +- .../Database/GameDatabaseContext.Tokens.cs | 10 ++-- .../Database/GameDatabaseContext.Users.cs | 30 ++++++------ .../Database/GameDatabaseContext.cs | 43 ++++++++++++++++- 15 files changed, 150 insertions(+), 111 deletions(-) diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs index 7029c507..4e86e86a 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs @@ -41,7 +41,7 @@ private IEnumerable GetRecentActivity(ActivityQueryParameters parameters) if (parameters.Timestamp == 0) parameters.Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - IEnumerable query = this._realm.All() + IEnumerable query = this.Events .Where(e => e.Timestamp < parameters.Timestamp && e.Timestamp >= parameters.EndTimestamp) .AsEnumerable(); @@ -91,5 +91,5 @@ ActivityQueryParameters parameters .OrderByDescending(e => e.Timestamp), parameters.Skip, parameters.Count); } - public int GetTotalEventCount() => this._realm.All().Count(); + public int GetTotalEventCount() => this.Events.Count(); } \ No newline at end of file diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs index a961a3f3..c55427ee 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs @@ -46,7 +46,7 @@ public partial class GameDatabaseContext // ActivityRead Debug.Assert(e.StoredObjectId != null); - return this._realm.All() + return this.RateLevelRelations .FirstOrDefault(l => l.RateLevelRelationId == e.StoredObjectId); } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs index 414c51f8..516c40fe 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs @@ -8,13 +8,13 @@ public partial class GameDatabaseContext // AssetConfiguration { if (hash == "0" || hash.StartsWith('g')) return null; - return this._realm.All() + return this.GameAssets .FirstOrDefault(a => a.AssetHash == hash); } public GameAssetType? GetConvertedType(string hash) { - IQueryable assets = this._realm.All(); + IQueryable assets = this.GameAssets; foreach (GameAsset asset in assets) { @@ -32,7 +32,7 @@ public partial class GameDatabaseContext // AssetConfiguration } public IEnumerable GetAssetDependencies(GameAsset asset) - => this._realm.All().Where(a => a.Dependent == asset.AssetHash) + => this.AssetDependencyRelations.Where(a => a.Dependent == asset.AssetHash) .AsEnumerable() .Select(a => a.Dependency); @@ -41,7 +41,7 @@ public void AddOrOverwriteAssetDependencyRelations(string dependent, IEnumerable this.Write(() => { // delete all existing relations. ensures duplicates won't exist when reprocessing - this._realm.RemoveRange(this._realm.All().Where(a => a.Dependent == dependent)); + this._realm.RemoveRange(this.AssetDependencyRelations.Where(a => a.Dependent == dependent)); foreach (string dependency in dependencies) this._realm.Add(new AssetDependencyRelation @@ -53,7 +53,7 @@ public void AddOrOverwriteAssetDependencyRelations(string dependent, IEnumerable } public IEnumerable GetAssetsByType(GameAssetType type) => - this._realm.All() + this.GameAssets .Where(a => a._AssetType == (int)type); public void AddAssetToDatabase(GameAsset asset) => diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs b/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs index db71b4e8..1df42c9f 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs @@ -8,7 +8,7 @@ namespace Refresh.GameServer.Database; public partial class GameDatabaseContext // Comments { public GameComment? GetCommentById(int id) => - this._realm.All().FirstOrDefault(c => c.SequentialId == id); + this.GameComments.FirstOrDefault(c => c.SequentialId == id); public GameComment PostCommentToProfile(GameUser profile, GameUser author, string content) { GameComment comment = new() diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs b/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs index 65c187aa..3b5be850 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs @@ -34,19 +34,19 @@ public void DeleteContest(GameContest contest) public GameContest? GetContestById(string? id) { if (id == null) return null; - return this._realm.All().FirstOrDefault(c => c.ContestId == id); + return this.GameContests.FirstOrDefault(c => c.ContestId == id); } public IEnumerable GetAllContests() { - return this._realm.All() + return this.GameContests .OrderBy(c => c.CreationDate); } public GameContest? GetNewestActiveContest() { DateTimeOffset now = this._time.Now; - return this._realm.All() + return this.GameContests .Where(c => c.StartDate <= now && c.EndDate > now) // Filter active contests .OrderByDescending(c => c.CreationDate) .FirstOrDefault(); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs index 79742f70..c6d883f9 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs @@ -64,7 +64,7 @@ public GameSubmittedScore SubmitScore(SerializedScore score, GameUser user, Game public DatabaseList GetTopScoresForLevel(GameLevel level, int count, int skip, byte type, bool showDuplicates = false) { - IEnumerable scores = this._realm.All() + IEnumerable scores = this.GameSubmittedScores .Where(s => s.ScoreType == type && s.Level == level) .OrderByDescending(s => s.Score) .AsEnumerable(); @@ -81,7 +81,7 @@ public IEnumerable GetRankedScoresAroundScore(GameSubmittedScore // this is probably REALLY fucking slow, and i probably shouldn't be trusted with LINQ anymore - List scores = this._realm.All().Where(s => s.ScoreType == score.ScoreType && s.Level == score.Level) + List scores = this.GameSubmittedScores.Where(s => s.ScoreType == score.ScoreType && s.Level == score.Level) .OrderByDescending(s => s.Score) .AsEnumerable() .ToList(); @@ -100,7 +100,7 @@ public IEnumerable GetRankedScoresAroundScore(GameSubmittedScore { if (uuid == null) return null; if(!ObjectId.TryParse(uuid, out ObjectId objectId)) return null; - return this._realm.All().FirstOrDefault(u => u.ScoreId == objectId); + return this.GameSubmittedScores.FirstOrDefault(u => u.ScoreId == objectId); } [Pure] @@ -108,12 +108,12 @@ public IEnumerable GetRankedScoresAroundScore(GameSubmittedScore public GameSubmittedScore? GetScoreByObjectId(ObjectId? id) { if (id == null) return null; - return this._realm.All().FirstOrDefault(u => u.ScoreId == id); + return this.GameSubmittedScores.FirstOrDefault(u => u.ScoreId == id); } public void DeleteScore(GameSubmittedScore score) { - IQueryable scoreEvents = this._realm.All() + IQueryable scoreEvents = this.Events .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); this.Write(() => @@ -125,7 +125,7 @@ public void DeleteScore(GameSubmittedScore score) public void DeleteScoresSetByUser(GameUser user) { - IEnumerable scores = this._realm.All() + IEnumerable scores = this.GameSubmittedScores // FIXME: Realm (ahem, I mean the atlas device sdk *rolls eyes*) is a fucking joke. // Realm doesn't support .Contains on IList. Yes, really. // This means we are forced to iterate over EVERY SCORE. @@ -137,7 +137,7 @@ public void DeleteScoresSetByUser(GameUser user) { foreach (GameSubmittedScore score in scores) { - IQueryable scoreEvents = this._realm.All() + IQueryable scoreEvents = this.Events .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); this._realm.RemoveRange(scoreEvents); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs index 111b62b7..592dbd24 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs @@ -33,7 +33,7 @@ public void AddLevel(GameLevel level) public GameLevel GetStoryLevelById(int id) { - GameLevel? level = this._realm.All().FirstOrDefault(l => l.StoryId == id); + GameLevel? level = this.GameLevels.FirstOrDefault(l => l.StoryId == id); if (level != null) return level; @@ -122,33 +122,33 @@ public void DeleteLevel(GameLevel level) { this.Write(() => { - IQueryable levelEvents = this._realm.All() + IQueryable levelEvents = this.Events .Where(e => e._StoredDataType == (int)EventDataType.Level && e.StoredSequentialId == level.LevelId); this._realm.RemoveRange(levelEvents); #region This is so terrible it needs to be hidden away - IQueryable favouriteLevelRelations = this._realm.All().Where(r => r.Level == level); + IQueryable favouriteLevelRelations = this.FavouriteLevelRelations.Where(r => r.Level == level); this._realm.RemoveRange(favouriteLevelRelations); - IQueryable playLevelRelations = this._realm.All().Where(r => r.Level == level); + IQueryable playLevelRelations = this.PlayLevelRelations.Where(r => r.Level == level); this._realm.RemoveRange(playLevelRelations); - IQueryable queueLevelRelations = this._realm.All().Where(r => r.Level == level); + IQueryable queueLevelRelations = this.QueueLevelRelations.Where(r => r.Level == level); this._realm.RemoveRange(queueLevelRelations); - IQueryable rateLevelRelations = this._realm.All().Where(r => r.Level == level); + IQueryable rateLevelRelations = this.RateLevelRelations.Where(r => r.Level == level); this._realm.RemoveRange(rateLevelRelations); - IQueryable uniquePlayLevelRelations = this._realm.All().Where(r => r.Level == level); + IQueryable uniquePlayLevelRelations = this.UniquePlayLevelRelations.Where(r => r.Level == level); this._realm.RemoveRange(uniquePlayLevelRelations); - IQueryable scores = this._realm.All().Where(r => r.Level == level); + IQueryable scores = this.GameSubmittedScores.Where(r => r.Level == level); foreach (GameSubmittedScore score in scores) { - IQueryable scoreEvents = this._realm.All() + IQueryable scoreEvents = this.Events .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); this._realm.RemoveRange(scoreEvents); } @@ -167,7 +167,7 @@ public void DeleteLevel(GameLevel level) } private IQueryable GetLevelsByGameVersion(TokenGame gameVersion) - => this._realm.All().Where(l => l._Source == (int)GameLevelSource.User).FilterByGameVersion(gameVersion); + => this.GameLevels.Where(l => l._Source == (int)GameLevelSource.User).FilterByGameVersion(gameVersion); [Pure] public DatabaseList GetLevelsByUser(GameUser user, int count, int skip, LevelFilterSettings levelFilterSettings, GameUser? accessor) @@ -191,11 +191,11 @@ public DatabaseList GetLevelsByUser(GameUser user, int count, int ski return new DatabaseList(this.GetLevelsByGameVersion(levelFilterSettings.GameVersion).FilterByLevelFilterSettings(accessor, levelFilterSettings).Where(l => l.Publisher == user), skip, count); } - public int GetTotalLevelsByUser(GameUser user) => this._realm.All().Count(l => l.Publisher == user); + public int GetTotalLevelsByUser(GameUser user) => this.GameLevels.Count(l => l.Publisher == user); [Pure] public DatabaseList GetUserLevelsChunk(int skip, int count) - => new(this._realm.All().Where(l => l._Source == (int)GameLevelSource.User), skip, count); + => new(this.GameLevels.Where(l => l._Source == (int)GameLevelSource.User), skip, count); [Pure] public DatabaseList GetNewestLevels(int count, int skip, GameUser? user, LevelFilterSettings levelFilterSettings) => @@ -218,7 +218,7 @@ public DatabaseList GetRandomLevels(int count, int skip, GameUser? us [Pure] public DatabaseList GetMostHeartedLevels(int count, int skip, GameUser? user, LevelFilterSettings levelFilterSettings) { - IQueryable favourites = this._realm.All(); + IQueryable favourites = this.FavouriteLevelRelations; IEnumerable mostHeartedLevels = favourites .AsEnumerable() @@ -237,7 +237,7 @@ public DatabaseList GetMostHeartedLevels(int count, int skip, GameUse [Pure] public DatabaseList GetMostUniquelyPlayedLevels(int count, int skip, GameUser? user, LevelFilterSettings levelFilterSettings) { - IQueryable uniquePlays = this._realm.All(); + IQueryable uniquePlays = this.UniquePlayLevelRelations; IEnumerable mostPlayed = uniquePlays .AsEnumerable() @@ -256,7 +256,7 @@ public DatabaseList GetMostUniquelyPlayedLevels(int count, int skip, [Pure] public DatabaseList GetMostReplayedLevels(int count, int skip, GameUser? user, LevelFilterSettings levelFilterSettings) { - IQueryable plays = this._realm.All(); + IQueryable plays = this.PlayLevelRelations; IEnumerable mostPlayed = plays .AsEnumerable() @@ -274,7 +274,7 @@ public DatabaseList GetMostReplayedLevels(int count, int skip, GameUs [Pure] public DatabaseList GetHighestRatedLevels(int count, int skip, GameUser? user, LevelFilterSettings levelFilterSettings) { - IQueryable ratings = this._realm.All(); + IQueryable ratings = this.RateLevelRelations; IEnumerable highestRated = ratings .AsEnumerable() @@ -299,7 +299,7 @@ public DatabaseList GetTeamPickedLevels(int count, int skip, GameUser [Pure] public DatabaseList GetDeveloperLevels(int count, int skip, LevelFilterSettings levelFilterSettings) => - new(this._realm.All() + new(this.GameLevels .Where(l => l._Source == (int)GameLevelSource.Story) .FilterByLevelFilterSettings(null, levelFilterSettings) .OrderByDescending(l => l.Title), skip, count); @@ -359,21 +359,21 @@ public DatabaseList SearchForLevels(int count, int skip, GameUser? us } [Pure] - public int GetTotalLevelCount(TokenGame game) => this._realm.All().FilterByGameVersion(game).Count(l => l._Source == (int)GameLevelSource.User); + public int GetTotalLevelCount(TokenGame game) => this.GameLevels.FilterByGameVersion(game).Count(l => l._Source == (int)GameLevelSource.User); [Pure] - public int GetTotalLevelCount() => this._realm.All().Count(l => l._Source == (int)GameLevelSource.User); + public int GetTotalLevelCount() => this.GameLevels.Count(l => l._Source == (int)GameLevelSource.User); public int GetTotalLevelsPublishedByUser(GameUser user) - => this._realm.All() + => this.GameLevels .Count(r => r.Publisher == user); public int GetTotalLevelsPublishedByUser(GameUser user, TokenGame game) - => this._realm.All() + => this.GameLevels .Count(r => r._GameVersion == (int)game); [Pure] - public int GetTotalTeamPickCount(TokenGame game) => this._realm.All().FilterByGameVersion(game).Count(l => l.TeamPicked); + public int GetTotalTeamPickCount(TokenGame game) => this.GameLevels.FilterByGameVersion(game).Count(l => l.TeamPicked); [Pure] public GameLevel? GetLevelByIdAndType(string slotType, int id) @@ -393,7 +393,7 @@ public int GetTotalLevelsPublishedByUser(GameUser user, TokenGame game) } [Pure] - public GameLevel? GetLevelById(int id) => this._realm.All().FirstOrDefault(l => l.LevelId == id); + public GameLevel? GetLevelById(int id) => this.GameLevels.FirstOrDefault(l => l.LevelId == id); private void SetLevelPickStatus(GameLevel level, bool status) { diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs index 330f3e86..1e2721bc 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs @@ -44,12 +44,12 @@ public void AddLoginFailNotification(string reason, GameUser user) [Pure] public int GetNotificationCountByUser(GameUser user) => - this._realm.All() + this.GameNotifications .Count(n => n.User == user); [Pure] public DatabaseList GetNotificationsByUser(GameUser user, int count, int skip) => - new(this._realm.All().Where(n => n.User == user), skip, count); + new(this.GameNotifications.Where(n => n.User == user), skip, count); [Pure] public GameNotification? GetNotificationByUuid(GameUser user, ObjectId id) @@ -61,7 +61,7 @@ public void DeleteNotificationsByUser(GameUser user) { this.Write(() => { - this._realm.RemoveRange(this._realm.All().Where(n => n.User == user)); + this._realm.RemoveRange(this.GameNotifications.Where(n => n.User == user)); }); } @@ -73,9 +73,9 @@ public void DeleteNotification(GameNotification notification) }); } - public IEnumerable GetAnnouncements() => this._realm.All(); + public IEnumerable GetAnnouncements() => this.GameAnnouncements; - public GameAnnouncement? GetAnnouncementById(ObjectId id) => this._realm.All().FirstOrDefault(a => a.AnnouncementId == id); + public GameAnnouncement? GetAnnouncementById(ObjectId id) => this.GameAnnouncements.FirstOrDefault(a => a.AnnouncementId == id); public GameAnnouncement AddAnnouncement(string title, string text) { diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs b/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs index 24c739d8..311ea353 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs @@ -60,30 +60,30 @@ public void RemovePhoto(GamePhoto photo) }); } - public int GetTotalPhotoCount() => this._realm.All().Count(); + public int GetTotalPhotoCount() => this.GamePhotos.Count(); [Pure] public DatabaseList GetRecentPhotos(int count, int skip) => - new(this._realm.All() + new(this.GamePhotos .OrderByDescending(p => p.PublishedAt), skip, count); [Pure] public GamePhoto? GetPhotoById(int id) => - this._realm.All().FirstOrDefault(p => p.PhotoId == id); + this.GamePhotos.FirstOrDefault(p => p.PhotoId == id); [Pure] public DatabaseList GetPhotosByUser(GameUser user, int count, int skip) => - new(this._realm.All().Where(p => p.Publisher == user) + new(this.GamePhotos.Where(p => p.Publisher == user) .OrderByDescending(p => p.TakenAt), skip, count); [Pure] public int GetTotalPhotosByUser(GameUser user) - => this._realm.All() + => this.GamePhotos .Count(p => p.Publisher == user); [Pure] public DatabaseList GetPhotosWithUser(GameUser user, int count, int skip) => - new(this._realm.All() + new(this.GamePhotos // FIXME: client-side enumeration .AsEnumerable() .Where(p => p.Subjects.FirstOrDefault(s => Equals(s.User, user)) != null) @@ -93,16 +93,16 @@ public DatabaseList GetPhotosWithUser(GameUser user, int count, int s [Pure] public int GetTotalPhotosWithUser(GameUser user) - => this._realm.All() + => this.GamePhotos // FIXME: client-side enumeration .AsEnumerable() .Count(p => p.Subjects.FirstOrDefault(s => Equals(s.User, user)) != null); [Pure] public DatabaseList GetPhotosInLevel(GameLevel level, int count, int skip) => - new(this._realm.All().Where(p => p.LevelId == level.LevelId), skip, count); + new(this.GamePhotos.Where(p => p.LevelId == level.LevelId), skip, count); [Pure] public int GetTotalPhotosInLevel(GameLevel level) - => this._realm.All().Count(p => p.LevelId == level.LevelId); + => this.GamePhotos.Count(p => p.LevelId == level.LevelId); } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs index f20eb5ba..d0b25ba3 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs @@ -81,14 +81,14 @@ public bool IsUsernameValid(string username) public bool IsUsernameTaken(string username) { - return this._realm.All().Any(u => u.Username == username) || - this._realm.All().Any(r => r.Username == username); + return this.GameUsers.Any(u => u.Username == username) || + this.QueuedRegistrations.Any(r => r.Username == username); } public bool IsEmailTaken(string emailAddress) { - return this._realm.All().Any(u => u.EmailAddress == emailAddress) || - this._realm.All().Any(r => r.EmailAddress == emailAddress); + return this.GameUsers.Any(u => u.EmailAddress == emailAddress) || + this.QueuedRegistrations.Any(r => r.EmailAddress == emailAddress); } public void AddRegistrationToQueue(string username, string emailAddress, string passwordBcrypt) @@ -132,30 +132,30 @@ public void RemoveAllRegistrationsFromQueue() public bool IsRegistrationExpired(QueuedRegistration registration) => registration.ExpiryDate < this._time.Now; public QueuedRegistration? GetQueuedRegistrationByUsername(string username) - => this._realm.All().FirstOrDefault(q => q.Username == username); + => this.QueuedRegistrations.FirstOrDefault(q => q.Username == username); public QueuedRegistration? GetQueuedRegistrationByObjectId(ObjectId id) - => this._realm.All().FirstOrDefault(q => q.RegistrationId == id); + => this.QueuedRegistrations.FirstOrDefault(q => q.RegistrationId == id); public DatabaseList GetAllQueuedRegistrations() - => new(this._realm.All()); + => new(this.QueuedRegistrations); public DatabaseList GetAllVerificationCodes() - => new(this._realm.All()); + => new(this.EmailVerificationCodes); public void VerifyUserEmail(GameUser user) { this.Write(() => { user.EmailAddressVerified = true; - this._realm.RemoveRange(this._realm.All() + this._realm.RemoveRange(this.EmailVerificationCodes .Where(c => c.User == user)); }); } public bool VerificationCodeMatches(GameUser user, string code) => - this._realm.All().Any(c => c.User == user && c.Code == code); + this.EmailVerificationCodes.Any(c => c.User == user && c.Code == code); public bool IsVerificationCodeExpired(EmailVerificationCode code) => code.ExpiryDate < this._time.Now; diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index e41c64ef..1333d01c 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -14,12 +14,12 @@ public partial class GameDatabaseContext // Relations { #region Favouriting Levels [Pure] - private bool IsLevelFavouritedByUser(GameLevel level, GameUser user) => this._realm.All() + private bool IsLevelFavouritedByUser(GameLevel level, GameUser user) => this.FavouriteLevelRelations .FirstOrDefault(r => r.Level == level && r.User == user) != null; [Pure] public DatabaseList GetLevelsFavouritedByUser(GameUser user, int count, int skip, LevelFilterSettings levelFilterSettings, GameUser? accessor) - => new(this._realm.All() + => new(this.FavouriteLevelRelations .Where(r => r.User == user) .AsEnumerable() .Select(r => r.Level) @@ -27,7 +27,7 @@ public DatabaseList GetLevelsFavouritedByUser(GameUser user, int coun .FilterByGameVersion(levelFilterSettings.GameVersion), skip, count); public int GetTotalLevelsFavouritedByUser(GameUser user) - => this._realm.All() + => this.FavouriteLevelRelations .Count(r => r.User == user); public bool FavouriteLevel(GameLevel level, GameUser user) @@ -48,7 +48,7 @@ public bool FavouriteLevel(GameLevel level, GameUser user) public bool UnfavouriteLevel(GameLevel level, GameUser user) { - FavouriteLevelRelation? relation = this._realm.All() + FavouriteLevelRelation? relation = this.FavouriteLevelRelations .FirstOrDefault(r => r.Level == level && r.User == user); if (relation == null) return false; @@ -62,7 +62,7 @@ public bool UnfavouriteLevel(GameLevel level, GameUser user) #region Favouriting Users [Pure] - private bool IsUserFavouritedByUser(GameUser userToFavourite, GameUser userFavouriting) => this._realm.All() + private bool IsUserFavouritedByUser(GameUser userToFavourite, GameUser userFavouriting) => this.FavouriteUserRelations .FirstOrDefault(r => r.UserToFavourite == userToFavourite && r.UserFavouriting == userFavouriting) != null; [Pure] @@ -78,7 +78,7 @@ public IEnumerable GetUsersMutuals(GameUser user) } [Pure] - public IEnumerable GetUsersFavouritedByUser(GameUser user, int count, int skip) => this._realm.All() + public IEnumerable GetUsersFavouritedByUser(GameUser user, int count, int skip) => this.FavouriteUserRelations .Where(r => r.UserFavouriting == user) .AsEnumerable() .Select(r => r.UserToFavourite) @@ -86,11 +86,11 @@ public IEnumerable GetUsersFavouritedByUser(GameUser user, int count, .Take(count); public int GetTotalUsersFavouritedByUser(GameUser user) - => this._realm.All() + => this.FavouriteUserRelations .Count(r => r.UserFavouriting == user); public int GetTotalUsersFavouritingUser(GameUser user) - => this._realm.All() + => this.FavouriteUserRelations .Count(r => r.UserToFavourite == user); public bool FavouriteUser(GameUser userToFavourite, GameUser userFavouriting) @@ -122,7 +122,7 @@ public bool FavouriteUser(GameUser userToFavourite, GameUser userFavouriting) public bool UnfavouriteUser(GameUser userToFavourite, GameUser userFavouriting) { - FavouriteUserRelation? relation = this._realm.All() + FavouriteUserRelation? relation = this.FavouriteUserRelations .FirstOrDefault(r => r.UserToFavourite == userToFavourite && r.UserFavouriting == userFavouriting); if (relation == null) return false; @@ -136,12 +136,12 @@ public bool UnfavouriteUser(GameUser userToFavourite, GameUser userFavouriting) #region Queueing [Pure] - private bool IsLevelQueuedByUser(GameLevel level, GameUser user) => this._realm.All() + private bool IsLevelQueuedByUser(GameLevel level, GameUser user) => this.QueueLevelRelations .FirstOrDefault(r => r.Level == level && r.User == user) != null; [Pure] public DatabaseList GetLevelsQueuedByUser(GameUser user, int count, int skip, LevelFilterSettings levelFilterSettings, GameUser? accessor) - => new(this._realm.All() + => new(this.QueueLevelRelations .Where(r => r.User == user) .AsEnumerable() .Select(r => r.Level) @@ -150,7 +150,7 @@ public DatabaseList GetLevelsQueuedByUser(GameUser user, int count, i [Pure] public int GetTotalLevelsQueuedByUser(GameUser user) - => this._realm.All() + => this.QueueLevelRelations .Count(r => r.User == user); public bool QueueLevel(GameLevel level, GameUser user) @@ -169,7 +169,7 @@ public bool QueueLevel(GameLevel level, GameUser user) public bool DequeueLevel(GameLevel level, GameUser user) { - QueueLevelRelation? relation = this._realm.All() + QueueLevelRelation? relation = this.QueueLevelRelations .FirstOrDefault(r => r.Level == level && r.User == user); if (relation == null) return false; @@ -181,7 +181,7 @@ public bool DequeueLevel(GameLevel level, GameUser user) public void ClearQueue(GameUser user) { - this.Write(() => this._realm.RemoveRange(this._realm.All().Where(r => r.User == user))); + this.Write(() => this._realm.RemoveRange(this.QueueLevelRelations.Where(r => r.User == user))); } #endregion @@ -189,7 +189,7 @@ public void ClearQueue(GameUser user) #region Rating and Reviewing private RateLevelRelation? GetRateRelationByUser(GameLevel level, GameUser user) - => this._realm.All().FirstOrDefault(r => r.User == user && r.Level == level); + => this.RateLevelRelations.FirstOrDefault(r => r.User == user && r.Level == level); /// /// Get a user's rating on a particular level. @@ -258,12 +258,12 @@ public void AddReviewToLevel(GameReview review, GameLevel level) public DatabaseList GetReviewsByUser(GameUser user, int count, int skip) { - return new DatabaseList(this._realm.All() + return new DatabaseList(this.GameReviews .Where(r => r.Publisher == user), skip, count); } public int GetTotalReviewsByUser(GameUser user) - => this._realm.All().Count(r => r.Publisher == user); + => this.GameReviews.Count(r => r.Publisher == user); public void DeleteReview(GameReview review) { @@ -277,12 +277,12 @@ public void DeleteReview(GameReview review) public DatabaseList GetReviewsForLevel(GameLevel level, int count, int skip) { - return new DatabaseList(this._realm.All() + return new DatabaseList(this.GameReviews .Where(r => r.Level == level), skip, count); } public int GetTotalReviewsForLevel(GameLevel level) - => this._realm.All().Count(r => r.Level == level); + => this.GameReviews.Count(r => r.Level == level); #endregion @@ -298,7 +298,7 @@ public void PlayLevel(GameLevel level, GameUser user, int count) Count = count, }; - UniquePlayLevelRelation? uniqueRelation = this._realm.All() + UniquePlayLevelRelation? uniqueRelation = this.UniquePlayLevelRelations .FirstOrDefault(r => r.Level == level && r.User == user); this.Write(() => @@ -318,7 +318,7 @@ public void PlayLevel(GameLevel level, GameUser user, int count) } public bool HasUserPlayedLevel(GameLevel level, GameUser user) => - this._realm.All() + this.UniquePlayLevelRelations .FirstOrDefault(r => r.Level == level && r.User == user) != null; #endregion @@ -340,7 +340,7 @@ public bool HasUserPlayedLevel(GameLevel level, GameUser user) => => this.GetCommentRelationByUser(comment, user)?.RatingType; public int GetTotalRatingsForComment(GameComment comment, RatingType type) => - this._realm.All().Count(r => r.Comment == comment && r._RatingType == (int)type); + this.CommentRelations.Count(r => r.Comment == comment && r._RatingType == (int)type); public bool RateComment(GameUser user, GameComment comment, RatingType ratingType) { diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs b/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs index 16aff170..0a6003cb 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs @@ -6,7 +6,7 @@ public partial class GameDatabaseContext // Statistics { public RequestStatistics GetRequestStatistics() { - RequestStatistics? statistics = this._realm.All().FirstOrDefault(); + RequestStatistics? statistics = this.RequestStatistics.FirstOrDefault(); if (statistics != null) return statistics; statistics = new RequestStatistics(); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs index 8fb6b27a..9417644e 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs @@ -69,7 +69,7 @@ public Token GenerateTokenForUser(GameUser user, TokenType type, TokenGame game, [ContractAnnotation("=> canbenull")] public Token? GetTokenFromTokenData(string tokenData, TokenType type) { - Token? token = this._realm.All() + Token? token = this.Tokens .FirstOrDefault(t => t.TokenData == tokenData && t._TokenType == (int)type); if (token == null) return null; @@ -102,7 +102,7 @@ public bool RevokeTokenByTokenData(string? tokenData, TokenType type) { if (tokenData == null) return false; - Token? token = this._realm.All().FirstOrDefault(t => t.TokenData == tokenData && t._TokenType == (int)type); + Token? token = this.Tokens.FirstOrDefault(t => t.TokenData == tokenData && t._TokenType == (int)type); if (token == null) return false; this.RevokeToken(token); @@ -122,7 +122,7 @@ public void RevokeAllTokensForUser(GameUser user) { this.Write(() => { - this._realm.RemoveRange(this._realm.All().Where(t => t.User == user)); + this._realm.RemoveRange(this.Tokens.Where(t => t.User == user)); }); } @@ -130,14 +130,14 @@ public void RevokeAllTokensForUser(GameUser user, TokenType type) { this.Write(() => { - this._realm.RemoveRange(this._realm.All().Where(t => t.User == user && t._TokenType == (int)type)); + this._realm.RemoveRange(this.Tokens.Where(t => t.User == user && t._TokenType == (int)type)); }); } public bool IsTokenExpired(Token token) => token.ExpiresAt < this._time.Now; public DatabaseList GetAllTokens() - => new(this._realm.All()); + => new(this.Tokens); public void AddIpVerificationRequest(GameUser user, string ipAddress) { diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs index 5493ead5..321e5981 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs @@ -36,9 +36,9 @@ public partial class GameDatabaseContext // Users }; if (!caseSensitive) - return this._realm.All().FirstOrDefault(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase)); + return this.GameUsers.FirstOrDefault(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase)); - return this._realm.All().FirstOrDefault(u => u.Username == username); + return this.GameUsers.FirstOrDefault(u => u.Username == username); } [Pure] @@ -47,7 +47,7 @@ public partial class GameDatabaseContext // Users { if (emailAddress == null) return null; emailAddress = emailAddress.ToLowerInvariant(); - return this._realm.All().FirstOrDefault(u => u.EmailAddress == emailAddress); + return this.GameUsers.FirstOrDefault(u => u.EmailAddress == emailAddress); } [Pure] @@ -55,7 +55,7 @@ public partial class GameDatabaseContext // Users public GameUser? GetUserByObjectId(ObjectId? id) { if (id == null) return null; - return this._realm.All().FirstOrDefault(u => u.UserId == id); + return this.GameUsers.FirstOrDefault(u => u.UserId == id); } [Pure] @@ -64,11 +64,11 @@ public partial class GameDatabaseContext // Users { if (uuid == null) return null; if(!ObjectId.TryParse(uuid, out ObjectId objectId)) return null; - return this._realm.All().FirstOrDefault(u => u.UserId == objectId); + return this.GameUsers.FirstOrDefault(u => u.UserId == objectId); } public DatabaseList GetUsers(int count, int skip) - => new(this._realm.All().OrderByDescending(u => u.JoinDate), skip, count); + => new(this.GameUsers.OrderByDescending(u => u.JoinDate), skip, count); public void UpdateUserData(GameUser user, SerializedUpdateData data, TokenGame game) { @@ -183,13 +183,13 @@ public void UpdateUserData(GameUser user, ApiUpdateUserRequest data) } [Pure] - public int GetTotalUserCount() => this._realm.All().Count(); + public int GetTotalUserCount() => this.GameUsers.Count(); [Pure] public int GetActiveUserCount() { DateTimeOffset timeFrame = this._time.Now.Subtract(TimeSpan.FromDays(7)); - return this._realm.All().Count(u => u.LastLoginDate > timeFrame); + return this.GameUsers.Count(u => u.LastLoginDate > timeFrame); } public void UpdateUserPins(GameUser user, UserPins pinsUpdate) @@ -253,7 +253,7 @@ public DatabaseList GetAllUsersWithRole(GameUserRole role) // for some stupid reason, we have to do the byte conversion here or realm won't work correctly. byte roleByte = (byte)role; - return new DatabaseList(this._realm.All().Where(u => u._Role == roleByte)); + return new DatabaseList(this.GameUsers.Where(u => u._Role == roleByte)); } public void RenameUser(GameUser user, string newUsername) @@ -305,13 +305,13 @@ public void DeleteUser(GameUser user) foreach (GamePhotoSubject subject in photo.Subjects.Where(s => s.User?.UserId == user.UserId)) subject.User = null; - this._realm.RemoveRange(this._realm.All().Where(r => r.User == user)); - this._realm.RemoveRange(this._realm.All().Where(r => r.UserToFavourite == user)); - this._realm.RemoveRange(this._realm.All().Where(r => r.UserFavouriting == user)); - this._realm.RemoveRange(this._realm.All().Where(r => r.User == user)); - this._realm.RemoveRange(this._realm.All().Where(p => p.Publisher == user)); + this._realm.RemoveRange(this.FavouriteLevelRelations.Where(r => r.User == user)); + this._realm.RemoveRange(this.FavouriteUserRelations.Where(r => r.UserToFavourite == user)); + this._realm.RemoveRange(this.FavouriteUserRelations.Where(r => r.UserFavouriting == user)); + this._realm.RemoveRange(this.QueueLevelRelations.Where(r => r.User == user)); + this._realm.RemoveRange(this.GamePhotos.Where(p => p.Publisher == user)); - foreach (GameLevel level in this._realm.All().Where(l => l.Publisher == user)) + foreach (GameLevel level in this.GameLevels.Where(l => l.Publisher == user)) { level.Publisher = null; } diff --git a/Refresh.GameServer/Database/GameDatabaseContext.cs b/Refresh.GameServer/Database/GameDatabaseContext.cs index 7091d853..ed999a28 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.cs @@ -2,8 +2,21 @@ using System.Runtime.CompilerServices; using Realms; using Bunkum.RealmDatabase; +using Refresh.GameServer.Authentication; using Refresh.GameServer.Time; using Refresh.GameServer.Types; +using Refresh.GameServer.Types.Activity; +using Refresh.GameServer.Types.Assets; +using Refresh.GameServer.Types.Comments; +using Refresh.GameServer.Types.Contests; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Levels.SkillRewards; +using Refresh.GameServer.Types.Notifications; +using Refresh.GameServer.Types.Photos; +using Refresh.GameServer.Types.Relations; +using Refresh.GameServer.Types.Reviews; +using Refresh.GameServer.Types.UserData; +using Refresh.GameServer.Types.UserData.Leaderboard; namespace Refresh.GameServer.Database; @@ -13,6 +26,32 @@ public partial class GameDatabaseContext : RealmDatabaseContext private static readonly object IdLock = new(); private readonly IDateTimeProvider _time; + + private IQueryable GameUsers => this._realm.All(); + private IQueryable Tokens => this._realm.All(); + private IQueryable GameLevels => this._realm.All(); + private IQueryable GameComments => this._realm.All(); + private IQueryable CommentRelations => this._realm.All(); + private IQueryable FavouriteLevelRelations => this._realm.All(); + private IQueryable QueueLevelRelations => this._realm.All(); + private IQueryable FavouriteUserRelations => this._realm.All(); + private IQueryable PlayLevelRelations => this._realm.All(); + private IQueryable UniquePlayLevelRelations => this._realm.All(); + private IQueryable RateLevelRelations => this._realm.All(); + private IQueryable Events => this._realm.All(); + private IQueryable GameSubmittedScores => this._realm.All(); + private IQueryable GameAssets => this._realm.All(); + private IQueryable GameNotifications => this._realm.All(); + private IQueryable GamePhotos => this._realm.All(); + private IQueryable GameAnnouncements => this._realm.All(); + private IQueryable QueuedRegistrations => this._realm.All(); + private IQueryable EmailVerificationCodes => this._realm.All(); + private IQueryable RequestStatistics => this._realm.All(); + private IQueryable SequentialIdStorage => this._realm.All(); + private IQueryable GameContests => this._realm.All(); + private IQueryable AssetDependencyRelations => this._realm.All(); + private IQueryable GameReviews => this._realm.All(); + private IQueryable DisallowedUsers => this._realm.All(); internal GameDatabaseContext(IDateTimeProvider time) { @@ -23,7 +62,7 @@ private int GetOrCreateSequentialId() where T : IRealmObject, ISequentialId { string name = typeof(T).Name; - SequentialIdStorage? storage = this._realm.All() + SequentialIdStorage? storage = this.SequentialIdStorage .FirstOrDefault(s => s.TypeName == name); if (storage != null) @@ -35,7 +74,7 @@ private int GetOrCreateSequentialId() where T : IRealmObject, ISequentialId storage = new SequentialIdStorage { TypeName = name, - SequentialId = this._realm.All().Count() * 2, // skip over a bunch of ids incase table is broken + SequentialId = this.SequentialIdStorage.Count() * 2, // skip over a bunch of ids incase table is broken }; // no need to do write block, this should only be called in a write transaction From 331c6d4eb39d48ff8cb0939418764fad86308b5b Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 22:02:40 -0400 Subject: [PATCH 22/37] Add RealmDbSet, ability to add to realm from db set --- .../GameDatabaseContext.ActivityWrite.cs | 24 ++++----- .../Database/GameDatabaseContext.Assets.cs | 6 +-- .../Database/GameDatabaseContext.Contests.cs | 2 +- .../GameDatabaseContext.Leaderboard.cs | 2 +- .../GameDatabaseContext.Notifications.cs | 4 +- .../GameDatabaseContext.Registration.cs | 8 +-- .../Database/GameDatabaseContext.Relations.cs | 14 ++--- .../GameDatabaseContext.Statistics.cs | 2 +- .../Database/GameDatabaseContext.Tokens.cs | 2 +- .../Database/GameDatabaseContext.cs | 52 +++++++++---------- Refresh.GameServer/Database/RealmDbSet.cs | 35 +++++++++++++ 11 files changed, 93 insertions(+), 58 deletions(-) create mode 100644 Refresh.GameServer/Database/RealmDbSet.cs diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs index 26726509..9484314e 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs @@ -22,7 +22,7 @@ public Event CreateLevelUploadEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -40,7 +40,7 @@ public Event CreateLevelFavouriteEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -58,7 +58,7 @@ public Event CreateLevelUnfavouriteEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -76,7 +76,7 @@ public Event CreateUserFavouriteEvent(GameUser userFrom, GameUser user) StoredObjectId = user.UserId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -94,7 +94,7 @@ public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser user) StoredObjectId = user.UserId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -112,7 +112,7 @@ public Event CreateLevelPlayEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -130,7 +130,7 @@ public Event CreateLevelTagEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -148,7 +148,7 @@ public Event CreateLevelTeamPickEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -166,7 +166,7 @@ public Event CreateRateLevelEvent(GameUser userFrom, RateLevelRelation relation) StoredObjectId = relation.RateLevelRelationId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -184,7 +184,7 @@ public Event CreateLevelReviewEvent(GameUser userFrom, GameLevel level) StoredSequentialId = level.LevelId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -202,7 +202,7 @@ public Event CreateLevelScoreEvent(GameUser userFrom, GameSubmittedScore score) StoredObjectId = score.ScoreId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } @@ -220,7 +220,7 @@ public Event CreateUserFirstLoginEvent(GameUser userFrom, GameUser user) StoredObjectId = user.UserId, }; - this.Write(() => this._realm.Add(e)); + this.Write(() => this.Events.Add(e)); return e; } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs index 516c40fe..3c00a5c1 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs @@ -44,7 +44,7 @@ public void AddOrOverwriteAssetDependencyRelations(string dependent, IEnumerable this._realm.RemoveRange(this.AssetDependencyRelations.Where(a => a.Dependent == dependent)); foreach (string dependency in dependencies) - this._realm.Add(new AssetDependencyRelation + this.AssetDependencyRelations.Add(new AssetDependencyRelation { Dependent = dependent, Dependency = dependency, @@ -59,13 +59,13 @@ public IEnumerable GetAssetsByType(GameAssetType type) => public void AddAssetToDatabase(GameAsset asset) => this.Write(() => { - this._realm.Add(asset); + this.GameAssets.Add(asset); }); public void AddOrUpdateAssetsInDatabase(IEnumerable assets) => this.Write(() => { - this._realm.Add(assets, true); + this.GameAssets.AddRange(assets, true); }); public void SetMainlineIconHash(GameAsset asset, string hash) => diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs b/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs index 3b5be850..5fa2cc42 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs @@ -19,7 +19,7 @@ public void CreateContest(GameContest contest) this.Write(() => { contest.CreationDate = this._time.Now; - this._realm.Add(contest); + this.GameContests.Add(contest); }); } diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs index c6d883f9..79453818 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs @@ -28,7 +28,7 @@ public GameSubmittedScore SubmitScore(SerializedScore score, GameUser user, Game this.Write(() => { - this._realm.Add(newScore); + this.GameSubmittedScores.Add(newScore); }); this.CreateLevelScoreEvent(user, newScore); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs index 1e2721bc..ce2d4b27 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs @@ -23,7 +23,7 @@ public void AddNotification(string title, string text, GameUser user, string? ic this.Write(() => { - this._realm.Add(notification); + this.GameNotifications.Add(notification); }); } @@ -89,7 +89,7 @@ public GameAnnouncement AddAnnouncement(string title, string text) this.Write(() => { - this._realm.Add(announcement); + this.GameAnnouncements.Add(announcement); }); return announcement; diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs index d0b25ba3..4eda5fb0 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs @@ -36,7 +36,7 @@ public GameUser CreateUser(string username, string emailAddress, bool skipChecks this.Write(() => { - this._realm.Add(user); + this.GameUsers.Add(user); }); return user; } @@ -109,7 +109,7 @@ public void AddRegistrationToQueue(string username, string emailAddress, string this.Write(() => { - this._realm.Add(registration); + this.QueuedRegistrations.Add(registration); }); } @@ -188,7 +188,7 @@ public EmailVerificationCode CreateEmailVerificationCode(GameUser user) this.Write(() => { - this._realm.Add(verificationCode); + this.EmailVerificationCodes.Add(verificationCode); }); return verificationCode; @@ -209,7 +209,7 @@ public bool DisallowUser(string username) this.Write(() => { - this._realm.Add(new DisallowedUser + this.DisallowedUsers.Add(new DisallowedUser { Username = username, }); diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 1333d01c..1e4d838b 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -39,7 +39,7 @@ public bool FavouriteLevel(GameLevel level, GameUser user) Level = level, User = user, }; - this.Write(() => this._realm.Add(relation)); + this.Write(() => this.FavouriteLevelRelations.Add(relation)); this.CreateLevelFavouriteEvent(user, level); @@ -103,7 +103,7 @@ public bool FavouriteUser(GameUser userToFavourite, GameUser userFavouriting) UserFavouriting = userFavouriting, }; - this.Write(() => this._realm.Add(relation)); + this.Write(() => this.FavouriteUserRelations.Add(relation)); this.CreateUserFavouriteEvent(userFavouriting, userToFavourite); @@ -162,7 +162,7 @@ public bool QueueLevel(GameLevel level, GameUser user) Level = level, User = user, }; - this.Write(() => this._realm.Add(relation)); + this.Write(() => this.QueueLevelRelations.Add(relation)); return true; } @@ -220,7 +220,7 @@ public bool RateLevel(GameLevel level, GameUser user, RatingType type) Timestamp = this._time.Now, }; - this.Write(() => this._realm.Add(rating)); + this.Write(() => this.RateLevelRelations.Add(rating)); return true; } @@ -303,10 +303,10 @@ public void PlayLevel(GameLevel level, GameUser user, int count) this.Write(() => { - this._realm.Add(relation); + this.PlayLevelRelations.Add(relation); // If the user hasn't played the level before, then add a unique relation too - if (uniqueRelation == null) this._realm.Add(new UniquePlayLevelRelation + if (uniqueRelation == null) this.UniquePlayLevelRelations.Add(new UniquePlayLevelRelation { Level = level, User = user, @@ -361,7 +361,7 @@ public bool RateComment(GameUser user, GameComment comment, RatingType ratingTyp this.Write(() => { - this._realm.Add(relation); + this.CommentRelations.Add(relation); }); } else diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs b/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs index 0a6003cb..a35a0c5b 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs @@ -12,7 +12,7 @@ public RequestStatistics GetRequestStatistics() statistics = new RequestStatistics(); this.Write(() => { - this._realm.Add(statistics); + this.RequestStatistics.Add(statistics); }); return statistics; diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs index 9417644e..38eadbfe 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs @@ -59,7 +59,7 @@ public Token GenerateTokenForUser(GameUser user, TokenType type, TokenGame game, this.Write(() => { user.LastLoginDate = this._time.Now; - this._realm.Add(token); + this.Tokens.Add(token); }); return token; diff --git a/Refresh.GameServer/Database/GameDatabaseContext.cs b/Refresh.GameServer/Database/GameDatabaseContext.cs index ed999a28..f7cd1089 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.cs @@ -27,31 +27,31 @@ public partial class GameDatabaseContext : RealmDatabaseContext private readonly IDateTimeProvider _time; - private IQueryable GameUsers => this._realm.All(); - private IQueryable Tokens => this._realm.All(); - private IQueryable GameLevels => this._realm.All(); - private IQueryable GameComments => this._realm.All(); - private IQueryable CommentRelations => this._realm.All(); - private IQueryable FavouriteLevelRelations => this._realm.All(); - private IQueryable QueueLevelRelations => this._realm.All(); - private IQueryable FavouriteUserRelations => this._realm.All(); - private IQueryable PlayLevelRelations => this._realm.All(); - private IQueryable UniquePlayLevelRelations => this._realm.All(); - private IQueryable RateLevelRelations => this._realm.All(); - private IQueryable Events => this._realm.All(); - private IQueryable GameSubmittedScores => this._realm.All(); - private IQueryable GameAssets => this._realm.All(); - private IQueryable GameNotifications => this._realm.All(); - private IQueryable GamePhotos => this._realm.All(); - private IQueryable GameAnnouncements => this._realm.All(); - private IQueryable QueuedRegistrations => this._realm.All(); - private IQueryable EmailVerificationCodes => this._realm.All(); - private IQueryable RequestStatistics => this._realm.All(); - private IQueryable SequentialIdStorage => this._realm.All(); - private IQueryable GameContests => this._realm.All(); - private IQueryable AssetDependencyRelations => this._realm.All(); - private IQueryable GameReviews => this._realm.All(); - private IQueryable DisallowedUsers => this._realm.All(); + private RealmDbSet GameUsers => new(this._realm); + private RealmDbSet Tokens => new(this._realm); + private RealmDbSet GameLevels => new(this._realm); + private RealmDbSet GameComments => new(this._realm); + private RealmDbSet CommentRelations => new(this._realm); + private RealmDbSet FavouriteLevelRelations => new(this._realm); + private RealmDbSet QueueLevelRelations => new(this._realm); + private RealmDbSet FavouriteUserRelations => new(this._realm); + private RealmDbSet PlayLevelRelations => new(this._realm); + private RealmDbSet UniquePlayLevelRelations => new(this._realm); + private RealmDbSet RateLevelRelations => new(this._realm); + private RealmDbSet Events => new(this._realm); + private RealmDbSet GameSubmittedScores => new(this._realm); + private RealmDbSet GameAssets => new(this._realm); + private RealmDbSet GameNotifications => new(this._realm); + private RealmDbSet GamePhotos => new(this._realm); + private RealmDbSet GameAnnouncements => new(this._realm); + private RealmDbSet QueuedRegistrations => new(this._realm); + private RealmDbSet EmailVerificationCodes => new(this._realm); + private RealmDbSet RequestStatistics => new(this._realm); + private RealmDbSet SequentialIdStorage => new(this._realm); + private RealmDbSet GameContests => new(this._realm); + private RealmDbSet AssetDependencyRelations => new(this._realm); + private RealmDbSet GameReviews => new(this._realm); + private RealmDbSet DisallowedUsers => new(this._realm); internal GameDatabaseContext(IDateTimeProvider time) { @@ -78,7 +78,7 @@ private int GetOrCreateSequentialId() where T : IRealmObject, ISequentialId }; // no need to do write block, this should only be called in a write transaction - this._realm.Add(storage); + this.SequentialIdStorage.Add(storage); return storage.SequentialId; } diff --git a/Refresh.GameServer/Database/RealmDbSet.cs b/Refresh.GameServer/Database/RealmDbSet.cs new file mode 100644 index 00000000..a3ac591a --- /dev/null +++ b/Refresh.GameServer/Database/RealmDbSet.cs @@ -0,0 +1,35 @@ +using System.Collections; +using System.Linq.Expressions; +using JetBrains.Annotations; +using Realms; + +namespace Refresh.GameServer.Database; + +public class RealmDbSet : IQueryable where T : IRealmObject +{ + private readonly Realm _realm; + private IQueryable Queryable => this._realm.All(); + + public RealmDbSet(Realm realm) + { + this._realm = realm; + } + + [MustDisposeResource] public IEnumerator GetEnumerator() => this.Queryable.GetEnumerator(); + [MustDisposeResource] IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + public Type ElementType => typeof(T); + public Expression Expression => this.Queryable.Expression; + public IQueryProvider Provider => this.Queryable.Provider; + + // functions for compatibility with EF + public void Add(T obj) + { + this._realm.Add(obj); + } + + public void AddRange(IEnumerable objs, bool update = false) + { + this._realm.Add(objs, update); + } +} \ No newline at end of file From 123c408f29106180bd1afcc72af2a29621ea22bd Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 22:03:40 -0400 Subject: [PATCH 23/37] Update misc .All calls --- .../Database/GameDatabaseContext.Notifications.cs | 3 +-- Refresh.GameServer/Database/GameDatabaseContext.Relations.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs index ce2d4b27..ea050edc 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs @@ -53,8 +53,7 @@ public DatabaseList GetNotificationsByUser(GameUser user, int [Pure] public GameNotification? GetNotificationByUuid(GameUser user, ObjectId id) - => this._realm - .All() + => this.GameNotifications .FirstOrDefault(n => n.User == user && n.NotificationId == id); public void DeleteNotificationsByUser(GameUser user) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 1e4d838b..88d77383 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -325,8 +325,8 @@ public bool HasUserPlayedLevel(GameLevel level, GameUser user) => #region Comments - private CommentRelation? GetCommentRelationByUser(GameComment comment, GameUser user) => this._realm - .All().FirstOrDefault(r => r.Comment == comment && r.User == user); + private CommentRelation? GetCommentRelationByUser(GameComment comment, GameUser user) => this.CommentRelations + .FirstOrDefault(r => r.Comment == comment && r.User == user); /// /// Get a user's rating on a particular comment. From b5677527dddf06d7737c1d1feab438c5d59f6e4c Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 22:11:20 -0400 Subject: [PATCH 24/37] Add RealmDbSet.Remove --- .../Database/GameDatabaseContext.Assets.cs | 2 +- .../Database/GameDatabaseContext.Contests.cs | 2 +- .../GameDatabaseContext.Leaderboard.cs | 8 ++++---- .../Database/GameDatabaseContext.Levels.cs | 18 ++++++++--------- .../GameDatabaseContext.Notifications.cs | 6 +++--- .../Database/GameDatabaseContext.Photos.cs | 2 +- .../GameDatabaseContext.Registration.cs | 20 +++++++------------ .../Database/GameDatabaseContext.Relations.cs | 12 +++++------ .../Database/GameDatabaseContext.Tokens.cs | 6 +++--- .../Database/GameDatabaseContext.Users.cs | 12 +++++------ .../Database/GameDatabaseContext.cs | 5 +++++ Refresh.GameServer/Database/RealmDbSet.cs | 10 ++++++++++ 12 files changed, 56 insertions(+), 47 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs index 3c00a5c1..cebf37e7 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Assets.cs @@ -41,7 +41,7 @@ public void AddOrOverwriteAssetDependencyRelations(string dependent, IEnumerable this.Write(() => { // delete all existing relations. ensures duplicates won't exist when reprocessing - this._realm.RemoveRange(this.AssetDependencyRelations.Where(a => a.Dependent == dependent)); + this.AssetDependencyRelations.RemoveRange(this.AssetDependencyRelations.Where(a => a.Dependent == dependent)); foreach (string dependency in dependencies) this.AssetDependencyRelations.Add(new AssetDependencyRelation diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs b/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs index 5fa2cc42..78a13f04 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs @@ -27,7 +27,7 @@ public void DeleteContest(GameContest contest) { this.Write(() => { - this._realm.Remove(contest); + this.GameContests.Remove(contest); }); } diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs index 79453818..7b95d6c9 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs @@ -118,8 +118,8 @@ public void DeleteScore(GameSubmittedScore score) this.Write(() => { - this._realm.RemoveRange(scoreEvents); - this._realm.Remove(score); + this.Events.RemoveRange(scoreEvents); + this.GameSubmittedScores.Remove(score); }); } @@ -140,8 +140,8 @@ public void DeleteScoresSetByUser(GameUser user) IQueryable scoreEvents = this.Events .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); - this._realm.RemoveRange(scoreEvents); - this._realm.Remove(score); + this.Events.RemoveRange(scoreEvents); + this.GameSubmittedScores.Remove(score); } }); } diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs index 592dbd24..87ba5328 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs @@ -125,24 +125,24 @@ public void DeleteLevel(GameLevel level) IQueryable levelEvents = this.Events .Where(e => e._StoredDataType == (int)EventDataType.Level && e.StoredSequentialId == level.LevelId); - this._realm.RemoveRange(levelEvents); + this.Events.RemoveRange(levelEvents); #region This is so terrible it needs to be hidden away IQueryable favouriteLevelRelations = this.FavouriteLevelRelations.Where(r => r.Level == level); - this._realm.RemoveRange(favouriteLevelRelations); + this.FavouriteLevelRelations.RemoveRange(favouriteLevelRelations); IQueryable playLevelRelations = this.PlayLevelRelations.Where(r => r.Level == level); - this._realm.RemoveRange(playLevelRelations); + this.PlayLevelRelations.RemoveRange(playLevelRelations); IQueryable queueLevelRelations = this.QueueLevelRelations.Where(r => r.Level == level); - this._realm.RemoveRange(queueLevelRelations); + this.QueueLevelRelations.RemoveRange(queueLevelRelations); IQueryable rateLevelRelations = this.RateLevelRelations.Where(r => r.Level == level); - this._realm.RemoveRange(rateLevelRelations); + this.RateLevelRelations.RemoveRange(rateLevelRelations); IQueryable uniquePlayLevelRelations = this.UniquePlayLevelRelations.Where(r => r.Level == level); - this._realm.RemoveRange(uniquePlayLevelRelations); + this.UniquePlayLevelRelations.RemoveRange(uniquePlayLevelRelations); IQueryable scores = this.GameSubmittedScores.Where(r => r.Level == level); @@ -150,10 +150,10 @@ public void DeleteLevel(GameLevel level) { IQueryable scoreEvents = this.Events .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); - this._realm.RemoveRange(scoreEvents); + this.Events.RemoveRange(scoreEvents); } - this._realm.RemoveRange(scores); + this.GameSubmittedScores.RemoveRange(scores); #endregion @@ -162,7 +162,7 @@ public void DeleteLevel(GameLevel level) //do in separate transaction in a vain attempt to fix Weirdness with favourite level relations having missing levels this.Write(() => { - this._realm.Remove(level); + this.GameLevels.Remove(level); }); } diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs index ea050edc..80f8caff 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs @@ -60,7 +60,7 @@ public void DeleteNotificationsByUser(GameUser user) { this.Write(() => { - this._realm.RemoveRange(this.GameNotifications.Where(n => n.User == user)); + this.GameNotifications.RemoveRange(this.GameNotifications.Where(n => n.User == user)); }); } @@ -68,7 +68,7 @@ public void DeleteNotification(GameNotification notification) { this.Write(() => { - this._realm.Remove(notification); + this.GameNotifications.Remove(notification); }); } @@ -98,7 +98,7 @@ public void DeleteAnnouncement(GameAnnouncement announcement) { this.Write(() => { - this._realm.Remove(announcement); + this.GameAnnouncements.Remove(announcement); }); } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs b/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs index 311ea353..5008c71e 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs @@ -56,7 +56,7 @@ public void RemovePhoto(GamePhoto photo) { this.Write(() => { - this._realm.Remove(photo); + this.GamePhotos.Remove(photo); }); } diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs index 4eda5fb0..e310f1e1 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs @@ -47,7 +47,7 @@ public GameUser CreateUserFromQueuedRegistration(QueuedRegistration registration this.Write(() => { - this._realm.Remove(registration); + this.QueuedRegistrations.Remove(registration); }); GameUser user = this.CreateUser(cloned.Username, cloned.EmailAddress); @@ -117,18 +117,12 @@ public void RemoveRegistrationFromQueue(QueuedRegistration registration) { this.Write(() => { - this._realm.Remove(registration); - }); - } - - public void RemoveAllRegistrationsFromQueue() - { - this.Write(() => - { - this._realm.RemoveAll(); + this.QueuedRegistrations.Remove(registration); }); } + public void RemoveAllRegistrationsFromQueue() => this.Write(this.RemoveAll); + public bool IsRegistrationExpired(QueuedRegistration registration) => registration.ExpiryDate < this._time.Now; public QueuedRegistration? GetQueuedRegistrationByUsername(string username) @@ -149,7 +143,7 @@ public void VerifyUserEmail(GameUser user) this.Write(() => { user.EmailAddressVerified = true; - this._realm.RemoveRange(this.EmailVerificationCodes + this.EmailVerificationCodes.RemoveRange(this.EmailVerificationCodes .Where(c => c.User == user)); }); } @@ -198,7 +192,7 @@ public void RemoveEmailVerificationCode(EmailVerificationCode code) { this.Write(() => { - this._realm.Remove(code); + this.EmailVerificationCodes.Remove(code); }); } @@ -226,7 +220,7 @@ public bool ReallowUser(string username) this.Write(() => { - this._realm.Remove(disallowedUser); + this.DisallowedUsers.Remove(disallowedUser); }); return true; diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 88d77383..6c366768 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -53,7 +53,7 @@ public bool UnfavouriteLevel(GameLevel level, GameUser user) if (relation == null) return false; - this.Write(() => this._realm.Remove(relation)); + this.Write(() => this.FavouriteLevelRelations.Remove(relation)); return true; } @@ -127,7 +127,7 @@ public bool UnfavouriteUser(GameUser userToFavourite, GameUser userFavouriting) if (relation == null) return false; - this.Write(() => this._realm.Remove(relation)); + this.Write(() => this.FavouriteUserRelations.Remove(relation)); return true; } @@ -174,14 +174,14 @@ public bool DequeueLevel(GameLevel level, GameUser user) if (relation == null) return false; - this.Write(() => this._realm.Remove(relation)); + this.Write(() => this.QueueLevelRelations.Remove(relation)); return true; } public void ClearQueue(GameUser user) { - this.Write(() => this._realm.RemoveRange(this.QueueLevelRelations.Where(r => r.User == user))); + this.Write(() => this.QueueLevelRelations.RemoveRange(this.QueueLevelRelations.Where(r => r.User == user))); } #endregion @@ -248,7 +248,7 @@ public void AddReviewToLevel(GameReview review, GameLevel level) foreach (GameReview reviewToDelete in toRemove) { level.Reviews.Remove(reviewToDelete); - this._realm.Remove(reviewToDelete); + this.GameReviews.Remove(reviewToDelete); } }); } @@ -267,7 +267,7 @@ public int GetTotalReviewsByUser(GameUser user) public void DeleteReview(GameReview review) { - this._realm.Remove(review); + this.GameReviews.Remove(review); } public GameReview? GetReviewByLevelAndUser(GameLevel level, GameUser user) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs index 38eadbfe..ee046c66 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs @@ -114,7 +114,7 @@ public void RevokeToken(Token token) { this.Write(() => { - this._realm.Remove(token); + this.Tokens.Remove(token); }); } @@ -122,7 +122,7 @@ public void RevokeAllTokensForUser(GameUser user) { this.Write(() => { - this._realm.RemoveRange(this.Tokens.Where(t => t.User == user)); + this.Tokens.RemoveRange(this.Tokens.Where(t => t.User == user)); }); } @@ -130,7 +130,7 @@ public void RevokeAllTokensForUser(GameUser user, TokenType type) { this.Write(() => { - this._realm.RemoveRange(this.Tokens.Where(t => t.User == user && t._TokenType == (int)type)); + this.Tokens.RemoveRange(this.Tokens.Where(t => t.User == user && t._TokenType == (int)type)); }); } diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs index 321e5981..a9581f74 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Users.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Users.cs @@ -305,11 +305,11 @@ public void DeleteUser(GameUser user) foreach (GamePhotoSubject subject in photo.Subjects.Where(s => s.User?.UserId == user.UserId)) subject.User = null; - this._realm.RemoveRange(this.FavouriteLevelRelations.Where(r => r.User == user)); - this._realm.RemoveRange(this.FavouriteUserRelations.Where(r => r.UserToFavourite == user)); - this._realm.RemoveRange(this.FavouriteUserRelations.Where(r => r.UserFavouriting == user)); - this._realm.RemoveRange(this.QueueLevelRelations.Where(r => r.User == user)); - this._realm.RemoveRange(this.GamePhotos.Where(p => p.Publisher == user)); + this.FavouriteLevelRelations.RemoveRange(this.FavouriteLevelRelations.Where(r => r.User == user)); + this.FavouriteUserRelations.RemoveRange(this.FavouriteUserRelations.Where(r => r.UserToFavourite == user)); + this.FavouriteUserRelations.RemoveRange(this.FavouriteUserRelations.Where(r => r.UserFavouriting == user)); + this.QueueLevelRelations.RemoveRange(this.QueueLevelRelations.Where(r => r.User == user)); + this.GamePhotos.RemoveRange(this.GamePhotos.Where(p => p.Publisher == user)); foreach (GameLevel level in this.GameLevels.Where(l => l.Publisher == user)) { @@ -325,7 +325,7 @@ public void FullyDeleteUser(GameUser user) this.Write(() => { - this._realm.Remove(user); + this.GameUsers.Remove(user); }); } diff --git a/Refresh.GameServer/Database/GameDatabaseContext.cs b/Refresh.GameServer/Database/GameDatabaseContext.cs index f7cd1089..a0cb9623 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.cs @@ -122,4 +122,9 @@ private void Write(Action callback) { this._realm.Write(callback); } + + private void RemoveAll() where T : IRealmObject + { + this._realm.RemoveAll(); + } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/RealmDbSet.cs b/Refresh.GameServer/Database/RealmDbSet.cs index a3ac591a..3a3f887e 100644 --- a/Refresh.GameServer/Database/RealmDbSet.cs +++ b/Refresh.GameServer/Database/RealmDbSet.cs @@ -32,4 +32,14 @@ public void AddRange(IEnumerable objs, bool update = false) { this._realm.Add(objs, update); } + + public void Remove(T obj) + { + this._realm.Remove(obj); + } + + public void RemoveRange(IQueryable objs) + { + this._realm.RemoveRange(objs); + } } \ No newline at end of file From 9fb505d148aebbaeba134a75f3ab0797bb9f1b8f Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 17 Jun 2024 22:12:42 -0400 Subject: [PATCH 25/37] Replace realm find calls --- .../Database/GameDatabaseContext.Registration.cs | 6 +++--- Refresh.GameServer/Database/RealmDbSet.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs index e310f1e1..7b46c964 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs @@ -198,7 +198,7 @@ public void RemoveEmailVerificationCode(EmailVerificationCode code) public bool DisallowUser(string username) { - if (this._realm.Find(username) != null) + if (this.DisallowedUsers.FirstOrDefault(u => u.Username == username) != null) return false; this.Write(() => @@ -214,7 +214,7 @@ public bool DisallowUser(string username) public bool ReallowUser(string username) { - DisallowedUser? disallowedUser = this._realm.Find(username); + DisallowedUser? disallowedUser = this.DisallowedUsers.FirstOrDefault(u => u.Username == username); if (disallowedUser == null) return false; @@ -228,6 +228,6 @@ public bool ReallowUser(string username) public bool IsUserDisallowed(string username) { - return this._realm.Find(username) != null; + return this.DisallowedUsers.FirstOrDefault(u => u.Username == username) != null; } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/RealmDbSet.cs b/Refresh.GameServer/Database/RealmDbSet.cs index 3a3f887e..5e8a9021 100644 --- a/Refresh.GameServer/Database/RealmDbSet.cs +++ b/Refresh.GameServer/Database/RealmDbSet.cs @@ -28,6 +28,7 @@ public void Add(T obj) this._realm.Add(obj); } + // TODO: update = true will need extra consideration for EF. for now just let consumers specify public void AddRange(IEnumerable objs, bool update = false) { this._realm.Add(objs, update); From 5b3d3d8b2d4cf3e7c1cec39a15a863ea937c63de Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 18 Jun 2024 02:35:36 -0400 Subject: [PATCH 26/37] Fix GetTotalLevelsPublishedByUser returning global level count --- Refresh.GameServer/Database/GameDatabaseContext.Levels.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs index dc99e048..ce2bf2e4 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs @@ -370,7 +370,7 @@ public int GetTotalLevelsPublishedByUser(GameUser user) public int GetTotalLevelsPublishedByUser(GameUser user, TokenGame game) => this._realm.All() - .Count(r => r._GameVersion == (int)game); + .Count(r => r.Publisher == user && r._GameVersion == (int)game); [Pure] public int GetTotalTeamPickCount(TokenGame game) => this._realm.All().FilterByGameVersion(game).Count(l => l.TeamPicked); From 4da1aae99c60e127b4f516272c4d2aeec7decfb7 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 18 Jun 2024 02:48:01 -0400 Subject: [PATCH 27/37] Fix recovery attempt of sequential ids for broken tables I misunderstood this code during the refactor. Could have been a disaster for the very small chance where someone is using an ancient version of Refresh and skipping straight to this version. --- Refresh.GameServer/Database/GameDatabaseContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.cs b/Refresh.GameServer/Database/GameDatabaseContext.cs index a0cb9623..4de7d4ed 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.cs @@ -74,7 +74,7 @@ private int GetOrCreateSequentialId() where T : IRealmObject, ISequentialId storage = new SequentialIdStorage { TypeName = name, - SequentialId = this.SequentialIdStorage.Count() * 2, // skip over a bunch of ids incase table is broken + SequentialId = this._realm.All().Count() * 2, // skip over a bunch of ids incase table is broken }; // no need to do write block, this should only be called in a write transaction From 9dd2c5d231ec241021031f01efcf62101e8bcaf4 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 18 Jun 2024 16:00:14 -0400 Subject: [PATCH 28/37] Remove LevelPlay events --- .../GameDatabaseContext.ActivityWrite.cs | 18 --------------- .../Database/GameDatabaseContext.Relations.cs | 2 -- .../Database/GameDatabaseProvider.cs | 8 ++++++- .../Types/Activity/ActivityPage.cs | 1 - .../SerializedEvents/SerializedEvent.cs | 1 - .../SerializedLevelPlayEvent.cs | 22 ------------------- 6 files changed, 7 insertions(+), 45 deletions(-) delete mode 100644 Refresh.GameServer/Types/Activity/SerializedEvents/SerializedLevelPlayEvent.cs diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs index 9484314e..c87b505d 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs @@ -98,24 +98,6 @@ public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser user) return e; } - /// - /// Creates a new LevelPlay event from a , and adds it to the event list. - /// - public Event CreateLevelPlayEvent(GameUser userFrom, GameLevel level) - { - Event e = new() - { - EventType = EventType.LevelPlay, - StoredDataType = EventDataType.Level, - Timestamp = this._time.TimestampMilliseconds, - User = userFrom, - StoredSequentialId = level.LevelId, - }; - - this.Write(() => this.Events.Add(e)); - return e; - } - /// /// Creates a new LevelTag event from a , and adds it to the event list. /// diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 6c366768..46ee8e77 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -313,8 +313,6 @@ public void PlayLevel(GameLevel level, GameUser user, int count) Timestamp = this._time.TimestampMilliseconds, }); }); - - this.CreateLevelPlayEvent(user, level); } public bool HasUserPlayedLevel(GameLevel level, GameUser user) => diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index e329d912..4649992f 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -34,7 +34,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 130; + protected override ulong SchemaVersion => 131; protected override string Filename => "refreshGameServer.realm"; @@ -282,6 +282,12 @@ protected override void Migrate(Migration migration, ulong oldVersion) GameSubmittedScore? score = migration.NewRealm.All().FirstOrDefault(s => s.ScoreId == newEvent.StoredObjectId); if(score == null) eventsToNuke.Add(newEvent); } + + // In version 131 we removed the LevelPlay event + if (oldVersion < 131 && newEvent.EventType == EventType.LevelPlay) + { + eventsToNuke.Add(newEvent); + } } // realm won't let you use an IEnumerable in RemoveRange. too bad! diff --git a/Refresh.GameServer/Types/Activity/ActivityPage.cs b/Refresh.GameServer/Types/Activity/ActivityPage.cs index c44cae4d..aa41bc6f 100644 --- a/Refresh.GameServer/Types/Activity/ActivityPage.cs +++ b/Refresh.GameServer/Types/Activity/ActivityPage.cs @@ -233,7 +233,6 @@ private void GenerateLevelGroups(ActivityGroups groups) levelEvent = @event.EventType switch { EventType.LevelUpload => SerializedLevelUploadEvent.FromSerializedLevelEvent(levelEvent), - EventType.LevelPlay => SerializedLevelPlayEvent.FromSerializedLevelEvent(levelEvent), _ => levelEvent, }; diff --git a/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedEvent.cs b/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedEvent.cs index 1cb6acd1..1f06da67 100644 --- a/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedEvent.cs +++ b/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedEvent.cs @@ -7,7 +7,6 @@ namespace Refresh.GameServer.Types.Activity.SerializedEvents; [XmlInclude(typeof(SerializedUserEvent))] [XmlInclude(typeof(SerializedLevelEvent))] [XmlInclude(typeof(SerializedLevelUploadEvent))] -[XmlInclude(typeof(SerializedLevelPlayEvent))] [XmlInclude(typeof(SerializedScoreSubmitEvent))] public abstract class SerializedEvent { diff --git a/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedLevelPlayEvent.cs b/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedLevelPlayEvent.cs deleted file mode 100644 index 0b93c940..00000000 --- a/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedLevelPlayEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Xml.Serialization; - -namespace Refresh.GameServer.Types.Activity.SerializedEvents; - -public class SerializedLevelPlayEvent : SerializedLevelEvent -{ - /// - /// The number of players that played. - /// - [XmlElement("count")] - public int ScoreType { get; set; } - - public static SerializedLevelPlayEvent FromSerializedLevelEvent(SerializedLevelEvent e) => new() - { - ScoreType = 1, - - Actor = e.Actor, - LevelId = e.LevelId, - Timestamp = e.Timestamp, - Type = e.Type, - }; -} \ No newline at end of file From 6ace34a8b0a5f9d812e7e583b51ffaebb1237ffc Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 18 Jun 2024 16:35:06 -0400 Subject: [PATCH 29/37] Remove backlinks from levels --- .../Database/GameDatabaseContext.Relations.cs | 22 +++++++++++++++++ .../Response/Levels/ApiGameLevelResponse.cs | 8 +++---- .../DataTypes/Response/GameLevelResponse.cs | 20 ++++++---------- Refresh.GameServer/Types/Levels/GameLevel.cs | 24 ++++--------------- .../Types/Reviews/SerializedGameReview.cs | 2 +- .../Workers/CoolLevelsWorker.cs | 18 +++++++------- .../Tests/Levels/ScoreLeaderboardTests.cs | 9 ++++--- 7 files changed, 52 insertions(+), 51 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 6c366768..fdf63da6 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -57,6 +57,10 @@ public bool UnfavouriteLevel(GameLevel level, GameUser user) return true; } + + public int GetFavouriteCountForLevel(GameLevel level) => this.FavouriteLevelRelations + .Count(r => r.Level == level); + #endregion #region Favouriting Users @@ -232,6 +236,9 @@ public bool RateLevel(GameLevel level, GameUser user, RatingType type) return true; } + public int GetTotalRatingsForLevel(GameLevel level, RatingType type) => + this.RateLevelRelations.Count(r => r.Level == level && r._RatingType == (int)type); + /// /// Adds a review to the database, deleting any old ones by the user on that level. /// @@ -320,6 +327,21 @@ public void PlayLevel(GameLevel level, GameUser user, int count) public bool HasUserPlayedLevel(GameLevel level, GameUser user) => this.UniquePlayLevelRelations .FirstOrDefault(r => r.Level == level && r.User == user) != null; + + public IEnumerable GetAllPlaysForLevel(GameLevel level) => + this.PlayLevelRelations.Where(r => r.Level == level); + + public IEnumerable GetAllPlaysForLevelByUser(GameLevel level, GameUser user) => + this.PlayLevelRelations.Where(r => r.Level == level && r.User == user); + + public int GetTotalPlaysForLevel(GameLevel level) => + this.GetAllPlaysForLevel(level).Sum(playLevelRelation => playLevelRelation.Count); + + public int GetTotalPlaysForLevelByUser(GameLevel level, GameUser user) => + this.GetAllPlaysForLevelByUser(level, user).Sum(playLevelRelation => playLevelRelation.Count); + + public int GetUniquePlaysForLevel(GameLevel level) => + this.UniquePlayLevelRelations.Count(r => r.Level == level); #endregion diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Levels/ApiGameLevelResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Levels/ApiGameLevelResponse.cs index d67292b9..dd2a021d 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Levels/ApiGameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/Levels/ApiGameLevelResponse.cs @@ -71,10 +71,10 @@ public class ApiGameLevelResponse : IApiResponse, IDataConvertableFrom r._RatingType == (int)RatingType.Yay), - BooRatings = level.Ratings.Count(r => r._RatingType == (int)RatingType.Boo), - Hearts = level.FavouriteRelations.Count(), - UniquePlays = level.UniquePlays.Count(), + YayRatings = dataContext.Database.GetTotalRatingsForLevel(level, RatingType.Yay), + BooRatings = dataContext.Database.GetTotalRatingsForLevel(level, RatingType.Boo), + Hearts = dataContext.Database.GetFavouriteCountForLevel(level), + UniquePlays = dataContext.Database.GetUniquePlaysForLevel(level), TeamPicked = level.TeamPicked, RootLevelHash = level.RootResource, GameVersion = level.GameVersion, diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs index e43b83da..9c663e52 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs @@ -143,12 +143,6 @@ public static GameLevelResponse FromHash(string hash, DataContext dataContext) { if (old == null) return null; - int totalPlayCount = 0; - foreach (PlayLevelRelation playLevelRelation in old.AllPlays) - { - totalPlayCount += playLevelRelation.Count; - } - GameLevelResponse response = new() { LevelId = old.LevelId, @@ -164,11 +158,11 @@ public static GameLevelResponse FromHash(string hash, DataContext dataContext) MaxPlayers = old.MaxPlayers, EnforceMinMaxPlayers = old.EnforceMinMaxPlayers, SameScreenGame = old.SameScreenGame, - HeartCount = old.FavouriteRelations.Count(), - TotalPlayCount = totalPlayCount, - UniquePlayCount = old.UniquePlays.Count(), - YayCount = old.Ratings.Count(r => r._RatingType == (int)RatingType.Yay), - BooCount = old.Ratings.Count(r => r._RatingType == (int)RatingType.Boo), + HeartCount = dataContext.Database.GetFavouriteCountForLevel(old), + TotalPlayCount = dataContext.Database.GetTotalPlaysForLevel(old), + UniquePlayCount = dataContext.Database.GetUniquePlaysForLevel(old), + YayCount = dataContext.Database.GetTotalRatingsForLevel(old, RatingType.Yay), + BooCount = dataContext.Database.GetTotalRatingsForLevel(old, RatingType.Boo), SkillRewards = old.SkillRewards.ToList(), TeamPicked = old.TeamPicked, LevelType = old.LevelType.ToGameString(), @@ -177,7 +171,7 @@ public static GameLevelResponse FromHash(string hash, DataContext dataContext) IsSubLevel = old.IsSubLevel, BackgroundGuid = old.BackgroundGuid, Links = "", - AverageStarRating = old.CalculateAverageStarRating(), + AverageStarRating = old.CalculateAverageStarRating(dataContext.Database), ReviewCount = old.Reviews.Count, CommentCount = old.LevelComments.Count, }; @@ -210,7 +204,7 @@ public static GameLevelResponse FromHash(string hash, DataContext dataContext) response.YourRating = rating?.ToDPad() ?? (int)RatingType.Neutral; response.YourStarRating = rating?.ToLBP1() ?? 0; - response.YourLbp2PlayCount = old.AllPlays.Count(p => p.User == dataContext.User); + response.YourLbp2PlayCount = dataContext.Database.GetTotalPlaysForLevelByUser(old, dataContext.User); } response.PlayerCount = dataContext.Match.GetPlayerCountForLevel(RoomSlotType.Online, response.LevelId); diff --git a/Refresh.GameServer/Types/Levels/GameLevel.cs b/Refresh.GameServer/Types/Levels/GameLevel.cs index f789ec9f..e31099c0 100644 --- a/Refresh.GameServer/Types/Levels/GameLevel.cs +++ b/Refresh.GameServer/Types/Levels/GameLevel.cs @@ -94,20 +94,6 @@ [Ignored] public GameLevelSource Source #nullable disable public IList LevelComments { get; } - [Backlink(nameof(FavouriteLevelRelation.Level))] - public IQueryable FavouriteRelations { get; } - - [Backlink(nameof(UniquePlayLevelRelation.Level))] - public IQueryable UniquePlays { get; } - - [Backlink(nameof(PlayLevelRelation.Level))] - public IQueryable AllPlays { get; } - [Backlink(nameof(GameSubmittedScore.Level))] - public IQueryable Scores { get; } - - [Backlink(nameof(RateLevelRelation.Level))] - public IQueryable Ratings { get; } - // ILists can't be serialized to XML, and Lists/Arrays cannot be stored in realm, // hence _SkillRewards and SkillRewards both existing // ReSharper disable once InconsistentNaming @@ -155,13 +141,13 @@ public int SequentialId /// Calculates the average rating of a level based on the ratings it has. /// /// A double between 1 and 5, indicating the level's average ratings. - public double CalculateAverageStarRating() + public double CalculateAverageStarRating(GameDatabaseContext database) { - int yayCount = this.Ratings.Count(x => x._RatingType == (int)RatingType.Yay); - int booCount = this.Ratings.Count(x => x._RatingType == (int)RatingType.Boo); - int neutralCount = this.Ratings.Count(x => x._RatingType == (int)RatingType.Neutral); + int yayCount = database.GetTotalRatingsForLevel(this, RatingType.Yay); + int booCount = database.GetTotalRatingsForLevel(this, RatingType.Boo); + int neutralCount = database.GetTotalRatingsForLevel(this, RatingType.Neutral); - // Return 0 if all the counts are 0, we dont want a div by 0 error! + // Return 0 if all the counts are 0, we don't want a div by 0 error! if (yayCount + booCount + neutralCount == 0) return 0; return (double)((5 * yayCount) + (1 * booCount) + (3 * neutralCount)) / (yayCount + booCount + neutralCount); diff --git a/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs index 1d7cbb27..2be8b7d3 100644 --- a/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs +++ b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs @@ -70,7 +70,7 @@ public class SerializedGameReview : IDataConvertableFrom r.User == review.Publisher)?.RatingType.ToDPad() ?? 0, + Thumb = dataContext.Database.GetRatingByUser(review.Level, dataContext.User!)?.ToDPad() ?? 0, ThumbsUp = 0, ThumbsDown = 0, YourThumb = 0, diff --git a/Refresh.GameServer/Workers/CoolLevelsWorker.cs b/Refresh.GameServer/Workers/CoolLevelsWorker.cs index e7f02b54..ff0a1bcf 100644 --- a/Refresh.GameServer/Workers/CoolLevelsWorker.cs +++ b/Refresh.GameServer/Workers/CoolLevelsWorker.cs @@ -48,8 +48,8 @@ public void DoWork(Logger logger, IDataStore dataStore, GameDatabaseContext data // Calculate positive & negative score separately so we don't run into issues with // the multiplier having an opposite effect with the negative score as time passes - float positiveScore = CalculatePositiveScore(logger, level); - float negativeScore = CalculateNegativeScore(logger, level); + float positiveScore = CalculatePositiveScore(logger, level, database); + float negativeScore = CalculateNegativeScore(logger, level, database); // Increase to tweak how little negative score gets affected by decay const int negativeScoreDecayMultiplier = 2; @@ -97,7 +97,7 @@ private static float CalculateLevelDecayMultiplier(Logger logger, long now, Game return multiplier; } - private static float CalculatePositiveScore(Logger logger, GameLevel level) + private static float CalculatePositiveScore(Logger logger, GameLevel level, GameDatabaseContext database) { // Start levels off with a few points to prevent one dislike from bombing the level // Don't apply this bonus to reuploads to discourage a flood of 15CR levels. @@ -111,13 +111,13 @@ private static float CalculatePositiveScore(Logger logger, GameLevel level) if (level.TeamPicked) score += 50; - int positiveRatings = level.Ratings.Count(r => r._RatingType == (int)RatingType.Yay); - int negativeRatings = level.Ratings.Count(r => r._RatingType == (int)RatingType.Boo); - int uniquePlays = level.UniquePlays.Count(); + int positiveRatings = database.GetTotalRatingsForLevel(level, RatingType.Yay); + int negativeRatings = database.GetTotalRatingsForLevel(level, RatingType.Boo); + int uniquePlays = database.GetUniquePlaysForLevel(level); score += positiveRatings * positiveRatingPoints; score += uniquePlays * uniquePlayPoints; - score += level.FavouriteRelations.Count() * heartPoints; + score += database.GetFavouriteCountForLevel(level) * heartPoints; // Reward for a good ratio between plays and yays float ratingRatio = (positiveRatings - negativeRatings) / (float)uniquePlays; @@ -133,7 +133,7 @@ private static float CalculatePositiveScore(Logger logger, GameLevel level) return score; } - private static float CalculateNegativeScore(Logger logger, GameLevel level) + private static float CalculateNegativeScore(Logger logger, GameLevel level, GameDatabaseContext database) { float penalty = 0; const float negativeRatingPenalty = 5; @@ -144,7 +144,7 @@ private static float CalculateNegativeScore(Logger logger, GameLevel level) // The percentage of how much penalty should be applied at the end of the calculation. const float penaltyMultiplier = 0.75f; - penalty += level.Ratings.Count(r => r._RatingType == (int)RatingType.Boo) * negativeRatingPenalty; + penalty += database.GetTotalRatingsForLevel(level, RatingType.Boo) * negativeRatingPenalty; if (level.Publisher == null) penalty += noAuthorPenalty; diff --git a/RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs b/RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs index d692a213..25f7770f 100644 --- a/RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs +++ b/RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs @@ -333,7 +333,7 @@ public void PlayLevel() context.Database.Refresh(); - Assert.That(level.AllPlays.Count(), Is.EqualTo(1)); + Assert.That(context.Database.GetTotalPlaysForLevel(level), Is.EqualTo(1)); } [Test] @@ -363,7 +363,7 @@ public void PlayLevelWithCount() context.Database.Refresh(); - Assert.That(level.AllPlays.AsEnumerable().Sum(p => p.Count), Is.EqualTo(2)); + Assert.That(context.Database.GetTotalPlaysForLevel(level), Is.EqualTo(2)); } [Test] @@ -415,8 +415,7 @@ public void OvertakeNotificationsWorkProperly() for (int i = 0; i < users.Count; i++) { - GameSubmittedScore? lastBestScore = level - .Scores.OrderByDescending(s => s.Score).FirstOrDefault(); + GameSubmittedScore? lastBestScore = context.Database.GetTopScoresForLevel(level, 1, 0, 1, true).Items.FirstOrDefault(); GameUser user = users[i]; context.SubmitScore(i, 1, level, user, TokenGame.LittleBigPlanet2, TokenPlatform.PS3); @@ -440,7 +439,7 @@ public void OvertakeNotificationsWorkProperly() } // Check that notification was not sent to people who weren't previously #1 - if (level.Scores.Count() <= 2) continue; + if (context.Database.GetTotalPlaysForLevel(level) <= 2) continue; notificationRecipient = users[i - 2]; Assert.That(context.Database.GetNotificationCountByUser(notificationRecipient), Is.Zero); From 3bb36dc7d8770d81d6dba98442898c6b291c1d61 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 18 Jun 2024 17:23:05 -0400 Subject: [PATCH 30/37] Only record play events when creating a unique play relation --- .../GameDatabaseContext.ActivityWrite.cs | 18 ++++++++++++++++++ .../Database/GameDatabaseContext.Relations.cs | 15 ++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs index c87b505d..31ec867f 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs @@ -97,6 +97,24 @@ public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser user) this.Write(() => this.Events.Add(e)); return e; } + + /// + /// Creates a new LevelPlay event from a , and adds it to the event list. + /// + public Event CreateLevelPlayEvent(GameUser userFrom, GameLevel level) + { + Event e = new() + { + EventType = EventType.LevelPlay, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } /// /// Creates a new LevelTag event from a , and adds it to the event list. diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 46ee8e77..9facdf18 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -306,12 +306,17 @@ public void PlayLevel(GameLevel level, GameUser user, int count) this.PlayLevelRelations.Add(relation); // If the user hasn't played the level before, then add a unique relation too - if (uniqueRelation == null) this.UniquePlayLevelRelations.Add(new UniquePlayLevelRelation + if (uniqueRelation == null) { - Level = level, - User = user, - Timestamp = this._time.TimestampMilliseconds, - }); + this.UniquePlayLevelRelations.Add(new UniquePlayLevelRelation + { + Level = level, + User = user, + Timestamp = this._time.TimestampMilliseconds, + }); + + this.CreateLevelPlayEvent(user, level); + } }); } From 5c54a573fbecf9215d6861cc6f740eefa8b7e128 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 18 Jun 2024 17:26:08 -0400 Subject: [PATCH 31/37] Handle multiple write transactions --- Refresh.GameServer/Database/GameDatabaseContext.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.cs b/Refresh.GameServer/Database/GameDatabaseContext.cs index 4de7d4ed..c526bb09 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.cs @@ -120,6 +120,14 @@ private void AddSequentialObject(T obj) where T : IRealmObject, ISequentialId [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Write(Action callback) { + // if already in a write transaction, include this within that write transaction + // throws RealmInvalidTransactionException without this + if (this._realm.IsInTransaction) + { + callback(); + return; + } + this._realm.Write(callback); } From c78d6db82009534f2dbc40e3f6e68548d1a35f2b Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 18 Jun 2024 17:33:16 -0400 Subject: [PATCH 32/37] Fix whitespace thing --- .../Database/Activity/GameDatabaseContext.ActivityWrite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs index 31ec867f..9484314e 100644 --- a/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs @@ -97,7 +97,7 @@ public Event CreateUserUnfavouriteEvent(GameUser userFrom, GameUser user) this.Write(() => this.Events.Add(e)); return e; } - + /// /// Creates a new LevelPlay event from a , and adds it to the event list. /// From b4be1948a14ae6a105f18a14dff1795a7bd7c2a4 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Tue, 18 Jun 2024 19:02:59 -0700 Subject: [PATCH 33/37] Prevent users from re-publishing a level twice. This should curb the rampant reuploads on the official instance *just a lil* --- .../Database/GameDatabaseContext.Levels.cs | 4 +- .../Endpoints/Game/Levels/PublishEndpoints.cs | 17 ++++-- .../Tests/Levels/PublishEndpointsTests.cs | 55 +++++++++++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs index d5c40be2..d36fe460 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs @@ -7,7 +7,6 @@ using Refresh.GameServer.Endpoints.Game.Levels.FilterSettings; using Refresh.GameServer.Extensions; using Refresh.GameServer.Services; -using Refresh.GameServer.Types; using Refresh.GameServer.Types.Activity; using Refresh.GameServer.Types.Levels; using Refresh.GameServer.Types.Matching; @@ -391,6 +390,9 @@ public int GetTotalLevelsPublishedByUser(GameUser user, TokenGame game) return null; } } + + public GameLevel? GetLevelByRootResource(string rootResource) => + this.GameLevels.FirstOrDefault(level => level.RootResource == rootResource); [Pure] public GameLevel? GetLevelById(int id) => this.GameLevels.FirstOrDefault(l => l.LevelId == id); diff --git a/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs b/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs index 19b1c165..0b1f86d7 100644 --- a/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs @@ -29,7 +29,7 @@ public class PublishEndpoints : EndpointGroup /// The associated GuidCheckerService with the request /// The game the level is being submitted from /// Whether or not validation succeeded - private static bool VerifyLevel(GameLevelRequest body, GameUser user, Logger logger, GuidCheckerService guidChecker, TokenGame game) + private static bool VerifyLevel(GameLevelRequest body, GameUser user, Logger logger, GuidCheckerService guidChecker, TokenGame game, GameDatabaseContext database) { if (body.Title.Length > 256) { @@ -52,6 +52,15 @@ private static bool VerifyLevel(GameLevelRequest body, GameUser user, Logger log if (!guidChecker.IsTextureGuid(game, long.Parse(body.IconHash.AsSpan()[1..]))) return false; } + + GameLevel? existingLevel = database.GetLevelByRootResource(body.RootResource); + // If there is an existing level with this root hash, and this isn't an update request, block the upload + if (existingLevel != null && body.LevelId != existingLevel.LevelId) + { + database.AddPublishFailNotification("You may not re-upload another user's level.", body.ToGameLevel(user), user); + + return false; + } return true; } @@ -61,7 +70,7 @@ private static bool VerifyLevel(GameLevelRequest body, GameUser user, Logger log public SerializedLevelResources? StartPublish(RequestContext context, GameUser user, GameDatabaseContext database, GameLevelRequest body, CommandService command, IDataStore dataStore, GuidCheckerService guidChecker, Token token) { //If verifying the request fails, return null - if (!VerifyLevel(body, user, context.Logger, guidChecker, token.TokenGame)) return null; + if (!VerifyLevel(body, user, context.Logger, guidChecker, token.TokenGame, database)) return null; List hashes = new(); hashes.AddRange(body.XmlResources); @@ -93,8 +102,8 @@ public Response PublishLevel(RequestContext context, IDataStore dataStore, GuidCheckerService guidChecker, DataContext dataContext) { - //If verifying the request fails, return null - if (!VerifyLevel(body, user, context.Logger, guidChecker, token.TokenGame)) return BadRequest; + //If verifying the request fails, return BadRequest + if (!VerifyLevel(body, user, context.Logger, guidChecker, token.TokenGame, database)) return BadRequest; GameLevel level = body.ToGameLevel(user); level.GameVersion = token.TokenGame; diff --git a/RefreshTests.GameServer/Tests/Levels/PublishEndpointsTests.cs b/RefreshTests.GameServer/Tests/Levels/PublishEndpointsTests.cs index 8890178d..703a7816 100644 --- a/RefreshTests.GameServer/Tests/Levels/PublishEndpointsTests.cs +++ b/RefreshTests.GameServer/Tests/Levels/PublishEndpointsTests.cs @@ -400,7 +400,62 @@ public void CantRepublishOtherUsersLevel() message = client2.PostAsync("/lbp/publish", new StringContent(level.AsXML())).Result; Assert.That(message.StatusCode, Is.EqualTo(BadRequest)); } + + [Test] + public void CantPublishSameRootLevelHashTwice() + { + using TestContext context = this.GetServer(); + GameUser user1 = context.CreateUser(); + GameUser user2 = context.CreateUser(); + + using HttpClient client1 = context.GetAuthenticatedClient(TokenType.Game, user1); + using HttpClient client2 = context.GetAuthenticatedClient(TokenType.Game, user2); + + GameLevelRequest level = new() + { + LevelId = 0, + Title = "TEST LEVEL", + IconHash = "g719", + Description = "DESCRIPTION", + Location = new GameLocation(), + GameVersion = 0, + RootResource = TEST_ASSET_HASH, + PublishDate = 0, + UpdateDate = 0, + MinPlayers = 0, + MaxPlayers = 0, + EnforceMinMaxPlayers = false, + SameScreenGame = false, + SkillRewards = new List(), + }; + + HttpResponseMessage message = client1.PostAsync("/lbp/startPublish", new StringContent(level.AsXML())).Result; + Assert.That(message.StatusCode, Is.EqualTo(OK)); + + SerializedLevelResources resourcesToUpload = message.Content.ReadAsXML(); + Assert.That(resourcesToUpload.Resources, Has.Length.EqualTo(1)); + Assert.That(resourcesToUpload.Resources[0], Is.EqualTo(TEST_ASSET_HASH)); + + //Upload our """level""" + message = client1.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent("LVLb"u8.ToArray())).Result; + Assert.That(message.StatusCode, Is.EqualTo(OK)); + + //As user 1, publish a level + message = client1.PostAsync("/lbp/publish", new StringContent(level.AsXML())).Result; + Assert.That(message.StatusCode, Is.EqualTo(OK)); + + GameLevelResponse response = message.Content.ReadAsXML(); + Assert.That(response.Title, Is.EqualTo(level.Title)); + Assert.That(response.Description, Is.EqualTo(level.Description)); + level.Title = "REPUBLISH!"; + level.Description = "PANA KIN!!!! MI PAIN PEKO E SINA"; + + //As user 2, try to publish a level with the same root hash + message = client2.PostAsync("/lbp/publish", new StringContent(level.AsXML())).Result; + Assert.That(message.StatusCode, Is.EqualTo(BadRequest)); + } + [Test] public void UnpublishLevel() { From 9f891bc363dedc94e14304cc0653ac5f4bfc6d15 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Tue, 18 Jun 2024 20:23:03 -0700 Subject: [PATCH 34/37] Update Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs Co-authored-by: jvyden Signed-off-by: Beyley Thomas --- Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs b/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs index 0b1f86d7..6689e964 100644 --- a/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs @@ -57,7 +57,7 @@ private static bool VerifyLevel(GameLevelRequest body, GameUser user, Logger log // If there is an existing level with this root hash, and this isn't an update request, block the upload if (existingLevel != null && body.LevelId != existingLevel.LevelId) { - database.AddPublishFailNotification("You may not re-upload another user's level.", body.ToGameLevel(user), user); + database.AddPublishFailNotification("The level you tried to publish has already been uploaded by another user.", body.ToGameLevel(user), user); return false; } From e30c380e9d3274fd85c7627c4f57c8bcc4484ec1 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Tue, 18 Jun 2024 21:03:09 -0700 Subject: [PATCH 35/37] Change VerifyLevel to use a DataContext --- .../Endpoints/Game/Levels/PublishEndpoints.cs | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs b/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs index 6689e964..38cedb2f 100644 --- a/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs @@ -29,7 +29,7 @@ public class PublishEndpoints : EndpointGroup /// The associated GuidCheckerService with the request /// The game the level is being submitted from /// Whether or not validation succeeded - private static bool VerifyLevel(GameLevelRequest body, GameUser user, Logger logger, GuidCheckerService guidChecker, TokenGame game, GameDatabaseContext database) + private static bool VerifyLevel(GameLevelRequest body, DataContext dataContext, GuidCheckerService guidChecker) { if (body.Title.Length > 256) { @@ -49,43 +49,51 @@ private static bool VerifyLevel(GameLevelRequest body, GameUser user, Logger log //If the icon hash is a GUID hash, verify its a valid texture GUID if (body.IconHash.StartsWith('g')) { - if (!guidChecker.IsTextureGuid(game, long.Parse(body.IconHash.AsSpan()[1..]))) + if (!guidChecker.IsTextureGuid(dataContext.Game, long.Parse(body.IconHash.AsSpan()[1..]))) return false; } - GameLevel? existingLevel = database.GetLevelByRootResource(body.RootResource); + GameLevel? existingLevel = dataContext.Database.GetLevelByRootResource(body.RootResource); // If there is an existing level with this root hash, and this isn't an update request, block the upload if (existingLevel != null && body.LevelId != existingLevel.LevelId) { - database.AddPublishFailNotification("The level you tried to publish has already been uploaded by another user.", body.ToGameLevel(user), user); - + dataContext.Database.AddPublishFailNotification("The level you tried to publish has already been uploaded by another user.", body.ToGameLevel(dataContext.User!), dataContext.User!); + return false; } - + return true; } - + [GameEndpoint("startPublish", ContentType.Xml, HttpMethods.Post)] [NullStatusCode(BadRequest)] - public SerializedLevelResources? StartPublish(RequestContext context, GameUser user, GameDatabaseContext database, GameLevelRequest body, CommandService command, IDataStore dataStore, GuidCheckerService guidChecker, Token token) + public SerializedLevelResources? StartPublish(RequestContext context, + GameUser user, + GameLevelRequest body, + CommandService command, + IDataStore dataStore, + GuidCheckerService guidChecker, + DataContext dataContext) { //If verifying the request fails, return null - if (!VerifyLevel(body, user, context.Logger, guidChecker, token.TokenGame, database)) return null; - - List hashes = new(); - hashes.AddRange(body.XmlResources); - hashes.Add(body.RootResource); - hashes.Add(body.IconHash); + if (!VerifyLevel(body, dataContext, guidChecker)) return null; + + List hashes = + [ + .. body.XmlResources, + body.RootResource, + body.IconHash + ]; //Remove all invalid or GUID assets hashes.RemoveAll(r => r == "0" || r.StartsWith('g') || string.IsNullOrWhiteSpace(r)); - + //Verify all hashes are valid SHA1 hashes if (hashes.Any(hash => !CommonPatterns.Sha1Regex().IsMatch(hash))) return null; //Mark the user as publishing command.StartPublishing(user.UserId); - + return new SerializedLevelResources { Resources = hashes.Where(r => !dataStore.ExistsInStore(r)).ToArray(), @@ -103,8 +111,8 @@ public Response PublishLevel(RequestContext context, GuidCheckerService guidChecker, DataContext dataContext) { //If verifying the request fails, return BadRequest - if (!VerifyLevel(body, user, context.Logger, guidChecker, token.TokenGame, database)) return BadRequest; - + if (!VerifyLevel(body, dataContext, guidChecker)) return BadRequest; + GameLevel level = body.ToGameLevel(user); level.GameVersion = token.TokenGame; @@ -112,7 +120,7 @@ public Response PublishLevel(RequestContext context, level.MaxPlayers = Math.Clamp(level.MaxPlayers, 1, 4); string rootResourcePath = context.IsPSP() ? $"psp/{level.RootResource}" : level.RootResource; - + //Check if the root resource is a SHA1 hash if (!CommonPatterns.Sha1Regex().IsMatch(level.RootResource)) return BadRequest; //Make sure the root resource exists in the data store @@ -128,11 +136,11 @@ public Response PublishLevel(RequestContext context, { return new Response(GameLevelResponse.FromOld(newBody, dataContext)!, ContentType.Xml); } - + database.AddPublishFailNotification("You may not republish another user's level.", level, user); return BadRequest; } - + //Mark the user as no longer publishing commandService.StopPublishing(user.UserId); @@ -140,9 +148,9 @@ public Response PublishLevel(RequestContext context, database.AddLevel(level); database.CreateLevelUploadEvent(user, level); - + context.Logger.LogInfo(BunkumCategory.UserContent, "User {0} (id: {1}) uploaded level id {2}", user.Username, user.UserId, level.LevelId); - + return new Response(GameLevelResponse.FromOld(level, dataContext)!, ContentType.Xml); } From 0a919ebef5c53978df5f441bfd0d53f6a8f86f54 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 19 Jun 2024 15:37:53 -0400 Subject: [PATCH 36/37] Undo removal of SerializedLevelPlayEvent --- .../Types/Activity/ActivityPage.cs | 1 + .../SerializedEvents/SerializedEvent.cs | 1 + .../SerializedLevelPlayEvent.cs | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 Refresh.GameServer/Types/Activity/SerializedEvents/SerializedLevelPlayEvent.cs diff --git a/Refresh.GameServer/Types/Activity/ActivityPage.cs b/Refresh.GameServer/Types/Activity/ActivityPage.cs index aa41bc6f..c44cae4d 100644 --- a/Refresh.GameServer/Types/Activity/ActivityPage.cs +++ b/Refresh.GameServer/Types/Activity/ActivityPage.cs @@ -233,6 +233,7 @@ private void GenerateLevelGroups(ActivityGroups groups) levelEvent = @event.EventType switch { EventType.LevelUpload => SerializedLevelUploadEvent.FromSerializedLevelEvent(levelEvent), + EventType.LevelPlay => SerializedLevelPlayEvent.FromSerializedLevelEvent(levelEvent), _ => levelEvent, }; diff --git a/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedEvent.cs b/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedEvent.cs index 1f06da67..1cb6acd1 100644 --- a/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedEvent.cs +++ b/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedEvent.cs @@ -7,6 +7,7 @@ namespace Refresh.GameServer.Types.Activity.SerializedEvents; [XmlInclude(typeof(SerializedUserEvent))] [XmlInclude(typeof(SerializedLevelEvent))] [XmlInclude(typeof(SerializedLevelUploadEvent))] +[XmlInclude(typeof(SerializedLevelPlayEvent))] [XmlInclude(typeof(SerializedScoreSubmitEvent))] public abstract class SerializedEvent { diff --git a/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedLevelPlayEvent.cs b/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedLevelPlayEvent.cs new file mode 100644 index 00000000..0b93c940 --- /dev/null +++ b/Refresh.GameServer/Types/Activity/SerializedEvents/SerializedLevelPlayEvent.cs @@ -0,0 +1,22 @@ +using System.Xml.Serialization; + +namespace Refresh.GameServer.Types.Activity.SerializedEvents; + +public class SerializedLevelPlayEvent : SerializedLevelEvent +{ + /// + /// The number of players that played. + /// + [XmlElement("count")] + public int ScoreType { get; set; } + + public static SerializedLevelPlayEvent FromSerializedLevelEvent(SerializedLevelEvent e) => new() + { + ScoreType = 1, + + Actor = e.Actor, + LevelId = e.LevelId, + Timestamp = e.Timestamp, + Type = e.Type, + }; +} \ No newline at end of file From 0574f15ce37f04edd0863c36aa0e0df43c0d4cbe Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 19 Jun 2024 17:30:56 -0400 Subject: [PATCH 37/37] Add API lookup for levels by their root resource --- .../Endpoints/ApiV3/LevelApiEndpoints.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs index 89fb2f0a..1b95bf91 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs @@ -89,6 +89,19 @@ public ApiResponse GetLevelById(RequestContext context, Ga return ApiGameLevelResponse.FromOld(level, dataContext); } + [ApiV3Endpoint("levels/hash/{hash}"), Authentication(false)] + [DocSummary("Gets an individual level by the level's RootResource hash")] + [DocError(typeof(ApiNotFoundError), "The level cannot be found")] + public ApiResponse GetLevelByRootResource(RequestContext context, GameDatabaseContext database, + IDataStore dataStore, + [DocSummary("The RootResource hash of the level")] string hash, DataContext dataContext) + { + GameLevel? level = database.GetLevelByRootResource(hash); + if (level == null) return ApiNotFoundError.LevelMissingError; + + return ApiGameLevelResponse.FromOld(level, dataContext); + } + [ApiV3Endpoint("levels/id/{id}", HttpMethods.Patch)] [DocSummary("Edits a level by the level's numerical ID")] [DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)]