From 546e900204d2e055f841ce2fce783d5241e15a45 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 18 Dec 2024 21:26:35 +0100 Subject: [PATCH] feat(backend): report context, fix deleting reports --- .../Moderation/ReportsController.cs | 7 ++ Foxnouns.Backend/Database/DatabaseContext.cs | 6 ++ .../20241218195457_AddContextToReports.cs | 30 +++++++++ ...41218201855_MakeAuditLogReportsNullable.cs | 65 +++++++++++++++++++ .../DatabaseContextModelSnapshot.cs | 15 ++++- .../Database/Models/AuditLogEntry.cs | 1 + Foxnouns.Backend/Database/Models/Report.cs | 3 + Foxnouns.Backend/Dto/Moderation.cs | 2 +- .../Utils/ValidationUtils.Strings.cs | 7 ++ 9 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 Foxnouns.Backend/Database/Migrations/20241218195457_AddContextToReports.cs create mode 100644 Foxnouns.Backend/Database/Migrations/20241218201855_MakeAuditLogReportsNullable.cs diff --git a/Foxnouns.Backend/Controllers/Moderation/ReportsController.cs b/Foxnouns.Backend/Controllers/Moderation/ReportsController.cs index 3e9f905..c46defb 100644 --- a/Foxnouns.Backend/Controllers/Moderation/ReportsController.cs +++ b/Foxnouns.Backend/Controllers/Moderation/ReportsController.cs @@ -18,6 +18,7 @@ using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Dto; using Foxnouns.Backend.Middleware; using Foxnouns.Backend.Services; +using Foxnouns.Backend.Utils; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; @@ -49,6 +50,8 @@ public class ReportsController( [FromBody] CreateReportRequest req ) { + ValidationUtils.Validate([("context", ValidationUtils.ValidateReportContext(req.Context))]); + User target = await db.ResolveUserAsync(id); if (target.Id == CurrentUser!.Id) @@ -96,6 +99,7 @@ public class ReportsController( TargetUserId = target.Id, TargetMemberId = null, Reason = req.Reason, + Context = req.Context, TargetType = ReportTargetType.User, TargetSnapshot = snapshot, }; @@ -112,6 +116,8 @@ public class ReportsController( [FromBody] CreateReportRequest req ) { + ValidationUtils.Validate([("context", ValidationUtils.ValidateReportContext(req.Context))]); + Member target = await db.ResolveMemberAsync(id); if (target.User.Id == CurrentUser!.Id) @@ -158,6 +164,7 @@ public class ReportsController( TargetUserId = target.User.Id, TargetMemberId = target.Id, Reason = req.Reason, + Context = req.Context, TargetType = ReportTargetType.Member, TargetSnapshot = snapshot, }; diff --git a/Foxnouns.Backend/Database/DatabaseContext.cs b/Foxnouns.Backend/Database/DatabaseContext.cs index 9baa143..42a5009 100644 --- a/Foxnouns.Backend/Database/DatabaseContext.cs +++ b/Foxnouns.Backend/Database/DatabaseContext.cs @@ -108,6 +108,12 @@ public class DatabaseContext(DbContextOptions options) : DbContext(options) .HasFilter("fediverse_application_id IS NULL") .IsUnique(); + modelBuilder + .Entity() + .HasOne(e => e.Report) + .WithOne(e => e.AuditLogEntry) + .OnDelete(DeleteBehavior.SetNull); + modelBuilder.Entity().Property(u => u.Sid).HasDefaultValueSql("find_free_user_sid()"); modelBuilder.Entity().Property(u => u.Fields).HasColumnType("jsonb"); modelBuilder.Entity().Property(u => u.Names).HasColumnType("jsonb"); diff --git a/Foxnouns.Backend/Database/Migrations/20241218195457_AddContextToReports.cs b/Foxnouns.Backend/Database/Migrations/20241218195457_AddContextToReports.cs new file mode 100644 index 0000000..3dc6029 --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20241218195457_AddContextToReports.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + [DbContext(typeof(DatabaseContext))] + [Migration("20241218195457_AddContextToReports")] + public partial class AddContextToReports : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "context", + table: "reports", + type: "text", + nullable: true + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "context", table: "reports"); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/20241218201855_MakeAuditLogReportsNullable.cs b/Foxnouns.Backend/Database/Migrations/20241218201855_MakeAuditLogReportsNullable.cs new file mode 100644 index 0000000..53a1f72 --- /dev/null +++ b/Foxnouns.Backend/Database/Migrations/20241218201855_MakeAuditLogReportsNullable.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Foxnouns.Backend.Database.Migrations +{ + /// + [DbContext(typeof(DatabaseContext))] + [Migration("20241218201855_MakeAuditLogReportsNullable")] + public partial class MakeAuditLogReportsNullable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_audit_log_reports_report_id", + table: "audit_log" + ); + + migrationBuilder.DropIndex(name: "ix_audit_log_report_id", table: "audit_log"); + + migrationBuilder.CreateIndex( + name: "ix_audit_log_report_id", + table: "audit_log", + column: "report_id", + unique: true + ); + + migrationBuilder.AddForeignKey( + name: "fk_audit_log_reports_report_id", + table: "audit_log", + column: "report_id", + principalTable: "reports", + principalColumn: "id", + onDelete: ReferentialAction.SetNull + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_audit_log_reports_report_id", + table: "audit_log" + ); + + migrationBuilder.DropIndex(name: "ix_audit_log_report_id", table: "audit_log"); + + migrationBuilder.CreateIndex( + name: "ix_audit_log_report_id", + table: "audit_log", + column: "report_id" + ); + + migrationBuilder.AddForeignKey( + name: "fk_audit_log_reports_report_id", + table: "audit_log", + column: "report_id", + principalTable: "reports", + principalColumn: "id" + ); + } + } +} diff --git a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs index 79a0232..a2fa6f2 100644 --- a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs +++ b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs @@ -113,6 +113,7 @@ namespace Foxnouns.Backend.Database.Migrations .HasName("pk_audit_log"); b.HasIndex("ReportId") + .IsUnique() .HasDatabaseName("ix_audit_log_report_id"); b.ToTable("audit_log", (string)null); @@ -409,6 +410,10 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("bigint") .HasColumnName("id"); + b.Property("Context") + .HasColumnType("text") + .HasColumnName("context"); + b.Property("Reason") .HasColumnType("integer") .HasColumnName("reason"); @@ -675,8 +680,9 @@ namespace Foxnouns.Backend.Database.Migrations modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuditLogEntry", b => { b.HasOne("Foxnouns.Backend.Database.Models.Report", "Report") - .WithMany() - .HasForeignKey("ReportId") + .WithOne("AuditLogEntry") + .HasForeignKey("Foxnouns.Backend.Database.Models.AuditLogEntry", "ReportId") + .OnDelete(DeleteBehavior.SetNull) .HasConstraintName("fk_audit_log_reports_report_id"); b.Navigation("Report"); @@ -839,6 +845,11 @@ namespace Foxnouns.Backend.Database.Migrations b.Navigation("ProfileFlags"); }); + modelBuilder.Entity("Foxnouns.Backend.Database.Models.Report", b => + { + b.Navigation("AuditLogEntry"); + }); + modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => { b.Navigation("AuthMethods"); diff --git a/Foxnouns.Backend/Database/Models/AuditLogEntry.cs b/Foxnouns.Backend/Database/Models/AuditLogEntry.cs index a4983ae..c65e675 100644 --- a/Foxnouns.Backend/Database/Models/AuditLogEntry.cs +++ b/Foxnouns.Backend/Database/Models/AuditLogEntry.cs @@ -12,6 +12,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.ComponentModel.DataAnnotations.Schema; using Foxnouns.Backend.Utils; using Newtonsoft.Json; diff --git a/Foxnouns.Backend/Database/Models/Report.cs b/Foxnouns.Backend/Database/Models/Report.cs index e668f44..47b994f 100644 --- a/Foxnouns.Backend/Database/Models/Report.cs +++ b/Foxnouns.Backend/Database/Models/Report.cs @@ -29,9 +29,12 @@ public class Report : BaseModel public ReportStatus Status { get; set; } public ReportReason Reason { get; init; } + public string? Context { get; init; } public ReportTargetType TargetType { get; init; } public string? TargetSnapshot { get; init; } + + public AuditLogEntry? AuditLogEntry { get; set; } } [JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] diff --git a/Foxnouns.Backend/Dto/Moderation.cs b/Foxnouns.Backend/Dto/Moderation.cs index 0de65c7..f9e6ab7 100644 --- a/Foxnouns.Backend/Dto/Moderation.cs +++ b/Foxnouns.Backend/Dto/Moderation.cs @@ -57,7 +57,7 @@ public record NotificationResponse( public record AuditLogEntity(Snowflake Id, string Username); -public record CreateReportRequest(ReportReason Reason); +public record CreateReportRequest(ReportReason Reason, string? Context = null); public record IgnoreReportRequest(string? Reason = null); diff --git a/Foxnouns.Backend/Utils/ValidationUtils.Strings.cs b/Foxnouns.Backend/Utils/ValidationUtils.Strings.cs index ea12043..d38f274 100644 --- a/Foxnouns.Backend/Utils/ValidationUtils.Strings.cs +++ b/Foxnouns.Backend/Utils/ValidationUtils.Strings.cs @@ -196,6 +196,13 @@ public static partial class ValidationUtils }; } + public const int MaximumReportContextLength = 512; + + public static ValidationError? ValidateReportContext(string? context) => + context?.Length > MaximumReportContextLength + ? ValidationError.GenericValidationError("Avatar is too large", null) + : null; + public const int MinimumPasswordLength = 12; public const int MaximumPasswordLength = 1024;