using Microsoft.AspNetCore.SignalR; using OTSSignsOrchestrator.Server.Data; using OTSSignsOrchestrator.Server.Data.Entities; using OTSSignsOrchestrator.Server.Hubs; namespace OTSSignsOrchestrator.Server.Workers; /// /// Helper that wraps pipeline step execution with lifecycle management: /// creates the row, sets Running, captures output, marks Completed/Failed, and broadcasts /// progress via SignalR. /// public sealed class StepRunner { private readonly OrchestratorDbContext _db; private readonly IHubContext _hub; private readonly ILogger _logger; private readonly Guid _jobId; private readonly int _totalSteps; private int _currentStep; public StepRunner( OrchestratorDbContext db, IHubContext hub, ILogger logger, Guid jobId, int totalSteps) { _db = db; _hub = hub; _logger = logger; _jobId = jobId; _totalSteps = totalSteps; } /// /// Execute a named step, persisting a record and broadcasting progress. /// /// Short identifier for the step (e.g. "mysql-setup"). /// /// Async delegate that performs the work. Return a log string summarising what happened. /// /// Cancellation token. public async Task RunAsync(string stepName, Func> action, CancellationToken ct) { _currentStep++; var pct = (int)((double)_currentStep / _totalSteps * 100); var step = new JobStep { Id = Guid.NewGuid(), JobId = _jobId, StepName = stepName, Status = JobStepStatus.Running, StartedAt = DateTime.UtcNow, }; _db.JobSteps.Add(step); await _db.SaveChangesAsync(ct); _logger.LogInformation("Job {JobId}: step [{Step}/{Total}] {StepName} started", _jobId, _currentStep, _totalSteps, stepName); await _hub.Clients.All.SendJobProgressUpdate( _jobId.ToString(), stepName, pct, $"Starting {stepName}…"); try { var logOutput = await action(); step.Status = JobStepStatus.Completed; step.LogOutput = logOutput; step.CompletedAt = DateTime.UtcNow; await _db.SaveChangesAsync(ct); _logger.LogInformation("Job {JobId}: step {StepName} completed", _jobId, stepName); await _hub.Clients.All.SendJobProgressUpdate( _jobId.ToString(), stepName, pct, logOutput); } catch (Exception ex) { step.Status = JobStepStatus.Failed; step.LogOutput = ex.Message; step.CompletedAt = DateTime.UtcNow; await _db.SaveChangesAsync(CancellationToken.None); _logger.LogError(ex, "Job {JobId}: step {StepName} failed", _jobId, stepName); await _hub.Clients.All.SendJobProgressUpdate( _jobId.ToString(), stepName, pct, $"FAILED: {ex.Message}"); throw; // re-throw to fail the job } } }