mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-08 00:43:12 -05:00
fix: user-feedback-on-schema-mismatch (#1558)
* validate schema version on restore * show user error on backup failure
This commit is contained in:
@@ -9,8 +9,8 @@ from mealie.core.security import create_file_token
|
||||
from mealie.pkgs.stats.fs_stats import pretty_size
|
||||
from mealie.routes._base import BaseAdminController, controller
|
||||
from mealie.schema.admin.backup import AllBackups, BackupFile
|
||||
from mealie.schema.response.responses import FileTokenResponse, SuccessResponse
|
||||
from mealie.services.backups_v2.backup_v2 import BackupV2
|
||||
from mealie.schema.response.responses import ErrorResponse, FileTokenResponse, SuccessResponse
|
||||
from mealie.services.backups_v2.backup_v2 import BackupSchemaMismatch, BackupV2
|
||||
|
||||
router = APIRouter(prefix="/backups")
|
||||
|
||||
@@ -100,6 +100,11 @@ class AdminBackupController(BaseAdminController):
|
||||
|
||||
try:
|
||||
backup.restore(file)
|
||||
except BackupSchemaMismatch as e:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
ErrorResponse.respond("database backup schema version does not match current database"),
|
||||
) from e
|
||||
except Exception as e:
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) from e
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ from pathlib import Path
|
||||
|
||||
|
||||
class BackupContents:
|
||||
_tables: dict = None
|
||||
|
||||
def __init__(self, file: Path) -> None:
|
||||
self.base = file
|
||||
self.data_directory = self.base / "data"
|
||||
@@ -22,9 +24,22 @@ class BackupContents:
|
||||
|
||||
return True
|
||||
|
||||
def schema_version(self) -> str:
|
||||
tables = self.read_tables()
|
||||
|
||||
alembic_version = tables.get("alembic_version", [])
|
||||
|
||||
if not alembic_version:
|
||||
return ""
|
||||
|
||||
return alembic_version[0].get("version_num", "")
|
||||
|
||||
def read_tables(self) -> dict:
|
||||
with open(self.tables) as f:
|
||||
return json.loads(f.read())
|
||||
if self._tables is None:
|
||||
with open(self.tables, "r") as f:
|
||||
self._tables = json.load(f)
|
||||
|
||||
return self._tables
|
||||
|
||||
|
||||
class BackupFile:
|
||||
|
||||
@@ -9,6 +9,10 @@ from mealie.services.backups_v2.alchemy_exporter import AlchemyExporter
|
||||
from mealie.services.backups_v2.backup_file import BackupFile
|
||||
|
||||
|
||||
class BackupSchemaMismatch(Exception):
|
||||
...
|
||||
|
||||
|
||||
class BackupV2(BaseService):
|
||||
def __init__(self, db_url: str = None) -> None:
|
||||
super().__init__()
|
||||
@@ -73,18 +77,28 @@ class BackupV2(BaseService):
|
||||
self._postgres()
|
||||
|
||||
with backup as contents:
|
||||
# ================================
|
||||
# Validation
|
||||
if not contents.validate():
|
||||
self.logger.error(
|
||||
"Invalid backup file. file does not contain required elements (data directory and database.json"
|
||||
)
|
||||
raise ValueError("Invalid backup file")
|
||||
|
||||
# Purge the Database
|
||||
database_json = contents.read_tables()
|
||||
|
||||
if not AlchemyExporter.validate_schemas(database_json, self.db_exporter.dump()):
|
||||
self.logger.error("Invalid backup file. Database schemas do not match")
|
||||
raise BackupSchemaMismatch("Invalid backup file. Database schemas do not match")
|
||||
|
||||
# ================================
|
||||
# Purge Database
|
||||
|
||||
self.logger.info("dropping all database tables")
|
||||
self.db_exporter.drop_all()
|
||||
|
||||
database_json = contents.read_tables()
|
||||
# ================================
|
||||
# Restore Database
|
||||
|
||||
self.logger.info("importing database tables")
|
||||
self.db_exporter.restore(database_json)
|
||||
|
||||
Reference in New Issue
Block a user