feat: Settings in Datenbank speichern statt localStorage

- Neue Spalte 'settings' in users-Tabelle (Migration via TryAddColumn)
- SettingsEndpoint: GET/PUT /api/settings
- DatabaseService: GetUserSettings() + UpdateUserSettings()
- Frontend: currentSettings als globaler State in app.js
- settings-page.js: localStorage komplett entfernt, nutzt API
- Settings werden beim App-Start aus DB geladen und angewendet
This commit is contained in:
Meleki committed 2026-03-03 16:47:56 +01:00
1 parent aac10901fa
commit a0752ea492
8 files changed
+102 -43

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
using System.Text.Json;
using Gott.Extensions;
using Gott.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Gott.Endpoints;
public static class SettingsEndpoint
{
public static void MapSettingsEndpoints(this WebApplication app)
{
app.MapGet("/api/settings", HandleGet);
app.MapPut("/api/settings", HandleUpdate);
}
private static async System.Threading.Tasks.Task HandleGet(HttpContext ctx, DatabaseService db)
{
var userId = ctx.GetUserId();
var json = await db.GetUserSettings(userId);
ctx.Response.ContentType = "application/json";
await ctx.Response.WriteAsync(json ?? "{}");
}
private static async System.Threading.Tasks.Task HandleUpdate(HttpContext ctx, DatabaseService db)
{
var userId = ctx.GetUserId();
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
var json = body.ToString();
await db.UpdateUserSettings(userId, json);
ctx.Response.ContentType = "application/json";
await ctx.Response.WriteAsync(json);
}
}
+1
View File
@@ -11,6 +11,7 @@ public class UserRecord
public string UpdatedAt { get; set; } public string UpdatedAt { get; set; }
public bool IsAdmin { get; set; } public bool IsAdmin { get; set; }
public string Persona { get; set; } public string Persona { get; set; }
public string Settings { get; set; }
public string GetEffectivePersona() public string GetEffectivePersona()
{ {
+1
View File
@@ -54,6 +54,7 @@ app.MapInfoEndpoint();
app.MapBackupEndpoint(); app.MapBackupEndpoint();
app.MapSkillEndpoints(); app.MapSkillEndpoints();
app.MapProfileEndpoints(); app.MapProfileEndpoints();
app.MapSettingsEndpoints();
app.MapTelegramEndpoints(); app.MapTelegramEndpoints();
app.Run("http://0.0.0.0:19000"); app.Run("http://0.0.0.0:19000");
+17
View File
@@ -162,6 +162,7 @@ public class DatabaseService
TryAddColumn(conn, "agents", "user_id", "TEXT"); TryAddColumn(conn, "agents", "user_id", "TEXT");
TryAddColumn(conn, "users", "is_admin", "INTEGER NOT NULL DEFAULT 0"); TryAddColumn(conn, "users", "is_admin", "INTEGER NOT NULL DEFAULT 0");
TryAddColumn(conn, "users", "persona", "TEXT"); TryAddColumn(conn, "users", "persona", "TEXT");
TryAddColumn(conn, "users", "settings", "TEXT");
conn.Execute(@" conn.Execute(@"
CREATE TABLE IF NOT EXISTS agent_skills ( CREATE TABLE IF NOT EXISTS agent_skills (
@@ -792,6 +793,22 @@ public class DatabaseService
new { userId, displayName, persona, now = DateTime.UtcNow.ToString("o") }); new { userId, displayName, persona, now = DateTime.UtcNow.ToString("o") });
} }
public async Task<string> GetUserSettings(string userId)
{
using var conn = CreateConnection();
return await conn.QuerySingleOrDefaultAsync<string>(
"SELECT settings FROM users WHERE user_id = @userId",
new { userId });
}
public async Task UpdateUserSettings(string userId, string settings)
{
using var conn = CreateConnection();
await conn.ExecuteAsync(
"UPDATE users SET settings = @settings, updated_at = @now WHERE user_id = @userId",
new { userId, settings, now = DateTime.UtcNow.ToString("o") });
}
// ── Telegram ── // ── Telegram ──
public async Task<string> CreateTelegramLinkCode(string userId, string sessionId, string agentId) public async Task<string> CreateTelegramLinkCode(string userId, string sessionId, string agentId)
+1 -1
View File
@@ -79,7 +79,6 @@ header {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
transition: var(--transition-duration);
} }
.page-content { .page-content {
@@ -96,4 +95,5 @@ header,
width: var(--page-width); width: var(--page-width);
max-width: 100%; max-width: 100%;
align-self: center; align-self: center;
transition: width var(--transition-duration);
} }
+15
View File
@@ -306,6 +306,21 @@ async function apiTelegramDeleteWebhook() {
return resp.json(); return resp.json();
} }
async function apiGetSettings() {
const resp = await fetch('/api/settings');
if (!resp.ok) return {};
return resp.json();
}
async function apiSaveSettings(settings) {
const resp = await fetch('/api/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings)
});
return resp.json();
}
async function apiBackupDb() { async function apiBackupDb() {
const resp = await fetch('/api/backup', { method: 'POST', credentials: 'include' }); const resp = await fetch('/api/backup', { method: 'POST', credentials: 'include' });
if (!resp.ok) throw new Error('Backup fehlgeschlagen'); if (!resp.ok) throw new Error('Backup fehlgeschlagen');
+26 -1
View File
@@ -1,5 +1,28 @@
// ── Global User State ── // ── Global State ──
var currentUser = null; var currentUser = null;
var currentSettings = null;
const SETTINGS_DEFAULTS = {
pageWidth: 900,
toolCallDisplay: 'modifications',
chainMetaDisplay: 'sometimes',
showDeletedMessages: false
};
function loadSettings() {
return Object.assign({}, SETTINGS_DEFAULTS, currentSettings);
}
async function saveSettings(settings) {
currentSettings = settings;
applySettings();
try { await apiSaveSettings(settings); } catch (_) {}
}
function applySettings() {
var s = loadSettings();
document.documentElement.style.setProperty('--page-width', s.pageWidth + 'px');
}
// ── App Init ── // ── App Init ──
@@ -12,6 +35,8 @@ document.addEventListener('DOMContentLoaded', async () => {
} }
currentUser = user; currentUser = user;
currentSettings = await apiGetSettings();
applySettings();
loadClaudeStatus(); loadClaudeStatus();
const { page, id } = parseHash(); const { page, id } = parseHash();
+6 -41
View File
@@ -1,38 +1,5 @@
// ── Settings Page ── // ── Settings Page ──
const SETTINGS_KEY = 'settings';
const SETTINGS_DEFAULTS = {
pageWidth: 900,
toolCallDisplay: 'modifications', // 'all' | 'modifications' | 'hidden'
chainMetaDisplay: 'sometimes', // 'always' | 'sometimes' | 'never'
showDeletedMessages: false
};
function loadSettings() {
try {
const raw = localStorage.getItem(SETTINGS_KEY);
if (raw) return Object.assign({}, SETTINGS_DEFAULTS, JSON.parse(raw));
} catch (_) {}
return Object.assign({}, SETTINGS_DEFAULTS);
}
function saveSettings(settings) {
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
}
function resetSettings() {
localStorage.removeItem(SETTINGS_KEY);
}
function applySettings() {
const s = loadSettings();
document.documentElement.style.setProperty('--page-width', s.pageWidth + 'px');
}
// Apply on load
applySettings();
registerPage('settings', { registerPage('settings', {
init() { init() {
const slider = document.getElementById('settingPageWidth'); const slider = document.getElementById('settingPageWidth');
@@ -62,26 +29,25 @@ registerPage('settings', {
}); });
slider.addEventListener('change', () => { slider.addEventListener('change', () => {
const s = loadSettings(); var s = loadSettings();
s.pageWidth = parseInt(slider.value, 10); s.pageWidth = parseInt(slider.value, 10);
saveSettings(s); saveSettings(s);
applySettings();
}); });
toolCallSelect.addEventListener('change', () => { toolCallSelect.addEventListener('change', () => {
const s = loadSettings(); var s = loadSettings();
s.toolCallDisplay = toolCallSelect.value; s.toolCallDisplay = toolCallSelect.value;
saveSettings(s); saveSettings(s);
}); });
chainMetaSelect.addEventListener('change', () => { chainMetaSelect.addEventListener('change', () => {
const s = loadSettings(); var s = loadSettings();
s.chainMetaDisplay = chainMetaSelect.value; s.chainMetaDisplay = chainMetaSelect.value;
saveSettings(s); saveSettings(s);
}); });
showDeletedToggle.addEventListener('change', () => { showDeletedToggle.addEventListener('change', () => {
const s = loadSettings(); var s = loadSettings();
s.showDeletedMessages = showDeletedToggle.checked; s.showDeletedMessages = showDeletedToggle.checked;
saveSettings(s); saveSettings(s);
}); });
@@ -100,10 +66,9 @@ registerPage('settings', {
} }
}); });
resetBtn.addEventListener('click', () => { resetBtn.addEventListener('click', async () => {
if (!confirm('Alle Einstellungen auf Standard zurücksetzen?')) return; if (!confirm('Alle Einstellungen auf Standard zurücksetzen?')) return;
resetSettings(); await saveSettings(Object.assign({}, SETTINGS_DEFAULTS));
applySettings();
// Update UI // Update UI
slider.value = SETTINGS_DEFAULTS.pageWidth; slider.value = SETTINGS_DEFAULTS.pageWidth;
display.textContent = SETTINGS_DEFAULTS.pageWidth + 'px'; display.textContent = SETTINGS_DEFAULTS.pageWidth + 'px';