using FastEndpoints; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ResponseStandard; using GB5Shared.FastEndPoint; using IceImportBLL.IceImportRun; using IceImportSL.Parameters.Run; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using static GB5Shared.GB5Constant.Constant; namespace IceImportSL.Endpoints.Run; // Creates a new TICEIMPORTRUN row (RetryOfRunId = req.RunId) reusing the original run's SourceFile // artifact, then kicks off the same enqueue-and-return background execution CommitImport uses — // mirroring FrameworkBLL.DataSync.DataSyncRunBLL's "new IServiceScopeFactory scope, not the request // scope" pattern exactly, since the request returns immediately while the pipeline keeps running. public class RetryImportRun : BaseEndpoint> { private readonly IIceImportRunBLL _runBll; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; public RetryImportRun(IIceImportRunBLL runBll, IServiceScopeFactory scopeFactory, ILogger logger) { _runBll = runBll; _scopeFactory = scopeFactory; _logger = logger; } public override void Configure() { Post("/IceImport/RetryImportRun"); AllowAnonymous(); } // Mutation endpoint — never cached. protected override string? GetCacheKey(RetryImportRunParameters req, LoginDTO login) => null; protected override async Task> ExecuteAsync( RetryImportRunParameters req, LoginDTO login, CancellationToken ct) { try { var newRunId = await _runBll.PrepareRetryAsync(req.RunId, login, ct).ConfigureAwait(false); var sourceArtifact = await _runBll .GetArtifactByTypeAsync(newRunId, "SourceFile", login, ct) .ConfigureAwait(false) ?? throw new InvalidOperationException($"Retry run {newRunId} is missing its SourceFile artifact."); var iceMapId = (await _runBll.GetByIdAsync(newRunId, login, ct).ConfigureAwait(false))!.IceMapId; // Fire in a NEW DI scope — the request scope (and this endpoint instance) is disposed // the moment ExecuteAsync returns, so the background task must resolve its own // scoped IIceImportRunExecutionService rather than capture anything from 'this'. var scopeFactory = _scopeFactory; var logger = _logger; var storageKey = sourceArtifact.StorageKey; _ = Task.Run(async () => { await using var scope = scopeFactory.CreateAsyncScope(); var executionService = scope.ServiceProvider.GetRequiredService(); try { await executionService .ExecuteTrackedRunAsync(newRunId, iceMapId, null, storageKey, login, CancellationToken.None) .ConfigureAwait(false); } catch (Exception ex) { // ExecuteTrackedRunAsync already wraps its own body in try/catch and always // calls CompleteRunAsync — this is the last-resort net if even that fails. logger.LogError(ex, "IceImport retry background execution failed for RunId {RunId}", newRunId); } }, CancellationToken.None); return await GB5Shared.ResponseStandard.Response.CreateSuccessResponse( new { RunId = newRunId }, CacheKeyLevel.NOT_REQUIRED, login); } catch (Exception ex) { return await GB5Shared.ResponseStandard.Response.CreateExceptionError( ex, CacheKeyLevel.NOT_REQUIRED, login, ex.Message, 500); } } }