using FrameworkBLL.EIPConversation.EIPHandlers.UrlResolver; using FrameworkDAL.DTO.EIPConversation; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace FrameworkBLL.EIPConversation.EIPHandlers.EIPEngine.ActionHandler { /// /// Generic CRUD capability handler that calls any internal GB5 service endpoint. /// /// context.Payload must contain: /// "Method" — HTTP verb: GET | POST | PUT | DELETE (default POST) /// "Endpoint" — relative path, e.g. "/Leave/SaveLeave" /// "Body" — optional JSON body string (may use {ContextKey} substitutions already resolved upstream) /// /// On success the raw response body is stored in context.Payload["CrudResult"]. /// /// ActionCode: "CRUD_OPERATION" /// Example flow step: /// { /// "StepType": "CAPABILITY", /// "CapabilityCode": "CRUD_OPERATION", /// "MessageTemplate": "Leave submitted successfully.", /// "NextStepCode": "END" /// } /// public class CrudOperationHandler : IEIPActionHandler { public string ActionCode => "CRUD_OPERATION"; private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _config; private readonly IEIPUrlResolver _urlResolver; private readonly ILogger _logger; public CrudOperationHandler( IHttpClientFactory httpClientFactory, IConfiguration config, IEIPUrlResolver urlResolver, ILogger logger) { _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); _config = config ?? throw new ArgumentNullException(nameof(config)); _urlResolver = urlResolver ?? throw new ArgumentNullException(nameof(urlResolver)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public bool CanHandle(string actionCode) => string.Equals(actionCode, ActionCode, StringComparison.OrdinalIgnoreCase); public async Task HandleAsync( EIPActionContextDTO context, LoginDTO loginDTO, CancellationToken ct) { if (context.Payload == null || context.Payload.Count == 0) return new EIPActionResultDTO { IsSuccess = false, Message = "Payload is empty." }; if (!context.Payload.TryGetValue("Endpoint", out var endpointObj) || string.IsNullOrWhiteSpace(endpointObj?.ToString())) { return new EIPActionResultDTO { IsSuccess = false, Message = "Payload missing required key 'Endpoint'." }; } var method = context.Payload.TryGetValue("Method", out var m) ? m?.ToString() ?? "POST" : "POST"; var body = context.Payload.TryGetValue("Body", out var b) ? b?.ToString() ?? "{}" : "{}"; string fullUrl; // New-style: ServicePrefix + PathTemplate in payload → route through YARP gateway if (context.Payload.TryGetValue("ServicePrefix", out var spObj) && !string.IsNullOrWhiteSpace(spObj?.ToString())) { var baseUri = loginDTO.BaseUri?.TrimEnd('/') ?? string.Empty; fullUrl = _urlResolver.ResolveUrlFromPayload(context.Payload, new System.Collections.Generic.Dictionary(), baseUri); } else { // Legacy: Endpoint relative to SysJobSettings:ServiceBaseUrl var relativeEndpoint = endpointObj.ToString()!.TrimStart('/'); var baseUrl = _config["SysJobSettings:ServiceBaseUrl"]?.TrimEnd('/'); if (string.IsNullOrWhiteSpace(baseUrl)) { _logger.LogError("CrudOperationHandler: SysJobSettings:ServiceBaseUrl is not configured."); return new EIPActionResultDTO { IsSuccess = false, Message = "Service base URL is not configured." }; } fullUrl = $"{baseUrl}/{relativeEndpoint}"; } _logger.LogInformation( "CrudOperationHandler: {Method} {Url} | ConversationId={Id}", method, fullUrl, context.ConversationId); try { var client = _httpClientFactory.CreateClient("gb5-internal"); HttpResponseMessage response; if (method.Equals("GET", StringComparison.OrdinalIgnoreCase)) { response = await client.GetAsync(fullUrl, ct).ConfigureAwait(false); } else if (method.Equals("DELETE", StringComparison.OrdinalIgnoreCase)) { response = await client.DeleteAsync(fullUrl, ct).ConfigureAwait(false); } else { using var content = new StringContent(body, Encoding.UTF8, "application/json"); response = method.Equals("PUT", StringComparison.OrdinalIgnoreCase) ? await client.PutAsync(fullUrl, content, ct).ConfigureAwait(false) : await client.PostAsync(fullUrl, content, ct).ConfigureAwait(false); } var responseBody = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { _logger.LogWarning( "CrudOperationHandler: {Status} from {Url} | Body={Body}", (int)response.StatusCode, fullUrl, responseBody); return new EIPActionResultDTO { IsSuccess = false, Message = $"Service returned {(int)response.StatusCode}.", ErrorMessage = responseBody }; } context.Payload["CrudResult"] = responseBody; _logger.LogInformation( "CrudOperationHandler: OK | {Method} {Url}", method, fullUrl); return new EIPActionResultDTO { IsSuccess = true, Message = "Operation completed successfully.", Data = responseBody }; } catch (OperationCanceledException) { _logger.LogWarning("CrudOperationHandler: cancelled | Url={Url}", fullUrl); return new EIPActionResultDTO { IsSuccess = false, Message = "Operation cancelled." }; } catch (Exception ex) { _logger.LogError(ex, "CrudOperationHandler: exception | Url={Url}", fullUrl); return new EIPActionResultDTO { IsSuccess = false, Message = ex.Message, ErrorMessage = ex.ToString() }; } } } }