diff --git a/Application/Commands/ClientTags/AddClientTagCommand.cs b/Application/Commands/ClientTags/AddClientTagCommand.cs new file mode 100644 index 000000000..7aa126668 --- /dev/null +++ b/Application/Commands/ClientTags/AddClientTagCommand.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Data.Models; +using Data.Models.Client; +using Microsoft.Extensions.Logging; +using SharedLibraryCore; +using SharedLibraryCore.Commands; +using SharedLibraryCore.Configuration; +using SharedLibraryCore.Interfaces; + +namespace IW4MAdmin.Application.Commands.ClientTags +{ + public class AddClientTagCommand : Command + { + private readonly IMetaServiceV2 _metaService; + + public AddClientTagCommand(ILogger commandLogger, CommandConfiguration config, + ITranslationLookup layout, IMetaServiceV2 metaService) : + base(config, layout) + { + Name = "addclienttag"; + Description = layout["COMMANDS_ADD_CLIENT_TAG_DESC"]; + Alias = "act"; + Permission = EFClient.Permission.Owner; + RequiresTarget = false; + Arguments = new[] + { + new CommandArgument + { + Name = _translationLookup["COMMANDS_ARGUMENT_TAG"], + Required = true + } + }; + + _metaService = metaService; + logger = commandLogger; + } + + public override async Task ExecuteAsync(GameEvent gameEvent) + { + var existingTags = await _metaService.GetPersistentMetaValue>(EFMeta.ClientTagNameV2) ?? + new List(); + + var tagName = gameEvent.Data.Trim(); + + if (existingTags.Any(tag => tag.TagName == tagName)) + { + logger.LogWarning("Tag with name {TagName} already exists", tagName); + return; + } + + existingTags.Add(new TagMeta + { + Id = (existingTags.LastOrDefault()?.TagId ?? 0) + 1, + Value = tagName + }); + + await _metaService.SetPersistentMetaValue(EFMeta.ClientTagNameV2, existingTags, + gameEvent.Owner.Manager.CancellationToken); + gameEvent.Origin.Tell(_translationLookup["COMMANDS_ADD_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data)); + } + } +} diff --git a/SharedLibraryCore/Commands/ListClientTags.cs b/Application/Commands/ClientTags/ListClientTags.cs similarity index 57% rename from SharedLibraryCore/Commands/ListClientTags.cs rename to Application/Commands/ClientTags/ListClientTags.cs index ee5b0555f..ac61de095 100644 --- a/SharedLibraryCore/Commands/ListClientTags.cs +++ b/Application/Commands/ClientTags/ListClientTags.cs @@ -1,17 +1,19 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Data.Models; using Data.Models.Client; +using SharedLibraryCore; using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; -namespace SharedLibraryCore.Commands +namespace IW4MAdmin.Application.Commands.ClientTags { public class ListClientTags : Command { - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; - public ListClientTags(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : base( + public ListClientTags(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) : base( config, layout) { Name = "listclienttags"; @@ -19,14 +21,17 @@ namespace SharedLibraryCore.Commands Alias = "lct"; Permission = EFClient.Permission.Owner; RequiresTarget = false; - _metaService = metaService; } public override async Task ExecuteAsync(GameEvent gameEvent) { - var tags = await _metaService.GetPersistentMeta(EFMeta.ClientTagName); - gameEvent.Origin.Tell(tags.Select(tag => tag.Value)); + var tags = await _metaService.GetPersistentMetaValue>(EFMeta.ClientTagNameV2); + + if (tags is not null) + { + gameEvent.Origin.Tell(tags.Select(tag => tag.TagName)); + } } } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/Commands/RemoveClientTagCommand.cs b/Application/Commands/ClientTags/RemoveClientTagCommand.cs similarity index 57% rename from SharedLibraryCore/Commands/RemoveClientTagCommand.cs rename to Application/Commands/ClientTags/RemoveClientTagCommand.cs index 405c89aa8..fb5c67926 100644 --- a/SharedLibraryCore/Commands/RemoveClientTagCommand.cs +++ b/Application/Commands/ClientTags/RemoveClientTagCommand.cs @@ -1,16 +1,20 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Linq; using Data.Models; using Data.Models.Client; +using SharedLibraryCore; +using SharedLibraryCore.Commands; using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; -namespace SharedLibraryCore.Commands +namespace IW4MAdmin.Application.Commands.ClientTags { public class RemoveClientTag : Command { - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; - public RemoveClientTag(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : base( + public RemoveClientTag(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) : base( config, layout) { Name = "removeclienttag"; @@ -32,8 +36,13 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent gameEvent) { - await _metaService.RemovePersistentMeta(EFMeta.ClientTagName, gameEvent.Data); + var existingMeta = await _metaService.GetPersistentMetaValue>(EFMeta.ClientTagNameV2, + gameEvent.Owner.Manager.CancellationToken); + existingMeta = existingMeta.Where(meta => meta.TagName != gameEvent.Data.Trim()).ToList(); + await _metaService.SetPersistentMetaValue(EFMeta.ClientTagNameV2, existingMeta, + gameEvent.Owner.Manager.CancellationToken); + gameEvent.Origin.Tell(_translationLookup["COMMANDS_REMOVE_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data)); } } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/Commands/SetClientTagCommand.cs b/Application/Commands/ClientTags/SetClientTagCommand.cs similarity index 68% rename from SharedLibraryCore/Commands/SetClientTagCommand.cs rename to Application/Commands/ClientTags/SetClientTagCommand.cs index 37959412a..888e48339 100644 --- a/SharedLibraryCore/Commands/SetClientTagCommand.cs +++ b/Application/Commands/ClientTags/SetClientTagCommand.cs @@ -1,18 +1,22 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Data.Models; using Data.Models.Client; +using SharedLibraryCore; +using SharedLibraryCore.Commands; using SharedLibraryCore.Configuration; +using SharedLibraryCore.Dtos; using SharedLibraryCore.Interfaces; -namespace SharedLibraryCore.Commands +namespace IW4MAdmin.Application.Commands.ClientTags { public class SetClientTagCommand : Command { - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; - public SetClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : + public SetClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) : base(config, layout) { Name = "setclienttag"; @@ -34,8 +38,10 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent gameEvent) { - var availableTags = await _metaService.GetPersistentMeta(EFMeta.ClientTagName); - var matchingTag = availableTags.FirstOrDefault(tag => tag.Value == gameEvent.Data); + var token = gameEvent.Owner.Manager.CancellationToken; + + var availableTags = await _metaService.GetPersistentMetaValue>>(EFMeta.ClientTagNameV2, token); + var matchingTag = availableTags.FirstOrDefault(tag => tag.Value == gameEvent.Data.Trim()); if (matchingTag == null) { @@ -44,8 +50,9 @@ namespace SharedLibraryCore.Commands } gameEvent.Target.Tag = matchingTag.Value; - await _metaService.AddPersistentMeta(EFMeta.ClientTag, string.Empty, gameEvent.Target, matchingTag); + await _metaService.SetPersistentMetaForLookupKey(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, matchingTag.Id, + gameEvent.Target.ClientId, token); gameEvent.Origin.Tell(_translationLookup["COMMANDS_SET_CLIENT_TAG_SUCCESS"].FormatExt(matchingTag.Value)); } } -} \ No newline at end of file +} diff --git a/Application/Commands/ClientTags/TagMeta.cs b/Application/Commands/ClientTags/TagMeta.cs new file mode 100644 index 000000000..c5afcdca8 --- /dev/null +++ b/Application/Commands/ClientTags/TagMeta.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using SharedLibraryCore.Interfaces; + +namespace IW4MAdmin.Application.Commands.ClientTags; + +public class TagMeta : ILookupValue +{ + [JsonIgnore] public int TagId => Id; + [JsonIgnore] public string TagName => Value; + + public int Id { get; set; } + public string Value { get; set; } +} diff --git a/SharedLibraryCore/Commands/UnsetClientTagCommand.cs b/Application/Commands/ClientTags/UnsetClientTagCommand.cs similarity index 77% rename from SharedLibraryCore/Commands/UnsetClientTagCommand.cs rename to Application/Commands/ClientTags/UnsetClientTagCommand.cs index 64a9e40f5..d3cc5a1ef 100644 --- a/SharedLibraryCore/Commands/UnsetClientTagCommand.cs +++ b/Application/Commands/ClientTags/UnsetClientTagCommand.cs @@ -1,17 +1,18 @@ using System.Threading.Tasks; using Data.Models; using Data.Models.Client; +using SharedLibraryCore; +using SharedLibraryCore.Commands; using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; -namespace SharedLibraryCore.Commands +namespace IW4MAdmin.Application.Commands.ClientTags { public class UnsetClientTagCommand : Command { - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; - - public UnsetClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : + public UnsetClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) : base(config, layout) { Name = "unsetclienttag"; @@ -34,8 +35,9 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent gameEvent) { gameEvent.Target.Tag = null; - await _metaService.RemovePersistentMeta(EFMeta.ClientTag, gameEvent.Target); + await _metaService.RemovePersistentMeta(EFMeta.ClientTagV2, gameEvent.Target.ClientId, + gameEvent.Owner.Manager.CancellationToken); gameEvent.Origin.Tell(_translationLookup["COMMANDS_UNSET_CLIENT_TAG_SUCCESS"]); } } -} \ No newline at end of file +} diff --git a/Application/Factories/GameServerInstanceFactory.cs b/Application/Factories/GameServerInstanceFactory.cs index 0e222fbcd..9cac20ffc 100644 --- a/Application/Factories/GameServerInstanceFactory.cs +++ b/Application/Factories/GameServerInstanceFactory.cs @@ -14,7 +14,7 @@ namespace IW4MAdmin.Application.Factories internal class GameServerInstanceFactory : IGameServerInstanceFactory { private readonly ITranslationLookup _translationLookup; - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; private readonly IServiceProvider _serviceProvider; /// @@ -23,7 +23,7 @@ namespace IW4MAdmin.Application.Factories /// /// public GameServerInstanceFactory(ITranslationLookup translationLookup, - IMetaService metaService, + IMetaServiceV2 metaService, IServiceProvider serviceProvider) { _translationLookup = translationLookup; @@ -45,4 +45,4 @@ namespace IW4MAdmin.Application.Factories _serviceProvider.GetRequiredService>()); } } -} \ No newline at end of file +} diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index bea9fe374..7b1c06601 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -35,7 +35,7 @@ namespace IW4MAdmin private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex; public GameLogEventDetection LogEvent; private readonly ITranslationLookup _translationLookup; - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; private const int REPORT_FLAG_COUNT = 4; private long lastGameTime = 0; @@ -49,7 +49,7 @@ namespace IW4MAdmin ServerConfiguration serverConfiguration, CommandConfiguration commandConfiguration, ITranslationLookup lookup, - IMetaService metaService, + IMetaServiceV2 metaService, IServiceProvider serviceProvider, IClientNoticeMessageFormatter messageFormatter, ILookupCache serverCache) : base(serviceProvider.GetRequiredService>(), @@ -350,7 +350,8 @@ namespace IW4MAdmin Time = DateTime.UtcNow }); - var clientTag = await _metaService.GetPersistentMeta(EFMeta.ClientTag, E.Origin); + var clientTag = await _metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, + EFMeta.ClientTagNameV2, E.Origin.ClientId, Manager.CancellationToken); if (clientTag?.LinkedMeta != null) { @@ -568,8 +569,10 @@ namespace IW4MAdmin Time = DateTime.UtcNow }); - await _metaService.AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin); - await _metaService.AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin); + await _metaService.SetPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin.ClientId, + Manager.CancellationToken); + await _metaService.SetPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin.ClientId, + Manager.CancellationToken); } else if (E.Type == GameEvent.EventType.PreDisconnect) diff --git a/Application/Main.cs b/Application/Main.cs index 478c74e67..946a4e17d 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -278,7 +278,7 @@ namespace IW4MAdmin.Application // register the native commands foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes() - .Concat(typeof(Program).Assembly.GetTypes().Where(type => type.Namespace == "IW4MAdmin.Application.Commands")) + .Concat(typeof(Program).Assembly.GetTypes().Where(type => type.Namespace?.StartsWith("IW4MAdmin.Application.Commands") ?? false)) .Where(command => command.BaseType == typeof(Command))) { defaultLogger.LogDebug("Registered native command type {Name}", commandType.Name); @@ -406,6 +406,7 @@ namespace IW4MAdmin.Application .AddSingleton() .AddSingleton, ClientService>() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Application/Meta/MetaRegistration.cs b/Application/Meta/MetaRegistration.cs index 186299f6d..7d4cd8270 100644 --- a/Application/Meta/MetaRegistration.cs +++ b/Application/Meta/MetaRegistration.cs @@ -5,6 +5,7 @@ using SharedLibraryCore.Interfaces; using SharedLibraryCore.QueryHelper; using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -15,17 +16,22 @@ namespace IW4MAdmin.Application.Meta { private readonly ILogger _logger; private ITranslationLookup _transLookup; - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; private readonly IEntityService _clientEntityService; private readonly IResourceQueryHelper _receivedPenaltyHelper; - private readonly IResourceQueryHelper _administeredPenaltyHelper; + + private readonly IResourceQueryHelper + _administeredPenaltyHelper; + private readonly IResourceQueryHelper _updatedAliasHelper; + private readonly IResourceQueryHelper _connectionHistoryHelper; + private readonly IResourceQueryHelper _permissionLevelHelper; - public MetaRegistration(ILogger logger, IMetaService metaService, + public MetaRegistration(ILogger logger, IMetaServiceV2 metaService, ITranslationLookup transLookup, IEntityService clientEntityService, IResourceQueryHelper receivedPenaltyHelper, IResourceQueryHelper administeredPenaltyHelper, @@ -46,19 +52,26 @@ namespace IW4MAdmin.Application.Meta public void Register() { - _metaService.AddRuntimeMeta(MetaType.Information, GetProfileMeta); - _metaService.AddRuntimeMeta(MetaType.ReceivedPenalty, GetReceivedPenaltiesMeta); - _metaService.AddRuntimeMeta(MetaType.Penalized, GetAdministeredPenaltiesMeta); - _metaService.AddRuntimeMeta(MetaType.AliasUpdate, GetUpdatedAliasMeta); - _metaService.AddRuntimeMeta(MetaType.ConnectionHistory, GetConnectionHistoryMeta); + _metaService.AddRuntimeMeta(MetaType.Information, + GetProfileMeta); + _metaService.AddRuntimeMeta(MetaType.ReceivedPenalty, + GetReceivedPenaltiesMeta); + _metaService.AddRuntimeMeta(MetaType.Penalized, + GetAdministeredPenaltiesMeta); + _metaService.AddRuntimeMeta(MetaType.AliasUpdate, + GetUpdatedAliasMeta); + _metaService.AddRuntimeMeta(MetaType.ConnectionHistory, + GetConnectionHistoryMeta); _metaService.AddRuntimeMeta( MetaType.PermissionLevel, GetPermissionLevelMeta); } - private async Task> GetProfileMeta(ClientPaginationRequest request) + private async Task> GetProfileMeta(ClientPaginationRequest request, + CancellationToken cancellationToken = default) { var metaList = new List(); - var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = request.ClientId }); + var lastMapMeta = + await _metaService.GetPersistentMeta("LastMapPlayed", request.ClientId, cancellationToken); if (lastMapMeta != null) { @@ -75,7 +88,8 @@ namespace IW4MAdmin.Application.Meta }); } - var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = request.ClientId }); + var lastServerMeta = + await _metaService.GetPersistentMeta("LastServerPlayed", request.ClientId, cancellationToken); if (lastServerMeta != null) { @@ -96,7 +110,7 @@ namespace IW4MAdmin.Application.Meta if (client == null) { - _logger.LogWarning("No client found with id {clientId} when generating profile meta", request.ClientId); + _logger.LogWarning("No client found with id {ClientId} when generating profile meta", request.ClientId); return metaList; } @@ -137,7 +151,8 @@ namespace IW4MAdmin.Application.Meta { ClientId = client.ClientId, Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"], - Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), + Value = client.Connections.ToString("#,##0", + new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), ShouldDisplay = true, Column = 1, Order = 3, @@ -148,7 +163,9 @@ namespace IW4MAdmin.Application.Meta { 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"], + Value = client.Masked + ? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"] + : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"], IsSensitive = true, Column = 1, Order = 4, @@ -158,32 +175,36 @@ namespace IW4MAdmin.Application.Meta return metaList; } - private async Task> GetReceivedPenaltiesMeta(ClientPaginationRequest request) + private async Task> GetReceivedPenaltiesMeta( + ClientPaginationRequest request, CancellationToken token = default) { var penalties = await _receivedPenaltyHelper.QueryResource(request); return penalties.Results; } - private async Task> GetAdministeredPenaltiesMeta(ClientPaginationRequest request) + private async Task> GetAdministeredPenaltiesMeta( + ClientPaginationRequest request, CancellationToken token = default) { var penalties = await _administeredPenaltyHelper.QueryResource(request); return penalties.Results; } - private async Task> GetUpdatedAliasMeta(ClientPaginationRequest request) + private async Task> GetUpdatedAliasMeta(ClientPaginationRequest request, + CancellationToken token = default) { var aliases = await _updatedAliasHelper.QueryResource(request); return aliases.Results; } - - private async Task> GetConnectionHistoryMeta(ClientPaginationRequest request) + + private async Task> GetConnectionHistoryMeta( + ClientPaginationRequest request, CancellationToken token = default) { var connections = await _connectionHistoryHelper.QueryResource(request); return connections.Results; } private async Task> GetPermissionLevelMeta( - ClientPaginationRequest request) + ClientPaginationRequest request, CancellationToken token = default) { var permissionChanges = await _permissionLevelHelper.QueryResource(request); return permissionChanges.Results; diff --git a/Application/Misc/MetaServiceV2.cs b/Application/Misc/MetaServiceV2.cs new file mode 100644 index 000000000..8fb49bff5 --- /dev/null +++ b/Application/Misc/MetaServiceV2.cs @@ -0,0 +1,500 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Data.Abstractions; +using Data.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using SharedLibraryCore.Dtos; +using SharedLibraryCore.Interfaces; +using SharedLibraryCore.QueryHelper; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace IW4MAdmin.Application.Misc; + +public class MetaServiceV2 : IMetaServiceV2 +{ + private readonly IDictionary> _metaActions; + private readonly IDatabaseContextFactory _contextFactory; + private readonly ILogger _logger; + + public MetaServiceV2(ILogger logger, IDatabaseContextFactory contextFactory) + { + _logger = logger; + _metaActions = new Dictionary>(); + _contextFactory = contextFactory; + } + + public async Task SetPersistentMeta(string metaKey, string metaValue, int clientId, + CancellationToken token = default) + { + if (!ValidArgs(metaKey, clientId)) + { + return; + } + + await using var context = _contextFactory.CreateContext(); + + var existingMeta = await context.EFMeta + .Where(meta => meta.Key == metaKey) + .Where(meta => meta.ClientId == clientId) + .FirstOrDefaultAsync(token); + + if (existingMeta != null) + { + _logger.LogDebug("Updating existing meta with key {Key} and id {Id}", existingMeta.Key, + existingMeta.MetaId); + existingMeta.Value = metaValue; + existingMeta.Updated = DateTime.UtcNow; + } + + else + { + _logger.LogDebug("Adding new meta with key {Key}", metaKey); + context.EFMeta.Add(new EFMeta + { + ClientId = clientId, + Created = DateTime.UtcNow, + Key = metaKey, + Value = metaValue, + }); + } + + await context.SaveChangesAsync(token); + } + + public async Task SetPersistentMetaValue(string metaKey, T metaValue, int clientId, + CancellationToken token = default) where T : class + { + if (!ValidArgs(metaKey, clientId)) + { + return; + } + + string serializedValue; + + try + { + serializedValue = JsonSerializer.Serialize(metaValue); + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not serialize meta with key {Key}", metaKey); + return; + } + + await SetPersistentMeta(metaKey, serializedValue, clientId, token); + } + + public async Task SetPersistentMetaForLookupKey(string metaKey, string lookupKey, int lookupId, int clientId, + CancellationToken token = default) + { + if (!ValidArgs(metaKey, clientId)) + { + return; + } + + await using var context = _contextFactory.CreateContext(); + + var lookupMeta = await context.EFMeta.FirstOrDefaultAsync(meta => meta.Key == lookupKey, token); + + if (lookupMeta is null) + { + _logger.LogWarning("No lookup meta exists for metaKey {MetaKey} and lookupKey {LookupKey}", metaKey, + lookupKey); + return; + } + + var lookupValues = JsonSerializer.Deserialize>>(lookupMeta.Value); + + if (lookupValues is null) + { + return; + } + + var foundLookup = lookupValues.FirstOrDefault(value => value.Id == lookupId); + + if (foundLookup is null) + { + _logger.LogWarning("No lookup meta found for provided lookup id {MetaKey}, {LookupKey}, {LookupId}", + metaKey, lookupKey, lookupId); + return; + } + + _logger.LogDebug("Setting meta for lookup {MetaKey}, {LookupKey}, {LookupId}", + metaKey, lookupKey, lookupId); + + await SetPersistentMeta(metaKey, lookupId.ToString(), clientId, token); + } + + public async Task IncrementPersistentMeta(string metaKey, int incrementAmount, int clientId, + CancellationToken token = default) + { + if (!ValidArgs(metaKey, clientId)) + { + return; + } + + var existingMeta = await GetPersistentMeta(metaKey, clientId, token); + + if (!long.TryParse(existingMeta?.Value ?? "0", out var existingValue)) + { + existingValue = 0; + } + + var newValue = existingValue + incrementAmount; + await SetPersistentMeta(metaKey, newValue.ToString(), clientId, token); + } + + public async Task DecrementPersistentMeta(string metaKey, int decrementAmount, int clientId, + CancellationToken token = default) + { + await IncrementPersistentMeta(metaKey, -decrementAmount, clientId, token); + } + + public async Task GetPersistentMeta(string metaKey, int clientId, CancellationToken token = default) + { + if (!ValidArgs(metaKey, clientId)) + { + return null; + } + + await using var ctx = _contextFactory.CreateContext(enableTracking: false); + + return await ctx.EFMeta + .Where(meta => meta.Key == metaKey) + .Where(meta => meta.ClientId == clientId) + .Select(meta => new EFMeta + { + MetaId = meta.MetaId, + Key = meta.Key, + ClientId = meta.ClientId, + Value = meta.Value, + }) + .FirstOrDefaultAsync(token); + } + + public async Task GetPersistentMetaValue(string metaKey, int clientId, CancellationToken token = default) + where T : class + { + var meta = await GetPersistentMeta(metaKey, clientId, token); + + if (meta is null) + { + return default; + } + + try + { + return JsonSerializer.Deserialize(meta.Value); + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not deserialize meta with key {Key} and value {Value}", metaKey, meta.Value); + return default; + } + } + + public async Task GetPersistentMetaByLookup(string metaKey, string lookupKey, int clientId, + CancellationToken token = default) + { + await using var context = _contextFactory.CreateContext(); + + var metaValue = await GetPersistentMeta(metaKey, clientId, token); + + if (metaValue is null) + { + _logger.LogWarning("No meta exists for key {Key}, clientId {ClientId}", metaKey, clientId); + return default; + } + + var lookupMeta = await context.EFMeta.FirstOrDefaultAsync(meta => meta.Key == lookupKey, token); + + if (lookupMeta is null) + { + _logger.LogWarning("No lookup meta exists for metaKey {MetaKey} and lookupKey {LookupKey}", metaKey, + lookupKey); + return default; + } + + var lookupId = int.Parse(metaValue.Value); + var lookupValues = JsonSerializer.Deserialize>>(lookupMeta.Value); + + if (lookupValues is null) + { + return default; + } + + var foundLookup = lookupValues.FirstOrDefault(value => value.Id == lookupId); + + if (foundLookup is not null) + { + return new EFMeta + { + Created = metaValue.Created, + Updated = metaValue.Updated, + Extra = metaValue.Extra, + MetaId = metaValue.MetaId, + Value = foundLookup.Value + }; + } + + _logger.LogWarning("No lookup meta found for provided lookup id {MetaKey}, {LookupKey}, {LookupId}", + metaKey, lookupKey, lookupId); + return default; + } + + public async Task RemovePersistentMeta(string metaKey, int clientId, CancellationToken token = default) + { + if (!ValidArgs(metaKey, clientId)) + { + return; + } + + await using var context = _contextFactory.CreateContext(); + + var existingMeta = await context.EFMeta + .FirstOrDefaultAsync(meta => meta.Key == metaKey && meta.ClientId == clientId, token); + + if (existingMeta == null) + { + _logger.LogDebug("No meta with key {Key} found for client id {Id}", metaKey, clientId); + return; + } + + _logger.LogDebug("Removing meta for key {Key} with id {Id}", metaKey, existingMeta.MetaId); + context.EFMeta.Remove(existingMeta); + await context.SaveChangesAsync(token); + } + + public async Task SetPersistentMeta(string metaKey, string metaValue, CancellationToken token = default) + { + if (string.IsNullOrWhiteSpace(metaKey)) + { + _logger.LogWarning("Cannot save meta with no key"); + return; + } + + await using var ctx = _contextFactory.CreateContext(); + + var existingMeta = await ctx.EFMeta + .Where(meta => meta.Key == metaKey) + .Where(meta => meta.ClientId == null) + .FirstOrDefaultAsync(token); + + if (existingMeta is not null) + { + _logger.LogDebug("Updating existing meta with key {Key} and id {Id}", existingMeta.Key, + existingMeta.MetaId); + + existingMeta.Value = metaValue; + existingMeta.Updated = DateTime.UtcNow; + + await ctx.SaveChangesAsync(token); + } + + else + { + _logger.LogDebug("Adding new meta with key {Key}", metaKey); + + ctx.EFMeta.Add(new EFMeta + { + Created = DateTime.UtcNow, + Key = metaKey, + Value = metaValue + }); + + await ctx.SaveChangesAsync(token); + } + } + + public async Task SetPersistentMetaValue(string metaKey, T metaValue, CancellationToken token = default) + where T : class + { + if (string.IsNullOrWhiteSpace(metaKey)) + { + _logger.LogWarning("Meta key is null, not setting"); + return; + } + + if (metaValue is null) + { + _logger.LogWarning("Meta value is null, not setting"); + return; + } + + string serializedMeta; + try + { + serializedMeta = JsonSerializer.Serialize(metaValue); + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not serialize meta with {Key} and value {Value}", metaKey, metaValue); + return; + } + + await SetPersistentMeta(metaKey, serializedMeta, token); + } + + public async Task GetPersistentMeta(string metaKey, CancellationToken token = default) + { + if (string.IsNullOrWhiteSpace(metaKey)) + { + return null; + } + + await using var context = _contextFactory.CreateContext(false); + return await context.EFMeta.FirstOrDefaultAsync(meta => meta.Key == metaKey, token); + } + + public async Task GetPersistentMetaValue(string metaKey, CancellationToken token = default) where T : class + { + if (string.IsNullOrWhiteSpace(metaKey)) + { + return default; + } + + var meta = await GetPersistentMeta(metaKey, token); + + if (meta is null) + { + return default; + } + + try + { + return JsonSerializer.Deserialize(meta.Value); + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not serialize meta with key {Key} and value {Value}", metaKey, meta.Value); + return default; + } + } + + public async Task RemovePersistentMeta(string metaKey, CancellationToken token = default) + { + if (string.IsNullOrWhiteSpace(metaKey)) + { + return; + } + + await using var context = _contextFactory.CreateContext(enableTracking: false); + + var existingMeta = await context.EFMeta + .Where(meta => meta.Key == metaKey) + .Where(meta => meta.ClientId == null) + .FirstOrDefaultAsync(token); + + if (existingMeta != null) + { + _logger.LogDebug("Removing meta for key {Key} with id {Id}", metaKey, existingMeta.MetaId); + context.Remove(existingMeta); + await context.SaveChangesAsync(token); + } + } + + public void AddRuntimeMeta(MetaType metaKey, + Func>> metaAction) + where T : PaginationRequest where TReturnType : IClientMeta + { + if (!_metaActions.ContainsKey(metaKey)) + { + _metaActions.Add(metaKey, new List { metaAction }); + } + + else + { + _metaActions[metaKey].Add(metaAction); + } + } + + public async Task> GetRuntimeMeta(ClientPaginationRequest request, CancellationToken token = default) + { + var metas = await Task.WhenAll(_metaActions.Where(kvp => kvp.Key != MetaType.Information) + .Select(async kvp => await kvp.Value[0](request, token))); + + return metas.SelectMany(m => (IEnumerable)m) + .OrderByDescending(m => m.When) + .Take(request.Count) + .ToList(); + } + + public async Task> GetRuntimeMeta(ClientPaginationRequest request, MetaType metaType, CancellationToken token = default) + where T : IClientMeta + { + if (metaType == MetaType.Information) + { + var allMeta = new List(); + + var completedMeta = await Task.WhenAll(_metaActions[metaType].Select(async individualMetaRegistration => + (IEnumerable)await individualMetaRegistration(request, token))); + + allMeta.AddRange(completedMeta.SelectMany(meta => meta)); + + return ProcessInformationMeta(allMeta); + } + + var meta = await _metaActions[metaType][0](request) as IEnumerable; + + return meta; + } + + private static IEnumerable ProcessInformationMeta(IEnumerable meta) where T : IClientMeta + { + var metaList = meta.ToList(); + var metaWithColumn = metaList + .Where(m => m.Column != null) + .ToList(); + + var columnGrouping = metaWithColumn + .GroupBy(m => m.Column) + .ToList(); + + var metaToSort = metaList.Except(metaWithColumn).ToList(); + + var table = columnGrouping.Select(metaItem => new List(metaItem)).ToList(); + + while (metaToSort.Count > 0) + { + var sortingMeta = metaToSort.First(); + + int IndexOfSmallestColumn() + { + var index = 0; + var smallestColumnSize = int.MaxValue; + for (var i = 0; i < table.Count; i++) + { + if (table[i].Count >= smallestColumnSize) + { + continue; + } + + smallestColumnSize = table[i].Count; + index = i; + } + + return index; + } + + var columnIndex = IndexOfSmallestColumn(); + + sortingMeta.Column = columnIndex; + sortingMeta.Order = columnGrouping + .First(group => group.Key == columnIndex) + .Count(); + + table[columnIndex].Add(sortingMeta); + + metaToSort.Remove(sortingMeta); + } + + return metaList; + } + + private static bool ValidArgs(string key, int clientId) => !string.IsNullOrWhiteSpace(key) && clientId > 0; +} diff --git a/Data/Models/EFMeta.cs b/Data/Models/EFMeta.cs index 2d2f81ac2..8b94c2ae6 100644 --- a/Data/Models/EFMeta.cs +++ b/Data/Models/EFMeta.cs @@ -10,8 +10,8 @@ namespace Data.Models /// public class EFMeta : SharedEntity { - public const string ClientTagName = nameof(ClientTagName); - public const string ClientTag = nameof(ClientTag); + public const string ClientTagNameV2 = nameof(ClientTagNameV2); + public const string ClientTagV2 = nameof(ClientTagV2); [Key] public int MetaId { get; set; } diff --git a/SharedLibraryCore/Commands/AddClientTagCommand.cs b/SharedLibraryCore/Commands/AddClientTagCommand.cs deleted file mode 100644 index 664b51d6f..000000000 --- a/SharedLibraryCore/Commands/AddClientTagCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Threading.Tasks; -using Data.Models; -using Data.Models.Client; -using SharedLibraryCore.Configuration; -using SharedLibraryCore.Interfaces; - -namespace SharedLibraryCore.Commands -{ - public class AddClientTagCommand : Command - { - private readonly IMetaService _metaService; - - public AddClientTagCommand(CommandConfiguration config, ITranslationLookup layout, IMetaService metaService) : - base(config, layout) - { - Name = "addclienttag"; - Description = layout["COMMANDS_ADD_CLIENT_TAG_DESC"]; - Alias = "act"; - Permission = EFClient.Permission.Owner; - RequiresTarget = false; - Arguments = new[] - { - new CommandArgument - { - Name = _translationLookup["COMMANDS_ARGUMENT_TAG"], - Required = true - } - }; - - _metaService = metaService; - } - - public override async Task ExecuteAsync(GameEvent gameEvent) - { - await _metaService.AddPersistentMeta(EFMeta.ClientTagName, gameEvent.Data); - gameEvent.Origin.Tell(_translationLookup["COMMANDS_ADD_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data)); - } - } -} \ No newline at end of file diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index efed1ffc4..ae9624f20 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -1137,10 +1137,10 @@ namespace SharedLibraryCore.Commands /// public class SetGravatarCommand : Command { - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup, - IMetaService metaService) : base(config, translationLookup) + IMetaServiceV2 metaService) : base(config, translationLookup) { Name = "setgravatar"; Description = _translationLookup["COMMANDS_GRAVATAR_DESC"]; @@ -1166,7 +1166,8 @@ namespace SharedLibraryCore.Commands var gravatarEmail = string.Concat(md5 .ComputeHash(E.Data.ToLower().Select(d => Convert.ToByte(d)).ToArray()) .Select(h => h.ToString("x2"))); - await _metaService.AddPersistentMeta("GravatarEmail", gravatarEmail, E.Origin); + await _metaService.SetPersistentMeta("GravatarEmail", gravatarEmail, E.Origin.ClientId, + E.Owner.Manager.CancellationToken); } E.Origin.Tell(_translationLookup["COMMANDS_GRAVATAR_SUCCESS_NEW"]); diff --git a/SharedLibraryCore/Dtos/LookupValue.cs b/SharedLibraryCore/Dtos/LookupValue.cs new file mode 100644 index 000000000..dba21dfc8 --- /dev/null +++ b/SharedLibraryCore/Dtos/LookupValue.cs @@ -0,0 +1,9 @@ +using SharedLibraryCore.Interfaces; + +namespace SharedLibraryCore.Dtos; + +public class LookupValue : ILookupValue +{ + public int Id { get; set; } + public TObject Value { get; set; } +} diff --git a/SharedLibraryCore/Interfaces/ILookupValue.cs b/SharedLibraryCore/Interfaces/ILookupValue.cs new file mode 100644 index 000000000..a1ba5edc8 --- /dev/null +++ b/SharedLibraryCore/Interfaces/ILookupValue.cs @@ -0,0 +1,7 @@ +namespace SharedLibraryCore.Interfaces; + +public interface ILookupValue +{ + int Id { get; } + TObject Value { get; } +} diff --git a/SharedLibraryCore/Interfaces/IMetaServiceV2.cs b/SharedLibraryCore/Interfaces/IMetaServiceV2.cs new file mode 100644 index 000000000..2b09c7fa2 --- /dev/null +++ b/SharedLibraryCore/Interfaces/IMetaServiceV2.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Data.Models; +using SharedLibraryCore.Dtos; +using SharedLibraryCore.QueryHelper; + +namespace SharedLibraryCore.Interfaces; + +public interface IMetaServiceV2 +{ + #region PER_CLIENT + + /// + /// adds or updates meta key and value to the database as simple string + /// + /// key of meta data + /// value of the meta data + /// id of the client to save the meta for + /// + /// + Task SetPersistentMeta(string metaKey, string metaValue, int clientId, CancellationToken token = default); + + /// + /// add or update meta key and value to the database as serialized object type + /// + /// + /// + /// + /// + /// type of object being serialized + /// + Task SetPersistentMetaValue(string metaKey, T metaValue, int clientId, CancellationToken token = default) + where T : class; + + /// + /// Sets meta key to a linked lookup key and id + /// + /// Key for the client meta + /// Key of the global lookup meta + /// Id in the list of lookup values + /// id of the client + /// + /// + /// + Task SetPersistentMetaForLookupKey(string metaKey, string lookupKey, int lookupId, int clientId, + CancellationToken token = default); + + /// + /// increments meta value and persists to the database + /// if the meta value does not already exist it will be set to the increment amount + /// the assumption is made that the existing value is + /// + /// key of meta data + /// value to increment by + /// id of the client to save the meta for + /// + /// + Task IncrementPersistentMeta(string metaKey, int incrementAmount, int clientId, + CancellationToken token = default); + + /// + /// decrements meta value and persists to the database + /// if the meta value does not already exist it will be set to the decrement amount + /// the assumption is made that the existing value is + /// + /// key of meta data + /// value to increment by + /// id of the client to save the meta for + /// + /// + Task DecrementPersistentMeta(string metaKey, int decrementAmount, int clientId, + CancellationToken token = default); + + /// + /// retrieves meta entry + /// + /// + /// + /// + /// + Task GetPersistentMeta(string metaKey, int clientId, CancellationToken token = default); + + /// + /// retrieves meta value as deserialized object + /// + /// + /// + /// + /// object type to deserialize into + /// + Task GetPersistentMetaValue(string metaKey, int clientId, CancellationToken token = default) + where T : class; + + /// + /// retrieves meta entry by with associated lookup value as string + /// + /// + /// + /// + /// + /// + /// + Task GetPersistentMetaByLookup(string metaKey, string lookupKey, int clientId, + CancellationToken token = default); + + /// + /// removes meta key with given value + /// + /// key of meta data + /// client to delete the meta for + /// + /// + Task RemovePersistentMeta(string metaKey, int clientId, CancellationToken token = default); + + #endregion + + #region GLOBAL + + /// + /// adds or updates meta key and value to the database + /// + /// key of meta data + /// value of the meta data + /// + /// + Task SetPersistentMeta(string metaKey, string metaValue, CancellationToken token = default); + + /// + /// serializes and sets (create or update) meta key and value + /// + /// + /// + /// + /// + /// + Task SetPersistentMetaValue(string metaKey, T metaValue, CancellationToken token = default) where T : class; + + /// + /// removes meta key with given value + /// + /// key of the meta data + /// value of the meta data + /// + /// + Task RemovePersistentMeta(string metaKey, CancellationToken token = default); + + /// + /// retrieves collection of meta for given key + /// + /// key to retrieve values for + /// + /// + Task GetPersistentMeta(string metaKey, CancellationToken token = default); + + /// + /// returns value of meta key if it exists + /// + /// + /// + /// + /// + Task GetPersistentMetaValue(string metaKey, CancellationToken token = default) where T : class; + + #endregion + + /// + /// adds a meta task to the runtime meta list + /// + /// type of meta + /// action to perform + void AddRuntimeMeta(MetaType metaKey, + Func>> metaAction) + where TReturn : IClientMeta where T : PaginationRequest; + + /// + /// retrieves all the runtime meta information for given client idea + /// + /// request information + /// + /// + Task> GetRuntimeMeta(ClientPaginationRequest request, CancellationToken token = default); + + /// + /// retrieves all the runtime of provided type + /// + /// >request information + /// type of meta to retreive + /// + /// + Task> GetRuntimeMeta(ClientPaginationRequest request, MetaType metaType, CancellationToken token = default) + where T : IClientMeta; +} diff --git a/SharedLibraryCore/PartialEntities/EFClient.cs b/SharedLibraryCore/PartialEntities/EFClient.cs index 5ade87603..dcc3c17a6 100644 --- a/SharedLibraryCore/PartialEntities/EFClient.cs +++ b/SharedLibraryCore/PartialEntities/EFClient.cs @@ -124,8 +124,8 @@ namespace SharedLibraryCore.Database.Models [NotMapped] public string Tag { - get => GetAdditionalProperty(EFMeta.ClientTag); - set => SetAdditionalProperty(EFMeta.ClientTag, value); + get => GetAdditionalProperty(EFMeta.ClientTagV2); + set => SetAdditionalProperty(EFMeta.ClientTagV2, value); } [NotMapped] diff --git a/WebfrontCore/Controllers/Client/ClientController.cs b/WebfrontCore/Controllers/Client/ClientController.cs index bcae0de52..06d02d715 100644 --- a/WebfrontCore/Controllers/Client/ClientController.cs +++ b/WebfrontCore/Controllers/Client/ClientController.cs @@ -8,6 +8,7 @@ using SharedLibraryCore.QueryHelper; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Data.Models; using Stats.Config; @@ -17,16 +18,16 @@ namespace WebfrontCore.Controllers { public class ClientController : BaseController { - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; private readonly StatsConfiguration _config; - public ClientController(IManager manager, IMetaService metaService, StatsConfiguration config) : base(manager) + public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config) : base(manager) { _metaService = metaService; _config = config; } - public async Task ProfileAsync(int id, MetaType? metaFilterType) + public async Task ProfileAsync(int id, MetaType? metaFilterType, CancellationToken token = default) { var client = await Manager.GetClientService().Get(id); @@ -40,17 +41,17 @@ namespace WebfrontCore.Controllers var persistentMetaTask = new[] { - _metaService.GetPersistentMeta(EFMeta.ClientTag, client), - _metaService.GetPersistentMeta("GravatarEmail", client) + _metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, client.ClientId, token), + _metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token) }; var persistentMeta = await Task.WhenAll(persistentMetaTask); var tag = persistentMeta[0]; var gravatar = persistentMeta[1]; - if (tag?.LinkedMeta != null) + if (tag?.Value != null) { - client.SetAdditionalProperty(EFMeta.ClientTag, tag.LinkedMeta.Value); + client.SetAdditionalProperty(EFMeta.ClientTagV2, tag.Value); } // even though we haven't set their level to "banned" yet diff --git a/WebfrontCore/Startup.cs b/WebfrontCore/Startup.cs index 9f1e618b5..79d7fbdd1 100644 --- a/WebfrontCore/Startup.cs +++ b/WebfrontCore/Startup.cs @@ -116,13 +116,14 @@ namespace WebfrontCore // todo: this needs to be handled more gracefully services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); - services.AddSingleton(Program.ApplicationServiceProvider.GetService()); - services.AddSingleton(Program.ApplicationServiceProvider.GetService()); - services.AddSingleton(Program.ApplicationServiceProvider.GetService()); - services.AddSingleton(Program.ApplicationServiceProvider.GetService()); - services.AddSingleton(Program.ApplicationServiceProvider.GetService>()); - services.AddSingleton(Program.ApplicationServiceProvider.GetService()); - services.AddSingleton(Program.ApplicationServiceProvider.GetService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService>()); + services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService()); services.AddSingleton( Program.ApplicationServiceProvider.GetRequiredService()); diff --git a/WebfrontCore/ViewComponents/ProfileMetaListViewComponent.cs b/WebfrontCore/ViewComponents/ProfileMetaListViewComponent.cs index 5314960bd..e0fef24d2 100644 --- a/WebfrontCore/ViewComponents/ProfileMetaListViewComponent.cs +++ b/WebfrontCore/ViewComponents/ProfileMetaListViewComponent.cs @@ -13,9 +13,9 @@ namespace WebfrontCore.ViewComponents { public class ProfileMetaListViewComponent : ViewComponent { - private readonly IMetaService _metaService; + private readonly IMetaServiceV2 _metaService; - public ProfileMetaListViewComponent(IMetaService metaService) + public ProfileMetaListViewComponent(IMetaServiceV2 metaService) { _metaService = metaService; } @@ -38,7 +38,7 @@ namespace WebfrontCore.ViewComponents return View("_List", meta); } - public static async Task> GetClientMeta(IMetaService metaService, MetaType? metaType, + public static async Task> GetClientMeta(IMetaServiceV2 metaService, MetaType? metaType, EFClient.Permission level, ClientPaginationRequest request) { IEnumerable meta = null;