using System.Security.Claims;
using Campaign_Tracker.Server.Audit;
using Campaign_Tracker.Server.Authorization;
using Campaign_Tracker.Server.ExtensionData;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Campaign_Tracker.Server.Controllers;
///
/// Admin API for the extension-to-legacy referential integrity check (Story 1.8 AC #4).
///
[ApiController]
[Authorize(Policy = ApplicationPolicy.AdminAccess)]
[Route("api/admin/legacy-link")]
public sealed class LegacyLinkController : ControllerBase
{
private readonly ILegacyLinkIntegrityCheck _integrityCheck;
private readonly IAuditService _audit;
private readonly TimeProvider _timeProvider;
public LegacyLinkController(
ILegacyLinkIntegrityCheck integrityCheck,
IAuditService audit,
TimeProvider timeProvider)
{
_integrityCheck = integrityCheck;
_audit = audit;
_timeProvider = timeProvider;
}
///
/// Runs the extension-to-legacy link integrity check on demand and returns a report.
/// Intended to be called by a scheduler for nightly runs and by admins for manual runs.
///
[HttpPost("integrity-check")]
public async Task> RunIntegrityCheck(
CancellationToken cancellationToken)
{
var report = await _integrityCheck.CheckAsync(cancellationToken);
var actor = User.Identity?.Name
?? User.FindFirstValue(ClaimTypes.NameIdentifier)
?? "unknown";
_audit.Record(new AuditEvent(
EventType: report.IsConsistent
? "LEGACY_LINK_INTEGRITY_PASSED"
: "LEGACY_LINK_INTEGRITY_FAILED",
ActorIdentity: actor,
Resource: "legacy-link/integrity-check",
Outcome: $"{report.ConsistentRecords}/{report.TotalRecords} consistent ({report.ConsistencyPercentage:F2}%)",
TraceIdentifier: HttpContext.TraceIdentifier,
RecordedAt: _timeProvider.GetUtcNow()));
return Ok(LegacyLinkIntegrityResponse.From(report));
}
}
public sealed record LegacyLinkIntegrityResponse(
bool IsConsistent,
DateTimeOffset CheckedAt,
int ProviderCount,
int TotalRecords,
int ConsistentRecords,
int FailedRecords,
double ConsistencyPercentage,
IReadOnlyList Failures)
{
public static LegacyLinkIntegrityResponse From(LegacyLinkIntegrityReport report) =>
new(report.IsConsistent,
report.CheckedAt,
report.ProviderCount,
report.TotalRecords,
report.ConsistentRecords,
report.FailedRecords,
report.ConsistencyPercentage,
report.Failures
.Select(f => new LegacyLinkFailureResponse(
f.RecordType, f.RecordId,
f.Reference.Type.ToString(), f.Reference.Value,
f.Reason))
.ToArray());
}
public sealed record LegacyLinkFailureResponse(
string RecordType,
string RecordId,
string LinkType,
string LinkValue,
string Reason);