Skip to content

Commit a904707

Browse files
committed
feat(integrations): add catalog-backed connections
1 parent b3fead8 commit a904707

25 files changed

Lines changed: 5089 additions & 1016 deletions
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""consolidated integrations catalog
2+
3+
Revision ID: b7e1c9f2a3d4
4+
Revises: a3d7c9e8b4f2
5+
Create Date: 2026-05-25 21:00:00.000000
6+
7+
Adds the consolidated Integrations catalog model: an integration catalog table.
8+
9+
Additive only -- no existing tables are dropped. Secret, OAuthIntegration,
10+
WorkspaceOAuthProvider, and MCPIntegration storage continue to drive credential
11+
and MCP behavior.
12+
"""
13+
14+
from collections.abc import Sequence
15+
16+
import sqlalchemy as sa
17+
from sqlalchemy.dialects import postgresql
18+
19+
from alembic import op
20+
21+
# revision identifiers, used by Alembic.
22+
revision: str = "b7e1c9f2a3d4"
23+
down_revision: str | None = "a3d7c9e8b4f2"
24+
branch_labels: str | Sequence[str] | None = None
25+
depends_on: str | Sequence[str] | None = None
26+
27+
28+
INTEGRATION_SOURCE_VALUES = ("platform", "workspace")
29+
30+
31+
def upgrade() -> None:
32+
# --- enums ---------------------------------------------------------
33+
sa.Enum(*INTEGRATION_SOURCE_VALUES, name="integrationsource").create(op.get_bind())
34+
35+
# --- integration ---------------------------------------------------
36+
op.create_table(
37+
"integration",
38+
sa.Column("id", sa.UUID(), nullable=False),
39+
sa.Column("workspace_id", sa.UUID(), nullable=True),
40+
sa.Column("namespace", sa.String(), nullable=False),
41+
sa.Column("display_name", sa.String(), nullable=False),
42+
sa.Column("description", sa.String(), nullable=True),
43+
sa.Column("icon_url", sa.String(), nullable=True),
44+
sa.Column(
45+
"source",
46+
postgresql.ENUM(
47+
*INTEGRATION_SOURCE_VALUES,
48+
name="integrationsource",
49+
create_type=False,
50+
),
51+
nullable=False,
52+
),
53+
sa.Column(
54+
"created_at",
55+
sa.TIMESTAMP(timezone=True),
56+
server_default=sa.text("now()"),
57+
nullable=False,
58+
),
59+
sa.Column(
60+
"updated_at",
61+
sa.TIMESTAMP(timezone=True),
62+
server_default=sa.text("now()"),
63+
nullable=False,
64+
),
65+
sa.ForeignKeyConstraint(
66+
["workspace_id"],
67+
["workspace.id"],
68+
name=op.f("fk_integration_workspace_id_workspace"),
69+
ondelete="CASCADE",
70+
),
71+
sa.PrimaryKeyConstraint("id", name=op.f("pk_integration")),
72+
sa.UniqueConstraint(
73+
"workspace_id",
74+
"namespace",
75+
name="uq_integration_workspace_namespace",
76+
),
77+
)
78+
op.create_index(op.f("ix_integration_id"), "integration", ["id"], unique=True)
79+
op.create_index("ix_integration_namespace", "integration", ["namespace"])
80+
81+
82+
def downgrade() -> None:
83+
op.drop_index("ix_integration_namespace", table_name="integration")
84+
op.drop_index(op.f("ix_integration_id"), table_name="integration")
85+
op.drop_table("integration")
86+
87+
sa.Enum(name="integrationsource").drop(op.get_bind())
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""backfill secret namespaces into integrations
2+
3+
Revision ID: c8f2d1e4a5b6
4+
Revises: b7e1c9f2a3d4
5+
Create Date: 2026-05-25 23:00:00.000000
6+
7+
For secret names that don't already have an Integration row, a workspace-scoped
8+
``Integration`` is created on demand so the Integrations catalog can project
9+
legacy static credentials without moving credential storage.
10+
11+
Additive only -- the legacy ``secret`` table remains the source of truth for
12+
static/API-key credentials.
13+
"""
14+
15+
from collections.abc import Sequence
16+
17+
from alembic import op
18+
19+
# revision identifiers, used by Alembic.
20+
revision: str = "c8f2d1e4a5b6"
21+
down_revision: str | None = "b7e1c9f2a3d4"
22+
branch_labels: str | Sequence[str] | None = None
23+
depends_on: str | Sequence[str] | None = None
24+
25+
26+
def upgrade() -> None:
27+
# Backfill workspace-scoped Integration rows for every Secret name that
28+
# doesn't already have a catalog entry. Source=workspace so we can tell
29+
# these apart from the platform-seeded providers.
30+
op.execute(
31+
"""
32+
INSERT INTO integration (
33+
id, workspace_id, namespace, display_name, description,
34+
source, created_at, updated_at
35+
)
36+
SELECT
37+
gen_random_uuid(),
38+
s.workspace_id,
39+
s.name,
40+
INITCAP(REPLACE(s.name, '_', ' ')),
41+
'Backfilled from legacy credential.',
42+
'workspace',
43+
NOW(),
44+
NOW()
45+
FROM (
46+
SELECT DISTINCT workspace_id, name
47+
FROM secret
48+
) AS s
49+
WHERE NOT EXISTS (
50+
SELECT 1
51+
FROM integration i
52+
WHERE i.namespace = s.name
53+
AND (
54+
i.workspace_id IS NULL
55+
OR i.workspace_id = s.workspace_id
56+
)
57+
)
58+
"""
59+
)
60+
61+
62+
def downgrade() -> None:
63+
# This migration only adds catalog rows for pre-existing secret names.
64+
# Leave them in place on downgrade; deleting them could hide credentials
65+
# from the catalog after a downgrade/upgrade loop.
66+
pass

0 commit comments

Comments
 (0)