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.Common/Refresh.Common.csproj b/Refresh.Common/Refresh.Common.csproj index 487bbc5d..a2d94508 100644 --- a/Refresh.Common/Refresh.Common.csproj +++ b/Refresh.Common/Refresh.Common.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/Refresh.GameServer/CommandLineManager.cs b/Refresh.GameServer/CommandLineManager.cs index 817e4bfa..86e0ad71 100644 --- a/Refresh.GameServer/CommandLineManager.cs +++ b/Refresh.GameServer/CommandLineManager.cs @@ -58,12 +58,27 @@ 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; } } 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] @@ -73,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); @@ -162,5 +177,15 @@ 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); + this._server.FullyDeleteUser(user); + } } } \ 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 93% rename from Refresh.GameServer/Database/GameDatabaseContext.Activity.cs rename to Refresh.GameServer/Database/Activity/GameDatabaseContext.Activity.cs index c407295e..4e86e86a 100644 --- a/Refresh.GameServer/Database/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 ); } @@ -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 new file mode 100644 index 00000000..c55427ee --- /dev/null +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityRead.cs @@ -0,0 +1,52 @@ +using System.Diagnostics; +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 e) + { + if (e.StoredDataType != EventDataType.User) + throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.User)})"); + + Debug.Assert(e.StoredObjectId != null); + + return this.GetUserByObjectId(e.StoredObjectId); + } + + public GameLevel? GetLevelFromEvent(Event e) + { + if (e.StoredDataType != EventDataType.Level) + throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.Level)})"); + + Debug.Assert(e.StoredSequentialId != null); + + return this.GetLevelById(e.StoredSequentialId.Value); + } + + public GameSubmittedScore? GetScoreFromEvent(Event e) + { + if (e.StoredDataType != EventDataType.Score) + throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.Score)})"); + + Debug.Assert(e.StoredObjectId != null); + + return this.GetScoreByObjectId(e.StoredObjectId); + } + + public RateLevelRelation? GetRateLevelRelationFromEvent(Event e) + { + if (e.StoredDataType != EventDataType.RateLevelRelation) + throw new InvalidOperationException($"Event does not store the correct data type (expected {nameof(EventDataType.RateLevelRelation)})"); + + Debug.Assert(e.StoredObjectId != null); + + return this.RateLevelRelations + .FirstOrDefault(l => l.RateLevelRelationId == e.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..9484314e --- /dev/null +++ b/Refresh.GameServer/Database/Activity/GameDatabaseContext.ActivityWrite.cs @@ -0,0 +1,226 @@ +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 LevelUpload event from a , and adds it to the event list. + /// + public Event CreateLevelUploadEvent(GameUser userFrom, GameLevel level) + { + Event e = new() + { + EventType = EventType.LevelUpload, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } + + /// + /// 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.LevelFavourite, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } + + /// + /// 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.LevelUnfavourite, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } + + /// + /// 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.UserFavourite, + StoredDataType = EventDataType.User, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = user.UserId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } + + /// + /// 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.UserUnfavourite, + StoredDataType = EventDataType.User, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = user.UserId, + }; + + 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. + /// + public Event CreateLevelTagEvent(GameUser userFrom, GameLevel level) + { + Event e = new() + { + EventType = EventType.LevelTag, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } + + /// + /// 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.LevelTeamPick, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } + + /// + /// 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.LevelRate, + StoredDataType = EventDataType.RateLevelRelation, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = relation.RateLevelRelationId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } + + /// + /// 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.LevelReview, + StoredDataType = EventDataType.Level, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredSequentialId = level.LevelId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } + + /// + /// 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.LevelScore, + StoredDataType = EventDataType.Score, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = score.ScoreId, + }; + + this.Write(() => this.Events.Add(e)); + return e; + } + + /// + /// 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.UserFirstLogin, + StoredDataType = EventDataType.User, + Timestamp = this._time.TimestampMilliseconds, + User = userFrom, + StoredObjectId = user.UserId, + }; + + 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 c9ce7162..cebf37e7 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) { @@ -30,37 +30,58 @@ public partial class GameDatabaseContext // AssetConfiguration return null; } + + public IEnumerable GetAssetDependencies(GameAsset asset) + => this.AssetDependencyRelations.Where(a => a.Dependent == asset.AssetHash) + .AsEnumerable() + .Select(a => a.Dependency); + + public void AddOrOverwriteAssetDependencyRelations(string dependent, IEnumerable dependencies) + { + this.Write(() => + { + // delete all existing relations. ensures duplicates won't exist when reprocessing + this.AssetDependencyRelations.RemoveRange(this.AssetDependencyRelations.Where(a => a.Dependent == dependent)); + + foreach (string dependency in dependencies) + this.AssetDependencyRelations.Add(new AssetDependencyRelation + { + Dependent = dependent, + Dependency = dependency, + }); + }); + } public IEnumerable GetAssetsByType(GameAssetType type) => - this._realm.All() + this.GameAssets .Where(a => a._AssetType == (int)type); public void AddAssetToDatabase(GameAsset asset) => - this._realm.Write(() => + this.Write(() => { - this._realm.Add(asset); + this.GameAssets.Add(asset); }); public void AddOrUpdateAssetsInDatabase(IEnumerable assets) => - this._realm.Write(() => + this.Write(() => { - this._realm.Add(assets, true); + this.GameAssets.AddRange(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..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() @@ -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..78a13f04 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Contests.cs @@ -16,37 +16,37 @@ 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); + this.GameContests.Add(contest); }); } public void DeleteContest(GameContest contest) { - this._realm.Write(() => + this.Write(() => { - this._realm.Remove(contest); + this.GameContests.Remove(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(); @@ -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 48299653..7b95d6c9 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; @@ -25,12 +26,12 @@ public GameSubmittedScore SubmitScore(SerializedScore score, GameUser user, Game Platform = platform, }; - this._realm.Write(() => + this.Write(() => { - this._realm.Add(newScore); + this.GameSubmittedScores.Add(newScore); }); - this.CreateSubmittedScoreCreateEvent(user, newScore); + this.CreateLevelScoreEvent(user, newScore); #region Notifications @@ -63,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(); @@ -80,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(); @@ -99,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] @@ -107,6 +108,41 @@ 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.Events + .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); + + this.Write(() => + { + this.Events.RemoveRange(scoreEvents); + this.GameSubmittedScores.Remove(score); + }); + } + + public void DeleteScoresSetByUser(GameUser user) + { + 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. + // I can't wait for Postgres. + .AsEnumerable() + .Where(s => s.Players.Contains(user)); + + this.Write(() => + { + foreach (GameSubmittedScore score in scores) + { + IQueryable scoreEvents = this.Events + .Where(e => e._StoredDataType == (int)EventDataType.Score && e.StoredObjectId == score.ScoreId); + + this.Events.RemoveRange(scoreEvents); + this.GameSubmittedScores.Remove(score); + } + }); } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs index 7a7d9df0..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; @@ -33,7 +32,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; @@ -42,7 +41,6 @@ public GameLevel GetStoryLevelById(int id) { Title = $"Story level #{id}", Publisher = null, - Location = GameLocation.Zero, Source = GameLevelSource.Story, StoryId = id, }; @@ -82,7 +80,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) @@ -97,7 +95,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) @@ -121,54 +119,54 @@ public GameLevel UpdateLevel(ApiEditLevelRequest body, GameLevel level) public void DeleteLevel(GameLevel level) { - this._realm.Write(() => + 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); + this.Events.RemoveRange(levelEvents); #region This is so terrible it needs to be hidden away - IQueryable favouriteLevelRelations = this._realm.All().Where(r => r.Level == level); - this._realm.RemoveRange(favouriteLevelRelations); + IQueryable favouriteLevelRelations = this.FavouriteLevelRelations.Where(r => r.Level == level); + this.FavouriteLevelRelations.RemoveRange(favouriteLevelRelations); - IQueryable playLevelRelations = this._realm.All().Where(r => r.Level == level); - this._realm.RemoveRange(playLevelRelations); + IQueryable playLevelRelations = this.PlayLevelRelations.Where(r => r.Level == level); + this.PlayLevelRelations.RemoveRange(playLevelRelations); - IQueryable queueLevelRelations = this._realm.All().Where(r => r.Level == level); - this._realm.RemoveRange(queueLevelRelations); + IQueryable queueLevelRelations = this.QueueLevelRelations.Where(r => r.Level == level); + this.QueueLevelRelations.RemoveRange(queueLevelRelations); - IQueryable rateLevelRelations = this._realm.All().Where(r => r.Level == level); - this._realm.RemoveRange(rateLevelRelations); + IQueryable rateLevelRelations = this.RateLevelRelations.Where(r => r.Level == level); + this.RateLevelRelations.RemoveRange(rateLevelRelations); - IQueryable uniquePlayLevelRelations = this._realm.All().Where(r => r.Level == level); - this._realm.RemoveRange(uniquePlayLevelRelations); + IQueryable uniquePlayLevelRelations = this.UniquePlayLevelRelations.Where(r => r.Level == level); + this.UniquePlayLevelRelations.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); + this.Events.RemoveRange(scoreEvents); } - this._realm.RemoveRange(scores); + this.GameSubmittedScores.RemoveRange(scores); #endregion }); //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); + this.GameLevels.Remove(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) @@ -192,11 +190,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) => @@ -219,7 +217,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() @@ -238,7 +236,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() @@ -257,7 +255,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() @@ -275,7 +273,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() @@ -300,7 +298,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); @@ -360,21 +358,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() - .Count(r => r._GameVersion == (int)game); + => this.GameLevels + .Count(r => r.Publisher == user && 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) @@ -392,13 +390,16 @@ 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._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) { - this._realm.Write(() => + this.Write(() => { level.TeamPicked = status; }); @@ -409,7 +410,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..80f8caff 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Notifications.cs @@ -21,9 +21,9 @@ public void AddNotification(string title, string text, GameUser user, string? ic CreatedAt = this._time.Now, }; - this._realm.Write(() => + this.Write(() => { - this._realm.Add(notification); + this.GameNotifications.Add(notification); }); } @@ -44,38 +44,37 @@ 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) - => this._realm - .All() + => this.GameNotifications .FirstOrDefault(n => n.User == user && n.NotificationId == id); public void DeleteNotificationsByUser(GameUser user) { - this._realm.Write(() => + this.Write(() => { - this._realm.RemoveRange(this._realm.All().Where(n => n.User == user)); + this.GameNotifications.RemoveRange(this.GameNotifications.Where(n => n.User == user)); }); } public void DeleteNotification(GameNotification notification) { - this._realm.Write(() => + this.Write(() => { - this._realm.Remove(notification); + this.GameNotifications.Remove(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) { @@ -87,9 +86,9 @@ public GameAnnouncement AddAnnouncement(string title, string text) CreatedAt = this._time.Now, }; - this._realm.Write(() => + this.Write(() => { - this._realm.Add(announcement); + this.GameAnnouncements.Add(announcement); }); return announcement; @@ -97,9 +96,9 @@ public GameAnnouncement AddAnnouncement(string title, string text) public void DeleteAnnouncement(GameAnnouncement announcement) { - this._realm.Write(() => + 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 ed4f66cb..5008c71e 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs @@ -54,36 +54,36 @@ public void UploadPhoto(SerializedPhoto photo, GameUser publisher) public void RemovePhoto(GamePhoto photo) { - this._realm.Write(() => + this.Write(() => { - this._realm.Remove(photo); + this.GamePhotos.Remove(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 b421220a..7b46c964 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs @@ -34,9 +34,9 @@ public GameUser CreateUser(string username, string emailAddress, bool skipChecks JoinDate = this._time.Now, }; - this._realm.Write(() => + this.Write(() => { - this._realm.Add(user); + this.GameUsers.Add(user); }); return user; } @@ -45,9 +45,9 @@ public GameUser CreateUserFromQueuedRegistration(QueuedRegistration registration { QueuedRegistration cloned = (QueuedRegistration)registration.Clone(); - this._realm.Write(() => + this.Write(() => { - this._realm.Remove(registration); + this.QueuedRegistrations.Remove(registration); }); GameUser user = this.CreateUser(cloned.Username, cloned.EmailAddress); @@ -55,7 +55,7 @@ public GameUser CreateUserFromQueuedRegistration(QueuedRegistration registration if (platform != null) { - this._realm.Write(() => + this.Write(() => { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (platform) @@ -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) @@ -107,55 +107,49 @@ public void AddRegistrationToQueue(string username, string emailAddress, string ExpiryDate = this._time.Now + TimeSpan.FromHours(1), }; - this._realm.Write(() => + this.Write(() => { - this._realm.Add(registration); + this.QueuedRegistrations.Add(registration); }); } public void RemoveRegistrationFromQueue(QueuedRegistration registration) { - this._realm.Write(() => + this.Write(() => { - this._realm.Remove(registration); - }); - } - - public void RemoveAllRegistrationsFromQueue() - { - this._realm.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) - => 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._realm.Write(() => + this.Write(() => { user.EmailAddressVerified = true; - this._realm.RemoveRange(this._realm.All() + this.EmailVerificationCodes.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; @@ -186,9 +180,9 @@ public EmailVerificationCode CreateEmailVerificationCode(GameUser user) ExpiryDate = this._time.Now + TimeSpan.FromDays(1), }; - this._realm.Write(() => + this.Write(() => { - this._realm.Add(verificationCode); + this.EmailVerificationCodes.Add(verificationCode); }); return verificationCode; @@ -196,20 +190,20 @@ public EmailVerificationCode CreateEmailVerificationCode(GameUser user) public void RemoveEmailVerificationCode(EmailVerificationCode code) { - this._realm.Write(() => + this.Write(() => { - this._realm.Remove(code); + this.EmailVerificationCodes.Remove(code); }); } public bool DisallowUser(string username) { - if (this._realm.Find(username) != null) + if (this.DisallowedUsers.FirstOrDefault(u => u.Username == username) != null) return false; - this._realm.Write(() => + this.Write(() => { - this._realm.Add(new DisallowedUser + this.DisallowedUsers.Add(new DisallowedUser { Username = username, }); @@ -220,13 +214,13 @@ 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; - this._realm.Write(() => + this.Write(() => { - this._realm.Remove(disallowedUser); + this.DisallowedUsers.Remove(disallowedUser); }); return true; @@ -234,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/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index b8c365ac..de49a391 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) @@ -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.FavouriteLevelRelations.Add(relation)); this.CreateLevelFavouriteEvent(user, level); @@ -48,17 +48,17 @@ 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; - this._realm.Write(() => this._realm.Remove(relation)); + this.Write(() => this.FavouriteLevelRelations.Remove(relation)); return true; } - public int GetFavouriteCountForLevel(GameLevel level) => this._realm.All() + public int GetFavouriteCountForLevel(GameLevel level) => this.FavouriteLevelRelations .Count(r => r.Level == level); #endregion @@ -66,7 +66,7 @@ public int GetFavouriteCountForLevel(GameLevel level) => this._realm.All this._realm.All() + private bool IsUserFavouritedByUser(GameUser userToFavourite, GameUser userFavouriting) => this.FavouriteUserRelations .FirstOrDefault(r => r.UserToFavourite == userToFavourite && r.UserFavouriting == userFavouriting) != null; [Pure] @@ -82,7 +82,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) @@ -90,11 +90,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) @@ -107,7 +107,7 @@ public bool FavouriteUser(GameUser userToFavourite, GameUser userFavouriting) UserFavouriting = userFavouriting, }; - this._realm.Write(() => this._realm.Add(relation)); + this.Write(() => this.FavouriteUserRelations.Add(relation)); this.CreateUserFavouriteEvent(userFavouriting, userToFavourite); @@ -126,12 +126,12 @@ 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; - this._realm.Write(() => this._realm.Remove(relation)); + this.Write(() => this.FavouriteUserRelations.Remove(relation)); return true; } @@ -140,12 +140,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) @@ -154,7 +154,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) @@ -166,26 +166,26 @@ public bool QueueLevel(GameLevel level, GameUser user) Level = level, User = user, }; - this._realm.Write(() => this._realm.Add(relation)); + this.Write(() => this.QueueLevelRelations.Add(relation)); return true; } 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; - this._realm.Write(() => this._realm.Remove(relation)); + this.Write(() => this.QueueLevelRelations.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.QueueLevelRelations.RemoveRange(this.QueueLevelRelations.Where(r => r.User == user))); } #endregion @@ -193,7 +193,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. @@ -224,11 +224,11 @@ public bool RateLevel(GameLevel level, GameUser user, RatingType type) Timestamp = this._time.Now, }; - this._realm.Write(() => this._realm.Add(rating)); + this.Write(() => this.RateLevelRelations.Add(rating)); return true; } - this._realm.Write(() => + this.Write(() => { rating.RatingType = type; rating.Timestamp = this._time.Now; @@ -237,7 +237,7 @@ public bool RateLevel(GameLevel level, GameUser user, RatingType type) } public int GetTotalRatingsForLevel(GameLevel level, RatingType type) => - this._realm.All().Count(r => r.Level == level && r._RatingType == (int)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. @@ -250,12 +250,12 @@ 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) { level.Reviews.Remove(reviewToDelete); - this._realm.Remove(reviewToDelete); + this.GameReviews.Remove(reviewToDelete); } }); } @@ -265,16 +265,16 @@ 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) { - this._realm.Remove(review); + this.GameReviews.Remove(review); } public GameReview? GetReviewByLevelAndUser(GameLevel level, GameUser user) @@ -284,12 +284,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 @@ -305,34 +305,37 @@ 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._realm.Write(() => + 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) { - 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); + this.CreateLevelPlayEvent(user, level); + } + }); } public bool HasUserPlayedLevel(GameLevel level, GameUser user) => - this._realm.All() + this.UniquePlayLevelRelations .FirstOrDefault(r => r.Level == level && r.User == user) != null; public IEnumerable GetAllPlaysForLevel(GameLevel level) => - this._realm.All().Where(r => r.Level == level); + this.PlayLevelRelations.Where(r => r.Level == level); public IEnumerable GetAllPlaysForLevelByUser(GameLevel level, GameUser user) => - this._realm.All().Where(r => r.Level == level && r.User == user); + this.PlayLevelRelations.Where(r => r.Level == level && r.User == user); public int GetTotalPlaysForLevel(GameLevel level) => this.GetAllPlaysForLevel(level).Sum(playLevelRelation => playLevelRelation.Count); @@ -341,14 +344,14 @@ public int GetTotalPlaysForLevelByUser(GameLevel level, GameUser user) => this.GetAllPlaysForLevelByUser(level, user).Sum(playLevelRelation => playLevelRelation.Count); public int GetUniquePlaysForLevel(GameLevel level) => - this._realm.All().Count(r => r.Level == level); - + this.UniquePlayLevelRelations.Count(r => r.Level == level); + #endregion #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. @@ -362,7 +365,7 @@ public int GetUniquePlaysForLevel(GameLevel level) => => 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) { @@ -381,14 +384,14 @@ public bool RateComment(GameUser user, GameComment comment, RatingType ratingTyp RatingType = ratingType, }; - this._realm.Write(() => + this.Write(() => { - this._realm.Add(relation); + this.CommentRelations.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..a35a0c5b 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs @@ -6,13 +6,13 @@ 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(); - this._realm.Write(() => + this.Write(() => { - this._realm.Add(statistics); + this.RequestStatistics.Add(statistics); }); return 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 47a52335..41d30e17 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs @@ -56,10 +56,10 @@ 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); + this.Tokens.Add(token); }); return token; @@ -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; @@ -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; @@ -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); @@ -112,32 +112,32 @@ public bool RevokeTokenByTokenData(string? tokenData, TokenType type) public void RevokeToken(Token token) { - this._realm.Write(() => + this.Write(() => { - this._realm.Remove(token); + this.Tokens.Remove(token); }); } public void RevokeAllTokensForUser(GameUser user) { - this._realm.Write(() => + this.Write(() => { - this._realm.RemoveRange(this._realm.All().Where(t => t.User == user)); + this.Tokens.RemoveRange(this.Tokens.Where(t => t.User == 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)); + this.Tokens.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) { @@ -148,29 +148,33 @@ public void AddIpVerificationRequest(GameUser user, string ipAddress) CreatedAt = this._time.Now, }; - this._realm.Write(() => + this.Write(() => { - this._realm.Add(request); + this.GameIpVerificationRequests.Add(request); }); } public void SetApprovedIp(GameUser user, string ipAddress) { - this._realm.Write(() => + this.Write(() => { user.CurrentVerifiedIp = ipAddress; - this._realm.RemoveRange(this._realm.All().Where(r => r.User == user)); + this.GameIpVerificationRequests.RemoveRange(this.GameIpVerificationRequests.Where(r => r.User == user)); }); } public void DenyIpVerificationRequest(GameUser user, string ipAddress) { - this._realm.Write(() => + IEnumerable requests = this.GameIpVerificationRequests.Where(r => r.IpAddress == ipAddress && r.User == user); + this.Write(() => { - this._realm.RemoveRange(this._realm.All().Where(r => r.User == user && r.IpAddress == ipAddress)); + foreach (GameIpVerificationRequest request in requests) + { + this.GameIpVerificationRequests.Remove(request); + } }); } public DatabaseList GetIpVerificationRequestsForUser(GameUser user, int count, int skip) - => new(this._realm.All().Where(r => r.User == user), skip, count); + => new(this.GameIpVerificationRequests.Where(r => r.User == user), 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 80358c66..a9581f74 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,16 +30,15 @@ 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, }; 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] @@ -49,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] @@ -57,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] @@ -66,21 +64,24 @@ 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) { - this._realm.Write(() => + this.Write(() => { if (data.Description != null) 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 @@ -143,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) { @@ -166,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; @@ -185,18 +183,18 @@ 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 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.GameUsers.Count(u => u.LastLoginDate > timeFrame); } 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); @@ -208,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) { @@ -222,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; @@ -255,14 +253,14 @@ 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) { string oldUsername = user.Username; - this._realm.Write(() => + this.Write(() => { user.Username = newUsername; }); @@ -283,10 +281,11 @@ public void DeleteUser(GameUser user) this.RevokeAllTokensForUser(user); this.DeleteNotificationsByUser(user); - this._realm.Write(() => + this.Write(() => { user.Pins = new UserPins(); - user.Location = new GameLocation(); + user.LocationX = 0; + user.LocationY = 0; user.Description = deletedReason; user.EmailAddress = null; user.PasswordBcrypt = "deleted"; @@ -306,22 +305,33 @@ 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.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._realm.All().Where(l => l.Publisher == user)) + foreach (GameLevel level in this.GameLevels.Where(l => l.Publisher == user)) { level.Publisher = null; } }); } + public void FullyDeleteUser(GameUser user) + { + // do an initial cleanup of everything before deleting the row + this.DeleteUser(user); + + this.Write(() => + { + this.GameUsers.Remove(user); + }); + } + public void ResetUserPlanets(GameUser user) { - this._realm.Write(() => + this.Write(() => { user.Lbp2PlanetsHash = "0"; user.Lbp3PlanetsHash = "0"; @@ -331,23 +341,15 @@ public void ResetUserPlanets(GameUser user) public void SetUnescapeXmlSequences(GameUser user, bool value) { - this._realm.Write(() => + this.Write(() => { user.UnescapeXmlSequences = value; }); } - - public void SetUserGriefReportRedirection(GameUser user, bool value) - { - this._realm.Write(() => - { - user.RedirectGriefReportsToPhotos = value; - }); - } public void ClearForceMatch(GameUser user) { - this._realm.Write(() => + this.Write(() => { user.ForceMatch = null; }); @@ -355,7 +357,7 @@ public void ClearForceMatch(GameUser user) public void SetForceMatch(GameUser user, GameUser target) { - this._realm.Write(() => + this.Write(() => { user.ForceMatch = target.ForceMatch; }); @@ -363,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; }); @@ -371,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; }); @@ -379,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; }); @@ -387,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 ef494e48..184ec608 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.cs @@ -1,8 +1,22 @@ using System.Diagnostics.CodeAnalysis; +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; @@ -12,6 +26,33 @@ public partial class GameDatabaseContext : RealmDatabaseContext private static readonly object IdLock = new(); private readonly IDateTimeProvider _time; + + 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 GameIpVerificationRequests => 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); public GameDatabaseContext(IDateTimeProvider time) { @@ -22,7 +63,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) @@ -38,7 +79,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; } @@ -48,7 +89,7 @@ private void AddSequentialObject(T obj, IList? list, Action? writtenCallba { lock (IdLock) { - this._realm.Write(() => + this.Write(() => { int newId = this.GetOrCreateSequentialId() + 1; @@ -63,7 +104,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 +117,23 @@ 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) + { + // 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); + } + + private void RemoveAll() where T : IRealmObject + { + this._realm.RemoveAll(); + } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index bc0c0e85..1273f4ef 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -41,7 +41,6 @@ protected GameDatabaseProvider(IDateTimeProvider time) protected override List SchemaTypes { get; } = new() { typeof(GameUser), - typeof(GameLocation), typeof(UserPins), typeof(Token), typeof(GameLevel), @@ -67,6 +66,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) typeof(RequestStatistics), typeof(SequentialIdStorage), typeof(GameContest), + typeof(AssetDependencyRelation), typeof(GameReview), typeof(DisallowedUser), }; @@ -103,7 +103,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 @@ -157,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(); @@ -177,6 +175,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"); @@ -237,6 +243,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 @@ -269,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! @@ -344,6 +363,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/Database/RealmDbSet.cs b/Refresh.GameServer/Database/RealmDbSet.cs new file mode 100644 index 00000000..5e8a9021 --- /dev/null +++ b/Refresh.GameServer/Database/RealmDbSet.cs @@ -0,0 +1,46 @@ +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); + } + + // 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); + } + + public void Remove(T obj) + { + this._realm.Remove(obj); + } + + public void RemoveRange(IQueryable objs) + { + this._realm.RemoveRange(objs); + } +} \ 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/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/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 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 43b73221..dd2a021d 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,18 +63,18 @@ public class ApiGameLevelResponse : IApiResponse, IDataConvertableFrom 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/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)] diff --git a/Refresh.GameServer/Endpoints/ApiV3/ResourceApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/ResourceApiEndpoints.cs index b558dc52..8ee1fbb0 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/ResourceApiEndpoints.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/ResourceApiEndpoints.cs @@ -185,7 +185,7 @@ public ApiResponse 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/DataTypes/Request/GameLevelRequest.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Request/GameLevelRequest.cs index e274d250..4a85234d 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Request/GameLevelRequest.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Request/GameLevelRequest.cs @@ -53,7 +53,8 @@ public GameLevel ToGameLevel(GameUser publisher) => 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 16776ca8..d08c78c0 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs @@ -149,7 +149,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/Endpoints/Game/Levels/PublishEndpoints.cs b/Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs index 19b1c165..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) + private static bool VerifyLevel(GameLevelRequest body, DataContext dataContext, GuidCheckerService guidChecker) { if (body.Title.Length > 256) { @@ -49,34 +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 = 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) + { + 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)) 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(), @@ -93,9 +110,9 @@ 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, dataContext, guidChecker)) return BadRequest; + GameLevel level = body.ToGameLevel(user); level.GameVersion = token.TokenGame; @@ -103,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 @@ -119,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); @@ -131,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); } diff --git a/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs b/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs index 99bbbc56..35ba1131 100644 --- a/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs @@ -42,48 +42,21 @@ public Response UploadReport(RequestContext context, GameDatabaseContext databas return BadRequest; } - if (user.RedirectGriefReportsToPhotos) - { - 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; - - database.UploadPhoto(new SerializedPhoto + List subjects = new(); + if (body.Players != null) + subjects.AddRange(body.Players.Select(player => new SerializedPhotoSubject { - 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(), - }, - }, - PhotoSubjects = subjects, - }, user); - - return OK; - } + 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; //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 @@ -94,7 +67,29 @@ public Response UploadReport(RequestContext context, GameDatabaseContext databas 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 + { + 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(), + }, + }, + PhotoSubjects = subjects, + }, user); + return OK; } } \ No newline at end of file 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..eb0d5d3a 100644 --- a/Refresh.GameServer/Refresh.GameServer.csproj +++ b/Refresh.GameServer/Refresh.GameServer.csproj @@ -52,12 +52,12 @@ - - - - - - + + + + + + @@ -67,21 +67,17 @@ - + - + - - 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.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index a4647955..768bc8d7 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -237,6 +237,18 @@ public void RenameUser(GameUser user, string newUsername) using GameDatabaseContext context = this.GetContext(); 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(); + context.FullyDeleteUser(user); + } public override void Dispose() { 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/Activity/ActivityPage.cs b/Refresh.GameServer/Types/Activity/ActivityPage.cs index ac2b85fd..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.SubmittedScore_Create)) + 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 f755b3a1..9dab9594 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 +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, - // [XmlEnum("rate_level")] - // Level_Rate = 6, + LevelPlay = 5, + [XmlEnum("rate_level")] + LevelStarRate = 6, // as opposed to dpad rating. unused since we convert stars to dpad [XmlEnum("tag_level")] - Level_Tag = 7, - // [XmlEnum("comment_on_level")] - // PostLevelComment = 8, - // [XmlEnum("delete_level_comment")] - // DeleteLevelComment = 9, - // [XmlEnum("upload_photo")] - // Photo_Upload = 10, - // [XmlEnum("unpublish_level")] - // Level_Unpublish = 11, - // [XmlEnum("news_post")] - // News_Post = 12, + 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("mm_pick_level")] - Level_TeamPick = 13, + LevelTeamPick = 13, [XmlEnum("dpad_rate_level")] - RateLevelRelation_Create = 14, + LevelRate = 14, [XmlEnum("review_level")] - Level_Review = 15, - // [XmlEnum("comment_on_user")] - // PostUserComment = 16, - // [XmlEnum("create_playlist")] - // Playlist_Create = 17, - // [XmlEnum("heart_playlist")] - // Playlist_Favourite = 18, - // [XmlEnum("add_level_to_playlist")] - // Playlist_AddLevel = 19, + 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("score")] - SubmittedScore_Create = 20, // FIXME: this name is shit + LevelScore = 20, // Custom events [XmlEnum("firstlogin")] - User_FirstLogin = 127, + UserFirstLogin = 127, } \ No newline at end of file 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; } diff --git a/Refresh.GameServer/Types/GameLocation.cs b/Refresh.GameServer/Types/GameLocation.cs index cbb70e29..24eb6ef2 100644 --- a/Refresh.GameServer/Types/GameLocation.cs +++ b/Refresh.GameServer/Types/GameLocation.cs @@ -1,18 +1,25 @@ using System.Xml.Serialization; -using Realms; namespace Refresh.GameServer.Types; [XmlType("location")] -[JsonObject(MemberSerialization.OptIn)] -public partial class GameLocation : IEmbeddedObject +public class GameLocation { public static GameLocation Zero => new() { 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 0415726c..e31099c0 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/Report/GameReport.cs b/Refresh.GameServer/Types/Report/GameReport.cs index 788b2d4b..ad53a22f 100644 --- a/Refresh.GameServer/Types/Report/GameReport.cs +++ b/Refresh.GameServer/Types/Report/GameReport.cs @@ -7,9 +7,10 @@ namespace Refresh.GameServer.Types.Report; #nullable disable [XmlRoot("griefReport")] -public partial class GameReport +public class GameReport { - [XmlIgnore] private List InternalInfoBubble { get; } = []; + [XmlIgnore] + private List InternalInfoBubble { get; } = []; [Ignored] [XmlElement("infoBubble")] @@ -59,8 +60,9 @@ public InfoBubble[] InfoBubble [XmlElement("jpegHash")] public string JpegHash { get; set; } - - [XmlIgnore] private List InternalPlayers { get; } = []; + + [XmlIgnore] + private List InternalPlayers { get; } = []; [Ignored] [XmlElement("player")] @@ -83,4 +85,11 @@ public Player[] Players [XmlElement("screenElements")] public ScreenElements ScreenElements { get; set; } + + [PrimaryKey] + public int SequentialId + { + get; + set; + } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Report/InfoBubble.cs b/Refresh.GameServer/Types/Report/InfoBubble.cs index e2e94d08..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 +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 e91d675c..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 +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 c7b4031b..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 +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 0f92f452..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 +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 a9159b1b..aed5217c 100644 --- a/Refresh.GameServer/Types/Report/ScreenElements.cs +++ b/Refresh.GameServer/Types/Report/ScreenElements.cs @@ -6,9 +6,10 @@ namespace Refresh.GameServer.Types.Report; #nullable disable [XmlRoot("screenElements")] -public partial class ScreenElements -{ - [XmlIgnore] private List InternalSlot { get; } = []; +public class ScreenElements +{ + [XmlIgnore] + private List InternalSlot { get; } = []; [XmlElement("slot")] public Slot[] Slot @@ -27,8 +28,9 @@ public Slot[] Slot this.InternalSlot.Add(slot); } } - - [XmlIgnore] private List InternalPlayer = []; + + [XmlIgnore] + private List InternalPlayer { get; } = []; [XmlElement("player")] public Player[] Player diff --git a/Refresh.GameServer/Types/Report/ScreenRect.cs b/Refresh.GameServer/Types/Report/ScreenRect.cs index d7e49418..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 +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 a9c97e82..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 +public class Slot { [XmlElement("id")] public int Id { get; set; } diff --git a/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs index 6ee88fd8..2be8b7d3 100644 --- a/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs +++ b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs @@ -70,7 +70,7 @@ public class SerializedGameReview : IDataConvertableFrom (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/Refresh.GameServer/Workers/CoolLevelsWorker.cs b/Refresh.GameServer/Workers/CoolLevelsWorker.cs index 75ab1c4f..ff0a1bcf 100644 --- a/Refresh.GameServer/Workers/CoolLevelsWorker.cs +++ b/Refresh.GameServer/Workers/CoolLevelsWorker.cs @@ -46,10 +46,10 @@ public void DoWork(Logger logger, IDataStore dataStore, GameDatabaseContext data Log(logger, LogLevel.Trace, "Calculating score for '{0}' ({1})", level.Title, level.LevelId); float decayMultiplier = CalculateLevelDecayMultiplier(logger, now, level); - // Calculate positive & negative score separately, so we don't run into issues with + // 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, database); - float negativeScore = CalculateNegativeScore(logger, level, database); + 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; diff --git a/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs b/Refresh.GameServer/Workers/DiscordIntegrationWorker.cs index b788785c..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.RateLevelRelation_Create => null, - EventType.Level_Review => null, - EventType.SubmittedScore_Create => $"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, }; diff --git a/Refresh.sln b/Refresh.sln index 971f2740..fbde6632 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}" @@ -29,12 +27,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 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, }; 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() { diff --git a/RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs b/RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs index e3b1e6d7..25f7770f 100644 --- a/RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs +++ b/RefreshTests.GameServer/Tests/Levels/ScoreLeaderboardTests.cs @@ -415,8 +415,6 @@ 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]; 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 diff --git a/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs b/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs index 31d3b8da..a4f78224 100644 --- a/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs +++ b/RefreshTests.GameServer/Tests/Reporting/ReportingEndpointsTests.cs @@ -12,6 +12,9 @@ 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() { @@ -20,6 +23,10 @@ public void UploadReport() 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() { @@ -42,7 +49,7 @@ public void UploadReport() LevelId = level.LevelId, Description = "This is a description, sent by LBP3", GriefStateHash = null, - JpegHash = null, + JpegHash = TEST_ASSET_HASH, Players = new Player[] { new() @@ -76,6 +83,12 @@ public void UploadReport() 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] @@ -85,14 +98,24 @@ public void CanUploadReportWithBadLevel() 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)] @@ -105,30 +128,40 @@ public void CantUploadReportWithBadPlayerCount(bool psp) 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(), }, }, }; @@ -147,9 +180,14 @@ public void CantUploadReportWithBadScreenElementPlayerCount(bool psp) 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[] @@ -157,22 +195,27 @@ public void CantUploadReportWithBadScreenElementPlayerCount(bool psp) 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(), }, }, }, @@ -181,9 +224,6 @@ public void CantUploadReportWithBadScreenElementPlayerCount(bool psp) 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)] @@ -195,10 +235,6 @@ 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); @@ -246,10 +282,6 @@ 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); @@ -292,10 +324,6 @@ 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);