614 lines
22 KiB
C#
614 lines
22 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using line_gestao_api.Data;
|
|
using line_gestao_api.Dtos;
|
|
using line_gestao_api.Models;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace line_gestao_api.Services;
|
|
|
|
public sealed class MveAuditService
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
|
};
|
|
|
|
private readonly AppDbContext _db;
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
private readonly ITenantProvider _tenantProvider;
|
|
private readonly MveCsvParserService _parser;
|
|
private readonly MveReconciliationService _reconciliation;
|
|
private readonly IVigenciaNotificationSyncService _vigenciaNotificationSyncService;
|
|
|
|
public MveAuditService(
|
|
AppDbContext db,
|
|
IHttpContextAccessor httpContextAccessor,
|
|
ITenantProvider tenantProvider,
|
|
MveCsvParserService parser,
|
|
MveReconciliationService reconciliation,
|
|
IVigenciaNotificationSyncService vigenciaNotificationSyncService)
|
|
{
|
|
_db = db;
|
|
_httpContextAccessor = httpContextAccessor;
|
|
_tenantProvider = tenantProvider;
|
|
_parser = parser;
|
|
_reconciliation = reconciliation;
|
|
_vigenciaNotificationSyncService = vigenciaNotificationSyncService;
|
|
}
|
|
|
|
public async Task<MveAuditRunDto> CreateRunAsync(IFormFile file, CancellationToken cancellationToken = default)
|
|
{
|
|
ValidateInputFile(file);
|
|
|
|
var parsedFile = await _parser.ParseAsync(file, cancellationToken);
|
|
var reconciliation = await _reconciliation.BuildAsync(parsedFile, cancellationToken);
|
|
|
|
var run = new MveAuditRun
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
FileName = parsedFile.FileName,
|
|
FileHashSha256 = parsedFile.FileHashSha256,
|
|
FileEncoding = parsedFile.FileEncoding,
|
|
Status = "READY",
|
|
TotalSystemLines = reconciliation.TotalSystemLines,
|
|
TotalReportLines = reconciliation.TotalReportLines,
|
|
TotalConciliated = reconciliation.TotalConciliated,
|
|
TotalStatusDivergences = reconciliation.TotalStatusDivergences,
|
|
TotalDataDivergences = reconciliation.TotalDataDivergences,
|
|
TotalOnlyInSystem = reconciliation.TotalOnlyInSystem,
|
|
TotalOnlyInReport = reconciliation.TotalOnlyInReport,
|
|
TotalDuplicateReportLines = reconciliation.TotalDuplicateReportLines,
|
|
TotalDuplicateSystemLines = reconciliation.TotalDuplicateSystemLines,
|
|
TotalInvalidRows = reconciliation.TotalInvalidRows,
|
|
TotalUnknownStatuses = reconciliation.TotalUnknownStatuses,
|
|
TotalSyncableIssues = reconciliation.TotalSyncableIssues,
|
|
ImportedAtUtc = DateTime.UtcNow
|
|
};
|
|
|
|
foreach (var issue in reconciliation.Issues)
|
|
{
|
|
run.Issues.Add(new MveAuditIssue
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
AuditRunId = run.Id,
|
|
SourceRowNumber = issue.SourceRowNumber,
|
|
NumeroLinha = string.IsNullOrWhiteSpace(issue.NumeroLinha) ? "-" : issue.NumeroLinha.Trim(),
|
|
MobileLineId = issue.MobileLineId,
|
|
SystemItem = issue.SystemItem,
|
|
IssueType = issue.IssueType,
|
|
Situation = issue.Situation,
|
|
Severity = issue.Severity,
|
|
Syncable = issue.Syncable,
|
|
ActionSuggestion = issue.ActionSuggestion,
|
|
Notes = issue.Notes,
|
|
SystemStatus = issue.SystemStatus,
|
|
ReportStatus = issue.ReportStatus,
|
|
SystemPlan = issue.SystemPlan,
|
|
ReportPlan = issue.ReportPlan,
|
|
SystemSnapshotJson = JsonSerializer.Serialize(issue.SystemSnapshot, JsonOptions),
|
|
ReportSnapshotJson = JsonSerializer.Serialize(issue.ReportSnapshot, JsonOptions),
|
|
DifferencesJson = JsonSerializer.Serialize(issue.Differences, JsonOptions),
|
|
CreatedAtUtc = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
_db.MveAuditRuns.Add(run);
|
|
_db.AuditLogs.Add(BuildAuditLog(
|
|
action: "MVE_AUDIT_RUN",
|
|
runId: run.Id,
|
|
fileName: run.FileName,
|
|
changes: new List<AuditFieldChangeDto>
|
|
{
|
|
new() { Field = "TotalLinhasSistema", ChangeType = "captured", NewValue = run.TotalSystemLines.ToString() },
|
|
new() { Field = "TotalLinhasRelatorio", ChangeType = "captured", NewValue = run.TotalReportLines.ToString() },
|
|
new() { Field = "DivergenciasStatus", ChangeType = "captured", NewValue = run.TotalStatusDivergences.ToString() },
|
|
new() { Field = "DivergenciasCadastro", ChangeType = "captured", NewValue = run.TotalDataDivergences.ToString() },
|
|
new() { Field = "ItensSincronizaveis", ChangeType = "captured", NewValue = run.TotalSyncableIssues.ToString() }
|
|
},
|
|
metadata: new
|
|
{
|
|
run.FileHashSha256,
|
|
run.FileEncoding,
|
|
run.TotalOnlyInSystem,
|
|
run.TotalOnlyInReport,
|
|
run.TotalDuplicateReportLines,
|
|
run.TotalDuplicateSystemLines,
|
|
run.TotalInvalidRows,
|
|
parsedFile.SourceRowCount
|
|
}));
|
|
|
|
await _db.SaveChangesAsync(cancellationToken);
|
|
return ToDto(run);
|
|
}
|
|
|
|
public async Task<MveAuditRunDto?> GetByIdAsync(Guid runId, CancellationToken cancellationToken = default)
|
|
{
|
|
var run = await _db.MveAuditRuns
|
|
.AsNoTracking()
|
|
.Include(x => x.Issues)
|
|
.FirstOrDefaultAsync(x => x.Id == runId, cancellationToken);
|
|
|
|
return run == null ? null : ToDto(run);
|
|
}
|
|
|
|
public async Task<MveAuditRunDto?> GetLatestAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var run = await _db.MveAuditRuns
|
|
.AsNoTracking()
|
|
.Include(x => x.Issues)
|
|
.OrderByDescending(x => x.ImportedAtUtc)
|
|
.ThenByDescending(x => x.Id)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
return run == null ? null : ToDto(run);
|
|
}
|
|
|
|
public async Task<ApplyMveAuditResultDto?> ApplyAsync(
|
|
Guid runId,
|
|
IReadOnlyCollection<Guid>? issueIds,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var run = await _db.MveAuditRuns
|
|
.Include(x => x.Issues)
|
|
.FirstOrDefaultAsync(x => x.Id == runId, cancellationToken);
|
|
|
|
if (run == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var requestedIds = issueIds?
|
|
.Where(x => x != Guid.Empty)
|
|
.Distinct()
|
|
.ToHashSet()
|
|
?? new HashSet<Guid>();
|
|
|
|
var selectedIssues = run.Issues
|
|
.Where(x => x.Syncable && !x.Applied)
|
|
.Where(x => requestedIds.Count == 0 || requestedIds.Contains(x.Id))
|
|
.ToList();
|
|
|
|
var result = new ApplyMveAuditResultDto
|
|
{
|
|
AuditRunId = run.Id,
|
|
RequestedIssues = requestedIds.Count == 0 ? selectedIssues.Count : requestedIds.Count
|
|
};
|
|
|
|
if (selectedIssues.Count == 0)
|
|
{
|
|
result.SkippedIssues = result.RequestedIssues;
|
|
return result;
|
|
}
|
|
|
|
var lineIds = selectedIssues
|
|
.Where(x => x.MobileLineId.HasValue)
|
|
.Select(x => x.MobileLineId!.Value)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
var linesById = await _db.MobileLines
|
|
.Where(x => lineIds.Contains(x.Id))
|
|
.ToDictionaryAsync(x => x.Id, cancellationToken);
|
|
|
|
var now = DateTime.UtcNow;
|
|
var updatedLineIds = new HashSet<Guid>();
|
|
var updatedFields = 0;
|
|
var appliedIssues = 0;
|
|
var skippedIssues = 0;
|
|
|
|
await using var transaction = await _db.Database.BeginTransactionAsync(cancellationToken);
|
|
|
|
foreach (var issue in selectedIssues)
|
|
{
|
|
if (!issue.MobileLineId.HasValue || !linesById.TryGetValue(issue.MobileLineId.Value, out var line))
|
|
{
|
|
skippedIssues++;
|
|
continue;
|
|
}
|
|
|
|
var reportSnapshot = DeserializeSnapshot(issue.ReportSnapshotJson);
|
|
if (reportSnapshot == null)
|
|
{
|
|
skippedIssues++;
|
|
continue;
|
|
}
|
|
|
|
var differences = DeserializeDifferences(issue.DifferencesJson);
|
|
var lineChanged = false;
|
|
|
|
foreach (var difference in differences.Where(x => x.Syncable && x.FieldKey == "status"))
|
|
{
|
|
var systemStatus = MveAuditNormalization.NormalizeStatusForSystem(reportSnapshot.StatusLinha);
|
|
if (SetString(line.Status, systemStatus, value => line.Status = value))
|
|
{
|
|
ApplyBlockedLineContext(line);
|
|
lineChanged = true;
|
|
updatedFields++;
|
|
}
|
|
}
|
|
|
|
if (lineChanged)
|
|
{
|
|
line.UpdatedAt = now;
|
|
updatedLineIds.Add(line.Id);
|
|
}
|
|
|
|
issue.Applied = true;
|
|
issue.AppliedAtUtc = now;
|
|
appliedIssues++;
|
|
}
|
|
|
|
run.AppliedIssuesCount = run.Issues.Count(x => x.Applied);
|
|
run.AppliedLinesCount += updatedLineIds.Count;
|
|
run.AppliedFieldsCount += updatedFields;
|
|
run.AppliedAtUtc = now;
|
|
run.AppliedByUserId = ResolveUserId(_httpContextAccessor.HttpContext?.User);
|
|
run.AppliedByUserName = ResolveUserName(_httpContextAccessor.HttpContext?.User);
|
|
run.AppliedByUserEmail = ResolveUserEmail(_httpContextAccessor.HttpContext?.User);
|
|
run.Status = run.AppliedIssuesCount >= run.TotalSyncableIssues
|
|
? "APPLIED"
|
|
: run.AppliedIssuesCount > 0
|
|
? "PARTIAL_APPLIED"
|
|
: "READY";
|
|
|
|
_db.AuditLogs.Add(BuildAuditLog(
|
|
action: "MVE_AUDIT_APPLY",
|
|
runId: run.Id,
|
|
fileName: run.FileName,
|
|
changes: new List<AuditFieldChangeDto>
|
|
{
|
|
new() { Field = "IssuesAplicadas", ChangeType = "modified", NewValue = appliedIssues.ToString() },
|
|
new() { Field = "LinhasAtualizadas", ChangeType = "modified", NewValue = updatedLineIds.Count.ToString() },
|
|
new() { Field = "CamposAtualizados", ChangeType = "modified", NewValue = updatedFields.ToString() }
|
|
},
|
|
metadata: new
|
|
{
|
|
requestedIssues = result.RequestedIssues,
|
|
appliedIssues,
|
|
updatedLines = updatedLineIds.Count,
|
|
updatedFields,
|
|
skippedIssues
|
|
}));
|
|
|
|
await _db.SaveChangesAsync(cancellationToken);
|
|
await transaction.CommitAsync(cancellationToken);
|
|
|
|
if (appliedIssues > 0)
|
|
{
|
|
await _vigenciaNotificationSyncService.SyncCurrentTenantAsync();
|
|
}
|
|
|
|
result.AppliedIssues = appliedIssues;
|
|
result.UpdatedLines = updatedLineIds.Count;
|
|
result.UpdatedFields = updatedFields;
|
|
result.SkippedIssues = skippedIssues;
|
|
return result;
|
|
}
|
|
|
|
private static void ValidateInputFile(IFormFile file)
|
|
{
|
|
if (file == null || file.Length <= 0)
|
|
{
|
|
throw new InvalidOperationException("Selecione um arquivo CSV do MVE para continuar.");
|
|
}
|
|
|
|
if (file.Length > 20_000_000)
|
|
{
|
|
throw new InvalidOperationException("O arquivo do MVE excede o limite de 20 MB.");
|
|
}
|
|
|
|
var extension = Path.GetExtension(file.FileName ?? string.Empty);
|
|
if (!string.Equals(extension, ".csv", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new InvalidOperationException("O relatório MVE deve ser enviado em formato CSV.");
|
|
}
|
|
}
|
|
|
|
private MveAuditRunDto ToDto(MveAuditRun run)
|
|
{
|
|
return new MveAuditRunDto
|
|
{
|
|
Id = run.Id,
|
|
FileName = run.FileName,
|
|
FileEncoding = run.FileEncoding,
|
|
Status = run.Status,
|
|
ImportedAtUtc = run.ImportedAtUtc,
|
|
AppliedAtUtc = run.AppliedAtUtc,
|
|
AppliedByUserName = run.AppliedByUserName,
|
|
AppliedByUserEmail = run.AppliedByUserEmail,
|
|
Summary = new MveAuditSummaryDto
|
|
{
|
|
TotalSystemLines = run.TotalSystemLines,
|
|
TotalReportLines = run.TotalReportLines,
|
|
TotalConciliated = run.TotalConciliated,
|
|
TotalStatusDivergences = run.TotalStatusDivergences,
|
|
TotalDataDivergences = run.TotalDataDivergences,
|
|
TotalOnlyInSystem = run.TotalOnlyInSystem,
|
|
TotalOnlyInReport = run.TotalOnlyInReport,
|
|
TotalDuplicateReportLines = run.TotalDuplicateReportLines,
|
|
TotalDuplicateSystemLines = run.TotalDuplicateSystemLines,
|
|
TotalInvalidRows = run.TotalInvalidRows,
|
|
TotalUnknownStatuses = run.TotalUnknownStatuses,
|
|
TotalSyncableIssues = run.TotalSyncableIssues,
|
|
AppliedIssuesCount = run.AppliedIssuesCount,
|
|
AppliedLinesCount = run.AppliedLinesCount,
|
|
AppliedFieldsCount = run.AppliedFieldsCount
|
|
},
|
|
Issues = run.Issues
|
|
.OrderByDescending(x => x.Syncable)
|
|
.ThenByDescending(x => x.Severity)
|
|
.ThenBy(x => x.NumeroLinha)
|
|
.Select(issue => new MveAuditIssueDto
|
|
{
|
|
Id = issue.Id,
|
|
SourceRowNumber = issue.SourceRowNumber,
|
|
NumeroLinha = issue.NumeroLinha,
|
|
MobileLineId = issue.MobileLineId,
|
|
SystemItem = issue.SystemItem,
|
|
IssueType = issue.IssueType,
|
|
Situation = issue.Situation,
|
|
Severity = issue.Severity,
|
|
Syncable = issue.Syncable,
|
|
Applied = issue.Applied,
|
|
ActionSuggestion = issue.ActionSuggestion,
|
|
Notes = issue.Notes,
|
|
SystemStatus = issue.SystemStatus,
|
|
ReportStatus = issue.ReportStatus,
|
|
SystemPlan = issue.SystemPlan,
|
|
ReportPlan = issue.ReportPlan,
|
|
SystemSnapshot = DeserializeSnapshot(issue.SystemSnapshotJson),
|
|
ReportSnapshot = DeserializeSnapshot(issue.ReportSnapshotJson),
|
|
Differences = DeserializeDifferences(issue.DifferencesJson)
|
|
})
|
|
.ToList()
|
|
};
|
|
}
|
|
|
|
private AuditLog BuildAuditLog(
|
|
string action,
|
|
Guid runId,
|
|
string? fileName,
|
|
IReadOnlyCollection<AuditFieldChangeDto> changes,
|
|
object metadata)
|
|
{
|
|
var actorTenantId = _tenantProvider.ActorTenantId;
|
|
if (!actorTenantId.HasValue || actorTenantId.Value == Guid.Empty)
|
|
{
|
|
throw new InvalidOperationException("Tenant inválido para registrar auditoria MVE.");
|
|
}
|
|
|
|
var user = _httpContextAccessor.HttpContext?.User;
|
|
var request = _httpContextAccessor.HttpContext?.Request;
|
|
|
|
return new AuditLog
|
|
{
|
|
TenantId = actorTenantId.Value,
|
|
ActorTenantId = actorTenantId.Value,
|
|
TargetTenantId = actorTenantId.Value,
|
|
ActorUserId = ResolveUserId(user),
|
|
UserId = ResolveUserId(user),
|
|
UserName = ResolveUserName(user),
|
|
UserEmail = ResolveUserEmail(user),
|
|
OccurredAtUtc = DateTime.UtcNow,
|
|
Action = action,
|
|
Page = "Geral",
|
|
EntityName = "MveAudit",
|
|
EntityId = runId.ToString(),
|
|
EntityLabel = string.IsNullOrWhiteSpace(fileName) ? "Auditoria MVE" : fileName.Trim(),
|
|
ChangesJson = JsonSerializer.Serialize(changes, JsonOptions),
|
|
MetadataJson = JsonSerializer.Serialize(metadata, JsonOptions),
|
|
RequestPath = request?.Path.Value,
|
|
RequestMethod = request?.Method,
|
|
IpAddress = _httpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString()
|
|
};
|
|
}
|
|
|
|
private static MveAuditSnapshotDto? DeserializeSnapshot(string? json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
return JsonSerializer.Deserialize<MveAuditSnapshotDto>(json, JsonOptions);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static List<MveAuditDifferenceDto> DeserializeDifferences(string? json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
{
|
|
return new List<MveAuditDifferenceDto>();
|
|
}
|
|
|
|
try
|
|
{
|
|
return JsonSerializer.Deserialize<List<MveAuditDifferenceDto>>(json, JsonOptions) ?? new List<MveAuditDifferenceDto>();
|
|
}
|
|
catch
|
|
{
|
|
return new List<MveAuditDifferenceDto>();
|
|
}
|
|
}
|
|
|
|
private VigenciaLine? ResolveVigencia(
|
|
MobileLine line,
|
|
string numeroLinha,
|
|
IDictionary<string, VigenciaLine> vigenciaByLine,
|
|
IDictionary<int, VigenciaLine> vigenciaByItem)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(numeroLinha) && vigenciaByLine.TryGetValue(numeroLinha, out var byLine))
|
|
{
|
|
return byLine;
|
|
}
|
|
|
|
if (line.Item > 0 && vigenciaByItem.TryGetValue(line.Item, out var byItem))
|
|
{
|
|
return byItem;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private UserData? ResolveUserData(
|
|
MobileLine line,
|
|
string numeroLinha,
|
|
IDictionary<string, UserData> userDataByLine,
|
|
IDictionary<int, UserData> userDataByItem)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(numeroLinha) && userDataByLine.TryGetValue(numeroLinha, out var byLine))
|
|
{
|
|
return byLine;
|
|
}
|
|
|
|
if (line.Item > 0 && userDataByItem.TryGetValue(line.Item, out var byItem))
|
|
{
|
|
return byItem;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private VigenciaLine CreateVigencia(MobileLine line)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
var vigencia = new VigenciaLine
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TenantId = line.TenantId,
|
|
Item = line.Item,
|
|
Linha = MveAuditNormalization.NullIfEmptyDigits(line.Linha),
|
|
Conta = line.Conta,
|
|
Cliente = line.Cliente,
|
|
Usuario = line.Usuario,
|
|
PlanoContrato = line.PlanoContrato,
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
_db.VigenciaLines.Add(vigencia);
|
|
return vigencia;
|
|
}
|
|
|
|
private UserData CreateUserData(MobileLine line)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
var userData = new UserData
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TenantId = line.TenantId,
|
|
Item = line.Item,
|
|
Linha = MveAuditNormalization.NullIfEmptyDigits(line.Linha),
|
|
Cliente = line.Cliente,
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
_db.UserDatas.Add(userData);
|
|
return userData;
|
|
}
|
|
|
|
private Aparelho EnsureAparelho(MobileLine line)
|
|
{
|
|
if (line.Aparelho != null)
|
|
{
|
|
return line.Aparelho;
|
|
}
|
|
|
|
var now = DateTime.UtcNow;
|
|
var aparelho = new Aparelho
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TenantId = line.TenantId,
|
|
CreatedAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
_db.Aparelhos.Add(aparelho);
|
|
line.AparelhoId = aparelho.Id;
|
|
line.Aparelho = aparelho;
|
|
return aparelho;
|
|
}
|
|
|
|
private static void ApplyBlockedLineContext(MobileLine line)
|
|
{
|
|
var normalized = MveAuditNormalization.NormalizeSystemStatus(line.Status).Key;
|
|
if (normalized is not "BLOQUEIO_PERDA_ROUBO" and not "BLOQUEIO_120_DIAS")
|
|
{
|
|
return;
|
|
}
|
|
|
|
line.Usuario = "RESERVA";
|
|
line.Skil = "RESERVA";
|
|
if (string.IsNullOrWhiteSpace(line.Cliente))
|
|
{
|
|
line.Cliente = "RESERVA";
|
|
}
|
|
}
|
|
|
|
private static bool SetString(string? currentValue, string? nextValue, Action<string?> assign)
|
|
{
|
|
var normalizedNext = string.IsNullOrWhiteSpace(nextValue)
|
|
? null
|
|
: MveAuditNormalization.CleanTextValue(nextValue);
|
|
|
|
var normalizedCurrent = string.IsNullOrWhiteSpace(currentValue)
|
|
? null
|
|
: MveAuditNormalization.CleanTextValue(currentValue);
|
|
|
|
if (string.Equals(normalizedCurrent, normalizedNext, StringComparison.Ordinal))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
assign(normalizedNext);
|
|
return true;
|
|
}
|
|
|
|
private static bool SetDate(DateTime? currentValue, DateTime? nextValue, Action<DateTime?> assign)
|
|
{
|
|
var normalizedCurrent = currentValue?.Date;
|
|
var normalizedNext = nextValue?.Date;
|
|
if (normalizedCurrent == normalizedNext)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
assign(nextValue);
|
|
return true;
|
|
}
|
|
|
|
private static Guid? ResolveUserId(ClaimsPrincipal? user)
|
|
{
|
|
var raw = user?.FindFirstValue(ClaimTypes.NameIdentifier)
|
|
?? user?.FindFirstValue(JwtRegisteredClaimNames.Sub)
|
|
?? user?.FindFirstValue("sub");
|
|
|
|
return Guid.TryParse(raw, out var parsed) ? parsed : null;
|
|
}
|
|
|
|
private static string? ResolveUserName(ClaimsPrincipal? user)
|
|
{
|
|
return user?.FindFirstValue("name")
|
|
?? user?.FindFirstValue(ClaimTypes.Name)
|
|
?? user?.Identity?.Name;
|
|
}
|
|
|
|
private static string? ResolveUserEmail(ClaimsPrincipal? user)
|
|
{
|
|
return user?.FindFirstValue(ClaimTypes.Email)
|
|
?? user?.FindFirstValue(JwtRegisteredClaimNames.Email)
|
|
?? user?.FindFirstValue("email");
|
|
}
|
|
}
|