Compare commits

...

140 Commits

Author SHA1 Message Date
a38789adb9 add default anticheat detection types 2022-06-02 18:30:22 -05:00
e459b2fcde Add per game anticheat configuration option for issue #203 2022-06-02 18:24:13 -05:00
26853a0005 fix issue with player name spacing on server overview at certain resolutions 2022-06-02 18:16:54 -05:00
ee14306db9 fix displaying correct server name on top players 2022-06-02 17:53:14 -05:00
169105e849 fix loader on mobile audit log view 2022-06-02 16:54:26 -05:00
7c10e0e3de add baninfo api 2022-06-02 16:48:47 -05:00
2f7eb07e39 Merge branch 'release/pre' of https://github.com/RaidMax/IW4M-Admin into release/pre 2022-06-02 15:51:59 -05:00
0f9f4f597b Create ParserH1MOD.js (#248)
Co-Authored-By: fed <58637860+fedddddd@users.noreply.github.com>

Co-authored-by: fed <58637860+fedddddd@users.noreply.github.com>
2022-06-02 09:25:29 -05:00
880f9333d9 Fixed formatting... Tabs/spaces 2022-06-02 09:25:00 -05:00
31da5d352e Broadcast bans (Anti-cheat and manuals) script plugin 2022-06-02 09:25:00 -05:00
83a469cae3 Fix !hide provide "mitigation" to noclip ghost bug 2022-06-02 09:25:00 -05:00
1f13f9122c fix intermittent issue with game interface during connection loss with servers 2022-06-01 11:25:11 -05:00
dd8c4f438f reduce logging for failed anticheat log parsing 2022-05-22 18:04:38 -05:00
2230036d45 fix issue with VPN banlist evaluation 2022-05-22 18:04:23 -05:00
1700b7da91 PlutoIW5 support for the Game Interface and improvements to the GSC part of it. (#242)
* Improvements to the GSC part of the Game Interface
* Adds compatibility with PlutoIW5 with minimal changes.
* Fixes issues when commands are called from the web interface when the used profile is not on the server.
    * New Debug output when the target or origin of a command is sent by IW4MAdmin but not found in-game.
    * Commands that can be run on the context of the target are now run in it.
* Simplifies the command registration and execution.
    * Got rid of the huge switch block.
    * Introduced AddClientCommand to register new commands for example
        * `AddClientCommand("SwitchTeams",  true,  ::TeamSwitchImpl);`
        * `AddClientCommand("Hide",         false, ::HideImpl);`
    * Callbacks are called with the full event object and the parsed data as parameters to allow maximum flexibility.
* Introduced level.eventBus.gamename to know which game we are to add minor changes.
* Changes - noclip/lockcontrols/playertome
Additional changes to support other games' functions

Co-Authored-By: Amos <4959320+MrAmos123@users.noreply.github.com>
2022-05-19 17:04:34 -05:00
fab97ccad4 fix suffixing commands with color code 2022-04-28 17:22:15 -05:00
0bf0d033f7 make social icons fit better 2022-04-28 17:22:01 -05:00
2bbabcb9e8 fix issue with side nav 2022-04-28 12:05:58 -05:00
1995dbd080 improve loading of recent clients 2022-04-28 11:42:23 -05:00
a3b94b50e3 reduce warning logs for connecting bots on live radar 2022-04-28 10:35:01 -05:00
bc38b36e4a ignore bots for game interface 2022-04-28 10:20:55 -05:00
e346aa037e don't use cancellation token when persisting meta on quit 2022-04-28 10:14:35 -05:00
389c687420 fix issue with kick from profile 2022-04-28 10:09:25 -05:00
074e36413e format all output for color keys 2022-04-27 15:36:58 -05:00
104d9bdc4c make recent clients pagination load 20 per request 2022-04-25 16:13:18 -05:00
c51d28937b max recent clients paginated 2022-04-25 16:12:25 -05:00
ffa8a46feb move action modal infront of context modal for mobile 2022-04-25 15:52:18 -05:00
91c46dbdd4 only show reports from the last 24 hours 2022-04-25 15:44:51 -05:00
ff0d22c142 fix rcon issue 2022-04-25 15:39:30 -05:00
3ad4aa2196 escape html characters in web console output 2022-04-25 10:43:16 -05:00
d462892467 add configuration link for console perms 2022-04-23 10:33:48 -05:00
cabedb6f0b fix live radar icon 2022-04-22 17:29:29 -05:00
5b7f5160b2 show login token for longer period 2022-04-22 16:56:29 -05:00
0a8e415af8 add game to client 2022-04-22 16:03:34 -05:00
7b3ddd58c6 clean up report dropdown 2022-04-22 15:14:23 -05:00
ed1032415e fix live radar links 2022-04-22 15:13:51 -05:00
35b43e7438 fix issue with penalty list 2022-04-22 08:04:01 -05:00
284c2e9726 ui tweaks/improvements including recent players and ip info lookup 2022-04-21 12:39:09 -05:00
fd049edb3f fix kick button margin 2022-04-20 14:46:15 -05:00
4884abee76 better align player names/chat 2022-04-20 14:16:34 -05:00
1df76b6ac3 add missing live radar view to source control 2022-04-20 13:14:17 -05:00
4c42a1d511 fix accent colors not showing 2022-04-20 13:12:42 -05:00
27635a6dd3 done 2022-04-20 10:57:00 -05:00
0175425708 fix subnet ban and vpn detection persistence 2022-04-20 10:45:30 -05:00
5e12bf60b5 finish pipeline edits 2022-04-20 10:32:39 -05:00
2f10ca8599 override the font library file name 2022-04-20 10:22:41 -05:00
62ec18309e testing script 2022-04-20 09:56:46 -05:00
87361bf3d7 cleanup 2022-04-20 09:26:10 -05:00
dc45136077 test replace script 2022-04-20 09:03:29 -05:00
21b0a7998d fixup icons font path 2022-04-20 08:39:16 -05:00
20b8f0b99a ok for real this time 2022-04-19 23:14:10 -05:00
314ff96e71 trying again 2022-04-19 22:44:36 -05:00
d7c4f5452c move bundle step after publish 2022-04-19 22:37:45 -05:00
3cb50635e5 small updates that got lost in last commit 2022-04-19 22:34:35 -05:00
4fbe0ee0ed huge commit for webfront facelift 2022-04-19 18:43:58 -05:00
4023ca37d4 Hide numerical prefix for about page rules if included in the config 2022-04-09 10:16:34 -05:00
425ec2621d strip color keys from webfront form lists 2022-04-08 17:14:04 -05:00
15c3ca53e2 fix edge case data collection for offline servers/clean up implementation 2022-04-08 16:41:44 -05:00
6097ca504c fix issues with infinite profile meta scrolling 2022-04-08 14:26:17 -05:00
70cd01eafb reduce logging for meta lookup 2022-04-06 14:08:00 -05:00
a2d5e37c6f simplify initial setup by removing extra prompts 2022-04-06 13:04:30 -05:00
19bd47d0f4 initial permissions based webfront access implementation 2022-04-04 22:16:40 -05:00
dc97956bc3 add searching by partial ip address 2022-04-04 14:27:22 -05:00
89fdc00f9b add subnet ban command 2022-03-30 22:15:29 -05:00
039a05d9ad fix game tab selection on home 2022-03-30 15:44:05 -05:00
25fb5fdc14 fix new client graph for smaller screen sizes 2022-03-29 18:59:27 -05:00
1e67f6e86c collect data when server offline 2022-03-29 17:18:41 -05:00
7dbdf87728 disable map change indicator temporarily 2022-03-29 16:59:32 -05:00
180a4911bc improve server clientcount/activity graph on server overview 2022-03-29 16:42:53 -05:00
31123d9a33 include css change for previous commit 2022-03-28 21:34:52 -05:00
3cf0f54ceb remove striped scoreboard and add spectator color 2022-03-28 18:23:11 -05:00
eafd7cb530 add join team and map change events to CSGO parser 2022-03-28 18:05:18 -05:00
770785e979 misc fix 2022-03-28 16:05:00 -05:00
92d713d188 tweak scoreboard zscore again 2022-03-25 13:39:51 -05:00
34e531ef8d mark no zscore as 0 for scoreboard 2022-03-25 13:18:24 -05:00
724992ef33 set team properly/tint scoreboard background for team 2022-03-25 13:16:41 -05:00
557cc1614f improve ban handling edge cases 2022-03-25 11:28:15 -05:00
f90cdbef16 fix meta filter on profile 2022-03-24 16:23:40 -05:00
a863f78678 only unload plugins once at shutdown
clean up some doc warnings
2022-03-24 11:34:32 -05:00
c93f896bc5 fix profile issue 2022-03-24 08:40:42 -05:00
ccc8316a2f fix minimap image for live radar 2022-03-24 08:40:20 -05:00
497c15a6a8 update stats to use new meta service 2022-03-23 13:54:42 -05:00
7be096e0b6 add vpn whitelist command 2022-03-23 13:34:04 -05:00
20858991e1 move live radar js into own file 2022-03-23 12:52:11 -05:00
85d44b0eb0 fix issue with multi line output freezing console 2022-03-23 12:09:40 -05:00
51ef67ae9c add BroadcastAsync 2022-03-23 11:43:20 -05:00
63b04be4c7 add tell async and update SharedLibraryCore version 2022-03-23 11:38:09 -05:00
36eb45bb2e mark old meta service as obsolete 2022-03-23 11:31:53 -05:00
04a4dcf153 implement metaservice v2 2022-03-23 08:43:57 -05:00
b46b1eb5e7 fix update on report to penalize flagged users 2022-03-23 08:22:23 -05:00
287635fa36 update integration gsc 2022-03-12 13:41:10 -06:00
f567a03fa7 implement team tracking via game interface (EFClient.Team and EFClient.TeamName) 2022-03-12 13:38:33 -06:00
1b6d8107ae Add T6 Weapon Name Parser Config (#236)
Add T6 Weapon Name Parser Config
2022-03-08 12:08:16 -06:00
1e8f06f3a3 Fix iw3 gamestring typo (#234)
RDP -> RPD
2022-03-08 12:08:04 -06:00
064879fead Add info api for #231 2022-03-08 12:06:46 -06:00
e32e97b9e6 fix issue with loading stats config #237 2022-03-08 11:24:59 -06:00
42313b7816 update action on report to use level enum string 2022-03-07 20:00:05 -06:00
9f4d06c265 refactor some game interface plugin approach 2022-03-07 19:59:34 -06:00
acf66da4ca tweak cod rcon connection and fix max health for hide integration command 2022-03-05 13:13:00 -06:00
59ca399045 more cod rcon tweaks 2022-03-03 08:54:17 -06:00
ef70496546 hopefully fix some issues with rcon socket 2022-03-02 18:21:08 -06:00
e6e56d8d14 add back helper methods without cancellation token for plugins 2022-03-02 08:29:15 -06:00
55b0caf900 tweak game interface values again 2022-03-02 08:28:41 -06:00
a4c3f9c2d1 update delete obsolete plugin migration 2022-03-01 12:47:35 -06:00
241aa0a5f6 tweak rcon timeout for script calls 2022-03-01 12:46:01 -06:00
ec0f59cdb1 add set spectator command for game interface 2022-03-01 12:45:39 -06:00
59d69bd22b add cancellation token for rcon connection to allow more granular control 2022-02-28 20:44:30 -06:00
e9c8ead829 simplify level update so we don't have to worry about linked account levels 2022-02-28 15:20:46 -06:00
58d48a211e make sure iw4madmin exits when selecting "no" to continue with failed server connections 2022-02-28 15:16:30 -06:00
edf8e03b04 don't refresh scoreboard on every page. though I fixed this already... 2022-02-27 21:35:16 -06:00
de2e804b84 improve meta filter menu on profile 2022-02-25 21:09:57 -06:00
b087d4c8de unescape utf characters when saving configs 2022-02-25 09:44:28 -06:00
bd6c0dd5be fix issue with tempban not displaying properly 2022-02-25 08:22:40 -06:00
4ace476242 mark permission changed as sensitive 2022-02-23 16:26:46 -06:00
bb7215dbb6 increment shared library references 2022-02-23 15:57:44 -06:00
88bd47f3ae add search ip shortcut on profile 2022-02-23 15:47:17 -06:00
39a1066c74 add permission level changed meta 2022-02-23 12:47:00 -06:00
18f3c59b9b allow search client exact with quotes 2022-02-23 09:32:59 -06:00
0d88b6293f add create/update times to penalty identifiers 2022-02-23 09:02:01 -06:00
a6b56ceded tweak for integration 2022-02-22 17:10:33 -06:00
78ef977268 simplify ban process with new system 2022-02-22 17:09:50 -06:00
d527a86911 improve mag command matching of maps and gametypes 2022-02-22 08:38:02 -06:00
2e531c4a50 validate game interface commands to ensure it's enabled before trying to execute 2022-02-18 10:15:11 -06:00
45059fcfd9 change mask command alias to not conflict with game interface hide 2022-02-18 10:04:48 -06:00
482cd9c339 Merge branch 'release/pre' of https://github.com/RaidMax/IW4M-Admin into release/pre 2022-02-15 20:23:30 -06:00
51667159a2 fix validation errors freezing initialization 2022-02-15 20:23:16 -06:00
ea18a286b2 improve error output when configuration is invalid 2022-02-15 20:16:21 -06:00
9a6d7c6a20 game interface improvements 2022-02-15 20:05:50 -06:00
adcb75319c Changed .NET 6 Direct Download... (#229)
Changed the .NET 6 Direct Download link to the hosting bundle as it includes both the .NET 6 runtime and the ASP .NET 6 runtime which are both needed.
2022-02-15 09:56:31 -06:00
037fac5786 game interface improvements 2022-02-13 21:38:40 -06:00
f4b892d8f4 improve network log support 2022-02-13 16:50:09 -06:00
3640d1df54 small updates for game interface 2022-02-12 21:54:21 -06:00
f3c6b10a35 add network game log reader ex: net.tcp://ip:port 2022-02-11 15:33:05 -06:00
4dec284b31 fix unnecessary output when not able to connect to all servers 2022-02-10 17:01:06 -06:00
c9cf7be341 add set client meta and inc/dec to framework 2022-02-10 16:50:45 -06:00
aa6ae0ab8d more integration tweaks 2022-02-09 14:45:28 -06:00
12dfd8c558 more integration tweaks
add configurable flood protect interval for rcon
2022-02-08 12:03:55 -06:00
07f675eadc fix issue with plugin registration 2022-02-07 22:02:50 -06:00
576d7015fa increase poll rate for reasonable response times 2022-02-07 18:47:16 -06:00
b1a1aae6c0 initial framework for gsc + iw4madmin integration
improvements to script plugin capabilities and error feedback
2022-02-07 18:43:36 -06:00
278 changed files with 46074 additions and 4804 deletions

1
.gitignore vendored
View File

@ -224,7 +224,6 @@ bootstrap-custom.min.css
bootstrap-custom.css bootstrap-custom.css
**/Master/static **/Master/static
**/Master/dev_env **/Master/dev_env
/WebfrontCore/Views/Plugins/*
/WebfrontCore/wwwroot/**/dds /WebfrontCore/wwwroot/**/dds
/WebfrontCore/wwwroot/images/radar/* /WebfrontCore/wwwroot/images/radar/*

View File

@ -24,7 +24,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Jint" Version="3.0.0-beta-1632" /> <PackageReference Include="Jint" Version="3.0.0-beta-2038" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -63,6 +64,9 @@
<None Update="Configuration\LoggingConfiguration.json"> <None Update="Configuration\LoggingConfiguration.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Resources\GeoLite2-Country.mmdb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View File

@ -59,7 +59,6 @@ namespace IW4MAdmin.Application
readonly PenaltyService PenaltySvc; readonly PenaltyService PenaltySvc;
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler; public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
readonly IPageList PageList; readonly IPageList PageList;
private readonly IMetaService _metaService;
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0); private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
private readonly CancellationTokenSource _tokenSource; private readonly CancellationTokenSource _tokenSource;
private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>(); private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>();
@ -81,7 +80,7 @@ namespace IW4MAdmin.Application
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration, ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory, IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents, IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaService metaService, IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory,
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider, IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider,
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService) ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService)
{ {
@ -97,7 +96,6 @@ namespace IW4MAdmin.Application
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) }; AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) };
TokenAuthenticator = new TokenAuthentication(); TokenAuthenticator = new TokenAuthentication();
_logger = logger; _logger = logger;
_metaService = metaService;
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
_commands = commands.ToList(); _commands = commands.ToList();
_translationLookup = translationLookup; _translationLookup = translationLookup;
@ -236,13 +234,6 @@ namespace IW4MAdmin.Application
.Select(ut => ut.Key) .Select(ut => ut.Key)
.ToList(); .ToList();
// this is to prevent the log reader from starting before the initial
// query of players on the server
if (serverTasksToRemove.Count > 0)
{
IsInitialized = true;
}
// remove the update tasks as they have completed // remove the update tasks as they have completed
foreach (var serverId in serverTasksToRemove.Where(serverId => runningUpdateTasks.ContainsKey(serverId))) foreach (var serverId in serverTasksToRemove.Where(serverId => runningUpdateTasks.ContainsKey(serverId)))
{ {
@ -419,7 +410,7 @@ namespace IW4MAdmin.Application
if (!validationResult.IsValid) if (!validationResult.IsValid)
{ {
throw new ConfigurationException("MANAGER_CONFIGURATION_ERROR") throw new ConfigurationException("Could not validate configuration")
{ {
Errors = validationResult.Errors.Select(_error => _error.ErrorMessage).ToArray(), Errors = validationResult.Errors.Select(_error => _error.ErrorMessage).ToArray(),
ConfigurationFileName = ConfigHandler.FileName ConfigurationFileName = ConfigHandler.FileName
@ -530,6 +521,7 @@ namespace IW4MAdmin.Application
Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]); Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]);
await InitializeServers(); await InitializeServers();
IsInitialized = true;
} }
private async Task InitializeServers() private async Task InitializeServers()
@ -595,16 +587,29 @@ namespace IW4MAdmin.Application
public async Task Start() => await UpdateServerStates(); public async Task Start() => await UpdateServerStates();
public void Stop() public async Task Stop()
{ {
foreach (var plugin in Plugins)
{
try
{
await plugin.OnUnloadAsync().WithTimeout(Utilities.DefaultCommandTimeout);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not cleanly unload plugin {PluginName}", plugin.Name);
}
}
_tokenSource.Cancel(); _tokenSource.Cancel();
IsRunning = false; IsRunning = false;
} }
public void Restart() public void Restart()
{ {
IsRestartRequested = true; IsRestartRequested = true;
Stop(); Stop().GetAwaiter().GetResult();
} }
[Obsolete] [Obsolete]

View File

@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Data.Models;
using Data.Models.Client;
using Microsoft.Extensions.Logging;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands.ClientTags
{
public class AddClientTagCommand : Command
{
private readonly IMetaServiceV2 _metaService;
public AddClientTagCommand(ILogger<AddClientTagCommand> commandLogger, CommandConfiguration config,
ITranslationLookup layout, IMetaServiceV2 metaService) :
base(config, layout)
{
Name = "addclienttag";
Description = layout["COMMANDS_ADD_CLIENT_TAG_DESC"];
Alias = "act";
Permission = EFClient.Permission.Owner;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGUMENT_TAG"],
Required = true
}
};
_metaService = metaService;
logger = commandLogger;
}
public override async Task ExecuteAsync(GameEvent gameEvent)
{
var existingTags = await _metaService.GetPersistentMetaValue<List<TagMeta>>(EFMeta.ClientTagNameV2) ??
new List<TagMeta>();
var tagName = gameEvent.Data.Trim();
if (existingTags.Any(tag => tag.TagName == tagName))
{
logger.LogWarning("Tag with name {TagName} already exists", tagName);
return;
}
existingTags.Add(new TagMeta
{
Id = (existingTags.LastOrDefault()?.TagId ?? 0) + 1,
Value = tagName
});
await _metaService.SetPersistentMetaValue(EFMeta.ClientTagNameV2, existingTags,
gameEvent.Owner.Manager.CancellationToken);
gameEvent.Origin.Tell(_translationLookup["COMMANDS_ADD_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data));
}
}
}

View File

@ -1,17 +1,19 @@
using System.Linq; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Models; using Data.Models;
using Data.Models.Client; using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
namespace SharedLibraryCore.Commands namespace IW4MAdmin.Application.Commands.ClientTags
{ {
public class ListClientTags : Command public class ListClientTags : Command
{ {
private readonly IMetaService _metaService; private readonly IMetaServiceV2 _metaService;
public ListClientTags(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : base( public ListClientTags(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) : base(
config, layout) config, layout)
{ {
Name = "listclienttags"; Name = "listclienttags";
@ -19,14 +21,18 @@ namespace SharedLibraryCore.Commands
Alias = "lct"; Alias = "lct";
Permission = EFClient.Permission.Owner; Permission = EFClient.Permission.Owner;
RequiresTarget = false; RequiresTarget = false;
_metaService = metaService; _metaService = metaService;
} }
public override async Task ExecuteAsync(GameEvent gameEvent) public override async Task ExecuteAsync(GameEvent gameEvent)
{ {
var tags = await _metaService.GetPersistentMeta(EFMeta.ClientTagName); var tags = await _metaService.GetPersistentMetaValue<List<TagMeta>>(EFMeta.ClientTagNameV2);
gameEvent.Origin.Tell(tags.Select(tag => tag.Value));
if (tags is not null)
{
await gameEvent.Origin.TellAsync(tags.Select(tag => tag.TagName),
gameEvent.Owner.Manager.CancellationToken);
}
} }
} }
} }

View File

@ -1,16 +1,20 @@
using System.Threading.Tasks; using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Data.Models; using Data.Models;
using Data.Models.Client; using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
namespace SharedLibraryCore.Commands namespace IW4MAdmin.Application.Commands.ClientTags
{ {
public class RemoveClientTag : Command public class RemoveClientTag : Command
{ {
private readonly IMetaService _metaService; private readonly IMetaServiceV2 _metaService;
public RemoveClientTag(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : base( public RemoveClientTag(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) : base(
config, layout) config, layout)
{ {
Name = "removeclienttag"; Name = "removeclienttag";
@ -32,8 +36,13 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent gameEvent) public override async Task ExecuteAsync(GameEvent gameEvent)
{ {
await _metaService.RemovePersistentMeta(EFMeta.ClientTagName, gameEvent.Data); var existingMeta = await _metaService.GetPersistentMetaValue<List<TagMeta>>(EFMeta.ClientTagNameV2,
gameEvent.Owner.Manager.CancellationToken);
existingMeta = existingMeta.Where(meta => meta.TagName != gameEvent.Data.Trim()).ToList();
await _metaService.SetPersistentMetaValue(EFMeta.ClientTagNameV2, existingMeta,
gameEvent.Owner.Manager.CancellationToken);
gameEvent.Origin.Tell(_translationLookup["COMMANDS_REMOVE_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data)); gameEvent.Origin.Tell(_translationLookup["COMMANDS_REMOVE_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data));
} }
} }
} }

View File

@ -1,18 +1,22 @@
using System.Linq; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Models; using Data.Models;
using Data.Models.Client; using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
namespace SharedLibraryCore.Commands namespace IW4MAdmin.Application.Commands.ClientTags
{ {
public class SetClientTagCommand : Command public class SetClientTagCommand : Command
{ {
private readonly IMetaService _metaService; private readonly IMetaServiceV2 _metaService;
public SetClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : public SetClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) :
base(config, layout) base(config, layout)
{ {
Name = "setclienttag"; Name = "setclienttag";
@ -34,8 +38,10 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent gameEvent) public override async Task ExecuteAsync(GameEvent gameEvent)
{ {
var availableTags = await _metaService.GetPersistentMeta(EFMeta.ClientTagName); var token = gameEvent.Owner.Manager.CancellationToken;
var matchingTag = availableTags.FirstOrDefault(tag => tag.Value == gameEvent.Data);
var availableTags = await _metaService.GetPersistentMetaValue<List<LookupValue<string>>>(EFMeta.ClientTagNameV2, token);
var matchingTag = availableTags.FirstOrDefault(tag => tag.Value == gameEvent.Data.Trim());
if (matchingTag == null) if (matchingTag == null)
{ {
@ -44,8 +50,9 @@ namespace SharedLibraryCore.Commands
} }
gameEvent.Target.Tag = matchingTag.Value; gameEvent.Target.Tag = matchingTag.Value;
await _metaService.AddPersistentMeta(EFMeta.ClientTag, string.Empty, gameEvent.Target, matchingTag); await _metaService.SetPersistentMetaForLookupKey(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, matchingTag.Id,
gameEvent.Target.ClientId, token);
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SET_CLIENT_TAG_SUCCESS"].FormatExt(matchingTag.Value)); gameEvent.Origin.Tell(_translationLookup["COMMANDS_SET_CLIENT_TAG_SUCCESS"].FormatExt(matchingTag.Value));
} }
} }
} }

View File

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands.ClientTags;
public class TagMeta : ILookupValue<string>
{
[JsonIgnore] public int TagId => Id;
[JsonIgnore] public string TagName => Value;
public int Id { get; set; }
public string Value { get; set; }
}

View File

@ -1,17 +1,18 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Models; using Data.Models;
using Data.Models.Client; using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
namespace SharedLibraryCore.Commands namespace IW4MAdmin.Application.Commands.ClientTags
{ {
public class UnsetClientTagCommand : Command public class UnsetClientTagCommand : Command
{ {
private readonly IMetaService _metaService; private readonly IMetaServiceV2 _metaService;
public UnsetClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) :
public UnsetClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) :
base(config, layout) base(config, layout)
{ {
Name = "unsetclienttag"; Name = "unsetclienttag";
@ -34,8 +35,9 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent gameEvent) public override async Task ExecuteAsync(GameEvent gameEvent)
{ {
gameEvent.Target.Tag = null; gameEvent.Target.Tag = null;
await _metaService.RemovePersistentMeta(EFMeta.ClientTag, gameEvent.Target); await _metaService.RemovePersistentMeta(EFMeta.ClientTagV2, gameEvent.Target.ClientId,
gameEvent.Owner.Manager.CancellationToken);
gameEvent.Origin.Tell(_translationLookup["COMMANDS_UNSET_CLIENT_TAG_SUCCESS"]); gameEvent.Origin.Tell(_translationLookup["COMMANDS_UNSET_CLIENT_TAG_SUCCESS"]);
} }
} }
} }

View File

@ -29,9 +29,9 @@ namespace IW4MAdmin.Application.Commands
$"[(Color::Accent){client.ClientPermission.Name}(Color::White){(string.IsNullOrEmpty(client.Tag) ? "" : $" {client.Tag}")}(Color::White)][(Color::Yellow)#{client.ClientNumber}(Color::White)] {client.Name}") $"[(Color::Accent){client.ClientPermission.Name}(Color::White){(string.IsNullOrEmpty(client.Tag) ? "" : $" {client.Tag}")}(Color::White)][(Color::Yellow)#{client.ClientNumber}(Color::White)] {client.Name}")
.ToArray(); .ToArray();
gameEvent.Origin.Tell(clientList); gameEvent.Origin.TellAsync(clientList, gameEvent.Owner.Manager.CancellationToken);
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
} }

View File

@ -54,20 +54,8 @@ namespace IW4MAdmin.Application.Commands
return; return;
} }
string map; var map = match.Groups[1].Length > 0 ? match.Groups[1].ToString() : match.Groups[2].ToString();
string gametype; var gametype = match.Groups[3].Length > 0 ? match.Groups[3].ToString() : match.Groups[4].ToString();
if (match.Groups.Count > 3)
{
map = match.Groups[2].ToString();
gametype = match.Groups[4].ToString();
}
else
{
map = match.Groups[1].ToString();
gametype = match.Groups[3].ToString();
}
var matchingMaps = gameEvent.Owner.FindMap(map); var matchingMaps = gameEvent.Owner.FindMap(map);
var matchingGametypes = _defaultSettings.FindGametype(gametype, gameEvent.Owner.GameName); var matchingGametypes = _defaultSettings.FindGametype(gametype, gameEvent.Owner.GameName);
@ -104,7 +92,7 @@ namespace IW4MAdmin.Application.Commands
_logger.LogDebug("Changing map to {Map} and gametype {Gametype}", map, gametype); _logger.LogDebug("Changing map to {Map} and gametype {Gametype}", map, gametype);
await gameEvent.Owner.SetDvarAsync("g_gametype", gametype); await gameEvent.Owner.SetDvarAsync("g_gametype", gametype, gameEvent.Owner.Manager.CancellationToken);
gameEvent.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(map)); gameEvent.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(map));
await Task.Delay(gameEvent.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds); await Task.Delay(gameEvent.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds);

View File

@ -2021,7 +2021,7 @@
"barrett": "Barrett .50cal", "barrett": "Barrett .50cal",
"mp44": "MP44", "mp44": "MP44",
"remington700": "R700", "remington700": "R700",
"rpd": "RDP", "rpd": "RPD",
"saw": " M249 SAW", "saw": " M249 SAW",
"usp": "USP .45", "usp": "USP .45",
"winchester1200": "W1200", "winchester1200": "W1200",
@ -2083,6 +2083,126 @@
"type99rifle": "Arisaka", "type99rifle": "Arisaka",
"mosinrifle": "Mosin-Nagant", "mosinrifle": "Mosin-Nagant",
"ptrs41": "PTRS-41" "ptrs41": "PTRS-41"
},
"T6" : {
"mp7": "MP7",
"pdw57": "PDW-57",
"vector": "Vector K10",
"insas": "MSMC",
"qcw05": "Chicom CQB",
"evoskorpion": "Skorpion EVO",
"peacekeeper": "Peacekeeper",
"tar21": "MTAR",
"type95": "Type 25",
"sig556": "SWAT-556",
"sa58": "FAL-OSW",
"hk416": "M27",
"scar": "SCAR-H",
"saritch": "SMR",
"xm8": "M8A1",
"an94": "AN-94",
"870mcs": "Remington-870 MCS",
"saiga12": "S12",
"ksg": "KSG",
"srm1216": "M1216",
"mk48": "MK 48",
"qbb95": "QBB LSW",
"lsat": "LSAT",
"hamr": "HAMR",
"svu": "SVU-AS",
"dsr50": "DSR 50",
"ballista": "Ballista",
"as50": "XPR-50",
"fiveseven": "Five-Seven",
"fnp45": "TAC-45",
"beretta93r": "B23R",
"judge": "Executioner",
"kard": "KAP-40",
"smaw": "SMAW",
"fhj18": "FHJ-18 AA",
"usrpg": "RPG",
"riotshield": "Assault Shield",
"crossbow": "Crossbow",
"knife_ballistic": "Ballistic Knife",
"knife_held": "Knife",
"knife": "Knife",
"frag_grenade": "Grenade",
"hatchet": "Combat Axe",
"sticky_grenade": "Semtex",
"satchel_charge": "C4",
"bouncingbetty": "Bouncing Betty",
"claymore": "Claymore",
"smoke_center": "Smoke Grenade",
"concussion_grenade": "Concussion",
"emp_grenade": "EMP Grenade",
"sensor_grenade": "Sensor Grenade",
"flash_grenade": "Flashbang",
"proximity_grenade": "Shock Charge",
"pda_hack": "Black Hat",
"trophy_system": "Trophy System",
"tactical_insertion": "Tactical Insertion",
"acog": "ACOG",
"stalker": "Stock",
"swayreduc": "Ballistics CPU",
"ir": "Dual Band",
"dw": "Dual Wield",
"extclip": "Extended Clip",
"halo": "EOTech",
"dualclip": "Fast Mag",
"fmj": "FMJ",
"grip": "Fore Grip",
"gl": "Grenade Launcher",
"dualoptic": "Hybrid Optic",
"is": "Iron Sights",
"steadyaim": "Laser Sight",
"extbarrel": "Long Barrel",
"mms": "MMS",
"fastads": "Quickdraw",
"rf": "Rapid Fire",
"reflex": "Reflex Sight",
"sf": "Select Fire",
"silencer": "Suppressor",
"tacknife": "Tactical Knife",
"stackfire": "Tri-Bolt",
"rangefinder": "Target Finder",
"vzoom": "Variable Zoom",
"spyplane": "UAV",
"rcbomb": "RC-XD",
"missile_drone": "Hunter Killer",
"supplydrop": "Care Package",
"counteruav": "Counter-UAV",
"microwave_turret": "Guardian",
"remote_missile": "Hellstorm Missile",
"planemortar": "Lightning Strike",
"auto_turret": "Sentry Gun",
"minigun": "Death Machine",
"m32": "War Machine",
"qrdrone": "Dragonfire",
"ai_tank_drop": "AGR",
"comlink": "Stealth Chopper",
"spyplane_direction": "Orbital VSAT",
"helicopter_guard": "Escort Drone",
"emp": "EMP",
"straferun": "Warthog",
"remote_mortar": "Lodestar",
"player_gunner": "VTOL Warship",
"dogs": "K9 Unit",
"missile_swarm": "Swarm"
} }
} }
} }

View File

@ -49,6 +49,13 @@ namespace IW4MAdmin.Application.EventParsers
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.JoinTeam.Pattern = @"^(JT);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(\w+);(.+)$";
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.OriginTeam, 4);
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.OriginName, 5);
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1);
@ -90,7 +97,8 @@ namespace IW4MAdmin.Application.EventParsers
{Configuration.Say, GameEvent.EventType.Say}, {Configuration.Say, GameEvent.EventType.Say},
{Configuration.Kill, GameEvent.EventType.Kill}, {Configuration.Kill, GameEvent.EventType.Kill},
{Configuration.MapChange, GameEvent.EventType.MapChange}, {Configuration.MapChange, GameEvent.EventType.MapChange},
{Configuration.MapEnd, GameEvent.EventType.MapEnd} {Configuration.MapEnd, GameEvent.EventType.MapEnd},
{Configuration.JoinTeam, GameEvent.EventType.JoinTeam}
}; };
_eventTypeMap = new Dictionary<string, GameEvent.EventType> _eventTypeMap = new Dictionary<string, GameEvent.EventType>
@ -100,7 +108,8 @@ namespace IW4MAdmin.Application.EventParsers
{"K", GameEvent.EventType.Kill}, {"K", GameEvent.EventType.Kill},
{"D", GameEvent.EventType.Damage}, {"D", GameEvent.EventType.Damage},
{"J", GameEvent.EventType.PreConnect}, {"J", GameEvent.EventType.PreConnect},
{"Q", GameEvent.EventType.PreDisconnect}, {"JT", GameEvent.EventType.JoinTeam},
{"Q", GameEvent.EventType.PreDisconnect}
}; };
} }
@ -322,6 +331,47 @@ namespace IW4MAdmin.Application.EventParsers
} }
} }
if (eventType == GameEvent.EventType.JoinTeam)
{
var match = Configuration.JoinTeam.PatternMatcher.Match(logLine);
if (match.Success)
{
var originIdString = match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
var originName = match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginName]];
var team = match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginTeam]];
if (Configuration.TeamMapping.ContainsKey(team))
{
team = Configuration.TeamMapping[team].ToString();
}
var networkId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
return new GameEvent
{
Type = GameEvent.EventType.JoinTeam,
Data = logLine,
Origin = new EFClient
{
CurrentAlias = new EFAlias
{
Name = match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginName]].TrimNewLine(),
},
NetworkId = networkId,
ClientNumber = Convert.ToInt32(match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]),
State = EFClient.ClientState.Connected,
},
Extra = team,
RequiredEntity = GameEvent.EventRequiredEntity.Origin,
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
}
if (eventType == GameEvent.EventType.PreDisconnect) if (eventType == GameEvent.EventType.PreDisconnect)
{ {
var match = Configuration.Quit.PatternMatcher.Match(logLine); var match = Configuration.Quit.PatternMatcher.Match(logLine);

View File

@ -1,6 +1,8 @@
using SharedLibraryCore.Interfaces; using System.Collections.Generic;
using SharedLibraryCore.Interfaces;
using System.Globalization; using System.Globalization;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Application.EventParsers namespace IW4MAdmin.Application.EventParsers
{ {
@ -8,11 +10,12 @@ namespace IW4MAdmin.Application.EventParsers
/// generic implementation of the IEventParserConfiguration /// generic implementation of the IEventParserConfiguration
/// allows script plugins to generate dynamic configurations /// allows script plugins to generate dynamic configurations
/// </summary> /// </summary>
sealed internal class DynamicEventParserConfiguration : IEventParserConfiguration internal sealed class DynamicEventParserConfiguration : IEventParserConfiguration
{ {
public string GameDirectory { get; set; } public string GameDirectory { get; set; }
public ParserRegex Say { get; set; } public ParserRegex Say { get; set; }
public ParserRegex Join { get; set; } public ParserRegex Join { get; set; }
public ParserRegex JoinTeam { get; set; }
public ParserRegex Quit { get; set; } public ParserRegex Quit { get; set; }
public ParserRegex Kill { get; set; } public ParserRegex Kill { get; set; }
public ParserRegex Damage { get; set; } public ParserRegex Damage { get; set; }
@ -22,10 +25,13 @@ namespace IW4MAdmin.Application.EventParsers
public ParserRegex MapEnd { get; set; } public ParserRegex MapEnd { get; set; }
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber; public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
public Dictionary<string, EFClient.TeamType> TeamMapping { get; set; } = new();
public DynamicEventParserConfiguration(IParserRegexFactory parserRegexFactory) public DynamicEventParserConfiguration(IParserRegexFactory parserRegexFactory)
{ {
Say = parserRegexFactory.CreateParserRegex(); Say = parserRegexFactory.CreateParserRegex();
Join = parserRegexFactory.CreateParserRegex(); Join = parserRegexFactory.CreateParserRegex();
JoinTeam = parserRegexFactory.CreateParserRegex();
Quit = parserRegexFactory.CreateParserRegex(); Quit = parserRegexFactory.CreateParserRegex();
Kill = parserRegexFactory.CreateParserRegex(); Kill = parserRegexFactory.CreateParserRegex();
Damage = parserRegexFactory.CreateParserRegex(); Damage = parserRegexFactory.CreateParserRegex();

View File

@ -18,14 +18,22 @@ namespace IW4MAdmin.Application.Factories
public IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser) public IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser)
{ {
var baseUri = logUris[0]; var baseUri = logUris[0];
if (baseUri.Scheme == Uri.UriSchemeHttp) if (baseUri.Scheme == Uri.UriSchemeHttp || baseUri.Scheme == Uri.UriSchemeHttps)
{ {
return new GameLogReaderHttp(logUris, eventParser, _serviceProvider.GetRequiredService<ILogger<GameLogReaderHttp>>()); return new GameLogReaderHttp(logUris, eventParser,
_serviceProvider.GetRequiredService<ILogger<GameLogReaderHttp>>());
} }
else if (baseUri.Scheme == Uri.UriSchemeFile) if (baseUri.Scheme == Uri.UriSchemeFile)
{ {
return new GameLogReader(baseUri.LocalPath, eventParser, _serviceProvider.GetRequiredService<ILogger<GameLogReader>>()); return new GameLogReader(baseUri.LocalPath, eventParser,
_serviceProvider.GetRequiredService<ILogger<GameLogReader>>());
}
if (baseUri.Scheme == Uri.UriSchemeNetTcp)
{
return new NetworkGameLogReader(logUris, eventParser,
_serviceProvider.GetRequiredService<ILogger<NetworkGameLogReader>>());
} }
throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\""); throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\"");

View File

@ -14,7 +14,7 @@ namespace IW4MAdmin.Application.Factories
internal class GameServerInstanceFactory : IGameServerInstanceFactory internal class GameServerInstanceFactory : IGameServerInstanceFactory
{ {
private readonly ITranslationLookup _translationLookup; private readonly ITranslationLookup _translationLookup;
private readonly IMetaService _metaService; private readonly IMetaServiceV2 _metaService;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
/// <summary> /// <summary>
@ -23,7 +23,7 @@ namespace IW4MAdmin.Application.Factories
/// <param name="translationLookup"></param> /// <param name="translationLookup"></param>
/// <param name="rconConnectionFactory"></param> /// <param name="rconConnectionFactory"></param>
public GameServerInstanceFactory(ITranslationLookup translationLookup, public GameServerInstanceFactory(ITranslationLookup translationLookup,
IMetaService metaService, IMetaServiceV2 metaService,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
{ {
_translationLookup = translationLookup; _translationLookup = translationLookup;
@ -45,4 +45,4 @@ namespace IW4MAdmin.Application.Factories
_serviceProvider.GetRequiredService<ILookupCache<EFServer>>()); _serviceProvider.GetRequiredService<ILookupCache<EFServer>>());
} }
} }
} }

View File

@ -6,6 +6,7 @@ using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client; using Data.Models.Client;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -30,7 +31,7 @@ namespace IW4MAdmin.Application.Factories
/// <inheritdoc/> /// <inheritdoc/>
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission,
bool isTargetRequired, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction) bool isTargetRequired, IEnumerable<(string, bool)> args, Func<GameEvent, Task> executeAction, Server.Game[] supportedGames)
{ {
var permissionEnum = Enum.Parse<EFClient.Permission>(permission); var permissionEnum = Enum.Parse<EFClient.Permission>(permission);
var argsArray = args.Select(_arg => new CommandArgument var argsArray = args.Select(_arg => new CommandArgument
@ -40,7 +41,7 @@ namespace IW4MAdmin.Application.Factories
}).ToArray(); }).ToArray();
return new ScriptCommand(name, alias, description, isTargetRequired, permissionEnum, argsArray, executeAction, return new ScriptCommand(name, alias, description, isTargetRequired, permissionEnum, argsArray, executeAction,
_config, _transLookup, _serviceProvider.GetRequiredService<ILogger<ScriptCommand>>()); _config, _transLookup, _serviceProvider.GetRequiredService<ILogger<ScriptCommand>>(), supportedGames);
} }
} }
} }

View File

@ -21,7 +21,7 @@ namespace IW4MAdmin.Application.IO
{ {
_reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser); _reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser);
_server = server; _server = server;
_ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false; _ignoreBots = server.Manager.GetApplicationSettings().Configuration()?.IgnoreBots ?? false;
_logger = logger; _logger = logger;
} }
@ -69,7 +69,7 @@ namespace IW4MAdmin.Application.IO
return; return;
} }
var events = await _reader.ReadEventsFromLog(fileDiff, previousFileSize); var events = await _reader.ReadEventsFromLog(fileDiff, previousFileSize, _server);
foreach (var gameEvent in events) foreach (var gameEvent in events)
{ {

View File

@ -28,7 +28,7 @@ namespace IW4MAdmin.Application.IO
_logger = logger; _logger = logger;
} }
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition) public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition, Server server = null)
{ {
// allocate the bytes for the new log lines // allocate the bytes for the new log lines
List<string> logLines = new List<string>(); List<string> logLines = new List<string>();

View File

@ -34,7 +34,7 @@ namespace IW4MAdmin.Application.IO
public int UpdateInterval => 500; public int UpdateInterval => 500;
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition) public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition, Server server = null)
{ {
var events = new List<GameEvent>(); var events = new List<GameEvent>();
var response = await _logServerApi.Log(_safeLogPath, lastKey); var response = await _logServerApi.Log(_safeLogPath, lastKey);

View File

@ -0,0 +1,163 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Integrations.Cod;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.IO
{
/// <summary>
/// provides capability of reading log files over udp
/// </summary>
class NetworkGameLogReader : IGameLogReader
{
private readonly IEventParser _eventParser;
private readonly ILogger _logger;
private readonly Uri _uri;
private static readonly NetworkLogState State = new();
private bool _stateRegistered;
private CancellationToken _token;
public NetworkGameLogReader(IReadOnlyList<Uri> uris, IEventParser parser, ILogger<NetworkGameLogReader> logger)
{
_eventParser = parser;
_uri = uris[0];
_logger = logger;
}
public long Length => -1;
public int UpdateInterval => 150;
public Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition,
Server server = null)
{
// todo: other games might support this
var serverEndpoint = (server?.RemoteConnection as CodRConConnection)?.Endpoint;
if (serverEndpoint is null)
{
return Task.FromResult(Enumerable.Empty<GameEvent>());
}
if (!_stateRegistered && !State.EndPointExists(serverEndpoint))
{
try
{
var client = State.RegisterEndpoint(serverEndpoint, BuildLocalEndpoint()).Client;
_stateRegistered = true;
_token = server.Manager.CancellationToken;
if (client == null)
{
using (LogContext.PushProperty("Server", server.ToString()))
{
_logger.LogInformation("Not registering {Name} socket because it is already bound",
nameof(NetworkGameLogReader));
}
return Task.FromResult(Enumerable.Empty<GameEvent>());
}
Task.Run(async () => await ReadNetworkData(client, _token), _token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not register {Name} endpoint {Endpoint}",
nameof(NetworkGameLogReader), _uri);
throw;
}
}
var events = new List<GameEvent>();
foreach (var logData in State.GetServerLogData(serverEndpoint)
.Select(log => Utilities.EncodingType.GetString(log)))
{
if (string.IsNullOrWhiteSpace(logData))
{
return Task.FromResult(Enumerable.Empty<GameEvent>());
}
var lines = logData
.Split('\n')
.Where(line => line.Length > 0 && !line.Contains('ÿ'));
foreach (var eventLine in lines)
{
try
{
// this trim end should hopefully fix the nasty runaway regex
var gameEvent = _eventParser.GenerateGameEvent(eventLine.TrimEnd('\r'));
events.Add(gameEvent);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not properly parse event line from http {EventLine}",
eventLine);
}
}
}
return Task.FromResult((IEnumerable<GameEvent>)events);
}
private async Task ReadNetworkData(UdpClient client, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
// get more data
IPEndPoint remoteEndpoint = null;
byte[] bufferedData = null;
if (client == null)
{
// we already have a socket listening on this port for data, so we don't need to run another thread
break;
}
try
{
var result = await client.ReceiveAsync(_token);
remoteEndpoint = result.RemoteEndPoint;
bufferedData = result.Buffer;
}
catch (OperationCanceledException)
{
_logger.LogDebug("Stopping network log receive");
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not receive lines for {LogReader}", nameof(NetworkGameLogReader));
}
if (bufferedData != null)
{
State.QueueServerLogData(remoteEndpoint, bufferedData);
}
}
}
private IPEndPoint BuildLocalEndpoint()
{
try
{
return new IPEndPoint(Dns.GetHostAddresses(_uri.Host).First(), _uri.Port);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not setup {LogReader} endpoint", nameof(NetworkGameLogReader));
throw;
}
}
}
}

View File

@ -0,0 +1,138 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace IW4MAdmin.Application.IO;
public class NetworkLogState : Dictionary<IPEndPoint, UdpClientState>
{
public UdpClientState RegisterEndpoint(IPEndPoint serverEndpoint, IPEndPoint localEndpoint)
{
try
{
lock (this)
{
if (!ContainsKey(serverEndpoint))
{
Add(serverEndpoint, new UdpClientState { Client = new UdpClient(localEndpoint) });
}
}
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
lock (this)
{
// we don't add the udp client because it already exists (listening to multiple servers from one socket)
Add(serverEndpoint, new UdpClientState());
}
}
return this[serverEndpoint];
}
public List<byte[]> GetServerLogData(IPEndPoint serverEndpoint)
{
try
{
var state = this[serverEndpoint];
if (state == null)
{
return new List<byte[]>();
}
// it's possible that we could be trying to read and write to the queue simultaneously so we need to wait
this[serverEndpoint].OnAction.Wait();
var data = new List<byte[]>();
while (this[serverEndpoint].AvailableLogData.Count > 0)
{
data.Add(this[serverEndpoint].AvailableLogData.Dequeue());
}
return data;
}
finally
{
if (this[serverEndpoint].OnAction.CurrentCount == 0)
{
this[serverEndpoint].OnAction.Release(1);
}
}
}
public void QueueServerLogData(IPEndPoint serverEndpoint, byte[] data)
{
var endpoint = Keys.FirstOrDefault(key =>
Equals(key.Address, serverEndpoint.Address) && key.Port == serverEndpoint.Port);
try
{
if (endpoint == null)
{
return;
}
// currently our expected start and end characters
var startsWithPrefix = StartsWith(data, "ÿÿÿÿprint\n");
var endsWithDelimiter = data[^1] == '\n';
// we have the data we expected
if (!startsWithPrefix || !endsWithDelimiter)
{
return;
}
// it's possible that we could be trying to read and write to the queue simultaneously so we need to wait
this[endpoint].OnAction.Wait();
this[endpoint].AvailableLogData.Enqueue(data);
}
finally
{
if (endpoint != null && this[endpoint].OnAction.CurrentCount == 0)
{
this[endpoint].OnAction.Release(1);
}
}
}
public bool EndPointExists(IPEndPoint serverEndpoint)
{
lock (this)
{
return ContainsKey(serverEndpoint);
}
}
private static bool StartsWith(byte[] sourceArray, string match)
{
if (sourceArray is null)
{
return false;
}
if (match.Length > sourceArray.Length)
{
return false;
}
return !match.Where((t, i) => sourceArray[i] != (byte)t).Any();
}
}
public class UdpClientState
{
public UdpClient Client { get; set; }
public Queue<byte[]> AvailableLogData { get; } = new();
public SemaphoreSlim OnAction { get; } = new(1, 1);
~UdpClientState()
{
OnAction.Dispose();
Client?.Dispose();
}
}

View File

@ -35,7 +35,7 @@ namespace IW4MAdmin
private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex; private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex;
public GameLogEventDetection LogEvent; public GameLogEventDetection LogEvent;
private readonly ITranslationLookup _translationLookup; private readonly ITranslationLookup _translationLookup;
private readonly IMetaService _metaService; private readonly IMetaServiceV2 _metaService;
private const int REPORT_FLAG_COUNT = 4; private const int REPORT_FLAG_COUNT = 4;
private long lastGameTime = 0; private long lastGameTime = 0;
@ -49,7 +49,7 @@ namespace IW4MAdmin
ServerConfiguration serverConfiguration, ServerConfiguration serverConfiguration,
CommandConfiguration commandConfiguration, CommandConfiguration commandConfiguration,
ITranslationLookup lookup, ITranslationLookup lookup,
IMetaService metaService, IMetaServiceV2 metaService,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IClientNoticeMessageFormatter messageFormatter, IClientNoticeMessageFormatter messageFormatter,
ILookupCache<EFServer> serverCache) : base(serviceProvider.GetRequiredService<ILogger<Server>>(), ILookupCache<EFServer> serverCache) : base(serviceProvider.GetRequiredService<ILogger<Server>>(),
@ -96,6 +96,8 @@ namespace IW4MAdmin
client.ClientNumber = clientFromLog.ClientNumber; client.ClientNumber = clientFromLog.ClientNumber;
client.Score = clientFromLog.Score; client.Score = clientFromLog.Score;
client.Ping = clientFromLog.Ping; client.Ping = clientFromLog.Ping;
client.Team = clientFromLog.Team;
client.TeamName = clientFromLog.TeamName;
client.CurrentServer = this; client.CurrentServer = this;
client.State = ClientState.Connecting; client.State = ClientState.Connecting;
@ -236,7 +238,7 @@ namespace IW4MAdmin
private async Task CreatePluginTask(IPlugin plugin, GameEvent gameEvent) private async Task CreatePluginTask(IPlugin plugin, GameEvent gameEvent)
{ {
// we don't want to run the events on parser plugins // we don't want to run the events on parser plugins
if (plugin is ScriptPlugin scriptPlugin && scriptPlugin.IsParser) if (plugin is ScriptPlugin { IsParser: true })
{ {
return; return;
} }
@ -248,6 +250,11 @@ namespace IW4MAdmin
{ {
await plugin.OnEventAsync(gameEvent, this).WithWaitCancellation(tokenSource.Token); await plugin.OnEventAsync(gameEvent, this).WithWaitCancellation(tokenSource.Token);
} }
catch (OperationCanceledException)
{
ServerLogger.LogWarning("Timed out executing event {EventType} for {Plugin}", gameEvent.Type,
plugin.Name);
}
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(loc["SERVER_PLUGIN_ERROR"].FormatExt(plugin.Name, ex.GetType().Name)); Console.WriteLine(loc["SERVER_PLUGIN_ERROR"].FormatExt(plugin.Name, ex.GetType().Name));
@ -290,7 +297,7 @@ namespace IW4MAdmin
} }
} }
if (E.Type == GameEvent.EventType.ConnectionLost) else if (E.Type == GameEvent.EventType.ConnectionLost)
{ {
var exception = E.Extra as Exception; var exception = E.Extra as Exception;
ServerLogger.LogError(exception, ServerLogger.LogError(exception,
@ -304,7 +311,7 @@ namespace IW4MAdmin
Throttled = true; Throttled = true;
} }
if (E.Type == GameEvent.EventType.ConnectionRestored) else if (E.Type == GameEvent.EventType.ConnectionRestored)
{ {
ServerLogger.LogInformation( ServerLogger.LogInformation(
"Connection restored with {server}", ToString()); "Connection restored with {server}", ToString());
@ -316,13 +323,13 @@ namespace IW4MAdmin
if (!string.IsNullOrEmpty(CustomSayName)) if (!string.IsNullOrEmpty(CustomSayName))
{ {
await this.SetDvarAsync("sv_sayname", CustomSayName); await this.SetDvarAsync("sv_sayname", CustomSayName, Manager.CancellationToken);
} }
Throttled = false; Throttled = false;
} }
if (E.Type == GameEvent.EventType.ChangePermission) else if (E.Type == GameEvent.EventType.ChangePermission)
{ {
var newPermission = (Permission) E.Extra; var newPermission = (Permission) E.Extra;
ServerLogger.LogInformation("{origin} is setting {target} to permission level {newPermission}", ServerLogger.LogInformation("{origin} is setting {target} to permission level {newPermission}",
@ -345,7 +352,8 @@ namespace IW4MAdmin
Time = DateTime.UtcNow Time = DateTime.UtcNow
}); });
var clientTag = await _metaService.GetPersistentMeta(EFMeta.ClientTag, E.Origin); var clientTag = await _metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2,
EFMeta.ClientTagNameV2, E.Origin.ClientId, Manager.CancellationToken);
if (clientTag?.LinkedMeta != null) if (clientTag?.LinkedMeta != null)
{ {
@ -423,6 +431,7 @@ namespace IW4MAdmin
Clients[E.Origin.ClientNumber] = E.Origin; Clients[E.Origin.ClientNumber] = E.Origin;
try try
{ {
E.Origin.GameName = (Reference.Game?)GameName;
E.Origin = await OnClientConnected(E.Origin); E.Origin = await OnClientConnected(E.Origin);
E.Target = E.Origin; E.Target = E.Origin;
} }
@ -477,7 +486,7 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.Unflag) else if (E.Type == GameEvent.EventType.Unflag)
{ {
var unflagPenalty = new EFPenalty() var unflagPenalty = new EFPenalty
{ {
Type = EFPenalty.PenaltyType.Unflag, Type = EFPenalty.PenaltyType.Unflag,
Expires = DateTime.UtcNow, Expires = DateTime.UtcNow,
@ -489,7 +498,8 @@ namespace IW4MAdmin
}; };
E.Target.SetLevel(Permission.User, E.Origin); E.Target.SetLevel(Permission.User, E.Origin);
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId); await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId,
E.Target.CurrentAlias?.IPAddress);
await Manager.GetPenaltyService().Create(unflagPenalty); await Manager.GetPenaltyService().Create(unflagPenalty);
} }
@ -499,7 +509,8 @@ namespace IW4MAdmin
{ {
Origin = E.Origin, Origin = E.Origin,
Target = E.Target, Target = E.Target,
Reason = E.Data Reason = E.Data,
ReportedOn = DateTime.UtcNow
}); });
var newReport = new EFPenalty() var newReport = new EFPenalty()
@ -562,8 +573,8 @@ namespace IW4MAdmin
Time = DateTime.UtcNow Time = DateTime.UtcNow
}); });
await _metaService.AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin); await _metaService.SetPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin.ClientId);
await _metaService.AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin); await _metaService.SetPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin.ClientId);
} }
else if (E.Type == GameEvent.EventType.PreDisconnect) else if (E.Type == GameEvent.EventType.PreDisconnect)
@ -614,7 +625,7 @@ namespace IW4MAdmin
await OnClientUpdate(E.Origin); await OnClientUpdate(E.Origin);
} }
if (E.Type == GameEvent.EventType.Say) else if (E.Type == GameEvent.EventType.Say)
{ {
if (E.Data?.Length > 0) if (E.Data?.Length > 0)
{ {
@ -634,7 +645,7 @@ namespace IW4MAdmin
} }
} }
ChatHistory.Add(new ChatInfo() ChatHistory.Add(new ChatInfo
{ {
Name = E.Origin.Name, Name = E.Origin.Name,
Message = message, Message = message,
@ -644,7 +655,7 @@ namespace IW4MAdmin
} }
} }
if (E.Type == GameEvent.EventType.MapChange) else if (E.Type == GameEvent.EventType.MapChange)
{ {
ServerLogger.LogInformation("New map loaded - {clientCount} active players", ClientNum); ServerLogger.LogInformation("New map loaded - {clientCount} active players", ClientNum);
@ -685,7 +696,7 @@ namespace IW4MAdmin
} }
} }
if (E.Type == GameEvent.EventType.MapEnd) else if (E.Type == GameEvent.EventType.MapEnd)
{ {
ServerLogger.LogInformation("Game ending..."); ServerLogger.LogInformation("Game ending...");
@ -695,18 +706,23 @@ namespace IW4MAdmin
} }
} }
if (E.Type == GameEvent.EventType.Tell) else if (E.Type == GameEvent.EventType.Tell)
{ {
await Tell(E.Message, E.Target); await Tell(E.Message, E.Target);
} }
if (E.Type == GameEvent.EventType.Broadcast) else if (E.Type == GameEvent.EventType.Broadcast)
{ {
if (!Utilities.IsDevelopment && E.Data != null) // hides broadcast when in development mode if (!Utilities.IsDevelopment && E.Data != null) // hides broadcast when in development mode
{ {
await E.Owner.ExecuteCommandAsync(E.Data); await E.Owner.ExecuteCommandAsync(E.Data);
} }
} }
else if (E.Type == GameEvent.EventType.JoinTeam)
{
E.Origin.UpdateTeam(E.Extra as string);
}
lock (ChatHistory) lock (ChatHistory)
{ {
@ -776,8 +792,16 @@ namespace IW4MAdmin
/// <returns></returns> /// <returns></returns>
async Task<List<EFClient>[]> PollPlayersAsync() async Task<List<EFClient>[]> PollPlayersAsync()
{ {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
var currentClients = GetClientsAsList(); var currentClients = GetClientsAsList();
var statusResponse = (await this.GetStatusAsync()); var statusResponse = await this.GetStatusAsync(tokenSource.Token);
if (statusResponse is null)
{
return null;
}
var polledClients = statusResponse.Clients.AsEnumerable(); var polledClients = statusResponse.Clients.AsEnumerable();
if (Manager.GetApplicationSettings().Configuration().IgnoreBots) if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
@ -889,16 +913,10 @@ namespace IW4MAdmin
await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token); await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token);
} }
foreach (var plugin in Manager.Plugins)
{
await plugin.OnUnloadAsync();
}
} }
DateTime start = DateTime.Now; private DateTime _lastMessageSent = DateTime.Now;
DateTime playerCountStart = DateTime.Now; private DateTime _lastPlayerCount = DateTime.Now;
DateTime lastCount = DateTime.Now;
public override async Task<bool> ProcessUpdatesAsync(CancellationToken cts) public override async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
{ {
@ -912,14 +930,21 @@ namespace IW4MAdmin
try try
{ {
if (Manager.GetApplicationSettings().Configuration().RConPollRate == int.MaxValue && Utilities.IsDevelopment) if (Manager.GetApplicationSettings().Configuration().RConPollRate == int.MaxValue &&
Utilities.IsDevelopment)
{ {
return true; return true;
} }
var polledClients = await PollPlayersAsync(); var polledClients = await PollPlayersAsync();
foreach (var disconnectingClient in polledClients[1].Where(_client => !_client.IsZombieClient /* ignores "fake" zombie clients */)) if (polledClients is null)
{
return true;
}
foreach (var disconnectingClient in polledClients[1]
.Where(client => !client.IsZombieClient /* ignores "fake" zombie clients */))
{ {
disconnectingClient.CurrentServer = this; disconnectingClient.CurrentServer = this;
var e = new GameEvent() var e = new GameEvent()
@ -935,23 +960,20 @@ namespace IW4MAdmin
} }
// this are our new connecting clients // this are our new connecting clients
foreach (var client in polledClients[0]) foreach (var client in polledClients[0].Where(client =>
!string.IsNullOrEmpty(client.Name) && (client.Ping != 999 || client.IsBot)))
{ {
// note: this prevents players in ZMBI state from being registered with no name
if (string.IsNullOrEmpty(client.Name) || (client.Ping == 999 && !client.IsBot))
{
continue;
}
client.CurrentServer = this; client.CurrentServer = this;
var e = new GameEvent() client.GameName = (Reference.Game?)GameName;
var e = new GameEvent
{ {
Type = GameEvent.EventType.PreConnect, Type = GameEvent.EventType.PreConnect,
Origin = client, Origin = client,
Owner = this, Owner = this,
IsBlocking = true, IsBlocking = true,
Extra = client.GetAdditionalProperty<string>("BotGuid"), Extra = client.GetAdditionalProperty<string>("BotGuid"),
Source = GameEvent.EventSource.Status Source = GameEvent.EventSource.Status,
}; };
Manager.AddEvent(e); Manager.AddEvent(e);
@ -962,19 +984,19 @@ namespace IW4MAdmin
foreach (var client in polledClients[2]) foreach (var client in polledClients[2])
{ {
client.CurrentServer = this; client.CurrentServer = this;
var e = new GameEvent() var gameEvent = new GameEvent
{ {
Type = GameEvent.EventType.Update, Type = GameEvent.EventType.Update,
Origin = client, Origin = client,
Owner = this Owner = this
}; };
Manager.AddEvent(e); Manager.AddEvent(gameEvent);
} }
if (Throttled) if (Throttled)
{ {
var _event = new GameEvent() var gameEvent = new GameEvent
{ {
Type = GameEvent.EventType.ConnectionRestored, Type = GameEvent.EventType.ConnectionRestored,
Owner = this, Owner = this,
@ -982,65 +1004,52 @@ namespace IW4MAdmin
Target = Utilities.IW4MAdminClient(this) Target = Utilities.IW4MAdminClient(this)
}; };
Manager.AddEvent(_event); Manager.AddEvent(gameEvent);
} }
LastPoll = DateTime.Now; LastPoll = DateTime.Now;
} }
catch (NetworkException e) catch (NetworkException ex)
{ {
if (!Throttled) if (Throttled)
{ {
var _event = new GameEvent() return true;
{
Type = GameEvent.EventType.ConnectionLost,
Owner = this,
Origin = Utilities.IW4MAdminClient(this),
Target = Utilities.IW4MAdminClient(this),
Extra = e,
Data = ConnectionErrors.ToString()
};
Manager.AddEvent(_event);
} }
var gameEvent = new GameEvent
{
Type = GameEvent.EventType.ConnectionLost,
Owner = this,
Origin = Utilities.IW4MAdminClient(this),
Target = Utilities.IW4MAdminClient(this),
Extra = ex,
Data = ConnectionErrors.ToString()
};
Manager.AddEvent(gameEvent);
return true;
}
finally
{
RunServerCollection();
}
if (DateTime.Now - _lastMessageSent <=
TimeSpan.FromSeconds(Manager.GetApplicationSettings().Configuration().AutoMessagePeriod) ||
BroadcastMessages.Count <= 0 || ClientNum <= 0)
{
return true; return true;
} }
LastMessage = DateTime.Now - start;
lastCount = DateTime.Now;
var appConfig = _serviceProvider.GetService<ApplicationConfiguration>();
// update the player history
if (lastCount - playerCountStart >= appConfig.ServerDataCollectionInterval)
{
var maxItems = Math.Ceiling(appConfig.MaxClientHistoryTime.TotalMinutes /
appConfig.ServerDataCollectionInterval.TotalMinutes);
while ( ClientHistory.Count > maxItems)
{
ClientHistory.Dequeue();
}
ClientHistory.Enqueue(new PlayerHistory(ClientNum));
playerCountStart = DateTime.Now;
}
// send out broadcast messages // send out broadcast messages
if (LastMessage.TotalSeconds > Manager.GetApplicationSettings().Configuration().AutoMessagePeriod var messages =
&& BroadcastMessages.Count > 0 (await this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])).Split(
&& ClientNum > 0) Environment.NewLine);
{ await BroadcastAsync(messages, token: Manager.CancellationToken);
string[] messages = (await this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])).Split(Environment.NewLine);
foreach (string message in messages) NextMessage = NextMessage == BroadcastMessages.Count - 1 ? 0 : NextMessage + 1;
{ _lastMessageSent = DateTime.Now;
Broadcast(message);
}
NextMessage = NextMessage == (BroadcastMessages.Count - 1) ? 0 : NextMessage + 1;
start = DateTime.Now;
}
return true; return true;
} }
@ -1052,26 +1061,56 @@ namespace IW4MAdmin
} }
// this one is ok // this one is ok
catch (Exception e) when(e is ServerException || e is RConException) catch (Exception e) when (e is ServerException || e is RConException)
{ {
using(LogContext.PushProperty("Server", ToString())) using (LogContext.PushProperty("Server", ToString()))
{ {
ServerLogger.LogWarning(e, "Undesirable exception occured during processing updates"); ServerLogger.LogWarning(e, "Undesirable exception occured during processing updates");
} }
return false; return false;
} }
catch (Exception e) catch (Exception e)
{ {
using(LogContext.PushProperty("Server", ToString())) using (LogContext.PushProperty("Server", ToString()))
{ {
ServerLogger.LogError(e, "Unexpected exception occured during processing updates"); ServerLogger.LogError(e, "Unexpected exception occured during processing updates");
} }
Console.WriteLine(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]")); Console.WriteLine(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]"));
return false; return false;
} }
} }
private void RunServerCollection()
{
var appConfig = _serviceProvider.GetService<ApplicationConfiguration>();
if (DateTime.Now - _lastPlayerCount < appConfig?.ServerDataCollectionInterval)
{
return;
}
var maxItems = Math.Ceiling(appConfig.MaxClientHistoryTime.TotalMinutes /
appConfig.ServerDataCollectionInterval.TotalMinutes);
while (ClientHistory.ClientCounts.Count > maxItems)
{
ClientHistory.ClientCounts.RemoveAt(0);
}
ClientHistory.ClientCounts.Add(new ClientCountSnapshot
{
ClientCount = ClientNum,
ConnectionInterrupted = Throttled,
Time = DateTime.UtcNow,
Map = CurrentMap.Name
});
_lastPlayerCount = DateTime.Now;
}
public async Task Initialize() public async Task Initialize()
{ {
try try
@ -1103,7 +1142,7 @@ namespace IW4MAdmin
RemoteConnection = RConConnectionFactory.CreateConnection(ResolvedIpEndPoint, Password, RconParser.RConEngine); RemoteConnection = RConConnectionFactory.CreateConnection(ResolvedIpEndPoint, Password, RconParser.RConEngine);
RemoteConnection.SetConfiguration(RconParser); RemoteConnection.SetConfiguration(RconParser);
var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version"); var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version", token: Manager.CancellationToken);
Version = version.Value; Version = version.Value;
GameName = Utilities.GetGame(version.Value ?? RconParser.Version); GameName = Utilities.GetGame(version.Value ?? RconParser.Version);
@ -1120,7 +1159,7 @@ namespace IW4MAdmin
Version = RconParser.Version; Version = RconParser.Version;
} }
var svRunning = await this.GetMappedDvarValueOrDefaultAsync<string>("sv_running"); var svRunning = await this.GetMappedDvarValueOrDefaultAsync<string>("sv_running", token: Manager.CancellationToken);
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1") if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
{ {
@ -1129,27 +1168,28 @@ namespace IW4MAdmin
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null; var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
string hostname = (await this.GetMappedDvarValueOrDefaultAsync<string>("sv_hostname", "hostname", infoResponse)).Value; var hostname = (await this.GetMappedDvarValueOrDefaultAsync<string>("sv_hostname", "hostname", infoResponse, token: Manager.CancellationToken)).Value;
string mapname = (await this.GetMappedDvarValueOrDefaultAsync<string>("mapname", infoResponse: infoResponse)).Value; var mapname = (await this.GetMappedDvarValueOrDefaultAsync<string>("mapname", infoResponse: infoResponse, token: Manager.CancellationToken)).Value;
int maxplayers = (await this.GetMappedDvarValueOrDefaultAsync<int>("sv_maxclients", infoResponse: infoResponse)).Value; var maxplayers = (await this.GetMappedDvarValueOrDefaultAsync<int>("sv_maxclients", infoResponse: infoResponse, token: Manager.CancellationToken)).Value;
string gametype = (await this.GetMappedDvarValueOrDefaultAsync<string>("g_gametype", "gametype", infoResponse)).Value; var gametype = (await this.GetMappedDvarValueOrDefaultAsync<string>("g_gametype", "gametype", infoResponse, token: Manager.CancellationToken)).Value;
var basepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basepath"); var basepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basepath", token: Manager.CancellationToken);
var basegame = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basegame"); var basegame = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basegame", token: Manager.CancellationToken);
var homepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_homepath"); var homepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_homepath", token: Manager.CancellationToken);
var game = (await this.GetMappedDvarValueOrDefaultAsync<string>("fs_game", infoResponse: infoResponse)); var game = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_game", infoResponse: infoResponse, token: Manager.CancellationToken);
var logfile = await this.GetMappedDvarValueOrDefaultAsync<string>("g_log"); var logfile = await this.GetMappedDvarValueOrDefaultAsync<string>("g_log", token: Manager.CancellationToken);
var logsync = await this.GetMappedDvarValueOrDefaultAsync<int>("g_logsync"); var logsync = await this.GetMappedDvarValueOrDefaultAsync<int>("g_logsync", token: Manager.CancellationToken);
var ip = await this.GetMappedDvarValueOrDefaultAsync<string>("net_ip"); var ip = await this.GetMappedDvarValueOrDefaultAsync<string>("net_ip", token: Manager.CancellationToken);
var gamePassword = await this.GetMappedDvarValueOrDefaultAsync("g_password", overrideDefault: ""); var gamePassword = await this.GetMappedDvarValueOrDefaultAsync("g_password", overrideDefault: "", token: Manager.CancellationToken);
if (Manager.GetApplicationSettings().Configuration().EnableCustomSayName) if (Manager.GetApplicationSettings().Configuration().EnableCustomSayName)
{ {
await this.SetDvarAsync("sv_sayname", Manager.GetApplicationSettings().Configuration().CustomSayName); await this.SetDvarAsync("sv_sayname", Manager.GetApplicationSettings().Configuration().CustomSayName,
Manager.CancellationToken);
} }
try try
{ {
var website = await this.GetMappedDvarValueOrDefaultAsync<string>("_website"); var website = await this.GetMappedDvarValueOrDefaultAsync<string>("_website", token: Manager.CancellationToken);
// this occurs for games that don't give us anything back when // this occurs for games that don't give us anything back when
// the dvar is not set // the dvar is not set
@ -1195,14 +1235,14 @@ namespace IW4MAdmin
if (logsync.Value == 0) if (logsync.Value == 0)
{ {
await this.SetDvarAsync("g_logsync", 2); // set to 2 for continous in other games, clamps to 1 for IW4 await this.SetDvarAsync("g_logsync", 2, Manager.CancellationToken); // set to 2 for continous in other games, clamps to 1 for IW4
needsRestart = true; needsRestart = true;
} }
if (string.IsNullOrWhiteSpace(logfile.Value)) if (string.IsNullOrWhiteSpace(logfile.Value))
{ {
logfile.Value = "games_mp.log"; logfile.Value = "games_mp.log";
await this.SetDvarAsync("g_log", logfile.Value); await this.SetDvarAsync("g_log", logfile.Value, Manager.CancellationToken);
needsRestart = true; needsRestart = true;
} }
@ -1214,7 +1254,7 @@ namespace IW4MAdmin
} }
// this DVAR isn't set until the a map is loaded // this DVAR isn't set until the a map is loaded
await this.SetDvarAsync("logfile", 2); await this.SetDvarAsync("logfile", 2, Manager.CancellationToken);
} }
CustomCallback = await ScriptLoaded(); CustomCallback = await ScriptLoaded();
@ -1439,7 +1479,6 @@ namespace IW4MAdmin
Offender = targetClient, Offender = targetClient,
Offense = reason, Offense = reason,
Punisher = originClient, Punisher = originClient,
Link = targetClient.AliasLink,
IsEvadedOffense = isEvade IsEvadedOffense = isEvade
}; };
@ -1474,7 +1513,8 @@ namespace IW4MAdmin
ServerLogger.LogDebug("Creating unban penalty for {targetClient}", targetClient.ToString()); ServerLogger.LogDebug("Creating unban penalty for {targetClient}", targetClient.ToString());
targetClient.SetLevel(Permission.User, originClient); targetClient.SetLevel(Permission.User, originClient);
await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId); await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId,
targetClient.NetworkId, targetClient.CurrentAlias?.IPAddress);
await Manager.GetPenaltyService().Create(unbanPenalty); await Manager.GetPenaltyService().Create(unbanPenalty);
} }

