From 90e43da27fb505b74a88933fd68268aa837f4a1d Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 18 Jun 2024 17:19:40 -0400 Subject: [PATCH 1/5] Remove serialization stuff from GameComment --- .../Endpoints/Game/CommentEndpoints.cs | 10 +---- .../Types/Comments/GameComment.cs | 34 ++------------ .../Types/Comments/SerializedComment.cs | 45 +++++++++++++++++++ .../Types/Lists/SerializedCommentList.cs | 4 +- .../Tests/Comments/CommentTests.cs | 8 ++-- .../Tests/Comments/LevelCommentTests.cs | 4 +- .../Tests/Comments/UserCommentTests.cs | 4 +- 7 files changed, 61 insertions(+), 48 deletions(-) create mode 100644 Refresh.GameServer/Types/Comments/SerializedComment.cs diff --git a/Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs b/Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs index 5f0b9923..5401d108 100644 --- a/Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs @@ -43,10 +43,7 @@ public Response PostProfileComment(RequestContext context, GameDatabaseContext d (int skip, int count) = context.GetPageData(); - List comments = database.GetProfileComments(profile, count, skip).ToList(); - foreach (GameComment comment in comments) comment.PrepareForSerialization(user, dataContext); - - return new SerializedCommentList(comments); + return new SerializedCommentList(SerializedComment.FromOldList(database.GetProfileComments(profile, count, skip), dataContext)); } [GameEndpoint("deleteUserComment/{username}", HttpMethods.Post)] @@ -98,10 +95,7 @@ public Response PostLevelComment(RequestContext context, GameDatabaseContext dat (int skip, int count) = context.GetPageData(); - List comments = database.GetLevelComments(level, count, skip).ToList(); - foreach(GameComment comment in comments) comment.PrepareForSerialization(user, dataContext); - - return new SerializedCommentList(comments); + return new SerializedCommentList(SerializedComment.FromOldList(database.GetLevelComments(level, count, skip), dataContext)); } [GameEndpoint("deleteComment/{slotType}/{id}", HttpMethods.Post)] diff --git a/Refresh.GameServer/Types/Comments/GameComment.cs b/Refresh.GameServer/Types/Comments/GameComment.cs index 0dd6ba68..e2f83886 100644 --- a/Refresh.GameServer/Types/Comments/GameComment.cs +++ b/Refresh.GameServer/Types/Comments/GameComment.cs @@ -1,45 +1,19 @@ -using System.Xml.Serialization; using Realms; using Refresh.GameServer.Database; -using Refresh.GameServer.Types.Data; -using Refresh.GameServer.Types.Reviews; using Refresh.GameServer.Types.UserData; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. namespace Refresh.GameServer.Types.Comments; -[XmlRoot("comment")] -[XmlType("comment")] public partial class GameComment : IRealmObject, ISequentialId { - [PrimaryKey] [XmlElement("id")] public int SequentialId { get; set; } + [PrimaryKey] public int SequentialId { get; set; } - [XmlIgnore] public GameUser Author { get; set; } = null!; - [XmlElement("message")] public string Content { get; set; } = string.Empty; + public GameUser Author { get; set; } = null!; + public string Content { get; set; } = string.Empty; /// /// Timestamp in Unix milliseconds /// - [XmlElement("timestamp")] public long Timestamp { get; set; } - - #region LBP Serialization Quirks - - // Comments are special; they do not include icons in the npHandle - [XmlElement("npHandle")] [Ignored] public string? Handle { get; set; } - [XmlElement("thumbsup")] [Ignored] public int? ThumbsUp { get; set; } - [XmlElement("thumbsdown")] [Ignored] public int? ThumbsDown { get; set; } - [XmlElement("yourthumb")] [Ignored] public int? YourThumb { get; set; } - - public void PrepareForSerialization(GameUser user, DataContext dataContext) - { - this.Handle = this.Author.Username; - - this.ThumbsUp = dataContext.Database.GetTotalRatingsForComment(this, RatingType.Yay); - this.ThumbsDown = dataContext.Database.GetTotalRatingsForComment(this, RatingType.Boo); - - this.YourThumb = (int?)dataContext.Database.GetRatingByUser(this, user) ?? 0; - } - - #endregion - + public long Timestamp { get; set; } } diff --git a/Refresh.GameServer/Types/Comments/SerializedComment.cs b/Refresh.GameServer/Types/Comments/SerializedComment.cs new file mode 100644 index 00000000..2c3a3574 --- /dev/null +++ b/Refresh.GameServer/Types/Comments/SerializedComment.cs @@ -0,0 +1,45 @@ +using System.Xml.Serialization; +using Refresh.GameServer.Endpoints.ApiV3.DataTypes; +using Refresh.GameServer.Types.Data; +using Refresh.GameServer.Types.Reviews; + +namespace Refresh.GameServer.Types.Comments; + +[XmlRoot("comment")] +[XmlType("comment")] +public class SerializedComment : IDataConvertableFrom +{ + [XmlElement("id")] public required int CommentId { get; set; } + + [XmlElement("message")] public required string Content { get; set; } = string.Empty; + + /// + /// Timestamp in Unix milliseconds + /// + [XmlElement("timestamp")] public required long Timestamp { get; set; } + + // Comments are special; they do not include icons in the npHandle + [XmlElement("npHandle")] public required string? Handle { get; set; } + + [XmlElement("thumbsup")] public required int? ThumbsUp { get; set; } + [XmlElement("thumbsdown")] public required int? ThumbsDown { get; set; } + [XmlElement("yourthumb")] public required int? YourThumb { get; set; } + + public static SerializedComment? FromOld(GameComment? old, DataContext dataContext) + { + if (old == null) return null; + + return new SerializedComment + { + CommentId = old.SequentialId, + Content = old.Content, + Timestamp = old.Timestamp, + Handle = old.Author.Username, + ThumbsUp = dataContext.Database.GetTotalRatingsForComment(old, RatingType.Yay), + ThumbsDown = dataContext.Database.GetTotalRatingsForComment(old, RatingType.Boo), + YourThumb = (int?)dataContext.Database.GetRatingByUser(old, dataContext.User!) ?? 0, + }; + } + + public static IEnumerable FromOldList(IEnumerable oldList, DataContext dataContext) => oldList.Select(old => FromOld(old, dataContext)).ToList()!; +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Lists/SerializedCommentList.cs b/Refresh.GameServer/Types/Lists/SerializedCommentList.cs index 0c11053c..12a5eace 100644 --- a/Refresh.GameServer/Types/Lists/SerializedCommentList.cs +++ b/Refresh.GameServer/Types/Lists/SerializedCommentList.cs @@ -9,11 +9,11 @@ public class SerializedCommentList { public SerializedCommentList() {} - public SerializedCommentList(IEnumerable comments) + public SerializedCommentList(IEnumerable comments) { this.Items = comments.ToList(); } [XmlElement("comment")] - public List Items { get; set; } = new(); + public List Items { get; set; } = []; } \ No newline at end of file diff --git a/RefreshTests.GameServer/Tests/Comments/CommentTests.cs b/RefreshTests.GameServer/Tests/Comments/CommentTests.cs index 72c45d7b..c693f6ae 100644 --- a/RefreshTests.GameServer/Tests/Comments/CommentTests.cs +++ b/RefreshTests.GameServer/Tests/Comments/CommentTests.cs @@ -24,7 +24,7 @@ public static void RateComment(TestContext context, GameUser user, GameComment c HttpResponseMessage response = client.GetAsync(getCommentsUrl).Result; SerializedCommentList userComments = response.Content.ReadAsXML(); - comment = userComments.Items.First(); + SerializedComment serializedComment = userComments.Items.First(); int expectedThumbsUp, expectedThumbsDown; @@ -48,9 +48,9 @@ public static void RateComment(TestContext context, GameUser user, GameComment c Assert.Multiple(() => { - Assert.That(comment.YourThumb, Is.EqualTo(ratingType.ToDPad())); - Assert.That(comment.ThumbsUp, Is.EqualTo(expectedThumbsUp)); - Assert.That(comment.ThumbsDown, Is.EqualTo(expectedThumbsDown)); + Assert.That(serializedComment.YourThumb, Is.EqualTo(ratingType.ToDPad())); + Assert.That(serializedComment.ThumbsUp, Is.EqualTo(expectedThumbsUp)); + Assert.That(serializedComment.ThumbsDown, Is.EqualTo(expectedThumbsDown)); }); } diff --git a/RefreshTests.GameServer/Tests/Comments/LevelCommentTests.cs b/RefreshTests.GameServer/Tests/Comments/LevelCommentTests.cs index 7c52b2c7..c2942e54 100644 --- a/RefreshTests.GameServer/Tests/Comments/LevelCommentTests.cs +++ b/RefreshTests.GameServer/Tests/Comments/LevelCommentTests.cs @@ -32,7 +32,7 @@ public void PostAndDeleteLevelComment() Assert.That(userComments.Items, Has.Count.EqualTo(1)); Assert.That(userComments.Items[0].Content, Is.EqualTo(comment.Content)); - response = client.PostAsync($"/lbp/deleteComment/user/{level.LevelId}?commentId={userComments.Items[0].SequentialId}", new ByteArrayContent(Array.Empty())).Result; + response = client.PostAsync($"/lbp/deleteComment/user/{level.LevelId}?commentId={userComments.Items[0].CommentId}", new ByteArrayContent(Array.Empty())).Result; Assert.That(response.StatusCode, Is.EqualTo(OK)); response = client.GetAsync($"/lbp/comments/user/{level.LevelId}").Result; @@ -152,7 +152,7 @@ public void CantDeleteAnotherUsersComment() Assert.That(userComments.Items, Has.Count.EqualTo(1)); Assert.That(userComments.Items[0].Content, Is.EqualTo(comment.Content)); - response = client2.PostAsync($"/lbp/deleteComment/user/{level.LevelId}?commentId={userComments.Items[0].SequentialId}", new ByteArrayContent(Array.Empty())).Result; + response = client2.PostAsync($"/lbp/deleteComment/user/{level.LevelId}?commentId={userComments.Items[0].CommentId}", new ByteArrayContent(Array.Empty())).Result; Assert.That(response.StatusCode, Is.EqualTo(Unauthorized)); } diff --git a/RefreshTests.GameServer/Tests/Comments/UserCommentTests.cs b/RefreshTests.GameServer/Tests/Comments/UserCommentTests.cs index 6b5cebd6..35f82188 100644 --- a/RefreshTests.GameServer/Tests/Comments/UserCommentTests.cs +++ b/RefreshTests.GameServer/Tests/Comments/UserCommentTests.cs @@ -31,7 +31,7 @@ public void PostAndDeleteUserComment() Assert.That(userComments.Items, Has.Count.EqualTo(1)); Assert.That(userComments.Items[0].Content, Is.EqualTo(comment.Content)); - response = client.PostAsync($"/lbp/deleteUserComment/{user2.Username}?commentId={userComments.Items[0].SequentialId}", new ByteArrayContent(Array.Empty())).Result; + response = client.PostAsync($"/lbp/deleteUserComment/{user2.Username}?commentId={userComments.Items[0].CommentId}", new ByteArrayContent(Array.Empty())).Result; Assert.That(response.StatusCode, Is.EqualTo(OK)); response = client.GetAsync($"/lbp/userComments/{user2.Username}").Result; @@ -148,7 +148,7 @@ public void CantDeleteAnotherUsersComment() Assert.That(userComments.Items, Has.Count.EqualTo(1)); Assert.That(userComments.Items[0].Content, Is.EqualTo(comment.Content)); - response = client2.PostAsync($"/lbp/deleteUserComment/{user2.Username}?commentId={userComments.Items[0].SequentialId}", new ByteArrayContent(Array.Empty())).Result; + response = client2.PostAsync($"/lbp/deleteUserComment/{user2.Username}?commentId={userComments.Items[0].CommentId}", new ByteArrayContent(Array.Empty())).Result; Assert.That(response.StatusCode, Is.EqualTo(Unauthorized)); } From 20226fd94e6b0cf62462c7d79ea8ef726c2a0dbc Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 15 Jul 2024 17:54:37 -0400 Subject: [PATCH 2/5] Interface for comments --- Refresh.GameServer/Types/Comments/GameComment.cs | 2 +- Refresh.GameServer/Types/Comments/IGameComment.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 Refresh.GameServer/Types/Comments/IGameComment.cs diff --git a/Refresh.GameServer/Types/Comments/GameComment.cs b/Refresh.GameServer/Types/Comments/GameComment.cs index e2f83886..8653ffa5 100644 --- a/Refresh.GameServer/Types/Comments/GameComment.cs +++ b/Refresh.GameServer/Types/Comments/GameComment.cs @@ -5,7 +5,7 @@ namespace Refresh.GameServer.Types.Comments; -public partial class GameComment : IRealmObject, ISequentialId +public partial class GameComment : IRealmObject, IGameComment, ISequentialId { [PrimaryKey] public int SequentialId { get; set; } diff --git a/Refresh.GameServer/Types/Comments/IGameComment.cs b/Refresh.GameServer/Types/Comments/IGameComment.cs new file mode 100644 index 00000000..e73a1c08 --- /dev/null +++ b/Refresh.GameServer/Types/Comments/IGameComment.cs @@ -0,0 +1,12 @@ +using Refresh.GameServer.Database; +using Refresh.GameServer.Types.UserData; + +namespace Refresh.GameServer.Types.Comments; + +public interface IGameComment : ISequentialId +{ + GameUser Author { get; set; } + string Content { get; set; } + + long Timestamp { get; set; } +} \ No newline at end of file From f0023e238e31a8511e08fcd3cefa8202280c9835 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 15 Jul 2024 19:07:06 -0400 Subject: [PATCH 3/5] Split level and profile commments into their own types --- .../Database/GameDatabaseContext.Comments.cs | 52 +++++++++++-------- .../Database/GameDatabaseContext.Relations.cs | 49 +++++++++++++---- .../Database/GameDatabaseContext.cs | 8 +-- .../Database/GameDatabaseProvider.cs | 13 +++-- .../Endpoints/Game/CommentEndpoints.cs | 33 ++++++++---- .../DataTypes/Response/GameLevelResponse.cs | 4 +- .../DataTypes/Response/GameUserResponse.cs | 2 +- .../Types/Comments/GameLevelComment.cs | 25 +++++++++ .../{GameComment.cs => GameProfileComment.cs} | 17 +++--- .../Types/Comments/IGameComment.cs | 15 +++++- .../Comments/Relations/ICommentRelation.cs | 16 ++++++ .../Relations/LevelCommentRelation.cs} | 10 ++-- .../Relations/ProfileCommentRelation.cs | 23 ++++++++ .../Types/Comments/SerializedComment.cs | 48 +++++++++++------ Refresh.GameServer/Types/Levels/GameLevel.cs | 2 - Refresh.GameServer/Types/UserData/GameUser.cs | 4 -- .../Tests/Comments/CommentTests.cs | 2 +- .../Tests/Comments/LevelCommentTests.cs | 30 +++++++---- .../Tests/Comments/UserCommentTests.cs | 28 ++++++---- .../Tests/Relations/CommentPublishTests.cs | 2 +- 20 files changed, 271 insertions(+), 112 deletions(-) create mode 100644 Refresh.GameServer/Types/Comments/GameLevelComment.cs rename Refresh.GameServer/Types/Comments/{GameComment.cs => GameProfileComment.cs} (58%) create mode 100644 Refresh.GameServer/Types/Comments/Relations/ICommentRelation.cs rename Refresh.GameServer/Types/{Relations/CommentRelation.cs => Comments/Relations/LevelCommentRelation.cs} (69%) create mode 100644 Refresh.GameServer/Types/Comments/Relations/ProfileCommentRelation.cs diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs b/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs index 1df42c9f..093129fc 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Comments.cs @@ -7,67 +7,75 @@ namespace Refresh.GameServer.Database; public partial class GameDatabaseContext // Comments { - public GameComment? GetCommentById(int id) => - this.GameComments.FirstOrDefault(c => c.SequentialId == id); - public GameComment PostCommentToProfile(GameUser profile, GameUser author, string content) + public GameProfileComment? GetProfileCommentById(int id) => + this.GameProfileComments.FirstOrDefault(c => c.SequentialId == id); + + public GameProfileComment PostCommentToProfile(GameUser profile, GameUser author, string content) { - GameComment comment = new() + GameProfileComment comment = new() { Author = author, + Profile = profile, Content = content, Timestamp = this._time.TimestampMilliseconds, }; - this.AddSequentialObject(comment, profile.ProfileComments); + this.AddSequentialObject(comment); return comment; } - public IEnumerable GetProfileComments(GameUser profile, int count, int skip) => - profile.ProfileComments + public IEnumerable GetProfileComments(GameUser profile, int count, int skip) => + this.GameProfileComments + .Where(c => c.Profile == profile) .OrderByDescending(c => c.Timestamp) .AsEnumerable() .Skip(skip) .Take(count); [Pure] - public int GetTotalCommentsForProfile(GameUser profile) => profile.ProfileComments.Count; + public int GetTotalCommentsForProfile(GameUser profile) => this.GameProfileComments.Count(c => c.Profile == profile); - public void DeleteProfileComment(GameComment comment, GameUser profile) + public void DeleteProfileComment(GameProfileComment comment, GameUser profile) { this.Write(() => { - profile.ProfileComments.Remove(comment); + this.GameProfileComments.Remove(comment); }); } + + public GameLevelComment? GetLevelCommentById(int id) => + this.GameLevelComments.FirstOrDefault(c => c.SequentialId == id); - public GameComment PostCommentToLevel(GameLevel level, GameUser author, string content) + public GameLevelComment PostCommentToLevel(GameLevel level, GameUser author, string content) { - GameComment comment = new() + GameLevelComment comment = new() { Author = author, + Level = level, Content = content, Timestamp = this._time.TimestampMilliseconds, }; - this.AddSequentialObject(comment, level.LevelComments); + this.AddSequentialObject(comment); return comment; } - public IEnumerable GetLevelComments(GameLevel level, int count, int skip) => - level.LevelComments - .OrderByDescending(c => c.Timestamp) - .AsEnumerable() - .Skip(skip) - .Take(count); + public IEnumerable GetLevelComments(GameLevel level, int count, int skip) => + this.GameLevelComments + .Where(c => c.Level == level) + .OrderByDescending(c => c.Timestamp) + .AsEnumerable() + .Skip(skip) + .Take(count); [Pure] - public int GetTotalCommentsForLevel(GameLevel level) => level.LevelComments.Count; + public int GetTotalCommentsForLevel(GameLevel level) => this.GameLevelComments.Count(c => c.Level == level); - public void DeleteLevelComment(GameComment comment, GameLevel level) + public void DeleteLevelComment(GameLevelComment comment, GameLevel level) { this.Write(() => { - level.LevelComments.Remove(comment); + this.GameLevelComments.Remove(comment); }); } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 305baa02..66b333d5 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -1,8 +1,10 @@ using System.Diagnostics.Contracts; +using Realms; using Refresh.GameServer.Authentication; using Refresh.GameServer.Endpoints.Game.Levels.FilterSettings; using Refresh.GameServer.Extensions; using Refresh.GameServer.Types.Comments; +using Refresh.GameServer.Types.Comments.Relations; using Refresh.GameServer.Types.Levels; using Refresh.GameServer.Types.Relations; using Refresh.GameServer.Types.Reviews; @@ -350,33 +352,52 @@ public int GetUniquePlaysForLevel(GameLevel level) => #region Comments - private CommentRelation? GetCommentRelationByUser(GameComment comment, GameUser user) => this.CommentRelations + private ProfileCommentRelation? GetProfileCommentRelationByUser(GameProfileComment comment, GameUser user) => this.ProfileCommentRelations + .FirstOrDefault(r => r.Comment == comment && r.User == user); + + private LevelCommentRelation? GetLevelCommentRelationByUser(GameLevelComment comment, GameUser user) => this.LevelCommentRelations .FirstOrDefault(r => r.Comment == comment && r.User == user); /// - /// Get a user's rating on a particular comment. + /// Get a user's rating on a particular profile comment. /// A null return value means a user has not set a rating. /// /// The comment to check /// The user to check /// The rating if found [Pure] - public RatingType? GetRatingByUser(GameComment comment, GameUser user) - => this.GetCommentRelationByUser(comment, user)?.RatingType; + public RatingType? GetProfileCommentRatingByUser(GameProfileComment comment, GameUser user) + => this.GetProfileCommentRelationByUser(comment, user)?.RatingType; - public int GetTotalRatingsForComment(GameComment comment, RatingType type) => - this.CommentRelations.Count(r => r.Comment == comment && r._RatingType == (int)type); + /// + /// Get a user's rating on a particular level comment. + /// A null return value means a user has not set a rating. + /// + /// The comment to check + /// The user to check + /// The rating if found + [Pure] + public RatingType? GetLevelCommentRatingByUser(GameLevelComment comment, GameUser user) + => this.GetLevelCommentRelationByUser(comment, user)?.RatingType; + + public int GetTotalRatingsForProfileComment(GameProfileComment comment, RatingType type) => + this.ProfileCommentRelations.Count(r => r.Comment == comment && r._RatingType == (int)type); - public bool RateComment(GameUser user, GameComment comment, RatingType ratingType) + public int GetTotalRatingsForLevelComment(GameLevelComment comment, RatingType type) => + this.LevelCommentRelations.Count(r => r.Comment == comment && r._RatingType == (int)type); + + private bool RateComment(GameUser user, TComment comment, RatingType ratingType, RealmDbSet list) + where TComment : class, IGameComment + where TCommentRelation : class, ICommentRelation, new() { if (ratingType == RatingType.Neutral) return false; - CommentRelation? relation = GetCommentRelationByUser(comment, user); - + TCommentRelation? relation = list.FirstOrDefault(r => r.Comment == comment && r.User == user); + if (relation == null) { - relation = new CommentRelation + relation = new TCommentRelation { User = user, Comment = comment, @@ -386,7 +407,7 @@ public bool RateComment(GameUser user, GameComment comment, RatingType ratingTyp this.Write(() => { - this.CommentRelations.Add(relation); + list.Add(relation); }); } else @@ -400,6 +421,12 @@ public bool RateComment(GameUser user, GameComment comment, RatingType ratingTyp return true; } + + public bool RateProfileComment(GameUser user, GameProfileComment comment, RatingType ratingType) + => this.RateComment(user, comment, ratingType, this.ProfileCommentRelations); + + public bool RateLevelComment(GameUser user, GameLevelComment comment, RatingType ratingType) + => this.RateComment(user, comment, ratingType, this.LevelCommentRelations); #endregion } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.cs b/Refresh.GameServer/Database/GameDatabaseContext.cs index b5402c38..544b9642 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.cs @@ -8,9 +8,9 @@ using Refresh.GameServer.Types.Activity; using Refresh.GameServer.Types.Assets; using Refresh.GameServer.Types.Comments; +using Refresh.GameServer.Types.Comments.Relations; 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; @@ -30,8 +30,10 @@ public partial class GameDatabaseContext : RealmDatabaseContext 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 GameProfileComments => new(this._realm); + private RealmDbSet GameLevelComments => new(this._realm); + private RealmDbSet ProfileCommentRelations => new(this._realm); + private RealmDbSet LevelCommentRelations => new(this._realm); private RealmDbSet FavouriteLevelRelations => new(this._realm); private RealmDbSet QueueLevelRelations => new(this._realm); private RealmDbSet FavouriteUserRelations => new(this._realm); diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index 254810bb..1dcda485 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -8,6 +8,7 @@ using Refresh.GameServer.Time; using Refresh.GameServer.Types.Activity; using Refresh.GameServer.Types.Assets; +using Refresh.GameServer.Types.Comments.Relations; using Refresh.GameServer.Types.Contests; using Refresh.GameServer.Types.Levels.SkillRewards; using Refresh.GameServer.Types.Notifications; @@ -43,8 +44,10 @@ protected GameDatabaseProvider(IDateTimeProvider time) typeof(Token), typeof(GameLevel), typeof(GameSkillReward), - typeof(GameComment), - typeof(CommentRelation), + typeof(GameProfileComment), + typeof(GameLevelComment), + typeof(ProfileCommentRelation), + typeof(LevelCommentRelation), typeof(FavouriteLevelRelation), typeof(QueueLevelRelation), typeof(FavouriteUserRelation), @@ -309,14 +312,14 @@ protected override void Migrate(Migration migration, ulong oldVersion) } IQueryable? oldComments = migration.OldRealm.DynamicApi.All("GameComment"); - IQueryable? newComments = migration.NewRealm.All(); + IQueryable? newComments = migration.NewRealm.DynamicApi.All("GameComment"); for (int i = 0; i < newComments.Count(); i++) { dynamic oldComment = oldComments.ElementAt(i); - GameComment newComment = newComments.ElementAt(i); + dynamic newComment = newComments.ElementAt(i); - // In version 40, we switched to Realm source generators which requires some values to be reset + // In version 40, we switched to Realm source generators, which requires some values to be reset if (oldVersion < 40) { newComment.Content = oldComment.Content; diff --git a/Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs b/Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs index 5401d108..518ed9f7 100644 --- a/Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs @@ -19,7 +19,7 @@ namespace Refresh.GameServer.Endpoints.Game; public class CommentEndpoints : EndpointGroup { [GameEndpoint("postUserComment/{username}", ContentType.Xml, HttpMethods.Post)] - public Response PostProfileComment(RequestContext context, GameDatabaseContext database, string username, GameComment body, GameUser user, IDateTimeProvider timeProvider) + public Response PostProfileComment(RequestContext context, GameDatabaseContext database, string username, SerializedComment body, GameUser user, IDateTimeProvider timeProvider) { if (body.Content.Length > 4096) { @@ -54,7 +54,7 @@ public Response DeleteProfileComment(RequestContext context, GameDatabaseContext GameUser? profile = database.GetUserByUsername(username); if (profile == null) return NotFound; - GameComment? comment = profile.ProfileComments.FirstOrDefault(comment => comment.SequentialId == commentId); + GameProfileComment? comment = database.GetProfileCommentById(commentId); if (comment == null) return BadRequest; //Validate someone doesnt try to delete someone elses comment @@ -70,7 +70,7 @@ public Response DeleteProfileComment(RequestContext context, GameDatabaseContext } [GameEndpoint("postComment/{slotType}/{id}", ContentType.Xml, HttpMethods.Post)] - public Response PostLevelComment(RequestContext context, GameDatabaseContext database, string slotType, int id, GameComment body, GameUser user) + public Response PostLevelComment(RequestContext context, GameDatabaseContext database, string slotType, int id, SerializedComment body, GameUser user) { if (body.Content.Length > 4096) { @@ -105,8 +105,8 @@ public Response DeleteLevelComment(RequestContext context, GameDatabaseContext d GameLevel? level = database.GetLevelByIdAndType(slotType, id); if (level == null) return NotFound; - - GameComment? comment = level.LevelComments.FirstOrDefault(comment => comment.SequentialId == commentId); + + GameLevelComment? comment = database.GetLevelCommentById(commentId); if (comment == null) return BadRequest; //Validate someone doesnt try to delete someone else's comment @@ -120,20 +120,35 @@ public Response DeleteLevelComment(RequestContext context, GameDatabaseContext d return OK; } + + [GameEndpoint("rateUserComment/{content}", HttpMethods.Post)] // profile comments + public Response RateProfileComment(RequestContext context, GameDatabaseContext database, GameUser user, string content) + { + if (!int.TryParse(context.QueryString["commentId"], out int commentId)) return BadRequest; + if (!Enum.TryParse(context.QueryString["rating"], out RatingType ratingType)) return BadRequest; + + GameProfileComment? comment = database.GetProfileCommentById(commentId); + if (comment == null) + return NotFound; + + if (!database.RateProfileComment(user, comment, ratingType)) + return BadRequest; + return OK; + } + [GameEndpoint("rateComment/user/{content}", HttpMethods.Post)] // `user` level comments [GameEndpoint("rateComment/developer/{content}", HttpMethods.Post)] // `developer` level comments - [GameEndpoint("rateUserComment/{content}", HttpMethods.Post)] // profile comments - public Response RateComment(RequestContext context, GameDatabaseContext database, GameUser user, string content) + public Response RateLevelComment(RequestContext context, GameDatabaseContext database, GameUser user, string content) { if (!int.TryParse(context.QueryString["commentId"], out int commentId)) return BadRequest; if (!Enum.TryParse(context.QueryString["rating"], out RatingType ratingType)) return BadRequest; - GameComment? comment = database.GetCommentById(commentId); + GameLevelComment? comment = database.GetLevelCommentById(commentId); if (comment == null) return NotFound; - if (!database.RateComment(user, comment, ratingType)) + if (!database.RateLevelComment(user, comment, ratingType)) return BadRequest; return OK; diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs index 9c663e52..0e45b364 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs @@ -173,7 +173,7 @@ public static GameLevelResponse FromHash(string hash, DataContext dataContext) Links = "", AverageStarRating = old.CalculateAverageStarRating(dataContext.Database), ReviewCount = old.Reviews.Count, - CommentCount = old.LevelComments.Count, + CommentCount = dataContext.Database.GetTotalCommentsForLevel(old), }; response.Type = "user"; @@ -221,7 +221,7 @@ public static GameLevelResponse FromHash(string hash, DataContext dataContext) response.IconHash = dataContext.Database.GetAssetFromHash(old.IconHash)?.GetAsIcon(dataContext.Game, dataContext) ?? response.IconHash; - response.CommentCount = old.LevelComments.Count; + response.CommentCount = dataContext.Database.GetTotalCommentsForLevel(old); return response; } diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs index b7526959..2f72d91e 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs @@ -63,7 +63,7 @@ public class GameUserResponse : IDataConvertableFrom PlanetsHash = "0", Handle = SerializedUserHandle.FromUser(old, dataContext), - CommentCount = old.ProfileComments.Count, + CommentCount = dataContext.Database.GetTotalCommentsForProfile(old), CommentsEnabled = true, FavouriteLevelCount = old.IsManaged ? dataContext.Database.GetTotalLevelsFavouritedByUser(old) : 0, FavouriteUserCount = old.IsManaged ? dataContext.Database.GetTotalUsersFavouritedByUser(old) : 0, diff --git a/Refresh.GameServer/Types/Comments/GameLevelComment.cs b/Refresh.GameServer/Types/Comments/GameLevelComment.cs new file mode 100644 index 00000000..4d68cf3b --- /dev/null +++ b/Refresh.GameServer/Types/Comments/GameLevelComment.cs @@ -0,0 +1,25 @@ +using Realms; +using Refresh.GameServer.Database; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.UserData; + +namespace Refresh.GameServer.Types.Comments; + +public partial class GameLevelComment : IRealmObject, IGameComment, ISequentialId +{ + [PrimaryKey] public int SequentialId { get; set; } + + /// + public GameUser Author { get; set; } = null!; + + /// + /// The destination level this comment was posted to. + /// + public GameLevel Level { get; set; } = null!; + + /// + public string Content { get; set; } = string.Empty; + + /// + public long Timestamp { get; set; } +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Comments/GameComment.cs b/Refresh.GameServer/Types/Comments/GameProfileComment.cs similarity index 58% rename from Refresh.GameServer/Types/Comments/GameComment.cs rename to Refresh.GameServer/Types/Comments/GameProfileComment.cs index 8653ffa5..7a15f6cd 100644 --- a/Refresh.GameServer/Types/Comments/GameComment.cs +++ b/Refresh.GameServer/Types/Comments/GameProfileComment.cs @@ -1,19 +1,24 @@ using Realms; using Refresh.GameServer.Database; using Refresh.GameServer.Types.UserData; -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. namespace Refresh.GameServer.Types.Comments; -public partial class GameComment : IRealmObject, IGameComment, ISequentialId +public partial class GameProfileComment : IRealmObject, IGameComment, ISequentialId { [PrimaryKey] public int SequentialId { get; set; } + /// public GameUser Author { get; set; } = null!; - public string Content { get; set; } = string.Empty; - + /// - /// Timestamp in Unix milliseconds + /// The destination profile this comment was posted to. /// + public GameUser Profile { get; set; } = null!; + + /// + public string Content { get; set; } = string.Empty; + + /// public long Timestamp { get; set; } -} +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Comments/IGameComment.cs b/Refresh.GameServer/Types/Comments/IGameComment.cs index e73a1c08..04d0f063 100644 --- a/Refresh.GameServer/Types/Comments/IGameComment.cs +++ b/Refresh.GameServer/Types/Comments/IGameComment.cs @@ -1,12 +1,23 @@ -using Refresh.GameServer.Database; using Refresh.GameServer.Types.UserData; namespace Refresh.GameServer.Types.Comments; -public interface IGameComment : ISequentialId +public interface IGameComment { + int SequentialId { get; set; } + + /// + /// The user who originally posted the comment. + /// GameUser Author { get; set; } + + /// + /// The text content of the comment. + /// string Content { get; set; } + /// + /// Timestamp in Unix milliseconds + /// long Timestamp { get; set; } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Comments/Relations/ICommentRelation.cs b/Refresh.GameServer/Types/Comments/Relations/ICommentRelation.cs new file mode 100644 index 00000000..e5c8f6c8 --- /dev/null +++ b/Refresh.GameServer/Types/Comments/Relations/ICommentRelation.cs @@ -0,0 +1,16 @@ +using MongoDB.Bson; +using Realms; +using Refresh.GameServer.Types.Reviews; +using Refresh.GameServer.Types.UserData; + +namespace Refresh.GameServer.Types.Comments.Relations; + +public interface ICommentRelation : IRealmObject + where TComment : IGameComment +{ + ObjectId CommentRelationId { get; set; } + GameUser User { get; set; } + TComment Comment { get; set; } + RatingType RatingType { get; set; } + DateTimeOffset Timestamp { get; set; } +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Relations/CommentRelation.cs b/Refresh.GameServer/Types/Comments/Relations/LevelCommentRelation.cs similarity index 69% rename from Refresh.GameServer/Types/Relations/CommentRelation.cs rename to Refresh.GameServer/Types/Comments/Relations/LevelCommentRelation.cs index ad01817d..86f04bad 100644 --- a/Refresh.GameServer/Types/Relations/CommentRelation.cs +++ b/Refresh.GameServer/Types/Comments/Relations/LevelCommentRelation.cs @@ -1,17 +1,15 @@ -using MongoDB.Bson; +using MongoDB.Bson; using Realms; -using Refresh.GameServer.Types.Comments; using Refresh.GameServer.Types.Reviews; using Refresh.GameServer.Types.UserData; -namespace Refresh.GameServer.Types.Relations; -#nullable disable +namespace Refresh.GameServer.Types.Comments.Relations; -public partial class CommentRelation : IRealmObject +public partial class LevelCommentRelation : IRealmObject, ICommentRelation { public ObjectId CommentRelationId { get; set; } = ObjectId.GenerateNewId(); public GameUser User { get; set; } - public GameComment Comment { get; set; } + public GameLevelComment Comment { get; set; } [Ignored] public RatingType RatingType { diff --git a/Refresh.GameServer/Types/Comments/Relations/ProfileCommentRelation.cs b/Refresh.GameServer/Types/Comments/Relations/ProfileCommentRelation.cs new file mode 100644 index 00000000..1f76ed10 --- /dev/null +++ b/Refresh.GameServer/Types/Comments/Relations/ProfileCommentRelation.cs @@ -0,0 +1,23 @@ +using MongoDB.Bson; +using Realms; +using Refresh.GameServer.Types.Reviews; +using Refresh.GameServer.Types.UserData; + +namespace Refresh.GameServer.Types.Comments.Relations; + +public partial class ProfileCommentRelation : IRealmObject, ICommentRelation +{ + public ObjectId CommentRelationId { get; set; } = ObjectId.GenerateNewId(); + public GameUser User { get; set; } + public GameProfileComment Comment { get; set; } + [Ignored] + public RatingType RatingType + { + get => (RatingType)this._RatingType; + set => this._RatingType = (int)value; + } + + // ReSharper disable once InconsistentNaming + internal int _RatingType { get; set; } + public DateTimeOffset Timestamp { get; set; } +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Comments/SerializedComment.cs b/Refresh.GameServer/Types/Comments/SerializedComment.cs index 2c3a3574..d7750fbd 100644 --- a/Refresh.GameServer/Types/Comments/SerializedComment.cs +++ b/Refresh.GameServer/Types/Comments/SerializedComment.cs @@ -7,7 +7,7 @@ namespace Refresh.GameServer.Types.Comments; [XmlRoot("comment")] [XmlType("comment")] -public class SerializedComment : IDataConvertableFrom +public class SerializedComment : IDataConvertableFrom, IDataConvertableFrom { [XmlElement("id")] public required int CommentId { get; set; } @@ -21,25 +21,41 @@ public class SerializedComment : IDataConvertableFrom + new() + { + CommentId = comment.SequentialId, + Content = comment.Content, + Timestamp = comment.Timestamp, + Handle = comment.Author.Username, + }; + + public static SerializedComment? FromOld(GameProfileComment? old, DataContext dataContext) + { + if (old == null) return null; + + SerializedComment comment = FromBase(old); + comment.ThumbsUp = dataContext.Database.GetTotalRatingsForProfileComment(old, RatingType.Yay); + comment.ThumbsDown = dataContext.Database.GetTotalRatingsForProfileComment(old, RatingType.Boo); + comment.YourThumb = (int?)dataContext.Database.GetProfileCommentRatingByUser(old, dataContext.User!) ?? 0; + return comment; + } - public static SerializedComment? FromOld(GameComment? old, DataContext dataContext) + public static SerializedComment? FromOld(GameLevelComment? old, DataContext dataContext) { if (old == null) return null; - return new SerializedComment - { - CommentId = old.SequentialId, - Content = old.Content, - Timestamp = old.Timestamp, - Handle = old.Author.Username, - ThumbsUp = dataContext.Database.GetTotalRatingsForComment(old, RatingType.Yay), - ThumbsDown = dataContext.Database.GetTotalRatingsForComment(old, RatingType.Boo), - YourThumb = (int?)dataContext.Database.GetRatingByUser(old, dataContext.User!) ?? 0, - }; + SerializedComment comment = FromBase(old); + comment.ThumbsUp = dataContext.Database.GetTotalRatingsForLevelComment(old, RatingType.Yay); + comment.ThumbsDown = dataContext.Database.GetTotalRatingsForLevelComment(old, RatingType.Boo); + comment.YourThumb = (int?)dataContext.Database.GetLevelCommentRatingByUser(old, dataContext.User!) ?? 0; + return comment; } - public static IEnumerable FromOldList(IEnumerable oldList, DataContext dataContext) => oldList.Select(old => FromOld(old, dataContext)).ToList()!; + public static IEnumerable FromOldList(IEnumerable oldList, DataContext dataContext) => oldList.Select(old => FromOld(old, dataContext)).ToList()!; + public static IEnumerable FromOldList(IEnumerable oldList, DataContext dataContext) => oldList.Select(old => FromOld(old, dataContext)).ToList()!; } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Levels/GameLevel.cs b/Refresh.GameServer/Types/Levels/GameLevel.cs index e31099c0..fee85df6 100644 --- a/Refresh.GameServer/Types/Levels/GameLevel.cs +++ b/Refresh.GameServer/Types/Levels/GameLevel.cs @@ -92,8 +92,6 @@ [Ignored] public GameLevelSource Source public float Score { get; set; } #nullable disable - public IList LevelComments { get; } - // ILists can't be serialized to XML, and Lists/Arrays cannot be stored in realm, // hence _SkillRewards and SkillRewards both existing // ReSharper disable once InconsistentNaming diff --git a/Refresh.GameServer/Types/UserData/GameUser.cs b/Refresh.GameServer/Types/UserData/GameUser.cs index 539c9718..aefe81dc 100644 --- a/Refresh.GameServer/Types/UserData/GameUser.cs +++ b/Refresh.GameServer/Types/UserData/GameUser.cs @@ -61,10 +61,6 @@ public partial class GameUser : IRealmObject, IRateLimitUser public DateTimeOffset JoinDate { get; set; } public UserPins Pins { get; set; } = new(); - - #nullable disable - public IList ProfileComments { get; } - #nullable restore public string BetaPlanetsHash { get; set; } = "0"; public string Lbp2PlanetsHash { get; set; } = "0"; diff --git a/RefreshTests.GameServer/Tests/Comments/CommentTests.cs b/RefreshTests.GameServer/Tests/Comments/CommentTests.cs index c693f6ae..deacd791 100644 --- a/RefreshTests.GameServer/Tests/Comments/CommentTests.cs +++ b/RefreshTests.GameServer/Tests/Comments/CommentTests.cs @@ -9,7 +9,7 @@ namespace RefreshTests.GameServer.Tests.Comments; public class CommentTests : GameServerTest { - public static void RateComment(TestContext context, GameUser user, GameComment comment, string rateCommentUrl, string getCommentsUrl) + public static void RateComment(TestContext context, GameUser user, IGameComment comment, string rateCommentUrl, string getCommentsUrl) { using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); diff --git a/RefreshTests.GameServer/Tests/Comments/LevelCommentTests.cs b/RefreshTests.GameServer/Tests/Comments/LevelCommentTests.cs index c2942e54..86639eaa 100644 --- a/RefreshTests.GameServer/Tests/Comments/LevelCommentTests.cs +++ b/RefreshTests.GameServer/Tests/Comments/LevelCommentTests.cs @@ -18,10 +18,12 @@ public void PostAndDeleteLevelComment() using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); - GameComment comment = new() + SerializedComment comment = new() { - Author = user, Content = "This is a test comment!", + CommentId = 0, + Timestamp = 0, + Handle = null, }; HttpResponseMessage response = client.PostAsync($"/lbp/postComment/user/{level.LevelId}", new StringContent(comment.AsXML())).Result; @@ -49,10 +51,12 @@ public void CantPostTooLongLevelComment() using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); - GameComment comment = new() + SerializedComment comment = new() { - Author = user, Content = new string('S', 5000), + CommentId = 0, + Timestamp = 0, + Handle = null, }; HttpResponseMessage response = client.PostAsync($"/lbp/postComment/user/{level.LevelId}", new StringContent(comment.AsXML())).Result; @@ -67,10 +71,12 @@ public void CantPostCommentToInvalidLevel() using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); - GameComment comment = new() + SerializedComment comment = new() { - Author = user, - Content = "This is a test comment", + Content = "This is a test comment!", + CommentId = 0, + Timestamp = 0, + Handle = null, }; HttpResponseMessage response = client.PostAsync($"/lbp/postComment/user/I_AM_NOT_REAL", new StringContent(comment.AsXML())).Result; @@ -138,10 +144,12 @@ public void CantDeleteAnotherUsersComment() using HttpClient client1 = context.GetAuthenticatedClient(TokenType.Game, user1); using HttpClient client2 = context.GetAuthenticatedClient(TokenType.Game, user2); - GameComment comment = new() + SerializedComment comment = new() { - Author = user1, Content = "This is a test comment!", + CommentId = 0, + Timestamp = 0, + Handle = null, }; HttpResponseMessage response = client1.PostAsync($"/lbp/postComment/user/{level.LevelId}", new StringContent(comment.AsXML())).Result; @@ -162,7 +170,7 @@ public void RateUserLevelComment() using TestContext context = this.GetServer(); GameUser user = context.CreateUser(); GameLevel level = context.CreateLevel(user); - GameComment comment = context.Database.PostCommentToLevel(level, user, "This is a test comment!"); + GameLevelComment comment = context.Database.PostCommentToLevel(level, user, "This is a test comment!"); CommentTests.RateComment(context, user, comment, $"/lbp/rateComment/user/{level.LevelId}", $"/lbp/comments/user/{level.LevelId}"); } @@ -175,7 +183,7 @@ public void RateDeveloperLevelComment() using TestContext context = this.GetServer(); GameUser user = context.CreateUser(); GameLevel level = context.Database.GetStoryLevelById(levelId); - GameComment comment = context.Database.PostCommentToLevel(level, user, "This is a test comment!"); + GameLevelComment comment = context.Database.PostCommentToLevel(level, user, "This is a test comment!"); CommentTests.RateComment(context, user, comment, $"/lbp/rateComment/developer/{level.LevelId}", $"/lbp/comments/developer/{level.LevelId}"); } diff --git a/RefreshTests.GameServer/Tests/Comments/UserCommentTests.cs b/RefreshTests.GameServer/Tests/Comments/UserCommentTests.cs index 35f82188..f062d70e 100644 --- a/RefreshTests.GameServer/Tests/Comments/UserCommentTests.cs +++ b/RefreshTests.GameServer/Tests/Comments/UserCommentTests.cs @@ -17,10 +17,12 @@ public void PostAndDeleteUserComment() using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user1); - GameComment comment = new() + SerializedComment comment = new() { - Author = user1, Content = "This is a test comment!", + CommentId = 0, + Timestamp = 0, + Handle = null, }; HttpResponseMessage response = client.PostAsync($"/lbp/postUserComment/{user2.Username}", new StringContent(comment.AsXML())).Result; @@ -48,10 +50,12 @@ public void CantPostTooLongUserComment() using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user1); - GameComment comment = new() + SerializedComment comment = new() { - Author = user1, Content = new string('S', 5000), + CommentId = 0, + Timestamp = 0, + Handle = null, }; HttpResponseMessage response = client.PostAsync($"/lbp/postUserComment/{user2.Username}", new StringContent(comment.AsXML())).Result; @@ -66,10 +70,12 @@ public void CantUserCommentToInvalidUser() using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); - GameComment comment = new() + SerializedComment comment = new() { - Author = user, - Content = "This is a test comment", + Content = "This is a test comment!", + CommentId = 0, + Timestamp = 0, + Handle = null, }; HttpResponseMessage response = client.PostAsync($"/lbp/postUserComment/I_AM_NOT_REAL", new StringContent(comment.AsXML())).Result; @@ -134,10 +140,12 @@ public void CantDeleteAnotherUsersComment() using HttpClient client1 = context.GetAuthenticatedClient(TokenType.Game, user1); using HttpClient client2 = context.GetAuthenticatedClient(TokenType.Game, user2); - GameComment comment = new() + SerializedComment comment = new() { - Author = user1, Content = "This is a test comment!", + CommentId = 0, + Timestamp = 0, + Handle = null, }; HttpResponseMessage response = client1.PostAsync($"/lbp/postUserComment/{user2.Username}", new StringContent(comment.AsXML())).Result; @@ -157,7 +165,7 @@ public void RateProfileComment() { using TestContext context = this.GetServer(); GameUser user = context.CreateUser(); - GameComment comment = context.Database.PostCommentToProfile(user, user, "This is a test comment!"); + GameProfileComment comment = context.Database.PostCommentToProfile(user, user, "This is a test comment!"); CommentTests.RateComment(context, user, comment, $"/lbp/rateUserComment/{user.Username}", $"/lbp/userComments/{user.Username}"); } diff --git a/RefreshTests.GameServer/Tests/Relations/CommentPublishTests.cs b/RefreshTests.GameServer/Tests/Relations/CommentPublishTests.cs index 707de8c2..6e60d5b4 100644 --- a/RefreshTests.GameServer/Tests/Relations/CommentPublishTests.cs +++ b/RefreshTests.GameServer/Tests/Relations/CommentPublishTests.cs @@ -12,7 +12,7 @@ public void CanCreateCommentOnProfile() GameUser profile = context.CreateUser(); GameUser commenter = context.CreateUser(); - GameComment comment = context.Database.PostCommentToProfile(profile, commenter, "Hi!"); + GameProfileComment comment = context.Database.PostCommentToProfile(profile, commenter, "Hi!"); Assert.That(context.Database.GetProfileComments(profile, 1, 0).First(), Is.EqualTo(comment)); } From 7f1a307b1e357f2b9b37ed8eda0945f37d056103 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 17 Jul 2024 21:14:16 -0400 Subject: [PATCH 4/5] Create migrations to fill out new tables --- .../Database/GameDatabaseProvider.cs | 65 ++++++++++++++----- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index 1dcda485..0001caa2 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -33,7 +33,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 133; + protected override ulong SchemaVersion => 134; protected override string Filename => "refreshGameServer.realm"; @@ -183,6 +183,30 @@ protected override void Migrate(Migration migration, ulong oldVersion) newUser.LocationX = (int)oldUser.Location.X; newUser.LocationY = (int)oldUser.Location.Y; } + + // In version 134, we split GameComments into multiple tables. + // This migration creates GameProfileComments + if (oldVersion < 134) + { + foreach (dynamic comment in oldUser.ProfileComments) + { + GameUser? author = comment.Author != null ? migration.NewRealm.Find(comment.Author.UserId) : null; + if (author == null) + { + Console.WriteLine($"Skipping migration for profile comment id {comment.SequentialId} due to missing author"); + continue; + } + + migration.NewRealm.Add(new GameProfileComment + { + SequentialId = (int)comment.SequentialId, + Author = author, + Profile = newUser, + Content = comment.Content, + Timestamp = comment.Timestamp, + }); + } + } } IQueryable? oldLevels = migration.OldRealm.DynamicApi.All("GameLevel"); @@ -250,6 +274,30 @@ protected override void Migrate(Migration migration, ulong oldVersion) newLevel.LocationX = (int)oldLevel.Location.X; newLevel.LocationY = (int)oldLevel.Location.Y; } + + // In version 134, we split GameComments into multiple tables. + // This migration creates GameLevelComments + if (oldVersion < 134) + { + foreach (dynamic comment in oldLevel.LevelComments) + { + GameUser? author = comment.Author != null ? migration.NewRealm.Find(comment.Author.UserId) : null; + if (author == null) + { + Console.WriteLine($"Skipping migration for level comment id {comment.SequentialId} due to missing author"); + continue; + } + + migration.NewRealm.Add(new GameLevelComment + { + SequentialId = (int)comment.SequentialId, + Author = author, + Level = newLevel, + Content = comment.Content, + Timestamp = comment.Timestamp, + }); + } + } } // In version 22, tokens added expiry and types so just wipe them all @@ -311,21 +359,6 @@ protected override void Migrate(Migration migration, ulong oldVersion) if (oldVersion < 36) newToken.LoginDate = DateTimeOffset.FromUnixTimeMilliseconds(timestampMilliseconds); } - IQueryable? oldComments = migration.OldRealm.DynamicApi.All("GameComment"); - IQueryable? newComments = migration.NewRealm.DynamicApi.All("GameComment"); - - for (int i = 0; i < newComments.Count(); i++) - { - dynamic oldComment = oldComments.ElementAt(i); - dynamic newComment = newComments.ElementAt(i); - - // In version 40, we switched to Realm source generators, which requires some values to be reset - if (oldVersion < 40) - { - newComment.Content = oldComment.Content; - } - } - IQueryable? oldPhotos = migration.OldRealm.DynamicApi.All("GamePhoto"); IQueryable? newPhotos = migration.NewRealm.All(); From 3d256ce9dc7f81f5ba23b14375ccaff97e161705 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 17 Jul 2024 22:18:45 -0400 Subject: [PATCH 5/5] Bump SchemaVersion to 135 --- Refresh.GameServer/Database/GameDatabaseProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index f201b5fe..07673d57 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -33,7 +33,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 134; + protected override ulong SchemaVersion => 135; protected override string Filename => "refreshGameServer.realm";