diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs index 15d06544..76f2b715 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs @@ -64,12 +64,16 @@ public class CommentController : ControllerBase PaginationData pageData = this.Request.GetPaginationData(); + IQueryable baseQuery = this.database.Comments.Where(c => c.Type == type); + if (type == CommentType.Level) { targetId = await this.database.Slots.Where(s => s.SlotId == slotId) .Where(s => s.CommentsEnabled && !s.Hidden) .Select(s => s.SlotId) .FirstOrDefaultAsync(); + + baseQuery = baseQuery.Where(c => c.TargetSlotId == targetId); } else { @@ -77,6 +81,8 @@ public class CommentController : ControllerBase .Where(u => u.CommentsEnabled) .Select(u => u.UserId) .FirstOrDefaultAsync(); + + baseQuery = baseQuery.Where(c => c.TargetUserId == targetId); } if (targetId == 0) return this.NotFound(); @@ -86,11 +92,10 @@ public class CommentController : ControllerBase where blockedProfile.UserId == token.UserId select blockedProfile.BlockedUserId).ToListAsync(); - List comments = (await this.database.Comments.Where(p => p.TargetId == targetId && p.Type == type) - .OrderByDescending(p => p.Timestamp) - .Where(p => !blockedUsers.Contains(p.PosterUserId)) + List comments = (await baseQuery.OrderByDescending(c => c.Timestamp) + .Where(c => !blockedUsers.Contains(c.PosterUserId)) .Include(c => c.Poster) - .Where(p => p.Poster.PermissionLevel != PermissionLevel.Banned) + .Where(c => c.Poster.PermissionLevel != PermissionLevel.Banned) .ApplyPagination(pageData) .ToListAsync()).ToSerializableList(c => GameComment.CreateFromEntity(c, token.UserId)); @@ -164,15 +169,15 @@ public class CommentController : ControllerBase bool canDelete; if (comment.Type == CommentType.Profile) { - canDelete = comment.PosterUserId == token.UserId || comment.TargetId == token.UserId; + canDelete = comment.PosterUserId == token.UserId || comment.TargetUserId == token.UserId; } else { if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); - if (slotId != comment.TargetId) return this.BadRequest(); + if (slotId != comment.TargetSlotId) return this.BadRequest(); - int slotCreator = await this.database.Slots.Where(s => s.SlotId == comment.TargetId) + int slotCreator = await this.database.Slots.Where(s => s.SlotId == comment.TargetSlotId) .Where(s => s.CommentsEnabled) .Select(s => s.CreatorId) .FirstOrDefaultAsync(); diff --git a/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationRemovalController.cs b/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationRemovalController.cs index 9395f01b..2ea4a3b3 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationRemovalController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationRemovalController.cs @@ -59,13 +59,13 @@ public class ModerationRemovalController : ControllerBase switch (comment.Type) { case CommentType.Level: - int slotCreatorId = await this.database.Slots.Where(s => s.SlotId == comment.TargetId) + int slotCreatorId = await this.database.Slots.Where(s => s.SlotId == comment.TargetSlotId) .Select(s => s.CreatorId) .FirstOrDefaultAsync(); canDelete = user.UserId == comment.PosterUserId || user.UserId == slotCreatorId; break; case CommentType.Profile: - canDelete = user.UserId == comment.PosterUserId || user.UserId == comment.TargetId; + canDelete = user.UserId == comment.PosterUserId || user.UserId == comment.TargetUserId; break; default: throw new ArgumentOutOfRangeException(nameof(commentId)); } diff --git a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs index 4fc956b6..e228c82b 100644 --- a/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/SlotPage.cshtml.cs @@ -57,9 +57,9 @@ public class SlotPage : BaseLayout this.CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && this.Slot.CommentsEnabled; if (this.CommentsEnabled) { - this.Comments = await this.Database.Comments.Include(p => p.Poster) - .OrderByDescending(p => p.Timestamp) - .Where(c => c.TargetId == id && c.Type == CommentType.Level) + this.Comments = await this.Database.Comments.Include(c => c.Poster) + .OrderByDescending(c => c.Timestamp) + .Where(c => c.Type == CommentType.Level && c.TargetSlotId == id) .Where(c => !blockedUsers.Contains(c.PosterUserId)) .Include(c => c.Poster) .Where(c => c.Poster.PermissionLevel != PermissionLevel.Banned) diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs index 35f7919b..ee85fc87 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml.cs @@ -96,7 +96,7 @@ public class UserPage : BaseLayout this.Comments = await this.Database.Comments.Include(p => p.Poster) .OrderByDescending(p => p.Timestamp) - .Where(p => p.TargetId == userId && p.Type == CommentType.Profile) + .Where(p => p.TargetUserId == userId && p.Type == CommentType.Profile) .Where(p => !blockedUsers.Contains(p.PosterUserId)) .Take(50) .ToDictionaryAsync(c => c, _ => (RatedCommentEntity?)null); diff --git a/ProjectLighthouse/Database/DatabaseContext.Comments.cs b/ProjectLighthouse/Database/DatabaseContext.Comments.cs index 5cce6750..7362936b 100644 --- a/ProjectLighthouse/Database/DatabaseContext.Comments.cs +++ b/ProjectLighthouse/Database/DatabaseContext.Comments.cs @@ -89,17 +89,15 @@ public partial class DatabaseContext if (await this.IsUserBlockedBy(userId, creatorId)) return false; } - this.Comments.Add - ( - new CommentEntity - { - PosterUserId = userId, - TargetId = targetId, - Type = type, - Message = message, - Timestamp = TimeHelper.TimestampMillis, - } - ); + this.Comments.Add(new CommentEntity + { + PosterUserId = userId, + TargetUserId = type == CommentType.Profile ? targetId : null, + TargetSlotId = type == CommentType.Level ? targetId : null, + Type = type, + Message = message, + Timestamp = TimeHelper.TimestampMillis, + }); await this.SaveChangesAsync(); return true; diff --git a/ProjectLighthouse/Migrations/20230215195324_ChangeLocationStorage.cs b/ProjectLighthouse/Migrations/20230215195324_ChangeLocationStorage.cs index 1faa5c44..96d208e4 100644 --- a/ProjectLighthouse/Migrations/20230215195324_ChangeLocationStorage.cs +++ b/ProjectLighthouse/Migrations/20230215195324_ChangeLocationStorage.cs @@ -59,7 +59,6 @@ namespace ProjectLighthouse.Migrations table: "Slots"); } - /// protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( diff --git a/ProjectLighthouse/Migrations/20230714212153_AddUserIdAndSlotIdToComment.cs b/ProjectLighthouse/Migrations/20230714212153_AddUserIdAndSlotIdToComment.cs new file mode 100644 index 00000000..083efccb --- /dev/null +++ b/ProjectLighthouse/Migrations/20230714212153_AddUserIdAndSlotIdToComment.cs @@ -0,0 +1,46 @@ +using LBPUnion.ProjectLighthouse.Database; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230714212153_AddUserIdAndSlotIdToComment")] + public partial class AddUserIdAndSlotIdToComment : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TargetSlotId", + table: "Comments", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "TargetUserId", + table: "Comments", + type: "int", + nullable: true); + + // Set SlotId and UserId to null if there isn't an existing slot or user + migrationBuilder.Sql("UPDATE Comments AS c INNER JOIN Users AS u ON c.TargetId = u.UserId AND c.Type = 0 SET c.TargetUserId = u.UserId"); + migrationBuilder.Sql("UPDATE Comments AS c INNER JOIN Slots AS s ON c.TargetId = s.SlotId AND c.Type = 1 SET c.TargetSlotId = s.SlotId"); + + // Delete rows that have null SlotIds or UserIds + migrationBuilder.Sql("DELETE FROM Comments WHERE TargetUserId IS NULL AND TargetSlotId IS NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TargetSlotId", + table: "Comments"); + + migrationBuilder.DropColumn( + name: "TargetUserId", + table: "Comments"); + } + } +} diff --git a/ProjectLighthouse/Migrations/20230714212234_AddForeignKeyConstraintToComment.cs b/ProjectLighthouse/Migrations/20230714212234_AddForeignKeyConstraintToComment.cs new file mode 100644 index 00000000..f3d68d0c --- /dev/null +++ b/ProjectLighthouse/Migrations/20230714212234_AddForeignKeyConstraintToComment.cs @@ -0,0 +1,72 @@ +using LBPUnion.ProjectLighthouse.Database; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230714212234_AddForeignKeyConstraintToComment")] + public partial class AddForeignKeyConstraintToComment : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TargetId", + table: "Comments"); + + migrationBuilder.CreateIndex( + name: "IX_Comments_TargetSlotId", + table: "Comments", + column: "TargetSlotId"); + + migrationBuilder.CreateIndex( + name: "IX_Comments_TargetUserId", + table: "Comments", + column: "TargetUserId"); + + migrationBuilder.AddForeignKey( + name: "FK_Comments_Slots_TargetSlotId", + table: "Comments", + column: "TargetSlotId", + principalTable: "Slots", + principalColumn: "SlotId", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Comments_Users_TargetUserId", + table: "Comments", + column: "TargetUserId", + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Comments_Slots_TargetSlotId", + table: "Comments"); + + migrationBuilder.DropForeignKey( + name: "FK_Comments_Users_TargetUserId", + table: "Comments"); + + migrationBuilder.DropIndex( + name: "IX_Comments_TargetSlotId", + table: "Comments"); + + migrationBuilder.DropIndex( + name: "IX_Comments_TargetUserId", + table: "Comments"); + + migrationBuilder.AddColumn( + name: "TargetId", + table: "Comments", + type: "int", + nullable: false, + defaultValue: 0); + } + } +} diff --git a/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 7a419db4..386017f5 100644 --- a/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -640,7 +640,10 @@ namespace ProjectLighthouse.Migrations b.Property("PosterUserId") .HasColumnType("int"); - b.Property("TargetId") + b.Property("TargetSlotId") + .HasColumnType("int"); + + b.Property("TargetUserId") .HasColumnType("int"); b.Property("ThumbsDown") @@ -659,6 +662,10 @@ namespace ProjectLighthouse.Migrations b.HasIndex("PosterUserId"); + b.HasIndex("TargetSlotId"); + + b.HasIndex("TargetUserId"); + b.ToTable("Comments"); }); @@ -1325,7 +1332,19 @@ namespace ProjectLighthouse.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "TargetSlot") + .WithMany() + .HasForeignKey("TargetSlotId"); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity", "TargetUser") + .WithMany() + .HasForeignKey("TargetUserId"); + b.Navigation("Poster"); + + b.Navigation("TargetSlot"); + + b.Navigation("TargetUser"); }); modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Profile.LastContactEntity", b => diff --git a/ProjectLighthouse/Types/Entities/Profile/CommentEntity.cs b/ProjectLighthouse/Types/Entities/Profile/CommentEntity.cs index ec2cd640..3b139aa3 100644 --- a/ProjectLighthouse/Types/Entities/Profile/CommentEntity.cs +++ b/ProjectLighthouse/Types/Entities/Profile/CommentEntity.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; namespace LBPUnion.ProjectLighthouse.Types.Entities.Profile; @@ -18,11 +19,23 @@ public class CommentEntity public int PosterUserId { get; set; } - public int TargetId { get; set; } - [ForeignKey(nameof(PosterUserId))] public UserEntity Poster { get; set; } + public CommentType Type { get; set; } + + #nullable enable + public int? TargetSlotId { get; set; } + + [ForeignKey(nameof(TargetSlotId))] + public SlotEntity? TargetSlot { get; set; } + + public int? TargetUserId { get; set; } + + [ForeignKey(nameof(TargetUserId))] + public UserEntity? TargetUser { get; set; } + #nullable disable + public bool Deleted { get; set; } public string DeletedType { get; set; } @@ -33,29 +46,25 @@ public class CommentEntity public string Message { get; set; } - public CommentType Type { get; set; } - public int ThumbsUp { get; set; } public int ThumbsDown { get; set; } public string GetCommentMessage(DatabaseContext database) { - if (!this.Deleted) - { - return this.Message; - } + if (!this.Deleted) return this.Message; - if (this.DeletedBy == this.Poster.Username) - { - return "This comment has been deleted by the author."; - } + if (this.DeletedBy == this.Poster.Username) return "This comment has been deleted by the author."; UserEntity deletedBy = database.Users.FirstOrDefault(u => u.Username == this.DeletedBy); - if (deletedBy != null && deletedBy.UserId == this.TargetId) - { + if (deletedBy == null) return "This comment has been deleted."; + + // If the owner of the comment section deletes + if (deletedBy.UserId == this.TargetUserId || deletedBy.UserId == database.Slots.Find(this.TargetSlotId)?.CreatorId) return "This comment has been deleted by the player."; - } + + if (this.DeletedType == "moderator" && deletedBy.IsModerator) + return "This comment has been deleted by a moderator."; return "This comment has been deleted."; } diff --git a/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs b/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs index ae0aaed0..3de55ee4 100644 --- a/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs +++ b/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs @@ -37,7 +37,7 @@ public class GameDeveloperSlot : SlotBase, INeedsPreparationForSerialization var stats = await database.Slots.Where(s => s.SlotId == this.SlotId) .Select(_ => new { - CommentCount = database.Comments.Count(c => c.TargetId == this.SlotId && c.Type == CommentType.Level), + CommentCount = database.Comments.Count(c => c.Type == CommentType.Level && c.TargetSlotId == this.SlotId), PhotoCount = database.Photos.Count(p => p.SlotId == this.SlotId), }) .OrderBy(_ => 1) diff --git a/ProjectLighthouse/Types/Serialization/GameUser.cs b/ProjectLighthouse/Types/Serialization/GameUser.cs index 885cf61e..cb365ed1 100644 --- a/ProjectLighthouse/Types/Serialization/GameUser.cs +++ b/ProjectLighthouse/Types/Serialization/GameUser.cs @@ -168,7 +168,7 @@ public class GameUser : ILbpSerializable, INeedsPreparationForSerialization BonusSlots = database.Users.Where(u => u.UserId == this.UserId).Select(u => u.AdminGrantedSlots).First(), PlaylistCount = database.Playlists.Count(p => p.CreatorId == this.UserId), ReviewCount = database.Reviews.Count(r => r.ReviewerId == this.UserId), - CommentCount = database.Comments.Count(c => c.TargetId == this.UserId && c.Type == CommentType.Profile), + CommentCount = database.Comments.Count(c => c.TargetUserId == this.UserId), HeartCount = database.HeartedProfiles.Count(h => h.HeartedUserId == this.UserId), PhotosByMeCount = database.Photos.Count(p => p.CreatorId == this.UserId), PhotosWithMeCount = database.Photos.Include(p => p.PhotoSubjects) diff --git a/ProjectLighthouse/Types/Serialization/GameUserSlot.cs b/ProjectLighthouse/Types/Serialization/GameUserSlot.cs index d6735b20..978818be 100644 --- a/ProjectLighthouse/Types/Serialization/GameUserSlot.cs +++ b/ProjectLighthouse/Types/Serialization/GameUserSlot.cs @@ -11,7 +11,6 @@ using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Level; -using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Misc; using LBPUnion.ProjectLighthouse.Types.Users; @@ -241,7 +240,7 @@ public class GameUserSlot : SlotBase, INeedsPreparationForSerialization ThumbsUp = database.RatedLevels.Count(r => r.SlotId == this.SlotId && r.Rating == 1), ThumbsDown = database.RatedLevels.Count(r => r.SlotId == this.SlotId && r.Rating == -1), ReviewCount = database.Reviews.Count(r => r.SlotId == this.SlotId), - CommentCount = database.Comments.Count(c => c.TargetId == this.SlotId && c.Type == CommentType.Level), + CommentCount = database.Comments.Count(c => c.TargetSlotId == this.SlotId), PhotoCount = database.Photos.Count(p => p.SlotId == this.SlotId), AuthorPhotoCount = database.Photos.Count(p => p.SlotId == this.SlotId && p.CreatorId == this.CreatorId), HeartCount = database.HeartedLevels.Count(h => h.SlotId == this.SlotId),