- Neuer ServerSettingsService: zentrale Settings in SQLite mit In-Memory-Cache - Auto-Migration: bestehende appsettings.Production.json wird einmalig in DB übernommen - Alle Services umgestellt: IConfiguration → ServerSettingsService (dynamisch, kein Neustart) - Port bleibt als einziges Setting in appsettings.Production.json (erfordert Neustart) - ServerSettingsEndpoint: liest/schreibt DB statt JSON-Datei, Port als Sonder-Setting - IConfiguration komplett entfernt aus allen Services (ChatService, ToolService, AnthropicService, OpenAiService, KreaService, TelegramService, KeyScannerService, GitHubEventScannerService) - AuthMiddleware: KreaApiKey dynamisch aus DB - appsettings.json bereinigt: nur noch Port und Logging - Dokumentation aktualisiert (ARCHITECTURE.md, ARCHITECTURE_QUICK.md, README.md)
125 lines
4.1 KiB
C#
125 lines
4.1 KiB
C#
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 Gott.Services;
|
||
using Microsoft.AspNetCore.Builder;
|
||
using Microsoft.AspNetCore.Http;
|
||
using Microsoft.Extensions.Configuration;
|
||
|
||
namespace Gott.Endpoints;
|
||
|
||
public static class ServerSettingsEndpoint
|
||
{
|
||
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, ServerSettingsService settings, IConfiguration config)
|
||
{
|
||
if (!ctx.IsAdmin())
|
||
{
|
||
ctx.Response.StatusCode = 403;
|
||
await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" });
|
||
return;
|
||
}
|
||
|
||
// Port aus appsettings (nicht DB) – als erstes Feld
|
||
var port = config.GetValue<int>("Port", 19000);
|
||
var allSettings = new List<object>
|
||
{
|
||
new
|
||
{
|
||
key = "Port",
|
||
type = "int",
|
||
value = (object)port,
|
||
label = "Server-Port (erfordert Neustart)",
|
||
inputType = "number",
|
||
}
|
||
};
|
||
allSettings.AddRange(settings.GetAll());
|
||
|
||
await ctx.Response.WriteAsJsonAsync(new { settings = allSettings });
|
||
}
|
||
|
||
private static async Task HandleUpdate(HttpContext ctx, ServerSettingsService settings, IConfiguration config)
|
||
{
|
||
if (!ctx.IsAdmin())
|
||
{
|
||
ctx.Response.StatusCode = 403;
|
||
await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" });
|
||
return;
|
||
}
|
||
|
||
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
|
||
var hint = "";
|
||
|
||
// Port separat behandeln – in appsettings.Production.json speichern
|
||
if (body.TryGetProperty("Port", out var portProp) && portProp.TryGetInt32(out var newPort))
|
||
{
|
||
var currentPort = config.GetValue<int>("Port", 19000);
|
||
if (newPort != currentPort && newPort > 0 && newPort <= 65535)
|
||
{
|
||
SavePortToAppSettings(ctx, newPort);
|
||
hint = "Port geändert – Neustart erforderlich!";
|
||
}
|
||
}
|
||
|
||
// Alle anderen Settings in DB speichern
|
||
foreach (var def in ServerSettingsService.Definitions)
|
||
{
|
||
if (!body.TryGetProperty(def.Key, out var prop)) continue;
|
||
|
||
switch (def.Type)
|
||
{
|
||
case "string":
|
||
settings.Set(def.Key, prop.GetString() ?? "");
|
||
break;
|
||
case "int":
|
||
settings.SetInt(def.Key, prop.TryGetInt32(out var i) ? i : 0);
|
||
break;
|
||
case "bool":
|
||
settings.SetBool(def.Key, prop.ValueKind == JsonValueKind.True);
|
||
break;
|
||
}
|
||
}
|
||
|
||
await ctx.Response.WriteAsJsonAsync(new { ok = true, hint = string.IsNullOrEmpty(hint) ? "Einstellungen gespeichert" : hint });
|
||
}
|
||
|
||
private static void SavePortToAppSettings(HttpContext ctx, int port)
|
||
{
|
||
// appsettings.Production.json im ContentRootPath (= publish-Verzeichnis)
|
||
var contentRoot = ctx.RequestServices.GetService(typeof(Microsoft.AspNetCore.Hosting.IWebHostEnvironment)) as Microsoft.AspNetCore.Hosting.IWebHostEnvironment;
|
||
var path = Path.Combine(contentRoot?.ContentRootPath ?? AppContext.BaseDirectory, "appsettings.Production.json");
|
||
|
||
JsonObject obj;
|
||
if (File.Exists(path))
|
||
{
|
||
try
|
||
{
|
||
var existing = File.ReadAllText(path);
|
||
obj = JsonNode.Parse(existing) as JsonObject ?? new JsonObject();
|
||
}
|
||
catch
|
||
{
|
||
obj = new JsonObject();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
obj = new JsonObject();
|
||
}
|
||
|
||
obj["Port"] = port;
|
||
|
||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||
File.WriteAllText(path, obj.ToJsonString(options));
|
||
}
|
||
}
|