using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; using PromotionExchangeBLL; namespace PromotionExchangeSL.Controllers; // ============================================================ // PromotionExchangeController — Tier 2 store-and-forward for Metadata // Promotion packages (see the plan's Transport section). Callers here are // NOT GB5 tenant sessions — no LoginDTO, no "Login" header, no // ResponseStandardDTO wrapper — they're origin/destination environments // authenticating purely via X-Api-Key, exactly the shape DXP's vendor // portal already proves works for a party that may not run this codebase // at all. MVC controller (not a FastEndpoint/BaseEndpoint), matching this // repo's established file-upload exception (GopFileUploadService et al.) // AND because BaseEndpoint's LoginDTO assumptions don't apply here at all. // // The destination for List/Download is ALWAYS the caller's own resolved // identity (from its authenticated API key) — never a request parameter — // so one party can never see or fetch another party's packages. // ============================================================ [ApiController] [Route("[controller]")] [EnableRateLimiting("promotion-exchange")] public class PromotionExchangeController : ControllerBase { private readonly IPromotionExchangeBLL _ExchangeBLL; public PromotionExchangeController(IPromotionExchangeBLL exchangeBLL) => _ExchangeBLL = exchangeBLL; [HttpPost("UploadPackage")] public async Task UploadPackage( IFormFile file, [FromForm] string destinationEnvironmentCode, [FromHeader(Name = "X-Api-Key")] string apiKey, CancellationToken ct) { try { if (file is null || file.Length == 0) return BadRequest(new { error = "No package file provided or file is empty." }); byte[] bytes; using (var ms = new MemoryStream()) { await file.CopyToAsync(ms, ct); bytes = ms.ToArray(); } var packageId = await _ExchangeBLL.UploadPackageAsync(apiKey, destinationEnvironmentCode, bytes, ct) .ConfigureAwait(false); return Ok(new { packageId }); } catch (UnauthorizedAccessException ex) { return Unauthorized(new { error = ex.Message }); } catch (Exception ex) { return BadRequest(new { error = ex.Message }); } } [HttpGet("ListPendingPackages")] public async Task ListPendingPackages( [FromHeader(Name = "X-Api-Key")] string apiKey, CancellationToken ct) { try { var pending = await _ExchangeBLL.ListPendingAsync(apiKey, ct).ConfigureAwait(false); return Ok(pending); } catch (UnauthorizedAccessException ex) { return Unauthorized(new { error = ex.Message }); } } [HttpGet("DownloadPackage/{packageId:int}")] public async Task DownloadPackage( int packageId, [FromHeader(Name = "X-Api-Key")] string apiKey, CancellationToken ct) { try { var package = await _ExchangeBLL.DownloadPackageAsync(apiKey, packageId, ct).ConfigureAwait(false); return File(package.PackageBytes, "application/octet-stream", $"promotion-package-{packageId}.gbpromo"); } catch (UnauthorizedAccessException ex) { return Unauthorized(new { error = ex.Message }); } catch (InvalidOperationException ex) { return NotFound(new { error = ex.Message }); } } }