implement metaservice v2

This commit is contained in:
RaidMax 2022-03-23 08:43:57 -05:00
parent b46b1eb5e7
commit 04a4dcf153
21 changed files with 919 additions and 120 deletions

View File

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

View File

@ -1,17 +1,19 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using 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<List<TagMeta>>(EFMeta.ClientTagNameV2);
if (tags is not null)
{
gameEvent.Origin.Tell(tags.Select(tag => tag.TagName));
}
}
}
}

View File

@ -1,16 +1,20 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Data.Models;
using Data.Models.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,7 +36,12 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent gameEvent)
{
await _metaService.RemovePersistentMeta(EFMeta.ClientTagName, gameEvent.Data);
var existingMeta = await _metaService.GetPersistentMetaValue<List<TagMeta>>(EFMeta.ClientTagNameV2,
gameEvent.Owner.Manager.CancellationToken);
existingMeta = existingMeta.Where(meta => meta.TagName != gameEvent.Data.Trim()).ToList();
await _metaService.SetPersistentMetaValue(EFMeta.ClientTagNameV2, existingMeta,
gameEvent.Owner.Manager.CancellationToken);
gameEvent.Origin.Tell(_translationLookup["COMMANDS_REMOVE_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data));
}
}

View File

@ -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<List<LookupValue<string>>>(EFMeta.ClientTagNameV2, token);
var matchingTag = availableTags.FirstOrDefault(tag => tag.Value == gameEvent.Data.Trim());
if (matchingTag == null)
{
@ -44,7 +50,8 @@ 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));
}
}

View File

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

View File

@ -1,17 +1,18 @@
using System.Threading.Tasks;
using 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,7 +35,8 @@ 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"]);
}
}

View File

@ -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;
/// <summary>
@ -23,7 +23,7 @@ namespace IW4MAdmin.Application.Factories
/// <param name="translationLookup"></param>
/// <param name="rconConnectionFactory"></param>
public GameServerInstanceFactory(ITranslationLookup translationLookup,
IMetaService metaService,
IMetaServiceV2 metaService,
IServiceProvider serviceProvider)
{
_translationLookup = translationLookup;

View File

@ -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<EFServer> serverCache) : base(serviceProvider.GetRequiredService<ILogger<Server>>(),
@ -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)

View File

@ -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<IAuditInformationRepository, AuditInformationRepository>()
.AddSingleton<IEntityService<EFClient>, ClientService>()
.AddSingleton<IMetaService, MetaService>()
.AddSingleton<IMetaServiceV2, MetaServiceV2>()
.AddSingleton<ClientService>()
.AddSingleton<PenaltyService>()
.AddSingleton<ChangeHistoryService>()

View File

@ -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<EFClient> _clientEntityService;
private readonly IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> _receivedPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> _administeredPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>
_administeredPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>
_connectionHistoryHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse>
_permissionLevelHelper;
public MetaRegistration(ILogger<MetaRegistration> logger, IMetaService metaService,
public MetaRegistration(ILogger<MetaRegistration> logger, IMetaServiceV2 metaService,
ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService,
IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper,
IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper,
@ -46,19 +52,26 @@ namespace IW4MAdmin.Application.Meta
public void Register()
{
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, GetProfileMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty, GetReceivedPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized, GetAdministeredPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate, GetUpdatedAliasMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ConnectionHistoryResponse>(MetaType.ConnectionHistory, GetConnectionHistoryMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information,
GetProfileMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty,
GetReceivedPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized,
GetAdministeredPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate,
GetUpdatedAliasMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ConnectionHistoryResponse>(MetaType.ConnectionHistory,
GetConnectionHistoryMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, PermissionLevelChangedResponse>(
MetaType.PermissionLevel, GetPermissionLevelMeta);
}
private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request)
private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request,
CancellationToken cancellationToken = default)
{
var metaList = new List<InformationResponse>();
var 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<IEnumerable<ReceivedPenaltyResponse>> GetReceivedPenaltiesMeta(ClientPaginationRequest request)
private async Task<IEnumerable<ReceivedPenaltyResponse>> GetReceivedPenaltiesMeta(
ClientPaginationRequest request, CancellationToken token = default)
{
var penalties = await _receivedPenaltyHelper.QueryResource(request);
return penalties.Results;
}
private async Task<IEnumerable<AdministeredPenaltyResponse>> GetAdministeredPenaltiesMeta(ClientPaginationRequest request)
private async Task<IEnumerable<AdministeredPenaltyResponse>> GetAdministeredPenaltiesMeta(
ClientPaginationRequest request, CancellationToken token = default)
{
var penalties = await _administeredPenaltyHelper.QueryResource(request);
return penalties.Results;
}
private async Task<IEnumerable<UpdatedAliasResponse>> GetUpdatedAliasMeta(ClientPaginationRequest request)
private async Task<IEnumerable<UpdatedAliasResponse>> GetUpdatedAliasMeta(ClientPaginationRequest request,
CancellationToken token = default)
{
var aliases = await _updatedAliasHelper.QueryResource(request);
return aliases.Results;
}
private async Task<IEnumerable<ConnectionHistoryResponse>> GetConnectionHistoryMeta(ClientPaginationRequest request)
private async Task<IEnumerable<ConnectionHistoryResponse>> GetConnectionHistoryMeta(
ClientPaginationRequest request, CancellationToken token = default)
{
var connections = await _connectionHistoryHelper.QueryResource(request);
return connections.Results;
}
private async Task<IEnumerable<PermissionLevelChangedResponse>> GetPermissionLevelMeta(
ClientPaginationRequest request)
ClientPaginationRequest request, CancellationToken token = default)
{
var permissionChanges = await _permissionLevelHelper.QueryResource(request);
return permissionChanges.Results;

View File

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

View File

@ -10,8 +10,8 @@ namespace Data.Models
/// </summary>
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; }

View File

@ -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));
}
}
}

