mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-04 21:15:22 +02:00
fix: Handle Data With Invalid User (#4325)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
parent
02791e294d
commit
cba381cb67
2 changed files with 66 additions and 0 deletions
|
@ -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
|
||||
|
@ -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)
|
||||
|
|
|
@ -14,6 +14,7 @@ from sqlalchemy.orm import sessionmaker
|
|||
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
|
||||
|
||||
|
@ -137,6 +138,14 @@ class AlchemyExporter(BaseService):
|
|||
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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue