Explorar el Código

Added embedded external links to backup/restore module

maziggy hace 5 meses
padre
commit
b4007c9594
Se han modificado 1 ficheros con 83 adiciones y 10 borrados
  1. 83 10
      backend/app/api/routes/settings.py

+ 83 - 10
backend/app/api/routes/settings.py

@@ -20,6 +20,7 @@ from backend.app.models.printer import Printer
 from backend.app.models.filament import Filament
 from backend.app.models.maintenance import MaintenanceType, PrinterMaintenance, MaintenanceHistory
 from backend.app.models.archive import PrintArchive
+from backend.app.models.external_link import ExternalLink
 from backend.app.schemas.settings import AppSettings, AppSettingsUpdate
 from backend.app.services.printer_manager import printer_manager
 from backend.app.services.spoolman import init_spoolman_client, get_spoolman_client
@@ -167,6 +168,7 @@ async def export_backup(
     include_notifications: bool = Query(True, description="Include notification providers"),
     include_templates: bool = Query(True, description="Include notification templates"),
     include_smart_plugs: bool = Query(True, description="Include smart plugs"),
+    include_external_links: bool = Query(True, description="Include external sidebar links"),
     include_printers: bool = Query(False, description="Include printers (without access codes)"),
     include_filaments: bool = Query(False, description="Include filament inventory"),
     include_maintenance: bool = Query(False, description="Include maintenance types and records"),
@@ -258,6 +260,28 @@ async def export_backup(
             })
         backup["included"].append("smart_plugs")
 
+    # External links
+    if include_external_links:
+        result = await db.execute(select(ExternalLink).order_by(ExternalLink.sort_order))
+        links = result.scalars().all()
+        backup["external_links"] = []
+        icons_dir = app_settings.base_dir / "icons"
+        for link in links:
+            link_data = {
+                "name": link.name,
+                "url": link.url,
+                "icon": link.icon,
+                "sort_order": link.sort_order,
+            }
+            # Include custom icon file path if exists
+            if link.custom_icon:
+                link_data["custom_icon"] = link.custom_icon
+                icon_path = icons_dir / link.custom_icon
+                if icon_path.exists():
+                    link_data["custom_icon_path"] = f"icons/{link.custom_icon}"
+            backup["external_links"].append(link_data)
+        backup["included"].append("external_links")
+
     # Printers (access codes only included if explicitly requested)
     if include_printers:
         result = await db.execute(select(Printer))
@@ -322,8 +346,19 @@ async def export_backup(
             })
         backup["included"].append("maintenance_types")
 
+    # Collect files for ZIP (icons + archives)
+    backup_files: list[tuple[str, Path]] = []  # (zip_path, local_path)
+
+    # Add external link icon files
+    if include_external_links and "external_links" in backup:
+        icons_dir = app_settings.base_dir / "icons"
+        for link_data in backup["external_links"]:
+            if "custom_icon_path" in link_data:
+                icon_path = icons_dir / link_data["custom_icon"]
+                if icon_path.exists():
+                    backup_files.append((link_data["custom_icon_path"], icon_path))
+
     # Print archives with file paths for ZIP
-    archive_files: list[tuple[str, Path]] = []  # (zip_path, local_path)
     if include_archives:
         result = await db.execute(select(PrintArchive))
         archives = result.scalars().all()
