using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Gott.Models; using Gott.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; namespace Gott.Endpoints; public static class HistoryEndpoint { public static void MapHistoryEndpoint(this IEndpointRouteBuilder app) { app.MapGet("/api/history", HandleHistory); app.MapGet("/api/history/poll", HandlePoll); app.MapPost("/api/archive", HandleArchive); app.MapGet("/api/archives", HandleListArchives); app.MapGet("/api/archive/{archiveId}", HandleGetArchive); app.MapGet("/api/attachments/{messageId}", HandleGetAttachments); app.MapPut("/api/messages/{messageId}/mark", HandleToggleMark); app.MapGet("/api/chains/{chainId}", HandleGetChain); } private static async Task HandleHistory(HttpContext ctx, DatabaseService db) { var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault(); if (string.IsNullOrEmpty(sessionId)) { ctx.Response.StatusCode = 400; await ctx.Response.WriteAsJsonAsync(new { error = "sessionId required" }); return; } const int messageLimit = 500; var beforeStr = ctx.Request.Query["beforeId"].FirstOrDefault(); var beforeId = long.TryParse(beforeStr, out var b) ? b : 0; var totalCount = await db.GetActiveMessageCount(sessionId); List messages; if (beforeId > 0) { messages = await db.GetActiveMessagesBeforeId(sessionId, beforeId, messageLimit); } else { messages = await db.GetLatestActiveMessages(sessionId, messageLimit); } // Zähle wie viele Nachrichten es VOR der ältesten geladenen noch gibt var oldestLoadedId = messages.Count > 0 ? messages[0].Id : 0; var remainingOlder = oldestLoadedId > 0 ? await db.GetActiveMessageCountBeforeId(sessionId, oldestLoadedId) : 0; // Nach Chain gruppieren (Reihenfolge beibehalten) var chainIds = messages.Select(m => m.ChainId).Distinct().ToList(); var chains = await db.GetActiveChains(sessionId); var chainMap = chains.ToDictionary(c => c.ChainId); var result = new List(); foreach (var chainId in chainIds) { chainMap.TryGetValue(chainId, out var chain); var chainMessages = messages.Where(m => m.ChainId == chainId).ToList(); result.Add(new { chain, messages = chainMessages }); } await ctx.Response.WriteAsJsonAsync(new { entries = result, hasMore = remainingOlder > 0, remainingCount = remainingOlder, totalCount, loadedCount = messages.Count }); } private static async Task HandlePoll(HttpContext ctx, DatabaseService db) { var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault(); var afterStr = ctx.Request.Query["afterId"].FirstOrDefault(); var afterId = long.TryParse(afterStr, out var a) ? a : 0; if (string.IsNullOrEmpty(sessionId)) { ctx.Response.StatusCode = 400; return; } var messages = await db.GetActiveMessagesAfterId(sessionId, afterId); await ctx.Response.WriteAsJsonAsync(messages); } private static async Task HandleArchive(HttpContext ctx, DatabaseService db) { var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault(); if (string.IsNullOrEmpty(sessionId)) { ctx.Response.StatusCode = 400; return; } var archive = await db.CreateArchive(sessionId); if (archive == null) { ctx.Response.StatusCode = 204; return; } await ctx.Response.WriteAsJsonAsync(archive); } private static async Task HandleListArchives(HttpContext ctx, DatabaseService db) { var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault(); if (string.IsNullOrEmpty(sessionId)) { ctx.Response.StatusCode = 400; return; } var archives = await db.GetArchives(sessionId); await ctx.Response.WriteAsJsonAsync(archives); } private static async Task HandleGetAttachments(HttpContext ctx, DatabaseService db) { var messageIdStr = ctx.Request.RouteValues["messageId"]?.ToString(); if (!long.TryParse(messageIdStr, out var messageId)) { ctx.Response.StatusCode = 400; return; } var attachments = await db.GetAttachments(messageId); // Ohne Base64-Data zurückgeben (zu groß), stattdessen als separater Download var result = new List(); foreach (var att in attachments) { result.Add(new { att.Id, att.MessageId, att.Type, att.MediaType, att.FileName, att.Data, att.CreatedAt }); } await ctx.Response.WriteAsJsonAsync(result); } private static async Task HandleToggleMark(HttpContext ctx, DatabaseService db) { var messageIdStr = ctx.Request.RouteValues["messageId"]?.ToString(); if (!long.TryParse(messageIdStr, out var messageId)) { ctx.Response.StatusCode = 400; return; } var isMarked = await db.ToggleMessageMark(messageId); await ctx.Response.WriteAsJsonAsync(new { messageId, isMarked }); } private static async Task HandleGetChain(HttpContext ctx, DatabaseService db) { var chainId = ctx.Request.RouteValues["chainId"]?.ToString(); if (string.IsNullOrEmpty(chainId)) { ctx.Response.StatusCode = 400; return; } var chain = await db.GetChain(chainId); if (chain == null) { ctx.Response.StatusCode = 404; return; } await ctx.Response.WriteAsJsonAsync(chain); } private static async Task HandleGetArchive(HttpContext ctx, DatabaseService db) { var archiveId = ctx.Request.RouteValues["archiveId"]?.ToString(); if (string.IsNullOrEmpty(archiveId)) { ctx.Response.StatusCode = 400; return; } var chains = await db.GetArchiveChains(archiveId); var result = new List(); foreach (var chain in chains) { var messages = await db.GetChainMessages(chain.ChainId); result.Add(new { chain, messages }); } await ctx.Response.WriteAsJsonAsync(result); } }