View File

@ -18,6 +18,7 @@ using SharedLibraryCore.Repositories;
using SharedLibraryCore.Services; using SharedLibraryCore.Services;
using Stats.Dtos; using Stats.Dtos;
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
@ -76,8 +77,12 @@ namespace IW4MAdmin.Application
/// <param name="e"></param> /// <param name="e"></param>
private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e) private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e)
{ {
_serverManager?.Stop(); if (_serverManager is not null)
if (_applicationTask != null) {
await _serverManager.Stop();
}
if (_applicationTask is not null)
{ {
await _applicationTask; await _applicationTask;
} }
@ -112,10 +117,11 @@ namespace IW4MAdmin.Application
var tasks = new[] var tasks = new[]
{ {
versionChecker.CheckVersion(), versionChecker.CheckVersion(),
_serverManager.Init(),
_applicationTask _applicationTask
}; };
await _serverManager.Init();
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
} }
@ -138,11 +144,8 @@ namespace IW4MAdmin.Application
if (e is ConfigurationException configException) if (e is ConfigurationException configException)
{ {
if (translationLookup != null) Console.WriteLine("{{fileName}} contains an error."
{ .FormatExt(Path.GetFileName(configException.ConfigurationFileName)));
Console.WriteLine(translationLookup[configException.Message]
.FormatExt(configException.ConfigurationFileName));
}
foreach (var error in configException.Errors) foreach (var error in configException.Errors)
{ {
@ -155,6 +158,11 @@ namespace IW4MAdmin.Application
Console.WriteLine(e.Message); Console.WriteLine(e.Message);
} }
if (_serverManager is not null)
{
await _serverManager?.Stop();
}
Console.WriteLine(exitMessage); Console.WriteLine(exitMessage);
await Console.In.ReadAsync(new char[1], 0, 1); await Console.In.ReadAsync(new char[1], 0, 1);
return; return;
@ -277,7 +285,7 @@ namespace IW4MAdmin.Application
// register the native commands // register the native commands
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes() foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Concat(typeof(Program).Assembly.GetTypes().Where(type => type.Namespace == "IW4MAdmin.Application.Commands")) .Concat(typeof(Program).Assembly.GetTypes().Where(type => type.Namespace?.StartsWith("IW4MAdmin.Application.Commands") ?? false))
.Where(command => command.BaseType == typeof(Command))) .Where(command => command.BaseType == typeof(Command)))
{ {
defaultLogger.LogDebug("Registered native command type {Name}", commandType.Name); defaultLogger.LogDebug("Registered native command type {Name}", commandType.Name);
@ -311,9 +319,9 @@ namespace IW4MAdmin.Application
} }
// register any script plugins // register any script plugins
foreach (var scriptPlugin in pluginImporter.DiscoverScriptPlugins()) foreach (var plugin in pluginImporter.DiscoverScriptPlugins())
{ {
serviceCollection.AddSingleton(scriptPlugin); serviceCollection.AddSingleton(plugin);
} }
// register any eventable types // register any eventable types
@ -347,7 +355,7 @@ namespace IW4MAdmin.Application
await defaultConfigHandler.BuildAsync(); await defaultConfigHandler.BuildAsync();
var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration"); var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration");
await commandConfigHandler.BuildAsync(); await commandConfigHandler.BuildAsync();
var statsCommandHandler = new BaseConfigurationHandler<StatsConfiguration>(); var statsCommandHandler = new BaseConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
await statsCommandHandler.BuildAsync(); await statsCommandHandler.BuildAsync();
var defaultConfig = defaultConfigHandler.Configuration(); var defaultConfig = defaultConfigHandler.Configuration();
var appConfig = appConfigHandler.Configuration(); var appConfig = appConfigHandler.Configuration();
@ -404,7 +412,10 @@ namespace IW4MAdmin.Application
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>() .AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>() .AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
.AddSingleton<IEntityService<EFClient>, ClientService>() .AddSingleton<IEntityService<EFClient>, ClientService>()
#pragma warning disable CS0618
.AddSingleton<IMetaService, MetaService>() .AddSingleton<IMetaService, MetaService>()
#pragma warning restore CS0618
.AddSingleton<IMetaServiceV2, MetaServiceV2>()
.AddSingleton<ClientService>() .AddSingleton<ClientService>()
.AddSingleton<PenaltyService>() .AddSingleton<PenaltyService>()
.AddSingleton<ChangeHistoryService>() .AddSingleton<ChangeHistoryService>()
@ -418,6 +429,7 @@ namespace IW4MAdmin.Application
UpdatedAliasResourceQueryHelper>() UpdatedAliasResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>() .AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>, ConnectionsResourceQueryHelper>() .AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>, ConnectionsResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse>, PermissionLevelChangedResourceQueryHelper>()
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>() .AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
.AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>() .AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>()
.AddSingleton<IMasterCommunication, MasterCommunication>() .AddSingleton<IMasterCommunication, MasterCommunication>()
@ -435,6 +447,8 @@ namespace IW4MAdmin.Application
.AddSingleton<IServerDataViewer, ServerDataViewer>() .AddSingleton<IServerDataViewer, ServerDataViewer>()
.AddSingleton<IServerDataCollector, ServerDataCollector>() .AddSingleton<IServerDataCollector, ServerDataCollector>()
.AddSingleton<IEventPublisher, EventPublisher>() .AddSingleton<IEventPublisher, EventPublisher>()
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
.AddSingleton(translationLookup) .AddSingleton(translationLookup)
.AddDatabaseContextOptions(appConfig); .AddDatabaseContextOptions(appConfig);

