using System.Security.Claims; using Campaign_Tracker.Server.Audit; using Campaign_Tracker.Server.Authorization; using Campaign_Tracker.Server.LegacyData.Schema; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Campaign_Tracker.Server.Controllers; /// /// Admin-only API for the legacy schema compatibility check (Story 1.7 AC #5). /// Exposes a manual trigger and a recent-history view. /// [ApiController] [Authorize(Policy = ApplicationPolicy.AdminAccess)] [Route("api/admin/legacy-schema")] public sealed class LegacySchemaController : ControllerBase { private readonly ILegacySchemaCompatibilityCheck _check; private readonly ILegacySchemaCheckHistory _history; private readonly IAuditService _audit; private readonly TimeProvider _timeProvider; public LegacySchemaController( ILegacySchemaCompatibilityCheck check, ILegacySchemaCheckHistory history, IAuditService audit, TimeProvider timeProvider) { _check = check; _history = history; _audit = audit; _timeProvider = timeProvider; } [HttpPost("check")] public async Task> RunCheck(CancellationToken cancellationToken) { var result = await _check.RunAsync(cancellationToken); _history.Record(result); var actor = User.Identity?.Name ?? User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "unknown"; _audit.Record(new AuditEvent( EventType: result.Passed ? "LEGACY_SCHEMA_CHECK_PASSED" : "LEGACY_SCHEMA_CHECK_FAILED", ActorIdentity: actor, Resource: "legacy-schema/compatibility-check", Outcome: result.Passed ? "pass" : "fail", TraceIdentifier: HttpContext.TraceIdentifier, RecordedAt: _timeProvider.GetUtcNow())); return Ok(LegacySchemaCheckResponse.From(result)); } [HttpGet("history")] public ActionResult> GetHistory([FromQuery] int max = 50) { var items = _history.GetRecent(max); return Ok(items.Select(LegacySchemaCheckResponse.From).ToArray()); } } public sealed record LegacySchemaCheckResponse( bool Passed, int TablesVerified, int DriftCount, DateTimeOffset CheckedAt, string BaselineSource, IReadOnlyList Drifts) { public static LegacySchemaCheckResponse From(LegacySchemaCheckResult result) => new(result.Passed, result.TablesVerified, result.DriftCount, result.CheckedAt, result.BaselineSource, result.Drifts .Select(d => new LegacySchemaDriftResponse( d.TableName, d.ColumnName, d.ChangeType.ToString(), d.Detail)) .ToArray()); } public sealed record LegacySchemaDriftResponse( string TableName, string? ColumnName, string ChangeType, string Detail);