using System; using System.Threading.Tasks; using FrameworkBLL.GOP; using FrameworkBLL.Promotion; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ResponseStandard; using GB5Shared.DTO.GOP; using GB5Shared.DTO.Promotion; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using static GB5Shared.GB5Constant.Constant; namespace FrameworkSL.Controllers.Promotion { // ============================================================ // ImportPromotionPackageService — Tier 1 (manual carry) package receipt. // // MVC Controller, not a FastEndpoint — matches this repo's one consistent, // established exception to "FastEndpoints only": every existing file-upload // endpoint (GopFileUploadService, UploadService, ECMUploadService, // TemplateAttachmentController) already uses this pattern, not FastEndpoints' // IFormFile binding. // // Decrypts the uploaded package (this deployment's own private key, never // accepted from the request), splits its manifest into individual bundles, // and submits ONE GOP execution per item (SourceCode="PromotionIngest") -- // same "N items -> N independent executions" shape GopFileUploadBLL already // uses for its own generic file upload, so a reviewer can approve/reject // items in a batch independently. Returns 202-style immediately; actual // Ingest->Approval->Import processing happens on GopWorkerService's own // schedule, not inline with this request. // ============================================================ [ApiController] [Route("[controller]")] public class ImportPromotionPackageService : ControllerBase { private readonly IPromotionBLL _PromotionBLL; private readonly IGopQueueBLL _GopQueueBLL; public ImportPromotionPackageService(IPromotionBLL promotionBLL, IGopQueueBLL gopQueueBLL) { _PromotionBLL = promotionBLL; _GopQueueBLL = gopQueueBLL; } [HttpPost("ImportPackage")] public async Task> ImportPackage( IFormFile file, [FromHeader(Name = "Login")] string loginDTOJson) { var loginDTO = JsonConvert.DeserializeObject(loginDTOJson)!; try { if (file is null || file.Length == 0) throw new ArgumentException("No package file provided or file is empty."); byte[] encryptedBytes; using (var ms = new System.IO.MemoryStream()) { await file.CopyToAsync(ms); encryptedBytes = ms.ToArray(); } var manifest = await _PromotionBLL.OpenPackageAsync(encryptedBytes, default); var response = new PromotionImportResponseDTO { TotalItems = manifest.Items.Count }; for (int i = 0; i < manifest.Items.Count; i++) { var bundle = manifest.Items[i]; var itemResult = new PromotionImportItemResultDTO { ItemIndex = i + 1, EntityTypeCode = bundle.EntityTypeCode, OriginEntityId = bundle.OriginEntityId }; try { var submitResult = await _GopQueueBLL.SubmitExecution( new GopSubmitRequestDTO { SourceCode = "PromotionIngest", PayloadJson = System.Text.Json.JsonSerializer.Serialize(bundle), Priority = 5 }, loginDTO); itemResult.QueueId = submitResult.QueueId; itemResult.ExecutionId = submitResult.ExecutionId; itemResult.Status = "Queued"; } catch (Exception ex) { itemResult.Status = "Failed"; itemResult.Error = ex.Message; } response.Results.Add(itemResult); } response.SubmittedCount = response.Results.FindAll(r => r.Status == "Queued").Count; response.FailedCount = response.Results.FindAll(r => r.Status == "Failed").Count; return await GB5Shared.ResponseStandard.Response.CreateSuccessResponse( response, CacheKeyLevel.NOT_REQUIRED, loginDTO); } catch (Exception ex) { return await GB5Shared.ResponseStandard.Response.CreateExceptionError( ex, CacheKeyLevel.NOT_REQUIRED, loginDTO, ex.Message, 500); } } } }