From b0b0f3517b2cf76dce7aefc4aa5690e3b85dcb11 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:35:52 +0530 Subject: [PATCH] feat(gateway): add Slack external chat platform --- .../145_add_slack_gateway_platform.py | 102 ++++++++++++++++++ surfsense_backend/app/db.py | 14 ++- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 surfsense_backend/alembic/versions/145_add_slack_gateway_platform.py diff --git a/surfsense_backend/alembic/versions/145_add_slack_gateway_platform.py b/surfsense_backend/alembic/versions/145_add_slack_gateway_platform.py new file mode 100644 index 000000000..f4ab18e72 --- /dev/null +++ b/surfsense_backend/alembic/versions/145_add_slack_gateway_platform.py @@ -0,0 +1,102 @@ +"""add slack gateway platform + +Revision ID: 145 +Revises: 144 +Create Date: 2026-05-31 +""" + +from __future__ import annotations + +from collections.abc import Sequence + +import sqlalchemy as sa + +from alembic import op + +revision: str = "145" +down_revision: str | None = "144" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def _enum_value_exists(enum_name: str, value: str) -> bool: + conn = op.get_bind() + return ( + conn.execute( + sa.text( + "SELECT 1 FROM pg_enum e " + "JOIN pg_type t ON t.oid = e.enumtypid " + "WHERE t.typname = :enum_name AND e.enumlabel = :value" + ), + {"enum_name": enum_name, "value": value}, + ).fetchone() + is not None + ) + + +def _index_exists(index_name: str) -> bool: + conn = op.get_bind() + return ( + conn.execute( + sa.text( + "SELECT 1 FROM pg_indexes " + "WHERE schemaname = current_schema() AND indexname = :index_name" + ), + {"index_name": index_name}, + ).fetchone() + is not None + ) + + +def upgrade() -> None: + if not _enum_value_exists("external_chat_platform", "slack"): + op.execute("ALTER TYPE external_chat_platform ADD VALUE 'slack'") + + if _index_exists("uq_external_chat_accounts_system_platform"): + op.drop_index( + "uq_external_chat_accounts_system_platform", + table_name="external_chat_accounts", + ) + + op.create_index( + "uq_external_chat_accounts_system_platform", + "external_chat_accounts", + ["platform"], + unique=True, + postgresql_where=sa.text( + "is_system_account = true AND NOT (cursor_state ? 'team_id')" + ), + if_not_exists=True, + ) + op.create_index( + "uq_external_chat_accounts_slack_team", + "external_chat_accounts", + ["platform", sa.text("(cursor_state ->> 'team_id')")], + unique=True, + postgresql_where=sa.text( + "is_system_account = true AND cursor_state ? 'team_id'" + ), + if_not_exists=True, + ) + + +def downgrade() -> None: + if _index_exists("uq_external_chat_accounts_slack_team"): + op.drop_index( + "uq_external_chat_accounts_slack_team", + table_name="external_chat_accounts", + ) + if _index_exists("uq_external_chat_accounts_system_platform"): + op.drop_index( + "uq_external_chat_accounts_system_platform", + table_name="external_chat_accounts", + ) + op.create_index( + "uq_external_chat_accounts_system_platform", + "external_chat_accounts", + ["platform"], + unique=True, + postgresql_where=sa.text("is_system_account = true"), + if_not_exists=True, + ) + # PostgreSQL enum values are intentionally not removed on downgrade. diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index e9b301ece..14ba8cdec 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -577,6 +577,7 @@ class ChatVisibility(StrEnum): class ExternalChatPlatform(StrEnum): TELEGRAM = "telegram" WHATSAPP = "whatsapp" + SLACK = "slack" SIGNAL = "signal" @@ -888,7 +889,18 @@ class ExternalChatAccount(Base, TimestampMixin): "uq_external_chat_accounts_system_platform", "platform", unique=True, - postgresql_where=text("is_system_account = true"), + postgresql_where=text( + "is_system_account = true AND NOT (cursor_state ? 'team_id')" + ), + ), + Index( + "uq_external_chat_accounts_slack_team", + "platform", + text("(cursor_state ->> 'team_id')"), + unique=True, + postgresql_where=text( + "is_system_account = true AND cursor_state ? 'team_id'" + ), ), Index( "uq_external_chat_accounts_webhook_secret",