Initial import into Gitea

This commit is contained in:
Grae Jones
2026-03-14 13:50:09 -07:00
parent 8e7e03702e
commit 34c1f09e01
154 changed files with 17666 additions and 1548 deletions

View File

@@ -0,0 +1,112 @@
using Management.Data;
using Management.Security;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
namespace Management.Controllers.Admin;
/// <summary>
/// Admin endpoint for manually triggering metric sync.
///
/// ENDPOINTS:
/// POST /api/admin/sync/metrics/{clientId} - Sync a specific client
/// POST /api/admin/sync/metrics - Sync all active clients
///
/// Proxies to Gateway /api/sync/metrics/* using an internal service key.
/// Gateway owns provider connectivity; Management owns the trigger surface.
/// Configure via GATEWAY_URL + INTERNAL_SERVICE_KEY env vars.
/// </summary>
[ApiController]
[Route("api/admin/sync")]
public sealed class AdminMetricSyncController : AdminControllerBase
{
private readonly IHttpClientFactory _http;
private readonly IConfiguration _config;
public AdminMetricSyncController(
SqlService sql,
ClientContext client,
ILogger<AdminMetricSyncController> log,
IHttpClientFactory http,
IConfiguration config)
: base(sql, client, log)
{
_http = http;
_config = config;
}
/// <summary>
/// Trigger metric sync for a specific client.
/// </summary>
[HttpPost("metrics/{clientId}")]
public async Task<IActionResult> SyncClient(
string clientId,
[FromQuery] string? startDate,
[FromQuery] string? endDate,
CancellationToken ct)
{
Logger.LogInformation("[Admin] MetricSync triggered for client {ClientId} | By={User}",
clientId, Client.Email);
return await ProxyToGateway($"metrics/{Uri.EscapeDataString(clientId)}", startDate, endDate, ct);
}
/// <summary>
/// Trigger metric sync for all active clients.
/// </summary>
[HttpPost("metrics")]
public async Task<IActionResult> SyncAll(
[FromQuery] string? startDate,
[FromQuery] string? endDate,
CancellationToken ct)
{
Logger.LogInformation("[Admin] MetricSync ALL clients triggered | By={User}", Client.Email);
return await ProxyToGateway("metrics/all", startDate, endDate, ct);
}
// ────────────────────────────────────────────────
// Proxy helper
// ────────────────────────────────────────────────
private async Task<IActionResult> ProxyToGateway(
string path, string? startDate, string? endDate, CancellationToken ct)
{
var gatewayUrl = _config["GATEWAY_URL"]?.TrimEnd('/');
var serviceKey = _config["INTERNAL_SERVICE_KEY"];
if (string.IsNullOrWhiteSpace(gatewayUrl))
return StatusCode(500, new { ok = false, error = "GATEWAY_URL not configured" });
if (string.IsNullOrWhiteSpace(serviceKey))
return StatusCode(500, new { ok = false, error = "INTERNAL_SERVICE_KEY not configured" });
var qs = new List<string>();
if (!string.IsNullOrWhiteSpace(startDate)) qs.Add($"startDate={Uri.EscapeDataString(startDate)}");
if (!string.IsNullOrWhiteSpace(endDate)) qs.Add($"endDate={Uri.EscapeDataString(endDate)}");
var url = $"{gatewayUrl}/api/sync/{path}";
if (qs.Count > 0) url += "?" + string.Join("&", qs);
try
{
var client = _http.CreateClient();
using var req = new HttpRequestMessage(HttpMethod.Post, url);
req.Headers.Add("X-Service-Key", serviceKey);
using var resp = await client.SendAsync(req, ct);
var body = await resp.Content.ReadAsStringAsync(ct);
Logger.LogInformation("[Admin] MetricSync Gateway response {Status} | Path={Path}",
(int)resp.StatusCode, path);
return resp.IsSuccessStatusCode
? Content(body, "application/json")
: StatusCode((int)resp.StatusCode, new { ok = false, error = $"Gateway returned {(int)resp.StatusCode}", detail = body });
}
catch (Exception ex)
{
Logger.LogError(ex, "[Admin] MetricSync proxy error | Path={Path}", path);
return StatusCode(500, new { ok = false, error = "Sync trigger failed", detail = ex.Message });
}
}
}