View File

@ -5,6 +5,7 @@ using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper; using SharedLibraryCore.QueryHelper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -15,19 +16,28 @@ namespace IW4MAdmin.Application.Meta
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private ITranslationLookup _transLookup; private ITranslationLookup _transLookup;
private readonly IMetaService _metaService; private readonly IMetaServiceV2 _metaService;
private readonly IEntityService<EFClient> _clientEntityService; private readonly IEntityService<EFClient> _clientEntityService;
private readonly IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> _receivedPenaltyHelper; private readonly IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> _receivedPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> _administeredPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>
_administeredPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper; private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse> private readonly IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>
_connectionHistoryHelper; _connectionHistoryHelper;
public MetaRegistration(ILogger<MetaRegistration> logger, IMetaService metaService, ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService, private readonly IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse>
_permissionLevelHelper;
public MetaRegistration(ILogger<MetaRegistration> logger, IMetaServiceV2 metaService,
ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService,
IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper, IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper,
IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper, IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper,
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper, IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper,
IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse> connectionHistoryHelper) IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse> connectionHistoryHelper,
IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse> permissionLevelHelper)
{ {
_logger = logger; _logger = logger;
_transLookup = transLookup; _transLookup = transLookup;
@ -37,21 +47,31 @@ namespace IW4MAdmin.Application.Meta
_administeredPenaltyHelper = administeredPenaltyHelper; _administeredPenaltyHelper = administeredPenaltyHelper;
_updatedAliasHelper = updatedAliasHelper; _updatedAliasHelper = updatedAliasHelper;
_connectionHistoryHelper = connectionHistoryHelper; _connectionHistoryHelper = connectionHistoryHelper;
_permissionLevelHelper = permissionLevelHelper;
} }
public void Register() public void Register()
{ {
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, GetProfileMeta); _metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information,
_metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty, GetReceivedPenaltiesMeta); GetProfileMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized, GetAdministeredPenaltiesMeta); _metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty,
_metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate, GetUpdatedAliasMeta); GetReceivedPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ConnectionHistoryResponse>(MetaType.ConnectionHistory, GetConnectionHistoryMeta); _metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized,
GetAdministeredPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate,
GetUpdatedAliasMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ConnectionHistoryResponse>(MetaType.ConnectionHistory,
GetConnectionHistoryMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, PermissionLevelChangedResponse>(
MetaType.PermissionLevel, GetPermissionLevelMeta);
} }
private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request) private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request,
CancellationToken cancellationToken = default)
{ {
var metaList = new List<InformationResponse>(); var metaList = new List<InformationResponse>();
var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = request.ClientId }); var lastMapMeta =
await _metaService.GetPersistentMeta("LastMapPlayed", request.ClientId, cancellationToken);
if (lastMapMeta != null) if (lastMapMeta != null)
{ {
@ -63,12 +83,12 @@ namespace IW4MAdmin.Application.Meta
Value = lastMapMeta.Value, Value = lastMapMeta.Value,
ShouldDisplay = true, ShouldDisplay = true,
Type = MetaType.Information, Type = MetaType.Information,
Column = 1,
Order = 6 Order = 6
}); });
} }
var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = request.ClientId }); var lastServerMeta =
await _metaService.GetPersistentMeta("LastServerPlayed", request.ClientId, cancellationToken);
if (lastServerMeta != null) if (lastServerMeta != null)
{ {
@ -80,8 +100,7 @@ namespace IW4MAdmin.Application.Meta
Value = lastServerMeta.Value, Value = lastServerMeta.Value,
ShouldDisplay = true, ShouldDisplay = true,
Type = MetaType.Information, Type = MetaType.Information,
Column = 0, Order = 7
Order = 6
}); });
} }
@ -89,7 +108,7 @@ namespace IW4MAdmin.Application.Meta
if (client == null) if (client == null)
{ {
_logger.LogWarning("No client found with id {clientId} when generating profile meta", request.ClientId); _logger.LogWarning("No client found with id {ClientId} when generating profile meta", request.ClientId);
return metaList; return metaList;
} }
@ -99,8 +118,7 @@ namespace IW4MAdmin.Application.Meta
Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"], Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"],
Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(), Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(),
ShouldDisplay = true, ShouldDisplay = true,
Column = 1, Order = 8,
Order = 0,
Type = MetaType.Information Type = MetaType.Information
}); });
@ -110,8 +128,7 @@ namespace IW4MAdmin.Application.Meta
Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"], Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"],
Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(), Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(),
ShouldDisplay = true, ShouldDisplay = true,
Column = 1, Order = 9,
Order = 1,
Type = MetaType.Information Type = MetaType.Information
}); });
@ -121,8 +138,7 @@ namespace IW4MAdmin.Application.Meta
Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"], Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"],
Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(), Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
ShouldDisplay = true, ShouldDisplay = true,
Column = 1, Order = 10,
Order = 2,
Type = MetaType.Information Type = MetaType.Information
}); });
@ -130,10 +146,10 @@ namespace IW4MAdmin.Application.Meta
{ {
ClientId = client.ClientId, ClientId = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"], Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"],
Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), Value = client.Connections.ToString("#,##0",
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
ShouldDisplay = true, ShouldDisplay = true,
Column = 1, Order = 11,
Order = 3,
Type = MetaType.Information Type = MetaType.Information
}); });
@ -141,38 +157,50 @@ namespace IW4MAdmin.Application.Meta
{ {
ClientId = client.ClientId, ClientId = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_MASKED"], Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_MASKED"],
Value = client.Masked ? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"] : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"], Value = client.Masked
? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"]
: Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
IsSensitive = true, IsSensitive = true,
Column = 1, Order = 12,
Order = 4,
Type = MetaType.Information Type = MetaType.Information
}); });
return metaList; return metaList;
} }
private async Task<IEnumerable<ReceivedPenaltyResponse>> GetReceivedPenaltiesMeta(ClientPaginationRequest request) private async Task<IEnumerable<ReceivedPenaltyResponse>> GetReceivedPenaltiesMeta(
ClientPaginationRequest request, CancellationToken token = default)
{ {
var penalties = await _receivedPenaltyHelper.QueryResource(request); var penalties = await _receivedPenaltyHelper.QueryResource(request);
return penalties.Results; return penalties.Results;
} }
private async Task<IEnumerable<AdministeredPenaltyResponse>> GetAdministeredPenaltiesMeta(ClientPaginationRequest request) private async Task<IEnumerable<AdministeredPenaltyResponse>> GetAdministeredPenaltiesMeta(
ClientPaginationRequest request, CancellationToken token = default)
{ {
var penalties = await _administeredPenaltyHelper.QueryResource(request); var penalties = await _administeredPenaltyHelper.QueryResource(request);
return penalties.Results; return penalties.Results;
} }
private async Task<IEnumerable<UpdatedAliasResponse>> GetUpdatedAliasMeta(ClientPaginationRequest request) private async Task<IEnumerable<UpdatedAliasResponse>> GetUpdatedAliasMeta(ClientPaginationRequest request,
CancellationToken token = default)
{ {
var aliases = await _updatedAliasHelper.QueryResource(request); var aliases = await _updatedAliasHelper.QueryResource(request);
return aliases.Results; return aliases.Results;
} }
private async Task<IEnumerable<ConnectionHistoryResponse>> GetConnectionHistoryMeta(ClientPaginationRequest request) private async Task<IEnumerable<ConnectionHistoryResponse>> GetConnectionHistoryMeta(
ClientPaginationRequest request, CancellationToken token = default)
{ {
var connections = await _connectionHistoryHelper.QueryResource(request); var connections = await _connectionHistoryHelper.QueryResource(request);
return connections.Results; return connections.Results;
} }
private async Task<IEnumerable<PermissionLevelChangedResponse>> GetPermissionLevelMeta(
ClientPaginationRequest request, CancellationToken token = default)
{
var permissionChanges = await _permissionLevelHelper.QueryResource(request);
return permissionChanges.Results;
}
} }
} }

