diff --git a/ProjectLighthouse/Controllers/GameApi/CommentController.cs b/ProjectLighthouse/Controllers/GameApi/CommentController.cs index e2ca484c..13bce479 100644 --- a/ProjectLighthouse/Controllers/GameApi/CommentController.cs +++ b/ProjectLighthouse/Controllers/GameApi/CommentController.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Levels; @@ -78,16 +79,18 @@ public class CommentController : ControllerBase [HttpPost("postComment/user/{slotId:int}")] public async Task PostComment(string? username, int? slotId) { + User? poster = await this.database.UserFromGameRequest(this.Request); + if (poster == null) return this.StatusCode(403, ""); + this.Request.Body.Position = 0; string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); XmlSerializer serializer = new(typeof(Comment)); Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString)); - CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level); + SanitizationHelper.SanitizeStringsInClass(comment); - User? poster = await this.database.UserFromGameRequest(this.Request); - if (poster == null) return this.StatusCode(403, ""); + CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level); if (comment == null) return this.BadRequest(); diff --git a/ProjectLighthouse/Controllers/GameApi/FriendsController.cs b/ProjectLighthouse/Controllers/GameApi/FriendsController.cs index 6fb6e1dc..f2a1159f 100644 --- a/ProjectLighthouse/Controllers/GameApi/FriendsController.cs +++ b/ProjectLighthouse/Controllers/GameApi/FriendsController.cs @@ -37,6 +37,8 @@ public class FriendsController : ControllerBase NPData? npData = (NPData?)serializer.Deserialize(new StringReader(bodyString)); if (npData == null) return this.BadRequest(); + SanitizationHelper.SanitizeStringsInClass(npData); + List friends = new(); foreach (string friendName in npData.Friends) { diff --git a/ProjectLighthouse/Controllers/GameApi/ReportController.cs b/ProjectLighthouse/Controllers/GameApi/ReportController.cs index b035d387..d3808a19 100644 --- a/ProjectLighthouse/Controllers/GameApi/ReportController.cs +++ b/ProjectLighthouse/Controllers/GameApi/ReportController.cs @@ -36,6 +36,8 @@ public class ReportController : ControllerBase if (report == null) return this.BadRequest(); + SanitizationHelper.SanitizeStringsInClass(report); + report.Bounds = JsonSerializer.Serialize(report.XmlBounds.Rect, typeof(Rectangle)); report.Players = JsonSerializer.Serialize(report.XmlPlayers, typeof(ReportPlayer[])); report.Timestamp = TimeHelper.UnixTimeMilliseconds(); diff --git a/ProjectLighthouse/Controllers/GameApi/Resources/PhotosController.cs b/ProjectLighthouse/Controllers/GameApi/Resources/PhotosController.cs index cee06cf8..b4149f2a 100644 --- a/ProjectLighthouse/Controllers/GameApi/Resources/PhotosController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Resources/PhotosController.cs @@ -44,6 +44,8 @@ public class PhotosController : ControllerBase Photo? photo = (Photo?)serializer.Deserialize(new StringReader(bodyString)); if (photo == null) return this.BadRequest(); + SanitizationHelper.SanitizeStringsInClass(photo); + foreach (Photo p in this.database.Photos.Where(p => p.CreatorId == user.UserId)) { if (p.LargeHash == photo.LargeHash) return this.Ok(); // photo already uplaoded diff --git a/ProjectLighthouse/Controllers/GameApi/Resources/ResourcesController.cs b/ProjectLighthouse/Controllers/GameApi/Resources/ResourcesController.cs index 0e2d5ae1..8162030f 100644 --- a/ProjectLighthouse/Controllers/GameApi/Resources/ResourcesController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Resources/ResourcesController.cs @@ -30,7 +30,7 @@ public class ResourcesController : ControllerBase string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); XmlSerializer serializer = new(typeof(ResourceList)); - ResourceList resourceList = (ResourceList)serializer.Deserialize(new StringReader(bodyString)); + ResourceList? resourceList = (ResourceList?)serializer.Deserialize(new StringReader(bodyString)); if (resourceList == null) return this.BadRequest(); diff --git a/ProjectLighthouse/Controllers/GameApi/Slots/PublishController.cs b/ProjectLighthouse/Controllers/GameApi/Slots/PublishController.cs index 60bae1f2..032164f8 100644 --- a/ProjectLighthouse/Controllers/GameApi/Slots/PublishController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Slots/PublishController.cs @@ -85,7 +85,14 @@ public class PublishController : ControllerBase User user = userAndToken.Value.Item1; GameToken gameToken = userAndToken.Value.Item2; Slot? slot = await this.getSlotFromBody(); - if (slot?.Location == null) return this.BadRequest(); + + if (slot == null) return this.BadRequest(); + + if (slot.Location == null) return this.BadRequest(); + + if (slot.Description.Length > 200) return this.BadRequest(); + + if (slot.Name.Length > 100) return this.BadRequest(); foreach (string resource in slot.Resources) { @@ -219,6 +226,8 @@ public class PublishController : ControllerBase XmlSerializer serializer = new(typeof(Slot)); Slot? slot = (Slot?)serializer.Deserialize(new StringReader(bodyString)); + + SanitizationHelper.SanitizeStringsInClass(slot); return slot; } diff --git a/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs b/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs index 161dd7a8..ab074769 100644 --- a/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs @@ -41,10 +41,12 @@ public class ReviewController : ControllerBase RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId); if (ratedLevel == null) { - ratedLevel = new RatedLevel(); - ratedLevel.SlotId = slotId; - ratedLevel.UserId = user.UserId; - ratedLevel.Rating = 0; + ratedLevel = new RatedLevel + { + SlotId = slotId, + UserId = user.UserId, + Rating = 0, + }; this.database.RatedLevels.Add(ratedLevel); } @@ -68,10 +70,12 @@ public class ReviewController : ControllerBase RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId); if (ratedLevel == null) { - ratedLevel = new RatedLevel(); - ratedLevel.SlotId = slotId; - ratedLevel.UserId = user.UserId; - ratedLevel.RatingLBP1 = 0; + ratedLevel = new RatedLevel + { + SlotId = slotId, + UserId = user.UserId, + RatingLBP1 = 0, + }; this.database.RatedLevels.Add(ratedLevel); } @@ -91,18 +95,23 @@ public class ReviewController : ControllerBase User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); - Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId); Review? newReview = await this.getReviewFromBody(); if (newReview == null) return this.BadRequest(); + if (newReview.Text.Length > 100) return this.BadRequest(); + + Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId); + if (review == null) { - review = new Review(); - review.SlotId = slotId; - review.ReviewerId = user.UserId; - review.DeletedBy = DeletedBy.None; - review.ThumbsUp = 0; - review.ThumbsDown = 0; + review = new Review + { + SlotId = slotId, + ReviewerId = user.UserId, + DeletedBy = DeletedBy.None, + ThumbsUp = 0, + ThumbsDown = 0, + }; this.database.Reviews.Add(review); } review.Thumb = newReview.Thumb; @@ -115,10 +124,12 @@ public class ReviewController : ControllerBase RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == slotId && r.UserId == user.UserId); if (ratedLevel == null) { - ratedLevel = new RatedLevel(); - ratedLevel.SlotId = slotId; - ratedLevel.UserId = user.UserId; - ratedLevel.RatingLBP1 = 0; + ratedLevel = new RatedLevel + { + SlotId = slotId, + UserId = user.UserId, + RatingLBP1 = 0, + }; this.database.RatedLevels.Add(ratedLevel); } @@ -149,12 +160,14 @@ public class ReviewController : ControllerBase .Where(r => r.SlotId == slotId) .Include(r => r.Reviewer) .Include(r => r.Slot) - .OrderByDescending(r => r.ThumbsUp) + .OrderByDescending(r => r.ThumbsUp - r.ThumbsDown) .ThenByDescending(r => r.Timestamp) .Skip(pageStart - 1) .Take(pageSize); - string inner = reviews.ToList() + List reviewList = reviews.ToList(); + + string inner = reviewList .Aggregate ( string.Empty, @@ -162,10 +175,10 @@ public class ReviewController : ControllerBase { if (review == null) return current; - return current + review.Serialize(); + RatedReview? yourThumb = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId); + return current + review.Serialize(null, yourThumb); } ); - string response = LbpSerializer.TaggedStringElement ( "reviews", @@ -176,7 +189,7 @@ public class ReviewController : ControllerBase "hint_start", pageStart + pageSize }, { - "hint", pageStart // not sure + "hint", reviewList.LastOrDefault()!.Timestamp // not sure }, } ); @@ -197,22 +210,24 @@ public class ReviewController : ControllerBase GameVersion gameVersion = gameToken.GameVersion; IEnumerable reviews = this.database.Reviews.ByGameVersion(gameVersion, true) - .Where(r => r.Reviewer.Username == username) .Include(r => r.Reviewer) .Include(r => r.Slot) + .Where(r => r.Reviewer!.Username == username) .OrderByDescending(r => r.Timestamp) .Skip(pageStart - 1) - .Take(pageSize) - .AsEnumerable(); + .Take(pageSize); - string inner = reviews.Aggregate + List reviewList = reviews.ToList(); + + string inner = reviewList.Aggregate ( string.Empty, (current, review) => { - //RatedLevel? ratedLevel = this.database.RatedLevels.FirstOrDefault(r => r.SlotId == review.SlotId && r.UserId == user.UserId); - //RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId); - return current + review.Serialize( /*, ratedReview*/); + if (review == null) return current; + + RatedReview? ratedReview = this.database.RatedReviews.FirstOrDefault(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId); + return current + review.Serialize(null, ratedReview); } ); @@ -226,7 +241,7 @@ public class ReviewController : ControllerBase "hint_start", pageStart }, { - "hint", reviews.Last().Timestamp // Seems to be the timestamp of oldest + "hint", reviewList.LastOrDefault()!.Timestamp // Seems to be the timestamp of oldest }, } ); @@ -249,25 +264,40 @@ public class ReviewController : ControllerBase RatedReview? ratedReview = await this.database.RatedReviews.FirstOrDefaultAsync(r => r.ReviewId == review.ReviewId && r.UserId == user.UserId); if (ratedReview == null) { - ratedReview = new RatedReview(); - ratedReview.ReviewId = review.ReviewId; - ratedReview.UserId = user.UserId; - ratedReview.Thumb = 0; + ratedReview = new RatedReview + { + ReviewId = review.ReviewId, + UserId = user.UserId, + Thumb = 0, + }; this.database.RatedReviews.Add(ratedReview); + await this.database.SaveChangesAsync(); } - int oldThumb = ratedReview.Thumb; - ratedReview.Thumb = Math.Max(Math.Min(1, rating), -1); + int oldRating = ratedReview.Thumb; + ratedReview.Thumb = Math.Clamp(rating, -1, 1); + if (oldRating == ratedReview.Thumb) return this.Ok(); - if (oldThumb != ratedReview.Thumb) + // if the user's rating changed then we recount the review's ratings to ensure accuracy + List reactions = await this.database.RatedReviews.Where(r => r.ReviewId == review.ReviewId).ToListAsync(); + int yay = 0; + int boo = 0; + foreach (RatedReview r in reactions) { - if (oldThumb == -1) review.ThumbsDown--; - else if (oldThumb == 1) review.ThumbsUp--; - - if (ratedReview.Thumb == -1) review.ThumbsDown++; - else if (ratedReview.Thumb == 1) review.ThumbsUp++; + switch (r.Thumb) + { + case -1: + boo++; + break; + case 1: + yay++; + break; + } } + review.ThumbsDown = boo; + review.ThumbsUp = yay; + await this.database.SaveChangesAsync(); return this.Ok(); @@ -296,7 +326,7 @@ public class ReviewController : ControllerBase XmlSerializer serializer = new(typeof(Review)); Review? review = (Review?)serializer.Deserialize(new StringReader(bodyString)); - + SanitizationHelper.SanitizeStringsInClass(review); return review; } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/GameApi/Slots/ScoreController.cs b/ProjectLighthouse/Controllers/GameApi/Slots/ScoreController.cs index 95df5843..4db9d290 100644 --- a/ProjectLighthouse/Controllers/GameApi/Slots/ScoreController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Slots/ScoreController.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Levels; @@ -43,6 +44,8 @@ public class ScoreController : ControllerBase Score? score = (Score?)serializer.Deserialize(new StringReader(bodyString)); if (score == null) return this.BadRequest(); + SanitizationHelper.SanitizeStringsInClass(score); + score.SlotId = id; Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId); diff --git a/ProjectLighthouse/Controllers/GameApi/UserController.cs b/ProjectLighthouse/Controllers/GameApi/UserController.cs index 9eb309da..a45419c1 100644 --- a/ProjectLighthouse/Controllers/GameApi/UserController.cs +++ b/ProjectLighthouse/Controllers/GameApi/UserController.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Profiles; @@ -93,7 +94,19 @@ public class UserController : ControllerBase if (update == null) return this.BadRequest(); - if (update.Biography != null) user.Biography = update.Biography; + SanitizationHelper.SanitizeStringsInClass(update); + + if (update.Biography != null) + { + if (update.Biography.Length > 100) return this.BadRequest(); + + user.Biography = update.Biography; + } + + foreach (string? resource in new[] {update.IconHash, update.YayHash, update.MehHash, update.BooHash, update.PlanetHash,}) + { + if (resource != null && !FileHelper.ResourceExists(resource)) return this.BadRequest(); + } if (update.IconHash != null) user.IconHash = update.IconHash; diff --git a/ProjectLighthouse/Controllers/Website/SlotPageController.cs b/ProjectLighthouse/Controllers/Website/SlotPageController.cs index 24968595..f9ccfc91 100644 --- a/ProjectLighthouse/Controllers/Website/SlotPageController.cs +++ b/ProjectLighthouse/Controllers/Website/SlotPageController.cs @@ -1,6 +1,7 @@ #nullable enable using System.Threading.Tasks; using Kettu; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Levels; @@ -48,6 +49,8 @@ public class SlotPageController : ControllerBase return this.Redirect("~/slot/" + id); } + msg = SanitizationHelper.SanitizeString(msg); + await this.database.PostComment(user, id, CommentType.Level, msg); Logger.Log($"Posted comment from {user.UserId}: \"{msg}\" on user {id}", LoggerLevelComments.Instance); diff --git a/ProjectLighthouse/Controllers/Website/UserPageController.cs b/ProjectLighthouse/Controllers/Website/UserPageController.cs index 627ff511..a5bb7969 100644 --- a/ProjectLighthouse/Controllers/Website/UserPageController.cs +++ b/ProjectLighthouse/Controllers/Website/UserPageController.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Kettu; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Helpers; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -42,6 +43,8 @@ public class UserPageController : ControllerBase return this.Redirect("~/user/" + id); } + msg = SanitizationHelper.SanitizeString(msg); + await this.database.PostComment(user, id, CommentType.Profile, msg); Logger.Log($"Posted comment from {user.UserId}: \"{msg}\" on user {id}", LoggerLevelComments.Instance); diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 9c38ff41..23998cd7 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -138,6 +138,8 @@ public class Database : DbContext reaction = newReaction; } + rating = Math.Clamp(rating, -1, 1); + int oldRating = reaction.Rating; if (oldRating == rating) return true; diff --git a/ProjectLighthouse/Helpers/SanitizationHelper.cs b/ProjectLighthouse/Helpers/SanitizationHelper.cs new file mode 100644 index 00000000..06882f02 --- /dev/null +++ b/ProjectLighthouse/Helpers/SanitizationHelper.cs @@ -0,0 +1,43 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace LBPUnion.ProjectLighthouse.Helpers; + +public static class SanitizationHelper +{ + + private static readonly Dictionary charsToReplace = new() { + {"<", "<"}, + {">", ">"}, + }; + + public static void SanitizeStringsInClass(object? instance) + { + if (instance == null) return; + PropertyInfo[] properties = instance.GetType().GetProperties(); + foreach (PropertyInfo property in properties) + { + if (property.PropertyType != typeof(string)) continue; + + string? before = (string?) property.GetValue(instance); + + if (before == null) continue; + if (!charsToReplace.Keys.Any(k => before.Contains(k))) continue; + + property.SetValue(instance, SanitizeString(before)); + } + } + + public static string SanitizeString(string input) + { + + foreach ((string? key, string? value) in charsToReplace) + { + input = input.Replace(key, value); + } + return input; + } + +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/MaintenanceJobs/CleanupXMLInjection.cs b/ProjectLighthouse/Maintenance/MaintenanceJobs/CleanupXMLInjection.cs new file mode 100644 index 00000000..c504a16e --- /dev/null +++ b/ProjectLighthouse/Maintenance/MaintenanceJobs/CleanupXMLInjection.cs @@ -0,0 +1,36 @@ +#nullable enable +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Levels; +using LBPUnion.ProjectLighthouse.Types.Profiles; +using LBPUnion.ProjectLighthouse.Types.Reports; +using LBPUnion.ProjectLighthouse.Types.Reviews; + +namespace LBPUnion.ProjectLighthouse.Maintenance.MaintenanceJobs; + +public class CleanupXmlInjection : IMaintenanceJob +{ + private readonly Database database = new(); + public string Name() => "Sanitize user content"; + public string Description() => "Sanitizes all user-generated strings in levels, reviews, comments, users, and scores to prevent XML injection. Only needs to be run once."; + + public async Task Run() + { + foreach (Slot slot in this.database.Slots) SanitizationHelper.SanitizeStringsInClass(slot); + + foreach (Review review in this.database.Reviews) SanitizationHelper.SanitizeStringsInClass(review); + + foreach (Comment comment in this.database.Comments) SanitizationHelper.SanitizeStringsInClass(comment); + + foreach (Score score in this.database.Scores) SanitizationHelper.SanitizeStringsInClass(score); + + foreach (User user in this.database.Users) SanitizationHelper.SanitizeStringsInClass(user); + + foreach (Photo photo in this.database.Photos) SanitizationHelper.SanitizeStringsInClass(photo); + + foreach (GriefReport report in this.database.Reports) SanitizationHelper.SanitizeStringsInClass(report); + + await this.database.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/SlotPage.cshtml b/ProjectLighthouse/Pages/SlotPage.cshtml index 09a1a277..e668bb08 100644 --- a/ProjectLighthouse/Pages/SlotPage.cshtml +++ b/ProjectLighthouse/Pages/SlotPage.cshtml @@ -1,4 +1,5 @@ @page "/slot/{id:int}" +@using System.Web @using LBPUnion.ProjectLighthouse.Helpers.Extensions @model LBPUnion.ProjectLighthouse.Pages.SlotPage @@ -31,7 +32,7 @@

Description

-

@(string.IsNullOrEmpty(Model.Slot.Description) ? "This level has no description." : Model.Slot.Description)

+

@HttpUtility.HtmlDecode(string.IsNullOrEmpty(Model.Slot.Description) ? "This level has no description." : Model.Slot.Description)

diff --git a/ProjectLighthouse/Pages/UserPage.cshtml b/ProjectLighthouse/Pages/UserPage.cshtml index f6b1be01..2ef24243 100644 --- a/ProjectLighthouse/Pages/UserPage.cshtml +++ b/ProjectLighthouse/Pages/UserPage.cshtml @@ -1,4 +1,5 @@ @page "/user/{userId:int}" +@using System.Web @using LBPUnion.ProjectLighthouse.Helpers.Extensions @using LBPUnion.ProjectLighthouse.Types @model LBPUnion.ProjectLighthouse.Pages.UserPage @@ -82,7 +83,7 @@ } else { -

@Model.ProfileUser.Biography

+

@HttpUtility.HtmlDecode(Model.ProfileUser.Biography)

}
@@ -120,14 +121,14 @@ @if (!Model.ProfileUser.Banned) { - - - Ban User - - -

+ + } - @await Html.PartialAsync("Partials/AdminSetGrantedSlotsFormPartial", Model.ProfileUser) - } \ No newline at end of file +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Levels/Slot.cs b/ProjectLighthouse/Types/Levels/Slot.cs index a85c6edf..481bdfab 100644 --- a/ProjectLighthouse/Types/Levels/Slot.cs +++ b/ProjectLighthouse/Types/Levels/Slot.cs @@ -131,12 +131,12 @@ public class Slot [XmlIgnore] [NotMapped] [JsonIgnore] - public int Hearts => database.HeartedLevels.Count(s => s.SlotId == this.SlotId); + public int Hearts => this.database.HeartedLevels.Count(s => s.SlotId == this.SlotId); [XmlIgnore] [NotMapped] [JsonIgnore] - public int Comments => database.Comments.Count(c => c.Type == CommentType.Level && c.TargetId == this.SlotId); + public int Comments => this.database.Comments.Count(c => c.Type == CommentType.Level && c.TargetId == this.SlotId); [XmlIgnore] [NotMapped] @@ -201,19 +201,19 @@ public class Slot [NotMapped] [JsonIgnore] [XmlElement("thumbsup")] - public int Thumbsup => database.RatedLevels.Count(r => r.SlotId == this.SlotId && r.Rating == 1); + public int Thumbsup => this.database.RatedLevels.Count(r => r.SlotId == this.SlotId && r.Rating == 1); [NotMapped] [JsonIgnore] [XmlElement("thumbsdown")] - public int Thumbsdown => database.RatedLevels.Count(r => r.SlotId == this.SlotId && r.Rating == -1); + public int Thumbsdown => this.database.RatedLevels.Count(r => r.SlotId == this.SlotId && r.Rating == -1); [NotMapped] [JsonPropertyName("averageRating")] [XmlElement("averageRating")] public double RatingLBP1 { get { - IQueryable ratedLevels = database.RatedLevels.Where(r => r.SlotId == this.SlotId && r.RatingLBP1 > 0); + IQueryable ratedLevels = this.database.RatedLevels.Where(r => r.SlotId == this.SlotId && r.RatingLBP1 > 0); if (!ratedLevels.Any()) return 3.0; return Enumerable.Average(ratedLevels, r => r.RatingLBP1); @@ -223,7 +223,7 @@ public class Slot [NotMapped] [JsonIgnore] [XmlElement("reviewCount")] - public int ReviewCount => database.Reviews.Count(r => r.SlotId == this.SlotId); + public int ReviewCount => this.database.Reviews.Count(r => r.SlotId == this.SlotId); [XmlElement("leveltype")] public string LevelType { get; set; } = ""; diff --git a/ProjectLighthouse/Types/Reviews/Review.cs b/ProjectLighthouse/Types/Reviews/Review.cs index 385233d7..8bc7b620 100644 --- a/ProjectLighthouse/Types/Reviews/Review.cs +++ b/ProjectLighthouse/Types/Reviews/Review.cs @@ -1,8 +1,6 @@ #nullable enable using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.IO; -using System.Xml; using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types.Levels; @@ -21,19 +19,19 @@ public class Review public int ReviewerId { get; set; } [ForeignKey(nameof(ReviewerId))] - public User Reviewer { get; set; } + public User? Reviewer { get; set; } [XmlElement("slot_id")] public int SlotId { get; set; } [ForeignKey(nameof(SlotId))] - public Slot Slot { get; set; } + public Slot? Slot { get; set; } [XmlElement("timestamp")] public long Timestamp { get; set; } [XmlElement("labels")] - public string LabelCollection { get; set; } + public string LabelCollection { get; set; } = ""; [NotMapped] [XmlIgnore] @@ -49,7 +47,7 @@ public class Review public DeletedBy DeletedBy { get; set; } [XmlElement("text")] - public string Text { get; set; } + public string Text { get; set; } = ""; [XmlElement("thumb")] public int Thumb { get; set; } @@ -66,27 +64,26 @@ public class Review public string Serialize(string elementOverride, RatedLevel? yourLevelRating = null, RatedReview? yourRatingStats = null) { + string deletedBy = this.DeletedBy switch + { + DeletedBy.None => "none", + DeletedBy.Moderator => "moderator", + DeletedBy.LevelAuthor => "level_author", + _ => "none", + }; - XmlWriterSettings settings = new(); - settings.OmitXmlDeclaration = true; - - XmlSerializer serializer = new(typeof(DeletedBy)); - StringWriter stringWriter = new(); - using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings)) serializer.Serialize(xmlWriter, this.DeletedBy); - string deletedBy = stringWriter.ToString(); - - string reviewData = LbpSerializer.TaggedStringElement("slot_id", this.SlotId, "type", this.Slot.Type) + - LbpSerializer.StringElement("reviewer", this.Reviewer.Username) + + string reviewData = LbpSerializer.TaggedStringElement("slot_id", this.SlotId, "type", this.Slot?.Type) + + LbpSerializer.StringElement("reviewer", this.Reviewer?.Username) + LbpSerializer.StringElement("thumb", this.Thumb) + LbpSerializer.StringElement("timestamp", this.Timestamp) + LbpSerializer.StringElement("labels", this.LabelCollection) + LbpSerializer.StringElement("deleted", this.Deleted) + - deletedBy + + LbpSerializer.StringElement("deleted_by", deletedBy) + LbpSerializer.StringElement("text", this.Text) + LbpSerializer.StringElement("thumbsup", this.ThumbsUp) + LbpSerializer.StringElement("thumbsdown", this.ThumbsDown) + - LbpSerializer.StringElement("yourthumb", yourRatingStats?.Thumb == null ? 0 : yourRatingStats?.Thumb); + LbpSerializer.StringElement("yourthumb", yourRatingStats?.Thumb ?? 0); - return LbpSerializer.TaggedStringElement(elementOverride, reviewData, "id", this.SlotId + "." + this.Reviewer.Username); + return LbpSerializer.TaggedStringElement(elementOverride, reviewData, "id", this.SlotId + "." + this.Reviewer?.Username); } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/UserUpdate.cs b/ProjectLighthouse/Types/UserUpdate.cs index a193ec8c..ef609967 100644 --- a/ProjectLighthouse/Types/UserUpdate.cs +++ b/ProjectLighthouse/Types/UserUpdate.cs @@ -7,23 +7,23 @@ namespace LBPUnion.ProjectLighthouse.Types; public class UserUpdate { [XmlElement("location")] - public Location? Location; + public Location? Location { get; set; } [XmlElement("biography")] - public string? Biography; + public string? Biography { get; set; } [XmlElement("icon")] - public string? IconHash; + public string? IconHash { get; set; } [XmlElement("planets")] - public string? PlanetHash; + public string? PlanetHash { get; set; } [XmlElement("yay2")] - public string? YayHash; + public string? YayHash { get; set; } [XmlElement("meh2")] - public string? MehHash; + public string? MehHash { get; set; } [XmlElement("boo2")] - public string? BooHash; + public string? BooHash { get; set; } } \ No newline at end of file