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