View File

@ -1137,10 +1137,10 @@ namespace SharedLibraryCore.Commands
/// </summary>
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"]);

View File

@ -0,0 +1,9 @@
using SharedLibraryCore.Interfaces;
namespace SharedLibraryCore.Dtos;
public class LookupValue<TObject> : ILookupValue<TObject>
{
public int Id { get; set; }
public TObject Value { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace SharedLibraryCore.Interfaces;
public interface ILookupValue<TObject>
{
int Id { get; }
TObject Value { get; }
}

View File

@ -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
/// <summary>
/// adds or updates meta key and value to the database as simple string
/// </summary>
/// <param name="metaKey">key of meta data</param>
/// <param name="metaValue">value of the meta data</param>
/// <param name="clientId">id of the client to save the meta for</param>
/// <param name="token"></param>
/// <returns></returns>
Task SetPersistentMeta(string metaKey, string metaValue, int clientId, CancellationToken token = default);
/// <summary>
/// add or update meta key and value to the database as serialized object type
/// </summary>
/// <param name="metaKey"></param>
/// <param name="metaValue"></param>
/// <param name="clientId"></param>
/// <param name="token"></param>
/// <typeparam name="T">type of object being serialized</typeparam>
/// <returns></returns>
Task SetPersistentMetaValue<T>(string metaKey, T metaValue, int clientId, CancellationToken token = default)
where T : class;
/// <summary>
/// Sets meta key to a linked lookup key and id
/// </summary>
/// <param name="metaKey">Key for the client meta</param>
/// <param name="lookupKey">Key of the global lookup meta</param>
/// <param name="lookupId">Id in the list of lookup values</param>
/// <param name="clientId">id of the client</param>
/// <param name="token"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task SetPersistentMetaForLookupKey(string metaKey, string lookupKey, int lookupId, int clientId,
CancellationToken token = default);
/// <summary>
/// increments meta value and persists to the database
/// <remarks>if the meta value does not already exist it will be set to the increment amount</remarks>
/// <remarks>the assumption is made that the existing value is <see cref="int"/></remarks>
/// </summary>
/// <param name="metaKey">key of meta data</param>
/// <param name="incrementAmount">value to increment by</param>
/// <param name="clientId">id of the client to save the meta for</param>
/// <param name="token"></param>
/// <returns></returns>
Task IncrementPersistentMeta(string metaKey, int incrementAmount, int clientId,
CancellationToken token = default);
/// <summary>
/// decrements meta value and persists to the database
/// <remarks>if the meta value does not already exist it will be set to the decrement amount</remarks>
/// <remarks>the assumption is made that the existing value is <see cref="int"/></remarks>
/// </summary>
/// <param name="metaKey">key of meta data</param>
/// <param name="decrementAmount">value to increment by</param>
/// <param name="clientId">id of the client to save the meta for</param>
/// <param name="token"></param>
/// <returns></returns>
Task DecrementPersistentMeta(string metaKey, int decrementAmount, int clientId,
CancellationToken token = default);
/// <summary>
/// retrieves meta entry
/// </summary>
/// <param name="metaKey"></param>
/// <param name="clientId"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<EFMeta> GetPersistentMeta(string metaKey, int clientId, CancellationToken token = default);
/// <summary>
/// retrieves meta value as deserialized object
/// </summary>
/// <param name="metaKey"></param>
/// <param name="clientId"></param>
/// <param name="token"></param>
/// <typeparam name="T">object type to deserialize into</typeparam>
/// <returns></returns>
Task<T> GetPersistentMetaValue<T>(string metaKey, int clientId, CancellationToken token = default)
where T : class;
/// <summary>
/// retrieves meta entry by with associated lookup value as string
/// </summary>
/// <param name="metaKey"></param>
/// <param name="lookupKey"></param>
/// <param name="clientId"></param>
/// <param name="token"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task<EFMeta> GetPersistentMetaByLookup(string metaKey, string lookupKey, int clientId,
CancellationToken token = default);
/// <summary>
/// removes meta key with given value
/// </summary>
/// <param name="metaKey">key of meta data</param>
/// <param name="clientId">client to delete the meta for</param>
/// <param name="token"></param>
/// <returns></returns>
Task RemovePersistentMeta(string metaKey, int clientId, CancellationToken token = default);
#endregion
#region GLOBAL
/// <summary>
/// adds or updates meta key and value to the database
/// </summary>
/// <param name="metaKey">key of meta data</param>
/// <param name="metaValue">value of the meta data</param>
/// <param name="token"></param>
/// <returns></returns>
Task SetPersistentMeta(string metaKey, string metaValue, CancellationToken token = default);
/// <summary>
/// serializes and sets (create or update) meta key and value
/// </summary>
/// <param name="metaKey"></param>
/// <param name="metaValue"></param>
/// <param name="token"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task SetPersistentMetaValue<T>(string metaKey, T metaValue, CancellationToken token = default) where T : class;
/// <summary>
/// removes meta key with given value
/// </summary>
/// <param name="metaKey">key of the meta data</param>
/// <param name="metaValue">value of the meta data</param>
/// <param name="token"></param>
/// <returns></returns>
Task RemovePersistentMeta(string metaKey, CancellationToken token = default);
/// <summary>
/// retrieves collection of meta for given key
/// </summary>
/// <param name="metaKey">key to retrieve values for</param>
/// <param name="token"></param>
/// <returns></returns>
Task<EFMeta> GetPersistentMeta(string metaKey, CancellationToken token = default);
/// <summary>
/// returns value of meta key if it exists
/// </summary>
/// <param name="metaKey"></param>
/// <param name="token"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task<T> GetPersistentMetaValue<T>(string metaKey, CancellationToken token = default) where T : class;
#endregion
/// <summary>
/// adds a meta task to the runtime meta list
/// </summary>
/// <param name="metaKey">type of meta</param>
/// <param name="metaAction">action to perform</param>
void AddRuntimeMeta<T, TReturn>(MetaType metaKey,
Func<T, CancellationToken, Task<IEnumerable<TReturn>>> metaAction)
where TReturn : IClientMeta where T : PaginationRequest;
/// <summary>
/// retrieves all the runtime meta information for given client idea
/// </summary>
/// <param name="request">request information</param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request, CancellationToken token = default);
/// <summary>
/// retrieves all the runtime of provided type
/// </summary>
/// <param name="request">>request information</param>
/// <param name="metaType">type of meta to retreive</param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType, CancellationToken token = default)
where T : IClientMeta;
}

View File

@ -124,8 +124,8 @@ namespace SharedLibraryCore.Database.Models
[NotMapped]
public string Tag
{
get => GetAdditionalProperty<string>(EFMeta.ClientTag);
set => SetAdditionalProperty(EFMeta.ClientTag, value);
get => GetAdditionalProperty<string>(EFMeta.ClientTagV2);
set => SetAdditionalProperty(EFMeta.ClientTagV2, value);
}
[NotMapped]

View File

@ -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<IActionResult> ProfileAsync(int id, MetaType? metaFilterType)
public async Task<IActionResult> 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

View File

@ -116,13 +116,14 @@ namespace WebfrontCore
// todo: this needs to be handled more gracefully
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<DefaultSettings>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ILoggerFactory>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IConfigurationHandlerFactory>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IDatabaseContextFactory>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IAuditInformationRepository>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<ITranslationLookup>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IEnumerable<IManagerCommand>>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IMetaService>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<ApplicationConfiguration>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IConfigurationHandlerFactory>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IDatabaseContextFactory>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IAuditInformationRepository>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ITranslationLookup>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IEnumerable<IManagerCommand>>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IMetaService>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IMetaServiceV2>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ApplicationConfiguration>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ClientService>());
services.AddSingleton(
Program.ApplicationServiceProvider.GetRequiredService<IServerDistributionCalculator>());

View File

@ -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<IEnumerable<IClientMeta>> GetClientMeta(IMetaService metaService, MetaType? metaType,
public static async Task<IEnumerable<IClientMeta>> GetClientMeta(IMetaServiceV2 metaService, MetaType? metaType,
EFClient.Permission level, ClientPaginationRequest request)
{
IEnumerable<IClientMeta> meta = null;