Skip to content

Commit

Permalink
fix: Handle Data With Invalid User (mealie-recipes#4325)
Browse files Browse the repository at this point in the history
Co-authored-by: Kuchenpirat <[email protected]>
  • Loading branch information
michael-genson and Kuchenpirat authored Oct 14, 2024
1 parent 02791e2 commit cba381c
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 0 deletions.
57 changes: 57 additions & 0 deletions mealie/db/fixes/fix_migration_data.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,73 @@
from uuid import uuid4

from slugify import slugify
from sqlalchemy import and_, update
from sqlalchemy.orm import Session

from mealie.core import root_logger
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.db.models.group.group import Group
from mealie.db.models.household.shopping_list import ShoppingList, ShoppingListMultiPurposeLabel
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
from mealie.db.models.recipe.recipe import RecipeModel
from mealie.db.models.users.users import User

logger = root_logger.get_logger("init_db")


def fix_dangling_refs(session: Session):
REASSIGN_REF_TABLES = ["group_meal_plans", "recipes", "shopping_lists"]
DELETE_REF_TABLES = ["long_live_tokens", "password_reset_tokens", "recipe_comments", "recipe_timeline_events"]

groups = session.query(Group).all()
for group in groups:
# Find an arbitrary admin user in the group
default_user = session.query(User).filter(User.group_id == group.id, User.admin == True).first() # noqa: E712 - required for SQLAlchemy comparison
if not default_user:
# If there is no admin user, just pick the first user
default_user = session.query(User).filter(User.group_id == group.id).first()

# If there are no users in the group, we can't do anything
if not default_user:
continue

valid_user_ids = {user.id for user in group.users}

for table_name in REASSIGN_REF_TABLES:
table = SqlAlchemyBase.metadata.tables[table_name]
update_stmt = (
update(table)
.where(
and_(
table.c.user_id.notin_(valid_user_ids),
table.c.group_id == group.id,
)
)
.values(user_id=default_user.id)
)
result = session.execute(update_stmt)

if result.rowcount:
logger.info(
f'Reassigned {result.rowcount} {"row" if result.rowcount == 1 else "rows"} '
f'in "{table_name}" table to default user ({default_user.id})'
)

for table_name in DELETE_REF_TABLES:
table = SqlAlchemyBase.metadata.tables[table_name]
delete_stmt = table.delete().where(table.c.user_id.notin_(valid_user_ids))
result = session.execute(delete_stmt)

if result.rowcount:
logger.info(
f'Deleted {result.rowcount} {"row" if result.rowcount == 1 else "rows"} '
f'in "{table_name}" table with invalid user ids'
)

session.commit()


def fix_recipe_normalized_search_properties(session: Session):
recipes = session.query(RecipeModel).all()
recipes_fixed = False
Expand Down Expand Up @@ -144,6 +199,8 @@ def fix_normalized_unit_and_food_names(session: Session):

def fix_migration_data(session: Session):
logger.info("Checking for migration data fixes")

fix_dangling_refs(session)
fix_recipe_normalized_search_properties(session)
fix_shopping_list_label_settings(session)
fix_group_slugs(session)
Expand Down
9 changes: 9 additions & 0 deletions mealie/services/backups_v2/alchemy_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from alembic import command
from alembic.config import Config
from mealie.db import init_db
from mealie.db.fixes.fix_migration_data import fix_migration_data
from mealie.db.models._model_utils.guid import GUID
from mealie.services._base_service import BaseService

Expand Down Expand Up @@ -137,6 +138,14 @@ def dump(self) -> dict[str, list[dict]]:
Returns the entire SQLAlchemy database as a python dictionary. This dictionary is wrapped by
jsonable_encoder to ensure that the object can be converted to a json string.
"""

# run database fixes first so we aren't backing up bad data
with self.session_maker() as session:
try:
fix_migration_data(session)
except Exception:
self.logger.error("Error fixing migration data during export; continuing anyway")

with self.engine.connect() as connection:
self.meta.reflect(bind=self.engine) # http://docs.sqlalchemy.org/en/rel_0_9/core/reflection.html

Expand Down

0 comments on commit cba381c

Please sign in to comment.