From 31535743783379dbec9fe53ec53b274f4c4c8019 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Fri, 19 Jan 2024 18:27:25 -0800 Subject: [PATCH 01/15] Basic level review infrastructure --- .../Endpoints/Game/ReviewEndpoints.cs | 27 ++++++- .../Types/Levels/GameLevelSource.cs | 13 +++ .../Types/Reviews/GameReview.cs | 34 ++++++++ .../Types/Reviews/GameReviewSlot.cs | 14 ++++ .../Types/Reviews/SerializedGameReview.cs | 79 +++++++++++++++++++ .../Reviews/SerializedGameReviewResponse.cs | 16 ++++ 6 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 Refresh.GameServer/Types/Reviews/GameReview.cs create mode 100644 Refresh.GameServer/Types/Reviews/GameReviewSlot.cs create mode 100644 Refresh.GameServer/Types/Reviews/SerializedGameReview.cs create mode 100644 Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs diff --git a/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs b/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs index 4d6606f7..51d6b9b0 100644 --- a/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs @@ -1,5 +1,6 @@ using Bunkum.Core; using Bunkum.Core.Endpoints; +using Bunkum.Core.Endpoints.Debugging; using Bunkum.Core.Responses; using Bunkum.Listener.Protocol; using Bunkum.Protocols.Http; @@ -56,5 +57,29 @@ public Response RateUserLevel(RequestContext context, GameDatabaseContext databa } return database.RateLevel(level, user, rating) ? OK : Unauthorized; - } + } + + [GameEndpoint("reviewsFor/{slotType}/{levelId}", ContentType.Xml)] + [DebugResponseBody] + [AllowEmptyBody] + public Response GetReviewsForLevel(RequestContext context, GameDatabaseContext database, string slotType, int levelId) + { + GameLevel? level; + switch (slotType) + { + case "developer": + level = database.GetStoryLevelById(levelId); + break; + case "user": + level = database.GetLevelById(levelId); + break; + default: + return BadRequest; + } + + if (level == null) + return NotFound; + + return new Response(new SerializedGameReviewResponse(), ContentType.Xml); + } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Levels/GameLevelSource.cs b/Refresh.GameServer/Types/Levels/GameLevelSource.cs index 6de06dee..adf34f06 100644 --- a/Refresh.GameServer/Types/Levels/GameLevelSource.cs +++ b/Refresh.GameServer/Types/Levels/GameLevelSource.cs @@ -10,4 +10,17 @@ public enum GameLevelSource /// A level created by the server to represent a game story level. /// Story, +} + +public static class GameLevelSourceExtensions +{ + public static string ToGameType(this GameLevelSource source) + { + return source switch + { + GameLevelSource.User => "user", + GameLevelSource.Story => "developer", + _ => throw new ArgumentOutOfRangeException(nameof(source), source, null), + }; + } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Reviews/GameReview.cs b/Refresh.GameServer/Types/Reviews/GameReview.cs new file mode 100644 index 00000000..57c2b457 --- /dev/null +++ b/Refresh.GameServer/Types/Reviews/GameReview.cs @@ -0,0 +1,34 @@ +using Realms; +using Refresh.GameServer.Database; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.UserData; + +namespace Refresh.GameServer.Types.Reviews; + +public partial class GameReview : IRealmObject, ISequentialId +{ + [PrimaryKey] + public int SequentialId { get; set; } + + public GameLevel Level { get; set; } + + public GameUser Publisher { get; set; } + + public long Timestamp { get; set; } + + public string Labels { get; set; } + + public bool Deleted { get; set; } + + private int _DeletedBy { get; set; } + + [Ignored] + public ReviewDeletedBy DeletedBy + { + //realm shenanigans + get => (ReviewDeletedBy)this._DeletedBy; + set => this._DeletedBy = (int)value; + } + + public string Text { get; set; } +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Reviews/GameReviewSlot.cs b/Refresh.GameServer/Types/Reviews/GameReviewSlot.cs new file mode 100644 index 00000000..53683e1d --- /dev/null +++ b/Refresh.GameServer/Types/Reviews/GameReviewSlot.cs @@ -0,0 +1,14 @@ +using System.Xml.Serialization; +using Refresh.GameServer.Types.Matching; + +namespace Refresh.GameServer.Types.Reviews; + +[XmlRoot("slot")] +public class GameReviewSlot +{ + [XmlAttribute("type")] + public string SlotType { get; set; } + + [XmlText] + public int SlotId { get; set; } +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs new file mode 100644 index 00000000..ae42aaf0 --- /dev/null +++ b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs @@ -0,0 +1,79 @@ +using System.Xml.Serialization; +using Realms; +using Refresh.GameServer.Types.Levels; + +namespace Refresh.GameServer.Types.Reviews; + +[XmlRoot("deleted_by")] +public enum ReviewDeletedBy +{ + [XmlEnum(Name = "none")] + None, + [XmlEnum(Name = "moderator")] + Moderator, + [XmlEnum(Name = "level_author")] + LevelAuthor, +} + +public class SerializedGameReview +{ + [XmlElement("id")] + public int Id { get; set; } + + [XmlElement("slot_id")] + public GameReviewSlot Slot { get; set; } + + [XmlElement("reviewer")] + public string Reviewer { get; set; } + + [XmlElement("timestamp")] + public long Timestamp { get; set; } + + [XmlElement("labels")] + public string Labels { get; set; } = ""; + + [XmlElement("deleted")] + public bool Deleted { get; set; } = false; + + [XmlElement("deleted_by")] + public ReviewDeletedBy DeletedBy { get; set; } = ReviewDeletedBy.None; + + [XmlElement("text")] + public string Text { get; set; } = ""; + + [XmlElement("thumb")] + public int Thumb { get; set; } + + [XmlElement("thumbsup")] + public int ThumbsUp { get; set; } + + [XmlElement("thumbsdown")] + public int ThumbsDown { get; set; } + + [XmlElement("yourthumb")] + public RatingType YourThumb { get; set; } + + public SerializedGameReview FromOld(GameReview review) + { + return new SerializedGameReview + { + Id = review.SequentialId, + Slot = new GameReviewSlot + { + SlotType = review.Level.Source.ToGameType(), + SlotId = review.Level.LevelId, + }, + Reviewer = review.Publisher.Username, + Timestamp = review.Timestamp, + Labels = review.Labels, + Deleted = review.Deleted, + DeletedBy = review.DeletedBy, + //If the review is deleted, dont send the review text + Text = review.Deleted ? "" : review.Text, + Thumb = 0, + ThumbsUp = 0, + ThumbsDown = 0, + YourThumb = 0, + }; + } +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs b/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs new file mode 100644 index 00000000..5a123ef8 --- /dev/null +++ b/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace Refresh.GameServer.Types.Reviews; + +[XmlRoot("reviews")] +public class SerializedGameReviewResponse +{ + [XmlElement("review")] + public List Reviews { get; set; } + + [XmlAttribute("hint")] + public long Hint { get; set; } + + [XmlAttribute("hint_start")] + public int HintStart { get; set; } +} \ No newline at end of file From c113c713565b2f2fac0a9ed86d2c58e5ce6f507e Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Sat, 20 Jan 2024 16:49:48 -0800 Subject: [PATCH 02/15] Add more stuffs + implement CommentCount --- Refresh.GameServer/Database/GameDatabaseProvider.cs | 4 +++- .../Endpoints/Game/DataTypes/Response/GameLevelResponse.cs | 6 ++++++ Refresh.GameServer/Types/Levels/GameMinimalLevelResponse.cs | 6 +++++- Refresh.GameServer/Types/Reviews/SerializedGameReview.cs | 2 ++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index b9dc4779..17c2b4b3 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -12,6 +12,7 @@ using Refresh.GameServer.Types.Notifications; using Refresh.GameServer.Types.Relations; using Refresh.GameServer.Types.Report; +using Refresh.GameServer.Types.Reviews; using Refresh.GameServer.Types.UserData.Leaderboard; using GamePhoto = Refresh.GameServer.Types.Photos.GamePhoto; using GamePhotoSubject = Refresh.GameServer.Types.Photos.GamePhotoSubject; @@ -32,7 +33,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 111; + protected override ulong SchemaVersion => 112; protected override string Filename => "refreshGameServer.realm"; @@ -72,6 +73,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) typeof(ScreenElements), typeof(ScreenRect), typeof(Slot), + typeof(GameReview), }; public override void Warmup() diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs index c45ccd3e..b33d70ed 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs @@ -70,6 +70,10 @@ public class GameLevelResponse : IDataConvertableFrom Date: Sat, 20 Jan 2024 22:13:18 -0500 Subject: [PATCH 03/15] Bump database schema version --- 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 17c2b4b3..de267d87 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 => 112; + protected override ulong SchemaVersion => 113; protected override string Filename => "refreshGameServer.realm"; From 25016db39d586b2a406e953a51d9949a81f5df35 Mon Sep 17 00:00:00 2001 From: jvyden Date: Sat, 20 Jan 2024 22:13:50 -0500 Subject: [PATCH 04/15] Add ReviewCount to GameUserResponse Fixes game not requesting reviews on levels --- .../Endpoints/Game/DataTypes/Response/GameLevelResponse.cs | 2 +- .../Endpoints/Game/DataTypes/Response/GameUserResponse.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs index b33d70ed..6c9aef2f 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs @@ -71,7 +71,7 @@ public class GameLevelResponse : IDataConvertableFrom [XmlElement("npHandle")] public required SerializedUserHandle Handle { get; set; } [XmlElement("commentCount")] public int CommentCount { get; set; } [XmlElement("commentsEnabled")] public bool CommentsEnabled { get; set; } + [XmlElement("reviewCount")] public int ReviewCount { get; set; } [XmlElement("favouriteSlotCount")] public int FavouriteLevelCount { get; set; } [XmlElement("favouriteUserCount")] public int FavouriteUserCount { get; set; } [XmlElement("lolcatftwCount")] public int QueuedLevelCount { get; set; } From 2e39c11951837823b6da3a8186bf9a662b72efc2 Mon Sep 17 00:00:00 2001 From: jvyden Date: Sat, 20 Jan 2024 22:40:42 -0500 Subject: [PATCH 05/15] Stub YourLbp2PlayCount Fixes review button not showing --- .../Endpoints/Game/DataTypes/Response/GameLevelResponse.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs index 6c9aef2f..e0881363 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs @@ -70,10 +70,11 @@ public class GameLevelResponse : IDataConvertableFrom Date: Sat, 20 Jan 2024 22:44:55 -0500 Subject: [PATCH 06/15] Implement YourLbp2PlayCount --- .../Endpoints/Game/DataTypes/Response/GameLevelResponse.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs index e0881363..0958860a 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs @@ -53,6 +53,9 @@ public class GameLevelResponse : IDataConvertableFrom SkillRewards { get; set; } @@ -74,7 +77,6 @@ public class GameLevelResponse : IDataConvertableFrom p.User == user); this.PlayerCount = matchService.GetPlayerCountForLevel(RoomSlotType.Online, this.LevelId); GameAsset? rootResourceAsset = database.GetAssetFromHash(this.RootResource); From 943f2c4d97493534c619bc7211139546f4a63a8f Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Wed, 24 Jan 2024 22:38:21 -0800 Subject: [PATCH 07/15] Implement ingame level reviews --- .../Database/GameDatabaseContext.Relations.cs | 41 ++++++++++++++++ .../Database/GameDatabaseProvider.cs | 2 +- .../DataTypes/Response/GameLevelResponse.cs | 2 + .../DataTypes/Response/GameUserResponse.cs | 2 + .../Endpoints/Game/ReviewEndpoints.cs | 49 ++++++++++++++++++- Refresh.GameServer/Types/Levels/GameLevel.cs | 2 + .../Types/Lists/SerializedList.cs | 3 +- .../Types/Reviews/GameReview.cs | 15 +----- .../Types/Reviews/ReviewDeletedBy.cs | 10 ++++ .../Types/Reviews/SerializedGameReview.cs | 47 +++++++++--------- .../Reviews/SerializedGameReviewResponse.cs | 12 ++--- 11 files changed, 140 insertions(+), 45 deletions(-) create mode 100644 Refresh.GameServer/Types/Reviews/ReviewDeletedBy.cs diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 3e2fd9d0..223ef481 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -217,6 +217,47 @@ public bool RateLevel(GameLevel level, GameUser user, RatingType type) }); return true; } + + public bool AddReviewToLevel(SerializedGameReview body, GameLevel level, GameUser user) + { + List toRemove = level.Reviews.Where(r => r.Publisher.UserId == user.UserId).ToList(); + if (toRemove.Count > 0) + { + this._realm.Write(() => + { + foreach (GameReview review in toRemove) + { + level.Reviews.Remove(review); + this._realm.Remove(review); + } + }); + } + + this.AddSequentialObject(new GameReview + { + Publisher = user, + Timestamp = this._time.TimestampMilliseconds, + Labels = body.Labels, + Text = body.Text, + }, level.Reviews); + + return true; + } + + public IQueryable GetReviewsByUser(GameUser user) + { + return this._realm.All().Where(r => r.Publisher == user); + } + + public void DeleteReview(GameReview review) + { + this._realm.Remove(review); + } + + public GameReview? GetReviewByLevelAndUser(GameLevel level, GameUser user) + { + return level.Reviews.FirstOrDefault(r => r.Publisher.UserId == user.UserId); + } #endregion diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index de267d87..0a264f12 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 => 113; + protected override ulong SchemaVersion => 115; protected override string Filename => "refreshGameServer.realm"; diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs index 0958860a..b8f33979 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameLevelResponse.cs @@ -127,6 +127,8 @@ public class GameLevelResponse : IDataConvertableFrom(level.Reviews.AsEnumerable(), skip, count).Items).ToList()), ContentType.Xml); + } + + [GameEndpoint("reviewsBy/{username}", ContentType.Xml)] + [AllowEmptyBody] + public Response GetReviewsForLevel(RequestContext context, GameDatabaseContext database, string username) + { + GameUser? user = database.GetUserByUsername(username); + + if (user == null) + return NotFound; + + (int skip, int count) = context.GetPageData(); + + return new Response(new SerializedGameReviewResponse(items: SerializedGameReview.FromOldList(new DatabaseList(database.GetReviewsByUser(user), skip, count).Items).ToList()), ContentType.Xml); + } + + [GameEndpoint("postReview/{slotType}/{levelId}", ContentType.Xml, HttpMethods.Post)] + public Response PostReviewForLevel(RequestContext context, GameDatabaseContext database, string slotType, int levelId, SerializedGameReview body, GameUser user) + { + GameLevel? level; + switch (slotType) + { + case "developer": + level = database.GetStoryLevelById(levelId); + break; + case "user": + level = database.GetLevelById(levelId); + break; + default: + return BadRequest; + } + + if(level == null) + return NotFound; + + //You cant review a level you haven't played. + if (!database.HasUserPlayedLevel(level, user)) + return BadRequest; + + if (!database.AddReviewToLevel(body, level, user)) + return BadRequest; + + return OK; } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Levels/GameLevel.cs b/Refresh.GameServer/Types/Levels/GameLevel.cs index e33e959c..838e820e 100644 --- a/Refresh.GameServer/Types/Levels/GameLevel.cs +++ b/Refresh.GameServer/Types/Levels/GameLevel.cs @@ -105,6 +105,8 @@ [Ignored] public GameLevelSource Source // ReSharper disable once InconsistentNaming public IList _SkillRewards { get; } + public IList Reviews { get; } + #nullable restore [XmlArray("customRewards")] diff --git a/Refresh.GameServer/Types/Lists/SerializedList.cs b/Refresh.GameServer/Types/Lists/SerializedList.cs index 0bb0f7d8..b3a0d212 100644 --- a/Refresh.GameServer/Types/Lists/SerializedList.cs +++ b/Refresh.GameServer/Types/Lists/SerializedList.cs @@ -7,7 +7,8 @@ public abstract class SerializedList [XmlAttribute("total")] public int Total { get; set; } - [XmlAttribute("hint_start")] public int NextPageStart { get; set; } + [XmlAttribute("hint_start")] + public int NextPageStart { get; set; } [XmlIgnore] public abstract List Items { get; set; } diff --git a/Refresh.GameServer/Types/Reviews/GameReview.cs b/Refresh.GameServer/Types/Reviews/GameReview.cs index 57c2b457..b3d058f5 100644 --- a/Refresh.GameServer/Types/Reviews/GameReview.cs +++ b/Refresh.GameServer/Types/Reviews/GameReview.cs @@ -10,7 +10,8 @@ public partial class GameReview : IRealmObject, ISequentialId [PrimaryKey] public int SequentialId { get; set; } - public GameLevel Level { get; set; } + [Backlink(nameof(GameLevel.Reviews))] + public IQueryable Level { get; } public GameUser Publisher { get; set; } @@ -18,17 +19,5 @@ public partial class GameReview : IRealmObject, ISequentialId public string Labels { get; set; } - public bool Deleted { get; set; } - - private int _DeletedBy { get; set; } - - [Ignored] - public ReviewDeletedBy DeletedBy - { - //realm shenanigans - get => (ReviewDeletedBy)this._DeletedBy; - set => this._DeletedBy = (int)value; - } - public string Text { get; set; } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Reviews/ReviewDeletedBy.cs b/Refresh.GameServer/Types/Reviews/ReviewDeletedBy.cs new file mode 100644 index 00000000..332e59ce --- /dev/null +++ b/Refresh.GameServer/Types/Reviews/ReviewDeletedBy.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; + +namespace Refresh.GameServer.Types.Reviews; + +[XmlRoot("deleted_by")] +public enum ReviewDeletedBy +{ + [XmlEnum(Name = "none")] + None, +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs index 5709c45d..984ee7fd 100644 --- a/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs +++ b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs @@ -1,23 +1,14 @@ using System.Xml.Serialization; -using Realms; +using Refresh.GameServer.Database; +using Refresh.GameServer.Endpoints.ApiV3.DataTypes; using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.UserData; namespace Refresh.GameServer.Types.Reviews; -[XmlRoot("deleted_by")] -public enum ReviewDeletedBy -{ - [XmlEnum(Name = "none")] - None, - [XmlEnum(Name = "moderator")] - Moderator, - [XmlEnum(Name = "level_author")] - LevelAuthor, -} - [XmlRoot("review")] [XmlType("review")] -public class SerializedGameReview +public class SerializedGameReview : IDataConvertableFrom { [XmlElement("id")] public int Id { get; set; } @@ -42,7 +33,10 @@ public class SerializedGameReview [XmlElement("text")] public string Text { get; set; } = ""; - + + /// + /// The rating the user has on the level. + /// [XmlElement("thumb")] public int Thumb { get; set; } @@ -55,27 +49,36 @@ public class SerializedGameReview [XmlElement("yourthumb")] public RatingType YourThumb { get; set; } - public SerializedGameReview FromOld(GameReview review) + public static SerializedGameReview? FromOld(GameReview? review) { + if (review == null) + return null; + return new SerializedGameReview { Id = review.SequentialId, Slot = new GameReviewSlot { - SlotType = review.Level.Source.ToGameType(), - SlotId = review.Level.LevelId, + SlotType = review.Level.First().Source.ToGameType(), + SlotId = review.Level.First().LevelId, }, Reviewer = review.Publisher.Username, Timestamp = review.Timestamp, Labels = review.Labels, - Deleted = review.Deleted, - DeletedBy = review.DeletedBy, - //If the review is deleted, dont send the review text - Text = review.Deleted ? "" : review.Text, - Thumb = 0, + Deleted = false, + DeletedBy = ReviewDeletedBy.None, + Text = review.Text, + Thumb = review.Level.First().Ratings.FirstOrDefault(r => r.User == review.Publisher)?.RatingType.ToDPad() ?? 0, ThumbsUp = 0, ThumbsDown = 0, YourThumb = 0, }; } + + public void FillInExtraData(GameDatabaseContext database, GameUser user) + { + //TODO: fill in this.YourThumb + } + + public static IEnumerable FromOldList(IEnumerable oldList) => oldList.Select(FromOld).ToList()!; } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs b/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs index 5a123ef8..7cfd98a9 100644 --- a/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs +++ b/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs @@ -1,16 +1,16 @@ using System.Xml.Serialization; +using Refresh.GameServer.Types.Lists; namespace Refresh.GameServer.Types.Reviews; +#nullable disable + [XmlRoot("reviews")] -public class SerializedGameReviewResponse +public class SerializedGameReviewResponse(List items) : SerializedList { [XmlElement("review")] - public List Reviews { get; set; } + public sealed override List Items { get; set; } = items; [XmlAttribute("hint")] - public long Hint { get; set; } - - [XmlAttribute("hint_start")] - public int HintStart { get; set; } + public string Hint { get; set; } = string.Empty; } \ No newline at end of file From 5f3affbbd94f45f33caf3aeee4e34583ed72a721 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Wed, 24 Jan 2024 22:40:32 -0800 Subject: [PATCH 08/15] Dont dispose database in unit tests thanks realm --- RefreshTests.GameServer/Tests/Users/UserRetrievalTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/RefreshTests.GameServer/Tests/Users/UserRetrievalTests.cs b/RefreshTests.GameServer/Tests/Users/UserRetrievalTests.cs index bd8a8e4a..52611932 100644 --- a/RefreshTests.GameServer/Tests/Users/UserRetrievalTests.cs +++ b/RefreshTests.GameServer/Tests/Users/UserRetrievalTests.cs @@ -21,7 +21,6 @@ public void SetUp() public void TearDown() { this._context.Dispose(); - this._db.Dispose(); } [Test] From 0e69cb74d1c8ebae7a1c2ce04546cdead5068b26 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Thu, 25 Jan 2024 18:10:20 -0800 Subject: [PATCH 09/15] Add unit tests --- .../Database/GameDatabaseContext.Relations.cs | 26 ++- .../Endpoints/Game/ReviewEndpoints.cs | 31 +++- .../Reviews/SerializedGameReviewResponse.cs | 10 +- .../Tests/Relations/ReviewTests.cs | 155 ++++++++++++++++++ 4 files changed, 198 insertions(+), 24 deletions(-) create mode 100644 RefreshTests.GameServer/Tests/Relations/ReviewTests.cs diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 223ef481..1fcecd00 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -218,30 +218,28 @@ public bool RateLevel(GameLevel level, GameUser user, RatingType type) return true; } - public bool AddReviewToLevel(SerializedGameReview body, GameLevel level, GameUser user) + /// + /// Adds a review to the database, deleting any old ones by the user on that level. + /// + /// The review to add + /// The level the review is for + /// The user who made the review + public void AddReviewToLevel(GameReview review, GameLevel level) { - List toRemove = level.Reviews.Where(r => r.Publisher.UserId == user.UserId).ToList(); + List toRemove = level.Reviews.Where(r => r.Publisher.UserId == review.Publisher.UserId).ToList(); if (toRemove.Count > 0) { this._realm.Write(() => { - foreach (GameReview review in toRemove) + foreach (GameReview reviewToDelete in toRemove) { - level.Reviews.Remove(review); - this._realm.Remove(review); + level.Reviews.Remove(reviewToDelete); + this._realm.Remove(reviewToDelete); } }); } - this.AddSequentialObject(new GameReview - { - Publisher = user, - Timestamp = this._time.TimestampMilliseconds, - Labels = body.Labels, - Text = body.Text, - }, level.Reviews); - - return true; + this.AddSequentialObject(review, level.Reviews); } public IQueryable GetReviewsByUser(GameUser user) diff --git a/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs b/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs index d9b571fa..b461bd8f 100644 --- a/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs @@ -6,6 +6,7 @@ using Bunkum.Protocols.Http; using Refresh.GameServer.Database; using Refresh.GameServer.Extensions; +using Refresh.GameServer.Time; using Refresh.GameServer.Types.Levels; using Refresh.GameServer.Types.Reviews; using Refresh.GameServer.Types.UserData; @@ -100,7 +101,15 @@ public Response GetReviewsForLevel(RequestContext context, GameDatabaseContext d } [GameEndpoint("postReview/{slotType}/{levelId}", ContentType.Xml, HttpMethods.Post)] - public Response PostReviewForLevel(RequestContext context, GameDatabaseContext database, string slotType, int levelId, SerializedGameReview body, GameUser user) + public Response PostReviewForLevel( + RequestContext context, + GameDatabaseContext database, + string slotType, + int levelId, + SerializedGameReview body, + GameUser user, + IDateTimeProvider timeProvider + ) { GameLevel? level; switch (slotType) @@ -114,17 +123,23 @@ public Response PostReviewForLevel(RequestContext context, GameDatabaseContext d default: return BadRequest; } - - if(level == null) + + if (level == null) return NotFound; - + //You cant review a level you haven't played. - if (!database.HasUserPlayedLevel(level, user)) + if (!database.HasUserPlayedLevel(level, user)) return BadRequest; - if (!database.AddReviewToLevel(body, level, user)) - return BadRequest; - + //Add the review to the database + database.AddReviewToLevel(new GameReview + { + Publisher = user, + Timestamp = timeProvider.TimestampMilliseconds, + Labels = body.Labels, + Text = body.Text, + }, level); + return OK; } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs b/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs index 7cfd98a9..e2e591b6 100644 --- a/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs +++ b/Refresh.GameServer/Types/Reviews/SerializedGameReviewResponse.cs @@ -6,10 +6,16 @@ namespace Refresh.GameServer.Types.Reviews; #nullable disable [XmlRoot("reviews")] -public class SerializedGameReviewResponse(List items) : SerializedList +public class SerializedGameReviewResponse : SerializedList { + public SerializedGameReviewResponse() {} + + public SerializedGameReviewResponse(List items) + { + this.Items = items; + } [XmlElement("review")] - public sealed override List Items { get; set; } = items; + public sealed override List Items { get; set; } = null!; [XmlAttribute("hint")] public string Hint { get; set; } = string.Empty; diff --git a/RefreshTests.GameServer/Tests/Relations/ReviewTests.cs b/RefreshTests.GameServer/Tests/Relations/ReviewTests.cs new file mode 100644 index 00000000..1d9fdd74 --- /dev/null +++ b/RefreshTests.GameServer/Tests/Relations/ReviewTests.cs @@ -0,0 +1,155 @@ +using Refresh.GameServer.Authentication; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Reviews; +using Refresh.GameServer.Types.UserData; +using RefreshTests.GameServer.Extensions; + +namespace RefreshTests.GameServer.Tests.Relations; + +public class ReviewTests : GameServerTest +{ + [TestCase("user")] + [TestCase("developer")] + public void TestPostReview(string slotType) + { + using TestContext context = this.GetServer(); + + GameUser levelPublisher = context.CreateUser(); + GameUser reviewPublisher = context.CreateUser(); + + GameLevel level = slotType == "developer" ? context.Database.GetStoryLevelById(1) : context.CreateLevel(levelPublisher); + + context.Database.PlayLevel(level, reviewPublisher, 1); + context.Database.Refresh(); + + using HttpClient reviewerClient = context.GetAuthenticatedClient(TokenType.Game, reviewPublisher); + + SerializedGameReview review = new() + { + Labels = "LABEL_SurvivalChallenge", + Text = "moku Sutolokanopu", + }; + + Assert.That(reviewerClient.PostAsync($"/lbp/postReview/{slotType}/{level.LevelId}", new StringContent(review.AsXML())).Result.StatusCode, Is.EqualTo(OK)); + + HttpResponseMessage response = reviewerClient.GetAsync($"/lbp/reviewsFor/{slotType}/{level.LevelId}").Result; + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + SerializedGameReviewResponse levelReviews = response.Content.ReadAsXML(); + Assert.That(levelReviews.Items, Has.Count.EqualTo(1)); + Assert.That(levelReviews.Items[0].Text, Is.EqualTo(review.Text)); + Assert.That(levelReviews.Items[0].Labels, Is.EqualTo(review.Labels)); + + response = reviewerClient.GetAsync($"/lbp/reviewsBy/{reviewPublisher.Username}").Result; + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + levelReviews = response.Content.ReadAsXML(); + Assert.That(levelReviews.Items, Has.Count.EqualTo(1)); + Assert.That(levelReviews.Items[0].Text, Is.EqualTo(review.Text)); + Assert.That(levelReviews.Items[0].Labels, Is.EqualTo(review.Labels)); + } + + [Test] + public void CantGetReviewsByInvalidUser() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); + + Assert.That(client.GetAsync("/lbp/reviewsBy/I_AM_NOT_REAL").Result.StatusCode, Is.EqualTo(NotFound)); + } + + [Test] + public void CantGetReviewsOnInvalidLevelSlotType() + { + using TestContext context = this.GetServer(); + + GameUser user = context.CreateUser(); + GameLevel level = context.CreateLevel(user); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); + + Assert.That(client.GetAsync($"/lbp/reviewsFor/badType/{level.LevelId}").Result.StatusCode, Is.EqualTo(BadRequest)); + } + + [Test] + public void CantGetReviewsOnInvalidLevelId() + { + using TestContext context = this.GetServer(); + + GameUser user = context.CreateUser(); + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); + + Assert.That(client.GetAsync($"/lbp/reviewsFor/user/{int.MaxValue}").Result.StatusCode, Is.EqualTo(NotFound)); + } + + [Test] + public void CantPostReviewOnInvalidSlotType() + { + using TestContext context = this.GetServer(); + + GameUser levelPublisher = context.CreateUser(); + GameUser reviewPublisher = context.CreateUser(); + + GameLevel level = context.CreateLevel(levelPublisher); + + context.Database.PlayLevel(level, reviewPublisher, 1); + context.Database.Refresh(); + + using HttpClient reviewerClient = context.GetAuthenticatedClient(TokenType.Game, reviewPublisher); + + SerializedGameReview review = new() + { + Labels = "LABEL_SurvivalChallenge", + Text = "moku Sutolokanopu", + }; + + Assert.That(reviewerClient.PostAsync($"/lbp/postReview/badType/{level.LevelId}", new StringContent(review.AsXML())).Result.StatusCode, Is.EqualTo(BadRequest)); + + context.Database.Refresh(); + Assert.That(level.Reviews, Is.Empty); + } + + [Test] + public void CantPostReviewOnInvalidLevel() + { + using TestContext context = this.GetServer(); + + GameUser reviewPublisher = context.CreateUser(); + + using HttpClient reviewerClient = context.GetAuthenticatedClient(TokenType.Game, reviewPublisher); + + SerializedGameReview review = new() + { + Labels = "LABEL_SurvivalChallenge", + Text = "moku Sutolokanopu", + }; + + Assert.That(reviewerClient.PostAsync($"/lbp/postReview/user/{int.MaxValue}", new StringContent(review.AsXML())).Result.StatusCode, Is.EqualTo(NotFound)); + } + + [Test] + public void CantPostReviewOnUnplayedLevel() + { + using TestContext context = this.GetServer(); + + GameUser levelPublisher = context.CreateUser(); + GameUser reviewPublisher = context.CreateUser(); + + GameLevel level = context.CreateLevel(levelPublisher); + + using HttpClient reviewerClient = context.GetAuthenticatedClient(TokenType.Game, reviewPublisher); + + SerializedGameReview review = new() + { + Labels = "LABEL_SurvivalChallenge", + Text = "moku Sutolokanopu", + }; + + Assert.That(reviewerClient.PostAsync($"/lbp/postReview/user/{level.LevelId}", new StringContent(review.AsXML())).Result.StatusCode, Is.EqualTo(BadRequest)); + + context.Database.Refresh(); + Assert.That(level.Reviews, Is.Empty); + } +} \ No newline at end of file From 59605c4c1ca8c961dff4d9681a3d889bcec092b6 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 28 Feb 2024 18:27:44 -0500 Subject: [PATCH 10/15] API Data Type --- .../Response/ApiGameReviewResponse.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs new file mode 100644 index 00000000..0c334e5f --- /dev/null +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs @@ -0,0 +1,29 @@ +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Reviews; + +namespace Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response; + +public class ApiGameReviewResponse : IDataConvertableFrom +{ + public required int ReviewId { get; set; } + public required ApiGameLevelResponse Level { get; set; } + public required ApiGameUserResponse Publisher { get; set; } + public required DateTimeOffset PostedAt { get; set; } + public required string Labels { get; set; } + public required string Text { get; set; } + public static ApiGameReviewResponse? FromOld(GameReview? old) + { + if (old == null) return null; + return new ApiGameReviewResponse + { + ReviewId = old.SequentialId, + Level = ApiGameLevelResponse.FromOld(old.Level.First())!, + Publisher = ApiGameUserResponse.FromOld(old.Publisher)!, + PostedAt = DateTimeOffset.FromUnixTimeMilliseconds(old.Timestamp), + Labels = old.Labels, + Text = old.Text, + }; + } + + public static IEnumerable FromOldList(IEnumerable oldList) => oldList.Select(FromOld).ToList()!; +} \ No newline at end of file From 2e33185138029fc32fa159a6d9fcd3221489a81f Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 28 Feb 2024 18:30:23 -0500 Subject: [PATCH 11/15] Refactor GameReview model to be consistent with other models --- .../DataTypes/Response/ApiGameReviewResponse.cs | 8 ++++---- .../Endpoints/Game/ReviewEndpoints.cs | 4 ++-- Refresh.GameServer/Types/Reviews/GameReview.cs | 17 +++++++++++------ .../Types/Reviews/SerializedGameReview.cs | 12 ++++++------ 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs index 0c334e5f..85f7ecb9 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs @@ -16,12 +16,12 @@ public class ApiGameReviewResponse : IDataConvertableFrom Level { get; } + public GameLevel Level { get; set; } public GameUser Publisher { get; set; } - public long Timestamp { get; set; } + public DateTimeOffset PostedAt { get; set; } public string Labels { get; set; } - public string Text { get; set; } + public string Content { get; set; } + + public int SequentialId + { + set => this.ReviewId = value; + } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs index 984ee7fd..4d5c49f5 100644 --- a/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs +++ b/Refresh.GameServer/Types/Reviews/SerializedGameReview.cs @@ -56,19 +56,19 @@ public class SerializedGameReview : IDataConvertableFrom r.User == review.Publisher)?.RatingType.ToDPad() ?? 0, + Text = review.Content, + Thumb = review.Level.Ratings.FirstOrDefault(r => r.User == review.Publisher)?.RatingType.ToDPad() ?? 0, ThumbsUp = 0, ThumbsDown = 0, YourThumb = 0, From cb7a370878d3319451a24009927f6ce8b61d5224 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 28 Feb 2024 18:32:36 -0500 Subject: [PATCH 12/15] Set level relation on reviews properly --- Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs b/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs index 1d664fe7..a5f5a74e 100644 --- a/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs @@ -135,6 +135,7 @@ IDateTimeProvider timeProvider database.AddReviewToLevel(new GameReview { Publisher = user, + Level = level, PostedAt = timeProvider.Now, Labels = body.Labels, Content = body.Text, From 5654c7383f4e5ef4753b23ca03d2ab0b227571ac Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 28 Feb 2024 18:47:46 -0500 Subject: [PATCH 13/15] Fix illegal realm usage --- .../Database/GameDatabaseContext.Relations.cs | 14 ++++++++++++-- .../DataTypes/Response/ApiGameReviewResponse.cs | 2 +- .../Game/DataTypes/Response/GameUserResponse.cs | 2 +- .../Endpoints/Game/ReviewEndpoints.cs | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs index 1fcecd00..c98eef3a 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Relations.cs @@ -242,10 +242,14 @@ public void AddReviewToLevel(GameReview review, GameLevel level) this.AddSequentialObject(review, level.Reviews); } - public IQueryable GetReviewsByUser(GameUser user) + public DatabaseList GetReviewsByUser(GameUser user, int count, int skip) { - return this._realm.All().Where(r => r.Publisher == user); + return new DatabaseList(this._realm.All() + .Where(r => r.Publisher == user), skip, count); } + + public int GetTotalReviewsByUser(GameUser user) + => this._realm.All().Count(r => r.Publisher == user); public void DeleteReview(GameReview review) { @@ -257,6 +261,12 @@ public void DeleteReview(GameReview review) return level.Reviews.FirstOrDefault(r => r.Publisher.UserId == user.UserId); } + public DatabaseList GetReviewsForLevel(GameLevel level, int count, int skip) + { + return new DatabaseList(this._realm.All() + .Where(r => r.Level == level), skip, count); + } + #endregion #region Playing diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs index 85f7ecb9..4e3edbcf 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs @@ -3,7 +3,7 @@ namespace Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response; -public class ApiGameReviewResponse : IDataConvertableFrom +public class ApiGameReviewResponse : IApiResponse, IDataConvertableFrom { public required int ReviewId { get; set; } public required ApiGameLevelResponse Level { get; set; } diff --git a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs index 420a4cb3..a27ca108 100644 --- a/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs +++ b/Refresh.GameServer/Endpoints/Game/DataTypes/Response/GameUserResponse.cs @@ -110,7 +110,7 @@ private void FillInExtraData(GameUser old, TokenGame gameVersion, GameDatabaseCo return; } - this.ReviewCount = database.GetReviewsByUser(old).Count(); + this.ReviewCount = database.GetTotalReviewsByUser(old); this.PlanetsHash = gameVersion switch { diff --git a/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs b/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs index a5f5a74e..8e0d2e90 100644 --- a/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/ReviewEndpoints.cs @@ -83,7 +83,7 @@ public Response GetReviewsForLevel(RequestContext context, GameDatabaseContext d (int skip, int count) = context.GetPageData(); - return new Response(new SerializedGameReviewResponse(items: SerializedGameReview.FromOldList(new DatabaseList(level.Reviews.AsEnumerable(), skip, count).Items).ToList()), ContentType.Xml); + return new Response(new SerializedGameReviewResponse(items: SerializedGameReview.FromOldList(database.GetReviewsForLevel(level, count, skip).Items).ToList()), ContentType.Xml); } [GameEndpoint("reviewsBy/{username}", ContentType.Xml)] @@ -97,7 +97,7 @@ public Response GetReviewsForLevel(RequestContext context, GameDatabaseContext d (int skip, int count) = context.GetPageData(); - return new Response(new SerializedGameReviewResponse(items: SerializedGameReview.FromOldList(new DatabaseList(database.GetReviewsByUser(user), skip, count).Items).ToList()), ContentType.Xml); + return new Response(new SerializedGameReviewResponse(SerializedGameReview.FromOldList(database.GetReviewsByUser(user, count, skip).Items).ToList()), ContentType.Xml); } [GameEndpoint("postReview/{slotType}/{levelId}", ContentType.Xml, HttpMethods.Post)] From 8b6046eb146a0fcae32a8b2f1832333aa9029288 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 28 Feb 2024 18:52:54 -0500 Subject: [PATCH 14/15] Fix ApiGameReviewResponse not using CamelCaseNamingStrategy --- .../Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs index 4e3edbcf..b0cf9140 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiGameReviewResponse.cs @@ -3,6 +3,7 @@ namespace Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response; +[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] public class ApiGameReviewResponse : IApiResponse, IDataConvertableFrom { public required int ReviewId { get; set; } From 8129918fed321393d0aec8881ae763de9c624c3a Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 28 Feb 2024 18:53:30 -0500 Subject: [PATCH 15/15] API endpoint for retrieving reviews --- .../Endpoints/ApiV3/ReviewApiEndpoints.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Refresh.GameServer/Endpoints/ApiV3/ReviewApiEndpoints.cs diff --git a/Refresh.GameServer/Endpoints/ApiV3/ReviewApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/ReviewApiEndpoints.cs new file mode 100644 index 00000000..599c2343 --- /dev/null +++ b/Refresh.GameServer/Endpoints/ApiV3/ReviewApiEndpoints.cs @@ -0,0 +1,35 @@ +using AttribDoc.Attributes; +using Bunkum.Core; +using Bunkum.Core.Endpoints; +using Bunkum.Core.Storage; +using Refresh.GameServer.Database; +using Refresh.GameServer.Documentation.Attributes; +using Refresh.GameServer.Endpoints.ApiV3.ApiTypes; +using Refresh.GameServer.Endpoints.ApiV3.ApiTypes.Errors; +using Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response; +using Refresh.GameServer.Extensions; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Reviews; + +namespace Refresh.GameServer.Endpoints.ApiV3; + +public class ReviewApiEndpoints : EndpointGroup +{ + [ApiV3Endpoint("levels/id/{id}/reviews"), Authentication(false)] + [DocUsesPageData, DocSummary("Gets a list of the reviews posted to a level.")] + [DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)] + public ApiListResponse GetTopScoresForLevel(RequestContext context, + GameDatabaseContext database, IDataStore dataStore, + [DocSummary("The ID of the level")] int id) + { + GameLevel? level = database.GetLevelById(id); + if (level == null) return ApiNotFoundError.LevelMissingError; + + (int skip, int count) = context.GetPageData(true); + + DatabaseList reviews = database.GetReviewsForLevel(level, count, skip); + DatabaseList ret = DatabaseList.FromOldList(reviews); + + return ret; + } +} \ No newline at end of file