using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Threading.Tasks; using Gott.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Formats.Webp; namespace Gott.Endpoints { public static class KreaEndpoint { public static void MapKreaEndpoints(this IEndpointRouteBuilder app) { app.MapPost("/api/images/generate", HandleGenerate); app.MapGet("/api/images", HandleList); app.MapGet("/api/images/file/{filename}", HandleFile); app.MapGet("/api/images/file/{filename}/thumb", HandleThumb); app.MapGet("/api/images/status", HandleStatus); } private static async Task HandleGenerate(HttpRequest req, HttpResponse res, KreaService krea) { if (!krea.IsConfigured) { res.StatusCode = 400; await res.WriteAsJsonAsync(new { error = "Krea API nicht konfiguriert (KreaToken fehlt)" }); return; } JsonElement body; try { body = await JsonSerializer.DeserializeAsync(req.Body); } catch { res.StatusCode = 400; await res.WriteAsJsonAsync(new { error = "Ungültiger JSON-Body" }); return; } var prompt = body.TryGetProperty("prompt", out var p) ? p.GetString() : null; if (string.IsNullOrWhiteSpace(prompt)) { res.StatusCode = 400; await res.WriteAsJsonAsync(new { error = "prompt ist erforderlich" }); return; } var model = body.TryGetProperty("model", out var m) ? m.GetString() : "flux"; var width = body.TryGetProperty("width", out var w) ? w.GetInt32() : 1024; var height = body.TryGetProperty("height", out var h) ? h.GetInt32() : 1024; var steps = body.TryGetProperty("steps", out var st) ? st.GetInt32() : 25; var guidanceScale = body.TryGetProperty("guidanceScale", out var g) ? g.GetDouble() : 3.0; var seed = body.TryGetProperty("seed", out var sd) ? sd.GetString() : null; var batchSize = body.TryGetProperty("batchSize", out var b) ? b.GetInt32() : 1; try { var result = await krea.GenerateAsync(prompt, model, width, height, steps, guidanceScale, seed, batchSize); await res.WriteAsJsonAsync(result); } catch (System.Exception ex) { res.StatusCode = 500; await res.WriteAsJsonAsync(new { error = ex.Message }); } } private static async Task HandleList(HttpRequest req, HttpResponse res, KreaService krea) { var images = krea.ListImages(); await res.WriteAsJsonAsync(images); } private static async Task HandleFile(HttpRequest req, HttpResponse res, KreaService krea, string filename) { // Sicherheitscheck – kein Path Traversal if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\")) { res.StatusCode = 400; return; } var path = krea.GetImagePath(filename); if (path == null) { res.StatusCode = 404; return; } var ext = Path.GetExtension(filename).ToLower(); var contentType = ext switch { ".png" => "image/png", ".jpg" => "image/jpeg", ".jpeg" => "image/jpeg", ".gif" => "image/gif", _ => "image/webp", }; res.ContentType = contentType; await res.SendFileAsync(path); } private static async Task HandleStatus(HttpRequest req, HttpResponse res, KreaService krea) { await res.WriteAsJsonAsync(new { configured = krea.IsConfigured, models = KreaService.IMAGE_MODELS.Keys, }); } private static readonly HashSet ThumbExtensions = new(StringComparer.OrdinalIgnoreCase) { ".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp" }; private static async Task HandleThumb(HttpRequest req, HttpResponse res, KreaService krea, string filename) { if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\")) { res.StatusCode = 400; return; } var ext = Path.GetExtension(filename); if (!ThumbExtensions.Contains(ext)) { res.StatusCode = 400; return; } var originalPath = krea.GetImagePath(filename); if (originalPath == null) { res.StatusCode = 404; return; } // Cache-Verzeichnis: data/cache/images/ var imagesDir = Path.GetDirectoryName(originalPath); var dataDir = Path.GetDirectoryName(imagesDir); var cacheDir = Path.Combine(dataDir, "cache", "images"); Directory.CreateDirectory(cacheDir); var thumbFilename = Path.GetFileNameWithoutExtension(filename) + ".webp"; var thumbPath = Path.Combine(cacheDir, thumbFilename); if (!File.Exists(thumbPath) || File.GetLastWriteTimeUtc(originalPath) > File.GetLastWriteTimeUtc(thumbPath)) { try { using var image = await Image.LoadAsync(originalPath); var maxSize = 320; image.Mutate(x => x.Resize(new ResizeOptions { Size = new Size(maxSize, maxSize), Mode = ResizeMode.Max })); var encoder = new WebpEncoder { Quality = 75 }; await image.SaveAsync(thumbPath, encoder); } catch { // Fallback: Original ausliefern var contentType = ext switch { ".png" => "image/png", ".jpg" => "image/jpeg", ".jpeg" => "image/jpeg", ".gif" => "image/gif", _ => "image/webp", }; res.ContentType = contentType; res.Headers["Cache-Control"] = "private, max-age=3600"; await res.SendFileAsync(originalPath); return; } } res.ContentType = "image/webp"; res.Headers["Cache-Control"] = "private, max-age=86400"; await res.SendFileAsync(thumbPath); } } }