From 8ae6561f4ecd4cfad9aa281289e371083a5de67e Mon Sep 17 00:00:00 2001 From: RaidMax Date: Wed, 15 Jun 2022 19:37:34 -0500 Subject: [PATCH] update schema to support unique guid + game combinations --- Application/ApplicationManager.cs | 4 +- Application/IW4MServer.cs | 14 +- Application/Misc/TokenAuthentication.cs | 29 +- Application/RConParsers/BaseRConParser.cs | 4 +- Data/Context/DatabaseContext.cs | 10 +- ...602_AddAlternateKeyToEFClients.Designer.cs | 1639 ++++++++++++++++ ...220613192602_AddAlternateKeyToEFClients.cs | 63 + .../MySqlDatabaseContextModelSnapshot.cs | 7 +- ...913_AddAlternateKeyToEFClients.Designer.cs | 1696 +++++++++++++++++ ...220613181913_AddAlternateKeyToEFClients.cs | 63 + .../PostgresqlDatabaseContextModelSnapshot.cs | 7 +- ...952_AddAlternateKeyToEFClients.Designer.cs | 1637 ++++++++++++++++ ...220613160952_AddAlternateKeyToEFClients.cs | 61 + .../SqliteDatabaseContextModelSnapshot.cs | 7 +- Data/Models/Client/EFClient.cs | 2 +- .../AutomessageFeed/AutomessageFeed.csproj | 2 +- Plugins/LiveRadar/LiveRadar.csproj | 2 +- Plugins/Login/Commands/LoginCommand.cs | 22 +- Plugins/Login/Login.csproj | 2 +- .../ProfanityDeterment.csproj | 2 +- .../AdvancedClientStatsResourceQueryHelper.cs | 12 +- Plugins/Stats/Stats.csproj | 2 +- Plugins/Welcome/Welcome.csproj | 2 +- SharedLibraryCore/BaseController.cs | 43 +- SharedLibraryCore/Commands/NativeCommands.cs | 4 +- .../Commands/RequestTokenCommand.cs | 15 +- SharedLibraryCore/Helpers/TokenIdentifier.cs | 11 + SharedLibraryCore/Helpers/TokenState.cs | 3 +- .../Interfaces/IEntityService.cs | 4 +- .../Interfaces/ITokenAuthentication.cs | 9 +- .../Interfaces/ITokenIdentifier.cs | 11 + SharedLibraryCore/PartialEntities/EFClient.cs | 2 +- SharedLibraryCore/Services/ClientService.cs | 76 +- SharedLibraryCore/Services/PenaltyService.cs | 14 +- SharedLibraryCore/SharedLibraryCore.csproj | 4 +- SharedLibraryCore/Utilities.cs | 6 +- .../Controllers/API/ClientController.cs | 16 +- WebfrontCore/Controllers/AccountController.cs | 35 +- WebfrontCore/Controllers/ActionController.cs | 8 +- .../Controllers/Client/ClientController.cs | 6 +- .../Client/ClientStatisticsController.cs | 7 +- .../Middleware/ClaimsPermissionRemoval.cs | 46 +- .../BanInfoResourceQueryHelper.cs | 5 +- WebfrontCore/QueryHelpers/Models/BanInfo.cs | 2 + WebfrontCore/Views/Admin/_BanEntries.cshtml | 8 +- WebfrontCore/Views/Server/_Server.cshtml | 6 +- 46 files changed, 5449 insertions(+), 181 deletions(-) create mode 100644 Data/Migrations/MySql/20220613192602_AddAlternateKeyToEFClients.Designer.cs create mode 100644 Data/Migrations/MySql/20220613192602_AddAlternateKeyToEFClients.cs create mode 100644 Data/Migrations/Postgresql/20220613181913_AddAlternateKeyToEFClients.Designer.cs create mode 100644 Data/Migrations/Postgresql/20220613181913_AddAlternateKeyToEFClients.cs create mode 100644 Data/Migrations/Sqlite/20220613160952_AddAlternateKeyToEFClients.Designer.cs create mode 100644 Data/Migrations/Sqlite/20220613160952_AddAlternateKeyToEFClients.cs create mode 100644 SharedLibraryCore/Helpers/TokenIdentifier.cs create mode 100644 SharedLibraryCore/Interfaces/ITokenIdentifier.cs diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 12f82c7f8..bb8960063 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -632,9 +632,9 @@ namespace IW4MAdmin.Application return _servers.SelectMany(s => s.Clients).ToList().Where(p => p != null).ToList(); } - public EFClient FindActiveClient(EFClient client) =>client.ClientNumber < 0 ? + public EFClient FindActiveClient(EFClient client) => client.ClientNumber < 0 ? GetActiveClients() - .FirstOrDefault(c => c.NetworkId == client.NetworkId) ?? client : + .FirstOrDefault(c => c.NetworkId == client.NetworkId && c.GameName == client.GameName) ?? client : client; public ClientService GetClientService() diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index e57f4df4f..fdae4da1e 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -75,7 +75,7 @@ namespace IW4MAdmin { ServerLogger.LogDebug("Client slot #{clientNumber} now reserved", clientFromLog.ClientNumber); - EFClient client = await Manager.GetClientService().GetUnique(clientFromLog.NetworkId); + var client = await Manager.GetClientService().GetUnique(clientFromLog.NetworkId, GameName); // first time client is connecting to server if (client == null) @@ -118,7 +118,7 @@ namespace IW4MAdmin public override async Task OnClientDisconnected(EFClient client) { - if (!GetClientsAsList().Any(_client => _client.NetworkId == client.NetworkId)) + if (GetClientsAsList().All(eachClient => eachClient.NetworkId != client.NetworkId)) { using (LogContext.PushProperty("Server", ToString())) { @@ -449,7 +449,7 @@ namespace IW4MAdmin Clients[E.Origin.ClientNumber] = E.Origin; try { - E.Origin.GameName = (Reference.Game?)GameName; + E.Origin.GameName = (Reference.Game)GameName; E.Origin = await OnClientConnected(E.Origin); E.Target = E.Origin; } @@ -517,7 +517,7 @@ namespace IW4MAdmin E.Target.SetLevel(Permission.User, E.Origin); await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId, - E.Target.CurrentAlias?.IPAddress); + E.Target.GameName, E.Target.CurrentAlias?.IPAddress); await Manager.GetPenaltyService().Create(unflagPenalty); } @@ -763,7 +763,7 @@ namespace IW4MAdmin private async Task OnClientUpdate(EFClient origin) { - var client = Manager.GetActiveClients().FirstOrDefault(c => c.NetworkId == origin.NetworkId); + var client = GetClientsAsList().FirstOrDefault(c => c.NetworkId == origin.NetworkId); if (client == null) { @@ -980,7 +980,7 @@ namespace IW4MAdmin !string.IsNullOrEmpty(client.Name) && (client.Ping != 999 || client.IsBot))) { client.CurrentServer = this; - client.GameName = (Reference.Game?)GameName; + client.GameName = (Reference.Game)GameName; var e = new GameEvent { @@ -1530,7 +1530,7 @@ namespace IW4MAdmin ServerLogger.LogDebug("Creating unban penalty for {targetClient}", targetClient.ToString()); targetClient.SetLevel(Permission.User, originClient); await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId, - targetClient.NetworkId, targetClient.CurrentAlias?.IPAddress); + targetClient.NetworkId, targetClient.GameName, targetClient.CurrentAlias?.IPAddress); await Manager.GetPenaltyService().Create(unbanPenalty); } diff --git a/Application/Misc/TokenAuthentication.cs b/Application/Misc/TokenAuthentication.cs index d87846d4e..d87887d26 100644 --- a/Application/Misc/TokenAuthentication.cs +++ b/Application/Misc/TokenAuthentication.cs @@ -9,40 +9,42 @@ namespace IW4MAdmin.Application.Misc { internal class TokenAuthentication : ITokenAuthentication { - private readonly ConcurrentDictionary _tokens; + private readonly ConcurrentDictionary _tokens; private readonly RandomNumberGenerator _random; - private static readonly TimeSpan TimeoutPeriod = new TimeSpan(0, 0, 120); + private static readonly TimeSpan TimeoutPeriod = new(0, 0, 120); private const short TokenLength = 4; public TokenAuthentication() { - _tokens = new ConcurrentDictionary(); + _tokens = new ConcurrentDictionary(); _random = RandomNumberGenerator.Create(); } - public bool AuthorizeToken(long networkId, string token) + public bool AuthorizeToken(ITokenIdentifier authInfo) { - var authorizeSuccessful = _tokens.ContainsKey(networkId) && _tokens[networkId].Token == token; + var key = BuildKey(authInfo); + var authorizeSuccessful = _tokens.ContainsKey(key) && _tokens[key].Token == key; if (authorizeSuccessful) { - _tokens.TryRemove(networkId, out _); + _tokens.TryRemove(key, out _); } return authorizeSuccessful; } - public TokenState GenerateNextToken(long networkId) + public TokenState GenerateNextToken(ITokenIdentifier authInfo) { TokenState state; + var genKey = BuildKey(authInfo); - if (_tokens.ContainsKey(networkId)) + if (_tokens.ContainsKey(genKey)) { - state = _tokens[networkId]; + state = _tokens[genKey]; - if ((DateTime.Now - state.RequestTime) > TimeoutPeriod) + if (DateTime.Now - state.RequestTime > TimeoutPeriod) { - _tokens.TryRemove(networkId, out _); + _tokens.TryRemove(genKey, out _); } else @@ -53,12 +55,11 @@ namespace IW4MAdmin.Application.Misc state = new TokenState { - NetworkId = networkId, Token = _generateToken(), TokenDuration = TimeoutPeriod }; - _tokens.TryAdd(networkId, state); + _tokens.TryAdd(genKey, state); // perform some housekeeping so we don't have built up tokens if they're not ever used foreach (var (key, value) in _tokens) @@ -96,5 +97,7 @@ namespace IW4MAdmin.Application.Misc _random.Dispose(); return token.ToString(); } + + private string BuildKey(ITokenIdentifier authInfo) => $"{authInfo.NetworkId}_${authInfo.Game}"; } } diff --git a/Application/RConParsers/BaseRConParser.cs b/Application/RConParsers/BaseRConParser.cs index 46a8424ce..b758b8706 100644 --- a/Application/RConParsers/BaseRConParser.cs +++ b/Application/RConParsers/BaseRConParser.cs @@ -314,9 +314,9 @@ namespace IW4MAdmin.Application.RConParsers continue; } - var client = new EFClient() + var client = new EFClient { - CurrentAlias = new EFAlias() + CurrentAlias = new EFAlias { Name = name, IPAddress = ip diff --git a/Data/Context/DatabaseContext.cs b/Data/Context/DatabaseContext.cs index 64f7aace3..9e0bd9fb0 100644 --- a/Data/Context/DatabaseContext.cs +++ b/Data/Context/DatabaseContext.cs @@ -85,7 +85,15 @@ namespace Data.Context protected override void OnModelCreating(ModelBuilder modelBuilder) { // make network id unique - modelBuilder.Entity(entity => { entity.HasIndex(e => e.NetworkId).IsUnique(); }); + modelBuilder.Entity(entity => + { + entity.HasIndex(e => e.NetworkId); + entity.HasAlternateKey(client => new + { + client.NetworkId, + client.GameName + }); + }); modelBuilder.Entity(entity => { diff --git a/Data/Migrations/MySql/20220613192602_AddAlternateKeyToEFClients.Designer.cs b/Data/Migrations/MySql/20220613192602_AddAlternateKeyToEFClients.Designer.cs new file mode 100644 index 000000000..151a7b7e0 --- /dev/null +++ b/Data/Migrations/MySql/20220613192602_AddAlternateKeyToEFClients.Designer.cs @@ -0,0 +1,1639 @@ +// +using System; +using Data.MigrationContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Data.Migrations.MySql +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20220613192602_AddAlternateKeyToEFClients")] + partial class AddAlternateKeyToEFClients + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.Property("ACSnapshotVector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("SnapshotId") + .HasColumnType("int"); + + b.Property("Vector3Id") + .HasColumnType("int"); + + b.HasKey("ACSnapshotVector3Id"); + + b.HasIndex("SnapshotId"); + + b.HasIndex("Vector3Id"); + + b.ToTable("EFACSnapshotVector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("AliasLinkId") + .HasColumnType("int"); + + b.Property("Connections") + .HasColumnType("int"); + + b.Property("CurrentAliasId") + .HasColumnType("int"); + + b.Property("FirstConnection") + .HasColumnType("datetime(6)"); + + b.Property("GameName") + .HasColumnType("int"); + + b.Property("LastConnection") + .HasColumnType("datetime(6)"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("Masked") + .HasColumnType("tinyint(1)"); + + b.Property("NetworkId") + .HasColumnType("bigint"); + + b.Property("Password") + .HasColumnType("longtext"); + + b.Property("PasswordSalt") + .HasColumnType("longtext"); + + b.Property("TotalConnectionTime") + .HasColumnType("int"); + + b.HasKey("ClientId"); + + b.HasAlternateKey("NetworkId", "GameName"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("NetworkId"); + + b.ToTable("EFClients", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.Property("ClientConnectionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ClientId") + .HasColumnType("int"); + + b.Property("ConnectionType") + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.HasKey("ClientConnectionId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientConnectionHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("AttackerId") + .HasColumnType("int"); + + b.Property("Damage") + .HasColumnType("int"); + + b.Property("DeathOriginVector3Id") + .HasColumnType("int"); + + b.Property("DeathType") + .HasColumnType("int"); + + b.Property("Fraction") + .HasColumnType("double"); + + b.Property("HitLoc") + .HasColumnType("int"); + + b.Property("IsKill") + .HasColumnType("tinyint(1)"); + + b.Property("KillOriginVector3Id") + .HasColumnType("int"); + + b.Property("Map") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("VictimId") + .HasColumnType("int"); + + b.Property("ViewAnglesVector3Id") + .HasColumnType("int"); + + b.Property("VisibilityPercentage") + .HasColumnType("double"); + + b.Property("Weapon") + .HasColumnType("int"); + + b.Property("WeaponReference") + .HasColumnType("longtext"); + + b.Property("When") + .HasColumnType("datetime(6)"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("ClientId") + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("SentIngame") + .HasColumnType("tinyint(1)"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("TimeSent") + .HasColumnType("datetime(6)"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("TimeSent"); + + b.ToTable("EFClientMessages", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("ClientId") + .HasColumnType("int"); + + b.Property("CurrentSessionLength") + .HasColumnType("int"); + + b.Property("CurrentStrain") + .HasColumnType("double"); + + b.Property("CurrentViewAngleId") + .HasColumnType("int"); + + b.Property("Deaths") + .HasColumnType("int"); + + b.Property("Distance") + .HasColumnType("double"); + + b.Property("EloRating") + .HasColumnType("double"); + + b.Property("HitDestinationId") + .HasColumnType("int"); + + b.Property("HitLocation") + .HasColumnType("int"); + + b.Property("HitLocationReference") + .HasColumnType("longtext"); + + b.Property("HitOriginId") + .HasColumnType("int"); + + b.Property("HitType") + .HasColumnType("int"); + + b.Property("Hits") + .HasColumnType("int"); + + b.Property("Kills") + .HasColumnType("int"); + + b.Property("LastStrainAngleId") + .HasColumnType("int"); + + b.Property("RecoilOffset") + .HasColumnType("double"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("SessionAngleOffset") + .HasColumnType("double"); + + b.Property("SessionAverageSnapValue") + .HasColumnType("double"); + + b.Property("SessionSPM") + .HasColumnType("double"); + + b.Property("SessionScore") + .HasColumnType("int"); + + b.Property("SessionSnapHits") + .HasColumnType("int"); + + b.Property("StrainAngleBetween") + .HasColumnType("double"); + + b.Property("TimeSinceLastEvent") + .HasColumnType("int"); + + b.Property("WeaponId") + .HasColumnType("int"); + + b.Property("WeaponReference") + .HasColumnType("longtext"); + + b.Property("When") + .HasColumnType("datetime(6)"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleId"); + + b.HasIndex("HitDestinationId"); + + b.HasIndex("HitOriginId"); + + b.HasIndex("LastStrainAngleId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFACSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.Property("ClientHitStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("DamageInflicted") + .HasColumnType("int"); + + b.Property("DamageReceived") + .HasColumnType("int"); + + b.Property("DeathCount") + .HasColumnType("int"); + + b.Property("HitCount") + .HasColumnType("int"); + + b.Property("HitLocationId") + .HasColumnType("int"); + + b.Property("KillCount") + .HasColumnType("int"); + + b.Property("MeansOfDeathId") + .HasColumnType("int"); + + b.Property("ReceivedHitCount") + .HasColumnType("int"); + + b.Property("Score") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("SuicideCount") + .HasColumnType("int"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("UsageSeconds") + .HasColumnType("int"); + + b.Property("WeaponAttachmentComboId") + .HasColumnType("int"); + + b.Property("WeaponId") + .HasColumnType("int"); + + b.HasKey("ClientHitStatisticId"); + + b.HasIndex("ClientId"); + + b.HasIndex("HitLocationId"); + + b.HasIndex("MeansOfDeathId"); + + b.HasIndex("ServerId"); + + b.HasIndex("WeaponAttachmentComboId"); + + b.HasIndex("WeaponId"); + + b.ToTable("EFClientHitStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.Property("ClientRankingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ClientId") + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("Newest") + .HasColumnType("tinyint(1)"); + + b.Property("PerformanceMetric") + .HasColumnType("double"); + + b.Property("Ranking") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("ZScore") + .HasColumnType("double"); + + b.HasKey("ClientRankingHistoryId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("Ranking"); + + b.HasIndex("ServerId"); + + b.HasIndex("UpdatedDateTime"); + + b.HasIndex("ZScore"); + + b.ToTable("EFClientRankingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("ClientId") + .HasColumnType("int"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Property("ClientId") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("AverageSnapValue") + .HasColumnType("double"); + + b.Property("Deaths") + .HasColumnType("int"); + + b.Property("EloRating") + .HasColumnType("double"); + + b.Property("Kills") + .HasColumnType("int"); + + b.Property("MaxStrain") + .HasColumnType("double"); + + b.Property("RollingWeightedKDR") + .HasColumnType("double"); + + b.Property("SPM") + .HasColumnType("double"); + + b.Property("Skill") + .HasColumnType("double"); + + b.Property("SnapHitCount") + .HasColumnType("int"); + + b.Property("TimePlayed") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ZScore") + .HasColumnType("double"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ZScore"); + + b.HasIndex("ClientId", "TimePlayed", "ZScore"); + + b.ToTable("EFClientStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("EFClientStatisticsClientId") + .HasColumnType("int") + .HasColumnName("EFClientStatisticsClientId"); + + b.Property("EFClientStatisticsServerId") + .HasColumnType("bigint") + .HasColumnName("EFClientStatisticsServerId"); + + b.Property("HitCount") + .HasColumnType("int"); + + b.Property("HitOffsetAverage") + .HasColumnType("float"); + + b.Property("Location") + .HasColumnType("int"); + + b.Property("MaxAngleDistance") + .HasColumnType("float"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("EFClientStatisticsServerId"); + + b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); + + b.ToTable("EFHitLocationCounts", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("ActivityAmount") + .HasColumnType("int"); + + b.Property("Newest") + .HasColumnType("tinyint(1)"); + + b.Property("Performance") + .HasColumnType("double"); + + b.Property("Ranking") + .HasColumnType("int"); + + b.Property("RatingHistoryId") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("When") + .HasColumnType("datetime(6)"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("Performance", "Ranking", "When"); + + b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); + + b.ToTable("EFRating", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => + { + b.Property("HitLocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.HasKey("HitLocationId"); + + b.HasIndex("Name"); + + b.ToTable("EFHitLocations", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => + { + b.Property("MapId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.HasKey("MapId"); + + b.ToTable("EFMaps", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => + { + b.Property("MeansOfDeathId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.HasKey("MeansOfDeathId"); + + b.ToTable("EFMeansOfDeath", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => + { + b.Property("WeaponId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.HasKey("WeaponId"); + + b.HasIndex("Name"); + + b.ToTable("EFWeapons", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => + { + b.Property("WeaponAttachmentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.HasKey("WeaponAttachmentId"); + + b.ToTable("EFWeaponAttachments", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.Property("WeaponAttachmentComboId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Attachment1Id") + .HasColumnType("int"); + + b.Property("Attachment2Id") + .HasColumnType("int"); + + b.Property("Attachment3Id") + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.HasKey("WeaponAttachmentComboId"); + + b.HasIndex("Attachment1Id"); + + b.HasIndex("Attachment2Id"); + + b.HasIndex("Attachment3Id"); + + b.ToTable("EFWeaponAttachmentCombos", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("DateAdded") + .HasColumnType("datetime(6)"); + + b.Property("IPAddress") + .HasColumnType("int"); + + b.Property("LinkId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("varchar(24)"); + + b.Property("SearchableIPAddress") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("varchar(255)") + .HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true); + + b.Property("SearchableName") + .HasMaxLength(24) + .HasColumnType("varchar(24)"); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.HasIndex("SearchableIPAddress"); + + b.HasIndex("SearchableName"); + + b.HasIndex("Name", "IPAddress"); + + b.ToTable("EFAlias", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Comment") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CurrentValue") + .HasColumnType("longtext"); + + b.Property("ImpersonationEntityId") + .HasColumnType("int"); + + b.Property("OriginEntityId") + .HasColumnType("int"); + + b.Property("PreviousValue") + .HasColumnType("longtext"); + + b.Property("TargetEntityId") + .HasColumnType("int"); + + b.Property("TimeChanged") + .HasColumnType("datetime(6)"); + + b.Property("TypeOfChange") + .HasColumnType("int"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("ClientId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("Extra") + .HasColumnType("longtext"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("LinkedMetaId") + .HasColumnType("int"); + + b.Property("Updated") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.HasIndex("Key"); + + b.HasIndex("LinkedMetaId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("AutomatedOffense") + .HasColumnType("longtext"); + + b.Property("Expires") + .HasColumnType("datetime(6)"); + + b.Property("IsEvadedOffense") + .HasColumnType("tinyint(1)"); + + b.Property("LinkId") + .HasColumnType("int"); + + b.Property("OffenderId") + .HasColumnType("int"); + + b.Property("Offense") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PunisherId") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("When") + .HasColumnType("datetime(6)"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.Property("PenaltyIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("IPv4Address") + .HasColumnType("int"); + + b.Property("NetworkId") + .HasColumnType("bigint"); + + b.Property("PenaltyId") + .HasColumnType("int"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.HasKey("PenaltyIdentifierId"); + + b.HasIndex("IPv4Address"); + + b.HasIndex("NetworkId"); + + b.HasIndex("PenaltyId"); + + b.ToTable("EFPenaltyIdentifiers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.Property("InboxMessageId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDateTime") + .HasColumnType("datetime(6)"); + + b.Property("DestinationClientId") + .HasColumnType("int"); + + b.Property("IsDelivered") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("SourceClientId") + .HasColumnType("int"); + + b.Property("UpdatedDateTime") + .HasColumnType("datetime(6)"); + + b.HasKey("InboxMessageId"); + + b.HasIndex("DestinationClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("InboxMessages"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServer", b => + { + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("EndPoint") + .HasColumnType("longtext"); + + b.Property("GameName") + .HasColumnType("int"); + + b.Property("HostName") + .HasColumnType("longtext"); + + b.Property("IsPasswordProtected") + .HasColumnType("tinyint(1)"); + + b.Property("Port") + .HasColumnType("int"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.Property("ServerSnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("CapturedAt") + .HasColumnType("datetime(6)"); + + b.Property("ClientCount") + .HasColumnType("int"); + + b.Property("ConnectionInterrupted") + .HasColumnType("tinyint(1)"); + + b.Property("MapId") + .HasColumnType("int"); + + b.Property("PeriodBlock") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.HasKey("ServerSnapshotId"); + + b.HasIndex("MapId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("TotalKills") + .HasColumnType("bigint"); + + b.Property("TotalPlayTime") + .HasColumnType("bigint"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("X") + .HasColumnType("float"); + + b.Property("Y") + .HasColumnType("float"); + + b.Property("Z") + .HasColumnType("float"); + + b.HasKey("Vector3Id"); + + b.ToTable("Vector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.HasOne("Data.Models.Client.Stats.EFACSnapshot", "Snapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("SnapshotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "Vector") + .WithMany() + .HasForeignKey("Vector3Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Snapshot"); + + b.Navigation("Vector"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.HasOne("Data.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AliasLink"); + + b.Navigation("CurrentAlias"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.HasOne("Data.Models.Client.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("Data.Models.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + + b.Navigation("Attacker"); + + b.Navigation("DeathOrigin"); + + b.Navigation("KillOrigin"); + + b.Navigation("Server"); + + b.Navigation("Victim"); + + b.Navigation("ViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("CurrentViewAngle"); + + b.Navigation("HitDestination"); + + b.Navigation("HitOrigin"); + + b.Navigation("LastStrainAngle"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFHitLocation", "HitLocation") + .WithMany() + .HasForeignKey("HitLocationId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFMeansOfDeath", "MeansOfDeath") + .WithMany() + .HasForeignKey("MeansOfDeathId"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", "WeaponAttachmentCombo") + .WithMany() + .HasForeignKey("WeaponAttachmentComboId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") + .WithMany() + .HasForeignKey("WeaponId"); + + b.Navigation("Client"); + + b.Navigation("HitLocation"); + + b.Navigation("MeansOfDeath"); + + b.Navigation("Server"); + + b.Navigation("Weapon"); + + b.Navigation("WeaponAttachmentCombo"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("EFClientStatisticsClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.EFClientStatistics", null) + .WithMany("HitLocations") + .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.HasOne("Data.Models.Client.Stats.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("RatingHistory"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment1") + .WithMany() + .HasForeignKey("Attachment1Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment2") + .WithMany() + .HasForeignKey("Attachment2Id"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") + .WithMany() + .HasForeignKey("Attachment3Id"); + + b.Navigation("Attachment1"); + + b.Navigation("Attachment2"); + + b.Navigation("Attachment3"); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId"); + + b.HasOne("Data.Models.EFMeta", "LinkedMeta") + .WithMany() + .HasForeignKey("LinkedMetaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Client"); + + b.Navigation("LinkedMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId"); + + b.HasOne("Data.Models.Client.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + + b.Navigation("Offender"); + + b.Navigation("Punisher"); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.HasOne("Data.Models.EFPenalty", "Penalty") + .WithMany() + .HasForeignKey("PenaltyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Penalty"); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "DestinationClient") + .WithMany() + .HasForeignKey("DestinationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DestinationClient"); + + b.Navigation("Server"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFMap", "Map") + .WithMany() + .HasForeignKey("MapId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Map"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Navigation("AdministeredPenalties"); + + b.Navigation("Meta"); + + b.Navigation("ReceivedPenalties"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Navigation("PredictedViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Navigation("Ratings"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Navigation("HitLocations"); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Navigation("Children"); + + b.Navigation("ReceivedPenalties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/MySql/20220613192602_AddAlternateKeyToEFClients.cs b/Data/Migrations/MySql/20220613192602_AddAlternateKeyToEFClients.cs new file mode 100644 index 000000000..2de5f9ec4 --- /dev/null +++ b/Data/Migrations/MySql/20220613192602_AddAlternateKeyToEFClients.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Data.Migrations.MySql +{ + public partial class AddAlternateKeyToEFClients : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients"); + + migrationBuilder.Sql("UPDATE `EFClients` set `GameName` = 0 WHERE `GameName` IS NULL"); + + migrationBuilder.AlterColumn( + name: "GameName", + table: "EFClients", + type: "int", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "int", + oldNullable: true); + + migrationBuilder.AddUniqueConstraint( + name: "AK_EFClients_NetworkId_GameName", + table: "EFClients", + columns: new[] { "NetworkId", "GameName" }); + + migrationBuilder.CreateIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients", + column: "NetworkId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropUniqueConstraint( + name: "AK_EFClients_NetworkId_GameName", + table: "EFClients"); + + migrationBuilder.DropIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients"); + + migrationBuilder.AlterColumn( + name: "GameName", + table: "EFClients", + type: "int", + nullable: true, + oldClrType: typeof(int), + oldType: "int"); + + migrationBuilder.CreateIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients", + column: "NetworkId", + unique: true); + } + } +} diff --git a/Data/Migrations/MySql/MySqlDatabaseContextModelSnapshot.cs b/Data/Migrations/MySql/MySqlDatabaseContextModelSnapshot.cs index 9b99bbe57..1292eaa88 100644 --- a/Data/Migrations/MySql/MySqlDatabaseContextModelSnapshot.cs +++ b/Data/Migrations/MySql/MySqlDatabaseContextModelSnapshot.cs @@ -64,7 +64,7 @@ namespace Data.Migrations.MySql b.Property("FirstConnection") .HasColumnType("datetime(6)"); - b.Property("GameName") + b.Property("GameName") .HasColumnType("int"); b.Property("LastConnection") @@ -90,12 +90,13 @@ namespace Data.Migrations.MySql b.HasKey("ClientId"); + b.HasAlternateKey("NetworkId", "GameName"); + b.HasIndex("AliasLinkId"); b.HasIndex("CurrentAliasId"); - b.HasIndex("NetworkId") - .IsUnique(); + b.HasIndex("NetworkId"); b.ToTable("EFClients", (string)null); }); diff --git a/Data/Migrations/Postgresql/20220613181913_AddAlternateKeyToEFClients.Designer.cs b/Data/Migrations/Postgresql/20220613181913_AddAlternateKeyToEFClients.Designer.cs new file mode 100644 index 000000000..954c61f02 --- /dev/null +++ b/Data/Migrations/Postgresql/20220613181913_AddAlternateKeyToEFClients.Designer.cs @@ -0,0 +1,1696 @@ +// +using System; +using Data.MigrationContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Data.Migrations.Postgresql +{ + [DbContext(typeof(PostgresqlDatabaseContext))] + [Migration("20220613181913_AddAlternateKeyToEFClients")] + partial class AddAlternateKeyToEFClients + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.Property("ACSnapshotVector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ACSnapshotVector3Id")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("SnapshotId") + .HasColumnType("integer"); + + b.Property("Vector3Id") + .HasColumnType("integer"); + + b.HasKey("ACSnapshotVector3Id"); + + b.HasIndex("SnapshotId"); + + b.HasIndex("Vector3Id"); + + b.ToTable("EFACSnapshotVector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("AliasLinkId") + .HasColumnType("integer"); + + b.Property("Connections") + .HasColumnType("integer"); + + b.Property("CurrentAliasId") + .HasColumnType("integer"); + + b.Property("FirstConnection") + .HasColumnType("timestamp without time zone"); + + b.Property("GameName") + .HasColumnType("integer"); + + b.Property("LastConnection") + .HasColumnType("timestamp without time zone"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Masked") + .HasColumnType("boolean"); + + b.Property("NetworkId") + .HasColumnType("bigint"); + + b.Property("Password") + .HasColumnType("text"); + + b.Property("PasswordSalt") + .HasColumnType("text"); + + b.Property("TotalConnectionTime") + .HasColumnType("integer"); + + b.HasKey("ClientId"); + + b.HasAlternateKey("NetworkId", "GameName"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("NetworkId"); + + b.ToTable("EFClients", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.Property("ClientConnectionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientConnectionId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("ConnectionType") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("ClientConnectionId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientConnectionHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("KillId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("AttackerId") + .HasColumnType("integer"); + + b.Property("Damage") + .HasColumnType("integer"); + + b.Property("DeathOriginVector3Id") + .HasColumnType("integer"); + + b.Property("DeathType") + .HasColumnType("integer"); + + b.Property("Fraction") + .HasColumnType("double precision"); + + b.Property("HitLoc") + .HasColumnType("integer"); + + b.Property("IsKill") + .HasColumnType("boolean"); + + b.Property("KillOriginVector3Id") + .HasColumnType("integer"); + + b.Property("Map") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("VictimId") + .HasColumnType("integer"); + + b.Property("ViewAnglesVector3Id") + .HasColumnType("integer"); + + b.Property("VisibilityPercentage") + .HasColumnType("double precision"); + + b.Property("Weapon") + .HasColumnType("integer"); + + b.Property("WeaponReference") + .HasColumnType("text"); + + b.Property("When") + .HasColumnType("timestamp without time zone"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MessageId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("SentIngame") + .HasColumnType("boolean"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("TimeSent") + .HasColumnType("timestamp without time zone"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("TimeSent"); + + b.ToTable("EFClientMessages", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SnapshotId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CurrentSessionLength") + .HasColumnType("integer"); + + b.Property("CurrentStrain") + .HasColumnType("double precision"); + + b.Property("CurrentViewAngleId") + .HasColumnType("integer"); + + b.Property("Deaths") + .HasColumnType("integer"); + + b.Property("Distance") + .HasColumnType("double precision"); + + b.Property("EloRating") + .HasColumnType("double precision"); + + b.Property("HitDestinationId") + .HasColumnType("integer"); + + b.Property("HitLocation") + .HasColumnType("integer"); + + b.Property("HitLocationReference") + .HasColumnType("text"); + + b.Property("HitOriginId") + .HasColumnType("integer"); + + b.Property("HitType") + .HasColumnType("integer"); + + b.Property("Hits") + .HasColumnType("integer"); + + b.Property("Kills") + .HasColumnType("integer"); + + b.Property("LastStrainAngleId") + .HasColumnType("integer"); + + b.Property("RecoilOffset") + .HasColumnType("double precision"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("SessionAngleOffset") + .HasColumnType("double precision"); + + b.Property("SessionAverageSnapValue") + .HasColumnType("double precision"); + + b.Property("SessionSPM") + .HasColumnType("double precision"); + + b.Property("SessionScore") + .HasColumnType("integer"); + + b.Property("SessionSnapHits") + .HasColumnType("integer"); + + b.Property("StrainAngleBetween") + .HasColumnType("double precision"); + + b.Property("TimeSinceLastEvent") + .HasColumnType("integer"); + + b.Property("WeaponId") + .HasColumnType("integer"); + + b.Property("WeaponReference") + .HasColumnType("text"); + + b.Property("When") + .HasColumnType("timestamp without time zone"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleId"); + + b.HasIndex("HitDestinationId"); + + b.HasIndex("HitOriginId"); + + b.HasIndex("LastStrainAngleId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFACSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.Property("ClientHitStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientHitStatisticId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("DamageInflicted") + .HasColumnType("integer"); + + b.Property("DamageReceived") + .HasColumnType("integer"); + + b.Property("DeathCount") + .HasColumnType("integer"); + + b.Property("HitCount") + .HasColumnType("integer"); + + b.Property("HitLocationId") + .HasColumnType("integer"); + + b.Property("KillCount") + .HasColumnType("integer"); + + b.Property("MeansOfDeathId") + .HasColumnType("integer"); + + b.Property("ReceivedHitCount") + .HasColumnType("integer"); + + b.Property("Score") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("SuicideCount") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("UsageSeconds") + .HasColumnType("integer"); + + b.Property("WeaponAttachmentComboId") + .HasColumnType("integer"); + + b.Property("WeaponId") + .HasColumnType("integer"); + + b.HasKey("ClientHitStatisticId"); + + b.HasIndex("ClientId"); + + b.HasIndex("HitLocationId"); + + b.HasIndex("MeansOfDeathId"); + + b.HasIndex("ServerId"); + + b.HasIndex("WeaponAttachmentComboId"); + + b.HasIndex("WeaponId"); + + b.ToTable("EFClientHitStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.Property("ClientRankingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientRankingHistoryId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Newest") + .HasColumnType("boolean"); + + b.Property("PerformanceMetric") + .HasColumnType("double precision"); + + b.Property("Ranking") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ZScore") + .HasColumnType("double precision"); + + b.HasKey("ClientRankingHistoryId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("Ranking"); + + b.HasIndex("ServerId"); + + b.HasIndex("UpdatedDateTime"); + + b.HasIndex("ZScore"); + + b.ToTable("EFClientRankingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RatingHistoryId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("AverageSnapValue") + .HasColumnType("double precision"); + + b.Property("Deaths") + .HasColumnType("integer"); + + b.Property("EloRating") + .HasColumnType("double precision"); + + b.Property("Kills") + .HasColumnType("integer"); + + b.Property("MaxStrain") + .HasColumnType("double precision"); + + b.Property("RollingWeightedKDR") + .HasColumnType("double precision"); + + b.Property("SPM") + .HasColumnType("double precision"); + + b.Property("Skill") + .HasColumnType("double precision"); + + b.Property("SnapHitCount") + .HasColumnType("integer"); + + b.Property("TimePlayed") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ZScore") + .HasColumnType("double precision"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ZScore"); + + b.HasIndex("ClientId", "TimePlayed", "ZScore"); + + b.ToTable("EFClientStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("HitLocationCountId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("EFClientStatisticsClientId") + .HasColumnType("integer") + .HasColumnName("EFClientStatisticsClientId"); + + b.Property("EFClientStatisticsServerId") + .HasColumnType("bigint") + .HasColumnName("EFClientStatisticsServerId"); + + b.Property("HitCount") + .HasColumnType("integer"); + + b.Property("HitOffsetAverage") + .HasColumnType("real"); + + b.Property("Location") + .HasColumnType("integer"); + + b.Property("MaxAngleDistance") + .HasColumnType("real"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("EFClientStatisticsServerId"); + + b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); + + b.ToTable("EFHitLocationCounts", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("RatingId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ActivityAmount") + .HasColumnType("integer"); + + b.Property("Newest") + .HasColumnType("boolean"); + + b.Property("Performance") + .HasColumnType("double precision"); + + b.Property("Ranking") + .HasColumnType("integer"); + + b.Property("RatingHistoryId") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("When") + .HasColumnType("timestamp without time zone"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("Performance", "Ranking", "When"); + + b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); + + b.ToTable("EFRating", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => + { + b.Property("HitLocationId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("HitLocationId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("HitLocationId"); + + b.HasIndex("Name"); + + b.ToTable("EFHitLocations", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => + { + b.Property("MapId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MapId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("MapId"); + + b.ToTable("EFMaps", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => + { + b.Property("MeansOfDeathId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeansOfDeathId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("MeansOfDeathId"); + + b.ToTable("EFMeansOfDeath", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => + { + b.Property("WeaponId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WeaponId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("WeaponId"); + + b.HasIndex("Name"); + + b.ToTable("EFWeapons", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => + { + b.Property("WeaponAttachmentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WeaponAttachmentId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("WeaponAttachmentId"); + + b.ToTable("EFWeaponAttachments", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.Property("WeaponAttachmentComboId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("WeaponAttachmentComboId")); + + b.Property("Attachment1Id") + .HasColumnType("integer"); + + b.Property("Attachment2Id") + .HasColumnType("integer"); + + b.Property("Attachment3Id") + .HasColumnType("integer"); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Game") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("WeaponAttachmentComboId"); + + b.HasIndex("Attachment1Id"); + + b.HasIndex("Attachment2Id"); + + b.HasIndex("Attachment3Id"); + + b.ToTable("EFWeaponAttachmentCombos", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AliasId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("DateAdded") + .HasColumnType("timestamp without time zone"); + + b.Property("IPAddress") + .HasColumnType("integer"); + + b.Property("LinkId") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("character varying(24)"); + + b.Property("SearchableIPAddress") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("text") + .HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true); + + b.Property("SearchableName") + .HasMaxLength(24) + .HasColumnType("character varying(24)"); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.HasIndex("SearchableIPAddress"); + + b.HasIndex("SearchableName"); + + b.HasIndex("Name", "IPAddress"); + + b.ToTable("EFAlias", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AliasLinkId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ChangeHistoryId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Comment") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CurrentValue") + .HasColumnType("text"); + + b.Property("ImpersonationEntityId") + .HasColumnType("integer"); + + b.Property("OriginEntityId") + .HasColumnType("integer"); + + b.Property("PreviousValue") + .HasColumnType("text"); + + b.Property("TargetEntityId") + .HasColumnType("integer"); + + b.Property("TimeChanged") + .HasColumnType("timestamp without time zone"); + + b.Property("TypeOfChange") + .HasColumnType("integer"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MetaId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Extra") + .HasColumnType("text"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("LinkedMetaId") + .HasColumnType("integer"); + + b.Property("Updated") + .HasColumnType("timestamp without time zone"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.HasIndex("Key"); + + b.HasIndex("LinkedMetaId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PenaltyId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("AutomatedOffense") + .HasColumnType("text"); + + b.Property("Expires") + .HasColumnType("timestamp without time zone"); + + b.Property("IsEvadedOffense") + .HasColumnType("boolean"); + + b.Property("LinkId") + .HasColumnType("integer"); + + b.Property("OffenderId") + .HasColumnType("integer"); + + b.Property("Offense") + .IsRequired() + .HasColumnType("text"); + + b.Property("PunisherId") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("When") + .HasColumnType("timestamp without time zone"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.Property("PenaltyIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("PenaltyIdentifierId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IPv4Address") + .HasColumnType("integer"); + + b.Property("NetworkId") + .HasColumnType("bigint"); + + b.Property("PenaltyId") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("PenaltyIdentifierId"); + + b.HasIndex("IPv4Address"); + + b.HasIndex("NetworkId"); + + b.HasIndex("PenaltyId"); + + b.ToTable("EFPenaltyIdentifiers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.Property("InboxMessageId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("InboxMessageId")); + + b.Property("CreatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("DestinationClientId") + .HasColumnType("integer"); + + b.Property("IsDelivered") + .HasColumnType("boolean"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("SourceClientId") + .HasColumnType("integer"); + + b.Property("UpdatedDateTime") + .HasColumnType("timestamp without time zone"); + + b.HasKey("InboxMessageId"); + + b.HasIndex("DestinationClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("InboxMessages"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServer", b => + { + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("EndPoint") + .HasColumnType("text"); + + b.Property("GameName") + .HasColumnType("integer"); + + b.Property("HostName") + .HasColumnType("text"); + + b.Property("IsPasswordProtected") + .HasColumnType("boolean"); + + b.Property("Port") + .HasColumnType("integer"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.Property("ServerSnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ServerSnapshotId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("CapturedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ClientCount") + .HasColumnType("integer"); + + b.Property("ConnectionInterrupted") + .HasColumnType("boolean"); + + b.Property("MapId") + .HasColumnType("integer"); + + b.Property("PeriodBlock") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.HasKey("ServerSnapshotId"); + + b.HasIndex("MapId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("StatisticId")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ServerId") + .HasColumnType("bigint"); + + b.Property("TotalKills") + .HasColumnType("bigint"); + + b.Property("TotalPlayTime") + .HasColumnType("bigint"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Vector3Id")); + + b.Property("X") + .HasColumnType("real"); + + b.Property("Y") + .HasColumnType("real"); + + b.Property("Z") + .HasColumnType("real"); + + b.HasKey("Vector3Id"); + + b.ToTable("Vector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.HasOne("Data.Models.Client.Stats.EFACSnapshot", "Snapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("SnapshotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "Vector") + .WithMany() + .HasForeignKey("Vector3Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Snapshot"); + + b.Navigation("Vector"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.HasOne("Data.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AliasLink"); + + b.Navigation("CurrentAlias"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.HasOne("Data.Models.Client.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("Data.Models.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + + b.Navigation("Attacker"); + + b.Navigation("DeathOrigin"); + + b.Navigation("KillOrigin"); + + b.Navigation("Server"); + + b.Navigation("Victim"); + + b.Navigation("ViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("CurrentViewAngle"); + + b.Navigation("HitDestination"); + + b.Navigation("HitOrigin"); + + b.Navigation("LastStrainAngle"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFHitLocation", "HitLocation") + .WithMany() + .HasForeignKey("HitLocationId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFMeansOfDeath", "MeansOfDeath") + .WithMany() + .HasForeignKey("MeansOfDeathId"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", "WeaponAttachmentCombo") + .WithMany() + .HasForeignKey("WeaponAttachmentComboId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") + .WithMany() + .HasForeignKey("WeaponId"); + + b.Navigation("Client"); + + b.Navigation("HitLocation"); + + b.Navigation("MeansOfDeath"); + + b.Navigation("Server"); + + b.Navigation("Weapon"); + + b.Navigation("WeaponAttachmentCombo"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("EFClientStatisticsClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.EFClientStatistics", null) + .WithMany("HitLocations") + .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.HasOne("Data.Models.Client.Stats.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("RatingHistory"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment1") + .WithMany() + .HasForeignKey("Attachment1Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment2") + .WithMany() + .HasForeignKey("Attachment2Id"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") + .WithMany() + .HasForeignKey("Attachment3Id"); + + b.Navigation("Attachment1"); + + b.Navigation("Attachment2"); + + b.Navigation("Attachment3"); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId"); + + b.HasOne("Data.Models.EFMeta", "LinkedMeta") + .WithMany() + .HasForeignKey("LinkedMetaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Client"); + + b.Navigation("LinkedMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId"); + + b.HasOne("Data.Models.Client.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + + b.Navigation("Offender"); + + b.Navigation("Punisher"); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.HasOne("Data.Models.EFPenalty", "Penalty") + .WithMany() + .HasForeignKey("PenaltyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Penalty"); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "DestinationClient") + .WithMany() + .HasForeignKey("DestinationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DestinationClient"); + + b.Navigation("Server"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFMap", "Map") + .WithMany() + .HasForeignKey("MapId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Map"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Navigation("AdministeredPenalties"); + + b.Navigation("Meta"); + + b.Navigation("ReceivedPenalties"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Navigation("PredictedViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Navigation("Ratings"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Navigation("HitLocations"); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Navigation("Children"); + + b.Navigation("ReceivedPenalties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/Postgresql/20220613181913_AddAlternateKeyToEFClients.cs b/Data/Migrations/Postgresql/20220613181913_AddAlternateKeyToEFClients.cs new file mode 100644 index 000000000..0ef9fda20 --- /dev/null +++ b/Data/Migrations/Postgresql/20220613181913_AddAlternateKeyToEFClients.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Data.Migrations.Postgresql +{ + public partial class AddAlternateKeyToEFClients : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients"); + + migrationBuilder.Sql("UPDATE \"EFClients\" SET \"GameName\" = 0 WHERE \"GameName\" IS NULL"); + + migrationBuilder.AlterColumn( + name: "GameName", + table: "EFClients", + type: "integer", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "integer", + oldNullable: true); + + migrationBuilder.AddUniqueConstraint( + name: "AK_EFClients_NetworkId_GameName", + table: "EFClients", + columns: new[] { "NetworkId", "GameName" }); + + migrationBuilder.CreateIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients", + column: "NetworkId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropUniqueConstraint( + name: "AK_EFClients_NetworkId_GameName", + table: "EFClients"); + + migrationBuilder.DropIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients"); + + migrationBuilder.AlterColumn( + name: "GameName", + table: "EFClients", + type: "integer", + nullable: true, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.CreateIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients", + column: "NetworkId", + unique: true); + } + } +} diff --git a/Data/Migrations/Postgresql/PostgresqlDatabaseContextModelSnapshot.cs b/Data/Migrations/Postgresql/PostgresqlDatabaseContextModelSnapshot.cs index f1fea9eee..dff368546 100644 --- a/Data/Migrations/Postgresql/PostgresqlDatabaseContextModelSnapshot.cs +++ b/Data/Migrations/Postgresql/PostgresqlDatabaseContextModelSnapshot.cs @@ -71,7 +71,7 @@ namespace Data.Migrations.Postgresql b.Property("FirstConnection") .HasColumnType("timestamp without time zone"); - b.Property("GameName") + b.Property("GameName") .HasColumnType("integer"); b.Property("LastConnection") @@ -97,12 +97,13 @@ namespace Data.Migrations.Postgresql b.HasKey("ClientId"); + b.HasAlternateKey("NetworkId", "GameName"); + b.HasIndex("AliasLinkId"); b.HasIndex("CurrentAliasId"); - b.HasIndex("NetworkId") - .IsUnique(); + b.HasIndex("NetworkId"); b.ToTable("EFClients", (string)null); }); diff --git a/Data/Migrations/Sqlite/20220613160952_AddAlternateKeyToEFClients.Designer.cs b/Data/Migrations/Sqlite/20220613160952_AddAlternateKeyToEFClients.Designer.cs new file mode 100644 index 000000000..70bf94a76 --- /dev/null +++ b/Data/Migrations/Sqlite/20220613160952_AddAlternateKeyToEFClients.Designer.cs @@ -0,0 +1,1637 @@ +// +using System; +using Data.MigrationContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Data.Migrations.Sqlite +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20220613160952_AddAlternateKeyToEFClients")] + partial class AddAlternateKeyToEFClients + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.1"); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.Property("ACSnapshotVector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("SnapshotId") + .HasColumnType("INTEGER"); + + b.Property("Vector3Id") + .HasColumnType("INTEGER"); + + b.HasKey("ACSnapshotVector3Id"); + + b.HasIndex("SnapshotId"); + + b.HasIndex("Vector3Id"); + + b.ToTable("EFACSnapshotVector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AliasLinkId") + .HasColumnType("INTEGER"); + + b.Property("Connections") + .HasColumnType("INTEGER"); + + b.Property("CurrentAliasId") + .HasColumnType("INTEGER"); + + b.Property("FirstConnection") + .HasColumnType("TEXT"); + + b.Property("GameName") + .HasColumnType("INTEGER"); + + b.Property("LastConnection") + .HasColumnType("TEXT"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Masked") + .HasColumnType("INTEGER"); + + b.Property("NetworkId") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("PasswordSalt") + .HasColumnType("TEXT"); + + b.Property("TotalConnectionTime") + .HasColumnType("INTEGER"); + + b.HasKey("ClientId"); + + b.HasAlternateKey("NetworkId", "GameName"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("NetworkId"); + + b.ToTable("EFClients", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.Property("ClientConnectionId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("ConnectionType") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("ClientConnectionId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientConnectionHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AttackerId") + .HasColumnType("INTEGER"); + + b.Property("Damage") + .HasColumnType("INTEGER"); + + b.Property("DeathOriginVector3Id") + .HasColumnType("INTEGER"); + + b.Property("DeathType") + .HasColumnType("INTEGER"); + + b.Property("Fraction") + .HasColumnType("REAL"); + + b.Property("HitLoc") + .HasColumnType("INTEGER"); + + b.Property("IsKill") + .HasColumnType("INTEGER"); + + b.Property("KillOriginVector3Id") + .HasColumnType("INTEGER"); + + b.Property("Map") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("VictimId") + .HasColumnType("INTEGER"); + + b.Property("ViewAnglesVector3Id") + .HasColumnType("INTEGER"); + + b.Property("VisibilityPercentage") + .HasColumnType("REAL"); + + b.Property("Weapon") + .HasColumnType("INTEGER"); + + b.Property("WeaponReference") + .HasColumnType("TEXT"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("SentIngame") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("TimeSent") + .HasColumnType("TEXT"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("TimeSent"); + + b.ToTable("EFClientMessages", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Property("SnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CurrentSessionLength") + .HasColumnType("INTEGER"); + + b.Property("CurrentStrain") + .HasColumnType("REAL"); + + b.Property("CurrentViewAngleId") + .HasColumnType("INTEGER"); + + b.Property("Deaths") + .HasColumnType("INTEGER"); + + b.Property("Distance") + .HasColumnType("REAL"); + + b.Property("EloRating") + .HasColumnType("REAL"); + + b.Property("HitDestinationId") + .HasColumnType("INTEGER"); + + b.Property("HitLocation") + .HasColumnType("INTEGER"); + + b.Property("HitLocationReference") + .HasColumnType("TEXT"); + + b.Property("HitOriginId") + .HasColumnType("INTEGER"); + + b.Property("HitType") + .HasColumnType("INTEGER"); + + b.Property("Hits") + .HasColumnType("INTEGER"); + + b.Property("Kills") + .HasColumnType("INTEGER"); + + b.Property("LastStrainAngleId") + .HasColumnType("INTEGER"); + + b.Property("RecoilOffset") + .HasColumnType("REAL"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("SessionAngleOffset") + .HasColumnType("REAL"); + + b.Property("SessionAverageSnapValue") + .HasColumnType("REAL"); + + b.Property("SessionSPM") + .HasColumnType("REAL"); + + b.Property("SessionScore") + .HasColumnType("INTEGER"); + + b.Property("SessionSnapHits") + .HasColumnType("INTEGER"); + + b.Property("StrainAngleBetween") + .HasColumnType("REAL"); + + b.Property("TimeSinceLastEvent") + .HasColumnType("INTEGER"); + + b.Property("WeaponId") + .HasColumnType("INTEGER"); + + b.Property("WeaponReference") + .HasColumnType("TEXT"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("SnapshotId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CurrentViewAngleId"); + + b.HasIndex("HitDestinationId"); + + b.HasIndex("HitOriginId"); + + b.HasIndex("LastStrainAngleId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFACSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.Property("ClientHitStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("DamageInflicted") + .HasColumnType("INTEGER"); + + b.Property("DamageReceived") + .HasColumnType("INTEGER"); + + b.Property("DeathCount") + .HasColumnType("INTEGER"); + + b.Property("HitCount") + .HasColumnType("INTEGER"); + + b.Property("HitLocationId") + .HasColumnType("INTEGER"); + + b.Property("KillCount") + .HasColumnType("INTEGER"); + + b.Property("MeansOfDeathId") + .HasColumnType("INTEGER"); + + b.Property("ReceivedHitCount") + .HasColumnType("INTEGER"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("SuicideCount") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.Property("UsageSeconds") + .HasColumnType("INTEGER"); + + b.Property("WeaponAttachmentComboId") + .HasColumnType("INTEGER"); + + b.Property("WeaponId") + .HasColumnType("INTEGER"); + + b.HasKey("ClientHitStatisticId"); + + b.HasIndex("ClientId"); + + b.HasIndex("HitLocationId"); + + b.HasIndex("MeansOfDeathId"); + + b.HasIndex("ServerId"); + + b.HasIndex("WeaponAttachmentComboId"); + + b.HasIndex("WeaponId"); + + b.ToTable("EFClientHitStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.Property("ClientRankingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Newest") + .HasColumnType("INTEGER"); + + b.Property("PerformanceMetric") + .HasColumnType("REAL"); + + b.Property("Ranking") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.Property("ZScore") + .HasColumnType("REAL"); + + b.HasKey("ClientRankingHistoryId"); + + b.HasIndex("ClientId"); + + b.HasIndex("CreatedDateTime"); + + b.HasIndex("Ranking"); + + b.HasIndex("ServerId"); + + b.HasIndex("UpdatedDateTime"); + + b.HasIndex("ZScore"); + + b.ToTable("EFClientRankingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Property("RatingHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.HasKey("RatingHistoryId"); + + b.HasIndex("ClientId"); + + b.ToTable("EFClientRatingHistory", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AverageSnapValue") + .HasColumnType("REAL"); + + b.Property("Deaths") + .HasColumnType("INTEGER"); + + b.Property("EloRating") + .HasColumnType("REAL"); + + b.Property("Kills") + .HasColumnType("INTEGER"); + + b.Property("MaxStrain") + .HasColumnType("REAL"); + + b.Property("RollingWeightedKDR") + .HasColumnType("REAL"); + + b.Property("SPM") + .HasColumnType("REAL"); + + b.Property("Skill") + .HasColumnType("REAL"); + + b.Property("SnapHitCount") + .HasColumnType("INTEGER"); + + b.Property("TimePlayed") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("ZScore") + .HasColumnType("REAL"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ZScore"); + + b.HasIndex("ClientId", "TimePlayed", "ZScore"); + + b.ToTable("EFClientStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("EFClientStatisticsClientId") + .HasColumnType("INTEGER") + .HasColumnName("EFClientStatisticsClientId"); + + b.Property("EFClientStatisticsServerId") + .HasColumnType("INTEGER") + .HasColumnName("EFClientStatisticsServerId"); + + b.Property("HitCount") + .HasColumnType("INTEGER"); + + b.Property("HitOffsetAverage") + .HasColumnType("REAL"); + + b.Property("Location") + .HasColumnType("INTEGER"); + + b.Property("MaxAngleDistance") + .HasColumnType("REAL"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("EFClientStatisticsServerId"); + + b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); + + b.ToTable("EFHitLocationCounts", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ActivityAmount") + .HasColumnType("INTEGER"); + + b.Property("Newest") + .HasColumnType("INTEGER"); + + b.Property("Performance") + .HasColumnType("REAL"); + + b.Property("Ranking") + .HasColumnType("INTEGER"); + + b.Property("RatingHistoryId") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("RatingId"); + + b.HasIndex("RatingHistoryId"); + + b.HasIndex("ServerId"); + + b.HasIndex("Performance", "Ranking", "When"); + + b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); + + b.ToTable("EFRating", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => + { + b.Property("HitLocationId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("HitLocationId"); + + b.HasIndex("Name"); + + b.ToTable("EFHitLocations", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => + { + b.Property("MapId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("MapId"); + + b.ToTable("EFMaps", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => + { + b.Property("MeansOfDeathId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("MeansOfDeathId"); + + b.ToTable("EFMeansOfDeath", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => + { + b.Property("WeaponId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("WeaponId"); + + b.HasIndex("Name"); + + b.ToTable("EFWeapons", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => + { + b.Property("WeaponAttachmentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("WeaponAttachmentId"); + + b.ToTable("EFWeaponAttachments", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.Property("WeaponAttachmentComboId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Attachment1Id") + .HasColumnType("INTEGER"); + + b.Property("Attachment2Id") + .HasColumnType("INTEGER"); + + b.Property("Attachment3Id") + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("Game") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("WeaponAttachmentComboId"); + + b.HasIndex("Attachment1Id"); + + b.HasIndex("Attachment2Id"); + + b.HasIndex("Attachment3Id"); + + b.ToTable("EFWeaponAttachmentCombos", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("IPAddress") + .HasColumnType("INTEGER"); + + b.Property("LinkId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.Property("SearchableIPAddress") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("TEXT") + .HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true); + + b.Property("SearchableName") + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.HasKey("AliasId"); + + b.HasIndex("IPAddress"); + + b.HasIndex("LinkId"); + + b.HasIndex("Name"); + + b.HasIndex("SearchableIPAddress"); + + b.HasIndex("SearchableName"); + + b.HasIndex("Name", "IPAddress"); + + b.ToTable("EFAlias", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFChangeHistory", b => + { + b.Property("ChangeHistoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CurrentValue") + .HasColumnType("TEXT"); + + b.Property("ImpersonationEntityId") + .HasColumnType("INTEGER"); + + b.Property("OriginEntityId") + .HasColumnType("INTEGER"); + + b.Property("PreviousValue") + .HasColumnType("TEXT"); + + b.Property("TargetEntityId") + .HasColumnType("INTEGER"); + + b.Property("TimeChanged") + .HasColumnType("TEXT"); + + b.Property("TypeOfChange") + .HasColumnType("INTEGER"); + + b.HasKey("ChangeHistoryId"); + + b.ToTable("EFChangeHistory"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.Property("MetaId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Extra") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("LinkedMetaId") + .HasColumnType("INTEGER"); + + b.Property("Updated") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("MetaId"); + + b.HasIndex("ClientId"); + + b.HasIndex("Key"); + + b.HasIndex("LinkedMetaId"); + + b.ToTable("EFMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("AutomatedOffense") + .HasColumnType("TEXT"); + + b.Property("Expires") + .HasColumnType("TEXT"); + + b.Property("IsEvadedOffense") + .HasColumnType("INTEGER"); + + b.Property("LinkId") + .HasColumnType("INTEGER"); + + b.Property("OffenderId") + .HasColumnType("INTEGER"); + + b.Property("Offense") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PunisherId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("When") + .HasColumnType("TEXT"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties", (string)null); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.Property("PenaltyIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("IPv4Address") + .HasColumnType("INTEGER"); + + b.Property("NetworkId") + .HasColumnType("INTEGER"); + + b.Property("PenaltyId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("PenaltyIdentifierId"); + + b.HasIndex("IPv4Address"); + + b.HasIndex("NetworkId"); + + b.HasIndex("PenaltyId"); + + b.ToTable("EFPenaltyIdentifiers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.Property("InboxMessageId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDateTime") + .HasColumnType("TEXT"); + + b.Property("DestinationClientId") + .HasColumnType("INTEGER"); + + b.Property("IsDelivered") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("SourceClientId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("InboxMessageId"); + + b.HasIndex("DestinationClientId"); + + b.HasIndex("ServerId"); + + b.HasIndex("SourceClientId"); + + b.ToTable("InboxMessages"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServer", b => + { + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("EndPoint") + .HasColumnType("TEXT"); + + b.Property("GameName") + .HasColumnType("INTEGER"); + + b.Property("HostName") + .HasColumnType("TEXT"); + + b.Property("IsPasswordProtected") + .HasColumnType("INTEGER"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.Property("ServerSnapshotId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("CapturedAt") + .HasColumnType("TEXT"); + + b.Property("ClientCount") + .HasColumnType("INTEGER"); + + b.Property("ConnectionInterrupted") + .HasColumnType("INTEGER"); + + b.Property("MapId") + .HasColumnType("INTEGER"); + + b.Property("PeriodBlock") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.HasKey("ServerSnapshotId"); + + b.HasIndex("MapId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerSnapshot", (string)null); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.Property("TotalKills") + .HasColumnType("INTEGER"); + + b.Property("TotalPlayTime") + .HasColumnType("INTEGER"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics", (string)null); + }); + + modelBuilder.Entity("Data.Models.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("X") + .HasColumnType("REAL"); + + b.Property("Y") + .HasColumnType("REAL"); + + b.Property("Z") + .HasColumnType("REAL"); + + b.HasKey("Vector3Id"); + + b.ToTable("Vector3", (string)null); + }); + + modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => + { + b.HasOne("Data.Models.Client.Stats.EFACSnapshot", "Snapshot") + .WithMany("PredictedViewAngles") + .HasForeignKey("SnapshotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "Vector") + .WithMany() + .HasForeignKey("Vector3Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Snapshot"); + + b.Navigation("Vector"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.HasOne("Data.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AliasLink"); + + b.Navigation("CurrentAlias"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientKill", b => + { + b.HasOne("Data.Models.Client.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("Data.Models.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + + b.Navigation("Attacker"); + + b.Navigation("DeathOrigin"); + + b.Navigation("KillOrigin"); + + b.Navigation("Server"); + + b.Navigation("Victim"); + + b.Navigation("ViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "CurrentViewAngle") + .WithMany() + .HasForeignKey("CurrentViewAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitDestination") + .WithMany() + .HasForeignKey("HitDestinationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "HitOrigin") + .WithMany() + .HasForeignKey("HitOriginId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Vector3", "LastStrainAngle") + .WithMany() + .HasForeignKey("LastStrainAngleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("CurrentViewAngle"); + + b.Navigation("HitDestination"); + + b.Navigation("HitOrigin"); + + b.Navigation("LastStrainAngle"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFHitLocation", "HitLocation") + .WithMany() + .HasForeignKey("HitLocationId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFMeansOfDeath", "MeansOfDeath") + .WithMany() + .HasForeignKey("MeansOfDeathId"); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", "WeaponAttachmentCombo") + .WithMany() + .HasForeignKey("WeaponAttachmentComboId"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") + .WithMany() + .HasForeignKey("WeaponId"); + + b.Navigation("Client"); + + b.Navigation("HitLocation"); + + b.Navigation("MeansOfDeath"); + + b.Navigation("Server"); + + b.Navigation("Weapon"); + + b.Navigation("WeaponAttachmentCombo"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany() + .HasForeignKey("EFClientStatisticsClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.EFClientStatistics", null) + .WithMany("HitLocations") + .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => + { + b.HasOne("Data.Models.Client.Stats.EFClientRatingHistory", "RatingHistory") + .WithMany("Ratings") + .HasForeignKey("RatingHistoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.Navigation("RatingHistory"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment1") + .WithMany() + .HasForeignKey("Attachment1Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment2") + .WithMany() + .HasForeignKey("Attachment2Id"); + + b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") + .WithMany() + .HasForeignKey("Attachment3Id"); + + b.Navigation("Attachment1"); + + b.Navigation("Attachment2"); + + b.Navigation("Attachment3"); + }); + + modelBuilder.Entity("Data.Models.EFAlias", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + }); + + modelBuilder.Entity("Data.Models.EFMeta", b => + { + b.HasOne("Data.Models.Client.EFClient", "Client") + .WithMany("Meta") + .HasForeignKey("ClientId"); + + b.HasOne("Data.Models.EFMeta", "LinkedMeta") + .WithMany() + .HasForeignKey("LinkedMetaId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Client"); + + b.Navigation("LinkedMeta"); + }); + + modelBuilder.Entity("Data.Models.EFPenalty", b => + { + b.HasOne("Data.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId"); + + b.HasOne("Data.Models.Client.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Data.Models.Client.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Link"); + + b.Navigation("Offender"); + + b.Navigation("Punisher"); + }); + + modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b => + { + b.HasOne("Data.Models.EFPenalty", "Penalty") + .WithMany() + .HasForeignKey("PenaltyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Penalty"); + }); + + modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => + { + b.HasOne("Data.Models.Client.EFClient", "DestinationClient") + .WithMany() + .HasForeignKey("DestinationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId"); + + b.HasOne("Data.Models.Client.EFClient", "SourceClient") + .WithMany() + .HasForeignKey("SourceClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DestinationClient"); + + b.Navigation("Server"); + + b.Navigation("SourceClient"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => + { + b.HasOne("Data.Models.Client.Stats.Reference.EFMap", "Map") + .WithMany() + .HasForeignKey("MapId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Map"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => + { + b.HasOne("Data.Models.Server.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Data.Models.Client.EFClient", b => + { + b.Navigation("AdministeredPenalties"); + + b.Navigation("Meta"); + + b.Navigation("ReceivedPenalties"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => + { + b.Navigation("PredictedViewAngles"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => + { + b.Navigation("Ratings"); + }); + + modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => + { + b.Navigation("HitLocations"); + }); + + modelBuilder.Entity("Data.Models.EFAliasLink", b => + { + b.Navigation("Children"); + + b.Navigation("ReceivedPenalties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/Sqlite/20220613160952_AddAlternateKeyToEFClients.cs b/Data/Migrations/Sqlite/20220613160952_AddAlternateKeyToEFClients.cs new file mode 100644 index 000000000..ee621b18b --- /dev/null +++ b/Data/Migrations/Sqlite/20220613160952_AddAlternateKeyToEFClients.cs @@ -0,0 +1,61 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Data.Migrations.Sqlite +{ + public partial class AddAlternateKeyToEFClients : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients"); + + migrationBuilder.AlterColumn( + name: "GameName", + table: "EFClients", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AddUniqueConstraint( + name: "AK_EFClients_NetworkId_GameName", + table: "EFClients", + columns: new[] { "NetworkId", "GameName" }); + + migrationBuilder.CreateIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients", + column: "NetworkId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropUniqueConstraint( + name: "AK_EFClients_NetworkId_GameName", + table: "EFClients"); + + migrationBuilder.DropIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients"); + + migrationBuilder.AlterColumn( + name: "GameName", + table: "EFClients", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + + migrationBuilder.CreateIndex( + name: "IX_EFClients_NetworkId", + table: "EFClients", + column: "NetworkId", + unique: true); + } + } +} diff --git a/Data/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs b/Data/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs index 6e3fea59d..09c89a272 100644 --- a/Data/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs +++ b/Data/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs @@ -62,7 +62,7 @@ namespace Data.Migrations.Sqlite b.Property("FirstConnection") .HasColumnType("TEXT"); - b.Property("GameName") + b.Property("GameName") .HasColumnType("INTEGER"); b.Property("LastConnection") @@ -88,12 +88,13 @@ namespace Data.Migrations.Sqlite b.HasKey("ClientId"); + b.HasAlternateKey("NetworkId", "GameName"); + b.HasIndex("AliasLinkId"); b.HasIndex("CurrentAliasId"); - b.HasIndex("NetworkId") - .IsUnique(); + b.HasIndex("NetworkId"); b.ToTable("EFClients", (string)null); }); diff --git a/Data/Models/Client/EFClient.cs b/Data/Models/Client/EFClient.cs index 8ba44276d..55fe19b66 100644 --- a/Data/Models/Client/EFClient.cs +++ b/Data/Models/Client/EFClient.cs @@ -63,7 +63,7 @@ namespace Data.Models.Client public DateTime FirstConnection { get; set; } [Required] public DateTime LastConnection { get; set; } - public Reference.Game? GameName { get; set; } = Reference.Game.UKN; + public Reference.Game GameName { get; set; } = Reference.Game.UKN; public bool Masked { get; set; } [Required] public int AliasLinkId { get; set; } diff --git a/Plugins/AutomessageFeed/AutomessageFeed.csproj b/Plugins/AutomessageFeed/AutomessageFeed.csproj index 8118e68b1..67c07f89a 100644 --- a/Plugins/AutomessageFeed/AutomessageFeed.csproj +++ b/Plugins/AutomessageFeed/AutomessageFeed.csproj @@ -10,7 +10,7 @@ - + diff --git a/Plugins/LiveRadar/LiveRadar.csproj b/Plugins/LiveRadar/LiveRadar.csproj index 7e872346e..110d4ca4b 100644 --- a/Plugins/LiveRadar/LiveRadar.csproj +++ b/Plugins/LiveRadar/LiveRadar.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Login/Commands/LoginCommand.cs b/Plugins/Login/Commands/LoginCommand.cs index debe4be20..64ce6abb5 100644 --- a/Plugins/Login/Commands/LoginCommand.cs +++ b/Plugins/Login/Commands/LoginCommand.cs @@ -4,6 +4,7 @@ using SharedLibraryCore.Configuration; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Interfaces; using System.Threading.Tasks; +using SharedLibraryCore.Helpers; namespace IW4MAdmin.Plugins.Login.Commands { @@ -18,7 +19,7 @@ namespace IW4MAdmin.Plugins.Login.Commands RequiresTarget = false; Arguments = new CommandArgument[] { - new CommandArgument() + new() { Name = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ARGS_PASSWORD"], Required = true @@ -26,24 +27,29 @@ namespace IW4MAdmin.Plugins.Login.Commands }; } - public override async Task ExecuteAsync(GameEvent E) + public override async Task ExecuteAsync(GameEvent gameEvent) { - bool success = E.Owner.Manager.TokenAuthenticator.AuthorizeToken(E.Origin.NetworkId, E.Data); + var success = gameEvent.Owner.Manager.TokenAuthenticator.AuthorizeToken(new TokenIdentifier + { + NetworkId = gameEvent.Origin.NetworkId, + Game = gameEvent.Origin.GameName, + Token = gameEvent.Data + }); if (!success) { - string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, E.Origin.PasswordSalt)); - success = hashedPassword[0] == E.Origin.Password; + var hashedPassword = await Task.FromResult(Hashing.Hash(gameEvent.Data, gameEvent.Origin.PasswordSalt)); + success = hashedPassword[0] == gameEvent.Origin.Password; } if (success) { - Plugin.AuthorizedClients[E.Origin.ClientId] = true; + Plugin.AuthorizedClients[gameEvent.Origin.ClientId] = true; } _ = success ? - E.Origin.Tell(_translationLookup["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]) : - E.Origin.Tell(_translationLookup["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]); + gameEvent.Origin.Tell(_translationLookup["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]) : + gameEvent.Origin.Tell(_translationLookup["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]); } } } diff --git a/Plugins/Login/Login.csproj b/Plugins/Login/Login.csproj index da6ef75a1..ba07b94f6 100644 --- a/Plugins/Login/Login.csproj +++ b/Plugins/Login/Login.csproj @@ -19,7 +19,7 @@ - + diff --git a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj index ecc32b9f3..7cb9202cd 100644 --- a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj +++ b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs b/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs index 571654251..f1e24ffa7 100644 --- a/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs +++ b/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Data.Abstractions; +using Data.Models; using Data.Models.Client; using Data.Models.Client.Stats; using IW4MAdmin.Plugins.Stats; @@ -12,7 +13,6 @@ using Microsoft.Extensions.Logging; using SharedLibraryCore.Dtos; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; -using Stats.Client.Abstractions; using Stats.Dtos; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -50,7 +50,8 @@ namespace Stats.Helpers { client.ClientId, client.CurrentAlias.Name, - client.Level + client.Level, + client.GameName }).FirstOrDefaultAsync(client => client.ClientId == query.ClientId); if (clientInfo == null) @@ -111,8 +112,9 @@ namespace Stats.Helpers Rating = mostRecentRanking?.PerformanceMetric, All = hitStats, Servers = _manager.GetServers() - .Select(server => new ServerInfo() - {Name = server.Hostname, IPAddress = server.IP, Port = server.Port}) + .Select(server => new ServerInfo + {Name = server.Hostname, IPAddress = server.IP, Port = server.Port, Game = (Reference.Game)server.GameName}) + .Where(server => server.Game == clientInfo.GameName) .ToList(), Aggregate = hitStats.FirstOrDefault(hit => hit.HitLocationId == null && hit.ServerId == serverId && hit.WeaponId == null && @@ -153,4 +155,4 @@ namespace Stats.Helpers && (zScore == null || stats.ZScore > zScore); } } -} \ No newline at end of file +} diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj index 9e9eda66d..d466aba9f 100644 --- a/Plugins/Stats/Stats.csproj +++ b/Plugins/Stats/Stats.csproj @@ -17,7 +17,7 @@ - + diff --git a/Plugins/Welcome/Welcome.csproj b/Plugins/Welcome/Welcome.csproj index d5491f3fa..29d82435b 100644 --- a/Plugins/Welcome/Welcome.csproj +++ b/Plugins/Welcome/Welcome.csproj @@ -20,7 +20,7 @@ - + diff --git a/SharedLibraryCore/BaseController.cs b/SharedLibraryCore/BaseController.cs index cbea583fc..7190bb4e3 100644 --- a/SharedLibraryCore/BaseController.cs +++ b/SharedLibraryCore/BaseController.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using Data.Context; using Data.Models; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; @@ -25,26 +24,32 @@ namespace SharedLibraryCore /// /// life span in months /// - private const int COOKIE_LIFESPAN = 3; + private const int CookieLifespan = 3; private static readonly byte[] LocalHost = { 127, 0, 0, 1 }; - private static string SocialLink; - private static string SocialTitle; - protected readonly DatabaseContext Context; + private static string _socialLink; + private static string _socialTitle; + protected List Pages; protected List PermissionsSet; + protected bool Authorized { get; set; } + protected TranslationLookup Localization { get; } + protected EFClient Client { get; } + protected ApplicationConfiguration AppConfig { get; } + + public IManager Manager { get; } public BaseController(IManager manager) { AlertManager = manager.AlertManager; Manager = manager; - Localization ??= Utilities.CurrentLocalization.LocalizationIndex; + Localization = Utilities.CurrentLocalization.LocalizationIndex; AppConfig = Manager.GetApplicationSettings().Configuration(); - if (AppConfig.EnableSocialLink && SocialLink == null) + if (AppConfig.EnableSocialLink && _socialLink == null) { - SocialLink = AppConfig.SocialLinkAddress; - SocialTitle = AppConfig.SocialLinkTitle; + _socialLink = AppConfig.SocialLinkAddress; + _socialTitle = AppConfig.SocialLinkTitle; } Pages = Manager.GetPageList().Pages @@ -59,7 +64,7 @@ namespace SharedLibraryCore ViewBag.EnableColorCodes = AppConfig.EnableColorCodes; ViewBag.Language = Utilities.CurrentLocalization.Culture.TwoLetterISOLanguageName; - Client ??= new EFClient + Client = new EFClient { ClientId = -1, Level = Data.Models.Client.EFClient.Permission.Banned, @@ -67,11 +72,7 @@ namespace SharedLibraryCore }; } - public IManager Manager { get; } - protected bool Authorized { get; set; } - protected TranslationLookup Localization { get; } - protected EFClient Client { get; } - protected ApplicationConfiguration AppConfig { get; } + protected async Task SignInAsync(ClaimsPrincipal claimsPrinciple) { @@ -79,7 +80,7 @@ namespace SharedLibraryCore new AuthenticationProperties { AllowRefresh = true, - ExpiresUtc = DateTime.UtcNow.AddMonths(COOKIE_LIFESPAN), + ExpiresUtc = DateTime.UtcNow.AddMonths(CookieLifespan), IsPersistent = true, IssuedUtc = DateTime.UtcNow }); @@ -99,7 +100,7 @@ namespace SharedLibraryCore Client.ClientId = clientId; Client.NetworkId = clientId == 1 ? 0 - : User.Claims.First(_claim => _claim.Type == ClaimTypes.PrimarySid).Value + : User.Claims.First(claim => claim.Type == ClaimTypes.PrimarySid).Value .ConvertGuidToLong(NumberStyles.HexNumber); Client.Level = (Data.Models.Client.EFClient.Permission)Enum.Parse( typeof(Data.Models.Client.EFClient.Permission), @@ -107,6 +108,9 @@ namespace SharedLibraryCore Client.CurrentAlias = new EFAlias { Name = User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value }; Authorized = Client.ClientId >= 0; + Client.GameName = + Enum.Parse(User.Claims + .First(claim => claim.Type == ClaimTypes.PrimaryGroupSid).Value); } } @@ -134,6 +138,7 @@ namespace SharedLibraryCore new Claim(ClaimTypes.Role, Client.Level.ToString()), new Claim(ClaimTypes.Sid, Client.ClientId.ToString()), new Claim(ClaimTypes.PrimarySid, Client.NetworkId.ToString("X")), + new Claim(ClaimTypes.PrimaryGroupSid, Client.GameName.ToString()) }; var claimsIdentity = new ClaimsIdentity(claims, "login"); SignInAsync(new ClaimsPrincipal(claimsIdentity)).Wait(); @@ -153,8 +158,8 @@ namespace SharedLibraryCore ViewBag.Url = AppConfig.WebfrontUrl; ViewBag.User = Client; ViewBag.Version = Manager.Version; - ViewBag.SocialLink = SocialLink ?? ""; - ViewBag.SocialTitle = SocialTitle; + ViewBag.SocialLink = _socialLink ?? ""; + ViewBag.SocialTitle = _socialTitle; ViewBag.Pages = Pages; ViewBag.Localization = Utilities.CurrentLocalization.LocalizationIndex; ViewBag.CustomBranding = shouldUseCommunityName diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index 6052fae8c..06dd87763 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -381,7 +381,7 @@ namespace SharedLibraryCore.Commands { // todo: don't do the lookup here var penalties = await gameEvent.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(gameEvent.Target.AliasLinkId, - gameEvent.Target.CurrentAliasId, gameEvent.Target.NetworkId, gameEvent.Target.CurrentAlias.IPAddress); + gameEvent.Target.CurrentAliasId, gameEvent.Target.NetworkId, gameEvent.Target.GameName, gameEvent.Target.CurrentAlias.IPAddress); if (penalties .FirstOrDefault(p => @@ -897,7 +897,7 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { var existingPenalties = await E.Owner.Manager.GetPenaltyService() - .GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.CurrentAliasId, E.Target.NetworkId, E.Target.IPAddress); + .GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.CurrentAliasId, E.Target.NetworkId, E.Target.GameName, E.Target.IPAddress); var penalty = existingPenalties.FirstOrDefault(b => b.Type > EFPenalty.PenaltyType.Kick); if (penalty == null) diff --git a/SharedLibraryCore/Commands/RequestTokenCommand.cs b/SharedLibraryCore/Commands/RequestTokenCommand.cs index 2409c53bb..9988b52aa 100644 --- a/SharedLibraryCore/Commands/RequestTokenCommand.cs +++ b/SharedLibraryCore/Commands/RequestTokenCommand.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Data.Models.Client; using SharedLibraryCore.Configuration; +using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; namespace SharedLibraryCore.Commands @@ -19,13 +20,17 @@ namespace SharedLibraryCore.Commands RequiresTarget = false; } - public override Task ExecuteAsync(GameEvent E) + public override Task ExecuteAsync(GameEvent gameEvent) { - var state = E.Owner.Manager.TokenAuthenticator.GenerateNextToken(E.Origin.NetworkId); - E.Origin.Tell(string.Format(_translationLookup["COMMANDS_GENERATETOKEN_SUCCESS"], state.Token, - $"{state.RemainingTime} {_translationLookup["GLOBAL_MINUTES"]}", E.Origin.ClientId)); + var state = gameEvent.Owner.Manager.TokenAuthenticator.GenerateNextToken(new TokenIdentifier + { + Game = gameEvent.Origin.GameName, + NetworkId = gameEvent.Origin.NetworkId + }); + gameEvent.Origin.Tell(string.Format(_translationLookup["COMMANDS_GENERATETOKEN_SUCCESS"], state.Token, + $"{state.RemainingTime} {_translationLookup["GLOBAL_MINUTES"]}", gameEvent.Origin.ClientId)); return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/Helpers/TokenIdentifier.cs b/SharedLibraryCore/Helpers/TokenIdentifier.cs new file mode 100644 index 000000000..b03eb9331 --- /dev/null +++ b/SharedLibraryCore/Helpers/TokenIdentifier.cs @@ -0,0 +1,11 @@ +using Data.Models; +using SharedLibraryCore.Interfaces; + +namespace SharedLibraryCore.Helpers; + +public class TokenIdentifier : ITokenIdentifier +{ + public long NetworkId { get; set; } + public Reference.Game Game { get; set; } + public string Token { get; set; } +} diff --git a/SharedLibraryCore/Helpers/TokenState.cs b/SharedLibraryCore/Helpers/TokenState.cs index 432019cbf..48403f32e 100644 --- a/SharedLibraryCore/Helpers/TokenState.cs +++ b/SharedLibraryCore/Helpers/TokenState.cs @@ -4,7 +4,6 @@ namespace SharedLibraryCore.Helpers { public sealed class TokenState { - public long NetworkId { get; set; } public DateTime RequestTime { get; set; } = DateTime.Now; public TimeSpan TokenDuration { get; set; } public string Token { get; set; } @@ -12,4 +11,4 @@ namespace SharedLibraryCore.Helpers public string RemainingTime => Math.Round(-(DateTime.Now - RequestTime).Subtract(TokenDuration).TotalMinutes, 1) .ToString(); } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/Interfaces/IEntityService.cs b/SharedLibraryCore/Interfaces/IEntityService.cs index 8df46be1a..b0cb90edf 100644 --- a/SharedLibraryCore/Interfaces/IEntityService.cs +++ b/SharedLibraryCore/Interfaces/IEntityService.cs @@ -10,7 +10,7 @@ namespace SharedLibraryCore.Interfaces Task Delete(T entity); Task Update(T entity); Task Get(int entityID); - Task GetUnique(long entityProperty); + Task GetUnique(long entityProperty, object altKey); Task> Find(Func expression); } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/Interfaces/ITokenAuthentication.cs b/SharedLibraryCore/Interfaces/ITokenAuthentication.cs index a19c3cc82..d782f1978 100644 --- a/SharedLibraryCore/Interfaces/ITokenAuthentication.cs +++ b/SharedLibraryCore/Interfaces/ITokenAuthentication.cs @@ -7,16 +7,15 @@ namespace SharedLibraryCore.Interfaces /// /// generates and returns a token for the given network id /// - /// network id of the players to generate the token for + /// auth information for next token generation /// 4 character string token - TokenState GenerateNextToken(long networkId); + TokenState GenerateNextToken(ITokenIdentifier authInfo); /// /// authorizes given token /// - /// network id of the client to authorize - /// token to authorize + /// auth information /// true if token authorized successfully, false otherwise - bool AuthorizeToken(long networkId, string token); + bool AuthorizeToken(ITokenIdentifier authInfo); } } diff --git a/SharedLibraryCore/Interfaces/ITokenIdentifier.cs b/SharedLibraryCore/Interfaces/ITokenIdentifier.cs new file mode 100644 index 000000000..0e2e0a0e9 --- /dev/null +++ b/SharedLibraryCore/Interfaces/ITokenIdentifier.cs @@ -0,0 +1,11 @@ + +using Data.Models; + +namespace SharedLibraryCore.Interfaces; + +public interface ITokenIdentifier +{ + long NetworkId { get; } + Reference.Game Game { get; set; } + string Token { get; set; } +} diff --git a/SharedLibraryCore/PartialEntities/EFClient.cs b/SharedLibraryCore/PartialEntities/EFClient.cs index b52e56789..289d583b7 100644 --- a/SharedLibraryCore/PartialEntities/EFClient.cs +++ b/SharedLibraryCore/PartialEntities/EFClient.cs @@ -682,7 +682,7 @@ namespace SharedLibraryCore.Database.Models // we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID) var activePenalties = await CurrentServer.Manager.GetPenaltyService() - .GetActivePenaltiesAsync(AliasLinkId, CurrentAliasId, NetworkId, ipAddress); + .GetActivePenaltiesAsync(AliasLinkId, CurrentAliasId, NetworkId, GameName, ipAddress); var banPenalty = activePenalties.FirstOrDefault(_penalty => _penalty.Type == EFPenalty.PenaltyType.Ban); var tempbanPenalty = activePenalties.FirstOrDefault(_penalty => _penalty.Type == EFPenalty.PenaltyType.TempBan); diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index 83cca25a8..3fd8ce1e9 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -23,25 +23,25 @@ namespace SharedLibraryCore.Services { public class ClientService : IEntityService, IResourceQueryHelper { - private static readonly Func> _getUniqueQuery = - EF.CompileAsyncQuery((DatabaseContext context, long networkId) => + private static readonly Func> GetUniqueQuery = + EF.CompileAsyncQuery((DatabaseContext context, long networkId, Reference.Game game) => context.Clients - .Select(_client => new EFClient + .Select(client => new EFClient { - ClientId = _client.ClientId, - AliasLinkId = _client.AliasLinkId, - Level = _client.Level, - Connections = _client.Connections, - FirstConnection = _client.FirstConnection, - LastConnection = _client.LastConnection, - Masked = _client.Masked, - NetworkId = _client.NetworkId, - TotalConnectionTime = _client.TotalConnectionTime, - AliasLink = _client.AliasLink, - Password = _client.Password, - PasswordSalt = _client.PasswordSalt + ClientId = client.ClientId, + AliasLinkId = client.AliasLinkId, + Level = client.Level, + Connections = client.Connections, + FirstConnection = client.FirstConnection, + LastConnection = client.LastConnection, + Masked = client.Masked, + NetworkId = client.NetworkId, + TotalConnectionTime = client.TotalConnectionTime, + AliasLink = client.AliasLink, + Password = client.Password, + PasswordSalt = client.PasswordSalt }) - .FirstOrDefault(c => c.NetworkId == networkId) + .FirstOrDefault(client => client.NetworkId == networkId && client.GameName == game) ); private readonly ApplicationConfiguration _appConfig; @@ -235,10 +235,14 @@ namespace SharedLibraryCore.Services return foundClient.Client; } - public virtual async Task GetUnique(long entityAttribute) + public virtual async Task GetUnique(long entityAttribute, object altKey = null) { + if (altKey is not Reference.Game game) + { + throw new ArgumentException($"Alternate key must be of type {nameof(Reference.Game)}"); + } await using var context = _contextFactory.CreateContext(false); - return await _getUniqueQuery(context, entityAttribute); + return await GetUniqueQuery(context, entityAttribute, game); } public async Task Update(EFClient temporalClient) @@ -285,7 +289,7 @@ namespace SharedLibraryCore.Services entity.PasswordSalt = temporalClient.PasswordSalt; } - entity.GameName ??= temporalClient.GameName; + entity.GameName = temporalClient.GameName; // update in database await context.SaveChangesAsync(); @@ -758,19 +762,20 @@ namespace SharedLibraryCore.Services { await using var context = _contextFactory.CreateContext(false); return await context.Clients - .Select(_client => new EFClient + .Select(client => new EFClient { - NetworkId = _client.NetworkId, - ClientId = _client.ClientId, + NetworkId = client.NetworkId, + ClientId = client.ClientId, CurrentAlias = new EFAlias { - Name = _client.CurrentAlias.Name + Name = client.CurrentAlias.Name }, - Password = _client.Password, - PasswordSalt = _client.PasswordSalt, - Level = _client.Level + Password = client.Password, + PasswordSalt = client.PasswordSalt, + GameName = client.GameName, + Level = client.Level }) - .FirstAsync(_client => _client.ClientId == clientId); + .FirstAsync(client => client.ClientId == clientId); } public async Task> GetPrivilegedClients(bool includeName = true) @@ -860,15 +865,16 @@ namespace SharedLibraryCore.Services // we want to project our results var iqClientProjection = iqClients.OrderByDescending(_client => _client.LastConnection) - .Select(_client => new PlayerInfo + .Select(client => new PlayerInfo { - Name = _client.CurrentAlias.Name, - LevelInt = (int)_client.Level, - LastConnection = _client.LastConnection, - ClientId = _client.ClientId, - IPAddress = _client.CurrentAlias.IPAddress.HasValue - ? _client.CurrentAlias.SearchableIPAddress - : "" + Name = client.CurrentAlias.Name, + LevelInt = (int)client.Level, + LastConnection = client.LastConnection, + ClientId = client.ClientId, + IPAddress = client.CurrentAlias.IPAddress.HasValue + ? client.CurrentAlias.SearchableIPAddress + : "", + Game = client.GameName }); var clients = await iqClientProjection.ToListAsync(); diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index 5ba266664..d5ccc5b5f 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -88,7 +88,7 @@ namespace SharedLibraryCore.Services throw new NotImplementedException(); } - public Task GetUnique(long entityProperty) + public Task GetUnique(long entityProperty, object altKey) { throw new NotImplementedException(); } @@ -139,10 +139,10 @@ namespace SharedLibraryCore.Services LinkedPenalties.Contains(pi.Penalty.Type) && pi.Penalty.Active && (pi.Penalty.Expires == null || pi.Penalty.Expires > DateTime.UtcNow); - public async Task> GetActivePenaltiesAsync(int linkId, int currentAliasId, long networkId, + public async Task> GetActivePenaltiesAsync(int linkId, int currentAliasId, long networkId, Reference.Game game, int? ip = null) { - var penaltiesByIdentifier = await GetActivePenaltiesByIdentifier(ip, networkId); + var penaltiesByIdentifier = await GetActivePenaltiesByIdentifier(ip, networkId, game); if (penaltiesByIdentifier.Any()) { @@ -183,12 +183,12 @@ namespace SharedLibraryCore.Services return activePenalties.OrderByDescending(p => p.When).ToList(); } - public async Task> GetActivePenaltiesByIdentifier(int? ip, long networkId) + public async Task> GetActivePenaltiesByIdentifier(int? ip, long networkId, Reference.Game game) { await using var context = _contextFactory.CreateContext(false); var activePenaltiesIds = context.PenaltyIdentifiers.Where(identifier => - identifier.IPv4Address != null && identifier.IPv4Address == ip || identifier.NetworkId == networkId) + identifier.IPv4Address != null && identifier.IPv4Address == ip || identifier.NetworkId == networkId && identifier.Penalty.Offender.GameName == game) .Where(FilterById); return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync(); } @@ -214,12 +214,12 @@ namespace SharedLibraryCore.Services return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync(); } - public virtual async Task RemoveActivePenalties(int aliasLinkId, long networkId, int? ipAddress = null) + public virtual async Task RemoveActivePenalties(int aliasLinkId, long networkId, Reference.Game game, int? ipAddress = null) { await using var context = _contextFactory.CreateContext(); var now = DateTime.UtcNow; - var activePenalties = await GetActivePenaltiesByIdentifier(ipAddress, networkId); + var activePenalties = await GetActivePenaltiesByIdentifier(ipAddress, networkId, game); if (activePenalties.Any()) { diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index 25d6be5b1..02b6da24c 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -4,7 +4,7 @@ Library net6.0 RaidMax.IW4MAdmin.SharedLibraryCore - 2022.6.9.1 + 2022.6.15.1 RaidMax Forever None Debug;Release;Prerelease @@ -19,7 +19,7 @@ true MIT Shared Library for IW4MAdmin - 2022.6.9.1 + 2022.6.15.1 true $(NoWarn);1591 diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 4e9e04931..d8b8d91b0 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -1181,7 +1181,8 @@ namespace SharedLibraryCore Meta = client.Meta, ReceivedPenalties = client.ReceivedPenalties, AdministeredPenalties = client.AdministeredPenalties, - Active = client.Active + Active = client.Active, + GameName = client.GameName }; } @@ -1264,5 +1265,8 @@ namespace SharedLibraryCore return allRules[index]; } + + public static string MakeAbbreviation(string gameName) => string.Join("", + gameName.Split(' ').Select(word => char.ToUpper(word.First())).ToArray()); } } diff --git a/WebfrontCore/Controllers/API/ClientController.cs b/WebfrontCore/Controllers/API/ClientController.cs index 810e2c1ba..9c980e773 100644 --- a/WebfrontCore/Controllers/API/ClientController.cs +++ b/WebfrontCore/Controllers/API/ClientController.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.Logging; using SharedLibraryCore; +using SharedLibraryCore.Helpers; using SharedLibraryCore.Services; using WebfrontCore.Controllers.API.Dtos; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -100,9 +101,16 @@ namespace WebfrontCore.Controllers.API if (!Authorized) { + var tokenData = new TokenIdentifier + { + Game = privilegedClient.GameName, + Token = request.Password, + NetworkId = privilegedClient.NetworkId + }; + loginSuccess = - Manager.TokenAuthenticator.AuthorizeToken(privilegedClient.NetworkId, request.Password) || - (await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(request.Password, + Manager.TokenAuthenticator.AuthorizeToken(tokenData) || + (await Task.FromResult(Hashing.Hash(request.Password, privilegedClient.PasswordSalt)))[0] == privilegedClient.Password; } @@ -120,7 +128,7 @@ namespace WebfrontCore.Controllers.API var claimsPrinciple = new ClaimsPrincipal(claimsIdentity); await SignInAsync(claimsPrinciple); - Manager.AddEvent(new GameEvent() + Manager.AddEvent(new GameEvent { Origin = privilegedClient, Type = GameEvent.EventType.Login, @@ -149,7 +157,7 @@ namespace WebfrontCore.Controllers.API { if (Authorized) { - Manager.AddEvent(new GameEvent() + Manager.AddEvent(new GameEvent { Origin = Client, Type = GameEvent.EventType.Logout, diff --git a/WebfrontCore/Controllers/AccountController.cs b/WebfrontCore/Controllers/AccountController.cs index 523ccdf43..e2302dbe7 100644 --- a/WebfrontCore/Controllers/AccountController.cs +++ b/WebfrontCore/Controllers/AccountController.cs @@ -7,7 +7,7 @@ using System; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; +using SharedLibraryCore.Helpers; namespace WebfrontCore.Controllers { @@ -19,6 +19,7 @@ namespace WebfrontCore.Controllers } [HttpGet] + [Obsolete] public async Task Login(int clientId, string password) { if (clientId == 0 || string.IsNullOrEmpty(password)) @@ -29,14 +30,23 @@ namespace WebfrontCore.Controllers try { var privilegedClient = await Manager.GetClientService().GetClientForLogin(clientId); - bool loginSuccess = false; -#if DEBUG - loginSuccess = clientId == 1; -#endif + var loginSuccess = false; + + if (Utilities.IsDevelopment) + { + loginSuccess = clientId == 1; + } + if (!Authorized && !loginSuccess) { - loginSuccess = Manager.TokenAuthenticator.AuthorizeToken(privilegedClient.NetworkId, password) || - (await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(password, privilegedClient.PasswordSalt)))[0] == privilegedClient.Password; + loginSuccess = Manager.TokenAuthenticator.AuthorizeToken(new TokenIdentifier + { + NetworkId = privilegedClient.NetworkId, + Game = privilegedClient.GameName, + Token = password + }) || + (await Task.FromResult(Hashing.Hash(password, privilegedClient.PasswordSalt)))[0] == + privilegedClient.Password; } if (loginSuccess) @@ -46,21 +56,22 @@ namespace WebfrontCore.Controllers new Claim(ClaimTypes.NameIdentifier, privilegedClient.Name), new Claim(ClaimTypes.Role, privilegedClient.Level.ToString()), new Claim(ClaimTypes.Sid, privilegedClient.ClientId.ToString()), - new Claim(ClaimTypes.PrimarySid, privilegedClient.NetworkId.ToString("X")) + new Claim(ClaimTypes.PrimarySid, privilegedClient.NetworkId.ToString("X")), + new Claim(ClaimTypes.PrimaryGroupSid, privilegedClient.GameName.ToString()) }; var claimsIdentity = new ClaimsIdentity(claims, "login"); var claimsPrinciple = new ClaimsPrincipal(claimsIdentity); await SignInAsync(claimsPrinciple); - Manager.AddEvent(new GameEvent() + Manager.AddEvent(new GameEvent { Origin = privilegedClient, Type = GameEvent.EventType.Login, Owner = Manager.GetServers().First(), Data = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For") ? HttpContext.Request.Headers["X-Forwarded-For"].ToString() - : HttpContext.Connection.RemoteIpAddress.ToString() + : HttpContext.Connection.RemoteIpAddress?.ToString() }); return Ok($"Welcome {privilegedClient.Name}. You are now logged in"); @@ -80,14 +91,14 @@ namespace WebfrontCore.Controllers { if (Authorized) { - Manager.AddEvent(new GameEvent() + Manager.AddEvent(new GameEvent { Origin = Client, Type = GameEvent.EventType.Logout, Owner = Manager.GetServers().First(), Data = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For") ? HttpContext.Request.Headers["X-Forwarded-For"].ToString() - : HttpContext.Connection.RemoteIpAddress.ToString() + : HttpContext.Connection.RemoteIpAddress?.ToString() }); } diff --git a/WebfrontCore/Controllers/ActionController.cs b/WebfrontCore/Controllers/ActionController.cs index a2d23b38e..bfd872b37 100644 --- a/WebfrontCore/Controllers/ActionController.cs +++ b/WebfrontCore/Controllers/ActionController.cs @@ -10,6 +10,7 @@ using SharedLibraryCore; using SharedLibraryCore.Commands; using SharedLibraryCore.Configuration; using SharedLibraryCore.Dtos; +using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; using WebfrontCore.Permissions; using WebfrontCore.ViewModels; @@ -274,7 +275,12 @@ namespace WebfrontCore.Controllers [Authorize] public string GenerateLoginTokenAsync() { - var state = Manager.TokenAuthenticator.GenerateNextToken(Client.NetworkId); + var state = Manager.TokenAuthenticator.GenerateNextToken(new TokenIdentifier + { + NetworkId = Client.NetworkId, + Game = Client.GameName + }); + return string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_SUCCESS"], state.Token, $"{state.RemainingTime} {Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_MINUTES"]}", diff --git a/WebfrontCore/Controllers/Client/ClientController.cs b/WebfrontCore/Controllers/Client/ClientController.cs index 9164c10fe..bad7b9be8 100644 --- a/WebfrontCore/Controllers/Client/ClientController.cs +++ b/WebfrontCore/Controllers/Client/ClientController.cs @@ -47,7 +47,7 @@ namespace WebfrontCore.Controllers } var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, - client.CurrentAliasId, client.NetworkId, client.IPAddress); + client.CurrentAliasId, client.NetworkId, client.GameName, client.IPAddress); var persistentMetaTask = new[] { @@ -88,7 +88,7 @@ namespace WebfrontCore.Controllers var clientDto = new PlayerInfo { Name = client.Name, - Game = client.GameName ?? Reference.Game.UKN, + Game = client.GameName, Level = displayLevel, LevelInt = displayLevelInt, ClientId = client.ClientId, @@ -183,7 +183,7 @@ namespace WebfrontCore.Controllers ClientId = admin.ClientId, LastConnection = admin.LastConnection, IsMasked = admin.Masked, - Game = admin.GameName ?? Reference.Game.UKN + Game = admin.GameName }); } diff --git a/WebfrontCore/Controllers/Client/ClientStatisticsController.cs b/WebfrontCore/Controllers/Client/ClientStatisticsController.cs index 262b43581..a49d8c3ca 100644 --- a/WebfrontCore/Controllers/Client/ClientStatisticsController.cs +++ b/WebfrontCore/Controllers/Client/ClientStatisticsController.cs @@ -34,7 +34,12 @@ namespace WebfrontCore.Controllers { ClientId = id, ServerEndpoint = serverId - })).Results.First(); + }))?.Results?.First(); + + if (hitInfo is null) + { + return NotFound(); + } var server = Manager.GetServers().FirstOrDefault(server => server.ToString() == serverId); long? matchedServerId = null; diff --git a/WebfrontCore/Middleware/ClaimsPermissionRemoval.cs b/WebfrontCore/Middleware/ClaimsPermissionRemoval.cs index 20e75dcca..cf470cd4a 100644 --- a/WebfrontCore/Middleware/ClaimsPermissionRemoval.cs +++ b/WebfrontCore/Middleware/ClaimsPermissionRemoval.cs @@ -36,24 +36,26 @@ namespace WebfrontCore.Middleware /// private void OnGameEvent(object sender, GameEvent gameEvent) { - if (gameEvent.Type == EventType.ChangePermission && - gameEvent.Extra is EFClient.Permission perm) + if (gameEvent.Type != EventType.ChangePermission || gameEvent.Extra is not EFClient.Permission perm) { - // we want to remove the claims when the client is demoted - if (perm < EFClient.Permission.Trusted) + return; + } + + lock (_privilegedClientIds) + { + switch (perm) { - lock (_privilegedClientIds) + // we want to remove the claims when the client is demoted + case < EFClient.Permission.Trusted: { _privilegedClientIds.RemoveAll(id => id == gameEvent.Target.ClientId); + break; } - } - // and add if promoted - else if (perm > EFClient.Permission.Trusted && - !_privilegedClientIds.Contains(gameEvent.Target.ClientId)) - { - lock (_privilegedClientIds) + // and add if promoted + case > EFClient.Permission.Trusted when !_privilegedClientIds.Contains(gameEvent.Target.ClientId): { _privilegedClientIds.Add(gameEvent.Target.ClientId); + break; } } } @@ -62,10 +64,16 @@ namespace WebfrontCore.Middleware public async Task Invoke(HttpContext context) { // we want to load the initial list of privileged clients - if (_privilegedClientIds.Count == 0) + bool hasAny; + lock (_privilegedClientIds) + { + hasAny = _privilegedClientIds.Any(); + } + + if (hasAny) { var ids = (await _manager.GetClientService().GetPrivilegedClients()) - .Select(_client => _client.ClientId); + .Select(client => client.ClientId); lock (_privilegedClientIds) { @@ -74,13 +82,19 @@ namespace WebfrontCore.Middleware } // sid stores the clientId - string claimsId = context.User.Claims.FirstOrDefault(_claim => _claim.Type == ClaimTypes.Sid)?.Value; + var claimsId = context.User.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Sid)?.Value; if (!string.IsNullOrEmpty(claimsId)) { - int clientId = int.Parse(claimsId); + var clientId = int.Parse(claimsId); + bool hasKey; + lock (_privilegedClientIds) + { + hasKey = _privilegedClientIds.Contains(clientId); + } + // they've been removed - if (!_privilegedClientIds.Contains(clientId) && clientId != 1) + if (!hasKey && clientId != 1) { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); } diff --git a/WebfrontCore/QueryHelpers/BanInfoResourceQueryHelper.cs b/WebfrontCore/QueryHelpers/BanInfoResourceQueryHelper.cs index aa0a95872..aadf86afc 100644 --- a/WebfrontCore/QueryHelpers/BanInfoResourceQueryHelper.cs +++ b/WebfrontCore/QueryHelpers/BanInfoResourceQueryHelper.cs @@ -54,7 +54,8 @@ public class BanInfoResourceQueryHelper : IResourceQueryHelper(); @@ -101,7 +102,6 @@ public class BanInfoResourceQueryHelper : IResourceQueryHelper alias.LinkId) .ToListAsync()).Distinct(); - matchedPenalties = await context.Penalties.Where(penalty => penalty.Type == EFPenalty.PenaltyType.Ban) .Where(penalty => penalty.Expires == null || penalty.Expires > lateDateTime) .Where(penalty => penalty.LinkId != null && linkIds.Contains(penalty.LinkId.Value)) @@ -158,6 +158,7 @@ public class BanInfoResourceQueryHelper : IResourceQueryHelper diff --git a/WebfrontCore/QueryHelpers/Models/BanInfo.cs b/WebfrontCore/QueryHelpers/Models/BanInfo.cs index a9e060c8c..2649cc2e3 100644 --- a/WebfrontCore/QueryHelpers/Models/BanInfo.cs +++ b/WebfrontCore/QueryHelpers/Models/BanInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Data.Models; namespace WebfrontCore.QueryHelpers.Models; @@ -9,6 +10,7 @@ public class BanInfo public int ClientId { get; set; } public int? IPAddress { get; set; } public long NetworkId { get; set; } + public Reference.Game Game { get; set; } public PenaltyInfo AttachedPenalty { get; set; } public IEnumerable AssociatedPenalties { get; set; } } diff --git a/WebfrontCore/Views/Admin/_BanEntries.cshtml b/WebfrontCore/Views/Admin/_BanEntries.cshtml index 5bd2180f9..637eb86fe 100644 --- a/WebfrontCore/Views/Admin/_BanEntries.cshtml +++ b/WebfrontCore/Views/Admin/_BanEntries.cshtml @@ -12,22 +12,23 @@
@ban.ClientName +
+
@Utilities.MakeAbbreviation(ViewBag.Localization[$"GAME_{ban.Game}"])
@ban.NetworkId.ToString("X")
@ban.IPAddress.ConvertIPtoString()
+
@if (ban.AttachedPenalty is not null) { -
@ban.AttachedPenalty.Offense.CapClientName(30)
@ban.AttachedPenalty.DateTime.ToStandardFormat()
-
Unban
+
Unban
} else { -
Link-Only Ban @@ -56,6 +57,7 @@
@associatedEntity.Offense.CapClientName(30)
@associatedEntity.DateTime.ToStandardFormat()
Unban
+
@Utilities.MakeAbbreviation(ViewBag.Localization[$"GAME_{ban.Game}"])
}
diff --git a/WebfrontCore/Views/Server/_Server.cshtml b/WebfrontCore/Views/Server/_Server.cshtml index 1675b10ff..669831b3d 100644 --- a/WebfrontCore/Views/Server/_Server.cshtml +++ b/WebfrontCore/Views/Server/_Server.cshtml @@ -15,9 +15,7 @@ foreach (var snapshot in Model.ClientHistory.ClientCounts) { snapshot.MapAlias = GetMapName(snapshot.Map); - }; - - string MakeAbbreviation(string gameName) => string.Join("", gameName.Split(' ').Select(word => char.ToUpper(word.First())).ToArray()); + } }
@@ -44,7 +42,7 @@ class="text-light align-self-center"> - @MakeAbbreviation(ViewBag.Localization[$"GAME_{Model.Game}"]) + @Utilities.MakeAbbreviation(ViewBag.Localization[$"GAME_{Model.Game}"])