"""initial schema part 1: foundation tables Revision ID: 202602260001 Revises: Create Date: 2026-02-26 20:10:00 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql revision: str = "202602260001" down_revision: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.create_table( "llm_factory", sa.Column("id", sa.UUID(), nullable=False), sa.Column("name", sa.String(length=50), nullable=False), sa.Column("request_url", sa.String(length=255), nullable=False), sa.Column("avatar", sa.Text(), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("name"), ) _enable_rls("llm_factory") op.create_table( "llms", sa.Column("id", sa.UUID(), nullable=False), sa.Column("factory_id", sa.UUID(), nullable=False), sa.Column("model_code", sa.String(length=50), nullable=False), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("model_code"), ) op.create_index("ix_llms_factory_id", "llms", ["factory_id"], unique=False) op.create_foreign_key( "fk_llms_factory_id", "llms", "llm_factory", ["factory_id"], ["id"], ondelete="RESTRICT", ) _enable_rls("llms") op.create_table( "profiles", sa.Column("id", sa.UUID(), nullable=False), sa.Column("username", sa.String(length=30), nullable=False), sa.Column("avatar_url", sa.Text(), nullable=True), sa.Column("bio", sa.String(length=200), nullable=True), sa.Column( "settings", postgresql.JSONB(astext_type=sa.Text()), server_default="{}", nullable=False, ), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True), sa.PrimaryKeyConstraint("id"), ) op.create_index("ix_profiles_username", "profiles", ["username"], unique=False) op.create_index( "ix_profiles_settings_gin", "profiles", ["settings"], unique=False, postgresql_using="gin", ) op.create_foreign_key( "fk_profiles_id", "profiles", "users", ["id"], ["id"], referent_schema="auth", ondelete="CASCADE", ) _enable_rls("profiles") op.execute( """ CREATE OR REPLACE FUNCTION public.create_profile_for_new_user() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$ BEGIN INSERT INTO public.profiles (id, username, avatar_url, bio, settings, created_at, updated_at) VALUES ( NEW.id, COALESCE( NEW.raw_user_meta_data ->> 'username', split_part(NEW.email, '@', 1), 'user_' || substring(NEW.id::text, 1, 8) ), NULL, NULL, '{}'::jsonb, now(), now() ) ON CONFLICT (id) DO NOTHING; RETURN NEW; END; $$; """ ) op.execute("DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users") op.execute( """ CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION public.create_profile_for_new_user(); """ ) op.create_table( "automation_jobs", sa.Column("id", sa.UUID(), nullable=False), sa.Column("owner_id", sa.UUID(), nullable=False), sa.Column("title", sa.String(length=255), nullable=False), sa.Column("prompt", sa.Text(), nullable=False), sa.Column("schedule_type", sa.String(length=20), nullable=False), sa.Column("run_at", sa.DateTime(timezone=True), nullable=False), sa.Column("next_run_at", sa.DateTime(timezone=True), nullable=False), sa.Column("timezone", sa.String(length=50), nullable=False), sa.Column("last_run_at", sa.DateTime(timezone=True), nullable=True), sa.Column("status", sa.String(length=20), nullable=False), sa.Column("created_by", sa.UUID(), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("id", "owner_id", name="uq_automation_jobs_id_owner"), ) op.create_index( "ix_automation_jobs_owner_status", "automation_jobs", ["owner_id", "status"], unique=False, ) op.create_index( "ix_automation_jobs_status_next_run", "automation_jobs", ["status", "next_run_at"], unique=False, ) op.execute( "ALTER TABLE automation_jobs ADD CONSTRAINT chk_automation_job_schedule_type CHECK (schedule_type IN ('daily', 'weekly'))" ) op.execute( "ALTER TABLE automation_jobs ADD CONSTRAINT chk_automation_job_status CHECK (status IN ('active', 'disabled'))" ) op.create_foreign_key( "fk_automation_jobs_owner_id", "automation_jobs", "users", ["owner_id"], ["id"], referent_schema="auth", ondelete="CASCADE", ) op.create_foreign_key( "fk_automation_jobs_created_by", "automation_jobs", "users", ["created_by"], ["id"], referent_schema="auth", ondelete="SET NULL", ) _enable_rls("automation_jobs") op.execute("REVOKE ALL ON TABLE public.alembic_version FROM anon") op.execute("REVOKE ALL ON TABLE public.alembic_version FROM authenticated") def downgrade() -> None: _drop_rls("automation_jobs") op.drop_constraint( "fk_automation_jobs_created_by", "automation_jobs", type_="foreignkey" ) op.drop_constraint( "fk_automation_jobs_owner_id", "automation_jobs", type_="foreignkey" ) op.drop_table("automation_jobs") _drop_rls("profiles") op.execute("DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users") op.execute("DROP FUNCTION IF EXISTS public.create_profile_for_new_user()") op.drop_constraint("fk_profiles_id", "profiles", type_="foreignkey") op.drop_table("profiles") _drop_rls("llms") op.drop_constraint("fk_llms_factory_id", "llms", type_="foreignkey") op.drop_table("llms") _drop_rls("llm_factory") op.drop_table("llm_factory") def _enable_rls(table_name: str) -> None: for role in ["anon", "authenticated"]: for action in ["select", "insert", "update", "delete"]: op.execute( f"DROP POLICY IF EXISTS {role}_{action}_{table_name} ON {table_name}" ) op.execute(f"ALTER TABLE {table_name} ENABLE ROW LEVEL SECURITY") for role in ["anon", "authenticated"]: op.execute( f"CREATE POLICY {role}_select_{table_name} ON {table_name} FOR SELECT TO {role} USING (false)" ) op.execute( f"CREATE POLICY {role}_insert_{table_name} ON {table_name} FOR INSERT TO {role} WITH CHECK (false)" ) op.execute( f"CREATE POLICY {role}_update_{table_name} ON {table_name} FOR UPDATE TO {role} USING (false) WITH CHECK (false)" ) op.execute( f"CREATE POLICY {role}_delete_{table_name} ON {table_name} FOR DELETE TO {role} USING (false)" ) def _drop_rls(table_name: str) -> None: for role in ["anon", "authenticated"]: op.execute(f"DROP POLICY IF EXISTS {role}_delete_{table_name} ON {table_name}") op.execute(f"DROP POLICY IF EXISTS {role}_update_{table_name} ON {table_name}") op.execute(f"DROP POLICY IF EXISTS {role}_insert_{table_name} ON {table_name}") op.execute(f"DROP POLICY IF EXISTS {role}_select_{table_name} ON {table_name}") op.execute(f"ALTER TABLE {table_name} DISABLE ROW LEVEL SECURITY")