Показывать в аудите имена карточек и колонок вместо идентификаторов
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
using Deal.Modules.Cards.Application.Models;
|
||||
using Deal.Modules.Kanban.Application.Abstractions;
|
||||
using Deal.Modules.Kanban.Application.Models;
|
||||
using Deal.Modules.Tenants.Application.Models;
|
||||
using Deal.SharedKernel.Tenants.Abstractions;
|
||||
using Deal.SharedKernel.Tenants.Models;
|
||||
|
||||
namespace Deal.Api.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Резолвер ссылок аудита на данные тенанта
|
||||
/// </summary>
|
||||
/// <param name="scopeFactory">Фабрика scope для чтения схемы тенанта.</param>
|
||||
/// <param name="logger">Логгер сбоев разрешения ссылок.</param>
|
||||
public sealed class AuditReferenceResolver(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
ILogger<AuditReferenceResolver> logger) : IAuditReferenceResolver
|
||||
{
|
||||
async Task<IReadOnlyDictionary<string, string>> IAuditReferenceResolver.ResolveAsync(
|
||||
IReadOnlyList<AuditRecordDto> records,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var names = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (IGrouping<Guid, AuditRecordDto> group in GroupByTenant(records))
|
||||
{
|
||||
await ResolveTenantAsync(group.Key, group, names, ct);
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private static IEnumerable<IGrouping<Guid, AuditRecordDto>> GroupByTenant(IReadOnlyList<AuditRecordDto> records) =>
|
||||
records
|
||||
.Where(record => record.TenantId is not null)
|
||||
.GroupBy(record => record.TenantId!.Value);
|
||||
|
||||
private async Task ResolveTenantAsync(
|
||||
Guid tenantId,
|
||||
IEnumerable<AuditRecordDto> records,
|
||||
Dictionary<string, string> names,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var cardIds = new HashSet<string>(StringComparer.Ordinal);
|
||||
var boardIds = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (AuditRecordDto record in records)
|
||||
{
|
||||
foreach (AuditChangeDto change in record.AuditChanges())
|
||||
{
|
||||
Collect(change.From, cardIds, boardIds);
|
||||
Collect(change.To, cardIds, boardIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (cardIds.Count == 0 && boardIds.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await using AsyncServiceScope scope = scopeFactory.CreateAsyncScope();
|
||||
ITenantContext tenantContext = scope.ServiceProvider.GetRequiredService<ITenantContext>();
|
||||
try
|
||||
{
|
||||
tenantContext.SetTenant(new TenantId(tenantId.ToString("N")));
|
||||
ICardStore store = scope.ServiceProvider.GetRequiredService<ICardStore>();
|
||||
await ResolveCardsAsync(store, cardIds, names, ct);
|
||||
await ResolveContainersAsync(store, boardIds, names, ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogWarning(exception, "Резолвер аудита: ссылки тенанта {TenantId} не разрешены", tenantId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
tenantContext.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Collect(
|
||||
string? value,
|
||||
HashSet<string> cardIds,
|
||||
HashSet<string> boardIds)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.StartsWith(CardIds.CardPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
cardIds.Add(value);
|
||||
}
|
||||
else if (value.StartsWith(KanbanIdPrefixes.Board, StringComparison.Ordinal))
|
||||
{
|
||||
boardIds.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ResolveCardsAsync(
|
||||
ICardStore store,
|
||||
HashSet<string> cardIds,
|
||||
Dictionary<string, string> names,
|
||||
CancellationToken ct)
|
||||
{
|
||||
foreach (string cardId in cardIds)
|
||||
{
|
||||
CardDto? card = await store.GetCardAsync(cardId, ct);
|
||||
if (card is not null && !string.IsNullOrWhiteSpace(card.Title))
|
||||
{
|
||||
names[cardId] = card.Title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ResolveContainersAsync(
|
||||
ICardStore store,
|
||||
HashSet<string> boardIds,
|
||||
Dictionary<string, string> names,
|
||||
CancellationToken ct)
|
||||
{
|
||||
foreach (string boardId in boardIds)
|
||||
{
|
||||
ContainerDto? container = await store.GetContainerAsync(boardId, ct);
|
||||
if (container is not null && !string.IsNullOrWhiteSpace(container.Name))
|
||||
{
|
||||
names[boardId] = container.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user