View File

@ -0,0 +1,51 @@
using System.Linq;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
namespace IW4MAdmin.Application.Meta;
public class
PermissionLevelChangedResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest,
PermissionLevelChangedResponse>
{
private readonly IDatabaseContextFactory _contextFactory;
public PermissionLevelChangedResourceQueryHelper(IDatabaseContextFactory contextFactory)
{
_contextFactory = contextFactory;
}
public async Task<ResourceQueryHelperResult<PermissionLevelChangedResponse>> QueryResource(
ClientPaginationRequest query)
{
await using var context = _contextFactory.CreateContext();
var auditEntries = context.EFChangeHistory.Where(change => change.TargetEntityId == query.ClientId)
.Where(change => change.TypeOfChange == EFChangeHistory.ChangeType.Permission);
var audits = from change in auditEntries
join client in context.Clients
on change.OriginEntityId equals client.ClientId
select new PermissionLevelChangedResponse
{
ChangedById = change.OriginEntityId,
ChangedByName = client.CurrentAlias.Name,
PreviousPermissionLevelValue = change.PreviousValue,
CurrentPermissionLevelValue = change.CurrentValue,
When = change.TimeChanged,
ClientId = change.TargetEntityId,
IsSensitive = true
};
return new ResourceQueryHelperResult<PermissionLevelChangedResponse>
{
Results = await audits.Skip(query.Offset).Take(query.Count).ToListAsync()
};
}
}

View File

@ -11,6 +11,7 @@ using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper; using SharedLibraryCore.QueryHelper;
using SharedLibraryCore.Services;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Meta namespace IW4MAdmin.Application.Meta
@ -19,13 +20,14 @@ namespace IW4MAdmin.Application.Meta
/// implementation of IResourceQueryHelper /// implementation of IResourceQueryHelper
/// used to pull in penalties applied to a given client id /// used to pull in penalties applied to a given client id
/// </summary> /// </summary>
public class ReceivedPenaltyResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> public class
ReceivedPenaltyResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IDatabaseContextFactory _contextFactory; private readonly IDatabaseContextFactory _contextFactory;
private readonly ApplicationConfiguration _appConfig; private readonly ApplicationConfiguration _appConfig;
public ReceivedPenaltyResourceQueryHelper(ILogger<ReceivedPenaltyResourceQueryHelper> logger, public ReceivedPenaltyResourceQueryHelper(ILogger<ReceivedPenaltyResourceQueryHelper> logger,
IDatabaseContextFactory contextFactory, ApplicationConfiguration appConfig) IDatabaseContextFactory contextFactory, ApplicationConfiguration appConfig)
{ {
_contextFactory = contextFactory; _contextFactory = contextFactory;
@ -33,45 +35,58 @@ namespace IW4MAdmin.Application.Meta
_appConfig = appConfig; _appConfig = appConfig;
} }
public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(ClientPaginationRequest query) public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(
ClientPaginationRequest query)
{ {
var linkedPenaltyType = Utilities.LinkedPenaltyTypes(); var linkedPenaltyType = Utilities.LinkedPenaltyTypes();
await using var ctx = _contextFactory.CreateContext(enableTracking: false); await using var ctx = _contextFactory.CreateContext(enableTracking: false);
var linkId = await ctx.Clients.AsNoTracking() var linkId = await ctx.Clients.AsNoTracking()
.Where(_client => _client.ClientId == query.ClientId) .Where(_client => _client.ClientId == query.ClientId)
.Select(_client => new {_client.AliasLinkId, _client.CurrentAliasId }) .Select(_client => new { _client.AliasLinkId, _client.CurrentAliasId })
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
var iqPenalties = ctx.Penalties.AsNoTracking() var iqPenalties = ctx.Penalties.AsNoTracking()
.Where(_penalty => _penalty.OffenderId == query.ClientId || .Where(_penalty => _penalty.OffenderId == query.ClientId ||
linkedPenaltyType.Contains(_penalty.Type) && _penalty.LinkId == linkId.AliasLinkId); linkedPenaltyType.Contains(_penalty.Type) && _penalty.LinkId == linkId.AliasLinkId);
IQueryable<EFPenalty> iqIpLinkedPenalties = null; IQueryable<EFPenalty> iqIpLinkedPenalties = null;
IQueryable<EFPenalty> identifierPenalties = null;
if (!_appConfig.EnableImplicitAccountLinking) if (!_appConfig.EnableImplicitAccountLinking)
{ {
var usedIps = await ctx.Aliases.AsNoTracking() var usedIps = await ctx.Aliases.AsNoTracking()
.Where(alias => (alias.LinkId == linkId.AliasLinkId || alias.AliasId == linkId.CurrentAliasId) && alias.IPAddress != null) .Where(alias =>
(alias.LinkId == linkId.AliasLinkId || alias.AliasId == linkId.CurrentAliasId) &&
alias.IPAddress != null)
.Select(alias => alias.IPAddress).ToListAsync(); .Select(alias => alias.IPAddress).ToListAsync();
identifierPenalties = ctx.PenaltyIdentifiers.AsNoTracking().Where(identifier =>
identifier.IPv4Address != null && usedIps.Contains(identifier.IPv4Address))
.Select(id => id.Penalty);
var aliasedIds = await ctx.Aliases.AsNoTracking().Where(alias => usedIps.Contains(alias.IPAddress)) var aliasedIds = await ctx.Aliases.AsNoTracking().Where(alias => usedIps.Contains(alias.IPAddress))
.Select(alias => alias.LinkId) .Select(alias => alias.LinkId)
.ToListAsync(); .ToListAsync();
iqIpLinkedPenalties = ctx.Penalties.AsNoTracking() iqIpLinkedPenalties = ctx.Penalties.AsNoTracking()
.Where(penalty => .Where(penalty =>
linkedPenaltyType.Contains(penalty.Type) && aliasedIds.Contains(penalty.LinkId)); linkedPenaltyType.Contains(penalty.Type) && aliasedIds.Contains(penalty.LinkId ?? -1));
} }
var iqAllPenalties = iqPenalties; var iqAllPenalties = iqPenalties;
if (iqIpLinkedPenalties != null) if (iqIpLinkedPenalties != null)
{ {
iqAllPenalties = iqPenalties.Union(iqIpLinkedPenalties); iqAllPenalties = iqPenalties.Union(iqIpLinkedPenalties);
} }
var penalties = await iqAllPenalties if (identifierPenalties != null)
{
iqAllPenalties = iqPenalties.Union(identifierPenalties);
}
var penalties = await iqAllPenalties
.Where(_penalty => _penalty.When < query.Before) .Where(_penalty => _penalty.When < query.Before)
.OrderByDescending(_penalty => _penalty.When) .OrderByDescending(_penalty => _penalty.When)
.Take(query.Count) .Take(query.Count)
@ -97,7 +112,7 @@ namespace IW4MAdmin.Application.Meta
{ {
// todo: maybe actually count // todo: maybe actually count
RetrievedResultCount = penalties.Count, RetrievedResultCount = penalties.Count,
Results = penalties Results = penalties.Distinct()
}; };
} }
} }

View File

