using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Gott.Extensions; using Gott.Models; using Gott.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; namespace Gott.Endpoints; public static class SharedEndpoint { public static void MapSharedEndpoints(this IEndpointRouteBuilder app) { // Authentifizierte Endpunkte app.MapPost("/api/messages/{messageId}/share", HandleShareMessage); app.MapGet("/api/shared", HandleListShares); app.MapDelete("/api/shared/{token}", HandleDeleteShare); // Öffentlicher Endpunkt (Auth-Ausnahme in Middleware) app.MapGet("/api/shared/{token}", HandleGetShared); } private static async Task HandleShareMessage(HttpContext ctx, DatabaseService db) { var userId = ctx.GetUserId(); var messageIdStr = ctx.Request.RouteValues["messageId"]?.ToString(); if (!long.TryParse(messageIdStr, out var messageId)) { ctx.Response.StatusCode = 400; await ctx.Response.WriteAsJsonAsync(new { error = "Invalid messageId" }); return; } // Body lesen (optional: includeContext) bool includeContext = false; try { var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); if (body.TryGetProperty("includeContext", out var ic)) includeContext = ic.GetBoolean(); } catch { /* kein Body oder ungültig – defaults verwenden */ } // Nachricht existiert und gehört dem User? var message = await db.GetMessage(messageId); if (message == null) { ctx.Response.StatusCode = 404; await ctx.Response.WriteAsJsonAsync(new { error = "Message not found" }); return; } // Chain holen → Session prüfen var chain = await db.GetChain(message.ChainId); if (chain == null) { ctx.Response.StatusCode = 404; await ctx.Response.WriteAsJsonAsync(new { error = "Chain not found" }); return; } var session = await db.GetSession(chain.SessionId); if (session == null || session.UserId != userId) { ctx.Response.StatusCode = 403; await ctx.Response.WriteAsJsonAsync(new { error = "Not authorized" }); return; } var shared = await db.CreateSharedMessage(messageId, userId, includeContext); await ctx.Response.WriteAsJsonAsync(new { token = shared.ShareToken }); } private static async Task HandleGetShared(HttpContext ctx, DatabaseService db) { var token = ctx.Request.RouteValues["token"]?.ToString(); if (string.IsNullOrEmpty(token)) { ctx.Response.StatusCode = 400; return; } var shared = await db.GetSharedMessage(token); if (shared == null) { ctx.Response.StatusCode = 404; await ctx.Response.WriteAsJsonAsync(new { error = "Share not found" }); return; } // Ablaufdatum prüfen if (!string.IsNullOrEmpty(shared.ExpiresAt) && DateTime.TryParse(shared.ExpiresAt, out var expires) && expires < DateTime.UtcNow) { ctx.Response.StatusCode = 410; await ctx.Response.WriteAsJsonAsync(new { error = "Share expired" }); return; } var message = await db.GetMessage(shared.MessageId); if (message == null) { ctx.Response.StatusCode = 404; await ctx.Response.WriteAsJsonAsync(new { error = "Message not found" }); return; } // Chain-Info holen var chain = await db.GetChain(message.ChainId); if (shared.IncludeContext) { // Alle Messages der Chain bis zu dieser Nachricht laden var contextMessages = await db.GetChainMessagesUpTo(message.ChainId, shared.MessageId); // Text- und Web-Search-Messages zurückgeben (keine tool_use/tool_result) var allowedTypes = new HashSet { "text", "web_search_result" }; var textMessages = contextMessages .Where(m => allowedTypes.Contains(m.Type) && m.DeletedOn == null) .Select(m => new { m.Id, m.Role, m.Type, m.Content, m.CreatedAt, m.Iteration }) .ToList(); await ctx.Response.WriteAsJsonAsync(new { mode = "context", messages = textMessages, model = chain?.Model, createdAt = shared.CreatedAt }); } else { // Einzelne Nachricht await ctx.Response.WriteAsJsonAsync(new { mode = "single", message = new { message.Id, message.Role, message.Content, message.CreatedAt }, model = chain?.Model, createdAt = shared.CreatedAt }); } } private static async Task HandleListShares(HttpContext ctx, DatabaseService db) { var userId = ctx.GetUserId(); var shares = await db.GetSharedMessagesByUser(userId); var result = new List(); foreach (var s in shares) { var message = await db.GetMessage(s.MessageId); string sessionName = null; if (message != null) { var chain = await db.GetChain(message.ChainId); if (chain != null) { var session = await db.GetSession(chain.SessionId); sessionName = session?.Name; } } var preview = message?.Content ?? ""; if (preview.Length > 150) preview = preview.Substring(0, 150) + "…"; result.Add(new { s.ShareToken, s.MessageId, s.IncludeContext, s.CreatedAt, role = message?.Role, preview, sessionName }); } await ctx.Response.WriteAsJsonAsync(result); } private static async Task HandleDeleteShare(HttpContext ctx, DatabaseService db) { var userId = ctx.GetUserId(); var token = ctx.Request.RouteValues["token"]?.ToString(); if (string.IsNullOrEmpty(token)) { ctx.Response.StatusCode = 400; return; } await db.DeleteSharedMessage(token, userId); ctx.Response.StatusCode = 204; } }