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 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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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");
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
Reference in new issue
Block a user