feat(export): Projekte exportieren und importieren
Bislang lagen alle Daten ausschliesslich in der Datenbank - ein "docker compose down -v" haette alles geloescht, ohne Rueckweg. Export: - alle eigenen Projekte oder ein einzelnes als JSON - ein Projekt zusaetzlich als lesbares Markdown, inklusive letztem bekannten Git-Stand Was bewusst nicht in der Datei steht: - Access Tokens. Die Verbindung wird nur beschrieben (Name, System, Basis-URL), das Token muss beim Import neu hinterlegt werden. Eine Exportdatei landet sonst schnell in einem Download-Ordner oder einem Cloud-Abgleich. - Repo-Snapshots. Reproduzierbar und gross; ein Abgleich holt sie zurueck. - Datenbank-IDs und der Besitzer. Beides gilt nur in der Quellinstanz. Import: - die hochgeladene Datei ist Fremdinhalt und wird vor jeder Verwendung streng gegen ein Zod-Schema geprueft - Abgleich ueber den Slug, drei Strategien: ueberspringen, zusaetzlich anlegen, ersetzen - dryRun liefert dieselbe Auswertung, ohne zu schreiben - Grundlage der Vorschau in der Oberflaeche - Verbindungen werden ueber Name oder Provider samt Basis-URL zugeordnet; fehlt eine, wird das Projekt ohne Verknuepfung angelegt und das gemeldet - beim Ersetzen werden Notizen ausgetauscht statt zusammengefuehrt: das ist vorhersagbar, und ein erneuter Import erzeugt keine Dubletten Die Instanz-Sicherung fuer Administratoren enthaelt dagegen alles, was zum Wiederherstellen noetig ist - Passwort-Hashes und Token-Chiffrate eingeschlossen. Ohne dasselbe APP_SECRET sind die Tokens wertlos. Sitzungen und Passkeys bleiben draussen: erstere sind kurzlebig, letztere haengen an Geraet und rpID. readJsonBody bekommt eine einstellbare Obergrenze - 256 KB reichen fuer eine Exportdatei mit vielen Notizen nicht. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f55e5ff2fe
commit
869bb61ef4
@@ -0,0 +1,29 @@
|
||||
import { requireUserApi } from "@/lib/auth/guard";
|
||||
import { APP_NAME } from "@/lib/config";
|
||||
import { exportFileName } from "@/lib/export/format";
|
||||
import { buildExportFile, loadProjectsForExport } from "@/lib/export/project";
|
||||
import { jsonError } from "@/lib/security/request";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/** Alle eigenen Projekte als JSON zum Herunterladen. */
|
||||
export async function GET() {
|
||||
const auth = await requireUserApi();
|
||||
if (!auth.ok) return auth.response;
|
||||
|
||||
const projects = await loadProjectsForExport(auth.user.id);
|
||||
if (projects.length === 0) {
|
||||
return jsonError("Es gibt noch keine Projekte zum Exportieren.", 404);
|
||||
}
|
||||
|
||||
const payload = buildExportFile(projects);
|
||||
const filename = exportFileName(`${APP_NAME}-projekte`, new Date(), "json");
|
||||
|
||||
return new Response(JSON.stringify(payload, null, 2), {
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"Content-Disposition": `attachment; filename="${filename}"`,
|
||||
"Cache-Control": "no-store",
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user