using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Core.Configuration;
using PolyTrader.Web.Configuration;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
namespace PolyTrader.Web.Endpoints
{
///
/// Server-Einstellungen ohne Geheimnisse (#561). Was angezeigt und geändert werden darf, legt
/// fest.
///
public static class ServerSettingsEndpoints
{
public sealed record UpdateServerSettingsRequest(Dictionary? Values);
public static IEndpointRouteBuilder MapServerSettingsEndpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/settings/server");
group.MapGet("/", (IServiceProvider sp) =>
{
var file = sp.GetService();
if (file == null)
return Results.Problem("Die Einstellungsdatei ist nicht verfügbar.", statusCode: StatusCodes.Status503ServiceUnavailable);
try
{
return Results.Ok(Describe(file.Read()));
}
catch (Exception ex)
{
return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
}
});
// Alles oder nichts: Erst werden alle Werte geprüft, dann wird in einem Zug geschrieben. Ein
// halb übernommener Satz Einstellungen wäre schwerer zu verstehen als ein abgelehnter.
group.MapPost("/", (UpdateServerSettingsRequest? req, IServiceProvider sp, TerminalLogger logger) =>
{
if (req?.Values == null || req.Values.Count == 0)
return Results.BadRequest(new { error = "Keine Werte übergeben: {\"values\": {\"Feld\": Wert}}." });
var parsed = new List<(SettingDescriptor Field, object Value)>();
var errors = new List();
foreach (var (key, raw) in req.Values)
{
if (ServerSettingsCatalog.DesktopOnly.TryGetValue(key, out var reason))
{
logger.Warning($"🌐 WebUI: Änderung an {key} abgelehnt – nur im Desktop ({reason}).");
return Results.Json(new { error = $"{key} lässt sich nur im Desktop ändern ({reason}). Es wurde nichts geändert." },
statusCode: StatusCodes.Status403Forbidden);
}
var field = ServerSettingsCatalog.Find(key);
if (field == null || !field.Editable)
{
errors.Add($"{key}: im WebUI nicht vorhanden");
continue;
}
var (value, error) = field.Parse!(raw);
if (error != null) errors.Add($"{field.Label}: {error}");
else parsed.Add((field, value!));
}
if (errors.Count > 0)
return Results.BadRequest(new { error = "Nichts gespeichert. " + string.Join(" · ", errors), errors });
var file = sp.GetService();
ServerSettings? written = null;
var failed = PersistenceGuard.Write(file != null,
() => written = file!.Update(s => { foreach (var (f, v) in parsed) f.Assign!(s, v); }),
logger, "Server-Einstellungen");
if (failed != null) return failed;
// Den laufenden Stand nachziehen. Das Singleton lesen WSS-Client, Alchemy-Listener und der
// VPN-Check des Watchdogs; VPN- und Watchdog-Dienst halten eigene, aus der Datei geladene Kopien.
var live = sp.GetService();
if (live != null)
foreach (var (f, v) in parsed) f.Assign!(live, v);
sp.GetService()?.ReloadSettings();
sp.GetService()?.ReloadSettings();
logger.Info("🌐 WebUI: Server-Einstellungen geändert – " +
string.Join(", ", parsed.Select(p => $"{p.Field.Key}={p.Value}")));
return Results.Ok(new
{
success = true,
changed = parsed.Select(p => new { key = p.Field.Key, label = p.Field.Label, requiresRestart = p.Field.RequiresRestart }).ToList(),
restartRequired = parsed.Any(p => p.Field.RequiresRestart),
settings = Describe(written!)
});
});
return routes;
}
internal static object Describe(ServerSettings s) => new
{
managedIn = "desktop",
sections = ServerSettingsCatalog.Fields
.GroupBy(f => f.Category)
.Select(g => new
{
title = g.Key,
fields = g.Select(f => new
{
key = f.Key,
label = f.Label,
description = f.Description,
kind = f.Kind.ToString().ToLowerInvariant(),
value = f.Get(s),
options = f.Options,
min = f.Min,
max = f.Max,
editable = f.Editable,
requiresRestart = f.RequiresRestart
}).ToList()
}).ToList(),
secrets = ServerSettingsCatalog.SecretStatus(s).Select(x => new { label = x.Label, status = x.Status }).ToList()
};
}
}