using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; using Gott.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; namespace Gott.Endpoints; public static class ServerSettingsEndpoint { private static readonly string ProductionConfigPath = Path.Combine(AppContext.BaseDirectory, "appsettings.Production.json"); private static readonly string FallbackConfigPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json"); // Definiert alle konfigurierbaren Properties mit Typ und Standardwert private static readonly List Definitions = new List { new SettingDefinition("AnthropicToken", "string", "", "Anthropic API Key", "password"), new SettingDefinition("MaxIterations", "int", 100, "Max Tool-Loop Iterationen pro Chat", "number"), new SettingDefinition("PayloadLogging", "bool", false, "API-Payloads loggen (Debug)", "toggle"), new SettingDefinition("PayloadLoggingGzip", "bool", false, "Payload-Logs als .gz komprimieren", "toggle"), new SettingDefinition("KreaToken", "string", "", "Krea API Token", "password"), new SettingDefinition("KreaApiKey", "string", "", "API-Key für Krea-Endpoints (Automation)", "password"), }; public static void MapServerSettingsEndpoints(this WebApplication app) { app.MapGet("/api/admin/server-settings", HandleGet); app.MapPut("/api/admin/server-settings", HandleUpdate); } private static async Task HandleGet(HttpContext ctx, IConfiguration config) { if (!ctx.IsAdmin()) { ctx.Response.StatusCode = 403; await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" }); return; } // Production-Config lesen (falls vorhanden) JsonObject prodConfig = null; if (File.Exists(ProductionConfigPath)) { var json = await File.ReadAllTextAsync(ProductionConfigPath); prodConfig = JsonNode.Parse(json) as JsonObject; } // Fallback-Config lesen JsonObject fallbackConfig = null; if (File.Exists(FallbackConfigPath)) { var json = await File.ReadAllTextAsync(FallbackConfigPath); fallbackConfig = JsonNode.Parse(json) as JsonObject; } var settings = new List(); foreach (var def in Definitions) { object value = def.DefaultValue; // Fallback: appsettings.json if (fallbackConfig != null && fallbackConfig.ContainsKey(def.Key)) { value = ExtractValue(fallbackConfig[def.Key], def.Type); } // Override: appsettings.Production.json if (prodConfig != null && prodConfig.ContainsKey(def.Key)) { value = ExtractValue(prodConfig[def.Key], def.Type); } settings.Add(new { key = def.Key, type = def.Type, value = value, label = def.Label, inputType = def.InputType, }); } await ctx.Response.WriteAsJsonAsync(new { settings }); } private static async Task HandleUpdate(HttpContext ctx) { if (!ctx.IsAdmin()) { ctx.Response.StatusCode = 403; await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" }); return; } var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); // Bestehende Production-Config laden oder neue erstellen JsonObject prodConfig; if (File.Exists(ProductionConfigPath)) { var existing = await File.ReadAllTextAsync(ProductionConfigPath); prodConfig = JsonNode.Parse(existing) as JsonObject ?? new JsonObject(); } else { prodConfig = new JsonObject(); } // Nur definierte Keys akzeptieren foreach (var def in Definitions) { if (!body.TryGetProperty(def.Key, out var prop)) continue; // Alten Wert entfernen falls vorhanden prodConfig.Remove(def.Key); switch (def.Type) { case "string": prodConfig.Add(def.Key, prop.GetString() ?? ""); break; case "int": prodConfig.Add(def.Key, prop.TryGetInt32(out var i) ? i : (int)def.DefaultValue); break; case "bool": prodConfig.Add(def.Key, prop.ValueKind == JsonValueKind.True); break; } } var options = new JsonSerializerOptions { WriteIndented = true }; var json = prodConfig.ToJsonString(options); await File.WriteAllTextAsync(ProductionConfigPath, json); await ctx.Response.WriteAsJsonAsync(new { ok = true, hint = "Die Änderungen erfordern einen Neustart des Servers." }); } private static object ExtractValue(JsonNode node, string type) { if (node == null) return null; switch (type) { case "string": return node.GetValue(); case "int": return node.GetValue(); case "bool": return node.GetValue(); default: return node.ToString(); } } private class SettingDefinition { public string Key { get; } public string Type { get; } public object DefaultValue { get; } public string Label { get; } public string InputType { get; } public SettingDefinition(string key, string type, object defaultValue, string label, string inputType) { Key = key; Type = type; DefaultValue = defaultValue; Label = label; InputType = inputType; } } }