Compare commits
2 Commits
d9747daab9
...
21cf212d8f
Author | SHA1 | Date | |
---|---|---|---|
21cf212d8f | |||
c6cb2a0dc3 |
30
DysonNetwork.Sphere/Account/AbuseReport.cs
Normal file
30
DysonNetwork.Sphere/Account/AbuseReport.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Account;
|
||||
|
||||
public enum AbuseReportType
|
||||
{
|
||||
Copyright,
|
||||
Harassment,
|
||||
Impersonation,
|
||||
OffensiveContent,
|
||||
Spam,
|
||||
PrivacyViolation,
|
||||
IllegalContent,
|
||||
Other
|
||||
}
|
||||
|
||||
public class AbuseReport : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[MaxLength(4096)] public string ResourceIdentifier { get; set; } = null!;
|
||||
public AbuseReportType Type { get; set; }
|
||||
[MaxLength(8192)] public string Reason { get; set; } = null!;
|
||||
|
||||
public Instant? ResolvedAt { get; set; }
|
||||
[MaxLength(8192)] public string? Resolution { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Account Account { get; set; } = null!;
|
||||
}
|
@ -4,7 +4,7 @@ using Point = NetTopologySuite.Geometries.Point;
|
||||
|
||||
namespace DysonNetwork.Sphere.Account;
|
||||
|
||||
public class ActionLogType
|
||||
public abstract class ActionLogType
|
||||
{
|
||||
public const string NewLogin = "login";
|
||||
public const string ChallengeAttempt = "challenges.attempt";
|
||||
@ -55,5 +55,4 @@ public class ActionLog : ModelBase
|
||||
public Guid AccountId { get; set; }
|
||||
public Account Account { get; set; } = null!;
|
||||
public Guid? SessionId { get; set; }
|
||||
public Auth.Session? Session { get; set; } = null!;
|
||||
}
|
@ -54,6 +54,7 @@ public class AppDatabase(
|
||||
public DbSet<NotificationPushSubscription> NotificationPushSubscriptions { get; set; }
|
||||
public DbSet<Badge> Badges { get; set; }
|
||||
public DbSet<ActionLog> ActionLogs { get; set; }
|
||||
public DbSet<AbuseReport> AbuseReports { get; set; }
|
||||
|
||||
public DbSet<Session> AuthSessions { get; set; }
|
||||
public DbSet<Challenge> AuthChallenges { get; set; }
|
||||
|
@ -2269,7 +2269,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -852,7 +852,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
file_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
user_meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
sensitive_marks = table.Column<List<CloudFileSensitiveMark>>(type: "jsonb", nullable: true),
|
||||
sensitive_marks = table.Column<List<ContentSensitiveMark>>(type: "jsonb", nullable: true),
|
||||
mime_type = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
hash = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
size = table.Column<long>(type: "bigint", nullable: false),
|
||||
|
@ -2287,7 +2287,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2292,7 +2292,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2262,7 +2262,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2266,7 +2266,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2266,7 +2266,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2268,7 +2268,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2268,7 +2268,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2274,7 +2274,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2274,7 +2274,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2290,7 +2290,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2294,7 +2294,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2236,7 +2236,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2249,7 +2249,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2260,7 +2260,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2279,7 +2279,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2337,7 +2337,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2341,7 +2341,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
@ -2341,7 +2341,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
|
3623
DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.Designer.cs
generated
Normal file
3623
DysonNetwork.Sphere/Migrations/20250624160304_DropActionLogSessionFk.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,38 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Sphere.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DropActionLogSessionFk : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_action_logs_auth_sessions_session_id",
|
||||
table: "action_logs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_action_logs_session_id",
|
||||
table: "action_logs");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_action_logs_session_id",
|
||||
table: "action_logs",
|
||||
column: "session_id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_action_logs_auth_sessions_session_id",
|
||||
table: "action_logs",
|
||||
column: "session_id",
|
||||
principalTable: "auth_sessions",
|
||||
principalColumn: "id");
|
||||
}
|
||||
}
|
||||
}
|
3696
DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.Designer.cs
generated
Normal file
3696
DysonNetwork.Sphere/Migrations/20250625150644_SafetyAbuseReport.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Sphere.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SafetyAbuseReport : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<List<ContentSensitiveMark>>(
|
||||
name: "sensitive_marks",
|
||||
table: "posts",
|
||||
type: "jsonb",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "abuse_reports",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
resource_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
reason = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
resolved_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
resolution = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_abuse_reports", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_abuse_reports_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_abuse_reports_account_id",
|
||||
table: "abuse_reports",
|
||||
column: "account_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "abuse_reports");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "sensitive_marks",
|
||||
table: "posts");
|
||||
}
|
||||
}
|
||||
}
|
@ -32,6 +32,63 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("reason");
|
||||
|
||||
b.Property<string>("Resolution")
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("resolution");
|
||||
|
||||
b.Property<Instant?>("ResolvedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("resolved_at");
|
||||
|
||||
b.Property<string>("ResourceIdentifier")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("resource_identifier");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_abuse_reports");
|
||||
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_abuse_reports_account_id");
|
||||
|
||||
b.ToTable("abuse_reports", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -314,9 +371,6 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_action_logs_account_id");
|
||||
|
||||
b.HasIndex("SessionId")
|
||||
.HasDatabaseName("ix_action_logs_session_id");
|
||||
|
||||
b.ToTable("action_logs", (string)null);
|
||||
});
|
||||
|
||||
@ -1615,6 +1669,10 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasAnnotation("Npgsql:TsVectorConfig", "simple")
|
||||
.HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" });
|
||||
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
@ -2338,7 +2396,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("post_id");
|
||||
|
||||
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
@ -2847,6 +2905,18 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
b.ToTable("post_tag_links", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||
.WithMany()
|
||||
.HasForeignKey("AccountId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_abuse_reports_accounts_account_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||
@ -2892,14 +2962,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_action_logs_accounts_account_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session")
|
||||
.WithMany()
|
||||
.HasForeignKey("SessionId")
|
||||
.HasConstraintName("fk_action_logs_auth_sessions_session_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
|
||||
b.Navigation("Session");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b =>
|
||||
|
@ -38,6 +38,7 @@ public class Post : ModelBase, IIdentifiedResource, IActivity
|
||||
|
||||
public PostType Type { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
|
||||
|
||||
public int ViewsUnique { get; set; }
|
||||
public int ViewsTotal { get; set; }
|
||||
|
153
DysonNetwork.Sphere/Safety/AbuseReportController.cs
Normal file
153
DysonNetwork.Sphere/Safety/AbuseReportController.cs
Normal file
@ -0,0 +1,153 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using DysonNetwork.Sphere.Account;
|
||||
using DysonNetwork.Sphere.Permission;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace DysonNetwork.Sphere.Safety;
|
||||
|
||||
[ApiController]
|
||||
[Route("/safety/reports")]
|
||||
public class AbuseReportController(
|
||||
SafetyService safety
|
||||
) : ControllerBase
|
||||
{
|
||||
public class CreateReportRequest
|
||||
{
|
||||
[Required] public string ResourceIdentifier { get; set; } = null!;
|
||||
|
||||
[Required] public AbuseReportType Type { get; set; }
|
||||
|
||||
[Required]
|
||||
[MinLength(10)]
|
||||
[MaxLength(1000)]
|
||||
public string Reason { get; set; } = null!;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
[Authorize]
|
||||
[ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<AbuseReport>> CreateReport([FromBody] CreateReportRequest request)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
try
|
||||
{
|
||||
var report = await safety.CreateReport(
|
||||
request.ResourceIdentifier,
|
||||
request.Type,
|
||||
request.Reason,
|
||||
currentUser.Id
|
||||
);
|
||||
|
||||
return Ok(report);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
[Authorize]
|
||||
[RequiredPermission("safety", "reports.view")]
|
||||
[ProducesResponseType<List<AbuseReport>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<List<AbuseReport>>> GetReports(
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 20,
|
||||
[FromQuery] bool includeResolved = false
|
||||
)
|
||||
{
|
||||
var totalCount = await safety.CountReports(includeResolved);
|
||||
var reports = await safety.GetReports(skip, take, includeResolved);
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
return Ok(reports);
|
||||
}
|
||||
|
||||
[HttpGet("me")]
|
||||
[Authorize]
|
||||
[ProducesResponseType<List<AbuseReport>>(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public async Task<ActionResult<List<AbuseReport>>> GetMyReports(
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 20,
|
||||
[FromQuery] bool includeResolved = false)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
var totalCount = await safety.CountUserReports(currentUser.Id, includeResolved);
|
||||
var reports = await safety.GetUserReports(currentUser.Id, skip, take, includeResolved);
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
return Ok(reports);
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[Authorize]
|
||||
[RequiredPermission("safety", "reports.view")]
|
||||
[ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<AbuseReport>> GetReportById(Guid id)
|
||||
{
|
||||
var report = await safety.GetReportById(id);
|
||||
return report == null ? NotFound() : Ok(report);
|
||||
}
|
||||
|
||||
[HttpGet("me/{id}")]
|
||||
[Authorize]
|
||||
[ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<AbuseReport>> GetMyReportById(Guid id)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
var report = await safety.GetReportById(id);
|
||||
if (report == null) return NotFound();
|
||||
|
||||
// Ensure the user only accesses their own reports
|
||||
if (report.AccountId != currentUser.Id) return Forbid();
|
||||
|
||||
return Ok(report);
|
||||
}
|
||||
|
||||
public class ResolveReportRequest
|
||||
{
|
||||
[Required]
|
||||
[MinLength(5)]
|
||||
[MaxLength(1000)]
|
||||
public string Resolution { get; set; } = null!;
|
||||
}
|
||||
|
||||
[HttpPost("{id}/resolve")]
|
||||
[Authorize]
|
||||
[RequiredPermission("safety", "reports.resolve")]
|
||||
[ProducesResponseType<AbuseReport>(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<AbuseReport>> ResolveReport(Guid id, [FromBody] ResolveReportRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var report = await safety.ResolveReport(id, request.Resolution);
|
||||
return Ok(report);
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("count")]
|
||||
[Authorize]
|
||||
[RequiredPermission("safety", "reports.view")]
|
||||
[ProducesResponseType<object>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> GetReportsCount()
|
||||
{
|
||||
var count = await safety.GetPendingReportsCount();
|
||||
return Ok(new { pendingCount = count });
|
||||
}
|
||||
}
|
105
DysonNetwork.Sphere/Safety/SafetyService.cs
Normal file
105
DysonNetwork.Sphere/Safety/SafetyService.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using DysonNetwork.Sphere.Account;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Safety;
|
||||
|
||||
public class SafetyService(AppDatabase db, ILogger<SafetyService> logger)
|
||||
{
|
||||
public async Task<AbuseReport> CreateReport(string resourceIdentifier, AbuseReportType type, string reason, Guid accountId)
|
||||
{
|
||||
// Check if a similar report already exists from this user
|
||||
var existingReport = await db.AbuseReports
|
||||
.Where(r => r.ResourceIdentifier == resourceIdentifier &&
|
||||
r.AccountId == accountId &&
|
||||
r.DeletedAt == null)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (existingReport != null)
|
||||
{
|
||||
throw new InvalidOperationException("You have already reported this content.");
|
||||
}
|
||||
|
||||
var report = new AbuseReport
|
||||
{
|
||||
ResourceIdentifier = resourceIdentifier,
|
||||
Type = type,
|
||||
Reason = reason,
|
||||
AccountId = accountId
|
||||
};
|
||||
|
||||
db.AbuseReports.Add(report);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
logger.LogInformation("New abuse report created: {ReportId} for resource {ResourceId}",
|
||||
report.Id, resourceIdentifier);
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
public async Task<int> CountReports(bool includeResolved = false)
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => includeResolved || r.ResolvedAt == null)
|
||||
.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<int> CountUserReports(Guid accountId, bool includeResolved = false)
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => r.AccountId == accountId)
|
||||
.Where(r => includeResolved || r.ResolvedAt == null)
|
||||
.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<List<AbuseReport>> GetReports(int skip = 0, int take = 20, bool includeResolved = false)
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => includeResolved || r.ResolvedAt == null)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.Include(r => r.Account)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<AbuseReport>> GetUserReports(Guid accountId, int skip = 0, int take = 20, bool includeResolved = false)
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => r.AccountId == accountId)
|
||||
.Where(r => includeResolved || r.ResolvedAt == null)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<AbuseReport?> GetReportById(Guid id)
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Include(r => r.Account)
|
||||
.FirstOrDefaultAsync(r => r.Id == id);
|
||||
}
|
||||
|
||||
public async Task<AbuseReport> ResolveReport(Guid id, string resolution)
|
||||
{
|
||||
var report = await db.AbuseReports.FindAsync(id);
|
||||
if (report == null)
|
||||
{
|
||||
throw new KeyNotFoundException("Report not found");
|
||||
}
|
||||
|
||||
report.ResolvedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
report.Resolution = resolution;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return report;
|
||||
}
|
||||
|
||||
public async Task<int> GetPendingReportsCount()
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => r.ResolvedAt == null)
|
||||
.CountAsync();
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
using System.Threading.RateLimiting;
|
||||
using DysonNetwork.Sphere.Connection.WebReader;
|
||||
using DysonNetwork.Sphere.Safety;
|
||||
using DysonNetwork.Sphere.Wallet.PaymentHandlers;
|
||||
using tusdotnet.Stores;
|
||||
|
||||
@ -224,6 +225,7 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<IRealtimeService, LivekitRealtimeService>();
|
||||
services.AddScoped<WebReaderService>();
|
||||
services.AddScoped<AfdianPaymentHandler>();
|
||||
services.AddScoped<SafetyService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? FileMeta { get; set; } = null!;
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? UserMeta { get; set; } = null!;
|
||||
[Column(TypeName = "jsonb")] public List<CloudFileSensitiveMark>? SensitiveMarks { get; set; } = [];
|
||||
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
|
||||
[MaxLength(256)] public string? MimeType { get; set; }
|
||||
[MaxLength(256)] public string? Hash { get; set; }
|
||||
public long Size { get; set; }
|
||||
@ -98,7 +98,7 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource
|
||||
public string ResourceIdentifier => $"file/{Id}";
|
||||
}
|
||||
|
||||
public enum CloudFileSensitiveMark
|
||||
public enum ContentSensitiveMark
|
||||
{
|
||||
Language,
|
||||
SexualContent,
|
||||
|
@ -28,6 +28,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fadcd336f9cde4e71998a851d7eb945bb87e00_003F0c_003F96dc130e_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEvents_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F20_003F86914b63_003FEvents_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F109293935a4844d5aa1610150b96edcde55000_003Fb7_003F8b7e5594_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3bef61b8a21d4c8e96872ecdd7782fa0e55000_003F49_003F21ccb952_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fbf_003F44af6d95_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExifTag_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa932cb9090ed48088111ae919dcdd9021ba00_003Fd7_003F0472c800_003FExifTag_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExifTag_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003F5c_003F8ed75f18_003FExifTag_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
Loading…
x
Reference in New Issue
Block a user