diff --git a/Content.Client/GameTicking/ClientGameTicker.cs b/Content.Client/GameTicking/ClientGameTicker.cs index be3e791a2a..9f7a41c67d 100644 --- a/Content.Client/GameTicking/ClientGameTicker.cs +++ b/Content.Client/GameTicking/ClientGameTicker.cs @@ -28,7 +28,7 @@ namespace Content.Client.GameTicking [ViewVariables] public string ServerInfoBlob { get; private set; } [ViewVariables] public DateTime StartTime { get; private set; } [ViewVariables] public bool Paused { get; private set; } - [ViewVariables] public Dictionary Status { get; private set; } + [ViewVariables] public Dictionary Status { get; private set; } public event Action InfoBlobUpdated; public event Action LobbyStatusUpdated; @@ -52,7 +52,7 @@ namespace Content.Client.GameTicking }); _netManager.RegisterNetMessage(nameof(MsgTickerLateJoinStatus), LateJoinStatus); - Status = new Dictionary(); + Status = new Dictionary(); _initialized = true; } diff --git a/Content.Client/Interfaces/IClientGameTicker.cs b/Content.Client/Interfaces/IClientGameTicker.cs index f7d2a994d9..d7e408d1b7 100644 --- a/Content.Client/Interfaces/IClientGameTicker.cs +++ b/Content.Client/Interfaces/IClientGameTicker.cs @@ -13,7 +13,7 @@ namespace Content.Client.Interfaces bool DisallowedLateJoin { get; } DateTime StartTime { get; } bool Paused { get; } - Dictionary Status { get; } + Dictionary Status { get; } void Initialize(); event Action InfoBlobUpdated; diff --git a/Content.Client/State/LobbyState.cs b/Content.Client/State/LobbyState.cs index 0851b8c945..b4c417ccc1 100644 --- a/Content.Client/State/LobbyState.cs +++ b/Content.Client/State/LobbyState.cs @@ -220,10 +220,10 @@ namespace Content.Client.State if (!_clientGameTicker.IsGameStarted) { var status = PlayerStatus.NotReady; - if (session.SessionId == _playerManager.LocalPlayer.SessionId) + if (session.UserId == _playerManager.LocalPlayer.UserId) status = _clientGameTicker.AreWeReady ? PlayerStatus.Ready : PlayerStatus.NotReady; else - _clientGameTicker.Status.TryGetValue(session.SessionId, out status); + _clientGameTicker.Status.TryGetValue(session.UserId, out status); readyState = status switch { diff --git a/Content.Client/UserInterface/HumanoidProfileEditor.Random.cs b/Content.Client/UserInterface/HumanoidProfileEditor.Random.cs index 2a563cc457..d823c05b90 100644 --- a/Content.Client/UserInterface/HumanoidProfileEditor.Random.cs +++ b/Content.Client/UserInterface/HumanoidProfileEditor.Random.cs @@ -1,8 +1,10 @@ -using System.Linq; +using System; +using System.Linq; using Content.Shared.Preferences; using Content.Shared.Preferences.Appearance; using Content.Shared.Text; using Robust.Shared.Interfaces.Random; +using Robust.Shared.Maths; using Robust.Shared.Random; namespace Content.Client.UserInterface @@ -51,9 +53,9 @@ namespace Content.Client.UserInterface var newHairColor = _random.Pick(HairStyles.RealisticHairColors); newHairColor = newHairColor - .WithRed(newHairColor.R + _random.Next(-25, 25) / 100f) - .WithGreen(newHairColor.G + _random.Next(-25, 25) / 100f) - .WithBlue(newHairColor.B + _random.Next(-25, 25) / 100f); + .WithRed(RandomizeColor(newHairColor.R)) + .WithGreen(RandomizeColor(newHairColor.G)) + .WithBlue(RandomizeColor(newHairColor.B)); Profile = Profile.WithCharacterAppearance( Profile.Appearance @@ -62,6 +64,11 @@ namespace Content.Client.UserInterface .WithHairColor(newHairColor) .WithFacialHairColor(newHairColor)); UpdateHairPickers(); + + float RandomizeColor(float channel) + { + return MathHelper.Clamp01(channel + _random.Next(-25, 25) / 100f); + } } } } diff --git a/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs b/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs index c9a8b982aa..f9c5e5b973 100644 --- a/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs +++ b/Content.IntegrationTests/Tests/MindEntityDeletionTest.cs @@ -37,7 +37,7 @@ namespace Content.IntegrationTests.Tests playerEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); visitEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); - mind = new Mind(player.SessionId); + mind = new Mind(player.UserId); player.ContentData().Mind = mind; mind.TransferTo(playerEnt); @@ -81,7 +81,7 @@ namespace Content.IntegrationTests.Tests playerEnt = entMgr.SpawnEntity(null, MapCoordinates.Nullspace); - mind = new Mind(player.SessionId); + mind = new Mind(player.UserId); player.ContentData().Mind = mind; mind.TransferTo(playerEnt); @@ -130,7 +130,7 @@ namespace Content.IntegrationTests.Tests playerEnt = entMgr.SpawnEntity(null, grid.ToCoordinates()); - mind = new Mind(player.SessionId); + mind = new Mind(player.UserId); player.ContentData().Mind = mind; mind.TransferTo(playerEnt); diff --git a/Content.Server.Database/Configuration.cs b/Content.Server.Database/Configuration.cs deleted file mode 100644 index 6db9a594d3..0000000000 --- a/Content.Server.Database/Configuration.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Npgsql; - -namespace Content.Server.Database -{ - public interface IDatabaseConfiguration - { - DbContextOptions Options { get; } - } - - public class PostgresConfiguration : IDatabaseConfiguration - { - private readonly string _database; - private readonly string _host; - private readonly string _password; - private readonly int _port; - private readonly string _username; - - public PostgresConfiguration(string host, - int port, - string database, - string username, - string password) - { - _host = host; - _port = port; - _database = database; - _username = username; - _password = password; - } - - public DbContextOptions Options - { - get - { - var optionsBuilder = new DbContextOptionsBuilder(); - var connectionString = new NpgsqlConnectionStringBuilder - { - Host = _host, - Port = _port, - Database = _database, - Username = _username, - Password = _password - }.ConnectionString; - optionsBuilder.UseNpgsql(connectionString); - return optionsBuilder.Options; - } - } - } - - public class SqliteConfiguration : IDatabaseConfiguration - { - private readonly string? _databaseFilePath; - - /// If null, an in-memory database is used. - public SqliteConfiguration(string? databaseFilePath) - { - _databaseFilePath = databaseFilePath; - } - - public DbContextOptions Options - { - get - { - var optionsBuilder = new DbContextOptionsBuilder(); - SqliteConnection connection; - if (_databaseFilePath != null) - { - connection = new SqliteConnection($"Data Source={_databaseFilePath}"); - } - else - { - connection = new SqliteConnection("Data Source=:memory:"); - // When using an in-memory DB we have to open it manually - // so EFCore doesn't open, close and wipe it. - connection.Open(); - } - - optionsBuilder.UseSqlite(connection); - return optionsBuilder.Options; - } - } - } -} diff --git a/Content.Server.Database/Content.Server.Database.csproj b/Content.Server.Database/Content.Server.Database.csproj index 8d92cff95d..849374ed4b 100644 --- a/Content.Server.Database/Content.Server.Database.csproj +++ b/Content.Server.Database/Content.Server.Database.csproj @@ -21,4 +21,9 @@ + + + + + diff --git a/Content.Server.Database/Migrations/Postgres/20200124133512_InitialPg.Designer.cs b/Content.Server.Database/Migrations/Postgres/20200124133512_InitialPg.Designer.cs deleted file mode 100644 index ada793d022..0000000000 --- a/Content.Server.Database/Migrations/Postgres/20200124133512_InitialPg.Designer.cs +++ /dev/null @@ -1,151 +0,0 @@ -// -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; - -namespace Content.Server.Database.Migrations.Postgres -{ - [DbContext(typeof(PostgresPreferencesDbContext))] - [Migration("20200124133512_InitialPg")] - partial class InitialPg - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.0") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Age") - .HasColumnType("integer"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("text"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("text"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("text"); - - b.Property("PreferenceUnavailable") - .HasColumnType("integer"); - - b.Property("PrefsId") - .HasColumnType("integer"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("text"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("Slot") - .HasColumnType("integer"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("JobId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("JobName") - .IsRequired() - .HasColumnType("text"); - - b.Property("Priority") - .HasColumnType("integer"); - - b.Property("ProfileHumanoidProfileId") - .HasColumnType("integer"); - - b.HasKey("JobId"); - - b.HasIndex("ProfileHumanoidProfileId"); - - b.ToTable("Job"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("SelectedCharacterSlot") - .HasColumnType("integer"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileHumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Postgres/20200124133512_InitialPg.cs b/Content.Server.Database/Migrations/Postgres/20200124133512_InitialPg.cs deleted file mode 100644 index 48dbf09481..0000000000 --- a/Content.Server.Database/Migrations/Postgres/20200124133512_InitialPg.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Content.Server.Database.Migrations.Postgres -{ - public partial class InitialPg : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Preferences", - columns: table => new - { - PrefsId = table.Column(nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Username = table.Column(nullable: false), - SelectedCharacterSlot = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Preferences", x => x.PrefsId); - }); - - migrationBuilder.CreateTable( - name: "HumanoidProfile", - columns: table => new - { - HumanoidProfileId = table.Column(nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Slot = table.Column(nullable: false), - SlotName = table.Column(nullable: false), - CharacterName = table.Column(nullable: false), - Age = table.Column(nullable: false), - Sex = table.Column(nullable: false), - HairName = table.Column(nullable: false), - HairColor = table.Column(nullable: false), - FacialHairName = table.Column(nullable: false), - FacialHairColor = table.Column(nullable: false), - EyeColor = table.Column(nullable: false), - SkinColor = table.Column(nullable: false), - PreferenceUnavailable = table.Column(nullable: false), - PrefsId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_HumanoidProfile", x => x.HumanoidProfileId); - table.ForeignKey( - name: "FK_HumanoidProfile_Preferences_PrefsId", - column: x => x.PrefsId, - principalTable: "Preferences", - principalColumn: "PrefsId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Job", - columns: table => new - { - JobId = table.Column(nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ProfileHumanoidProfileId = table.Column(nullable: false), - JobName = table.Column(nullable: false), - Priority = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Job", x => x.JobId); - table.ForeignKey( - name: "FK_Job_HumanoidProfile_ProfileHumanoidProfileId", - column: x => x.ProfileHumanoidProfileId, - principalTable: "HumanoidProfile", - principalColumn: "HumanoidProfileId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_HumanoidProfile_PrefsId", - table: "HumanoidProfile", - column: "PrefsId"); - - migrationBuilder.CreateIndex( - name: "IX_Job_ProfileHumanoidProfileId", - table: "Job", - column: "ProfileHumanoidProfileId"); - - migrationBuilder.CreateIndex( - name: "IX_Preferences_Username", - table: "Preferences", - column: "Username", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Job"); - - migrationBuilder.DropTable( - name: "HumanoidProfile"); - - migrationBuilder.DropTable( - name: "Preferences"); - } - } -} diff --git a/Content.Server.Database/Migrations/Postgres/20200625230829_AddSlotPrefsIdIndex.Designer.cs b/Content.Server.Database/Migrations/Postgres/20200625230829_AddSlotPrefsIdIndex.Designer.cs deleted file mode 100644 index 408755d35a..0000000000 --- a/Content.Server.Database/Migrations/Postgres/20200625230829_AddSlotPrefsIdIndex.Designer.cs +++ /dev/null @@ -1,154 +0,0 @@ -// -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; - -namespace Content.Server.Database.Migrations.Postgres -{ - [DbContext(typeof(PostgresPreferencesDbContext))] - [Migration("20200625230829_AddSlotPrefsIdIndex")] - partial class AddSlotPrefsIdIndex - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.4") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Age") - .HasColumnType("integer"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("text"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("text"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("text"); - - b.Property("PreferenceUnavailable") - .HasColumnType("integer"); - - b.Property("PrefsId") - .HasColumnType("integer"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("text"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("Slot") - .HasColumnType("integer"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.HasIndex("Slot", "PrefsId") - .IsUnique(); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("JobId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("JobName") - .IsRequired() - .HasColumnType("text"); - - b.Property("Priority") - .HasColumnType("integer"); - - b.Property("ProfileHumanoidProfileId") - .HasColumnType("integer"); - - b.HasKey("JobId"); - - b.HasIndex("ProfileHumanoidProfileId"); - - b.ToTable("Job"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("SelectedCharacterSlot") - .HasColumnType("integer"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileHumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Postgres/20200625230829_AddSlotPrefsIdIndex.cs b/Content.Server.Database/Migrations/Postgres/20200625230829_AddSlotPrefsIdIndex.cs deleted file mode 100644 index 088a48bafa..0000000000 --- a/Content.Server.Database/Migrations/Postgres/20200625230829_AddSlotPrefsIdIndex.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Content.Server.Database.Migrations.Postgres -{ - public partial class AddSlotPrefsIdIndex : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "IX_HumanoidProfile_Slot_PrefsId", - table: "HumanoidProfile", - columns: new[] { "Slot", "PrefsId" }, - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_HumanoidProfile_Slot_PrefsId", - table: "HumanoidProfile"); - } - } -} diff --git a/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.Designer.cs b/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.Designer.cs deleted file mode 100644 index 266aa377e5..0000000000 --- a/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.Designer.cs +++ /dev/null @@ -1,185 +0,0 @@ -// -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; - -namespace Content.Server.Database.Migrations.Postgres -{ - [DbContext(typeof(PostgresPreferencesDbContext))] - [Migration("20200706172726_Antags")] - partial class Antags - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.4") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.Property("AntagId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AntagName") - .IsRequired() - .HasColumnType("text"); - - b.Property("HumanoidProfileId") - .HasColumnType("integer"); - - b.HasKey("AntagId"); - - b.HasIndex("HumanoidProfileId", "AntagName") - .IsUnique(); - - b.ToTable("Antag"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Age") - .HasColumnType("integer"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("text"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("text"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("text"); - - b.Property("PreferenceUnavailable") - .HasColumnType("integer"); - - b.Property("PrefsId") - .HasColumnType("integer"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("text"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("Slot") - .HasColumnType("integer"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.HasIndex("Slot", "PrefsId") - .IsUnique(); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("JobId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("JobName") - .IsRequired() - .HasColumnType("text"); - - b.Property("Priority") - .HasColumnType("integer"); - - b.Property("ProfileHumanoidProfileId") - .HasColumnType("integer"); - - b.HasKey("JobId"); - - b.HasIndex("ProfileHumanoidProfileId"); - - b.ToTable("Job"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("SelectedCharacterSlot") - .HasColumnType("integer"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Antags") - .HasForeignKey("HumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileHumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.cs b/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.cs deleted file mode 100644 index 23d595a79b..0000000000 --- a/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Content.Server.Database.Migrations.Postgres -{ - public partial class Antags : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Antag", - columns: table => new - { - AntagId = table.Column(nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - HumanoidProfileId = table.Column(nullable: false), - AntagName = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Antag", x => x.AntagId); - table.ForeignKey( - name: "FK_Antag_HumanoidProfile_HumanoidProfileId", - column: x => x.HumanoidProfileId, - principalTable: "HumanoidProfile", - principalColumn: "HumanoidProfileId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Antag_HumanoidProfileId_AntagName", - table: "Antag", - columns: new[] { "HumanoidProfileId", "AntagName" }, - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Antag"); - } - } -} diff --git a/Content.Server.Database/Migrations/Postgres/20200929113117_Init.Designer.cs b/Content.Server.Database/Migrations/Postgres/20200929113117_Init.Designer.cs new file mode 100644 index 0000000000..24f55b1f95 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20200929113117_Init.Designer.cs @@ -0,0 +1,391 @@ +// +using System; +using System.Net; +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; + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20200929113117_Init")] + partial class Init + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "3.1.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("antag_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AntagName") + .IsRequired() + .HasColumnName("antag_name") + .HasColumnType("text"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag"); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("assigned_user_id_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("job_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("JobName") + .IsRequired() + .HasColumnName("job_name") + .HasColumnType("text"); + + b.Property("Priority") + .HasColumnName("priority") + .HasColumnType("integer"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.ToTable("job"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("connection_log_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Address") + .IsRequired() + .HasColumnName("address") + .HasColumnType("inet"); + + b.Property("Time") + .HasColumnName("time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log"); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresPlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("player_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("FirstSeenTime") + .HasColumnName("first_seen_time") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnName("last_seen_address") + .HasColumnType("inet"); + + b.Property("LastSeenTime") + .HasColumnName("last_seen_time") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnName("last_seen_user_name") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player"); + + b.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("server_ban_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property?>("Address") + .HasColumnName("address") + .HasColumnType("inet"); + + b.Property("BanTime") + .HasColumnName("ban_time") + .HasColumnType("timestamp with time zone"); + + b.Property("BanningAdmin") + .HasColumnName("banning_admin") + .HasColumnType("uuid"); + + b.Property("ExpirationTime") + .HasColumnName("expiration_time") + .HasColumnType("timestamp with time zone"); + + b.Property("Reason") + .IsRequired() + .HasColumnName("reason") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("UserId"); + + b.ToTable("server_ban"); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + b.HasCheckConstraint("HaveEitherAddressOrUserId", "address IS NOT NULL OR user_id IS NOT NULL"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("unban_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("BanId") + .HasColumnName("ban_id") + .HasColumnType("integer"); + + b.Property("UnbanTime") + .HasColumnName("unban_time") + .HasColumnType("timestamp with time zone"); + + b.Property("UnbanningAdmin") + .HasColumnName("unbanning_admin") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("preference_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("SelectedCharacterSlot") + .HasColumnName("selected_character_slot") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("profile_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Age") + .HasColumnName("age") + .HasColumnType("integer"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnName("char_name") + .HasColumnType("text"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnName("eye_color") + .HasColumnType("text"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnName("facial_hair_color") + .HasColumnType("text"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnName("facial_hair_name") + .HasColumnType("text"); + + b.Property("HairColor") + .IsRequired() + .HasColumnName("hair_color") + .HasColumnType("text"); + + b.Property("HairName") + .IsRequired() + .HasColumnName("hair_name") + .HasColumnType("text"); + + b.Property("PreferenceId") + .HasColumnName("preference_id") + .HasColumnType("integer"); + + b.Property("PreferenceUnavailable") + .HasColumnName("pref_unavailable") + .HasColumnType("integer"); + + b.Property("Sex") + .IsRequired() + .HasColumnName("sex") + .HasColumnType("text"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnName("skin_color") + .HasColumnType("text"); + + b.Property("Slot") + .HasColumnName("slot") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PreferenceId"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => + { + b.HasOne("Content.Server.Database.PostgresServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.PostgresServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20200929113117_Init.cs b/Content.Server.Database/Migrations/Postgres/20200929113117_Init.cs new file mode 100644 index 0000000000..eea30ffa1c --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20200929113117_Init.cs @@ -0,0 +1,285 @@ +using System; +using System.Net; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Content.Server.Database.Migrations.Postgres +{ + public partial class Init : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "assigned_user_id", + columns: table => new + { + assigned_user_id_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_name = table.Column(nullable: false), + user_id = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_assigned_user_id", x => x.assigned_user_id_id); + }); + + migrationBuilder.CreateTable( + name: "connection_log", + columns: table => new + { + connection_log_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(nullable: false), + user_name = table.Column(nullable: false), + time = table.Column(type: "timestamp with time zone", nullable: false), + address = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_connection_log", x => x.connection_log_id); + table.CheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + + migrationBuilder.CreateTable( + name: "player", + columns: table => new + { + player_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(nullable: false), + first_seen_time = table.Column(type: "timestamp with time zone", nullable: false), + last_seen_user_name = table.Column(nullable: false), + last_seen_time = table.Column(type: "timestamp with time zone", nullable: false), + last_seen_address = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_player", x => x.player_id); + table.CheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + + migrationBuilder.CreateTable( + name: "preference", + columns: table => new + { + preference_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(nullable: false), + selected_character_slot = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_preference", x => x.preference_id); + }); + + migrationBuilder.CreateTable( + name: "server_ban", + columns: table => new + { + server_ban_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(nullable: true), + address = table.Column>(type: "inet", nullable: true), + ban_time = table.Column(type: "timestamp with time zone", nullable: false), + expiration_time = table.Column(type: "timestamp with time zone", nullable: true), + reason = table.Column(nullable: false), + banning_admin = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_server_ban", x => x.server_ban_id); + table.CheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + table.CheckConstraint("HaveEitherAddressOrUserId", "address IS NOT NULL OR user_id IS NOT NULL"); + }); + + migrationBuilder.CreateTable( + name: "profile", + columns: table => new + { + profile_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slot = table.Column(nullable: false), + char_name = table.Column(nullable: false), + age = table.Column(nullable: false), + sex = table.Column(nullable: false), + hair_name = table.Column(nullable: false), + hair_color = table.Column(nullable: false), + facial_hair_name = table.Column(nullable: false), + facial_hair_color = table.Column(nullable: false), + eye_color = table.Column(nullable: false), + skin_color = table.Column(nullable: false), + pref_unavailable = table.Column(nullable: false), + preference_id = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_profile", x => x.profile_id); + table.ForeignKey( + name: "FK_profile_preference_preference_id", + column: x => x.preference_id, + principalTable: "preference", + principalColumn: "preference_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "server_unban", + columns: table => new + { + unban_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ban_id = table.Column(nullable: false), + unbanning_admin = table.Column(nullable: true), + unban_time = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_server_unban", x => x.unban_id); + table.ForeignKey( + name: "FK_server_unban_server_ban_ban_id", + column: x => x.ban_id, + principalTable: "server_ban", + principalColumn: "server_ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "antag", + columns: table => new + { + antag_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + profile_id = table.Column(nullable: false), + antag_name = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_antag", x => x.antag_id); + table.ForeignKey( + name: "FK_antag_profile_profile_id", + column: x => x.profile_id, + principalTable: "profile", + principalColumn: "profile_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "job", + columns: table => new + { + job_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + profile_id = table.Column(nullable: false), + job_name = table.Column(nullable: false), + priority = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_job", x => x.job_id); + table.ForeignKey( + name: "FK_job_profile_profile_id", + column: x => x.profile_id, + principalTable: "profile", + principalColumn: "profile_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_antag_profile_id_antag_name", + table: "antag", + columns: new[] { "profile_id", "antag_name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_assigned_user_id_user_id", + table: "assigned_user_id", + column: "user_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_assigned_user_id_user_name", + table: "assigned_user_id", + column: "user_name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_connection_log_user_id", + table: "connection_log", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "IX_job_profile_id", + table: "job", + column: "profile_id"); + + migrationBuilder.CreateIndex( + name: "IX_player_user_id", + table: "player", + column: "user_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_preference_user_id", + table: "preference", + column: "user_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_profile_preference_id", + table: "profile", + column: "preference_id"); + + migrationBuilder.CreateIndex( + name: "IX_profile_slot_preference_id", + table: "profile", + columns: new[] { "slot", "preference_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_address", + table: "server_ban", + column: "address"); + + migrationBuilder.CreateIndex( + name: "IX_server_ban_user_id", + table: "server_ban", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_unban_ban_id", + table: "server_unban", + column: "ban_id", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "antag"); + + migrationBuilder.DropTable( + name: "assigned_user_id"); + + migrationBuilder.DropTable( + name: "connection_log"); + + migrationBuilder.DropTable( + name: "job"); + + migrationBuilder.DropTable( + name: "player"); + + migrationBuilder.DropTable( + name: "server_unban"); + + migrationBuilder.DropTable( + name: "profile"); + + migrationBuilder.DropTable( + name: "server_ban"); + + migrationBuilder.DropTable( + name: "preference"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresPreferencesDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresPreferencesDbContextModelSnapshot.cs deleted file mode 100644 index 9429c9c003..0000000000 --- a/Content.Server.Database/Migrations/Postgres/PostgresPreferencesDbContextModelSnapshot.cs +++ /dev/null @@ -1,182 +0,0 @@ -// - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Content.Server.Database.Migrations.Postgres -{ - [DbContext(typeof(PostgresPreferencesDbContext))] - partial class PostgresPreferencesDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.4") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.Property("AntagId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AntagName") - .IsRequired() - .HasColumnType("text"); - - b.Property("HumanoidProfileId") - .HasColumnType("integer"); - - b.HasKey("AntagId"); - - b.HasIndex("HumanoidProfileId", "AntagName") - .IsUnique(); - - b.ToTable("Antag"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Age") - .HasColumnType("integer"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("text"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("text"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("text"); - - b.Property("PreferenceUnavailable") - .HasColumnType("integer"); - - b.Property("PrefsId") - .HasColumnType("integer"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("text"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("text"); - - b.Property("Slot") - .HasColumnType("integer"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.HasIndex("Slot", "PrefsId") - .IsUnique(); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("JobId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("JobName") - .IsRequired() - .HasColumnType("text"); - - b.Property("Priority") - .HasColumnType("integer"); - - b.Property("ProfileHumanoidProfileId") - .HasColumnType("integer"); - - b.HasKey("JobId"); - - b.HasIndex("ProfileHumanoidProfileId"); - - b.ToTable("Job"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("SelectedCharacterSlot") - .HasColumnType("integer"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Antags") - .HasForeignKey("HumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileHumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs new file mode 100644 index 0000000000..59ae3c45f4 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -0,0 +1,389 @@ +// +using System; +using System.Net; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + partial class PostgresServerDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "3.1.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("antag_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AntagName") + .IsRequired() + .HasColumnName("antag_name") + .HasColumnType("text"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag"); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("assigned_user_id_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("job_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("JobName") + .IsRequired() + .HasColumnName("job_name") + .HasColumnType("text"); + + b.Property("Priority") + .HasColumnName("priority") + .HasColumnType("integer"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.ToTable("job"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("connection_log_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Address") + .IsRequired() + .HasColumnName("address") + .HasColumnType("inet"); + + b.Property("Time") + .HasColumnName("time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log"); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresPlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("player_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("FirstSeenTime") + .HasColumnName("first_seen_time") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnName("last_seen_address") + .HasColumnType("inet"); + + b.Property("LastSeenTime") + .HasColumnName("last_seen_time") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnName("last_seen_user_name") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player"); + + b.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("server_ban_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property?>("Address") + .HasColumnName("address") + .HasColumnType("inet"); + + b.Property("BanTime") + .HasColumnName("ban_time") + .HasColumnType("timestamp with time zone"); + + b.Property("BanningAdmin") + .HasColumnName("banning_admin") + .HasColumnType("uuid"); + + b.Property("ExpirationTime") + .HasColumnName("expiration_time") + .HasColumnType("timestamp with time zone"); + + b.Property("Reason") + .IsRequired() + .HasColumnName("reason") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("UserId"); + + b.ToTable("server_ban"); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + b.HasCheckConstraint("HaveEitherAddressOrUserId", "address IS NOT NULL OR user_id IS NOT NULL"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("unban_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("BanId") + .HasColumnName("ban_id") + .HasColumnType("integer"); + + b.Property("UnbanTime") + .HasColumnName("unban_time") + .HasColumnType("timestamp with time zone"); + + b.Property("UnbanningAdmin") + .HasColumnName("unbanning_admin") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("preference_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("SelectedCharacterSlot") + .HasColumnName("selected_character_slot") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("profile_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Age") + .HasColumnName("age") + .HasColumnType("integer"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnName("char_name") + .HasColumnType("text"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnName("eye_color") + .HasColumnType("text"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnName("facial_hair_color") + .HasColumnType("text"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnName("facial_hair_name") + .HasColumnType("text"); + + b.Property("HairColor") + .IsRequired() + .HasColumnName("hair_color") + .HasColumnType("text"); + + b.Property("HairName") + .IsRequired() + .HasColumnName("hair_name") + .HasColumnType("text"); + + b.Property("PreferenceId") + .HasColumnName("preference_id") + .HasColumnType("integer"); + + b.Property("PreferenceUnavailable") + .HasColumnName("pref_unavailable") + .HasColumnType("integer"); + + b.Property("Sex") + .IsRequired() + .HasColumnName("sex") + .HasColumnType("text"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnName("skin_color") + .HasColumnType("text"); + + b.Property("Slot") + .HasColumnName("slot") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PreferenceId"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => + { + b.HasOne("Content.Server.Database.PostgresServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.PostgresServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20200118020532_initial.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20200118020532_initial.Designer.cs deleted file mode 100644 index a0cfca0af8..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200118020532_initial.Designer.cs +++ /dev/null @@ -1,109 +0,0 @@ -// - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Content.Server.Database.Migrations.Sqlite -{ - [DbContext(typeof(SqlitePreferencesDbContext))] - [Migration("20200118020532_initial")] - partial class initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.0"); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Age") - .HasColumnType("INTEGER"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PrefsId") - .HasColumnType("INTEGER"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Slot") - .HasColumnType("INTEGER"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("SelectedCharacterSlot") - .HasColumnType("INTEGER"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200118020532_initial.cs b/Content.Server.Database/Migrations/Sqlite/20200118020532_initial.cs deleted file mode 100644 index 9fdf32e225..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200118020532_initial.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Content.Server.Database.Migrations.Sqlite -{ - public partial class initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Preferences", - columns: table => new - { - PrefsId = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Username = table.Column(nullable: false), - SelectedCharacterSlot = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Preferences", x => x.PrefsId); - }); - - migrationBuilder.CreateTable( - name: "HumanoidProfile", - columns: table => new - { - HumanoidProfileId = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Slot = table.Column(nullable: false), - SlotName = table.Column(nullable: false), - CharacterName = table.Column(nullable: false), - Age = table.Column(nullable: false), - Sex = table.Column(nullable: false), - HairName = table.Column(nullable: false), - HairColor = table.Column(nullable: false), - FacialHairName = table.Column(nullable: false), - FacialHairColor = table.Column(nullable: false), - EyeColor = table.Column(nullable: false), - SkinColor = table.Column(nullable: false), - PrefsId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_HumanoidProfile", x => x.HumanoidProfileId); - table.ForeignKey( - name: "FK_HumanoidProfile_Preferences_PrefsId", - column: x => x.PrefsId, - principalTable: "Preferences", - principalColumn: "PrefsId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_HumanoidProfile_PrefsId", - table: "HumanoidProfile", - column: "PrefsId"); - - migrationBuilder.CreateIndex( - name: "IX_Preferences_Username", - table: "Preferences", - column: "Username", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "HumanoidProfile"); - - migrationBuilder.DropTable( - name: "Preferences"); - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200118195640_jobs.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20200118195640_jobs.Designer.cs deleted file mode 100644 index c7235a5ef9..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200118195640_jobs.Designer.cs +++ /dev/null @@ -1,141 +0,0 @@ -// - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Content.Server.Database.Migrations.Sqlite -{ - [DbContext(typeof(SqlitePreferencesDbContext))] - [Migration("20200118195640_jobs")] - partial class jobs - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.0"); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Age") - .HasColumnType("INTEGER"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PrefsId") - .HasColumnType("INTEGER"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Slot") - .HasColumnType("INTEGER"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("JobId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("JobName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Priority") - .HasColumnType("INTEGER"); - - b.Property("ProfileHumanoidProfileId") - .HasColumnType("INTEGER"); - - b.HasKey("JobId"); - - b.HasIndex("ProfileHumanoidProfileId"); - - b.ToTable("Job"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("SelectedCharacterSlot") - .HasColumnType("INTEGER"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileHumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200118195640_jobs.cs b/Content.Server.Database/Migrations/Sqlite/20200118195640_jobs.cs deleted file mode 100644 index 44b8beb651..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200118195640_jobs.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Content.Server.Database.Migrations.Sqlite -{ - public partial class jobs : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Job", - columns: table => new - { - JobId = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ProfileHumanoidProfileId = table.Column(nullable: false), - JobName = table.Column(nullable: false), - Priority = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Job", x => x.JobId); - table.ForeignKey( - name: "FK_Job_HumanoidProfile_ProfileHumanoidProfileId", - column: x => x.ProfileHumanoidProfileId, - principalTable: "HumanoidProfile", - principalColumn: "HumanoidProfileId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Job_ProfileHumanoidProfileId", - table: "Job", - column: "ProfileHumanoidProfileId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Job"); - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200119103426_preferenceUnavailable.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20200119103426_preferenceUnavailable.Designer.cs deleted file mode 100644 index 4b13876840..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200119103426_preferenceUnavailable.Designer.cs +++ /dev/null @@ -1,144 +0,0 @@ -// - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Content.Server.Database.Migrations.Sqlite -{ - [DbContext(typeof(SqlitePreferencesDbContext))] - [Migration("20200119103426_preferenceUnavailable")] - partial class preferenceUnavailable - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.0"); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Age") - .HasColumnType("INTEGER"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PreferenceUnavailable") - .HasColumnType("INTEGER"); - - b.Property("PrefsId") - .HasColumnType("INTEGER"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Slot") - .HasColumnType("INTEGER"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("JobId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("JobName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Priority") - .HasColumnType("INTEGER"); - - b.Property("ProfileHumanoidProfileId") - .HasColumnType("INTEGER"); - - b.HasKey("JobId"); - - b.HasIndex("ProfileHumanoidProfileId"); - - b.ToTable("Job"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("SelectedCharacterSlot") - .HasColumnType("INTEGER"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileHumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200119103426_preferenceUnavailable.cs b/Content.Server.Database/Migrations/Sqlite/20200119103426_preferenceUnavailable.cs deleted file mode 100644 index e5091391f2..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200119103426_preferenceUnavailable.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Content.Server.Database.Migrations.Sqlite -{ - public partial class preferenceUnavailable : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "PreferenceUnavailable", - table: "HumanoidProfile", - nullable: false, - defaultValue: 0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "PreferenceUnavailable", - table: "HumanoidProfile"); - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200625230839_AddSlotPrefsIdIndex.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20200625230839_AddSlotPrefsIdIndex.Designer.cs deleted file mode 100644 index 62f527c4f4..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200625230839_AddSlotPrefsIdIndex.Designer.cs +++ /dev/null @@ -1,148 +0,0 @@ -// -using Content.Server.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Content.Server.Database.Migrations.Sqlite -{ - [DbContext(typeof(SqlitePreferencesDbContext))] - [Migration("20200625230839_AddSlotPrefsIdIndex")] - partial class AddSlotPrefsIdIndex - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.4"); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Age") - .HasColumnType("INTEGER"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PreferenceUnavailable") - .HasColumnType("INTEGER"); - - b.Property("PrefsId") - .HasColumnType("INTEGER"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Slot") - .HasColumnType("INTEGER"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.HasIndex("Slot", "PrefsId") - .IsUnique(); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("JobId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("JobName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Priority") - .HasColumnType("INTEGER"); - - b.Property("ProfileHumanoidProfileId") - .HasColumnType("INTEGER"); - - b.HasKey("JobId"); - - b.HasIndex("ProfileHumanoidProfileId"); - - b.ToTable("Job"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("SelectedCharacterSlot") - .HasColumnType("INTEGER"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileHumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200625230839_AddSlotPrefsIdIndex.cs b/Content.Server.Database/Migrations/Sqlite/20200625230839_AddSlotPrefsIdIndex.cs deleted file mode 100644 index 53863e24b7..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200625230839_AddSlotPrefsIdIndex.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Content.Server.Database.Migrations.Sqlite -{ - public partial class AddSlotPrefsIdIndex : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "IX_HumanoidProfile_Slot_PrefsId", - table: "HumanoidProfile", - columns: new[] { "Slot", "PrefsId" }, - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_HumanoidProfile_Slot_PrefsId", - table: "HumanoidProfile"); - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.Designer.cs deleted file mode 100644 index 00fe13a785..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.Designer.cs +++ /dev/null @@ -1,178 +0,0 @@ -// -using Content.Server.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Content.Server.Database.Migrations.Sqlite -{ - [DbContext(typeof(SqlitePreferencesDbContext))] - [Migration("20200706172741_Antags")] - partial class Antags - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.4"); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.Property("AntagId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AntagName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HumanoidProfileId") - .HasColumnType("INTEGER"); - - b.HasKey("AntagId"); - - b.HasIndex("HumanoidProfileId", "AntagName") - .IsUnique(); - - b.ToTable("Antag"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Age") - .HasColumnType("INTEGER"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PreferenceUnavailable") - .HasColumnType("INTEGER"); - - b.Property("PrefsId") - .HasColumnType("INTEGER"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Slot") - .HasColumnType("INTEGER"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.HasIndex("Slot", "PrefsId") - .IsUnique(); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("JobId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("JobName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Priority") - .HasColumnType("INTEGER"); - - b.Property("ProfileHumanoidProfileId") - .HasColumnType("INTEGER"); - - b.HasKey("JobId"); - - b.HasIndex("ProfileHumanoidProfileId"); - - b.ToTable("Job"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("SelectedCharacterSlot") - .HasColumnType("INTEGER"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Antags") - .HasForeignKey("HumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileHumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.cs b/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.cs deleted file mode 100644 index f2c1185b00..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Content.Server.Database.Migrations.Sqlite -{ - public partial class Antags : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Antag", - columns: table => new - { - AntagId = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - HumanoidProfileId = table.Column(nullable: false), - AntagName = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Antag", x => x.AntagId); - table.ForeignKey( - name: "FK_Antag_HumanoidProfile_HumanoidProfileId", - column: x => x.HumanoidProfileId, - principalTable: "HumanoidProfile", - principalColumn: "HumanoidProfileId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Antag_HumanoidProfileId_AntagName", - table: "Antag", - columns: new[] { "HumanoidProfileId", "AntagName" }, - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Antag"); - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20200929113112_Init.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20200929113112_Init.Designer.cs new file mode 100644 index 0000000000..e1b403291b --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20200929113112_Init.Designer.cs @@ -0,0 +1,361 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20200929113112_Init")] + partial class Init + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.4"); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("antag_id") + .HasColumnType("INTEGER"); + + b.Property("AntagName") + .IsRequired() + .HasColumnName("antag_name") + .HasColumnType("TEXT"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag"); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("assigned_user_id_id") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("job_id") + .HasColumnType("INTEGER"); + + b.Property("JobName") + .IsRequired() + .HasColumnName("job_name") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnName("priority") + .HasColumnType("INTEGER"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.ToTable("job"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("preference_id") + .HasColumnType("INTEGER"); + + b.Property("SelectedCharacterSlot") + .HasColumnName("selected_character_slot") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("profile_id") + .HasColumnType("INTEGER"); + + b.Property("Age") + .HasColumnName("age") + .HasColumnType("INTEGER"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnName("char_name") + .HasColumnType("TEXT"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnName("eye_color") + .HasColumnType("TEXT"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnName("facial_hair_color") + .HasColumnType("TEXT"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnName("facial_hair_name") + .HasColumnType("TEXT"); + + b.Property("HairColor") + .IsRequired() + .HasColumnName("hair_color") + .HasColumnType("TEXT"); + + b.Property("HairName") + .IsRequired() + .HasColumnName("hair_name") + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnName("preference_id") + .HasColumnType("INTEGER"); + + b.Property("PreferenceUnavailable") + .HasColumnName("pref_unavailable") + .HasColumnType("INTEGER"); + + b.Property("Sex") + .IsRequired() + .HasColumnName("sex") + .HasColumnType("TEXT"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnName("skin_color") + .HasColumnType("TEXT"); + + b.Property("Slot") + .HasColumnName("slot") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PreferenceId"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("connection_log_id") + .HasColumnType("INTEGER"); + + b.Property("Address") + .IsRequired() + .HasColumnName("address") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnName("time") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("connection_log"); + }); + + modelBuilder.Entity("Content.Server.Database.SqlitePlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("player_id") + .HasColumnType("INTEGER"); + + b.Property("FirstSeenTime") + .HasColumnName("first_seen_time") + .HasColumnType("TEXT"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnName("last_seen_address") + .HasColumnType("TEXT"); + + b.Property("LastSeenTime") + .HasColumnName("last_seen_time") + .HasColumnType("TEXT"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnName("last_seen_user_name") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("player"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("ban_id") + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnName("address") + .HasColumnType("TEXT"); + + b.Property("BanTime") + .HasColumnName("ban_time") + .HasColumnType("TEXT"); + + b.Property("BanningAdmin") + .HasColumnName("banning_admin") + .HasColumnType("TEXT"); + + b.Property("ExpirationTime") + .HasColumnName("expiration_time") + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasColumnName("reason") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ban"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("unban_id") + .HasColumnType("INTEGER"); + + b.Property("BanId") + .HasColumnName("ban_id") + .HasColumnType("INTEGER"); + + b.Property("UnbanTime") + .HasColumnName("unban_time") + .HasColumnType("TEXT"); + + b.Property("UnbanningAdmin") + .HasColumnName("unbanning_admin") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b => + { + b.HasOne("Content.Server.Database.SqliteServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.SqliteServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20200929113112_Init.cs b/Content.Server.Database/Migrations/Sqlite/20200929113112_Init.cs new file mode 100644 index 0000000000..2c9dc8ebd1 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20200929113112_Init.cs @@ -0,0 +1,258 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Sqlite +{ + public partial class Init : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "assigned_user_id", + columns: table => new + { + assigned_user_id_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + user_name = table.Column(nullable: false), + user_id = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_assigned_user_id", x => x.assigned_user_id_id); + }); + + migrationBuilder.CreateTable( + name: "ban", + columns: table => new + { + ban_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + user_id = table.Column(nullable: true), + address = table.Column(nullable: true), + ban_time = table.Column(nullable: false), + expiration_time = table.Column(nullable: true), + reason = table.Column(nullable: false), + banning_admin = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ban", x => x.ban_id); + }); + + migrationBuilder.CreateTable( + name: "connection_log", + columns: table => new + { + connection_log_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + user_id = table.Column(nullable: false), + user_name = table.Column(nullable: false), + time = table.Column(nullable: false), + address = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_connection_log", x => x.connection_log_id); + }); + + migrationBuilder.CreateTable( + name: "player", + columns: table => new + { + player_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + user_id = table.Column(nullable: false), + first_seen_time = table.Column(nullable: false), + last_seen_user_name = table.Column(nullable: false), + last_seen_time = table.Column(nullable: false), + last_seen_address = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_player", x => x.player_id); + }); + + migrationBuilder.CreateTable( + name: "preference", + columns: table => new + { + preference_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + user_id = table.Column(nullable: false), + selected_character_slot = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_preference", x => x.preference_id); + }); + + migrationBuilder.CreateTable( + name: "unban", + columns: table => new + { + unban_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ban_id = table.Column(nullable: false), + unbanning_admin = table.Column(nullable: true), + unban_time = table.Column(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.CreateTable( + name: "profile", + columns: table => new + { + profile_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + slot = table.Column(nullable: false), + char_name = table.Column(nullable: false), + age = table.Column(nullable: false), + sex = table.Column(nullable: false), + hair_name = table.Column(nullable: false), + hair_color = table.Column(nullable: false), + facial_hair_name = table.Column(nullable: false), + facial_hair_color = table.Column(nullable: false), + eye_color = table.Column(nullable: false), + skin_color = table.Column(nullable: false), + pref_unavailable = table.Column(nullable: false), + preference_id = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_profile", x => x.profile_id); + table.ForeignKey( + name: "FK_profile_preference_preference_id", + column: x => x.preference_id, + principalTable: "preference", + principalColumn: "preference_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "antag", + columns: table => new + { + antag_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + profile_id = table.Column(nullable: false), + antag_name = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_antag", x => x.antag_id); + table.ForeignKey( + name: "FK_antag_profile_profile_id", + column: x => x.profile_id, + principalTable: "profile", + principalColumn: "profile_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "job", + columns: table => new + { + job_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + profile_id = table.Column(nullable: false), + job_name = table.Column(nullable: false), + priority = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_job", x => x.job_id); + table.ForeignKey( + name: "FK_job_profile_profile_id", + column: x => x.profile_id, + principalTable: "profile", + principalColumn: "profile_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_antag_profile_id_antag_name", + table: "antag", + columns: new[] { "profile_id", "antag_name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_assigned_user_id_user_id", + table: "assigned_user_id", + column: "user_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_assigned_user_id_user_name", + table: "assigned_user_id", + column: "user_name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_job_profile_id", + table: "job", + column: "profile_id"); + + migrationBuilder.CreateIndex( + name: "IX_preference_user_id", + table: "preference", + column: "user_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_profile_preference_id", + table: "profile", + column: "preference_id"); + + migrationBuilder.CreateIndex( + name: "IX_profile_slot_preference_id", + table: "profile", + columns: new[] { "slot", "preference_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_unban_ban_id", + table: "unban", + column: "ban_id", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "antag"); + + migrationBuilder.DropTable( + name: "assigned_user_id"); + + migrationBuilder.DropTable( + name: "connection_log"); + + migrationBuilder.DropTable( + name: "job"); + + migrationBuilder.DropTable( + name: "player"); + + migrationBuilder.DropTable( + name: "unban"); + + migrationBuilder.DropTable( + name: "profile"); + + migrationBuilder.DropTable( + name: "ban"); + + migrationBuilder.DropTable( + name: "preference"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqlitePreferencesDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqlitePreferencesDbContextModelSnapshot.cs deleted file mode 100644 index 5715275f50..0000000000 --- a/Content.Server.Database/Migrations/Sqlite/SqlitePreferencesDbContextModelSnapshot.cs +++ /dev/null @@ -1,175 +0,0 @@ -// - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Content.Server.Database.Migrations.Sqlite -{ - [DbContext(typeof(SqlitePreferencesDbContext))] - partial class SqlitePreferencesDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.4"); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.Property("AntagId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AntagName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HumanoidProfileId") - .HasColumnType("INTEGER"); - - b.HasKey("AntagId"); - - b.HasIndex("HumanoidProfileId", "AntagName") - .IsUnique(); - - b.ToTable("Antag"); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.Property("HumanoidProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Age") - .HasColumnType("INTEGER"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PreferenceUnavailable") - .HasColumnType("INTEGER"); - - b.Property("PrefsId") - .HasColumnType("INTEGER"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Slot") - .HasColumnType("INTEGER"); - - b.Property("SlotName") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("HumanoidProfileId"); - - b.HasIndex("PrefsId"); - - b.HasIndex("Slot", "PrefsId") - .IsUnique(); - - b.ToTable("HumanoidProfile"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("JobId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("JobName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Priority") - .HasColumnType("INTEGER"); - - b.Property("ProfileHumanoidProfileId") - .HasColumnType("INTEGER"); - - b.HasKey("JobId"); - - b.HasIndex("ProfileHumanoidProfileId"); - - b.ToTable("Job"); - }); - - modelBuilder.Entity("Content.Server.Database.Prefs", b => - { - b.Property("PrefsId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("SelectedCharacterSlot") - .HasColumnType("INTEGER"); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("PrefsId"); - - b.HasIndex("Username") - .IsUnique(); - - b.ToTable("Preferences"); - }); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Antags") - .HasForeignKey("HumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => - { - b.HasOne("Content.Server.Database.Prefs", "Prefs") - .WithMany("HumanoidProfiles") - .HasForeignKey("PrefsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileHumanoidProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs new file mode 100644 index 0000000000..b00a7e32c7 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -0,0 +1,359 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + partial class SqliteServerDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.4"); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("antag_id") + .HasColumnType("INTEGER"); + + b.Property("AntagName") + .IsRequired() + .HasColumnName("antag_name") + .HasColumnType("TEXT"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag"); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("assigned_user_id_id") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("job_id") + .HasColumnType("INTEGER"); + + b.Property("JobName") + .IsRequired() + .HasColumnName("job_name") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnName("priority") + .HasColumnType("INTEGER"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.ToTable("job"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("preference_id") + .HasColumnType("INTEGER"); + + b.Property("SelectedCharacterSlot") + .HasColumnName("selected_character_slot") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("profile_id") + .HasColumnType("INTEGER"); + + b.Property("Age") + .HasColumnName("age") + .HasColumnType("INTEGER"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnName("char_name") + .HasColumnType("TEXT"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnName("eye_color") + .HasColumnType("TEXT"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnName("facial_hair_color") + .HasColumnType("TEXT"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnName("facial_hair_name") + .HasColumnType("TEXT"); + + b.Property("HairColor") + .IsRequired() + .HasColumnName("hair_color") + .HasColumnType("TEXT"); + + b.Property("HairName") + .IsRequired() + .HasColumnName("hair_name") + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnName("preference_id") + .HasColumnType("INTEGER"); + + b.Property("PreferenceUnavailable") + .HasColumnName("pref_unavailable") + .HasColumnType("INTEGER"); + + b.Property("Sex") + .IsRequired() + .HasColumnName("sex") + .HasColumnType("TEXT"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnName("skin_color") + .HasColumnType("TEXT"); + + b.Property("Slot") + .HasColumnName("slot") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PreferenceId"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("connection_log_id") + .HasColumnType("INTEGER"); + + b.Property("Address") + .IsRequired() + .HasColumnName("address") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnName("time") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("connection_log"); + }); + + modelBuilder.Entity("Content.Server.Database.SqlitePlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("player_id") + .HasColumnType("INTEGER"); + + b.Property("FirstSeenTime") + .HasColumnName("first_seen_time") + .HasColumnType("TEXT"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnName("last_seen_address") + .HasColumnType("TEXT"); + + b.Property("LastSeenTime") + .HasColumnName("last_seen_time") + .HasColumnType("TEXT"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnName("last_seen_user_name") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("player"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("ban_id") + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnName("address") + .HasColumnType("TEXT"); + + b.Property("BanTime") + .HasColumnName("ban_time") + .HasColumnType("TEXT"); + + b.Property("BanningAdmin") + .HasColumnName("banning_admin") + .HasColumnType("TEXT"); + + b.Property("ExpirationTime") + .HasColumnName("expiration_time") + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasColumnName("reason") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ban"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("unban_id") + .HasColumnType("INTEGER"); + + b.Property("BanId") + .HasColumnName("ban_id") + .HasColumnType("INTEGER"); + + b.Property("UnbanTime") + .HasColumnName("unban_time") + .HasColumnType("TEXT"); + + b.Property("UnbanningAdmin") + .HasColumnName("unbanning_admin") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b => + { + b.HasOne("Content.Server.Database.SqliteServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.SqliteServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index a28d1dcb8f..341c05484b 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -1,42 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; namespace Content.Server.Database { - public class PostgresPreferencesDbContext : PreferencesDbContext - { - // This is used by the "dotnet ef" CLI tool. - public PostgresPreferencesDbContext() - { - } - protected override void OnConfiguring(DbContextOptionsBuilder options) - { - if(!InitializedWithOptions) - options.UseNpgsql("dummy connection string"); - } - - public PostgresPreferencesDbContext(DbContextOptions options) : base(options) - { - } - } - - public class SqlitePreferencesDbContext : PreferencesDbContext - { - public SqlitePreferencesDbContext() - { - } - protected override void OnConfiguring(DbContextOptionsBuilder options) - { - if (!InitializedWithOptions) - options.UseSqlite("dummy connection string"); - } - - public SqlitePreferencesDbContext(DbContextOptions options) : base(options) - { - } - } - - public abstract class PreferencesDbContext : DbContext + public abstract class ServerDbContext : DbContext { /// /// The "dotnet ef" CLI tool uses the parameter-less constructor. @@ -44,70 +13,86 @@ namespace Content.Server.Database /// To use the context within the application, the options need to be passed the constructor instead. /// protected readonly bool InitializedWithOptions; - public PreferencesDbContext() + + public ServerDbContext() { } - public PreferencesDbContext(DbContextOptions options) : base(options) + + public ServerDbContext(DbContextOptions options) : base(options) { InitializedWithOptions = true; } - public DbSet Preferences { get; set; } = null!; - public DbSet HumanoidProfile { get; set; } = null!; + public DbSet Preference { get; set; } = null!; + public DbSet Profile { get; set; } = null!; + public DbSet AssignedUserId { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity() - .HasIndex(p => p.Username) + modelBuilder.Entity() + .HasIndex(p => p.UserId) .IsUnique(); - modelBuilder.Entity() - .HasIndex(p => new {p.Slot, p.PrefsId}) + modelBuilder.Entity() + .HasIndex(p => new {p.Slot, PrefsId = p.PreferenceId}) .IsUnique(); modelBuilder.Entity() - .HasIndex(p => new {p.HumanoidProfileId , p.AntagName}) + .HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.AntagName}) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(p => p.UserName) + .IsUnique(); + + // Can't have two usernames with the same user ID. + modelBuilder.Entity() + .HasIndex(p => p.UserId) .IsUnique(); } } - public class Prefs + [Table("preference")] + public class Preference { - public int PrefsId { get; set; } - public string Username { get; set; } = null!; - public int SelectedCharacterSlot { get; set; } - public List HumanoidProfiles { get; } = new List(); + [Column("preference_id")] public int Id { get; set; } + [Column("user_id")] public Guid UserId { get; set; } + [Column("selected_character_slot")] public int SelectedCharacterSlot { get; set; } + public List Profiles { get; } = new List(); } - public class HumanoidProfile + [Table("profile")] + public class Profile { - public int HumanoidProfileId { get; set; } - public int Slot { get; set; } - public string SlotName { get; set; } = null!; - public string CharacterName { get; set; } = null!; - public int Age { get; set; } - public string Sex { get; set; } = null!; - public string HairName { get; set; } = null!; - public string HairColor { get; set; } = null!; - public string FacialHairName { get; set; } = null!; - public string FacialHairColor { get; set; } = null!; - public string EyeColor { get; set; } = null!; - public string SkinColor { get; set; } = null!; + [Column("profile_id")] public int Id { get; set; } + [Column("slot")] public int Slot { get; set; } + [Column("char_name")] public string CharacterName { get; set; } = null!; + [Column("age")] public int Age { get; set; } + [Column("sex")] public string Sex { get; set; } = null!; + [Column("hair_name")] public string HairName { get; set; } = null!; + [Column("hair_color")] public string HairColor { get; set; } = null!; + [Column("facial_hair_name")] public string FacialHairName { get; set; } = null!; + [Column("facial_hair_color")] public string FacialHairColor { get; set; } = null!; + [Column("eye_color")] public string EyeColor { get; set; } = null!; + [Column("skin_color")] public string SkinColor { get; set; } = null!; public List Jobs { get; } = new List(); public List Antags { get; } = new List(); - public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; } - public int PrefsId { get; set; } - public Prefs Prefs { get; set; } = null!; + [Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; } + + [Column("preference_id")] public int PreferenceId { get; set; } + public Preference Preference { get; set; } = null!; } + [Table("job")] public class Job { - public int JobId { get; set; } - public HumanoidProfile Profile { get; set; } = null!; + [Column("job_id")] public int Id { get; set; } + public Profile Profile { get; set; } = null!; + [Column("profile_id")] public int ProfileId { get; set; } - public string JobName { get; set; } = null!; - public DbJobPriority Priority { get; set; } + [Column("job_name")] public string JobName { get; set; } = null!; + [Column("priority")] public DbJobPriority Priority { get; set; } } public enum DbJobPriority @@ -119,13 +104,14 @@ namespace Content.Server.Database High = 3 } + [Table("antag")] public class Antag { - public int AntagId { get; set; } - public HumanoidProfile Profile { get; set; } = null!; - public int HumanoidProfileId { get; set; } + [Column("antag_id")] public int Id { get; set; } + public Profile Profile { get; set; } = null!; + [Column("profile_id")] public int ProfileId { get; set; } - public string AntagName { get; set; } = null!; + [Column("antag_name")] public string AntagName { get; set; } = null!; } public enum DbPreferenceUnavailableMode @@ -134,4 +120,13 @@ namespace Content.Server.Database StayInLobby = 0, SpawnAsOverflow, } + + [Table("assigned_user_id")] + public class AssignedUserId + { + [Column("assigned_user_id_id")] public int Id { get; set; } + [Column("user_name")] public string UserName { get; set; } = null!; + + [Column("user_id")] public Guid UserId { get; set; } + } } diff --git a/Content.Server.Database/ModelPostgres.cs b/Content.Server.Database/ModelPostgres.cs new file mode 100644 index 0000000000..998c517c51 --- /dev/null +++ b/Content.Server.Database/ModelPostgres.cs @@ -0,0 +1,144 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; +using System.Net; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Content.Server.Database +{ + public sealed class PostgresServerDbContext : ServerDbContext + { + // This is used by the "dotnet ef" CLI tool. + public PostgresServerDbContext() + { + } + + public DbSet Ban { get; set; } = default!; + public DbSet Unban { get; set; } = default!; + public DbSet Player { get; set; } = default!; + public DbSet ConnectionLog { get; set; } = default!; + + + protected override void OnConfiguring(DbContextOptionsBuilder options) + { + if (!InitializedWithOptions) + options.UseNpgsql("dummy connection string"); + + options.ReplaceService(); + } + + public PostgresServerDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasIndex(p => p.UserId); + + modelBuilder.Entity() + .HasIndex(p => p.Address); + + modelBuilder.Entity() + .HasIndex(p => p.UserId); + + modelBuilder.Entity() + .HasIndex(p => p.BanId) + .IsUnique(); + + // ReSharper disable once CommentTypo + // ReSharper disable once 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() + .HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address") + .HasCheckConstraint("HaveEitherAddressOrUserId", "address IS NOT NULL OR user_id IS NOT NULL"); + + modelBuilder.Entity() + .HasIndex(p => p.UserId) + .IsUnique(); + + // ReSharper disable once StringLiteralTypo + modelBuilder.Entity() + .HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", + "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + + modelBuilder.Entity() + .HasIndex(p => p.UserId); + + modelBuilder.Entity() + .HasCheckConstraint("AddressNotIPv6MappedIPv4", + "NOT inet '::ffff:0.0.0.0/96' >>= address"); + } + } + + [Table("server_ban")] + public class PostgresServerBan + { + [Column("server_ban_id")] public int Id { get; set; } + + [Column("user_id")] public Guid? UserId { get; set; } + [Column("address", TypeName = "inet")] public (IPAddress, int)? Address { get; set; } + + [Column("ban_time", TypeName = "timestamp with time zone")] + public DateTime BanTime { get; set; } + + [Column("expiration_time", TypeName = "timestamp with time zone")] + public DateTime? ExpirationTime { get; set; } + + [Column("reason")] public string Reason { get; set; } = null!; + [Column("banning_admin")] public Guid? BanningAdmin { get; set; } + + public PostgresServerUnban? Unban { get; set; } + } + + [Table("server_unban")] + public class PostgresServerUnban + { + [Column("unban_id")] public int Id { get; set; } + + [Column("ban_id")] public int BanId { get; set; } + [Column("ban")] public PostgresServerBan Ban { get; set; } = null!; + + [Column("unbanning_admin")] public Guid? UnbanningAdmin { get; set; } + + [Column("unban_time", TypeName = "timestamp with time zone")] + public DateTime UnbanTime { get; set; } + } + + [Table("player")] + public class PostgresPlayer + { + [Column("player_id")] public int Id { get; set; } + + // Permanent data + [Column("user_id")] public Guid UserId { get; set; } + + [Column("first_seen_time", TypeName = "timestamp with time zone")] + public DateTime FirstSeenTime { get; set; } + + // Data that gets updated on each join. + [Column("last_seen_user_name")] public string LastSeenUserName { get; set; } = null!; + + [Column("last_seen_time", TypeName = "timestamp with time zone")] + public DateTime LastSeenTime { get; set; } + + [Column("last_seen_address")] public IPAddress LastSeenAddress { get; set; } = null!; + } + + [Table("connection_log")] + public class PostgresConnectionLog + { + [Column("connection_log_id")] public int Id { get; set; } + + [Column("user_id")] public Guid UserId { get; set; } + [Column("user_name")] public string UserName { get; set; } = null!; + + [Column("time", TypeName = "timestamp with time zone")] + public DateTime Time { get; set; } + + [Column("address")] public IPAddress Address { get; set; } = null!; + } +} diff --git a/Content.Server.Database/ModelSqlite.cs b/Content.Server.Database/ModelSqlite.cs new file mode 100644 index 0000000000..0a7ca69e48 --- /dev/null +++ b/Content.Server.Database/ModelSqlite.cs @@ -0,0 +1,82 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Content.Server.Database +{ + public sealed class SqliteServerDbContext : ServerDbContext + { + public DbSet Ban { get; set; } = default!; + public DbSet Unban { get; set; } = default!; + public DbSet Player { get; set; } = default!; + public DbSet ConnectionLog { get; set; } = default!; + + public SqliteServerDbContext() + { + } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + { + if (!InitializedWithOptions) + options.UseSqlite("dummy connection string"); + } + + public SqliteServerDbContext(DbContextOptions options) : base(options) + { + } + } + + [Table("ban")] + public class SqliteServerBan + { + [Column("ban_id")] public int Id { get; set; } + + [Column("user_id")] public Guid? UserId { get; set; } + [Column("address")] public string? Address { get; set; } + + [Column("ban_time")] public DateTime BanTime { get; set; } + [Column("expiration_time")] public DateTime? ExpirationTime { get; set; } + [Column("reason")] public string Reason { get; set; } = null!; + [Column("banning_admin")] public Guid? BanningAdmin { get; set; } + + public SqliteServerUnban? Unban { get; set; } + } + + [Table("unban")] + public class SqliteServerUnban + { + [Column("unban_id")] public int Id { get; set; } + + [Column("ban_id")] public int BanId { get; set; } + public SqliteServerBan Ban { get; set; } = null!; + + [Column("unbanning_admin")] public Guid? UnbanningAdmin { get; set; } + [Column("unban_time")] public DateTime UnbanTime { get; set; } + } + + [Table("player")] + public class SqlitePlayer + { + [Column("player_id")] public int Id { get; set; } + + // Permanent data + [Column("user_id")] public Guid UserId { get; set; } + [Column("first_seen_time")] public DateTime FirstSeenTime { get; set; } + + // Data that gets updated on each join. + [Column("last_seen_user_name")] public string LastSeenUserName { get; set; } = null!; + [Column("last_seen_time")] public DateTime LastSeenTime { get; set; } + [Column("last_seen_address")] public string LastSeenAddress { get; set; } = null!; + } + + [Table("connection_log")] + public class SqliteConnectionLog + { + [Column("connection_log_id")] public int Id { get; set; } + + [Column("user_id")] public Guid UserId { get; set; } + [Column("user_name")] public string UserName { get; set; } = null!; + [Column("time")] public DateTime Time { get; set; } + [Column("address")] public string Address { get; set; } = null!; + } +} diff --git a/Content.Server.Database/NpgsqlTypeMapping.cs b/Content.Server.Database/NpgsqlTypeMapping.cs new file mode 100644 index 0000000000..c3300d51d3 --- /dev/null +++ b/Content.Server.Database/NpgsqlTypeMapping.cs @@ -0,0 +1,68 @@ +using System.Linq.Expressions; +using System.Net; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Storage; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; + +namespace Content.Server.Database +{ + // Taken from https://github.com/npgsql/efcore.pg/issues/1158 + // To support inet -> (IPAddress, int) mapping. + #pragma warning disable EF1001 + public class CustomNpgsqlTypeMappingSource : NpgsqlTypeMappingSource + #pragma warning restore EF1001 + { + public CustomNpgsqlTypeMappingSource( + TypeMappingSourceDependencies dependencies, + RelationalTypeMappingSourceDependencies relationalDependencies, + ISqlGenerationHelper sqlGenerationHelper, + INpgsqlOptions? npgsqlOptions = null) + : base(dependencies, relationalDependencies, sqlGenerationHelper, npgsqlOptions) + { + StoreTypeMappings["inet"] = + new RelationalTypeMapping[] + { + new NpgsqlInetWithMaskTypeMapping(), + new NpgsqlInetTypeMapping() + }; + } + } + + // Basically copied from NpgsqlCidrTypeMapping + public class NpgsqlInetWithMaskTypeMapping : NpgsqlTypeMapping + { + public NpgsqlInetWithMaskTypeMapping() : base("inet", typeof((IPAddress, int)), NpgsqlTypes.NpgsqlDbType.Inet) + { + } + + protected NpgsqlInetWithMaskTypeMapping(RelationalTypeMappingParameters parameters) + : base(parameters, NpgsqlTypes.NpgsqlDbType.Inet) + { + } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new NpgsqlInetWithMaskTypeMapping(parameters); + + protected override string GenerateNonNullSqlLiteral(object value) + { + var cidr = ((IPAddress Address, int Subnet)) value; + return $"INET '{cidr.Address}/{cidr.Subnet}'"; + } + + public override Expression GenerateCodeLiteral(object value) + { + var cidr = ((IPAddress Address, int Subnet)) value; + return Expression.New( + Constructor, + Expression.Call(ParseMethod, Expression.Constant(cidr.Address.ToString())), + Expression.Constant(cidr.Subnet)); + } + + static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", new[] {typeof(string)})!; + + static readonly ConstructorInfo Constructor = + typeof((IPAddress, int)).GetConstructor(new[] {typeof(IPAddress), typeof(int)})!; + } +} diff --git a/Content.Server.Database/PrefsDb.cs b/Content.Server.Database/PrefsDb.cs deleted file mode 100644 index 73e98b0216..0000000000 --- a/Content.Server.Database/PrefsDb.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; - -namespace Content.Server.Database -{ - public class PrefsDb - { - private readonly PreferencesDbContext _prefsCtx; - - public PrefsDb(IDatabaseConfiguration dbConfig) - { - _prefsCtx = dbConfig switch - { - SqliteConfiguration sqlite => (PreferencesDbContext) new SqlitePreferencesDbContext( - sqlite.Options), - PostgresConfiguration postgres => new PostgresPreferencesDbContext(postgres.Options), - _ => throw new NotImplementedException() - }; - _prefsCtx.Database.Migrate(); - } - - public async Task GetPlayerPreferences(string username) - { - return await _prefsCtx - .Preferences - .Include(p => p.HumanoidProfiles).ThenInclude(h => h.Jobs) - .Include(p => p.HumanoidProfiles).ThenInclude(h => h.Antags) - .SingleOrDefaultAsync(p => p.Username == username); - } - - public async Task SaveSelectedCharacterIndex(string username, int slot) - { - var prefs = _prefsCtx.Preferences.SingleOrDefault(p => p.Username == username); - if (prefs is null) - _prefsCtx.Preferences.Add(new Prefs - { - Username = username, - SelectedCharacterSlot = slot - }); - else - prefs.SelectedCharacterSlot = slot; - await _prefsCtx.SaveChangesAsync(); - } - - public async Task SaveCharacterSlotAsync(string username, HumanoidProfile newProfile) - { - var prefs = _prefsCtx - .Preferences - .Single(p => p.Username == username); - var oldProfile = prefs - .HumanoidProfiles - .SingleOrDefault(h => h.Slot == newProfile.Slot); - if (!(oldProfile is null)) prefs.HumanoidProfiles.Remove(oldProfile); - prefs.HumanoidProfiles.Add(newProfile); - await _prefsCtx.SaveChangesAsync(); - } - - public async Task DeleteCharacterSlotAsync(string username, int slot) - { - var profile = _prefsCtx - .Preferences - .Single(p => p.Username == username) - .HumanoidProfiles - .RemoveAll(h => h.Slot == slot); - await _prefsCtx.SaveChangesAsync(); - } - - public async Task> GetProfilesForPlayersAsync(List usernames) - { - return await _prefsCtx.HumanoidProfile - .Include(p => p.Jobs) - .Include(a => a.Antags) - .Join(_prefsCtx.Preferences, - profile => new {profile.Slot, profile.PrefsId}, - prefs => new {Slot = prefs.SelectedCharacterSlot, prefs.PrefsId}, - (profile, prefs) => new {prefs.Username, profile}) - .Where(p => usernames.Contains(p.Username)) - .ToDictionaryAsync(arg => arg.Username, arg => arg.profile); - } - } -} diff --git a/Content.Server.Database/add-migration.ps1 b/Content.Server.Database/add-migration.ps1 new file mode 100755 index 0000000000..7f44558eb7 --- /dev/null +++ b/Content.Server.Database/add-migration.ps1 @@ -0,0 +1,12 @@ +#!/usr/bin/env pwsh + +param([String]$name) + +if ($name -eq "") +{ + Write-Error "must specify migration name" + exit +} + +dotnet ef migrations add --context SqliteServerDbContext -o Migrations/Sqlite $name +dotnet ef migrations add --context PostgresServerDbContext -o Migrations/Postgres $name diff --git a/Content.Server/Administration/BanCommand.cs b/Content.Server/Administration/BanCommand.cs new file mode 100644 index 0000000000..2dbabd2302 --- /dev/null +++ b/Content.Server/Administration/BanCommand.cs @@ -0,0 +1,57 @@ +using System; +using System.Net; +using Content.Server.Database; +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Shared.IoC; +using Robust.Shared.Network; + +#nullable enable + +namespace Content.Server.Administration +{ + public sealed class BanCommand : IClientCommand + { + public string Command => "ban"; + public string Description => "Bans somebody"; + public string Help => "Usage: "; + + public async void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + { + var plyMgr = IoCManager.Resolve(); + var dbMan = IoCManager.Resolve(); + + var target = args[0]; + var reason = args[1]; + var duration = int.Parse(args[2]); + NetUserId targetUid; + + if (plyMgr.TryGetSessionByUsername(target, out var targetSession)) + { + targetUid = targetSession.UserId; + } + else if (Guid.TryParse(target, out var targetGuid)) + { + targetUid = new NetUserId(targetGuid); + } + else + { + shell.SendText(player, "Unable to find user with that name."); + return; + } + + DateTimeOffset? expires = null; + if (duration > 0) + { + expires = DateTimeOffset.Now + TimeSpan.FromMinutes(duration); + } + + await dbMan.AddServerBanAsync(new ServerBanDef(targetUid, null, DateTimeOffset.Now, expires, reason, player?.UserId)); + + if (plyMgr.TryGetSessionById(targetUid, out var targetPlayer)) + { + targetPlayer.ConnectedClient.Disconnect("You've been banned. Tough shit."); + } + } + } +} diff --git a/Content.Server/Chat/ChatManager.cs b/Content.Server/Chat/ChatManager.cs index cab6f373e8..37689f3d7c 100644 --- a/Content.Server/Chat/ChatManager.cs +++ b/Content.Server/Chat/ChatManager.cs @@ -165,10 +165,10 @@ namespace Content.Server.Chat var msg = _netManager.CreateNetMessage(); msg.Channel = ChatChannel.OOC; msg.Message = message; - msg.MessageWrap = $"OOC: {player.SessionId}: {{0}}"; + msg.MessageWrap = $"OOC: {player.Name}: {{0}}"; _netManager.ServerSendToAll(msg); - _mommiLink.SendOOCMessage(player.SessionId.ToString(), message); + _mommiLink.SendOOCMessage(player.Name, message); } public void SendDeadChat(IPlayerSession player, string message) @@ -210,7 +210,7 @@ namespace Content.Server.Chat msg.Channel = ChatChannel.AdminChat; msg.Message = message; - msg.MessageWrap = $"{Loc.GetString("ADMIN")}: {player.SessionId}: {{0}}"; + msg.MessageWrap = $"{Loc.GetString("ADMIN")}: {player.Name}: {{0}}"; _netManager.ServerSendToMany(msg, clients.ToList()); } diff --git a/Content.Server/ConnectionManager.cs b/Content.Server/ConnectionManager.cs new file mode 100644 index 0000000000..c655913943 --- /dev/null +++ b/Content.Server/ConnectionManager.cs @@ -0,0 +1,109 @@ +using System; +using System.Threading.Tasks; +using Content.Server.Database; +using Content.Server.Preferences; +using Content.Shared; +using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.IoC; +using Robust.Shared.Network; + +#nullable enable + +namespace Content.Server +{ + public interface IConnectionManager + { + void Initialize(); + } + + /// + /// Handles various duties like guest username assignment, bans, connection logs, etc... + /// + public sealed class ConnectionManager : IConnectionManager + { + [Dependency] private readonly IServerNetManager _netMgr = default!; + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + public void Initialize() + { + _netMgr.Connecting += NetMgrOnConnecting; + _netMgr.AssignUserIdCallback = AssignUserIdCallback; + // Approval-based IP bans disabled because they don't play well with Happy Eyeballs. + // _netMgr.HandleApprovalCallback = HandleApproval; + } + + /* + private async Task HandleApproval(NetApprovalEventArgs eventArgs) + { + var ban = await _db.GetServerBanByIpAsync(eventArgs.Connection.RemoteEndPoint.Address); + if (ban != null) + { + var expires = "This is a permanent ban."; + if (ban.ExpirationTime is { } expireTime) + { + var duration = expireTime - ban.BanTime; + var utc = expireTime.ToUniversalTime(); + expires = $"This ban is for {duration.TotalMinutes} minutes and will expire at {utc:f} UTC."; + } + var reason = $@"You, or another user of this computer or connection is banned from playing here. +The ban reason is: ""{ban.Reason}"" +{expires}"; + return NetApproval.Deny(reason); + } + + return NetApproval.Allow(); + } + */ + + private async Task NetMgrOnConnecting(NetConnectingArgs e) + { + // Check if banned. + var addr = e.IP.Address; + var userId = e.UserId; + var ban = await _db.GetServerBanAsync(addr, userId); + if (ban != null) + { + var expires = "This is a permanent ban."; + if (ban.ExpirationTime is { } expireTime) + { + var duration = expireTime - ban.BanTime; + var utc = expireTime.ToUniversalTime(); + expires = $"This ban is for {duration.TotalMinutes:N0} minutes and will expire at {utc:f} UTC."; + } + var reason = $@"You, or another user of this computer or connection, are banned from playing here. +The ban reason is: ""{ban.Reason}"" +{expires}"; + e.Deny(reason); + return; + } + + if (!ServerPreferencesManager.ShouldStorePrefs(e.AuthType)) + { + return; + } + + await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr); + await _db.AddConnectionLogAsync(userId, e.UserName, addr); + } + + private async Task AssignUserIdCallback(string name) + { + if (!_cfg.GetCVar(CCVars.GamePersistGuests)) + { + return null; + } + + var userId = await _db.GetAssignedUserIdAsync(name); + if (userId != null) + { + return userId; + } + + var assigned = new NetUserId(Guid.NewGuid()); + await _db.AssignUserIdAsync(name, assigned); + return assigned; + } + } +} diff --git a/Content.Server/Database/ServerBanDef.cs b/Content.Server/Database/ServerBanDef.cs new file mode 100644 index 0000000000..835e45699b --- /dev/null +++ b/Content.Server/Database/ServerBanDef.cs @@ -0,0 +1,41 @@ +using System; +using System.Net; +using Robust.Shared.Network; + +#nullable enable + +namespace Content.Server.Database +{ + public sealed class ServerBanDef + { + public NetUserId? UserId { get; } + public (IPAddress address, int cidrMask)? Address { get; } + + public DateTimeOffset BanTime { get; } + public DateTimeOffset? ExpirationTime { get; } + public string Reason { get; } + public NetUserId? BanningAdmin { get; } + + public ServerBanDef(NetUserId? userId, (IPAddress, int)? address, DateTimeOffset banTime, DateTimeOffset? expirationTime, string reason, NetUserId? banningAdmin) + { + if (userId == null && address == null) + { + throw new ArgumentException("Must have a banned user, banned address, or both."); + } + + 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); + } + + UserId = userId; + Address = address; + BanTime = banTime; + ExpirationTime = expirationTime; + Reason = reason; + BanningAdmin = banningAdmin; + } + } +} diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs new file mode 100644 index 0000000000..051f6432d1 --- /dev/null +++ b/Content.Server/Database/ServerDbBase.cs @@ -0,0 +1,221 @@ +#nullable enable +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Content.Shared.Preferences; +using Microsoft.EntityFrameworkCore; +using Robust.Shared.Maths; +using Robust.Shared.Network; + +namespace Content.Server.Database +{ + public abstract class ServerDbBase + { + public async Task GetPlayerPreferencesAsync(NetUserId userId) + { + await using var db = await GetDb(); + + var prefs = await db.DbContext + .Preference + .Include(p => p.Profiles).ThenInclude(h => h.Jobs) + .Include(p => p.Profiles).ThenInclude(h => h.Antags) + .SingleOrDefaultAsync(p => p.UserId == userId.UserId); + + if (prefs is null) return null; + + var maxSlot = prefs.Profiles.Max(p => p.Slot)+1; + var profiles = new ICharacterProfile[maxSlot]; + foreach (var profile in prefs.Profiles) + { + profiles[profile.Slot] = ConvertProfiles(profile); + } + + return new PlayerPreferences + ( + profiles, + prefs.SelectedCharacterSlot + ); + } + + public async Task SaveSelectedCharacterIndexAsync(NetUserId userId, int index) + { + await using var db = await GetDb(); + + var prefs = await db.DbContext.Preference.SingleAsync(p => p.UserId == userId.UserId); + prefs.SelectedCharacterSlot = index; + + await db.DbContext.SaveChangesAsync(); + } + + public async Task SaveCharacterSlotAsync(NetUserId userId, ICharacterProfile? profile, int slot) + { + if (profile is null) + { + await DeleteCharacterSlotAsync(userId, slot); + return; + } + + await using var db = await GetDb(); + if (!(profile is HumanoidCharacterProfile humanoid)) + { + // TODO: Handle other ICharacterProfile implementations properly + throw new NotImplementedException(); + } + + var entity = ConvertProfiles(humanoid, slot); + + var prefs = await db.DbContext + .Preference + .Include(p => p.Profiles) + .SingleAsync(p => p.UserId == userId.UserId); + + var oldProfile = prefs + .Profiles + .SingleOrDefault(h => h.Slot == entity.Slot); + + if (!(oldProfile is null)) + { + prefs.Profiles.Remove(oldProfile); + } + + prefs.Profiles.Add(entity); + await db.DbContext.SaveChangesAsync(); + } + + private async Task DeleteCharacterSlotAsync(NetUserId userId, int slot) + { + await using var db = await GetDb(); + + db.DbContext + .Preference + .Single(p => p.UserId == userId.UserId) + .Profiles + .RemoveAll(h => h.Slot == slot); + + await db.DbContext.SaveChangesAsync(); + } + + public async Task InitPrefsAsync(NetUserId userId, ICharacterProfile defaultProfile) + { + await using var db = await GetDb(); + + var profile = ConvertProfiles((HumanoidCharacterProfile) defaultProfile, 0); + var prefs = new Preference + { + UserId = userId.UserId, + SelectedCharacterSlot = 0 + }; + + prefs.Profiles.Add(profile); + + db.DbContext.Preference.Add(prefs); + + await db.DbContext.SaveChangesAsync(); + + return new PlayerPreferences(new []{defaultProfile}, 0); + } + + private static HumanoidCharacterProfile ConvertProfiles(Profile profile) + { + var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority); + var antags = profile.Antags.Select(a => a.AntagName); + return new HumanoidCharacterProfile( + profile.CharacterName, + profile.Age, + profile.Sex == "Male" ? Sex.Male : Sex.Female, + new HumanoidCharacterAppearance + ( + profile.HairName, + Color.FromHex(profile.HairColor), + profile.FacialHairName, + Color.FromHex(profile.FacialHairColor), + Color.FromHex(profile.EyeColor), + Color.FromHex(profile.SkinColor) + ), + jobs, + (PreferenceUnavailableMode) profile.PreferenceUnavailable, + antags.ToList() + ); + } + + private static Profile ConvertProfiles(HumanoidCharacterProfile humanoid, int slot) + { + var appearance = (HumanoidCharacterAppearance) humanoid.CharacterAppearance; + + var entity = new Profile + { + CharacterName = humanoid.Name, + Age = humanoid.Age, + Sex = humanoid.Sex.ToString(), + HairName = appearance.HairStyleName, + HairColor = appearance.HairColor.ToHex(), + FacialHairName = appearance.FacialHairStyleName, + FacialHairColor = appearance.FacialHairColor.ToHex(), + EyeColor = appearance.EyeColor.ToHex(), + SkinColor = appearance.SkinColor.ToHex(), + Slot = slot, + PreferenceUnavailable = (DbPreferenceUnavailableMode) humanoid.PreferenceUnavailable + }; + entity.Jobs.AddRange( + humanoid.JobPriorities + .Where(j => j.Value != JobPriority.Never) + .Select(j => new Job {JobName = j.Key, Priority = (DbJobPriority) j.Value}) + ); + entity.Antags.AddRange( + humanoid.AntagPreferences + .Select(a => new Antag {AntagName = a}) + ); + + return entity; + } + + public async Task GetAssignedUserIdAsync(string name) + { + await using var db = await GetDb(); + + var assigned = await db.DbContext.AssignedUserId.SingleOrDefaultAsync(p => p.UserName == name); + return assigned?.UserId is { } g ? new NetUserId(g) : default(NetUserId?); + } + + public async Task AssignUserIdAsync(string name, NetUserId netUserId) + { + await using var db = await GetDb(); + + db.DbContext.AssignedUserId.Add(new AssignedUserId + { + UserId = netUserId.UserId, + UserName = name + }); + + await db.DbContext.SaveChangesAsync(); + } + + /* + * BAN STUFF + */ + public abstract Task GetServerBanAsync(IPAddress? address, NetUserId? userId); + public abstract Task AddServerBanAsync(ServerBanDef serverBan); + + /* + * PLAYER RECORDS + */ + public abstract Task UpdatePlayerRecord(NetUserId userId, string userName, IPAddress address); + + /* + * CONNECTION LOG + */ + public abstract Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address); + + + protected abstract Task GetDb(); + + protected abstract class DbGuard : IAsyncDisposable + { + public abstract ServerDbContext DbContext { get; } + + public abstract ValueTask DisposeAsync(); + } + + } +} diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs new file mode 100644 index 0000000000..39ffdf39f6 --- /dev/null +++ b/Content.Server/Database/ServerDbManager.cs @@ -0,0 +1,245 @@ +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using Content.Shared; +using Content.Shared.Preferences; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Npgsql; +using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.Interfaces.Log; +using Robust.Shared.Interfaces.Resources; +using Robust.Shared.IoC; +using Robust.Shared.Network; +using MSLogLevel = Microsoft.Extensions.Logging.LogLevel; +using LogLevel = Robust.Shared.Log.LogLevel; + +#nullable enable + +namespace Content.Server.Database +{ + public interface IServerDbManager + { + void Init(); + + // Preferences + Task InitPrefsAsync(NetUserId userId, ICharacterProfile defaultProfile); + Task SaveSelectedCharacterIndexAsync(NetUserId userId, int index); + Task SaveCharacterSlotAsync(NetUserId userId, ICharacterProfile profile, int slot); + Task GetPlayerPreferencesAsync(NetUserId userId); + + // Username assignment (for guest accounts, so they persist GUID) + Task AssignUserIdAsync(string name, NetUserId userId); + Task GetAssignedUserIdAsync(string name); + + // Ban stuff + Task GetServerBanAsync(IPAddress? address, NetUserId? userId); + Task AddServerBanAsync(ServerBanDef serverBan); + + // Player records + Task UpdatePlayerRecordAsync(NetUserId userId, string userName, IPAddress address); + + // Connection log + Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address); + } + + public sealed class ServerDbManager : IServerDbManager + { + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IResourceManager _res = default!; + [Dependency] private readonly ILogManager _logMgr = default!; + + private ServerDbBase _db = default!; + private LoggingProvider _msLogProvider = default!; + private ILoggerFactory _msLoggerFactory = default!; + + + public void Init() + { + _msLogProvider = new LoggingProvider(_logMgr); + _msLoggerFactory = LoggerFactory.Create(builder => + { + builder.AddProvider(_msLogProvider); + }); + + var engine = _cfg.GetCVar(CCVars.DatabaseEngine).ToLower(); + switch (engine) + { + case "sqlite": + var options = CreateSqliteOptions(); + _db = new ServerDbSqlite(options); + break; + case "postgres": + options = CreatePostgresOptions(); + _db = new ServerDbPostgres(options); + break; + default: + throw new InvalidDataException("Unknown database engine {engine}."); + } + } + + public Task InitPrefsAsync(NetUserId userId, ICharacterProfile defaultProfile) + { + return _db.InitPrefsAsync(userId, defaultProfile); + } + + public Task SaveSelectedCharacterIndexAsync(NetUserId userId, int index) + { + return _db.SaveSelectedCharacterIndexAsync(userId, index); + } + + public Task SaveCharacterSlotAsync(NetUserId userId, ICharacterProfile profile, int slot) + { + return _db.SaveCharacterSlotAsync(userId, profile, slot); + } + + public Task GetPlayerPreferencesAsync(NetUserId userId) + { + return _db.GetPlayerPreferencesAsync(userId); + } + + public Task AssignUserIdAsync(string name, NetUserId userId) + { + return _db.AssignUserIdAsync(name, userId); + } + + public Task GetAssignedUserIdAsync(string name) + { + return _db.GetAssignedUserIdAsync(name); + } + + public Task GetServerBanAsync(IPAddress? address, NetUserId? userId) + { + return _db.GetServerBanAsync(address, userId); + } + + public Task AddServerBanAsync(ServerBanDef serverBan) + { + return _db.AddServerBanAsync(serverBan); + } + + public Task UpdatePlayerRecordAsync(NetUserId userId, string userName, IPAddress address) + { + return _db.UpdatePlayerRecord(userId, userName, address); + } + + public Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address) + { + return _db.AddConnectionLogAsync(userId, userName, address); + } + + private DbContextOptions CreatePostgresOptions() + { + var host = _cfg.GetCVar(CCVars.DatabasePgHost); + var port = _cfg.GetCVar(CCVars.DatabasePgPort); + var db = _cfg.GetCVar(CCVars.DatabasePgDatabase); + var user = _cfg.GetCVar(CCVars.DatabasePgUsername); + var pass = _cfg.GetCVar(CCVars.DatabasePgPassword); + + var builder = new DbContextOptionsBuilder(); + var connectionString = new NpgsqlConnectionStringBuilder + { + Host = host, + Port = port, + Database = db, + Username = user, + Password = pass + }.ConnectionString; + builder.UseNpgsql(connectionString); + SetupLogging(builder); + return builder.Options; + } + + private DbContextOptions CreateSqliteOptions() + { + var builder = new DbContextOptionsBuilder(); + + var configPreferencesDbPath = _cfg.GetCVar(CCVars.DatabaseSqliteDbPath); + var inMemory = _res.UserData.RootDir == null; + + SqliteConnection connection; + if (!inMemory) + { + var finalPreferencesDbPath = Path.Combine(_res.UserData.RootDir!, configPreferencesDbPath); + connection = new SqliteConnection($"Data Source={finalPreferencesDbPath}"); + } + else + { + connection = new SqliteConnection("Data Source=:memory:"); + // When using an in-memory DB we have to open it manually + // so EFCore doesn't open, close and wipe it. + connection.Open(); + } + + builder.UseSqlite(connection); + SetupLogging(builder); + return builder.Options; + } + + private void SetupLogging(DbContextOptionsBuilder builder) + { + builder.UseLoggerFactory(_msLoggerFactory); + } + + private sealed class LoggingProvider : ILoggerProvider + { + private readonly ILogManager _logManager; + + public LoggingProvider(ILogManager logManager) + { + _logManager = logManager; + } + + public void Dispose() + { + } + + public ILogger CreateLogger(string categoryName) + { + return new MSLogger(_logManager.GetSawmill("db.ef")); + } + } + + private sealed class MSLogger : ILogger + { + private readonly ISawmill _sawmill; + + public MSLogger(ISawmill sawmill) + { + _sawmill = sawmill; + } + + public void Log(MSLogLevel logLevel, EventId eventId, TState state, Exception exception, + Func formatter) + { + var lvl = logLevel switch + { + MSLogLevel.Trace => LogLevel.Debug, + MSLogLevel.Debug => LogLevel.Debug, + // EFCore feels the need to log individual DB commands as "Information" so I'm slapping debug on it. + MSLogLevel.Information => LogLevel.Debug, + MSLogLevel.Warning => LogLevel.Warning, + MSLogLevel.Error => LogLevel.Error, + MSLogLevel.Critical => LogLevel.Fatal, + MSLogLevel.None => LogLevel.Debug, + _ => LogLevel.Debug + }; + + _sawmill.Log(lvl, formatter(state, exception)); + } + + public bool IsEnabled(MSLogLevel logLevel) + { + return true; + } + + public IDisposable BeginScope(TState state) + { + // TODO: this + return null!; + } + } + } +} diff --git a/Content.Server/Database/ServerDbPostgres.cs b/Content.Server/Database/ServerDbPostgres.cs new file mode 100644 index 0000000000..b3ac0388cb --- /dev/null +++ b/Content.Server/Database/ServerDbPostgres.cs @@ -0,0 +1,184 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Robust.Shared.Network; + +#nullable enable + +namespace Content.Server.Database +{ + public sealed class ServerDbPostgres : ServerDbBase + { + private readonly DbContextOptions _options; + private readonly Task _dbReadyTask; + + public ServerDbPostgres(DbContextOptions options) + { + _options = options; + + _dbReadyTask = Task.Run(async () => + { + await using var ctx = new PostgresServerDbContext(_options); + try + { + await ctx.Database.MigrateAsync(); + } + finally + { + await ctx.DisposeAsync(); + } + }); + } + + public override async Task GetServerBanAsync(IPAddress? address, NetUserId? userId) + { + if (address == null && userId == null) + { + throw new ArgumentException("Address and userId cannot both be null"); + } + + await using var db = await GetDbImpl(); + + var query = db.PgDbContext.Ban + .Include(p => p.Unban) + .Where(p => p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.Now)); + + if (userId is { } uid) + { + if (address == null) + { + // Only have a user ID. + query = query.Where(p => p.UserId == uid.UserId); + } + else + { + // Have both user ID and IP address. + query = query.Where(p => + (p.Address != null && EF.Functions.ContainsOrEqual(p.Address.Value, address)) + || p.UserId == uid.UserId); + } + } + else + { + // Only have a connecting address. + query = query.Where( + p => p.Address != null && EF.Functions.ContainsOrEqual(p.Address.Value, address)); + } + + var ban = await query.FirstOrDefaultAsync(); + + return ConvertBan(ban); + } + + private static ServerBanDef? ConvertBan(PostgresServerBan? ban) + { + if (ban == null) + { + return null; + } + + NetUserId? uid = null; + if (ban.UserId is {} guid) + { + uid = new NetUserId(guid); + } + + NetUserId? aUid = null; + if (ban.BanningAdmin is {} aGuid) + { + aUid = new NetUserId(aGuid); + } + + return new ServerBanDef( + uid, + ban.Address, + ban.BanTime, + ban.ExpirationTime, + ban.Reason, + aUid); + } + + public override async Task AddServerBanAsync(ServerBanDef serverBan) + { + await using var db = await GetDbImpl(); + + db.PgDbContext.Ban.Add(new PostgresServerBan + { + Address = serverBan.Address, + Reason = serverBan.Reason, + BanningAdmin = serverBan.BanningAdmin?.UserId, + BanTime = serverBan.BanTime.UtcDateTime, + ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, + UserId = serverBan.UserId?.UserId + }); + + await db.PgDbContext.SaveChangesAsync(); + } + + public override async Task UpdatePlayerRecord(NetUserId userId, string userName, IPAddress address) + { + await using var db = await GetDbImpl(); + + var record = await db.PgDbContext.Player.SingleOrDefaultAsync(p => p.UserId == userId.UserId); + if (record == null) + { + db.PgDbContext.Player.Add(record = new PostgresPlayer + { + FirstSeenTime = DateTime.UtcNow, + UserId = userId.UserId, + }); + } + + record.LastSeenTime = DateTime.UtcNow; + record.LastSeenAddress = address; + record.LastSeenUserName = userName; + + await db.PgDbContext.SaveChangesAsync(); + } + + public override async Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address) + { + await using var db = await GetDbImpl(); + + db.PgDbContext.ConnectionLog.Add(new PostgresConnectionLog + { + Address = address, + Time = DateTime.UtcNow, + UserId = userId.UserId, + UserName = userName + }); + + await db.PgDbContext.SaveChangesAsync(); + } + + private async Task GetDbImpl() + { + await _dbReadyTask; + + return new DbGuardImpl(new PostgresServerDbContext(_options)); + } + + protected override async Task GetDb() + { + return await GetDbImpl(); + } + + private sealed class DbGuardImpl : DbGuard + { + public DbGuardImpl(PostgresServerDbContext dbC) + { + PgDbContext = dbC; + } + + public PostgresServerDbContext PgDbContext { get; } + public override ServerDbContext DbContext => PgDbContext; + + public override ValueTask DisposeAsync() + { + return DbContext.DisposeAsync(); + } + } + } +} diff --git a/Content.Server/Database/ServerDbSqlite.cs b/Content.Server/Database/ServerDbSqlite.cs new file mode 100644 index 0000000000..9ecefb0fc2 --- /dev/null +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -0,0 +1,192 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Content.Server.Preferences; +using Content.Server.Utility; +using Microsoft.EntityFrameworkCore; +using Robust.Shared.Network; + +#nullable enable + +namespace Content.Server.Database +{ + /// + /// Provides methods to retrieve and update character preferences. + /// Don't use this directly, go through instead. + /// + public sealed class ServerDbSqlite : ServerDbBase + { + // For SQLite we use a single DB context via SQLite. + // This doesn't allow concurrent access so that's what the semaphore is for. + // That said, this is bloody SQLite, I don't even think EFCore bothers to truly async it. + private readonly SemaphoreSlim _prefsSemaphore = new SemaphoreSlim(1, 1); + + private readonly Task _dbReadyTask; + private readonly SqliteServerDbContext _prefsCtx; + + public ServerDbSqlite(DbContextOptions options) + { + _prefsCtx = new SqliteServerDbContext(options); + + _dbReadyTask = Task.Run(() => _prefsCtx.Database.Migrate()); + } + + public override async Task GetServerBanAsync(IPAddress? address, NetUserId? userId) + { + 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 bans = await db.SqliteDbContext.Ban + .Include(p => p.Unban) + .Where(p => p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow)) + .ToListAsync(); + + foreach (var ban in bans) + { + if (address != null && ban.Address != null && address.IsInSubnet(ban.Address)) + { + return ConvertBan(ban); + } + + if (userId is { } id && ban.UserId == id.UserId) + { + return ConvertBan(ban); + } + } + + return null; + } + + public override async Task AddServerBanAsync(ServerBanDef serverBan) + { + await using var db = await GetDbImpl(); + + string? addrStr = null; + if (serverBan.Address is { } addr) + { + addrStr = $"{addr.address}/{addr.cidrMask}"; + } + + db.SqliteDbContext.Ban.Add(new SqliteServerBan + { + Address = addrStr, + Reason = serverBan.Reason, + BanningAdmin = serverBan.BanningAdmin?.UserId, + BanTime = serverBan.BanTime.UtcDateTime, + ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, + UserId = serverBan.UserId?.UserId + }); + + await db.SqliteDbContext.SaveChangesAsync(); + } + + public override async Task UpdatePlayerRecord(NetUserId userId, string userName, IPAddress address) + { + await using var db = await GetDbImpl(); + + var record = await db.SqliteDbContext.Player.SingleOrDefaultAsync(p => p.UserId == userId.UserId); + if (record == null) + { + db.SqliteDbContext.Player.Add(record = new SqlitePlayer + { + FirstSeenTime = DateTime.UtcNow, + UserId = userId.UserId, + }); + } + + record.LastSeenTime = DateTime.UtcNow; + record.LastSeenAddress = address.ToString(); + record.LastSeenUserName = userName; + + await db.SqliteDbContext.SaveChangesAsync(); + } + + private static ServerBanDef? ConvertBan(SqliteServerBan? ban) + { + if (ban == null) + { + return null; + } + + NetUserId? uid = null; + if (ban.UserId is {} guid) + { + uid = new NetUserId(guid); + } + + NetUserId? aUid = null; + if (ban.BanningAdmin is {} aGuid) + { + aUid = new NetUserId(aGuid); + } + + (IPAddress, int)? addrTuple = null; + if (ban.Address != null) + { + var idx = ban.Address.IndexOf('/', StringComparison.Ordinal); + addrTuple = (IPAddress.Parse(ban.Address.AsSpan(0, idx)), + int.Parse(ban.Address.AsSpan(idx + 1), provider: CultureInfo.InvariantCulture)); + } + + return new ServerBanDef( + uid, + addrTuple, + ban.BanTime, + ban.ExpirationTime, + ban.Reason, + aUid); + } + + public override async Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address) + { + await using var db = await GetDbImpl(); + + db.SqliteDbContext.ConnectionLog.Add(new SqliteConnectionLog + { + Address = address.ToString(), + Time = DateTime.UtcNow, + UserId = userId.UserId, + UserName = userName + }); + + await db.SqliteDbContext.SaveChangesAsync(); + } + + + private async Task GetDbImpl() + { + await _dbReadyTask; + await _prefsSemaphore.WaitAsync(); + + return new DbGuardImpl(this); + } + + protected override async Task GetDb() + { + return await GetDbImpl(); + } + + private sealed class DbGuardImpl : DbGuard + { + private readonly ServerDbSqlite _db; + + public DbGuardImpl(ServerDbSqlite db) + { + _db = db; + } + + public override ServerDbContext DbContext => _db._prefsCtx; + public SqliteServerDbContext SqliteDbContext => _db._prefsCtx; + + public override ValueTask DisposeAsync() + { + _db._prefsSemaphore.Release(); + return default; + } + } + } +} diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index c42c6a5171..64eb99018c 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -1,6 +1,7 @@ using Content.Server.AI.Utility.Considerations; using Content.Server.AI.WorldState; using Content.Server.Body.Network; +using Content.Server.Database; using Content.Server.GameObjects.Components.Mobs.Speech; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.Interfaces; @@ -62,7 +63,9 @@ namespace Content.Server var logManager = IoCManager.Resolve(); logManager.GetSawmill("Storage").Level = LogLevel.Info; - IoCManager.Resolve().StartInit(); + IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Init(); + IoCManager.Resolve().Init(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); @@ -72,7 +75,6 @@ namespace Content.Server { base.PostInit(); - IoCManager.Resolve().FinishInit(); _gameTicker.Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); diff --git a/Content.Server/GameObjects/Components/Medical/CloningPodComponent.cs b/Content.Server/GameObjects/Components/Medical/CloningPodComponent.cs index 13958ca08d..496c9f37b2 100644 --- a/Content.Server/GameObjects/Components/Medical/CloningPodComponent.cs +++ b/Content.Server/GameObjects/Components/Medical/CloningPodComponent.cs @@ -22,6 +22,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Network; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -144,7 +145,7 @@ namespace Content.Server.GameObjects.Components.Medical UserInterface?.Open(actor.playerSession); } - private async void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) + private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) { if (!(obj.Message is CloningPodUiButtonPressedMessage message)) return; @@ -167,11 +168,10 @@ namespace Content.Server.GameObjects.Components.Medical var mob = _entityManager.SpawnEntity("HumanMob_Content", Owner.Transform.MapPosition); - var client = _playerManager - .GetPlayersBy(x => x.SessionId == mind.SessionId).First(); - mob.GetComponent() - .UpdateFromProfile(GetPlayerProfileAsync(client.Name).Result); - mob.Name = GetPlayerProfileAsync(client.Name).Result.Name; + var client = _playerManager.GetSessionByUserId(mind.UserId!.Value); + var profile = GetPlayerProfileAsync(client.UserId); + mob.GetComponent().UpdateFromProfile(profile); + mob.Name = profile.Name; _bodyContainer.Insert(mob); _capturedMind = mind; @@ -209,10 +209,9 @@ namespace Content.Server.GameObjects.Components.Medical } - private async Task GetPlayerProfileAsync(string username) + private HumanoidCharacterProfile GetPlayerProfileAsync(NetUserId userId) { - return (HumanoidCharacterProfile) (await _prefsManager.GetPreferencesAsync(username)) - .SelectedCharacter; + return (HumanoidCharacterProfile) _prefsManager.GetPreferences(userId).SelectedCharacter; } private void HandleGhostReturn(GhostComponent.GhostReturnMessage message) diff --git a/Content.Server/GameObjects/EntitySystems/SignalLinkerSystem.cs b/Content.Server/GameObjects/EntitySystems/SignalLinkerSystem.cs index de07fe0a8c..a9c58b076a 100644 --- a/Content.Server/GameObjects/EntitySystems/SignalLinkerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/SignalLinkerSystem.cs @@ -16,16 +16,16 @@ namespace Content.Server.GameObjects.EntitySystems { public class SignalLinkerSystem : EntitySystem { - private Dictionary _transmitters; + private Dictionary _transmitters; public override void Initialize() { base.Initialize(); - _transmitters = new Dictionary(); + _transmitters = new Dictionary(); } - public void SignalLinkerKeybind(NetSessionId id, bool? enable) + public void SignalLinkerKeybind(NetUserId id, bool? enable) { if (enable == null) { @@ -66,7 +66,7 @@ namespace Content.Server.GameObjects.EntitySystems private bool HandleUse(ICommonSession session, EntityCoordinates coords, EntityUid uid) { - if (!_transmitters.TryGetValue(session.SessionId, out var signalTransmitter)) + if (!_transmitters.TryGetValue(session.UserId, out var signalTransmitter)) { return false; } @@ -86,7 +86,7 @@ namespace Content.Server.GameObjects.EntitySystems if (entity.TryGetComponent(out var transmitter)) { - _transmitters[session.SessionId] = transmitter.GetSignal(session.AttachedEntity); + _transmitters[session.UserId] = transmitter.GetSignal(session.AttachedEntity); return true; } @@ -129,7 +129,7 @@ namespace Content.Server.GameObjects.EntitySystems return; } - system.SignalLinkerKeybind(player.SessionId, enable); + system.SignalLinkerKeybind(player.UserId, enable); } } } diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs index ddde553722..a91436220c 100644 --- a/Content.Server/GameTicking/GamePreset.cs +++ b/Content.Server/GameTicking/GamePreset.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Content.Shared.Preferences; using Robust.Server.Interfaces.Player; +using Robust.Shared.Network; namespace Content.Server.GameTicking { @@ -13,6 +14,6 @@ namespace Content.Server.GameTicking public virtual string ModeTitle => "Sandbox"; public virtual string Description => "Secret!"; public virtual bool DisallowLateJoin => false; - public Dictionary readyProfiles; + public Dictionary readyProfiles; } } diff --git a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs index 43dd52eaa1..9021cd4fef 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs @@ -76,11 +76,11 @@ namespace Content.Server.GameTicking.GamePresets foreach (var player in list) { - if (!readyProfiles.ContainsKey(player.Name)) + if (!readyProfiles.ContainsKey(player.UserId)) { continue; } - var profile = readyProfiles[player.Name]; + var profile = readyProfiles[player.UserId]; if (profile.AntagPreferences.Contains(_prototypeManager.Index(TraitorID).Name)) { prefList.Add(player); diff --git a/Content.Server/GameTicking/GameTicker.JobController.cs b/Content.Server/GameTicking/GameTicker.JobController.cs index 271939e076..0d57f10768 100644 --- a/Content.Server/GameTicking/GameTicker.JobController.cs +++ b/Content.Server/GameTicking/GameTicker.JobController.cs @@ -6,6 +6,7 @@ using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Server.Interfaces.Player; using Robust.Shared.Localization; +using Robust.Shared.Network; using Robust.Shared.Random; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; @@ -19,7 +20,7 @@ namespace Content.Server.GameTicking private readonly Dictionary _spawnedPositions = new Dictionary(); private Dictionary AssignJobs(List available, - Dictionary profiles) + Dictionary profiles) { // Calculate positions available round-start for each job. var availablePositions = GetBasePositions(true); @@ -38,7 +39,7 @@ namespace Content.Server.GameTicking var candidates = available .Select(player => { - var profile = profiles[player.Name]; + var profile = profiles[player.UserId]; var availableJobs = profile.JobPriorities .Where(j => diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 70a352e630..ad39d3d26d 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Content.Server.GameObjects.Components.Access; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; @@ -34,7 +33,6 @@ using Robust.Server.Interfaces.Maps; using Robust.Server.Interfaces.Player; using Robust.Server.Player; using Robust.Server.ServerStatus; -using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; @@ -222,7 +220,7 @@ namespace Content.Server.GameTicking } } - public async void StartRound(bool force = false) + public void StartRound(bool force = false) { DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby); Logger.InfoS("ticker", "Starting round!"); @@ -244,16 +242,16 @@ namespace Content.Server.GameTicking RoundLengthMetric.Set(0); // Get the profiles for each player for easier lookup. - var profiles = (await _prefsManager.GetSelectedProfilesForPlayersAsync( + var profiles = _prefsManager.GetSelectedProfilesForPlayers( readyPlayers - .Select(p => p.Name).ToList())) + .Select(p => p.UserId).ToList()) .ToDictionary(p => p.Key, p => (HumanoidCharacterProfile) p.Value); foreach (var readyPlayer in readyPlayers) { - if (!profiles.ContainsKey(readyPlayer.Name)) + if (!profiles.ContainsKey(readyPlayer.UserId)) { - profiles.Add(readyPlayer.Name, HumanoidCharacterProfile.Default()); + profiles.Add(readyPlayer.UserId, HumanoidCharacterProfile.Default()); } } @@ -267,7 +265,7 @@ namespace Content.Server.GameTicking continue; } - var profile = profiles[player.Name]; + var profile = profiles[player.UserId]; if (profile.PreferenceUnavailable == PreferenceUnavailableMode.SpawnAsOverflow) { assignedJobs.Add(player, OverflowJob); @@ -277,7 +275,7 @@ namespace Content.Server.GameTicking // Spawn everybody in! foreach (var (player, job) in assignedJobs) { - SpawnPlayer(player, profiles[player.Name], job, false); + SpawnPlayer(player, profiles[player.UserId], job, false); } // Time to start the preset. @@ -320,9 +318,10 @@ namespace Content.Server.GameTicking IoCManager.Resolve().ServerSendToAll(msg); } - private async Task GetPlayerProfileAsync(IPlayerSession p) => - (HumanoidCharacterProfile) (await _prefsManager.GetPreferencesAsync(p.SessionId.Username)) - .SelectedCharacter; + private HumanoidCharacterProfile GetPlayerProfile(IPlayerSession p) + { + return (HumanoidCharacterProfile) _prefsManager.GetPreferences(p.UserId).SelectedCharacter; + } public void EndRound(string roundEndText = "") { @@ -373,7 +372,7 @@ namespace Content.Server.GameTicking if (LobbyEnabled) _playerJoinLobby(targetPlayer); else - SpawnPlayerAsync(targetPlayer); + SpawnPlayer(targetPlayer); } public void MakeObserve(IPlayerSession player) @@ -389,13 +388,23 @@ namespace Content.Server.GameTicking { if (!_playersInLobby.ContainsKey(player)) return; - SpawnPlayerAsync(player, jobId); + if (!_prefsManager.HavePreferencesLoaded(player)) + { + return; + } + + SpawnPlayer(player, jobId); } public void ToggleReady(IPlayerSession player, bool ready) { if (!_playersInLobby.ContainsKey(player)) return; + if (!_prefsManager.HavePreferencesLoaded(player)) + { + return; + } + var status = ready ? PlayerStatus.Ready : PlayerStatus.NotReady; _playersInLobby[player] = ready ? PlayerStatus.Ready : PlayerStatus.NotReady; _netManager.ServerSendMessage(_getStatusMsg(player), player.ConnectedClient); @@ -703,7 +712,7 @@ namespace Content.Server.GameTicking case SessionStatus.Connected: { - _chatManager.DispatchServerAnnouncement($"Player {args.Session.SessionId} joined server!"); + _chatManager.DispatchServerAnnouncement($"Player {args.Session.Name} joined server!"); if (LobbyEnabled && _roundStartCountdownHasNotStartedYetDueToNoPlayers) { @@ -716,6 +725,8 @@ namespace Content.Server.GameTicking case SessionStatus.InGame: { + _prefsManager.OnClientConnected(session); + var data = session.ContentData(); if (data.Mind == null) { @@ -725,13 +736,14 @@ namespace Content.Server.GameTicking return; } - SpawnPlayerAsync(session); + + SpawnWaitPrefs(); } else { if (data.Mind.CurrentEntity == null) { - SpawnPlayerAsync(session); + SpawnWaitPrefs(); } else { @@ -747,11 +759,18 @@ namespace Content.Server.GameTicking { if (_playersInLobby.ContainsKey(session)) _playersInLobby.Remove(session); - _chatManager.DispatchServerAnnouncement($"Player {args.Session.SessionId} left server!"); + _chatManager.DispatchServerAnnouncement($"Player {args.Session} left server!"); ServerEmptyUpdateRestartCheck(); + _prefsManager.OnClientDisconnected(session); break; } } + + async void SpawnWaitPrefs() + { + await _prefsManager.WaitPreferencesLoaded(session); + SpawnPlayer(session); + } } /// @@ -785,11 +804,9 @@ namespace Content.Server.GameTicking }, _updateShutdownCts.Token); } - private async void SpawnPlayerAsync(IPlayerSession session, string jobId = null, bool lateJoin = true) + private void SpawnPlayer(IPlayerSession session, string jobId = null, bool lateJoin = true) { - var character = (HumanoidCharacterProfile) (await _prefsManager - .GetPreferencesAsync(session.SessionId.Username)) - .SelectedCharacter; + var character = GetPlayerProfile(session); SpawnPlayer(session, character, jobId, lateJoin); } @@ -809,7 +826,7 @@ namespace Content.Server.GameTicking var data = session.ContentData(); data.WipeMind(); - data.Mind = new Mind(session.SessionId) + data.Mind = new Mind(session.UserId) { CharacterName = character.Name }; @@ -866,17 +883,15 @@ namespace Content.Server.GameTicking _manifest.Add(new ManifestEntry(characterName, jobId)); } - private async void _spawnObserver(IPlayerSession session) + private void _spawnObserver(IPlayerSession session) { _playerJoinGame(session); - var name = (await _prefsManager - .GetPreferencesAsync(session.SessionId.Username)) - .SelectedCharacter.Name; + var name = GetPlayerProfile(session).Name; var data = session.ContentData(); data.WipeMind(); - data.Mind = new Mind(session.SessionId); + data.Mind = new Mind(session.UserId); var mob = _spawnObserverMob(); mob.Name = name; @@ -888,7 +903,6 @@ namespace Content.Server.GameTicking { _playersInLobby.Add(session, PlayerStatus.NotReady); - _prefsManager.OnClientConnected(session); _netManager.ServerSendMessage(_netManager.CreateNetMessage(), session.ConnectedClient); _netManager.ServerSendMessage(_getStatusMsg(session), session.ConnectedClient); _netManager.ServerSendMessage(GetInfoMsg(), session.ConnectedClient); @@ -907,11 +921,11 @@ namespace Content.Server.GameTicking private MsgTickerLobbyReady GetPlayerStatus() { var msg = _netManager.CreateNetMessage(); - msg.PlayerStatus = new Dictionary(); + msg.PlayerStatus = new Dictionary(); foreach (var player in _playersInLobby.Keys) { _playersInLobby.TryGetValue(player, out var status); - msg.PlayerStatus.Add(player.SessionId, status); + msg.PlayerStatus.Add(player.UserId, status); } return msg; } @@ -919,9 +933,9 @@ namespace Content.Server.GameTicking private MsgTickerLobbyReady GetStatusSingle(IPlayerSession player, PlayerStatus status) { var msg = _netManager.CreateNetMessage(); - msg.PlayerStatus = new Dictionary + msg.PlayerStatus = new Dictionary { - { player.SessionId, status } + { player.UserId, status } }; return msg; } @@ -967,7 +981,7 @@ The current game mode is: [color=white]{0}[/color]. _netManager.ServerSendToMany(infoMsg, _playersInLobby.Keys.Select(p => p.ConnectedClient).ToList()); } - private GamePreset MakeGamePreset(Dictionary readyProfiles) + private GamePreset MakeGamePreset(Dictionary readyProfiles) { var preset = _dynamicTypeFactory.CreateInstance(_presetType ?? typeof(PresetSandbox)); preset.readyProfiles = readyProfiles; diff --git a/Content.Server/GameTicking/GameTickerBase.cs b/Content.Server/GameTicking/GameTickerBase.cs index 917be07cd8..178000aa80 100644 --- a/Content.Server/GameTicking/GameTickerBase.cs +++ b/Content.Server/GameTicking/GameTickerBase.cs @@ -31,7 +31,7 @@ namespace Content.Server.GameTicking { // Always make sure the client has player data. Mind gets assigned on spawn. if (session.Data.ContentDataUncast == null) - session.Data.ContentDataUncast = new PlayerData(session.SessionId); + session.Data.ContentDataUncast = new PlayerData(session.UserId); // timer time must be > tick length Timer.Spawn(0, args.Session.JoinGame); diff --git a/Content.Server/GameTicking/GameTickerCommands.cs b/Content.Server/GameTicking/GameTickerCommands.cs index 860f172a1a..9d017a4bb5 100644 --- a/Content.Server/GameTicking/GameTickerCommands.cs +++ b/Content.Server/GameTicking/GameTickerCommands.cs @@ -130,7 +130,7 @@ namespace Content.Server.GameTicking var playerMgr = IoCManager.Resolve(); var ticker = IoCManager.Resolve(); - NetSessionId sessionId; + NetUserId userId; if (args.Length == 0) { if (player == null) @@ -139,16 +139,17 @@ namespace Content.Server.GameTicking return; } - sessionId = player.SessionId; + userId = player.UserId; } - else + else if (!playerMgr.TryGetUserId(args[0], out userId)) { - sessionId = new NetSessionId(args[0]); + shell.SendText(player, "Unknown player"); + return; } - if (!playerMgr.TryGetSessionById(sessionId, out var targetPlayer)) + if (!playerMgr.TryGetSessionById(userId, out var targetPlayer)) { - if (!playerMgr.TryGetPlayerData(sessionId, out var data)) + if (!playerMgr.TryGetPlayerData(userId, out var data)) { shell.SendText(player, "Unknown player"); return; diff --git a/Content.Server/Interfaces/IServerPreferencesManager.cs b/Content.Server/Interfaces/IServerPreferencesManager.cs index 676a16f67f..ef8256d25f 100644 --- a/Content.Server/Interfaces/IServerPreferencesManager.cs +++ b/Content.Server/Interfaces/IServerPreferencesManager.cs @@ -2,15 +2,21 @@ using System.Collections.Generic; using System.Threading.Tasks; using Content.Shared.Preferences; using Robust.Server.Interfaces.Player; +using Robust.Shared.Network; namespace Content.Server.Interfaces { public interface IServerPreferencesManager { - void FinishInit(); + void Init(); + void OnClientConnected(IPlayerSession session); - Task GetPreferencesAsync(string username); - Task>> GetSelectedProfilesForPlayersAsync(List usernames); - void StartInit(); + void OnClientDisconnected(IPlayerSession session); + + bool HavePreferencesLoaded(IPlayerSession session); + Task WaitPreferencesLoaded(IPlayerSession session); + + PlayerPreferences GetPreferences(NetUserId userId); + IEnumerable> GetSelectedProfilesForPlayers(List userIds); } } diff --git a/Content.Server/Mobs/Commands.cs b/Content.Server/Mobs/Commands.cs index 81efc2a4eb..6d64578411 100644 --- a/Content.Server/Mobs/Commands.cs +++ b/Content.Server/Mobs/Commands.cs @@ -29,12 +29,12 @@ namespace Content.Server.Mobs } var mgr = IoCManager.Resolve(); - if (mgr.TryGetPlayerData(new NetSessionId(args[0]), out var data)) + if (mgr.TryGetSessionByUsername(args[0], out var data)) { var mind = data.ContentData().Mind; var builder = new StringBuilder(); - builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.SessionId, mind.OwnedMob?.Owner?.Uid); + builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedMob?.Owner?.Uid); foreach (var role in mind.AllRoles) { builder.AppendFormat("{0} ", role.Name); @@ -68,7 +68,7 @@ namespace Content.Server.Mobs } var mgr = IoCManager.Resolve(); - if (mgr.TryGetPlayerData(new NetSessionId(args[0]), out var data)) + if (mgr.TryGetPlayerDataByUsername(args[0], out var data)) { var mind = data.ContentData().Mind; var role = new Job(mind, _prototypeManager.Index(args[1])); @@ -100,7 +100,7 @@ namespace Content.Server.Mobs } var mgr = IoCManager.Resolve(); - if (mgr.TryGetPlayerData(new NetSessionId(args[0]), out var data)) + if (mgr.TryGetPlayerDataByUsername(args[0], out var data)) { var mind = data.ContentData().Mind; var role = new Job(mind, _prototypeManager.Index(args[1])); diff --git a/Content.Server/Mobs/Mind.cs b/Content.Server/Mobs/Mind.cs index 1fc6e5f71c..9adcb4a776 100644 --- a/Content.Server/Mobs/Mind.cs +++ b/Content.Server/Mobs/Mind.cs @@ -30,10 +30,10 @@ namespace Content.Server.Mobs /// /// Creates the new mind attached to a specific player session. /// - /// The session ID of the owning player. - public Mind(NetSessionId sessionId) + /// The session ID of the owning player. + public Mind(NetUserId userId) { - SessionId = sessionId; + UserId = userId; } // TODO: This session should be able to be changed, probably. @@ -41,7 +41,7 @@ namespace Content.Server.Mobs /// The session ID of the player owning this mind. /// [ViewVariables] - public NetSessionId? SessionId { get; private set; } + public NetUserId? UserId { get; private set; } [ViewVariables] public bool IsVisitingEntity => VisitingEntity != null; @@ -83,12 +83,12 @@ namespace Content.Server.Mobs { get { - if (!SessionId.HasValue) + if (!UserId.HasValue) { return null; } var playerMgr = IoCManager.Resolve(); - playerMgr.TryGetSessionById(SessionId.Value, out var ret); + playerMgr.TryGetSessionById(UserId.Value, out var ret); return ret; } } @@ -195,7 +195,7 @@ namespace Content.Server.Mobs VisitingEntity = null; } - public void ChangeOwningPlayer(NetSessionId? newOwner) + public void ChangeOwningPlayer(NetUserId? newOwner) { var playerMgr = IoCManager.Resolve(); PlayerData newOwnerData = null; @@ -216,12 +216,12 @@ namespace Content.Server.Mobs var oldSession = Session; oldSession?.AttachToEntity(null); - if (SessionId.HasValue) + if (UserId.HasValue) { - playerMgr.GetPlayerData(SessionId.Value).ContentData().Mind = null; + playerMgr.GetPlayerData(UserId.Value).ContentData().Mind = null; } - SessionId = newOwner; + UserId = newOwner; if (!newOwner.HasValue) { return; diff --git a/Content.Server/Players/PlayerData.cs b/Content.Server/Players/PlayerData.cs index 2ef562d5de..c66b6f7024 100644 --- a/Content.Server/Players/PlayerData.cs +++ b/Content.Server/Players/PlayerData.cs @@ -16,7 +16,7 @@ namespace Content.Server.Players /// The session ID of the player owning this data. /// [ViewVariables] - public NetSessionId SessionId { get; } + public NetUserId UserId { get; } /// /// The currently occupied mind of the player owning this data. @@ -31,9 +31,9 @@ namespace Content.Server.Players Mind = null; } - public PlayerData(NetSessionId sessionId) + public PlayerData(NetUserId userId) { - SessionId = sessionId; + UserId = userId; } } diff --git a/Content.Server/Preferences/PreferencesDatabase.cs b/Content.Server/Preferences/PreferencesDatabase.cs deleted file mode 100644 index e4a0b4176f..0000000000 --- a/Content.Server/Preferences/PreferencesDatabase.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Content.Server.Database; -using Content.Shared.Preferences; -using Robust.Shared.Maths; -using static Content.Shared.Preferences.Sex; - -namespace Content.Server.Preferences -{ - /// - /// Provides methods to retrieve and update character preferences. - /// Don't use this directly, go through instead. - /// - public class PreferencesDatabase - { - private readonly int _maxCharacterSlots; - private readonly PrefsDb _prefsDb; - - // We use a single DbContext for the entire DB connection, and EFCore doesn't allow concurrent access. - // So we need this semaphore to prevent bugs. - private readonly SemaphoreSlim _prefsSemaphore = new SemaphoreSlim(1, 1); - - public PreferencesDatabase(IDatabaseConfiguration dbConfig, int maxCharacterSlots) - { - _maxCharacterSlots = maxCharacterSlots; - _prefsDb = new PrefsDb(dbConfig); - } - - public async Task GetPlayerPreferencesAsync(string username) - { - await _prefsSemaphore.WaitAsync(); - try - { - var prefs = await _prefsDb.GetPlayerPreferences(username); - if (prefs is null) return null; - - var profiles = new ICharacterProfile[_maxCharacterSlots]; - foreach (var profile in prefs.HumanoidProfiles) - { - profiles[profile.Slot] = ConvertProfiles(profile); - } - - return new PlayerPreferences - ( - profiles, - prefs.SelectedCharacterSlot - ); - } - finally - { - _prefsSemaphore.Release(); - } - } - - public async Task SaveSelectedCharacterIndexAsync(string username, int index) - { - await _prefsSemaphore.WaitAsync(); - try - { - index = MathHelper.Clamp(index, 0, _maxCharacterSlots - 1); - await _prefsDb.SaveSelectedCharacterIndex(username, index); - } - finally - { - _prefsSemaphore.Release(); - } - } - - public async Task SaveCharacterSlotAsync(string username, ICharacterProfile profile, int slot) - { - if (slot < 0 || slot >= _maxCharacterSlots) - return; - - await _prefsSemaphore.WaitAsync(); - try - { - if (profile is null) - { - await DeleteCharacterSlotAsync(username, slot); - return; - } - - if (!(profile is HumanoidCharacterProfile humanoid)) - // TODO: Handle other ICharacterProfile implementations properly - throw new NotImplementedException(); - var appearance = (HumanoidCharacterAppearance) humanoid.CharacterAppearance; - var entity = new HumanoidProfile - { - SlotName = humanoid.Name, - CharacterName = humanoid.Name, - Age = humanoid.Age, - Sex = humanoid.Sex.ToString(), - HairName = appearance.HairStyleName, - HairColor = appearance.HairColor.ToHex(), - FacialHairName = appearance.FacialHairStyleName, - FacialHairColor = appearance.FacialHairColor.ToHex(), - EyeColor = appearance.EyeColor.ToHex(), - SkinColor = appearance.SkinColor.ToHex(), - Slot = slot, - PreferenceUnavailable = (DbPreferenceUnavailableMode) humanoid.PreferenceUnavailable - }; - entity.Jobs.AddRange( - humanoid.JobPriorities - .Where(j => j.Value != JobPriority.Never) - .Select(j => new Job {JobName = j.Key, Priority = (DbJobPriority) j.Value}) - ); - entity.Antags.AddRange( - humanoid.AntagPreferences - .Select(a => new Antag {AntagName = a}) - ); - await _prefsDb.SaveCharacterSlotAsync(username, entity); - } - finally - { - _prefsSemaphore.Release(); - } - } - - - private async Task DeleteCharacterSlotAsync(string username, int slot) - { - await _prefsDb.DeleteCharacterSlotAsync(username, slot); - } - - public async Task>> GetSelectedProfilesForPlayersAsync( - List usernames) - { - await _prefsSemaphore.WaitAsync(); - try - { - var profiles = await _prefsDb.GetProfilesForPlayersAsync(usernames); - return profiles.Select( - p => new KeyValuePair(p.Key, ConvertProfiles(p.Value))); - } - finally - { - _prefsSemaphore.Release(); - } - } - - private static HumanoidCharacterProfile ConvertProfiles(HumanoidProfile profile) - { - var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority); - var antags = profile.Antags.Select(a => a.AntagName); - return new HumanoidCharacterProfile( - profile.CharacterName, - profile.Age, - profile.Sex == "Male" ? Male : Female, - new HumanoidCharacterAppearance - ( - profile.HairName, - Color.FromHex(profile.HairColor), - profile.FacialHairName, - Color.FromHex(profile.FacialHairColor), - Color.FromHex(profile.EyeColor), - Color.FromHex(profile.SkinColor) - ), - jobs, - (PreferenceUnavailableMode) profile.PreferenceUnavailable, - antags.ToList() - ); - } - } -} diff --git a/Content.Server/Preferences/ServerPreferencesManager.cs b/Content.Server/Preferences/ServerPreferencesManager.cs index f673a4e7b2..209ee4afb1 100644 --- a/Content.Server/Preferences/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/ServerPreferencesManager.cs @@ -1,15 +1,20 @@ using System; using System.Collections.Generic; -using System.IO; +using System.Linq; using System.Threading.Tasks; using Content.Server.Database; using Content.Server.Interfaces; +using Content.Shared; using Content.Shared.Preferences; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Network; -using Robust.Shared.Interfaces.Resources; using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +#nullable enable namespace Content.Server.Preferences { @@ -20,119 +25,188 @@ namespace Content.Server.Preferences public class ServerPreferencesManager : SharedPreferencesManager, IServerPreferencesManager { [Dependency] private readonly IServerNetManager _netManager = default!; - [Dependency] private readonly IConfigurationManager _configuration = default!; - [Dependency] private readonly IResourceManager _resourceManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly IPrototypeManager _protos = default!; - private PreferencesDatabase _preferencesDb; - private Task _prefsDbLoadTask; + // Cache player prefs on the server so we don't need as much async hell related to them. + private readonly Dictionary _cachedPlayerPrefs = + new Dictionary(); - public void StartInit() + private int MaxCharacterSlots => _cfg.GetCVar(CCVars.GameMaxCharacterSlots); + + public void Init() { _netManager.RegisterNetMessage(nameof(MsgPreferencesAndSettings)); _netManager.RegisterNetMessage(nameof(MsgSelectCharacter), HandleSelectCharacterMessage); _netManager.RegisterNetMessage(nameof(MsgUpdateCharacter), HandleUpdateCharacterMessage); - - _configuration.RegisterCVar("game.maxcharacterslots", 10); - _configuration.RegisterCVar("database.prefs_engine", "sqlite"); - _configuration.RegisterCVar("database.prefs_sqlite_dbpath", "preferences.db"); - _configuration.RegisterCVar("database.prefs_pg_host", "localhost"); - _configuration.RegisterCVar("database.prefs_pg_port", 5432); - _configuration.RegisterCVar("database.prefs_pg_database", "ss14_prefs"); - _configuration.RegisterCVar("database.prefs_pg_username", string.Empty); - _configuration.RegisterCVar("database.prefs_pg_password", string.Empty); - - var engine = _configuration.GetCVar("database.prefs_engine").ToLower(); - IDatabaseConfiguration dbConfig; - switch (engine) - { - case "sqlite": - var configPreferencesDbPath = _configuration.GetCVar("database.prefs_sqlite_dbpath"); - var inMemory = _resourceManager.UserData.RootDir == null; - var finalPreferencesDbPath = inMemory ? - null : - Path.Combine(_resourceManager.UserData.RootDir, configPreferencesDbPath); - dbConfig = new SqliteConfiguration(finalPreferencesDbPath); - break; - case "postgres": - dbConfig = new PostgresConfiguration( - _configuration.GetCVar("database.prefs_pg_host"), - _configuration.GetCVar("database.prefs_pg_port"), - _configuration.GetCVar("database.prefs_pg_database"), - _configuration.GetCVar("database.prefs_pg_username"), - _configuration.GetCVar("database.prefs_pg_password") - ); - break; - default: - throw new NotImplementedException("Unknown database engine {engine}."); - } - - var maxCharacterSlots = _configuration.GetCVar("game.maxcharacterslots"); - - // Actually loading the preferences database takes a while, - // because EFCore has to initialize and run migrations. - // We load it in the thread pool here and then fetch the .Result in FinishInit. - // This means it'll run in parallel with other loading like prototypes & map load. - _prefsDbLoadTask = Task.Run(() => new PreferencesDatabase(dbConfig, maxCharacterSlots)); } - public void FinishInit() - { - _preferencesDb = _prefsDbLoadTask.Result; - } private async void HandleSelectCharacterMessage(MsgSelectCharacter message) { - await _preferencesDb.SaveSelectedCharacterIndexAsync(message.MsgChannel.SessionId.Username, - message.SelectedCharacterIndex); + var index = message.SelectedCharacterIndex; + var userId = message.MsgChannel.UserId; + + if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded.IsCompleted) + { + Logger.WarningS("prefs", $"User {userId} tried to modify preferences before they loaded."); + return; + } + + if (index < 0 || index >= MaxCharacterSlots) + { + return; + } + + var curPrefs = prefsData.Prefs!; + + prefsData.Prefs = new PlayerPreferences(curPrefs.Characters, index); + + if (ShouldStorePrefs(message.MsgChannel.AuthType)) + { + await _db.SaveSelectedCharacterIndexAsync(message.MsgChannel.UserId, message.SelectedCharacterIndex); + } } private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message) { - await _preferencesDb.SaveCharacterSlotAsync(message.MsgChannel.SessionId.Username, message.Profile, - message.Slot); + var slot = message.Slot; + var profile = message.Profile; + var userId = message.MsgChannel.UserId; + + if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded.IsCompleted) + { + Logger.WarningS("prefs", $"User {userId} tried to modify preferences before they loaded."); + return; + } + + if (slot < 0 || slot >= MaxCharacterSlots) + { + return; + } + + var curPrefs = prefsData.Prefs!; + + var arr = new ICharacterProfile[MaxCharacterSlots]; + curPrefs.Characters.ToList().CopyTo(arr, 0); + + arr[slot] = HumanoidCharacterProfile.EnsureValid((HumanoidCharacterProfile) profile, _protos); + + prefsData.Prefs = new PlayerPreferences(arr, slot); + + if (ShouldStorePrefs(message.MsgChannel.AuthType)) + { + await _db.SaveCharacterSlotAsync(message.MsgChannel.UserId, message.Profile, message.Slot); + } } public async void OnClientConnected(IPlayerSession session) { - var msg = _netManager.CreateNetMessage(); - msg.Preferences = await GetPreferencesAsync(session.SessionId.Username); - msg.Settings = new GameSettings + if (!ShouldStorePrefs(session.ConnectedClient.AuthType)) { - MaxCharacterSlots = _configuration.GetCVar("game.maxcharacterslots") - }; - _netManager.ServerSendMessage(msg, session.ConnectedClient); + // Don't store data for guests. + var prefsData = new PlayerPrefData + { + PrefsLoaded = Task.CompletedTask, + Prefs = new PlayerPreferences( + new ICharacterProfile[] {HumanoidCharacterProfile.Default()}, + 0) + }; + + _cachedPlayerPrefs[session.UserId] = prefsData; + } + else + { + var prefsData = new PlayerPrefData(); + var loadTask = LoadPrefs(); + prefsData.PrefsLoaded = loadTask; + _cachedPlayerPrefs[session.UserId] = prefsData; + + await loadTask; + + async Task LoadPrefs() + { + var prefs = await GetOrCreatePreferencesAsync(session.UserId); + prefsData.Prefs = prefs; + + var msg = _netManager.CreateNetMessage(); + msg.Preferences = prefs; + msg.Settings = new GameSettings + { + MaxCharacterSlots = MaxCharacterSlots + }; + _netManager.ServerSendMessage(msg, session.ConnectedClient); + } + } } - /// - /// Returns the requested or null if not found. - /// - private async Task GetFromSql(string username) + + public void OnClientDisconnected(IPlayerSession session) { - return await _preferencesDb.GetPlayerPreferencesAsync(username); + _cachedPlayerPrefs.Remove(session.UserId); + } + + public bool HavePreferencesLoaded(IPlayerSession session) + { + return _cachedPlayerPrefs.ContainsKey(session.UserId); + } + + public Task WaitPreferencesLoaded(IPlayerSession session) + { + return _cachedPlayerPrefs[session.UserId].PrefsLoaded; } /// /// Retrieves preferences for the given username from storage. /// Creates and saves default preferences if they are not found, then returns them. /// - public async Task GetPreferencesAsync(string username) + public PlayerPreferences GetPreferences(NetUserId userId) { - var prefs = await GetFromSql(username); - if (prefs is null) + var prefs = _cachedPlayerPrefs[userId].Prefs; + if (prefs == null) { - await _preferencesDb.SaveSelectedCharacterIndexAsync(username, 0); - await _preferencesDb.SaveCharacterSlotAsync(username, HumanoidCharacterProfile.Default(), 0); - prefs = await GetFromSql(username); + throw new InvalidOperationException("Preferences for this player have not loaded yet."); } return prefs; } - public async Task>> GetSelectedProfilesForPlayersAsync(List usernames) + private async Task GetOrCreatePreferencesAsync(NetUserId userId) { - return await _preferencesDb.GetSelectedProfilesForPlayersAsync(usernames); + var prefs = await _db.GetPlayerPreferencesAsync(userId); + if (prefs is null) + { + return await _db.InitPrefsAsync(userId, HumanoidCharacterProfile.Default()); + } + + return prefs; + } + + public IEnumerable> GetSelectedProfilesForPlayers( + List usernames) + { + return usernames + .Select(p => (_cachedPlayerPrefs[p].Prefs, p)) + .Where(p => p.Prefs != null) + .Select(p => + { + var idx = p.Prefs!.SelectedCharacterIndex; + return new KeyValuePair(p.p, p.Prefs!.GetProfile(idx)); + }); + } + + internal static bool ShouldStorePrefs(LoginType loginType) + { + return loginType.HasStaticUserId(); + } + + private sealed class PlayerPrefData + { + public Task PrefsLoaded = default!; + public PlayerPreferences? Prefs; } } } diff --git a/Content.Server/ServerContentIoC.cs b/Content.Server/ServerContentIoC.cs index 1e757d4232..b8b7e665ef 100644 --- a/Content.Server/ServerContentIoC.cs +++ b/Content.Server/ServerContentIoC.cs @@ -3,6 +3,7 @@ using Content.Server.AI.WorldState; using Content.Server.Body.Network; using Content.Server.Cargo; using Content.Server.Chat; +using Content.Server.Database; using Content.Server.GameObjects.Components.Mobs.Speech; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.GameObjects.Components.Power.PowerNetComponents; @@ -34,6 +35,7 @@ namespace Content.Server IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); @@ -43,6 +45,7 @@ namespace Content.Server IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Server/Utility/IPAddressExt.cs b/Content.Server/Utility/IPAddressExt.cs new file mode 100644 index 0000000000..f343727ee0 --- /dev/null +++ b/Content.Server/Utility/IPAddressExt.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections; +using System.Linq; +using System.Net; +using System.Net.Sockets; + +namespace Content.Server.Utility +{ + public static class IPAddressExt + { + // Taken from https://stackoverflow.com/a/56461160/4678631 + public static bool IsInSubnet(this IPAddress address, string subnetMask) + { + var slashIdx = subnetMask.IndexOf("/", StringComparison.Ordinal); + if (slashIdx == -1) + { + // We only handle netmasks in format "IP/PrefixLength". + throw new NotSupportedException("Only SubNetMasks with a given prefix length are supported."); + } + + // First parse the address of the netmask before the prefix length. + var maskAddress = IPAddress.Parse(subnetMask.Substring(0, slashIdx)); + + if (maskAddress.AddressFamily != address.AddressFamily) + { + // We got something like an IPV4-Address for an IPv6-Mask. This is not valid. + return false; + } + + // Now find out how long the prefix is. + int maskLength = int.Parse(subnetMask.Substring(slashIdx + 1)); + + if (maskAddress.AddressFamily == AddressFamily.InterNetwork) + { + // Convert the mask address to an unsigned integer. + var maskAddressBits = BitConverter.ToUInt32(maskAddress.GetAddressBytes().Reverse().ToArray(), 0); + + // And convert the IpAddress to an unsigned integer. + var ipAddressBits = BitConverter.ToUInt32(address.GetAddressBytes().Reverse().ToArray(), 0); + + // Get the mask/network address as unsigned integer. + uint mask = uint.MaxValue << (32 - maskLength); + + // https://stackoverflow.com/a/1499284/3085985 + // Bitwise AND mask and MaskAddress, this should be the same as mask and IpAddress + // as the end of the mask is 0000 which leads to both addresses to end with 0000 + // and to start with the prefix. + return (maskAddressBits & mask) == (ipAddressBits & mask); + } + + if (maskAddress.AddressFamily == AddressFamily.InterNetworkV6) + { + // Convert the mask address to a BitArray. + var maskAddressBits = new BitArray(maskAddress.GetAddressBytes()); + + // And convert the IpAddress to a BitArray. + var ipAddressBits = new BitArray(address.GetAddressBytes()); + + if (maskAddressBits.Length != ipAddressBits.Length) + { + throw new ArgumentException("Length of IP Address and Subnet Mask do not match."); + } + + // Compare the prefix bits. + for (int maskIndex = 0; maskIndex < maskLength; maskIndex++) + { + if (ipAddressBits[maskIndex] != maskAddressBits[maskIndex]) + { + return false; + } + } + + return true; + } + + throw new NotSupportedException("Only InterNetworkV6 or InterNetwork address families are supported."); + } + } +} diff --git a/Content.Shared/CCVars.cs b/Content.Shared/CCVars.cs index 18e710acfa..f3b2ba5a92 100644 --- a/Content.Shared/CCVars.cs +++ b/Content.Shared/CCVars.cs @@ -21,5 +21,40 @@ namespace Content.Shared public static readonly CVarDef GameLobbyEnableWin = CVarDef.Create("game.enablewin", true, CVar.ARCHIVE); + + public static readonly CVarDef + GameMaxCharacterSlots = CVarDef.Create("game.maxcharacterslots", 10, CVar.ARCHIVE | CVar.SERVERONLY); + + /// + /// When enabled, guests will be assigned permanent UIDs and will have their preferences stored. + /// + public static readonly CVarDef + GamePersistGuests = CVarDef.Create("game.persistguests", true, CVar.ARCHIVE | CVar.SERVERONLY); + + + /* + * Database stuff + */ + + public static readonly CVarDef DatabaseEngine = + CVarDef.Create("database.engine", "sqlite", CVar.SERVERONLY); + + public static readonly CVarDef DatabaseSqliteDbPath = + CVarDef.Create("database.sqlite_dbpath", "preferences.db", CVar.SERVERONLY); + + public static readonly CVarDef DatabasePgHost = + CVarDef.Create("database.pg_host", "localhost", CVar.SERVERONLY); + + public static readonly CVarDef DatabasePgPort = + CVarDef.Create("database.pg_port", 5432, CVar.SERVERONLY); + + public static readonly CVarDef DatabasePgDatabase = + CVarDef.Create("database.pg_database", "ss14", CVar.SERVERONLY); + + public static readonly CVarDef DatabasePgUsername = + CVarDef.Create("database.pg_username", "", CVar.SERVERONLY); + + public static readonly CVarDef DatabasePgPassword = + CVarDef.Create("database.pg_password", "", CVar.SERVERONLY); } } diff --git a/Content.Shared/Preferences/HumanoidCharacterAppearance.cs b/Content.Shared/Preferences/HumanoidCharacterAppearance.cs index a2139a91c5..7b1001304a 100644 --- a/Content.Shared/Preferences/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Preferences/HumanoidCharacterAppearance.cs @@ -1,4 +1,5 @@ using System; +using Content.Shared.Preferences.Appearance; using Robust.Shared.Maths; using Robust.Shared.Serialization; @@ -72,6 +73,47 @@ namespace Content.Shared.Preferences ); } + public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance) + { + string hairStyleName; + if (!HairStyles.HairStylesMap.ContainsKey(appearance.HairStyleName)) + { + hairStyleName = HairStyles.DefaultHairStyle; + } + else + { + hairStyleName = appearance.HairStyleName; + } + + string facialHairStyleName; + if (!HairStyles.FacialHairStylesMap.ContainsKey(appearance.FacialHairStyleName)) + { + facialHairStyleName = HairStyles.DefaultFacialHairStyle; + } + else + { + facialHairStyleName = appearance.FacialHairStyleName; + } + + var hairColor = ClampColor(appearance.HairColor); + var facialHairColor = ClampColor(appearance.FacialHairColor); + var eyeColor = ClampColor(appearance.EyeColor); + var skinColor = ClampColor(appearance.SkinColor); + + return new HumanoidCharacterAppearance( + hairStyleName, + hairColor, + facialHairStyleName, + facialHairColor, + eyeColor, + skinColor); + + static Color ClampColor(Color color) + { + return new Color(color.RByte, color.GByte, color.BByte); + } + } + public bool MemberwiseEquals(ICharacterAppearance maybeOther) { if (!(maybeOther is HumanoidCharacterAppearance other)) return false; diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 0f2feac203..bf319b7094 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using Content.Shared.Roles; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Preferences @@ -12,6 +14,7 @@ namespace Content.Shared.Preferences private readonly List _antagPreferences; public static int MinimumAge = 18; public static int MaximumAge = 120; + public static int MaxNameLength = 32; private HumanoidCharacterProfile( string name, @@ -146,6 +149,69 @@ namespace Content.Shared.Preferences return new HumanoidCharacterProfile(Name, Age, Sex, Appearance, _jobPriorities, PreferenceUnavailable, list); } + /// + /// Makes this profile valid so there's no bad data like negative ages. + /// + public static HumanoidCharacterProfile EnsureValid( + HumanoidCharacterProfile profile, + IPrototypeManager prototypeManager) + { + var age = Math.Clamp(profile.Age, MinimumAge, MaximumAge); + var sex = profile.Sex switch + { + Sex.Male => Sex.Male, + Sex.Female => Sex.Female, + _ => Sex.Male // Invalid enum values. + }; + + string name; + if (string.IsNullOrEmpty(profile.Name)) + { + name = "John Doe"; + } + else if (profile.Name.Length > MaxNameLength) + { + name = profile.Name[..MaxNameLength]; + } + else + { + name = profile.Name; + } + + // TODO: Avoid Z̨͇̙͉͎̭͔̼̿͋A͚̖̞̗̞͈̓̾̀ͩͩ̔L̟ͮ̈͝G̙O͍͎̗̺̺ͫ̀̽͊̓͝ͅ tier shenanigans. + // And other stuff like RTL overrides and such. + // Probably also emojis... + + name = name.Trim(); + + var appearance = HumanoidCharacterAppearance.EnsureValid(profile.Appearance); + + var prefsUnavailableMode = profile.PreferenceUnavailable switch + { + PreferenceUnavailableMode.StayInLobby => PreferenceUnavailableMode.StayInLobby, + PreferenceUnavailableMode.SpawnAsOverflow => PreferenceUnavailableMode.SpawnAsOverflow, + _ => PreferenceUnavailableMode.StayInLobby // Invalid enum values. + }; + + var priorities = profile.JobPriorities + .Where(p => prototypeManager.HasIndex(p.Key) && p.Value switch + { + JobPriority.Never => false, // Drop never since that's assumed default. + JobPriority.Low => true, + JobPriority.Medium => true, + JobPriority.High => true, + _ => false + }) + .ToDictionary(p => p.Key, p => p.Value); + + + var antags = profile.AntagPreferences + .Where(prototypeManager.HasIndex) + .ToList(); + + return new HumanoidCharacterProfile(name, age, sex, appearance, priorities, prefsUnavailableMode, antags); + } + public string Summary => $"{Name}, {Age} years old {Sex.ToString().ToLower()} human."; diff --git a/Content.Shared/Preferences/PlayerPreferences.cs b/Content.Shared/Preferences/PlayerPreferences.cs index 5655e72c61..bf044d0d99 100644 --- a/Content.Shared/Preferences/PlayerPreferences.cs +++ b/Content.Shared/Preferences/PlayerPreferences.cs @@ -26,6 +26,11 @@ namespace Content.Shared.Preferences /// public IEnumerable Characters => _characters.AsEnumerable(); + public ICharacterProfile GetProfile(int index) + { + return _characters[index]; + } + /// /// Index of the currently selected character. /// diff --git a/Content.Shared/SharedGameTicker.cs b/Content.Shared/SharedGameTicker.cs index 7d2f5c8464..d4bb2bc161 100644 --- a/Content.Shared/SharedGameTicker.cs +++ b/Content.Shared/SharedGameTicker.cs @@ -193,23 +193,23 @@ namespace Content.Shared /// /// The Status of the Player in the lobby (ready, observer, ...) /// - public Dictionary PlayerStatus { get; set; } + public Dictionary PlayerStatus { get; set; } public override void ReadFromBuffer(NetIncomingMessage buffer) { - PlayerStatus = new Dictionary(); + PlayerStatus = new Dictionary(); var length = buffer.ReadInt32(); for (int i = 0; i < length; i++) { var serializer = IoCManager.Resolve(); var byteLength = buffer.ReadVariableInt32(); - NetSessionId sessionID; + NetUserId userId; using (var stream = buffer.ReadAsStream(byteLength)) { - serializer.DeserializeDirect(stream, out sessionID); + serializer.DeserializeDirect(stream, out userId); } var status = (PlayerStatus)buffer.ReadByte(); - PlayerStatus.Add(sessionID, status); + PlayerStatus.Add(userId, status); } } diff --git a/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs b/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs deleted file mode 100644 index 42482a7e19..0000000000 --- a/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Content.Server.Database; -using Content.Server.Preferences; -using Content.Shared; -using Content.Shared.Preferences; -using NUnit.Framework; -using Robust.Shared.Maths; -using Robust.UnitTesting; - -namespace Content.Tests.Server.Preferences -{ - [TestFixture] - public class PreferencesDatabaseTests : RobustUnitTest - { - private const int MaxCharacterSlots = 10; - - private static HumanoidCharacterProfile CharlieCharlieson() - { - return new HumanoidCharacterProfile( - "Charlie Charlieson", - 21, - Sex.Male, - new HumanoidCharacterAppearance( - "Afro", - Color.Aqua, - "Shaved", - Color.Aquamarine, - Color.Azure, - Color.Beige - ), - new Dictionary - { - {SharedGameTicker.OverflowJob, JobPriority.High} - }, - PreferenceUnavailableMode.StayInLobby, - new List{} - ); - } - - private static PreferencesDatabase GetDb() - { - return new PreferencesDatabase(new SqliteConfiguration(Path.GetTempFileName()), MaxCharacterSlots); - } - - [Test] - public async Task TestUserDoesNotExist() - { - var db = GetDb(); - Assert.Null(await db.GetPlayerPreferencesAsync("[The database should be empty so any string should do]")); - } - - [Test] - public async Task TestUserDoesExist() - { - var db = GetDb(); - const string username = "bobby"; - await db.SaveSelectedCharacterIndexAsync(username, 0); - var prefs = await db.GetPlayerPreferencesAsync(username); - Assert.NotNull(prefs); - Assert.Zero(prefs.SelectedCharacterIndex); - Assert.That(prefs.Characters.ToList().TrueForAll(character => character is null)); - } - - [Test] - public async Task TestUpdateCharacter() - { - var db = GetDb(); - const string username = "charlie"; - const int slot = 0; - var originalProfile = CharlieCharlieson(); - await db.SaveSelectedCharacterIndexAsync(username, slot); - await db.SaveCharacterSlotAsync(username, originalProfile, slot); - var prefs = await db.GetPlayerPreferencesAsync(username); - Assert.That(prefs.Characters.ElementAt(slot).MemberwiseEquals(originalProfile)); - } - - [Test] - public async Task TestDeleteCharacter() - { - var db = GetDb(); - const string username = "charlie"; - const int slot = 0; - await db.SaveSelectedCharacterIndexAsync(username, slot); - await db.SaveCharacterSlotAsync(username, CharlieCharlieson(), slot); - await db.SaveCharacterSlotAsync(username, null, slot); - var prefs = await db.GetPlayerPreferencesAsync(username); - Assert.That(prefs.Characters.ToList().TrueForAll(character => character is null)); - } - - [Test] - public async Task TestInvalidSlot() - { - var db = GetDb(); - const string username = "charlie"; - const int slot = -1; - - await db.SaveSelectedCharacterIndexAsync(username, slot); - await db.SaveCharacterSlotAsync(username, CharlieCharlieson(), slot); - var prefs = await db.GetPlayerPreferencesAsync(username); - Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0)); - - await db.SaveSelectedCharacterIndexAsync(username, MaxCharacterSlots); - prefs = await db.GetPlayerPreferencesAsync(username); - Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(MaxCharacterSlots - 1)); - } - } -} diff --git a/Content.Tests/Server/Preferences/ServerDbSqliteTests.cs b/Content.Tests/Server/Preferences/ServerDbSqliteTests.cs new file mode 100644 index 0000000000..57d2dfa613 --- /dev/null +++ b/Content.Tests/Server/Preferences/ServerDbSqliteTests.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Content.Server.Database; +using Content.Shared; +using Content.Shared.Preferences; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using NUnit.Framework; +using Robust.Shared.Maths; +using Robust.Shared.Network; +using Robust.UnitTesting; + +namespace Content.Tests.Server.Preferences +{ + [TestFixture] + public class ServerDbSqliteTests : RobustUnitTest + { + private const int MaxCharacterSlots = 10; + + private static HumanoidCharacterProfile CharlieCharlieson() + { + return new HumanoidCharacterProfile( + "Charlie Charlieson", + 21, + Sex.Male, + new HumanoidCharacterAppearance( + "Afro", + Color.Aqua, + "Shaved", + Color.Aquamarine, + Color.Azure, + Color.Beige + ), + new Dictionary + { + {SharedGameTicker.OverflowJob, JobPriority.High} + }, + PreferenceUnavailableMode.StayInLobby, + new List () + ); + } + + private static ServerDbSqlite GetDb() + { + var builder = new DbContextOptionsBuilder(); + var conn = new SqliteConnection("Data Source=:memory:"); + conn.Open(); + builder.UseSqlite(conn); + return new ServerDbSqlite(builder.Options); + } + + [Test] + public async Task TestUserDoesNotExist() + { + var db = GetDb(); + // Database should be empty so a new GUID should do it. + Assert.Null(await db.GetPlayerPreferencesAsync(NewUserId())); + } + + [Test] + public async Task TestInitPrefs() + { + var db = GetDb(); + var username = new NetUserId(new Guid("640bd619-fc8d-4fe2-bf3c-4a5fb17d6ddd")); + const int slot = 0; + var originalProfile = CharlieCharlieson(); + await db.InitPrefsAsync(username, originalProfile); + var prefs = await db.GetPlayerPreferencesAsync(username); + Assert.That(prefs.Characters.ElementAt(slot).MemberwiseEquals(originalProfile)); + } + + [Test] + public async Task TestDeleteCharacter() + { + var db = GetDb(); + var username = new NetUserId(new Guid("640bd619-fc8d-4fe2-bf3c-4a5fb17d6ddd")); + await db.InitPrefsAsync(username, HumanoidCharacterProfile.Default()); + await db.SaveCharacterSlotAsync(username, CharlieCharlieson(), 1); + await db.SaveSelectedCharacterIndexAsync(username, 1); + await db.SaveCharacterSlotAsync(username, null, 1); + var prefs = await db.GetPlayerPreferencesAsync(username); + Assert.That(prefs.Characters.Skip(1).All(character => character is null)); + } + + private static NetUserId NewUserId() + { + return new NetUserId(Guid.NewGuid()); + } + } +} diff --git a/Content.Tests/Server/Utility/IPAddressExtTest.cs b/Content.Tests/Server/Utility/IPAddressExtTest.cs new file mode 100644 index 0000000000..5d6d41b207 --- /dev/null +++ b/Content.Tests/Server/Utility/IPAddressExtTest.cs @@ -0,0 +1,61 @@ +using System.Net; +using Content.Server.Utility; +using NUnit.Framework; + +namespace Content.Tests.Server.Utility +{ + public class IPAddressExtTest + { + [Test] + [TestCase("192.168.5.85/24", "192.168.5.1")] + [TestCase("192.168.5.85/24", "192.168.5.254")] + [TestCase("10.128.240.50/30", "10.128.240.48")] + [TestCase("10.128.240.50/30", "10.128.240.49")] + [TestCase("10.128.240.50/30", "10.128.240.50")] + [TestCase("10.128.240.50/30", "10.128.240.51")] + public void IpV4SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPAddress.Parse(ipAddress); + Assert.That(ipAddressObj.IsInSubnet(netMask), Is.True); + } + + [Test] + [TestCase("192.168.5.85/24", "192.168.4.254")] + [TestCase("192.168.5.85/24", "191.168.5.254")] + [TestCase("10.128.240.50/30", "10.128.240.47")] + [TestCase("10.128.240.50/30", "10.128.240.52")] + [TestCase("10.128.240.50/30", "10.128.239.50")] + [TestCase("10.128.240.50/30", "10.127.240.51")] + public void IpV4SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPAddress.Parse(ipAddress); + Assert.That(ipAddressObj.IsInSubnet(netMask), Is.False); + } + + // ReSharper disable StringLiteralTypo + [Test] + [TestCase("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")] + [TestCase("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF")] + [TestCase("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0001:0000:0000:0000")] + [TestCase("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFF0")] + [TestCase("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")] + public void IpV6SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPAddress.Parse(ipAddress); + Assert.That(ipAddressObj.IsInSubnet(netMask), Is.True); + } + + [Test] + [TestCase("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFFF")] + [TestCase("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0000:0000:0000:0000")] + [TestCase("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0001:0000:0000:0000")] + [TestCase("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFF0")] + [TestCase("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0001")] + // ReSharper restore StringLiteralTypo + public void IpV6SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress) + { + var ipAddressObj = IPAddress.Parse(ipAddress); + Assert.That(ipAddressObj.IsInSubnet(netMask), Is.False); + } + } +} diff --git a/RobustToolbox b/RobustToolbox index ab86b59bc8..aa64528a03 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit ab86b59bc8035add76bf8e32c94c4cbf7bd49a09 +Subproject commit aa64528a03de0f47d0b8d01816011e6ab4cddf3e diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 9bfa9e6c77..e9230966bf 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -39,15 +39,19 @@ NEXT_LINE NEXT_LINE NEXT_LINE + CC GD GL + IP KHR + MS OGL OOC OS PCM PNG RSI + SA UI UTF UV