Add proper relationships to Comments (#838)

* Add specific foreign keys for comment targets

* Remove inheritdoc tags from migration

* Fix punctuation of deleted comment message and add mod deletion message

* Fix broken merge

* Cleanup comment queries
This commit is contained in:
Josh 2023-08-24 13:58:03 -05:00 committed by GitHub
parent 9cb9fb62e4
commit a316c866c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 192 additions and 45 deletions

View file

@ -64,12 +64,16 @@ public class CommentController : ControllerBase
PaginationData pageData = this.Request.GetPaginationData();
IQueryable<CommentEntity> 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<GameComment> comments = (await this.database.Comments.Where(p => p.TargetId == targetId && p.Type == type)
.OrderByDescending(p => p.Timestamp)
.Where(p => !blockedUsers.Contains(p.PosterUserId))
List<GameComment> 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();

View file

@ -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));
}

View file

@ -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)

View file

@ -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);

View file

@ -89,17 +89,15 @@ public partial class DatabaseContext
if (await this.IsUserBlockedBy(userId, creatorId)) return false;
}
this.Comments.Add
(
new CommentEntity
this.Comments.Add(new CommentEntity
{
PosterUserId = userId,
TargetId = targetId,
TargetUserId = type == CommentType.Profile ? targetId : null,
TargetSlotId = type == CommentType.Level ? targetId : null,
Type = type,
Message = message,
Timestamp = TimeHelper.TimestampMillis,
}
);
});
await this.SaveChangesAsync();
return true;

View file

@ -59,7 +59,6 @@ namespace ProjectLighthouse.Migrations
table: "Slots");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(

View file

@ -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<int>(
name: "TargetSlotId",
table: "Comments",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
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");
}
}
}

View file

@ -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<int>(
name: "TargetId",
table: "Comments",
type: "int",
nullable: false,
defaultValue: 0);
}
}
}

View file

@ -640,7 +640,10 @@ namespace ProjectLighthouse.Migrations
b.Property<int>("PosterUserId")
.HasColumnType("int");
b.Property<int>("TargetId")
b.Property<int?>("TargetSlotId")
.HasColumnType("int");
b.Property<int?>("TargetUserId")
.HasColumnType("int");
b.Property<int>("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 =>

View file

@ -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.";
}

View file

@ -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)

View file

@ -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)

View file

@ -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),