Public Access
feat(meals): enforce three lower-calorie dinners with media
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""Add global recipe import staging and provenance storage.
|
||||
|
||||
Revision ID: 0017
|
||||
Revises: 0016
|
||||
Create Date: 2026-08-14
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "0017"
|
||||
down_revision: Union[str, None] = "0016"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
recipe_import_status_enum = postgresql.ENUM(
|
||||
"pending",
|
||||
"quarantined",
|
||||
"approved",
|
||||
"rejected",
|
||||
name="recipe_import_status_enum",
|
||||
create_type=False,
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
recipe_import_status_enum.create(op.get_bind(), checkfirst=True)
|
||||
op.create_table(
|
||||
"recipe_import_staging",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("source", sa.String(length=100), nullable=False),
|
||||
sa.Column("external_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("source_url", sa.Text(), nullable=False),
|
||||
sa.Column("source_revision", sa.String(length=255), nullable=False),
|
||||
sa.Column("license_name", sa.String(length=255), nullable=False),
|
||||
sa.Column("license_url", sa.Text(), nullable=False),
|
||||
sa.Column("attribution", sa.Text(), nullable=False),
|
||||
sa.Column("raw_payload", postgresql.JSONB(), nullable=False),
|
||||
sa.Column("payload_sha256", sa.String(length=64), nullable=False),
|
||||
sa.Column("normalized_recipe", postgresql.JSONB(), nullable=False),
|
||||
sa.Column("validation_report", postgresql.JSONB(), nullable=False),
|
||||
sa.Column(
|
||||
"status",
|
||||
recipe_import_status_enum,
|
||||
nullable=False,
|
||||
server_default="pending",
|
||||
),
|
||||
sa.Column("rejection_reason", sa.Text(), nullable=True),
|
||||
sa.Column("recipe_id", postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column(
|
||||
"acquired_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.text("now()"),
|
||||
),
|
||||
sa.Column("reviewed_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("reviewed_by", sa.String(length=300), nullable=True),
|
||||
sa.CheckConstraint(
|
||||
"payload_sha256 ~ '^[0-9a-f]{64}$'",
|
||||
name="ck_recipe_import_staging_payload_sha256",
|
||||
),
|
||||
sa.CheckConstraint(
|
||||
"(reviewed_at IS NULL) = (reviewed_by IS NULL)",
|
||||
name="ck_recipe_import_staging_review_metadata",
|
||||
),
|
||||
sa.ForeignKeyConstraint(["recipe_id"], ["recipe.id"], ondelete="SET NULL"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint(
|
||||
"source",
|
||||
"external_id",
|
||||
"source_revision",
|
||||
name="uq_recipe_import_staging_source_revision",
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_recipe_import_staging_status_acquired_at",
|
||||
"recipe_import_staging",
|
||||
["status", "acquired_at"],
|
||||
unique=False,
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
CREATE FUNCTION prevent_recipe_import_staging_raw_payload_update()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF NEW.raw_payload IS DISTINCT FROM OLD.raw_payload THEN
|
||||
RAISE EXCEPTION 'recipe_import_staging.raw_payload is immutable';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql
|
||||
"""
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
CREATE TRIGGER trg_recipe_import_staging_raw_payload_immutable
|
||||
BEFORE UPDATE OF raw_payload ON recipe_import_staging
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION prevent_recipe_import_staging_raw_payload_update()
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(
|
||||
"DROP TRIGGER trg_recipe_import_staging_raw_payload_immutable "
|
||||
"ON recipe_import_staging"
|
||||
)
|
||||
op.execute("DROP FUNCTION prevent_recipe_import_staging_raw_payload_update()")
|
||||
op.drop_index(
|
||||
"ix_recipe_import_staging_status_acquired_at",
|
||||
table_name="recipe_import_staging",
|
||||
)
|
||||
op.drop_table("recipe_import_staging")
|
||||
recipe_import_status_enum.drop(op.get_bind(), checkfirst=True)
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Add recipe catalog quarantine state.
|
||||
|
||||
Revision ID: 0018
|
||||
Revises: 0017
|
||||
Create Date: 2026-08-14
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
|
||||
revision: str = "0018"
|
||||
down_revision: Union[str, None] = "0017"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
recipe_catalog_status_enum = postgresql.ENUM(
|
||||
"active",
|
||||
"quarantined",
|
||||
name="recipe_catalog_status_enum",
|
||||
create_type=False,
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
recipe_catalog_status_enum.create(op.get_bind(), checkfirst=True)
|
||||
op.add_column(
|
||||
"recipe",
|
||||
sa.Column(
|
||||
"catalog_status",
|
||||
recipe_catalog_status_enum,
|
||||
nullable=False,
|
||||
server_default="active",
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"recipe",
|
||||
sa.Column(
|
||||
"catalog_findings",
|
||||
postgresql.JSONB(),
|
||||
nullable=False,
|
||||
server_default=sa.text("'[]'::jsonb"),
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"recipe",
|
||||
sa.Column("catalog_reviewed_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_recipe_catalog_status",
|
||||
"recipe",
|
||||
["catalog_status"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_recipe_catalog_status", table_name="recipe")
|
||||
op.drop_column("recipe", "catalog_reviewed_at")
|
||||
op.drop_column("recipe", "catalog_findings")
|
||||
op.drop_column("recipe", "catalog_status")
|
||||
recipe_catalog_status_enum.drop(op.get_bind(), checkfirst=True)
|
||||
Reference in New Issue
Block a user