From 51e8b31e42730b35967415994cb733a72812519b Mon Sep 17 00:00:00 2001 From: RaidMax Date: Wed, 20 Jul 2022 10:32:26 -0500 Subject: [PATCH] add client note command and feature --- Application/Commands/AddClientNoteCommand.cs | 52 +++++++++++++++++++ .../Meta/Responses/ClientNoteMetaResponse.cs | 13 +++++ SharedLibraryCore/Dtos/PlayerInfo.cs | 1 + SharedLibraryCore/Services/ClientService.cs | 8 +++ WebfrontCore/Controllers/ActionController.cs | 52 +++++++++++++++++++ .../Controllers/Client/ClientController.cs | 18 +++++-- WebfrontCore/Permissions/WebfrontEntity.cs | 6 +-- WebfrontCore/Views/Action/_ActionForm.cshtml | 5 ++ .../Views/Client/Profile/Index.cshtml | 37 +++++++++++-- 9 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 Application/Commands/AddClientNoteCommand.cs create mode 100644 SharedLibraryCore/Dtos/Meta/Responses/ClientNoteMetaResponse.cs diff --git a/Application/Commands/AddClientNoteCommand.cs b/Application/Commands/AddClientNoteCommand.cs new file mode 100644 index 000000000..7636b677e --- /dev/null +++ b/Application/Commands/AddClientNoteCommand.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; +using Data.Models.Client; +using IW4MAdmin.Application.Meta; +using SharedLibraryCore; +using SharedLibraryCore.Commands; +using SharedLibraryCore.Configuration; +using SharedLibraryCore.Dtos.Meta.Responses; +using SharedLibraryCore.Interfaces; + +namespace IW4MAdmin.Application.Commands; + +public class AddClientNoteCommand : Command +{ + private readonly IMetaServiceV2 _metaService; + + public AddClientNoteCommand(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) : base(config, layout) + { + Name = "addnote"; + Description = _translationLookup["COMMANDS_ADD_CLIENT_NOTE_DESCRIPTION"]; + Alias = "an"; + Permission = EFClient.Permission.Moderator; + RequiresTarget = true; + Arguments = new[] + { + new CommandArgument + { + Name = _translationLookup["COMMANDS_ARGS_PLAYER"], + Required = true + }, + new CommandArgument + { + Name = _translationLookup["COMMANDS_ARGS_NOTE"], + Required = true + } + }; + + _metaService = metaService; + } + + public override async Task ExecuteAsync(GameEvent gameEvent) + { + var note = new ClientNoteMetaResponse + { + Note = gameEvent.Data?.Trim(), + OriginEntityId = gameEvent.Origin.ClientId, + ModifiedDate = DateTime.UtcNow + }; + await _metaService.SetPersistentMetaValue("ClientNotes", note, gameEvent.Target.ClientId); + gameEvent.Origin.Tell(_translationLookup["COMMANDS_ADD_CLIENT_NOTE_SUCCESS"]); + } +} diff --git a/SharedLibraryCore/Dtos/Meta/Responses/ClientNoteMetaResponse.cs b/SharedLibraryCore/Dtos/Meta/Responses/ClientNoteMetaResponse.cs new file mode 100644 index 000000000..3cdf821d3 --- /dev/null +++ b/SharedLibraryCore/Dtos/Meta/Responses/ClientNoteMetaResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Text.Json.Serialization; + +namespace SharedLibraryCore.Dtos.Meta.Responses; + +public class ClientNoteMetaResponse +{ + public string Note { get; set; } + public int OriginEntityId { get; set; } + [JsonIgnore] + public string OriginEntityName { get; set; } + public DateTime ModifiedDate { get; set; } +} diff --git a/SharedLibraryCore/Dtos/PlayerInfo.cs b/SharedLibraryCore/Dtos/PlayerInfo.cs index dfbd752f3..1f8d2008e 100644 --- a/SharedLibraryCore/Dtos/PlayerInfo.cs +++ b/SharedLibraryCore/Dtos/PlayerInfo.cs @@ -33,5 +33,6 @@ namespace SharedLibraryCore.Dtos public string ConnectProtocolUrl { get;set; } public string CurrentServerName { get; set; } public IGeoLocationResult GeoLocationInfo { get; set; } + public ClientNoteMetaResponse NoteMeta { get; set; } } } diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index 681aa6cf3..0bca3fc39 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -938,6 +938,14 @@ namespace SharedLibraryCore.Services return clientList; } + public async Task GetClientNameById(int clientId) + { + await using var context = _contextFactory.CreateContext(); + var match = await context.Clients.Select(client => new { client.CurrentAlias.Name, client.ClientId }) + .FirstOrDefaultAsync(client => client.ClientId == clientId); + return match?.Name; + } + #endregion } } diff --git a/WebfrontCore/Controllers/ActionController.cs b/WebfrontCore/Controllers/ActionController.cs index fb12df8ce..27764c0e2 100644 --- a/WebfrontCore/Controllers/ActionController.cs +++ b/WebfrontCore/Controllers/ActionController.cs @@ -7,11 +7,13 @@ using System.Threading.Tasks; using Data.Models; using Data.Models.Client; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; using SharedLibraryCore.Commands; using SharedLibraryCore.Configuration; using SharedLibraryCore.Dtos; +using SharedLibraryCore.Dtos.Meta.Responses; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; using WebfrontCore.ViewModels; @@ -32,6 +34,7 @@ namespace WebfrontCore.Controllers private readonly string _unflagCommandName; private readonly string _setLevelCommandName; private readonly string _setClientTagCommandName; + private readonly string _addClientNoteCommandName; public ActionController(IManager manager, IEnumerable registeredCommands, ApplicationConfiguration appConfig, IMetaServiceV2 metaService) : base(manager) @@ -76,6 +79,9 @@ namespace WebfrontCore.Controllers case "SetClientTagCommand": _setClientTagCommandName = cmd.Name; break; + case "AddClientNoteCommand": + _addClientNoteCommandName = cmd.Name; + break; } } } @@ -639,6 +645,52 @@ namespace WebfrontCore.Controllers $"{_appConfig.CommandPrefix}{_setClientTagCommandName} @{targetId} {clientTag}" })); } + + public async Task AddClientNoteForm(int id) + { + var existingNote = await _metaService.GetPersistentMetaValue("ClientNotes", id); + var info = new ActionInfo + { + ActionButtonLabel = Localization["WEBFRONT_CONFIGURATION_BUTTON_SAVE"], + Name = Localization["WEBFRONT_PROFILE_CONTEXT_MENU_NOTE"], + Inputs = new List + { + new() + { + Name = "note", + Label = Localization["WEBFRONT_ACTION_NOTE_FORM_NOTE"], + Value = existingNote?.Note, + Type = "textarea" + } + }, + Action = nameof(AddClientNote), + ShouldRefresh = true + }; + + return View("_ActionForm", info); + } + + public async Task AddClientNote(int targetId, string note) + { + if (note?.Length > 350 || note?.Count(c => c == '\n') > 4) + { + return StatusCode(StatusCodes.Status400BadRequest, new[] + { + new CommandResponseInfo + { + Response = Localization["WEBFRONT_ACTION_NOTE_INVALID_LENGTH"] + } + }); + } + + var server = Manager.GetServers().First(); + return await Task.FromResult(RedirectToAction("Execute", "Console", new + { + serverId = server.EndPoint, + command = + $"{_appConfig.CommandPrefix}{_addClientNoteCommandName} @{targetId} {note}" + })); + } private Dictionary GetPresetPenaltyReasons() => _appConfig.PresetPenaltyReasons.Values .Concat(_appConfig.GlobalRules) diff --git a/WebfrontCore/Controllers/Client/ClientController.cs b/WebfrontCore/Controllers/Client/ClientController.cs index 9c812a0ea..445c10f62 100644 --- a/WebfrontCore/Controllers/Client/ClientController.cs +++ b/WebfrontCore/Controllers/Client/ClientController.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Data.Models; +using SharedLibraryCore.Services; using Stats.Config; using WebfrontCore.Permissions; using WebfrontCore.ViewComponents; @@ -23,13 +24,15 @@ namespace WebfrontCore.Controllers private readonly IMetaServiceV2 _metaService; private readonly StatsConfiguration _config; private readonly IGeoLocationService _geoLocationService; + private readonly ClientService _clientService; public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config, - IGeoLocationService geoLocationService) : base(manager) + IGeoLocationService geoLocationService, ClientService clientService) : base(manager) { _metaService = metaService; _config = config; _geoLocationService = geoLocationService; + _clientService = clientService; } [Obsolete] @@ -53,18 +56,26 @@ namespace WebfrontCore.Controllers { _metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, client.ClientId, token), - _metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token) + _metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token), + }; var persistentMeta = await Task.WhenAll(persistentMetaTask); var tag = persistentMeta[0]; var gravatar = persistentMeta[1]; + var note = await _metaService.GetPersistentMetaValue("ClientNotes", client.ClientId, + token); if (tag?.Value != null) { client.SetAdditionalProperty(EFMeta.ClientTagV2, tag.Value); } + if (note is not null) + { + note.OriginEntityName = await _clientService.GetClientNameById(note.OriginEntityId); + } + // even though we haven't set their level to "banned" yet // (ie they haven't reconnected with the infringing player identifier) // we want to show them as banned as to not confuse people. @@ -123,7 +134,8 @@ namespace WebfrontCore.Controllers : ingameClient.CurrentServer.IP, ingameClient.CurrentServer.Port), CurrentServerName = ingameClient?.CurrentServer?.Hostname, - GeoLocationInfo = await _geoLocationService.Locate(client.IPAddressString) + GeoLocationInfo = await _geoLocationService.Locate(client.IPAddressString), + NoteMeta = note }; var meta = await _metaService.GetRuntimeMeta(new ClientPaginationRequest diff --git a/WebfrontCore/Permissions/WebfrontEntity.cs b/WebfrontCore/Permissions/WebfrontEntity.cs index c9bd4558c..a6a045826 100644 --- a/WebfrontCore/Permissions/WebfrontEntity.cs +++ b/WebfrontCore/Permissions/WebfrontEntity.cs @@ -14,13 +14,13 @@ public enum WebfrontEntity AuditPage, RecentPlayersPage, ProfilePage, - AdminMenu + AdminMenu, + ClientNote } public enum WebfrontPermission { Read, - Create, - Update, + Write, Delete } diff --git a/WebfrontCore/Views/Action/_ActionForm.cshtml b/WebfrontCore/Views/Action/_ActionForm.cshtml index a12779099..f8220137a 100644 --- a/WebfrontCore/Views/Action/_ActionForm.cshtml +++ b/WebfrontCore/Views/Action/_ActionForm.cshtml @@ -51,6 +51,11 @@ } + + else if (inputType == "textarea") + { + + } else { diff --git a/WebfrontCore/Views/Client/Profile/Index.cshtml b/WebfrontCore/Views/Client/Profile/Index.cshtml index b92a44ebc..55846bc40 100644 --- a/WebfrontCore/Views/Client/Profile/Index.cshtml +++ b/WebfrontCore/Views/Client/Profile/Index.cshtml @@ -173,6 +173,25 @@ + @if (!string.IsNullOrWhiteSpace(Model.NoteMeta?.Note)) + { + +
+ +
+ @foreach (var line in Model.NoteMeta.Note.Split("\n")) + { +
@line.TrimEnd('\r')
+ } +
+ @Model.NoteMeta.OriginEntityName + — @Model.NoteMeta.ModifiedDate.HumanizeForCurrentCulture() +
+
+
+
+ } +