diff --git a/ProjectLighthouse/Controllers/GameApi/CommentController.cs b/ProjectLighthouse/Controllers/GameApi/CommentController.cs index f1960429..cfca4f09 100644 --- a/ProjectLighthouse/Controllers/GameApi/CommentController.cs +++ b/ProjectLighthouse/Controllers/GameApi/CommentController.cs @@ -33,49 +33,9 @@ public class CommentController : ControllerBase User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); - Comment? comment = await this.database.Comments.Include(c => c.Poster).FirstOrDefaultAsync(c => commentId == c.CommentId); + bool success = await this.database.RateComment(user, commentId, rating); + if (!success) return this.BadRequest(); - if (comment == null) return this.BadRequest(); - - Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == user.UserId && r.TargetId == commentId); - if (reaction == null) - { - Reaction newReaction = new Reaction() - { - UserId = user.UserId, - TargetId = commentId, - Rating = 0, - }; - this.database.Reactions.Add(newReaction); - await this.database.SaveChangesAsync(); - reaction = newReaction; - } - int oldRating = reaction.Rating; - if (oldRating == rating) return this.Ok(); - - reaction.Rating = rating; - // if rating changed then we count the number of reactions to ensure accuracy - List reactions = await this.database.Reactions - .Where(c => c.TargetId == commentId) - .ToListAsync(); - int yay = 0; - int boo = 0; - foreach (Reaction r in reactions) - { - switch (r.Rating) - { - case -1: - boo++; - break; - case 1: - yay++; - break; - } - } - - comment.ThumbsDown = boo; - comment.ThumbsUp = yay; - await this.database.SaveChangesAsync(); return this.Ok(); } @@ -135,27 +95,12 @@ public class CommentController : ControllerBase int targetId = slotId.GetValueOrDefault(); - if (type == CommentType.Profile) - { - User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); - if (target == null) return this.BadRequest(); - targetId = target.UserId; - } - else - { - Slot? target = await this.database.Slots.FirstOrDefaultAsync(u => u.SlotId == slotId); - if (target == null) return this.BadRequest(); - } + if (type == CommentType.Profile) targetId = this.database.Users.First(u => u.Username == username).UserId; - comment.PosterUserId = poster.UserId; - comment.TargetId = targetId; - comment.Type = type; + bool success = await this.database.PostComment(poster, targetId, type, comment.Message); + if (success) return this.Ok(); - comment.Timestamp = TimeHelper.UnixTimeMilliseconds(); - - this.database.Comments.Add(comment); - await this.database.SaveChangesAsync(); - return this.Ok(); + return this.BadRequest(); } [HttpPost("deleteUserComment/{username}")] diff --git a/ProjectLighthouse/Controllers/GameApi/ReportController.cs b/ProjectLighthouse/Controllers/GameApi/ReportController.cs new file mode 100644 index 00000000..4b0a37da --- /dev/null +++ b/ProjectLighthouse/Controllers/GameApi/ReportController.cs @@ -0,0 +1,56 @@ +#nullable enable +using System; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using System.Xml.Serialization; +using Kettu; +using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Reports; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; +using System.Text.Json.Serialization; +using LBPUnion.ProjectLighthouse.Helpers; + +namespace LBPUnion.ProjectLighthouse.Controllers.GameApi; + +[ApiController] +[Route("LITTLEBIGPLANETPS3_XML/")] +[Produces("text/xml")] +public class ReportController : ControllerBase +{ + private readonly Database database; + + public ReportController(Database database) + { + this.database = database; + } + + [HttpPost("grief")] + public async Task Report() + { + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + this.Request.Body.Position = 0; + string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); + + Console.WriteLine(bodyString); + XmlSerializer serializer = new(typeof(GriefReport)); + GriefReport? report = (GriefReport?) serializer.Deserialize(new StringReader(bodyString)); + + if (report == null) return this.BadRequest(); + + report.Bounds = JsonSerializer.Serialize(report.XmlBounds.Rect, typeof(Rectangle)); + report.Players = JsonSerializer.Serialize(report.XmlPlayers, typeof(ReportPlayer[])); + report.VisiblePlayers = JsonSerializer.Serialize(report.XmlVisiblePlayers, typeof(VisiblePlayer[])); + report.Timestamp = TimeHelper.UnixTimeMilliseconds(); + report.ReportingPlayerId = user.UserId; + + this.database.Reports.Add(report); + await this.database.SaveChangesAsync(); + + return this.Ok(); + } + +} \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/GameApi/Resources/ResourcesController.cs b/ProjectLighthouse/Controllers/GameApi/Resources/ResourcesController.cs index cb528216..36ec216e 100644 --- a/ProjectLighthouse/Controllers/GameApi/Resources/ResourcesController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Resources/ResourcesController.cs @@ -55,7 +55,7 @@ public class ResourcesController : ControllerBase [HttpGet("/gameAssets/{hash}")] public IActionResult GetGameImage(string hash) { - string path = $"png/{hash}.png"; + string path = $"png{Path.DirectorySeparatorChar}{hash}.png"; if (IOFile.Exists(path)) { diff --git a/ProjectLighthouse/Controllers/Website/SlotPageController.cs b/ProjectLighthouse/Controllers/Website/SlotPageController.cs index 18fb14e3..73481dc0 100644 --- a/ProjectLighthouse/Controllers/Website/SlotPageController.cs +++ b/ProjectLighthouse/Controllers/Website/SlotPageController.cs @@ -23,6 +23,32 @@ public class SlotPageController : ControllerBase this.database = database; } + [HttpGet("rateComment")] + public async Task RateComment([FromRoute] int id, [FromQuery] int commentId, [FromQuery] int rating) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + await this.database.RateComment(user, + commentId, + rating); + + return this.Redirect("~/slot/" + id + "#" + commentId); + } + + [HttpGet("postComment")] + public async Task PostComment([FromRoute] int id, [FromQuery] string msg) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + bool success = await this.database.PostComment(user, id, CommentType.Level, msg); + + if (!success) return this.NotFound(); + + return this.Redirect("~/slot/" + id); + } + [HttpGet("heart")] public async Task HeartLevel([FromRoute] int id, [FromQuery] string? callbackUrl) { diff --git a/ProjectLighthouse/Controllers/Website/UserPageController.cs b/ProjectLighthouse/Controllers/Website/UserPageController.cs index 0f44d876..2008f4ae 100644 --- a/ProjectLighthouse/Controllers/Website/UserPageController.cs +++ b/ProjectLighthouse/Controllers/Website/UserPageController.cs @@ -1,7 +1,10 @@ #nullable enable +using System; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Mvc; +using Microsoft.CodeAnalysis; using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Controllers.Website; @@ -31,6 +34,30 @@ public class UserPageController : ControllerBase return this.Redirect("~/user/" + id); } + [HttpGet("rateComment")] + public async Task RateComment([FromRoute] int id, [FromQuery] int commentId, [FromQuery] int rating) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + await this.database.RateComment(user, commentId, rating); + + return this.Redirect("~/user/" + id + "#" + commentId); + } + + [HttpGet("postComment")] + public async Task PostComment([FromRoute] int id, [FromQuery] string msg) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + bool success = await this.database.PostComment(user, id, CommentType.Profile, msg); + + if (!success) return this.NotFound(); + + return this.Redirect("~/user/" + id); + } + [HttpGet("unheart")] public async Task UnheartUser([FromRoute] int id) { diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index bead8124..6ae7f4e2 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Helpers; @@ -6,6 +7,7 @@ using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Categories; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Profiles; +using LBPUnion.ProjectLighthouse.Types.Reports; using LBPUnion.ProjectLighthouse.Types.Reviews; using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Tickets; @@ -37,6 +39,7 @@ public class Database : DbContext public DbSet UserApprovedIpAddresses { get; set; } public DbSet CustomCategories { get; set; } public DbSet Reactions { get; set; } + public DbSet Reports { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); @@ -89,6 +92,83 @@ public class Database : DbContext #region Hearts & Queues + public async Task RateComment(User user, int commentId, int rating) + { + Comment? comment = await this.Comments.FirstOrDefaultAsync(c => commentId == c.CommentId); + + if (comment == null) return false; + + if (comment.PosterUserId == user.UserId) return false; + + Reaction? reaction = await this.Reactions.FirstOrDefaultAsync(r => r.UserId == user.UserId && r.TargetId == commentId); + if (reaction == null) + { + Reaction newReaction = new() + { + UserId = user.UserId, + TargetId = commentId, + Rating = 0, + }; + this.Reactions.Add(newReaction); + await this.SaveChangesAsync(); + reaction = newReaction; + } + + int oldRating = reaction.Rating; + if (oldRating == rating) return true; + + reaction.Rating = rating; + // if rating changed then we count the number of reactions to ensure accuracy + List reactions = await this.Reactions.Where(c => c.TargetId == commentId).ToListAsync(); + int yay = 0; + int boo = 0; + foreach (Reaction r in reactions) + { + switch (r.Rating) + { + case -1: + boo++; + break; + case 1: + yay++; + break; + } + } + + comment.ThumbsDown = boo; + comment.ThumbsUp = yay; + await this.SaveChangesAsync(); + return true; + } + + public async Task PostComment(User user, int targetId, CommentType type, string message) + { + if (type == CommentType.Profile) + { + User? targetUser = await this.Users.FirstOrDefaultAsync(u => u.UserId == targetId); + if (targetUser == null) return false; + } + else + { + Slot? targetSlot = await this.Slots.FirstOrDefaultAsync(u => u.SlotId == targetId); + if(targetSlot == null) return false; + } + + this.Comments.Add + ( + new Comment + { + PosterUserId = user.UserId, + TargetId = targetId, + Type = type, + Message = message, + Timestamp = TimeHelper.UnixTimeMilliseconds(), + } + ); + await this.SaveChangesAsync(); + return true; + } + public async Task HeartUser(User user, User heartedUser) { HeartedProfile? heartedProfile = await this.HeartedProfiles.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); diff --git a/ProjectLighthouse/Helpers/HashHelper.cs b/ProjectLighthouse/Helpers/HashHelper.cs index 15dc3022..e613df39 100644 --- a/ProjectLighthouse/Helpers/HashHelper.cs +++ b/ProjectLighthouse/Helpers/HashHelper.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Runtime.Intrinsics.Arm; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -11,7 +12,7 @@ namespace LBPUnion.ProjectLighthouse.Helpers; [SuppressMessage("ReSharper", "UnusedMember.Global")] public static class HashHelper { - private static readonly SHA1 sha1 = SHA1.Create(); + // private static readonly SHA1 sha1 = SHA1.Create(); private static readonly SHA256 sha256 = SHA256.Create(); private static readonly Random random = new(); @@ -71,7 +72,7 @@ public static class HashHelper public static string Sha1Hash(string str) => Sha1Hash(Encoding.UTF8.GetBytes(str)); - public static string Sha1Hash(byte[] bytes) => BitConverter.ToString(sha1.ComputeHash(bytes)).Replace("-", ""); + public static string Sha1Hash(byte[] bytes) => BitConverter.ToString(SHA1.Create().ComputeHash(bytes)).Replace("-",""); public static string BCryptHash(string str) => BCrypt.Net.BCrypt.HashPassword(str); diff --git a/ProjectLighthouse/Helpers/ImageHelper.cs b/ProjectLighthouse/Helpers/ImageHelper.cs index 0ca57332..92684520 100644 --- a/ProjectLighthouse/Helpers/ImageHelper.cs +++ b/ProjectLighthouse/Helpers/ImageHelper.cs @@ -18,7 +18,7 @@ public static class ImageHelper { if (type != LbpFileType.Jpeg && type != LbpFileType.Png && type != LbpFileType.Texture) return false; - if (File.Exists($"png/{hash}.png")) return true; + if (File.Exists($"png{Path.DirectorySeparatorChar}{hash}.png")) return true; using MemoryStream ms = new(data); using BinaryReader reader = new(ms); diff --git a/ProjectLighthouse/Migrations/20220212041106_AddGriefReports.cs b/ProjectLighthouse/Migrations/20220212041106_AddGriefReports.cs new file mode 100644 index 00000000..1e2db007 --- /dev/null +++ b/ProjectLighthouse/Migrations/20220212041106_AddGriefReports.cs @@ -0,0 +1,67 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20220212041106_AddGriefReports")] + public partial class AddGriefReports : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Reports", + columns: table => new + { + ReportId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Type = table.Column(type: "int", nullable: false), + Timestamp = table.Column(type: "bigint", nullable: false), + VisiblePlayers = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ReportingPlayerId = table.Column(type: "int", nullable: false), + Players = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + GriefStateHash = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + LevelOwner = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + InitialStateHash = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + JpegHash = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + LevelId = table.Column(type: "int", nullable: false), + LevelType = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Bounds = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_Reports", x => x.ReportId); + table.ForeignKey( + name: "FK_Reports_Users_ReportingPlayerId", + column: x => x.ReportingPlayerId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_Reports_ReportingPlayerId", + table: "Reports", + column: "ReportingPlayerId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Reports"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 15bc4226..b4e5a89e 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -15,7 +15,7 @@ namespace ProjectLighthouse.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("ProductVersion", "6.0.2") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.AuthenticationAttempt", b => @@ -503,6 +503,55 @@ namespace ProjectLighthouse.Migrations b.ToTable("Reactions"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reports.GriefReport", b => + { + b.Property("ReportId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bounds") + .HasColumnType("longtext"); + + b.Property("GriefStateHash") + .HasColumnType("longtext"); + + b.Property("InitialStateHash") + .HasColumnType("longtext"); + + b.Property("JpegHash") + .HasColumnType("longtext"); + + b.Property("LevelId") + .HasColumnType("int"); + + b.Property("LevelOwner") + .HasColumnType("longtext"); + + b.Property("LevelType") + .HasColumnType("longtext"); + + b.Property("Players") + .HasColumnType("longtext"); + + b.Property("ReportingPlayerId") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("VisiblePlayers") + .HasColumnType("longtext"); + + b.HasKey("ReportId"); + + b.HasIndex("ReportingPlayerId"); + + b.ToTable("Reports"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => { b.Property("RatedReviewId") @@ -862,6 +911,17 @@ namespace ProjectLighthouse.Migrations b.Navigation("Poster"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reports.GriefReport", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "ReportingPlayer") + .WithMany() + .HasForeignKey("ReportingPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReportingPlayer"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => { b.HasOne("LBPUnion.ProjectLighthouse.Types.Reviews.Review", "Review") diff --git a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml index 279ca575..bfafbb1c 100644 --- a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml +++ b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml @@ -19,6 +19,7 @@ @if (Model.User.IsAdmin) { + Model.NavigationItems.Add(new PageNavigationItem("Reports", "/reports/0", "exclamation circle")); Model.NavigationItemsRight.Add(new PageNavigationItem("Admin Panel", "/admin", "cogs")); } Model.NavigationItemsRight.Add(new PageNavigationItem("Log out", "/logout", "user alternate slash")); // should always be last diff --git a/ProjectLighthouse/Pages/Partials/CommentsPartial.cshtml b/ProjectLighthouse/Pages/Partials/CommentsPartial.cshtml new file mode 100644 index 00000000..9b4b4107 --- /dev/null +++ b/ProjectLighthouse/Pages/Partials/CommentsPartial.cshtml @@ -0,0 +1,74 @@ +@using System.IO +@using System.Web +@using LBPUnion.ProjectLighthouse.Types.Profiles + +
+ +

