feat: Move alembic config into mealie package for easier distribution (#4329)

This commit is contained in:
Michael Chisholm
2024-12-05 02:57:57 +10:00
committed by GitHub
parent f1e9615efd
commit a6cbf1308e
48 changed files with 24 additions and 37 deletions

1
mealie/alembic/README Normal file
View File

@@ -0,0 +1 @@
Generic single-database configuration.

View File

@@ -0,0 +1,60 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = %(here)s
# template used to generate migration files
file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d.%%(minute).2d.%%(second).2d_%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python-dateutil library that can be
# installed by adding `alembic[tz]` to the pip requirements
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator"
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
# version path separator; As mentioned above, this is the character used to split
# version_locations. Valid values are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os # default: use os.pathsep
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
# Will be replaced in alembic/env.py with the actual url
sqlalchemy.url =
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples

109
mealie/alembic/env.py Normal file
View File

@@ -0,0 +1,109 @@
from typing import Any
import sqlalchemy as sa
from alembic import context
import mealie.db.models._all_models # noqa: F401
from mealie.core.config import get_app_settings
from mealie.db.models._model_base import SqlAlchemyBase
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = SqlAlchemyBase.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
# Set DB url from config
settings = get_app_settings()
if not settings.DB_URL:
raise Exception("DB URL not set in config")
config.set_main_option("sqlalchemy.url", settings.DB_URL.replace("%", "%%"))
def include_object(object: Any, name: str, type_: str, reflected: bool, compare_to: Any):
# skip dropping food/unit unique constraints; they are defined manually so alembic doesn't see them
# see: revision dded3119c1fe
if type_ == "unique_constraint" and name == "ingredient_foods_name_group_id_key" and compare_to is None:
return False
if type_ == "unique_constraint" and name == "ingredient_units_name_group_id_key" and compare_to is None:
return False
# skip changing the quantity column in recipes_ingredients; it's a float on postgres, but an integer on sqlite
# see: revision 263dd6707191
if (
type_ == "column"
and name == "quantity"
and object.table.name == "recipes_ingredients"
and hasattr(compare_to, "type")
and isinstance(compare_to.type, sa.Integer)
):
return False
return True
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = sa.engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=sa.pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
user_module_prefix="mealie.db.migration_types.",
render_as_batch=True,
include_object=include_object,
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -0,0 +1,28 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
import sqlalchemy as sa
from alembic import op
import mealie.db.migration_types
% if imports:
${imports}
% endif
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision: str | None = ${repr(down_revision)}
branch_labels: str | tuple[str, ...] | None = ${repr(branch_labels)}
depends_on: str | tuple[str, ...] | None = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,921 @@
"""Initial tables
Revision ID: 6b0f5f32d602
Revises:
Create Date: 2022-02-21 19:56:24.351115
"""
import sqlalchemy as sa
from sqlalchemy import engine_from_config
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "6b0f5f32d602"
down_revision: str | None = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Adapted from https://improveandrepeat.com/2021/09/python-friday-87-handling-pre-existing-tables-with-alembic-and-sqlalchemy/
def table_exists(table, schema=None):
config = op.get_context().config
engine = engine_from_config(config.get_section(config.config_ini_section), prefix="sqlalchemy.")
insp = sa.inspect(engine)
return insp.has_table(table, schema)
def upgrade():
# Only create initial tables if they don't exist yet, to ease transition from pre-alembic state
if table_exists("users"):
return
op.create_table(
"groups",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_groups_name"), "groups", ["name"], unique=True)
op.create_table(
"categories",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("slug", "group_id", name="category_slug_group_id_key"),
)
op.create_index(op.f("ix_categories_group_id"), "categories", ["group_id"], unique=False)
op.create_index(op.f("ix_categories_name"), "categories", ["name"], unique=False)
op.create_index(op.f("ix_categories_slug"), "categories", ["slug"], unique=False)
op.create_table(
"cookbooks",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("position", sa.Integer(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"group_data_exports",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("filename", sa.String(), nullable=False),
sa.Column("path", sa.String(), nullable=False),
sa.Column("size", sa.String(), nullable=False),
sa.Column("expires", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_data_exports_group_id"), "group_data_exports", ["group_id"], unique=False)
op.create_table(
"group_events_notifiers",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("enabled", sa.Boolean(), nullable=False),
sa.Column("apprise_url", sa.String(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_events_notifiers_group_id"), "group_events_notifiers", ["group_id"], unique=False)
op.create_table(
"group_meal_plan_rules",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("day", sa.String(), nullable=False),
sa.Column("entry_type", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"group_preferences",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("private_group", sa.Boolean(), nullable=True),
sa.Column("first_day_of_week", sa.Integer(), nullable=True),
sa.Column("recipe_public", sa.Boolean(), nullable=True),
sa.Column("recipe_show_nutrition", sa.Boolean(), nullable=True),
sa.Column("recipe_show_assets", sa.Boolean(), nullable=True),
sa.Column("recipe_landscape_view", sa.Boolean(), nullable=True),
sa.Column("recipe_disable_comments", sa.Boolean(), nullable=True),
sa.Column("recipe_disable_amount", sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_preferences_group_id"), "group_preferences", ["group_id"], unique=False)
op.create_table(
"group_reports",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("status", sa.String(), nullable=False),
sa.Column("category", sa.String(), nullable=False),
sa.Column("timestamp", sa.DateTime(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_reports_category"), "group_reports", ["category"], unique=False)
op.create_index(op.f("ix_group_reports_group_id"), "group_reports", ["group_id"], unique=False)
op.create_table(
"ingredient_units",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.Column("abbreviation", sa.String(), nullable=True),
sa.Column("fraction", sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"invite_tokens",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("token", sa.String(), nullable=False),
sa.Column("uses_left", sa.Integer(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_invite_tokens_token"), "invite_tokens", ["token"], unique=True)
op.create_table(
"multi_purpose_labels",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(length=255), nullable=False),
sa.Column("color", sa.String(length=10), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_multi_purpose_labels_group_id"), "multi_purpose_labels", ["group_id"], unique=False)
op.create_table(
"recipes",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("slug", sa.String(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=True),
sa.Column("image", sa.String(), nullable=True),
sa.Column("total_time", sa.String(), nullable=True),
sa.Column("prep_time", sa.String(), nullable=True),
sa.Column("perform_time", sa.String(), nullable=True),
sa.Column("cook_time", sa.String(), nullable=True),
sa.Column("recipe_yield", sa.String(), nullable=True),
sa.Column("recipeCuisine", sa.String(), nullable=True),
sa.Column("rating", sa.Integer(), nullable=True),
sa.Column("org_url", sa.String(), nullable=True),
sa.Column("date_added", sa.Date(), nullable=True),
sa.Column("date_updated", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], use_alter=True),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("slug", "group_id", name="recipe_slug_group_id_key"),
)
op.create_index(op.f("ix_recipes_group_id"), "recipes", ["group_id"], unique=False)
op.create_index(op.f("ix_recipes_slug"), "recipes", ["slug"], unique=False)
op.create_index(op.f("ix_recipes_user_id"), "recipes", ["user_id"], unique=False)
op.create_table(
"server_tasks",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("completed_date", sa.DateTime(), nullable=True),
sa.Column("status", sa.String(), nullable=False),
sa.Column("log", sa.String(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_server_tasks_group_id"), "server_tasks", ["group_id"], unique=False)
op.create_table(
"shopping_lists",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_shopping_lists_group_id"), "shopping_lists", ["group_id"], unique=False)
op.create_table(
"tags",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("slug", "group_id", name="tags_slug_group_id_key"),
)
op.create_index(op.f("ix_tags_group_id"), "tags", ["group_id"], unique=False)
op.create_index(op.f("ix_tags_name"), "tags", ["name"], unique=False)
op.create_index(op.f("ix_tags_slug"), "tags", ["slug"], unique=False)
op.create_table(
"tools",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=False),
sa.Column("on_hand", sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("slug", "group_id", name="tools_slug_group_id_key"),
)
op.create_index(op.f("ix_tools_name"), "tools", ["name"], unique=True)
op.create_index(op.f("ix_tools_slug"), "tools", ["slug"], unique=True)
op.create_table(
"webhook_urls",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("enabled", sa.Boolean(), nullable=True),
sa.Column("name", sa.String(), nullable=True),
sa.Column("url", sa.String(), nullable=True),
sa.Column("time", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_webhook_urls_group_id"), "webhook_urls", ["group_id"], unique=False)
op.create_table(
"api_extras",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipee_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("key_name", sa.String(), nullable=True),
sa.Column("value", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipee_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"cookbooks_to_categories",
sa.Column("cookbook_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("category_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["category_id"],
["categories.id"],
),
sa.ForeignKeyConstraint(
["cookbook_id"],
["cookbooks.id"],
),
)
op.create_table(
"group_events_notifier_options",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("event_notifier_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_created", sa.Boolean(), nullable=False),
sa.Column("recipe_updated", sa.Boolean(), nullable=False),
sa.Column("recipe_deleted", sa.Boolean(), nullable=False),
sa.Column("user_signup", sa.Boolean(), nullable=False),
sa.Column("data_migrations", sa.Boolean(), nullable=False),
sa.Column("data_export", sa.Boolean(), nullable=False),
sa.Column("data_import", sa.Boolean(), nullable=False),
sa.Column("mealplan_entry_created", sa.Boolean(), nullable=False),
sa.Column("shopping_list_created", sa.Boolean(), nullable=False),
sa.Column("shopping_list_updated", sa.Boolean(), nullable=False),
sa.Column("shopping_list_deleted", sa.Boolean(), nullable=False),
sa.Column("cookbook_created", sa.Boolean(), nullable=False),
sa.Column("cookbook_updated", sa.Boolean(), nullable=False),
sa.Column("cookbook_deleted", sa.Boolean(), nullable=False),
sa.Column("tag_created", sa.Boolean(), nullable=False),
sa.Column("tag_updated", sa.Boolean(), nullable=False),
sa.Column("tag_deleted", sa.Boolean(), nullable=False),
sa.Column("category_created", sa.Boolean(), nullable=False),
sa.Column("category_updated", sa.Boolean(), nullable=False),
sa.Column("category_deleted", sa.Boolean(), nullable=False),
sa.ForeignKeyConstraint(
["event_notifier_id"],
["group_events_notifiers.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"group_meal_plans",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("date", sa.Date(), nullable=False),
sa.Column("entry_type", sa.String(), nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("text", sa.String(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_group_meal_plans_date"), "group_meal_plans", ["date"], unique=False)
op.create_index(op.f("ix_group_meal_plans_entry_type"), "group_meal_plans", ["entry_type"], unique=False)
op.create_index(op.f("ix_group_meal_plans_group_id"), "group_meal_plans", ["group_id"], unique=False)
op.create_index(op.f("ix_group_meal_plans_recipe_id"), "group_meal_plans", ["recipe_id"], unique=False)
op.create_index(op.f("ix_group_meal_plans_title"), "group_meal_plans", ["title"], unique=False)
op.create_table(
"group_to_categories",
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("category_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["category_id"],
["categories.id"],
),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
)
op.create_table(
"ingredient_foods",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.Column("label_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(
["label_id"],
["multi_purpose_labels.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"notes",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("title", sa.String(), nullable=True),
sa.Column("text", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"plan_rules_to_categories",
sa.Column("group_plan_rule_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("category_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["category_id"],
["categories.id"],
),
sa.ForeignKeyConstraint(
["group_plan_rule_id"],
["group_meal_plan_rules.id"],
),
)
op.create_table(
"plan_rules_to_tags",
sa.Column("plan_rule_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("tag_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["plan_rule_id"],
["group_meal_plan_rules.id"],
),
sa.ForeignKeyConstraint(
["tag_id"],
["tags.id"],
),
)
op.create_table(
"recipe_assets",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("name", sa.String(), nullable=True),
sa.Column("icon", sa.String(), nullable=True),
sa.Column("file_name", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_instructions",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("position", sa.Integer(), nullable=True),
sa.Column("type", sa.String(), nullable=True),
sa.Column("title", sa.String(), nullable=True),
sa.Column("text", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_nutrition",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("calories", sa.String(), nullable=True),
sa.Column("fat_content", sa.String(), nullable=True),
sa.Column("fiber_content", sa.String(), nullable=True),
sa.Column("protein_content", sa.String(), nullable=True),
sa.Column("carbohydrate_content", sa.String(), nullable=True),
sa.Column("sodium_content", sa.String(), nullable=True),
sa.Column("sugar_content", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_settings",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("public", sa.Boolean(), nullable=True),
sa.Column("show_nutrition", sa.Boolean(), nullable=True),
sa.Column("show_assets", sa.Boolean(), nullable=True),
sa.Column("landscape_view", sa.Boolean(), nullable=True),
sa.Column("disable_amount", sa.Boolean(), nullable=True),
sa.Column("disable_comments", sa.Boolean(), nullable=True),
sa.Column("locked", sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_share_tokens",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("expires_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_recipe_share_tokens_group_id"), "recipe_share_tokens", ["group_id"], unique=False)
op.create_table(
"recipes_to_categories",
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("category_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["category_id"],
["categories.id"],
),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
)
op.create_table(
"recipes_to_tags",
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("tag_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["tag_id"],
["tags.id"],
),
)
op.create_table(
"recipes_to_tools",
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("tool_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["tool_id"],
["tools.id"],
),
)
op.create_table(
"report_entries",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("success", sa.Boolean(), nullable=True),
sa.Column("message", sa.String(), nullable=True),
sa.Column("exception", sa.String(), nullable=True),
sa.Column("timestamp", sa.DateTime(), nullable=False),
sa.Column("report_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["report_id"],
["group_reports.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"shopping_list_recipe_reference",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("shopping_list_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("recipe_quantity", sa.Float(), nullable=False),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["shopping_list_id"],
["shopping_lists.id"],
),
sa.PrimaryKeyConstraint("id", "shopping_list_id"),
)
op.create_index(
op.f("ix_shopping_list_recipe_reference_recipe_id"),
"shopping_list_recipe_reference",
["recipe_id"],
unique=False,
)
op.create_table(
"users",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("full_name", sa.String(), nullable=True),
sa.Column("username", sa.String(), nullable=True),
sa.Column("email", sa.String(), nullable=True),
sa.Column("password", sa.String(), nullable=True),
sa.Column("admin", sa.Boolean(), nullable=True),
sa.Column("advanced", sa.Boolean(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("cache_key", sa.String(), nullable=True),
sa.Column("can_manage", sa.Boolean(), nullable=True),
sa.Column("can_invite", sa.Boolean(), nullable=True),
sa.Column("can_organize", sa.Boolean(), nullable=True),
sa.Column("owned_recipes_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.ForeignKeyConstraint(
["owned_recipes_id"],
["recipes.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True)
op.create_index(op.f("ix_users_full_name"), "users", ["full_name"], unique=False)
op.create_index(op.f("ix_users_group_id"), "users", ["group_id"], unique=False)
op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True)
op.create_table(
"long_live_tokens",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("token", sa.String(), nullable=False),
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"password_reset_tokens",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("token", sa.String(length=64), nullable=False),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("token"),
)
op.create_table(
"recipe_comments",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("text", sa.String(), nullable=True),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=False),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipe_ingredient_ref_link",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("instruction_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("reference_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["instruction_id"],
["recipe_instructions.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"recipes_ingredients",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("position", sa.Integer(), nullable=True),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("title", sa.String(), nullable=True),
sa.Column("note", sa.String(), nullable=True),
sa.Column("unit_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("food_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("quantity", sa.Integer(), nullable=True),
sa.Column("reference_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["food_id"],
["ingredient_foods.id"],
),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["unit_id"],
["ingredient_units.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"shopping_list_items",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("shopping_list_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("is_ingredient", sa.Boolean(), nullable=True),
sa.Column("position", sa.Integer(), nullable=False),
sa.Column("checked", sa.Boolean(), nullable=True),
sa.Column("quantity", sa.Float(), nullable=True),
sa.Column("note", sa.String(), nullable=True),
sa.Column("is_food", sa.Boolean(), nullable=True),
sa.Column("unit_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("food_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("label_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["food_id"],
["ingredient_foods.id"],
),
sa.ForeignKeyConstraint(
["label_id"],
["multi_purpose_labels.id"],
),
sa.ForeignKeyConstraint(
["shopping_list_id"],
["shopping_lists.id"],
),
sa.ForeignKeyConstraint(
["unit_id"],
["ingredient_units.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"users_to_favorites",
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
)
op.create_table(
"shopping_list_item_recipe_reference",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("shopping_list_item_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("recipe_quantity", sa.Float(), nullable=False),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["shopping_list_item_id"],
["shopping_list_items.id"],
),
sa.PrimaryKeyConstraint("id", "shopping_list_item_id"),
)
op.create_index(
op.f("ix_shopping_list_item_recipe_reference_recipe_id"),
"shopping_list_item_recipe_reference",
["recipe_id"],
unique=False,
)
def downgrade():
op.drop_index(
op.f("ix_shopping_list_item_recipe_reference_recipe_id"), table_name="shopping_list_item_recipe_reference"
)
op.drop_table("shopping_list_item_recipe_reference")
op.drop_table("users_to_favorites")
op.drop_table("shopping_list_items")
op.drop_table("recipes_ingredients")
op.drop_table("recipe_ingredient_ref_link")
op.drop_table("recipe_comments")
op.drop_table("password_reset_tokens")
op.drop_table("long_live_tokens")
op.drop_index(op.f("ix_users_username"), table_name="users")
op.drop_index(op.f("ix_users_group_id"), table_name="users")
op.drop_index(op.f("ix_users_full_name"), table_name="users")
op.drop_index(op.f("ix_users_email"), table_name="users")
op.drop_table("users")
op.drop_index(op.f("ix_shopping_list_recipe_reference_recipe_id"), table_name="shopping_list_recipe_reference")
op.drop_table("shopping_list_recipe_reference")
op.drop_table("report_entries")
op.drop_table("recipes_to_tools")
op.drop_table("recipes_to_tags")
op.drop_table("recipes_to_categories")
op.drop_index(op.f("ix_recipe_share_tokens_group_id"), table_name="recipe_share_tokens")
op.drop_table("recipe_share_tokens")
op.drop_table("recipe_settings")
op.drop_table("recipe_nutrition")
op.drop_table("recipe_instructions")
op.drop_table("recipe_assets")
op.drop_table("plan_rules_to_tags")
op.drop_table("plan_rules_to_categories")
op.drop_table("notes")
op.drop_table("ingredient_foods")
op.drop_table("group_to_categories")
op.drop_index(op.f("ix_group_meal_plans_title"), table_name="group_meal_plans")
op.drop_index(op.f("ix_group_meal_plans_recipe_id"), table_name="group_meal_plans")
op.drop_index(op.f("ix_group_meal_plans_group_id"), table_name="group_meal_plans")
op.drop_index(op.f("ix_group_meal_plans_entry_type"), table_name="group_meal_plans")
op.drop_index(op.f("ix_group_meal_plans_date"), table_name="group_meal_plans")
op.drop_table("group_meal_plans")
op.drop_table("group_events_notifier_options")
op.drop_table("cookbooks_to_categories")
op.drop_table("api_extras")
op.drop_index(op.f("ix_webhook_urls_group_id"), table_name="webhook_urls")
op.drop_table("webhook_urls")
op.drop_index(op.f("ix_tools_slug"), table_name="tools")
op.drop_index(op.f("ix_tools_name"), table_name="tools")
op.drop_table("tools")
op.drop_index(op.f("ix_tags_slug"), table_name="tags")
op.drop_index(op.f("ix_tags_name"), table_name="tags")
op.drop_index(op.f("ix_tags_group_id"), table_name="tags")
op.drop_table("tags")
op.drop_index(op.f("ix_shopping_lists_group_id"), table_name="shopping_lists")
op.drop_table("shopping_lists")
op.drop_index(op.f("ix_server_tasks_group_id"), table_name="server_tasks")
op.drop_table("server_tasks")
op.drop_index(op.f("ix_recipes_user_id"), table_name="recipes")
op.drop_index(op.f("ix_recipes_slug"), table_name="recipes")
op.drop_index(op.f("ix_recipes_group_id"), table_name="recipes")
op.drop_table("recipes")
op.drop_index(op.f("ix_multi_purpose_labels_group_id"), table_name="multi_purpose_labels")
op.drop_table("multi_purpose_labels")
op.drop_index(op.f("ix_invite_tokens_token"), table_name="invite_tokens")
op.drop_table("invite_tokens")
op.drop_table("ingredient_units")
op.drop_index(op.f("ix_group_reports_group_id"), table_name="group_reports")
op.drop_index(op.f("ix_group_reports_category"), table_name="group_reports")
op.drop_table("group_reports")
op.drop_index(op.f("ix_group_preferences_group_id"), table_name="group_preferences")
op.drop_table("group_preferences")
op.drop_table("group_meal_plan_rules")
op.drop_index(op.f("ix_group_events_notifiers_group_id"), table_name="group_events_notifiers")
op.drop_table("group_events_notifiers")
op.drop_index(op.f("ix_group_data_exports_group_id"), table_name="group_data_exports")
op.drop_table("group_data_exports")
op.drop_table("cookbooks")
op.drop_index(op.f("ix_categories_slug"), table_name="categories")
op.drop_index(op.f("ix_categories_name"), table_name="categories")
op.drop_index(op.f("ix_categories_group_id"), table_name="categories")
op.drop_table("categories")
op.drop_index(op.f("ix_groups_name"), table_name="groups")
op.drop_table("groups")

View File

@@ -0,0 +1,52 @@
"""convert quantity from integer to float
Revision ID: 263dd6707191
Revises: 6b0f5f32d602
Create Date: 2022-03-23 17:43:34.727829
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "263dd6707191"
down_revision = "6b0f5f32d602"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
return op.get_context().dialect.name == "postgresql"
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
# SQLite doesn't require migration as types are not enforced.
# Postgres Specific Migration
if is_postgres():
op.alter_column(
"recipes_ingredients",
"quantity",
type_=sa.Float(),
existing_type=sa.Integer(),
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
# SQLite doesn't require migration as types are not enforced.
# Postgres Specific Migration
if is_postgres():
op.alter_column(
"recipes_ingredients",
"quantity",
type_=sa.Integer(),
existing_type=sa.Float(),
)
# ### end Alembic commands ###

View File

@@ -0,0 +1,25 @@
"""Add original_text column to recipes_ingredients
Revision ID: f1a2dbee5fe9
Revises: 263dd6707191
Create Date: 2022-03-27 19:30:28.545846
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "f1a2dbee5fe9"
down_revision = "263dd6707191"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
op.add_column("recipes_ingredients", sa.Column("original_text", sa.String(), nullable=True))
def downgrade():
op.drop_column("recipes_ingredients", "original_text")

View File

@@ -0,0 +1,58 @@
"""add tags to cookbooks
Revision ID: 59eb59135381
Revises: f1a2dbee5fe9
Create Date: 2022-03-31 19:19:55.428965
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "59eb59135381"
down_revision = "f1a2dbee5fe9"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"cookbooks_to_tags",
sa.Column("cookbook_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("tag_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["cookbook_id"],
["cookbooks.id"],
),
sa.ForeignKeyConstraint(
["tag_id"],
["tags.id"],
),
)
op.create_table(
"cookbooks_to_tools",
sa.Column("cookbook_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("tool_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["cookbook_id"],
["cookbooks.id"],
),
sa.ForeignKeyConstraint(
["tool_id"],
["tools.id"],
),
)
op.add_column("cookbooks", sa.Column("public", sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("cookbooks", "public")
op.drop_table("cookbooks_to_tools")
op.drop_table("cookbooks_to_tags")
# ### end Alembic commands ###

View File

@@ -0,0 +1,46 @@
"""add require_all for cookbook filters
Revision ID: 09dfc897ad62
Revises: 59eb59135381
Create Date: 2022-04-03 10:48:51.379968
"""
import sqlalchemy as sa
import mealie.db.migration_types # noqa: F401
from alembic import op
# revision identifiers, used by Alembic.
revision = "09dfc897ad62"
down_revision = "59eb59135381"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("cookbooks", sa.Column("require_all_categories", sa.Boolean(), nullable=True))
op.add_column("cookbooks", sa.Column("require_all_tags", sa.Boolean(), nullable=True))
op.add_column("cookbooks", sa.Column("require_all_tools", sa.Boolean(), nullable=True))
# Set Defaults for Existing Cookbooks
op.execute(
"""
UPDATE cookbooks
SET require_all_categories = TRUE,
require_all_tags = TRUE,
require_all_tools = TRUE
"""
)
# ### end Alembic commands ###
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("cookbooks", "require_all_tools")
op.drop_column("cookbooks", "require_all_tags")
op.drop_column("cookbooks", "require_all_categories")
# ### end Alembic commands ###

View File

@@ -0,0 +1,31 @@
"""Add use_abbreviation column to ingredients
Revision ID: ab0bae02578f
Revises: 09dfc897ad62
Create Date: 2022-06-01 11:12:06.748383
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "ab0bae02578f"
down_revision = "09dfc897ad62"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("ingredient_units", sa.Column("use_abbreviation", sa.Boolean(), nullable=True))
op.execute("UPDATE ingredient_units SET use_abbreviation = FALSE WHERE use_abbreviation IS NULL")
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("ingredient_units", "use_abbreviation")
# ### end Alembic commands ###

View File

@@ -0,0 +1,32 @@
"""add new webhook fields
Revision ID: f30cf048c228
Revises: ab0bae02578f
Create Date: 2022-06-15 21:05:34.851857
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "f30cf048c228"
down_revision = "ab0bae02578f"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("webhook_urls", sa.Column("webhook_type", sa.String(), nullable=True))
op.add_column("webhook_urls", sa.Column("scheduled_time", sa.Time(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("webhook_urls", "scheduled_time")
op.drop_column("webhook_urls", "webhook_type")
# ### end Alembic commands ###

View File

@@ -0,0 +1,27 @@
"""add login_attemps and locked_at field to user table
Revision ID: 188374910655
Revises: f30cf048c228
Create Date: 2022-08-12 19:05:59.776361
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "188374910655"
down_revision = "f30cf048c228"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
op.add_column("users", sa.Column("login_attemps", sa.Integer(), nullable=True))
op.add_column("users", sa.Column("locked_at", sa.DateTime(), nullable=True))
def downgrade():
op.drop_column("users", "locked_at")
op.drop_column("users", "login_attemps")

View File

@@ -0,0 +1,28 @@
"""Add is_ocr_recipe column to recipes
Revision ID: 089bfa50d0ed
Revises: 188374910655
Create Date: 2022-08-05 17:07:07.389271
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "089bfa50d0ed"
down_revision = "188374910655"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
op.add_column("recipes", sa.Column("is_ocr_recipe", sa.Boolean(), default=False, nullable=True))
op.execute("UPDATE recipes SET is_ocr_recipe = FALSE")
# SQLITE does not support ALTER COLUMN, so the column will stay nullable to prevent making this migration a mess
# The Recipe pydantic model and the SQL server use False as default value anyway for this column so Null should be a very rare sight
def downgrade():
op.drop_column("recipes", "is_ocr_recipe")

View File

@@ -0,0 +1,73 @@
"""add extras to shopping lists, list items, and ingredient foods
Revision ID: 44e8d670719d
Revises: 089bfa50d0ed
Create Date: 2022-08-29 13:57:40.452245
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "44e8d670719d"
down_revision = "089bfa50d0ed"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"shopping_list_extras",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("key_name", sa.String(), nullable=True),
sa.Column("value", sa.String(), nullable=True),
sa.Column("shopping_list_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["shopping_list_id"],
["shopping_lists.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"ingredient_food_extras",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("key_name", sa.String(), nullable=True),
sa.Column("value", sa.String(), nullable=True),
sa.Column("ingredient_food_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["ingredient_food_id"],
["ingredient_foods.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"shopping_list_item_extras",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("key_name", sa.String(), nullable=True),
sa.Column("value", sa.String(), nullable=True),
sa.Column("shopping_list_item_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["shopping_list_item_id"],
["shopping_list_items.id"],
),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("shopping_list_item_extras")
op.drop_table("ingredient_food_extras")
op.drop_table("shopping_list_extras")
# ### end Alembic commands ###

View File

@@ -0,0 +1,51 @@
"""add recipe_timeline_events table
Revision ID: 2ea7a807915c
Revises: 44e8d670719d
Create Date: 2022-09-27 14:53:14.111054
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "2ea7a807915c"
down_revision = "44e8d670719d"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"recipe_timeline_events",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("subject", sa.String(), nullable=False),
sa.Column("message", sa.String(), nullable=True),
sa.Column("event_type", sa.String(), nullable=True),
sa.Column("image", sa.String(), nullable=True),
sa.Column("timestamp", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("recipe_timeline_events")
# ### end Alembic commands ###

View File

@@ -0,0 +1,29 @@
"""added recipe last made timestamp
Revision ID: 1923519381ad
Revises: 2ea7a807915c
Create Date: 2022-11-03 13:10:24.811134
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "1923519381ad"
down_revision = "2ea7a807915c"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("recipes", sa.Column("last_made", sa.DateTime(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("recipes", "last_made")
# ### end Alembic commands ###

View File

@@ -0,0 +1,30 @@
"""add recipe_scale to shopping list item ref
Revision ID: 167eb69066ad
Revises: 1923519381ad
Create Date: 2022-11-22 03:42:45.494567
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "167eb69066ad"
down_revision = "1923519381ad"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("shopping_list_item_recipe_reference", sa.Column("recipe_scale", sa.Float(), nullable=True))
op.execute("UPDATE shopping_list_item_recipe_reference SET recipe_scale = 1")
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("shopping_list_item_recipe_reference", "recipe_scale")
# ### end Alembic commands ###

View File

@@ -0,0 +1,44 @@
"""add related user to mealplan
Revision ID: 165d943c64ee
Revises: 167eb69066ad
Create Date: 2023-01-21 16:54:44.368768
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "165d943c64ee"
down_revision = "167eb69066ad"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("group_meal_plans", schema=None) as batch_op:
batch_op.add_column(sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(batch_op.f("ix_group_meal_plans_user_id"), ["user_id"], unique=False)
batch_op.create_foreign_key("fk_user_mealplans", "users", ["user_id"], ["id"])
with op.batch_alter_table("shopping_list_item_recipe_reference", schema=None) as batch_op:
batch_op.alter_column("recipe_scale", existing_type=sa.FLOAT(), nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("shopping_list_item_recipe_reference", schema=None) as batch_op:
batch_op.alter_column("recipe_scale", existing_type=sa.FLOAT(), nullable=True)
with op.batch_alter_table("group_meal_plans", schema=None) as batch_op:
batch_op.drop_constraint("fk_user_mealplans", type_="foreignkey")
batch_op.drop_index(batch_op.f("ix_group_meal_plans_user_id"))
batch_op.drop_column("user_id")
# ### end Alembic commands ###

View File

@@ -0,0 +1,280 @@
"""add missing foreign key and order indices
Revision ID: ff5f73b01a7a
Revises: 165d943c64ee
Create Date: 2023-02-07 20:57:21.066927
"""
from alembic import op
# revision identifiers, used by Alembic.
revision = "ff5f73b01a7a"
down_revision = "165d943c64ee"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_index(op.f("ix_api_extras_created_at"), "api_extras", ["created_at"], unique=False)
op.create_index(op.f("ix_api_extras_recipee_id"), "api_extras", ["recipee_id"], unique=False)
op.create_index(op.f("ix_categories_created_at"), "categories", ["created_at"], unique=False)
op.create_index(op.f("ix_cookbooks_created_at"), "cookbooks", ["created_at"], unique=False)
op.create_index(op.f("ix_cookbooks_group_id"), "cookbooks", ["group_id"], unique=False)
op.create_index(op.f("ix_cookbooks_slug"), "cookbooks", ["slug"], unique=False)
op.create_index(
op.f("ix_cookbooks_to_categories_category_id"), "cookbooks_to_categories", ["category_id"], unique=False
)
op.create_index(
op.f("ix_cookbooks_to_categories_cookbook_id"), "cookbooks_to_categories", ["cookbook_id"], unique=False
)
op.create_index(op.f("ix_cookbooks_to_tags_cookbook_id"), "cookbooks_to_tags", ["cookbook_id"], unique=False)
op.create_index(op.f("ix_cookbooks_to_tags_tag_id"), "cookbooks_to_tags", ["tag_id"], unique=False)
op.create_index(op.f("ix_cookbooks_to_tools_cookbook_id"), "cookbooks_to_tools", ["cookbook_id"], unique=False)
op.create_index(op.f("ix_cookbooks_to_tools_tool_id"), "cookbooks_to_tools", ["tool_id"], unique=False)
op.create_index(op.f("ix_group_data_exports_created_at"), "group_data_exports", ["created_at"], unique=False)
op.create_index(
op.f("ix_group_events_notifier_options_created_at"),
"group_events_notifier_options",
["created_at"],
unique=False,
)
op.create_index(
op.f("ix_group_events_notifiers_created_at"), "group_events_notifiers", ["created_at"], unique=False
)
op.create_index(op.f("ix_group_meal_plan_rules_created_at"), "group_meal_plan_rules", ["created_at"], unique=False)
op.create_index(op.f("ix_group_meal_plan_rules_group_id"), "group_meal_plan_rules", ["group_id"], unique=False)
op.create_index(op.f("ix_group_meal_plans_created_at"), "group_meal_plans", ["created_at"], unique=False)
op.create_index(op.f("ix_group_preferences_created_at"), "group_preferences", ["created_at"], unique=False)
op.create_index(op.f("ix_group_reports_created_at"), "group_reports", ["created_at"], unique=False)
op.create_index(op.f("ix_group_to_categories_category_id"), "group_to_categories", ["category_id"], unique=False)
op.create_index(op.f("ix_group_to_categories_group_id"), "group_to_categories", ["group_id"], unique=False)
op.create_index(op.f("ix_groups_created_at"), "groups", ["created_at"], unique=False)
op.create_index(
op.f("ix_ingredient_food_extras_created_at"), "ingredient_food_extras", ["created_at"], unique=False
)
op.create_index(
op.f("ix_ingredient_food_extras_ingredient_food_id"),
"ingredient_food_extras",
["ingredient_food_id"],
unique=False,
)
op.create_index(op.f("ix_ingredient_foods_created_at"), "ingredient_foods", ["created_at"], unique=False)
op.create_index(op.f("ix_ingredient_foods_group_id"), "ingredient_foods", ["group_id"], unique=False)
op.create_index(op.f("ix_ingredient_foods_label_id"), "ingredient_foods", ["label_id"], unique=False)
op.create_index(op.f("ix_ingredient_units_created_at"), "ingredient_units", ["created_at"], unique=False)
op.create_index(op.f("ix_ingredient_units_group_id"), "ingredient_units", ["group_id"], unique=False)
op.create_index(op.f("ix_invite_tokens_created_at"), "invite_tokens", ["created_at"], unique=False)
op.create_index(op.f("ix_invite_tokens_group_id"), "invite_tokens", ["group_id"], unique=False)
op.create_index(op.f("ix_long_live_tokens_created_at"), "long_live_tokens", ["created_at"], unique=False)
op.create_index(op.f("ix_long_live_tokens_token"), "long_live_tokens", ["token"], unique=False)
op.create_index(op.f("ix_long_live_tokens_user_id"), "long_live_tokens", ["user_id"], unique=False)
op.create_index(op.f("ix_multi_purpose_labels_created_at"), "multi_purpose_labels", ["created_at"], unique=False)
op.create_index(op.f("ix_notes_created_at"), "notes", ["created_at"], unique=False)
op.create_index(op.f("ix_notes_recipe_id"), "notes", ["recipe_id"], unique=False)
op.create_index(op.f("ix_password_reset_tokens_created_at"), "password_reset_tokens", ["created_at"], unique=False)
op.create_index(op.f("ix_password_reset_tokens_user_id"), "password_reset_tokens", ["user_id"], unique=False)
op.create_index(
op.f("ix_plan_rules_to_categories_category_id"), "plan_rules_to_categories", ["category_id"], unique=False
)
op.create_index(
op.f("ix_plan_rules_to_categories_group_plan_rule_id"),
"plan_rules_to_categories",
["group_plan_rule_id"],
unique=False,
)
op.create_index(op.f("ix_plan_rules_to_tags_plan_rule_id"), "plan_rules_to_tags", ["plan_rule_id"], unique=False)
op.create_index(op.f("ix_plan_rules_to_tags_tag_id"), "plan_rules_to_tags", ["tag_id"], unique=False)
op.create_index(op.f("ix_recipe_assets_created_at"), "recipe_assets", ["created_at"], unique=False)
op.create_index(op.f("ix_recipe_assets_recipe_id"), "recipe_assets", ["recipe_id"], unique=False)
op.create_index(op.f("ix_recipe_comments_created_at"), "recipe_comments", ["created_at"], unique=False)
op.create_index(op.f("ix_recipe_comments_recipe_id"), "recipe_comments", ["recipe_id"], unique=False)
op.create_index(op.f("ix_recipe_comments_user_id"), "recipe_comments", ["user_id"], unique=False)
op.create_index(
op.f("ix_recipe_ingredient_ref_link_created_at"), "recipe_ingredient_ref_link", ["created_at"], unique=False
)
op.create_index(
op.f("ix_recipe_ingredient_ref_link_instruction_id"),
"recipe_ingredient_ref_link",
["instruction_id"],
unique=False,
)
op.create_index(
op.f("ix_recipe_ingredient_ref_link_reference_id"), "recipe_ingredient_ref_link", ["reference_id"], unique=False
)
op.create_index(op.f("ix_recipe_instructions_created_at"), "recipe_instructions", ["created_at"], unique=False)
op.create_index(op.f("ix_recipe_instructions_position"), "recipe_instructions", ["position"], unique=False)
op.create_index(op.f("ix_recipe_instructions_recipe_id"), "recipe_instructions", ["recipe_id"], unique=False)
op.create_index(op.f("ix_recipe_nutrition_created_at"), "recipe_nutrition", ["created_at"], unique=False)
op.create_index(op.f("ix_recipe_nutrition_recipe_id"), "recipe_nutrition", ["recipe_id"], unique=False)
op.create_index(op.f("ix_recipe_settings_created_at"), "recipe_settings", ["created_at"], unique=False)
op.create_index(op.f("ix_recipe_settings_recipe_id"), "recipe_settings", ["recipe_id"], unique=False)
op.create_index(op.f("ix_recipe_share_tokens_created_at"), "recipe_share_tokens", ["created_at"], unique=False)
op.create_index(op.f("ix_recipe_share_tokens_recipe_id"), "recipe_share_tokens", ["recipe_id"], unique=False)
op.create_index(
op.f("ix_recipe_timeline_events_created_at"), "recipe_timeline_events", ["created_at"], unique=False
)
op.create_index(op.f("ix_recipe_timeline_events_recipe_id"), "recipe_timeline_events", ["recipe_id"], unique=False)
op.create_index(op.f("ix_recipe_timeline_events_timestamp"), "recipe_timeline_events", ["timestamp"], unique=False)
op.create_index(op.f("ix_recipe_timeline_events_user_id"), "recipe_timeline_events", ["user_id"], unique=False)
op.create_index(op.f("ix_recipes_created_at"), "recipes", ["created_at"], unique=False)
op.create_index(op.f("ix_recipes_name"), "recipes", ["name"], unique=False)
op.create_index(op.f("ix_recipes_ingredients_created_at"), "recipes_ingredients", ["created_at"], unique=False)
op.create_index(op.f("ix_recipes_ingredients_food_id"), "recipes_ingredients", ["food_id"], unique=False)
op.create_index(op.f("ix_recipes_ingredients_position"), "recipes_ingredients", ["position"], unique=False)
op.create_index(op.f("ix_recipes_ingredients_unit_id"), "recipes_ingredients", ["unit_id"], unique=False)
op.create_index(
op.f("ix_recipes_to_categories_category_id"), "recipes_to_categories", ["category_id"], unique=False
)
op.create_index(op.f("ix_recipes_to_categories_recipe_id"), "recipes_to_categories", ["recipe_id"], unique=False)
op.create_index(op.f("ix_recipes_to_tags_recipe_id"), "recipes_to_tags", ["recipe_id"], unique=False)
op.create_index(op.f("ix_recipes_to_tags_tag_id"), "recipes_to_tags", ["tag_id"], unique=False)
op.create_index(op.f("ix_recipes_to_tools_recipe_id"), "recipes_to_tools", ["recipe_id"], unique=False)
op.create_index(op.f("ix_recipes_to_tools_tool_id"), "recipes_to_tools", ["tool_id"], unique=False)
op.create_index(op.f("ix_report_entries_created_at"), "report_entries", ["created_at"], unique=False)
op.create_index(op.f("ix_report_entries_report_id"), "report_entries", ["report_id"], unique=False)
op.create_index(op.f("ix_server_tasks_created_at"), "server_tasks", ["created_at"], unique=False)
op.create_index(op.f("ix_shopping_list_extras_created_at"), "shopping_list_extras", ["created_at"], unique=False)
op.create_index(
op.f("ix_shopping_list_extras_shopping_list_id"), "shopping_list_extras", ["shopping_list_id"], unique=False
)
op.create_index(
op.f("ix_shopping_list_item_extras_created_at"), "shopping_list_item_extras", ["created_at"], unique=False
)
op.create_index(
op.f("ix_shopping_list_item_extras_shopping_list_item_id"),
"shopping_list_item_extras",
["shopping_list_item_id"],
unique=False,
)
op.create_index(
op.f("ix_shopping_list_item_recipe_reference_created_at"),
"shopping_list_item_recipe_reference",
["created_at"],
unique=False,
)
op.create_index(op.f("ix_shopping_list_items_created_at"), "shopping_list_items", ["created_at"], unique=False)
op.create_index(op.f("ix_shopping_list_items_position"), "shopping_list_items", ["position"], unique=False)
op.create_index(
op.f("ix_shopping_list_items_shopping_list_id"), "shopping_list_items", ["shopping_list_id"], unique=False
)
op.create_index(
op.f("ix_shopping_list_recipe_reference_created_at"),
"shopping_list_recipe_reference",
["created_at"],
unique=False,
)
op.create_index(op.f("ix_shopping_lists_created_at"), "shopping_lists", ["created_at"], unique=False)
op.create_index(op.f("ix_tags_created_at"), "tags", ["created_at"], unique=False)
op.create_index(op.f("ix_tools_created_at"), "tools", ["created_at"], unique=False)
op.create_index(op.f("ix_tools_group_id"), "tools", ["group_id"], unique=False)
op.create_index(op.f("ix_users_created_at"), "users", ["created_at"], unique=False)
op.create_index(op.f("ix_users_to_favorites_recipe_id"), "users_to_favorites", ["recipe_id"], unique=False)
op.create_index(op.f("ix_users_to_favorites_user_id"), "users_to_favorites", ["user_id"], unique=False)
op.create_index(op.f("ix_webhook_urls_created_at"), "webhook_urls", ["created_at"], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_webhook_urls_created_at"), table_name="webhook_urls")
op.drop_index(op.f("ix_users_to_favorites_user_id"), table_name="users_to_favorites")
op.drop_index(op.f("ix_users_to_favorites_recipe_id"), table_name="users_to_favorites")
op.drop_index(op.f("ix_users_created_at"), table_name="users")
op.drop_index(op.f("ix_tools_group_id"), table_name="tools")
op.drop_index(op.f("ix_tools_created_at"), table_name="tools")
op.drop_index(op.f("ix_tags_created_at"), table_name="tags")
op.drop_index(op.f("ix_shopping_lists_created_at"), table_name="shopping_lists")
op.drop_index(op.f("ix_shopping_list_recipe_reference_created_at"), table_name="shopping_list_recipe_reference")
op.drop_index(op.f("ix_shopping_list_items_shopping_list_id"), table_name="shopping_list_items")
op.drop_index(op.f("ix_shopping_list_items_position"), table_name="shopping_list_items")
op.drop_index(op.f("ix_shopping_list_items_created_at"), table_name="shopping_list_items")
op.drop_index(
op.f("ix_shopping_list_item_recipe_reference_created_at"), table_name="shopping_list_item_recipe_reference"
)
op.drop_index(op.f("ix_shopping_list_item_extras_shopping_list_item_id"), table_name="shopping_list_item_extras")
op.drop_index(op.f("ix_shopping_list_item_extras_created_at"), table_name="shopping_list_item_extras")
op.drop_index(op.f("ix_shopping_list_extras_shopping_list_id"), table_name="shopping_list_extras")
op.drop_index(op.f("ix_shopping_list_extras_created_at"), table_name="shopping_list_extras")
op.drop_index(op.f("ix_server_tasks_created_at"), table_name="server_tasks")
op.drop_index(op.f("ix_report_entries_report_id"), table_name="report_entries")
op.drop_index(op.f("ix_report_entries_created_at"), table_name="report_entries")
op.drop_index(op.f("ix_recipes_to_tools_tool_id"), table_name="recipes_to_tools")
op.drop_index(op.f("ix_recipes_to_tools_recipe_id"), table_name="recipes_to_tools")
op.drop_index(op.f("ix_recipes_to_tags_tag_id"), table_name="recipes_to_tags")
op.drop_index(op.f("ix_recipes_to_tags_recipe_id"), table_name="recipes_to_tags")
op.drop_index(op.f("ix_recipes_to_categories_recipe_id"), table_name="recipes_to_categories")
op.drop_index(op.f("ix_recipes_to_categories_category_id"), table_name="recipes_to_categories")
op.drop_index(op.f("ix_recipes_ingredients_unit_id"), table_name="recipes_ingredients")
op.drop_index(op.f("ix_recipes_ingredients_position"), table_name="recipes_ingredients")
op.drop_index(op.f("ix_recipes_ingredients_food_id"), table_name="recipes_ingredients")
op.drop_index(op.f("ix_recipes_ingredients_created_at"), table_name="recipes_ingredients")
op.drop_index(op.f("ix_recipes_name"), table_name="recipes")
op.drop_index(op.f("ix_recipes_created_at"), table_name="recipes")
op.drop_index(op.f("ix_recipe_timeline_events_user_id"), table_name="recipe_timeline_events")
op.drop_index(op.f("ix_recipe_timeline_events_timestamp"), table_name="recipe_timeline_events")
op.drop_index(op.f("ix_recipe_timeline_events_recipe_id"), table_name="recipe_timeline_events")
op.drop_index(op.f("ix_recipe_timeline_events_created_at"), table_name="recipe_timeline_events")
op.drop_index(op.f("ix_recipe_share_tokens_recipe_id"), table_name="recipe_share_tokens")
op.drop_index(op.f("ix_recipe_share_tokens_created_at"), table_name="recipe_share_tokens")
op.drop_index(op.f("ix_recipe_settings_recipe_id"), table_name="recipe_settings")
op.drop_index(op.f("ix_recipe_settings_created_at"), table_name="recipe_settings")
op.drop_index(op.f("ix_recipe_nutrition_recipe_id"), table_name="recipe_nutrition")
op.drop_index(op.f("ix_recipe_nutrition_created_at"), table_name="recipe_nutrition")
op.drop_index(op.f("ix_recipe_instructions_recipe_id"), table_name="recipe_instructions")
op.drop_index(op.f("ix_recipe_instructions_position"), table_name="recipe_instructions")
op.drop_index(op.f("ix_recipe_instructions_created_at"), table_name="recipe_instructions")
op.drop_index(op.f("ix_recipe_ingredient_ref_link_reference_id"), table_name="recipe_ingredient_ref_link")
op.drop_index(op.f("ix_recipe_ingredient_ref_link_instruction_id"), table_name="recipe_ingredient_ref_link")
op.drop_index(op.f("ix_recipe_ingredient_ref_link_created_at"), table_name="recipe_ingredient_ref_link")
op.drop_index(op.f("ix_recipe_comments_user_id"), table_name="recipe_comments")
op.drop_index(op.f("ix_recipe_comments_recipe_id"), table_name="recipe_comments")
op.drop_index(op.f("ix_recipe_comments_created_at"), table_name="recipe_comments")
op.drop_index(op.f("ix_recipe_assets_recipe_id"), table_name="recipe_assets")
op.drop_index(op.f("ix_recipe_assets_created_at"), table_name="recipe_assets")
op.drop_index(op.f("ix_plan_rules_to_tags_tag_id"), table_name="plan_rules_to_tags")
op.drop_index(op.f("ix_plan_rules_to_tags_plan_rule_id"), table_name="plan_rules_to_tags")
op.drop_index(op.f("ix_plan_rules_to_categories_group_plan_rule_id"), table_name="plan_rules_to_categories")
op.drop_index(op.f("ix_plan_rules_to_categories_category_id"), table_name="plan_rules_to_categories")
op.drop_index(op.f("ix_password_reset_tokens_user_id"), table_name="password_reset_tokens")
op.drop_index(op.f("ix_password_reset_tokens_created_at"), table_name="password_reset_tokens")
op.drop_index(op.f("ix_notes_recipe_id"), table_name="notes")
op.drop_index(op.f("ix_notes_created_at"), table_name="notes")
op.drop_index(op.f("ix_multi_purpose_labels_created_at"), table_name="multi_purpose_labels")
op.drop_index(op.f("ix_long_live_tokens_user_id"), table_name="long_live_tokens")
op.drop_index(op.f("ix_long_live_tokens_token"), table_name="long_live_tokens")
op.drop_index(op.f("ix_long_live_tokens_created_at"), table_name="long_live_tokens")
op.drop_index(op.f("ix_invite_tokens_group_id"), table_name="invite_tokens")
op.drop_index(op.f("ix_invite_tokens_created_at"), table_name="invite_tokens")
op.drop_index(op.f("ix_ingredient_units_group_id"), table_name="ingredient_units")
op.drop_index(op.f("ix_ingredient_units_created_at"), table_name="ingredient_units")
op.drop_index(op.f("ix_ingredient_foods_label_id"), table_name="ingredient_foods")
op.drop_index(op.f("ix_ingredient_foods_group_id"), table_name="ingredient_foods")
op.drop_index(op.f("ix_ingredient_foods_created_at"), table_name="ingredient_foods")
op.drop_index(op.f("ix_ingredient_food_extras_ingredient_food_id"), table_name="ingredient_food_extras")
op.drop_index(op.f("ix_ingredient_food_extras_created_at"), table_name="ingredient_food_extras")
op.drop_index(op.f("ix_groups_created_at"), table_name="groups")
op.drop_index(op.f("ix_group_to_categories_group_id"), table_name="group_to_categories")
op.drop_index(op.f("ix_group_to_categories_category_id"), table_name="group_to_categories")
op.drop_index(op.f("ix_group_reports_created_at"), table_name="group_reports")
op.drop_index(op.f("ix_group_preferences_created_at"), table_name="group_preferences")
op.drop_index(op.f("ix_group_meal_plans_created_at"), table_name="group_meal_plans")
op.drop_index(op.f("ix_group_meal_plan_rules_group_id"), table_name="group_meal_plan_rules")
op.drop_index(op.f("ix_group_meal_plan_rules_created_at"), table_name="group_meal_plan_rules")
op.drop_index(op.f("ix_group_events_notifiers_created_at"), table_name="group_events_notifiers")
op.drop_index(op.f("ix_group_events_notifier_options_created_at"), table_name="group_events_notifier_options")
op.drop_index(op.f("ix_group_data_exports_created_at"), table_name="group_data_exports")
op.drop_index(op.f("ix_cookbooks_to_tools_tool_id"), table_name="cookbooks_to_tools")
op.drop_index(op.f("ix_cookbooks_to_tools_cookbook_id"), table_name="cookbooks_to_tools")
op.drop_index(op.f("ix_cookbooks_to_tags_tag_id"), table_name="cookbooks_to_tags")
op.drop_index(op.f("ix_cookbooks_to_tags_cookbook_id"), table_name="cookbooks_to_tags")
op.drop_index(op.f("ix_cookbooks_to_categories_cookbook_id"), table_name="cookbooks_to_categories")
op.drop_index(op.f("ix_cookbooks_to_categories_category_id"), table_name="cookbooks_to_categories")
op.drop_index(op.f("ix_cookbooks_slug"), table_name="cookbooks")
op.drop_index(op.f("ix_cookbooks_group_id"), table_name="cookbooks")
op.drop_index(op.f("ix_cookbooks_created_at"), table_name="cookbooks")
op.drop_index(op.f("ix_categories_created_at"), table_name="categories")
op.drop_index(op.f("ix_api_extras_recipee_id"), table_name="api_extras")
op.drop_index(op.f("ix_api_extras_created_at"), table_name="api_extras")
# ### end Alembic commands ###

View File

@@ -0,0 +1,35 @@
"""add more indices necessary for search
Revision ID: 16160bf731a0
Revises: ff5f73b01a7a
Create Date: 2023-02-10 21:18:32.405130
"""
from alembic import op
# revision identifiers, used by Alembic.
revision = "16160bf731a0"
down_revision = "ff5f73b01a7a"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_index(op.f("ix_recipe_instructions_text"), "recipe_instructions", ["text"], unique=False)
op.create_index(op.f("ix_recipes_description"), "recipes", ["description"], unique=False)
op.create_index(op.f("ix_recipes_ingredients_note"), "recipes_ingredients", ["note"], unique=False)
op.create_index(
op.f("ix_recipes_ingredients_original_text"), "recipes_ingredients", ["original_text"], unique=False
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_recipes_ingredients_original_text"), table_name="recipes_ingredients")
op.drop_index(op.f("ix_recipes_ingredients_note"), table_name="recipes_ingredients")
op.drop_index(op.f("ix_recipes_description"), table_name="recipes")
op.drop_index(op.f("ix_recipe_instructions_text"), table_name="recipe_instructions")
# ### end Alembic commands ###

View File

@@ -0,0 +1,133 @@
"""add normalized search properties
Revision ID: 5ab195a474eb
Revises: 16160bf731a0
Create Date: 2023-02-14 20:45:41.102571
"""
import sqlalchemy as sa
from sqlalchemy import orm, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from text_unidecode import unidecode
from alembic import op
from mealie.db.models._model_utils.guid import GUID
# revision identifiers, used by Alembic.
revision = "5ab195a474eb"
down_revision = "16160bf731a0"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
class SqlAlchemyBase(DeclarativeBase):
pass
# Intermediate table definitions
class RecipeModel(SqlAlchemyBase):
__tablename__ = "recipes"
id: Mapped[GUID] = mapped_column(GUID, primary_key=True, default=GUID.generate)
name: Mapped[str] = mapped_column(sa.String, nullable=False)
description: Mapped[str | None] = mapped_column(sa.String)
name_normalized: Mapped[str] = mapped_column(sa.String, nullable=False, index=True)
description_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
class RecipeIngredient(SqlAlchemyBase):
__tablename__ = "recipes_ingredients"
id: Mapped[int] = mapped_column(sa.Integer, primary_key=True)
note: Mapped[str | None] = mapped_column(sa.String)
original_text: Mapped[str | None] = mapped_column(sa.String)
note_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
original_text_normalized: Mapped[str | None] = mapped_column(sa.String, index=True)
def do_data_migration():
bind = op.get_bind()
session = orm.Session(bind=bind)
recipes = session.execute(select(RecipeModel)).scalars().all()
for recipe in recipes:
if recipe.name is not None:
session.execute(
sa.text(
f"UPDATE {RecipeModel.__tablename__} SET name_normalized=:name_normalized WHERE id=:id"
).bindparams(name_normalized=unidecode(recipe.name).lower().strip(), id=recipe.id)
)
if recipe.description is not None:
session.execute(
sa.text(
f"UPDATE {RecipeModel.__tablename__} SET description_normalized=:description_normalized WHERE id=:id"
).bindparams(description_normalized=unidecode(recipe.description).lower().strip(), id=recipe.id)
)
ingredients = session.execute(select(RecipeIngredient)).scalars().all()
for ingredient in ingredients:
if ingredient.note is not None:
session.execute(
sa.text(
f"UPDATE {RecipeIngredient.__tablename__} SET note_normalized=:note_normalized WHERE id=:id"
).bindparams(note_normalized=unidecode(ingredient.note).lower().strip(), id=ingredient.id)
)
if ingredient.original_text is not None:
session.execute(
sa.text(
f"UPDATE {RecipeIngredient.__tablename__} SET original_text_normalized=:original_text_normalized WHERE id=:id"
).bindparams(
original_text_normalized=unidecode(ingredient.original_text).lower().strip(), id=ingredient.id
)
)
session.commit()
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
# Set column default first, since we do not have values here yet
op.add_column("recipes", sa.Column("name_normalized", sa.String(), nullable=False, server_default=""))
op.add_column("recipes", sa.Column("description_normalized", sa.String(), nullable=True))
op.drop_index("ix_recipes_description", table_name="recipes")
op.drop_index("ix_recipes_name", table_name="recipes")
op.create_index(op.f("ix_recipes_description_normalized"), "recipes", ["description_normalized"], unique=False)
op.create_index(op.f("ix_recipes_name_normalized"), "recipes", ["name_normalized"], unique=False)
op.add_column("recipes_ingredients", sa.Column("note_normalized", sa.String(), nullable=True))
op.add_column("recipes_ingredients", sa.Column("original_text_normalized", sa.String(), nullable=True))
op.drop_index("ix_recipes_ingredients_note", table_name="recipes_ingredients")
op.drop_index("ix_recipes_ingredients_original_text", table_name="recipes_ingredients")
op.create_index(
op.f("ix_recipes_ingredients_note_normalized"), "recipes_ingredients", ["note_normalized"], unique=False
)
op.create_index(
op.f("ix_recipes_ingredients_original_text_normalized"),
"recipes_ingredients",
["original_text_normalized"],
unique=False,
)
do_data_migration()
# Remove server default now that column should be filled for all rows
with op.batch_alter_table("recipes", schema=None) as batch_op:
batch_op.alter_column("name_normalized", existing_type=sa.String(), server_default=None)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_recipes_ingredients_original_text_normalized"), table_name="recipes_ingredients")
op.drop_index(op.f("ix_recipes_ingredients_note_normalized"), table_name="recipes_ingredients")
op.create_index("ix_recipes_ingredients_original_text", "recipes_ingredients", ["original_text"], unique=False)
op.create_index("ix_recipes_ingredients_note", "recipes_ingredients", ["note"], unique=False)
op.drop_column("recipes_ingredients", "original_text_normalized")
op.drop_column("recipes_ingredients", "note_normalized")
op.drop_index(op.f("ix_recipes_name_normalized"), table_name="recipes")
op.drop_index(op.f("ix_recipes_description_normalized"), table_name="recipes")
op.create_index("ix_recipes_name", "recipes", ["name"], unique=False)
op.create_index("ix_recipes_description", "recipes", ["description"], unique=False)
op.drop_column("recipes", "description_normalized")
op.drop_column("recipes", "name_normalized")
# ### end Alembic commands ###

View File

@@ -0,0 +1,90 @@
"""added shopping list label settings
Revision ID: b04a08da2108
Revises: 5ab195a474eb
Create Date: 2023-21-02 22:03:19.837244
"""
from uuid import uuid4
import sqlalchemy as sa
from sqlalchemy import orm
import mealie.db.migration_types
from alembic import op
from mealie.db.models._model_utils.guid import GUID
# revision identifiers, used by Alembic.
revision = "b04a08da2108"
down_revision = "5ab195a474eb"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Intermediate table definitions
class SqlAlchemyBase(orm.DeclarativeBase):
pass
class ShoppingList(SqlAlchemyBase):
__tablename__ = "shopping_lists"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
class MultiPurposeLabel(SqlAlchemyBase):
__tablename__ = "multi_purpose_labels"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
def populate_shopping_lists_multi_purpose_labels(
shopping_lists_multi_purpose_labels_table: sa.Table, session: orm.Session
):
shopping_lists = session.query(ShoppingList).options(orm.load_only(ShoppingList.id, ShoppingList.group_id)).all()
shopping_lists_labels_data: list[dict] = []
for shopping_list in shopping_lists:
labels = session.query(MultiPurposeLabel).filter(MultiPurposeLabel.group_id == ShoppingList.group_id).all()
for i, label in enumerate(labels):
shopping_lists_labels_data.append(
{"id": uuid4(), "shopping_list_id": shopping_list.id, "label_id": label.id, "position": i}
)
op.bulk_insert(shopping_lists_multi_purpose_labels_table, shopping_lists_labels_data)
session.commit()
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
shopping_lists_multi_purpose_labels_table = op.create_table(
"shopping_lists_multi_purpose_labels",
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("shopping_list_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("label_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("position", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["label_id"],
["multi_purpose_labels.id"],
),
sa.ForeignKeyConstraint(
["shopping_list_id"],
["shopping_lists.id"],
),
sa.PrimaryKeyConstraint("id", "shopping_list_id", "label_id"),
)
# ### end Alembic commands ###
session = orm.Session(bind=op.get_bind())
populate_shopping_lists_multi_purpose_labels(shopping_lists_multi_purpose_labels_table, session)
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("shopping_lists_multi_purpose_labels")
# ### end Alembic commands ###

View File

@@ -0,0 +1,43 @@
"""add auth_method to user table
Revision ID: 38514b39a824
Revises: b04a08da2108
Create Date: 2023-02-22 21:45:52.900964
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "38514b39a824"
down_revision = "b04a08da2108"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
return op.get_context().dialect.name == "postgresql"
authMethod = sa.Enum("MEALIE", "LDAP", name="authmethod")
def upgrade():
if is_postgres():
authMethod.create(op.get_bind())
op.add_column(
"users",
sa.Column("auth_method", authMethod, nullable=False, server_default="MEALIE"),
)
op.execute("UPDATE users SET auth_method = 'LDAP' WHERE password = 'LDAP'")
def downgrade():
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.drop_column("auth_method")
if is_postgres():
authMethod.drop(op.get_bind())

View File

@@ -0,0 +1,85 @@
"""postgres fuzzy search
Revision ID: b3dbb554ba53
Revises: 38514b39a824
Create Date: 2023-04-13 06:47:04.617131
"""
from alembic import op
# revision identifiers, used by Alembic.
revision = "b3dbb554ba53"
down_revision = "38514b39a824"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def get_db_type():
return op.get_context().dialect.name
def setup_postgres_trigrams():
op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
op.create_index(
"ix_recipes_name_normalized_gin",
table_name="recipes",
columns=["name_normalized"],
unique=False,
postgresql_using="gin",
postgresql_ops={
"name_normalized": "gin_trgm_ops",
},
)
op.create_index(
"ix_recipes_description_normalized_gin",
table_name="recipes",
columns=["description_normalized"],
unique=False,
postgresql_using="gin",
postgresql_ops={
"description_normalized": "gin_trgm_ops",
},
)
op.create_index(
"ix_recipes_ingredients_note_normalized_gin",
table_name="recipes_ingredients",
columns=["note_normalized"],
unique=False,
postgresql_using="gin",
postgresql_ops={
"note_normalized": "gin_trgm_ops",
},
)
op.create_index(
"ix_recipes_ingredients_original_text_normalized_gin",
table_name="recipes_ingredients",
columns=["original_text_normalized"],
unique=False,
postgresql_using="gin",
postgresql_ops={
"original_text_normalized": "gin_trgm_ops",
},
)
def remove_postgres_trigrams():
op.execute("DROP EXTENSION IF EXISTS pg_trgm;")
op.drop_index("ix_recipes_name_normalized_gin", table_name="recipe")
op.drop_index("ix_recipes_description_normalized_gin", table_name="recipe")
op.drop_index("ix_recipes_ingredients_note_normalized_gin", table_name="recipes_ingredients")
op.drop_index("ix_recipes_ingredients_original_text_normalized_gin", table_name="recipes_ingredients")
def upgrade():
if get_db_type() == "postgresql":
setup_postgres_trigrams()
else:
pass
def downgrade():
if get_db_type() == "postgres":
remove_postgres_trigrams()
else:
pass

View File

@@ -0,0 +1,62 @@
"""added group slug
Revision ID: 04ac51cbe9a4
Revises: b3dbb554ba53
Create Date: 2023-08-06 21:00:34.582905
"""
import sqlalchemy as sa
from slugify import slugify
from sqlalchemy.orm import Session
from alembic import op
from mealie.db.models.group.group import Group
# revision identifiers, used by Alembic.
revision = "04ac51cbe9a4"
down_revision = "b3dbb554ba53"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def populate_group_slugs(session: Session):
groups: list[Group] = session.query(Group).all()
seen_slugs: set[str] = set()
for group in groups:
original_name = group.name
new_name = original_name
attempts = 0
while True:
slug = slugify(new_name)
if slug not in seen_slugs:
break
attempts += 1
new_name = f"{original_name} ({attempts})"
seen_slugs.add(slug)
session.execute(
sa.text(f"UPDATE {Group.__tablename__} SET name=:name, slug=:slug WHERE id=:id").bindparams(
name=new_name, slug=slug, id=group.id
)
)
session.commit()
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("groups", sa.Column("slug", sa.String(), nullable=True))
op.create_index(op.f("ix_groups_slug"), "groups", ["slug"], unique=True)
# ### end Alembic commands ###
session = Session(bind=op.get_bind())
populate_group_slugs(session)
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_groups_slug"), table_name="groups")
op.drop_column("groups", "slug")
# ### end Alembic commands ###

View File

@@ -0,0 +1,29 @@
"""added recipe note to shopping list recipe ref
Revision ID: 1825b5225403
Revises: 04ac51cbe9a4
Create Date: 2023-08-14 19:30:49.103185
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "1825b5225403"
down_revision = "04ac51cbe9a4"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("shopping_list_item_recipe_reference", sa.Column("recipe_note", sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("shopping_list_item_recipe_reference", "recipe_note")
# ### end Alembic commands ###

View File

@@ -0,0 +1,33 @@
"""remove tool name and slug unique contraints
Revision ID: bcfdad6b7355
Revises: 1825b5225403
Create Date: 2023-08-15 16:25:07.058929
"""
from alembic import op
# revision identifiers, used by Alembic.
revision = "bcfdad6b7355"
down_revision = "1825b5225403"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index("ix_tools_name", table_name="tools")
op.create_index(op.f("ix_tools_name"), "tools", ["name"], unique=False)
op.drop_index("ix_tools_slug", table_name="tools")
op.create_index(op.f("ix_tools_slug"), "tools", ["slug"], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_tools_slug"), table_name="tools")
op.create_index("ix_tools_slug", "tools", ["slug"], unique=True)
op.drop_index(op.f("ix_tools_name"), table_name="tools")
op.create_index("ix_tools_name", "tools", ["name"], unique=True)
# ### end Alembic commands ###

View File

@@ -0,0 +1,90 @@
"""added normalized unit and food names
Revision ID: 0341b154f79a
Revises: bcfdad6b7355
Create Date: 2023-09-01 14:55:42.166766
"""
import sqlalchemy as sa
from sqlalchemy import orm, select
from alembic import op
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
# revision identifiers, used by Alembic.
revision = "0341b154f79a"
down_revision = "bcfdad6b7355"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def populate_normalized_fields():
bind = op.get_bind()
session = orm.Session(bind=bind)
units = (
session.execute(
select(IngredientUnitModel).options(
orm.load_only(IngredientUnitModel.name, IngredientUnitModel.abbreviation)
)
)
.scalars()
.all()
)
for unit in units:
if unit.name is not None:
session.execute(
sa.text(
f"UPDATE {IngredientUnitModel.__tablename__} SET name_normalized=:name_normalized WHERE id=:id"
).bindparams(name_normalized=IngredientUnitModel.normalize(unit.name), id=unit.id)
)
if unit.abbreviation is not None:
session.execute(
sa.text(
f"UPDATE {IngredientUnitModel.__tablename__} SET abbreviation_normalized=:abbreviation_normalized WHERE id=:id"
).bindparams(abbreviation_normalized=IngredientUnitModel.normalize(unit.abbreviation), id=unit.id)
)
foods = (
session.execute(select(IngredientFoodModel).options(orm.load_only(IngredientFoodModel.name))).scalars().all()
)
for food in foods:
if food.name is not None:
session.execute(
sa.text(
f"UPDATE {IngredientFoodModel.__tablename__} SET name_normalized=:name_normalized WHERE id=:id"
).bindparams(name_normalized=IngredientFoodModel.normalize(food.name), id=food.id)
)
session.commit()
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("ingredient_foods", sa.Column("name_normalized", sa.String(), nullable=True))
op.create_index(op.f("ix_ingredient_foods_name_normalized"), "ingredient_foods", ["name_normalized"], unique=False)
op.add_column("ingredient_units", sa.Column("name_normalized", sa.String(), nullable=True))
op.add_column("ingredient_units", sa.Column("abbreviation_normalized", sa.String(), nullable=True))
op.create_index(
op.f("ix_ingredient_units_abbreviation_normalized"),
"ingredient_units",
["abbreviation_normalized"],
unique=False,
)
op.create_index(op.f("ix_ingredient_units_name_normalized"), "ingredient_units", ["name_normalized"], unique=False)
# ### end Alembic commands ###
populate_normalized_fields()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_ingredient_units_name_normalized"), table_name="ingredient_units")
op.drop_index(op.f("ix_ingredient_units_abbreviation_normalized"), table_name="ingredient_units")
op.drop_column("ingredient_units", "abbreviation_normalized")
op.drop_column("ingredient_units", "name_normalized")
op.drop_index(op.f("ix_ingredient_foods_name_normalized"), table_name="ingredient_foods")
op.drop_column("ingredient_foods", "name_normalized")
# ### end Alembic commands ###

View File

@@ -0,0 +1,306 @@
"""added unique constraints
Revision ID: dded3119c1fe
Revises: 0341b154f79a
Create Date: 2023-10-04 14:29:26.688065
"""
from collections import defaultdict
from dataclasses import dataclass
from typing import Any
import sqlalchemy as sa
from pydantic import UUID4
from sqlalchemy import orm
from sqlalchemy.orm import Session, load_only
from alembic import op
from mealie.db.models._model_utils.guid import GUID
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel
# revision identifiers, used by Alembic.
revision = "dded3119c1fe"
down_revision = "0341b154f79a"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Intermediate table definitions
class SqlAlchemyBase(orm.DeclarativeBase):
pass
class ShoppingList(SqlAlchemyBase):
__tablename__ = "shopping_lists"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
class ShoppingListItem(SqlAlchemyBase):
__tablename__ = "shopping_list_items"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
food_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("ingredient_foods.id"))
unit_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("ingredient_units.id"))
label_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("multi_purpose_labels.id"))
@dataclass
class TableMeta:
tablename: str
pk_1: str
pk_2: str
@classmethod
def composite_pk(self, pk_1_val: Any, pk_2_val: Any) -> str:
return "$$".join([pk_1_val, pk_2_val])
def _is_postgres():
return op.get_context().dialect.name == "postgresql"
def _get_duplicates(session: Session, model: orm.DeclarativeBase) -> defaultdict[str, list]:
duplicate_map: defaultdict[str, list] = defaultdict(list)
query = session.execute(sa.text(f"SELECT id, group_id, name FROM {model.__tablename__}"))
for row in query.all():
id, group_id, name = row
key = f"{group_id}$${name}"
duplicate_map[key].append(id)
return duplicate_map
def _resolve_duplicate_food(
session: Session,
keep_food_id: UUID4,
dupe_food_id: UUID4,
):
for shopping_list_item in (
session.query(ShoppingListItem)
.options(load_only(ShoppingListItem.id, ShoppingListItem.food_id))
.filter_by(food_id=dupe_food_id)
.all()
):
shopping_list_item.food_id = keep_food_id
for recipe_ingredient in (
session.query(RecipeIngredientModel)
.options(load_only(RecipeIngredientModel.id, RecipeIngredientModel.food_id))
.filter_by(food_id=dupe_food_id)
.all()
):
recipe_ingredient.food_id = keep_food_id
session.commit()
session.execute(
sa.text(f"DELETE FROM {IngredientFoodModel.__tablename__} WHERE id=:id").bindparams(id=dupe_food_id)
)
session.commit()
def _resolve_duplicate_unit(
session: Session,
keep_unit_id: UUID4,
dupe_unit_id: UUID4,
):
for shopping_list_item in (
session.query(ShoppingListItem)
.options(load_only(ShoppingListItem.id, ShoppingListItem.unit_id))
.filter_by(unit_id=dupe_unit_id)
.all()
):
shopping_list_item.unit_id = keep_unit_id
for recipe_ingredient in (
session.query(RecipeIngredientModel)
.options(load_only(RecipeIngredientModel.id, RecipeIngredientModel.unit_id))
.filter_by(unit_id=dupe_unit_id)
.all()
):
recipe_ingredient.unit_id = keep_unit_id
session.commit()
session.execute(
sa.text(f"DELETE FROM {IngredientUnitModel.__tablename__} WHERE id=:id").bindparams(id=dupe_unit_id)
)
session.commit()
def _resolve_duplicate_label(
session: Session,
keep_label_id: UUID4,
dupe_label_id: UUID4,
):
for shopping_list_item in (
session.query(ShoppingListItem)
.options(load_only(ShoppingListItem.id, ShoppingListItem.label_id))
.filter_by(label_id=dupe_label_id)
.all()
):
shopping_list_item.label_id = keep_label_id
for ingredient_food in (
session.query(IngredientFoodModel)
.options(load_only(IngredientFoodModel.id, IngredientFoodModel.label_id))
.filter_by(label_id=dupe_label_id)
.all()
):
ingredient_food.label_id = keep_label_id
session.commit()
session.execute(sa.text(f"DELETE FROM {MultiPurposeLabel.__tablename__} WHERE id=:id").bindparams(id=dupe_label_id))
session.commit()
def _resolve_duplicate_foods_units_labels(session: Session):
for model, resolve_func in [
(IngredientFoodModel, _resolve_duplicate_food),
(IngredientUnitModel, _resolve_duplicate_unit),
(MultiPurposeLabel, _resolve_duplicate_label),
]:
duplicate_map = _get_duplicates(session, model)
for ids in duplicate_map.values():
if len(ids) < 2:
continue
keep_id = ids[0]
for dupe_id in ids[1:]:
resolve_func(session, keep_id, dupe_id)
def _remove_duplicates_from_m2m_table(session: Session, table_meta: TableMeta):
if _is_postgres():
default_pk = "CTID"
else:
default_pk = "ROWID"
# some of these tables are missing defined unique pks, so we have to rely on the database default pk
query = sa.text(
f"""
DELETE FROM {table_meta.tablename}
WHERE EXISTS (
SELECT 1 FROM {table_meta.tablename} t2
WHERE {table_meta.tablename}.{table_meta.pk_1} = t2.{table_meta.pk_1}
AND {table_meta.tablename}.{table_meta.pk_2} = t2.{table_meta.pk_2}
AND {table_meta.tablename}.{default_pk} > t2.{default_pk}
)
"""
)
session.execute(query)
session.commit()
def _remove_duplicates_from_m2m_tables(session: Session, table_metas: list[TableMeta]):
for table_meta in table_metas:
_remove_duplicates_from_m2m_table(session, table_meta)
def upgrade():
bind = op.get_bind()
session = Session(bind=bind)
_resolve_duplicate_foods_units_labels(session)
_remove_duplicates_from_m2m_tables(
session,
[
TableMeta("cookbooks_to_categories", "cookbook_id", "category_id"),
TableMeta("cookbooks_to_tags", "cookbook_id", "tag_id"),
TableMeta("cookbooks_to_tools", "cookbook_id", "tool_id"),
TableMeta("group_to_categories", "group_id", "category_id"),
TableMeta("plan_rules_to_categories", "group_plan_rule_id", "category_id"),
TableMeta("plan_rules_to_tags", "plan_rule_id", "tag_id"),
TableMeta("recipes_to_categories", "recipe_id", "category_id"),
TableMeta("recipes_to_tags", "recipe_id", "tag_id"),
TableMeta("recipes_to_tools", "recipe_id", "tool_id"),
TableMeta("users_to_favorites", "user_id", "recipe_id"),
TableMeta("shopping_lists_multi_purpose_labels", "shopping_list_id", "label_id"),
],
)
session.commit()
# ### commands auto generated by Alembic - please adjust! ###
# we use batch_alter_table here because otherwise this fails on sqlite
# M2M
with op.batch_alter_table("cookbooks_to_categories") as batch_op:
batch_op.create_unique_constraint("cookbook_id_category_id_key", ["cookbook_id", "category_id"])
with op.batch_alter_table("cookbooks_to_tags") as batch_op:
batch_op.create_unique_constraint("cookbook_id_tag_id_key", ["cookbook_id", "tag_id"])
with op.batch_alter_table("cookbooks_to_tools") as batch_op:
batch_op.create_unique_constraint("cookbook_id_tool_id_key", ["cookbook_id", "tool_id"])
with op.batch_alter_table("group_to_categories") as batch_op:
batch_op.create_unique_constraint("group_id_category_id_key", ["group_id", "category_id"])
with op.batch_alter_table("plan_rules_to_categories") as batch_op:
batch_op.create_unique_constraint("group_plan_rule_id_category_id_key", ["group_plan_rule_id", "category_id"])
with op.batch_alter_table("plan_rules_to_tags") as batch_op:
batch_op.create_unique_constraint("plan_rule_id_tag_id_key", ["plan_rule_id", "tag_id"])
with op.batch_alter_table("recipes_to_categories") as batch_op:
batch_op.create_unique_constraint("recipe_id_category_id_key", ["recipe_id", "category_id"])
with op.batch_alter_table("recipes_to_tags") as batch_op:
batch_op.create_unique_constraint("recipe_id_tag_id_key", ["recipe_id", "tag_id"])
with op.batch_alter_table("recipes_to_tools") as batch_op:
batch_op.create_unique_constraint("recipe_id_tool_id_key", ["recipe_id", "tool_id"])
with op.batch_alter_table("users_to_favorites") as batch_op:
batch_op.create_unique_constraint("user_id_recipe_id_key", ["user_id", "recipe_id"])
with op.batch_alter_table("shopping_lists_multi_purpose_labels") as batch_op:
batch_op.create_unique_constraint("shopping_list_id_label_id_key", ["shopping_list_id", "label_id"])
# Foods/Units/Labels
with op.batch_alter_table("ingredient_foods") as batch_op:
batch_op.create_unique_constraint("ingredient_foods_name_group_id_key", ["name", "group_id"])
with op.batch_alter_table("ingredient_units") as batch_op:
batch_op.create_unique_constraint("ingredient_units_name_group_id_key", ["name", "group_id"])
with op.batch_alter_table("multi_purpose_labels") as batch_op:
batch_op.create_unique_constraint("multi_purpose_labels_name_group_id_key", ["name", "group_id"])
op.create_index(
op.f("ix_shopping_lists_multi_purpose_labels_created_at"),
"shopping_lists_multi_purpose_labels",
["created_at"],
unique=False,
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
# M2M
op.drop_constraint("user_id_recipe_id_key", "users_to_favorites", type_="unique")
op.drop_index(
op.f("ix_shopping_lists_multi_purpose_labels_created_at"), table_name="shopping_lists_multi_purpose_labels"
)
op.drop_constraint("recipe_id_tool_id_key", "recipes_to_tools", type_="unique")
op.drop_constraint("recipe_id_tag_id_key", "recipes_to_tags", type_="unique")
op.drop_constraint("recipe_id_category_id_key", "recipes_to_categories", type_="unique")
op.drop_constraint("plan_rule_id_tag_id_key", "plan_rules_to_tags", type_="unique")
op.drop_constraint("group_plan_rule_id_category_id_key", "plan_rules_to_categories", type_="unique")
op.drop_constraint("group_id_category_id_key", "group_to_categories", type_="unique")
op.drop_constraint("cookbook_id_tool_id_key", "cookbooks_to_tools", type_="unique")
op.drop_constraint("cookbook_id_tag_id_key", "cookbooks_to_tags", type_="unique")
op.drop_constraint("cookbook_id_category_id_key", "cookbooks_to_categories", type_="unique")
op.drop_constraint("shopping_list_id_label_id_key", "shopping_lists_multi_purpose_labels", type_="unique")
# Foods/Units/Labels
op.drop_constraint("multi_purpose_labels_name_group_id_key", "multi_purpose_labels", type_="unique")
op.drop_constraint("ingredient_units_name_group_id_key", "ingredient_units", type_="unique")
op.drop_constraint("ingredient_foods_name_group_id_key", "ingredient_foods", type_="unique")
# ### end Alembic commands ###

View File

@@ -0,0 +1,107 @@
"""added plural names and alias tables for foods and units
Revision ID: ba1e4a6cfe99
Revises: dded3119c1fe
Create Date: 2023-10-19 19:22:55.369319
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "ba1e4a6cfe99"
down_revision = "dded3119c1fe"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"ingredient_units_aliases",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("unit_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("name_normalized", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["unit_id"],
["ingredient_units.id"],
),
sa.PrimaryKeyConstraint("id", "unit_id"),
)
op.create_index(
op.f("ix_ingredient_units_aliases_created_at"), "ingredient_units_aliases", ["created_at"], unique=False
)
op.create_index(
op.f("ix_ingredient_units_aliases_name_normalized"),
"ingredient_units_aliases",
["name_normalized"],
unique=False,
)
op.create_table(
"ingredient_foods_aliases",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("food_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("name_normalized", sa.String(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["food_id"],
["ingredient_foods.id"],
),
sa.PrimaryKeyConstraint("id", "food_id"),
)
op.create_index(
op.f("ix_ingredient_foods_aliases_created_at"), "ingredient_foods_aliases", ["created_at"], unique=False
)
op.create_index(
op.f("ix_ingredient_foods_aliases_name_normalized"),
"ingredient_foods_aliases",
["name_normalized"],
unique=False,
)
op.add_column("ingredient_foods", sa.Column("plural_name", sa.String(), nullable=True))
op.add_column("ingredient_foods", sa.Column("plural_name_normalized", sa.String(), nullable=True))
op.create_index(
op.f("ix_ingredient_foods_plural_name_normalized"), "ingredient_foods", ["plural_name_normalized"], unique=False
)
op.add_column("ingredient_units", sa.Column("plural_name", sa.String(), nullable=True))
op.add_column("ingredient_units", sa.Column("plural_name_normalized", sa.String(), nullable=True))
op.create_index(
op.f("ix_ingredient_units_plural_name_normalized"), "ingredient_units", ["plural_name_normalized"], unique=False
)
op.add_column("ingredient_units", sa.Column("plural_abbreviation", sa.String(), nullable=True))
op.add_column("ingredient_units", sa.Column("plural_abbreviation_normalized", sa.String(), nullable=True))
op.create_index(
op.f("ix_ingredient_units_plural_abbreviation_normalized"),
"ingredient_units",
["plural_abbreviation_normalized"],
unique=False,
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_ingredient_units_plural_abbreviation_normalized"), table_name="ingredient_units")
op.drop_column("ingredient_units", "plural_abbreviation_normalized")
op.drop_column("ingredient_units", "plural_abbreviation")
op.drop_index(op.f("ix_ingredient_units_plural_name_normalized"), table_name="ingredient_units")
op.drop_column("ingredient_units", "plural_name_normalized")
op.drop_column("ingredient_units", "plural_name")
op.drop_index(op.f("ix_ingredient_foods_plural_name_normalized"), table_name="ingredient_foods")
op.drop_column("ingredient_foods", "plural_name_normalized")
op.drop_column("ingredient_foods", "plural_name")
op.drop_index(op.f("ix_ingredient_foods_aliases_name_normalized"), table_name="ingredient_foods_aliases")
op.drop_index(op.f("ix_ingredient_foods_aliases_created_at"), table_name="ingredient_foods_aliases")
op.drop_table("ingredient_foods_aliases")
op.drop_index(op.f("ix_ingredient_units_aliases_name_normalized"), table_name="ingredient_units_aliases")
op.drop_index(op.f("ix_ingredient_units_aliases_created_at"), table_name="ingredient_units_aliases")
op.drop_table("ingredient_units_aliases")
# ### end Alembic commands ###

View File

@@ -0,0 +1,101 @@
"""added user to shopping list
Revision ID: 2298bb460ffd
Revises: ba1e4a6cfe99
Create Date: 2024-02-23 16:15:07.115641
"""
from uuid import UUID
import sqlalchemy as sa
from alembic import op
from sqlalchemy import orm
import mealie.db.migration_types
from mealie.core.root_logger import get_logger
logger = get_logger()
# revision identifiers, used by Alembic.
revision = "2298bb460ffd"
down_revision = "ba1e4a6cfe99"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
return op.get_context().dialect.name == "postgresql"
def find_user_id_for_group(group_id: UUID):
bind = op.get_bind()
session = orm.Session(bind=bind)
if is_postgres():
stmt = "SELECT id FROM users WHERE group_id=:group_id AND admin = TRUE LIMIT 1"
else:
stmt = "SELECT id FROM users WHERE group_id=:group_id AND admin = 1 LIMIT 1"
with session:
try:
# try to find an admin user
return session.execute(sa.text(stmt).bindparams(group_id=group_id)).scalar_one()
except orm.exc.NoResultFound:
pass
try:
# fallback to any user
return session.execute(
sa.text("SELECT id FROM users WHERE group_id=:group_id LIMIT 1").bindparams(group_id=group_id)
).scalar_one()
except orm.exc.NoResultFound:
pass
# no user could be found
return None
def populate_shopping_list_users():
bind = op.get_bind()
session = orm.Session(bind=bind)
with session:
list_ids_and_group_ids = session.execute(sa.text("SELECT id, group_id FROM shopping_lists")).all()
for list_id, group_id in list_ids_and_group_ids:
user_id = find_user_id_for_group(group_id)
if user_id:
session.execute(
sa.text("UPDATE shopping_lists SET user_id=:user_id WHERE id=:id").bindparams(
user_id=user_id, id=list_id
)
)
else:
logger.warning(
f"No user found for shopping list {list_id} with group {group_id}; deleting shopping list"
)
session.execute(sa.text("DELETE FROM shopping_lists WHERE id=:id").bindparams(id=list_id))
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("shopping_lists") as batch_op:
# allow nulls during migration
batch_op.add_column(sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_shopping_lists_user_id"), ["user_id"], unique=False)
batch_op.create_foreign_key("fk_user_shopping_lists", "users", ["user_id"], ["id"])
# ### end Alembic commands ###
populate_shopping_list_users()
# forbid nulls after migration
with op.batch_alter_table("shopping_lists") as batch_op:
batch_op.alter_column("user_id", nullable=False)
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, "shopping_lists", type_="foreignkey")
op.drop_index(op.f("ix_shopping_lists_user_id"), table_name="shopping_lists")
op.drop_column("shopping_lists", "user_id")
# ### end Alembic commands ###

View File

@@ -0,0 +1,28 @@
"""add OIDC auth method
Revision ID: 09aba125b57a
Revises: 2298bb460ffd
Create Date: 2024-03-10 05:08:32.397027
"""
from alembic import op
# revision identifiers, used by Alembic.
revision = "09aba125b57a"
down_revision = "2298bb460ffd"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
return op.get_context().dialect.name == "postgresql"
def upgrade():
if is_postgres():
op.execute("ALTER TYPE authmethod ADD VALUE 'OIDC'")
def downgrade():
pass

View File

@@ -0,0 +1,227 @@
"""migrate favorites and ratings to user_ratings
Revision ID: d7c6efd2de42
Revises: 09aba125b57a
Create Date: 2024-03-18 02:28:15.896959
"""
from datetime import datetime, timezone
from textwrap import dedent
from typing import Any
from uuid import uuid4
import sqlalchemy as sa
from alembic import op
from sqlalchemy import orm
import mealie.db.migration_types
# revision identifiers, used by Alembic.
revision = "d7c6efd2de42"
down_revision = "09aba125b57a"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
return op.get_context().dialect.name == "postgresql"
def new_user_rating(user_id: Any, recipe_id: Any, rating: float | None = None, is_favorite: bool = False):
if is_postgres():
id = str(uuid4())
else:
id = "%.32x" % uuid4().int # noqa: UP031
now = datetime.now(timezone.utc).isoformat()
return {
"id": id,
"user_id": user_id,
"recipe_id": recipe_id,
"rating": rating,
"is_favorite": is_favorite,
"created_at": now,
"update_at": now,
}
def migrate_user_favorites_to_user_ratings():
bind = op.get_bind()
session = orm.Session(bind=bind)
with session:
user_ids_and_recipe_ids = session.execute(sa.text("SELECT user_id, recipe_id FROM users_to_favorites")).all()
rows = [
new_user_rating(user_id, recipe_id, is_favorite=True)
for user_id, recipe_id in user_ids_and_recipe_ids
if user_id and recipe_id
]
if is_postgres():
query = dedent(
"""
INSERT INTO users_to_recipes (id, user_id, recipe_id, rating, is_favorite, created_at, update_at)
VALUES (:id, :user_id, :recipe_id, :rating, :is_favorite, :created_at, :update_at)
ON CONFLICT DO NOTHING
"""
)
else:
query = dedent(
"""
INSERT OR IGNORE INTO users_to_recipes
(id, user_id, recipe_id, rating, is_favorite, created_at, update_at)
VALUES (:id, :user_id, :recipe_id, :rating, :is_favorite, :created_at, :update_at)
"""
)
for row in rows:
session.execute(sa.text(query), row)
def migrate_group_to_user_ratings(group_id: Any):
bind = op.get_bind()
session = orm.Session(bind=bind)
with session:
user_ids = (
session.execute(sa.text("SELECT id FROM users WHERE group_id=:group_id").bindparams(group_id=group_id))
.scalars()
.all()
)
recipe_ids_ratings = session.execute(
sa.text(
"SELECT id, rating FROM recipes WHERE group_id=:group_id AND rating > 0 AND rating IS NOT NULL"
).bindparams(group_id=group_id)
).all()
# Convert recipe ratings to user ratings. Since we don't know who
# rated the recipe initially, we copy the rating to all users.
rows: list[dict] = []
for recipe_id, rating in recipe_ids_ratings:
for user_id in user_ids:
rows.append(new_user_rating(user_id, recipe_id, rating, is_favorite=False))
if is_postgres():
insert_query = dedent(
"""
INSERT INTO users_to_recipes (id, user_id, recipe_id, rating, is_favorite, created_at, update_at)
VALUES (:id, :user_id, :recipe_id, :rating, :is_favorite, :created_at, :update_at)
ON CONFLICT (user_id, recipe_id) DO NOTHING;
"""
)
else:
insert_query = dedent(
"""
INSERT OR IGNORE INTO users_to_recipes
(id, user_id, recipe_id, rating, is_favorite, created_at, update_at)
VALUES (:id, :user_id, :recipe_id, :rating, :is_favorite, :created_at, :update_at);
"""
)
update_query = dedent(
"""
UPDATE users_to_recipes
SET rating = :rating, update_at = :update_at
WHERE user_id = :user_id AND recipe_id = :recipe_id;
"""
)
# Create new user ratings with is_favorite set to False
for row in rows:
session.execute(sa.text(insert_query), row)
# Update existing user ratings with the correct rating
for row in rows:
session.execute(sa.text(update_query), row)
def migrate_to_user_ratings():
migrate_user_favorites_to_user_ratings()
bind = op.get_bind()
session = orm.Session(bind=bind)
with session:
group_ids = session.execute(sa.text("SELECT id FROM groups")).scalars().all()
for group_id in group_ids:
migrate_group_to_user_ratings(group_id)
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"users_to_recipes",
sa.Column("user_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("recipe_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("rating", sa.Float(), nullable=True),
sa.Column("is_favorite", sa.Boolean(), nullable=False),
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("user_id", "recipe_id", "id"),
sa.UniqueConstraint("user_id", "recipe_id", name="user_id_recipe_id_rating_key"),
)
op.create_index(op.f("ix_users_to_recipes_created_at"), "users_to_recipes", ["created_at"], unique=False)
op.create_index(op.f("ix_users_to_recipes_is_favorite"), "users_to_recipes", ["is_favorite"], unique=False)
op.create_index(op.f("ix_users_to_recipes_rating"), "users_to_recipes", ["rating"], unique=False)
op.create_index(op.f("ix_users_to_recipes_recipe_id"), "users_to_recipes", ["recipe_id"], unique=False)
op.create_index(op.f("ix_users_to_recipes_user_id"), "users_to_recipes", ["user_id"], unique=False)
migrate_to_user_ratings()
if is_postgres():
op.drop_index("ix_users_to_favorites_recipe_id", table_name="users_to_favorites")
op.drop_index("ix_users_to_favorites_user_id", table_name="users_to_favorites")
op.alter_column("recipes", "rating", existing_type=sa.INTEGER(), type_=sa.Float(), existing_nullable=True)
else:
op.execute("DROP INDEX IF EXISTS ix_users_to_favorites_recipe_id")
op.execute("DROP INDEX IF EXISTS ix_users_to_favorites_user_id")
with op.batch_alter_table("recipes") as batch_op:
batch_op.alter_column("rating", existing_type=sa.INTEGER(), type_=sa.Float(), existing_nullable=True)
op.drop_table("users_to_favorites")
op.create_index(op.f("ix_recipes_rating"), "recipes", ["rating"], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(
"recipes_ingredients", "quantity", existing_type=sa.Float(), type_=sa.INTEGER(), existing_nullable=True
)
op.drop_index(op.f("ix_recipes_rating"), table_name="recipes")
op.alter_column("recipes", "rating", existing_type=sa.Float(), type_=sa.INTEGER(), existing_nullable=True)
op.create_table(
"users_to_favorites",
sa.Column("user_id", sa.CHAR(length=32), nullable=True),
sa.Column("recipe_id", sa.CHAR(length=32), nullable=True),
sa.ForeignKeyConstraint(
["recipe_id"],
["recipes.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.UniqueConstraint("user_id", "recipe_id", name="user_id_recipe_id_key"),
)
op.create_index("ix_users_to_favorites_user_id", "users_to_favorites", ["user_id"], unique=False)
op.create_index("ix_users_to_favorites_recipe_id", "users_to_favorites", ["recipe_id"], unique=False)
op.drop_index(op.f("ix_users_to_recipes_user_id"), table_name="users_to_recipes")
op.drop_index(op.f("ix_users_to_recipes_recipe_id"), table_name="users_to_recipes")
op.drop_index(op.f("ix_users_to_recipes_rating"), table_name="users_to_recipes")
op.drop_index(op.f("ix_users_to_recipes_is_favorite"), table_name="users_to_recipes")
op.drop_index(op.f("ix_users_to_recipes_created_at"), table_name="users_to_recipes")
op.drop_table("users_to_recipes")
# ### end Alembic commands ###

View File

@@ -0,0 +1,52 @@
"""add group recipe actions
Revision ID: 7788478a0338
Revises: d7c6efd2de42
Create Date: 2024-04-07 01:05:20.816270
"""
import sqlalchemy as sa
from alembic import op
import mealie.db.migration_types
# revision identifiers, used by Alembic.
revision = "7788478a0338"
down_revision = "d7c6efd2de42"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"recipe_actions",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("action_type", sa.String(), nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("url", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_recipe_actions_action_type"), "recipe_actions", ["action_type"], unique=False)
op.create_index(op.f("ix_recipe_actions_created_at"), "recipe_actions", ["created_at"], unique=False)
op.create_index(op.f("ix_recipe_actions_group_id"), "recipe_actions", ["group_id"], unique=False)
op.create_index(op.f("ix_recipe_actions_title"), "recipe_actions", ["title"], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_recipe_actions_title"), table_name="recipe_actions")
op.drop_index(op.f("ix_recipe_actions_group_id"), table_name="recipe_actions")
op.drop_index(op.f("ix_recipe_actions_created_at"), table_name="recipe_actions")
op.drop_index(op.f("ix_recipe_actions_action_type"), table_name="recipe_actions")
op.drop_table("recipe_actions")
# ### end Alembic commands ###

View File

@@ -0,0 +1,46 @@
"""Add staple flag to foods
Revision ID: 32d69327997b
Revises: 7788478a0338
Create Date: 2024-06-22 10:17:03.323966
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy import orm
# revision identifiers, used by Alembic.
revision = "32d69327997b"
down_revision = "7788478a0338"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
return op.get_context().dialect.name == "postgresql"
def upgrade():
with op.batch_alter_table("ingredient_foods") as batch_op:
batch_op.add_column(sa.Column("on_hand", sa.Boolean(), nullable=True, default=False))
bind = op.get_bind()
session = orm.Session(bind=bind)
with session:
if is_postgres():
stmt = "UPDATE ingredient_foods SET on_hand = FALSE;"
else:
stmt = "UPDATE ingredient_foods SET on_hand = 0;"
session.execute(sa.text(stmt))
# forbid nulls after migration
with op.batch_alter_table("ingredient_foods") as batch_op:
batch_op.alter_column("on_hand", nullable=False)
def downgrade():
with op.batch_alter_table("ingredient_foods") as batch_op:
batch_op.drop_column("on_hand")

View File

@@ -0,0 +1,321 @@
"""add households
Revision ID: feecc8ffb956
Revises: 32d69327997b
Create Date: 2024-07-12 16:16:29.973929
"""
from datetime import datetime, timezone
from textwrap import dedent
from typing import Any
from uuid import uuid4
import sqlalchemy as sa
from alembic import op
from slugify import slugify
from sqlalchemy import orm
import mealie.db.migration_types
from mealie.core.config import get_app_settings
# revision identifiers, used by Alembic.
revision = "feecc8ffb956"
down_revision = "32d69327997b"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
settings = get_app_settings()
def is_postgres():
return op.get_context().dialect.name == "postgresql"
def generate_id() -> str:
"""See GUID.convert_value_to_guid"""
val = uuid4()
if is_postgres():
return str(val)
else:
return f"{val.int:032x}"
def dedupe_cookbook_slugs():
bind = op.get_bind()
session = orm.Session(bind=bind)
with session:
sql = sa.text(
dedent(
"""
SELECT slug, group_id, COUNT(*)
FROM cookbooks
GROUP BY slug, group_id
HAVING COUNT(*) > 1
"""
)
)
rows = session.execute(sql).fetchall()
for slug, group_id, _ in rows:
sql = sa.text(
dedent(
"""
SELECT id
FROM cookbooks
WHERE slug = :slug AND group_id = :group_id
ORDER BY id
"""
)
)
cookbook_ids = session.execute(sql, {"slug": slug, "group_id": group_id}).fetchall()
for i, (cookbook_id,) in enumerate(cookbook_ids):
if i == 0:
continue
sql = sa.text(
dedent(
"""
UPDATE cookbooks
SET slug = :slug || '-' || :i
WHERE id = :id
"""
)
)
session.execute(sql, {"slug": slug, "i": i, "id": cookbook_id})
def create_household(session: orm.Session, group_id: str) -> str:
# create/insert household
household_id = generate_id()
timestamp = datetime.now(timezone.utc).isoformat()
household_data = {
"id": household_id,
"name": settings.DEFAULT_HOUSEHOLD,
"slug": slugify(settings.DEFAULT_HOUSEHOLD),
"group_id": group_id,
"created_at": timestamp,
"update_at": timestamp,
}
columns = ", ".join(household_data.keys())
placeholders = ", ".join(f":{key}" for key in household_data.keys())
sql_statement = f"INSERT INTO households ({columns}) VALUES ({placeholders})"
session.execute(sa.text(sql_statement), household_data)
# fetch group preferences so we can copy them over to household preferences
migrated_field_defaults = {
"private_group": True, # this is renamed later
"first_day_of_week": 0,
"recipe_public": True,
"recipe_show_nutrition": False,
"recipe_show_assets": False,
"recipe_landscape_view": False,
"recipe_disable_comments": False,
"recipe_disable_amount": True,
}
sql_statement = (
f"SELECT {', '.join(migrated_field_defaults.keys())} FROM group_preferences WHERE group_id = :group_id"
)
group_preferences = session.execute(sa.text(sql_statement), {"group_id": group_id}).fetchone()
# build preferences data
if group_preferences:
preferences_data: dict[str, Any] = {}
for i, (field, default_value) in enumerate(migrated_field_defaults.items()):
value = group_preferences[i]
preferences_data[field] = value if value is not None else default_value
else:
preferences_data = migrated_field_defaults
preferences_data["id"] = generate_id()
preferences_data["household_id"] = household_id
preferences_data["created_at"] = timestamp
preferences_data["update_at"] = timestamp
preferences_data["private_household"] = preferences_data.pop("private_group")
# insert preferences data
columns = ", ".join(preferences_data.keys())
placeholders = ", ".join(f":{key}" for key in preferences_data.keys())
sql_statement = f"INSERT INTO household_preferences ({columns}) VALUES ({placeholders})"
session.execute(sa.text(sql_statement), preferences_data)
return household_id
def create_households_for_groups() -> dict[str, str]:
bind = op.get_bind()
session = orm.Session(bind=bind)
group_id_household_id_map: dict[str, str] = {}
with session:
rows = session.execute(sa.text("SELECT id FROM groups")).fetchall()
for row in rows:
group_id = row[0]
group_id_household_id_map[group_id] = create_household(session, group_id)
return group_id_household_id_map
def _do_assignment(session: orm.Session, table: str, group_id: str, household_id: str):
sql = sa.text(
dedent(
f"""
UPDATE {table}
SET household_id = :household_id
WHERE group_id = :group_id
""",
)
)
session.execute(sql, {"group_id": group_id, "household_id": household_id})
def assign_households(group_id_household_id_map: dict[str, str]):
tables = [
"cookbooks",
"group_events_notifiers",
"group_meal_plan_rules",
"invite_tokens",
"recipe_actions",
"users",
"webhook_urls",
]
bind = op.get_bind()
session = orm.Session(bind=bind)
with session:
for table in tables:
for group_id, household_id in group_id_household_id_map.items():
_do_assignment(session, table, group_id, household_id)
def populate_household_data():
group_id_household_id_map = create_households_for_groups()
assign_households(group_id_household_id_map)
def upgrade():
dedupe_cookbook_slugs()
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"households",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("group_id", "name", name="household_name_group_id_key"),
sa.UniqueConstraint("group_id", "slug", name="household_slug_group_id_key"),
)
op.create_index(op.f("ix_households_created_at"), "households", ["created_at"], unique=False)
op.create_index(op.f("ix_households_group_id"), "households", ["group_id"], unique=False)
op.create_index(op.f("ix_households_name"), "households", ["name"], unique=False)
op.create_index(op.f("ix_households_slug"), "households", ["slug"], unique=False)
op.create_table(
"household_preferences",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("private_household", sa.Boolean(), nullable=True),
sa.Column("first_day_of_week", sa.Integer(), nullable=True),
sa.Column("recipe_public", sa.Boolean(), nullable=True),
sa.Column("recipe_show_nutrition", sa.Boolean(), nullable=True),
sa.Column("recipe_show_assets", sa.Boolean(), nullable=True),
sa.Column("recipe_landscape_view", sa.Boolean(), nullable=True),
sa.Column("recipe_disable_comments", sa.Boolean(), nullable=True),
sa.Column("recipe_disable_amount", sa.Boolean(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["household_id"],
["households.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_household_preferences_created_at"), "household_preferences", ["created_at"], unique=False)
op.create_index(
op.f("ix_household_preferences_household_id"), "household_preferences", ["household_id"], unique=False
)
with op.batch_alter_table("cookbooks") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_cookbooks_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_cookbooks_household_id", "households", ["household_id"], ["id"])
# not directly related to households, but important for frontend routes
batch_op.create_unique_constraint("cookbook_slug_group_id_key", ["slug", "group_id"])
with op.batch_alter_table("group_events_notifiers") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_group_events_notifiers_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_group_events_notifiers_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("group_meal_plan_rules") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_group_meal_plan_rules_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_group_meal_plan_rules_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("invite_tokens") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_invite_tokens_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_invite_tokens_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("recipe_actions") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_recipe_actions_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_recipe_actions_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("users") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_users_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_users_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("webhook_urls") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_webhook_urls_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_webhook_urls_household_id", "households", ["household_id"], ["id"])
# ### end Alembic commands ###
populate_household_data()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, "webhook_urls", type_="foreignkey")
op.drop_index(op.f("ix_webhook_urls_household_id"), table_name="webhook_urls")
op.drop_column("webhook_urls", "household_id")
op.drop_constraint(None, "users", type_="foreignkey")
op.drop_index(op.f("ix_users_household_id"), table_name="users")
op.drop_column("users", "household_id")
op.drop_constraint(None, "recipe_actions", type_="foreignkey")
op.drop_index(op.f("ix_recipe_actions_household_id"), table_name="recipe_actions")
op.drop_column("recipe_actions", "household_id")
op.drop_constraint(None, "invite_tokens", type_="foreignkey")
op.drop_index(op.f("ix_invite_tokens_household_id"), table_name="invite_tokens")
op.drop_column("invite_tokens", "household_id")
op.drop_constraint(None, "group_meal_plan_rules", type_="foreignkey")
op.drop_index(op.f("ix_group_meal_plan_rules_household_id"), table_name="group_meal_plan_rules")
op.drop_column("group_meal_plan_rules", "household_id")
op.drop_constraint(None, "group_events_notifiers", type_="foreignkey")
op.drop_index(op.f("ix_group_events_notifiers_household_id"), table_name="group_events_notifiers")
op.drop_column("group_events_notifiers", "household_id")
op.drop_constraint(None, "cookbooks", type_="foreignkey")
op.drop_index(op.f("ix_cookbooks_household_id"), table_name="cookbooks")
op.drop_column("cookbooks", "household_id")
op.drop_constraint("cookbook_slug_group_id_key", "cookbooks", type_="unique")
op.drop_index(op.f("ix_household_preferences_household_id"), table_name="household_preferences")
op.drop_index(op.f("ix_household_preferences_created_at"), table_name="household_preferences")
op.drop_table("household_preferences")
op.drop_index(op.f("ix_households_slug"), table_name="households")
op.drop_index(op.f("ix_households_name"), table_name="households")
op.drop_index(op.f("ix_households_group_id"), table_name="households")
op.drop_index(op.f("ix_households_created_at"), table_name="households")
op.drop_table("households")
# ### end Alembic commands ###

View File

@@ -0,0 +1,74 @@
"""added household recipe lock setting and household management user permission
Revision ID: be568e39ffdf
Revises: feecc8ffb956
Create Date: 2024-09-02 21:39:49.210355
"""
from textwrap import dedent
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "be568e39ffdf"
down_revision = "feecc8ffb956"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def populate_defaults():
if op.get_context().dialect.name == "postgresql":
TRUE = "TRUE"
FALSE = "FALSE"
else:
TRUE = "1"
FALSE = "0"
op.execute(
dedent(
f"""
UPDATE household_preferences
SET lock_recipe_edits_from_other_households = {TRUE}
WHERE lock_recipe_edits_from_other_households IS NULL
"""
)
)
op.execute(
dedent(
f"""
UPDATE users
SET can_manage_household = {FALSE}
WHERE can_manage_household IS NULL AND admin = {FALSE}
"""
)
)
op.execute(
dedent(
f"""
UPDATE users
SET can_manage_household = {TRUE}
WHERE can_manage_household IS NULL AND admin = {TRUE}
"""
)
)
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"household_preferences",
sa.Column("lock_recipe_edits_from_other_households", sa.Boolean(), nullable=True),
)
op.add_column("users", sa.Column("can_manage_household", sa.Boolean(), nullable=True))
# ### end Alembic commands ###
populate_defaults()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("users", "can_manage_household")
op.drop_column("household_preferences", "lock_recipe_edits_from_other_households")
# ### end Alembic commands ###

View File

@@ -0,0 +1,53 @@
"""add households filter to meal plans
Revision ID: 1fe4bd37ccc8
Revises: be568e39ffdf
Create Date: 2024-09-18 14:52:55.831540
"""
import sqlalchemy as sa
from alembic import op
import mealie.db.migration_types
# revision identifiers, used by Alembic.
revision = "1fe4bd37ccc8"
down_revision: str | None = "be568e39ffdf"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"plan_rules_to_households",
sa.Column("group_plan_rule_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_plan_rule_id"],
["group_meal_plan_rules.id"],
),
sa.ForeignKeyConstraint(
["household_id"],
["households.id"],
),
sa.UniqueConstraint("group_plan_rule_id", "household_id", name="group_plan_rule_id_household_id_key"),
)
with op.batch_alter_table("plan_rules_to_households", schema=None) as batch_op:
batch_op.create_index(
batch_op.f("ix_plan_rules_to_households_group_plan_rule_id"), ["group_plan_rule_id"], unique=False
)
batch_op.create_index(batch_op.f("ix_plan_rules_to_households_household_id"), ["household_id"], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("plan_rules_to_households", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_plan_rules_to_households_household_id"))
batch_op.drop_index(batch_op.f("ix_plan_rules_to_households_group_plan_rule_id"))
op.drop_table("plan_rules_to_households")
# ### end Alembic commands ###

View File

@@ -0,0 +1,38 @@
"""'add the rest of the schema.org nutrition properties'
Revision ID: 602927e1013e
Revises: 1fe4bd37ccc8
Create Date: 2024-10-01 14:17:00.611398
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "602927e1013e"
down_revision: str | None = "1fe4bd37ccc8"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipe_nutrition", schema=None) as batch_op:
batch_op.add_column(sa.Column("cholesterol_content", sa.String(), nullable=True))
batch_op.add_column(sa.Column("saturated_fat_content", sa.String(), nullable=True))
batch_op.add_column(sa.Column("trans_fat_content", sa.String(), nullable=True))
batch_op.add_column(sa.Column("unsaturated_fat_content", sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipe_nutrition", schema=None) as batch_op:
batch_op.drop_column("unsaturated_fat_content")
batch_op.drop_column("trans_fat_content")
batch_op.drop_column("saturated_fat_content")
batch_op.drop_column("cholesterol_content")
# ### end Alembic commands ###

View File

@@ -0,0 +1,188 @@
"""added query_filter_string to cookbook and mealplan
Revision ID: 86054b40fd06
Revises: 602927e1013e
Create Date: 2024-10-08 21:17:31.601903
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy import orm
from mealie.db.models._model_utils import guid
# revision identifiers, used by Alembic.
revision = "86054b40fd06"
down_revision: str | None = "602927e1013e"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Intermediate table definitions
class SqlAlchemyBase(orm.DeclarativeBase):
pass
class Category(SqlAlchemyBase):
__tablename__ = "categories"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
class Tag(SqlAlchemyBase):
__tablename__ = "tags"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
class Tool(SqlAlchemyBase):
__tablename__ = "tools"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
class Household(SqlAlchemyBase):
__tablename__ = "households"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
cookbooks_to_categories = sa.Table(
"cookbooks_to_categories",
SqlAlchemyBase.metadata,
sa.Column("cookbook_id", guid.GUID, sa.ForeignKey("cookbooks.id"), index=True),
sa.Column("category_id", guid.GUID, sa.ForeignKey("categories.id"), index=True),
)
cookbooks_to_tags = sa.Table(
"cookbooks_to_tags",
SqlAlchemyBase.metadata,
sa.Column("cookbook_id", guid.GUID, sa.ForeignKey("cookbooks.id"), index=True),
sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id"), index=True),
)
cookbooks_to_tools = sa.Table(
"cookbooks_to_tools",
SqlAlchemyBase.metadata,
sa.Column("cookbook_id", guid.GUID, sa.ForeignKey("cookbooks.id"), index=True),
sa.Column("tool_id", guid.GUID, sa.ForeignKey("tools.id"), index=True),
)
plan_rules_to_categories = sa.Table(
"plan_rules_to_categories",
SqlAlchemyBase.metadata,
sa.Column("group_plan_rule_id", guid.GUID, sa.ForeignKey("group_meal_plan_rules.id"), index=True),
sa.Column("category_id", guid.GUID, sa.ForeignKey("categories.id"), index=True),
)
plan_rules_to_tags = sa.Table(
"plan_rules_to_tags",
SqlAlchemyBase.metadata,
sa.Column("plan_rule_id", guid.GUID, sa.ForeignKey("group_meal_plan_rules.id"), index=True),
sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id"), index=True),
)
plan_rules_to_households = sa.Table(
"plan_rules_to_households",
SqlAlchemyBase.metadata,
sa.Column("group_plan_rule_id", guid.GUID, sa.ForeignKey("group_meal_plan_rules.id"), index=True),
sa.Column("household_id", guid.GUID, sa.ForeignKey("households.id"), index=True),
)
class CookBook(SqlAlchemyBase):
__tablename__ = "cookbooks"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
query_filter_string: orm.Mapped[str] = orm.mapped_column(sa.String, nullable=False, default="")
categories: orm.Mapped[list[Category]] = orm.relationship(
Category, secondary=cookbooks_to_categories, single_parent=True
)
require_all_categories: orm.Mapped[bool | None] = orm.mapped_column(sa.Boolean, default=True)
tags: orm.Mapped[list[Tag]] = orm.relationship(Tag, secondary=cookbooks_to_tags, single_parent=True)
require_all_tags: orm.Mapped[bool | None] = orm.mapped_column(sa.Boolean, default=True)
tools: orm.Mapped[list[Tool]] = orm.relationship(Tool, secondary=cookbooks_to_tools, single_parent=True)
require_all_tools: orm.Mapped[bool | None] = orm.mapped_column(sa.Boolean, default=True)
class GroupMealPlanRules(SqlAlchemyBase):
__tablename__ = "group_meal_plan_rules"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
query_filter_string: orm.Mapped[str] = orm.mapped_column(sa.String, nullable=False, default="")
categories: orm.Mapped[list[Category]] = orm.relationship(Category, secondary=plan_rules_to_categories)
tags: orm.Mapped[list[Tag]] = orm.relationship(Tag, secondary=plan_rules_to_tags)
households: orm.Mapped[list["Household"]] = orm.relationship("Household", secondary=plan_rules_to_households)
def migrate_cookbooks():
bind = op.get_bind()
session = orm.Session(bind=bind)
cookbooks = session.query(CookBook).all()
for cookbook in cookbooks:
parts = []
if cookbook.categories:
relop = "CONTAINS ALL" if cookbook.require_all_categories else "IN"
vals = ",".join([f'"{cat.id}"' for cat in cookbook.categories])
parts.append(f"recipe_category.id {relop} [{vals}]")
if cookbook.tags:
relop = "CONTAINS ALL" if cookbook.require_all_tags else "IN"
vals = ",".join([f'"{tag.id}"' for tag in cookbook.tags])
parts.append(f"tags.id {relop} [{vals}]")
if cookbook.tools:
relop = "CONTAINS ALL" if cookbook.require_all_tools else "IN"
vals = ",".join([f'"{tool.id}"' for tool in cookbook.tools])
parts.append(f"tools.id {relop} [{vals}]")
cookbook.query_filter_string = " AND ".join(parts)
session.commit()
def migrate_mealplan_rules():
bind = op.get_bind()
session = orm.Session(bind=bind)
rules = session.query(GroupMealPlanRules).all()
for rule in rules:
parts = []
if rule.categories:
vals = ",".join([f'"{cat.id}"' for cat in rule.categories])
parts.append(f"recipe_category.id CONTAINS ALL [{vals}]")
if rule.tags:
vals = ",".join([f'"{tag.id}"' for tag in rule.tags])
parts.append(f"tags.id CONTAINS ALL [{vals}]")
if rule.households:
vals = ",".join([f'"{household.id}"' for household in rule.households])
parts.append(f"household_id IN [{vals}]")
rule.query_filter_string = " AND ".join(parts)
session.commit()
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("cookbooks", schema=None) as batch_op:
batch_op.add_column(sa.Column("query_filter_string", sa.String(), nullable=False, server_default=""))
with op.batch_alter_table("group_meal_plan_rules", schema=None) as batch_op:
batch_op.add_column(sa.Column("query_filter_string", sa.String(), nullable=False, server_default=""))
# ### end Alembic commands ###
migrate_cookbooks()
migrate_mealplan_rules()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("group_meal_plan_rules", schema=None) as batch_op:
batch_op.drop_column("query_filter_string")
with op.batch_alter_table("cookbooks", schema=None) as batch_op:
batch_op.drop_column("query_filter_string")
# ### end Alembic commands ###

View File

@@ -0,0 +1,32 @@
"""'Add summary to recipe instructions'
Revision ID: 3897397b4631
Revises: 86054b40fd06
Create Date: 2024-10-20 09:47:46.844436
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "3897397b4631"
down_revision: str | None = "86054b40fd06"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipe_instructions", schema=None) as batch_op:
batch_op.add_column(sa.Column("summary", sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipe_instructions", schema=None) as batch_op:
batch_op.drop_column("summary")
# ### end Alembic commands ###

View File

@@ -0,0 +1,72 @@
"""add recipe yield quantity
Revision ID: b1020f328e98
Revises: 3897397b4631
Create Date: 2024-10-23 15:50:59.888793
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy import orm
from mealie.db.models._model_utils.guid import GUID
from mealie.services.scraper.cleaner import clean_yield
# revision identifiers, used by Alembic.
revision = "b1020f328e98"
down_revision: str | None = "3897397b4631"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Intermediate table definitions
class SqlAlchemyBase(orm.DeclarativeBase):
pass
class RecipeModel(SqlAlchemyBase):
__tablename__ = "recipes"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
recipe_yield: orm.Mapped[str | None] = orm.mapped_column(sa.String)
recipe_yield_quantity: orm.Mapped[float] = orm.mapped_column(sa.Float, index=True, default=0)
recipe_servings: orm.Mapped[float] = orm.mapped_column(sa.Float, index=True, default=0)
def parse_recipe_yields():
bind = op.get_bind()
session = orm.Session(bind=bind)
for recipe in session.query(RecipeModel).all():
try:
recipe.recipe_servings, recipe.recipe_yield_quantity, recipe.recipe_yield = clean_yield(recipe.recipe_yield)
except Exception:
recipe.recipe_servings = 0
recipe.recipe_yield_quantity = 0
session.commit()
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipes", schema=None) as batch_op:
batch_op.add_column(sa.Column("recipe_yield_quantity", sa.Float(), nullable=False, server_default="0"))
batch_op.create_index(batch_op.f("ix_recipes_recipe_yield_quantity"), ["recipe_yield_quantity"], unique=False)
batch_op.add_column(sa.Column("recipe_servings", sa.Float(), nullable=False, server_default="0"))
batch_op.create_index(batch_op.f("ix_recipes_recipe_servings"), ["recipe_servings"], unique=False)
# ### end Alembic commands ###
parse_recipe_yields()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipes", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_recipes_recipe_servings"))
batch_op.drop_column("recipe_servings")
batch_op.drop_index(batch_op.f("ix_recipes_recipe_yield_quantity"))
batch_op.drop_column("recipe_yield_quantity")
# ### end Alembic commands ###