@ -88,7 +88,7 @@ namespace IW4MAdmin.Application.Migration
public static void RemoveObsoletePlugins20210322() public static void RemoveObsoletePlugins20210322()
{ {
var files = new[] {"StatsWeb.dll", "StatsWeb.Views.dll"}; var files = new[] {"StatsWeb.dll", "StatsWeb.Views.dll", "IW4ScriptCommands.dll"};
foreach (var file in files) foreach (var file in files)
{ {

View File

@ -0,0 +1,12 @@
using System;
using System.Threading;
namespace IW4MAdmin.Application.Misc;
public class AsyncResult : IAsyncResult
{
public object AsyncState { get; set; }
public WaitHandle AsyncWaitHandle { get; set; }
public bool CompletedSynchronously { get; set; }
public bool IsCompleted { get; set; }
}

View File

@ -27,6 +27,7 @@ namespace IW4MAdmin.Application.Misc
_serializerOptions = new JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{ {
WriteIndented = true, WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}; };
_serializerOptions.Converters.Add(new JsonStringEnumConverter()); _serializerOptions.Converters.Add(new JsonStringEnumConverter());
_onSaving = new SemaphoreSlim(1, 1); _onSaving = new SemaphoreSlim(1, 1);
@ -59,7 +60,7 @@ namespace IW4MAdmin.Application.Misc
catch (Exception e) catch (Exception e)
{ {
throw new ConfigurationException("MANAGER_CONFIGURATION_ERROR") throw new ConfigurationException("Could not load configuration")
{ {
Errors = new[] { e.Message }, Errors = new[] { e.Message },
ConfigurationFileName = FileName ConfigurationFileName = FileName

View File

@ -0,0 +1,13 @@
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Misc;
public class GeoLocationResult : IGeoLocationResult
{
public string Country { get; set; }
public string CountryCode { get; set; }
public string Region { get; set; }
public string ASN { get; set; }
public string Timezone { get; set; }
public string Organization { get; set; }
}

View File

@ -0,0 +1,40 @@
using System;
using System.Threading.Tasks;
using MaxMind.GeoIP2;
using MaxMind.GeoIP2.Responses;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Misc;
public class GeoLocationService : IGeoLocationService
{
private readonly string _sourceAddress;
public GeoLocationService(string sourceAddress)
{
_sourceAddress = sourceAddress;
}
public Task<IGeoLocationResult> Locate(string address)
{
CountryResponse country = null;
try
{
using var reader = new DatabaseReader(_sourceAddress);
reader.TryCountry(address, out country);
}
catch
{
// ignored
}
var response = new GeoLocationResult
{
Country = country?.Country.Name ?? "Unknown",
CountryCode = country?.Country.IsoCode ?? ""
};
return Task.FromResult((IGeoLocationResult)response);
}
}

View File

@ -18,6 +18,7 @@ namespace IW4MAdmin.Application.Misc
/// implementation of IMetaService /// implementation of IMetaService
/// used to add and retrieve runtime and persistent meta /// used to add and retrieve runtime and persistent meta
/// </summary> /// </summary>
[Obsolete("Use MetaServiceV2")]
public class MetaService : IMetaService public class MetaService : IMetaService
{ {
private readonly IDictionary<MetaType, List<dynamic>> _metaActions; private readonly IDictionary<MetaType, List<dynamic>> _metaActions;
@ -68,6 +69,29 @@ namespace IW4MAdmin.Application.Misc
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
public async Task SetPersistentMeta(string metaKey, string metaValue, int clientId)
{
await AddPersistentMeta(metaKey, metaValue, new EFClient { ClientId = clientId });
}
public async Task IncrementPersistentMeta(string metaKey, int incrementAmount, int clientId)
{
var existingMeta = await GetPersistentMeta(metaKey, new EFClient { ClientId = clientId });
if (!long.TryParse(existingMeta?.Value ?? "0", out var existingValue))
{
existingValue = 0;
}
var newValue = existingValue + incrementAmount;
await SetPersistentMeta(metaKey, newValue.ToString(), clientId);
}
public async Task DecrementPersistentMeta(string metaKey, int decrementAmount, int clientId)
{
await IncrementPersistentMeta(metaKey, -decrementAmount, clientId);
}
public async Task AddPersistentMeta(string metaKey, string metaValue) public async Task AddPersistentMeta(string metaKey, string metaValue)
{ {
await using var ctx = _contextFactory.CreateContext(); await using var ctx = _contextFactory.CreateContext();

View File

@ -0,0 +1,453 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Misc;
public class MetaServiceV2 : IMetaServiceV2
{
private readonly IDictionary<MetaType, List<dynamic>> _metaActions;
private readonly IDatabaseContextFactory _contextFactory;
private readonly ILogger _logger;
public MetaServiceV2(ILogger<MetaServiceV2> logger, IDatabaseContextFactory contextFactory)
{
_logger = logger;
_metaActions = new Dictionary<MetaType, List<dynamic>>();
_contextFactory = contextFactory;
}
public async Task SetPersistentMeta(string metaKey, string metaValue, int clientId,
CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
await using var context = _contextFactory.CreateContext();
var existingMeta = await context.EFMeta
.Where(meta => meta.Key == metaKey)
.Where(meta => meta.ClientId == clientId)
.FirstOrDefaultAsync(token);
if (existingMeta != null)
{
_logger.LogDebug("Updating existing meta with key {Key} and id {Id}", existingMeta.Key,
existingMeta.MetaId);
existingMeta.Value = metaValue;
existingMeta.Updated = DateTime.UtcNow;
}
else
{
_logger.LogDebug("Adding new meta with key {Key}", metaKey);
context.EFMeta.Add(new EFMeta
{
ClientId = clientId,
Created = DateTime.UtcNow,
Key = metaKey,
Value = metaValue,
});
}
await context.SaveChangesAsync(token);
}
public async Task SetPersistentMetaValue<T>(string metaKey, T metaValue, int clientId,
CancellationToken token = default) where T : class
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
string serializedValue;
try
{
serializedValue = JsonSerializer.Serialize(metaValue);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not serialize meta with key {Key}", metaKey);
return;
}
await SetPersistentMeta(metaKey, serializedValue, clientId, token);
}
public async Task SetPersistentMetaForLookupKey(string metaKey, string lookupKey, int lookupId, int clientId,
CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
await using var context = _contextFactory.CreateContext();
var lookupMeta = await context.EFMeta.FirstOrDefaultAsync(meta => meta.Key == lookupKey, token);
if (lookupMeta is null)
{
_logger.LogWarning("No lookup meta exists for metaKey {MetaKey} and lookupKey {LookupKey}", metaKey,
lookupKey);
return;
}
var lookupValues = JsonSerializer.Deserialize<List<LookupValue<string>>>(lookupMeta.Value);
if (lookupValues is null)
{
return;
}
var foundLookup = lookupValues.FirstOrDefault(value => value.Id == lookupId);
if (foundLookup is null)
{
_logger.LogWarning("No lookup meta found for provided lookup id {MetaKey}, {LookupKey}, {LookupId}",
metaKey, lookupKey, lookupId);
return;
}
_logger.LogDebug("Setting meta for lookup {MetaKey}, {LookupKey}, {LookupId}",
metaKey, lookupKey, lookupId);
await SetPersistentMeta(metaKey, lookupId.ToString(), clientId, token);
}
public async Task IncrementPersistentMeta(string metaKey, int incrementAmount, int clientId,
CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
var existingMeta = await GetPersistentMeta(metaKey, clientId, token);
if (!long.TryParse(existingMeta?.Value ?? "0", out var existingValue))
{
existingValue = 0;
}
var newValue = existingValue + incrementAmount;
await SetPersistentMeta(metaKey, newValue.ToString(), clientId, token);
}
public async Task DecrementPersistentMeta(string metaKey, int decrementAmount, int clientId,
CancellationToken token = default)
{
await IncrementPersistentMeta(metaKey, -decrementAmount, clientId, token);
}
public async Task<EFMeta> GetPersistentMeta(string metaKey, int clientId, CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return null;
}
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
return await ctx.EFMeta
.Where(meta => meta.Key == metaKey)
.Where(meta => meta.ClientId == clientId)
.Select(meta => new EFMeta
{
MetaId = meta.MetaId,
Key = meta.Key,
ClientId = meta.ClientId,
Value = meta.Value,
})
.FirstOrDefaultAsync(token);
}
public async Task<T> GetPersistentMetaValue<T>(string metaKey, int clientId, CancellationToken token = default)
where T : class
{
var meta = await GetPersistentMeta(metaKey, clientId, token);
if (meta is null)
{
return default;
}
try
{
return JsonSerializer.Deserialize<T>(meta.Value);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not deserialize meta with key {Key} and value {Value}", metaKey, meta.Value);
return default;
}
}
public async Task<EFMeta> GetPersistentMetaByLookup(string metaKey, string lookupKey, int clientId,
CancellationToken token = default)
{
await using var context = _contextFactory.CreateContext();
var metaValue = await GetPersistentMeta(metaKey, clientId, token);
if (metaValue is null)
{
_logger.LogDebug("No meta exists for key {Key}, clientId {ClientId}", metaKey, clientId);
return default;
}
var lookupMeta = await context.EFMeta.FirstOrDefaultAsync(meta => meta.Key == lookupKey, token);
if (lookupMeta is null)
{
_logger.LogWarning("No lookup meta exists for metaKey {MetaKey} and lookupKey {LookupKey}", metaKey,
lookupKey);
return default;
}
var lookupId = int.Parse(metaValue.Value);
var lookupValues = JsonSerializer.Deserialize<List<LookupValue<string>>>(lookupMeta.Value);
if (lookupValues is null)
{
return default;
}
var foundLookup = lookupValues.FirstOrDefault(value => value.Id == lookupId);
if (foundLookup is not null)
{
return new EFMeta
{
Created = metaValue.Created,
Updated = metaValue.Updated,
Extra = metaValue.Extra,
MetaId = metaValue.MetaId,
Value = foundLookup.Value
};
}
_logger.LogWarning("No lookup meta found for provided lookup id {MetaKey}, {LookupKey}, {LookupId}",
metaKey, lookupKey, lookupId);
return default;
}
public async Task RemovePersistentMeta(string metaKey, int clientId, CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
await using var context = _contextFactory.CreateContext();
var existingMeta = await context.EFMeta
.FirstOrDefaultAsync(meta => meta.Key == metaKey && meta.ClientId == clientId, token);
if (existingMeta == null)
{
_logger.LogDebug("No meta with key {Key} found for client id {Id}", metaKey, clientId);
return;
}
_logger.LogDebug("Removing meta for key {Key} with id {Id}", metaKey, existingMeta.MetaId);
context.EFMeta.Remove(existingMeta);
await context.SaveChangesAsync(token);
}
public async Task SetPersistentMeta(string metaKey, string metaValue, CancellationToken token = default)
{
if (string.IsNullOrWhiteSpace(metaKey))
{
_logger.LogWarning("Cannot save meta with no key");
return;
}
await using var ctx = _contextFactory.CreateContext();
var existingMeta = await ctx.EFMeta
.Where(meta => meta.Key == metaKey)
.Where(meta => meta.ClientId == null)
.FirstOrDefaultAsync(token);
if (existingMeta is not null)
{
_logger.LogDebug("Updating existing meta with key {Key} and id {Id}", existingMeta.Key,
existingMeta.MetaId);
existingMeta.Value = metaValue;
existingMeta.Updated = DateTime.UtcNow;
await ctx.SaveChangesAsync(token);
}
else
{
_logger.LogDebug("Adding new meta with key {Key}", metaKey);
ctx.EFMeta.Add(new EFMeta
{
Created = DateTime.UtcNow,
Key = metaKey,
Value = metaValue
});
await ctx.SaveChangesAsync(token);
}
}
public async Task SetPersistentMetaValue<T>(string metaKey, T metaValue, CancellationToken token = default)
where T : class
{
if (string.IsNullOrWhiteSpace(metaKey))
{
_logger.LogWarning("Meta key is null, not setting");
return;
}
if (metaValue is null)
{
_logger.LogWarning("Meta value is null, not setting");
return;
}
string serializedMeta;
try
{
serializedMeta = JsonSerializer.Serialize(metaValue);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not serialize meta with {Key} and value {Value}", metaKey, metaValue);
return;
}
await SetPersistentMeta(metaKey, serializedMeta, token);
}
public async Task<EFMeta> GetPersistentMeta(string metaKey, CancellationToken token = default)
{
if (string.IsNullOrWhiteSpace(metaKey))
{
return null;
}
await using var context = _contextFactory.CreateContext(false);
return await context.EFMeta.FirstOrDefaultAsync(meta => meta.Key == metaKey, token);
}
public async Task<T> GetPersistentMetaValue<T>(string metaKey, CancellationToken token = default) where T : class
{
if (string.IsNullOrWhiteSpace(metaKey))
{
return default;
}
var meta = await GetPersistentMeta(metaKey, token);
if (meta is null)
{
return default;
}
try
{
return JsonSerializer.Deserialize<T>(meta.Value);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not serialize meta with key {Key} and value {Value}", metaKey, meta.Value);
return default;
}
}
public async Task RemovePersistentMeta(string metaKey, CancellationToken token = default)
{
if (string.IsNullOrWhiteSpace(metaKey))
{
return;
}
await using var context = _contextFactory.CreateContext(enableTracking: false);
var existingMeta = await context.EFMeta
.Where(meta => meta.Key == metaKey)
.Where(meta => meta.ClientId == null)
.FirstOrDefaultAsync(token);
if (existingMeta != null)
{
_logger.LogDebug("Removing meta for key {Key} with id {Id}", metaKey, existingMeta.MetaId);
context.Remove(existingMeta);
await context.SaveChangesAsync(token);
}
}
public void AddRuntimeMeta<T, TReturnType>(MetaType metaKey,
Func<T, CancellationToken, Task<IEnumerable<TReturnType>>> metaAction)
where T : PaginationRequest where TReturnType : IClientMeta
{
if (!_metaActions.ContainsKey(metaKey))
{
_metaActions.Add(metaKey, new List<dynamic> { metaAction });
}
else
{
_metaActions[metaKey].Add(metaAction);
}
}
public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request, CancellationToken token = default)
{
var metas = await Task.WhenAll(_metaActions.Where(kvp => kvp.Key != MetaType.Information)
.Select(async kvp => await kvp.Value[0](request, token)));
return metas.SelectMany(m => (IEnumerable<IClientMeta>)m)
.OrderByDescending(m => m.When)
.Take(request.Count)
.ToList();
}
public async Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType, CancellationToken token = default)
where T : IClientMeta
{
if (metaType == MetaType.Information)
{
var allMeta = new List<T>();
var completedMeta = await Task.WhenAll(_metaActions[metaType].Select(async individualMetaRegistration =>
(IEnumerable<T>)await individualMetaRegistration(request, token)));
allMeta.AddRange(completedMeta.SelectMany(meta => meta));
return ProcessInformationMeta(allMeta);
}
var meta = await _metaActions[metaType][0](request, token) as IEnumerable<T>;
return meta;
}
private static IEnumerable<T> ProcessInformationMeta<T>(IEnumerable<T> meta) where T : IClientMeta
{
return meta;
}
private static bool ValidArgs(string key, int clientId) => !string.IsNullOrWhiteSpace(key) && clientId > 0;
}

View File

@ -6,6 +6,7 @@ using SharedLibraryCore.Interfaces;
using System.Linq; using System.Linq;
using SharedLibraryCore; using SharedLibraryCore;
using IW4MAdmin.Application.API.Master; using IW4MAdmin.Application.API.Master;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -39,24 +40,23 @@ namespace IW4MAdmin.Application.Misc
/// <returns></returns> /// <returns></returns>
public IEnumerable<IPlugin> DiscoverScriptPlugins() public IEnumerable<IPlugin> DiscoverScriptPlugins()
{ {
string pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}"; var pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}";
if (Directory.Exists(pluginDir)) if (!Directory.Exists(pluginDir))
{ {
var scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts()); return Enumerable.Empty<IPlugin>();
_logger.LogDebug("Discovered {count} potential script plugins", scriptPluginFiles.Count());
if (scriptPluginFiles.Count() > 0)
{
foreach (string fileName in scriptPluginFiles)
{
_logger.LogDebug("Discovered script plugin {fileName}", fileName);
var plugin = new ScriptPlugin(_logger, fileName);
yield return plugin;
}
}
} }
var scriptPluginFiles =
Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts()).ToList();
_logger.LogDebug("Discovered {count} potential script plugins", scriptPluginFiles.Count);
return scriptPluginFiles.Select(fileName =>
{
_logger.LogDebug("Discovered script plugin {fileName}", fileName);
return new ScriptPlugin(_logger, fileName);
}).ToList();
} }
/// <summary> /// <summary>

View File

@ -6,7 +6,6 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Models.Client; using Data.Models.Client;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static SharedLibraryCore.Database.Models.EFClient;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Misc namespace IW4MAdmin.Application.Misc
@ -16,14 +15,15 @@ namespace IW4MAdmin.Application.Misc
/// </summary> /// </summary>
public class ScriptCommand : Command public class ScriptCommand : Command
{ {
private readonly Action<GameEvent> _executeAction; private readonly Func<GameEvent, Task> _executeAction;
private readonly ILogger _logger; private readonly ILogger _logger;
public ScriptCommand(string name, string alias, string description, bool isTargetRequired, EFClient.Permission permission, public ScriptCommand(string name, string alias, string description, bool isTargetRequired,
CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout, ILogger<ScriptCommand> logger) EFClient.Permission permission,
CommandArgument[] args, Func<GameEvent, Task> executeAction, CommandConfiguration config,
ITranslationLookup layout, ILogger<ScriptCommand> logger, Server.Game[] supportedGames)
: base(config, layout) : base(config, layout)
{ {
_executeAction = executeAction; _executeAction = executeAction;
_logger = logger; _logger = logger;
Name = name; Name = name;
@ -32,6 +32,7 @@ namespace IW4MAdmin.Application.Misc
RequiresTarget = isTargetRequired; RequiresTarget = isTargetRequired;
Permission = permission; Permission = permission;
Arguments = args; Arguments = args;
SupportedGames = supportedGames;
} }
public override async Task ExecuteAsync(GameEvent e) public override async Task ExecuteAsync(GameEvent e)
@ -43,7 +44,7 @@ namespace IW4MAdmin.Application.Misc
try try
{ {
await Task.Run(() => _executeAction(e)); await _executeAction(e);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -13,6 +13,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jint.Runtime.Interop;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Serilog.Context; using Serilog.Context;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -36,12 +37,12 @@ namespace IW4MAdmin.Application.Misc
/// </summary> /// </summary>
public bool IsParser { get; private set; } public bool IsParser { get; private set; }
public FileSystemWatcher Watcher { get; private set; } public FileSystemWatcher Watcher { get; }
private Engine _scriptEngine; private Engine _scriptEngine;
private readonly string _fileName; private readonly string _fileName;
private readonly SemaphoreSlim _onProcessing; private readonly SemaphoreSlim _onProcessing = new(1, 1);
private bool successfullyLoaded; private bool _successfullyLoaded;
private readonly List<string> _registeredCommandNames; private readonly List<string> _registeredCommandNames;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -49,15 +50,14 @@ namespace IW4MAdmin.Application.Misc
{ {
_logger = logger; _logger = logger;
_fileName = filename; _fileName = filename;
Watcher = new FileSystemWatcher() Watcher = new FileSystemWatcher
{ {
Path = workingDirectory == null ? $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}" : workingDirectory, Path = workingDirectory ?? $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}",
NotifyFilter = NotifyFilters.Size, NotifyFilter = NotifyFilters.Size,
Filter = _fileName.Split(Path.DirectorySeparatorChar).Last() Filter = _fileName.Split(Path.DirectorySeparatorChar).Last()
}; };
Watcher.EnableRaisingEvents = true; Watcher.EnableRaisingEvents = true;
_onProcessing = new SemaphoreSlim(1, 1);
_registeredCommandNames = new List<string>(); _registeredCommandNames = new List<string>();
} }
@ -67,12 +67,13 @@ namespace IW4MAdmin.Application.Misc
_onProcessing.Dispose(); _onProcessing.Dispose();
} }
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory, IScriptPluginServiceResolver serviceResolver) public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory,
IScriptPluginServiceResolver serviceResolver)
{ {
await _onProcessing.WaitAsync();
try try
{ {
await _onProcessing.WaitAsync();
// for some reason we get an event trigger when the file is not finished being modified. // for some reason we get an event trigger when the file is not finished being modified.
// this must have been a change in .NET CORE 3.x // this must have been a change in .NET CORE 3.x
// so if the new file is empty we can't process it yet // so if the new file is empty we can't process it yet
@ -81,26 +82,27 @@ namespace IW4MAdmin.Application.Misc
return; return;
} }
bool firstRun = _scriptEngine == null; var firstRun = _scriptEngine == null;
// it's been loaded before so we need to call the unload event // it's been loaded before so we need to call the unload event
if (!firstRun) if (!firstRun)
{ {
await OnUnloadAsync(); await OnUnloadAsync();
foreach (string commandName in _registeredCommandNames) foreach (var commandName in _registeredCommandNames)
{ {
_logger.LogDebug("Removing plugin registered command {command}", commandName); _logger.LogDebug("Removing plugin registered command {Command}", commandName);
manager.RemoveCommandByName(commandName); manager.RemoveCommandByName(commandName);
} }
_registeredCommandNames.Clear(); _registeredCommandNames.Clear();
} }
successfullyLoaded = false; _successfullyLoaded = false;
string script; string script;
using (var stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) await using (var stream =
new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{ {
using (var reader = new StreamReader(stream, Encoding.Default)) using (var reader = new StreamReader(stream, Encoding.Default))
{ {
@ -110,45 +112,34 @@ namespace IW4MAdmin.Application.Misc
_scriptEngine = new Engine(cfg => _scriptEngine = new Engine(cfg =>
cfg.AllowClr(new[] cfg.AllowClr(new[]
{ {
typeof(System.Net.Http.HttpClient).Assembly, typeof(System.Net.Http.HttpClient).Assembly,
typeof(EFClient).Assembly, typeof(EFClient).Assembly,
typeof(Utilities).Assembly, typeof(Utilities).Assembly,
typeof(Encoding).Assembly typeof(Encoding).Assembly
}) })
.CatchClrExceptions()); .CatchClrExceptions()
.AddObjectConverter(new PermissionLevelToStringConverter()));
try
{
_scriptEngine.Execute(script);
}
catch (JavaScriptException ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {methodName} for script plugin {plugin} at {@locationInfo}",
nameof(Initialize), _fileName, ex.Location);
throw new PluginException($"A JavaScript parsing error occured while initializing script plugin");
}
catch (Exception e)
{
_logger.LogError(e,
"Encountered unexpected error while running {methodName} for script plugin {plugin}",
nameof(Initialize), _fileName);
throw new PluginException($"An unexpected error occured while initialization script plugin");
}
_scriptEngine.Execute(script);
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization); _scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
_scriptEngine.SetValue("_serviceResolver", serviceResolver); _scriptEngine.SetValue("_serviceResolver", serviceResolver);
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject(); _scriptEngine.SetValue("_lock", _onProcessing);
dynamic pluginObject = _scriptEngine.Evaluate("plugin").ToObject();
Author = pluginObject.author; Author = pluginObject.author;
Name = pluginObject.name; Name = pluginObject.name;
Version = (float)pluginObject.version; Version = (float)pluginObject.version;
var commands = _scriptEngine.GetValue("commands"); var commands = JsValue.Undefined;
try
{
commands = _scriptEngine.Evaluate("commands");
}
catch (JavaScriptException)
{
// ignore because commands aren't defined;
}
if (commands != JsValue.Undefined) if (commands != JsValue.Undefined)
{ {
@ -156,7 +147,7 @@ namespace IW4MAdmin.Application.Misc
{ {
foreach (var command in GenerateScriptCommands(commands, scriptCommandFactory)) foreach (var command in GenerateScriptCommands(commands, scriptCommandFactory))
{ {
_logger.LogDebug("Adding plugin registered command {commandName}", command.Name); _logger.LogDebug("Adding plugin registered command {CommandName}", command.Name);
manager.AddAdditionalCommand(command); manager.AddAdditionalCommand(command);
_registeredCommandNames.Add(command.Name); _registeredCommandNames.Add(command.Name);
} }
@ -164,7 +155,8 @@ namespace IW4MAdmin.Application.Misc
catch (RuntimeBinderException e) catch (RuntimeBinderException e)
{ {
throw new PluginException($"Not all required fields were found: {e.Message}") { PluginFile = _fileName }; throw new PluginException($"Not all required fields were found: {e.Message}")
{ PluginFile = _fileName };
} }
} }
@ -174,8 +166,8 @@ namespace IW4MAdmin.Application.Misc
{ {
await OnLoadAsync(manager); await OnLoadAsync(manager);
IsParser = true; IsParser = true;
var eventParser = (IEventParser)_scriptEngine.GetValue("eventParser").ToObject(); var eventParser = (IEventParser)_scriptEngine.Evaluate("eventParser").ToObject();
var rconParser = (IRConParser)_scriptEngine.GetValue("rconParser").ToObject(); var rconParser = (IRConParser)_scriptEngine.Evaluate("rconParser").ToObject();
manager.AdditionalEventParsers.Add(eventParser); manager.AdditionalEventParsers.Add(eventParser);
manager.AdditionalRConParsers.Add(rconParser); manager.AdditionalRConParsers.Add(rconParser);
} }
@ -194,25 +186,79 @@ namespace IW4MAdmin.Application.Misc
await OnLoadAsync(manager); await OnLoadAsync(manager);
} }
successfullyLoaded = true; _successfullyLoaded = true;
}
catch (JavaScriptException ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} at {@LocationInfo}",
nameof(Initialize), Path.GetFileName(_fileName), ex.Location);
throw new PluginException("An error occured while initializing script plugin");
}
catch (Exception ex) when (ex.InnerException is JavaScriptException jsEx)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} initialization {@LocationInfo}",
nameof(Initialize), _fileName, jsEx.Location);
throw new PluginException("An error occured while initializing script plugin");
}
catch (Exception ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin}",
nameof(OnLoadAsync), Path.GetFileName(_fileName));
throw new PluginException("An error occured while executing action for script plugin");
}
finally
{
if (_onProcessing.CurrentCount == 0)
{
_onProcessing.Release(1);
}
}
}
public async Task OnEventAsync(GameEvent gameEvent, Server server)
{
if (!_successfullyLoaded)
{
return;
}
try
{
await _onProcessing.WaitAsync();
_scriptEngine.SetValue("_gameEvent", gameEvent);
_scriptEngine.SetValue("_server", server);
_scriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(server));
_scriptEngine.Evaluate("plugin.onEventAsync(_gameEvent, _server)");
} }
catch (JavaScriptException ex) catch (JavaScriptException ex)
{ {
_logger.LogError(ex, using (LogContext.PushProperty("Server", server.ToString()))
"Encountered JavaScript runtime error while executing {methodName} for script plugin {plugin} initialization {@locationInfo}", {
nameof(OnLoadAsync), _fileName, ex.Location); _logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} with event type {EventType} {@LocationInfo}",
throw new PluginException("An error occured while initializing script plugin"); nameof(OnEventAsync), Path.GetFileName(_fileName), gameEvent.Type, ex.Location);
}
throw new PluginException("An error occured while executing action for script plugin");
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, using (LogContext.PushProperty("Server", server.ToString()))
"Encountered unexpected error while running {methodName} for script plugin {plugin}", {
nameof(OnLoadAsync), _fileName); _logger.LogError(ex,
"Encountered error while running {MethodName} for script plugin {Plugin} with event type {EventType}",
throw new PluginException("An unexpected error occured while initializing script plugin"); nameof(OnEventAsync), _fileName, gameEvent.Type);
}
throw new PluginException("An error occured while executing action for script plugin");
} }
finally finally
@ -224,73 +270,71 @@ namespace IW4MAdmin.Application.Misc
} }
} }
public async Task OnEventAsync(GameEvent E, Server S) public Task OnLoadAsync(IManager manager)
{ {
if (successfullyLoaded) try
{ {
await _onProcessing.WaitAsync(); _logger.LogDebug("OnLoad executing for {Name}", Name);
_scriptEngine.SetValue("_manager", manager);
_scriptEngine.SetValue("getDvar", BeginGetDvar);
_scriptEngine.SetValue("setDvar", BeginSetDvar);
_scriptEngine.Evaluate("plugin.onLoadAsync(_manager)");
try return Task.CompletedTask;
{ }
_scriptEngine.SetValue("_gameEvent", E); catch (JavaScriptException ex)
_scriptEngine.SetValue("_server", S); {
_scriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S)); _logger.LogError(ex,
_scriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue(); "Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} at {@LocationInfo}",
} nameof(OnLoadAsync), Path.GetFileName(_fileName), ex.Location);
catch (JavaScriptException ex)
{
using (LogContext.PushProperty("Server", S.ToString()))
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {methodName} for script plugin {plugin} with event type {eventType} {@locationInfo}",
nameof(OnEventAsync), _fileName, E.Type, ex.Location);
}
throw new PluginException($"An error occured while executing action for script plugin"); throw new PluginException("A runtime error occured while executing action for script plugin");
} }
catch (Exception ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin}",
nameof(OnLoadAsync), Path.GetFileName(_fileName));
catch (Exception e) throw new PluginException("An error occured while executing action for script plugin");
{
using (LogContext.PushProperty("Server", S.ToString()))
{
_logger.LogError(e,
"Encountered unexpected error while running {methodName} for script plugin {plugin} with event type {eventType}",
nameof(OnEventAsync), _fileName, E.Type);
}
throw new PluginException($"An error occured while executing action for script plugin");
}
finally
{
if (_onProcessing.CurrentCount == 0)
{
_onProcessing.Release(1);
}
}
} }
} }
public async Task OnLoadAsync(IManager manager) public async Task OnTickAsync(Server server)
{ {
_logger.LogDebug("OnLoad executing for {name}", Name); _scriptEngine.SetValue("_server", server);
_scriptEngine.SetValue("_manager", manager); await Task.FromResult(_scriptEngine.Evaluate("plugin.onTickAsync(_server)"));
await Task.FromResult(_scriptEngine.Execute("plugin.onLoadAsync(_manager)").GetCompletionValue());
} }
public async Task OnTickAsync(Server S) public Task OnUnloadAsync()
{ {
_scriptEngine.SetValue("_server", S); if (!_successfullyLoaded)
await Task.FromResult(_scriptEngine.Execute("plugin.onTickAsync(_server)").GetCompletionValue());
}
public async Task OnUnloadAsync()
{
if (successfullyLoaded)
{ {
await Task.FromResult(_scriptEngine.Execute("plugin.onUnloadAsync()").GetCompletionValue()); return Task.CompletedTask;
} }
try
{
_scriptEngine.Evaluate("plugin.onUnloadAsync()");
}
catch (JavaScriptException ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} at {@LocationInfo}",
nameof(OnUnloadAsync), Path.GetFileName(_fileName), ex.Location);
throw new PluginException("A runtime error occured while executing action for script plugin");
}
catch (Exception ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin}",
nameof(OnUnloadAsync), Path.GetFileName(_fileName));
throw new PluginException("An error occured while executing action for script plugin");
}
return Task.CompletedTask;
} }
/// <summary> /// <summary>
@ -299,9 +343,10 @@ namespace IW4MAdmin.Application.Misc
/// <param name="commands">commands value from jint parser</param> /// <param name="commands">commands value from jint parser</param>
/// <param name="scriptCommandFactory">factory to create the command from</param> /// <param name="scriptCommandFactory">factory to create the command from</param>
/// <returns></returns> /// <returns></returns>
public IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory) private IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands,
IScriptCommandFactory scriptCommandFactory)
{ {
List<IManagerCommand> commandList = new List<IManagerCommand>(); var commandList = new List<IManagerCommand>();
// go through each defined command // go through each defined command
foreach (var command in commands.AsArray()) foreach (var command in commands.AsArray())
@ -311,9 +356,10 @@ namespace IW4MAdmin.Application.Misc
string alias = dynamicCommand.alias; string alias = dynamicCommand.alias;
string description = dynamicCommand.description; string description = dynamicCommand.description;
string permission = dynamicCommand.permission; string permission = dynamicCommand.permission;
bool targetRequired = false; List<Server.Game> supportedGames = null;
var targetRequired = false;
List<(string, bool)> args = new List<(string, bool)>(); var args = new List<(string, bool)>();
dynamic arguments = null; dynamic arguments = null;
try try
@ -344,26 +390,152 @@ namespace IW4MAdmin.Application.Misc
} }
} }
void execute(GameEvent e) try
{ {
_scriptEngine.SetValue("_event", e); foreach (var game in dynamicCommand.supportedGames)
var jsEventObject = _scriptEngine.GetValue("_event"); {
supportedGames ??= new List<Server.Game>();
supportedGames.Add(Enum.Parse(typeof(Server.Game), game.ToString()));
}
}
catch (RuntimeBinderException)
{
// supported games is optional
}
async Task Execute(GameEvent gameEvent)
{
try try
{ {
dynamicCommand.execute.Target.Invoke(jsEventObject); await _onProcessing.WaitAsync();
_scriptEngine.SetValue("_event", gameEvent);
var jsEventObject = _scriptEngine.Evaluate("_event");
dynamicCommand.execute.Target.Invoke(_scriptEngine, jsEventObject);
} }
catch (JavaScriptException ex) catch (JavaScriptException ex)
{ {
throw new PluginException($"An error occured while executing action for script plugin: {ex.Error} (Line: {ex.Location.Start.Line}, Character: {ex.Location.Start.Column})") { PluginFile = _fileName }; using (LogContext.PushProperty("Server", gameEvent.Owner?.ToString()))
{
_logger.LogError(ex, "Could not execute command action for {Filename} {@Location}",
Path.GetFileName(_fileName), ex.Location);
}
throw new PluginException("A runtime error occured while executing action for script plugin");
}
catch (Exception ex)
{
using (LogContext.PushProperty("Server", gameEvent.Owner?.ToString()))
{
_logger.LogError(ex,
"Could not execute command action for script plugin {FileName}",
Path.GetFileName(_fileName));
}
throw new PluginException("An error occured while executing action for script plugin");
}
finally
{
if (_onProcessing.CurrentCount == 0)
{
_onProcessing.Release(1);
}
} }
} }
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, targetRequired, args, execute)); commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission,
targetRequired, args, Execute, supportedGames?.ToArray()));
} }
return commandList; return commandList;
} }
private void BeginGetDvar(Server server, string dvarName, Delegate onCompleted)
{
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
server.BeginGetDvar(dvarName, result =>
{
var shouldRelease = false;
try
{
_onProcessing.Wait(tokenSource.Token);
shouldRelease = true;
var (success, value) = (ValueTuple<bool, string>)result.AsyncState;
onCompleted.DynamicInvoke(JsValue.Undefined,
new[]
{
JsValue.FromObject(_scriptEngine, server),
JsValue.FromObject(_scriptEngine, dvarName),
JsValue.FromObject(_scriptEngine, value),
JsValue.FromObject(_scriptEngine, success)
});
}
finally
{
if (_onProcessing.CurrentCount == 0 && shouldRelease)
{
_onProcessing.Release();
}
}
}, tokenSource.Token);
}
private void BeginSetDvar(Server server, string dvarName, string dvarValue, Delegate onCompleted)
{
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
server.BeginSetDvar(dvarName, dvarValue, result =>
{
var shouldRelease = false;
try
{
_onProcessing.Wait(tokenSource.Token);
shouldRelease = true;
var success = (bool)result.AsyncState;
onCompleted.DynamicInvoke(JsValue.Undefined,
new[]
{
JsValue.FromObject(_scriptEngine, server),
JsValue.FromObject(_scriptEngine, dvarName),
JsValue.FromObject(_scriptEngine, dvarValue),
JsValue.FromObject(_scriptEngine, success)
});
}
finally
{
if (_onProcessing.CurrentCount == 0 && shouldRelease)
{
_onProcessing.Release();
}
}
}, tokenSource.Token);
}
}
public class PermissionLevelToStringConverter : IObjectConverter
{
public bool TryConvert(Engine engine, object value, out JsValue result)
{
if (value is Data.Models.Client.EFClient.Permission)
{
result = value.ToString();
return true;
}
result = JsValue.Null;
return false;
}
} }
} }

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using IW4MAdmin.Application.Configuration; using IW4MAdmin.Application.Configuration;
using Jint; using Jint;
@ -84,9 +85,9 @@ namespace IW4MAdmin.Application.Misc
var item = _config[_pluginName][key]; var item = _config[_pluginName][key];
if (item is JArray array) if (item is JsonElement { ValueKind: JsonValueKind.Array } jElem)
{ {
item = array.ToObject<List<dynamic>>(); item = jElem.Deserialize<List<dynamic>>();
} }
return JsValue.FromObject(_scriptEngine, item); return JsValue.FromObject(_scriptEngine, item);