Comments

+ @if (Model.Comments.Count == 0) + { +

There are no comments.

+ } + + @for (int i = 0; i < Model.Comments.Count; i++) + { + Comment comment = Model.Comments[i]; + DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000); + StringWriter messageWriter = new(); + HttpUtility.HtmlDecode(comment.getComment(), messageWriter); + string decodedMessage = messageWriter.ToString(); + string url = Url.RouteUrl(ViewContext.RouteData.Values); + int rating = comment.ThumbsUp - comment.ThumbsDown; +
+
+ + + + @(rating) + + + +
+ +
+ @comment.Poster.Username: + @if (comment.Deleted) + { + @decodedMessage + } + else + { + @decodedMessage + } +

+ @timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC +

+ @if (i != Model.Comments.Count - 1) + { +
+ } +
+
+ } + @if(Model.CommentsEnabled){ +
+
+
+ +
+ +
+ } +
\ No newline at end of file diff --git a/ProjectLighthouse/Pages/PhotosPage.cshtml.cs b/ProjectLighthouse/Pages/PhotosPage.cshtml.cs index 96f7ba70..dcf8cef0 100644 --- a/ProjectLighthouse/Pages/PhotosPage.cshtml.cs +++ b/ProjectLighthouse/Pages/PhotosPage.cshtml.cs @@ -31,16 +31,17 @@ public class PhotosPage : BaseLayout { if (string.IsNullOrWhiteSpace(name)) name = ""; - this.PhotoCount = await this.Database.Photos.CountAsync(p => p.Creator.Username.Contains(name) || p.PhotoSubjectCollection.Contains(name)); + this.SearchValue = name.Replace(" ", string.Empty); + + this.PhotoCount = await this.Database.Photos.CountAsync(p => p.Creator.Username.Contains(this.SearchValue) || p.PhotoSubjectCollection.Contains(this.SearchValue)); - this.SearchValue = name; this.PageNumber = pageNumber; this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.PhotoCount / ServerStatics.PageSize)); if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/photos/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}"); this.Photos = await this.Database.Photos.Include(p => p.Creator) - .Where(p => p.Creator.Username.Contains(name) || p.PhotoSubjectCollection.Contains(name)) + .Where(p => p.Creator.Username.Contains(this.SearchValue) || p.PhotoSubjectCollection.Contains(this.SearchValue)) .OrderByDescending(p => p.Timestamp) .Skip(pageNumber * ServerStatics.PageSize) .Take(ServerStatics.PageSize) diff --git a/ProjectLighthouse/Pages/ReportsPage.cshtml b/ProjectLighthouse/Pages/ReportsPage.cshtml new file mode 100644 index 00000000..f2e8844e --- /dev/null +++ b/ProjectLighthouse/Pages/ReportsPage.cshtml @@ -0,0 +1,206 @@ +@page "/reports/{pageNumber:int}" +@using LBPUnion.ProjectLighthouse.Types.Reports +@model LBPUnion.ProjectLighthouse.Pages.ReportsPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.Title = "Reports"; +} + +

