From 59ad6f38498d07b2f88e4dff97dbbcf8a72c659d Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Sun, 24 May 2026 09:00:32 +0200
Subject: [PATCH] fixed db download
---
modules/routes/routeAdminDatabaseHealth.py | 76 +++++++++++++++++++++-
1 file changed, 74 insertions(+), 2 deletions(-)
diff --git a/modules/routes/routeAdminDatabaseHealth.py b/modules/routes/routeAdminDatabaseHealth.py
index 84e3443f..560c0fbd 100644
--- a/modules/routes/routeAdminDatabaseHealth.py
+++ b/modules/routes/routeAdminDatabaseHealth.py
@@ -377,16 +377,38 @@ async def postMigrationImport(
# Per-DB endpoints (progress-friendly)
# ---------------------------------------------------------------------------
+_pendingExports: Dict[str, dict] = {}
+
+
+@router.post("/migration/export-start")
+@limiter.limit("10/minute")
+def postMigrationExportStart(
+ request: Request,
+ currentUser: User = Depends(requireSysAdmin),
+) -> Dict[str, Any]:
+ """Start an export session. Returns a token for subsequent per-DB calls."""
+ import uuid
+ token = str(uuid.uuid4())
+ _pendingExports[token] = {"databases": {}}
+ logger.info("SysAdmin migration export-start: user=%s token=%s", currentUser.username, token)
+ return {"token": token}
+
+
@router.get("/migration/export-single")
@limiter.limit("60/minute")
def getMigrationExportSingle(
request: Request,
+ token: str,
database: str,
currentUser: User = Depends(requireSysAdmin),
) -> Dict[str, Any]:
- """Export a single database as JSON (used by the frontend for per-DB progress)."""
+ """Export a single database and store it server-side. Returns only metadata."""
from modules.shared.dbRegistry import getRegisteredDatabases
+ pending = _pendingExports.get(token)
+ if not pending:
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid export token.")
+
if database not in getRegisteredDatabases():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -404,7 +426,57 @@ def getMigrationExportSingle(
detail=f"Export failed for '{database}': {e}",
) from e
- return {"database": database, "data": dbPayload}
+ pending["databases"][database] = dbPayload
+ logger.info("SysAdmin migration export-single done: user=%s db=%s tables=%s records=%s",
+ currentUser.username, database, dbPayload.get("tableCount", 0), dbPayload.get("totalRecords", 0))
+
+ return {
+ "database": database,
+ "tableCount": dbPayload.get("tableCount", 0),
+ "totalRecords": dbPayload.get("totalRecords", 0),
+ }
+
+
+@router.get("/migration/export-download")
+@limiter.limit("5/minute")
+def getMigrationExportDownload(
+ request: Request,
+ token: str,
+ filename: str = "backup.json",
+ currentUser: User = Depends(requireSysAdmin),
+) -> StreamingResponse:
+ """Assemble and stream the final export file from server-side data."""
+ from datetime import datetime, timezone
+
+ pending = _pendingExports.pop(token, None)
+ if not pending or not pending.get("databases"):
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid or expired export token.")
+
+ databases = pending["databases"]
+ totalTables = sum(d.get("tableCount", 0) for d in databases.values())
+ totalRecords = sum(d.get("totalRecords", 0) for d in databases.values())
+
+ exportData = {
+ "meta": {
+ "exportedAt": datetime.now(timezone.utc).isoformat(),
+ "version": "1.0",
+ "databaseCount": len(databases),
+ "totalTables": totalTables,
+ "totalRecords": totalRecords,
+ },
+ "databases": databases,
+ }
+
+ logger.info("SysAdmin migration export-download: user=%s dbs=%s tables=%s records=%s",
+ currentUser.username, len(databases), totalTables, totalRecords)
+
+ content = json.dumps(exportData, ensure_ascii=False, default=str)
+
+ return StreamingResponse(
+ iter([content]),
+ media_type="application/json",
+ headers={"Content-Disposition": f'attachment; filename="{filename}"'},
+ )
@router.post("/migration/prepare-import")