View File

@ -0,0 +1,159 @@
using System;
using System.Threading;
using Jint.Native;
using Jint.Runtime;
using Microsoft.Extensions.Logging;
using SharedLibraryCore.Interfaces;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Misc;
public class ScriptPluginTimerHelper : IScriptPluginTimerHelper
{
private Timer _timer;
private Action _actions;
private Delegate _jsAction;
private string _actionName;
private const int DefaultDelay = 0;
private const int DefaultInterval = 1000;
private readonly ILogger _logger;
private readonly ManualResetEventSlim _onRunningTick = new();
private SemaphoreSlim _onDependentAction;
public ScriptPluginTimerHelper(ILogger<ScriptPluginTimerHelper> logger)
{
_logger = logger;
}
~ScriptPluginTimerHelper()
{
if (_timer != null)
{
Stop();
}
_onRunningTick.Dispose();
}
public void Start(int delay, int interval)
{
if (_actions is null)
{
throw new InvalidOperationException("Timer action must be defined before starting");
}
if (delay < 0)
{
throw new ArgumentException("Timer delay must be >= 0");
}
if (interval < 20)
{
throw new ArgumentException("Timer interval must be at least 20ms");
}
Stop();
_logger.LogDebug("Starting script timer...");
_onRunningTick.Set();
_timer ??= new Timer(callback => _actions(), null, delay, interval);
IsRunning = true;
}
public void Start(int interval)
{
Start(DefaultDelay, interval);
}
public void Start()
{
Start(DefaultDelay, DefaultInterval);
}
public void Stop()
{
if (_timer == null)
{
return;
}
_logger.LogDebug("Stopping script timer...");
_timer.Change(Timeout.Infinite, Timeout.Infinite);
_timer.Dispose();
_timer = null;
IsRunning = false;
}
public void OnTick(Delegate action, string actionName)
{
if (string.IsNullOrEmpty(actionName))
{
throw new ArgumentException("actionName must be provided", nameof(actionName));
}
if (action is null)
{
throw new ArgumentException("action must be provided", nameof(action));
}
_logger.LogDebug("Adding new action with name {ActionName}", actionName);
_jsAction = action;
_actionName = actionName;
_actions = OnTick;
}
private void ReleaseThreads()
{
_onRunningTick.Set();
if (_onDependentAction?.CurrentCount != 0)
{
return;
}
_onDependentAction?.Release(1);
}
private void OnTick()
{
try
{
if (!_onRunningTick.IsSet)
{
_logger.LogDebug("Previous {OnTick} is still running, so we are skipping this one",
nameof(OnTick));
return;
}
_onRunningTick.Reset();
// the js engine is not thread safe so we need to ensure we're not executing OnTick and OnEventAsync simultaneously
_onDependentAction?.WaitAsync().Wait();
var start = DateTime.Now;
_jsAction.DynamicInvoke(JsValue.Undefined, new[] { JsValue.Undefined });
_logger.LogDebug("OnTick took {Time}ms", (DateTime.Now - start).TotalMilliseconds);
ReleaseThreads();
}
catch (Exception ex) when (ex.InnerException is JavaScriptException jsex)
{
_logger.LogError(jsex,
"Could not execute timer tick for script action {ActionName} [@{LocationInfo}]", _actionName,
jsex.Location);
ReleaseThreads();
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not execute timer tick for script action {ActionName}", _actionName);
_onRunningTick.Set();
ReleaseThreads();
}
}
public void SetDependency(SemaphoreSlim dependentSemaphore)
{
_onDependentAction = dependentSemaphore;
}
public bool IsRunning { get; private set; }
}

View File

@ -94,7 +94,8 @@ namespace IW4MAdmin.Application.Misc
PeriodBlock = (int) (DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch).TotalMinutes, PeriodBlock = (int) (DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch).TotalMinutes,
ServerId = await server.GetIdForServer(), ServerId = await server.GetIdForServer(),
MapId = await GetOrCreateMap(server.CurrentMap.Name, (Reference.Game) server.GameName, token), MapId = await GetOrCreateMap(server.CurrentMap.Name, (Reference.Game) server.GameName, token),
ClientCount = server.ClientNum ClientCount = server.ClientNum,
ConnectionInterrupted = server.Throttled,
})); }));
return data; return data;
@ -144,4 +145,4 @@ namespace IW4MAdmin.Application.Misc
context.SaveChanges(); context.SaveChanges();
} }
} }
} }

View File

@ -135,7 +135,9 @@ namespace IW4MAdmin.Application.Misc
{ {
snapshot.ServerId, snapshot.ServerId,
snapshot.CapturedAt, snapshot.CapturedAt,
snapshot.ClientCount snapshot.ClientCount,
snapshot.ConnectionInterrupted,
MapName = snapshot.Map.Name,
}) })
.OrderBy(snapshot => snapshot.CapturedAt) .OrderBy(snapshot => snapshot.CapturedAt)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
@ -143,8 +145,8 @@ namespace IW4MAdmin.Application.Misc
return history.GroupBy(snapshot => snapshot.ServerId).Select(byServer => new ClientHistoryInfo return history.GroupBy(snapshot => snapshot.ServerId).Select(byServer => new ClientHistoryInfo
{ {
ServerId = byServer.Key, ServerId = byServer.Key,
ClientCounts = byServer.Select(snapshot => new ClientCountSnapshot() ClientCounts = byServer.Select(snapshot => new ClientCountSnapshot
{Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount}).ToList() { Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount, ConnectionInterrupted = snapshot.ConnectionInterrupted ?? false, Map = snapshot.MapName}).ToList()
}).ToList(); }).ToList();
}, nameof(_clientHistoryCache), TimeSpan.MaxValue); }, nameof(_clientHistoryCache), TimeSpan.MaxValue);
@ -159,4 +161,4 @@ namespace IW4MAdmin.Application.Misc
} }
} }
} }
} }

View File

@ -7,8 +7,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Models; using Data.Models;
using IW4MAdmin.Application.Misc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static SharedLibraryCore.Server; using static SharedLibraryCore.Server;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -77,19 +79,20 @@ namespace IW4MAdmin.Application.RConParsers
public string RConEngine { get; set; } = "COD"; public string RConEngine { get; set; } = "COD";
public bool IsOneLog { get; set; } public bool IsOneLog { get; set; }
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command) public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command, CancellationToken token = default)
{ {
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command); command = command.FormatMessageForEngine(Configuration?.ColorCodeMapping);
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, token);
return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray(); return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray();
} }
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default) public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default)
{ {
string[] lineSplit; string[] lineSplit;
try try
{ {
lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName); lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName, token);
} }
catch catch
{ {
@ -98,10 +101,10 @@ namespace IW4MAdmin.Application.RConParsers
throw; throw;
} }
lineSplit = new string[0]; lineSplit = Array.Empty<string>();
} }
string response = string.Join('\n', lineSplit).TrimEnd('\0'); var response = string.Join('\n', lineSplit).TrimEnd('\0');
var match = Regex.Match(response, Configuration.Dvar.Pattern); var match = Regex.Match(response, Configuration.Dvar.Pattern);
if (response.Contains("Unknown command") || if (response.Contains("Unknown command") ||
@ -109,7 +112,7 @@ namespace IW4MAdmin.Application.RConParsers
{ {
if (fallbackValue != null) if (fallbackValue != null)
{ {
return new Dvar<T>() return new Dvar<T>
{ {
Name = dvarName, Name = dvarName,
Value = fallbackValue Value = fallbackValue
@ -119,17 +122,17 @@ namespace IW4MAdmin.Application.RConParsers
throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName)); throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName));
} }
string value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value; var value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value;
string defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value; var defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value;
string latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value; var latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value;
string removeTrailingColorCode(string input) => Regex.Replace(input, @"\^7$", ""); string RemoveTrailingColorCode(string input) => Regex.Replace(input, @"\^7$", "");
value = removeTrailingColorCode(value); value = RemoveTrailingColorCode(value);
defaultValue = removeTrailingColorCode(defaultValue); defaultValue = RemoveTrailingColorCode(defaultValue);
latchedValue = removeTrailingColorCode(latchedValue); latchedValue = RemoveTrailingColorCode(latchedValue);
return new Dvar<T>() return new Dvar<T>
{ {
Name = dvarName, Name = dvarName,
Value = string.IsNullOrEmpty(value) ? default : (T)Convert.ChangeType(value, typeof(T)), Value = string.IsNullOrEmpty(value) ? default : (T)Convert.ChangeType(value, typeof(T)),
@ -139,10 +142,36 @@ namespace IW4MAdmin.Application.RConParsers
}; };
} }
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection) public void BeginGetDvar(IRConConnection connection, string dvarName, AsyncCallback callback, CancellationToken token = default)
{ {
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS); GetDvarAsync<string>(connection, dvarName, token: token).ContinueWith(action =>
_logger.LogDebug("Status Response {response}", string.Join(Environment.NewLine, response)); {
if (action.Exception is null)
{
callback?.Invoke(new AsyncResult
{
IsCompleted = true,
AsyncState = (true, action.Result.Value)
});
}
else
{
callback?.Invoke(new AsyncResult
{
IsCompleted = true,
AsyncState = (false, (string)null)
});
}
}, token);
}
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default)
{
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS, "status", token);
_logger.LogDebug("Status Response {Response}", string.Join(Environment.NewLine, response));
return new StatusResponse return new StatusResponse
{ {
Clients = ClientsFromStatus(response).ToArray(), Clients = ClientsFromStatus(response).ToArray(),
@ -183,13 +212,38 @@ namespace IW4MAdmin.Application.RConParsers
return (T)Convert.ChangeType(value, typeof(T)); return (T)Convert.ChangeType(value, typeof(T));
} }
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue) public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default)
{ {
string dvarString = (dvarValue is string str) var dvarString = (dvarValue is string str)
? $"{dvarName} \"{str}\"" ? $"{dvarName} \"{str}\""
: $"{dvarName} {dvarValue}"; : $"{dvarName} {dvarValue}";
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString)).Length > 0; return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString, token)).Length > 0;
}
public void BeginSetDvar(IRConConnection connection, string dvarName, object dvarValue, AsyncCallback callback,
CancellationToken token = default)
{
SetDvarAsync(connection, dvarName, dvarValue, token).ContinueWith(action =>
{
if (action.Exception is null)
{
callback?.Invoke(new AsyncResult
{
IsCompleted = true,
AsyncState = true
});
}
else
{
callback?.Invoke(new AsyncResult
{
IsCompleted = true,
AsyncState = false
});
}
}, token);
} }
private List<EFClient> ClientsFromStatus(string[] Status) private List<EFClient> ClientsFromStatus(string[] Status)

View File

@ -31,6 +31,7 @@ namespace IW4MAdmin.Application.RConParsers
public string NoticeLineSeparator { get; set; } = Environment.NewLine; public string NoticeLineSeparator { get; set; } = Environment.NewLine;
public int? DefaultRConPort { get; set; } public int? DefaultRConPort { get; set; }
public string DefaultInstallationDirectoryHint { get; set; } public string DefaultInstallationDirectoryHint { get; set; }
public short FloodProtectInterval { get; set; } = 750;
public ColorCodeMapping ColorCodeMapping { get; set; } = new ColorCodeMapping public ColorCodeMapping ColorCodeMapping { get; set; } = new ColorCodeMapping
{ {
@ -59,4 +60,4 @@ namespace IW4MAdmin.Application.RConParsers
MaxPlayersStatus = parserRegexFactory.CreateParserRegex(); MaxPlayersStatus = parserRegexFactory.CreateParserRegex();
} }
} }
} }

Binary file not shown.

View File

@ -18,6 +18,7 @@ namespace Data.Context
public DbSet<EFAlias> Aliases { get; set; } public DbSet<EFAlias> Aliases { get; set; }
public DbSet<EFAliasLink> AliasLinks { get; set; } public DbSet<EFAliasLink> AliasLinks { get; set; }
public DbSet<EFPenalty> Penalties { get; set; } public DbSet<EFPenalty> Penalties { get; set; }
public DbSet<EFPenaltyIdentifier> PenaltyIdentifiers { get; set; }
public DbSet<EFMeta> EFMeta { get; set; } public DbSet<EFMeta> EFMeta { get; set; }
public DbSet<EFChangeHistory> EFChangeHistory { get; set; } public DbSet<EFChangeHistory> EFChangeHistory { get; set; }
@ -119,6 +120,9 @@ namespace Data.Context
ent.Property(_alias => _alias.SearchableName).HasMaxLength(24); ent.Property(_alias => _alias.SearchableName).HasMaxLength(24);
ent.HasIndex(_alias => _alias.SearchableName); ent.HasIndex(_alias => _alias.SearchableName);
ent.HasIndex(_alias => new {_alias.Name, _alias.IPAddress}); ent.HasIndex(_alias => new {_alias.Name, _alias.IPAddress});
ent.Property(alias => alias.SearchableIPAddress)
.HasComputedColumnSql(@"((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", stored: true);
ent.HasIndex(alias => alias.SearchableIPAddress);
}); });
modelBuilder.Entity<EFMeta>(ent => modelBuilder.Entity<EFMeta>(ent =>
@ -130,6 +134,12 @@ namespace Data.Context
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
}); });
modelBuilder.Entity<EFPenaltyIdentifier>(ent =>
{
ent.HasIndex(identifiers => identifiers.NetworkId);
ent.HasIndex(identifiers => identifiers.IPv4Address);
});
modelBuilder.Entity<EFClientConnectionHistory>(ent => ent.HasIndex(history => history.CreatedDateTime)); modelBuilder.Entity<EFClientConnectionHistory>(ent => ent.HasIndex(history => history.CreatedDateTime));
// force full name for database conversion // force full name for database conversion
@ -137,6 +147,7 @@ namespace Data.Context
modelBuilder.Entity<EFAlias>().ToTable("EFAlias"); modelBuilder.Entity<EFAlias>().ToTable("EFAlias");
modelBuilder.Entity<EFAliasLink>().ToTable("EFAliasLinks"); modelBuilder.Entity<EFAliasLink>().ToTable("EFAliasLinks");
modelBuilder.Entity<EFPenalty>().ToTable("EFPenalties"); modelBuilder.Entity<EFPenalty>().ToTable("EFPenalties");
modelBuilder.Entity<EFPenaltyIdentifier>().ToTable("EFPenaltyIdentifiers");
modelBuilder.Entity<EFServerSnapshot>().ToTable(nameof(EFServerSnapshot)); modelBuilder.Entity<EFServerSnapshot>().ToTable(nameof(EFServerSnapshot));
modelBuilder.Entity<EFClientConnectionHistory>().ToTable(nameof(EFClientConnectionHistory)); modelBuilder.Entity<EFClientConnectionHistory>().ToTable(nameof(EFClientConnectionHistory));

View File

@ -24,7 +24,8 @@ namespace Data.MigrationContext
{ {
if (MigrationExtensions.IsMigration) if (MigrationExtensions.IsMigration)
{ {
optionsBuilder.UseMySql(ServerVersion.AutoDetect("Server=127.0.0.1;Database=IW4MAdmin_Migration;Uid=root;Pwd=password;")) var connectionString = "Server=127.0.0.1;Database=IW4MAdmin_Migration;Uid=root;Pwd=password;";
optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
.EnableDetailedErrors() .EnableDetailedErrors()
.EnableSensitiveDataLogging(); .EnableSensitiveDataLogging();
} }

View File

@ -23,6 +23,7 @@ namespace Data.MigrationContext
{ {
if (MigrationExtensions.IsMigration) if (MigrationExtensions.IsMigration)
{ {
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
optionsBuilder.UseNpgsql( optionsBuilder.UseNpgsql(
"Host=127.0.0.1;Database=IW4MAdmin_Migration;Username=postgres;Password=password;", "Host=127.0.0.1;Database=IW4MAdmin_Migration;Username=postgres;Password=password;",
options => options.SetPostgresVersion(new Version("12.9"))) options => options.SetPostgresVersion(new Version("12.9")))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,250 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EFPenaltyIdentifiers",
columns: table => new
{
PenaltyIdentifierId = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
IPv4Address = table.Column<int>(type: "int", nullable: true),
NetworkId = table.Column<long>(type: "bigint", nullable: false),
PenaltyId = table.Column<int>(type: "int", nullable: false),
Active = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EFPenaltyIdentifiers", x => x.PenaltyIdentifierId);
table.ForeignKey(
name: "FK_EFPenaltyIdentifiers_EFPenalties_PenaltyId",
column: x => x.PenaltyId,
principalTable: "EFPenalties",
principalColumn: "PenaltyId",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_IPv4Address",
table: "EFPenaltyIdentifiers",
column: "IPv4Address");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_NetworkId",
table: "EFPenaltyIdentifiers",
column: "NetworkId");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_PenaltyId",
table: "EFPenaltyIdentifiers",
column: "PenaltyId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EFPenaltyIdentifiers");
migrationBuilder.AlterColumn<string>(
name: "Message",
table: "InboxMessages",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "EFWeaponAttachments",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "HostName",
table: "EFServers",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "EndPoint",
table: "EFServers",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Offense",
table: "EFPenalties",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "AutomatedOffense",
table: "EFPenalties",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Value",
table: "EFMeta",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Extra",
table: "EFMeta",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "EFMeansOfDeath",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "EFMaps",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "PasswordSalt",
table: "EFClients",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Password",
table: "EFClients",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Message",
table: "EFClientMessages",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "WeaponReference",
table: "EFClientKills",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "PreviousValue",
table: "EFChangeHistory",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "CurrentValue",
table: "EFChangeHistory",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "WeaponReference",
table: "EFACSnapshot",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "HitLocationReference",
table: "EFACSnapshot",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class MakeEFPenaltyLinkIdNullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId",
onDelete: ReferentialAction.Cascade);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddAuditFieldsToEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Active",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<DateTime>(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers",
type: "datetime(6)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.DropColumn(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<bool>(
name: "Active",
table: "EFPenaltyIdentifiers",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddConnectionInterruptedToEFServerSnapshot : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "ConnectionInterrupted",
table: "EFServerSnapshot",
type: "tinyint(1)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ConnectionInterrupted",
table: "EFServerSnapshot");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SearchableIPAddress",
table: "EFAlias",
type: "longtext",
nullable: true,
computedColumnSql: "CONCAT((IPAddress & 255), \".\", ((IPAddress >> 8) & 255), \".\", ((IPAddress >> 16) & 255), \".\", ((IPAddress >> 24) & 255))",
stored: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SearchableIPAddress",
table: "EFAlias");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddIndexToSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias",
column: "SearchableIPAddress");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddGameToEFClient : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "GameName",
table: "EFClients",
type: "int",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GameName",
table: "EFClients");
}
}
}

View File

