using System; using System.IO; using System.Threading; using System.Threading.Tasks; using FastEndpoints; using GB5Shared.FileUpload; using FrameworkBLL.SystemJob; using FrameworkDAL.CustomCode.SysJobShare; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ResponseStandard; using GB5Shared.FastEndPoint; using Microsoft.Extensions.Caching.Distributed; using static FrameworkDAL.Constant.Constant; namespace FrameworkSL.Endpoints.SystemJob { public class GetSystemJobResult : BaseEndpoint> { private readonly ISystemJobBLL _SystemJobBLL; private readonly ISysJobShareDAL _SysJobShareDAL; private readonly IDistributedCache _resultCache; private readonly IFileUploadBLL _fileUploadBLL; public GetSystemJobResult(ISystemJobBLL systemJobBLL, ISysJobShareDAL sysJobShareDAL, IDistributedCache resultCache, IFileUploadBLL fileUploadBLL) { _SystemJobBLL = systemJobBLL; _SysJobShareDAL = sysJobShareDAL; _resultCache = resultCache; _fileUploadBLL = fileUploadBLL; } public override void Configure() { Get("/SystemJob/GetSystemJobResult"); AllowAnonymous(); } public record GetSystemJobResultParameters( [property: FromHeader] string Login, [property: QueryParam] int SysJobId ); protected override async Task> ExecuteAsync(GetSystemJobResultParameters req, LoginDTO LoginDTO, CancellationToken ct) { try { // ACL check — must be owner or have CanViewResult share int access = await _SysJobShareDAL.CheckResultAccess(req.SysJobId, LoginDTO!.UserId, LoginDTO); if (access == 0) return await GB5Shared.ResponseStandard.Response.CreateExceptionError( new UnauthorizedAccessException(), CacheKeyLevel.UserLevel, LoginDTO, "You do not have permission to view the result of this job.", 403); string resultPath = await _SystemJobBLL.GetJobResultPath(req.SysJobId, LoginDTO!); if (string.IsNullOrWhiteSpace(resultPath)) return await GB5Shared.ResponseStandard.Response.CreateExceptionError( new FileNotFoundException(), CacheKeyLevel.UserLevel, LoginDTO, "No result available for this job yet.", 404); if (resultPath.StartsWith("att:", StringComparison.Ordinal)) { // DMS path: result stored in TATTACHMENT, linked to this TSYSJOB row. // The report service saved the file via IFileUploadBLL and returned "att:{attachmentId}". if (!int.TryParse(resultPath["att:".Length..], out int attachmentId)) return await GB5Shared.ResponseStandard.Response.CreateExceptionError( new FormatException(), CacheKeyLevel.UserLevel, LoginDTO, "Result reference is invalid.", 500); var attachment = await _fileUploadBLL.DownloadAttachmentAsBase64(attachmentId, LoginDTO).ConfigureAwait(false); if (attachment is null || string.IsNullOrEmpty(attachment.Base64Content)) return await GB5Shared.ResponseStandard.Response.CreateExceptionError( new FileNotFoundException(), CacheKeyLevel.UserLevel, LoginDTO, "Result file not found in document store.", 404); byte[] bytes = Convert.FromBase64String(attachment.Base64Content); string contentType = string.IsNullOrEmpty(attachment.MimeType) ? ResolveContentType(attachment.FileName) : attachment.MimeType; using var ms = new MemoryStream(bytes); await HttpContext.Response.SendStreamAsync(ms, attachment.FileName, ms.Length, contentType, cancellation: ct); } else if (resultPath.StartsWith("redis:", StringComparison.Ordinal)) { // Redis path: result stored in distributed cache string redisKey = resultPath["redis:".Length..]; byte[]? cached = await _resultCache.GetAsync(redisKey, ct).ConfigureAwait(false); if (cached is null) return await GB5Shared.ResponseStandard.Response.CreateExceptionError( new FileNotFoundException(), CacheKeyLevel.UserLevel, LoginDTO, "Result has expired or is not yet available.", 404); string fileName = Path.GetFileName(redisKey); string contentType = ResolveContentType(fileName); using var ms = new MemoryStream(cached); await HttpContext.Response.SendStreamAsync(ms, fileName, ms.Length, contentType, cancellation: ct); } else { // Legacy path: result stored on the filesystem if (!System.IO.File.Exists(resultPath)) return await GB5Shared.ResponseStandard.Response.CreateExceptionError( new FileNotFoundException(), CacheKeyLevel.UserLevel, LoginDTO, "Result file not found on server.", 404); string fileName = Path.GetFileName(resultPath); string contentType = ResolveContentType(fileName); using var fs = new FileStream(resultPath, FileMode.Open, FileAccess.Read, FileShare.Read); await HttpContext.Response.SendStreamAsync(fs, fileName, fs.Length, contentType, cancellation: ct); } // Return value is not used when SendStreamAsync is called — return null sentinel return null!; } catch (Exception ex) { return await GB5Shared.ResponseStandard.Response.CreateExceptionError(ex, CacheKeyLevel.UserLevel, LoginDTO, ex.Message, 500); } } private static string ResolveContentType(string fileName) { string ext = Path.GetExtension(fileName).ToLowerInvariant(); return ext switch { ".pdf" => "application/pdf", ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xls" => "application/vnd.ms-excel", ".csv" => "text/csv", ".json" => "application/json", ".zip" => "application/zip", _ => "application/octet-stream" }; } } }