PMFlow/backend/alembic/versions/20260421_finalize_v1_produc...

175 lines
10 KiB
Python

"""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")