diff --git a/dev/manual_tests.md b/dev/manual_tests.md new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/data/backups/dev_sample_data_2021-Jan-12.zip b/mealie/data/backups/dev_sample_data_2021-Jan-12.zip new file mode 100644 index 000000000..ee5da95cc Binary files /dev/null and b/mealie/data/backups/dev_sample_data_2021-Jan-12.zip differ diff --git a/mealie/routes/backup_routes.py b/mealie/routes/backup_routes.py index 38e696334..bfa732e1f 100644 --- a/mealie/routes/backup_routes.py +++ b/mealie/routes/backup_routes.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, HTTPException from models.backup_models import BackupJob, Imports -from services.backups.export import backup_all +from services.backups.exports import backup_all +from services.backups.imports import ImportDatabase from settings import BACKUP_DIR, TEMPLATE_DIR from utils.snackbar import SnackResponse @@ -39,7 +40,14 @@ async def export_database(data: BackupJob): ) async def import_database(file_name: str): """ Import a database backup file generated from Mealie. """ - imported = import_from_archive(file_name) + import_db = ImportDatabase( + zip_archive=file_name, + import_recipes=True, + import_settings=False, + import_themes=False, + ) + + imported = import_db.run() return imported diff --git a/mealie/services/backups/export.py b/mealie/services/backups/exports.py similarity index 100% rename from mealie/services/backups/export.py rename to mealie/services/backups/exports.py diff --git a/mealie/services/backups/import.py b/mealie/services/backups/import.py deleted file mode 100644 index a614b6aa7..000000000 --- a/mealie/services/backups/import.py +++ /dev/null @@ -1,9 +0,0 @@ -import json -import shutil -import zipfile -from pathlib import Path - -from utils.logger import logger - -from settings import IMG_DIR, BACKUP_DIR, TEMPLATE_DIR, TEMP_DIR - diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py new file mode 100644 index 000000000..486a02aec --- /dev/null +++ b/mealie/services/backups/imports.py @@ -0,0 +1,103 @@ +import json +import shutil +import zipfile +from pathlib import Path +from typing import List + +from services.recipe_services import Recipe +from settings import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR +from utils.logger import logger + + +class ImportDatabase: + def __init__( + self, + zip_archive: str, + import_recipes: bool = True, + import_settings: bool = True, + import_themes: bool = True, + force_import: bool = False, + rebase: bool = False, + ) -> None: + + self.archive = BACKUP_DIR.joinpath(zip_archive) + self.imp_recipes = import_recipes + self.imp_settings = import_settings + self.imp_themes = import_themes + self.force_imports = force_import + self.force_rebase = rebase + + if self.archive.is_file(): + self.import_dir = TEMP_DIR.joinpath("active_import") + self.import_dir.mkdir(parents=True, exist_ok=True) + + with zipfile.ZipFile(self.archive, "r") as zip_ref: + zip_ref.extractall(self.import_dir) + pass + else: + raise Exception("Import file does not exist") + + def run(self): + if self.imp_recipes: + report = self.import_recipes() + if self.imp_settings: + self.import_settings() + if self.imp_themes: + self.import_themes() + + self.clean_up() + + return report if report else None + + def import_recipes(self): + recipe_dir: Path = self.import_dir.joinpath("recipes") + + successful_imports = [] + failed_imports = [] + + for recipe in recipe_dir.glob("*.json"): + with open(recipe, "r") as f: + recipe_dict = json.loads(f.read()) + recipe_dict = ImportDatabase._recipe_migration(recipe_dict) + + try: + recipe_obj = Recipe(**recipe_dict) + recipe_obj.save_to_db() + successful_imports.append(recipe.stem) + logger.info(f"Imported: {recipe.stem}") + except: + logger.info(f"Failed Import: {recipe.stem}") + failed_imports.append(recipe.stem) + + self._import_images(successful_imports) + + return {"successful": successful_imports, "failed": failed_imports} + + @staticmethod + def _recipe_migration(recipe_dict: dict) -> dict: + try: + del recipe_dict["_id"] + del recipe_dict["dateAdded"] + except: + logger.info("Detected new backup Schema, skipping migration...") + return recipe_dict + # Migration from list to Object Type Data + if type(recipe_dict["extras"]) == list: + recipe_dict["extras"] = {} + + return recipe_dict + + def _import_images(self, successful_imports: List[str]): + image_dir = self.import_dir.joinpath("images") + for image in image_dir.iterdir(): + if image.stem in successful_imports: + shutil.copy(image, IMG_DIR) + + def import_themes(self): + pass + + def import_settings(self): + pass + + def clean_up(self): + shutil.rmtree(TEMP_DIR) diff --git a/mealie/services/migrations/nextcloud.py b/mealie/services/migrations/nextcloud.py index 11a0af157..a8698cf06 100644 --- a/mealie/services/migrations/nextcloud.py +++ b/mealie/services/migrations/nextcloud.py @@ -6,9 +6,8 @@ from pathlib import Path from services.recipe_services import IMG_DIR, Recipe from services.scrape_services import normalize_data, process_recipe_data - +from settings import TEMP_DIR CWD = Path(__file__).parent -TEMP_DIR = CWD.parent.parent.joinpath("data", "temp") MIGRTAION_DIR = CWD.parent.parent.joinpath("data", "migration") diff --git a/mealie/services/scheduler_services.py b/mealie/services/scheduler_services.py index e464c1d73..be3fe04e0 100644 --- a/mealie/services/scheduler_services.py +++ b/mealie/services/scheduler_services.py @@ -5,7 +5,7 @@ import requests from apscheduler.schedulers.background import BackgroundScheduler from utils.logger import logger -from services.backups.export import auto_backup_job +from services.backups.exports import auto_backup_job from services.meal_services import MealPlan from services.recipe_services import Recipe from services.settings_services import SiteSettings