using System.Diagnostics.CodeAnalysis; using System.Net.Http.Json; using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using MoonlightServers.Api.Infrastructure.Database.Entities; using MoonlightServers.DaemonShared.Http; using MoonlightServers.DaemonShared.Http.Daemon; namespace MoonlightServers.Api.Admin.Nodes; public class NodeService { private readonly IHttpClientFactory ClientFactory; private readonly ILogger Logger; public NodeService(IHttpClientFactory clientFactory, ILogger logger) { ClientFactory = clientFactory; Logger = logger; } public async Task GetHealthAsync(Node node) { var client = ClientFactory.CreateClient(); var request = CreateBaseRequest(node, HttpMethod.Get, "api/health"); try { var response = await client.SendAsync(request); if (!response.IsSuccessStatusCode) return new NodeHealthStatus((int)response.StatusCode, null); try { var health = await response .Content .ReadFromJsonAsync(SerializationContext.Default.Options); return new NodeHealthStatus((int)response.StatusCode, health); } catch (Exception e) { Logger.LogTrace(e, "An unhandled error occured while processing health response of node {id}", node.Id); return new NodeHealthStatus((int)response.StatusCode, null); } } catch (Exception e) { Logger.LogTrace(e, "An error occured while fetching health status of node {id}", node.Id); return new NodeHealthStatus(0, null); } } private static HttpRequestMessage CreateBaseRequest( Node node, [StringSyntax(StringSyntaxAttribute.Uri)] HttpMethod method, string endpoint ) { var request = new HttpRequestMessage(); request.Headers.Add(HeaderNames.Authorization, node.Token); request.RequestUri = new Uri(new Uri(node.HttpEndpointUrl), endpoint); request.Method = method; return request; } private async Task EnsureSuccessAsync(HttpResponseMessage message) { if (message.IsSuccessStatusCode) return; try { var problemDetails = await message.Content.ReadFromJsonAsync( SerializationContext.Default.Options ); if (problemDetails == null) { // If we cant handle problem details, we handle it natively message.EnsureSuccessStatusCode(); return; } // Parse into exception throw new NodeException( problemDetails.Type, problemDetails.Title, problemDetails.Status, problemDetails.Detail, problemDetails.Errors ); } catch (JsonException) { // If we cant handle problem details, we handle it natively message.EnsureSuccessStatusCode(); } } } public record NodeHealthStatus(int StatusCode, HealthDto? Dto); public class NodeException : Exception { public string Type { get; } public string Title { get; } public int Status { get; } public string? Detail { get; } public Dictionary? Errors { get; } public NodeException( string type, string title, int status, string? detail = null, Dictionary? errors = null) : base(detail ?? title) { Type = type; Title = title; Status = status; Detail = detail; Errors = errors; } }