@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Data.Migrations.MySql namespace Data.Migrations.MySql
{ {
[DbContext(typeof(MySqlDatabaseContext))] [DbContext(typeof(MySqlDatabaseContext))]
@ -14,7 +16,7 @@ namespace Data.Migrations.MySql
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "3.1.10") .HasAnnotation("ProductVersion", "6.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
@ -38,7 +40,7 @@ namespace Data.Migrations.MySql
b.HasIndex("Vector3Id"); b.HasIndex("Vector3Id");
b.ToTable("EFACSnapshotVector3"); b.ToTable("EFACSnapshotVector3", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClient", b => modelBuilder.Entity("Data.Models.Client.EFClient", b =>
@ -62,6 +64,9 @@ namespace Data.Migrations.MySql
b.Property<DateTime>("FirstConnection") b.Property<DateTime>("FirstConnection")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<int?>("GameName")
.HasColumnType("int");
b.Property<DateTime>("LastConnection") b.Property<DateTime>("LastConnection")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -75,10 +80,10 @@ namespace Data.Migrations.MySql
.HasColumnType("bigint"); .HasColumnType("bigint");
b.Property<string>("Password") b.Property<string>("Password")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<string>("PasswordSalt") b.Property<string>("PasswordSalt")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<int>("TotalConnectionTime") b.Property<int>("TotalConnectionTime")
.HasColumnType("int"); .HasColumnType("int");
@ -92,7 +97,7 @@ namespace Data.Migrations.MySql
b.HasIndex("NetworkId") b.HasIndex("NetworkId")
.IsUnique(); .IsUnique();
b.ToTable("EFClients"); b.ToTable("EFClients", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
@ -124,7 +129,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFClientConnectionHistory"); b.ToTable("EFClientConnectionHistory", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClientKill", b => modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
@ -179,7 +184,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("WeaponReference") b.Property<string>("WeaponReference")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<DateTime>("When") b.Property<DateTime>("When")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -198,7 +203,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ViewAnglesVector3Id"); b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills"); b.ToTable("EFClientKills", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
@ -214,7 +219,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("Message") b.Property<string>("Message")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<bool>("SentIngame") b.Property<bool>("SentIngame")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
@ -233,7 +238,7 @@ namespace Data.Migrations.MySql
b.HasIndex("TimeSent"); b.HasIndex("TimeSent");
b.ToTable("EFClientMessages"); b.ToTable("EFClientMessages", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
@ -273,7 +278,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("HitLocationReference") b.Property<string>("HitLocationReference")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<int>("HitOriginId") b.Property<int>("HitOriginId")
.HasColumnType("int"); .HasColumnType("int");
@ -321,7 +326,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("WeaponReference") b.Property<string>("WeaponReference")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<DateTime>("When") b.Property<DateTime>("When")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -340,7 +345,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFACSnapshot"); b.ToTable("EFACSnapshot", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
@ -414,7 +419,7 @@ namespace Data.Migrations.MySql
b.HasIndex("WeaponId"); b.HasIndex("WeaponId");
b.ToTable("EFClientHitStatistics"); b.ToTable("EFClientHitStatistics", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
@ -459,7 +464,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ZScore"); b.HasIndex("ZScore");
b.ToTable("EFClientRankingHistory"); b.ToTable("EFClientRankingHistory", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
@ -478,7 +483,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ClientId"); b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory"); b.ToTable("EFClientRatingHistory", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
@ -536,7 +541,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ClientId", "TimePlayed", "ZScore"); b.HasIndex("ClientId", "TimePlayed", "ZScore");
b.ToTable("EFClientStatistics"); b.ToTable("EFClientStatistics", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
@ -549,12 +554,12 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<int>("EFClientStatisticsClientId") b.Property<int>("EFClientStatisticsClientId")
.HasColumnName("EFClientStatisticsClientId") .HasColumnType("int")
.HasColumnType("int"); .HasColumnName("EFClientStatisticsClientId");
b.Property<long>("EFClientStatisticsServerId") b.Property<long>("EFClientStatisticsServerId")
.HasColumnName("EFClientStatisticsServerId") .HasColumnType("bigint")
.HasColumnType("bigint"); .HasColumnName("EFClientStatisticsServerId");
b.Property<int>("HitCount") b.Property<int>("HitCount")
.HasColumnType("int"); .HasColumnType("int");
@ -574,7 +579,7 @@ namespace Data.Migrations.MySql
b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId");
b.ToTable("EFHitLocationCounts"); b.ToTable("EFHitLocationCounts", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
@ -617,7 +622,7 @@ namespace Data.Migrations.MySql
b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); b.HasIndex("When", "ServerId", "Performance", "ActivityAmount");
b.ToTable("EFRating"); b.ToTable("EFRating", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b =>
@ -634,7 +639,7 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4"); .HasColumnType("varchar(255)");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -643,7 +648,7 @@ namespace Data.Migrations.MySql
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("EFHitLocations"); b.ToTable("EFHitLocations", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b =>
@ -660,14 +665,14 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.HasKey("MapId"); b.HasKey("MapId");
b.ToTable("EFMaps"); b.ToTable("EFMaps", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b =>
@ -684,14 +689,14 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.HasKey("MeansOfDeathId"); b.HasKey("MeansOfDeathId");
b.ToTable("EFMeansOfDeath"); b.ToTable("EFMeansOfDeath", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b =>
@ -708,7 +713,7 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4"); .HasColumnType("varchar(255)");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -717,7 +722,7 @@ namespace Data.Migrations.MySql
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("EFWeapons"); b.ToTable("EFWeapons", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b =>
@ -734,14 +739,14 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.HasKey("WeaponAttachmentId"); b.HasKey("WeaponAttachmentId");
b.ToTable("EFWeaponAttachments"); b.ToTable("EFWeaponAttachments", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
@ -776,7 +781,7 @@ namespace Data.Migrations.MySql
b.HasIndex("Attachment3Id"); b.HasIndex("Attachment3Id");
b.ToTable("EFWeaponAttachmentCombos"); b.ToTable("EFWeaponAttachmentCombos", (string)null);
}); });
modelBuilder.Entity("Data.Models.EFAlias", b => modelBuilder.Entity("Data.Models.EFAlias", b =>
@ -799,12 +804,17 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("varchar(24) CHARACTER SET utf8mb4") .HasMaxLength(24)
.HasMaxLength(24); .HasColumnType("varchar(24)");
b.Property<string>("SearchableIPAddress")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("varchar(255)")
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
b.Property<string>("SearchableName") b.Property<string>("SearchableName")
.HasColumnType("varchar(24) CHARACTER SET utf8mb4") .HasMaxLength(24)
.HasMaxLength(24); .HasColumnType("varchar(24)");
b.HasKey("AliasId"); b.HasKey("AliasId");
@ -814,11 +824,13 @@ namespace Data.Migrations.MySql
b.HasIndex("Name"); b.HasIndex("Name");
b.HasIndex("SearchableIPAddress");
b.HasIndex("SearchableName"); b.HasIndex("SearchableName");
b.HasIndex("Name", "IPAddress"); b.HasIndex("Name", "IPAddress");
b.ToTable("EFAlias"); b.ToTable("EFAlias", (string)null);
}); });
modelBuilder.Entity("Data.Models.EFAliasLink", b => modelBuilder.Entity("Data.Models.EFAliasLink", b =>
@ -832,7 +844,7 @@ namespace Data.Migrations.MySql
b.HasKey("AliasLinkId"); b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks"); b.ToTable("EFAliasLinks", (string)null);
}); });
modelBuilder.Entity("Data.Models.EFChangeHistory", b => modelBuilder.Entity("Data.Models.EFChangeHistory", b =>
@ -845,11 +857,11 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<string>("Comment") b.Property<string>("Comment")
.HasColumnType("varchar(128) CHARACTER SET utf8mb4") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("varchar(128)");
b.Property<string>("CurrentValue") b.Property<string>("CurrentValue")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<int?>("ImpersonationEntityId") b.Property<int?>("ImpersonationEntityId")
.HasColumnType("int"); .HasColumnType("int");
@ -858,7 +870,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("PreviousValue") b.Property<string>("PreviousValue")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<int>("TargetEntityId") b.Property<int>("TargetEntityId")
.HasColumnType("int"); .HasColumnType("int");
@ -890,12 +902,12 @@ namespace Data.Migrations.MySql
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<string>("Extra") b.Property<string>("Extra")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<string>("Key") b.Property<string>("Key")
.IsRequired() .IsRequired()
.HasColumnType("varchar(32) CHARACTER SET utf8mb4") .HasMaxLength(32)
.HasMaxLength(32); .HasColumnType("varchar(32)");
b.Property<int?>("LinkedMetaId") b.Property<int?>("LinkedMetaId")
.HasColumnType("int"); .HasColumnType("int");
@ -905,7 +917,7 @@ namespace Data.Migrations.MySql
b.Property<string>("Value") b.Property<string>("Value")
.IsRequired() .IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.HasKey("MetaId"); b.HasKey("MetaId");
@ -928,7 +940,7 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<string>("AutomatedOffense") b.Property<string>("AutomatedOffense")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<DateTime?>("Expires") b.Property<DateTime?>("Expires")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -936,7 +948,7 @@ namespace Data.Migrations.MySql
b.Property<bool>("IsEvadedOffense") b.Property<bool>("IsEvadedOffense")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<int>("LinkId") b.Property<int?>("LinkId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("OffenderId") b.Property<int>("OffenderId")
@ -944,7 +956,7 @@ namespace Data.Migrations.MySql
b.Property<string>("Offense") b.Property<string>("Offense")
.IsRequired() .IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<int>("PunisherId") b.Property<int>("PunisherId")
.HasColumnType("int"); .HasColumnType("int");
@ -963,7 +975,39 @@ namespace Data.Migrations.MySql
b.HasIndex("PunisherId"); b.HasIndex("PunisherId");
b.ToTable("EFPenalties"); b.ToTable("EFPenalties", (string)null);
});
modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b =>
{
b.Property<int>("PenaltyIdentifierId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedDateTime")
.HasColumnType("datetime(6)");
b.Property<int?>("IPv4Address")
.HasColumnType("int");
b.Property<long>("NetworkId")
.HasColumnType("bigint");
b.Property<int>("PenaltyId")
.HasColumnType("int");
b.Property<DateTime?>("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 => modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
@ -982,7 +1026,7 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<string>("Message") b.Property<string>("Message")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<long?>("ServerId") b.Property<long?>("ServerId")
.HasColumnType("bigint"); .HasColumnType("bigint");
@ -1013,13 +1057,13 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<string>("EndPoint") b.Property<string>("EndPoint")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<int?>("GameName") b.Property<int?>("GameName")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("HostName") b.Property<string>("HostName")
.HasColumnType("longtext CHARACTER SET utf8mb4"); .HasColumnType("longtext");
b.Property<bool>("IsPasswordProtected") b.Property<bool>("IsPasswordProtected")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
@ -1029,7 +1073,7 @@ namespace Data.Migrations.MySql
b.HasKey("ServerId"); b.HasKey("ServerId");
b.ToTable("EFServers"); b.ToTable("EFServers", (string)null);
}); });
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
@ -1047,6 +1091,9 @@ namespace Data.Migrations.MySql
b.Property<int>("ClientCount") b.Property<int>("ClientCount")
.HasColumnType("int"); .HasColumnType("int");
b.Property<bool?>("ConnectionInterrupted")
.HasColumnType("tinyint(1)");
b.Property<int>("MapId") b.Property<int>("MapId")
.HasColumnType("int"); .HasColumnType("int");
@ -1062,7 +1109,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFServerSnapshot"); b.ToTable("EFServerSnapshot", (string)null);
}); });
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
@ -1087,7 +1134,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFServerStatistics"); b.ToTable("EFServerStatistics", (string)null);
}); });
modelBuilder.Entity("Data.Models.Vector3", b => modelBuilder.Entity("Data.Models.Vector3", b =>
@ -1107,7 +1154,7 @@ namespace Data.Migrations.MySql
b.HasKey("Vector3Id"); b.HasKey("Vector3Id");
b.ToTable("Vector3"); b.ToTable("Vector3", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
@ -1123,6 +1170,10 @@ namespace Data.Migrations.MySql
.HasForeignKey("Vector3Id") .HasForeignKey("Vector3Id")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Snapshot");
b.Navigation("Vector");
}); });
modelBuilder.Entity("Data.Models.Client.EFClient", b => modelBuilder.Entity("Data.Models.Client.EFClient", b =>
@ -1138,6 +1189,10 @@ namespace Data.Migrations.MySql
.HasForeignKey("CurrentAliasId") .HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("AliasLink");
b.Navigation("CurrentAlias");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
@ -1153,6 +1208,10 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientKill", b => modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
@ -1186,6 +1245,18 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Vector3", "ViewAngles") b.HasOne("Data.Models.Vector3", "ViewAngles")
.WithMany() .WithMany()
.HasForeignKey("ViewAnglesVector3Id"); .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 => modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
@ -1201,6 +1272,10 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
@ -1238,6 +1313,18 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .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 => modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
@ -1267,6 +1354,18 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon")
.WithMany() .WithMany()
.HasForeignKey("WeaponId"); .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 => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
@ -1280,6 +1379,10 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
@ -1289,6 +1392,8 @@ namespace Data.Migrations.MySql
.HasForeignKey("ClientId") .HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
@ -1304,6 +1409,10 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
@ -1325,6 +1434,10 @@ namespace Data.Migrations.MySql
.HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
@ -1338,6 +1451,10 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
b.Navigation("RatingHistory");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
@ -1355,6 +1472,12 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3")
.WithMany() .WithMany()
.HasForeignKey("Attachment3Id"); .HasForeignKey("Attachment3Id");
b.Navigation("Attachment1");
b.Navigation("Attachment2");
b.Navigation("Attachment3");
}); });
modelBuilder.Entity("Data.Models.EFAlias", b => modelBuilder.Entity("Data.Models.EFAlias", b =>
@ -1364,6 +1487,8 @@ namespace Data.Migrations.MySql
.HasForeignKey("LinkId") .HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.Navigation("Link");
}); });
modelBuilder.Entity("Data.Models.EFMeta", b => modelBuilder.Entity("Data.Models.EFMeta", b =>
@ -1376,15 +1501,17 @@ namespace Data.Migrations.MySql
.WithMany() .WithMany()
.HasForeignKey("LinkedMetaId") .HasForeignKey("LinkedMetaId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.Navigation("Client");
b.Navigation("LinkedMeta");
}); });
modelBuilder.Entity("Data.Models.EFPenalty", b => modelBuilder.Entity("Data.Models.EFPenalty", b =>
{ {
b.HasOne("Data.Models.EFAliasLink", "Link") b.HasOne("Data.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties") .WithMany("ReceivedPenalties")
.HasForeignKey("LinkId") .HasForeignKey("LinkId");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Data.Models.Client.EFClient", "Offender") b.HasOne("Data.Models.Client.EFClient", "Offender")
.WithMany("ReceivedPenalties") .WithMany("ReceivedPenalties")
@ -1397,6 +1524,23 @@ namespace Data.Migrations.MySql
.HasForeignKey("PunisherId") .HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .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 => modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
@ -1416,6 +1560,12 @@ namespace Data.Migrations.MySql
.HasForeignKey("SourceClientId") .HasForeignKey("SourceClientId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("DestinationClient");
b.Navigation("Server");
b.Navigation("SourceClient");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
@ -1431,6 +1581,10 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Map");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
@ -1440,6 +1594,39 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .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 #pragma warning restore 612, 618
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EFPenaltyIdentifiers",
columns: table => new
{
PenaltyIdentifierId = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
IPv4Address = table.Column<int>(type: "integer", nullable: true),
NetworkId = table.Column<long>(type: "bigint", nullable: false),
PenaltyId = table.Column<int>(type: "integer", nullable: false),
Active = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EFPenaltyIdentifiers", x => x.PenaltyIdentifierId);
table.ForeignKey(
name: "FK_EFPenaltyIdentifiers_EFPenalties_PenaltyId",
column: x => x.PenaltyId,
principalTable: "EFPenalties",
principalColumn: "PenaltyId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_IPv4Address",
table: "EFPenaltyIdentifiers",
column: "IPv4Address");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_NetworkId",
table: "EFPenaltyIdentifiers",
column: "NetworkId");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_PenaltyId",
table: "EFPenaltyIdentifiers",
column: "PenaltyId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EFPenaltyIdentifiers");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class MakeEFPenaltyLinkIdNullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "integer",
nullable: true,
oldClrType: typeof(int),
oldType: "integer");
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "integer",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "integer",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId",
onDelete: ReferentialAction.Cascade);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddAuditFieldsToEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Active",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<DateTime>(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers",
type: "timestamp without time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers",
type: "timestamp without time zone",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.DropColumn(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<bool>(
name: "Active",
table: "EFPenaltyIdentifiers",
type: "boolean",
nullable: false,
defaultValue: false);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddConnectionInterruptedToEFServerSnapshot : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "ConnectionInterrupted",
table: "EFServerSnapshot",
type: "boolean",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ConnectionInterrupted",
table: "EFServerSnapshot");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SearchableIPAddress",
table: "EFAlias",
type: "text",
nullable: true,
computedColumnSql: "CASE WHEN \"IPAddress\" IS NULL THEN 'NULL'::text ELSE (\"IPAddress\" & 255)::text || '.'::text || ((\"IPAddress\" >> 8) & 255)::text || '.'::text || ((\"IPAddress\" >> 16) & 255)::text || '.'::text || ((\"IPAddress\" >> 24) & 255)::text END",
stored: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SearchableIPAddress",
table: "EFAlias");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddIndexToSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias",
column: "SearchableIPAddress");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddGameToEFClient : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "GameName",
table: "EFClients",
type: "integer",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GameName",
table: "EFClients");
}
}
}

View File