@@ -366,25 +401,25 @@ async def export_backup(
                 file_path = base_dir / a.file_path
                 if file_path.exists():
                     archive_data["file_path"] = a.file_path
-                    archive_files.append((a.file_path, file_path))
+                    backup_files.append((a.file_path, file_path))
 
             if a.thumbnail_path:
                 thumb_path = base_dir / a.thumbnail_path
                 if thumb_path.exists():
                     archive_data["thumbnail_path"] = a.thumbnail_path
-                    archive_files.append((a.thumbnail_path, thumb_path))
+                    backup_files.append((a.thumbnail_path, thumb_path))
 
             if a.timelapse_path:
                 timelapse_path = base_dir / a.timelapse_path
                 if timelapse_path.exists():
                     archive_data["timelapse_path"] = a.timelapse_path
-                    archive_files.append((a.timelapse_path, timelapse_path))
+                    backup_files.append((a.timelapse_path, timelapse_path))
 
             if a.source_3mf_path:
                 source_path = base_dir / a.source_3mf_path
                 if source_path.exists():
                     archive_data["source_3mf_path"] = a.source_3mf_path
-                    archive_files.append((a.source_3mf_path, source_path))
+                    backup_files.append((a.source_3mf_path, source_path))
 
             # Include photos
             if a.photos:
@@ -392,21 +427,21 @@ async def export_backup(
                     photo_path = base_dir / "archive" / "photos" / photo
                     if photo_path.exists():
                         zip_photo_path = f"archive/photos/{photo}"
-                        archive_files.append((zip_photo_path, photo_path))
+                        backup_files.append((zip_photo_path, photo_path))
 
             backup["archives"].append(archive_data)
         backup["included"].append("archives")
 
-    # If archives included, create ZIP file with all files
-    if include_archives and archive_files:
+    # If there are files to include (icons or archives), create ZIP file
+    if backup_files:
         zip_buffer = io.BytesIO()
         with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
             # Add backup.json
             zf.writestr("backup.json", json.dumps(backup, indent=2))
 
-            # Add all archive files
+            # Add all backup files (icons, archives, etc.)
             added_files = set()
-            for zip_path, local_path in archive_files:
+            for zip_path, local_path in backup_files:
                 if zip_path not in added_files and local_path.exists():
                     try:
                         zf.write(local_path, zip_path)
@@ -481,6 +516,7 @@ async def import_backup(
         "notification_providers": 0,
         "notification_templates": 0,
         "smart_plugs": 0,
+        "external_links": 0,
         "printers": 0,
         "filaments": 0,
         "maintenance_types": 0,
@@ -490,6 +526,7 @@ async def import_backup(
         "notification_providers": 0,
         "notification_templates": 0,
         "smart_plugs": 0,
+        "external_links": 0,
         "printers": 0,
         "filaments": 0,
         "maintenance_types": 0,
@@ -498,6 +535,7 @@ async def import_backup(
     skipped_details = {
         "notification_providers": [],
         "smart_plugs": [],
+        "external_links": [],
         "printers": [],
         "filaments": [],
         "maintenance_types": [],
@@ -652,6 +690,41 @@ async def import_backup(
                 db.add(plug)
                 restored["smart_plugs"] += 1
 
+    # Restore external links (skip or overwrite duplicates by name+url)
+    if "external_links" in backup:
+        icons_dir = base_dir / "icons"
+        icons_dir.mkdir(parents=True, exist_ok=True)
+
+        for link_data in backup["external_links"]:
+            result = await db.execute(
+                select(ExternalLink).where(
+                    ExternalLink.name == link_data["name"],
+                    ExternalLink.url == link_data["url"]
+                )
+            )
+            existing = result.scalar_one_or_none()
+            if existing:
+                if overwrite:
+                    existing.icon = link_data.get("icon", "link")
+                    existing.sort_order = link_data.get("sort_order", 0)
+                    # Handle custom icon
+                    if link_data.get("custom_icon"):
+                        existing.custom_icon = link_data["custom_icon"]
+                    restored["external_links"] += 1
+                else:
+                    skipped["external_links"] += 1
+                    skipped_details["external_links"].append(link_data["name"])
+            else:
+                link = ExternalLink(
+                    name=link_data["name"],
+                    url=link_data["url"],
+                    icon=link_data.get("icon", "link"),
+                    custom_icon=link_data.get("custom_icon"),
+                    sort_order=link_data.get("sort_order", 0),
+                )
+                db.add(link)
+                restored["external_links"] += 1
+
     # Restore printers (skip or overwrite duplicates by serial_number)
     if "printers" in backup:
         for printer_data in backup["printers"]: