Files
gott/Endpoints/SharedEndpoint.cs

215 lines
6.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<JsonElement>(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<string> { "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<object>();
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;
}
}