@ -6,6 +6,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Data.Migrations.Postgresql namespace Data.Migrations.Postgresql
{ {
[DbContext(typeof(PostgresqlDatabaseContext))] [DbContext(typeof(PostgresqlDatabaseContext))]
@ -15,16 +17,18 @@ namespace Data.Migrations.Postgresql
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "6.0.1")
.HasAnnotation("ProductVersion", "3.1.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
{ {
b.Property<int>("ACSnapshotVector3Id") b.Property<int>("ACSnapshotVector3Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ACSnapshotVector3Id"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -41,15 +45,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("Vector3Id"); b.HasIndex("Vector3Id");
b.ToTable("EFACSnapshotVector3"); b.ToTable("EFACSnapshotVector3", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClient", b => modelBuilder.Entity("Data.Models.Client.EFClient", b =>
{ {
b.Property<int>("ClientId") b.Property<int>("ClientId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ClientId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -66,6 +71,9 @@ namespace Data.Migrations.Postgresql
b.Property<DateTime>("FirstConnection") b.Property<DateTime>("FirstConnection")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
b.Property<int?>("GameName")
.HasColumnType("integer");
b.Property<DateTime>("LastConnection") b.Property<DateTime>("LastConnection")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
@ -96,15 +104,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("NetworkId") b.HasIndex("NetworkId")
.IsUnique(); .IsUnique();
b.ToTable("EFClients"); b.ToTable("EFClients", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
{ {
b.Property<long>("ClientConnectionId") b.Property<long>("ClientConnectionId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bigint") .HasColumnType("bigint");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("ClientConnectionId"));
b.Property<int>("ClientId") b.Property<int>("ClientId")
.HasColumnType("integer"); .HasColumnType("integer");
@ -129,15 +138,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFClientConnectionHistory"); b.ToTable("EFClientConnectionHistory", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClientKill", b => modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
{ {
b.Property<long>("KillId") b.Property<long>("KillId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bigint") .HasColumnType("bigint");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("KillId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -204,15 +214,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("ViewAnglesVector3Id"); b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills"); b.ToTable("EFClientKills", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
{ {
b.Property<long>("MessageId") b.Property<long>("MessageId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bigint") .HasColumnType("bigint");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("MessageId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -240,15 +251,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("TimeSent"); b.HasIndex("TimeSent");
b.ToTable("EFClientMessages"); b.ToTable("EFClientMessages", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
{ {
b.Property<int>("SnapshotId") b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SnapshotId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -348,15 +360,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFACSnapshot"); b.ToTable("EFACSnapshot", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
{ {
b.Property<int>("ClientHitStatisticId") b.Property<int>("ClientHitStatisticId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ClientHitStatisticId"));
b.Property<int>("ClientId") b.Property<int>("ClientId")
.HasColumnType("integer"); .HasColumnType("integer");
@ -423,15 +436,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("WeaponId"); b.HasIndex("WeaponId");
b.ToTable("EFClientHitStatistics"); b.ToTable("EFClientHitStatistics", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
{ {
b.Property<long>("ClientRankingHistoryId") b.Property<long>("ClientRankingHistoryId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bigint") .HasColumnType("bigint");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("ClientRankingHistoryId"));
b.Property<int>("ClientId") b.Property<int>("ClientId")
.HasColumnType("integer"); .HasColumnType("integer");
@ -469,15 +483,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("ZScore"); b.HasIndex("ZScore");
b.ToTable("EFClientRankingHistory"); b.ToTable("EFClientRankingHistory", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
{ {
b.Property<int>("RatingHistoryId") b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RatingHistoryId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -489,7 +504,7 @@ namespace Data.Migrations.Postgresql
b.HasIndex("ClientId"); b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory"); b.ToTable("EFClientRatingHistory", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
@ -547,26 +562,27 @@ namespace Data.Migrations.Postgresql
b.HasIndex("ClientId", "TimePlayed", "ZScore"); b.HasIndex("ClientId", "TimePlayed", "ZScore");
b.ToTable("EFClientStatistics"); b.ToTable("EFClientStatistics", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
{ {
b.Property<int>("HitLocationCountId") b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("HitLocationCountId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<int>("EFClientStatisticsClientId") b.Property<int>("EFClientStatisticsClientId")
.HasColumnName("EFClientStatisticsClientId") .HasColumnType("integer")
.HasColumnType("integer"); .HasColumnName("EFClientStatisticsClientId");
b.Property<long>("EFClientStatisticsServerId") b.Property<long>("EFClientStatisticsServerId")
.HasColumnName("EFClientStatisticsServerId") .HasColumnType("bigint")
.HasColumnType("bigint"); .HasColumnName("EFClientStatisticsServerId");
b.Property<int>("HitCount") b.Property<int>("HitCount")
.HasColumnType("integer"); .HasColumnType("integer");
@ -586,15 +602,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId");
b.ToTable("EFHitLocationCounts"); b.ToTable("EFHitLocationCounts", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
{ {
b.Property<int>("RatingId") b.Property<int>("RatingId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("RatingId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -630,15 +647,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); b.HasIndex("When", "ServerId", "Performance", "ActivityAmount");
b.ToTable("EFRating"); b.ToTable("EFRating", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b =>
{ {
b.Property<int>("HitLocationId") b.Property<int>("HitLocationId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("HitLocationId"));
b.Property<DateTime>("CreatedDateTime") b.Property<DateTime>("CreatedDateTime")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
@ -657,15 +675,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("EFHitLocations"); b.ToTable("EFHitLocations", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b =>
{ {
b.Property<int>("MapId") b.Property<int>("MapId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("MapId"));
b.Property<DateTime>("CreatedDateTime") b.Property<DateTime>("CreatedDateTime")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
@ -682,15 +701,16 @@ namespace Data.Migrations.Postgresql
b.HasKey("MapId"); b.HasKey("MapId");
b.ToTable("EFMaps"); b.ToTable("EFMaps", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b =>
{ {
b.Property<int>("MeansOfDeathId") b.Property<int>("MeansOfDeathId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("MeansOfDeathId"));
b.Property<DateTime>("CreatedDateTime") b.Property<DateTime>("CreatedDateTime")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
@ -707,15 +727,16 @@ namespace Data.Migrations.Postgresql
b.HasKey("MeansOfDeathId"); b.HasKey("MeansOfDeathId");
b.ToTable("EFMeansOfDeath"); b.ToTable("EFMeansOfDeath", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b =>
{ {
b.Property<int>("WeaponId") b.Property<int>("WeaponId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("WeaponId"));
b.Property<DateTime>("CreatedDateTime") b.Property<DateTime>("CreatedDateTime")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
@ -734,15 +755,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("EFWeapons"); b.ToTable("EFWeapons", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b =>
{ {
b.Property<int>("WeaponAttachmentId") b.Property<int>("WeaponAttachmentId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("WeaponAttachmentId"));
b.Property<DateTime>("CreatedDateTime") b.Property<DateTime>("CreatedDateTime")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
@ -759,15 +781,16 @@ namespace Data.Migrations.Postgresql
b.HasKey("WeaponAttachmentId"); b.HasKey("WeaponAttachmentId");
b.ToTable("EFWeaponAttachments"); b.ToTable("EFWeaponAttachments", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
{ {
b.Property<int>("WeaponAttachmentComboId") b.Property<int>("WeaponAttachmentComboId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("WeaponAttachmentComboId"));
b.Property<int>("Attachment1Id") b.Property<int>("Attachment1Id")
.HasColumnType("integer"); .HasColumnType("integer");
@ -795,15 +818,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("Attachment3Id"); b.HasIndex("Attachment3Id");
b.ToTable("EFWeaponAttachmentCombos"); b.ToTable("EFWeaponAttachmentCombos", (string)null);
}); });
modelBuilder.Entity("Data.Models.EFAlias", b => modelBuilder.Entity("Data.Models.EFAlias", b =>
{ {
b.Property<int>("AliasId") b.Property<int>("AliasId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("AliasId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -819,12 +843,17 @@ namespace Data.Migrations.Postgresql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("character varying(24)") .HasMaxLength(24)
.HasMaxLength(24); .HasColumnType("character varying(24)");
b.Property<string>("SearchableIPAddress")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("text")
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
b.Property<string>("SearchableName") b.Property<string>("SearchableName")
.HasColumnType("character varying(24)") .HasMaxLength(24)
.HasMaxLength(24); .HasColumnType("character varying(24)");
b.HasKey("AliasId"); b.HasKey("AliasId");
@ -834,41 +863,45 @@ namespace Data.Migrations.Postgresql
b.HasIndex("Name"); b.HasIndex("Name");
b.HasIndex("SearchableIPAddress");
b.HasIndex("SearchableName"); b.HasIndex("SearchableName");
b.HasIndex("Name", "IPAddress"); b.HasIndex("Name", "IPAddress");
b.ToTable("EFAlias"); b.ToTable("EFAlias", (string)null);
}); });
modelBuilder.Entity("Data.Models.EFAliasLink", b => modelBuilder.Entity("Data.Models.EFAliasLink", b =>
{ {
b.Property<int>("AliasLinkId") b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("AliasLinkId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.HasKey("AliasLinkId"); b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks"); b.ToTable("EFAliasLinks", (string)null);
}); });
modelBuilder.Entity("Data.Models.EFChangeHistory", b => modelBuilder.Entity("Data.Models.EFChangeHistory", b =>
{ {
b.Property<int>("ChangeHistoryId") b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ChangeHistoryId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<string>("Comment") b.Property<string>("Comment")
.HasColumnType("character varying(128)") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("character varying(128)");
b.Property<string>("CurrentValue") b.Property<string>("CurrentValue")
.HasColumnType("text"); .HasColumnType("text");
@ -900,8 +933,9 @@ namespace Data.Migrations.Postgresql
{ {
b.Property<int>("MetaId") b.Property<int>("MetaId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("MetaId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -917,8 +951,8 @@ namespace Data.Migrations.Postgresql
b.Property<string>("Key") b.Property<string>("Key")
.IsRequired() .IsRequired()
.HasColumnType("character varying(32)") .HasMaxLength(32)
.HasMaxLength(32); .HasColumnType("character varying(32)");
b.Property<int?>("LinkedMetaId") b.Property<int?>("LinkedMetaId")
.HasColumnType("integer"); .HasColumnType("integer");
@ -945,8 +979,9 @@ namespace Data.Migrations.Postgresql
{ {
b.Property<int>("PenaltyId") b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("PenaltyId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -960,7 +995,7 @@ namespace Data.Migrations.Postgresql
b.Property<bool>("IsEvadedOffense") b.Property<bool>("IsEvadedOffense")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<int>("LinkId") b.Property<int?>("LinkId")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<int>("OffenderId") b.Property<int>("OffenderId")
@ -987,15 +1022,50 @@ namespace Data.Migrations.Postgresql
b.HasIndex("PunisherId"); b.HasIndex("PunisherId");
b.ToTable("EFPenalties"); b.ToTable("EFPenalties", (string)null);
});
modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b =>
{
b.Property<int>("PenaltyIdentifierId")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("PenaltyIdentifierId"));
b.Property<DateTime>("CreatedDateTime")
.HasColumnType("timestamp without time zone");
b.Property<int?>("IPv4Address")
.HasColumnType("integer");
b.Property<long>("NetworkId")
.HasColumnType("bigint");
b.Property<int>("PenaltyId")
.HasColumnType("integer");
b.Property<DateTime?>("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 => modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
{ {
b.Property<int>("InboxMessageId") b.Property<int>("InboxMessageId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("InboxMessageId"));
b.Property<DateTime>("CreatedDateTime") b.Property<DateTime>("CreatedDateTime")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
@ -1054,15 +1124,16 @@ namespace Data.Migrations.Postgresql
b.HasKey("ServerId"); b.HasKey("ServerId");
b.ToTable("EFServers"); b.ToTable("EFServers", (string)null);
}); });
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
{ {
b.Property<long>("ServerSnapshotId") b.Property<long>("ServerSnapshotId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("bigint") .HasColumnType("bigint");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("ServerSnapshotId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -1073,6 +1144,9 @@ namespace Data.Migrations.Postgresql
b.Property<int>("ClientCount") b.Property<int>("ClientCount")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<bool?>("ConnectionInterrupted")
.HasColumnType("boolean");
b.Property<int>("MapId") b.Property<int>("MapId")
.HasColumnType("integer"); .HasColumnType("integer");
@ -1088,15 +1162,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFServerSnapshot"); b.ToTable("EFServerSnapshot", (string)null);
}); });
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
{ {
b.Property<int>("StatisticId") b.Property<int>("StatisticId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("StatisticId"));
b.Property<bool>("Active") b.Property<bool>("Active")
.HasColumnType("boolean"); .HasColumnType("boolean");
@ -1114,15 +1189,16 @@ namespace Data.Migrations.Postgresql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFServerStatistics"); b.ToTable("EFServerStatistics", (string)null);
}); });
modelBuilder.Entity("Data.Models.Vector3", b => modelBuilder.Entity("Data.Models.Vector3", b =>
{ {
b.Property<int>("Vector3Id") b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Vector3Id"));
b.Property<float>("X") b.Property<float>("X")
.HasColumnType("real"); .HasColumnType("real");
@ -1135,7 +1211,7 @@ namespace Data.Migrations.Postgresql
b.HasKey("Vector3Id"); b.HasKey("Vector3Id");
b.ToTable("Vector3"); b.ToTable("Vector3", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
@ -1151,6 +1227,10 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("Vector3Id") .HasForeignKey("Vector3Id")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Snapshot");
b.Navigation("Vector");
}); });
modelBuilder.Entity("Data.Models.Client.EFClient", b => modelBuilder.Entity("Data.Models.Client.EFClient", b =>
@ -1166,6 +1246,10 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("CurrentAliasId") .HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("AliasLink");
b.Navigation("CurrentAlias");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
@ -1181,6 +1265,10 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientKill", b => modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
@ -1214,6 +1302,18 @@ namespace Data.Migrations.Postgresql
b.HasOne("Data.Models.Vector3", "ViewAngles") b.HasOne("Data.Models.Vector3", "ViewAngles")
.WithMany() .WithMany()
.HasForeignKey("ViewAnglesVector3Id"); .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 => modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
@ -1229,6 +1329,10 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
@ -1266,6 +1370,18 @@ namespace Data.Migrations.Postgresql
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .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 => modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
@ -1295,6 +1411,18 @@ namespace Data.Migrations.Postgresql
b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon")
.WithMany() .WithMany()
.HasForeignKey("WeaponId"); .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 => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
@ -1308,6 +1436,10 @@ namespace Data.Migrations.Postgresql
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
@ -1317,6 +1449,8 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("ClientId") .HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
@ -1332,6 +1466,10 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
@ -1353,6 +1491,10 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
@ -1366,6 +1508,10 @@ namespace Data.Migrations.Postgresql
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
b.Navigation("RatingHistory");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
@ -1383,6 +1529,12 @@ namespace Data.Migrations.Postgresql
b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3")
.WithMany() .WithMany()
.HasForeignKey("Attachment3Id"); .HasForeignKey("Attachment3Id");
b.Navigation("Attachment1");
b.Navigation("Attachment2");
b.Navigation("Attachment3");
}); });
modelBuilder.Entity("Data.Models.EFAlias", b => modelBuilder.Entity("Data.Models.EFAlias", b =>
@ -1392,6 +1544,8 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("LinkId") .HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.Navigation("Link");
}); });
modelBuilder.Entity("Data.Models.EFMeta", b => modelBuilder.Entity("Data.Models.EFMeta", b =>
@ -1404,15 +1558,17 @@ namespace Data.Migrations.Postgresql
.WithMany() .WithMany()
.HasForeignKey("LinkedMetaId") .HasForeignKey("LinkedMetaId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.Navigation("Client");
b.Navigation("LinkedMeta");
}); });
modelBuilder.Entity("Data.Models.EFPenalty", b => modelBuilder.Entity("Data.Models.EFPenalty", b =>
{ {
b.HasOne("Data.Models.EFAliasLink", "Link") b.HasOne("Data.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties") .WithMany("ReceivedPenalties")
.HasForeignKey("LinkId") .HasForeignKey("LinkId");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Data.Models.Client.EFClient", "Offender") b.HasOne("Data.Models.Client.EFClient", "Offender")
.WithMany("ReceivedPenalties") .WithMany("ReceivedPenalties")
@ -1425,6 +1581,23 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("PunisherId") .HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .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 => modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
@ -1444,6 +1617,12 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("SourceClientId") .HasForeignKey("SourceClientId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("DestinationClient");
b.Navigation("Server");
b.Navigation("SourceClient");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
@ -1459,6 +1638,10 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Map");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
@ -1468,6 +1651,39 @@ namespace Data.Migrations.Postgresql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .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 #pragma warning restore 612, 618
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Sqlite
{
public partial class AddEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EFPenaltyIdentifiers",
columns: table => new
{
PenaltyIdentifierId = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
IPv4Address = table.Column<int>(type: "INTEGER", nullable: true),
NetworkId = table.Column<long>(type: "INTEGER", nullable: false),
PenaltyId = table.Column<int>(type: "INTEGER", nullable: false),
Active = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EFPenaltyIdentifiers", x => x.PenaltyIdentifierId);
table.ForeignKey(
name: "FK_EFPenaltyIdentifiers_EFPenalties_PenaltyId",
column: x => x.PenaltyId,
principalTable: "EFPenalties",
principalColumn: "PenaltyId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_IPv4Address",
table: "EFPenaltyIdentifiers",
column: "IPv4Address");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_NetworkId",
table: "EFPenaltyIdentifiers",
column: "NetworkId");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_PenaltyId",
table: "EFPenaltyIdentifiers",
column: "PenaltyId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EFPenaltyIdentifiers");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Sqlite
{
public partial class MakeEFPenaltyLinkIdNullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "INTEGER",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER");
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "INTEGER",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "INTEGER",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId",
onDelete: ReferentialAction.Cascade);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Sqlite
{
public partial class AddAuditFieldsToEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Active",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<DateTime>(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers",
type: "TEXT",
nullable: false,
defaultValue: DateTime.UtcNow);
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.DropColumn(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<bool>(
name: "Active",
table: "EFPenaltyIdentifiers",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Sqlite
{
public partial class AddConnectionInterruptedToEFServerSnapshot : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "ConnectionInterrupted",
table: "EFServerSnapshot",
type: "INTEGER",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ConnectionInterrupted",
table: "EFServerSnapshot");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Sqlite
{
public partial class AddSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SearchableIPAddress",
table: "EFAlias",
type: "TEXT",
nullable: true,
computedColumnSql: "((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SearchableIPAddress",
table: "EFAlias");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Sqlite
{
public partial class AddIndexToSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias",
column: "SearchableIPAddress");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Sqlite
{
public partial class AddGameToEFClient : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "GameName",
table: "EFClients",
type: "INTEGER",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GameName",
table: "EFClients");
}
}
}

View File

@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Data.Migrations.Sqlite namespace Data.Migrations.Sqlite
{ {
[DbContext(typeof(SqliteDatabaseContext))] [DbContext(typeof(SqliteDatabaseContext))]
@ -13,8 +15,7 @@ namespace Data.Migrations.Sqlite
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
.HasAnnotation("ProductVersion", "3.1.10");
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
{ {
@ -37,7 +38,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("Vector3Id"); b.HasIndex("Vector3Id");
b.ToTable("EFACSnapshotVector3"); b.ToTable("EFACSnapshotVector3", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClient", b => modelBuilder.Entity("Data.Models.Client.EFClient", b =>
@ -61,6 +62,9 @@ namespace Data.Migrations.Sqlite
b.Property<DateTime>("FirstConnection") b.Property<DateTime>("FirstConnection")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int?>("GameName")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastConnection") b.Property<DateTime>("LastConnection")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -91,7 +95,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("NetworkId") b.HasIndex("NetworkId")
.IsUnique(); .IsUnique();
b.ToTable("EFClients"); b.ToTable("EFClients", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
@ -123,7 +127,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFClientConnectionHistory"); b.ToTable("EFClientConnectionHistory", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClientKill", b => modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
@ -197,7 +201,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("ViewAnglesVector3Id"); b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills"); b.ToTable("EFClientKills", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
@ -232,7 +236,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("TimeSent"); b.HasIndex("TimeSent");
b.ToTable("EFClientMessages"); b.ToTable("EFClientMessages", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
@ -339,7 +343,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFACSnapshot"); b.ToTable("EFACSnapshot", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
@ -413,7 +417,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("WeaponId"); b.HasIndex("WeaponId");
b.ToTable("EFClientHitStatistics"); b.ToTable("EFClientHitStatistics", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
@ -458,7 +462,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("ZScore"); b.HasIndex("ZScore");
b.ToTable("EFClientRankingHistory"); b.ToTable("EFClientRankingHistory", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
@ -477,7 +481,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("ClientId"); b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory"); b.ToTable("EFClientRatingHistory", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
@ -535,7 +539,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("ClientId", "TimePlayed", "ZScore"); b.HasIndex("ClientId", "TimePlayed", "ZScore");
b.ToTable("EFClientStatistics"); b.ToTable("EFClientStatistics", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
@ -548,12 +552,12 @@ namespace Data.Migrations.Sqlite
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("EFClientStatisticsClientId") b.Property<int>("EFClientStatisticsClientId")
.HasColumnName("EFClientStatisticsClientId") .HasColumnType("INTEGER")
.HasColumnType("INTEGER"); .HasColumnName("EFClientStatisticsClientId");
b.Property<long>("EFClientStatisticsServerId") b.Property<long>("EFClientStatisticsServerId")
.HasColumnName("EFClientStatisticsServerId") .HasColumnType("INTEGER")
.HasColumnType("INTEGER"); .HasColumnName("EFClientStatisticsServerId");
b.Property<int>("HitCount") b.Property<int>("HitCount")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -573,7 +577,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId");
b.ToTable("EFHitLocationCounts"); b.ToTable("EFHitLocationCounts", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
@ -616,7 +620,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); b.HasIndex("When", "ServerId", "Performance", "ActivityAmount");
b.ToTable("EFRating"); b.ToTable("EFRating", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b =>
@ -642,7 +646,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("EFHitLocations"); b.ToTable("EFHitLocations", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b =>
@ -666,7 +670,7 @@ namespace Data.Migrations.Sqlite
b.HasKey("MapId"); b.HasKey("MapId");
b.ToTable("EFMaps"); b.ToTable("EFMaps", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b =>
@ -690,7 +694,7 @@ namespace Data.Migrations.Sqlite
b.HasKey("MeansOfDeathId"); b.HasKey("MeansOfDeathId");
b.ToTable("EFMeansOfDeath"); b.ToTable("EFMeansOfDeath", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b =>
@ -716,7 +720,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("EFWeapons"); b.ToTable("EFWeapons", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b =>
@ -740,7 +744,7 @@ namespace Data.Migrations.Sqlite
b.HasKey("WeaponAttachmentId"); b.HasKey("WeaponAttachmentId");
b.ToTable("EFWeaponAttachments"); b.ToTable("EFWeaponAttachments", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
@ -775,7 +779,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("Attachment3Id"); b.HasIndex("Attachment3Id");
b.ToTable("EFWeaponAttachmentCombos"); b.ToTable("EFWeaponAttachmentCombos", (string)null);
}); });
modelBuilder.Entity("Data.Models.EFAlias", b => modelBuilder.Entity("Data.Models.EFAlias", b =>
@ -798,12 +802,17 @@ namespace Data.Migrations.Sqlite
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(24)
.HasColumnType("TEXT");
b.Property<string>("SearchableIPAddress")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasMaxLength(24); .HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
b.Property<string>("SearchableName") b.Property<string>("SearchableName")
.HasColumnType("TEXT") .HasMaxLength(24)
.HasMaxLength(24); .HasColumnType("TEXT");
b.HasKey("AliasId"); b.HasKey("AliasId");
@ -813,11 +822,13 @@ namespace Data.Migrations.Sqlite
b.HasIndex("Name"); b.HasIndex("Name");
b.HasIndex("SearchableIPAddress");
b.HasIndex("SearchableName"); b.HasIndex("SearchableName");
b.HasIndex("Name", "IPAddress"); b.HasIndex("Name", "IPAddress");
b.ToTable("EFAlias"); b.ToTable("EFAlias", (string)null);
}); });
modelBuilder.Entity("Data.Models.EFAliasLink", b => modelBuilder.Entity("Data.Models.EFAliasLink", b =>
@ -831,7 +842,7 @@ namespace Data.Migrations.Sqlite
b.HasKey("AliasLinkId"); b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks"); b.ToTable("EFAliasLinks", (string)null);
}); });
modelBuilder.Entity("Data.Models.EFChangeHistory", b => modelBuilder.Entity("Data.Models.EFChangeHistory", b =>
@ -844,8 +855,8 @@ namespace Data.Migrations.Sqlite
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Comment") b.Property<string>("Comment")
.HasColumnType("TEXT") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("TEXT");
b.Property<string>("CurrentValue") b.Property<string>("CurrentValue")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -893,8 +904,8 @@ namespace Data.Migrations.Sqlite
b.Property<string>("Key") b.Property<string>("Key")
.IsRequired() .IsRequired()
.HasColumnType("TEXT") .HasMaxLength(32)
.HasMaxLength(32); .HasColumnType("TEXT");
b.Property<int?>("LinkedMetaId") b.Property<int?>("LinkedMetaId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -935,7 +946,7 @@ namespace Data.Migrations.Sqlite
b.Property<bool>("IsEvadedOffense") b.Property<bool>("IsEvadedOffense")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("LinkId") b.Property<int?>("LinkId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("OffenderId") b.Property<int>("OffenderId")
@ -962,7 +973,39 @@ namespace Data.Migrations.Sqlite
b.HasIndex("PunisherId"); b.HasIndex("PunisherId");
b.ToTable("EFPenalties"); b.ToTable("EFPenalties", (string)null);
});
modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b =>
{
b.Property<int>("PenaltyIdentifierId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedDateTime")
.HasColumnType("TEXT");
b.Property<int?>("IPv4Address")
.HasColumnType("INTEGER");
b.Property<long>("NetworkId")
.HasColumnType("INTEGER");
b.Property<int>("PenaltyId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("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 => modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
@ -1028,7 +1071,7 @@ namespace Data.Migrations.Sqlite
b.HasKey("ServerId"); b.HasKey("ServerId");
b.ToTable("EFServers"); b.ToTable("EFServers", (string)null);
}); });
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
@ -1046,6 +1089,9 @@ namespace Data.Migrations.Sqlite
b.Property<int>("ClientCount") b.Property<int>("ClientCount")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool?>("ConnectionInterrupted")
.HasColumnType("INTEGER");
b.Property<int>("MapId") b.Property<int>("MapId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -1061,7 +1107,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFServerSnapshot"); b.ToTable("EFServerSnapshot", (string)null);
}); });
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
@ -1086,7 +1132,7 @@ namespace Data.Migrations.Sqlite
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFServerStatistics"); b.ToTable("EFServerStatistics", (string)null);
}); });
modelBuilder.Entity("Data.Models.Vector3", b => modelBuilder.Entity("Data.Models.Vector3", b =>
@ -1106,7 +1152,7 @@ namespace Data.Migrations.Sqlite
b.HasKey("Vector3Id"); b.HasKey("Vector3Id");
b.ToTable("Vector3"); b.ToTable("Vector3", (string)null);
}); });
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
@ -1122,6 +1168,10 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("Vector3Id") .HasForeignKey("Vector3Id")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Snapshot");
b.Navigation("Vector");
}); });
modelBuilder.Entity("Data.Models.Client.EFClient", b => modelBuilder.Entity("Data.Models.Client.EFClient", b =>
@ -1137,6 +1187,10 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("CurrentAliasId") .HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("AliasLink");
b.Navigation("CurrentAlias");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
@ -1152,6 +1206,10 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientKill", b => modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
@ -1185,6 +1243,18 @@ namespace Data.Migrations.Sqlite
b.HasOne("Data.Models.Vector3", "ViewAngles") b.HasOne("Data.Models.Vector3", "ViewAngles")
.WithMany() .WithMany()
.HasForeignKey("ViewAnglesVector3Id"); .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 => modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
@ -1200,6 +1270,10 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
@ -1237,6 +1311,18 @@ namespace Data.Migrations.Sqlite
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .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 => modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
@ -1266,6 +1352,18 @@ namespace Data.Migrations.Sqlite
b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon")
.WithMany() .WithMany()
.HasForeignKey("WeaponId"); .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 => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
@ -1279,6 +1377,10 @@ namespace Data.Migrations.Sqlite
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
@ -1288,6 +1390,8 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("ClientId") .HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
@ -1303,6 +1407,10 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
@ -1324,6 +1432,10 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
@ -1337,6 +1449,10 @@ namespace Data.Migrations.Sqlite
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
b.Navigation("RatingHistory");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
@ -1354,6 +1470,12 @@ namespace Data.Migrations.Sqlite
b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3")
.WithMany() .WithMany()
.HasForeignKey("Attachment3Id"); .HasForeignKey("Attachment3Id");
b.Navigation("Attachment1");
b.Navigation("Attachment2");
b.Navigation("Attachment3");
}); });
modelBuilder.Entity("Data.Models.EFAlias", b => modelBuilder.Entity("Data.Models.EFAlias", b =>
@ -1363,6 +1485,8 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("LinkId") .HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.Navigation("Link");
}); });
modelBuilder.Entity("Data.Models.EFMeta", b => modelBuilder.Entity("Data.Models.EFMeta", b =>
@ -1375,15 +1499,17 @@ namespace Data.Migrations.Sqlite
.WithMany() .WithMany()
.HasForeignKey("LinkedMetaId") .HasForeignKey("LinkedMetaId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.Navigation("Client");
b.Navigation("LinkedMeta");
}); });
modelBuilder.Entity("Data.Models.EFPenalty", b => modelBuilder.Entity("Data.Models.EFPenalty", b =>
{ {
b.HasOne("Data.Models.EFAliasLink", "Link") b.HasOne("Data.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties") .WithMany("ReceivedPenalties")
.HasForeignKey("LinkId") .HasForeignKey("LinkId");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Data.Models.Client.EFClient", "Offender") b.HasOne("Data.Models.Client.EFClient", "Offender")
.WithMany("ReceivedPenalties") .WithMany("ReceivedPenalties")
@ -1396,6 +1522,23 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("PunisherId") .HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .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 => modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
@ -1415,6 +1558,12 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("SourceClientId") .HasForeignKey("SourceClientId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("DestinationClient");
b.Navigation("Server");
b.Navigation("SourceClient");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
@ -1430,6 +1579,10 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Map");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
@ -1439,6 +1592,39 @@ namespace Data.Migrations.Sqlite
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .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 #pragma warning restore 612, 618
} }

View File

@ -10,4 +10,4 @@ namespace Stats.Models
public DateTime CreatedDateTime { get; set; } = DateTime.UtcNow; public DateTime CreatedDateTime { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedDateTime { get; set; } public DateTime? UpdatedDateTime { get; set; }
} }
} }

View File

@ -63,6 +63,7 @@ namespace Data.Models.Client
public DateTime FirstConnection { get; set; } public DateTime FirstConnection { get; set; }
[Required] [Required]
public DateTime LastConnection { get; set; } public DateTime LastConnection { get; set; }
public Reference.Game? GameName { get; set; } = Reference.Game.UKN;
public bool Masked { get; set; } public bool Masked { get; set; }
[Required] [Required]
public int AliasLinkId { get; set; } public int AliasLinkId { get; set; }

View File

@ -19,6 +19,7 @@ namespace Data.Models
public string SearchableName { get; set; } public string SearchableName { get; set; }
[Required] [Required]
public int? IPAddress { get; set; } public int? IPAddress { get; set; }
public string SearchableIPAddress { get; set; }
[Required] [Required]
public DateTime DateAdded { get; set; } public DateTime DateAdded { get; set; }

View File

@ -10,8 +10,8 @@ namespace Data.Models
/// </summary> /// </summary>
public class EFMeta : SharedEntity public class EFMeta : SharedEntity
{ {
public const string ClientTagName = nameof(ClientTagName); public const string ClientTagNameV2 = nameof(ClientTagNameV2);
public const string ClientTag = nameof(ClientTag); public const string ClientTagV2 = nameof(ClientTagV2);
[Key] [Key]
public int MetaId { get; set; } public int MetaId { get; set; }

View File

@ -23,8 +23,7 @@ namespace Data.Models
[Key] [Key]
public int PenaltyId { get; set; } public int PenaltyId { get; set; }
[Required] public int? LinkId { get; set; }
public int LinkId { get; set; }
[ForeignKey("LinkId")] [ForeignKey("LinkId")]
public virtual EFAliasLink Link { get; set; } public virtual EFAliasLink Link { get; set; }
[Required] [Required]

View File

@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Stats.Models;
namespace Data.Models;
public class EFPenaltyIdentifier : AuditFields
{
[Key]
public int PenaltyIdentifierId { get; set; }
public int? IPv4Address { get; set; }
[Required]
public long NetworkId { get; set; }
[Required]
public int PenaltyId { get; set; }
[ForeignKey(nameof(PenaltyId))]
public EFPenalty Penalty { get; set; }
}

View File

@ -32,5 +32,7 @@ namespace Data.Models.Server
public EFMap Map { get; set; } public EFMap Map { get; set; }
public int ClientCount { get; set; } public int ClientCount { get; set; }
public bool? ConnectionInterrupted {get;set;}
} }
} }

View File

@ -53,7 +53,9 @@ steps:
script: | script: |
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)' Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
md -Force lib\open-iconic\font\css md -Force lib\open-iconic\font\css
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap.scss wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
cd lib\open-iconic\font\css
(Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/font/') | Set-Content open-iconic-bootstrap-override.scss
failOnStderr: true failOnStderr: true
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot' workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
@ -75,6 +77,7 @@ steps:
Write-Host 'Unzipping download' Write-Host 'Unzipping download'
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath) Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)
Write-Host 'Executing dotnet-bundle' Write-Host 'Executing dotnet-bundle'
$(Build.Repository.LocalPath)\dotnet-bundle.exe clean $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
$(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json $(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
failOnStderr: true failOnStderr: true
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore' workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore'

Some files were not shown because too many files have changed in this diff Show More