From 778e339a611197ff08857e44bb106f5af26e02ea Mon Sep 17 00:00:00 2001 From: RaidMax Date: Mon, 17 Aug 2020 21:21:11 -0500 Subject: [PATCH] QOL updates for profile meta implement filterable meta for issue #158 update translations and use humanizer lib with datetime/timespan for issue #80 --- Application/Application.csproj | 10 +- Application/ApplicationManager.cs | 138 +----------- .../Factories/GameServerInstanceFactory.cs | 6 +- Application/IW4MServer.cs | 23 +- Application/Main.cs | 18 +- .../AdministeredPenaltyResourceQueryHelper.cs | 64 ++++++ Application/Meta/MetaRegistration.cs | 165 ++++++++++++++ .../ReceivedPenaltyResourceQueryHelper.cs | 72 ++++++ .../Meta/UpdatedAliasResourceQueryHelper.cs | 60 +++++ Application/Misc/MetaService.cs | 187 ++++++++++++++++ .../AutomessageFeed/AutomessageFeed.csproj | 2 +- .../IW4ScriptCommands.csproj | 2 +- Plugins/LiveRadar/LiveRadar.csproj | 2 +- Plugins/Login/Login.csproj | 2 +- .../ProfanityDeterment.csproj | 2 +- Plugins/Stats/Commands/MostPlayedCommand.cs | 5 +- Plugins/Stats/Helpers/StatManager.cs | 10 +- Plugins/Stats/Plugin.cs | 193 +++++++--------- Plugins/Stats/Stats.csproj | 2 +- .../StatsWeb/Controllers/StatsController.cs | 1 + Plugins/Web/StatsWeb/Dtos/ChatSearchQuery.cs | 5 +- Plugins/Web/StatsWeb/StatsWeb.csproj | 2 +- Plugins/Web/StatsWeb/Views/Stats/Index.cshtml | 2 +- Plugins/Welcome/Welcome.csproj | 2 +- SharedLibraryCore/Commands/NativeCommands.cs | 24 +- SharedLibraryCore/Dtos/FindClientRequest.cs | 2 +- .../Meta/Requests/BaseClientMetaRequest.cs | 11 + .../Meta/Requests/ReceivedPenaltyRequest.cs | 11 + .../Responses/AdministeredPenaltyResponse.cs | 10 + .../Dtos/Meta/Responses/BaseMetaResponse.cs | 19 ++ .../Meta/Responses/InformationResponse.cs | 14 ++ .../Dtos/Meta/Responses/MessageResponse.cs | 12 + .../Meta/Responses/ReceivedPenaltyResponse.cs | 22 ++ .../Meta/Responses/UpdatedAliasResponse.cs | 22 ++ .../Dtos/Meta/WebfrontTranslationHelper.cs | 13 ++ ...PaginationInfo.cs => PaginationRequest.cs} | 8 +- SharedLibraryCore/Dtos/PenaltyInfo.cs | 4 +- SharedLibraryCore/Dtos/PlayerInfo.cs | 14 +- SharedLibraryCore/Dtos/ProfileMeta.cs | 32 --- .../Helpers/ResourceQueryHelperResult.cs | 4 +- .../Interfaces/IAuditInformationRepository.cs | 2 +- SharedLibraryCore/Interfaces/IClientMeta.cs | 31 +++ .../Interfaces/IClientMetaResponse.cs | 12 + .../Interfaces/IMetaRegistration.cs | 11 + SharedLibraryCore/Interfaces/IMetaService.cs | 51 +++++ SharedLibraryCore/Localization/Layout.cs | 13 +- SharedLibraryCore/PartialEntities/EFClient.cs | 2 +- .../QueryHelper/ClientPaginationRequest.cs | 12 + .../AuditInformationRepository.cs | 2 +- SharedLibraryCore/Services/MetaService.cs | 169 -------------- SharedLibraryCore/SharedLibraryCore.csproj | 37 ++-- SharedLibraryCore/Utilities.cs | 159 ++++++------- .../ApplicationTests/ApplicationTests.csproj | 6 +- .../DependencyInjectionExtensions.cs | 3 +- .../Fixtures/ClientGenerators.cs | 4 +- .../Fixtures/PenaltyGenerators.cs | 28 +++ Tests/ApplicationTests/QueryHelperTests.cs | 209 ++++++++++++++++++ Tests/ApplicationTests/ServerTests.cs | 4 +- Tests/ApplicationTests/StatsTests.cs | 2 +- WebfrontCore/Controllers/ActionController.cs | 2 +- WebfrontCore/Controllers/AdminController.cs | 4 +- WebfrontCore/Controllers/ClientController.cs | 68 +++--- WebfrontCore/Startup.cs | 5 + .../ProfileMetaListViewComponent.cs | 69 +++++- .../Views/Client/Profile/Index.cshtml | 83 +++++-- .../Meta/_AdministeredPenaltyResponse.cshtml | 53 +++++ .../Client/Profile/Meta/_Information.cshtml | 43 ++++ .../Profile/Meta/_MessageResponse.cshtml | 9 + .../Meta/_ReceivedPenaltyResponse.cshtml | 74 +++++++ .../Profile/Meta/_UpdatedAliasResponse.cshtml | 27 +++ .../Components/ProfileMetaList/_List.cshtml | 116 ++++------ WebfrontCore/Views/_ViewImports.cshtml | 3 +- WebfrontCore/WebfrontCore.csproj | 12 +- WebfrontCore/compilerconfig.json | 9 +- WebfrontCore/wwwroot/css/src/main.scss | 5 +- WebfrontCore/wwwroot/css/src/profile.scss | 9 +- WebfrontCore/wwwroot/js/loader.js | 12 +- WebfrontCore/wwwroot/js/profile.js | 18 +- 78 files changed, 1800 insertions(+), 775 deletions(-) create mode 100644 Application/Meta/AdministeredPenaltyResourceQueryHelper.cs create mode 100644 Application/Meta/MetaRegistration.cs create mode 100644 Application/Meta/ReceivedPenaltyResourceQueryHelper.cs create mode 100644 Application/Meta/UpdatedAliasResourceQueryHelper.cs create mode 100644 Application/Misc/MetaService.cs create mode 100644 SharedLibraryCore/Dtos/Meta/Requests/BaseClientMetaRequest.cs create mode 100644 SharedLibraryCore/Dtos/Meta/Requests/ReceivedPenaltyRequest.cs create mode 100644 SharedLibraryCore/Dtos/Meta/Responses/AdministeredPenaltyResponse.cs create mode 100644 SharedLibraryCore/Dtos/Meta/Responses/BaseMetaResponse.cs create mode 100644 SharedLibraryCore/Dtos/Meta/Responses/InformationResponse.cs create mode 100644 SharedLibraryCore/Dtos/Meta/Responses/MessageResponse.cs create mode 100644 SharedLibraryCore/Dtos/Meta/Responses/ReceivedPenaltyResponse.cs create mode 100644 SharedLibraryCore/Dtos/Meta/Responses/UpdatedAliasResponse.cs create mode 100644 SharedLibraryCore/Dtos/Meta/WebfrontTranslationHelper.cs rename SharedLibraryCore/Dtos/{PaginationInfo.cs => PaginationRequest.cs} (84%) delete mode 100644 SharedLibraryCore/Dtos/ProfileMeta.cs create mode 100644 SharedLibraryCore/Interfaces/IClientMeta.cs create mode 100644 SharedLibraryCore/Interfaces/IClientMetaResponse.cs create mode 100644 SharedLibraryCore/Interfaces/IMetaRegistration.cs create mode 100644 SharedLibraryCore/Interfaces/IMetaService.cs create mode 100644 SharedLibraryCore/QueryHelper/ClientPaginationRequest.cs delete mode 100644 SharedLibraryCore/Services/MetaService.cs create mode 100644 Tests/ApplicationTests/Fixtures/PenaltyGenerators.cs create mode 100644 Tests/ApplicationTests/QueryHelperTests.cs create mode 100644 WebfrontCore/Views/Client/Profile/Meta/_AdministeredPenaltyResponse.cshtml create mode 100644 WebfrontCore/Views/Client/Profile/Meta/_Information.cshtml create mode 100644 WebfrontCore/Views/Client/Profile/Meta/_MessageResponse.cshtml create mode 100644 WebfrontCore/Views/Client/Profile/Meta/_ReceivedPenaltyResponse.cshtml create mode 100644 WebfrontCore/Views/Client/Profile/Meta/_UpdatedAliasResponse.cshtml diff --git a/Application/Application.csproj b/Application/Application.csproj index 1951099e9..b0ef4959c 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -25,20 +25,20 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + false true true - 7.1 + Latest diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index a1980aad5..bad4832a9 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -13,6 +13,7 @@ using SharedLibraryCore.Dtos; using SharedLibraryCore.Exceptions; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; using SharedLibraryCore.Services; using System; using System.Collections; @@ -54,7 +55,7 @@ namespace IW4MAdmin.Application public IConfigurationHandler ConfigHandler; readonly IPageList PageList; private readonly Dictionary _loggers = new Dictionary(); - private readonly MetaService _metaService; + private readonly IMetaService _metaService; private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0); private readonly CancellationTokenSource _tokenSource; private readonly Dictionary> _operationLookup = new Dictionary>(); @@ -65,12 +66,14 @@ namespace IW4MAdmin.Application private readonly IEnumerable _customParserEvents; private readonly IEventHandler _eventHandler; private readonly IScriptCommandFactory _scriptCommandFactory; + private readonly IMetaRegistration _metaRegistration; public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable commands, ITranslationLookup translationLookup, IConfigurationHandler commandConfiguration, IConfigurationHandler appConfigHandler, IGameServerInstanceFactory serverInstanceFactory, IEnumerable plugins, IParserRegexFactory parserRegexFactory, IEnumerable customParserEvents, - IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory) + IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaService metaService, + IMetaRegistration metaRegistration) { MiddlewareActionHandler = actionHandler; _servers = new ConcurrentBag(); @@ -85,7 +88,7 @@ namespace IW4MAdmin.Application AdditionalRConParsers = new List() { new BaseRConParser(parserRegexFactory) }; TokenAuthenticator = new TokenAuthentication(); _logger = logger; - _metaService = new MetaService(); + _metaService = metaService; _tokenSource = new CancellationTokenSource(); _loggers.Add(0, logger); _commands = commands.ToList(); @@ -96,6 +99,7 @@ namespace IW4MAdmin.Application _customParserEvents = customParserEvents; _eventHandler = eventHandler; _scriptCommandFactory = scriptCommandFactory; + _metaRegistration = metaRegistration; Plugins = plugins; } @@ -453,133 +457,7 @@ namespace IW4MAdmin.Application await _commandConfiguration.Save(); #endregion - #region META - async Task> getProfileMeta(int clientId, int offset, int count, DateTime? startAt) - { - var metaList = new List(); - - // we don't want to return anything because it means we're trying to retrieve paged meta data - if (count > 1) - { - return metaList; - } - - var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = clientId }); - - if (lastMapMeta != null) - { - metaList.Add(new ProfileMeta() - { - Id = lastMapMeta.MetaId, - Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_MAP"], - Value = lastMapMeta.Value, - Show = true, - Type = ProfileMeta.MetaType.Information, - }); - } - - var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = clientId }); - - if (lastServerMeta != null) - { - metaList.Add(new ProfileMeta() - { - Id = lastServerMeta.MetaId, - Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_SERVER"], - Value = lastServerMeta.Value, - Show = true, - Type = ProfileMeta.MetaType.Information - }); - } - - var client = await GetClientService().Get(clientId); - - if (client == null) - { - _logger.WriteWarning($"No client found with id {clientId} when generating profile meta"); - return metaList; - } - - metaList.Add(new ProfileMeta() - { - Id = client.ClientId, - Key = $"{Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]} {Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PLAYER"]}", - Value = Math.Round(client.TotalConnectionTime / 3600.0, 1).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), - Show = true, - Column = 1, - Order = 0, - Type = ProfileMeta.MetaType.Information - }); - - metaList.Add(new ProfileMeta() - { - Id = client.ClientId, - Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_FSEEN"], - Value = Utilities.GetTimePassed(client.FirstConnection, false), - Show = true, - Column = 1, - Order = 1, - Type = ProfileMeta.MetaType.Information - }); - - metaList.Add(new ProfileMeta() - { - Id = client.ClientId, - Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_LSEEN"], - Value = Utilities.GetTimePassed(client.LastConnection, false), - Show = true, - Column = 1, - Order = 2, - Type = ProfileMeta.MetaType.Information - }); - - metaList.Add(new ProfileMeta() - { - Id = client.ClientId, - Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"], - Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), - Show = true, - Column = 1, - Order = 3, - Type = ProfileMeta.MetaType.Information - }); - - metaList.Add(new ProfileMeta() - { - 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"], - Sensitive = true, - Column = 1, - Order = 4, - Type = ProfileMeta.MetaType.Information - }); - - return metaList; - } - - async Task> getPenaltyMeta(int clientId, int offset, int count, DateTime? startAt) - { - if (count <= 1) - { - return new List(); - } - - var penalties = await GetPenaltyService().GetClientPenaltyForMetaAsync(clientId, count, offset, startAt); - - return penalties.Select(_penalty => new ProfileMeta() - { - Id = _penalty.Id, - Type = _penalty.PunisherId == clientId ? ProfileMeta.MetaType.Penalized : ProfileMeta.MetaType.ReceivedPenalty, - Value = _penalty, - When = _penalty.TimePunished, - Sensitive = _penalty.Sensitive - }) - .ToList(); - } - - MetaService.AddRuntimeMeta(getProfileMeta); - MetaService.AddRuntimeMeta(getPenaltyMeta); - #endregion + _metaRegistration.Register(); #region CUSTOM_EVENTS foreach (var customEvent in _customParserEvents.SelectMany(_events => _events.Events)) diff --git a/Application/Factories/GameServerInstanceFactory.cs b/Application/Factories/GameServerInstanceFactory.cs index afab231a0..97a532579 100644 --- a/Application/Factories/GameServerInstanceFactory.cs +++ b/Application/Factories/GameServerInstanceFactory.cs @@ -13,17 +13,19 @@ namespace IW4MAdmin.Application.Factories private readonly ITranslationLookup _translationLookup; private readonly IRConConnectionFactory _rconConnectionFactory; private readonly IGameLogReaderFactory _gameLogReaderFactory; + private readonly IMetaService _metaService; /// /// base constructor /// /// /// - public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory) + public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory, IMetaService metaService) { _translationLookup = translationLookup; _rconConnectionFactory = rconConnectionFactory; _gameLogReaderFactory = gameLogReaderFactory; + _metaService = metaService; } /// @@ -34,7 +36,7 @@ namespace IW4MAdmin.Application.Factories /// public Server CreateServer(ServerConfiguration config, IManager manager) { - return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory); + return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory, _metaService); } } } diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index aa16ca4da..4e80917c0 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -7,7 +7,6 @@ using SharedLibraryCore.Dtos; using SharedLibraryCore.Exceptions; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; -using SharedLibraryCore.Services; using System; using System.Collections.Generic; using System.IO; @@ -26,15 +25,17 @@ namespace IW4MAdmin private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex; public GameLogEventDetection LogEvent; private readonly ITranslationLookup _translationLookup; + private readonly IMetaService _metaService; private const int REPORT_FLAG_COUNT = 4; private int lastGameTime = 0; public int Id { get; private set; } public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup, - IRConConnectionFactory connectionFactory, IGameLogReaderFactory gameLogReaderFactory) : base(cfg, mgr, connectionFactory, gameLogReaderFactory) + IRConConnectionFactory connectionFactory, IGameLogReaderFactory gameLogReaderFactory, IMetaService metaService) : base(cfg, mgr, connectionFactory, gameLogReaderFactory) { _translationLookup = lookup; + _metaService = metaService; } override public async Task OnClientConnected(EFClient clientFromLog) @@ -475,8 +476,8 @@ namespace IW4MAdmin Time = DateTime.UtcNow }); - await new MetaService().AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin); - await new MetaService().AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin); + await _metaService.AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin); + await _metaService.AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin); } else if (E.Type == GameEvent.EventType.PreDisconnect) @@ -610,13 +611,10 @@ namespace IW4MAdmin if (E.Type == GameEvent.EventType.Broadcast) { -#if DEBUG == false - // this is a little ugly but I don't want to change the abstract class - if (E.Data != null) + if (!Utilities.IsDevelopment && E.Data != null) // hides broadcast when in development mode { await E.Owner.ExecuteCommandAsync(E.Data); } -#endif } lock (ChatHistory) @@ -1083,9 +1081,11 @@ namespace IW4MAdmin Logger.WriteInfo($"Log file is {LogPath}"); _ = Task.Run(() => LogEvent.PollForChanges()); -#if !DEBUG - Broadcast(loc["BROADCAST_ONLINE"]); -#endif + + if (!Utilities.IsDevelopment) + { + Broadcast(loc["BROADCAST_ONLINE"]); + } } public Uri[] GenerateUriForLog(string logPath, string gameLogServerUrl) @@ -1233,6 +1233,7 @@ namespace IW4MAdmin if (targetClient.IsIngame) { string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"^7{loc["SERVER_TB_TEXT"]}- ^5{Reason}"); + Logger.WriteDebug($"Executing tempban kick command for {targetClient}"); await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick); } } diff --git a/Application/Main.cs b/Application/Main.cs index 45355be12..9959440a2 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -2,16 +2,22 @@ using IW4MAdmin.Application.EventParsers; using IW4MAdmin.Application.Factories; using IW4MAdmin.Application.Helpers; +using IW4MAdmin.Application.Meta; using IW4MAdmin.Application.Migration; using IW4MAdmin.Application.Misc; using Microsoft.Extensions.DependencyInjection; using RestEase; using SharedLibraryCore; using SharedLibraryCore.Configuration; +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Dtos.Meta.Requests; +using SharedLibraryCore.Dtos.Meta.Responses; using SharedLibraryCore.Exceptions; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; using SharedLibraryCore.Repositories; +using SharedLibraryCore.Services; using System; using System.Linq; using System.Text; @@ -128,7 +134,11 @@ namespace IW4MAdmin.Application await ApplicationTask; } - catch { } + catch (Exception e) + { + string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"]; + Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}"); + } if (ServerManager.IsRestartRequested) { @@ -235,6 +245,12 @@ namespace IW4MAdmin.Application .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton, ClientService>() + .AddSingleton() + .AddSingleton() + .AddSingleton, ReceivedPenaltyResourceQueryHelper>() + .AddSingleton, AdministeredPenaltyResourceQueryHelper>() + .AddSingleton, UpdatedAliasResourceQueryHelper>() .AddTransient() .AddSingleton(_serviceProvider => { diff --git a/Application/Meta/AdministeredPenaltyResourceQueryHelper.cs b/Application/Meta/AdministeredPenaltyResourceQueryHelper.cs new file mode 100644 index 000000000..1276a4a6a --- /dev/null +++ b/Application/Meta/AdministeredPenaltyResourceQueryHelper.cs @@ -0,0 +1,64 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Dtos.Meta.Responses; +using SharedLibraryCore.Helpers; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; + +namespace IW4MAdmin.Application.Meta +{ + /// + /// implementation of IResourceQueryHelper + /// query helper that retrieves administered penalties for provided client id + /// + public class AdministeredPenaltyResourceQueryHelper : IResourceQueryHelper + { + private readonly ILogger _logger; + private readonly IDatabaseContextFactory _contextFactory; + + public AdministeredPenaltyResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory) + { + _contextFactory = contextFactory; + _logger = logger; + } + + public async Task> QueryResource(ClientPaginationRequest query) + { + using var ctx = _contextFactory.CreateContext(enableTracking: false); + + var iqPenalties = ctx.Penalties.AsNoTracking() + .Where(_penalty => query.ClientId == _penalty.PunisherId) + .Where(_penalty => _penalty.When < query.Before) + .OrderByDescending(_penalty => _penalty.When); + + var penalties = await iqPenalties + .Take(query.Count) + .Select(_penalty => new AdministeredPenaltyResponse() + { + PenaltyId = _penalty.PenaltyId, + Offense = _penalty.Offense, + AutomatedOffense = _penalty.AutomatedOffense, + ClientId = _penalty.OffenderId, + OffenderName = _penalty.Offender.CurrentAlias.Name, + OffenderClientId = _penalty.Offender.ClientId, + PunisherClientId = _penalty.PunisherId, + PunisherName = _penalty.Punisher.CurrentAlias.Name, + PenaltyType = _penalty.Type, + When = _penalty.When, + ExpirationDate = _penalty.Expires, + IsLinked = _penalty.OffenderId != query.ClientId, + IsSensitive = _penalty.Type == EFPenalty.PenaltyType.Flag + }) + .ToListAsync(); + + return new ResourceQueryHelperResult + { + // todo: might need to do count at some point + RetrievedResultCount = penalties.Count, + Results = penalties + }; + } + } +} diff --git a/Application/Meta/MetaRegistration.cs b/Application/Meta/MetaRegistration.cs new file mode 100644 index 000000000..479f87bb3 --- /dev/null +++ b/Application/Meta/MetaRegistration.cs @@ -0,0 +1,165 @@ +using SharedLibraryCore; +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Dtos.Meta.Responses; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace IW4MAdmin.Application.Meta +{ + public class MetaRegistration : IMetaRegistration + { + private readonly ILogger _logger; + private ITranslationLookup _transLookup; + private readonly IMetaService _metaService; + private readonly IEntityService _clientEntityService; + private readonly IResourceQueryHelper _receivedPenaltyHelper; + private readonly IResourceQueryHelper _administeredPenaltyHelper; + private readonly IResourceQueryHelper _updatedAliasHelper; + + public MetaRegistration(ILogger logger, IMetaService metaService, ITranslationLookup transLookup, IEntityService clientEntityService, + IResourceQueryHelper receivedPenaltyHelper, + IResourceQueryHelper administeredPenaltyHelper, + IResourceQueryHelper updatedAliasHelper) + { + _logger = logger; + _transLookup = transLookup; + _metaService = metaService; + _clientEntityService = clientEntityService; + _receivedPenaltyHelper = receivedPenaltyHelper; + _administeredPenaltyHelper = administeredPenaltyHelper; + _updatedAliasHelper = updatedAliasHelper; + } + + public void Register() + { + _metaService.AddRuntimeMeta(MetaType.Information, GetProfileMeta); + _metaService.AddRuntimeMeta(MetaType.ReceivedPenalty, GetReceivedPenaltiesMeta); + _metaService.AddRuntimeMeta(MetaType.Penalized, GetAdministeredPenaltiesMeta); + _metaService.AddRuntimeMeta(MetaType.AliasUpdate, GetUpdatedAliasMeta); + } + + private async Task> GetProfileMeta(ClientPaginationRequest request) + { + var metaList = new List(); + var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = request.ClientId }); + + if (lastMapMeta != null) + { + metaList.Add(new InformationResponse() + { + ClientId = request.ClientId, + MetaId = lastMapMeta.MetaId, + Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_MAP"], + Value = lastMapMeta.Value, + ShouldDisplay = true, + Type = MetaType.Information, + Column = 1, + Order = 6 + }); + } + + var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = request.ClientId }); + + if (lastServerMeta != null) + { + metaList.Add(new InformationResponse() + { + ClientId = request.ClientId, + MetaId = lastServerMeta.MetaId, + Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_SERVER"], + Value = lastServerMeta.Value, + ShouldDisplay = true, + Type = MetaType.Information, + Column = 0, + Order = 6 + }); + } + + var client = await _clientEntityService.Get(request.ClientId); + + if (client == null) + { + _logger.WriteWarning($"No client found with id {request.ClientId} when generating profile meta"); + return metaList; + } + + metaList.Add(new InformationResponse() + { + ClientId = client.ClientId, + Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"], + Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(), + ShouldDisplay = true, + Column = 1, + Order = 0, + Type = MetaType.Information + }); + + metaList.Add(new InformationResponse() + { + ClientId = client.ClientId, + Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"], + Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(), + ShouldDisplay = true, + Column = 1, + Order = 1, + Type = MetaType.Information + }); + + metaList.Add(new InformationResponse() + { + ClientId = client.ClientId, + Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"], + Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(), + ShouldDisplay = true, + Column = 1, + Order = 2, + Type = MetaType.Information + }); + + metaList.Add(new InformationResponse() + { + ClientId = client.ClientId, + Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"], + Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), + ShouldDisplay = true, + Column = 1, + Order = 3, + Type = MetaType.Information + }); + + metaList.Add(new InformationResponse() + { + ClientId = client.ClientId, + 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"], + IsSensitive = true, + Column = 1, + Order = 4, + Type = MetaType.Information + }); + + return metaList; + } + + private async Task> GetReceivedPenaltiesMeta(ClientPaginationRequest request) + { + var penalties = await _receivedPenaltyHelper.QueryResource(request); + return penalties.Results; + } + + private async Task> GetAdministeredPenaltiesMeta(ClientPaginationRequest request) + { + var penalties = await _administeredPenaltyHelper.QueryResource(request); + return penalties.Results; + } + + private async Task> GetUpdatedAliasMeta(ClientPaginationRequest request) + { + var aliases = await _updatedAliasHelper.QueryResource(request); + return aliases.Results; + } + } +} diff --git a/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs b/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs new file mode 100644 index 000000000..82ed8f9be --- /dev/null +++ b/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs @@ -0,0 +1,72 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using SharedLibraryCore; +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Dtos.Meta.Responses; +using SharedLibraryCore.Helpers; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; + +namespace IW4MAdmin.Application.Meta +{ + /// + /// implementation of IResourceQueryHelper + /// used to pull in penalties applied to a given client id + /// + public class ReceivedPenaltyResourceQueryHelper : IResourceQueryHelper + { + private readonly ILogger _logger; + private readonly IDatabaseContextFactory _contextFactory; + + public ReceivedPenaltyResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory) + { + _contextFactory = contextFactory; + _logger = logger; + } + + public async Task> QueryResource(ClientPaginationRequest query) + { + var linkedPenaltyType = Utilities.LinkedPenaltyTypes(); + using var ctx = _contextFactory.CreateContext(enableTracking: false); + + var linkId = await ctx.Clients.AsNoTracking() + .Where(_client => _client.ClientId == query.ClientId) + .Select(_client => _client.AliasLinkId) + .FirstOrDefaultAsync(); + + var iqPenalties = ctx.Penalties.AsNoTracking() + .Where(_penalty => _penalty.OffenderId == query.ClientId || (linkedPenaltyType.Contains(_penalty.Type) && _penalty.LinkId == linkId)) + .Where(_penalty => _penalty.When < query.Before) + .OrderByDescending(_penalty => _penalty.When); + + var penalties = await iqPenalties + .Take(query.Count) + .Select(_penalty => new ReceivedPenaltyResponse() + { + PenaltyId = _penalty.PenaltyId, + ClientId = query.ClientId, + Offense = _penalty.Offense, + AutomatedOffense = _penalty.AutomatedOffense, + OffenderClientId = _penalty.OffenderId, + OffenderName = _penalty.Offender.CurrentAlias.Name, + PunisherClientId = _penalty.PunisherId, + PunisherName = _penalty.Punisher.CurrentAlias.Name, + PenaltyType = _penalty.Type, + When = _penalty.When, + ExpirationDate = _penalty.Expires, + IsLinked = _penalty.OffenderId != query.ClientId, + IsSensitive = _penalty.Type == EFPenalty.PenaltyType.Flag + }) + .ToListAsync(); + + return new ResourceQueryHelperResult + { + // todo: maybe actually count + RetrievedResultCount = penalties.Count, + Results = penalties + }; + } + } +} diff --git a/Application/Meta/UpdatedAliasResourceQueryHelper.cs b/Application/Meta/UpdatedAliasResourceQueryHelper.cs new file mode 100644 index 000000000..d5ad99896 --- /dev/null +++ b/Application/Meta/UpdatedAliasResourceQueryHelper.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore; +using SharedLibraryCore; +using SharedLibraryCore.Dtos.Meta.Responses; +using SharedLibraryCore.Helpers; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; +using System.Linq; +using System.Threading.Tasks; + +namespace IW4MAdmin.Application.Meta +{ + /// + /// implementation if IResrouceQueryHerlp + /// used to pull alias changes for given client id + /// + public class UpdatedAliasResourceQueryHelper : IResourceQueryHelper + { + private readonly ILogger _logger; + private readonly IDatabaseContextFactory _contextFactory; + + public UpdatedAliasResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory) + { + _logger = logger; + _contextFactory = contextFactory; + } + + public async Task> QueryResource(ClientPaginationRequest query) + { + using var ctx = _contextFactory.CreateContext(enableTracking: false); + int linkId = ctx.Clients.First(_client => _client.ClientId == query.ClientId).AliasLinkId; + + var iqAliasUpdates = ctx.Aliases + .Where(_alias => _alias.LinkId == linkId) + .Where(_alias => _alias.DateAdded < query.Before) + .Where(_alias => _alias.IPAddress != null) + .OrderByDescending(_alias => _alias.DateAdded) + .Select(_alias => new UpdatedAliasResponse + { + MetaId = _alias.AliasId, + Name = _alias.Name, + IPAddress = _alias.IPAddress.ConvertIPtoString(), + When = _alias.DateAdded, + Type = MetaType.AliasUpdate, + IsSensitive = true + }); + + var result = (await iqAliasUpdates + .Take(query.Count) + .ToListAsync()) + .Distinct(); + + + return new ResourceQueryHelperResult + { + Results = result, // we can potentially have duplicates + RetrievedResultCount = result.Count() + }; + } + } +} diff --git a/Application/Misc/MetaService.cs b/Application/Misc/MetaService.cs new file mode 100644 index 000000000..0f98ad7c2 --- /dev/null +++ b/Application/Misc/MetaService.cs @@ -0,0 +1,187 @@ +using Microsoft.EntityFrameworkCore; +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Dtos; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace IW4MAdmin.Application.Misc +{ + /// + /// implementation of IMetaService + /// used to add and retrieve runtime and persistent meta + /// + public class MetaService : IMetaService + { + private readonly IDictionary> _metaActions; + private readonly IDatabaseContextFactory _contextFactory; + private readonly ILogger _logger; + + public MetaService(ILogger logger, IDatabaseContextFactory contextFactory) + { + _logger = logger; + _metaActions = new Dictionary>(); + _contextFactory = contextFactory; + } + + public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client) + { + // this seems to happen if the client disconnects before they've had time to authenticate and be added + if (client.ClientId < 1) + { + return; + } + + using var ctx = _contextFactory.CreateContext(); + + var existingMeta = await ctx.EFMeta + .Where(_meta => _meta.Key == metaKey) + .Where(_meta => _meta.ClientId == client.ClientId) + .FirstOrDefaultAsync(); + + if (existingMeta != null) + { + existingMeta.Value = metaValue; + existingMeta.Updated = DateTime.UtcNow; + } + + else + { + ctx.EFMeta.Add(new EFMeta() + { + ClientId = client.ClientId, + Created = DateTime.UtcNow, + Key = metaKey, + Value = metaValue + }); + } + + await ctx.SaveChangesAsync(); + } + + public async Task GetPersistentMeta(string metaKey, EFClient client) + { + using var ctx = _contextFactory.CreateContext(enableTracking: false); + + return await ctx.EFMeta + .Where(_meta => _meta.Key == metaKey) + .Where(_meta => _meta.ClientId == client.ClientId) + .Select(_meta => new EFMeta() + { + MetaId = _meta.MetaId, + Key = _meta.Key, + ClientId = _meta.ClientId, + Value = _meta.Value + }) + .FirstOrDefaultAsync(); + } + + public void AddRuntimeMeta(MetaType metaKey, Func>> metaAction) where T : PaginationRequest where V : IClientMeta + { + if (!_metaActions.ContainsKey(metaKey)) + { + _metaActions.Add(metaKey, new List() { metaAction }); + } + + else + { + _metaActions[metaKey].Add(metaAction); + } + } + + public async Task> GetRuntimeMeta(ClientPaginationRequest request) + { + var meta = new List(); + + foreach (var (type, actions) in _metaActions) + { + // information is not listed chronologically + if (type != MetaType.Information) + { + var metaItems = await actions[0](request); + meta.AddRange(metaItems); + } + } + + return meta.OrderByDescending(_meta => _meta.When) + .Take(request.Count) + .ToList(); + } + + public async Task> GetRuntimeMeta(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta + { + IEnumerable meta; + if (metaType == MetaType.Information) + { + var allMeta = new List(); + + foreach (var individualMetaRegistration in _metaActions[metaType]) + { + allMeta.AddRange(await individualMetaRegistration(request)); + } + + return ProcessInformationMeta(allMeta); + } + + else + { + meta = await _metaActions[metaType][0](request) as IEnumerable; + } + + return meta; + } + + private static IEnumerable ProcessInformationMeta(IEnumerable meta) where T : IClientMeta + { + var table = new List>(); + var metaWithColumn = meta + .Where(_meta => _meta.Column != null); + + var columnGrouping = metaWithColumn + .GroupBy(_meta => _meta.Column); + + var metaToSort = meta.Except(metaWithColumn).ToList(); + + foreach (var metaItem in columnGrouping) + { + table.Add(new List(metaItem)); + } + + while (metaToSort.Count > 0) + { + var sortingMeta = metaToSort.First(); + + int indexOfSmallestColumn() + { + int index = 0; + int smallestColumnSize = int.MaxValue; + for (int i = 0; i < table.Count; i++) + { + if (table[i].Count < smallestColumnSize) + { + smallestColumnSize = table[i].Count; + index = i; + } + } + return index; + } + + int columnIndex = indexOfSmallestColumn(); + + sortingMeta.Column = columnIndex; + sortingMeta.Order = columnGrouping + .First(_group => _group.Key == columnIndex) + .Count(); + + table[columnIndex].Add(sortingMeta); + + metaToSort.Remove(sortingMeta); + } + + return meta; + } + } +} diff --git a/Plugins/AutomessageFeed/AutomessageFeed.csproj b/Plugins/AutomessageFeed/AutomessageFeed.csproj index bbd8c0ac4..83e90adfb 100644 --- a/Plugins/AutomessageFeed/AutomessageFeed.csproj +++ b/Plugins/AutomessageFeed/AutomessageFeed.csproj @@ -10,7 +10,7 @@ - + diff --git a/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj b/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj index 068ab4fca..505584c71 100644 --- a/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj +++ b/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj @@ -10,7 +10,7 @@ - + diff --git a/Plugins/LiveRadar/LiveRadar.csproj b/Plugins/LiveRadar/LiveRadar.csproj index 15e9bf864..fc9e82617 100644 --- a/Plugins/LiveRadar/LiveRadar.csproj +++ b/Plugins/LiveRadar/LiveRadar.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Login/Login.csproj b/Plugins/Login/Login.csproj index 377ed87c1..fd8cafa3f 100644 --- a/Plugins/Login/Login.csproj +++ b/Plugins/Login/Login.csproj @@ -23,7 +23,7 @@ - + diff --git a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj index 50c0bcd2c..95f18dc8b 100644 --- a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj +++ b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Stats/Commands/MostPlayedCommand.cs b/Plugins/Stats/Commands/MostPlayedCommand.cs index db08a0146..4001dacc8 100644 --- a/Plugins/Stats/Commands/MostPlayedCommand.cs +++ b/Plugins/Stats/Commands/MostPlayedCommand.cs @@ -22,7 +22,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands List mostPlayed = new List() { - $"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--" + $"^5--{translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--" }; using (var db = new DatabaseContext(true)) @@ -51,8 +51,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands var iqList = await iqStats.ToListAsync(); - mostPlayed.AddRange(iqList.Select(stats => - $"^3{stats.Name}^7 - ^5{stats.Kills} ^7{translationLookup["PLUGINS_STATS_TEXT_KILLS"]} | ^5{Utilities.GetTimePassed(DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime), false)} ^7{translationLookup["WEBFRONT_PROFILE_PLAYER"].ToLower()}")); + mostPlayed.AddRange(iqList.Select(stats => translationLookup["COMMANDS_MOST_PLAYED_FORMAT"].FormatExt(stats.Name, (DateTime.UtcNow - DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime)).HumanizeForCurrentCulture()))); } diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 1af6dfe17..6ca7295c1 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -156,7 +156,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Deaths = s.Deaths, Kills = s.Kills, KDR = Math.Round(s.KDR, 2), - LastSeen = Utilities.GetTimePassed(clientRatingsDict[s.ClientId].LastConnection, false), + LastSeen = (DateTime.UtcNow - clientRatingsDict[s.ClientId].LastConnection).HumanizeForCurrentCulture(), Name = clientRatingsDict[s.ClientId].Name, Performance = Math.Round(clientRatingsDict[s.ClientId].Performance, 2), RatingChange = ratingInfo.First(r => r.Key == s.ClientId).Ratings.First().Ranking - ratingInfo.First(r => r.Key == s.ClientId).Ratings.Last().Ranking, @@ -663,6 +663,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" : $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}"; + penaltyClient.AdministeredPenalties = new List() + { + new EFPenalty() + { + AutomatedOffense = flagReason + } + }; + await attacker.Flag(flagReason, penaltyClient, new TimeSpan(168, 0, 0)).WaitAsync(Utilities.DefaultCommandTimeout, attacker.CurrentServer.Manager.CancellationToken); break; } diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index 452bcff8f..fc2edc35a 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -6,8 +6,10 @@ using SharedLibraryCore; using SharedLibraryCore.Database; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Dtos; +using SharedLibraryCore.Dtos.Meta.Responses; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; using SharedLibraryCore.Services; using System; using System.Collections.Generic; @@ -33,13 +35,15 @@ namespace IW4MAdmin.Plugins.Stats #endif private readonly IDatabaseContextFactory _databaseContextFactory; private readonly ITranslationLookup _translationLookup; + private readonly IMetaService _metaService; public Plugin(IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory, - ITranslationLookup translationLookup) + ITranslationLookup translationLookup, IMetaService metaService) { Config = configurationHandlerFactory.GetConfigurationHandler("StatsPluginSettings"); _databaseContextFactory = databaseContextFactory; _translationLookup = translationLookup; + _metaService = metaService; } public async Task OnEventAsync(GameEvent E, Server S) @@ -188,17 +192,14 @@ namespace IW4MAdmin.Plugins.Stats "/Stats/TopPlayersAsync"); // meta data info - async Task> getStats(int clientId, int offset, int count, DateTime? startAt) + async Task> getStats(ClientPaginationRequest request) { - if (count > 1) - { - return new List(); - } - IList clientStats; - using (var ctx = new DatabaseContext(disableTracking: true)) + int messageCount = 0; + using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false)) { - clientStats = await ctx.Set().Where(c => c.ClientId == clientId).ToListAsync(); + clientStats = await ctx.Set().Where(c => c.ClientId == request.ClientId).ToListAsync(); + messageCount = await ctx.Set().CountAsync(_message => _message.ClientId == request.ClientId); } int kills = clientStats.Sum(c => c.Kills); @@ -209,73 +210,76 @@ namespace IW4MAdmin.Plugins.Stats double performance = Math.Round(validPerformanceValues.Sum(c => c.Performance * c.TimePlayed / performancePlayTime), 2); double spm = Math.Round(clientStats.Sum(c => c.SPM) / clientStats.Where(c => c.SPM > 0).Count(), 1); - return new List() + return new List() { - new ProfileMeta() + new InformationResponse() { Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"], - Value = "#" + (await Manager.GetClientOverallRanking(clientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), + Value = "#" + (await Manager.GetClientOverallRanking(request.ClientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), Column = 0, Order = 0, - Type = ProfileMeta.MetaType.Information + Type = MetaType.Information }, - new ProfileMeta() + new InformationResponse() { Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"], Value = kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), Column = 0, Order = 1, - Type = ProfileMeta.MetaType.Information + Type = MetaType.Information }, - new ProfileMeta() + new InformationResponse() { Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"], Value = deaths.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), Column = 0, Order = 2, - Type = ProfileMeta.MetaType.Information + Type = MetaType.Information }, - new ProfileMeta() + new InformationResponse() { Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"], Value = kdr.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), Column = 0, Order = 3, - Type = ProfileMeta.MetaType.Information + Type = MetaType.Information }, - new ProfileMeta() + new InformationResponse() { - Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_PERFORMANCE"], + Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PERFORMANCE"], Value = performance.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), Column = 0, Order = 4, - Type = ProfileMeta.MetaType.Information + Type = MetaType.Information }, - new ProfileMeta() + new InformationResponse() { Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_META_SPM"], Value = spm.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), Column = 0, Order = 5, - Type = ProfileMeta.MetaType.Information + Type = MetaType.Information + }, + new InformationResponse() + { + Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"], + Value = messageCount.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), + Column = 1, + Order = 4, + Type = MetaType.Information } }; } - async Task> getAnticheatInfo(int clientId, int offset, int count, DateTime? startAt) + async Task> getAnticheatInfo(ClientPaginationRequest request) { - if (count > 1) - { - return new List(); - } - IList clientStats; - using (var ctx = new DatabaseContext(disableTracking: true)) + using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false)) { clientStats = await ctx.Set() .Include(c => c.HitLocations) - .Where(c => c.ClientId == clientId) + .Where(c => c.ClientId == request.ClientId) .ToListAsync(); } @@ -310,147 +314,120 @@ namespace IW4MAdmin.Plugins.Stats averageSnapValue = clientStats.Any(_stats => _stats.AverageSnapValue > 0) ? clientStats.Where(_stats => _stats.AverageSnapValue > 0).Average(_stat => _stat.AverageSnapValue) : 0; } - return new List() + return new List() { - new ProfileMeta() + new InformationResponse() { Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1", Value = chestRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%', - Type = ProfileMeta.MetaType.Information, + Type = MetaType.Information, Column = 2, Order = 0, - Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"], - Sensitive = true + ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"], + IsSensitive = true }, - new ProfileMeta() + new InformationResponse() { Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2", Value = abdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%', - Type = ProfileMeta.MetaType.Information, + Type = MetaType.Information, Column = 2, Order = 1, - Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"], - Sensitive = true + ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"], + IsSensitive = true }, - new ProfileMeta() + new InformationResponse() { Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3", Value = chestAbdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%', - Type = ProfileMeta.MetaType.Information, + Type = MetaType.Information, Column = 2, Order = 2, - Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"], - Sensitive = true + ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"], + IsSensitive = true }, - new ProfileMeta() + new InformationResponse() { Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4", Value = headRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%', - Type = ProfileMeta.MetaType.Information, + Type = MetaType.Information, Column = 2, Order = 3, - Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"], - Sensitive = true + ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"], + IsSensitive = true }, - new ProfileMeta() + new InformationResponse() { Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 5", // todo: make sure this is wrapped somewhere else Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°", - Type = ProfileMeta.MetaType.Information, + Type = MetaType.Information, Column = 2, Order = 4, - Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"], - Sensitive = true + ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"], + IsSensitive = true }, - new ProfileMeta() + new InformationResponse() { Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6", Value = Math.Round(maxStrain, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), - Type = ProfileMeta.MetaType.Information, + Type = MetaType.Information, Column = 2, Order = 5, - Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"], - Sensitive = true + ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"], + IsSensitive = true }, - new ProfileMeta() + new InformationResponse() { Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 7", Value = Math.Round(averageSnapValue, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), - Type = ProfileMeta.MetaType.Information, + Type = MetaType.Information, Column = 2, Order = 6, - Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"], - Sensitive = true + ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"], + IsSensitive = true } }; } - async Task> getMessages(int clientId, int offset, int count, DateTime? startAt) + async Task> getMessages(ClientPaginationRequest request) { - if (count <= 1) - { - using (var ctx = new DatabaseContext(true)) - { - return new List - { - new ProfileMeta() - { - Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"], - Value = (await ctx.Set() - .CountAsync(_message => _message.ClientId == clientId)) - .ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), - Column = 1, - Order= 4, - Type = ProfileMeta.MetaType.Information - } - }; - } - } - - List messageMeta; - using (var ctx = new DatabaseContext(disableTracking: true)) + List messageMeta; + using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false)) { var messages = ctx.Set() - .Where(m => m.ClientId == clientId) - .Where(_message => _message.TimeSent < startAt) + .Where(m => m.ClientId == request.ClientId) + .Where(_message => _message.TimeSent < request.Before) .OrderByDescending(_message => _message.TimeSent) - .Skip(offset) - .Take(count); + .Take(request.Count); - messageMeta = await messages.Select(m => new ProfileMeta() + messageMeta = await messages.Select(m => new MessageResponse() { - Key = null, - Value = new { m.Message, m.Server.GameName }, + // todo: game name + Message = m.Message, When = m.TimeSent, - Extra = m.ServerId.ToString(), - Type = ProfileMeta.MetaType.ChatMessage + ServerId = m.ServerId, + Type = MetaType.ChatMessage }).ToListAsync(); - foreach (var message in messageMeta) + foreach (var meta in messageMeta) { - if ((message.Value.Message as string).IsQuickMessage()) + if ((meta.Message).IsQuickMessage()) { try { var quickMessages = ServerManager.GetApplicationSettings().Configuration() .QuickMessages - .First(_qm => _qm.Game == message.Value.GameName); - message.Value = quickMessages.Messages[(message.Value.Message as string).Substring(1)]; - message.Type = ProfileMeta.MetaType.QuickMessage; + .First(/*_qm => _qm.Game == meta.GameName*/); + meta.Message = quickMessages.Messages[(meta.Message as string).Substring(1)]; + meta.Type = MetaType.QuickMessage; } catch { - message.Value = message.Value.Message; + } } - - else - { - message.Value = message.Value.Message; - } } - } return messageMeta; @@ -458,11 +435,11 @@ namespace IW4MAdmin.Plugins.Stats if (Config.Configuration().EnableAntiCheat) { - MetaService.AddRuntimeMeta(getAnticheatInfo); + _metaService.AddRuntimeMeta(MetaType.Information, getAnticheatInfo); } - MetaService.AddRuntimeMeta(getStats); - MetaService.AddRuntimeMeta(getMessages); + _metaService.AddRuntimeMeta(MetaType.Information, getStats); + _metaService.AddRuntimeMeta(MetaType.ChatMessage, getMessages); async Task totalKills(Server server) { diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj index 0c26fcd35..9f8e916b5 100644 --- a/Plugins/Stats/Stats.csproj +++ b/Plugins/Stats/Stats.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Web/StatsWeb/Controllers/StatsController.cs b/Plugins/Web/StatsWeb/Controllers/StatsController.cs index 690538b92..4b7e3311d 100644 --- a/Plugins/Web/StatsWeb/Controllers/StatsController.cs +++ b/Plugins/Web/StatsWeb/Controllers/StatsController.cs @@ -36,6 +36,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"]; ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"]; ViewBag.Servers = _manager.GetServers().Select(_server => new ServerInfo() { Name = _server.Hostname, ID = _server.EndPoint }); + ViewBag.Localization = _translationLookup; return View("Index"); } diff --git a/Plugins/Web/StatsWeb/Dtos/ChatSearchQuery.cs b/Plugins/Web/StatsWeb/Dtos/ChatSearchQuery.cs index a63185fdb..fe4cc318b 100644 --- a/Plugins/Web/StatsWeb/Dtos/ChatSearchQuery.cs +++ b/Plugins/Web/StatsWeb/Dtos/ChatSearchQuery.cs @@ -1,9 +1,10 @@ using SharedLibraryCore.Dtos; +using SharedLibraryCore.QueryHelper; using System; namespace StatsWeb.Dtos { - public class ChatSearchQuery : PaginationInfo + public class ChatSearchQuery : ClientPaginationRequest { /// /// specifies the partial content of the message to search for @@ -18,7 +19,7 @@ namespace StatsWeb.Dtos /// /// identifier for the client /// - public int? ClientId { get; set; } + public new int? ClientId { get; set; } /// /// only look for messages sent after this date diff --git a/Plugins/Web/StatsWeb/StatsWeb.csproj b/Plugins/Web/StatsWeb/StatsWeb.csproj index 59c6853a0..85390fc92 100644 --- a/Plugins/Web/StatsWeb/StatsWeb.csproj +++ b/Plugins/Web/StatsWeb/StatsWeb.csproj @@ -14,7 +14,7 @@ Always - + diff --git a/Plugins/Web/StatsWeb/Views/Stats/Index.cshtml b/Plugins/Web/StatsWeb/Views/Stats/Index.cshtml index 98fd9c3d8..7d8ace74c 100644 --- a/Plugins/Web/StatsWeb/Views/Stats/Index.cshtml +++ b/Plugins/Web/StatsWeb/Views/Stats/Index.cshtml @@ -1,6 +1,6 @@  public class SetGravatarCommand : Command { - public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup) + private readonly IMetaService _metaService; + + public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup, IMetaService metaService) : base(config, translationLookup) { Name = "setgravatar"; Description = _translationLookup["COMMANDS_GRAVATAR_DESC"]; @@ -1558,17 +1554,17 @@ namespace SharedLibraryCore.Commands Required = true } }; + + _metaService = metaService; } public override async Task ExecuteAsync(GameEvent E) { - var metaSvc = new MetaService(); - using (var md5 = MD5.Create()) { string gravatarEmail = string.Concat(md5.ComputeHash(E.Data.ToLower().Select(d => Convert.ToByte(d)).ToArray()) .Select(h => h.ToString("x2"))); - await metaSvc.AddPersistentMeta("GravatarEmail", gravatarEmail, E.Origin); + await _metaService.AddPersistentMeta("GravatarEmail", gravatarEmail, E.Origin); } E.Origin.Tell(_translationLookup["COMMANDS_GRAVATAR_SUCCESS_NEW"]); diff --git a/SharedLibraryCore/Dtos/FindClientRequest.cs b/SharedLibraryCore/Dtos/FindClientRequest.cs index 7586c749c..b1869a72d 100644 --- a/SharedLibraryCore/Dtos/FindClientRequest.cs +++ b/SharedLibraryCore/Dtos/FindClientRequest.cs @@ -1,6 +1,6 @@ namespace SharedLibraryCore.Dtos { - public class FindClientRequest : PaginationInfo + public class FindClientRequest : PaginationRequest { /// /// name of client diff --git a/SharedLibraryCore/Dtos/Meta/Requests/BaseClientMetaRequest.cs b/SharedLibraryCore/Dtos/Meta/Requests/BaseClientMetaRequest.cs new file mode 100644 index 000000000..25479339f --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/Requests/BaseClientMetaRequest.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Dtos.Meta.Requests +{ + public class BaseClientMetaRequest : PaginationRequest + { + public int ClientId { get; set; } + } +} diff --git a/SharedLibraryCore/Dtos/Meta/Requests/ReceivedPenaltyRequest.cs b/SharedLibraryCore/Dtos/Meta/Requests/ReceivedPenaltyRequest.cs new file mode 100644 index 000000000..4c959894f --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/Requests/ReceivedPenaltyRequest.cs @@ -0,0 +1,11 @@ +using SharedLibraryCore.QueryHelper; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Dtos.Meta.Requests +{ + public class ReceivedPenaltyRequest : BaseClientMetaRequest + { + } +} diff --git a/SharedLibraryCore/Dtos/Meta/Responses/AdministeredPenaltyResponse.cs b/SharedLibraryCore/Dtos/Meta/Responses/AdministeredPenaltyResponse.cs new file mode 100644 index 000000000..f985f6570 --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/Responses/AdministeredPenaltyResponse.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Dtos.Meta.Responses +{ + public class AdministeredPenaltyResponse : ReceivedPenaltyResponse + { + } +} diff --git a/SharedLibraryCore/Dtos/Meta/Responses/BaseMetaResponse.cs b/SharedLibraryCore/Dtos/Meta/Responses/BaseMetaResponse.cs new file mode 100644 index 000000000..7e593293d --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/Responses/BaseMetaResponse.cs @@ -0,0 +1,19 @@ +using SharedLibraryCore.Interfaces; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Dtos.Meta.Responses +{ + public class BaseMetaResponse : IClientMeta, IClientMetaResponse + { + public int MetaId { get; set; } + public int ClientId { get; set; } + public MetaType Type { get; set; } + public DateTime When { get; set; } + public bool IsSensitive { get; set; } + public bool ShouldDisplay { get; set; } + public int? Column { get; set; } + public int? Order { get; set; } + } +} diff --git a/SharedLibraryCore/Dtos/Meta/Responses/InformationResponse.cs b/SharedLibraryCore/Dtos/Meta/Responses/InformationResponse.cs new file mode 100644 index 000000000..adfd090ad --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/Responses/InformationResponse.cs @@ -0,0 +1,14 @@ +using SharedLibraryCore.Interfaces; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Dtos.Meta.Responses +{ + public class InformationResponse : BaseMetaResponse + { + public string Key { get; set; } + public string Value { get; set; } + public string ToolTipText { get; set; } + } +} diff --git a/SharedLibraryCore/Dtos/Meta/Responses/MessageResponse.cs b/SharedLibraryCore/Dtos/Meta/Responses/MessageResponse.cs new file mode 100644 index 000000000..18cc845d0 --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/Responses/MessageResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Dtos.Meta.Responses +{ + public class MessageResponse : BaseMetaResponse + { + public long ServerId { get; set; } + public string Message { get; set; } + } +} diff --git a/SharedLibraryCore/Dtos/Meta/Responses/ReceivedPenaltyResponse.cs b/SharedLibraryCore/Dtos/Meta/Responses/ReceivedPenaltyResponse.cs new file mode 100644 index 000000000..6911ab7f1 --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/Responses/ReceivedPenaltyResponse.cs @@ -0,0 +1,22 @@ +using System; +using static SharedLibraryCore.Database.Models.EFPenalty; + +namespace SharedLibraryCore.Dtos.Meta.Responses +{ + public class ReceivedPenaltyResponse : BaseMetaResponse + { + public int PenaltyId { get; set; } + public int OffenderClientId { get; set; } + public string OffenderName { get; set; } + public string PunisherName { get; set; } + public int PunisherClientId { get; set; } + public PenaltyType PenaltyType { get; set; } + public string Offense { get; set; } + public string AutomatedOffense { get; set; } + public DateTime? ExpirationDate { get; set; } + public string ExpiresInText => ExpirationDate.HasValue && ExpirationDate.Value > DateTime.UtcNow ? (ExpirationDate - DateTime.UtcNow).Value.HumanizeForCurrentCulture() : ""; + public string LengthText => ExpirationDate.HasValue ? (ExpirationDate.Value.AddMinutes(1) - When).HumanizeForCurrentCulture() : ""; + public bool IsLinked { get; set; } + public int LinkedClientId { get; set; } + } +} diff --git a/SharedLibraryCore/Dtos/Meta/Responses/UpdatedAliasResponse.cs b/SharedLibraryCore/Dtos/Meta/Responses/UpdatedAliasResponse.cs new file mode 100644 index 000000000..58772627f --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/Responses/UpdatedAliasResponse.cs @@ -0,0 +1,22 @@ +using System; + +namespace SharedLibraryCore.Dtos.Meta.Responses +{ + public class UpdatedAliasResponse : BaseMetaResponse + { + public string Name { get; set; } + public string IPAddress { get; set; } = "--"; + + public override bool Equals(object obj) + { + if (obj is UpdatedAliasResponse resp) + { + return resp.Name.StripColors() == Name.StripColors() && resp.IPAddress == IPAddress; + } + + return false; + } + + public override int GetHashCode() => HashCode.Combine(Name.StripColors(), IPAddress); + } +} diff --git a/SharedLibraryCore/Dtos/Meta/WebfrontTranslationHelper.cs b/SharedLibraryCore/Dtos/Meta/WebfrontTranslationHelper.cs new file mode 100644 index 000000000..eb3a692d7 --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/WebfrontTranslationHelper.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Dtos.Meta +{ + public class WebfrontTranslationHelper + { + public bool IsInterpolation { get; set; } + public string MatchValue { get; set; } + public string TranslationValue { get; set; } + } +} diff --git a/SharedLibraryCore/Dtos/PaginationInfo.cs b/SharedLibraryCore/Dtos/PaginationRequest.cs similarity index 84% rename from SharedLibraryCore/Dtos/PaginationInfo.cs rename to SharedLibraryCore/Dtos/PaginationRequest.cs index e062ea4f9..e5c6fd364 100644 --- a/SharedLibraryCore/Dtos/PaginationInfo.cs +++ b/SharedLibraryCore/Dtos/PaginationRequest.cs @@ -1,9 +1,11 @@ -namespace SharedLibraryCore.Dtos +using System; + +namespace SharedLibraryCore.Dtos { /// /// pagination information holder class /// - public class PaginationInfo + public class PaginationRequest { /// /// how many items to skip @@ -24,6 +26,8 @@ /// direction of ordering /// public SortDirection Direction { get; set; } = SortDirection.Descending; + + public DateTime? Before { get; set; } } public enum SortDirection diff --git a/SharedLibraryCore/Dtos/PenaltyInfo.cs b/SharedLibraryCore/Dtos/PenaltyInfo.cs index 9de39b315..e8f4c8871 100644 --- a/SharedLibraryCore/Dtos/PenaltyInfo.cs +++ b/SharedLibraryCore/Dtos/PenaltyInfo.cs @@ -21,8 +21,8 @@ namespace SharedLibraryCore.Dtos public PenaltyType PenaltyType { get; set; } public string PenaltyTypeText => PenaltyType.ToString(); public DateTime TimePunished { get; set; } - public string TimePunishedString => Utilities.GetTimePassed(TimePunished, true); - public string TimeRemaining => DateTime.UtcNow > Expires ? "" : $"{((Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? Utilities.GetTimePassed(TimePunished, true) : Utilities.TimeSpanText((Expires ?? DateTime.MaxValue) - DateTime.UtcNow))}"; + public string TimePunishedString => TimePunished.HumanizeForCurrentCulture(); + public string TimeRemaining => DateTime.UtcNow > Expires ? "" : $"{((Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? TimePunishedString : ((Expires ?? DateTime.MaxValue) - DateTime.UtcNow).HumanizeForCurrentCulture())}"; public bool Expired => Expires.HasValue && Expires <= DateTime.UtcNow; public DateTime? Expires { get; set; } public override bool Sensitive => PenaltyType == PenaltyType.Flag || PenaltyType == PenaltyType.Unflag; diff --git a/SharedLibraryCore/Dtos/PlayerInfo.cs b/SharedLibraryCore/Dtos/PlayerInfo.cs index a72516f3c..94b91561e 100644 --- a/SharedLibraryCore/Dtos/PlayerInfo.cs +++ b/SharedLibraryCore/Dtos/PlayerInfo.cs @@ -1,8 +1,8 @@ -using System; +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Dtos.Meta.Responses; +using SharedLibraryCore.Interfaces; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharedLibraryCore.Dtos { @@ -19,11 +19,13 @@ namespace SharedLibraryCore.Dtos public bool HasActivePenalty { get; set; } public string ActivePenaltyType { get; set; } public bool Authenticated { get; set; } - public List Meta { get; set; } + public List Meta { get; set; } + public EFPenalty ActivePenalty { get; set; } public bool Online { get; set; } public string TimeOnline { get; set; } public DateTime LastConnection { get; set; } - public string LastConnectionText => Utilities.GetTimePassed(LastConnection, true); + public string LastConnectionText => (DateTime.UtcNow - LastConnection).HumanizeForCurrentCulture(); public IDictionary LinkedAccounts { get; set; } + public MetaType? MetaFilterType { get; set; } } } diff --git a/SharedLibraryCore/Dtos/ProfileMeta.cs b/SharedLibraryCore/Dtos/ProfileMeta.cs deleted file mode 100644 index 98af69d5f..000000000 --- a/SharedLibraryCore/Dtos/ProfileMeta.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SharedLibraryCore.Dtos -{ - public class ProfileMeta : SharedInfo - { - public enum MetaType - { - Other, - Information, - AliasUpdate, - ChatMessage, - Penalized, - ReceivedPenalty, - QuickMessage - } - - public DateTime When { get; set; } - public string WhenString => Utilities.GetTimePassed(When, false); - public string Key { get; set; } - public dynamic Value { get; set; } - public string Extra { get; set; } - public virtual string Class => Value.GetType().ToString(); - public MetaType Type { get; set; } - public int? Column { get; set; } - public int? Order { get; set; } - } -} diff --git a/SharedLibraryCore/Helpers/ResourceQueryHelperResult.cs b/SharedLibraryCore/Helpers/ResourceQueryHelperResult.cs index 48076e774..8e6a69678 100644 --- a/SharedLibraryCore/Helpers/ResourceQueryHelperResult.cs +++ b/SharedLibraryCore/Helpers/ResourceQueryHelperResult.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using SharedLibraryCore.Dtos.Meta.Responses; namespace SharedLibraryCore.Helpers { diff --git a/SharedLibraryCore/Interfaces/IAuditInformationRepository.cs b/SharedLibraryCore/Interfaces/IAuditInformationRepository.cs index a4bd53a9b..1cc24a20e 100644 --- a/SharedLibraryCore/Interfaces/IAuditInformationRepository.cs +++ b/SharedLibraryCore/Interfaces/IAuditInformationRepository.cs @@ -14,6 +14,6 @@ namespace SharedLibraryCore.Interfaces /// /// pagination info /// - Task> ListAuditInformation(PaginationInfo paginationInfo); + Task> ListAuditInformation(PaginationRequest paginationInfo); } } diff --git a/SharedLibraryCore/Interfaces/IClientMeta.cs b/SharedLibraryCore/Interfaces/IClientMeta.cs new file mode 100644 index 000000000..31e589a7b --- /dev/null +++ b/SharedLibraryCore/Interfaces/IClientMeta.cs @@ -0,0 +1,31 @@ +using System; + +namespace SharedLibraryCore.Interfaces +{ + /// + /// describes all the base attributes of a client meta object + /// + public interface IClientMeta + { + MetaType Type { get; } + DateTime When { get; } + + bool IsSensitive { get; } + bool ShouldDisplay { get; } + + // sorting purposes + public int? Column { get; set; } + public int? Order { get; set; } + } + + public enum MetaType + { + Other, + Information, + AliasUpdate, + ChatMessage, + Penalized, + ReceivedPenalty, + QuickMessage + } +} diff --git a/SharedLibraryCore/Interfaces/IClientMetaResponse.cs b/SharedLibraryCore/Interfaces/IClientMetaResponse.cs new file mode 100644 index 000000000..3980b6ee7 --- /dev/null +++ b/SharedLibraryCore/Interfaces/IClientMetaResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Interfaces +{ + public interface IClientMetaResponse + { + int ClientId { get;} + int MetaId { get; } + } +} diff --git a/SharedLibraryCore/Interfaces/IMetaRegistration.cs b/SharedLibraryCore/Interfaces/IMetaRegistration.cs new file mode 100644 index 000000000..6aa444122 --- /dev/null +++ b/SharedLibraryCore/Interfaces/IMetaRegistration.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Interfaces +{ + public interface IMetaRegistration + { + void Register(); + } +} diff --git a/SharedLibraryCore/Interfaces/IMetaService.cs b/SharedLibraryCore/Interfaces/IMetaService.cs new file mode 100644 index 000000000..71e0ec23a --- /dev/null +++ b/SharedLibraryCore/Interfaces/IMetaService.cs @@ -0,0 +1,51 @@ +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Dtos; +using SharedLibraryCore.QueryHelper; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace SharedLibraryCore.Interfaces +{ + public interface IMetaService + { + /// + /// adds or updates meta key and value to the database + /// + /// key of meta data + /// value of the meta data + /// client to save the meta for + /// + Task AddPersistentMeta(string metaKey, string metaValue, EFClient client); + + /// + /// retrieves meta data for given client and key + /// + /// key to retrieve value for + /// client to retrieve meta for + /// + Task GetPersistentMeta(string metaKey, EFClient client); + + /// + /// adds a meta task to the runtime meta list + /// + /// type of meta + /// action to perform + void AddRuntimeMeta(MetaType metaKey, Func>> metaAction) where V : IClientMeta where T: PaginationRequest; + + /// + /// retrieves all the runtime meta information for given client idea + /// + /// request information + /// + Task> GetRuntimeMeta(ClientPaginationRequest request); + + /// + /// retreives all the runtime of provided type + /// + /// >request information + /// type of meta to retreive + /// + Task> GetRuntimeMeta(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta; + } +} diff --git a/SharedLibraryCore/Localization/Layout.cs b/SharedLibraryCore/Localization/Layout.cs index fd8460372..76e636fe2 100644 --- a/SharedLibraryCore/Localization/Layout.cs +++ b/SharedLibraryCore/Localization/Layout.cs @@ -1,12 +1,23 @@ using SharedLibraryCore.Interfaces; using System.Collections.Generic; +using System.Globalization; namespace SharedLibraryCore.Localization { public class Layout { - public string LocalizationName { get; set; } + private string localizationName; + public string LocalizationName + { + get => localizationName; + set + { + localizationName = value; + Culture = new CultureInfo(value); + } + } public TranslationLookup LocalizationIndex { get; set; } + public CultureInfo Culture { get; private set; } public Layout(Dictionary set) { diff --git a/SharedLibraryCore/PartialEntities/EFClient.cs b/SharedLibraryCore/PartialEntities/EFClient.cs index 1990160f5..fc259ef6e 100644 --- a/SharedLibraryCore/PartialEntities/EFClient.cs +++ b/SharedLibraryCore/PartialEntities/EFClient.cs @@ -618,7 +618,7 @@ namespace SharedLibraryCore.Database.Models if (tempbanPenalty != null) { CurrentServer.Logger.WriteDebug($"Kicking {this} because their GUID is temporarily banned"); - Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempbanPenalty.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient); + Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempbanPenalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient); return false; } diff --git a/SharedLibraryCore/QueryHelper/ClientPaginationRequest.cs b/SharedLibraryCore/QueryHelper/ClientPaginationRequest.cs new file mode 100644 index 000000000..5eaef22e7 --- /dev/null +++ b/SharedLibraryCore/QueryHelper/ClientPaginationRequest.cs @@ -0,0 +1,12 @@ +using SharedLibraryCore.Dtos; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.QueryHelper +{ + public class ClientPaginationRequest : PaginationRequest + { + public int ClientId { get; set; } + } +} diff --git a/SharedLibraryCore/Repositories/AuditInformationRepository.cs b/SharedLibraryCore/Repositories/AuditInformationRepository.cs index 90f327e43..fea61c2af 100644 --- a/SharedLibraryCore/Repositories/AuditInformationRepository.cs +++ b/SharedLibraryCore/Repositories/AuditInformationRepository.cs @@ -20,7 +20,7 @@ namespace SharedLibraryCore.Repositories } /// - public async Task> ListAuditInformation(PaginationInfo paginationInfo) + public async Task> ListAuditInformation(PaginationRequest paginationInfo) { using (var ctx = _contextFactory.CreateContext(enableTracking: false)) { diff --git a/SharedLibraryCore/Services/MetaService.cs b/SharedLibraryCore/Services/MetaService.cs deleted file mode 100644 index 8f68f5444..000000000 --- a/SharedLibraryCore/Services/MetaService.cs +++ /dev/null @@ -1,169 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using SharedLibraryCore.Database; -using SharedLibraryCore.Database.Models; -using SharedLibraryCore.Dtos; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace SharedLibraryCore.Services -{ - public class MetaService - { - private static List>>> _metaActions = new List>>>(); - - /// - /// adds or updates meta key and value to the database - /// - /// key of meta data - /// value of the meta data - /// client to save the meta for - /// - public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client) - { - // this seems to happen if the client disconnects before they've had time to authenticate and be added - if (client.ClientId < 1) - { - return; - } - - using (var ctx = new DatabaseContext()) - { - var existingMeta = await ctx.EFMeta - .Where(_meta => _meta.Key == metaKey) - .Where(_meta => _meta.ClientId == client.ClientId) - .FirstOrDefaultAsync(); - - if (existingMeta != null) - { - existingMeta.Value = metaValue; - existingMeta.Updated = DateTime.UtcNow; - } - - else - { - ctx.EFMeta.Add(new EFMeta() - { - ClientId = client.ClientId, - Created = DateTime.UtcNow, - Key = metaKey, - Value = metaValue - }); - } - - await ctx.SaveChangesAsync(); - } - } - - internal static void Clear() - { - _metaActions.Clear(); - } - - /// - /// retrieves meta data for given client and key - /// - /// key to retrieve value for - /// client to retrieve meta for - /// - public async Task GetPersistentMeta(string metaKey, EFClient client) - { - using (var ctx = new DatabaseContext(disableTracking: true)) - { - return await ctx.EFMeta - .Where(_meta => _meta.Key == metaKey) - .Where(_meta => _meta.ClientId == client.ClientId) - .Select(_meta => new EFMeta() - { - MetaId = _meta.MetaId, - Key = _meta.Key, - ClientId = _meta.ClientId, - Value = _meta.Value - }) - .FirstOrDefaultAsync(); - } - } - - /// - /// aads a meta task to the runtime meta list - /// - /// - public static void AddRuntimeMeta(Func>> metaAction) - { - _metaActions.Add(metaAction); - } - - /// - /// retrieves all the runtime meta information for given client idea - /// - /// id of the client - /// number of meta items to retrieve - /// offset from the first item - /// - public static async Task> GetRuntimeMeta(int clientId, int offset = 0, int count = int.MaxValue, DateTime? startAt = null) - { - var meta = new List(); - - foreach (var action in _metaActions) - { - var metaItems = await action(clientId, offset, count, startAt); - meta.AddRange(metaItems); - } - - if (count == 1) - { - var table = new List>(); - var metaWithColumn = meta - .Where(_meta => _meta.Column != null); - - var columnGrouping = metaWithColumn - .GroupBy(_meta => _meta.Column); - - var metaToSort = meta.Except(metaWithColumn).ToList(); - - foreach (var metaItem in columnGrouping) - { - table.Add(new List(metaItem)); - } - - while (metaToSort.Count > 0) - { - var sortingMeta = metaToSort.First(); - - int indexOfSmallestColumn() - { - int index = 0; - int smallestColumnSize = int.MaxValue; - for (int i = 0; i < table.Count; i++) - { - if (table[i].Count < smallestColumnSize) - { - smallestColumnSize = table[i].Count; - index = i; - } - } - return index; - } - - int columnIndex = indexOfSmallestColumn(); - - sortingMeta.Column = columnIndex; - sortingMeta.Order = columnGrouping - .First(_group => _group.Key == columnIndex) - .Count(); - - table[columnIndex].Add(sortingMeta); - - metaToSort.Remove(sortingMeta); - } - - return meta; - } - - return meta.OrderByDescending(_meta => _meta.When) - .Take(count) - .ToList(); - } - } -} diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index dd2bfedc7..43629b6d3 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -6,7 +6,7 @@ RaidMax.IW4MAdmin.SharedLibraryCore - 2.4.3 + 2.4.6 RaidMax Forever None Debug;Release;Prerelease @@ -20,8 +20,8 @@ true MIT Shared Library for IW4MAdmin - 2.4.3.0 - 2.4.3.0 + 2.4.6.0 + 2.4.6.0 @@ -30,30 +30,33 @@ - + + + + - - - + + + all runtime; build; native; contentfiles - - - - - - + + + + + + - - - + + + - + diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 46118dcd9..4a0ee059b 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -1,5 +1,8 @@  +using Humanizer; +using Humanizer.Localisation; using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Dtos.Meta; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; using System; @@ -381,57 +384,6 @@ namespace SharedLibraryCore return !ip.HasValue ? "" : new IPAddress(BitConverter.GetBytes(ip.Value)).ToString(); } - public static string GetTimePassed(DateTime start) - { - return GetTimePassed(start, true); - } - - public static string GetTimePassed(DateTime start, bool includeAgo) - { - TimeSpan Elapsed = DateTime.UtcNow - start; - string ago = includeAgo ? $" {CurrentLocalization.LocalizationIndex["WEBFRONT_PENALTY_TEMPLATE_AGO"]}" : ""; - - if (Elapsed.TotalSeconds < 30) - { - return CurrentLocalization.LocalizationIndex["GLOBAL_TIME_JUSTNOW"] + ago; - } - if (Elapsed.TotalMinutes < 120) - { - if (Elapsed.TotalMinutes < 1.5) - { - return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}"; - } - - return Math.Round(Elapsed.TotalMinutes, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}"; - } - if (Elapsed.TotalHours <= 24) - { - if (Elapsed.TotalHours < 1.5) - { - return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}"; - } - - return Math.Round(Elapsed.TotalHours, 0) + $" { CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}"; - } - if (Elapsed.TotalDays <= 90) - { - if (Elapsed.TotalDays < 1.5) - { - return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}"; - } - - return Math.Round(Elapsed.TotalDays, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}"; - } - if (Elapsed.TotalDays <= 365) - { - return $"{Math.Round(Elapsed.TotalDays / 7)} {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_WEEKS"]}{ago}"; - } - else - { - return $"{Math.Round(Elapsed.TotalDays / 30, 0)} {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MONTHS"]}{ago}"; - } - } - public static Game GetGame(string gameName) { if (string.IsNullOrEmpty(gameName)) @@ -519,42 +471,6 @@ namespace SharedLibraryCore return new TimeSpan(1, 0, 0); } - public static string TimeSpanText(this TimeSpan span) - { - var loc = CurrentLocalization.LocalizationIndex; - - if (span.TotalMinutes < 60) - { - return $"{span.Minutes} {loc["GLOBAL_TIME_MINUTES"]}"; - } - else if (span.Hours >= 1 && span.TotalHours < 24) - { - return $"{span.Hours} {loc["GLOBAL_TIME_HOURS"]}"; - } - else if (span.TotalDays >= 1 && span.TotalDays < 7) - { - return $"{span.Days} {loc["GLOBAL_TIME_DAYS"]}"; - } - else if (span.TotalDays >= 7 && span.TotalDays < 90) - { - return $"{Math.Round(span.Days / 7.0, 0)} {loc["GLOBAL_TIME_WEEKS"]}"; - } - else if (span.TotalDays >= 90 && span.TotalDays < 365) - { - return $"{Math.Round(span.Days / 30.0, 0)} {loc["GLOBAL_TIME_MONTHS"]}"; - } - else if (span.TotalDays >= 365 && span.TotalDays < 36500) - { - return $"{Math.Round(span.Days / 365.0, 0)} {loc["GLOBAL_TIME_YEARS"]}"; - } - else if (span.TotalDays >= 36500) - { - return loc["GLOBAL_TIME_FOREVER"]; - } - - return "unknown"; - } - /// /// returns a list of penalty types that should be shown across all profiles /// @@ -932,7 +848,7 @@ namespace SharedLibraryCore /// /// /// - /// true of the creat succeeds, false otherwise + /// true of the create succeeds, false otherwise public static async Task TryCreatePenalty(this EFPenalty penalty, IEntityService penaltyService, ILogger logger) { try @@ -969,7 +885,50 @@ namespace SharedLibraryCore } public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged; - + + /// + /// parses translation string into tokens that are able to be formatted by the webfront + /// + /// key for translation lookup + /// + public static WebfrontTranslationHelper[] SplitTranslationTokens(string translationKey) + { + string translationString = CurrentLocalization.LocalizationIndex[translationKey]; + var builder = new StringBuilder(); + var results = new List(); + + foreach (string word in translationString.Split(' ')) + { + string finalWord = word; + + if ((word.StartsWith("{{") && !word.EndsWith("}}")) || + (builder.Length > 0 && !word.EndsWith("}}"))) + { + builder.Append($"{word} "); + continue; + } + + if (builder.Length > 0) + { + builder.Append(word); + finalWord = builder.ToString(); + builder.Clear(); + } + + var match = Regex.Match(finalWord, @"{{([^}|^-]+)(?:->)([^}]+)}}|{{([^}]+)}}"); + bool isInterpolation = match.Success; + + results.Add(new WebfrontTranslationHelper + { + IsInterpolation = isInterpolation, + MatchValue = isInterpolation ? match.Groups[3].Length > 0 ? match.Groups[3].ToString() : match.Groups[1].ToString() : finalWord, + TranslationValue = isInterpolation && match.Groups[2].Length > 0 ? match.Groups[2].ToString() : "" + }); + } + + return results.ToArray(); + } + /// /// indicates if running in development mode /// @@ -991,5 +950,27 @@ namespace SharedLibraryCore return path; } + + /// + /// wrapper method for humanizee that uses current current culture + /// + public static string HumanizeForCurrentCulture(this TimeSpan timeSpan, int precision = 1, TimeUnit maxUnit = TimeUnit.Week, + TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", bool toWords = false) + { + return timeSpan.Humanize(precision, CurrentLocalization.Culture, maxUnit, minUnit, collectionSeparator, toWords); + } + + /// + /// wrapper method for humanizee that uses current current culture + /// + public static string HumanizeForCurrentCulture(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null, CultureInfo culture = null) + { + return input.Humanize(utcDate, dateToCompareAgainst, CurrentLocalization.Culture); + } + + public static string ToTranslatedName(this MetaType metaType) + { + return CurrentLocalization.LocalizationIndex[$"META_TYPE_{metaType.ToString().ToUpper()}_NAME"]; + } } } diff --git a/Tests/ApplicationTests/ApplicationTests.csproj b/Tests/ApplicationTests/ApplicationTests.csproj index 13eae0ddf..d41f5f2af 100644 --- a/Tests/ApplicationTests/ApplicationTests.csproj +++ b/Tests/ApplicationTests/ApplicationTests.csproj @@ -6,10 +6,10 @@ - - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/ApplicationTests/DependencyInjectionExtensions.cs b/Tests/ApplicationTests/DependencyInjectionExtensions.cs index a3bb79d1a..76dcc2b3e 100644 --- a/Tests/ApplicationTests/DependencyInjectionExtensions.cs +++ b/Tests/ApplicationTests/DependencyInjectionExtensions.cs @@ -43,13 +43,14 @@ namespace ApplicationTests .AddSingleton(A.Fake()) .AddSingleton() .AddSingleton(A.Fake()) + .AddSingleton(A.Fake()) .AddSingleton(eventHandler) .AddSingleton(ConfigurationGenerators.CreateApplicationConfiguration()) .AddSingleton(ConfigurationGenerators.CreateCommandConfiguration()) .AddSingleton, ApplicationConfigurationHandlerMock>(); serviceCollection.AddSingleton(_sp => new IW4MServer(_sp.GetRequiredService(), ConfigurationGenerators.CreateServerConfiguration(), - _sp.GetRequiredService(), _sp.GetRequiredService(), _sp.GetRequiredService()) + _sp.GetRequiredService(), _sp.GetRequiredService(), _sp.GetRequiredService(), _sp.GetRequiredService()) { RconParser = _sp.GetRequiredService() }); diff --git a/Tests/ApplicationTests/Fixtures/ClientGenerators.cs b/Tests/ApplicationTests/Fixtures/ClientGenerators.cs index 4cb132a8f..16e5ab02b 100644 --- a/Tests/ApplicationTests/Fixtures/ClientGenerators.cs +++ b/Tests/ApplicationTests/Fixtures/ClientGenerators.cs @@ -21,9 +21,9 @@ namespace ApplicationTests.Fixtures CurrentServer = currentServer }; - public static EFClient CreateDatabaseClient(bool hasIp = true) => new EFClient() + public static EFClient CreateDatabaseClient(bool hasIp = true, int clientId = 1) => new EFClient() { - ClientId = 1, + ClientId = clientId, ClientNumber = -1, AliasLinkId = 1, Level = EFClient.Permission.User, diff --git a/Tests/ApplicationTests/Fixtures/PenaltyGenerators.cs b/Tests/ApplicationTests/Fixtures/PenaltyGenerators.cs new file mode 100644 index 000000000..c0f8050b1 --- /dev/null +++ b/Tests/ApplicationTests/Fixtures/PenaltyGenerators.cs @@ -0,0 +1,28 @@ +using SharedLibraryCore.Database.Models; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ApplicationTests.Fixtures +{ + class PenaltyGenerators + { + public static EFPenalty Create(EFPenalty.PenaltyType type = EFPenalty.PenaltyType.Ban, EFClient originClient = null, EFClient targetClient = null, DateTime? occurs = null, string reason = null) + { + originClient ??= ClientGenerators.CreateDatabaseClient(clientId: 1); + targetClient ??= ClientGenerators.CreateDatabaseClient(clientId: 2); + occurs ??= DateTime.UtcNow; + reason ??= "test"; + + return new EFPenalty() + { + Offender = targetClient, + Punisher = originClient, + When = occurs.Value, + Offense = reason, + Type = type, + LinkId = targetClient.AliasLinkId + }; + } + } +} diff --git a/Tests/ApplicationTests/QueryHelperTests.cs b/Tests/ApplicationTests/QueryHelperTests.cs new file mode 100644 index 000000000..d0107bcfd --- /dev/null +++ b/Tests/ApplicationTests/QueryHelperTests.cs @@ -0,0 +1,209 @@ +using ApplicationTests.Fixtures; +using IW4MAdmin.Application.Meta; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Dtos; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace ApplicationTests +{ + public class QueryHelperTests + { + private IServiceProvider serviceProvider; + + [SetUp] + public void Setup() + { + serviceProvider = new ServiceCollection().BuildBase() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); + + SetupPenalties(); + SetupAliases(); + } + + private void SetupAliases() + { + using var ctx = serviceProvider.GetRequiredService().CreateContext(); + + var client = ClientGenerators.CreateDatabaseClient(); + + var aliases = new[] + { + new EFAlias() + { + LinkId = client.AliasLinkId, + Name = "Test1", + IPAddress = -1, + DateAdded = DateTime.UtcNow.AddMinutes(-1) + }, + new EFAlias() + { + LinkId = client.AliasLinkId, + Name = "Test2", + IPAddress = -1, + DateAdded = DateTime.UtcNow + } + }; + + ctx.Aliases.AddRange(aliases); + ctx.SaveChanges(); + } + + private void SetupPenalties() + { + using var ctx = serviceProvider.GetRequiredService().CreateContext(); + + var firstPenalty = PenaltyGenerators.Create(occurs: DateTime.UtcNow.AddMinutes(-2), reason: "first"); + var secondPenalty = PenaltyGenerators.Create(occurs: DateTime.UtcNow.AddMinutes(-1), reason: "second", originClient: firstPenalty.Punisher, targetClient: firstPenalty.Offender); + var linkedPenalty = PenaltyGenerators.Create(occurs: DateTime.UtcNow, reason: "linked", originClient: firstPenalty.Punisher, targetClient: ClientGenerators.CreateDatabaseClient(clientId: 3)); + + ctx.Add(firstPenalty); + ctx.Add(secondPenalty); + ctx.Add(linkedPenalty); + ctx.SaveChanges(); + } + + [TearDown] + public void Teardown() + { + using var ctx = serviceProvider.GetRequiredService().CreateContext(); + ctx.Database.EnsureDeleted(); + } + + #region ADMINISTERED PENALTIES + [Test] + public async Task Test_AdministeredPenaltyResourceQueryHelper_QueryResource_TakesAppropriateCount() + { + var queryHelper = serviceProvider.GetRequiredService(); + + var request = new ClientPaginationRequest + { + Count = 1, + Before = DateTime.UtcNow, + ClientId = 1 + }; + + var result = await queryHelper.QueryResource(request); + + Assert.AreEqual(request.Count, result.RetrievedResultCount); + } + + [Test] + public async Task Test_AdministeredPenaltyResourceQueryHelper_QueryResource_OrdersDescending() + { + var queryHelper = serviceProvider.GetRequiredService(); + + var request = new ClientPaginationRequest + { + Count = 2, + Before = DateTime.UtcNow, + ClientId = 1, + Direction = SortDirection.Descending + }; + + var result = await queryHelper.QueryResource(request); + + Assert.Less(result.Results.Last().When.ToFileTimeUtc(), result.Results.First().When.ToFileTimeUtc()); + } + #endregion + + #region RECEIVED PENALTIES + [Test] + public async Task Test_ReceivedPenaltyResourceQueryHelper_QueryResource_TakesAppropriateCount() + { + var queryHelper = serviceProvider.GetRequiredService(); + + var request = new ClientPaginationRequest + { + Count = 1, + Before = DateTime.UtcNow, + ClientId = 2 + }; + + var result = await queryHelper.QueryResource(request); + + Assert.AreEqual(request.Count, result.RetrievedResultCount); + } + + [Test] + public async Task Test_ReceivedPenaltyResourceQueryHelper_QueryResource_OrdersDescending() + { + var queryHelper = serviceProvider.GetRequiredService(); + + var request = new ClientPaginationRequest + { + Count = 2, + Before = DateTime.UtcNow, + ClientId = 2, + Direction = SortDirection.Descending + }; + + var result = await queryHelper.QueryResource(request); + + Assert.Less(result.Results.Last().When.ToFileTimeUtc(), result.Results.First().When.ToFileTimeUtc()); + } + + [Test] + public async Task Test_ReceivedPenaltyResourceQueryHelper_QueryResource_IncludesLinkedPenalty() + { + var queryHelper = serviceProvider.GetRequiredService(); + + var request = new ClientPaginationRequest + { + Count = 3, + Before = DateTime.UtcNow, + ClientId = 3, + }; + + var result = await queryHelper.QueryResource(request); + + Assert.AreEqual(request.Count, result.RetrievedResultCount); + } + #endregion + + #region ALIAS UPDATE + [Test] + public async Task Test_UpdatedAliasResourceQueryHelper_QueryResource_TakesAppropriateCount() + { + var queryHelper = serviceProvider.GetRequiredService(); + + var request = new ClientPaginationRequest + { + Count = 1, + Before = DateTime.UtcNow, + ClientId = 1 + }; + + var result = await queryHelper.QueryResource(request); + + Assert.AreEqual(request.Count, result.RetrievedResultCount); + } + + [Test] + public async Task Test_UpdatedAliasResourceQueryHelper_QueryResource_OrdersDescending() + { + var queryHelper = serviceProvider.GetRequiredService(); + + var request = new ClientPaginationRequest + { + Count = 2, + Before = DateTime.UtcNow, + ClientId = 1, + Direction = SortDirection.Descending + }; + + var result = await queryHelper.QueryResource(request); + + Assert.Less(result.Results.Last().When.ToFileTimeUtc(), result.Results.First().When.ToFileTimeUtc()); + } + #endregion + } +} diff --git a/Tests/ApplicationTests/ServerTests.cs b/Tests/ApplicationTests/ServerTests.cs index dff1ae17c..df792d8bb 100644 --- a/Tests/ApplicationTests/ServerTests.cs +++ b/Tests/ApplicationTests/ServerTests.cs @@ -34,7 +34,7 @@ namespace ApplicationTests var mgr = A.Fake(); var server = new IW4MServer(mgr, new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, - A.Fake(), A.Fake(), A.Fake()); + A.Fake(), A.Fake(), A.Fake(), A.Fake()); var parser = new BaseEventParser(A.Fake(), A.Fake(), A.Fake()); parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; @@ -60,7 +60,7 @@ namespace ApplicationTests var server = new IW4MServer(mgr, new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, - A.Fake(), A.Fake(), A.Fake()); + A.Fake(), A.Fake(), A.Fake(), A.Fake()); var parser = new BaseEventParser(A.Fake(), A.Fake(), A.Fake()); parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; diff --git a/Tests/ApplicationTests/StatsTests.cs b/Tests/ApplicationTests/StatsTests.cs index 5f611c21e..e8fa46b31 100644 --- a/Tests/ApplicationTests/StatsTests.cs +++ b/Tests/ApplicationTests/StatsTests.cs @@ -72,7 +72,7 @@ namespace ApplicationTests var server = new IW4MServer(mgr, new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 }, A.Fake(), - A.Fake(), A.Fake()); + A.Fake(), A.Fake(), A.Fake()); var parser = new BaseEventParser(A.Fake(), A.Fake(), A.Fake()); parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer; diff --git a/WebfrontCore/Controllers/ActionController.cs b/WebfrontCore/Controllers/ActionController.cs index 0c79e3c1d..a6e1caaa6 100644 --- a/WebfrontCore/Controllers/ActionController.cs +++ b/WebfrontCore/Controllers/ActionController.cs @@ -242,7 +242,7 @@ namespace WebfrontCore.Controllers { var info = new ActionInfo() { - ActionButtonLabel = "Generate", + ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_GENERATE_TOKEN"], Name = "GenerateLoginToken", Action = "GenerateLoginTokenAsync", Inputs = new List() diff --git a/WebfrontCore/Controllers/AdminController.cs b/WebfrontCore/Controllers/AdminController.cs index e8215e2ca..a809ea907 100644 --- a/WebfrontCore/Controllers/AdminController.cs +++ b/WebfrontCore/Controllers/AdminController.cs @@ -27,7 +27,7 @@ namespace WebfrontCore.Controllers ViewBag.Title = _translationLookup["WEBFRONT_NAV_AUDIT_LOG"]; ViewBag.InitialOffset = DEFAULT_COUNT; - var auditItems = await _auditInformationRepository.ListAuditInformation(new PaginationInfo() + var auditItems = await _auditInformationRepository.ListAuditInformation(new PaginationRequest() { Count = DEFAULT_COUNT }); @@ -35,7 +35,7 @@ namespace WebfrontCore.Controllers return View(auditItems); } - public async Task ListAuditLog([FromQuery] PaginationInfo paginationInfo) + public async Task ListAuditLog([FromQuery] PaginationRequest paginationInfo) { ViewBag.EnableColorCodes = Manager.GetApplicationSettings().Configuration().EnableColorCodes; var auditItems = await _auditInformationRepository.ListAuditInformation(paginationInfo); diff --git a/WebfrontCore/Controllers/ClientController.cs b/WebfrontCore/Controllers/ClientController.cs index 678ba6285..7b3d75c20 100644 --- a/WebfrontCore/Controllers/ClientController.cs +++ b/WebfrontCore/Controllers/ClientController.cs @@ -2,12 +2,15 @@ using SharedLibraryCore; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Dtos; +using SharedLibraryCore.Dtos.Meta.Responses; using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; using SharedLibraryCore.Services; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using WebfrontCore.ViewComponents; using static SharedLibraryCore.Database.Models.EFClient; using static SharedLibraryCore.Database.Models.EFPenalty; @@ -15,12 +18,14 @@ namespace WebfrontCore.Controllers { public class ClientController : BaseController { - public ClientController(IManager manager) : base(manager) - { + private readonly IMetaService _metaService; + public ClientController(IManager manager, IMetaService metaService) : base(manager) + { + _metaService = metaService; } - public async Task ProfileAsync(int id) + public async Task ProfileAsync(int id, MetaType? metaFilterType) { var client = await Manager.GetClientService().Get(id); @@ -29,8 +34,8 @@ namespace WebfrontCore.Controllers return NotFound(); } - var activePenalties = (await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, client.IPAddress)) - .Where(_penalty => _penalty.Type != PenaltyType.Flag); + var activePenalties = (await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, client.IPAddress)); + int displayLevelInt = (int)client.Level; string displayLevel = client.Level.ToLocalizedLevelName(); @@ -49,7 +54,7 @@ namespace WebfrontCore.Controllers ClientId = client.ClientId, IPAddress = client.IPAddressString, NetworkId = client.NetworkId, - Meta = new List(), + Meta = new List(), Aliases = client.AliasLink.Children .Select(_alias => _alias.Name) .GroupBy(_alias => _alias.StripColors()) @@ -65,38 +70,32 @@ namespace WebfrontCore.Controllers .Prepend(client.CurrentAlias.IPAddress.ConvertIPtoString()) .Distinct() .ToList(), - HasActivePenalty = activePenalties.Count() > 0, - ActivePenaltyType = activePenalties.Count() > 0 ? activePenalties.First().Type.ToString() : null, + HasActivePenalty = activePenalties.Any(_penalty => _penalty.Type != PenaltyType.Flag), Online = Manager.GetActiveClients().FirstOrDefault(c => c.ClientId == client.ClientId) != null, - TimeOnline = (DateTime.UtcNow - client.LastConnection).TimeSpanText(), - LinkedAccounts = client.LinkedAccounts + TimeOnline = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(), + LinkedAccounts = client.LinkedAccounts, + MetaFilterType = metaFilterType }; - var meta = await MetaService.GetRuntimeMeta(client.ClientId, 0, 1, DateTime.UtcNow); - var gravatar = await new MetaService().GetPersistentMeta("GravatarEmail", client); + var meta = await _metaService.GetRuntimeMeta(new ClientPaginationRequest + { + ClientId = client.ClientId, + Before = DateTime.UtcNow + }, MetaType.Information); + + var gravatar = await _metaService.GetPersistentMeta("GravatarEmail", client); if (gravatar != null) { - clientDto.Meta.Add(new ProfileMeta() + clientDto.Meta.Add(new InformationResponse() { Key = "GravatarEmail", - Type = ProfileMeta.MetaType.Other, + Type = MetaType.Other, Value = gravatar.Value }); } - var currentPenalty = activePenalties.FirstOrDefault(); - - if (currentPenalty != null && currentPenalty.Type == PenaltyType.TempBan) - { - clientDto.Meta.Add(new ProfileMeta() - { - Key = Localization["WEBFRONT_CLIENT_META_REMAINING_BAN"], - Value = ((currentPenalty.Expires - DateTime.UtcNow) ?? new TimeSpan()).TimeSpanText(), - When = currentPenalty.When - }); - } - - clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.Sensitive)); + clientDto.ActivePenalty = activePenalties.OrderByDescending(_penalty => _penalty.Type).FirstOrDefault(); + clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive)); string strippedName = clientDto.Name.StripColors(); ViewBag.Title = strippedName.Substring(strippedName.Length - 1).ToLower()[0] == 's' ? @@ -160,14 +159,17 @@ namespace WebfrontCore.Controllers return View("Find/Index", clientsDto); } - public async Task Meta(int id, int count, int offset, DateTime? startAt) + public async Task Meta(int id, int count, int offset, long? startAt, MetaType? metaFilterType) { - IEnumerable meta = await MetaService.GetRuntimeMeta(id, startAt == null ? offset : 0, count, startAt ?? DateTime.UtcNow); - - if (!Authorized) + var request = new ClientPaginationRequest { - meta = meta.Where(_meta => !_meta.Sensitive); - } + ClientId = id, + Count = count, + Offset = offset, + Before = DateTime.FromFileTimeUtc(startAt ?? DateTime.UtcNow.ToFileTimeUtc()) + }; + + var meta = await ProfileMetaListViewComponent.GetClientMeta(_metaService, metaFilterType, Client.Level, request); if (meta.Count() == 0) { diff --git a/WebfrontCore/Startup.cs b/WebfrontCore/Startup.cs index b961579b9..8bcd167bc 100644 --- a/WebfrontCore/Startup.cs +++ b/WebfrontCore/Startup.cs @@ -19,6 +19,10 @@ using Stats.Dtos; using Stats.Helpers; using StatsWeb; using StatsWeb.Dtos; +/*using Stats.Dtos; +using Stats.Helpers; +using StatsWeb; +using StatsWeb.Dtos;*/ using System.Collections.Generic; using System.IO; using System.Linq; @@ -125,6 +129,7 @@ namespace WebfrontCore services.AddSingleton(Program.ApplicationServiceProvider.GetService()); services.AddSingleton(Program.ApplicationServiceProvider.GetService()); services.AddSingleton(Program.ApplicationServiceProvider.GetService>()); + services.AddSingleton(Program.ApplicationServiceProvider.GetService()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/WebfrontCore/ViewComponents/ProfileMetaListViewComponent.cs b/WebfrontCore/ViewComponents/ProfileMetaListViewComponent.cs index 8e4c7a3db..c5c83a3bb 100644 --- a/WebfrontCore/ViewComponents/ProfileMetaListViewComponent.cs +++ b/WebfrontCore/ViewComponents/ProfileMetaListViewComponent.cs @@ -1,7 +1,8 @@ using Microsoft.AspNetCore.Mvc; using SharedLibraryCore.Database.Models; -using SharedLibraryCore.Dtos; -using SharedLibraryCore.Services; +using SharedLibraryCore.Dtos.Meta.Responses; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; using System; using System.Collections.Generic; using System.Linq; @@ -12,18 +13,70 @@ namespace WebfrontCore.ViewComponents { public class ProfileMetaListViewComponent : ViewComponent { - public async Task InvokeAsync(int clientId, int count, int offset, DateTime? startAt) + private readonly IMetaService _metaService; + + public ProfileMetaListViewComponent(IMetaService metaService) + { + _metaService = metaService; + } + + public async Task InvokeAsync(int clientId, int count, int offset, DateTime? startAt, MetaType? metaType) { var level = (EFClient.Permission)Enum.Parse(typeof(EFClient.Permission), UserClaimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value ?? "User"); - IEnumerable meta = await MetaService.GetRuntimeMeta(clientId, offset, count, startAt ?? DateTime.UtcNow); - - if (level < EFClient.Permission.Trusted) + var request = new ClientPaginationRequest { - meta = meta.Where(_meta => !_meta.Sensitive); - } + ClientId = clientId, + Count = count, + Offset = offset, + Before = startAt, + }; + + var meta = await GetClientMeta(_metaService, metaType, level, request); + ViewBag.Localization = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex; return View("_List", meta); } + + public static async Task> GetClientMeta(IMetaService metaService, MetaType? metaType, EFClient.Permission level, ClientPaginationRequest request) + { + IEnumerable meta = null; + + if (metaType == null) // all types + { + meta = await metaService.GetRuntimeMeta(request); + } + + else + { + switch (metaType) + { + case MetaType.Information: + meta = await metaService.GetRuntimeMeta(request, metaType.Value); + break; + case MetaType.AliasUpdate: + meta = await metaService.GetRuntimeMeta(request, metaType.Value); + break; + case MetaType.ChatMessage: + meta = await metaService.GetRuntimeMeta(request, metaType.Value); + break; + case MetaType.Penalized: + meta = await metaService.GetRuntimeMeta(request, metaType.Value); + break; + case MetaType.ReceivedPenalty: + meta = await metaService.GetRuntimeMeta(request, metaType.Value); + break; + default: + break; + } + } + + if (level < EFClient.Permission.Trusted) + { + meta = meta.Where(_meta => !_meta.IsSensitive); + } + + return meta; + } } } diff --git a/WebfrontCore/Views/Client/Profile/Index.cshtml b/WebfrontCore/Views/Client/Profile/Index.cshtml index d24aec794..4d2350ce5 100644 --- a/WebfrontCore/Views/Client/Profile/Index.cshtml +++ b/WebfrontCore/Views/Client/Profile/Index.cshtml @@ -1,17 +1,17 @@ -@model SharedLibraryCore.Dtos.PlayerInfo +@using SharedLibraryCore.Database.Models +@using SharedLibraryCore.Interfaces +@using SharedLibraryCore +@model SharedLibraryCore.Dtos.PlayerInfo @{ string match = System.Text.RegularExpressions.Regex.Match(Model.Name.ToUpper(), "[A-Z]").Value; string shortCode = match == string.Empty ? "?" : match; var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex; string gravatarUrl = Model.Meta.FirstOrDefault(m => m.Key == "GravatarEmail")?.Value; - bool isTempBanned = Model.ActivePenaltyType == "TempBan"; bool isFlagged = Model.LevelInt == (int)SharedLibraryCore.Database.Models.EFClient.Permission.Flagged; bool isPermBanned = Model.LevelInt == (int)SharedLibraryCore.Database.Models.EFClient.Permission.Banned; - var informationMeta = Model.Meta - .Where(_meta => _meta.Type == SharedLibraryCore.Dtos.ProfileMeta.MetaType.Information) - .OrderBy(_meta => _meta.Order) - .GroupBy(_meta => _meta.Column) - .OrderBy(_grouping => _grouping.Key); + bool isTempBanned = Model.ActivePenalty?.Type == EFPenalty.PenaltyType.TempBan; + string translationKey = $"WEBFRONT_PROFILE_{Model.ActivePenalty?.Type.ToString().ToUpper()}_INFO"; + var ignoredMetaTypes = new[] { MetaType.Information, MetaType.Other, MetaType.QuickMessage }; }
@@ -23,7 +23,7 @@ }
-
+
@if (ViewBag.Authorized) @@ -31,6 +31,7 @@
}
+ @if (ViewBag.Authorized) {
@@ -49,9 +50,34 @@ }
} -
- @Model.Level @(isTempBanned ? $"({loc["WEBFRONT_PROFILE_TEMPBAN"]})" : "") -
+ @if (Model.ActivePenalty != null) + { +
+ @foreach (var result in Utilities.SplitTranslationTokens(translationKey)) + { + switch (result.MatchValue) + { + case "reason": + @(ViewBag.Authorized ? !string.IsNullOrEmpty(Model.ActivePenalty.AutomatedOffense) && Model.ActivePenalty.Type != EFPenalty.PenaltyType.Warning ? Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.ActivePenalty.AutomatedOffense) : Model.ActivePenalty.Offense : Model.ActivePenalty.Offense) + break; + case "time": + + @Utilities.HumanizeForCurrentCulture(Model.ActivePenalty.Expires.Value - DateTime.UtcNow) + + break; + default: + @result.MatchValue + break; + } + } +
+ } + else + { +
+ @Model.Level +
+ }
@if (ViewBag.Authorized) { @@ -89,23 +115,32 @@
- @foreach (var metaColumn in informationMeta) - { -
- @foreach (var meta in metaColumn) + +
+ +
+
+ + Filter Meta +
+
+ @foreach (MetaType type in Enum.GetValues(typeof(MetaType))) + { + if (!ignoredMetaTypes.Contains(type)) { -
- - @meta.Key -
+ @type.ToTranslatedName() } -
- } + } +
-
- @await Component.InvokeAsync("ProfileMetaList", new { clientId = Model.ClientId, count = 30, offset = 0 }) +
+ @await Component.InvokeAsync("ProfileMetaList", new { clientId = Model.ClientId, count = 30, offset = 0, startAt = DateTime.UtcNow, metaType = Model.MetaFilterType })
@@ -122,5 +157,5 @@ - + } diff --git a/WebfrontCore/Views/Client/Profile/Meta/_AdministeredPenaltyResponse.cshtml b/WebfrontCore/Views/Client/Profile/Meta/_AdministeredPenaltyResponse.cshtml new file mode 100644 index 000000000..bb573a236 --- /dev/null +++ b/WebfrontCore/Views/Client/Profile/Meta/_AdministeredPenaltyResponse.cshtml @@ -0,0 +1,53 @@ +@using SharedLibraryCore.Dtos.Meta.Responses +@model AdministeredPenaltyResponse +@{ + string localizationKey = $"WEBFRONT_CLIENT_META_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2"; +} + +
+ @foreach (var match in Utilities.SplitTranslationTokens(localizationKey)) + { + if (match.IsInterpolation) + { + if (match.MatchValue == "action") + { + @match.TranslationValue + } + + else if (match.MatchValue == "offender") + { + + + + + + } + + else if (match.MatchValue == "reason") + { + + @if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != SharedLibraryCore.Database.Models.EFPenalty.PenaltyType.Warning) + { + @Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense) + + } + else + { + + } + + + } + + else if (match.MatchValue == "time") + { + @Model.LengthText + } + } + + else + { + @match.MatchValue + } + } +
diff --git a/WebfrontCore/Views/Client/Profile/Meta/_Information.cshtml b/WebfrontCore/Views/Client/Profile/Meta/_Information.cshtml new file mode 100644 index 000000000..9004f9d19 --- /dev/null +++ b/WebfrontCore/Views/Client/Profile/Meta/_Information.cshtml @@ -0,0 +1,43 @@ +@model IEnumerable +@{ + var informationMeta = Model + .Where(_meta => _meta.Type == SharedLibraryCore.Interfaces.MetaType.Information) + .OrderBy(_meta => _meta.Order) + .GroupBy(_meta => _meta.Column) + .OrderBy(_grouping => _grouping.Key); +} + +@foreach (var metaColumn in informationMeta) +{ +
+ @foreach (var meta in metaColumn) + { +
+ + @{var results = Utilities.SplitTranslationTokens(meta.Key);} + + @if (results.Any(_result => _result.IsInterpolation)) + { + foreach (var result in results) + { + if (result.IsInterpolation) + { + + } + + else + { + @result.MatchValue + } + } + } + + else + { + + @meta.Key + } +
+ } +
+} \ No newline at end of file diff --git a/WebfrontCore/Views/Client/Profile/Meta/_MessageResponse.cshtml b/WebfrontCore/Views/Client/Profile/Meta/_MessageResponse.cshtml new file mode 100644 index 000000000..6a717c979 --- /dev/null +++ b/WebfrontCore/Views/Client/Profile/Meta/_MessageResponse.cshtml @@ -0,0 +1,9 @@ +@using SharedLibraryCore.Dtos.Meta.Responses; + +@model MessageResponse + + + + + + \ No newline at end of file diff --git a/WebfrontCore/Views/Client/Profile/Meta/_ReceivedPenaltyResponse.cshtml b/WebfrontCore/Views/Client/Profile/Meta/_ReceivedPenaltyResponse.cshtml new file mode 100644 index 000000000..bb06fc1a7 --- /dev/null +++ b/WebfrontCore/Views/Client/Profile/Meta/_ReceivedPenaltyResponse.cshtml @@ -0,0 +1,74 @@ +@using SharedLibraryCore.Dtos.Meta.Responses +@using SharedLibraryCore +@model ReceivedPenaltyResponse + +@{ + string localizationKey = $"WEBFRONT_CLIENT_META_WAS_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2"; +} + +
+ @foreach (var match in Utilities.SplitTranslationTokens(localizationKey)) + { + if (match.IsInterpolation) + { + if (match.MatchValue == "action") + { + @match.TranslationValue + } + + else if (match.MatchValue == "punisher") + { + + + + + + } + + else if (match.MatchValue == "reason") + { + + @if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != SharedLibraryCore.Database.Models.EFPenalty.PenaltyType.Warning) + { + @Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense) + + } + else + { + + } + + + } + + else if (match.MatchValue == "time") + { + @Model.LengthText + } + } + + else + { + @match.MatchValue + } + } + + @if (Model.ClientId != Model.OffenderClientId) + { + + @foreach (var helperResult in Utilities.SplitTranslationTokens("WEBFRONT_PROFILE_LINKED_ACCOUNT")) + { + if (!helperResult.IsInterpolation) + { + @helperResult.MatchValue + } + + else + { + + + + } + } + } +
diff --git a/WebfrontCore/Views/Client/Profile/Meta/_UpdatedAliasResponse.cshtml b/WebfrontCore/Views/Client/Profile/Meta/_UpdatedAliasResponse.cshtml new file mode 100644 index 000000000..5094a7126 --- /dev/null +++ b/WebfrontCore/Views/Client/Profile/Meta/_UpdatedAliasResponse.cshtml @@ -0,0 +1,27 @@ +@using SharedLibraryCore.Dtos.Meta.Responses +@using SharedLibraryCore +@model UpdatedAliasResponse + +@foreach (var token in Utilities.SplitTranslationTokens("WEBFRONT_PROFILE_META_CONNECT_ALIAS")) +{ + if (token.IsInterpolation) + { + switch (token.MatchValue) + { + case "action": + @token.TranslationValue + break; + case "alias": + + + [@Model.IPAddress] + + break; + } + } + + else + { + @token.MatchValue + } +} diff --git a/WebfrontCore/Views/Shared/Components/ProfileMetaList/_List.cshtml b/WebfrontCore/Views/Shared/Components/ProfileMetaList/_List.cshtml index 1a37eeac3..d80d31f09 100644 --- a/WebfrontCore/Views/Shared/Components/ProfileMetaList/_List.cshtml +++ b/WebfrontCore/Views/Shared/Components/ProfileMetaList/_List.cshtml @@ -1,94 +1,66 @@ -@model IEnumerable +@using SharedLibraryCore.Interfaces; + +@model IEnumerable @{ Layout = null; - var timeSinceLastEvent = DateTime.MinValue; + var lastHeaderEventDate = DateTime.UtcNow; - dynamic formatPenaltyInfo(SharedLibraryCore.Dtos.ProfileMeta meta) + TimeSpan timeSpanForEvent(DateTime When) { - var penalty = meta.Value as SharedLibraryCore.Dtos.PenaltyInfo; + var timePassed = (DateTime.UtcNow - When); + var daysPassed = timePassed.TotalDays; + var minutesPassed = timePassed.TotalMinutes; - string localizationKey = meta.Type == SharedLibraryCore.Dtos.ProfileMeta.MetaType.Penalized ? - $"WEBFRONT_CLIENT_META_PENALIZED_{penalty.PenaltyTypeText.ToUpper()}" : - $"WEBFRONT_CLIENT_META_WAS_PENALIZED_{penalty.PenaltyTypeText.ToUpper()}"; - - string localizationMessage = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex[localizationKey]; - var regexMatch = System.Text.RegularExpressions.Regex.Match(localizationMessage, @"^.*{{([^{}]+)}}.+$"); - string penaltyType = regexMatch.Groups[1].Value.ToString(); - var secondMatch = System.Text.RegularExpressions.Regex.Match(localizationMessage, @"\{\{.+\}\}(.+)\{0\}(.+)\{1\}"); - - return new + if (minutesPassed <= 60) { - Type = meta.Type, - Match = secondMatch, - Penalty = penalty, - PenaltyType = penaltyType - }; + return TimeSpan.FromMinutes(5); + } + + if (minutesPassed > 60 && daysPassed <= 1) + { + return TimeSpan.FromHours(1); + } + + if (daysPassed > 1 && daysPassed <= 7) + { + return TimeSpan.FromDays(1); + } + + if (daysPassed > 7 && daysPassed <= 31) + { + return TimeSpan.FromDays(31); + } + + if (daysPassed > 31 && daysPassed <= 365) + { + return TimeSpan.FromDays(31); + } + + else + { + return TimeSpan.FromDays(365); + } } } @if (Model.Count() == 0) { -
@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_NONE"]
+
@ViewBag.Localization["WEBFRONT_CLIENT_META_NONE"]
} @foreach (var meta in Model.OrderByDescending(_meta => _meta.When)) { - @if (Math.Abs((meta.When - timeSinceLastEvent).TotalDays) >= 1) + @if ((lastHeaderEventDate - meta.When) > timeSpanForEvent(lastHeaderEventDate)) {
- @SharedLibraryCore.Utilities.GetTimePassed(meta.When, true) + @meta.When.HumanizeForCurrentCulture()
- - timeSinceLastEvent = meta.When; + lastHeaderEventDate = meta.When; } - @switch (meta.Type) - { - case SharedLibraryCore.Dtos.ProfileMeta.MetaType.ChatMessage: - case SharedLibraryCore.Dtos.ProfileMeta.MetaType.QuickMessage: -
- > - -
- break; - case SharedLibraryCore.Dtos.ProfileMeta.MetaType.ReceivedPenalty: - case SharedLibraryCore.Dtos.ProfileMeta.MetaType.Penalized: -
- @{ var penaltyInfo = formatPenaltyInfo(meta); } - @if (meta.Type == SharedLibraryCore.Dtos.ProfileMeta.MetaType.Penalized) - { - @penaltyInfo.PenaltyType - @penaltyInfo.Match.Groups[1].ToString() - - - - - - - - @penaltyInfo.Match.Groups[2].ToString() - @(ViewBag.Authorized ? penaltyInfo.Penalty.AdditionalPenaltyInformation : "") - } - - @if (meta.Type == SharedLibraryCore.Dtos.ProfileMeta.MetaType.ReceivedPenalty) - { - @penaltyInfo.PenaltyType - @penaltyInfo.Match.Groups[1].ToString() - - - - - - @penaltyInfo.Match.Groups[2] - - @(ViewBag.Authorized ? penaltyInfo.Penalty.AdditionalPenaltyInformation : "") - - } -
- break; - } -} \ No newline at end of file +
+ +
+} diff --git a/WebfrontCore/Views/_ViewImports.cshtml b/WebfrontCore/Views/_ViewImports.cshtml index 29240f8b2..44edf4ac0 100644 --- a/WebfrontCore/Views/_ViewImports.cshtml +++ b/WebfrontCore/Views/_ViewImports.cshtml @@ -1,4 +1,5 @@ @using SharedLibraryCore @using WebfrontCore @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, SharedLibraryCore \ No newline at end of file +@addTagHelper *, SharedLibraryCore +@addTagHelper *, WebfrontCore \ No newline at end of file diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index f26fc89c4..de3d3302f 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -64,20 +64,20 @@ - + - - + + - + - + - + diff --git a/WebfrontCore/compilerconfig.json b/WebfrontCore/compilerconfig.json index 9750bdd28..f1809e57b 100644 --- a/WebfrontCore/compilerconfig.json +++ b/WebfrontCore/compilerconfig.json @@ -1,7 +1,10 @@ -[ +[ { "outputFile": "wwwroot/css/global.css", - "inputFile": "wwwroot/css/src/main.scss", - "sourceMap": false + "inputFile": "wwwroot/css/src/main.scss" + }, + { + "outputFile": "wwwroot/css/src/profile.css", + "inputFile": "wwwroot/css/src/profile.scss" } ] \ No newline at end of file diff --git a/WebfrontCore/wwwroot/css/src/main.scss b/WebfrontCore/wwwroot/css/src/main.scss index d7d5e2b0e..b9d09af92 100644 --- a/WebfrontCore/wwwroot/css/src/main.scss +++ b/WebfrontCore/wwwroot/css/src/main.scss @@ -1,5 +1,4 @@ -@import 'bootstrap-custom.scss'; -@import 'profile.scss'; +@import 'profile.scss'; $icon-font-path: '/font/' !default; @import '../../lib/open-iconic/font/css/open-iconic-bootstrap.scss'; @@ -195,7 +194,7 @@ form *, select { font-size: 1rem; } -.client-message, .automated-penalty-info-detailed, .oi { +.oi { cursor: pointer; } diff --git a/WebfrontCore/wwwroot/css/src/profile.scss b/WebfrontCore/wwwroot/css/src/profile.scss index bb1ab518e..defc867ae 100644 --- a/WebfrontCore/wwwroot/css/src/profile.scss +++ b/WebfrontCore/wwwroot/css/src/profile.scss @@ -1,4 +1,6 @@ -.level-bgcolor-console { +@import 'bootstrap-custom.scss'; + +.level-bgcolor-console { background-color: grey; } @@ -194,3 +196,8 @@ #profile_events span { word-break: break-all; } + +#filter_meta_container .nav-link:hover { + background-color: $dark; + color: $white !important; +} diff --git a/WebfrontCore/wwwroot/js/loader.js b/WebfrontCore/wwwroot/js/loader.js index f8f26769f..a334f4029 100644 --- a/WebfrontCore/wwwroot/js/loader.js +++ b/WebfrontCore/wwwroot/js/loader.js @@ -5,12 +5,14 @@ let startAt = null; let isLoaderLoading = false; let loadUri = ''; let loaderResponseId = ''; +let additionalParams = []; -function initLoader(location, loaderId, count = 10, start = count) { +function initLoader(location, loaderId, count = 10, start = count, additional) { loadUri = location; loaderResponseId = loaderId; loadCount = count; loaderOffset = start; + additionalParams = additional; setupListeners(); } @@ -21,7 +23,13 @@ function loadMoreItems() { showLoader(); isLoaderLoading = true; - $.get(loadUri, { offset: loaderOffset, count: loadCount, startAt: startAt }) + let params = { offset: loaderOffset, count: loadCount, startAt: startAt }; + for (i = 0; i < additionalParams.length; i++) { + let param = additionalParams[i]; + params[param.name] = param.value; + } + + $.get(loadUri, params) .done(function (response) { $(loaderResponseId).append(response); if (response.trim().length === 0) { diff --git a/WebfrontCore/wwwroot/js/profile.js b/WebfrontCore/wwwroot/js/profile.js index cf4d7087b..8e555ea3a 100644 --- a/WebfrontCore/wwwroot/js/profile.js +++ b/WebfrontCore/wwwroot/js/profile.js @@ -11,7 +11,15 @@ }); /* set the end time for initial event query */ - startAt = $('#profile_events').children().last().data('time'); + startAt = $('.loader-data-time').last().data('time'); + + + $('#filter_meta_container_button').click(function () { + $('#filter_meta_container').hide(); + $('#filter_meta_container').removeClass('d-none'); + $('#filter_meta_container').addClass('d-block'); + $('#filter_meta_container').slideDown(); + }); /* * load context of chat @@ -20,6 +28,14 @@ $(document).on('click', '.client-message', function (e) { showLoader(); const location = $(this); + $('.client-message-prefix').removeClass('oi-chevron-bottom'); + $('.client-message-prefix').removeClass('oi-chevron-right'); + + $('.client-message-prefix').addClass('oi-chevron-right'); + + $(this).children().filter('.client-message-prefix').removeClass('oi-chevron-right'); + $(this).children().filter('.client-message-prefix').addClass('oi-chevron-bottom'); + $.get('/Stats/GetMessageAsync', { 'serverId': $(this).data('serverid'), 'when': $(this).data('when')