There are @Model.ReportCount total reports!

+ +
+
+ + +
+
+
+ +@foreach (GriefReport report in Model.Reports) +{ +
+
+ + + Grief report picture +
+

Report submitted by @report.ReportingPlayer.Username

+ Report contains @report.XmlPlayers.Length @(report.XmlPlayers.Length == 1 ? "player" : "players") + @foreach (ReportPlayer player in report.XmlPlayers) + { + + } +
Report time: @(DateTimeOffset.FromUnixTimeMilliseconds(report.Timestamp).ToString("R"))
+
Report reason: @report.Type
+
Level ID: @report.LevelId
+
Level type: @report.LevelType
+
Level owner: @report.LevelOwner
+
Hover to see reported region
+
+ +} + + +@if (Model.PageNumber != 0) +{ + Previous Page +} +@(Model.PageNumber + 1) / @(Model.PageAmount) +@if (Model.PageNumber < Model.PageAmount - 1) +{ + Next Page +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/ReportsPage.cshtml.cs b/ProjectLighthouse/Pages/ReportsPage.cshtml.cs new file mode 100644 index 00000000..89589acc --- /dev/null +++ b/ProjectLighthouse/Pages/ReportsPage.cshtml.cs @@ -0,0 +1,65 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using JetBrains.Annotations; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types.Reports; +using LBPUnion.ProjectLighthouse.Types.Settings; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Pages; + +public class ReportsPage : BaseLayout +{ + + public int PageAmount; + + public int PageNumber; + + public int ReportCount; + + public List Reports; + + public string SearchValue; + + public ReportsPage([NotNull] Database database) : base(database) + { } + + public async Task OnGet([FromRoute] int pageNumber, [FromQuery] string? name) + { + if (string.IsNullOrWhiteSpace(name)) name = ""; + + this.SearchValue = name.Replace(" ", string.Empty); + + this.ReportCount = await this.Database.Reports.Include(r => r.ReportingPlayer).CountAsync(r => r.ReportingPlayer.Username.Contains(this.SearchValue)); + + this.PageNumber = pageNumber; + this.PageAmount = Math.Max(1, (int) Math.Ceiling((double) this.ReportCount / ServerStatics.PageSize)); + + if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) + return this.Redirect($"/reports/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}"); + + this.Reports = await this.Database.Reports.Include(r => r.ReportingPlayer) + .Where(r => r.ReportingPlayer.Username.Contains(this.SearchValue)) + .OrderByDescending(r => r.Timestamp) + .Skip(pageNumber * ServerStatics.PageSize) + .Take(ServerStatics.PageSize) + .ToListAsync(); + + foreach (GriefReport r in this.Reports) + { + r.XmlPlayers = (ReportPlayer[]) JsonSerializer.Deserialize(r.Players, typeof(ReportPlayer[]))!; + + r.XmlBounds = new Marqee() + { + Rect = (Rectangle) JsonSerializer.Deserialize(r.Bounds, typeof(Rectangle))!, + }; + } + + return this.Page(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/SlotPage.cshtml b/ProjectLighthouse/Pages/SlotPage.cshtml index b4061fc8..09a1a277 100644 --- a/ProjectLighthouse/Pages/SlotPage.cshtml +++ b/ProjectLighthouse/Pages/SlotPage.cshtml @@ -1,8 +1,5 @@ @page "/slot/{id:int}" -@using System.IO -@using System.Web @using LBPUnion.ProjectLighthouse.Helpers.Extensions -@using LBPUnion.ProjectLighthouse.Types.Profiles @model LBPUnion.ProjectLighthouse.Pages.SlotPage @{ @@ -57,36 +54,9 @@ -
-

Comments

- @if (Model.Comments.Count == 0) - { -

There are no comments.

- } - @foreach (Comment comment in Model.Comments!) - { - DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000); - StringWriter messageWriter = new(); - HttpUtility.HtmlDecode(comment.getComment(), messageWriter); - string decodedMessage = messageWriter.ToString(); -
- @comment.Poster.Username: - @if (comment.Deleted) - { - @decodedMessage - } - else - { - @decodedMessage - } -

- @timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC -

-
-
- } -
+@await Html.PartialAsync("Partials/CommentsPartial") + @if (Model.User != null && Model.User.IsAdmin) {
diff --git a/ProjectLighthouse/Pages/SlotPage.cshtml.cs b/ProjectLighthouse/Pages/SlotPage.cshtml.cs index 43b04646..9476c3c6 100644 --- a/ProjectLighthouse/Pages/SlotPage.cshtml.cs +++ b/ProjectLighthouse/Pages/SlotPage.cshtml.cs @@ -7,6 +7,7 @@ using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Profiles; +using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -16,6 +17,8 @@ public class SlotPage : BaseLayout { public List Comments; + public bool CommentsEnabled = ServerSettings.Instance.LevelCommentsEnabled; + public Slot Slot; public SlotPage([NotNull] Database database) : base(database) {} @@ -33,6 +36,15 @@ public class SlotPage : BaseLayout this.Slot = slot; + if (this.User == null) return this.Page(); + + foreach (Comment c in this.Comments) + { + Reaction? reaction = await this.Database.Reactions.FirstOrDefaultAsync(r => + r.UserId == this.User.UserId && r.TargetId == c.CommentId); + if (reaction != null) c.YourThumb = reaction.Rating; + } + return this.Page(); } } \ No newline at end of file diff --git a/ProjectLighthouse/Pages/SlotsPage.cshtml.cs b/ProjectLighthouse/Pages/SlotsPage.cshtml.cs index 48c030c0..970aeca0 100644 --- a/ProjectLighthouse/Pages/SlotsPage.cshtml.cs +++ b/ProjectLighthouse/Pages/SlotsPage.cshtml.cs @@ -32,16 +32,17 @@ public class SlotsPage : BaseLayout { if (string.IsNullOrWhiteSpace(name)) name = ""; - this.SlotCount = await this.Database.Slots.CountAsync(p => p.Name.Contains(name)); + this.SearchValue = name.Replace(" ", string.Empty); + + this.SlotCount = await this.Database.Slots.CountAsync(p => p.Name.Contains(this.SearchValue)); - this.SearchValue = name; this.PageNumber = pageNumber; this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.SlotCount / ServerStatics.PageSize)); if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/slots/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}"); this.Slots = await this.Database.Slots.Include(p => p.Creator) - .Where(p => p.Name.Contains(name)) + .Where(p => p.Name.Contains(this.SearchValue)) .OrderByDescending(p => p.FirstUploaded) .Skip(pageNumber * ServerStatics.PageSize) .Take(ServerStatics.PageSize) diff --git a/ProjectLighthouse/Pages/UserPage.cshtml b/ProjectLighthouse/Pages/UserPage.cshtml index 8d812908..61200e7f 100644 --- a/ProjectLighthouse/Pages/UserPage.cshtml +++ b/ProjectLighthouse/Pages/UserPage.cshtml @@ -1,9 +1,6 @@ @page "/user/{userId:int}" -@using System.IO -@using System.Web @using LBPUnion.ProjectLighthouse.Helpers.Extensions @using LBPUnion.ProjectLighthouse.Types -@using LBPUnion.ProjectLighthouse.Types.Profiles @model LBPUnion.ProjectLighthouse.Pages.UserPage @{ @@ -114,35 +111,4 @@
} - -
-

