Stable to master (#42599)

Ban database refactor (#42495)

* Ban DB refactor seems to work at a basic level for PostgreSQL

* New ban creation API

Supports all the new functionality (multiple players/addresses/hwids/roles/rounds per ban).

* Make the migration irreversible

* Re-implement ban notifications

The server ID check is no longer done as admins may want to place bans spanning multiple rounds irrelevant of the source server.

* Fix some split query warnings

* Implement migration on SQLite

* More comments

* Remove required from ban reason

SS14.Admin changes would like this

* More missing AsSplitQuery() calls

* Fix missing ban type filter

* Fix old CreateServerBan API with permanent time

* Fix department and role ban commands with permanent time

* Re-add banhits navigation property

Dropped this on accident, SS14.Admin needs it.

* More ban API fixes.

* Don't fetch ban exemption info for role bans

Not relevant, reduces query performance

* Regenerate migrations

* Fix adminnotes command for players that never connected

Would blow up handling null player records. Not a new bug introduced by the refactor, but I ran into it.

* Great shame... I accidentally committed submodule update...

* Update GDPR scripts

* Fix sandbox violation

* Fix bans with duplicate info causing DB exceptions

Most notably happened with role bans, as multiple departments may include the same role.
This commit is contained in:
Pieter-Jan Briers
2026-01-23 15:34:23 +01:00
committed by GitHub
parent facd7da394
commit 29b7fc4463
65 changed files with 7712 additions and 2698 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,535 @@
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");
}
}
}

View File

@@ -519,6 +519,221 @@ namespace Content.Server.Database.Migrations.Postgres
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")
@@ -1069,95 +1284,6 @@ namespace Content.Server.Database.Migrations.Postgres
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")
@@ -1207,152 +1333,6 @@ namespace Content.Server.Database.Migrations.Postgres
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");
b.HasIndex("BanId")
.IsUnique();
b.ToTable("server_unban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
b.Property<int>("Id")
@@ -1380,6 +1360,36 @@ namespace Content.Server.Database.Migrations.Postgres
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")
@@ -1664,6 +1674,123 @@ namespace Content.Server.Database.Migrations.Postgres
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")
@@ -1820,70 +1947,14 @@ namespace Content.Server.Database.Migrations.Postgres
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")
@@ -1897,86 +1968,6 @@ namespace Content.Server.Database.Migrations.Postgres
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 =>
{
b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
.WithOne("Unban")
.HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
.WithOne("Unban")
.HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_unban_server_ban_ban_id");
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
@@ -1989,6 +1980,18 @@ namespace Content.Server.Database.Migrations.Postgres
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)
@@ -2023,6 +2026,23 @@ namespace Content.Server.Database.Migrations.Postgres
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");
@@ -2052,10 +2072,6 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("AdminServerBansLastEdited");
b.Navigation("AdminServerRoleBansCreated");
b.Navigation("AdminServerRoleBansLastEdited");
b.Navigation("AdminWatchlistsCreated");
b.Navigation("AdminWatchlistsDeleted");
@@ -2104,18 +2120,6 @@ namespace Content.Server.Database.Migrations.Postgres
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
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,498 @@
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");
}
}
}

View File

@@ -489,6 +489,207 @@ namespace Content.Server.Database.Migrations.Sqlite
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")
@@ -1010,91 +1211,6 @@ namespace Content.Server.Database.Migrations.Sqlite
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")
@@ -1142,144 +1258,6 @@ namespace Content.Server.Database.Migrations.Sqlite
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 =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("role_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_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");
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_server_unban");
b.HasIndex("BanId")
.IsUnique();
b.ToTable("server_unban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
b.Property<int>("Id")
@@ -1305,6 +1283,34 @@ namespace Content.Server.Database.Migrations.Sqlite
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")
@@ -1587,6 +1593,123 @@ namespace Content.Server.Database.Migrations.Sqlite
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")
@@ -1743,70 +1866,14 @@ namespace Content.Server.Database.Migrations.Sqlite
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")
@@ -1820,86 +1887,6 @@ namespace Content.Server.Database.Migrations.Sqlite
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 =>
{
b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
.WithOne("Unban")
.HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
.WithOne("Unban")
.HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_unban_server_ban_ban_id");
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
@@ -1912,6 +1899,18 @@ namespace Content.Server.Database.Migrations.Sqlite
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)
@@ -1946,6 +1945,23 @@ namespace Content.Server.Database.Migrations.Sqlite
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");
@@ -1975,10 +1991,6 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("AdminServerBansLastEdited");
b.Navigation("AdminServerRoleBansCreated");
b.Navigation("AdminServerRoleBansLastEdited");
b.Navigation("AdminWatchlistsCreated");
b.Navigation("AdminWatchlistsDeleted");
@@ -2027,18 +2039,6 @@ namespace Content.Server.Database.Migrations.Sqlite
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
}
}

View File

@@ -0,0 +1,328 @@
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 &lt; 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; }
}

View File

@@ -9,7 +9,6 @@ using System.Net;
using System.Text.Json;
using Content.Shared.Database;
using Microsoft.EntityFrameworkCore;
using NpgsqlTypes;
namespace Content.Server.Database
{
@@ -31,13 +30,17 @@ 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!;
@@ -145,43 +148,11 @@ namespace Content.Server.Database
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();
@@ -296,34 +267,6 @@ namespace Content.Server.Database
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)
@@ -342,26 +285,6 @@ namespace Content.Server.Database
.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)
@@ -371,6 +294,8 @@ namespace Content.Server.Database
.OwnsOne(p => p.HWId)
.Property(p => p.Type)
.HasDefaultValue(HwidType.Legacy);
ModelBan.OnModelCreating(modelBuilder);
}
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
@@ -591,10 +516,8 @@ namespace Content.Server.Database
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!;
}
@@ -724,30 +647,6 @@ namespace Content.Server.Database
[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>
@@ -785,138 +684,6 @@ namespace Content.Server.Database
// @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 &lt; 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>
@@ -937,7 +704,7 @@ namespace Content.Server.Database
/// <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; }
}
@@ -1000,54 +767,10 @@ namespace Content.Server.Database
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
{
@@ -1247,31 +970,31 @@ namespace Content.Server.Database
/// <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; }
}

View File

@@ -39,10 +39,7 @@ namespace Content.Server.Database
// 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 =>

View File

@@ -58,13 +58,7 @@ namespace Content.Server.Database
);
modelBuilder
.Entity<ServerBan>()
.Property(e => e.Address)
.HasColumnType("TEXT")
.HasConversion(ipMaskConverter);
modelBuilder
.Entity<ServerRoleBan>()
.Entity<BanAddress>()
.Property(e => e.Address)
.HasColumnType("TEXT")
.HasConversion(ipMaskConverter);