Initial import into Gitea
This commit is contained in:
122
IntelligenceApi/Controllers/InternalController.cs
Normal file
122
IntelligenceApi/Controllers/InternalController.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using IntelligenceApi.Engines;
|
||||
using IntelligenceApi.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace IntelligenceApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Internal execution endpoint — the single entry point for Gateway-routed requests.
|
||||
///
|
||||
/// Called exclusively by the Gateway's ExecutionService via:
|
||||
/// POST /internal/execute
|
||||
/// Headers: X-Internal-Key, X-Request-Id
|
||||
/// Body: { "operation": "Ping" | "SpendDistribution", "payload": { ... } }
|
||||
///
|
||||
/// Operations:
|
||||
/// Ping — liveness check, no payload required
|
||||
/// SpendDistribution — routes to EngineRouter; payload maps to SpendDistributionRequest
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("internal/execute")]
|
||||
public sealed class InternalController : ControllerBase
|
||||
{
|
||||
private readonly EngineRouter _router;
|
||||
private readonly ILogger<InternalController> _log;
|
||||
|
||||
private static readonly JsonSerializerOptions _jsonOpts =
|
||||
new(JsonSerializerDefaults.Web);
|
||||
|
||||
public InternalController(EngineRouter router, ILogger<InternalController> log)
|
||||
{
|
||||
_router = router;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Execute(
|
||||
[FromBody] InternalExecuteRequest? request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (request == null)
|
||||
return BadRequest(new { ok = false, error = "Request body required" });
|
||||
|
||||
var requestId = request.RequestId ?? HttpContext.Request.Headers["X-Request-Id"].FirstOrDefault() ?? Guid.NewGuid().ToString("N");
|
||||
var operation = (request.Operation ?? "").Trim();
|
||||
|
||||
_log.LogInformation(
|
||||
"[Internal] Operation={Operation} RequestId={RequestId}",
|
||||
operation, requestId);
|
||||
|
||||
return operation.ToLowerInvariant() switch
|
||||
{
|
||||
"ping" => Ok(new { ok = true, requestId, service = "IntelligenceApi", timestamp = DateTimeOffset.UtcNow }),
|
||||
"spenddistribution" => await SpendDistribution(request.Payload, requestId, ct),
|
||||
_ => BadRequest(new { ok = false, requestId, error = $"Unknown operation: '{operation}'" })
|
||||
};
|
||||
}
|
||||
|
||||
// ── Spend Distribution ───────────────────────────────────────────────────
|
||||
|
||||
private async Task<IActionResult> SpendDistribution(
|
||||
JsonElement? payload,
|
||||
string requestId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (payload == null || payload.Value.ValueKind == JsonValueKind.Null)
|
||||
return BadRequest(new { ok = false, requestId, error = "Payload required for SpendDistribution" });
|
||||
|
||||
SpendDistributionRequest? distRequest;
|
||||
try
|
||||
{
|
||||
distRequest = JsonSerializer.Deserialize<SpendDistributionRequest>(
|
||||
payload.Value.GetRawText(), _jsonOpts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogWarning("[Internal] Payload deserialize failed | RequestId={RequestId} Error={Error}",
|
||||
requestId, ex.Message);
|
||||
return BadRequest(new { ok = false, requestId, error = "Invalid payload shape for SpendDistribution" });
|
||||
}
|
||||
|
||||
if (distRequest == null)
|
||||
return BadRequest(new { ok = false, requestId, error = "Payload required for SpendDistribution" });
|
||||
|
||||
if (distRequest.MonthlyBudget <= 0)
|
||||
return BadRequest(new { ok = false, requestId, error = "monthlyBudget must be greater than zero" });
|
||||
|
||||
if (distRequest.Keywords.Count == 0)
|
||||
return BadRequest(new { ok = false, requestId, error = "At least one keyword is required" });
|
||||
|
||||
try
|
||||
{
|
||||
var engine = _router.Resolve(distRequest.ClientCategory);
|
||||
var response = await engine.RecommendAsync(distRequest, ct);
|
||||
|
||||
_log.LogInformation(
|
||||
"[Internal] SpendDistribution OK | RequestId={RequestId} Engine={Engine} Channels={N}",
|
||||
requestId, response.Metadata.Engine, response.Channels.Count);
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "[Internal] SpendDistribution error | RequestId={RequestId}", requestId);
|
||||
return StatusCode(500, new { ok = false, requestId, error = "Intelligence service error" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Request model ────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Shape sent by Gateway's ExecutionService to every provider container.
|
||||
/// Matches the object built in ExecutionService.BuildProviderRequest().
|
||||
/// </summary>
|
||||
public sealed class InternalExecuteRequest
|
||||
{
|
||||
public string? Operation { get; set; }
|
||||
public string? RequestId { get; set; }
|
||||
public string? TenantId { get; set; }
|
||||
public JsonElement? Payload { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user