gate2: fix all migrations for fresh DB installation
This commit is contained in:
@@ -29,7 +29,7 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_tenants_slug", "tenants", ["slug"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_tenants_slug ON tenants (slug)')
|
||||
|
||||
# users
|
||||
op.create_table(
|
||||
@@ -47,7 +47,7 @@ def upgrade() -> None:
|
||||
sa.UniqueConstraint("tenant_id", "email", name="uq_users_tenant_email"),
|
||||
)
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_users_tenant_id ON users (tenant_id)")
|
||||
op.create_index("ix_users_email", "users", ["email"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_users_email ON users (email)')
|
||||
|
||||
# user_tenants
|
||||
op.create_table(
|
||||
@@ -68,7 +68,7 @@ def upgrade() -> None:
|
||||
sa.Column("field_permissions", postgresql.JSONB, nullable=False, server_default=sa.text("'{}'::jsonb")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_roles_tenant_id", "roles", ["tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_roles_tenant_id ON roles (tenant_id)')
|
||||
|
||||
# sessions
|
||||
op.create_table(
|
||||
@@ -80,8 +80,8 @@ def upgrade() -> None:
|
||||
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_sessions_tenant_id", "sessions", ["tenant_id"])
|
||||
op.create_index("ix_sessions_user_id", "sessions", ["user_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_sessions_tenant_id ON sessions (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_sessions_user_id ON sessions (user_id)')
|
||||
|
||||
# audit_log
|
||||
op.create_table(
|
||||
@@ -95,10 +95,10 @@ def upgrade() -> None:
|
||||
sa.Column("changes", postgresql.JSONB, nullable=True),
|
||||
sa.Column("timestamp", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_audit_log_tenant_id", "audit_log", ["tenant_id"])
|
||||
op.create_index("ix_audit_log_entity_type", "audit_log", ["entity_type"])
|
||||
op.create_index("ix_audit_log_user_id", "audit_log", ["user_id"])
|
||||
op.create_index("ix_audit_log_timestamp", "audit_log", ["timestamp"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_audit_log_tenant_id ON audit_log (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_audit_log_entity_type ON audit_log (entity_type)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_audit_log_user_id ON audit_log (user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_audit_log_timestamp ON audit_log (timestamp)')
|
||||
|
||||
# deletion_log
|
||||
op.create_table(
|
||||
@@ -124,9 +124,9 @@ def upgrade() -> None:
|
||||
sa.Column("read_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_notifications_tenant_id", "notifications", ["tenant_id"])
|
||||
op.create_index("ix_notifications_user_id", "notifications", ["user_id"])
|
||||
op.create_index("ix_notifications_tenant_user_read", "notifications", ["tenant_id", "user_id", "read_at"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_notifications_tenant_id ON notifications (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_notifications_user_id ON notifications (user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_notifications_tenant_user_read ON notifications (tenant_id, user_id, read_at)')
|
||||
|
||||
# password_reset_tokens
|
||||
op.create_table(
|
||||
@@ -138,9 +138,9 @@ def upgrade() -> None:
|
||||
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("used_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_password_reset_tokens_tenant_id", "password_reset_tokens", ["tenant_id"])
|
||||
op.create_index("ix_password_reset_tokens_user_id", "password_reset_tokens", ["user_id"])
|
||||
op.create_index("ix_password_reset_tokens_token_hash", "password_reset_tokens", ["token_hash"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_password_reset_tokens_tenant_id ON password_reset_tokens (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_password_reset_tokens_user_id ON password_reset_tokens (user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_password_reset_tokens_token_hash ON password_reset_tokens (token_hash)')
|
||||
|
||||
# api_tokens
|
||||
op.create_table(
|
||||
@@ -156,9 +156,9 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_api_tokens_tenant_id", "api_tokens", ["tenant_id"])
|
||||
op.create_index("ix_api_tokens_token_hash", "api_tokens", ["token_hash"])
|
||||
op.create_index("ix_api_tokens_tenant_user", "api_tokens", ["tenant_id", "user_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_api_tokens_tenant_id ON api_tokens (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_api_tokens_token_hash ON api_tokens (token_hash)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_api_tokens_tenant_user ON api_tokens (tenant_id, user_id)')
|
||||
|
||||
# companies
|
||||
op.create_table(
|
||||
@@ -178,9 +178,9 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_companies_tenant_id", "companies", ["tenant_id"])
|
||||
op.create_index("ix_companies_tenant_deleted", "companies", ["tenant_id", "deleted_at"])
|
||||
op.create_index("ix_companies_tenant_name", "companies", ["tenant_id", "name"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_companies_tenant_id ON companies (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_companies_tenant_deleted ON companies (tenant_id, deleted_at)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_companies_tenant_name ON companies (tenant_id, name)')
|
||||
|
||||
# Enable RLS on tenant-scoped tables
|
||||
for table in ["companies", "users", "roles", "sessions", "audit_log", "notifications", "api_tokens"]:
|
||||
|
||||
@@ -34,17 +34,8 @@ def upgrade() -> None:
|
||||
) STORED
|
||||
"""
|
||||
)
|
||||
op.create_index(
|
||||
"ix_companies_search_vec",
|
||||
"companies",
|
||||
["search_tsv"],
|
||||
postgresql_using="gin",
|
||||
)
|
||||
op.create_index(
|
||||
"ix_companies_industry",
|
||||
"companies",
|
||||
["tenant_id", "industry"],
|
||||
)
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_companies_search_vec ON companies (search_tsv)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_companies_industry ON companies (tenant_id, industry)')
|
||||
|
||||
# --- contacts ---
|
||||
op.create_table(
|
||||
@@ -84,9 +75,9 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.UniqueConstraint("company_id", "contact_id", "tenant_id", name="uq_company_contact_tenant"),
|
||||
)
|
||||
op.create_index("ix_cc_company", "company_contacts", ["company_id"])
|
||||
op.create_index("ix_cc_contact", "company_contacts", ["contact_id"])
|
||||
op.create_index("ix_company_contacts_tenant_id", "company_contacts", ["tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_cc_company ON company_contacts (company_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_cc_contact ON company_contacts (contact_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_company_contacts_tenant_id ON company_contacts (tenant_id)')
|
||||
|
||||
# --- RLS on new tenant-scoped tables ---
|
||||
for table in ["contacts", "company_contacts"]:
|
||||
|
||||
@@ -34,7 +34,7 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_plugins_name", "plugins", ["name"], unique=True)
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_plugins_name ON plugins (name)')
|
||||
|
||||
# --- plugin_migrations table (tracks which migrations have been applied) ---
|
||||
op.create_table(
|
||||
@@ -47,7 +47,7 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.UniqueConstraint("plugin_name", "migration_file", name="ix_plugin_migrations_unique"),
|
||||
)
|
||||
op.create_index("ix_plugin_migrations_plugin", "plugin_migrations", ["plugin_name"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_plugin_migrations_plugin ON plugin_migrations (plugin_name)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -31,8 +31,8 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_ai_conversations_tenant_id", "ai_conversations", ["tenant_id"])
|
||||
op.create_index("ix_ai_conversations_tenant_user", "ai_conversations", ["tenant_id", "user_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ai_conversations_tenant_id ON ai_conversations (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ai_conversations_tenant_user ON ai_conversations (tenant_id, user_id)')
|
||||
|
||||
# --- ai_messages table (tenant-scoped) ---
|
||||
op.create_table(
|
||||
@@ -49,9 +49,9 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_ai_messages_tenant_id", "ai_messages", ["tenant_id"])
|
||||
op.create_index("ix_ai_messages_tenant_conversation", "ai_messages", ["tenant_id", "conversation_id"])
|
||||
op.create_index("ix_ai_messages_conversation_id", "ai_messages", ["conversation_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ai_messages_tenant_id ON ai_messages (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ai_messages_tenant_conversation ON ai_messages (tenant_id, conversation_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ai_messages_conversation_id ON ai_messages (conversation_id)')
|
||||
|
||||
# --- workflows table (tenant-scoped) ---
|
||||
op.create_table(
|
||||
@@ -67,9 +67,9 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_workflows_tenant_id", "workflows", ["tenant_id"])
|
||||
op.create_index("ix_workflows_tenant_active", "workflows", ["tenant_id", "is_active"])
|
||||
op.create_index("ix_workflows_tenant_trigger", "workflows", ["tenant_id", "trigger_event"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_workflows_tenant_id ON workflows (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_workflows_tenant_active ON workflows (tenant_id, is_active)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_workflows_tenant_trigger ON workflows (tenant_id, trigger_event)')
|
||||
|
||||
# --- workflow_instances table (tenant-scoped) ---
|
||||
op.create_table(
|
||||
@@ -87,10 +87,10 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_wf_instances_tenant_id", "workflow_instances", ["tenant_id"])
|
||||
op.create_index("ix_wf_instances_tenant_status", "workflow_instances", ["tenant_id", "status"])
|
||||
op.create_index("ix_wf_instances_tenant_workflow", "workflow_instances", ["tenant_id", "workflow_id"])
|
||||
op.create_index("ix_wf_instances_workflow_id", "workflow_instances", ["workflow_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wf_instances_tenant_id ON workflow_instances (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wf_instances_tenant_status ON workflow_instances (tenant_id, status)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wf_instances_tenant_workflow ON workflow_instances (tenant_id, workflow_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wf_instances_workflow_id ON workflow_instances (workflow_id)')
|
||||
|
||||
# --- workflow_step_history table (tenant-scoped) ---
|
||||
op.create_table(
|
||||
@@ -106,9 +106,9 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_wf_step_history_tenant_id", "workflow_step_history", ["tenant_id"])
|
||||
op.create_index("ix_wf_step_history_tenant_instance", "workflow_step_history", ["tenant_id", "instance_id"])
|
||||
op.create_index("ix_wf_step_history_instance_id", "workflow_step_history", ["instance_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wf_step_history_tenant_id ON workflow_step_history (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wf_step_history_tenant_instance ON workflow_step_history (tenant_id, instance_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wf_step_history_instance_id ON workflow_step_history (instance_id)')
|
||||
|
||||
# --- RLS Policies ---
|
||||
for table in ["ai_conversations", "ai_messages", "workflows", "workflow_instances", "workflow_step_history"]:
|
||||
|
||||
@@ -32,8 +32,8 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_currencies_tenant_code", "currencies", ["tenant_id", "code"])
|
||||
op.create_index("ix_currencies_tenant_default", "currencies", ["tenant_id", "is_default"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_currencies_tenant_code ON currencies (tenant_id, code)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_currencies_tenant_default ON currencies (tenant_id, is_default)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -32,8 +32,8 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_tax_rates_tenant_name", "tax_rates", ["tenant_id", "name"])
|
||||
op.create_index("ix_tax_rates_tenant_default", "tax_rates", ["tenant_id", "is_default"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_tax_rates_tenant_name ON tax_rates (tenant_id, name)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_tax_rates_tenant_default ON tax_rates (tenant_id, is_default)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -32,7 +32,7 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_sequences_tenant_name", "sequences", ["tenant_id", "name"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_sequences_tenant_name ON sequences (tenant_id, name)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -48,7 +48,7 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_system_settings_tenant", "system_settings", ["tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_system_settings_tenant ON system_settings (tenant_id)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -36,7 +36,7 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_attachments_entity", "attachments", ["entity_type", "entity_id", "tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_attachments_entity ON attachments (entity_type, entity_id, tenant_id)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -41,8 +41,8 @@ def upgrade() -> None:
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
|
||||
op.create_index("ix_addresses_tenant_entity", "addresses", ["tenant_id", "entity_type", "entity_id"])
|
||||
op.create_index("ix_addresses_tenant_type", "addresses", ["tenant_id", "address_type"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_addresses_tenant_entity ON addresses (tenant_id, entity_type, entity_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_addresses_tenant_type ON addresses (tenant_id, address_type)')
|
||||
|
||||
# Unique constraint: one default per (tenant, entity_type, entity_id, address_type)
|
||||
# Using a partial unique index WHERE is_default = true
|
||||
|
||||
@@ -54,7 +54,7 @@ def upgrade() -> None:
|
||||
sa.Column("is_enabled_by_default", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_notification_types_key", "notification_types", ["type_key"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_notification_types_key ON notification_types (type_key)')
|
||||
|
||||
# notification_preferences table
|
||||
op.create_table(
|
||||
@@ -67,8 +67,8 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.UniqueConstraint("user_id", "type_key", name="uq_notif_pref_user_type"),
|
||||
)
|
||||
op.create_index("ix_notif_prefs_user", "notification_preferences", ["user_id"])
|
||||
op.create_index("ix_notif_prefs_tenant", "notification_preferences", ["tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_notif_prefs_user ON notification_preferences (user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_notif_prefs_tenant ON notification_preferences (tenant_id)')
|
||||
|
||||
# Seed mail plugin notification types
|
||||
for nt in MAIL_NOTIFICATION_TYPES:
|
||||
|
||||
@@ -53,7 +53,7 @@ def upgrade() -> None:
|
||||
"user_tenants",
|
||||
sa.Column("role_id", PGUUID(as_uuid=True), sa.ForeignKey("roles.id", ondelete="SET NULL"), nullable=True),
|
||||
)
|
||||
op.create_index("ix_user_tenants_role_id", "user_tenants", ["role_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_user_tenants_role_id ON user_tenants (role_id)')
|
||||
|
||||
# ── roles: add denied_permissions + permission_version + missing mixin columns ──
|
||||
op.add_column(
|
||||
|
||||
@@ -165,14 +165,14 @@ def upgrade() -> None:
|
||||
)
|
||||
op.execute("DROP INDEX IF EXISTS ix_contacts_tenant_deleted")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_contacts_tenant_deleted ON contacts (tenant_id, deleted_at)")
|
||||
op.create_index("ix_contacts_tenant_type", "contacts", ["tenant_id", "type"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contacts_tenant_type ON contacts (tenant_id, type)')
|
||||
op.execute("DROP INDEX IF EXISTS ix_contacts_tenant_name")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_contacts_tenant_name ON contacts (tenant_id, name)")
|
||||
op.create_index("ix_contacts_tenant_displayname", "contacts", ["tenant_id", "displayname"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contacts_tenant_displayname ON contacts (tenant_id, displayname)')
|
||||
op.execute("DROP INDEX IF EXISTS ix_contacts_email")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_contacts_email ON contacts (email_1)")
|
||||
op.create_index("ix_contacts_code", "contacts", ["code"])
|
||||
op.create_index("ix_contacts_search_vec", "contacts", ["search_tsv"], postgresql_using="gin")
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contacts_code ON contacts (code)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contacts_search_vec ON contacts (search_tsv)')
|
||||
|
||||
# ── 3. Create contactpersons table ────────────────────────────────
|
||||
op.create_table(
|
||||
@@ -202,9 +202,9 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_contactpersons_tenant_deleted", "contactpersons", ["tenant_id", "deleted_at"])
|
||||
op.create_index("ix_contactpersons_contact", "contactpersons", ["contact_id"])
|
||||
op.create_index("ix_contactpersons_email", "contactpersons", ["email"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contactpersons_tenant_deleted ON contactpersons (tenant_id, deleted_at)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contactpersons_contact ON contactpersons (contact_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contactpersons_email ON contactpersons (email)')
|
||||
|
||||
# ── 4. Add FK columns to contacts that reference contactpersons ───
|
||||
op.execute("ALTER TABLE contacts ADD COLUMN IF NOT EXISTS default_person_id UUID REFERENCES contactpersons(id) ON DELETE SET NULL")
|
||||
|
||||
@@ -27,12 +27,12 @@ def upgrade():
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_contact_folders_tenant_parent", "contact_folders", ["tenant_id", "parent_id"])
|
||||
op.create_index("ix_contact_folders_user", "contact_folders", ["user_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contact_folders_tenant_parent ON contact_folders (tenant_id, parent_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contact_folders_user ON contact_folders (user_id)')
|
||||
|
||||
# 2. Add folder_id column to contacts
|
||||
op.execute("ALTER TABLE contacts ADD COLUMN IF NOT EXISTS folder_id UUID REFERENCES contact_folders(id) ON DELETE SET NULL")
|
||||
op.create_index("ix_contacts_folder_id", "contacts", ["folder_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contacts_folder_id ON contacts (folder_id)')
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
@@ -13,9 +13,15 @@ down_revision = "0023_theme_customization"
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.execute("ALTER TABLE ai_proactive_settings ADD COLUMN IF NOT EXISTS heartbeat_enabled BOOLEAN NOT NULL DEFAULT true")
|
||||
op.execute("ALTER TABLE ai_proactive_settings ADD COLUMN IF NOT EXISTS heartbeat_interval_seconds INTEGER NOT NULL DEFAULT 300")
|
||||
op.execute("ALTER TABLE ai_proactive_settings ADD COLUMN IF NOT EXISTS heartbeat_target_room VARCHAR(200) NOT NULL DEFAULT 'Live KI'")
|
||||
op.execute("""
|
||||
DO $$ BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ai_proactive_settings') THEN
|
||||
ALTER TABLE ai_proactive_settings ADD COLUMN IF NOT EXISTS heartbeat_enabled BOOLEAN NOT NULL DEFAULT true;
|
||||
ALTER TABLE ai_proactive_settings ADD COLUMN IF NOT EXISTS heartbeat_interval_seconds INTEGER NOT NULL DEFAULT 300;
|
||||
ALTER TABLE ai_proactive_settings ADD COLUMN IF NOT EXISTS heartbeat_target_room VARCHAR(200) NOT NULL DEFAULT 'Live KI';
|
||||
END IF;
|
||||
END $$
|
||||
""")
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
@@ -35,16 +35,12 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_entity_history_tenant_id", "entity_history", ["tenant_id"])
|
||||
op.create_index("ix_entity_history_entity_type", "entity_history", ["entity_type"])
|
||||
op.create_index("ix_entity_history_entity_id", "entity_history", ["entity_id"])
|
||||
op.create_index("ix_entity_history_user_id", "entity_history", ["user_id"])
|
||||
op.create_index("ix_entity_history_created_at", "entity_history", ["created_at"])
|
||||
op.create_index(
|
||||
"ix_entity_history_tenant_entity",
|
||||
"entity_history",
|
||||
["tenant_id", "entity_type", "entity_id", "created_at"],
|
||||
)
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_history_tenant_id ON entity_history (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_history_entity_type ON entity_history (entity_type)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_history_entity_id ON entity_history (entity_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_history_user_id ON entity_history (user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_history_created_at ON entity_history (created_at)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_history_tenant_entity ON entity_history (tenant_id, entity_type, entity_id, created_at)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -17,7 +17,7 @@ down_revision = "0025_entity_history"
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.execute("ALTER TABLE mail_accounts ADD COLUMN IF NOT EXISTS password_salt VARCHAR(64) NOT NULL DEFAULT ''")
|
||||
op.execute("ALTER TABLE IF EXISTS mail_accounts ADD COLUMN IF NOT EXISTS password_salt VARCHAR(64) NOT NULL DEFAULT ''")
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
@@ -34,9 +34,9 @@ def upgrade():
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.UniqueConstraint("tenant_id", "user_id", "key", name="uq_user_prefs_tenant_user_key"),
|
||||
)
|
||||
op.create_index("ix_user_prefs_tenant_user", "user_preferences", ["tenant_id", "user_id"])
|
||||
op.create_index("ix_user_prefs_user_id", "user_preferences", ["user_id"])
|
||||
op.create_index("ix_user_prefs_tenant_id", "user_preferences", ["tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_user_prefs_tenant_user ON user_preferences (tenant_id, user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_user_prefs_user_id ON user_preferences (user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_user_prefs_tenant_id ON user_preferences (tenant_id)')
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
@@ -30,8 +30,8 @@ def upgrade() -> None:
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.UniqueConstraint("tenant_id", "user_id", "entity_type", "name", name="uq_saved_filters_tenant_user_entity_name"),
|
||||
)
|
||||
op.create_index("ix_saved_filters_tenant_user", "saved_filters", ["tenant_id", "user_id"])
|
||||
op.create_index("ix_saved_filters_tenant_entity", "saved_filters", ["tenant_id", "entity_type"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_saved_filters_tenant_user ON saved_filters (tenant_id, user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_saved_filters_tenant_entity ON saved_filters (tenant_id, entity_type)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -30,9 +30,9 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_contact_merge_history_tenant", "contact_merge_history", ["tenant_id"])
|
||||
op.create_index("ix_contact_merge_history_target", "contact_merge_history", ["tenant_id", "target_contact_id"])
|
||||
op.create_index("ix_contact_merge_history_source", "contact_merge_history", ["tenant_id", "source_contact_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contact_merge_history_tenant ON contact_merge_history (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contact_merge_history_target ON contact_merge_history (tenant_id, target_contact_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_contact_merge_history_source ON contact_merge_history (tenant_id, source_contact_id)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -21,15 +21,9 @@ depends_on = None
|
||||
|
||||
def upgrade() -> None:
|
||||
# Add deleted_at to permissions table (if not exists)
|
||||
op.add_column(
|
||||
"permissions",
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.execute("ALTER TABLE IF EXISTS permissions ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP WITH TIME ZONE")
|
||||
# Add deleted_at to share_links table (if not exists)
|
||||
op.add_column(
|
||||
"share_links",
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.execute("ALTER TABLE IF EXISTS share_links ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP WITH TIME ZONE")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -35,8 +35,8 @@ def upgrade() -> None:
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
|
||||
op.create_index("ix_bank_accounts_tenant", "bank_accounts", ["tenant_id"])
|
||||
op.create_index("ix_bank_accounts_tenant_default", "bank_accounts", ["tenant_id", "is_default"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_bank_accounts_tenant ON bank_accounts (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_bank_accounts_tenant_default ON bank_accounts (tenant_id, is_default)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -21,27 +21,29 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
def upgrade() -> None:
|
||||
# Add search_tsv column for full-text search
|
||||
op.add_column(
|
||||
"comm_messages",
|
||||
sa.Column("search_tsv", TSVECTOR, nullable=True),
|
||||
)
|
||||
op.execute("ALTER TABLE IF EXISTS comm_messages ADD COLUMN IF NOT EXISTS search_tsv tsvector")
|
||||
# Add embedding column for vector search (768 dimensions matching pgvector)
|
||||
op.execute(
|
||||
"ALTER TABLE comm_messages ADD COLUMN embedding vector(768)"
|
||||
)
|
||||
# Create GIN index on search_tsv for fast FTS queries
|
||||
op.create_index(
|
||||
"ix_comm_messages_search_tsv",
|
||||
"comm_messages",
|
||||
["search_tsv"],
|
||||
postgresql_using="gin",
|
||||
)
|
||||
# Create IVFFlat index on embedding for fast vector search
|
||||
op.execute(
|
||||
"CREATE INDEX IF NOT EXISTS ix_comm_messages_embedding "
|
||||
"ON comm_messages USING ivfflat (embedding vector_cosine_ops) "
|
||||
"WITH (lists = 100)"
|
||||
"ALTER TABLE IF EXISTS comm_messages ADD COLUMN IF NOT EXISTS embedding vector(768)"
|
||||
)
|
||||
# Create GIN index on search_tsv for fast FTS queries (only if table exists)
|
||||
op.execute("""
|
||||
DO $$ BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'comm_messages') THEN
|
||||
CREATE INDEX IF NOT EXISTS ix_comm_messages_search_tsv ON comm_messages (search_tsv);
|
||||
END IF;
|
||||
END $$
|
||||
""")
|
||||
# Create IVFFlat index on embedding for fast vector search (only if table exists)
|
||||
op.execute("""
|
||||
DO $$ BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'comm_messages') THEN
|
||||
CREATE INDEX IF NOT EXISTS ix_comm_messages_embedding
|
||||
ON comm_messages USING ivfflat (embedding vector_cosine_ops)
|
||||
WITH (lists = 100);
|
||||
END IF;
|
||||
END $$
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -165,7 +165,7 @@ def downgrade() -> None:
|
||||
tenant_col_result = conn.execute(sa.text(_column_exists("users", "tenant_id"))).fetchone()
|
||||
if tenant_col_result is None:
|
||||
op.add_column("users", sa.Column("tenant_id", sa.dialects.postgresql.UUID(as_uuid=True), nullable=True))
|
||||
op.create_index("ix_users_tenant_id", "users", ["tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_users_tenant_id ON users (tenant_id)')
|
||||
|
||||
role_col_result = conn.execute(sa.text(_column_exists("users", "role"))).fetchone()
|
||||
if role_col_result is None:
|
||||
@@ -174,7 +174,7 @@ def downgrade() -> None:
|
||||
role_id_col_result = conn.execute(sa.text(_column_exists("users", "role_id"))).fetchone()
|
||||
if role_id_col_result is None:
|
||||
op.add_column("users", sa.Column("role_id", sa.dialects.postgresql.UUID(as_uuid=True), nullable=True))
|
||||
op.create_index("ix_users_role_id", "users", ["role_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_users_role_id ON users (role_id)')
|
||||
# Re-add FK
|
||||
op.create_foreign_key("fk_users_role_id", "users", "roles", ["role_id"], ["id"], ondelete="SET NULL")
|
||||
|
||||
|
||||
@@ -32,9 +32,13 @@ def _column_exists(table: str, column: str) -> str:
|
||||
|
||||
def upgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
# Check if table exists first
|
||||
table_exists = conn.execute(sa.text("SELECT 1 FROM information_schema.tables WHERE table_name = 'files'")).fetchone()
|
||||
if table_exists is None:
|
||||
return
|
||||
result = conn.execute(sa.text(_column_exists("files", "content_hash"))).fetchone()
|
||||
if result is None:
|
||||
op.add_column("files", sa.Column("content_hash", sa.String(64), nullable=True))
|
||||
op.execute("ALTER TABLE files ADD COLUMN IF NOT EXISTS content_hash VARCHAR(64)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -30,8 +30,8 @@ def upgrade():
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_plugin_allowlist_plugin_name", "plugin_allowlist", ["plugin_name"])
|
||||
op.create_index("ix_plugin_allowlist_hash", "plugin_allowlist", ["allowed_hash"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_plugin_allowlist_plugin_name ON plugin_allowlist (plugin_name)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_plugin_allowlist_hash ON plugin_allowlist (allowed_hash)')
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
@@ -28,8 +28,8 @@ def upgrade() -> None:
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_unique_constraint("uq_saved_views_tenant_user_entity_name", "saved_views", ["tenant_id", "user_id", "entity_type", "name"])
|
||||
op.create_index("ix_saved_views_tenant_user", "saved_views", ["tenant_id", "user_id"])
|
||||
op.create_index("ix_saved_views_tenant_entity", "saved_views", ["tenant_id", "entity_type"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_saved_views_tenant_user ON saved_views (tenant_id, user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_saved_views_tenant_entity ON saved_views (tenant_id, entity_type)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -34,10 +34,10 @@ def upgrade() -> None:
|
||||
name="ck_cfp_exactly_one_principal",
|
||||
),
|
||||
)
|
||||
op.create_index("ix_cfp_folder", "contact_folder_permissions", ["folder_id"])
|
||||
op.create_index("ix_cfp_user", "contact_folder_permissions", ["user_id"])
|
||||
op.create_index("ix_cfp_group", "contact_folder_permissions", ["group_id"])
|
||||
op.create_index("ix_cfp_tenant", "contact_folder_permissions", ["tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_cfp_folder ON contact_folder_permissions (folder_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_cfp_user ON contact_folder_permissions (user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_cfp_group ON contact_folder_permissions (group_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_cfp_tenant ON contact_folder_permissions (tenant_id)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -33,10 +33,10 @@ def upgrade() -> None:
|
||||
sa.CheckConstraint("principal_type IN ('user', 'group', 'role', 'guest')", name="ck_ep_principal_type"),
|
||||
sa.CheckConstraint("permission_level IN ('none', 'read', 'write', 'admin', 'delete')", name="ck_ep_permission_level"),
|
||||
)
|
||||
op.create_index("ix_ep_entity", "entity_permissions", ["entity_type", "entity_id"])
|
||||
op.create_index("ix_ep_principal", "entity_permissions", ["principal_type", "principal_id"])
|
||||
op.create_index("ix_ep_tenant", "entity_permissions", ["tenant_id"])
|
||||
op.create_index("ix_ep_expires", "entity_permissions", ["expires_at"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ep_entity ON entity_permissions (entity_type, entity_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ep_principal ON entity_permissions (principal_type, principal_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ep_tenant ON entity_permissions (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ep_expires ON entity_permissions (expires_at)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -20,8 +20,8 @@ depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.execute("ALTER TABLE mail_accounts ADD COLUMN IF NOT EXISTS owner_id UUID REFERENCES users(id) ON DELETE SET NULL")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_mail_accounts_owner ON mail_accounts (owner_id)")
|
||||
op.execute("ALTER TABLE IF EXISTS mail_accounts ADD COLUMN IF NOT EXISTS owner_id UUID REFERENCES users(id) ON DELETE SET NULL")
|
||||
op.execute("DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'mail_accounts') THEN CREATE INDEX IF NOT EXISTS ix_mail_accounts_owner ON mail_accounts (owner_id); END IF; END $$")
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
@@ -29,6 +29,15 @@ def upgrade() -> None:
|
||||
# Check which columns already exist before adding
|
||||
conn = op.get_bind()
|
||||
for table in TABLES:
|
||||
# Check if table exists
|
||||
table_exists = conn.execute(
|
||||
sa.text(
|
||||
"SELECT 1 FROM information_schema.tables WHERE table_name = :table"
|
||||
),
|
||||
{"table": table},
|
||||
).fetchone()
|
||||
if table_exists is None:
|
||||
continue
|
||||
# Check if column already exists
|
||||
result = conn.execute(
|
||||
sa.text(
|
||||
@@ -38,16 +47,8 @@ def upgrade() -> None:
|
||||
{"table": table},
|
||||
)
|
||||
if result.fetchone() is None:
|
||||
op.add_column(
|
||||
table,
|
||||
sa.Column(
|
||||
"owner_id",
|
||||
PGUUID(as_uuid=True),
|
||||
sa.ForeignKey("users.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.create_index(f"ix_{table}_owner", table, ["owner_id"])
|
||||
op.execute(f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS owner_id UUID REFERENCES users(id) ON DELETE SET NULL")
|
||||
op.execute(f"CREATE INDEX IF NOT EXISTS ix_{table}_owner ON {table} (owner_id)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -39,11 +39,11 @@ def upgrade() -> None:
|
||||
name="ck_epol_effect",
|
||||
),
|
||||
)
|
||||
op.create_index("ix_epol_entity_type", "entity_policies", ["entity_type"])
|
||||
op.create_index("ix_epol_principal", "entity_policies", ["principal_type", "principal_id"])
|
||||
op.create_index("ix_epol_tenant", "entity_policies", ["tenant_id"])
|
||||
op.create_index("ix_epol_priority", "entity_policies", ["priority"])
|
||||
op.create_index("ix_epol_enabled", "entity_policies", ["enabled"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_epol_entity_type ON entity_policies (entity_type)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_epol_principal ON entity_policies (principal_type, principal_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_epol_tenant ON entity_policies (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_epol_priority ON entity_policies (priority)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_epol_enabled ON entity_policies (enabled)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -32,8 +32,8 @@ def upgrade() -> None:
|
||||
name="ck_pt_level",
|
||||
),
|
||||
)
|
||||
op.create_index("ix_pt_entity_type", "permission_templates", ["entity_type"])
|
||||
op.create_index("ix_pt_tenant", "permission_templates", ["tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_pt_entity_type ON permission_templates (entity_type)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_pt_tenant ON permission_templates (tenant_id)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -33,10 +33,10 @@ def upgrade() -> None:
|
||||
name="ck_pd_end_after_start",
|
||||
),
|
||||
)
|
||||
op.create_index("ix_pd_from_user", "permission_delegations", ["from_user_id"])
|
||||
op.create_index("ix_pd_to_user", "permission_delegations", ["to_user_id"])
|
||||
op.create_index("ix_pd_tenant", "permission_delegations", ["tenant_id"])
|
||||
op.create_index("ix_pd_active", "permission_delegations", ["active"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_pd_from_user ON permission_delegations (from_user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_pd_to_user ON permission_delegations (to_user_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_pd_tenant ON permission_delegations (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_pd_active ON permission_delegations (active)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -34,9 +34,9 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
)
|
||||
op.create_index("ix_guest_users_email_tenant", "guest_users", ["email", "tenant_id"], unique=True)
|
||||
op.create_index("ix_guest_users_status", "guest_users", ["status", "tenant_id"])
|
||||
op.create_index("ix_guest_users_invited_by", "guest_users", ["invited_by"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_guest_users_email_tenant ON guest_users (email, tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_guest_users_status ON guest_users (status, tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_guest_users_invited_by ON guest_users (invited_by)')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -22,6 +22,45 @@ depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Create folders table if it doesn't exist (DMS plugin table normally created via create_all)
|
||||
op.execute("""
|
||||
CREATE TABLE IF NOT EXISTS folders (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
parent_id UUID REFERENCES folders(id) ON DELETE CASCADE,
|
||||
owner_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_by UUID NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
deleted_at TIMESTAMP WITH TIME ZONE
|
||||
)
|
||||
""")
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_folders_parent ON folders (parent_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_folders_tenant ON folders (tenant_id)')
|
||||
|
||||
# Create files table if it doesn't exist (DMS plugin table normally created via create_all)
|
||||
op.execute("""
|
||||
CREATE TABLE IF NOT EXISTS files (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
folder_id UUID REFERENCES folders(id) ON DELETE SET NULL,
|
||||
owner_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
uploaded_by UUID NOT NULL,
|
||||
mime_type VARCHAR(255) NOT NULL,
|
||||
size_bytes INTEGER NOT NULL,
|
||||
storage_path VARCHAR(1024) NOT NULL,
|
||||
content_hash VARCHAR(64),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
deleted_at TIMESTAMP WITH TIME ZONE
|
||||
)
|
||||
""")
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_files_folder ON files (folder_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_files_tenant ON files (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_files_name ON files (name)')
|
||||
|
||||
op.create_table(
|
||||
"entity_attachments",
|
||||
sa.Column("id", PGUUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")),
|
||||
@@ -38,10 +77,10 @@ def upgrade() -> None:
|
||||
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
|
||||
op.create_index("ix_entity_attachments_entity", "entity_attachments", ["entity_type", "entity_id", "tenant_id"])
|
||||
op.create_index("ix_entity_attachments_tenant", "entity_attachments", ["tenant_id"])
|
||||
op.create_index("ix_entity_attachments_dms_file", "entity_attachments", ["dms_file_id"])
|
||||
op.create_index("ix_entity_attachments_owner", "entity_attachments", ["owner_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_attachments_entity ON entity_attachments (entity_type, entity_id, tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_attachments_tenant ON entity_attachments (tenant_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_attachments_dms_file ON entity_attachments (dms_file_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_entity_attachments_owner ON entity_attachments (owner_id)')
|
||||
|
||||
# Enable RLS on entity_attachments (tenant isolation)
|
||||
op.execute("ALTER TABLE entity_attachments ENABLE ROW LEVEL SECURITY")
|
||||
|
||||
@@ -30,7 +30,7 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
|
||||
sa.UniqueConstraint("tenant_id", "name", name="uq_workspaces_tenant_name"),
|
||||
)
|
||||
op.create_index("ix_workspaces_tenant", "workspaces", ["tenant_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_workspaces_tenant ON workspaces (tenant_id)')
|
||||
op.execute(
|
||||
"CREATE UNIQUE INDEX uq_workspace_default_per_tenant "
|
||||
"ON workspaces (tenant_id) WHERE is_default = true"
|
||||
@@ -50,7 +50,7 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
|
||||
sa.UniqueConstraint("tenant_id", "workspace_id", "module_key", name="uq_wm_tenant_workspace_module"),
|
||||
)
|
||||
op.create_index("ix_wm_workspace", "workspace_modules", ["tenant_id", "workspace_id", "menu_order"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wm_workspace ON workspace_modules (tenant_id, workspace_id, menu_order)')
|
||||
|
||||
# workspace_users
|
||||
op.create_table(
|
||||
@@ -66,8 +66,8 @@ def upgrade() -> None:
|
||||
sa.UniqueConstraint("tenant_id", "workspace_id", "user_id", name="uq_wu_tenant_workspace_user"),
|
||||
sa.CheckConstraint("role IN ('member', 'manager')", name="ck_wu_role"),
|
||||
)
|
||||
op.create_index("ix_wu_workspace", "workspace_users", ["tenant_id", "workspace_id"])
|
||||
op.create_index("ix_wu_user", "workspace_users", ["tenant_id", "user_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wu_workspace ON workspace_users (tenant_id, workspace_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_wu_user ON workspace_users (tenant_id, user_id)')
|
||||
|
||||
# workspace_widgets
|
||||
op.create_table(
|
||||
@@ -84,7 +84,7 @@ def upgrade() -> None:
|
||||
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),
|
||||
)
|
||||
op.create_index("ix_ww_workspace", "workspace_widgets", ["tenant_id", "workspace_id"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_ww_workspace ON workspace_widgets (tenant_id, workspace_id)')
|
||||
|
||||
# RLS on all workspace tables
|
||||
for table in ["workspaces", "workspace_modules", "workspace_users", "workspace_widgets"]:
|
||||
|
||||
@@ -30,14 +30,14 @@ depends_on = None
|
||||
|
||||
def upgrade() -> None:
|
||||
# 1. Add envelope columns to event_outbox
|
||||
op.execute("ALTER TABLE event_outbox ADD COLUMN IF NOT EXISTS aggregate_type VARCHAR(100)")
|
||||
op.execute("ALTER TABLE event_outbox ADD COLUMN IF NOT EXISTS aggregate_id UUID")
|
||||
op.execute("ALTER TABLE event_outbox ADD COLUMN IF NOT EXISTS occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()")
|
||||
op.execute("ALTER TABLE event_outbox ADD COLUMN IF NOT EXISTS correlation_id UUID")
|
||||
op.execute("ALTER TABLE event_outbox ADD COLUMN IF NOT EXISTS schema_version INTEGER NOT NULL DEFAULT 1")
|
||||
op.execute("ALTER TABLE IF EXISTS event_outbox ADD COLUMN IF NOT EXISTS aggregate_type VARCHAR(100)")
|
||||
op.execute("ALTER TABLE IF EXISTS event_outbox ADD COLUMN IF NOT EXISTS aggregate_id UUID")
|
||||
op.execute("ALTER TABLE IF EXISTS event_outbox ADD COLUMN IF NOT EXISTS occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()")
|
||||
op.execute("ALTER TABLE IF EXISTS event_outbox ADD COLUMN IF NOT EXISTS correlation_id UUID")
|
||||
op.execute("ALTER TABLE IF EXISTS event_outbox ADD COLUMN IF NOT EXISTS schema_version INTEGER NOT NULL DEFAULT 1")
|
||||
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_event_outbox_aggregate ON event_outbox (tenant_id, aggregate_type, aggregate_id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_event_outbox_correlation ON event_outbox (correlation_id)")
|
||||
op.execute("DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'event_outbox') THEN CREATE INDEX IF NOT EXISTS ix_event_outbox_aggregate ON event_outbox (tenant_id, aggregate_type, aggregate_id); END IF; END $$")
|
||||
op.execute("DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'event_outbox') THEN CREATE INDEX IF NOT EXISTS ix_event_outbox_correlation ON event_outbox (correlation_id); END IF; END $$")
|
||||
|
||||
# 2. Create outbox_deliveries table
|
||||
op.create_table(
|
||||
@@ -54,8 +54,8 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
|
||||
sa.UniqueConstraint("event_id", "consumer_name", name="uq_outbox_deliveries_event_consumer"),
|
||||
)
|
||||
op.create_index("ix_outbox_deliveries_event", "outbox_deliveries", ["event_id"])
|
||||
op.create_index("ix_outbox_deliveries_status", "outbox_deliveries", ["status", "next_attempt_at"])
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_outbox_deliveries_event ON outbox_deliveries (event_id)')
|
||||
op.execute('CREATE INDEX IF NOT EXISTS ix_outbox_deliveries_status ON outbox_deliveries (status, next_attempt_at)')
|
||||
|
||||
# RLS + Grants
|
||||
op.execute("ALTER TABLE outbox_deliveries ENABLE ROW LEVEL SECURITY")
|
||||
|
||||
@@ -13,10 +13,7 @@ depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"custom_field_definitions",
|
||||
sa.Column("sensitivity", sa.String(20), nullable=False, server_default="normal"),
|
||||
)
|
||||
op.execute("ALTER TABLE IF EXISTS custom_field_definitions ADD COLUMN IF NOT EXISTS sensitivity VARCHAR(20) NOT NULL DEFAULT 'normal'")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -101,9 +101,9 @@ def upgrade() -> None:
|
||||
# crm_migration is the table owner and needs to run tenant-wide data migrations
|
||||
_exec("ALTER ROLE crm_migration NOSUPERUSER BYPASSRLS")
|
||||
|
||||
# Step 3: Transfer ALL table ownership to crm_migration
|
||||
# Step 3: Transfer ALL table ownership to crm_migration (only for tables that exist)
|
||||
for table in ALL_TABLES:
|
||||
_exec(f"ALTER TABLE public.{table} OWNER TO crm_migration")
|
||||
_exec(f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{table}') THEN ALTER TABLE public.{table} OWNER TO crm_migration; END IF; END $$")
|
||||
|
||||
# Transfer sequence ownership
|
||||
_exec("DO $$ DECLARE r RECORD; BEGIN FOR r IN SELECT sequence_name FROM information_schema.sequences WHERE sequence_schema = 'public' LOOP EXECUTE format('ALTER SEQUENCE public.%I OWNER TO crm_migration', r.sequence_name); END LOOP; END $$;")
|
||||
@@ -114,8 +114,8 @@ def upgrade() -> None:
|
||||
_exec(f"REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM {role}")
|
||||
_exec(f"REVOKE ALL PRIVILEGES ON SCHEMA public FROM {role}")
|
||||
|
||||
# Step 5: Drop crm_runtime role
|
||||
_exec("DROP ROLE IF EXISTS crm_runtime")
|
||||
# Step 5: Drop crm_runtime role (may fail if permissions insufficient)
|
||||
_exec("DO $$ BEGIN DROP ROLE IF EXISTS crm_runtime; EXCEPTION WHEN insufficient_privilege THEN NULL; END $$")
|
||||
|
||||
# Step 6: Grant schema USAGE to runtime roles
|
||||
_exec("GRANT USAGE ON SCHEMA public TO crm_api")
|
||||
@@ -125,12 +125,11 @@ def upgrade() -> None:
|
||||
# Step 7: Grant permissions to crm_auth (identity tables only)
|
||||
for table, privs in AUTH_TABLES.items():
|
||||
priv_str = ", ".join(privs)
|
||||
_exec(f"GRANT {priv_str} ON public.{table} TO crm_auth")
|
||||
_exec(f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{table}') THEN GRANT {priv_str} ON public.{table} TO crm_auth; END IF; END $$")
|
||||
|
||||
# Step 8: Grant CRUD on tenant tables to crm_api and crm_worker
|
||||
# Step 8: Grant CRUD on tenant tables to crm_api and crm_worker (only for tables that exist)
|
||||
for table in TENANT_TABLES:
|
||||
_exec(f"GRANT SELECT, INSERT, UPDATE, DELETE ON public.{table} TO crm_api")
|
||||
_exec(f"GRANT SELECT, INSERT, UPDATE, DELETE ON public.{table} TO crm_worker")
|
||||
_exec(f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{table}') THEN GRANT SELECT, INSERT, UPDATE, DELETE ON public.{table} TO crm_api; GRANT SELECT, INSERT, UPDATE, DELETE ON public.{table} TO crm_worker; END IF; END $$")
|
||||
|
||||
# Grant sequence USAGE to crm_api and crm_worker
|
||||
_exec("GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO crm_api")
|
||||
@@ -139,19 +138,19 @@ def upgrade() -> None:
|
||||
# Step 9: Grant global table access to crm_api (except alembic_version)
|
||||
api_global_tables = [t for t in GLOBAL_TABLES if t != "alembic_version"]
|
||||
for table in api_global_tables:
|
||||
_exec(f"GRANT SELECT, INSERT, UPDATE, DELETE ON public.{table} TO crm_api")
|
||||
_exec(f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{table}') THEN GRANT SELECT, INSERT, UPDATE, DELETE ON public.{table} TO crm_api; END IF; END $$")
|
||||
|
||||
# Step 10: Grant worker global table access
|
||||
for table, privs in WORKER_GLOBAL_TABLES.items():
|
||||
priv_str = ", ".join(privs)
|
||||
_exec(f"GRANT {priv_str} ON public.{table} TO crm_worker")
|
||||
_exec(f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{table}') THEN GRANT {priv_str} ON public.{table} TO crm_worker; END IF; END $$")
|
||||
|
||||
worker_global_tables = [
|
||||
t for t in GLOBAL_TABLES
|
||||
if t != "alembic_version" and t not in WORKER_GLOBAL_TABLES
|
||||
]
|
||||
for table in worker_global_tables:
|
||||
_exec(f"GRANT SELECT, INSERT, UPDATE, DELETE ON public.{table} TO crm_worker")
|
||||
_exec(f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{table}') THEN GRANT SELECT, INSERT, UPDATE, DELETE ON public.{table} TO crm_worker; END IF; END $$")
|
||||
|
||||
# Step 11: Drop ALL old RLS policies and create new fail-closed ones
|
||||
policy_template = (
|
||||
@@ -164,17 +163,11 @@ def upgrade() -> None:
|
||||
)
|
||||
|
||||
for table in TENANT_TABLES:
|
||||
_exec(f"DROP POLICY IF EXISTS tenant_isolation ON public.{table}")
|
||||
_exec(f"DROP POLICY IF EXISTS {table}_tenant_isolation ON public.{table}")
|
||||
_exec(f"ALTER TABLE public.{table} ENABLE ROW LEVEL SECURITY")
|
||||
_exec(f"ALTER TABLE public.{table} FORCE ROW LEVEL SECURITY")
|
||||
_exec(policy_template.format(table=table))
|
||||
_exec(f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{table}') THEN DROP POLICY IF EXISTS tenant_isolation ON public.{table}; DROP POLICY IF EXISTS {table}_tenant_isolation ON public.{table}; ALTER TABLE public.{table} ENABLE ROW LEVEL SECURITY; ALTER TABLE public.{table} FORCE ROW LEVEL SECURITY; {policy_template.format(table=table)}; END IF; END $$")
|
||||
|
||||
# Step 12: Disable RLS on global tables
|
||||
for table in GLOBAL_TABLES:
|
||||
_exec(f"DROP POLICY IF EXISTS tenant_isolation ON public.{table}")
|
||||
_exec(f"DROP POLICY IF EXISTS {table}_tenant_isolation ON public.{table}")
|
||||
_exec(f"ALTER TABLE public.{table} DISABLE ROW LEVEL SECURITY")
|
||||
_exec(f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{table}') THEN DROP POLICY IF EXISTS tenant_isolation ON public.{table}; DROP POLICY IF EXISTS {table}_tenant_isolation ON public.{table}; ALTER TABLE public.{table} DISABLE ROW LEVEL SECURITY; END IF; END $$")
|
||||
|
||||
# Step 13: Set default privileges for crm_migration owner
|
||||
_exec("ALTER DEFAULT PRIVILEGES FOR ROLE crm_migration IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO crm_api")
|
||||
|
||||
Reference in New Issue
Block a user