diff --git a/ProjectLighthouse/Controllers/GameApi/CommentController.cs b/ProjectLighthouse/Controllers/GameApi/CommentController.cs index 334b7f04..f1960429 100644 --- a/ProjectLighthouse/Controllers/GameApi/CommentController.cs +++ b/ProjectLighthouse/Controllers/GameApi/CommentController.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,6 +8,7 @@ using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -24,37 +26,130 @@ public class CommentController : ControllerBase this.database = database; } - [HttpGet("userComments/{username}")] - public async Task GetComments(string username) + [HttpPost("rateUserComment/{username}")] + [HttpPost("rateComment/user/{slotId:int}")] + public async Task RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, int? slotId) { - List comments = await this.database.Comments.Include - (c => c.Target) + 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); + + 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(); + } + + + [HttpGet("comments/user/{slotId:int}")] + [HttpGet("userComments/{username}")] + public async Task GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, int? slotId) + { + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + int targetId = slotId.GetValueOrDefault(); + CommentType type = CommentType.Level; + if (!string.IsNullOrWhiteSpace(username)) + { + targetId = this.database.Users.First(u => u.Username.Equals(username)).UserId; + type = CommentType.Profile; + } + + List comments = await this.database.Comments .Include(c => c.Poster) - .Where(c => c.Target.Username == username) + .Where(c => c.TargetId == targetId && c.Type == type) .OrderByDescending(c => c.Timestamp) + .Skip(pageStart - 1) + .Take(Math.Min(pageSize, + 30)) .ToListAsync(); - string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + comment.Serialize()); + string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + + comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result)); return this.Ok(LbpSerializer.StringElement("comments", outputXml)); } - [HttpPost("postUserComment/{username}")] - public async Task PostComment(string username) + public async Task getReaction(int userId, int commentId) { - this.Request.Body.Position = 0; + Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId); + if (reaction == null) return 0; + return reaction.Rating; + } + + [HttpPost("postUserComment/{username}")] + [HttpPost("postComment/user/{slotId:int}")] + public async Task PostComment(string? username, int? slotId) + { + 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)); + Comment? comment = (Comment?) serializer.Deserialize(new StringReader(bodyString)); + + CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level); User? poster = await this.database.UserFromGameRequest(this.Request); if (poster == null) return this.StatusCode(403, ""); - User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); - if (comment == null || target == null) return this.BadRequest(); + if (comment == null) return this.BadRequest(); + + 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(); + } comment.PosterUserId = poster.UserId; - comment.TargetUserId = target.UserId; + comment.TargetId = targetId; + comment.Type = type; comment.Timestamp = TimeHelper.UnixTimeMilliseconds(); @@ -64,17 +159,40 @@ public class CommentController : ControllerBase } [HttpPost("deleteUserComment/{username}")] - public async Task DeleteComment([FromQuery] int commentId, string username) + [HttpPost("deleteComment/user/{slotId:int}")] + public async Task DeleteComment([FromQuery] int commentId, string? username, int? slotId) { User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); if (comment == null) return this.NotFound(); + // if you are not the poster + if (comment.PosterUserId != user.UserId) + { + if (comment.Type == CommentType.Profile) + { + // if you aren't the poster and aren't the profile owner + if (comment.TargetId != user.UserId) + { + return this.StatusCode(403, ""); + } + } + else + { + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == comment.TargetId); + // if you aren't the creator of the level + if (slot == null || slot.CreatorId != user.UserId || slotId.GetValueOrDefault() != slot.SlotId) + { + return this.StatusCode(403, ""); + } + } + } - if (comment.TargetUserId != user.UserId && comment.PosterUserId != user.UserId) return this.StatusCode(403, ""); + comment.Deleted = true; + comment.DeletedBy = user.Username; + comment.DeletedType = "user"; - this.database.Comments.Remove(comment); await this.database.SaveChangesAsync(); return this.Ok(); diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 365319fd..bead8124 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -36,6 +36,7 @@ public class Database : DbContext public DbSet RatedReviews { get; set; } public DbSet UserApprovedIpAddresses { get; set; } public DbSet CustomCategories { get; set; } + public DbSet Reactions { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); @@ -272,6 +273,7 @@ public class Database : DbContext this.Comments.RemoveRange(this.Comments.Where(c => c.PosterUserId == user.UserId)); this.Reviews.RemoveRange(this.Reviews.Where(r => r.ReviewerId == user.UserId)); this.Photos.RemoveRange(this.Photos.Where(p => p.CreatorId == user.UserId)); + this.Reactions.RemoveRange(this.Reactions.Where(p => p.UserId == user.UserId)); this.Users.Remove(user); diff --git a/ProjectLighthouse/Migrations/20220205082513_CommentRefactor.cs b/ProjectLighthouse/Migrations/20220205082513_CommentRefactor.cs new file mode 100644 index 00000000..a42defb2 --- /dev/null +++ b/ProjectLighthouse/Migrations/20220205082513_CommentRefactor.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + public partial class CommentRefactor : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Comments_Slots_TargetId", + table: "Comments"); + + migrationBuilder.DropForeignKey( + name: "FK_Comments_Users_TargetId", + table: "Comments"); + + migrationBuilder.DropIndex( + name: "IX_Comments_TargetId", + table: "Comments"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Comments_TargetId", + table: "Comments", + column: "TargetId"); + + migrationBuilder.AddForeignKey( + name: "FK_Comments_Slots_TargetId", + table: "Comments", + column: "TargetId", + principalTable: "Slots", + principalColumn: "SlotId", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Comments_Users_TargetId", + table: "Comments", + column: "TargetId", + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 28c95e9d..15bc4226 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -412,13 +412,22 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + b.Property("Deleted") + .HasColumnType("tinyint(1)"); + + b.Property("DeletedBy") + .HasColumnType("longtext"); + + b.Property("DeletedType") + .HasColumnType("longtext"); + b.Property("Message") .HasColumnType("longtext"); b.Property("PosterUserId") .HasColumnType("int"); - b.Property("TargetUserId") + b.Property("TargetId") .HasColumnType("int"); b.Property("ThumbsDown") @@ -430,12 +439,13 @@ namespace ProjectLighthouse.Migrations b.Property("Timestamp") .HasColumnType("bigint"); + b.Property("Type") + .HasColumnType("int"); + b.HasKey("CommentId"); b.HasIndex("PosterUserId"); - b.HasIndex("TargetUserId"); - b.ToTable("Comments"); }); @@ -473,6 +483,26 @@ namespace ProjectLighthouse.Migrations b.ToTable("Locations"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reaction", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("TargetId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("RatingId"); + + b.ToTable("Reactions"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => { b.Property("RatedReviewId") @@ -829,15 +859,7 @@ namespace ProjectLighthouse.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") - .WithMany() - .HasForeignKey("TargetUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - b.Navigation("Poster"); - - b.Navigation("Target"); }); modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => diff --git a/ProjectLighthouse/Pages/PasswordResetPage.cshtml b/ProjectLighthouse/Pages/PasswordResetPage.cshtml index 7cc087e1..c6e0f631 100644 --- a/ProjectLighthouse/Pages/PasswordResetPage.cshtml +++ b/ProjectLighthouse/Pages/PasswordResetPage.cshtml @@ -10,11 +10,13 @@