-using System.Numerics;
+using System.Linq;
+using System.Numerics;
using Content.Client.Administration.UI.BanList.Bans;
using Content.Client.Administration.UI.BanList.RoleBans;
using Content.Client.Eui;
return date.ToString("MM/dd/yyyy h:mm tt");
}
- public static void SetData<T>(IBanListLine<T> line, SharedServerBan ban) where T : SharedServerBan
+ public static void SetData<T>(IBanListLine<T> line, SharedBan ban) where T : SharedBan
{
line.Reason.Text = ban.Reason;
line.BanTime.Text = FormatDate(ban.BanTime);
line.BanningAdmin.Text = ban.BanningAdminName;
}
- private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedServerBan
+ private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedBan
{
_popup?.Close();
_popup = null;
var ban = line.Ban;
var id = ban.Id == null ? string.Empty : Loc.GetString("ban-list-id", ("id", ban.Id.Value));
- var ip = ban.Address == null
+ var ip = ban.Addresses.Length == 0
? string.Empty
- : Loc.GetString("ban-list-ip", ("ip", ban.Address.Value.address));
- var hwid = ban.HWId == null ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", ban.HWId));
- var guid = ban.UserId == null
+ : Loc.GetString("ban-list-ip", ("ip", string.Join(',', ban.Addresses.Select(a => a.address))));
+ var hwid = ban.HWIds.Length == 0 ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", string.Join(',', ban.HWIds)));
+ var guid = ban.UserIds.Length == 0
? string.Empty
- : Loc.GetString("ban-list-guid", ("guid", ban.UserId.Value.ToString()));
+ : Loc.GetString("ban-list-guid", ("guid", string.Join(',', ban.UserIds)));
_popup = new BanListIdsPopup(id, ip, hwid, guid);
RobustXamlLoader.Load(this);
}
- public void SetBans(List<SharedServerBan> bans)
+ public void SetBans(List<SharedBan> bans)
{
for (var i = Bans.ChildCount - 1; i >= 1; i--)
{
namespace Content.Client.Administration.UI.BanList.Bans;
[GenerateTypedNameReferences]
-public sealed partial class BanListLine : BoxContainer, IBanListLine<SharedServerBan>
+public sealed partial class BanListLine : BoxContainer, IBanListLine<SharedBan>
{
- public SharedServerBan Ban { get; }
+ public SharedBan Ban { get; }
public event Action<BanListLine>? IdsClicked;
- public BanListLine(SharedServerBan ban)
+ public BanListLine(SharedBan ban)
{
RobustXamlLoader.Load(this);
namespace Content.Client.Administration.UI.BanList;
-public interface IBanListLine<T> where T : SharedServerBan
+public interface IBanListLine<T> where T : SharedBan
{
T Ban { get; }
Label Reason { get; }
RobustXamlLoader.Load(this);
}
- public void SetRoleBans(List<SharedServerRoleBan> bans)
+ public void SetRoleBans(List<SharedBan> bans)
{
for (var i = RoleBans.ChildCount - 1; i >= 1; i--)
{
namespace Content.Client.Administration.UI.BanList.RoleBans;
[GenerateTypedNameReferences]
-public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedServerRoleBan>
+public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedBan>
{
- public SharedServerRoleBan Ban { get; }
+ public SharedBan Ban { get; }
public event Action<RoleBanListLine>? IdsClicked;
- public RoleBanListLine(SharedServerRoleBan ban)
+ public RoleBanListLine(SharedBan ban)
{
RobustXamlLoader.Load(this);
IdsHidden.OnPressed += IdsPressed;
BanListEui.SetData(this, ban);
- Role.Text = ban.Role;
+ Role.Text = string.Join(", ", ban.Roles ?? []);
}
private void IdsPressed(ButtonEventArgs buttonEventArgs)
TimeLabel.Text = Note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
ServerLabel.Text = Note.ServerName ?? "Unknown";
- RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round;
+ RoundLabel.Text = Note.Rounds.Length == 0 ? "Unknown round" : "Round " + string.Join(',', Note.Rounds);
AdminLabel.Text = Note.CreatedByName;
PlaytimeLabel.Text = $"{Note.PlaytimeAtNote.TotalHours: 0.0}h";
private string FormatRoleBanMessage()
{
- var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new[] { "unknown" })} ");
+ var rolesText = string.Join(
+ ", ",
+ // Explicit cast here to avoid sandbox violation.
+ (IEnumerable<BanRoleDef>?)Note.BannedRoles ?? [new BanRoleDef("what", "You should not be seeing this")]);
+
+ var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {rolesText} ");
return FormatBanMessageCommon(banMessage);
}
IdLabel.Text = Loc.GetString("admin-notes-id", ("id", note.Id));
TypeLabel.Text = Loc.GetString("admin-notes-type", ("type", note.NoteType));
SeverityLabel.Text = Loc.GetString("admin-notes-severity", ("severity", note.NoteSeverity ?? NoteSeverity.None));
- RoundIdLabel.Text = note.Round == null
+ RoundIdLabel.Text = note.Rounds.Length == 0
? Loc.GetString("admin-notes-round-id-unknown")
- : Loc.GetString("admin-notes-round-id", ("id", note.Round));
+ : Loc.GetString("admin-notes-round-id", ("id", string.Join(',', note.Rounds)));
CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName));
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")));
EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName));
[Dependency] private readonly IPrototypeManager _prototypes = default!;
private readonly Dictionary<string, TimeSpan> _roles = new();
- private readonly List<string> _jobBans = new();
- private readonly List<string> _antagBans = new();
+ private readonly List<ProtoId<JobPrototype>> _jobBans = new();
+ private readonly List<ProtoId<AntagPrototype>> _antagBans = new();
private readonly List<string> _jobWhitelists = new();
private ISawmill _sawmill = default!;
// No bans on record
Assert.Multiple(async () =>
{
- Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
- Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null);
- Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty);
+ Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null);
+ Assert.That(await sDatabase.GetBanAsync(1), Is.Null);
+ Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Is.Empty);
});
// Try to pardon a ban that does not exist
// Still no bans on record
Assert.Multiple(async () =>
{
- Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
- Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null);
- Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty);
+ Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null);
+ Assert.That(await sDatabase.GetBanAsync(1), Is.Null);
+ Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Is.Empty);
});
var banReason = "test";
// Should have one ban on record now
Assert.Multiple(async () =>
{
- Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null);
- Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null);
- Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
+ Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Not.Null);
+ Assert.That(await sDatabase.GetBanAsync(1), Is.Not.Null);
+ Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
});
await pair.RunTicksSync(5);
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 2"));
// The existing ban is unaffected
- Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null);
+ Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Not.Null);
- var ban = await sDatabase.GetServerBanAsync(1);
+ var ban = await sDatabase.GetBanAsync(1);
Assert.Multiple(async () =>
{
Assert.That(ban, Is.Not.Null);
- Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
+ Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
// Check that it matches
Assert.That(ban.Id, Is.EqualTo(1));
- Assert.That(ban.UserId, Is.EqualTo(clientId));
+ Assert.That(ban.UserIds, Is.EquivalentTo([clientId]));
Assert.That(ban.BanTime.UtcDateTime - DateTime.UtcNow, Is.LessThanOrEqualTo(MarginOfError));
Assert.That(ban.ExpirationTime, Is.Not.Null);
Assert.That(ban.ExpirationTime.Value.UtcDateTime - DateTime.UtcNow.AddHours(24), Is.LessThanOrEqualTo(MarginOfError));
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 1"));
// No bans should be returned
- Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
+ Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null);
// Direct id lookup returns a pardoned ban
- var pardonedBan = await sDatabase.GetServerBanAsync(1);
+ var pardonedBan = await sDatabase.GetBanAsync(1);
Assert.Multiple(async () =>
{
// Check that it matches
Assert.That(pardonedBan, Is.Not.Null);
// The list is still returned since that ignores pardons
- Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
+ Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
Assert.That(pardonedBan.Id, Is.EqualTo(1));
- Assert.That(pardonedBan.UserId, Is.EqualTo(clientId));
+ Assert.That(pardonedBan.UserIds, Is.EquivalentTo([clientId]));
Assert.That(pardonedBan.BanTime.UtcDateTime - DateTime.UtcNow, Is.LessThanOrEqualTo(MarginOfError));
Assert.That(pardonedBan.ExpirationTime, Is.Not.Null);
Assert.That(pardonedBan.ExpirationTime.Value.UtcDateTime - DateTime.UtcNow.AddHours(24), Is.LessThanOrEqualTo(MarginOfError));
Assert.Multiple(async () =>
{
// No bans should be returned
- Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
+ Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null);
// Direct id lookup returns a pardoned ban
- Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null);
+ Assert.That(await sDatabase.GetBanAsync(1), Is.Not.Null);
// The list is still returned since that ignores pardons
- Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
+ Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
});
// Reconnect client. Slightly faster than dirtying the pair.
--- /dev/null
+// <auto-generated />
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text.Json;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using NpgsqlTypes;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ [DbContext(typeof(PostgresServerDbContext))]
+ [Migration("20260120200503_BanRefactor")]
+ partial class BanRefactor
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.0")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<int?>("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<bool>("Deadminned")
+ .HasColumnType("boolean")
+ .HasColumnName("deadminned");
+
+ b.Property<bool>("Suspended")
+ .HasColumnType("boolean")
+ .HasColumnName("suspended");
+
+ b.Property<string>("Title")
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("AdminId")
+ .HasColumnType("uuid")
+ .HasColumnName("admin_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.Property<bool>("Negative")
+ .HasColumnType("boolean")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Id")
+ .HasColumnType("integer")
+ .HasColumnName("admin_log_id");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property<short>("Impact")
+ .HasColumnType("smallint")
+ .HasColumnName("impact");
+
+ b.Property<JsonDocument>("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("message");
+
+ b.Property<int>("Type")
+ .HasColumnType("integer")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Message")
+ .HasAnnotation("Npgsql:TsVectorConfig", "english");
+
+ NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<int>("LogId")
+ .HasColumnType("integer")
+ .HasColumnName("log_id");
+
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_messages_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<bool>("Dismissed")
+ .HasColumnType("boolean")
+ .HasColumnName("dismissed");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Seen")
+ .HasColumnType("boolean")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", null, t =>
+ {
+ t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_notes_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Secret")
+ .HasColumnType("boolean")
+ .HasColumnName("secret");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_flag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("AdminRankId")
+ .HasColumnType("integer")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("admin_watchlists_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("uuid")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("boolean")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("uuid")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_watchlists");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_watchlists_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_watchlists_round_id");
+
+ b.ToTable("admin_watchlists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("antag_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("AntagName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("antag_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("assigned_user_id_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("boolean")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("integer")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.Property<byte>("Type")
+ .HasColumnType("smallint")
+ .HasColumnName("type");
+
+ b.HasKey("Id")
+ .HasName("PK_ban");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.ToTable("ban", null, t =>
+ {
+ t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_address_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<NpgsqlInet>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_address");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_address_ban_id");
+
+ b.ToTable("ban_address", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_hwid_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_hwid");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_hwid_ban_id");
+
+ b.ToTable("ban_hwid", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_player_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_player");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_player_ban_id");
+
+ b.HasIndex("UserId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRole", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_role_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_id");
+
+ b.Property<string>("RoleType")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_type");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_role");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_role_ban_id");
+
+ b.HasIndex("RoleType", "RoleId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_role", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRound", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_round_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_round");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_round_ban_id");
+
+ b.HasIndex("RoundId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_template_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("boolean")
+ .HasColumnName("auto_delete");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("integer")
+ .HasColumnName("exempt_flags");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property<TimeSpan>("Length")
+ .HasColumnType("interval")
+ .HasColumnName("length");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.Property<string>("Title")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_template");
+
+ b.ToTable("ban_template", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Blacklist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_blacklist");
+
+ b.ToTable("blacklist", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("connection_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<IPAddress>("Address")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<byte?>("Denied")
+ .HasColumnType("smallint")
+ .HasColumnName("denied");
+
+ b.Property<int>("ServerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0)
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("time");
+
+ b.Property<float>("Trust")
+ .HasColumnType("real")
+ .HasColumnName("trust");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_connection_log_server_id");
+
+ b.HasIndex("Time");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.IPIntelCache", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ipintel_cache_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<IPAddress>("Address")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<float>("Score")
+ .HasColumnType("real")
+ .HasColumnName("score");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("time");
+
+ b.HasKey("Id")
+ .HasName("PK_ipintel_cache");
+
+ b.ToTable("ipintel_cache", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("job_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("JobName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("job_name");
+
+ b.Property<int>("Priority")
+ .HasColumnType("integer")
+ .HasColumnName("priority");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("play_time_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<Guid>("PlayerId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_id");
+
+ b.Property<TimeSpan>("TimeSpent")
+ .HasColumnType("interval")
+ .HasColumnName("time_spent");
+
+ b.Property<string>("Tracker")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("player_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<DateTime>("FirstSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("first_seen_time");
+
+ b.Property<DateTime?>("LastReadRules")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_read_rules");
+
+ b.Property<IPAddress>("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("inet")
+ .HasColumnName("last_seen_address");
+
+ b.Property<DateTime>("LastSeenTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_seen_time");
+
+ b.Property<string>("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", null, t =>
+ {
+ t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("admin_ooc_color");
+
+ b.PrimitiveCollection<List<string>>("ConstructionFavorites")
+ .IsRequired()
+ .HasColumnType("text[]")
+ .HasColumnName("construction_favorites");
+
+ b.Property<int>("SelectedCharacterSlot")
+ .HasColumnType("integer")
+ .HasColumnName("selected_character_slot");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("Age")
+ .HasColumnType("integer")
+ .HasColumnName("age");
+
+ b.Property<string>("CharacterName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("char_name");
+
+ b.Property<string>("EyeColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("eye_color");
+
+ b.Property<string>("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_color");
+
+ b.Property<string>("FacialHairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("facial_hair_name");
+
+ b.Property<string>("FlavorText")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("flavor_text");
+
+ b.Property<string>("Gender")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("gender");
+
+ b.Property<string>("HairColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_color");
+
+ b.Property<string>("HairName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("hair_name");
+
+ b.Property<JsonDocument>("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property<int>("PreferenceId")
+ .HasColumnType("integer")
+ .HasColumnName("preference_id");
+
+ b.Property<int>("PreferenceUnavailable")
+ .HasColumnType("integer")
+ .HasColumnName("pref_unavailable");
+
+ b.Property<string>("Sex")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("sex");
+
+ b.Property<string>("SkinColor")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("skin_color");
+
+ b.Property<int>("Slot")
+ .HasColumnType("integer")
+ .HasColumnName("slot");
+
+ b.Property<int>("SpawnPriority")
+ .HasColumnType("integer")
+ .HasColumnName("spawn_priority");
+
+ b.Property<string>("Species")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("LoadoutName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileLoadoutGroupId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout");
+
+ b.HasIndex("ProfileLoadoutGroupId");
+
+ b.ToTable("profile_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_loadout_group_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("group_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("profile_role_loadout_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("EntityName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("entity_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("profile_role_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
+ {
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("uuid")
+ .HasColumnName("player_user_id");
+
+ b.Property<string>("RoleId")
+ .HasColumnType("text")
+ .HasColumnName("role_id");
+
+ b.HasKey("PlayerUserId", "RoleId")
+ .HasName("PK_role_whitelists");
+
+ b.ToTable("role_whitelists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ServerId")
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ b.Property<DateTime?>("StartDate")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("start_date");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.HasIndex("StartDate");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.Property<int>("Flags")
+ .HasColumnType("integer")
+ .HasColumnName("flags");
+
+ b.HasKey("UserId")
+ .HasName("PK_server_ban_exemption");
+
+ b.ToTable("server_ban_exemption", null, t =>
+ {
+ t.HasCheckConstraint("FlagsNotZero", "flags != 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("server_ban_hit_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("ConnectionId")
+ .HasColumnType("integer")
+ .HasColumnName("connection_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban_hit");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
+
+ b.ToTable("server_ban_hit", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("trait_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("integer")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("TraitName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("trait_name");
+
+ b.HasKey("Id")
+ .HasName("PK_trait");
+
+ b.HasIndex("ProfileId", "TraitName")
+ .IsUnique();
+
+ b.ToTable("trait", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Unban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("uploaded_resource_log_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("data");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("date");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("path");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_uploaded_resource_log");
+
+ b.ToTable("uploaded_resource_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_whitelist");
+
+ b.ToTable("whitelist", (string)null);
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.Property<int>("PlayersId")
+ .HasColumnType("integer")
+ .HasColumnName("players_id");
+
+ b.Property<int>("RoundsId")
+ .HasColumnType("integer")
+ .HasColumnName("rounds_id");
+
+ b.HasKey("PlayersId", "RoundsId")
+ .HasName("PK_player_round");
+
+ b.HasIndex("RoundsId")
+ .HasDatabaseName("IX_player_round_rounds_id");
+
+ b.ToTable("player_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
+ .WithMany("Admins")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_admin_rank_admin_rank_id");
+
+ b.Navigation("AdminRank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.Admin", "Admin")
+ .WithMany("Flags")
+ .HasForeignKey("AdminId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_flag_admin_admin_id");
+
+ b.Navigation("Admin");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany("AdminLogs")
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_round_round_id");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminLogs")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.AdminLog", "Log")
+ .WithMany("Players")
+ .HasForeignKey("RoundId", "LogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id");
+
+ b.Navigation("Log");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminMessagesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminMessagesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminMessagesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminMessagesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_messages_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_messages_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminNotesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminNotesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminNotesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminNotesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_notes_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_notes_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "Rank")
+ .WithMany("Flags")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
+
+ b.Navigation("Rank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminWatchlistsCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminWatchlistsDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminWatchlistsLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminWatchlistsReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_watchlists_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_watchlists_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Antags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_antag_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_ban_player_last_edited_by_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Addresses")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_address_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Hwids")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_hwid_ban_ban_id");
+
+ b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
+ {
+ b1.Property<int>("BanHwidId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_hwid_id");
+
+ b1.Property<byte[]>("Hwid")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b1.Property<int>("Type")
+ .HasColumnType("integer")
+ .HasColumnName("hwid_type");
+
+ b1.HasKey("BanHwidId");
+
+ b1.ToTable("ban_hwid");
+
+ b1.WithOwner()
+ .HasForeignKey("BanHwidId")
+ .HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id");
+ });
+
+ b.Navigation("Ban");
+
+ b.Navigation("HWId")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Players")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_player_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRole", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Roles")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Rounds")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_round_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_round_round_round_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("ConnectionLogs")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .IsRequired()
+ .HasConstraintName("FK_connection_log_server_server_id");
+
+ b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
+ {
+ b1.Property<int>("ConnectionLogId")
+ .HasColumnType("integer")
+ .HasColumnName("connection_log_id");
+
+ b1.Property<byte[]>("Hwid")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b1.Property<int>("Type")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0)
+ .HasColumnName("hwid_type");
+
+ b1.HasKey("ConnectionLogId");
+
+ b1.ToTable("connection_log");
+
+ b1.WithOwner()
+ .HasForeignKey("ConnectionLogId")
+ .HasConstraintName("FK_connection_log_connection_log_connection_log_id");
+ });
+
+ b.Navigation("HWId");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Jobs")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_job_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 =>
+ {
+ b1.Property<int>("PlayerId")
+ .HasColumnType("integer")
+ .HasColumnName("player_id");
+
+ b1.Property<byte[]>("Hwid")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("last_seen_hwid");
+
+ b1.Property<int>("Type")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0)
+ .HasColumnName("last_seen_hwid_type");
+
+ b1.HasKey("PlayerId");
+
+ b1.ToTable("player");
+
+ b1.WithOwner()
+ .HasForeignKey("PlayerId")
+ .HasConstraintName("FK_player_player_player_id");
+ });
+
+ b.Navigation("LastSeenHWId");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.HasOne("Content.Server.Database.Preference", "Preference")
+ .WithMany("Profiles")
+ .HasForeignKey("PreferenceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_preference_preference_id");
+
+ b.Navigation("Preference");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileLoadoutGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~");
+
+ b.Navigation("ProfileLoadoutGroup");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("JobWhitelists")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_role_whitelists_player_player_user_id");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("Rounds")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_round_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("BanHits")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
+ .WithMany("BanHits")
+ .HasForeignKey("ConnectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Connection");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_trait_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Unban", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.Unban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_unban_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", null)
+ .WithMany()
+ .HasForeignKey("PlayersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_player_players_id");
+
+ b.HasOne("Content.Server.Database.Round", null)
+ .WithMany()
+ .HasForeignKey("RoundsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_round_rounds_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Navigation("Admins");
+
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.Navigation("Addresses");
+
+ b.Navigation("BanHits");
+
+ b.Navigation("Hwids");
+
+ b.Navigation("Players");
+
+ b.Navigation("Roles");
+
+ b.Navigation("Rounds");
+
+ b.Navigation("Unban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Navigation("BanHits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Navigation("AdminLogs");
+
+ b.Navigation("AdminMessagesCreated");
+
+ b.Navigation("AdminMessagesDeleted");
+
+ b.Navigation("AdminMessagesLastEdited");
+
+ b.Navigation("AdminMessagesReceived");
+
+ b.Navigation("AdminNotesCreated");
+
+ b.Navigation("AdminNotesDeleted");
+
+ b.Navigation("AdminNotesLastEdited");
+
+ b.Navigation("AdminNotesReceived");
+
+ b.Navigation("AdminServerBansCreated");
+
+ b.Navigation("AdminServerBansLastEdited");
+
+ b.Navigation("AdminWatchlistsCreated");
+
+ b.Navigation("AdminWatchlistsDeleted");
+
+ b.Navigation("AdminWatchlistsLastEdited");
+
+ b.Navigation("AdminWatchlistsReceived");
+
+ b.Navigation("JobWhitelists");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Navigation("Profiles");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Navigation("Antags");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("Loadouts");
+
+ b.Navigation("Traits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Navigation("Loadouts");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Navigation("AdminLogs");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Navigation("ConnectionLogs");
+
+ b.Navigation("Rounds");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
--- /dev/null
+using System;
+using Content.Shared.Database;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using NpgsqlTypes;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Postgres
+{
+ /// <inheritdoc />
+ public partial class BanRefactor : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "ban",
+ columns: table => new
+ {
+ ban_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ type = table.Column<byte>(type: "smallint", nullable: false),
+ playtime_at_note = table.Column<TimeSpan>(type: "interval", nullable: false),
+ ban_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
+ expiration_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
+ reason = table.Column<string>(type: "text", nullable: false),
+ severity = table.Column<int>(type: "integer", nullable: false),
+ banning_admin = table.Column<Guid>(type: "uuid", nullable: true),
+ last_edited_by_id = table.Column<Guid>(type: "uuid", nullable: true),
+ last_edited_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
+ exempt_flags = table.Column<int>(type: "integer", nullable: false),
+ auto_delete = table.Column<bool>(type: "boolean", nullable: false),
+ hidden = table.Column<bool>(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban", x => x.ban_id);
+ table.CheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
+ table.ForeignKey(
+ name: "FK_ban_player_banning_admin",
+ column: x => x.banning_admin,
+ principalTable: "player",
+ principalColumn: "user_id",
+ onDelete: ReferentialAction.SetNull);
+ table.ForeignKey(
+ name: "FK_ban_player_last_edited_by_id",
+ column: x => x.last_edited_by_id,
+ principalTable: "player",
+ principalColumn: "user_id",
+ onDelete: ReferentialAction.SetNull);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_address",
+ columns: table => new
+ {
+ ban_address_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ address = table.Column<NpgsqlInet>(type: "inet", nullable: false),
+ ban_id = table.Column<int>(type: "integer", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_address", x => x.ban_address_id);
+ table.CheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+ table.ForeignKey(
+ name: "FK_ban_address_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_hwid",
+ columns: table => new
+ {
+ ban_hwid_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ hwid = table.Column<byte[]>(type: "bytea", nullable: false),
+ hwid_type = table.Column<int>(type: "integer", nullable: false),
+ ban_id = table.Column<int>(type: "integer", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_hwid", x => x.ban_hwid_id);
+ table.ForeignKey(
+ name: "FK_ban_hwid_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_player",
+ columns: table => new
+ {
+ ban_player_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ user_id = table.Column<Guid>(type: "uuid", nullable: false),
+ ban_id = table.Column<int>(type: "integer", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_player", x => x.ban_player_id);
+ table.ForeignKey(
+ name: "FK_ban_player_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_role",
+ columns: table => new
+ {
+ ban_role_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ role_type = table.Column<string>(type: "text", nullable: false),
+ role_id = table.Column<string>(type: "text", nullable: false),
+ ban_id = table.Column<int>(type: "integer", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_role", x => x.ban_role_id);
+ table.ForeignKey(
+ name: "FK_ban_role_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_round",
+ columns: table => new
+ {
+ ban_round_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ ban_id = table.Column<int>(type: "integer", nullable: false),
+ round_id = table.Column<int>(type: "integer", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_round", x => x.ban_round_id);
+ table.ForeignKey(
+ name: "FK_ban_round_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_ban_round_round_round_id",
+ column: x => x.round_id,
+ principalTable: "round",
+ principalColumn: "round_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "unban",
+ columns: table => new
+ {
+ unban_id = table.Column<int>(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ ban_id = table.Column<int>(type: "integer", nullable: false),
+ unbanning_admin = table.Column<Guid>(type: "uuid", nullable: true),
+ unban_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_unban", x => x.unban_id);
+ table.ForeignKey(
+ name: "FK_unban_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_banning_admin",
+ table: "ban",
+ column: "banning_admin");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_last_edited_by_id",
+ table: "ban",
+ column: "last_edited_by_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_address_ban_id",
+ table: "ban_address",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_hwid_ban_id",
+ table: "ban_hwid",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_player_ban_id",
+ table: "ban_player",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_player_user_id_ban_id",
+ table: "ban_player",
+ columns: new[] { "user_id", "ban_id" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_role_ban_id",
+ table: "ban_role",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_role_role_type_role_id_ban_id",
+ table: "ban_role",
+ columns: new[] { "role_type", "role_id", "ban_id" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_round_ban_id",
+ table: "ban_round",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_round_round_id_ban_id",
+ table: "ban_round",
+ columns: new[] { "round_id", "ban_id" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_unban_ban_id",
+ table: "unban",
+ column: "ban_id",
+ unique: true);
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_server_ban_hit_ban_ban_id",
+ table: "server_ban_hit",
+ column: "ban_id",
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+
+ migrationBuilder.Sql("""
+ CREATE INDEX "IX_ban_address_address"
+ ON ban_address
+ USING gist
+ (address inet_ops)
+ INCLUDE (ban_id);
+
+ CREATE UNIQUE INDEX "IX_ban_hwid_hwid_ban_id"
+ ON ban_hwid
+ (hwid_type, hwid, ban_id);
+
+ CREATE UNIQUE INDEX "IX_ban_address_address_ban_id"
+ ON ban_address
+ (address, ban_id);
+ """);
+
+ migrationBuilder.Sql($"""
+ -- REMOVE:
+ -- TRUNCATE ban RESTART IDENTITY CASCADE;
+
+ --
+ -- Insert game bans
+ --
+ INSERT INTO
+ ban (ban_id, type, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden)
+ SELECT
+ server_ban_id, {(int)BanType.Server}, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden
+ FROM
+ server_ban;
+
+ -- Update ID sequence to be after newly inserted IDs.
+ SELECT setval('ban_ban_id_seq', (SELECT MAX(ban_id) FROM ban));
+
+ -- Insert ban player records.
+ INSERT INTO
+ ban_player (user_id, ban_id)
+ SELECT
+ player_user_id, server_ban_id
+ FROM
+ server_ban
+ WHERE
+ player_user_id IS NOT NULL;
+
+ -- Insert ban address records.
+ INSERT INTO
+ ban_address (address, ban_id)
+ SELECT
+ address, server_ban_id
+ FROM
+ server_ban
+ WHERE
+ address IS NOT NULL;
+
+ -- Insert ban HWID records.
+ INSERT INTO
+ ban_hwid (hwid, hwid_type, ban_id)
+ SELECT
+ hwid, hwid_type, server_ban_id
+ FROM
+ server_ban
+ WHERE
+ hwid IS NOT NULL;
+
+ -- Insert ban unban records.
+ INSERT INTO
+ unban (ban_id, unbanning_admin, unban_time)
+ SELECT
+ ban_id, unbanning_admin, unban_time
+ FROM server_unban;
+
+
+ -- Insert ban round records.
+ INSERT INTO
+ ban_round (round_id, ban_id)
+ SELECT
+ round_id, server_ban_id
+ FROM
+ server_ban
+ WHERE
+ round_id IS NOT NULL;
+
+ --
+ -- Insert role bans
+ -- This shit is a pain in the ass
+ -- > Declarative language
+ -- > Has to write procedural code in it
+ --
+
+ -- Create mapping table from role ban -> server ban.
+ -- We have to manually calculate the new ban IDs by using the sequence.
+ -- We also want to merge role ban records because the game code previously did that in some UI,
+ -- and that code is now gone, expecting the DB to do it.
+
+ -- Create a table to store IDs to merge.
+ CREATE TEMPORARY TABLE /*IF NOT EXISTS*/ _role_ban_import_merge_map (merge_id INTEGER, server_role_ban_id INTEGER UNIQUE) ON COMMIT DROP;
+ -- TRUNCATE _role_ban_import_merge_map;
+
+ -- Create a table to store merged IDs -> new ban IDs
+ CREATE TEMPORARY TABLE /*IF NOT EXISTS*/ _role_ban_import_id_map (ban_id INTEGER UNIQUE, merge_id INTEGER UNIQUE) ON COMMIT DROP;
+ -- TRUNCATE _role_ban_import_id_map;
+
+ -- Calculate merged role bans.
+ INSERT INTO
+ _role_ban_import_merge_map
+ SELECT
+ (
+ SELECT
+ sub.server_role_ban_id
+ FROM
+ server_role_ban AS sub
+ LEFT JOIN server_role_unban AS sub_unban
+ ON sub_unban.ban_id = sub.server_role_ban_id
+ WHERE
+ main.reason IS NOT DISTINCT FROM sub.reason
+ AND main.player_user_id IS NOT DISTINCT FROM sub.player_user_id
+ AND main.address IS NOT DISTINCT FROM sub.address
+ AND main.hwid IS NOT DISTINCT FROM sub.hwid
+ AND main.hwid_type IS NOT DISTINCT FROM sub.hwid_type
+ AND date_trunc('second', main.ban_time, 'utc') = date_trunc('second', sub.ban_time, 'utc')
+ AND (
+ (main.expiration_time IS NULL) = (sub.expiration_time IS NULL)
+ OR date_trunc('minute', main.expiration_time, 'utc') = date_trunc('minute', sub.expiration_time, 'utc')
+ )
+ AND main.round_id IS NOT DISTINCT FROM sub.round_id
+ AND main.severity IS NOT DISTINCT FROM sub.severity
+ AND main.hidden IS NOT DISTINCT FROM sub.hidden
+ AND main.banning_admin IS NOT DISTINCT FROM sub.banning_admin
+ AND (sub_unban.ban_id IS NULL) = (main_unban.ban_id IS NULL)
+ ORDER BY
+ sub.server_role_ban_id ASC
+ LIMIT 1
+ ), main.server_role_ban_id
+ FROM
+ server_role_ban AS main
+ LEFT JOIN server_role_unban AS main_unban
+ ON main_unban.ban_id = main.server_role_ban_id;
+
+ -- Assign new ban IDs for merged IDs.
+ INSERT INTO
+ _role_ban_import_id_map
+ SELECT
+ DISTINCT ON (merge_id)
+ nextval('ban_ban_id_seq'),
+ merge_id
+ FROM
+ _role_ban_import_merge_map;
+
+ -- I sure fucking wish CTEs could span multiple queries...
+
+ -- Insert new ban records
+ INSERT INTO
+ ban (ban_id, type, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden)
+ SELECT
+ im.ban_id, {(int)BanType.Role}, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, 0, FALSE, hidden
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id;
+
+ -- Insert role ban player records.
+ INSERT INTO
+ ban_player (user_id, ban_id)
+ SELECT
+ player_user_id, im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id
+ AND player_user_id IS NOT NULL;
+
+ -- Insert role ban address records.
+ INSERT INTO
+ ban_address (address, ban_id)
+ SELECT
+ address, im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id
+ AND address IS NOT NULL;
+
+ -- Insert role ban HWID records.
+ INSERT INTO
+ ban_hwid (hwid, hwid_type, ban_id)
+ SELECT
+ hwid, hwid_type, im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id
+ AND hwid IS NOT NULL;
+
+ -- Insert role ban role records.
+ INSERT INTO
+ ban_role (role_type, role_id, ban_id)
+ SELECT
+ split_part(role_id, ':', 1), split_part(role_id, ':', 2), im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = mm.server_role_ban_id
+ -- Yes, we have some messy ban records which, after merging, end up with duplicate roles.
+ ON CONFLICT DO NOTHING;
+
+ -- Insert role unban records.
+ INSERT INTO
+ unban (ban_id, unbanning_admin, unban_time)
+ SELECT
+ im.ban_id, unbanning_admin, unban_time
+ FROM server_role_unban sru
+ INNER JOIN _role_ban_import_id_map im
+ ON im.merge_id = sru.ban_id;
+
+ -- Insert role rounds
+ INSERT INTO
+ ban_round (round_id, ban_id)
+ SELECT
+ round_id, im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id
+ AND round_id IS NOT NULL;
+ """);
+
+ migrationBuilder.DropForeignKey(
+ name: "FK_server_ban_hit_server_ban_ban_id",
+ table: "server_ban_hit");
+
+ migrationBuilder.DropTable(
+ name: "server_role_unban");
+
+ migrationBuilder.DropTable(
+ name: "server_unban");
+
+ migrationBuilder.DropTable(
+ name: "server_role_ban");
+
+ migrationBuilder.DropTable(
+ name: "server_ban");
+
+ migrationBuilder.Sql($"""
+ CREATE OR REPLACE FUNCTION send_server_ban_notification()
+ RETURNS trigger AS $$
+ BEGIN
+ PERFORM pg_notify(
+ 'ban_notification',
+ json_build_object('ban_id', NEW.ban_id)::text
+ );
+ RETURN NEW;
+ END;
+ $$ LANGUAGE plpgsql;
+
+ CREATE TRIGGER notify_on_server_ban_insert
+ AFTER INSERT ON ban
+ FOR EACH ROW
+ WHEN (NEW.type = {(int)BanType.Server})
+ EXECUTE FUNCTION send_server_ban_notification();
+ """);
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ throw new NotSupportedException("This migration cannot be rolled back");
+ }
+ }
+}
b.ToTable("assigned_user_id", (string)null);
});
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("boolean")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("integer")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expiration_time");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("boolean")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("uuid")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("interval")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("reason");
+
+ b.Property<int>("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.Property<byte>("Type")
+ .HasColumnType("smallint")
+ .HasColumnName("type");
+
+ b.HasKey("Id")
+ .HasName("PK_ban");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.ToTable("ban", null, t =>
+ {
+ t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_address_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<NpgsqlInet>("Address")
+ .HasColumnType("inet")
+ .HasColumnName("address");
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_address");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_address_ban_id");
+
+ b.ToTable("ban_address", null, t =>
+ {
+ t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_hwid_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_hwid");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_hwid_ban_id");
+
+ b.ToTable("ban_hwid", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_player_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_player");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_player_ban_id");
+
+ b.HasIndex("UserId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRole", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_role_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_id");
+
+ b.Property<string>("RoleType")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("role_type");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_role");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_role_ban_id");
+
+ b.HasIndex("RoleType", "RoleId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_role", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRound", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("ban_round_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("RoundId")
+ .HasColumnType("integer")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_round");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_round_ban_id");
+
+ b.HasIndex("RoundId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_round", (string)null);
+ });
+
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
{
b.Property<int>("Id")
b.ToTable("server", (string)null);
});
- modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
- {
- b.Property<int>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer")
- .HasColumnName("server_ban_id");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
-
- b.Property<NpgsqlInet?>("Address")
- .HasColumnType("inet")
- .HasColumnName("address");
-
- b.Property<bool>("AutoDelete")
- .HasColumnType("boolean")
- .HasColumnName("auto_delete");
-
- b.Property<DateTime>("BanTime")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("ban_time");
-
- b.Property<Guid?>("BanningAdmin")
- .HasColumnType("uuid")
- .HasColumnName("banning_admin");
-
- b.Property<int>("ExemptFlags")
- .HasColumnType("integer")
- .HasColumnName("exempt_flags");
-
- b.Property<DateTime?>("ExpirationTime")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("expiration_time");
-
- b.Property<bool>("Hidden")
- .HasColumnType("boolean")
- .HasColumnName("hidden");
-
- b.Property<DateTime?>("LastEditedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_edited_at");
-
- b.Property<Guid?>("LastEditedById")
- .HasColumnType("uuid")
- .HasColumnName("last_edited_by_id");
-
- b.Property<Guid?>("PlayerUserId")
- .HasColumnType("uuid")
- .HasColumnName("player_user_id");
-
- b.Property<TimeSpan>("PlaytimeAtNote")
- .HasColumnType("interval")
- .HasColumnName("playtime_at_note");
-
- b.Property<string>("Reason")
- .IsRequired()
- .HasColumnType("text")
- .HasColumnName("reason");
-
- b.Property<int?>("RoundId")
- .HasColumnType("integer")
- .HasColumnName("round_id");
-
- b.Property<int>("Severity")
- .HasColumnType("integer")
- .HasColumnName("severity");
-
- b.HasKey("Id")
- .HasName("PK_server_ban");
-
- b.HasIndex("Address");
-
- b.HasIndex("BanningAdmin");
-
- b.HasIndex("LastEditedById");
-
- b.HasIndex("PlayerUserId")
- .HasDatabaseName("IX_server_ban_player_user_id");
-
- b.HasIndex("RoundId")
- .HasDatabaseName("IX_server_ban_round_id");
-
- b.ToTable("server_ban", null, t =>
- {
- t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
-
- t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
- });
- });
-
modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
{
b.Property<Guid>("UserId")
.HasColumnName("connection_id");
b.HasKey("Id")
- .HasName("PK_server_ban_hit");
-
- b.HasIndex("BanId")
- .HasDatabaseName("IX_server_ban_hit_ban_id");
-
- b.HasIndex("ConnectionId")
- .HasDatabaseName("IX_server_ban_hit_connection_id");
-
- b.ToTable("server_ban_hit", (string)null);
- });
-
- modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
- {
- b.Property<int>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer")
- .HasColumnName("server_role_ban_id");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
-
- b.Property<NpgsqlInet?>("Address")
- .HasColumnType("inet")
- .HasColumnName("address");
-
- b.Property<DateTime>("BanTime")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("ban_time");
-
- b.Property<Guid?>("BanningAdmin")
- .HasColumnType("uuid")
- .HasColumnName("banning_admin");
-
- b.Property<DateTime?>("ExpirationTime")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("expiration_time");
-
- b.Property<bool>("Hidden")
- .HasColumnType("boolean")
- .HasColumnName("hidden");
-
- b.Property<DateTime?>("LastEditedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("last_edited_at");
-
- b.Property<Guid?>("LastEditedById")
- .HasColumnType("uuid")
- .HasColumnName("last_edited_by_id");
-
- b.Property<Guid?>("PlayerUserId")
- .HasColumnType("uuid")
- .HasColumnName("player_user_id");
-
- b.Property<TimeSpan>("PlaytimeAtNote")
- .HasColumnType("interval")
- .HasColumnName("playtime_at_note");
-
- b.Property<string>("Reason")
- .IsRequired()
- .HasColumnType("text")
- .HasColumnName("reason");
-
- b.Property<string>("RoleId")
- .IsRequired()
- .HasColumnType("text")
- .HasColumnName("role_id");
-
- b.Property<int?>("RoundId")
- .HasColumnType("integer")
- .HasColumnName("round_id");
-
- b.Property<int>("Severity")
- .HasColumnType("integer")
- .HasColumnName("severity");
-
- b.HasKey("Id")
- .HasName("PK_server_role_ban");
-
- b.HasIndex("Address");
-
- b.HasIndex("BanningAdmin");
-
- b.HasIndex("LastEditedById");
-
- b.HasIndex("PlayerUserId")
- .HasDatabaseName("IX_server_role_ban_player_user_id");
-
- b.HasIndex("RoundId")
- .HasDatabaseName("IX_server_role_ban_round_id");
-
- b.ToTable("server_role_ban", null, t =>
- {
- t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
-
- t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
- });
- });
-
- modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
- {
- b.Property<int>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer")
- .HasColumnName("role_unban_id");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
-
- b.Property<int>("BanId")
- .HasColumnType("integer")
- .HasColumnName("ban_id");
-
- b.Property<DateTime>("UnbanTime")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("unban_time");
-
- b.Property<Guid?>("UnbanningAdmin")
- .HasColumnType("uuid")
- .HasColumnName("unbanning_admin");
-
- b.HasKey("Id")
- .HasName("PK_server_role_unban");
-
- b.HasIndex("BanId")
- .IsUnique();
-
- b.ToTable("server_role_unban", (string)null);
- });
-
- modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
- {
- b.Property<int>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer")
- .HasColumnName("unban_id");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
-
- b.Property<int>("BanId")
- .HasColumnType("integer")
- .HasColumnName("ban_id");
-
- b.Property<DateTime>("UnbanTime")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("unban_time");
-
- b.Property<Guid?>("UnbanningAdmin")
- .HasColumnType("uuid")
- .HasColumnName("unbanning_admin");
-
- b.HasKey("Id")
- .HasName("PK_server_unban");
+ .HasName("PK_server_ban_hit");
b.HasIndex("BanId")
- .IsUnique();
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
- b.ToTable("server_unban", (string)null);
+ b.ToTable("server_ban_hit", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Trait", b =>
b.ToTable("trait", (string)null);
});
+ modelBuilder.Entity("Content.Server.Database.Unban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("unban_id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
+
+ b.Property<int>("BanId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("uuid")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("unban", (string)null);
+ });
+
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
{
b.Property<int>("Id")
b.Navigation("Profile");
});
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_ban_player_last_edited_by_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Addresses")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_address_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Hwids")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_hwid_ban_ban_id");
+
+ b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
+ {
+ b1.Property<int>("BanHwidId")
+ .HasColumnType("integer")
+ .HasColumnName("ban_hwid_id");
+
+ b1.Property<byte[]>("Hwid")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("hwid");
+
+ b1.Property<int>("Type")
+ .HasColumnType("integer")
+ .HasColumnName("hwid_type");
+
+ b1.HasKey("BanHwidId");
+
+ b1.ToTable("ban_hwid");
+
+ b1.WithOwner()
+ .HasForeignKey("BanHwidId")
+ .HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id");
+ });
+
+ b.Navigation("Ban");
+
+ b.Navigation("HWId")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Players")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_player_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRole", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Roles")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Rounds")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_round_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_round_round_round_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Round");
+ });
+
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
b.Navigation("Server");
});
- modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
- {
- b.HasOne("Content.Server.Database.Player", "CreatedBy")
- .WithMany("AdminServerBansCreated")
- .HasForeignKey("BanningAdmin")
- .HasPrincipalKey("UserId")
- .OnDelete(DeleteBehavior.SetNull)
- .HasConstraintName("FK_server_ban_player_banning_admin");
-
- b.HasOne("Content.Server.Database.Player", "LastEditedBy")
- .WithMany("AdminServerBansLastEdited")
- .HasForeignKey("LastEditedById")
- .HasPrincipalKey("UserId")
- .OnDelete(DeleteBehavior.SetNull)
- .HasConstraintName("FK_server_ban_player_last_edited_by_id");
-
- b.HasOne("Content.Server.Database.Round", "Round")
- .WithMany()
- .HasForeignKey("RoundId")
- .HasConstraintName("FK_server_ban_round_round_id");
-
- b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
- {
- b1.Property<int>("ServerBanId")
- .HasColumnType("integer")
- .HasColumnName("server_ban_id");
-
- b1.Property<byte[]>("Hwid")
- .IsRequired()
- .HasColumnType("bytea")
- .HasColumnName("hwid");
-
- b1.Property<int>("Type")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer")
- .HasDefaultValue(0)
- .HasColumnName("hwid_type");
-
- b1.HasKey("ServerBanId");
-
- b1.ToTable("server_ban");
-
- b1.WithOwner()
- .HasForeignKey("ServerBanId")
- .HasConstraintName("FK_server_ban_server_ban_server_ban_id");
- });
-
- b.Navigation("CreatedBy");
-
- b.Navigation("HWId");
-
- b.Navigation("LastEditedBy");
-
- b.Navigation("Round");
- });
-
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
{
- b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ b.HasOne("Content.Server.Database.Ban", "Ban")
.WithMany("BanHits")
.HasForeignKey("BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
- .HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
+ .HasConstraintName("FK_server_ban_hit_ban_ban_id");
b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
.WithMany("BanHits")
b.Navigation("Connection");
});
- modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
- {
- b.HasOne("Content.Server.Database.Player", "CreatedBy")
- .WithMany("AdminServerRoleBansCreated")
- .HasForeignKey("BanningAdmin")
- .HasPrincipalKey("UserId")
- .OnDelete(DeleteBehavior.SetNull)
- .HasConstraintName("FK_server_role_ban_player_banning_admin");
-
- b.HasOne("Content.Server.Database.Player", "LastEditedBy")
- .WithMany("AdminServerRoleBansLastEdited")
- .HasForeignKey("LastEditedById")
- .HasPrincipalKey("UserId")
- .OnDelete(DeleteBehavior.SetNull)
- .HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
-
- b.HasOne("Content.Server.Database.Round", "Round")
- .WithMany()
- .HasForeignKey("RoundId")
- .HasConstraintName("FK_server_role_ban_round_round_id");
-
- b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
- {
- b1.Property<int>("ServerRoleBanId")
- .HasColumnType("integer")
- .HasColumnName("server_role_ban_id");
-
- b1.Property<byte[]>("Hwid")
- .IsRequired()
- .HasColumnType("bytea")
- .HasColumnName("hwid");
-
- b1.Property<int>("Type")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer")
- .HasDefaultValue(0)
- .HasColumnName("hwid_type");
-
- b1.HasKey("ServerRoleBanId");
-
- b1.ToTable("server_role_ban");
-
- b1.WithOwner()
- .HasForeignKey("ServerRoleBanId")
- .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id");
- });
-
- b.Navigation("CreatedBy");
-
- b.Navigation("HWId");
-
- b.Navigation("LastEditedBy");
-
- b.Navigation("Round");
- });
-
- modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
- b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
- .WithOne("Unban")
- .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
- .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
+ .HasConstraintName("FK_trait_profile_profile_id");
- b.Navigation("Ban");
+ b.Navigation("Profile");
});
- modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ modelBuilder.Entity("Content.Server.Database.Unban", b =>
{
- b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ b.HasOne("Content.Server.Database.Ban", "Ban")
.WithOne("Unban")
- .HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
+ .HasForeignKey("Content.Server.Database.Unban", "BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
- .HasConstraintName("FK_server_unban_server_ban_ban_id");
+ .HasConstraintName("FK_unban_ban_ban_id");
b.Navigation("Ban");
});
- modelBuilder.Entity("Content.Server.Database.Trait", b =>
- {
- b.HasOne("Content.Server.Database.Profile", "Profile")
- .WithMany("Traits")
- .HasForeignKey("ProfileId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired()
- .HasConstraintName("FK_trait_profile_profile_id");
-
- b.Navigation("Profile");
- });
-
modelBuilder.Entity("PlayerRound", b =>
{
b.HasOne("Content.Server.Database.Player", null)
b.Navigation("Flags");
});
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.Navigation("Addresses");
+
+ b.Navigation("BanHits");
+
+ b.Navigation("Hwids");
+
+ b.Navigation("Players");
+
+ b.Navigation("Roles");
+
+ b.Navigation("Rounds");
+
+ b.Navigation("Unban");
+ });
+
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.Navigation("BanHits");
b.Navigation("AdminServerBansLastEdited");
- b.Navigation("AdminServerRoleBansCreated");
-
- b.Navigation("AdminServerRoleBansLastEdited");
-
b.Navigation("AdminWatchlistsCreated");
b.Navigation("AdminWatchlistsDeleted");
b.Navigation("Rounds");
});
-
- modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
- {
- b.Navigation("BanHits");
-
- b.Navigation("Unban");
- });
-
- modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
- {
- b.Navigation("Unban");
- });
#pragma warning restore 612, 618
}
}
--- /dev/null
+// <auto-generated />
+using System;
+using Content.Server.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Sqlite
+{
+ [DbContext(typeof(SqliteServerDbContext))]
+ [Migration("20260120200455_BanRefactor")]
+ partial class BanRefactor
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<int?>("AdminRankId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<bool>("Deadminned")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deadminned");
+
+ b.Property<bool>("Suspended")
+ .HasColumnType("INTEGER")
+ .HasColumnName("suspended");
+
+ b.Property<string>("Title")
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.HasKey("UserId")
+ .HasName("PK_admin");
+
+ b.HasIndex("AdminRankId")
+ .HasDatabaseName("IX_admin_admin_rank_id");
+
+ b.ToTable("admin", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_flag_id");
+
+ b.Property<Guid>("AdminId")
+ .HasColumnType("TEXT")
+ .HasColumnName("admin_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flag");
+
+ b.Property<bool>("Negative")
+ .HasColumnType("INTEGER")
+ .HasColumnName("negative");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_flag");
+
+ b.HasIndex("AdminId")
+ .HasDatabaseName("IX_admin_flag_admin_id");
+
+ b.HasIndex("Flag", "AdminId")
+ .IsUnique();
+
+ b.ToTable("admin_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("Id")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_log_id");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("TEXT")
+ .HasColumnName("date");
+
+ b.Property<sbyte>("Impact")
+ .HasColumnType("INTEGER")
+ .HasColumnName("impact");
+
+ b.Property<string>("Json")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("json");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<int>("Type")
+ .HasColumnType("INTEGER")
+ .HasColumnName("type");
+
+ b.HasKey("RoundId", "Id")
+ .HasName("PK_admin_log");
+
+ b.HasIndex("Date");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_admin_log_type");
+
+ b.ToTable("admin_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("LogId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("log_id");
+
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.HasKey("RoundId", "LogId", "PlayerUserId")
+ .HasName("PK_admin_log_player");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_log_player_player_user_id");
+
+ b.ToTable("admin_log_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_messages_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<bool>("Dismissed")
+ .HasColumnType("INTEGER")
+ .HasColumnName("dismissed");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Seen")
+ .HasColumnType("INTEGER")
+ .HasColumnName("seen");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_messages");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_messages_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_messages_round_id");
+
+ b.ToTable("admin_messages", null, t =>
+ {
+ t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_notes_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<bool>("Secret")
+ .HasColumnType("INTEGER")
+ .HasColumnName("secret");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_notes");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_notes_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_notes_round_id");
+
+ b.ToTable("admin_notes", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank");
+
+ b.ToTable("admin_rank", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_flag_id");
+
+ b.Property<int>("AdminRankId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_rank_id");
+
+ b.Property<string>("Flag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flag");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_rank_flag");
+
+ b.HasIndex("AdminRankId");
+
+ b.HasIndex("Flag", "AdminRankId")
+ .IsUnique();
+
+ b.ToTable("admin_rank_flag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("admin_watchlists_id");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property<Guid?>("CreatedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_by_id");
+
+ b.Property<bool>("Deleted")
+ .HasColumnType("INTEGER")
+ .HasColumnName("deleted");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_at");
+
+ b.Property<Guid?>("DeletedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("deleted_by_id");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<DateTime>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<string>("Message")
+ .IsRequired()
+ .HasMaxLength(4096)
+ .HasColumnType("TEXT")
+ .HasColumnName("message");
+
+ b.Property<Guid?>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<int?>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_admin_watchlists");
+
+ b.HasIndex("CreatedById");
+
+ b.HasIndex("DeletedById");
+
+ b.HasIndex("LastEditedById");
+
+ b.HasIndex("PlayerUserId")
+ .HasDatabaseName("IX_admin_watchlists_player_user_id");
+
+ b.HasIndex("RoundId")
+ .HasDatabaseName("IX_admin_watchlists_round_id");
+
+ b.ToTable("admin_watchlists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("antag_id");
+
+ b.Property<string>("AntagName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("antag_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_antag");
+
+ b.HasIndex("ProfileId", "AntagName")
+ .IsUnique();
+
+ b.ToTable("antag", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("assigned_user_id_id");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_assigned_user_id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.HasIndex("UserName")
+ .IsUnique();
+
+ b.ToTable("assigned_user_id", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("INTEGER")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("reason");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.Property<byte>("Type")
+ .HasColumnType("INTEGER")
+ .HasColumnName("type");
+
+ b.HasKey("Id")
+ .HasName("PK_ban");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.ToTable("ban", null, t =>
+ {
+ t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_address_id");
+
+ b.Property<string>("Address")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_address");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_address_ban_id");
+
+ b.ToTable("ban_address", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_hwid_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_hwid");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_hwid_ban_id");
+
+ b.ToTable("ban_hwid", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_player_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_player");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_player_ban_id");
+
+ b.HasIndex("UserId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRole", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_role_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_id");
+
+ b.Property<string>("RoleType")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_type");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_role");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_role_ban_id");
+
+ b.HasIndex("RoleType", "RoleId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_role", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRound", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_round_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_round");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_round_ban_id");
+
+ b.HasIndex("RoundId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_template_id");
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("INTEGER")
+ .HasColumnName("auto_delete");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("exempt_flags");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hidden");
+
+ b.Property<TimeSpan>("Length")
+ .HasColumnType("TEXT")
+ .HasColumnName("length");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("reason");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.Property<string>("Title")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_template");
+
+ b.ToTable("ban_template", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Blacklist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_blacklist");
+
+ b.ToTable("blacklist", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("connection_log_id");
+
+ b.Property<string>("Address")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<byte?>("Denied")
+ .HasColumnType("INTEGER")
+ .HasColumnName("denied");
+
+ b.Property<int>("ServerId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("server_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.Property<float>("Trust")
+ .HasColumnType("REAL")
+ .HasColumnName("trust");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<string>("UserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_name");
+
+ b.HasKey("Id")
+ .HasName("PK_connection_log");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_connection_log_server_id");
+
+ b.HasIndex("Time");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("connection_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.IPIntelCache", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ipintel_cache_id");
+
+ b.Property<string>("Address")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<float>("Score")
+ .HasColumnType("REAL")
+ .HasColumnName("score");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.HasKey("Id")
+ .HasName("PK_ipintel_cache");
+
+ b.HasIndex("Address")
+ .IsUnique();
+
+ b.ToTable("ipintel_cache", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("job_id");
+
+ b.Property<string>("JobName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("job_name");
+
+ b.Property<int>("Priority")
+ .HasColumnType("INTEGER")
+ .HasColumnName("priority");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.HasKey("Id")
+ .HasName("PK_job");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "JobName")
+ .IsUnique();
+
+ b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
+ .IsUnique()
+ .HasFilter("priority = 3");
+
+ b.ToTable("job", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("play_time_id");
+
+ b.Property<Guid>("PlayerId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_id");
+
+ b.Property<TimeSpan>("TimeSpent")
+ .HasColumnType("TEXT")
+ .HasColumnName("time_spent");
+
+ b.Property<string>("Tracker")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("tracker");
+
+ b.HasKey("Id")
+ .HasName("PK_play_time");
+
+ b.HasIndex("PlayerId", "Tracker")
+ .IsUnique();
+
+ b.ToTable("play_time", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("player_id");
+
+ b.Property<DateTime>("FirstSeenTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("first_seen_time");
+
+ b.Property<DateTime?>("LastReadRules")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_read_rules");
+
+ b.Property<string>("LastSeenAddress")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_address");
+
+ b.Property<DateTime>("LastSeenTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_time");
+
+ b.Property<string>("LastSeenUserName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_seen_user_name");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_player");
+
+ b.HasAlternateKey("UserId")
+ .HasName("ak_player_user_id");
+
+ b.HasIndex("LastSeenUserName");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("preference_id");
+
+ b.Property<string>("AdminOOCColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("admin_ooc_color");
+
+ b.PrimitiveCollection<string>("ConstructionFavorites")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("construction_favorites");
+
+ b.Property<int>("SelectedCharacterSlot")
+ .HasColumnType("INTEGER")
+ .HasColumnName("selected_character_slot");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_preference");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("preference", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<int>("Age")
+ .HasColumnType("INTEGER")
+ .HasColumnName("age");
+
+ b.Property<string>("CharacterName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("char_name");
+
+ b.Property<string>("EyeColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("eye_color");
+
+ b.Property<string>("FacialHairColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("facial_hair_color");
+
+ b.Property<string>("FacialHairName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("facial_hair_name");
+
+ b.Property<string>("FlavorText")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("flavor_text");
+
+ b.Property<string>("Gender")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("gender");
+
+ b.Property<string>("HairColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("hair_color");
+
+ b.Property<string>("HairName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("hair_name");
+
+ b.Property<byte[]>("Markings")
+ .HasColumnType("jsonb")
+ .HasColumnName("markings");
+
+ b.Property<int>("PreferenceId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("preference_id");
+
+ b.Property<int>("PreferenceUnavailable")
+ .HasColumnType("INTEGER")
+ .HasColumnName("pref_unavailable");
+
+ b.Property<string>("Sex")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("sex");
+
+ b.Property<string>("SkinColor")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("skin_color");
+
+ b.Property<int>("Slot")
+ .HasColumnType("INTEGER")
+ .HasColumnName("slot");
+
+ b.Property<int>("SpawnPriority")
+ .HasColumnType("INTEGER")
+ .HasColumnName("spawn_priority");
+
+ b.Property<string>("Species")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("species");
+
+ b.HasKey("Id")
+ .HasName("PK_profile");
+
+ b.HasIndex("PreferenceId")
+ .HasDatabaseName("IX_profile_preference_id");
+
+ b.HasIndex("Slot", "PreferenceId")
+ .IsUnique();
+
+ b.ToTable("profile", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_id");
+
+ b.Property<string>("LoadoutName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("loadout_name");
+
+ b.Property<int>("ProfileLoadoutGroupId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout");
+
+ b.HasIndex("ProfileLoadoutGroupId");
+
+ b.ToTable("profile_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_loadout_group_id");
+
+ b.Property<string>("GroupName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("group_name");
+
+ b.Property<int>("ProfileRoleLoadoutId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_loadout_group");
+
+ b.HasIndex("ProfileRoleLoadoutId");
+
+ b.ToTable("profile_loadout_group", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_role_loadout_id");
+
+ b.Property<string>("EntityName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT")
+ .HasColumnName("entity_name");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("RoleName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_name");
+
+ b.HasKey("Id")
+ .HasName("PK_profile_role_loadout");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("profile_role_loadout", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
+ {
+ b.Property<Guid>("PlayerUserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("player_user_id");
+
+ b.Property<string>("RoleId")
+ .HasColumnType("TEXT")
+ .HasColumnName("role_id");
+
+ b.HasKey("PlayerUserId", "RoleId")
+ .HasName("PK_role_whitelists");
+
+ b.ToTable("role_whitelists", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.Property<int>("ServerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_id");
+
+ b.Property<DateTime?>("StartDate")
+ .HasColumnType("TEXT")
+ .HasColumnName("start_date");
+
+ b.HasKey("Id")
+ .HasName("PK_round");
+
+ b.HasIndex("ServerId")
+ .HasDatabaseName("IX_round_server_id");
+
+ b.HasIndex("StartDate");
+
+ b.ToTable("round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_id");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("PK_server");
+
+ b.ToTable("server", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.Property<int>("Flags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("flags");
+
+ b.HasKey("UserId")
+ .HasName("PK_server_ban_exemption");
+
+ b.ToTable("server_ban_exemption", null, t =>
+ {
+ t.HasCheckConstraint("FlagsNotZero", "flags != 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("server_ban_hit_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("ConnectionId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("connection_id");
+
+ b.HasKey("Id")
+ .HasName("PK_server_ban_hit");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_server_ban_hit_ban_id");
+
+ b.HasIndex("ConnectionId")
+ .HasDatabaseName("IX_server_ban_hit_connection_id");
+
+ b.ToTable("server_ban_hit", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("trait_id");
+
+ b.Property<int>("ProfileId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("profile_id");
+
+ b.Property<string>("TraitName")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("trait_name");
+
+ b.HasKey("Id")
+ .HasName("PK_trait");
+
+ b.HasIndex("ProfileId", "TraitName")
+ .IsUnique();
+
+ b.ToTable("trait", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Unban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("unban_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<DateTime>("UnbanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("unban_time");
+
+ b.Property<Guid?>("UnbanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("unbanning_admin");
+
+ b.HasKey("Id")
+ .HasName("PK_unban");
+
+ b.HasIndex("BanId")
+ .IsUnique();
+
+ b.ToTable("unban", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("uploaded_resource_log_id");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
+
+ b.Property<DateTime>("Date")
+ .HasColumnType("TEXT")
+ .HasColumnName("date");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("path");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_uploaded_resource_log");
+
+ b.ToTable("uploaded_resource_log", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
+ {
+ b.Property<Guid>("UserId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("UserId")
+ .HasName("PK_whitelist");
+
+ b.ToTable("whitelist", (string)null);
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.Property<int>("PlayersId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("players_id");
+
+ b.Property<int>("RoundsId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("rounds_id");
+
+ b.HasKey("PlayersId", "RoundsId")
+ .HasName("PK_player_round");
+
+ b.HasIndex("RoundsId")
+ .HasDatabaseName("IX_player_round_rounds_id");
+
+ b.ToTable("player_round", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
+ .WithMany("Admins")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_admin_rank_admin_rank_id");
+
+ b.Navigation("AdminRank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.Admin", "Admin")
+ .WithMany("Flags")
+ .HasForeignKey("AdminId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_flag_admin_admin_id");
+
+ b.Navigation("Admin");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany("AdminLogs")
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_round_round_id");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminLogs")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.AdminLog", "Log")
+ .WithMany("Players")
+ .HasForeignKey("RoundId", "LogId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id");
+
+ b.Navigation("Log");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminMessagesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminMessagesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminMessagesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_messages_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminMessagesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_messages_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_messages_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminNotesCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminNotesDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminNotesLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_notes_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminNotesReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_notes_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_notes_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
+ {
+ b.HasOne("Content.Server.Database.AdminRank", "Rank")
+ .WithMany("Flags")
+ .HasForeignKey("AdminRankId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
+
+ b.Navigation("Rank");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminWatchlistsCreated")
+ .HasForeignKey("CreatedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_created_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "DeletedBy")
+ .WithMany("AdminWatchlistsDeleted")
+ .HasForeignKey("DeletedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminWatchlistsLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
+
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("AdminWatchlistsReceived")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .HasConstraintName("FK_admin_watchlists_player_player_user_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .HasConstraintName("FK_admin_watchlists_round_round_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("DeletedBy");
+
+ b.Navigation("LastEditedBy");
+
+ b.Navigation("Player");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Antag", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Antags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_antag_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_ban_player_last_edited_by_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Addresses")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_address_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Hwids")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_hwid_ban_ban_id");
+
+ b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
+ {
+ b1.Property<int>("BanHwidId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_hwid_id");
+
+ b1.Property<byte[]>("Hwid")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b1.Property<int>("Type")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hwid_type");
+
+ b1.HasKey("BanHwidId");
+
+ b1.ToTable("ban_hwid");
+
+ b1.WithOwner()
+ .HasForeignKey("BanHwidId")
+ .HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id");
+ });
+
+ b.Navigation("Ban");
+
+ b.Navigation("HWId")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Players")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_player_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRole", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Roles")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Rounds")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_round_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_round_round_round_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Round");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("ConnectionLogs")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .IsRequired()
+ .HasConstraintName("FK_connection_log_server_server_id");
+
+ b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
+ {
+ b1.Property<int>("ConnectionLogId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("connection_log_id");
+
+ b1.Property<byte[]>("Hwid")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b1.Property<int>("Type")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("hwid_type");
+
+ b1.HasKey("ConnectionLogId");
+
+ b1.ToTable("connection_log");
+
+ b1.WithOwner()
+ .HasForeignKey("ConnectionLogId")
+ .HasConstraintName("FK_connection_log_connection_log_connection_log_id");
+ });
+
+ b.Navigation("HWId");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Job", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Jobs")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_job_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.OwnsOne("Content.Server.Database.TypedHwid", "LastSeenHWId", b1 =>
+ {
+ b1.Property<int>("PlayerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("player_id");
+
+ b1.Property<byte[]>("Hwid")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("last_seen_hwid");
+
+ b1.Property<int>("Type")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("last_seen_hwid_type");
+
+ b1.HasKey("PlayerId");
+
+ b1.ToTable("player");
+
+ b1.WithOwner()
+ .HasForeignKey("PlayerId")
+ .HasConstraintName("FK_player_player_player_id");
+ });
+
+ b.Navigation("LastSeenHWId");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.HasOne("Content.Server.Database.Preference", "Preference")
+ .WithMany("Profiles")
+ .HasForeignKey("PreferenceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_preference_preference_id");
+
+ b.Navigation("Preference");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileLoadoutGroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id");
+
+ b.Navigation("ProfileLoadoutGroup");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
+ .WithMany("Groups")
+ .HasForeignKey("ProfileRoleLoadoutId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id");
+
+ b.Navigation("ProfileRoleLoadout");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Loadouts")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_profile_role_loadout_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "Player")
+ .WithMany("JobWhitelists")
+ .HasForeignKey("PlayerUserId")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_role_whitelists_player_player_user_id");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.HasOne("Content.Server.Database.Server", "Server")
+ .WithMany("Rounds")
+ .HasForeignKey("ServerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_round_server_server_id");
+
+ b.Navigation("Server");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("BanHits")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
+ .WithMany("BanHits")
+ .HasForeignKey("ConnectionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Connection");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
+ {
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_trait_profile_profile_id");
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Unban", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithOne("Unban")
+ .HasForeignKey("Content.Server.Database.Unban", "BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_unban_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("PlayerRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", null)
+ .WithMany()
+ .HasForeignKey("PlayersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_player_players_id");
+
+ b.HasOne("Content.Server.Database.Round", null)
+ .WithMany()
+ .HasForeignKey("RoundsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_player_round_round_rounds_id");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Admin", b =>
+ {
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
+ {
+ b.Navigation("Players");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
+ {
+ b.Navigation("Admins");
+
+ b.Navigation("Flags");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.Navigation("Addresses");
+
+ b.Navigation("BanHits");
+
+ b.Navigation("Hwids");
+
+ b.Navigation("Players");
+
+ b.Navigation("Roles");
+
+ b.Navigation("Rounds");
+
+ b.Navigation("Unban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
+ {
+ b.Navigation("BanHits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Player", b =>
+ {
+ b.Navigation("AdminLogs");
+
+ b.Navigation("AdminMessagesCreated");
+
+ b.Navigation("AdminMessagesDeleted");
+
+ b.Navigation("AdminMessagesLastEdited");
+
+ b.Navigation("AdminMessagesReceived");
+
+ b.Navigation("AdminNotesCreated");
+
+ b.Navigation("AdminNotesDeleted");
+
+ b.Navigation("AdminNotesLastEdited");
+
+ b.Navigation("AdminNotesReceived");
+
+ b.Navigation("AdminServerBansCreated");
+
+ b.Navigation("AdminServerBansLastEdited");
+
+ b.Navigation("AdminWatchlistsCreated");
+
+ b.Navigation("AdminWatchlistsDeleted");
+
+ b.Navigation("AdminWatchlistsLastEdited");
+
+ b.Navigation("AdminWatchlistsReceived");
+
+ b.Navigation("JobWhitelists");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Preference", b =>
+ {
+ b.Navigation("Profiles");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Profile", b =>
+ {
+ b.Navigation("Antags");
+
+ b.Navigation("Jobs");
+
+ b.Navigation("Loadouts");
+
+ b.Navigation("Traits");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
+ {
+ b.Navigation("Loadouts");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
+ {
+ b.Navigation("Groups");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Round", b =>
+ {
+ b.Navigation("AdminLogs");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.Server", b =>
+ {
+ b.Navigation("ConnectionLogs");
+
+ b.Navigation("Rounds");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
--- /dev/null
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Content.Server.Database.Migrations.Sqlite
+{
+ /// <inheritdoc />
+ public partial class BanRefactor : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "ban",
+ columns: table => new
+ {
+ ban_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ type = table.Column<byte>(type: "INTEGER", nullable: false),
+ playtime_at_note = table.Column<TimeSpan>(type: "TEXT", nullable: false),
+ ban_time = table.Column<DateTime>(type: "TEXT", nullable: false),
+ expiration_time = table.Column<DateTime>(type: "TEXT", nullable: true),
+ reason = table.Column<string>(type: "TEXT", nullable: false),
+ severity = table.Column<int>(type: "INTEGER", nullable: false),
+ banning_admin = table.Column<Guid>(type: "TEXT", nullable: true),
+ last_edited_by_id = table.Column<Guid>(type: "TEXT", nullable: true),
+ last_edited_at = table.Column<DateTime>(type: "TEXT", nullable: true),
+ exempt_flags = table.Column<int>(type: "INTEGER", nullable: false),
+ auto_delete = table.Column<bool>(type: "INTEGER", nullable: false),
+ hidden = table.Column<bool>(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban", x => x.ban_id);
+ table.CheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
+ table.ForeignKey(
+ name: "FK_ban_player_banning_admin",
+ column: x => x.banning_admin,
+ principalTable: "player",
+ principalColumn: "user_id",
+ onDelete: ReferentialAction.SetNull);
+ table.ForeignKey(
+ name: "FK_ban_player_last_edited_by_id",
+ column: x => x.last_edited_by_id,
+ principalTable: "player",
+ principalColumn: "user_id",
+ onDelete: ReferentialAction.SetNull);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_address",
+ columns: table => new
+ {
+ ban_address_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ address = table.Column<string>(type: "TEXT", nullable: false),
+ ban_id = table.Column<int>(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_address", x => x.ban_address_id);
+ table.ForeignKey(
+ name: "FK_ban_address_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_hwid",
+ columns: table => new
+ {
+ ban_hwid_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ hwid = table.Column<byte[]>(type: "BLOB", nullable: false),
+ hwid_type = table.Column<int>(type: "INTEGER", nullable: false),
+ ban_id = table.Column<int>(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_hwid", x => x.ban_hwid_id);
+ table.ForeignKey(
+ name: "FK_ban_hwid_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_player",
+ columns: table => new
+ {
+ ban_player_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ user_id = table.Column<Guid>(type: "TEXT", nullable: false),
+ ban_id = table.Column<int>(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_player", x => x.ban_player_id);
+ table.ForeignKey(
+ name: "FK_ban_player_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_role",
+ columns: table => new
+ {
+ ban_role_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ role_type = table.Column<string>(type: "TEXT", nullable: false),
+ role_id = table.Column<string>(type: "TEXT", nullable: false),
+ ban_id = table.Column<int>(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_role", x => x.ban_role_id);
+ table.ForeignKey(
+ name: "FK_ban_role_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ban_round",
+ columns: table => new
+ {
+ ban_round_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ ban_id = table.Column<int>(type: "INTEGER", nullable: false),
+ round_id = table.Column<int>(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ban_round", x => x.ban_round_id);
+ table.ForeignKey(
+ name: "FK_ban_round_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_ban_round_round_round_id",
+ column: x => x.round_id,
+ principalTable: "round",
+ principalColumn: "round_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "unban",
+ columns: table => new
+ {
+ unban_id = table.Column<int>(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ ban_id = table.Column<int>(type: "INTEGER", nullable: false),
+ unbanning_admin = table.Column<Guid>(type: "TEXT", nullable: true),
+ unban_time = table.Column<DateTime>(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_unban", x => x.unban_id);
+ table.ForeignKey(
+ name: "FK_unban_ban_ban_id",
+ column: x => x.ban_id,
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_banning_admin",
+ table: "ban",
+ column: "banning_admin");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_last_edited_by_id",
+ table: "ban",
+ column: "last_edited_by_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_address_ban_id",
+ table: "ban_address",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_hwid_ban_id",
+ table: "ban_hwid",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_player_ban_id",
+ table: "ban_player",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_player_user_id_ban_id",
+ table: "ban_player",
+ columns: new[] { "user_id", "ban_id" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_role_ban_id",
+ table: "ban_role",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_role_role_type_role_id_ban_id",
+ table: "ban_role",
+ columns: new[] { "role_type", "role_id", "ban_id" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_round_ban_id",
+ table: "ban_round",
+ column: "ban_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ban_round_round_id_ban_id",
+ table: "ban_round",
+ columns: new[] { "round_id", "ban_id" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_unban_ban_id",
+ table: "unban",
+ column: "ban_id",
+ unique: true);
+
+ migrationBuilder.Sql("""
+ CREATE UNIQUE INDEX "IX_ban_hwid_hwid_ban_id"
+ ON ban_hwid
+ (hwid_type, hwid, ban_id);
+
+ CREATE UNIQUE INDEX "IX_ban_address_address_ban_id"
+ ON ban_address
+ (address, ban_id);
+ """);
+
+ migrationBuilder.Sql("""
+ --
+ -- Insert game bans
+ --
+ INSERT INTO
+ ban (ban_id, type, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden)
+ SELECT
+ server_ban_id, 0, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden
+ FROM
+ server_ban;
+
+ -- Insert ban player records.
+ INSERT INTO
+ ban_player (user_id, ban_id)
+ SELECT
+ player_user_id, server_ban_id
+ FROM
+ server_ban
+ WHERE
+ player_user_id IS NOT NULL;
+
+ -- Insert ban address records.
+ INSERT INTO
+ ban_address (address, ban_id)
+ SELECT
+ address, server_ban_id
+ FROM
+ server_ban
+ WHERE
+ address IS NOT NULL;
+
+ -- Insert ban HWID records.
+ INSERT INTO
+ ban_hwid (hwid, hwid_type, ban_id)
+ SELECT
+ hwid, hwid_type, server_ban_id
+ FROM
+ server_ban
+ WHERE
+ hwid IS NOT NULL;
+
+ -- Insert ban unban records.
+ INSERT INTO
+ unban (ban_id, unbanning_admin, unban_time)
+ SELECT
+ ban_id, unbanning_admin, unban_time
+ FROM server_unban;
+
+ -- Insert ban round records.
+ INSERT INTO
+ ban_round (round_id, ban_id)
+ SELECT
+ round_id, server_ban_id
+ FROM
+ server_ban
+ WHERE
+ round_id IS NOT NULL;
+
+ --
+ -- Insert role bans
+ -- This shit is a pain in the ass
+ -- > Declarative language
+ -- > Has to write procedural code in it
+ --
+
+ -- Create mapping table from role ban -> server ban.
+ -- We have to manually calculate the new ban IDs by using the sequence.
+ -- We also want to merge role ban records because the game code previously did that in some UI,
+ -- and that code is now gone, expecting the DB to do it.
+
+ -- Create a table to store IDs to merge.
+ CREATE TEMPORARY TABLE _role_ban_import_merge_map (merge_id INTEGER, server_role_ban_id INTEGER UNIQUE);
+
+ -- Create a table to store merged IDs -> new ban IDs
+ CREATE TEMPORARY TABLE _role_ban_import_id_map (ban_id INTEGER UNIQUE, merge_id INTEGER UNIQUE);
+
+ -- Calculate merged role bans.
+ INSERT INTO
+ _role_ban_import_merge_map
+ SELECT
+ (
+ SELECT
+ sub.server_role_ban_id
+ FROM
+ server_role_ban AS sub
+ LEFT JOIN server_role_unban AS sub_unban
+ ON sub_unban.ban_id = sub.server_role_ban_id
+ WHERE
+ main.reason IS NOT DISTINCT FROM sub.reason
+ AND main.player_user_id IS NOT DISTINCT FROM sub.player_user_id
+ AND main.address IS NOT DISTINCT FROM sub.address
+ AND main.hwid IS NOT DISTINCT FROM sub.hwid
+ AND main.hwid_type IS NOT DISTINCT FROM sub.hwid_type
+ AND main.ban_time = sub.ban_time
+ AND (
+ (main.expiration_time IS NULL) = (sub.expiration_time IS NULL)
+ OR main.expiration_time = sub.expiration_time
+ )
+ AND main.round_id IS NOT DISTINCT FROM sub.round_id
+ AND main.severity IS NOT DISTINCT FROM sub.severity
+ AND main.hidden IS NOT DISTINCT FROM sub.hidden
+ AND main.banning_admin IS NOT DISTINCT FROM sub.banning_admin
+ AND (sub_unban.ban_id IS NULL) = (main_unban.ban_id IS NULL)
+ ORDER BY
+ sub.server_role_ban_id ASC
+ LIMIT 1
+ ), main.server_role_ban_id
+ FROM
+ server_role_ban AS main
+ LEFT JOIN server_role_unban AS main_unban
+ ON main_unban.ban_id = main.server_role_ban_id;
+
+ -- Assign new ban IDs for merged IDs.
+ INSERT OR IGNORE INTO
+ _role_ban_import_id_map
+ SELECT
+ merge_id + (SELECT seq FROM sqlite_sequence WHERE name = 'ban'),
+ merge_id
+ FROM
+ _role_ban_import_merge_map;
+
+ -- I sure fucking wish CTEs could span multiple queries...
+
+ -- Insert new ban records
+ INSERT INTO
+ ban (ban_id, type, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden)
+ SELECT
+ im.ban_id, 1, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, 0, FALSE, hidden
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id;
+
+ -- Insert role ban player records.
+ INSERT INTO
+ ban_player (user_id, ban_id)
+ SELECT
+ player_user_id, im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id
+ AND player_user_id IS NOT NULL;
+
+ -- Insert role ban address records.
+ INSERT INTO
+ ban_address (address, ban_id)
+ SELECT
+ address, im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id
+ AND address IS NOT NULL;
+
+ -- Insert role ban HWID records.
+ INSERT INTO
+ ban_hwid (hwid, hwid_type, ban_id)
+ SELECT
+ hwid, hwid_type, im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id
+ AND hwid IS NOT NULL;
+
+ -- Insert role ban role records.
+ INSERT INTO
+ ban_role (role_type, role_id, ban_id)
+ SELECT
+ substr(role_id, 1, instr(role_id, ':')-1),
+ substr(role_id, instr(role_id, ':')+1),
+ im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = mm.server_role_ban_id
+ -- Yes, we have some messy ban records which, after merging, end up with duplicate roles.
+ ON CONFLICT DO NOTHING;
+
+ -- Insert role unban records.
+ INSERT INTO
+ unban (ban_id, unbanning_admin, unban_time)
+ SELECT
+ im.ban_id, unbanning_admin, unban_time
+ FROM server_role_unban sru
+ INNER JOIN _role_ban_import_id_map im
+ ON im.merge_id = sru.ban_id;
+
+ -- Insert role rounds
+ INSERT INTO
+ ban_round (round_id, ban_id)
+ SELECT
+ round_id, im.ban_id
+ FROM
+ _role_ban_import_id_map im
+ INNER JOIN _role_ban_import_merge_map mm
+ ON im.merge_id = mm.merge_id
+ INNER JOIN server_role_ban srb
+ ON srb.server_role_ban_id = im.merge_id
+ WHERE mm.merge_id = mm.server_role_ban_id
+ AND round_id IS NOT NULL;
+ """);
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_server_ban_hit_ban_ban_id",
+ table: "server_ban_hit",
+ column: "ban_id",
+ principalTable: "ban",
+ principalColumn: "ban_id",
+ onDelete: ReferentialAction.Cascade);
+
+ migrationBuilder.DropForeignKey(
+ name: "FK_server_ban_hit_server_ban_ban_id",
+ table: "server_ban_hit");
+
+ migrationBuilder.DropTable(
+ name: "server_role_unban");
+
+ migrationBuilder.DropTable(
+ name: "server_unban");
+
+ migrationBuilder.DropTable(
+ name: "server_role_ban");
+
+ migrationBuilder.DropTable(
+ name: "server_ban");
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ throw new NotSupportedException("This migration cannot be rolled back");
+ }
+ }
+}
b.ToTable("assigned_user_id", (string)null);
});
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<bool>("AutoDelete")
+ .HasColumnType("INTEGER")
+ .HasColumnName("auto_delete");
+
+ b.Property<DateTime>("BanTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("ban_time");
+
+ b.Property<Guid?>("BanningAdmin")
+ .HasColumnType("TEXT")
+ .HasColumnName("banning_admin");
+
+ b.Property<int>("ExemptFlags")
+ .HasColumnType("INTEGER")
+ .HasColumnName("exempt_flags");
+
+ b.Property<DateTime?>("ExpirationTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("expiration_time");
+
+ b.Property<bool>("Hidden")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hidden");
+
+ b.Property<DateTime?>("LastEditedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_at");
+
+ b.Property<Guid?>("LastEditedById")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_edited_by_id");
+
+ b.Property<TimeSpan>("PlaytimeAtNote")
+ .HasColumnType("TEXT")
+ .HasColumnName("playtime_at_note");
+
+ b.Property<string>("Reason")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("reason");
+
+ b.Property<int>("Severity")
+ .HasColumnType("INTEGER")
+ .HasColumnName("severity");
+
+ b.Property<byte>("Type")
+ .HasColumnType("INTEGER")
+ .HasColumnName("type");
+
+ b.HasKey("Id")
+ .HasName("PK_ban");
+
+ b.HasIndex("BanningAdmin");
+
+ b.HasIndex("LastEditedById");
+
+ b.ToTable("ban", null, t =>
+ {
+ t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
+ });
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_address_id");
+
+ b.Property<string>("Address")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("address");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_address");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_address_ban_id");
+
+ b.ToTable("ban_address", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_hwid_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_hwid");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_hwid_ban_id");
+
+ b.ToTable("ban_hwid", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_player_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_player");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_player_ban_id");
+
+ b.HasIndex("UserId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_player", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRole", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_role_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<string>("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_id");
+
+ b.Property<string>("RoleType")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("role_type");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_role");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_role_ban_id");
+
+ b.HasIndex("RoleType", "RoleId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_role", (string)null);
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRound", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_round_id");
+
+ b.Property<int>("BanId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_id");
+
+ b.Property<int>("RoundId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("round_id");
+
+ b.HasKey("Id")
+ .HasName("PK_ban_round");
+
+ b.HasIndex("BanId")
+ .HasDatabaseName("IX_ban_round_ban_id");
+
+ b.HasIndex("RoundId", "BanId")
+ .IsUnique();
+
+ b.ToTable("ban_round", (string)null);
+ });
+
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
{
b.Property<int>("Id")
b.ToTable("server", (string)null);
});
- modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
- {
- b.Property<int>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasColumnName("server_ban_id");
-
- b.Property<string>("Address")
- .HasColumnType("TEXT")
- .HasColumnName("address");
-
- b.Property<bool>("AutoDelete")
- .HasColumnType("INTEGER")
- .HasColumnName("auto_delete");
-
- b.Property<DateTime>("BanTime")
- .HasColumnType("TEXT")
- .HasColumnName("ban_time");
-
- b.Property<Guid?>("BanningAdmin")
- .HasColumnType("TEXT")
- .HasColumnName("banning_admin");
-
- b.Property<int>("ExemptFlags")
- .HasColumnType("INTEGER")
- .HasColumnName("exempt_flags");
-
- b.Property<DateTime?>("ExpirationTime")
- .HasColumnType("TEXT")
- .HasColumnName("expiration_time");
-
- b.Property<bool>("Hidden")
- .HasColumnType("INTEGER")
- .HasColumnName("hidden");
-
- b.Property<DateTime?>("LastEditedAt")
- .HasColumnType("TEXT")
- .HasColumnName("last_edited_at");
-
- b.Property<Guid?>("LastEditedById")
- .HasColumnType("TEXT")
- .HasColumnName("last_edited_by_id");
-
- b.Property<Guid?>("PlayerUserId")
- .HasColumnType("TEXT")
- .HasColumnName("player_user_id");
-
- b.Property<TimeSpan>("PlaytimeAtNote")
- .HasColumnType("TEXT")
- .HasColumnName("playtime_at_note");
-
- b.Property<string>("Reason")
- .IsRequired()
- .HasColumnType("TEXT")
- .HasColumnName("reason");
-
- b.Property<int?>("RoundId")
- .HasColumnType("INTEGER")
- .HasColumnName("round_id");
-
- b.Property<int>("Severity")
- .HasColumnType("INTEGER")
- .HasColumnName("severity");
-
- b.HasKey("Id")
- .HasName("PK_server_ban");
-
- b.HasIndex("Address");
-
- b.HasIndex("BanningAdmin");
-
- b.HasIndex("LastEditedById");
-
- b.HasIndex("PlayerUserId")
- .HasDatabaseName("IX_server_ban_player_user_id");
-
- b.HasIndex("RoundId")
- .HasDatabaseName("IX_server_ban_round_id");
-
- b.ToTable("server_ban", null, t =>
- {
- t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
- });
- });
-
modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
{
b.Property<Guid>("UserId")
b.ToTable("server_ban_hit", (string)null);
});
- modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
- {
- b.Property<int>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasColumnName("server_role_ban_id");
-
- b.Property<string>("Address")
- .HasColumnType("TEXT")
- .HasColumnName("address");
-
- b.Property<DateTime>("BanTime")
- .HasColumnType("TEXT")
- .HasColumnName("ban_time");
-
- b.Property<Guid?>("BanningAdmin")
- .HasColumnType("TEXT")
- .HasColumnName("banning_admin");
-
- b.Property<DateTime?>("ExpirationTime")
- .HasColumnType("TEXT")
- .HasColumnName("expiration_time");
-
- b.Property<bool>("Hidden")
- .HasColumnType("INTEGER")
- .HasColumnName("hidden");
-
- b.Property<DateTime?>("LastEditedAt")
- .HasColumnType("TEXT")
- .HasColumnName("last_edited_at");
-
- b.Property<Guid?>("LastEditedById")
- .HasColumnType("TEXT")
- .HasColumnName("last_edited_by_id");
-
- b.Property<Guid?>("PlayerUserId")
- .HasColumnType("TEXT")
- .HasColumnName("player_user_id");
-
- b.Property<TimeSpan>("PlaytimeAtNote")
- .HasColumnType("TEXT")
- .HasColumnName("playtime_at_note");
-
- b.Property<string>("Reason")
- .IsRequired()
- .HasColumnType("TEXT")
- .HasColumnName("reason");
-
- b.Property<string>("RoleId")
- .IsRequired()
- .HasColumnType("TEXT")
- .HasColumnName("role_id");
-
- b.Property<int?>("RoundId")
- .HasColumnType("INTEGER")
- .HasColumnName("round_id");
-
- b.Property<int>("Severity")
- .HasColumnType("INTEGER")
- .HasColumnName("severity");
-
- b.HasKey("Id")
- .HasName("PK_server_role_ban");
-
- b.HasIndex("Address");
-
- b.HasIndex("BanningAdmin");
-
- b.HasIndex("LastEditedById");
-
- b.HasIndex("PlayerUserId")
- .HasDatabaseName("IX_server_role_ban_player_user_id");
-
- b.HasIndex("RoundId")
- .HasDatabaseName("IX_server_role_ban_round_id");
-
- b.ToTable("server_role_ban", null, t =>
- {
- t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
- });
- });
-
- modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
- .HasColumnName("role_unban_id");
+ .HasColumnName("trait_id");
- b.Property<int>("BanId")
+ b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
- .HasColumnName("ban_id");
-
- b.Property<DateTime>("UnbanTime")
- .HasColumnType("TEXT")
- .HasColumnName("unban_time");
+ .HasColumnName("profile_id");
- b.Property<Guid?>("UnbanningAdmin")
+ b.Property<string>("TraitName")
+ .IsRequired()
.HasColumnType("TEXT")
- .HasColumnName("unbanning_admin");
+ .HasColumnName("trait_name");
b.HasKey("Id")
- .HasName("PK_server_role_unban");
+ .HasName("PK_trait");
- b.HasIndex("BanId")
+ b.HasIndex("ProfileId", "TraitName")
.IsUnique();
- b.ToTable("server_role_unban", (string)null);
+ b.ToTable("trait", (string)null);
});
- modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ modelBuilder.Entity("Content.Server.Database.Unban", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnName("unbanning_admin");
b.HasKey("Id")
- .HasName("PK_server_unban");
+ .HasName("PK_unban");
b.HasIndex("BanId")
.IsUnique();
- b.ToTable("server_unban", (string)null);
- });
-
- modelBuilder.Entity("Content.Server.Database.Trait", b =>
- {
- b.Property<int>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasColumnName("trait_id");
-
- b.Property<int>("ProfileId")
- .HasColumnType("INTEGER")
- .HasColumnName("profile_id");
-
- b.Property<string>("TraitName")
- .IsRequired()
- .HasColumnType("TEXT")
- .HasColumnName("trait_name");
-
- b.HasKey("Id")
- .HasName("PK_trait");
-
- b.HasIndex("ProfileId", "TraitName")
- .IsUnique();
-
- b.ToTable("trait", (string)null);
+ b.ToTable("unban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
b.Navigation("Profile");
});
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.HasOne("Content.Server.Database.Player", "CreatedBy")
+ .WithMany("AdminServerBansCreated")
+ .HasForeignKey("BanningAdmin")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_ban_player_banning_admin");
+
+ b.HasOne("Content.Server.Database.Player", "LastEditedBy")
+ .WithMany("AdminServerBansLastEdited")
+ .HasForeignKey("LastEditedById")
+ .HasPrincipalKey("UserId")
+ .OnDelete(DeleteBehavior.SetNull)
+ .HasConstraintName("FK_ban_player_last_edited_by_id");
+
+ b.Navigation("CreatedBy");
+
+ b.Navigation("LastEditedBy");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Addresses")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_address_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Hwids")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_hwid_ban_ban_id");
+
+ b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
+ {
+ b1.Property<int>("BanHwidId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ban_hwid_id");
+
+ b1.Property<byte[]>("Hwid")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("hwid");
+
+ b1.Property<int>("Type")
+ .HasColumnType("INTEGER")
+ .HasColumnName("hwid_type");
+
+ b1.HasKey("BanHwidId");
+
+ b1.ToTable("ban_hwid");
+
+ b1.WithOwner()
+ .HasForeignKey("BanHwidId")
+ .HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id");
+ });
+
+ b.Navigation("Ban");
+
+ b.Navigation("HWId")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Players")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_player_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRole", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Roles")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_role_ban_ban_id");
+
+ b.Navigation("Ban");
+ });
+
+ modelBuilder.Entity("Content.Server.Database.BanRound", b =>
+ {
+ b.HasOne("Content.Server.Database.Ban", "Ban")
+ .WithMany("Rounds")
+ .HasForeignKey("BanId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_round_ban_ban_id");
+
+ b.HasOne("Content.Server.Database.Round", "Round")
+ .WithMany()
+ .HasForeignKey("RoundId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("FK_ban_round_round_round_id");
+
+ b.Navigation("Ban");
+
+ b.Navigation("Round");
+ });
+
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
b.Navigation("Server");
});
- modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
- {
- b.HasOne("Content.Server.Database.Player", "CreatedBy")
- .WithMany("AdminServerBansCreated")
- .HasForeignKey("BanningAdmin")
- .HasPrincipalKey("UserId")
- .OnDelete(DeleteBehavior.SetNull)
- .HasConstraintName("FK_server_ban_player_banning_admin");
-
- b.HasOne("Content.Server.Database.Player", "LastEditedBy")
- .WithMany("AdminServerBansLastEdited")
- .HasForeignKey("LastEditedById")
- .HasPrincipalKey("UserId")
- .OnDelete(DeleteBehavior.SetNull)
- .HasConstraintName("FK_server_ban_player_last_edited_by_id");
-
- b.HasOne("Content.Server.Database.Round", "Round")
- .WithMany()
- .HasForeignKey("RoundId")
- .HasConstraintName("FK_server_ban_round_round_id");
-
- b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
- {
- b1.Property<int>("ServerBanId")
- .HasColumnType("INTEGER")
- .HasColumnName("server_ban_id");
-
- b1.Property<byte[]>("Hwid")
- .IsRequired()
- .HasColumnType("BLOB")
- .HasColumnName("hwid");
-
- b1.Property<int>("Type")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasDefaultValue(0)
- .HasColumnName("hwid_type");
-
- b1.HasKey("ServerBanId");
-
- b1.ToTable("server_ban");
-
- b1.WithOwner()
- .HasForeignKey("ServerBanId")
- .HasConstraintName("FK_server_ban_server_ban_server_ban_id");
- });
-
- b.Navigation("CreatedBy");
-
- b.Navigation("HWId");
-
- b.Navigation("LastEditedBy");
-
- b.Navigation("Round");
- });
-
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
{
- b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ b.HasOne("Content.Server.Database.Ban", "Ban")
.WithMany("BanHits")
.HasForeignKey("BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
- .HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
+ .HasConstraintName("FK_server_ban_hit_ban_ban_id");
b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
.WithMany("BanHits")
b.Navigation("Connection");
});
- modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
- {
- b.HasOne("Content.Server.Database.Player", "CreatedBy")
- .WithMany("AdminServerRoleBansCreated")
- .HasForeignKey("BanningAdmin")
- .HasPrincipalKey("UserId")
- .OnDelete(DeleteBehavior.SetNull)
- .HasConstraintName("FK_server_role_ban_player_banning_admin");
-
- b.HasOne("Content.Server.Database.Player", "LastEditedBy")
- .WithMany("AdminServerRoleBansLastEdited")
- .HasForeignKey("LastEditedById")
- .HasPrincipalKey("UserId")
- .OnDelete(DeleteBehavior.SetNull)
- .HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
-
- b.HasOne("Content.Server.Database.Round", "Round")
- .WithMany()
- .HasForeignKey("RoundId")
- .HasConstraintName("FK_server_role_ban_round_round_id");
-
- b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
- {
- b1.Property<int>("ServerRoleBanId")
- .HasColumnType("INTEGER")
- .HasColumnName("server_role_ban_id");
-
- b1.Property<byte[]>("Hwid")
- .IsRequired()
- .HasColumnType("BLOB")
- .HasColumnName("hwid");
-
- b1.Property<int>("Type")
- .ValueGeneratedOnAdd()
- .HasColumnType("INTEGER")
- .HasDefaultValue(0)
- .HasColumnName("hwid_type");
-
- b1.HasKey("ServerRoleBanId");
-
- b1.ToTable("server_role_ban");
-
- b1.WithOwner()
- .HasForeignKey("ServerRoleBanId")
- .HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id");
- });
-
- b.Navigation("CreatedBy");
-
- b.Navigation("HWId");
-
- b.Navigation("LastEditedBy");
-
- b.Navigation("Round");
- });
-
- modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
+ modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
- b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
- .WithOne("Unban")
- .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
+ b.HasOne("Content.Server.Database.Profile", "Profile")
+ .WithMany("Traits")
+ .HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
- .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
+ .HasConstraintName("FK_trait_profile_profile_id");
- b.Navigation("Ban");
+ b.Navigation("Profile");
});
- modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
+ modelBuilder.Entity("Content.Server.Database.Unban", b =>
{
- b.HasOne("Content.Server.Database.ServerBan", "Ban")
+ b.HasOne("Content.Server.Database.Ban", "Ban")
.WithOne("Unban")
- .HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
+ .HasForeignKey("Content.Server.Database.Unban", "BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
- .HasConstraintName("FK_server_unban_server_ban_ban_id");
+ .HasConstraintName("FK_unban_ban_ban_id");
b.Navigation("Ban");
});
- modelBuilder.Entity("Content.Server.Database.Trait", b =>
- {
- b.HasOne("Content.Server.Database.Profile", "Profile")
- .WithMany("Traits")
- .HasForeignKey("ProfileId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired()
- .HasConstraintName("FK_trait_profile_profile_id");
-
- b.Navigation("Profile");
- });
-
modelBuilder.Entity("PlayerRound", b =>
{
b.HasOne("Content.Server.Database.Player", null)
b.Navigation("Flags");
});
+ modelBuilder.Entity("Content.Server.Database.Ban", b =>
+ {
+ b.Navigation("Addresses");
+
+ b.Navigation("BanHits");
+
+ b.Navigation("Hwids");
+
+ b.Navigation("Players");
+
+ b.Navigation("Roles");
+
+ b.Navigation("Rounds");
+
+ b.Navigation("Unban");
+ });
+
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.Navigation("BanHits");
b.Navigation("AdminServerBansLastEdited");
- b.Navigation("AdminServerRoleBansCreated");
-
- b.Navigation("AdminServerRoleBansLastEdited");
-
b.Navigation("AdminWatchlistsCreated");
b.Navigation("AdminWatchlistsDeleted");
b.Navigation("Rounds");
});
-
- modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
- {
- b.Navigation("BanHits");
-
- b.Navigation("Unban");
- });
-
- modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
- {
- b.Navigation("Unban");
- });
#pragma warning restore 612, 618
}
}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using Content.Shared.Database;
+using Microsoft.EntityFrameworkCore;
+using NpgsqlTypes;
+
+// ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength
+
+namespace Content.Server.Database;
+
+//
+// Contains model definitions primarily related to bans.
+//
+
+internal static class ModelBan
+{
+ public static void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity<Ban>()
+ .HasOne(b => b.CreatedBy)
+ .WithMany(pl => pl.AdminServerBansCreated)
+ .HasForeignKey(b => b.BanningAdmin)
+ .HasPrincipalKey(pl => pl.UserId)
+ .OnDelete(DeleteBehavior.SetNull);
+
+ modelBuilder.Entity<Ban>()
+ .HasOne(b => b.LastEditedBy)
+ .WithMany(pl => pl.AdminServerBansLastEdited)
+ .HasForeignKey(b => b.LastEditedById)
+ .HasPrincipalKey(pl => pl.UserId)
+ .OnDelete(DeleteBehavior.SetNull);
+
+ modelBuilder.Entity<BanPlayer>()
+ .HasIndex(bp => new { bp.UserId, bp.BanId })
+ .IsUnique();
+
+ modelBuilder.Entity<BanHwid>()
+ .OwnsOne(bp => bp.HWId)
+ .Property(hwid => hwid.Hwid)
+ .HasColumnName("hwid");
+
+ modelBuilder.Entity<BanRole>()
+ .HasIndex(bp => new { bp.RoleType, bp.RoleId, bp.BanId })
+ .IsUnique();
+
+ modelBuilder.Entity<BanRound>()
+ .HasIndex(bp => new { bp.RoundId, bp.BanId })
+ .IsUnique();
+
+ // Following indices have to be made manually by migration, due to limitations in EF Core:
+ // https://github.com/dotnet/efcore/issues/11336
+ // https://github.com/npgsql/efcore.pg/issues/2567
+ // modelBuilder.Entity<BanAddress>()
+ // .HasIndex(bp => new { bp.Address, bp.BanId })
+ // .IsUnique();
+ // modelBuilder.Entity<BanHwid>()
+ // .HasIndex(hwid => new { hwid.HWId.Type, hwid.HWId.Hwid, hwid.Hwid })
+ // .IsUnique();
+ // (postgres only)
+ // modelBuilder.Entity<BanAddress>()
+ // .HasIndex(ba => ba.Address)
+ // .IncludeProperties(ba => ba.BanId)
+ // .IsUnique()
+ // .HasMethod("gist")
+ // .HasOperators("inet_ops");
+
+ modelBuilder.Entity<Ban>()
+ .ToTable(t => t.HasCheckConstraint("NoExemptOnRoleBan", $"type = {(int)BanType.Server} OR exempt_flags = 0"));
+ }
+}
+
+/// <summary>
+/// Specifies a ban of some kind.
+/// </summary>
+/// <remarks>
+/// <para>
+/// Bans come in two types: <see cref="BanType.Server"/> and <see cref="BanType.Role"/>,
+/// distinguished with <see cref="Type"/>.
+/// </para>
+/// <para>
+/// Bans have one or more "matching data", these being <see cref="BanAddress"/>, <see cref="BanPlayer"/>,
+/// and <see cref="BanHwid"/> entities. If a player's connection info matches any of these,
+/// the ban's effects will apply to that player.
+/// </para>
+/// <para>
+/// Bans can be set to expire after a certain point in time, or be permanent. They can also be removed manually
+/// ("unbanned") by an admin, which is stored as an <see cref="Unban"/> entity existing for this ban.
+/// </para>
+/// </remarks>
+public sealed class Ban
+{
+ public int Id { get; set; }
+
+ /// <summary>
+ /// Whether this is a role or server ban.
+ /// </summary>
+ public required BanType Type { get; set; }
+
+ public TimeSpan PlaytimeAtNote { get; set; }
+
+ /// <summary>
+ /// The time when the ban was applied by an administrator.
+ /// </summary>
+ public DateTime BanTime { get; set; }
+
+ /// <summary>
+ /// The time the ban will expire. If null, the ban is permanent and will not expire naturally.
+ /// </summary>
+ public DateTime? ExpirationTime { get; set; }
+
+ /// <summary>
+ /// The administrator-stated reason for applying the ban.
+ /// </summary>
+ public string Reason { get; set; } = null!;
+
+ /// <summary>
+ /// The severity of the incident
+ /// </summary>
+ public NoteSeverity Severity { get; set; }
+
+ /// <summary>
+ /// User ID of the admin that initially applied the ban.
+ /// </summary>
+ [ForeignKey(nameof(CreatedBy))]
+ public Guid? BanningAdmin { get; set; }
+
+ public Player? CreatedBy { get; set; }
+
+ /// <summary>
+ /// User ID of the admin that last edited the note
+ /// </summary>
+ [ForeignKey(nameof(LastEditedBy))]
+ public Guid? LastEditedById { get; set; }
+
+ public Player? LastEditedBy { get; set; }
+ public DateTime? LastEditedAt { get; set; }
+
+ /// <summary>
+ /// Optional flags that allow adding exemptions to the ban via <see cref="ServerBanExemption"/>.
+ /// </summary>
+ public ServerBanExemptFlags ExemptFlags { get; set; }
+
+ /// <summary>
+ /// Whether this ban should be automatically deleted from the database when it expires.
+ /// </summary>
+ /// <remarks>
+ /// This isn't done automatically by the game,
+ /// you will need to set up something like a cron job to clear this from your database,
+ /// using a command like this:
+ /// psql -d ss14 -c "DELETE FROM server_ban WHERE auto_delete AND expiration_time < NOW()"
+ /// </remarks>
+ public bool AutoDelete { get; set; }
+
+ /// <summary>
+ /// Whether to display this ban in the admin remarks (notes) panel
+ /// </summary>
+ public bool Hidden { get; set; }
+
+ /// <summary>
+ /// If present, an administrator has manually repealed this ban.
+ /// </summary>
+ public Unban? Unban { get; set; }
+
+ public List<BanRound>? Rounds { get; set; }
+ public List<BanPlayer>? Players { get; set; }
+ public List<BanAddress>? Addresses { get; set; }
+ public List<BanHwid>? Hwids { get; set; }
+ public List<BanRole>? Roles { get; set; }
+ public List<ServerBanHit>? BanHits { get; set; }
+}
+
+/// <summary>
+/// Base type for entities that specify ban matching data.
+/// </summary>
+public interface IBanSelector
+{
+ int BanId { get; }
+ Ban? Ban { get; }
+}
+
+/// <summary>
+/// Indicates that a ban was related to a round (e.g. placed on that round).
+/// </summary>
+public sealed class BanRound
+{
+ public int Id { get; set; }
+
+ /// <summary>
+ /// The ID of the ban to which this round was relevant.
+ /// </summary>
+ [ForeignKey(nameof(Ban))]
+ public int BanId { get; set; }
+
+ public Ban? Ban { get; set; }
+
+ /// <summary>
+ /// The ID of the round to which this ban was relevant to.
+ /// </summary>
+ [ForeignKey(nameof(Round))]
+ public int RoundId { get; set; }
+
+ public Round? Round { get; set; }
+}
+
+/// <summary>
+/// Specifies a player that a <see cref="T:Database.Ban"/> matches.
+/// </summary>
+public sealed class BanPlayer : IBanSelector
+{
+ public int Id { get; set; }
+
+ /// <summary>
+ /// The user ID of the banned player.
+ /// </summary>
+ public Guid UserId { get; set; }
+
+ /// <summary>
+ /// The ID of the ban to which this applies.
+ /// </summary>
+ [ForeignKey(nameof(Ban))]
+ public int BanId { get; set; }
+
+ public Ban? Ban { get; set; }
+}
+
+/// <summary>
+/// Specifies an IP address range that a <see cref="T:Database.Ban"/> matches.
+/// </summary>
+public sealed class BanAddress : IBanSelector
+{
+ public int Id { get; set; }
+
+ /// <summary>
+ /// The address range being matched.
+ /// </summary>
+ public required NpgsqlInet Address { get; set; }
+
+ /// <summary>
+ /// The ID of the ban to which this applies.
+ /// </summary>
+ [ForeignKey(nameof(Ban))]
+ public int BanId { get; set; }
+
+ public Ban? Ban { get; set; }
+}
+
+/// <summary>
+/// Specifies a HWID that a <see cref="T:Database.Ban"/> matches.
+/// </summary>
+public sealed class BanHwid : IBanSelector
+{
+ public int Id { get; set; }
+
+ /// <summary>
+ /// The HWID being matched.
+ /// </summary>
+ public required TypedHwid HWId { get; set; }
+
+ /// <summary>
+ /// The ID of the ban to which this applies.
+ /// </summary>
+ [ForeignKey(nameof(Ban))]
+ public int BanId { get; set; }
+
+ public Ban? Ban { get; set; }
+}
+
+/// <summary>
+/// A single role banned among a greater role ban record.
+/// </summary>
+/// <remarks>
+/// <see cref="Ban"/>s of type <see cref="BanType.Role"/> should have one or more <see cref="BanRole"/>s
+/// to store which roles are actually banned.
+/// It is invalid for <see cref="BanType.Server"/> bans to have <see cref="BanRole"/> entities.
+/// </remarks>
+public sealed class BanRole
+{
+ public int Id { get; set; }
+
+ /// <summary>
+ /// What type of role is being banned. For example <c>Job</c> or <c>Antag</c>.
+ /// </summary>
+ public required string RoleType { get; set; }
+
+ /// <summary>
+ /// The ID of the role being banned. This is probably something like a prototype.
+ /// </summary>
+ public required string RoleId { get; set; }
+
+ /// <summary>
+ /// The ID of the ban to which this applies.
+ /// </summary>
+ [ForeignKey(nameof(Ban))]
+ public int BanId { get; set; }
+
+ public Ban? Ban { get; set; }
+}
+
+/// <summary>
+/// An explicit repeal of a <see cref="Ban"/> by an administrator.
+/// Having an entry for a ban neutralizes it.
+/// </summary>
+public sealed class Unban
+{
+ public int Id { get; set; }
+
+ /// <summary>
+ /// The ID of ban that is being repealed.
+ /// </summary>
+ [ForeignKey(nameof(Ban))]
+ public int BanId { get; set; }
+
+ /// <summary>
+ /// The ban that is being repealed.
+ /// </summary>
+ public Ban? Ban { get; set; }
+
+ /// <summary>
+ /// The admin that repealed the ban.
+ /// </summary>
+ public Guid? UnbanningAdmin { get; set; }
+
+ /// <summary>
+ /// The time the ban was repealed.
+ /// </summary>
+ public DateTime UnbanTime { get; set; }
+}
using System.Text.Json;
using Content.Shared.Database;
using Microsoft.EntityFrameworkCore;
-using NpgsqlTypes;
namespace Content.Server.Database
{
public DbSet<AdminLogPlayer> AdminLogPlayer { get; set; } = null!;
public DbSet<Whitelist> Whitelist { get; set; } = null!;
public DbSet<Blacklist> Blacklist { get; set; } = null!;
- public DbSet<ServerBan> Ban { get; set; } = default!;
- public DbSet<ServerUnban> Unban { get; set; } = default!;
+ public DbSet<Ban> Ban { get; set; } = default!;
+ public DbSet<BanRound> BanRound { get; set; } = default!;
+ public DbSet<BanPlayer> BanPlayer { get; set; } = default!;
+ public DbSet<BanAddress> BanAddress { get; set; } = default!;
+ public DbSet<BanHwid> BanHwid { get; set; } = default!;
+ public DbSet<BanRole> BanRole { get; set; } = default!;
+ public DbSet<Unban> Unban { get; set; } = default!;
public DbSet<ServerBanExemption> BanExemption { get; set; } = default!;
public DbSet<ConnectionLog> ConnectionLog { get; set; } = default!;
public DbSet<ServerBanHit> ServerBanHit { get; set; } = default!;
- public DbSet<ServerRoleBan> RoleBan { get; set; } = default!;
- public DbSet<ServerRoleUnban> RoleUnban { get; set; } = default!;
+
public DbSet<PlayTime> PlayTime { get; set; } = default!;
public DbSet<UploadedResourceLog> UploadedResourceLog { get; set; } = default!;
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
modelBuilder.Entity<AdminLogPlayer>()
.HasKey(logPlayer => new {logPlayer.RoundId, logPlayer.LogId, logPlayer.PlayerUserId});
- modelBuilder.Entity<ServerBan>()
- .HasIndex(p => p.PlayerUserId);
-
- modelBuilder.Entity<ServerBan>()
- .HasIndex(p => p.Address);
-
- modelBuilder.Entity<ServerBan>()
- .HasIndex(p => p.PlayerUserId);
-
- modelBuilder.Entity<ServerUnban>()
- .HasIndex(p => p.BanId)
- .IsUnique();
-
- modelBuilder.Entity<ServerBan>().ToTable(t =>
- t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"));
-
// Ban exemption can't have flags 0 since that wouldn't exempt anything.
// The row should be removed if setting to 0.
modelBuilder.Entity<ServerBanExemption>().ToTable(t =>
t.HasCheckConstraint("FlagsNotZero", "flags != 0"));
- modelBuilder.Entity<ServerRoleBan>()
- .HasIndex(p => p.PlayerUserId);
-
- modelBuilder.Entity<ServerRoleBan>()
- .HasIndex(p => p.Address);
-
- modelBuilder.Entity<ServerRoleBan>()
- .HasIndex(p => p.PlayerUserId);
-
- modelBuilder.Entity<ServerRoleUnban>()
- .HasIndex(p => p.BanId)
- .IsUnique();
-
- modelBuilder.Entity<ServerRoleBan>().ToTable(t =>
- t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"));
-
modelBuilder.Entity<Player>()
.HasIndex(p => p.UserId)
.IsUnique();
t.HasCheckConstraint("NotDismissedAndSeen",
"NOT dismissed OR seen"));
- modelBuilder.Entity<ServerBan>()
- .HasOne(ban => ban.CreatedBy)
- .WithMany(author => author.AdminServerBansCreated)
- .HasForeignKey(ban => ban.BanningAdmin)
- .HasPrincipalKey(author => author.UserId)
- .OnDelete(DeleteBehavior.SetNull);
-
- modelBuilder.Entity<ServerBan>()
- .HasOne(ban => ban.LastEditedBy)
- .WithMany(author => author.AdminServerBansLastEdited)
- .HasForeignKey(ban => ban.LastEditedById)
- .HasPrincipalKey(author => author.UserId)
- .OnDelete(DeleteBehavior.SetNull);
-
- modelBuilder.Entity<ServerRoleBan>()
- .HasOne(ban => ban.CreatedBy)
- .WithMany(author => author.AdminServerRoleBansCreated)
- .HasForeignKey(ban => ban.BanningAdmin)
- .HasPrincipalKey(author => author.UserId)
- .OnDelete(DeleteBehavior.SetNull);
-
- modelBuilder.Entity<ServerRoleBan>()
- .HasOne(ban => ban.LastEditedBy)
- .WithMany(author => author.AdminServerRoleBansLastEdited)
- .HasForeignKey(ban => ban.LastEditedById)
- .HasPrincipalKey(author => author.UserId)
- .OnDelete(DeleteBehavior.SetNull);
-
modelBuilder.Entity<RoleWhitelist>()
.HasOne(w => w.Player)
.WithMany(p => p.JobWhitelists)
.Property(p => p.Type)
.HasDefaultValue(HwidType.Legacy);
- modelBuilder.Entity<ServerBan>()
- .OwnsOne(p => p.HWId)
- .Property(p => p.Hwid)
- .HasColumnName("hwid");
-
- modelBuilder.Entity<ServerBan>()
- .OwnsOne(p => p.HWId)
- .Property(p => p.Type)
- .HasDefaultValue(HwidType.Legacy);
-
- modelBuilder.Entity<ServerRoleBan>()
- .OwnsOne(p => p.HWId)
- .Property(p => p.Hwid)
- .HasColumnName("hwid");
-
- modelBuilder.Entity<ServerRoleBan>()
- .OwnsOne(p => p.HWId)
- .Property(p => p.Type)
- .HasDefaultValue(HwidType.Legacy);
-
modelBuilder.Entity<ConnectionLog>()
.OwnsOne(p => p.HWId)
.Property(p => p.Hwid)
.OwnsOne(p => p.HWId)
.Property(p => p.Type)
.HasDefaultValue(HwidType.Legacy);
+
+ ModelBan.OnModelCreating(modelBuilder);
}
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
public List<AdminMessage> AdminMessagesCreated { get; set; } = null!;
public List<AdminMessage> AdminMessagesLastEdited { get; set; } = null!;
public List<AdminMessage> AdminMessagesDeleted { get; set; } = null!;
- public List<ServerBan> AdminServerBansCreated { get; set; } = null!;
- public List<ServerBan> AdminServerBansLastEdited { get; set; } = null!;
- public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
- public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
+ public List<Ban> AdminServerBansCreated { get; set; } = null!;
+ public List<Ban> AdminServerBansLastEdited { get; set; } = null!;
public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
}
[ForeignKey("RoundId,LogId")] public AdminLog Log { get; set; } = default!;
}
- // Used by SS14.Admin
- public interface IBanCommon<TUnban> where TUnban : IUnbanCommon
- {
- int Id { get; set; }
- Guid? PlayerUserId { get; set; }
- NpgsqlInet? Address { get; set; }
- TypedHwid? HWId { get; set; }
- DateTime BanTime { get; set; }
- DateTime? ExpirationTime { get; set; }
- string Reason { get; set; }
- NoteSeverity Severity { get; set; }
- Guid? BanningAdmin { get; set; }
- TUnban? Unban { get; set; }
- }
-
- // Used by SS14.Admin
- public interface IUnbanCommon
- {
- int Id { get; set; }
- int BanId { get; set; }
- Guid? UnbanningAdmin { get; set; }
- DateTime UnbanTime { get; set; }
- }
-
/// <summary>
/// Flags for use with <see cref="ServerBanExemption"/>.
/// </summary>
// @formatter:on
}
- /// <summary>
- /// A ban from playing on the server.
- /// If an incoming connection matches any of UserID, IP, or HWID, they will be blocked from joining the server.
- /// </summary>
- /// <remarks>
- /// At least one of UserID, IP, or HWID must be given (otherwise the ban would match nothing).
- /// </remarks>
- [Table("server_ban"), Index(nameof(PlayerUserId))]
- public class ServerBan : IBanCommon<ServerUnban>
- {
- public int Id { get; set; }
-
- [ForeignKey("Round")]
- public int? RoundId { get; set; }
- public Round? Round { get; set; }
-
- /// <summary>
- /// The user ID of the banned player.
- /// </summary>
- public Guid? PlayerUserId { get; set; }
- [Required] public TimeSpan PlaytimeAtNote { get; set; }
-
- /// <summary>
- /// CIDR IP address range of the ban. The whole range can match the ban.
- /// </summary>
- public NpgsqlInet? Address { get; set; }
-
- /// <summary>
- /// Hardware ID of the banned player.
- /// </summary>
- public TypedHwid? HWId { get; set; }
-
- /// <summary>
- /// The time when the ban was applied by an administrator.
- /// </summary>
- public DateTime BanTime { get; set; }
-
- /// <summary>
- /// The time the ban will expire. If null, the ban is permanent and will not expire naturally.
- /// </summary>
- public DateTime? ExpirationTime { get; set; }
-
- /// <summary>
- /// The administrator-stated reason for applying the ban.
- /// </summary>
- public string Reason { get; set; } = null!;
-
- /// <summary>
- /// The severity of the incident
- /// </summary>
- public NoteSeverity Severity { get; set; }
-
- /// <summary>
- /// User ID of the admin that applied the ban.
- /// </summary>
- [ForeignKey("CreatedBy")]
- public Guid? BanningAdmin { get; set; }
-
- public Player? CreatedBy { get; set; }
-
- /// <summary>
- /// User ID of the admin that last edited the note
- /// </summary>
- [ForeignKey("LastEditedBy")]
- public Guid? LastEditedById { get; set; }
-
- public Player? LastEditedBy { get; set; }
-
- /// <summary>
- /// When the ban was last edited
- /// </summary>
- public DateTime? LastEditedAt { get; set; }
-
- /// <summary>
- /// Optional flags that allow adding exemptions to the ban via <see cref="ServerBanExemption"/>.
- /// </summary>
- public ServerBanExemptFlags ExemptFlags { get; set; }
-
- /// <summary>
- /// If present, an administrator has manually repealed this ban.
- /// </summary>
- public ServerUnban? Unban { get; set; }
-
- /// <summary>
- /// Whether this ban should be automatically deleted from the database when it expires.
- /// </summary>
- /// <remarks>
- /// This isn't done automatically by the game,
- /// you will need to set up something like a cron job to clear this from your database,
- /// using a command like this:
- /// psql -d ss14 -c "DELETE FROM server_ban WHERE auto_delete AND expiration_time < NOW()"
- /// </remarks>
- public bool AutoDelete { get; set; }
-
- /// <summary>
- /// Whether to display this ban in the admin remarks (notes) panel
- /// </summary>
- public bool Hidden { get; set; }
-
- public List<ServerBanHit> BanHits { get; set; } = null!;
- }
-
- /// <summary>
- /// An explicit repeal of a <see cref="ServerBan"/> by an administrator.
- /// Having an entry for a ban neutralizes it.
- /// </summary>
- [Table("server_unban")]
- public class ServerUnban : IUnbanCommon
- {
- [Column("unban_id")] public int Id { get; set; }
-
- /// <summary>
- /// The ID of ban that is being repealed.
- /// </summary>
- public int BanId { get; set; }
-
- /// <summary>
- /// The ban that is being repealed.
- /// </summary>
- public ServerBan Ban { get; set; } = null!;
-
- /// <summary>
- /// The admin that repealed the ban.
- /// </summary>
- public Guid? UnbanningAdmin { get; set; }
-
- /// <summary>
- /// The time the ban repealed.
- /// </summary>
- public DateTime UnbanTime { get; set; }
- }
-
/// <summary>
/// An exemption for a specific user to a certain type of <see cref="ServerBan"/>.
/// </summary>
/// <summary>
/// The ban flags to exempt this player from.
- /// If any bit overlaps <see cref="ServerBan.ExemptFlags"/>, the ban is ignored.
+ /// If any bit overlaps <see cref="Ban.ExemptFlags"/>, the ban is ignored.
/// </summary>
public ServerBanExemptFlags Flags { get; set; }
}
public int BanId { get; set; }
public int ConnectionId { get; set; }
- public ServerBan Ban { get; set; } = null!;
+ public Ban Ban { get; set; } = null!;
public ConnectionLog Connection { get; set; } = null!;
}
- [Table("server_role_ban"), Index(nameof(PlayerUserId))]
- public sealed class ServerRoleBan : IBanCommon<ServerRoleUnban>
- {
- public int Id { get; set; }
- public int? RoundId { get; set; }
- public Round? Round { get; set; }
- public Guid? PlayerUserId { get; set; }
- [Required] public TimeSpan PlaytimeAtNote { get; set; }
- public NpgsqlInet? Address { get; set; }
- public TypedHwid? HWId { get; set; }
-
- public DateTime BanTime { get; set; }
-
- public DateTime? ExpirationTime { get; set; }
-
- public string Reason { get; set; } = null!;
-
- public NoteSeverity Severity { get; set; }
- [ForeignKey("CreatedBy")] public Guid? BanningAdmin { get; set; }
- public Player? CreatedBy { get; set; }
-
- [ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
- public Player? LastEditedBy { get; set; }
- public DateTime? LastEditedAt { get; set; }
-
- public ServerRoleUnban? Unban { get; set; }
- public bool Hidden { get; set; }
-
- public string RoleId { get; set; } = null!;
- }
-
- [Table("server_role_unban")]
- public sealed class ServerRoleUnban : IUnbanCommon
- {
- [Column("role_unban_id")] public int Id { get; set; }
-
- public int BanId { get; set; }
- public ServerRoleBan Ban { get; set; } = null!;
-
- public Guid? UnbanningAdmin { get; set; }
-
- public DateTime UnbanTime { get; set; }
- }
-
[Table("play_time")]
public sealed class PlayTime
{
/// <summary>
/// The reason for the ban.
/// </summary>
- /// <seealso cref="ServerBan.Reason"/>
+ /// <seealso cref="Ban.Reason"/>
public string Reason { get; set; } = "";
/// <summary>
/// Exemptions granted to the ban.
/// </summary>
- /// <seealso cref="ServerBan.ExemptFlags"/>
+ /// <seealso cref="Ban.ExemptFlags"/>
public ServerBanExemptFlags ExemptFlags { get; set; }
/// <summary>
/// Severity of the ban
/// </summary>
- /// <seealso cref="ServerBan.Severity"/>
+ /// <seealso cref="Ban.Severity"/>
public NoteSeverity Severity { get; set; }
/// <summary>
/// Ban will be automatically deleted once expired.
/// </summary>
- /// <seealso cref="ServerBan.AutoDelete"/>
+ /// <seealso cref="Ban.AutoDelete"/>
public bool AutoDelete { get; set; }
/// <summary>
/// Ban is not visible to players in the remarks menu.
/// </summary>
- /// <seealso cref="ServerBan.Hidden"/>
+ /// <seealso cref="Ban.Hidden"/>
public bool Hidden { get; set; }
}
// ReSharper disable StringLiteralTypo
// Enforce that an address cannot be IPv6-mapped IPv4.
// So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
- modelBuilder.Entity<ServerBan>().ToTable(t =>
- t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"));
-
- modelBuilder.Entity<ServerRoleBan>().ToTable( t =>
+ modelBuilder.Entity<BanAddress>().ToTable(t =>
t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"));
modelBuilder.Entity<Player>().ToTable(t =>
);
modelBuilder
- .Entity<ServerBan>()
- .Property(e => e.Address)
- .HasColumnType("TEXT")
- .HasConversion(ipMaskConverter);
-
- modelBuilder
- .Entity<ServerRoleBan>()
+ .Entity<BanAddress>()
.Property(e => e.Address)
.HasColumnType("TEXT")
.HasConversion(ipMaskConverter);
-using System.Threading.Tasks;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading.Tasks;
using Content.Server.Administration.Managers;
using Content.Server.Database;
using Content.Server.EUI;
using Content.Shared.Administration;
using Content.Shared.Administration.BanList;
+using Content.Shared.Database;
using Content.Shared.Eui;
using Robust.Shared.Network;
private Guid BanListPlayer { get; set; }
private string BanListPlayerName { get; set; } = string.Empty;
- private List<SharedServerBan> Bans { get; } = new();
- private List<SharedServerRoleBan> RoleBans { get; } = new();
+ private List<SharedBan> Bans { get; } = new();
+ private List<SharedBan> RoleBans { get; } = new();
public override void Opened()
{
private async Task LoadBans(NetUserId userId)
{
- foreach (var ban in await _db.GetServerBansAsync(null, userId, null, null))
- {
- SharedServerUnban? unban = null;
- if (ban.Unban is { } unbanDef)
- {
- var unbanningAdmin = unbanDef.UnbanningAdmin == null
- ? null
- : (await _playerLocator.LookupIdAsync(unbanDef.UnbanningAdmin.Value))?.Username;
- unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime);
- }
-
- (string, int cidrMask)? ip = ("*Hidden*", 0);
- var hwid = "*Hidden*";
-
- if (_admins.HasAdminFlag(Player, AdminFlags.Pii))
- {
- ip = ban.Address is { } address
- ? (address.address.ToString(), address.cidrMask)
- : null;
-
- hwid = ban.HWId?.ToString();
- }
-
- Bans.Add(new SharedServerBan(
- ban.Id,
- ban.UserId,
- ip,
- hwid,
- ban.BanTime.UtcDateTime,
- ban.ExpirationTime?.UtcDateTime,
- ban.Reason,
- ban.BanningAdmin == null
- ? null
- : (await _playerLocator.LookupIdAsync(ban.BanningAdmin.Value))?.Username,
- unban
- ));
- }
+ await LoadBansCore(userId, BanType.Server, Bans);
+ await LoadBansCore(userId, BanType.Role, RoleBans);
}
- private async Task LoadRoleBans(NetUserId userId)
+ private async Task LoadBansCore(NetUserId userId, BanType banType, List<SharedBan> list)
{
- foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null, null))
+ foreach (var ban in await _db.GetBansAsync(null, userId, null, null, type: banType))
{
- SharedServerUnban? unban = null;
+ SharedUnban? unban = null;
if (ban.Unban is { } unbanDef)
{
var unbanningAdmin = unbanDef.UnbanningAdmin == null
? null
: (await _playerLocator.LookupIdAsync(unbanDef.UnbanningAdmin.Value))?.Username;
- unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime);
+ unban = new SharedUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime);
}
- (string, int cidrMask)? ip = ("*Hidden*", 0);
- var hwid = "*Hidden*";
+ ImmutableArray<(string, int cidrMask)> ips = [("*Hidden*", 0)];
+ ImmutableArray<string> hwids = ["*Hidden*"];
if (_admins.HasAdminFlag(Player, AdminFlags.Pii))
{
- ip = ban.Address is { } address
- ? (address.address.ToString(), address.cidrMask)
- : null;
-
- hwid = ban.HWId?.ToString();
+ ips = [..ban.Addresses.Select(a => (a.address.ToString(), a.cidrMask))];
+ hwids = [..ban.HWIds.Select(h => h.ToString())];
}
- RoleBans.Add(new SharedServerRoleBan(
+
+ list.Add(new SharedBan(
ban.Id,
- ban.UserId,
- ip,
- hwid,
+ ban.Type,
+ ban.UserIds,
+ ips,
+ hwids,
ban.BanTime.UtcDateTime,
ban.ExpirationTime?.UtcDateTime,
ban.Reason,
? null
: (await _playerLocator.LookupIdAsync(ban.BanningAdmin.Value))?.Username,
unban,
- ban.Role
+ ban.Roles
));
}
}
string.Empty;
await LoadBans(userId);
- await LoadRoleBans(userId);
StateDirty();
}
private string PlayerName { get; set; } = string.Empty;
private IPAddress? LastAddress { get; set; }
private ImmutableTypedHwid? LastHwid { get; set; }
- private const int Ipv4_CIDR = 32;
- private const int Ipv6_CIDR = 64;
+ private const int Ipv4_CIDR = CreateBanInfo.DefaultMaskIpv4;
+ private const int Ipv6_CIDR = CreateBanInfo.DefaultMaskIpv6;
public BanPanelEui()
{
return;
}
+ var isRoleBan = ban.BannedJobs?.Length > 0 || ban.BannedAntags?.Length > 0;
+
+ CreateBanInfo banInfo = isRoleBan ? new CreateRoleBanInfo(ban.Reason) : new CreateServerBanInfo(ban.Reason);
+
+ banInfo.WithBanningAdmin(Player.UserId);
+ banInfo.WithSeverity(ban.Severity);
+ if (ban.BanDurationMinutes > 0)
+ banInfo.WithMinutes(ban.BanDurationMinutes);
+
(IPAddress, int)? addressRange = null;
if (ban.IpAddress is not null)
{
targetHWid = ban.UseLastHwid ? located.LastHWId : ban.Hwid;
}
- if (ban.BannedJobs?.Length > 0 || ban.BannedAntags?.Length > 0)
+ if (addressRange != null)
+ banInfo.AddAddressRange(addressRange.Value);
+
+ if (targetUid != null)
+ banInfo.AddUser(targetUid.Value, ban.Target!);
+
+ banInfo.AddHWId(targetHWid);
+
+ if (isRoleBan)
{
- var now = DateTimeOffset.UtcNow;
- foreach (var role in ban.BannedJobs ?? [])
+ var roleBanInfo = (CreateRoleBanInfo)banInfo;
+ foreach (var row in ban.BannedJobs ?? [])
{
- _banManager.CreateRoleBan(
- targetUid,
- ban.Target,
- Player.UserId,
- addressRange,
- targetHWid,
- role,
- ban.BanDurationMinutes,
- ban.Severity,
- ban.Reason,
- now
- );
+ roleBanInfo.AddJob(row);
}
- foreach (var role in ban.BannedAntags ?? [])
+ foreach (var row in ban.BannedAntags ?? [])
{
- _banManager.CreateRoleBan(
- targetUid,
- ban.Target,
- Player.UserId,
- addressRange,
- targetHWid,
- role,
- ban.BanDurationMinutes,
- ban.Severity,
- ban.Reason,
- now
- );
+ roleBanInfo.AddAntag(row);
}
- Close();
-
- return;
+ _banManager.CreateRoleBan(roleBanInfo);
}
-
- if (ban.Erase && targetUid is not null)
+ else
{
- try
- {
- if (_entities.TrySystem(out AdminSystem? adminSystem))
- adminSystem.Erase(targetUid.Value);
- }
- catch (Exception e)
+ if (ban.Erase && targetUid is not null)
{
- _sawmill.Error($"Error while erasing banned player:\n{e}");
+ try
+ {
+ if (_entities.TrySystem(out AdminSystem? adminSystem))
+ adminSystem.Erase(targetUid.Value);
+ }
+ catch (Exception e)
+ {
+ _sawmill.Error($"Error while erasing banned player:\n{e}");
+ }
}
- }
- _banManager.CreateServerBan(
- targetUid,
- ban.Target,
- Player.UserId,
- addressRange,
- targetHWid,
- ban.BanDurationMinutes,
- ban.Severity,
- ban.Reason
- );
+ _banManager.CreateServerBan((CreateServerBanInfo)banInfo);
+ }
Close();
}
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
- _bans.CreateServerBan(targetUid, target, player?.UserId, null, targetHWid, minutes, severity, reason);
+ var banInfo = new CreateServerBanInfo(reason);
+ banInfo.WithBanningAdmin(player?.UserId);
+ banInfo.AddUser(targetUid, target);
+ banInfo.AddHWId(targetHWid);
+ if (minutes > 0)
+ banInfo.WithMinutes(minutes);
+ banInfo.WithSeverity(severity);
+
+ _bans.CreateServerBan(banInfo);
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
if (shell.Player is not { } player)
{
- var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false);
+ var bans = await _dbManager.GetBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false);
if (bans.Count == 0)
{
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
- // If you are trying to remove the following variable, please don't. It's there because the note system groups role bans by time, reason and banning admin.
- // Without it the note list will get needlessly cluttered.
- var now = DateTimeOffset.UtcNow;
+ var banInfo = new CreateRoleBanInfo(reason);
+ if (minutes > 0)
+ banInfo.WithMinutes(minutes);
+ banInfo.AddUser(targetUid, located.Username);
+ banInfo.WithBanningAdmin(shell.Player?.UserId);
+ banInfo.AddHWId(targetHWid);
+ banInfo.WithSeverity(severity);
+
foreach (var job in departmentProto.Roles)
{
- _banManager.CreateRoleBan(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, job, minutes, severity, reason, now);
+ banInfo.AddJob(job);
}
+
+ _banManager.CreateRoleBan(banInfo);
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
using Content.Shared.Administration;
using Robust.Server.Player;
using Robust.Shared.Console;
+using Robust.Shared.Network;
namespace Content.Server.Administration.Commands;
return;
}
- await _adminNotes.OpenEui(player, notedPlayer);
+ await _adminNotes.OpenEui(player, new NetUserId(notedPlayer));
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
return;
}
- var ban = await _dbManager.GetServerBanAsync(banId);
+ var ban = await _dbManager.GetBanAsync(banId);
if (ban == null)
{
return;
}
- await _dbManager.AddServerUnbanAsync(new ServerUnbanDef(banId, player?.UserId, DateTimeOffset.Now));
+ await _dbManager.AddUnbanAsync(new UnbanDef(banId, player?.UserId, DateTimeOffset.Now));
shell.WriteLine(Loc.GetString($"cmd-pardon-success", ("id", banId)));
}
-using System.Linq;
-using System.Text;
-using Content.Server.Administration.Managers;
+using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Database;
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
+ var banInfo = new CreateRoleBanInfo(reason);
+ if (minutes > 0)
+ banInfo.WithMinutes(minutes);
+ banInfo.AddUser(targetUid, located.Username);
+ banInfo.WithBanningAdmin(shell.Player?.UserId);
+ banInfo.AddHWId(targetHWid);
+ banInfo.WithSeverity(severity);
+
if (_proto.HasIndex<JobPrototype>(role))
- _bans.CreateRoleBan<JobPrototype>(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, role, minutes, severity, reason, DateTimeOffset.UtcNow);
+ {
+ banInfo.AddJob(new ProtoId<JobPrototype>(role));
+ }
else if (_proto.HasIndex<AntagPrototype>(role))
- _bans.CreateRoleBan<AntagPrototype>(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, role, minutes, severity, reason, DateTimeOffset.UtcNow);
+ {
+ banInfo.AddAntag(new ProtoId<AntagPrototype>(role));
+ }
else
+ {
shell.WriteError(Loc.GetString("cmd-roleban-job-parse", ("job", role)));
+ return;
+ }
+
+ _bans.CreateRoleBan(banInfo);
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
-using System.Linq;
-using System.Text;
-using Content.Server.Administration.BanList;
+using Content.Server.Administration.BanList;
using Content.Server.EUI;
using Content.Server.Database;
using Content.Shared.Administration;
-using Robust.Server.Player;
+using Content.Shared.Database;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
if (shell.Player is not { } player)
{
- var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned);
+ var bans = await _dbManager.GetBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned, type: BanType.Role);
if (bans.Count == 0)
{
foreach (var ban in bans)
{
- var msg = $"ID: {ban.Id}: Role: {ban.Role} Reason: {ban.Reason}";
+ var msg = $"ID: {ban.Id}: Role(s): {string.Join(",", ban.Roles ?? [])} Reason: {ban.Reason}";
shell.WriteLine(msg);
}
return;
private async void ProcessBanNotification(BanNotificationData data)
{
- if ((await _entryManager.ServerEntity).Id == data.ServerId)
- {
- _sawmill.Verbose("Not processing ban notification: came from this server");
- return;
- }
-
_sawmill.Verbose($"Processing ban notification for ban {data.BanId}");
- var ban = await _db.GetServerBanAsync(data.BanId);
+ var ban = await _db.GetBanAsync(data.BanId);
if (ban == null)
{
_sawmill.Warning($"Ban in notification ({data.BanId}) didn't exist?");
/// </summary>
[JsonRequired, JsonPropertyName("ban_id")]
public int BanId { get; init; }
-
- /// <summary>
- /// The id of the server the ban was made on.
- /// This is used to avoid double work checking the ban on the originating server.
- /// </summary>
- /// <remarks>
- /// This is optional in case the ban was made outside a server (SS14.Admin)
- /// </remarks>
- [JsonPropertyName("server_id")]
- public int? ServerId { get; init; }
}
}
using System.Collections.Immutable;
using System.Linq;
-using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
+using Robust.Shared.Utility;
namespace Content.Server.Administration.Managers;
private ISawmill _sawmill = default!;
public const string SawmillId = "admin.bans";
- public const string PrefixAntag = "Antag:";
- public const string PrefixJob = "Job:";
+ public const string DbTypeAntag = "Antag";
+ public const string DbTypeJob = "Job";
- private readonly Dictionary<ICommonSession, List<ServerRoleBanDef>> _cachedRoleBans = new();
+ private readonly Dictionary<ICommonSession, List<BanDef>> _cachedRoleBans = new();
// Cached ban exemption flags are used to handle
private readonly Dictionary<ICommonSession, ServerBanExemptFlags> _cachedBanExemptions = new();
var netChannel = player.Channel;
ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
var modernHwids = netChannel.UserData.ModernHWIds;
- var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, modernHwids, false);
-
- var userRoleBans = new List<ServerRoleBanDef>();
+ var roleBans = await _db.GetBansAsync(
+ netChannel.RemoteEndPoint.Address,
+ player.UserId,
+ hwId,
+ modernHwids,
+ false,
+ type: BanType.Role);
+
+ var userRoleBans = new List<BanDef>();
foreach (var ban in roleBans)
{
userRoleBans.Add(ban);
}
#region Server Bans
- public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason)
+ public async void CreateServerBan(CreateServerBanInfo banInfo)
{
- DateTimeOffset? expires = null;
- if (minutes > 0)
+ var (banDef, expires) = await CreateBanDef(banInfo, BanType.Server, null);
+
+ await _db.AddBanAsync(banDef);
+
+ if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules))
{
- expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
+ // Reset their last read rules. They probably need a refresher!
+ foreach (var (userId, _) in banInfo.Users)
+ {
+ await _db.SetLastReadRules(userId, null);
+ }
}
- _systems.TryGetEntitySystem<GameTicker>(out var ticker);
- int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
- var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
-
- var banDef = new ServerBanDef(
- null,
- target,
- addressRange,
- hwid,
- DateTimeOffset.Now,
- expires,
- roundId,
- playtime,
- reason,
- severity,
- banningAdmin,
- null);
-
- await _db.AddServerBanAsync(banDef);
- if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules) && target != null)
- await _db.SetLastReadRules(target.Value, null); // Reset their last read rules. They probably need a refresher!
- var adminName = banningAdmin == null
+ var adminName = banInfo.BanningAdmin == null
? Loc.GetString("system-user")
- : (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
- var targetName = target is null ? "null" : $"{targetUsername} ({target})";
- var addressRangeString = addressRange != null
- ? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}"
- : "null";
- var hwidString = hwid?.ToString() ?? "null";
+ : (await _db.GetPlayerRecordByUserId(banInfo.BanningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
+
+ var targetName = banInfo.Users.Count == 0
+ ? "null"
+ : string.Join(", ", banInfo.Users.Select(u => $"{u.UserName} ({u.UserId})"));
+
+ var addressRangeString = banInfo.AddressRanges.Count != 0
+ ? "null"
+ : string.Join(", ", banInfo.AddressRanges.Select(a => $"{a.Address}/{a.Mask}"));
+
+ var hwidString = banInfo.HWIds.Count == 0
+ ? "null"
+ : string.Join(", ", banInfo.HWIds);
+
var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}";
var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii";
var logMessage = Loc.GetString(
key,
("admin", adminName),
- ("severity", severity),
+ ("severity", banDef.Severity),
("expires", expiresString),
("name", targetName),
("ip", addressRangeString),
("hwid", hwidString),
- ("reason", reason));
+ ("reason", banInfo.Reason));
_sawmill.Info(logMessage);
_chat.SendAdminAlert(logMessage);
KickMatchingConnectedPlayers(banDef, "newly placed ban");
}
- private void KickMatchingConnectedPlayers(ServerBanDef def, string source)
+ private NoteSeverity GetSeverityForServerBan(CreateBanInfo banInfo, CVarDef<string> defaultCVar)
+ {
+ if (banInfo.Severity != null)
+ return banInfo.Severity.Value;
+
+ if (Enum.TryParse(_cfg.GetCVar(defaultCVar), true, out NoteSeverity parsedSeverity))
+ return parsedSeverity;
+
+ _sawmill.Error($"CVar {defaultCVar.Name} has invalid ban severity!");
+ return NoteSeverity.None;
+ }
+
+ private void KickMatchingConnectedPlayers(BanDef def, string source)
{
foreach (var player in _playerManager.Sessions)
{
}
}
- private bool BanMatchesPlayer(ICommonSession player, ServerBanDef ban)
+ private bool BanMatchesPlayer(ICommonSession player, BanDef ban)
{
var playerInfo = new BanMatcher.PlayerInfo
{
return BanMatcher.BanMatches(ban, playerInfo);
}
- private void KickForBanDef(ICommonSession player, ServerBanDef def)
+ private void KickForBanDef(ICommonSession player, BanDef def)
{
var message = def.FormatBanMessage(_cfg, _localizationManager);
player.Channel.Disconnect(message);
#region Role Bans
- // If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin.
- // Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset.
- public async void CreateRoleBan<T>(
- NetUserId? target,
- string? targetUsername,
- NetUserId? banningAdmin,
- (IPAddress, int)? addressRange,
- ImmutableTypedHwid? hwid,
- ProtoId<T> role,
- uint? minutes,
- NoteSeverity severity,
- string reason,
- DateTimeOffset timeOfBan
- ) where T : class, IPrototype
+ public async void CreateRoleBan(CreateRoleBanInfo banInfo)
{
- string encodedRole;
+ ImmutableArray<BanRoleDef> roleDefs =
+ [
+ .. ToBanRoleDef(banInfo.JobPrototypes),
+ .. ToBanRoleDef(banInfo.AntagPrototypes),
+ ];
- // TODO: Note that it's possible to clash IDs here between a job and an antag. The refactor that introduced
- // this check has consciously avoided refactoring Job and Antag prototype.
- // Refactor Job- and Antag- Prototype to introduce a common RolePrototype, which will fix this possible clash.
+ if (roleDefs.Length == 0)
+ throw new ArgumentException("Must specify at least one role to ban!");
- //TODO remove this check as part of the above refactor
- if (_prototypeManager.HasIndex<JobPrototype>(role) && _prototypeManager.HasIndex<AntagPrototype>(role))
- {
- _sawmill.Error($"Creating role ban for {role}: cannot create role ban, role is both JobPrototype and AntagPrototype.");
+ var (banDef, expires) = await CreateBanDef(banInfo, BanType.Role, roleDefs);
- return;
- }
+ await AddRoleBan(banDef);
- // Don't trust the input: make sure the job or antag actually exists.
- if (_prototypeManager.HasIndex<JobPrototype>(role))
- encodedRole = PrefixJob + role;
- else if (_prototypeManager.HasIndex<AntagPrototype>(role))
- encodedRole = PrefixAntag + role;
- else
- {
- _sawmill.Error($"Creating role ban for {role}: cannot create role ban, role is not a JobPrototype or an AntagPrototype.");
+ var length = expires == null
+ ? Loc.GetString("cmd-roleban-inf")
+ : Loc.GetString("cmd-roleban-until", ("expires", expires));
- return;
+ var targetName = banInfo.Users.Count == 0
+ ? "null"
+ : string.Join(", ", banInfo.Users.Select(u => $"{u.UserName} ({u.UserId})"));
+
+ _chat.SendAdminAlert(Loc.GetString(
+ "cmd-roleban-success",
+ ("target", targetName),
+ ("role", string.Join(", ", roleDefs)),
+ ("reason", banInfo.Reason),
+ ("length", length)));
+
+ foreach (var (userId, _) in banInfo.Users)
+ {
+ if (_playerManager.TryGetSessionById(userId, out var session))
+ SendRoleBans(session);
}
+ }
- DateTimeOffset? expires = null;
+ private async Task<(BanDef Ban, DateTimeOffset? Expires)> CreateBanDef(
+ CreateBanInfo banInfo,
+ BanType type,
+ ImmutableArray<BanRoleDef>? roleBans)
+ {
+ if (banInfo.Users.Count == 0 && banInfo.HWIds.Count == 0 && banInfo.AddressRanges.Count == 0)
+ throw new ArgumentException("Must specify at least one user, HWID, or address range");
- if (minutes > 0)
- expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
+ DateTimeOffset? expires = null;
+ if (banInfo.Duration is { } duration)
+ expires = DateTimeOffset.Now + duration;
- _systems.TryGetEntitySystem(out GameTicker? ticker);
- int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
- var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
+ ImmutableArray<int> roundIds;
+ if (banInfo.RoundIds.Count > 0)
+ {
+ roundIds = [..banInfo.RoundIds];
+ }
+ else if (_systems.TryGetEntitySystem<GameTicker>(out var ticker) && ticker.RoundId != 0)
+ {
+ roundIds = [ticker.RoundId];
+ }
+ else
+ {
+ roundIds = [];
+ }
- var banDef = new ServerRoleBanDef(
+ return (new BanDef(
null,
- target,
- addressRange,
- hwid,
- timeOfBan,
+ type,
+ [..banInfo.Users.Select(u => u.UserId)],
+ [..banInfo.AddressRanges],
+ [..banInfo.HWIds],
+ DateTimeOffset.Now,
expires,
- roundId,
- playtime,
- reason,
- severity,
- banningAdmin,
+ roundIds,
+ await GetPlayTime(banInfo),
+ banInfo.Reason,
+ GetSeverityForServerBan(banInfo, CCVars.ServerBanDefaultSeverity),
+ banInfo.BanningAdmin,
null,
- encodedRole);
+ roles: roleBans), expires);
+ }
+
+ private async Task<TimeSpan> GetPlayTime(CreateBanInfo banInfo)
+ {
+ var firstPlayer = banInfo.Users.FirstOrNull()?.UserId;
+ if (firstPlayer == null)
+ return TimeSpan.Zero;
- if (!await AddRoleBan(banDef))
+ return (await _db.GetPlayTimes(firstPlayer.Value))
+ .Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)
+ ?.TimeSpent ?? TimeSpan.Zero;
+ }
+
+ private IEnumerable<BanRoleDef> ToBanRoleDef<T>(IEnumerable<ProtoId<T>> protoIds) where T : class, IPrototype
+ {
+ return protoIds.Select(protoId =>
{
- _chat.SendAdminAlert(Loc.GetString("cmd-roleban-existing", ("target", targetUsername ?? "null"), ("role", role)));
+ // TODO: I have no idea if this check is necessary. The previous code was a complete mess,
+ // so out of safety I'm leaving this in.
+ if (_prototypeManager.HasIndex<JobPrototype>(protoId) && _prototypeManager.HasIndex<AntagPrototype>(protoId))
+ {
+ throw new InvalidOperationException(
+ $"Creating role ban for {protoId}: cannot create role ban, role is both JobPrototype and AntagPrototype.");
+ }
- return;
- }
+ // Don't trust the input: make sure the role actually exists.
+ if (!_prototypeManager.HasIndex(protoId))
+ throw new UnknownPrototypeException(protoId, typeof(T));
- var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires));
- _chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length)));
+ return new BanRoleDef(PrototypeKindToDbType<T>(), protoId);
+ });
+ }
+
+ private static string PrototypeKindToDbType<T>() where T : class, IPrototype
+ {
+ if (typeof(T) == typeof(JobPrototype))
+ return DbTypeJob;
- if (target is not null && _playerManager.TryGetSessionById(target.Value, out var session))
- SendRoleBans(session);
+ if (typeof(T) == typeof(AntagPrototype))
+ return DbTypeAntag;
+
+ throw new ArgumentException($"Unknown prototype kind for role bans: {typeof(T)}");
}
- private async Task<bool> AddRoleBan(ServerRoleBanDef banDef)
+ private async Task AddRoleBan(BanDef banDef)
{
- banDef = await _db.AddServerRoleBanAsync(banDef);
+ banDef = await _db.AddBanAsync(banDef);
- if (banDef.UserId != null
- && _playerManager.TryGetSessionById(banDef.UserId, out var player)
- && _cachedRoleBans.TryGetValue(player, out var cachedBans))
+ foreach (var user in banDef.UserIds)
{
- cachedBans.Add(banDef);
+ if (_playerManager.TryGetSessionById(user, out var player)
+ && _cachedRoleBans.TryGetValue(player, out var cachedBans))
+ {
+ cachedBans.Add(banDef);
+ }
}
-
- return true;
}
public async Task<string> PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
{
- var ban = await _db.GetServerRoleBanAsync(banId);
+ var ban = await _db.GetBanAsync(banId);
if (ban == null)
{
return $"No ban found with id {banId}";
}
+ if (ban.Type != BanType.Role)
+ throw new InvalidOperationException("Ban was not a role ban!");
+
if (ban.Unban != null)
{
var response = new StringBuilder("This ban has already been pardoned");
return response.ToString();
}
- await _db.AddServerRoleUnbanAsync(new ServerRoleUnbanDef(banId, unbanningAdmin, DateTimeOffset.Now));
+ await _db.AddUnbanAsync(new UnbanDef(banId, unbanningAdmin, DateTimeOffset.Now));
- if (ban.UserId is { } player
- && _playerManager.TryGetSessionById(player, out var session)
- && _cachedRoleBans.TryGetValue(session, out var roleBans))
+ foreach (var user in ban.UserIds)
{
- roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id);
- SendRoleBans(session);
+ if (_playerManager.TryGetSessionById(user, out var session)
+ && _cachedRoleBans.TryGetValue(session, out var roleBans))
+ {
+ roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id);
+ SendRoleBans(session);
+ }
+
}
return $"Pardoned ban with id {banId}";
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId)
{
- return GetRoleBans<JobPrototype>(playerUserId, PrefixJob);
+ return GetRoleBans<JobPrototype>(playerUserId);
}
public HashSet<ProtoId<AntagPrototype>>? GetAntagBans(NetUserId playerUserId)
{
- return GetRoleBans<AntagPrototype>(playerUserId, PrefixAntag);
+ return GetRoleBans<AntagPrototype>(playerUserId);
}
- private HashSet<ProtoId<T>>? GetRoleBans<T>(NetUserId playerUserId, string prefix) where T : class, IPrototype
+ private HashSet<ProtoId<T>>? GetRoleBans<T>(NetUserId playerUserId) where T : class, IPrototype
{
if (!_playerManager.TryGetSessionById(playerUserId, out var session))
return null;
- return GetRoleBans<T>(session, prefix);
+ return GetRoleBans<T>(session);
}
- private HashSet<ProtoId<T>>? GetRoleBans<T>(ICommonSession playerSession, string prefix) where T : class, IPrototype
+ private HashSet<ProtoId<T>>? GetRoleBans<T>(ICommonSession playerSession) where T : class, IPrototype
{
if (!_cachedRoleBans.TryGetValue(playerSession, out var roleBans))
return null;
+ var dbType = PrototypeKindToDbType<T>();
+
return roleBans
- .Where(ban => ban.Role.StartsWith(prefix, StringComparison.Ordinal))
- .Select(ban => new ProtoId<T>(ban.Role[prefix.Length..]))
+ .SelectMany(ban => ban.Roles!.Value)
+ .Where(role => role.RoleType == dbType)
+ .Select(role => new ProtoId<T>(role.RoleId))
.ToHashSet();
}
- public HashSet<string>? GetRoleBans(NetUserId playerUserId)
+ public HashSet<BanRoleDef>? GetRoleBans(NetUserId playerUserId)
{
if (!_playerManager.TryGetSessionById(playerUserId, out var session))
return null;
return _cachedRoleBans.TryGetValue(session, out var roleBans)
- ? roleBans.Select(banDef => banDef.Role).ToHashSet()
+ ? roleBans.SelectMany(banDef => banDef.Roles ?? []).ToHashSet()
: null;
}
public bool IsRoleBanned(ICommonSession player, List<ProtoId<JobPrototype>> jobs)
{
- return IsRoleBanned(player, jobs, PrefixJob);
+ return IsRoleBanned<JobPrototype>(player, jobs);
}
public bool IsRoleBanned(ICommonSession player, List<ProtoId<AntagPrototype>> antags)
{
- return IsRoleBanned(player, antags, PrefixAntag);
+ return IsRoleBanned<AntagPrototype>(player, antags);
}
- private bool IsRoleBanned<T>(ICommonSession player, List<ProtoId<T>> roles, string prefix) where T : class, IPrototype
+ private bool IsRoleBanned<T>(ICommonSession player, List<ProtoId<T>> roles) where T : class, IPrototype
{
var bans = GetRoleBans(player.UserId);
if (bans is null || bans.Count == 0)
return false;
+ var dbType = PrototypeKindToDbType<T>();
+
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
foreach (var role in roles)
{
- if (bans.Contains(prefix + role))
+ if (bans.Contains(new BanRoleDef(dbType, role)))
return true;
}
public void SendRoleBans(ICommonSession pSession)
{
- var jobBans = GetRoleBans<JobPrototype>(pSession, PrefixJob);
- var jobBansList = new List<string>(jobBans?.Count ?? 0);
-
- if (jobBans is not null)
- {
- // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
- foreach (var encodedId in jobBans)
- {
- jobBansList.Add(encodedId.ToString().Replace(PrefixJob, ""));
- }
- }
-
- var antagBans = GetRoleBans<AntagPrototype>(pSession, PrefixAntag);
- var antagBansList = new List<string>(antagBans?.Count ?? 0);
-
- if (antagBans is not null)
- {
- // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
- foreach (var encodedId in antagBans)
- {
- antagBansList.Add(encodedId.ToString().Replace(PrefixAntag, ""));
- }
- }
-
var bans = new MsgRoleBans()
{
- JobBans = jobBansList,
- AntagBans = antagBansList,
+ JobBans = (GetRoleBans<JobPrototype>(pSession) ?? []).ToList(),
+ AntagBans = (GetRoleBans<AntagPrototype>(pSession) ?? []).ToList(),
};
_sawmill.Debug($"Sent role bans to {pSession.Name}");
using System.Net;
+using System.Net.Sockets;
using System.Threading.Tasks;
using Content.Shared.Database;
using Content.Shared.Roles;
public void Initialize();
public void Restart();
+ /// <summary>
+ /// Create a server ban in the database, blocking connection for matching players.
+ /// </summary>
+ void CreateServerBan(CreateServerBanInfo banInfo);
+
/// <summary>
/// Bans the specified target, address range and / or HWID. One of them must be non-null
/// </summary>
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
/// <param name="severity">Severity of the resulting ban note</param>
/// <param name="reason">Reason for the ban</param>
- public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason);
+ [Obsolete("Use CreateServerBan(CreateBanInfo) instead")]
+ public void CreateServerBan(NetUserId? target,
+ string? targetUsername,
+ NetUserId? banningAdmin,
+ (IPAddress, int)? addressRange,
+ ImmutableTypedHwid? hwid,
+ uint? minutes,
+ NoteSeverity severity,
+ string reason)
+ {
+ var info = new CreateServerBanInfo(reason);
+ if (target != null)
+ {
+ ArgumentNullException.ThrowIfNull(targetUsername);
+ info.AddUser(target.Value, targetUsername);
+ }
+
+ if (addressRange != null)
+ info.AddAddressRange(addressRange.Value);
+
+ if (hwid != null)
+ info.AddHWId(hwid);
+
+ if (minutes > 0)
+ info.WithMinutes(minutes.Value);
+
+ if (banningAdmin != null)
+ info.WithBanningAdmin(banningAdmin.Value);
+
+ info.WithSeverity(severity);
+
+ CreateServerBan(info);
+ }
/// <summary>
/// Gets a list of prefixed prototype IDs with the player's role bans.
/// </summary>
- public HashSet<string>? GetRoleBans(NetUserId playerUserId);
+ public HashSet<BanRoleDef>? GetRoleBans(NetUserId playerUserId);
/// <summary>
/// Checks if the player is currently banned from any of the listed roles.
public HashSet<ProtoId<AntagPrototype>>? GetAntagBans(NetUserId playerUserId);
/// <summary>
- /// Creates a job ban for the specified target, username or GUID
+ /// Creates a role ban, preventing matching players from playing said roles.
/// </summary>
- /// <param name="target">Target user, username or GUID, null for none</param>
- /// <param name="targetUsername">The username of the target, if known</param>
- /// <param name="banningAdmin">The responsible admin for the ban</param>
- /// <param name="addressRange">The range of IPs that are to be banned, if known</param>
- /// <param name="hwid">The HWID to be banned, if known</param>
- /// <param name="role">The role ID to be banned from. Either an AntagPrototype or a JobPrototype</param>
- /// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
- /// <param name="severity">Severity of the resulting ban note</param>
- /// <param name="reason">Reason for the ban</param>
- /// <param name="timeOfBan">Time when the ban was applied, used for grouping role bans</param>
- public void CreateRoleBan<T>(
- NetUserId? target,
- string? targetUsername,
- NetUserId? banningAdmin,
- (IPAddress, int)? addressRange,
- ImmutableTypedHwid? hwid,
- ProtoId<T> role,
- uint? minutes,
- NoteSeverity severity,
- string reason,
- DateTimeOffset timeOfBan
- ) where T : class, IPrototype;
+ public void CreateRoleBan(CreateRoleBanInfo banInfo);
/// <summary>
- /// Pardons a role ban for the specified target, username or GUID
+ /// Pardons a role ban by its ID.
/// </summary>
/// <param name="banId">The id of the role ban to pardon.</param>
/// <param name="unbanningAdmin">The admin, if any, that pardoned the role ban.</param>
/// <param name="pSession">Player's session</param>
public void SendRoleBans(ICommonSession pSession);
}
+
+/// <summary>
+/// Base info to fill out in created ban records.
+/// </summary>
+/// <seealso cref="CreateServerBanInfo"/>
+/// <seealso cref="CreateRoleBanInfo"/>
+[Access(typeof(BanManager), Other = AccessPermissions.Execute)]
+public abstract class CreateBanInfo
+{
+ [Access(Other = AccessPermissions.Read)]
+ public const int DefaultMaskIpv4 = 32;
+ [Access(Other = AccessPermissions.Read)]
+ public const int DefaultMaskIpv6 = 64;
+
+ internal readonly HashSet<(NetUserId UserId, string UserName)> Users = [];
+ internal readonly HashSet<(IPAddress Address, int Mask)> AddressRanges = [];
+ internal readonly HashSet<ImmutableTypedHwid> HWIds = [];
+ internal readonly HashSet<int> RoundIds = [];
+ internal TimeSpan? Duration;
+ internal NoteSeverity? Severity;
+ internal string Reason;
+ internal NetUserId? BanningAdmin;
+
+ protected CreateBanInfo(string reason)
+ {
+ Reason = reason;
+ }
+
+ /// <summary>
+ /// Add a user to be matched by the ban.
+ /// </summary>
+ /// <remarks>
+ /// Bans can target multiple users at once.
+ /// </remarks>
+ /// <param name="userId">The ID of the user.</param>
+ /// <param name="username">The name of the user (used for logging purposes).</param>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateBanInfo AddUser(NetUserId userId, string username)
+ {
+ Users.Add((userId, username));
+ return this;
+ }
+
+ /// <summary>
+ /// Add an IP address to be matched by the ban.
+ /// </summary>
+ /// <remarks>
+ /// Bans can target multiple addresses at once.
+ /// </remarks>
+ /// <param name="address">
+ /// The IP address to add. If null, nothing is done.
+ /// </param>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateBanInfo AddAddress(IPAddress? address)
+ {
+ if (address == null)
+ return this;
+
+ return AddAddressRange(
+ address,
+ address.AddressFamily == AddressFamily.InterNetwork ? DefaultMaskIpv4 : DefaultMaskIpv6);
+ }
+
+ /// <summary>
+ /// Add an IP address range to be matched by the ban.
+ /// </summary>
+ /// <remarks>
+ /// Bans can target multiple address ranges at once.
+ /// </remarks>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateBanInfo AddAddressRange((IPAddress Address, int Mask) addressRange)
+ {
+ return AddAddressRange(addressRange.Address, addressRange.Mask);
+ }
+
+ /// <summary>
+ /// Add an IP address range to be matched by the ban.
+ /// </summary>
+ /// <remarks>
+ /// Bans can target multiple address ranges at once.
+ /// </remarks>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateBanInfo AddAddressRange(IPAddress address, int mask)
+ {
+ AddressRanges.Add((address, mask));
+ return this;
+ }
+
+ /// <summary>
+ /// Add a hardware IP (HWID) to be matched by the ban.
+ /// </summary>
+ /// <remarks>
+ /// Bans can target multiple HWIDs at once.
+ /// </remarks>
+ /// <param name="hwId">
+ /// The HWID to add. If null, nothing is done.
+ /// </param>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateBanInfo AddHWId(ImmutableTypedHwid? hwId)
+ {
+ if (hwId != null)
+ HWIds.Add(hwId);
+
+ return this;
+ }
+
+ /// <summary>
+ /// Add a relevant round ID to this ban.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// If not specified, the current round ID is used for the ban.
+ /// Therefore, the first call to this function will <i>replace</i> the round ID,
+ /// and further calls will add additional round IDs.
+ /// </para>
+ /// <para>
+ /// Bans can target multiple round IDs at once.
+ /// </para>
+ /// </remarks>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateBanInfo AddRoundId(int roundId)
+ {
+ RoundIds.Add(roundId);
+ return this;
+ }
+
+ /// <summary>
+ /// Set how long the ban will last, in minutes.
+ /// </summary>
+ /// <remarks>
+ /// If no duration is specified, the ban is permanent.
+ /// </remarks>
+ /// <param name="minutes">The duration of the ban, in minutes.</param>
+ /// <returns>The current object, for easy chaining.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Thrown if <see cref="minutes"/> is not a positive number.
+ /// </exception>
+ public CreateBanInfo WithMinutes(int minutes)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minutes);
+ return WithMinutes((uint)minutes);
+ }
+
+ /// <summary>
+ /// Set how long the ban will last, in minutes.
+ /// </summary>
+ /// <remarks>
+ /// If no duration is specified, the ban is permanent.
+ /// </remarks>
+ /// <param name="minutes">The duration of the ban, in minutes.</param>
+ /// <returns>The current object, for easy chaining.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Thrown if <see cref="minutes"/> is not a positive number.
+ /// </exception>
+ public CreateBanInfo WithMinutes(uint minutes)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minutes);
+ return WithDuration(TimeSpan.FromMinutes(minutes));
+ }
+
+ /// <summary>
+ /// Set how long the ban will last.
+ /// </summary>
+ /// <remarks>
+ /// If no duration is specified, the ban is permanent.
+ /// </remarks>
+ /// <param name="duration">The duration of the ban.</param>
+ /// <returns>The current object, for easy chaining.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Thrown if <see cref="duration"/> is not a positive amount of time.
+ /// </exception>
+ public CreateBanInfo WithDuration(TimeSpan duration)
+ {
+ if (duration <= TimeSpan.Zero)
+ throw new ArgumentOutOfRangeException(nameof(duration), "Duration must be greater than zero.");
+
+ Duration = duration;
+ return this;
+ }
+
+ /// <summary>
+ /// Set the severity of the ban.
+ /// </summary>
+ /// <remarks>
+ /// If no severity is specified, the default is specified through server configuration.
+ /// </remarks>
+ /// <param name="severity"></param>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateBanInfo WithSeverity(NoteSeverity severity)
+ {
+ Severity = severity;
+ return this;
+ }
+
+ /// <summary>
+ /// Set the reason for the ban.
+ /// </summary>
+ /// <remarks>
+ /// This replaces the value given via the object constructor.
+ /// </remarks>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateBanInfo WithReason(string reason)
+ {
+ Reason = reason;
+ return this;
+ }
+
+ /// <summary>
+ /// Specify the admin responsible for placing the ban.
+ /// </summary>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateBanInfo WithBanningAdmin(NetUserId? banningAdmin)
+ {
+ BanningAdmin = banningAdmin;
+ return this;
+ }
+}
+
+/// <summary>
+/// Stores info to create server ban records.
+/// </summary>
+/// <seealso cref="IBanManager.CreateServerBan(CreateServerBanInfo)"/>
+[Access(typeof(BanManager), Other = AccessPermissions.Execute)]
+public sealed class CreateServerBanInfo : CreateBanInfo
+{
+ /// <param name="reason">The reason for the server ban.</param>
+ public CreateServerBanInfo(string reason) : base(reason)
+ {
+ }
+}
+
+/// <summary>
+/// Stores info to create role ban records.
+/// </summary>
+/// <seealso cref="IBanManager.CreateRoleBan(CreateRoleBanInfo)"/>
+[Access(typeof(BanManager), Other = AccessPermissions.Execute)]
+public sealed class CreateRoleBanInfo : CreateBanInfo
+{
+ internal readonly HashSet<ProtoId<AntagPrototype>> AntagPrototypes = [];
+ internal readonly HashSet<ProtoId<JobPrototype>> JobPrototypes = [];
+
+ /// <param name="reason">The reason for the role ban.</param>
+ public CreateRoleBanInfo(string reason) : base(reason)
+ {
+ }
+
+ /// <summary>
+ /// Add an antag role that will be unavailable for banned players.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Bans can have multiple roles at once.
+ /// </para>
+ /// <para>
+ /// While not checked in this function, adding a ban with invalid role IDs will cause a
+ /// <see cref="UnknownPrototypeException"/> when actually creating the ban.
+ /// </para>
+ /// </remarks>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateRoleBanInfo AddAntag(ProtoId<AntagPrototype> protoId)
+ {
+ AntagPrototypes.Add(protoId);
+ return this;
+ }
+
+ /// <summary>
+ /// Add a job role that will be unavailable for banned players.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Bans can have multiple roles at once.
+ /// </para>
+ /// <para>
+ /// While not checked in this function, adding a ban with invalid role IDs will cause a
+ /// <see cref="UnknownPrototypeException"/> when actually creating the ban.
+ /// </para>
+ /// </remarks>
+ /// <returns>The current object, for easy chaining.</returns>
+ public CreateRoleBanInfo AddJob(ProtoId<JobPrototype> protoId)
+ {
+ JobPrototypes.Add(protoId);
+ return this;
+ }
+}
IoCManager.InjectDependencies(this);
}
- private Guid NotedPlayer { get; set; }
+ private NetUserId NotedPlayer { get; set; }
private string NotedPlayerName { get; set; } = string.Empty;
private bool HasConnectedBefore { get; set; }
private Dictionary<(int, NoteType), SharedAdminNote> Notes { get; set; } = new();
}
}
- public async Task ChangeNotedPlayer(Guid notedPlayer)
+ public async Task ChangeNotedPlayer(NetUserId notedPlayer)
{
NotedPlayer = notedPlayer;
await LoadFromDb();
private void NoteModified(SharedAdminNote note)
{
- if (note.Player != NotedPlayer)
+ if (!note.Players.Contains(NotedPlayer))
return;
Notes[(note.Id, note.NoteType)] = note;
private void NoteDeleted(SharedAdminNote note)
{
- if (note.Player != NotedPlayer)
+ if (!note.Players.Contains(NotedPlayer))
return;
Notes.Remove((note.Id, note.NoteType));
+using System.Collections.Immutable;
+using System.Linq;
using Content.Server.Database;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
NoteSeverity? severity = null;
var secret = false;
NoteType type;
- string[]? bannedRoles = null;
+ ImmutableArray<BanRoleDef>? bannedRoles = null;
string? unbannedByName = null;
DateTime? unbannedTime = null;
bool? seen = null;
type = NoteType.Message;
seen = adminMessage.Seen;
break;
- case ServerBanNoteRecord ban:
+ case BanNoteRecord { Type: BanType.Server } ban:
type = NoteType.ServerBan;
severity = ban.Severity;
unbannedTime = ban.UnbanTime;
unbannedByName = ban.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user");
break;
- case ServerRoleBanNoteRecord roleBan:
+ case BanNoteRecord { Type: BanType.Role } roleBan:
type = NoteType.RoleBan;
severity = roleBan.Severity;
bannedRoles = roleBan.Roles;
}
// There may be bans without a user, but why would we ever be converting them to shared notes?
- if (note.Player is null)
- throw new ArgumentNullException(nameof(note), "Player user ID cannot be null for a note");
+ if (note.Players.Length == 0)
+ throw new ArgumentNullException(nameof(note), "Player user ID cannot be empty for a note");
return new SharedAdminNote(
note.Id,
- note.Player!.UserId,
- note.Round?.Id,
- note.Round?.Server.Name,
+ [..note.Players.Select(p => p.UserId)],
+ [..note.Rounds.Select(r => r.Id)],
+ note.Rounds.SingleOrDefault()?.Server.Name, // TODO: Show all server names?
note.PlaytimeAtNote,
type,
note.Message,
return _admins.HasAdminFlag(admin, AdminFlags.ViewNotes);
}
- public async Task OpenEui(ICommonSession admin, Guid notedPlayer)
+ public async Task OpenEui(ICommonSession admin, NetUserId notedPlayer)
{
var ui = new AdminNotesEui();
_euis.OpenEui(ui, admin);
var note = new SharedAdminNote(
noteId,
- (NetUserId) player,
- roundId,
+ [(NetUserId) player],
+ roundId.HasValue ? [roundId.Value] : [],
serverName,
playtime,
type,
NoteType.Note => (await _db.GetAdminNote(id))?.ToShared(),
NoteType.Watchlist => (await _db.GetAdminWatchlist(id))?.ToShared(),
NoteType.Message => (await _db.GetAdminMessage(id))?.ToShared(),
- NoteType.ServerBan => (await _db.GetServerBanAsNoteAsync(id))?.ToShared(),
- NoteType.RoleBan => (await _db.GetServerRoleBanAsNoteAsync(id))?.ToShared(),
+ NoteType.ServerBan or NoteType.RoleBan => (await _db.GetBanAsNoteAsync(id))?.ToShared(),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type")
};
}
case NoteType.Message:
await _db.DeleteAdminMessage(noteId, deletedBy.UserId, deletedAt);
break;
- case NoteType.ServerBan:
- await _db.HideServerBanFromNotes(noteId, deletedBy.UserId, deletedAt);
- break;
- case NoteType.RoleBan:
- await _db.HideServerRoleBanFromNotes(noteId, deletedBy.UserId, deletedAt);
+ case NoteType.ServerBan or NoteType.RoleBan:
+ await _db.HideBanFromNotes(noteId, deletedBy.UserId, deletedAt);
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
case NoteType.Message:
await _db.EditAdminMessage(noteId, message, editedBy.UserId, editedAt, expiryTime);
break;
- case NoteType.ServerBan:
+ case NoteType.ServerBan or NoteType.RoleBan:
if (severity is null)
throw new ArgumentException("Severity cannot be null for a ban", nameof(severity));
- await _db.EditServerBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
- break;
- case NoteType.RoleBan:
- if (severity is null)
- throw new ArgumentException("Severity cannot be null for a role ban", nameof(severity));
- await _db.EditServerRoleBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
+ await _db.EditBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
using Content.Server.Database;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
+using Robust.Shared.Network;
using Robust.Shared.Player;
namespace Content.Server.Administration.Notes;
bool CanDelete(ICommonSession admin);
bool CanEdit(ICommonSession admin);
bool CanView(ICommonSession admin);
- Task OpenEui(ICommonSession admin, Guid notedPlayer);
+ Task OpenEui(ICommonSession admin, NetUserId notedPlayer);
Task OpenUserNotesEui(ICommonSession player);
Task AddAdminRemark(ICommonSession createdBy, Guid player, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime);
Task DeleteAdminRemark(int noteId, NoteType type, ICommonSession deletedBy);
{
_whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId);
// This won't get associated ip or hwid bans but they were not placed on this account anyways
- _bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null, null)).Count;
- // Unfortunately role bans for departments and stuff are issued individually. This means that a single role ban can have many individual role bans internally
- // The only way to distinguish whether a role ban is the same is to compare the ban time.
- // This is horrible and I would love to just erase the database and start from scratch instead but that's what I can do for now.
- _roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null, null)).DistinctBy(rb => rb.BanTime).Count();
+ _bans = (await _db.GetBansAsync(null, _targetPlayer.UserId, null, null)).Count;
+ _roleBans = (await _db.GetBansAsync(null, _targetPlayer.UserId, null, null, type: BanType.Role)).Count();
}
else
{
}
// Check if the user has been banned
- var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null, null);
+ var ban = await _dbManager.GetBanAsync(null, e.Session.UserId, null, null);
if (ban != null)
{
var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason));
* TODO: Jesus H Christ what is this utter mess of a function
* TODO: Break this apart into is constituent steps.
*/
- private async Task<(ConnectionDenyReason, string, List<ServerBanDef>? bansHit)?> ShouldDeny(
+ private async Task<(ConnectionDenyReason, string, List<BanDef>? bansHit)?> ShouldDeny(
NetConnectingArgs e)
{
// Check if banned.
return (ConnectionDenyReason.NoHwid, Loc.GetString("hwid-required"), null);
}
- var bans = await _db.GetServerBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false);
+ var bans = await _db.GetBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false);
if (bans.Count > 0)
{
var firstBan = bans[0];
--- /dev/null
+using System.Collections.Immutable;
+using System.Linq;
+using System.Net;
+using Content.Shared.CCVar;
+using Content.Shared.Database;
+using Robust.Shared.Configuration;
+using Robust.Shared.Network;
+
+
+namespace Content.Server.Database
+{
+ public sealed class BanDef
+ {
+ public int? Id { get; }
+ public BanType Type { get; }
+ public ImmutableArray<NetUserId> UserIds { get; }
+ public ImmutableArray<(IPAddress address, int cidrMask)> Addresses { get; }
+ public ImmutableArray<ImmutableTypedHwid> HWIds { get; }
+
+ public DateTimeOffset BanTime { get; }
+ public DateTimeOffset? ExpirationTime { get; }
+ public ImmutableArray<int> RoundIds { get; }
+ public TimeSpan PlaytimeAtNote { get; }
+ public string Reason { get; }
+ public NoteSeverity Severity { get; set; }
+ public NetUserId? BanningAdmin { get; }
+ public UnbanDef? Unban { get; }
+ public ServerBanExemptFlags ExemptFlags { get; }
+
+ public ImmutableArray<BanRoleDef>? Roles { get; }
+
+ public BanDef(
+ int? id,
+ BanType type,
+ ImmutableArray<NetUserId> userIds,
+ ImmutableArray<(IPAddress address, int cidrMask)> addresses,
+ ImmutableArray<ImmutableTypedHwid> hwIds,
+ DateTimeOffset banTime,
+ DateTimeOffset? expirationTime,
+ ImmutableArray<int> roundIds,
+ TimeSpan playtimeAtNote,
+ string reason,
+ NoteSeverity severity,
+ NetUserId? banningAdmin,
+ UnbanDef? unban,
+ ServerBanExemptFlags exemptFlags = default,
+ ImmutableArray<BanRoleDef>? roles = null)
+ {
+ if (userIds.Length == 0 && addresses.Length == 0 && hwIds.Length == 0)
+ {
+ throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID");
+ }
+
+ addresses = addresses.Select(address =>
+ {
+ if (address is { address.IsIPv4MappedToIPv6: true } addr)
+ {
+ // Fix IPv6-mapped IPv4 addresses
+ // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
+ address = (addr.address.MapToIPv4(), addr.cidrMask - 96);
+ }
+
+ return address;
+ })
+ .ToImmutableArray();
+
+ Id = id;
+ Type = type;
+ UserIds = userIds;
+ Addresses = addresses;
+ HWIds = hwIds;
+ BanTime = banTime;
+ ExpirationTime = expirationTime;
+ RoundIds = roundIds;
+ PlaytimeAtNote = playtimeAtNote;
+ Reason = reason;
+ Severity = severity;
+ BanningAdmin = banningAdmin;
+ Unban = unban;
+ ExemptFlags = exemptFlags;
+
+ switch (Type)
+ {
+ case BanType.Server:
+ if (roles != null)
+ throw new ArgumentException("Cannot specify roles for server ban types", nameof(roles));
+ break;
+
+ case BanType.Role:
+ if (roles is not { Length: > 0 })
+ throw new ArgumentException("Must specify roles for server ban types", nameof(roles));
+ if (exemptFlags != 0)
+ throw new ArgumentException("Role bans cannot have exempt flags", nameof(exemptFlags));
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(type));
+ }
+
+ Roles = roles;
+ }
+
+ public string FormatBanMessage(IConfigurationManager cfg, ILocalizationManager loc)
+ {
+ string expires;
+ if (ExpirationTime is { } expireTime)
+ {
+ var duration = expireTime - BanTime;
+ var utc = expireTime.ToUniversalTime();
+ expires = loc.GetString("ban-expires", ("duration", duration.TotalMinutes.ToString("N0")), ("time", utc.ToString("f")));
+ }
+ else
+ {
+ var appeal = cfg.GetCVar(CCVars.InfoLinksAppeal);
+ expires = !string.IsNullOrWhiteSpace(appeal)
+ ? loc.GetString("ban-banned-permanent-appeal", ("link", appeal))
+ : loc.GetString("ban-banned-permanent");
+ }
+
+ return $"""
+ {loc.GetString("ban-banned-1")}
+ {loc.GetString("ban-banned-2", ("reason", Reason))}
+ {expires}
+ {loc.GetString("ban-banned-3")}
+ """;
+ }
+ }
+}
using System.Collections.Immutable;
+using System.Linq;
using System.Net;
using Content.Server.IP;
using Content.Shared.Database;
namespace Content.Server.Database;
/// <summary>
-/// Implements logic to match a <see cref="ServerBanDef"/> against a player query.
+/// Implements logic to match a <see cref="BanDef"/> against a player query.
/// </summary>
/// <remarks>
/// <para>
/// <param name="ban">The ban information.</param>
/// <param name="player">Information about the player to match against.</param>
/// <returns>True if the ban matches the provided player info.</returns>
- public static bool BanMatches(ServerBanDef ban, in PlayerInfo player)
+ public static bool BanMatches(BanDef ban, in PlayerInfo player)
{
var exemptFlags = player.ExemptFlags;
// Any flag to bypass BlacklistedRange bans.
if ((ban.ExemptFlags & exemptFlags) != 0)
return false;
+ var playerAddr = player.Address;
if (!player.ExemptFlags.HasFlag(ServerBanExemptFlags.IP)
- && player.Address != null
- && ban.Address is not null
- && player.Address.IsInSubnet(ban.Address.Value)
+ && playerAddr != null
+ && ban.Addresses.Any(addr => playerAddr.IsInSubnet(addr))
&& (!ban.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) || player.IsNewPlayer))
{
return true;
}
- if (player.UserId is { } id && ban.UserId == id.UserId)
+ if (player.UserId is { } id && ban.UserIds.Contains(id))
{
return true;
}
- switch (ban.HWId?.Type)
+ foreach (var banHwid in ban.HWIds)
{
- case HwidType.Legacy:
- if (player.HWId is { Length: > 0 } hwIdVar
- && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan()))
- {
- return true;
- }
- break;
- case HwidType.Modern:
- if (player.ModernHWIds is { Length: > 0 } modernHwIdVar)
- {
- foreach (var hwid in modernHwIdVar)
+ switch (banHwid.Type)
+ {
+ case HwidType.Legacy:
+ if (player.HWId is { Length: > 0 } hwIdVar
+ && hwIdVar.AsSpan().SequenceEqual(banHwid.Hwid.AsSpan()))
{
- if (hwid.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan()))
- return true;
+ return true;
}
- }
- break;
+
+ break;
+ case HwidType.Modern:
+ if (player.ModernHWIds is { Length: > 0 } modernHwIdVar)
+ {
+ foreach (var hwid in modernHwIdVar)
+ {
+ if (hwid.AsSpan().SequenceEqual(banHwid.Hwid.AsSpan()))
+ return true;
+ }
+ }
+
+ break;
+ }
}
return false;
+using System.Collections.Immutable;
using System.Net;
using Content.Shared.Database;
using Robust.Shared.Network;
{
public int Id { get; }
- public RoundRecord? Round { get; }
+ public ImmutableArray<RoundRecord> Rounds { get; }
- public PlayerRecord? Player { get; }
+ public ImmutableArray<PlayerRecord> Players { get; }
public TimeSpan PlaytimeAtNote { get; }
public string Message { get; }
public bool Deleted { get; }
}
-public sealed record ServerRoleBanNoteRecord(
+public sealed record BanNoteRecord(
int Id,
- RoundRecord? Round,
- PlayerRecord? Player,
+ BanType Type,
+ ImmutableArray<RoundRecord> Rounds,
+ ImmutableArray<PlayerRecord> Players,
TimeSpan PlaytimeAtNote,
string Message,
NoteSeverity Severity,
DateTimeOffset? LastEditedAt,
DateTimeOffset? ExpirationTime,
bool Deleted,
- string[] Roles,
PlayerRecord? UnbanningAdmin,
- DateTime? UnbanTime) : IAdminRemarksRecord;
-
-public sealed record ServerBanNoteRecord(
- int Id,
- RoundRecord? Round,
- PlayerRecord? Player,
- TimeSpan PlaytimeAtNote,
- string Message,
- NoteSeverity Severity,
- PlayerRecord? CreatedBy,
- DateTimeOffset CreatedAt,
- PlayerRecord? LastEditedBy,
- DateTimeOffset? LastEditedAt,
- DateTimeOffset? ExpirationTime,
- bool Deleted,
- PlayerRecord? UnbanningAdmin,
- DateTime? UnbanTime) : IAdminRemarksRecord;
+ DateTime? UnbanTime,
+ ImmutableArray<BanRoleDef> Roles) : IAdminRemarksRecord;
public sealed record AdminNoteRecord(
int Id,
bool Deleted,
PlayerRecord? DeletedBy,
DateTimeOffset? DeletedAt,
- bool Secret) : IAdminRemarksRecord;
+ bool Secret) : IAdminRemarksRecord
+{
+ ImmutableArray<RoundRecord> IAdminRemarksRecord.Rounds => Round != null ? [Round] : [];
+ ImmutableArray<PlayerRecord> IAdminRemarksRecord.Players => Player != null ? [Player] : [];
+}
public sealed record AdminWatchlistRecord(
int Id,
DateTimeOffset? ExpirationTime,
bool Deleted,
PlayerRecord? DeletedBy,
- DateTimeOffset? DeletedAt) : IAdminRemarksRecord;
+ DateTimeOffset? DeletedAt) : IAdminRemarksRecord
+{
+ ImmutableArray<RoundRecord> IAdminRemarksRecord.Rounds => Round != null ? [Round] : [];
+ ImmutableArray<PlayerRecord> IAdminRemarksRecord.Players => Player != null ? [Player] : [];
+}
public sealed record AdminMessageRecord(
int Id,
PlayerRecord? DeletedBy,
DateTimeOffset? DeletedAt,
bool Seen,
- bool Dismissed) : IAdminRemarksRecord;
-
+ bool Dismissed) : IAdminRemarksRecord
+{
+ ImmutableArray<RoundRecord> IAdminRemarksRecord.Rounds => Round != null ? [Round] : [];
+ ImmutableArray<PlayerRecord> IAdminRemarksRecord.Players => Player != null ? [Player] : [];
+}
public sealed record PlayerRecord(
NetUserId UserId,
DateTimeOffset FirstSeenTime,
string LastSeenUserName,
DateTimeOffset LastSeenTime,
- IPAddress LastSeenAddress,
+ IPAddress? LastSeenAddress,
ImmutableTypedHwid? HWId);
public sealed record RoundRecord(int Id, DateTimeOffset? StartDate, ServerRecord Server);
--- /dev/null
+using System.Linq;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore;
+
+namespace Content.Server.Database;
+
+internal static class EFCoreExtensions
+{
+ extension<TEntity>(IQueryable<TEntity> query) where TEntity : class
+ {
+ public IQueryable<TEntity> ApplyIncludes(
+ IEnumerable<Expression<Func<TEntity, object>>> properties)
+ {
+ var q = query;
+ foreach (var property in properties)
+ {
+ q = q.Include(property);
+ }
+
+ return q;
+ }
+
+ public IQueryable<TEntity> ApplyIncludes<TDerived>(
+ IEnumerable<Expression<Func<TDerived, object>>> properties,
+ Expression<Func<TEntity, TDerived>> getDerived)
+ where TDerived : class
+ {
+ var q = query;
+ foreach (var property in properties)
+ {
+ q = q.Include(getDerived).ThenInclude(property);
+ }
+
+ return q;
+ }
+ }
+}
+++ /dev/null
-using System.Net;
-using Content.Shared.CCVar;
-using Content.Shared.Database;
-using Robust.Shared.Configuration;
-using Robust.Shared.Network;
-
-
-namespace Content.Server.Database
-{
- public sealed class ServerBanDef
- {
- public int? Id { get; }
- public NetUserId? UserId { get; }
- public (IPAddress address, int cidrMask)? Address { get; }
- public ImmutableTypedHwid? HWId { get; }
-
- public DateTimeOffset BanTime { get; }
- public DateTimeOffset? ExpirationTime { get; }
- public int? RoundId { get; }
- public TimeSpan PlaytimeAtNote { get; }
- public string Reason { get; }
- public NoteSeverity Severity { get; set; }
- public NetUserId? BanningAdmin { get; }
- public ServerUnbanDef? Unban { get; }
- public ServerBanExemptFlags ExemptFlags { get; }
-
- public ServerBanDef(int? id,
- NetUserId? userId,
- (IPAddress, int)? address,
- TypedHwid? hwId,
- DateTimeOffset banTime,
- DateTimeOffset? expirationTime,
- int? roundId,
- TimeSpan playtimeAtNote,
- string reason,
- NoteSeverity severity,
- NetUserId? banningAdmin,
- ServerUnbanDef? unban,
- ServerBanExemptFlags exemptFlags = default)
- {
- if (userId == null && address == null && hwId == null)
- {
- throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID");
- }
-
- if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6)
- {
- // Fix IPv6-mapped IPv4 addresses
- // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
- address = (addr.Item1.MapToIPv4(), addr.Item2 - 96);
- }
-
- Id = id;
- UserId = userId;
- Address = address;
- HWId = hwId;
- BanTime = banTime;
- ExpirationTime = expirationTime;
- RoundId = roundId;
- PlaytimeAtNote = playtimeAtNote;
- Reason = reason;
- Severity = severity;
- BanningAdmin = banningAdmin;
- Unban = unban;
- ExemptFlags = exemptFlags;
- }
-
- public string FormatBanMessage(IConfigurationManager cfg, ILocalizationManager loc)
- {
- string expires;
- if (ExpirationTime is { } expireTime)
- {
- var duration = expireTime - BanTime;
- var utc = expireTime.ToUniversalTime();
- expires = loc.GetString("ban-expires", ("duration", duration.TotalMinutes.ToString("N0")), ("time", utc.ToString("f")));
- }
- else
- {
- var appeal = cfg.GetCVar(CCVars.InfoLinksAppeal);
- expires = !string.IsNullOrWhiteSpace(appeal)
- ? loc.GetString("ban-banned-permanent-appeal", ("link", appeal))
- : loc.GetString("ban-banned-permanent");
- }
-
- return $"""
- {loc.GetString("ban-banned-1")}
- {loc.GetString("ban-banned-2", ("reason", Reason))}
- {expires}
- {loc.GetString("ban-banned-3")}
- """;
- }
- }
-}
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Linq.Expressions;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Administration.Logs;
-using Content.Server.Administration.Managers;
using Content.Shared.Administration.Logs;
using Content.Shared.Body;
using Content.Shared.Construction.Prototypes;
/// </summary>
/// <param name="id">The ban id to look for.</param>
/// <returns>The ban with the given id or null if none exist.</returns>
- public abstract Task<ServerBanDef?> GetServerBanAsync(int id);
+ public abstract Task<BanDef?> GetBanAsync(int id);
/// <summary>
/// Looks up an user's most recent received un-pardoned ban.
/// <param name="hwId">The legacy HWId of the user.</param>
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
/// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns>
- public abstract Task<ServerBanDef?> GetServerBanAsync(
+ public abstract Task<BanDef?> GetBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds);
+ ImmutableArray<ImmutableArray<byte>>? modernHWIds,
+ BanType type);
/// <summary>
/// Looks up an user's ban history.
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
/// <param name="includeUnbanned">Include pardoned and expired bans.</param>
/// <returns>The user's ban history.</returns>
- public abstract Task<List<ServerBanDef>> GetServerBansAsync(
+ public abstract Task<List<BanDef>> GetBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned);
+ bool includeUnbanned,
+ BanType type);
- public abstract Task AddServerBanAsync(ServerBanDef serverBan);
- public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban);
+ public abstract Task<BanDef> AddBanAsync(BanDef ban);
+ public abstract Task AddUnbanAsync(UnbanDef unban);
- public async Task EditServerBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
+ public async Task EditBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
{
await using var db = await GetDb();
return flags ?? ServerBanExemptFlags.None;
}
- #endregion
-
- #region Role Bans
- /*
- * ROLE BANS
- */
- /// <summary>
- /// Looks up a role ban by id.
- /// This will return a pardoned role ban as well.
- /// </summary>
- /// <param name="id">The role ban id to look for.</param>
- /// <returns>The role ban with the given id or null if none exist.</returns>
- public abstract Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id);
-
- /// <summary>
- /// Looks up an user's role ban history.
- /// This will return pardoned role bans based on the <see cref="includeUnbanned"/> bool.
- /// Requires one of <see cref="address"/>, <see cref="userId"/>, or <see cref="hwId"/> to not be null.
- /// </summary>
- /// <param name="address">The IP address of the user.</param>
- /// <param name="userId">The NetUserId of the user.</param>
- /// <param name="hwId">The Hardware Id of the user.</param>
- /// <param name="modernHWIds">The modern HWIDs of the user.</param>
- /// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
- /// <returns>The user's role ban history.</returns>
- public abstract Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
- NetUserId? userId,
- ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned);
-
- public abstract Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan);
- public abstract Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban);
-
- public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
+ protected static List<Expression<Func<Ban, object>>> GetBanDefIncludes(BanType? type = null)
{
- await using var db = await GetDb();
- var roleBanDetails = await db.DbContext.RoleBan
- .Where(b => b.Id == id)
- .Select(b => new { b.BanTime, b.PlayerUserId })
- .SingleOrDefaultAsync();
+ List<Expression<Func<Ban, object>>> list =
+ [
+ b => b.Players!,
+ b => b.Rounds!,
+ b => b.Hwids!,
+ b => b.Unban!,
+ b => b.Addresses!,
+ ];
- if (roleBanDetails == default)
- return;
+ if (type != BanType.Server)
+ list.Add(b => b.Roles!);
- await db.DbContext.RoleBan
- .Where(b => b.BanTime == roleBanDetails.BanTime && b.PlayerUserId == roleBanDetails.PlayerUserId)
- .ExecuteUpdateAsync(setters => setters
- .SetProperty(b => b.Severity, severity)
- .SetProperty(b => b.Reason, reason)
- .SetProperty(b => b.ExpirationTime, expiration.HasValue ? expiration.Value.UtcDateTime : (DateTime?)null)
- .SetProperty(b => b.LastEditedById, editedBy)
- .SetProperty(b => b.LastEditedAt, editedAt.UtcDateTime)
- );
+ return list;
}
+
#endregion
#region Playtime
if (player == null)
return null;
+ return MakePlayerRecord(player.UserId, player);
+ }
+
+ protected PlayerRecord MakePlayerRecord(Guid userId, Player? player)
+ {
+ if (player == null)
+ {
+ // We don't have a record for this player in the database.
+ // This is possible, for example, when banning people that never connected to the server.
+ // Just return fallback data here, I guess.
+ return new PlayerRecord(new NetUserId(userId), default, userId.ToString(), default, null, null);
+ }
+
return new PlayerRecord(
new NetUserId(player.UserId),
new DateTimeOffset(NormalizeDatabaseTime(player.FirstSeenTime)),
ConnectionDenyReason? denied,
int serverId);
- public async Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans)
+ public async Task AddServerBanHitsAsync(int connection, IEnumerable<BanDef> bans)
{
await using var db = await GetDb();
entity.Dismissed);
}
- public async Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
+ public async Task<BanNoteRecord?> GetBanAsNoteAsync(int id)
{
await using var db = await GetDb();
- var ban = await db.DbContext.Ban
- .Include(ban => ban.Unban)
- .Include(ban => ban.Round)
- .ThenInclude(r => r!.Server)
- .Include(ban => ban.CreatedBy)
- .Include(ban => ban.LastEditedBy)
- .Include(ban => ban.Unban)
+ var ban = await BanRecordQuery(db.DbContext)
.SingleOrDefaultAsync(b => b.Id == id);
if (ban is null)
return null;
- var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId);
- return new ServerBanNoteRecord(
- ban.Id,
- MakeRoundRecord(ban.Round),
- MakePlayerRecord(player),
- ban.PlaytimeAtNote,
- ban.Reason,
- ban.Severity,
- MakePlayerRecord(ban.CreatedBy),
- ban.BanTime,
- MakePlayerRecord(ban.LastEditedBy),
- ban.LastEditedAt,
- ban.ExpirationTime,
- ban.Hidden,
- MakePlayerRecord(ban.Unban?.UnbanningAdmin == null
- ? null
- : await db.DbContext.Player.SingleOrDefaultAsync(p =>
- p.UserId == ban.Unban.UnbanningAdmin.Value)),
- ban.Unban?.UnbanTime);
- }
-
- public async Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id)
- {
- await using var db = await GetDb();
-
- var ban = await db.DbContext.RoleBan
- .Include(ban => ban.Unban)
- .Include(ban => ban.Round)
- .ThenInclude(r => r!.Server)
- .Include(ban => ban.CreatedBy)
- .Include(ban => ban.LastEditedBy)
- .Include(ban => ban.Unban)
- .SingleOrDefaultAsync(b => b.Id == id);
-
- if (ban is null)
- return null;
-
- var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId);
- var unbanningAdmin =
- ban.Unban is null
- ? null
- : await db.DbContext.Player.SingleOrDefaultAsync(b => b.UserId == ban.Unban.UnbanningAdmin);
-
- return new ServerRoleBanNoteRecord(
- ban.Id,
- MakeRoundRecord(ban.Round),
- MakePlayerRecord(player),
- ban.PlaytimeAtNote,
- ban.Reason,
- ban.Severity,
- MakePlayerRecord(ban.CreatedBy),
- ban.BanTime,
- MakePlayerRecord(ban.LastEditedBy),
- ban.LastEditedAt,
- ban.ExpirationTime,
- ban.Hidden,
- new [] { ban.RoleId.Replace(BanManager.PrefixJob, null).Replace(BanManager.PrefixAntag, null) },
- MakePlayerRecord(unbanningAdmin),
- ban.Unban?.UnbanTime);
+ return await MakeBanNoteRecord(db.DbContext, ban);
}
public async Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
.ToListAsync()).Select(MakeAdminNoteRecord));
notes.AddRange(await GetActiveWatchlistsImpl(db, player));
notes.AddRange(await GetMessagesImpl(db, player));
- notes.AddRange(await GetServerBansAsNotesForUser(db, player));
- notes.AddRange(await GetGroupedServerRoleBansAsNotesForUser(db, player));
+ notes.AddRange(await GetBansAsNotesForUser(db, player));
return notes;
}
public async Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
await db.DbContext.SaveChangesAsync();
}
- public async Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
+ public async Task HideBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
{
await using var db = await GetDb();
await db.DbContext.SaveChangesAsync();
}
- public async Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
- {
- await using var db = await GetDb();
-
- var roleBan = await db.DbContext.RoleBan.Where(roleBan => roleBan.Id == id).SingleAsync();
-
- roleBan.Hidden = true;
- roleBan.LastEditedById = deletedBy;
- roleBan.LastEditedAt = deletedAt.UtcDateTime;
-
- await db.DbContext.SaveChangesAsync();
- }
-
public async Task<List<IAdminRemarksRecord>> GetVisibleAdminRemarks(Guid player)
{
await using var db = await GetDb();
.Include(note => note.Player)
.ToListAsync()).Select(MakeAdminNoteRecord));
notesCol.AddRange(await GetMessagesImpl(db, player));
- notesCol.AddRange(await GetServerBansAsNotesForUser(db, player));
- notesCol.AddRange(await GetGroupedServerRoleBansAsNotesForUser(db, player));
+ notesCol.AddRange(await GetBansAsNotesForUser(db, player));
return notesCol;
}
await db.DbContext.SaveChangesAsync();
}
- // These two are here because they get converted into notes later
- protected async Task<List<ServerBanNoteRecord>> GetServerBansAsNotesForUser(DbGuard db, Guid user)
+ private static IQueryable<Ban> BanRecordQuery(ServerDbContext dbContext)
{
- // You can't group queries, as player will not always exist. When it doesn't, the
- // whole query returns nothing
- var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user);
- var bans = await db.DbContext.Ban
- .Where(ban => ban.PlayerUserId == user && !ban.Hidden)
+ return dbContext.Ban
.Include(ban => ban.Unban)
- .Include(ban => ban.Round)
+ .Include(ban => ban.Rounds!)
+ .ThenInclude(r => r.Round)
.ThenInclude(r => r!.Server)
+ .Include(ban => ban.Addresses)
+ .Include(ban => ban.Players)
+ .Include(ban => ban.Roles)
+ .Include(ban => ban.Hwids)
.Include(ban => ban.CreatedBy)
.Include(ban => ban.LastEditedBy)
- .Include(ban => ban.Unban)
- .ToArrayAsync();
-
- var banNotes = new List<ServerBanNoteRecord>();
- foreach (var ban in bans)
- {
- var banNote = new ServerBanNoteRecord(
- ban.Id,
- MakeRoundRecord(ban.Round),
- MakePlayerRecord(player),
- ban.PlaytimeAtNote,
- ban.Reason,
- ban.Severity,
- MakePlayerRecord(ban.CreatedBy),
- NormalizeDatabaseTime(ban.BanTime),
- MakePlayerRecord(ban.LastEditedBy),
- NormalizeDatabaseTime(ban.LastEditedAt),
- NormalizeDatabaseTime(ban.ExpirationTime),
- ban.Hidden,
- MakePlayerRecord(ban.Unban?.UnbanningAdmin == null
- ? null
- : await db.DbContext.Player.SingleOrDefaultAsync(
- p => p.UserId == ban.Unban.UnbanningAdmin.Value)),
- NormalizeDatabaseTime(ban.Unban?.UnbanTime));
+ .Include(ban => ban.Unban);
+ }
- banNotes.Add(banNote);
- }
+ private async Task<BanNoteRecord> MakeBanNoteRecord(ServerDbContext dbContext, Ban ban)
+ {
+ var playerRecords = await AsyncSelect(ban.Players,
+ async bp => MakePlayerRecord(bp.UserId,
+ await dbContext.Player.SingleOrDefaultAsync(p => p.UserId == bp.UserId)));
- return banNotes;
+ return new BanNoteRecord(
+ ban.Id,
+ ban.Type,
+ [..ban.Rounds!.Select(br => MakeRoundRecord(br.Round!))],
+ [..playerRecords],
+ ban.PlaytimeAtNote,
+ ban.Reason,
+ ban.Severity,
+ MakePlayerRecord(ban.CreatedBy!),
+ NormalizeDatabaseTime(ban.BanTime),
+ MakePlayerRecord(ban.LastEditedBy!),
+ NormalizeDatabaseTime(ban.LastEditedAt),
+ NormalizeDatabaseTime(ban.ExpirationTime),
+ ban.Hidden,
+ ban.Unban?.UnbanningAdmin == null
+ ? null
+ : MakePlayerRecord(
+ ban.Unban.UnbanningAdmin.Value,
+ await dbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.Unban.UnbanningAdmin.Value)),
+ NormalizeDatabaseTime(ban.Unban?.UnbanTime),
+ [..ban.Roles!.Select(br => new BanRoleDef(br.RoleType, br.RoleId))]);
}
- protected async Task<List<ServerRoleBanNoteRecord>> GetGroupedServerRoleBansAsNotesForUser(DbGuard db, Guid user)
+ // These two are here because they get converted into notes later
+ protected async Task<List<BanNoteRecord>> GetBansAsNotesForUser(DbGuard db, Guid user)
{
- // Server side query
- var bansQuery = await db.DbContext.RoleBan
- .Where(ban => ban.PlayerUserId == user && !ban.Hidden)
- .Include(ban => ban.Unban)
- .Include(ban => ban.Round)
- .ThenInclude(r => r!.Server)
- .Include(ban => ban.CreatedBy)
- .Include(ban => ban.LastEditedBy)
- .Include(ban => ban.Unban)
+ // You can't group queries, as player will not always exist. When it doesn't, the
+ // whole query returns nothing
+ var bans = await BanRecordQuery(db.DbContext)
+ .AsSplitQuery()
+ .Where(ban => ban.Players!.Any(bp => bp.UserId == user) && !ban.Hidden)
.ToArrayAsync();
- // Client side query, as EF can't do groups yet
- var bansEnumerable = bansQuery
- .GroupBy(ban => new { ban.BanTime, CreatedBy = (Player?)ban.CreatedBy, ban.Reason, Unbanned = ban.Unban == null })
- .Select(banGroup => banGroup)
- .ToArray();
-
- List<ServerRoleBanNoteRecord> bans = new();
- var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user);
- foreach (var banGroup in bansEnumerable)
+ var banNotes = new List<BanNoteRecord>();
+ foreach (var ban in bans)
{
- var firstBan = banGroup.First();
- Player? unbanningAdmin = null;
-
- if (firstBan.Unban?.UnbanningAdmin is not null)
- unbanningAdmin = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == firstBan.Unban.UnbanningAdmin.Value);
-
- bans.Add(new ServerRoleBanNoteRecord(
- firstBan.Id,
- MakeRoundRecord(firstBan.Round),
- MakePlayerRecord(player),
- firstBan.PlaytimeAtNote,
- firstBan.Reason,
- firstBan.Severity,
- MakePlayerRecord(firstBan.CreatedBy),
- NormalizeDatabaseTime(firstBan.BanTime),
- MakePlayerRecord(firstBan.LastEditedBy),
- NormalizeDatabaseTime(firstBan.LastEditedAt),
- NormalizeDatabaseTime(firstBan.ExpirationTime),
- firstBan.Hidden,
- banGroup.Select(ban => ban.RoleId.Replace(BanManager.PrefixJob, null).Replace(BanManager.PrefixAntag, null)).ToArray(),
- MakePlayerRecord(unbanningAdmin),
- NormalizeDatabaseTime(firstBan.Unban?.UnbanTime)));
+ var banNote = await MakeBanNoteRecord(db.DbContext, ban);
+
+ banNotes.Add(banNote);
}
- return bans;
+ return banNotes;
}
#endregion
{
}
+
+ private static async Task<IEnumerable<TResult>> AsyncSelect<T, TResult>(
+ IEnumerable<T>? enumerable,
+ Func<T, Task<TResult>> selector)
+ {
+ var results = new List<TResult>();
+
+ foreach (var item in enumerable ?? [])
+ {
+ results.Add(await selector(item));
+ }
+
+ return [..results];
+ }
}
}
/// </summary>
/// <param name="id">The ban id to look for.</param>
/// <returns>The ban with the given id or null if none exist.</returns>
- Task<ServerBanDef?> GetServerBanAsync(int id);
+ Task<BanDef?> GetBanAsync(int id);
/// <summary>
/// Looks up an user's most recent received un-pardoned ban.
/// <param name="hwId">The legacy HWID of the user.</param>
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
/// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns>
- Task<ServerBanDef?> GetServerBanAsync(
+ Task<BanDef?> GetBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds);
+ ImmutableArray<ImmutableArray<byte>>? modernHWIds,
+ BanType type = BanType.Server);
/// <summary>
/// Looks up an user's ban history.
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
/// <param name="includeUnbanned">If true, bans that have been expired or pardoned are also included.</param>
/// <returns>The user's ban history.</returns>
- Task<List<ServerBanDef>> GetServerBansAsync(
+ Task<List<BanDef>> GetBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned=true);
+ bool includeUnbanned=true,
+ BanType type = BanType.Server);
- Task AddServerBanAsync(ServerBanDef serverBan);
- Task AddServerUnbanAsync(ServerUnbanDef serverBan);
+ Task<BanDef> AddBanAsync(BanDef ban);
+ Task AddUnbanAsync(UnbanDef ban);
- public Task EditServerBan(
+ public Task EditBan(
int id,
string reason,
NoteSeverity severity,
#endregion
- #region Role Bans
- /// <summary>
- /// Looks up a role ban by id.
- /// This will return a pardoned role ban as well.
- /// </summary>
- /// <param name="id">The role ban id to look for.</param>
- /// <returns>The role ban with the given id or null if none exist.</returns>
- Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id);
-
- /// <summary>
- /// Looks up an user's role ban history.
- /// This will return pardoned role bans based on the <see cref="includeUnbanned"/> bool.
- /// Requires one of <see cref="address"/>, <see cref="userId"/>, or <see cref="hwId"/> to not be null.
- /// </summary>
- /// <param name="address">The IP address of the user.</param>
- /// <param name="userId">The NetUserId of the user.</param>
- /// <param name="hwId">The Hardware Id of the user.</param>
- /// <param name="modernHWIds">The modern HWIDs of the user.</param>
- /// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
- /// <returns>The user's role ban history.</returns>
- Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
- IPAddress? address,
- NetUserId? userId,
- ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned = true);
-
- Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverBan);
- Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverBan);
-
- public Task EditServerRoleBan(
- int id,
- string reason,
- NoteSeverity severity,
- DateTimeOffset? expiration,
- Guid editedBy,
- DateTimeOffset editedAt);
- #endregion
-
#region Playtime
/// <summary>
ConnectionDenyReason? denied,
int serverId);
- Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans);
+ Task AddServerBanHitsAsync(int connection, IEnumerable<BanDef> bans);
#endregion
Task<AdminNoteRecord?> GetAdminNote(int id);
Task<AdminWatchlistRecord?> GetAdminWatchlist(int id);
Task<AdminMessageRecord?> GetAdminMessage(int id);
- Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id);
- Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id);
+ Task<BanNoteRecord?> GetBanAsNoteAsync(int id);
Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player);
Task<List<IAdminRemarksRecord>> GetVisibleAdminNotes(Guid player);
Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player);
Task DeleteAdminNote(int id, Guid deletedBy, DateTimeOffset deletedAt);
Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTimeOffset deletedAt);
Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt);
- Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
- Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
+ Task HideBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
/// <summary>
/// Mark an admin message as being seen by the target player.
return RunDbCommand(() => _db.GetAssignedUserIdAsync(name));
}
- public Task<ServerBanDef?> GetServerBanAsync(int id)
+ public Task<BanDef?> GetBanAsync(int id)
{
DbReadOpsMetric.Inc();
- return RunDbCommand(() => _db.GetServerBanAsync(id));
+ return RunDbCommand(() => _db.GetBanAsync(id));
}
- public Task<ServerBanDef?> GetServerBanAsync(
+ public Task<BanDef?> GetBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds)
+ ImmutableArray<ImmutableArray<byte>>? modernHWIds,
+ BanType type = BanType.Server)
{
DbReadOpsMetric.Inc();
- return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId, modernHWIds));
+ return RunDbCommand(() => _db.GetBanAsync(address, userId, hwId, modernHWIds, type));
}
- public Task<List<ServerBanDef>> GetServerBansAsync(
+ public Task<List<BanDef>> GetBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned=true)
+ bool includeUnbanned=true,
+ BanType type = BanType.Server)
{
DbReadOpsMetric.Inc();
- return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
+ return RunDbCommand(() => _db.GetBansAsync(address, userId, hwId, modernHWIds, includeUnbanned, type));
}
- public Task AddServerBanAsync(ServerBanDef serverBan)
+ public Task<BanDef> AddBanAsync(BanDef ban)
{
DbWriteOpsMetric.Inc();
- return RunDbCommand(() => _db.AddServerBanAsync(serverBan));
+ return RunDbCommand(() => _db.AddBanAsync(ban));
}
- public Task AddServerUnbanAsync(ServerUnbanDef serverUnban)
+ public Task AddUnbanAsync(UnbanDef unban)
{
DbWriteOpsMetric.Inc();
- return RunDbCommand(() => _db.AddServerUnbanAsync(serverUnban));
+ return RunDbCommand(() => _db.AddUnbanAsync(unban));
}
- public Task EditServerBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
+ public Task EditBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
{
DbWriteOpsMetric.Inc();
- return RunDbCommand(() => _db.EditServerBan(id, reason, severity, expiration, editedBy, editedAt));
+ return RunDbCommand(() => _db.EditBan(id, reason, severity, expiration, editedBy, editedAt));
}
public Task UpdateBanExemption(NetUserId userId, ServerBanExemptFlags flags)
return RunDbCommand(() => _db.GetBanExemption(userId, cancel));
}
- #region Role Ban
- public Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
- {
- DbReadOpsMetric.Inc();
- return RunDbCommand(() => _db.GetServerRoleBanAsync(id));
- }
-
- public Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
- IPAddress? address,
- NetUserId? userId,
- ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned = true)
- {
- DbReadOpsMetric.Inc();
- return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
- }
-
- public Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan)
- {
- DbWriteOpsMetric.Inc();
- return RunDbCommand(() => _db.AddServerRoleBanAsync(serverRoleBan));
- }
-
- public Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban)
- {
- DbWriteOpsMetric.Inc();
- return RunDbCommand(() => _db.AddServerRoleUnbanAsync(serverRoleUnban));
- }
-
- public Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
- {
- DbWriteOpsMetric.Inc();
- return RunDbCommand(() => _db.EditServerRoleBan(id, reason, severity, expiration, editedBy, editedAt));
- }
- #endregion
-
#region Playtime
public Task<List<PlayTime>> GetPlayTimes(Guid player, CancellationToken cancel)
return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, trust, denied, serverId));
}
- public Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans)
+ public Task AddServerBanHitsAsync(int connection, IEnumerable<BanDef> bans)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.AddServerBanHitsAsync(connection, bans));
return RunDbCommand(() => _db.GetAdminMessage(id));
}
- public Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
+ public Task<BanNoteRecord?> GetBanAsNoteAsync(int id)
{
DbReadOpsMetric.Inc();
- return RunDbCommand(() => _db.GetServerBanAsNoteAsync(id));
- }
-
- public Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id)
- {
- DbReadOpsMetric.Inc();
- return RunDbCommand(() => _db.GetServerRoleBanAsNoteAsync(id));
+ return RunDbCommand(() => _db.GetBanAsNoteAsync(id));
}
public Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
return RunDbCommand(() => _db.DeleteAdminMessage(id, deletedBy, deletedAt));
}
- public Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
- {
- DbWriteOpsMetric.Inc();
- return RunDbCommand(() => _db.HideServerBanFromNotes(id, deletedBy, deletedAt));
- }
-
- public Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
+ public Task HideBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
{
DbWriteOpsMetric.Inc();
- return RunDbCommand(() => _db.HideServerRoleBanFromNotes(id, deletedBy, deletedAt));
+ return RunDbCommand(() => _db.HideBanFromNotes(id, deletedBy, deletedAt));
}
public Task MarkMessageAsSeen(int id, bool dismissedToo)
}
#region Ban
- public override async Task<ServerBanDef?> GetServerBanAsync(int id)
+ public override async Task<BanDef?> GetBanAsync(int id)
{
await using var db = await GetDbImpl();
var query = db.PgDbContext.Ban
- .Include(p => p.Unban)
- .Where(p => p.Id == id);
+ .ApplyIncludes(GetBanDefIncludes())
+ .Where(p => p.Id == id)
+ .AsSplitQuery();
var ban = await query.SingleOrDefaultAsync();
return ConvertBan(ban);
}
- public override async Task<ServerBanDef?> GetServerBanAsync(
+ public override async Task<BanDef?> GetBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds)
+ ImmutableArray<ImmutableArray<byte>>? modernHWIds,
+ BanType type)
{
if (address == null && userId == null && hwId == null)
{
var exempt = await GetBanExemptionCore(db, userId);
var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value);
- var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned: false, exempt, newPlayer)
+ var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned: false, exempt, newPlayer, type)
.OrderByDescending(b => b.BanTime);
var ban = await query.FirstOrDefaultAsync();
return ConvertBan(ban);
}
- public override async Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address,
+ public override async Task<List<BanDef>> GetBansAsync(IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned)
+ bool includeUnbanned,
+ BanType type)
{
if (address == null && userId == null && hwId == null)
{
await using var db = await GetDbImpl();
- var exempt = await GetBanExemptionCore(db, userId);
+ var exempt = type == BanType.Role ? null : await GetBanExemptionCore(db, userId);
var newPlayer = !await db.PgDbContext.Player.AnyAsync(p => p.UserId == userId);
- var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned, exempt, newPlayer);
-
+ var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned, exempt, newPlayer, type);
var queryBans = await query.ToArrayAsync();
- var bans = new List<ServerBanDef>(queryBans.Length);
+ var bans = new List<BanDef>(queryBans.Length);
foreach (var ban in queryBans)
{
return bans;
}
- private static IQueryable<ServerBan> MakeBanLookupQuery(
+ // This has to return IDs instead of direct objects because otherwise all the includes are too complicated.
+ private static IQueryable<Ban> MakeBanLookupQuery(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
DbGuardImpl db,
bool includeUnbanned,
ServerBanExemptFlags? exemptFlags,
- bool newPlayer)
+ bool newPlayer,
+ BanType type)
{
DebugTools.Assert(!(address == null && userId == null && hwId == null));
- var query = MakeBanLookupQualityShared<ServerBan, ServerUnban>(
- userId,
- hwId,
- modernHWIds,
- db.PgDbContext.Ban);
-
- if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP))
- {
- var newQ = db.PgDbContext.Ban
- .Include(p => p.Unban)
- .Where(b => b.Address != null
- && EF.Functions.ContainsOrEqual(b.Address.Value, address)
- && !(b.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) && !newPlayer));
-
- query = query == null ? newQ : query.Union(newQ);
- }
-
- DebugTools.Assert(
- query != null,
- "At least one filter item (IP/UserID/HWID) must have been given to make query not null.");
-
- if (!includeUnbanned)
- {
- query = query.Where(p =>
- p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
- }
-
- if (exemptFlags is { } exempt)
- {
- if (exempt != ServerBanExemptFlags.None)
- exempt |= ServerBanExemptFlags.BlacklistedRange; // Any kind of exemption should bypass BlacklistedRange
-
- query = query.Where(b => (b.ExemptFlags & exempt) == 0);
- }
-
- return query.Distinct();
- }
-
- private static IQueryable<TBan>? MakeBanLookupQualityShared<TBan, TUnban>(
- NetUserId? userId,
- ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- DbSet<TBan> set)
- where TBan : class, IBanCommon<TUnban>
- where TUnban : class, IUnbanCommon
- {
- IQueryable<TBan>? query = null;
+ var selectorQueries = new List<IQueryable<IBanSelector>>();
if (userId is { } uid)
- {
- var newQ = set
- .Include(p => p.Unban)
- .Where(b => b.PlayerUserId == uid.UserId);
-
- query = query == null ? newQ : query.Union(newQ);
- }
+ selectorQueries.Add(db.DbContext.BanPlayer.Where(b => b.UserId == uid.UserId));
if (hwId != null && hwId.Value.Length > 0)
{
- var newQ = set
- .Include(p => p.Unban)
- .Where(b => b.HWId!.Type == HwidType.Legacy && b.HWId!.Hwid.SequenceEqual(hwId.Value.ToArray()));
-
- query = query == null ? newQ : query.Union(newQ);
+ selectorQueries.Add(db.DbContext.BanHwid.Where(bh =>
+ bh.HWId!.Type == HwidType.Legacy && bh.HWId!.Hwid.SequenceEqual(hwId.Value.ToArray())
+ ));
}
if (modernHWIds != null)
{
foreach (var modernHwid in modernHWIds)
{
- var newQ = set
- .Include(p => p.Unban)
- .Where(b => b.HWId!.Type == HwidType.Modern && b.HWId!.Hwid.SequenceEqual(modernHwid.ToArray()));
-
- query = query == null ? newQ : query.Union(newQ);
+ selectorQueries.Add(db.DbContext.BanHwid
+ .Where(b => b.HWId!.Type == HwidType.Modern
+ && b.HWId!.Hwid.SequenceEqual(modernHwid.ToArray())));
}
}
- return query;
- }
-
- private static ServerBanDef? ConvertBan(ServerBan? ban)
- {
- if (ban == null)
- {
- return null;
- }
-
- NetUserId? uid = null;
- if (ban.PlayerUserId is {} guid)
- {
- uid = new NetUserId(guid);
- }
-
- NetUserId? aUid = null;
- if (ban.BanningAdmin is {} aGuid)
+ if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None)
+ .HasFlag(ServerBanExemptFlags.IP))
{
- aUid = new NetUserId(aGuid);
+ selectorQueries.Add(db.PgDbContext.BanAddress
+ .Where(ba => EF.Functions.ContainsOrEqual(ba.Address, address)
+ && !(ba.Ban!.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) &&
+ !newPlayer)));
}
- var unbanDef = ConvertUnban(ban.Unban);
-
- return new ServerBanDef(
- ban.Id,
- uid,
- ban.Address.ToTuple(),
- ban.HWId,
- ban.BanTime,
- ban.ExpirationTime,
- ban.RoundId,
- ban.PlaytimeAtNote,
- ban.Reason,
- ban.Severity,
- aUid,
- unbanDef,
- ban.ExemptFlags);
- }
-
- private static ServerUnbanDef? ConvertUnban(ServerUnban? unban)
- {
- if (unban == null)
- {
- return null;
- }
-
- NetUserId? aUid = null;
- if (unban.UnbanningAdmin is {} aGuid)
- {
- aUid = new NetUserId(aGuid);
- }
-
- return new ServerUnbanDef(
- unban.Id,
- aUid,
- unban.UnbanTime);
- }
-
- public override async Task AddServerBanAsync(ServerBanDef serverBan)
- {
- await using var db = await GetDbImpl();
-
- db.PgDbContext.Ban.Add(new ServerBan
- {
- Address = serverBan.Address.ToNpgsqlInet(),
- HWId = serverBan.HWId,
- Reason = serverBan.Reason,
- Severity = serverBan.Severity,
- BanningAdmin = serverBan.BanningAdmin?.UserId,
- BanTime = serverBan.BanTime.UtcDateTime,
- ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
- RoundId = serverBan.RoundId,
- PlaytimeAtNote = serverBan.PlaytimeAtNote,
- PlayerUserId = serverBan.UserId?.UserId,
- ExemptFlags = serverBan.ExemptFlags
- });
-
- await db.PgDbContext.SaveChangesAsync();
- }
-
- public override async Task AddServerUnbanAsync(ServerUnbanDef serverUnban)
- {
- await using var db = await GetDbImpl();
-
- db.PgDbContext.Unban.Add(new ServerUnban
- {
- BanId = serverUnban.BanId,
- UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId,
- UnbanTime = serverUnban.UnbanTime.UtcDateTime
- });
-
- await db.PgDbContext.SaveChangesAsync();
- }
- #endregion
-
- #region Role Ban
- public override async Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
- {
- await using var db = await GetDbImpl();
-
- var query = db.PgDbContext.RoleBan
- .Include(p => p.Unban)
- .Where(p => p.Id == id);
-
- var ban = await query.SingleOrDefaultAsync();
-
- return ConvertRoleBan(ban);
-
- }
-
- public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
- NetUserId? userId,
- ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned)
- {
- if (address == null && userId == null && hwId == null)
- {
- throw new ArgumentException("Address, userId, and hwId cannot all be null");
- }
-
- await using var db = await GetDbImpl();
-
- var query = MakeRoleBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned)
- .OrderByDescending(b => b.BanTime);
+ DebugTools.Assert(
+ selectorQueries.Count > 0,
+ "At least one filter item (IP/UserID/HWID) must have been given to make query not null.");
- return await QueryRoleBans(query);
- }
+ var selectorQuery = selectorQueries
+ .Select(q => q.Select(sel => sel.BanId))
+ .Aggregate((selectors, queryable) => selectors.Union(queryable));
- private static async Task<List<ServerRoleBanDef>> QueryRoleBans(IQueryable<ServerRoleBan> query)
- {
- var queryRoleBans = await query.ToArrayAsync();
- var bans = new List<ServerRoleBanDef>(queryRoleBans.Length);
+ var banQuery = db.DbContext.Ban.Where(b => selectorQuery.Contains(b.Id));
- foreach (var ban in queryRoleBans)
+ if (!includeUnbanned)
{
- var banDef = ConvertRoleBan(ban);
-
- if (banDef != null)
- {
- bans.Add(banDef);
- }
+ banQuery = banQuery.Where(p =>
+ p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
}
- return bans;
- }
-
- private static IQueryable<ServerRoleBan> MakeRoleBanLookupQuery(
- IPAddress? address,
- NetUserId? userId,
- ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- DbGuardImpl db,
- bool includeUnbanned)
- {
- var query = MakeBanLookupQualityShared<ServerRoleBan, ServerRoleUnban>(
- userId,
- hwId,
- modernHWIds,
- db.PgDbContext.RoleBan);
-
- if (address != null)
+ if (exemptFlags is { } exempt)
{
- var newQ = db.PgDbContext.RoleBan
- .Include(p => p.Unban)
- .Where(b => b.Address != null && EF.Functions.ContainsOrEqual(b.Address.Value, address));
+ if (exempt != ServerBanExemptFlags.None)
+ exempt |= ServerBanExemptFlags.BlacklistedRange; // Any kind of exemption should bypass BlacklistedRange
- query = query == null ? newQ : query.Union(newQ);
+ banQuery = banQuery.Where(b => (b.ExemptFlags & exempt) == 0);
}
- if (!includeUnbanned)
- {
- query = query?.Where(p =>
- p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
- }
-
- query = query!.Distinct();
- return query;
+ return banQuery
+ .Where(b => b.Type == type)
+ .ApplyIncludes(GetBanDefIncludes(type))
+ .AsSplitQuery();
}
[return: NotNullIfNotNull(nameof(ban))]
- private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban)
+ private static BanDef? ConvertBan(Ban? ban)
{
if (ban == null)
{
return null;
}
- NetUserId? uid = null;
- if (ban.PlayerUserId is {} guid)
- {
- uid = new NetUserId(guid);
- }
-
NetUserId? aUid = null;
if (ban.BanningAdmin is {} aGuid)
{
aUid = new NetUserId(aGuid);
}
- var unbanDef = ConvertRoleUnban(ban.Unban);
+ var unbanDef = ConvertUnban(ban.Unban);
- return new ServerRoleBanDef(
+ ImmutableArray<BanRoleDef>? roles = null;
+ if (ban.Type == BanType.Role)
+ {
+ roles = [..ban.Roles!.Select(br => new BanRoleDef(br.RoleType, br.RoleId))];
+ }
+
+ return new BanDef(
ban.Id,
- uid,
- ban.Address.ToTuple(),
- ban.HWId,
+ ban.Type,
+ [..ban.Players!.Select(bp => new NetUserId(bp.UserId))],
+ [..ban.Addresses!.Select(ba => ba.Address.ToTuple())],
+ [..ban.Hwids!.Select(bh => bh.HWId)],
ban.BanTime,
ban.ExpirationTime,
- ban.RoundId,
+ [..ban.Rounds!.Select(r => r.RoundId)],
ban.PlaytimeAtNote,
ban.Reason,
ban.Severity,
aUid,
unbanDef,
- ban.RoleId);
+ ban.ExemptFlags,
+ roles);
}
- private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban)
+ private static UnbanDef? ConvertUnban(Unban? unban)
{
if (unban == null)
{
aUid = new NetUserId(aGuid);
}
- return new ServerRoleUnbanDef(
+ return new UnbanDef(
unban.Id,
aUid,
unban.UnbanTime);
}
- public override async Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan)
+ public override async Task<BanDef> AddBanAsync(BanDef ban)
{
await using var db = await GetDbImpl();
- var ban = new ServerRoleBan
- {
- Address = serverRoleBan.Address.ToNpgsqlInet(),
- HWId = serverRoleBan.HWId,
- Reason = serverRoleBan.Reason,
- Severity = serverRoleBan.Severity,
- BanningAdmin = serverRoleBan.BanningAdmin?.UserId,
- BanTime = serverRoleBan.BanTime.UtcDateTime,
- ExpirationTime = serverRoleBan.ExpirationTime?.UtcDateTime,
- RoundId = serverRoleBan.RoundId,
- PlaytimeAtNote = serverRoleBan.PlaytimeAtNote,
- PlayerUserId = serverRoleBan.UserId?.UserId,
- RoleId = serverRoleBan.Role,
+ var banEntity = new Ban
+ {
+ Type = ban.Type,
+ Addresses = [..ban.Addresses.Select(ba => new BanAddress { Address = ba.ToNpgsqlInet() })],
+ Hwids = [..ban.HWIds.Select(bh => new BanHwid { HWId = bh })],
+ Reason = ban.Reason,
+ Severity = ban.Severity,
+ BanningAdmin = ban.BanningAdmin?.UserId,
+ BanTime = ban.BanTime.UtcDateTime,
+ ExpirationTime = ban.ExpirationTime?.UtcDateTime,
+ Rounds = [..ban.RoundIds.Select(bri => new BanRound { RoundId = bri })],
+ PlaytimeAtNote = ban.PlaytimeAtNote,
+ Players = [..ban.UserIds.Select(bp => new BanPlayer { UserId = bp.UserId })],
+ ExemptFlags = ban.ExemptFlags,
+ Roles = ban.Roles == null
+ ? []
+ : ban.Roles.Value.Select(brd => new BanRole
+ {
+ RoleType = brd.RoleType,
+ RoleId = brd.RoleId
+ })
+ .ToList(),
};
- db.PgDbContext.RoleBan.Add(ban);
+ db.PgDbContext.Ban.Add(banEntity);
await db.PgDbContext.SaveChangesAsync();
- return ConvertRoleBan(ban);
+ return ConvertBan(banEntity);
}
- public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban)
+ public override async Task AddUnbanAsync(UnbanDef unban)
{
await using var db = await GetDbImpl();
- db.PgDbContext.RoleUnban.Add(new ServerRoleUnban
+ db.PgDbContext.Unban.Add(new Unban
{
- BanId = serverRoleUnban.BanId,
- UnbanningAdmin = serverRoleUnban.UnbanningAdmin?.UserId,
- UnbanTime = serverRoleUnban.UnbanTime.UtcDateTime
+ BanId = unban.BanId,
+ UnbanningAdmin = unban.UnbanningAdmin?.UserId,
+ UnbanTime = unban.UnbanTime.UtcDateTime
});
await db.PgDbContext.SaveChangesAsync();
}
#region Ban
- public override async Task<ServerBanDef?> GetServerBanAsync(int id)
+ public override async Task<BanDef?> GetBanAsync(int id)
{
await using var db = await GetDbImpl();
var ban = await db.SqliteDbContext.Ban
- .Include(p => p.Unban)
+ .ApplyIncludes(GetBanDefIncludes())
.Where(p => p.Id == id)
+ .AsSplitQuery()
.SingleOrDefaultAsync();
return ConvertBan(ban);
}
- public override async Task<ServerBanDef?> GetServerBanAsync(
+ public override async Task<BanDef?> GetBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds)
+ ImmutableArray<ImmutableArray<byte>>? modernHWIds,
+ BanType type)
{
await using var db = await GetDbImpl();
- return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned: false)).FirstOrDefault();
+ return (await GetBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned: false, type)).FirstOrDefault();
}
- public override async Task<List<ServerBanDef>> GetServerBansAsync(
+ public override async Task<List<BanDef>> GetBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned)
+ bool includeUnbanned,
+ BanType type)
{
await using var db = await GetDbImpl();
- return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned)).ToList();
+ return (await GetBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned, type)).ToList();
}
- private async Task<IEnumerable<ServerBanDef>> GetServerBanQueryAsync(
+ private async Task<IEnumerable<BanDef>> GetBanQueryAsync(
DbGuardImpl db,
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned)
+ bool includeUnbanned,
+ BanType type)
{
var exempt = await GetBanExemptionCore(db, userId);
// SQLite can't do the net masking stuff we need to match IP address ranges.
// So just pull down the whole list into memory.
- var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt);
+ var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt, type);
var playerInfo = new BanMatcher.PlayerInfo
{
.Where(b => BanMatcher.BanMatches(b!, playerInfo))!;
}
- private static async Task<List<ServerBan>> GetAllBans(
- SqliteServerDbContext db,
+ private static async Task<List<Ban>> GetAllBans(SqliteServerDbContext db,
bool includeUnbanned,
- ServerBanExemptFlags? exemptFlags)
+ ServerBanExemptFlags? exemptFlags,
+ BanType type)
{
- IQueryable<ServerBan> query = db.Ban.Include(p => p.Unban);
+ var query = db.Ban.Where(b => b.Type == type).ApplyIncludes(GetBanDefIncludes(type));
if (!includeUnbanned)
{
query = query.Where(p =>
query = query.Where(b => (b.ExemptFlags & exempt) == 0);
}
- return await query.ToListAsync();
- }
-
- public override async Task AddServerBanAsync(ServerBanDef serverBan)
- {
- await using var db = await GetDbImpl();
-
- db.SqliteDbContext.Ban.Add(new ServerBan
- {
- Address = serverBan.Address.ToNpgsqlInet(),
- Reason = serverBan.Reason,
- Severity = serverBan.Severity,
- BanningAdmin = serverBan.BanningAdmin?.UserId,
- HWId = serverBan.HWId,
- BanTime = serverBan.BanTime.UtcDateTime,
- ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
- RoundId = serverBan.RoundId,
- PlaytimeAtNote = serverBan.PlaytimeAtNote,
- PlayerUserId = serverBan.UserId?.UserId,
- ExemptFlags = serverBan.ExemptFlags
- });
-
- await db.SqliteDbContext.SaveChangesAsync();
+ return await query.AsSplitQuery().ToListAsync();
}
- public override async Task AddServerUnbanAsync(ServerUnbanDef serverUnban)
- {
- await using var db = await GetDbImpl();
-
- db.SqliteDbContext.Unban.Add(new ServerUnban
- {
- BanId = serverUnban.BanId,
- UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId,
- UnbanTime = serverUnban.UnbanTime.UtcDateTime
- });
-
- await db.SqliteDbContext.SaveChangesAsync();
- }
- #endregion
-
- #region Role Ban
- public override async Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
- {
- await using var db = await GetDbImpl();
-
- var ban = await db.SqliteDbContext.RoleBan
- .Include(p => p.Unban)
- .Where(p => p.Id == id)
- .SingleOrDefaultAsync();
-
- return ConvertRoleBan(ban);
- }
-
- public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
- IPAddress? address,
- NetUserId? userId,
- ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds,
- bool includeUnbanned)
+ public override async Task<BanDef> AddBanAsync(BanDef ban)
{
await using var db = await GetDbImpl();
- // SQLite can't do the net masking stuff we need to match IP address ranges.
- // So just pull down the whole list into memory.
- var queryBans = await GetAllRoleBans(db.SqliteDbContext, includeUnbanned);
-
- return queryBans
- .Where(b => RoleBanMatches(b, address, userId, hwId, modernHWIds))
- .Select(ConvertRoleBan)
- .ToList()!;
- }
-
- private static async Task<List<ServerRoleBan>> GetAllRoleBans(
- SqliteServerDbContext db,
- bool includeUnbanned)
- {
- IQueryable<ServerRoleBan> query = db.RoleBan.Include(p => p.Unban);
- if (!includeUnbanned)
- {
- query = query.Where(p =>
- p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
- }
-
- return await query.ToListAsync();
- }
-
- private static bool RoleBanMatches(
- ServerRoleBan ban,
- IPAddress? address,
- NetUserId? userId,
- ImmutableArray<byte>? hwId,
- ImmutableArray<ImmutableArray<byte>>? modernHWIds)
- {
- if (address != null && ban.Address is not null && address.IsInSubnet(ban.Address.ToTuple().Value))
- {
- return true;
- }
-
- if (userId is { } id && ban.PlayerUserId == id.UserId)
- {
- return true;
- }
-
- switch (ban.HWId?.Type)
- {
- case HwidType.Legacy:
- if (hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid))
- return true;
- break;
-
- case HwidType.Modern:
- if (modernHWIds != null)
- {
- foreach (var modernHWId in modernHWIds)
+ var banEntity = new Ban
+ {
+ Type = ban.Type,
+ Addresses = [..ban.Addresses.Select(ba => new BanAddress { Address = ba.ToNpgsqlInet() })],
+ Hwids = [..ban.HWIds.Select(bh => new BanHwid { HWId = bh })],
+ Reason = ban.Reason,
+ Severity = ban.Severity,
+ BanningAdmin = ban.BanningAdmin?.UserId,
+ BanTime = ban.BanTime.UtcDateTime,
+ ExpirationTime = ban.ExpirationTime?.UtcDateTime,
+ Rounds = [..ban.RoundIds.Select(bri => new BanRound { RoundId = bri })],
+ PlaytimeAtNote = ban.PlaytimeAtNote,
+ Players = [..ban.UserIds.Select(bp => new BanPlayer { UserId = bp.UserId })],
+ ExemptFlags = ban.ExemptFlags,
+ Roles = ban.Roles == null
+ ? []
+ : ban.Roles.Value.Select(brd => new BanRole
{
- if (modernHWId.AsSpan().SequenceEqual(ban.HWId.Hwid))
- return true;
- }
- }
-
- break;
- }
-
- return false;
- }
-
- public override async Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverBan)
- {
- await using var db = await GetDbImpl();
-
- var ban = new ServerRoleBan
- {
- Address = serverBan.Address.ToNpgsqlInet(),
- Reason = serverBan.Reason,
- Severity = serverBan.Severity,
- BanningAdmin = serverBan.BanningAdmin?.UserId,
- HWId = serverBan.HWId,
- BanTime = serverBan.BanTime.UtcDateTime,
- ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
- RoundId = serverBan.RoundId,
- PlaytimeAtNote = serverBan.PlaytimeAtNote,
- PlayerUserId = serverBan.UserId?.UserId,
- RoleId = serverBan.Role,
+ RoleType = brd.RoleType,
+ RoleId = brd.RoleId
+ })
+ .ToList(),
};
- db.SqliteDbContext.RoleBan.Add(ban);
+ db.SqliteDbContext.Ban.Add(banEntity);
await db.SqliteDbContext.SaveChangesAsync();
- return ConvertRoleBan(ban);
+ return ConvertBan(banEntity);
}
- public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverUnban)
+ public override async Task AddUnbanAsync(UnbanDef unban)
{
await using var db = await GetDbImpl();
- db.SqliteDbContext.RoleUnban.Add(new ServerRoleUnban
+ db.SqliteDbContext.Unban.Add(new Unban
{
- BanId = serverUnban.BanId,
- UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId,
- UnbanTime = serverUnban.UnbanTime.UtcDateTime
+ BanId = unban.BanId,
+ UnbanningAdmin = unban.UnbanningAdmin?.UserId,
+ UnbanTime = unban.UnbanTime.UtcDateTime
});
await db.SqliteDbContext.SaveChangesAsync();
}
+ #endregion
[return: NotNullIfNotNull(nameof(ban))]
- private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban)
+ private static BanDef? ConvertBan(Ban? ban)
{
if (ban == null)
{
return null;
}
- NetUserId? uid = null;
- if (ban.PlayerUserId is { } guid)
- {
- uid = new NetUserId(guid);
- }
-
NetUserId? aUid = null;
if (ban.BanningAdmin is { } aGuid)
{
aUid = new NetUserId(aGuid);
}
- var unban = ConvertRoleUnban(ban.Unban);
-
- return new ServerRoleBanDef(
- ban.Id,
- uid,
- ban.Address.ToTuple(),
- ban.HWId,
- // SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
- DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
- ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
- ban.RoundId,
- ban.PlaytimeAtNote,
- ban.Reason,
- ban.Severity,
- aUid,
- unban,
- ban.RoleId);
- }
-
- private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban)
- {
- if (unban == null)
- {
- return null;
- }
-
- NetUserId? aUid = null;
- if (unban.UnbanningAdmin is { } aGuid)
- {
- aUid = new NetUserId(aGuid);
- }
-
- return new ServerRoleUnbanDef(
- unban.Id,
- aUid,
- // SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
- DateTime.SpecifyKind(unban.UnbanTime, DateTimeKind.Utc));
- }
- #endregion
-
- [return: NotNullIfNotNull(nameof(ban))]
- private static ServerBanDef? ConvertBan(ServerBan? ban)
- {
- if (ban == null)
- {
- return null;
- }
-
- NetUserId? uid = null;
- if (ban.PlayerUserId is { } guid)
- {
- uid = new NetUserId(guid);
- }
+ var unban = ConvertUnban(ban.Unban);
- NetUserId? aUid = null;
- if (ban.BanningAdmin is { } aGuid)
+ ImmutableArray<BanRoleDef>? roles = null;
+ if (ban.Type == BanType.Role)
{
- aUid = new NetUserId(aGuid);
+ roles = [..ban.Roles!.Select(br => new BanRoleDef(br.RoleType, br.RoleId))];
}
- var unban = ConvertUnban(ban.Unban);
-
- return new ServerBanDef(
+ return new BanDef(
ban.Id,
- uid,
- ban.Address.ToTuple(),
- ban.HWId,
+ ban.Type,
+ [..ban.Players!.Select(bp => new NetUserId(bp.UserId))],
+ [..ban.Addresses!.Select(ba => ba.Address.ToTuple())],
+ [..ban.Hwids!.Select(bh => bh.HWId)],
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
- ban.RoundId,
+ [..ban.Rounds!.Select(r => r.RoundId)],
ban.PlaytimeAtNote,
ban.Reason,
ban.Severity,
aUid,
- unban);
+ unban,
+ ban.ExemptFlags,
+ roles);
}
- private static ServerUnbanDef? ConvertUnban(ServerUnban? unban)
+ private static UnbanDef? ConvertUnban(Unban? unban)
{
if (unban == null)
{
aUid = new NetUserId(aGuid);
}
- return new ServerUnbanDef(
+ return new UnbanDef(
unban.Id,
aUid,
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
+++ /dev/null
-using System.Net;
-using Content.Shared.Database;
-using Robust.Shared.Network;
-
-namespace Content.Server.Database;
-
-public sealed class ServerRoleBanDef
-{
- public int? Id { get; }
- public NetUserId? UserId { get; }
- public (IPAddress address, int cidrMask)? Address { get; }
- public ImmutableTypedHwid? HWId { get; }
-
- public DateTimeOffset BanTime { get; }
- public DateTimeOffset? ExpirationTime { get; }
- public int? RoundId { get; }
- public TimeSpan PlaytimeAtNote { get; }
- public string Reason { get; }
- public NoteSeverity Severity { get; set; }
- public NetUserId? BanningAdmin { get; }
- public ServerRoleUnbanDef? Unban { get; }
- public string Role { get; }
-
- public ServerRoleBanDef(
- int? id,
- NetUserId? userId,
- (IPAddress, int)? address,
- ImmutableTypedHwid? hwId,
- DateTimeOffset banTime,
- DateTimeOffset? expirationTime,
- int? roundId,
- TimeSpan playtimeAtNote,
- string reason,
- NoteSeverity severity,
- NetUserId? banningAdmin,
- ServerRoleUnbanDef? unban,
- string role)
- {
- if (userId == null && address == null && hwId == null)
- {
- throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID");
- }
-
- if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6)
- {
- // Fix IPv6-mapped IPv4 addresses
- // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
- address = (addr.Item1.MapToIPv4(), addr.Item2 - 96);
- }
-
- Id = id;
- UserId = userId;
- Address = address;
- HWId = hwId;
- BanTime = banTime;
- ExpirationTime = expirationTime;
- RoundId = roundId;
- PlaytimeAtNote = playtimeAtNote;
- Reason = reason;
- Severity = severity;
- BanningAdmin = banningAdmin;
- Unban = unban;
- Role = role;
- }
-}
+++ /dev/null
-using Robust.Shared.Network;
-
-namespace Content.Server.Database;
-
-public sealed class ServerRoleUnbanDef
-{
- public int BanId { get; }
-
- public NetUserId? UnbanningAdmin { get; }
-
- public DateTimeOffset UnbanTime { get; }
-
- public ServerRoleUnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
- {
- BanId = banId;
- UnbanningAdmin = unbanningAdmin;
- UnbanTime = unbanTime;
- }
-}
namespace Content.Server.Database
{
- public sealed class ServerUnbanDef
+ public sealed class UnbanDef
{
public int BanId { get; }
public DateTimeOffset UnbanTime { get; }
- public ServerUnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
+ public UnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
{
BanId = banId;
UnbanningAdmin = unbanningAdmin;
{
// Npgsql used to map inet types as a tuple like this.
// I'm upgrading the dependencies and I don't wanna rewrite a bunch of DB code, so a few helpers it shall be.
- [return: NotNullIfNotNull(nameof(tuple))]
- public static NpgsqlInet? ToNpgsqlInet(this (IPAddress, int)? tuple)
+ public static NpgsqlInet ToNpgsqlInet(this (IPAddress, int) tuple)
{
- if (tuple == null)
- return null;
-
- return new NpgsqlInet(tuple.Value.Item1, (byte) tuple.Value.Item2);
+ return new NpgsqlInet(tuple.Item1, (byte)tuple.Item2);
}
- [return: NotNullIfNotNull(nameof(inet))]
- public static (IPAddress, int)? ToTuple(this NpgsqlInet? inet)
+ public static (IPAddress, int) ToTuple(this NpgsqlInet inet)
{
- if (inet == null)
- return null;
-
- return (inet.Value.Address, inet.Value.Netmask);
+ return (inet.Address, inet.Netmask);
}
// Taken from https://stackoverflow.com/a/56461160/4678631
}
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
- (IPAddress, int)? targetIP = null;
-
- if (located.LastAddress is not null)
- {
- targetIP = located.LastAddress.AddressFamily is AddressFamily.InterNetwork
- ? (located.LastAddress, 32) // People with ipv4 addresses get a /32 address so we ban that
- : (located.LastAddress, 64); // This can only be an ipv6 address. People with ipv6 address should get /64 addresses so we ban that.
- }
+ var targetIP = located.LastAddress;
if (!_playerManager.TryGetSessionById(located.UserId, out ICommonSession? targetSession))
{
uint minutes = (uint)_cfg.GetCVar(CCVars.VotekickBanDuration);
- _bans.CreateServerBan(targetUid, target, null, targetIP, targetHWid, minutes, severity, Loc.GetString("votekick-ban-reason", ("reason", reason)));
+ var banInfo = new CreateServerBanInfo(Loc.GetString("votekick-ban-reason", ("reason", reason)));
+ banInfo.AddUser(targetUid, target);
+ banInfo.AddHWId(targetHWid);
+ banInfo.AddAddress(targetIP);
+ banInfo.WithSeverity(severity);
+ if (minutes > 0)
+ banInfo.WithMinutes(minutes);
+
+ _bans.CreateServerBan(banInfo);
}
}
else
--- /dev/null
+namespace Content.Shared.Database;
+
+/// <summary>
+/// Types of bans that can be stored in the database.
+/// </summary>
+public enum BanType : byte
+{
+ /// <summary>
+ /// A ban from the entire server. If a player matches the ban info, they will be refused connection.
+ /// </summary>
+ Server,
+
+ /// <summary>
+ /// A ban from playing one or more roles.
+ /// </summary>
+ Role,
+}
+
+/// <summary>
+/// A single role for a database role ban.
+/// </summary>
+/// <param name="RoleType">The type of role being banned, e.g. <c>Job</c>.</param>
+/// <param name="RoleId">
+/// The ID of the role being banned. This is likely a prototype ID based on <paramref name="RoleType"/>.
+/// </param>
+[Serializable]
+public record struct BanRoleDef(string RoleType, string RoleId)
+{
+ public override string ToString()
+ {
+ return $"{RoleType}:{RoleId}";
+ }
+}
[Serializable, NetSerializable]
public sealed class BanListEuiState : EuiStateBase
{
- public BanListEuiState(string banListPlayerName, List<SharedServerBan> bans, List<SharedServerRoleBan> roleBans)
+ public BanListEuiState(string banListPlayerName, List<SharedBan> bans, List<SharedBan> roleBans)
{
BanListPlayerName = banListPlayerName;
Bans = bans;
}
public string BanListPlayerName { get; }
- public List<SharedServerBan> Bans { get; }
- public List<SharedServerRoleBan> RoleBans { get; }
+ public List<SharedBan> Bans { get; }
+ public List<SharedBan> RoleBans { get; }
}
--- /dev/null
+using System.Collections.Immutable;
+using Content.Shared.Database;
+using Robust.Shared.Network;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Administration.BanList;
+
+[Serializable, NetSerializable]
+public record SharedBan(
+ int? Id,
+ BanType Type,
+ ImmutableArray<NetUserId> UserIds,
+ ImmutableArray<(string address, int cidrMask)> Addresses,
+ ImmutableArray<string> HWIds,
+ DateTime BanTime,
+ DateTime? ExpirationTime,
+ string Reason,
+ string? BanningAdminName,
+ SharedUnban? Unban,
+ ImmutableArray<BanRoleDef>? Roles);
+++ /dev/null
-using Robust.Shared.Network;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Administration.BanList;
-
-[Serializable, NetSerializable]
-public record SharedServerBan(
- int? Id,
- NetUserId? UserId,
- (string address, int cidrMask)? Address,
- string? HWId,
- DateTime BanTime,
- DateTime? ExpirationTime,
- string Reason,
- string? BanningAdminName,
- SharedServerUnban? Unban
-);
+++ /dev/null
-using Robust.Shared.Network;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Administration.BanList;
-
-[Serializable, NetSerializable]
-public sealed record SharedServerRoleBan(
- int? Id,
- NetUserId? UserId,
- (string address, int cidrMask)? Address,
- string? HWId,
- DateTime BanTime,
- DateTime? ExpirationTime,
- string Reason,
- string? BanningAdminName,
- SharedServerUnban? Unban,
- string Role
-) : SharedServerBan(Id, UserId, Address, HWId, BanTime, ExpirationTime, Reason, BanningAdminName, Unban);
namespace Content.Shared.Administration.BanList;
[Serializable, NetSerializable]
-public sealed record SharedServerUnban(
+public sealed record SharedUnban(
string? UnbanningAdmin,
DateTime UnbanTime
);
+using System.Collections.Immutable;
using Content.Shared.Database;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
[Serializable, NetSerializable]
public sealed record SharedAdminNote(
int Id, // Id of note, message, watchlist, ban or role ban. Should be paired with NoteType to uniquely identify a shared admin note.
- NetUserId Player, // Notes player
- int? Round, // Which round was it added in?
+ ImmutableArray<NetUserId> Players, // Notes player
+ ImmutableArray<int> Rounds, // Which round was it added in?
string? ServerName, // Which server was this added on?
TimeSpan PlaytimeAtNote, // Playtime at the time of getting the note
NoteType NoteType, // Type of note
DateTime CreatedAt, // When was it created?
DateTime? LastEditedAt, // When was it last edited?
DateTime? ExpiryTime, // Does it expire?
- string[]? BannedRoles, // Only valid for role bans. List of banned roles
+ ImmutableArray<BanRoleDef>? BannedRoles, // Only valid for role bans. List of banned roles
DateTime? UnbannedTime, // Only valid for bans. Set if unbanned
string? UnbannedByName, // Only valid for bans. Set if unbanned
bool? Seen // Only valid for messages, otherwise should be null. Has the user seen this message?
+using Content.Shared.Roles;
using Lidgren.Network;
using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Players;
{
public override MsgGroups MsgGroup => MsgGroups.EntityEvent;
- public List<string> JobBans = new();
- public List<string> AntagBans = new();
+ public List<ProtoId<JobPrototype>> JobBans = new();
+ public List<ProtoId<AntagPrototype>> AntagBans = new();
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
cmd-roleban-arg-count = Invalid amount of arguments.
cmd-roleban-job-parse = Job {$job} does not exist.
cmd-roleban-name-parse = Unable to find a player with that name.
-cmd-roleban-existing = {$target} already has a role ban for {$role}.
cmd-roleban-success = Role banned {$target} from {$role} with reason {$reason} {$length}.
cmd-roleban-inf = permanently
- fuck
- replay_recording_start
- replay_recording_stop
+ - transfer_test
- Flags: QUERY
Commands:
import psycopg2
from uuid import UUID
-LATEST_DB_MIGRATION = "20250314222016_ConstructionFavorites"
+LATEST_DB_MIGRATION = "20260120200503_BanRefactor"
def main():
parser = argparse.ArgumentParser()
dump_player(cur, user_id, arg_output)
dump_preference(cur, user_id, arg_output)
dump_role_whitelists(cur, user_id, arg_output)
- dump_server_ban(cur, user_id, arg_output)
+ dump_ban(cur, user_id, arg_output)
dump_server_ban_exemption(cur, user_id, arg_output)
- dump_server_role_ban(cur, user_id, arg_output)
dump_uploaded_resource_log(cur, user_id, arg_output)
dump_whitelist(cur, user_id, arg_output)
f.write(json_data)
-def dump_server_ban(cur: "psycopg2.cursor", user_id: str, outdir: str):
+def dump_ban(cur: "psycopg2.cursor", user_id: str, outdir: str):
print("Dumping server_ban...")
cur.execute("""
SELECT
*,
(SELECT to_jsonb(unban_sq) - 'ban_id' FROM (
- SELECT * FROM server_unban WHERE server_unban.ban_id = server_ban.server_ban_id
+ SELECT * FROM unban WHERE unban.ban_id = ban.ban_id
) unban_sq)
- as unban
+ as unban,
+ (SELECT COALESCE(json_agg(to_jsonb(ban_player_subq) - 'ban_id'), '[]') FROM (
+ SELECT * FROM ban_player WHERE ban_player.ban_id = ban.ban_id
+ ) ban_player_subq)
+ as ban_player,
+ (SELECT COALESCE(json_agg(to_jsonb(ban_address_subq) - 'ban_id'), '[]') FROM (
+ SELECT * FROM ban_address WHERE ban_address.ban_id = ban.ban_id
+ ) ban_address_subq)
+ as ban_address,
+ (SELECT COALESCE(json_agg(to_jsonb(ban_role_subq) - 'ban_id'), '[]') FROM (
+ SELECT * FROM ban_role WHERE ban_role.ban_id = ban.ban_id
+ ) ban_role_subq)
+ as ban_role,
+ (SELECT COALESCE(json_agg(to_jsonb(ban_hwid_subq) - 'ban_id'), '[]') FROM (
+ SELECT * FROM ban_hwid WHERE ban_hwid.ban_id = ban.ban_id
+ ) ban_hwid_subq)
+ as ban_hwid,
+ (SELECT COALESCE(json_agg(to_jsonb(ban_round_subq) - 'ban_id'), '[]') FROM (
+ SELECT * FROM ban_round WHERE ban_round.ban_id = ban.ban_id
+ ) ban_round_subq)
+ as ban_round
FROM
- server_ban
+ ban
WHERE
- player_user_id = %s
+ ban_id IN (SELECT bp.ban_id FROM ban_player bp WHERE bp.user_id = %s)
) as data
""", (user_id,))
json_data = cur.fetchall()[0][0]
- with open(os.path.join(outdir, "server_ban.json"), "w", encoding="utf-8") as f:
+ with open(os.path.join(outdir, "ban.json"), "w", encoding="utf-8") as f:
f.write(json_data)
import psycopg2
from uuid import UUID
-LATEST_DB_MIGRATION = "20250314222016_ConstructionFavorites"
+LATEST_DB_MIGRATION = "20260120200503_BanRefactor"
def main():
parser = argparse.ArgumentParser()
clear_play_time(cur, user_id)
clear_player(cur, user_id)
clear_preference(cur, user_id)
- clear_server_ban(cur, user_id)
+ clear_ban(cur, user_id)
clear_server_ban_exemption(cur, user_id)
- clear_server_role_ban(cur, user_id)
clear_uploaded_resource_log(cur, user_id)
clear_whitelist(cur, user_id)
clear_blacklist(cur, user_id)
""", (user_id,))
-def clear_server_ban(cur: "psycopg2.cursor", user_id: str):
- print("Clearing server_ban...")
+def clear_ban(cur: "psycopg2.cursor", user_id: str):
+ print("Clearing ban...")
cur.execute("""
DELETE FROM
- server_ban
+ ban
WHERE
- player_user_id = %s
+ ban_id IN (SELECT bp.ban_id FROM ban_player bp WHERE bp.user_id = %s)
""", (user_id,))
""", (user_id,))
-def clear_server_role_ban(cur: "psycopg2.cursor", user_id: str):
- print("Clearing server_role_ban...")
-
- cur.execute("""
-DELETE FROM
- server_role_ban
-WHERE
- player_user_id = %s
-""", (user_id,))
-
-
def clear_uploaded_resource_log(cur: "psycopg2.cursor", user_id: str):
print("Clearing uploaded_resource_log...")