"""finalize v1 productization Revision ID: 20260421_finalize_v1_productization Revises: 20260421_add_ai_project_control_tables Create Date: 2026-04-21 """ from __future__ import annotations from alembic import op import sqlalchemy as sa try: from sqlalchemy.dialects.postgresql import JSONB except Exception: # pragma: no cover JSONB = sa.JSON revision = "20260421_finalize_v1_productization" down_revision = "20260421_add_ai_project_control_tables" branch_labels = None depends_on = None def _json_type(bind): return JSONB() if bind.dialect.name == "postgresql" else sa.JSON() def _table_exists(bind, table_name: str) -> bool: return sa.inspect(bind).has_table(table_name) def _column_exists(bind, table_name: str, column_name: str) -> bool: if not _table_exists(bind, table_name): return False return any(column.get("name") == column_name for column in sa.inspect(bind).get_columns(table_name)) def upgrade() -> None: bind = op.get_bind() json_type = _json_type(bind) if _table_exists(bind, "users") and _column_exists(bind, "users", "role"): op.execute("UPDATE users SET role = 'OWNER' WHERE UPPER(role) = 'BOSS'") op.execute("UPDATE users SET role = 'REVIEWER' WHERE UPPER(role) = 'ADMIN'") op.execute("UPDATE users SET role = 'BUILDER' WHERE UPPER(role) IN ('AI', 'FE', 'BE')") if _table_exists(bind, "revenue_events") and not _column_exists(bind, "revenue_events", "category"): op.add_column( "revenue_events", sa.Column("category", sa.String(length=32), nullable=False, server_default="other"), ) op.create_index("ix_revenue_events_category", "revenue_events", ["category"], unique=False) if _table_exists(bind, "repo_bindings") and not _column_exists(bind, "repo_bindings", "installation_account_login"): op.add_column( "repo_bindings", sa.Column("installation_account_login", sa.String(length=255), nullable=True), ) if _table_exists(bind, "progress_events") and not _column_exists(bind, "progress_events", "delivery_id"): op.add_column("progress_events", sa.Column("delivery_id", sa.String(length=255), nullable=True)) op.create_index("ix_progress_events_delivery_id", "progress_events", ["delivery_id"], unique=False) if _table_exists(bind, "progress_events") and not _column_exists(bind, "progress_events", "event_source_id"): op.add_column("progress_events", sa.Column("event_source_id", sa.String(length=255), nullable=True)) op.create_index("ix_progress_events_event_source_id", "progress_events", ["event_source_id"], unique=False) if not _table_exists(bind, "github_app_installations"): op.create_table( "github_app_installations", sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True), sa.Column("tenant_id", sa.BigInteger(), nullable=False), sa.Column("created_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("updated_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("installation_id", sa.String(length=255), nullable=False), sa.Column("account_login", sa.String(length=255), nullable=False), sa.Column("account_type", sa.String(length=64), nullable=True), sa.Column("app_slug", sa.String(length=255), nullable=True), sa.Column("target_type", sa.String(length=64), nullable=True), sa.Column("status", sa.String(length=32), nullable=False, server_default="active"), sa.Column("permissions", json_type, nullable=False), sa.Column("events", json_type, nullable=False), sa.Column("metadata", json_type, nullable=False), sa.Column("last_synced_at", sa.TIMESTAMP(), nullable=True), sa.UniqueConstraint("tenant_id", "installation_id", name="uq_github_app_installations_tenant_installation"), ) op.create_index("ix_github_app_installations_tenant_id", "github_app_installations", ["tenant_id"], unique=False) op.create_index("ix_github_app_installations_installation_id", "github_app_installations", ["installation_id"], unique=False) op.create_index("ix_github_app_installations_account_login", "github_app_installations", ["account_login"], unique=False) op.create_index("ix_github_app_installations_status", "github_app_installations", ["status"], unique=False) if not _table_exists(bind, "github_app_repositories"): op.create_table( "github_app_repositories", sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True), sa.Column("tenant_id", sa.BigInteger(), nullable=False), sa.Column("created_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("updated_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("installation_id", sa.String(length=255), nullable=False), sa.Column("external_repo_id", sa.String(length=255), nullable=False), sa.Column("owner", sa.String(length=255), nullable=False), sa.Column("repo_name", sa.String(length=255), nullable=False), sa.Column("full_name", sa.String(length=255), nullable=False), sa.Column("default_branch", sa.String(length=255), nullable=True), sa.Column("is_private", sa.Boolean(), nullable=False, server_default=sa.true()), sa.Column("permissions", json_type, nullable=False), sa.Column("status", sa.String(length=32), nullable=False, server_default="active"), sa.Column("last_synced_at", sa.TIMESTAMP(), nullable=True), sa.UniqueConstraint( "tenant_id", "installation_id", "external_repo_id", name="uq_github_app_repositories_repo", ), ) op.create_index("ix_github_app_repositories_tenant_id", "github_app_repositories", ["tenant_id"], unique=False) op.create_index("ix_github_app_repositories_installation_id", "github_app_repositories", ["installation_id"], unique=False) op.create_index("ix_github_app_repositories_external_repo_id", "github_app_repositories", ["external_repo_id"], unique=False) op.create_index("ix_github_app_repositories_owner", "github_app_repositories", ["owner"], unique=False) op.create_index("ix_github_app_repositories_repo_name", "github_app_repositories", ["repo_name"], unique=False) op.create_index("ix_github_app_repositories_full_name", "github_app_repositories", ["full_name"], unique=False) op.create_index("ix_github_app_repositories_status", "github_app_repositories", ["status"], unique=False) if not _table_exists(bind, "github_webhook_deliveries"): op.create_table( "github_webhook_deliveries", sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True), sa.Column("tenant_id", sa.BigInteger(), nullable=False), sa.Column("created_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("updated_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False), sa.Column("delivery_id", sa.String(length=255), nullable=False), sa.Column("event_type", sa.String(length=64), nullable=False), sa.Column("repo_binding_id", sa.BigInteger(), sa.ForeignKey("repo_bindings.id"), nullable=True), sa.Column("project_id", sa.BigInteger(), sa.ForeignKey("projects.id"), nullable=True), sa.Column("installation_id", sa.String(length=255), nullable=True), sa.Column("status", sa.String(length=32), nullable=False, server_default="received"), sa.Column("failure_reason", sa.Text(), nullable=True), sa.Column("payload", json_type, nullable=False), sa.Column("received_at", sa.TIMESTAMP(), nullable=True), sa.UniqueConstraint("tenant_id", "delivery_id", name="uq_github_webhook_deliveries_tenant_delivery"), ) op.create_index("ix_github_webhook_deliveries_tenant_id", "github_webhook_deliveries", ["tenant_id"], unique=False) op.create_index("ix_github_webhook_deliveries_delivery_id", "github_webhook_deliveries", ["delivery_id"], unique=False) op.create_index("ix_github_webhook_deliveries_event_type", "github_webhook_deliveries", ["event_type"], unique=False) op.create_index("ix_github_webhook_deliveries_repo_binding_id", "github_webhook_deliveries", ["repo_binding_id"], unique=False) op.create_index("ix_github_webhook_deliveries_project_id", "github_webhook_deliveries", ["project_id"], unique=False) op.create_index("ix_github_webhook_deliveries_installation_id", "github_webhook_deliveries", ["installation_id"], unique=False) op.create_index("ix_github_webhook_deliveries_status", "github_webhook_deliveries", ["status"], unique=False) op.create_index("ix_github_webhook_deliveries_received_at", "github_webhook_deliveries", ["received_at"], unique=False) def downgrade() -> None: bind = op.get_bind() if _table_exists(bind, "github_webhook_deliveries"): op.drop_table("github_webhook_deliveries") if _table_exists(bind, "github_app_repositories"): op.drop_table("github_app_repositories") if _table_exists(bind, "github_app_installations"): op.drop_table("github_app_installations") if _table_exists(bind, "progress_events") and _column_exists(bind, "progress_events", "event_source_id"): op.drop_index("ix_progress_events_event_source_id", table_name="progress_events") op.drop_column("progress_events", "event_source_id") if _table_exists(bind, "progress_events") and _column_exists(bind, "progress_events", "delivery_id"): op.drop_index("ix_progress_events_delivery_id", table_name="progress_events") op.drop_column("progress_events", "delivery_id") if _table_exists(bind, "repo_bindings") and _column_exists(bind, "repo_bindings", "installation_account_login"): op.drop_column("repo_bindings", "installation_account_login") if _table_exists(bind, "revenue_events") and _column_exists(bind, "revenue_events", "category"): op.drop_index("ix_revenue_events_category", table_name="revenue_events") op.drop_column("revenue_events", "category")