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