diff --git a/Endpoints/AgentEndpoint.cs b/Endpoints/AgentEndpoint.cs index 301046c..ea3d947 100644 --- a/Endpoints/AgentEndpoint.cs +++ b/Endpoints/AgentEndpoint.cs @@ -1,5 +1,6 @@ using System.Text.Json; using System.Threading.Tasks; +using Gott.Extensions; using Gott.Models; using Gott.Services; using Microsoft.AspNetCore.Builder; @@ -21,7 +22,8 @@ public static class AgentEndpoint private static async Task HandleList(HttpContext ctx, DatabaseService db) { - var agents = await db.GetAgents(); + var userId = ctx.GetUserId(); + var agents = await db.GetAgents(userId); await ctx.Response.WriteAsJsonAsync(agents); } @@ -31,13 +33,14 @@ public static class AgentEndpoint if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; } var agent = await db.GetAgent(agentId); - if (agent == null) { ctx.Response.StatusCode = 404; return; } + if (agent == null || agent.UserId != ctx.GetUserId()) { ctx.Response.StatusCode = 404; return; } await ctx.Response.WriteAsJsonAsync(agent); } private static async Task HandleCreate(HttpContext ctx, DatabaseService db, ImageService imageService, MemoryService memoryService) { + var userId = ctx.GetUserId(); var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); var avatarRaw = body.TryGetProperty("avatar", out var av) ? av.GetString() : null; @@ -48,6 +51,7 @@ public static class AgentEndpoint var agent = new AgentRecord { + UserId = userId, Name = body.TryGetProperty("name", out var n) ? n.GetString() ?? "Neuer Agent" : "Neuer Agent", Avatar = imageService.ResizeDataUrl(avatarRaw), Description = body.TryGetProperty("description", out var d) ? d.GetString() : null, @@ -68,11 +72,12 @@ public static class AgentEndpoint private static async Task HandleUpdate(HttpContext ctx, DatabaseService db, ImageService imageService, MemoryService memoryService) { + var userId = ctx.GetUserId(); var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; } var agent = await db.GetAgent(agentId); - if (agent == null) { ctx.Response.StatusCode = 404; return; } + if (agent == null || agent.UserId != userId) { ctx.Response.StatusCode = 404; return; } var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); @@ -97,10 +102,11 @@ public static class AgentEndpoint private static async Task HandleDelete(HttpContext ctx, DatabaseService db) { + var userId = ctx.GetUserId(); var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; } - await db.DeleteAgent(agentId); + await db.DeleteAgent(agentId, userId); ctx.Response.StatusCode = 204; } } diff --git a/Endpoints/ChatEndpoint.cs b/Endpoints/ChatEndpoint.cs index 71747e1..4f04faf 100644 --- a/Endpoints/ChatEndpoint.cs +++ b/Endpoints/ChatEndpoint.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Gott.Extensions; using Gott.Models; using Gott.Services; using Microsoft.AspNetCore.Builder; @@ -50,8 +51,9 @@ public static class ChatEndpoint } // Agent aus Session auflösen + var userId = req.HttpContext.GetUserId(); var session = await db.GetSession(sessionId); - if (session == null || string.IsNullOrEmpty(session.AgentId)) + if (session == null || session.UserId != userId || string.IsNullOrEmpty(session.AgentId)) { res.StatusCode = 400; await res.WriteAsJsonAsync(new { error = "Kein Agent ausgewählt" }); diff --git a/Endpoints/HistoryEndpoint.cs b/Endpoints/HistoryEndpoint.cs index fa3a348..547a00c 100644 --- a/Endpoints/HistoryEndpoint.cs +++ b/Endpoints/HistoryEndpoint.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Gott.Extensions; using Gott.Models; using Gott.Services; using Microsoft.AspNetCore.Builder; @@ -23,10 +24,17 @@ public static class HistoryEndpoint app.MapGet("/api/chains/{chainId}", HandleGetChain); } + private static async Task VerifySessionOwner(HttpContext ctx, DatabaseService db, string sessionId) + { + if (string.IsNullOrEmpty(sessionId)) return false; + var session = await db.GetSession(sessionId); + return session != null && session.UserId == ctx.GetUserId(); + } + private static async Task HandleHistory(HttpContext ctx, DatabaseService db) { var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault(); - if (string.IsNullOrEmpty(sessionId)) + if (string.IsNullOrEmpty(sessionId) || !await VerifySessionOwner(ctx, db, sessionId)) { ctx.Response.StatusCode = 400; await ctx.Response.WriteAsJsonAsync(new { error = "sessionId required" }); @@ -84,7 +92,7 @@ public static class HistoryEndpoint var afterStr = ctx.Request.Query["afterId"].FirstOrDefault(); var afterId = long.TryParse(afterStr, out var a) ? a : 0; - if (string.IsNullOrEmpty(sessionId)) + if (string.IsNullOrEmpty(sessionId) || !await VerifySessionOwner(ctx, db, sessionId)) { ctx.Response.StatusCode = 400; return; @@ -97,7 +105,7 @@ public static class HistoryEndpoint private static async Task HandleArchive(HttpContext ctx, DatabaseService db) { var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault(); - if (string.IsNullOrEmpty(sessionId)) + if (string.IsNullOrEmpty(sessionId) || !await VerifySessionOwner(ctx, db, sessionId)) { ctx.Response.StatusCode = 400; return; @@ -115,7 +123,7 @@ public static class HistoryEndpoint private static async Task HandleListArchives(HttpContext ctx, DatabaseService db) { var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault(); - if (string.IsNullOrEmpty(sessionId)) + if (string.IsNullOrEmpty(sessionId) || !await VerifySessionOwner(ctx, db, sessionId)) { ctx.Response.StatusCode = 400; return; diff --git a/Endpoints/SessionEndpoint.cs b/Endpoints/SessionEndpoint.cs index c883809..307b023 100644 --- a/Endpoints/SessionEndpoint.cs +++ b/Endpoints/SessionEndpoint.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Text.Json; using System.Threading.Tasks; +using Gott.Extensions; using Gott.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -29,12 +30,14 @@ public static class SessionEndpoint private static async Task HandleList(HttpContext ctx, DatabaseService db) { - var sessions = await db.GetSessionsWithStats(); + var userId = ctx.GetUserId(); + var sessions = await db.GetSessionsWithStats(userId); await ctx.Response.WriteAsJsonAsync(sessions); } private static async Task HandleGet(HttpContext ctx, DatabaseService db) { + var userId = ctx.GetUserId(); var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString(); if (string.IsNullOrEmpty(sessionId)) { @@ -42,7 +45,7 @@ public static class SessionEndpoint return; } var session = await db.GetSession(sessionId); - if (session == null) + if (session == null || session.UserId != userId) { ctx.Response.StatusCode = 404; return; @@ -52,6 +55,7 @@ public static class SessionEndpoint private static async Task HandleCreate(HttpContext ctx, DatabaseService db, ImageService imageService) { + var userId = ctx.GetUserId(); var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); var name = body.TryGetProperty("name", out var n) ? n.GetString() : "Neue Session"; @@ -63,12 +67,13 @@ public static class SessionEndpoint var context = body.TryGetProperty("context", out var ctxEl) ? ctxEl.GetString() : null; var customCommands = body.TryGetProperty("customCommands", out var cc) && cc.ValueKind != JsonValueKind.Null ? cc.GetRawText() : null; - var session = await db.CreateSession(name, description, image, agentId, workspace, context, customCommands); + var session = await db.CreateSession(userId, name, description, image, agentId, workspace, context, customCommands); await ctx.Response.WriteAsJsonAsync(session); } private static async Task HandleUpdate(HttpContext ctx, DatabaseService db, ImageService imageService) { + var userId = ctx.GetUserId(); var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString(); if (string.IsNullOrEmpty(sessionId)) { @@ -76,6 +81,13 @@ public static class SessionEndpoint return; } + var existing = await db.GetSession(sessionId); + if (existing == null || existing.UserId != userId) + { + ctx.Response.StatusCode = 404; + return; + } + var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); await db.UpdateSession(sessionId, body, imageService); @@ -85,15 +97,17 @@ public static class SessionEndpoint private static async Task HandleDelete(HttpContext ctx, DatabaseService db) { + var userId = ctx.GetUserId(); var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString(); if (string.IsNullOrEmpty(sessionId)) { ctx.Response.StatusCode = 400; return; } - await db.DeleteSession(sessionId); + await db.DeleteSession(sessionId, userId); ctx.Response.StatusCode = 204; } private static async Task HandleSystemPrompt(HttpContext ctx, DatabaseService db, MemoryService memoryService, SkillService skillService) { + var userId = ctx.GetUserId(); var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString(); if (string.IsNullOrEmpty(sessionId)) { @@ -102,7 +116,7 @@ public static class SessionEndpoint } var session = await db.GetSession(sessionId); - if (session == null) + if (session == null || session.UserId != userId) { ctx.Response.StatusCode = 404; return; @@ -135,11 +149,12 @@ public static class SessionEndpoint private static async Task HandleSystemPromptBlocks(HttpContext ctx, DatabaseService db, MemoryService memoryService, SkillService skillService) { + var userId = ctx.GetUserId(); var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString(); if (string.IsNullOrEmpty(sessionId)) { ctx.Response.StatusCode = 400; return; } var session = await db.GetSession(sessionId); - if (session == null) { ctx.Response.StatusCode = 404; return; } + if (session == null || session.UserId != userId) { ctx.Response.StatusCode = 404; return; } if (string.IsNullOrEmpty(session.AgentId)) { @@ -165,8 +180,9 @@ public static class SessionEndpoint await ctx.Response.WriteAsJsonAsync(blocks.Select(b => new { b.Icon, b.Label, b.Content })); } - private static async Task HandleStreaming(HttpContext ctx, StreamingTracker streamingTracker) + private static async Task HandleStreaming(HttpContext ctx, DatabaseService db, StreamingTracker streamingTracker) { + var userId = ctx.GetUserId(); var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString(); if (string.IsNullOrEmpty(sessionId)) { @@ -174,6 +190,13 @@ public static class SessionEndpoint return; } + var session = await db.GetSession(sessionId); + if (session == null || session.UserId != userId) + { + ctx.Response.StatusCode = 404; + return; + } + var isStreaming = streamingTracker.IsStreaming(sessionId); var chainId = streamingTracker.GetChainId(sessionId); await ctx.Response.WriteAsJsonAsync(new { streaming = isStreaming, chainId }); @@ -181,6 +204,7 @@ public static class SessionEndpoint private static async Task HandleExec(HttpContext ctx, DatabaseService db) { + var userId = ctx.GetUserId(); var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString(); if (string.IsNullOrEmpty(sessionId)) { @@ -189,7 +213,7 @@ public static class SessionEndpoint } var session = await db.GetSession(sessionId); - if (session == null) + if (session == null || session.UserId != userId) { ctx.Response.StatusCode = 404; return; @@ -304,6 +328,7 @@ public static class SessionEndpoint private static async Task HandleGitStatus(HttpContext ctx, DatabaseService db) { + var userId = ctx.GetUserId(); var sessionId = ctx.Request.RouteValues["sessionId"]?.ToString(); if (string.IsNullOrEmpty(sessionId)) { @@ -312,7 +337,7 @@ public static class SessionEndpoint } var session = await db.GetSession(sessionId); - if (session == null) + if (session == null || session.UserId != userId) { ctx.Response.StatusCode = 404; return; diff --git a/Endpoints/SkillEndpoint.cs b/Endpoints/SkillEndpoint.cs index 242d3a9..ba7e35c 100644 --- a/Endpoints/SkillEndpoint.cs +++ b/Endpoints/SkillEndpoint.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; +using Gott.Extensions; using Gott.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -50,10 +51,17 @@ public static class SkillEndpoint await ctx.Response.WriteAsJsonAsync(skills); } + private static async Task VerifyAgentOwner(HttpContext ctx, DatabaseService db, string agentId) + { + if (string.IsNullOrEmpty(agentId)) return false; + var agent = await db.GetAgent(agentId); + return agent != null && agent.UserId == ctx.GetUserId(); + } + private static async Task HandleGetAgentSkills(HttpContext ctx, DatabaseService db, SkillService skillService) { var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); - if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; } + if (string.IsNullOrEmpty(agentId) || !await VerifyAgentOwner(ctx, db, agentId)) { ctx.Response.StatusCode = 400; return; } var slugs = await db.GetAgentSkills(agentId); var allSkills = skillService.GetAllSkills(); @@ -69,7 +77,7 @@ public static class SkillEndpoint private static async Task HandleSetAgentSkills(HttpContext ctx, DatabaseService db) { var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); - if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; } + if (string.IsNullOrEmpty(agentId) || !await VerifyAgentOwner(ctx, db, agentId)) { ctx.Response.StatusCode = 400; return; } var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); var slugs = body.TryGetProperty("skills", out var arr) @@ -86,7 +94,7 @@ public static class SkillEndpoint { var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); var slug = ctx.Request.RouteValues["slug"]?.ToString(); - if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug)) { ctx.Response.StatusCode = 400; return; } + if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug) || !await VerifyAgentOwner(ctx, db, agentId)) { ctx.Response.StatusCode = 400; return; } await db.AddAgentSkill(agentId, slug); ctx.Response.StatusCode = 204; @@ -96,7 +104,7 @@ public static class SkillEndpoint { var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); var slug = ctx.Request.RouteValues["slug"]?.ToString(); - if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug)) { ctx.Response.StatusCode = 400; return; } + if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(slug) || !await VerifyAgentOwner(ctx, db, agentId)) { ctx.Response.StatusCode = 400; return; } await db.RemoveAgentSkill(agentId, slug); ctx.Response.StatusCode = 204; diff --git a/Endpoints/WorkspaceEndpoint.cs b/Endpoints/WorkspaceEndpoint.cs index e894a95..ac3237a 100644 --- a/Endpoints/WorkspaceEndpoint.cs +++ b/Endpoints/WorkspaceEndpoint.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Gott.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; @@ -54,7 +55,7 @@ public static class WorkspaceEndpoint // Workspace aus Session holen var db = ctx.RequestServices.GetRequiredService(); var session = await db.GetSession(sessionId ?? ""); - if (session == null || string.IsNullOrEmpty(session.Workspace)) + if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace)) { ctx.Response.StatusCode = 400; await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" }); @@ -118,7 +119,7 @@ public static class WorkspaceEndpoint var db = ctx.RequestServices.GetRequiredService(); var session = await db.GetSession(sessionId ?? ""); - if (session == null || string.IsNullOrEmpty(session.Workspace)) + if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace)) { ctx.Response.StatusCode = 400; await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" }); @@ -176,7 +177,7 @@ public static class WorkspaceEndpoint var db = ctx.RequestServices.GetRequiredService(); var session = await db.GetSession(sessionId ?? ""); - if (session == null || string.IsNullOrEmpty(session.Workspace)) + if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace)) { ctx.Response.StatusCode = 400; await ctx.Response.WriteAsJsonAsync(new { error = "Kein Workspace konfiguriert" }); @@ -446,7 +447,7 @@ public static class WorkspaceEndpoint var db = ctx.RequestServices.GetRequiredService(); var session = await db.GetSession(sessionId ?? ""); - if (session == null || string.IsNullOrEmpty(session.Workspace)) + if (session == null || session.UserId != ctx.GetUserId() || string.IsNullOrEmpty(session.Workspace)) { ctx.Response.StatusCode = 400; return; diff --git a/Extensions/HttpContextExtensions.cs b/Extensions/HttpContextExtensions.cs new file mode 100644 index 0000000..a35ab3f --- /dev/null +++ b/Extensions/HttpContextExtensions.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Http; + +namespace Gott.Extensions; + +public static class HttpContextExtensions +{ + public static string GetUserId(this HttpContext ctx) + { + return ctx.Items["UserId"] as string; + } +} diff --git a/Models/AgentRecord.cs b/Models/AgentRecord.cs index 17467ff..06ebce9 100644 --- a/Models/AgentRecord.cs +++ b/Models/AgentRecord.cs @@ -15,6 +15,7 @@ public class AgentRecord public bool UseIdentity { get; set; } = true; public bool UseUserInfo { get; set; } = true; public bool UseMemory { get; set; } = true; + public string UserId { get; set; } public bool IsDeleted { get; set; } public string CreatedAt { get; set; } public string UpdatedAt { get; set; } diff --git a/Models/SessionRecord.cs b/Models/SessionRecord.cs index b5a94db..5b5668d 100644 --- a/Models/SessionRecord.cs +++ b/Models/SessionRecord.cs @@ -11,6 +11,7 @@ public class SessionRecord public string Workspace { get; set; } public string Context { get; set; } public string CustomCommands { get; set; } + public string UserId { get; set; } public bool IsDeleted { get; set; } public string CreatedAt { get; set; } public string UpdatedAt { get; set; } diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index 5e89857..7a7d822 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -145,6 +145,10 @@ public class DatabaseService TryAddColumn(conn, "agents", "use_user_info", "INTEGER NOT NULL DEFAULT 1"); TryAddColumn(conn, "agents", "use_memory", "INTEGER NOT NULL DEFAULT 1"); + // User-Zuordnung + TryAddColumn(conn, "sessions", "user_id", "TEXT"); + TryAddColumn(conn, "agents", "user_id", "TEXT"); + conn.Execute(@" CREATE TABLE IF NOT EXISTS agent_skills ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -185,17 +189,17 @@ public class DatabaseService agent.CreatedAt = now; agent.UpdatedAt = now; await conn.ExecuteAsync(@" - INSERT INTO agents (agent_id, name, avatar, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, is_deleted, created_at, updated_at) - VALUES (@AgentId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, 0, @CreatedAt, @UpdatedAt) + INSERT INTO agents (agent_id, user_id, name, avatar, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, is_deleted, created_at, updated_at) + VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, 0, @CreatedAt, @UpdatedAt) ", agent); return agent; } - public async Task> GetAgents() + public async Task> GetAgents(string userId) { using var conn = CreateConnection(); return await conn.QueryAsync( - "SELECT * FROM agents WHERE is_deleted = 0 ORDER BY name"); + "SELECT * FROM agents WHERE user_id = @userId AND is_deleted = 0 ORDER BY name", new { userId }); } public async Task GetAgent(string agentId) @@ -215,27 +219,28 @@ public class DatabaseService custom_commands = @CustomCommands, auto_commit = @AutoCommit, use_soul = @UseSoul, use_identity = @UseIdentity, use_user_info = @UseUserInfo, use_memory = @UseMemory, updated_at = @UpdatedAt - WHERE agent_id = @AgentId + WHERE agent_id = @AgentId AND user_id = @UserId ", agent); } - public async Task DeleteAgent(string agentId) + public async Task DeleteAgent(string agentId, string userId) { using var conn = CreateConnection(); await conn.ExecuteAsync( - "UPDATE agents SET is_deleted = 1, updated_at = @now WHERE agent_id = @agentId", - new { agentId, now = DateTime.UtcNow.ToString("o") }); + "UPDATE agents SET is_deleted = 1, updated_at = @now WHERE agent_id = @agentId AND user_id = @userId", + new { agentId, userId, now = DateTime.UtcNow.ToString("o") }); } // ── Sessions ── - public async Task CreateSession(string name = null, string description = null, string image = null, string agentId = null, string workspace = null, string context = null, string customCommands = null) + public async Task CreateSession(string userId, string name = null, string description = null, string image = null, string agentId = null, string workspace = null, string context = null, string customCommands = null) { using var conn = CreateConnection(); var now = DateTime.UtcNow.ToString("o"); var session = new SessionRecord { SessionId = Guid.NewGuid().ToString("N"), + UserId = userId, Name = name, Description = description, Image = image, @@ -247,24 +252,24 @@ public class DatabaseService UpdatedAt = now }; await conn.ExecuteAsync(@" - INSERT INTO sessions (session_id, name, description, image, agent_id, workspace, context, custom_commands, created_at, updated_at) - VALUES (@SessionId, @Name, @Description, @Image, @AgentId, @Workspace, @Context, @CustomCommands, @CreatedAt, @UpdatedAt) + INSERT INTO sessions (session_id, user_id, name, description, image, agent_id, workspace, context, custom_commands, created_at, updated_at) + VALUES (@SessionId, @UserId, @Name, @Description, @Image, @AgentId, @Workspace, @Context, @CustomCommands, @CreatedAt, @UpdatedAt) ", session); return session; } - public async Task> GetSessions() + public async Task> GetSessions(string userId) { using var conn = CreateConnection(); return await conn.QueryAsync( - "SELECT * FROM sessions WHERE is_deleted = 0 ORDER BY updated_at DESC"); + "SELECT * FROM sessions WHERE user_id = @userId AND is_deleted = 0 ORDER BY updated_at DESC", new { userId }); } - public async Task> GetSessionsWithStats() + public async Task> GetSessionsWithStats(string userId) { using var conn = CreateConnection(); var sessions = await conn.QueryAsync( - "SELECT * FROM sessions WHERE is_deleted = 0 ORDER BY updated_at DESC"); + "SELECT * FROM sessions WHERE user_id = @userId AND is_deleted = 0 ORDER BY updated_at DESC", new { userId }); var results = new List(); foreach (var s in sessions) @@ -341,12 +346,12 @@ public class DatabaseService await conn.ExecuteAsync($"UPDATE sessions SET {string.Join(", ", sets)} WHERE session_id = @sessionId", p); } - public async Task DeleteSession(string sessionId) + public async Task DeleteSession(string sessionId, string userId) { using var conn = CreateConnection(); await conn.ExecuteAsync( - "UPDATE sessions SET is_deleted = 1, updated_at = @now WHERE session_id = @sessionId", - new { sessionId, now = DateTime.UtcNow.ToString("o") }); + "UPDATE sessions SET is_deleted = 1, updated_at = @now WHERE session_id = @sessionId AND user_id = @userId", + new { sessionId, userId, now = DateTime.UtcNow.ToString("o") }); } // ── Archives ── @@ -657,21 +662,31 @@ public class DatabaseService { using var conn = CreateConnection(); var count = conn.ExecuteScalar("SELECT COUNT(*) FROM users"); + string adminUserId; if (count == 0) { + adminUserId = Guid.NewGuid().ToString("N"); var now = DateTime.UtcNow.ToString("o"); conn.Execute(@" INSERT INTO users (user_id, username, password_hash, display_name, created_at, updated_at) VALUES (@userId, @username, @hash, @displayName, @now, @now)", new { - userId = Guid.NewGuid().ToString("N"), + userId = adminUserId, username = "admin", hash = auth.HashPassword("admin"), displayName = "Admin", now }); } + else + { + adminUserId = conn.ExecuteScalar("SELECT user_id FROM users ORDER BY id LIMIT 1"); + } + + // Bestehende Sessions/Agents ohne User dem ersten User zuweisen + conn.Execute("UPDATE sessions SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId }); + conn.Execute("UPDATE agents SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId }); } public async Task GetUserByUsername(string username)