Comments

- @if (Model.ProfileUser.Comments == 0) - { -

There are no comments.

- } - - @foreach (Comment comment in Model.Comments!) - { - DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000); - StringWriter messageWriter = new(); - HttpUtility.HtmlDecode(comment.getComment(), messageWriter); - string decodedMessage = messageWriter.ToString(); -
- @comment.Poster.Username: - @if (comment.Deleted) - { - @decodedMessage - } - else - { - @decodedMessage - } - -

- @timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC -

-
-
- } -
\ No newline at end of file +@await Html.PartialAsync("Partials/CommentsPartial") \ No newline at end of file diff --git a/ProjectLighthouse/Pages/UserPage.cshtml.cs b/ProjectLighthouse/Pages/UserPage.cshtml.cs index 00a21717..c10ab132 100644 --- a/ProjectLighthouse/Pages/UserPage.cshtml.cs +++ b/ProjectLighthouse/Pages/UserPage.cshtml.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Profiles; +using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -14,6 +15,8 @@ public class UserPage : BaseLayout { public List? Comments; + public bool CommentsEnabled = ServerSettings.Instance.ProfileCommentsEnabled; + public bool IsProfileUserHearted; public List? Photos; @@ -34,11 +37,19 @@ public class UserPage : BaseLayout .Where(p => p.TargetId == userId && p.Type == CommentType.Profile) .Take(50) .ToListAsync(); - if (this.User != null) - this.IsProfileUserHearted = await this.Database.HeartedProfiles.FirstOrDefaultAsync - (u => u.UserId == this.User.UserId && u.HeartedUserId == this.ProfileUser.UserId) != + { + foreach (Comment c in this.Comments) + { + Reaction? reaction = await this.Database.Reactions.FirstOrDefaultAsync(r => + r.UserId == this.User.UserId && r.TargetId == c.CommentId); + if (reaction != null) c.YourThumb = reaction.Rating; + } + this.IsProfileUserHearted = await this.Database.HeartedProfiles.FirstOrDefaultAsync(u => + u.UserId == this.User.UserId && + u.HeartedUserId == this.ProfileUser.UserId) != null; + } return this.Page(); } diff --git a/ProjectLighthouse/Pages/UsersPage.cshtml.cs b/ProjectLighthouse/Pages/UsersPage.cshtml.cs index e5d7ebcc..ce2a3e22 100644 --- a/ProjectLighthouse/Pages/UsersPage.cshtml.cs +++ b/ProjectLighthouse/Pages/UsersPage.cshtml.cs @@ -31,15 +31,16 @@ public class UsersPage : BaseLayout { if (string.IsNullOrWhiteSpace(name)) name = ""; - this.UserCount = await this.Database.Users.CountAsync(u => !u.Banned && u.Username.Contains(name)); + this.SearchValue = name.Replace(" ", string.Empty); + + this.UserCount = await this.Database.Users.CountAsync(u => !u.Banned && u.Username.Contains(this.SearchValue)); - this.SearchValue = name; this.PageNumber = pageNumber; this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.UserCount / ServerStatics.PageSize)); if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/users/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}"); - this.Users = await this.Database.Users.Where(u => !u.Banned && u.Username.Contains(name)) + this.Users = await this.Database.Users.Where(u => !u.Banned && u.Username.Contains(this.SearchValue)) .OrderByDescending(b => b.UserId) .Skip(pageNumber * ServerStatics.PageSize) .Take(ServerStatics.PageSize) diff --git a/ProjectLighthouse/Program.cs b/ProjectLighthouse/Program.cs index 687d76db..afcd1018 100644 --- a/ProjectLighthouse/Program.cs +++ b/ProjectLighthouse/Program.cs @@ -100,7 +100,7 @@ public static class Program { while (fileQueue.TryDequeue(out string? filename)) { - LbpFile? file = LbpFile.FromHash(filename.Replace("r/", "")); + LbpFile? file = LbpFile.FromHash(filename.Replace("r" + Path.DirectorySeparatorChar, "")); if (file == null) continue; if (file.FileType == LbpFileType.Jpeg || file.FileType == LbpFileType.Png || file.FileType == LbpFileType.Texture) diff --git a/ProjectLighthouse/Types/Profiles/Comment.cs b/ProjectLighthouse/Types/Profiles/Comment.cs index 7b296228..d263c091 100644 --- a/ProjectLighthouse/Types/Profiles/Comment.cs +++ b/ProjectLighthouse/Types/Profiles/Comment.cs @@ -37,6 +37,10 @@ public class Comment public int ThumbsUp { get; set; } public int ThumbsDown { get; set; } + [NotMapped] + [XmlIgnore] + public int YourThumb; + public string getComment() { if (!this.Deleted) diff --git a/ProjectLighthouse/Types/Reports/GriefReport.cs b/ProjectLighthouse/Types/Reports/GriefReport.cs new file mode 100644 index 00000000..c44e0aee --- /dev/null +++ b/ProjectLighthouse/Types/Reports/GriefReport.cs @@ -0,0 +1,59 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Xml.Serialization; + +namespace LBPUnion.ProjectLighthouse.Types.Reports; + +[XmlRoot("griefReport")] +public class GriefReport +{ + [Key] + public int ReportId { get; set; } + + [XmlElement("griefTypeId")] + public GriefType Type { get; set; } + + public long Timestamp { get; set; } + + [NotMapped] + [XmlElement("visibleBadge")] + public VisiblePlayer[] XmlVisiblePlayers { get; set; } + + public string VisiblePlayers { get; set; } + + public int ReportingPlayerId { get; set; } + + [ForeignKey(nameof(ReportingPlayerId))] + public User ReportingPlayer { get; set; } + + [NotMapped] + [XmlElement("player")] + public ReportPlayer[] XmlPlayers { get; set; } + + public string Players { get; set; } + + [XmlElement("griefStateHash")] + public string GriefStateHash { get; set; } + + [XmlElement("levelOwner")] + public string LevelOwner { get; set; } + + [XmlElement("initialStateHash")] + public string InitialStateHash { get; set; } + + [XmlElement("jpegHash")] + public string JpegHash { get; set; } + + [XmlElement("levelId")] + public int LevelId { get; set; } + + [XmlElement("levelType")] + public string LevelType { get; set; } + + [NotMapped] + [XmlElement("marqee")] + public Marqee XmlBounds { get; set; } + + public string Bounds { get; set; } + +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Reports/GriefType.cs b/ProjectLighthouse/Types/Reports/GriefType.cs new file mode 100644 index 00000000..ccce115b --- /dev/null +++ b/ProjectLighthouse/Types/Reports/GriefType.cs @@ -0,0 +1,23 @@ +using System.Xml.Serialization; + +namespace LBPUnion.ProjectLighthouse.Types.Reports; + +public enum GriefType +{ + [XmlEnum("1")] + Obscene = 1, + [XmlEnum("2")] + Mature = 2, + [XmlEnum("3")] + Offensive = 3, + [XmlEnum("4")] + Violence = 4, + [XmlEnum("5")] + Illegal = 5, + [XmlEnum("6")] + Unknown = 6, + [XmlEnum("7")] + Tos = 7, + [XmlEnum("8")] + Other = 8, +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Reports/Marqee.cs b/ProjectLighthouse/Types/Reports/Marqee.cs new file mode 100644 index 00000000..82e554cf --- /dev/null +++ b/ProjectLighthouse/Types/Reports/Marqee.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; + +namespace LBPUnion.ProjectLighthouse.Types.Reports; + +[XmlRoot("marqee")] +public class Marqee +{ + [XmlElement("rect")] + public Rectangle Rect { get; set; } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Reports/Rectangle.cs b/ProjectLighthouse/Types/Reports/Rectangle.cs new file mode 100644 index 00000000..24757bc0 --- /dev/null +++ b/ProjectLighthouse/Types/Reports/Rectangle.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Serialization; +using Newtonsoft.Json; + +namespace LBPUnion.ProjectLighthouse.Types.Reports; + +public class Rectangle +{ + [XmlAttribute("t")] + public int Top { get; set; } + + [XmlAttribute("l")] + public int Left { get; set; } + + [XmlAttribute("b")] + public int Bottom { get; set; } + + [XmlAttribute("r")] + public int Right { get; set; } + +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Reports/ReportPlayer.cs b/ProjectLighthouse/Types/Reports/ReportPlayer.cs new file mode 100644 index 00000000..22c94a68 --- /dev/null +++ b/ProjectLighthouse/Types/Reports/ReportPlayer.cs @@ -0,0 +1,23 @@ +using System.Xml.Serialization; + +namespace LBPUnion.ProjectLighthouse.Types.Reports; + +[XmlRoot("player")] +public class ReportPlayer +{ + [XmlElement("id")] + public string Name { get; set; } + + [XmlElement("rect")] + public Rectangle Location { get; set; } + + [XmlAttribute("reporter")] + public bool Reporter { get; set; } + + [XmlAttribute("ingamenow")] + public bool InGame { get; set; } + + [XmlAttribute("playerNumber")] + public int PlayerNum { get; set; } + +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Reports/VisiblePlayer.cs b/ProjectLighthouse/Types/Reports/VisiblePlayer.cs new file mode 100644 index 00000000..78507bb1 --- /dev/null +++ b/ProjectLighthouse/Types/Reports/VisiblePlayer.cs @@ -0,0 +1,22 @@ +using System; +using System.Xml.Serialization; + +namespace LBPUnion.ProjectLighthouse.Types.Reports; + +[XmlRoot("visibleBadge")] +[Serializable] +public class VisiblePlayer +{ + [XmlElement("id")] + public string Name { get; set; } + + [XmlElement("hash")] + public string Hash { get; set; } + + [XmlElement("rect")] + public Rectangle Bounds { get; set; } + + [XmlAttribute("type")] + public string Type { get; set; } + +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/User.cs b/ProjectLighthouse/Types/User.cs index dfe315cd..34113c26 100644 --- a/ProjectLighthouse/Types/User.cs +++ b/ProjectLighthouse/Types/User.cs @@ -190,8 +190,8 @@ public class User LbpSerializer.BlankElement("photos") + LbpSerializer.StringElement("heartCount", this.Hearts) + LbpSerializer.StringElement("yay2", this.YayHash) + - LbpSerializer.StringElement("boo2", this.YayHash) + - LbpSerializer.StringElement("meh2", this.YayHash); + LbpSerializer.StringElement("boo2", this.BooHash) + + LbpSerializer.StringElement("meh2", this.MehHash); this.ClientsConnected.Serialize(); return LbpSerializer.TaggedStringElement("user", user, "type", "user");