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:
1 parent
aac10901fa
commit
a0752ea492
8 files changed
+102
-43
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public class UserRecord
|
||||
public string UpdatedAt { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
public string Persona { get; set; }
|
||||
public string Settings { get; set; }
|
||||
|
||||
public string GetEffectivePersona()
|
||||
{
|
||||
|
||||
@@ -54,6 +54,7 @@ app.MapInfoEndpoint();
|
||||
app.MapBackupEndpoint();
|
||||
app.MapSkillEndpoints();
|
||||
app.MapProfileEndpoints();
|
||||
app.MapSettingsEndpoints();
|
||||
app.MapTelegramEndpoints();
|
||||
|
||||
app.Run("http://0.0.0.0:19000");
|
||||
@@ -162,6 +162,7 @@ public class DatabaseService
|
||||
TryAddColumn(conn, "agents", "user_id", "TEXT");
|
||||
TryAddColumn(conn, "users", "is_admin", "INTEGER NOT NULL DEFAULT 0");
|
||||
TryAddColumn(conn, "users", "persona", "TEXT");
|
||||
TryAddColumn(conn, "users", "settings", "TEXT");
|
||||
|
||||
conn.Execute(@"
|
||||
CREATE TABLE IF NOT EXISTS agent_skills (
|
||||
@@ -792,6 +793,22 @@ public class DatabaseService
|
||||
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 ──
|
||||
|
||||
public async Task<string> CreateTelegramLinkCode(string userId, string sessionId, string agentId)
|
||||
|
||||
@@ -79,7 +79,6 @@ header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition: var(--transition-duration);
|
||||
}
|
||||
|
||||
.page-content {
|
||||
@@ -96,4 +95,5 @@ header,
|
||||
width: var(--page-width);
|
||||
max-width: 100%;
|
||||
align-self: center;
|
||||
transition: width var(--transition-duration);
|
||||
}
|
||||
@@ -306,6 +306,21 @@ async function apiTelegramDeleteWebhook() {
|
||||
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() {
|
||||
const resp = await fetch('/api/backup', { method: 'POST', credentials: 'include' });
|
||||
if (!resp.ok) throw new Error('Backup fehlgeschlagen');
|
||||
|
||||
+26
-1
@@ -1,5 +1,28 @@
|
||||
// ── Global User State ──
|
||||
// ── Global State ──
|
||||
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 ──
|
||||
|
||||
@@ -12,6 +35,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
}
|
||||
|
||||
currentUser = user;
|
||||
currentSettings = await apiGetSettings();
|
||||
applySettings();
|
||||
loadClaudeStatus();
|
||||
|
||||
const { page, id } = parseHash();
|
||||
|
||||
@@ -1,38 +1,5 @@
|
||||
// ── 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', {
|
||||
init() {
|
||||
const slider = document.getElementById('settingPageWidth');
|
||||
@@ -62,26 +29,25 @@ registerPage('settings', {
|
||||
});
|
||||
|
||||
slider.addEventListener('change', () => {
|
||||
const s = loadSettings();
|
||||
var s = loadSettings();
|
||||
s.pageWidth = parseInt(slider.value, 10);
|
||||
saveSettings(s);
|
||||
applySettings();
|
||||
});
|
||||
|
||||
toolCallSelect.addEventListener('change', () => {
|
||||
const s = loadSettings();
|
||||
var s = loadSettings();
|
||||
s.toolCallDisplay = toolCallSelect.value;
|
||||
saveSettings(s);
|
||||
});
|
||||
|
||||
chainMetaSelect.addEventListener('change', () => {
|
||||
const s = loadSettings();
|
||||
var s = loadSettings();
|
||||
s.chainMetaDisplay = chainMetaSelect.value;
|
||||
saveSettings(s);
|
||||
});
|
||||
|
||||
showDeletedToggle.addEventListener('change', () => {
|
||||
const s = loadSettings();
|
||||
var s = loadSettings();
|
||||
s.showDeletedMessages = showDeletedToggle.checked;
|
||||
saveSettings(s);
|
||||
});
|
||||
@@ -100,10 +66,9 @@ registerPage('settings', {
|
||||
}
|
||||
});
|
||||
|
||||
resetBtn.addEventListener('click', () => {
|
||||
resetBtn.addEventListener('click', async () => {
|
||||
if (!confirm('Alle Einstellungen auf Standard zurücksetzen?')) return;
|
||||
resetSettings();
|
||||
applySettings();
|
||||
await saveSettings(Object.assign({}, SETTINGS_DEFAULTS));
|
||||
// Update UI
|
||||
slider.value = SETTINGS_DEFAULTS.pageWidth;
|
||||
display.textContent = SETTINGS_DEFAULTS.pageWidth + 'px';
|
||||
|
||||
Reference in new issue
Block a user