using System;
using System.Net;
using System.Threading.Tasks;
using AutopilotMonitor.Functions.Helpers;
using AutopilotMonitor.Functions.Services.Maintenance;
using AutopilotMonitor.Shared.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace AutopilotMonitor.Functions.Functions.Admin
{
///
/// POST /api/maintenance/trigger — manual Global Admin trigger of the platform
/// maintenance run. The run takes minutes, so the request only queues it:
/// 202 Accepted when queued, 409 while a run (timer or manual) is active,
/// 500 when the enqueue failed — never a hollow success. Progress and the run report
/// surface as Maintenance* ops events.
///
public class TriggerMaintenanceFunction
{
private readonly ILogger _logger;
private readonly IMaintenanceTriggerProducer _producer;
private readonly MaintenanceRunGate _runGate;
public TriggerMaintenanceFunction(
ILogger logger,
IMaintenanceTriggerProducer producer,
MaintenanceRunGate runGate)
{
_logger = logger;
_producer = producer;
_runGate = runGate;
}
///
/// Query parameters:
/// - date: Optional date to aggregate (yyyy-MM-dd). If not provided, uses yesterday.
/// - aggregateOnly: If true, only runs aggregation (skips timeout and cleanup)
///
[Function("TriggerMaintenance")]
public async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "maintenance/trigger")] HttpRequestData req,
FunctionContext context)
{
// Authentication + GlobalAdminOnly authorization enforced by PolicyEnforcementMiddleware
var userEmail = TenantHelper.GetUserIdentifier(req) ?? "GlobalAdmin";
var ct = context.CancellationToken;
try
{
if (!QueryParams.TryUtcInstant(req.Query["date"], "date", out var dateInstant, out _))
{
return await req.BadRequestAsync("Invalid date format. Use yyyy-MM-dd");
}
DateTime? targetDate = dateInstant?.Date;
bool aggregateOnly = req.Query["aggregateOnly"]?.ToLower() == "true";
if (await _runGate.IsRunActiveAsync(ct))
{
return await req.ConflictAsync("A maintenance run is already active.");
}
await _producer.EnqueueAsync(new MaintenanceTriggerEnvelope
{
TriggeredBy = userEmail,
TargetDate = targetDate,
AggregateOnly = aggregateOnly,
}, ct);
_logger.LogInformation("TriggerMaintenance: run queued (triggeredBy={TriggeredBy}, date={Date:yyyy-MM-dd}, aggregateOnly={AggregateOnly})",
userEmail, targetDate, aggregateOnly);
return await req.JsonAsync(HttpStatusCode.Accepted, new TriggerMaintenanceResponse
{
Message = "Maintenance run queued",
TriggeredBy = userEmail,
TriggeredAt = DateTime.UtcNow,
TargetDate = targetDate?.ToString("yyyy-MM-dd"),
AggregateOnly = aggregateOnly,
});
}
catch (Exception ex)
{
return await req.InternalServerErrorAsync(_logger, ex, "TriggerMaintenance");
}
}
}
}