diff --git a/Endpoints/BackupEndpoint.cs b/Endpoints/BackupEndpoint.cs new file mode 100644 index 0000000..4295d3f --- /dev/null +++ b/Endpoints/BackupEndpoint.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace Gott.Endpoints; + +public static class BackupEndpoint +{ + public static void MapBackupEndpoint(this IEndpointRouteBuilder app) + { + app.MapPost("/api/backup", HandleBackup); + } + + private static async System.Threading.Tasks.Task HandleBackup() + { + var dataDir = Path.Combine(AppContext.BaseDirectory, "data"); + var dbPath = Path.Combine(dataDir, "gott.db"); + + if (!File.Exists(dbPath)) + return Results.NotFound(new { error = "Datenbank nicht gefunden" }); + + var backupDir = Path.Combine(dataDir, "backup"); + Directory.CreateDirectory(backupDir); + + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var archiveName = $"gott-{timestamp}.tar.gz"; + var archivePath = Path.Combine(backupDir, archiveName); + + var psi = new ProcessStartInfo + { + FileName = "tar", + ArgumentList = { "-czf", archivePath, "-C", dataDir, "gott.db" }, + RedirectStandardError = true, + UseShellExecute = false + }; + + using (var process = Process.Start(psi)) + { + if (process == null) + return Results.StatusCode(500); + + var stderr = await process.StandardError.ReadToEndAsync(); + await process.WaitForExitAsync(); + + if (process.ExitCode != 0) + return Results.Problem($"tar fehlgeschlagen: {stderr}"); + } + + var bytes = await File.ReadAllBytesAsync(archivePath); + return Results.File(bytes, "application/gzip", archiveName); + } +} diff --git a/Program.cs b/Program.cs index b36bb24..112b7d7 100644 --- a/Program.cs +++ b/Program.cs @@ -25,5 +25,6 @@ app.MapSessionEndpoints(); app.MapAgentEndpoints(); app.MapWorkspaceEndpoint(); app.MapInfoEndpoint(); +app.MapBackupEndpoint(); app.Run("http://0.0.0.0:19000"); diff --git a/wwwroot/index.html b/wwwroot/index.html index 5a889ae..d841784 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -147,6 +147,7 @@ diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js index d11b10a..6560c8e 100644 --- a/wwwroot/js/api.js +++ b/wwwroot/js/api.js @@ -128,3 +128,21 @@ async function apiGetChain(chainId) { if (!resp.ok) return null; return resp.json(); } + +async function apiBackupDb() { + const resp = await fetch('/api/backup', { method: 'POST' }); + if (!resp.ok) throw new Error('Backup fehlgeschlagen'); + const blob = await resp.blob(); + const cd = resp.headers.get('content-disposition') || ''; + const filename = cd.match(/filename\*=UTF-8''(.+?)(?:;|$)/i)?.[1] + || cd.match(/filename="?([^";]+)"?/)?.[1] + || 'gott-backup.tar.gz'; + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); +} diff --git a/wwwroot/js/pages/chat-page.js b/wwwroot/js/pages/chat-page.js index 6236640..b10f92d 100644 --- a/wwwroot/js/pages/chat-page.js +++ b/wwwroot/js/pages/chat-page.js @@ -190,6 +190,22 @@ registerPage('chat', { clearChat(); }); + // Datenbank sichern + const backupDbBtn = document.getElementById('backupDbBtn'); + if (backupDbBtn) backupDbBtn.addEventListener('click', async () => { + if (chatMenuDropdown) chatMenuDropdown.hidden = true; + backupDbBtn.disabled = true; + try { + await apiBackupDb(); + setMeta('✅ Backup erstellt und heruntergeladen'); + } catch (e) { + console.error('Backup fehlgeschlagen:', e); + setMeta('❌ Backup fehlgeschlagen'); + } finally { + backupDbBtn.disabled = false; + } + }); + // State zurücksetzen und History laden messages.length = 0; lastKnownMessageId = 0;