diff --git a/surfsense_backend/alembic/versions/57_allow_multiple_connectors_per_type.py b/surfsense_backend/alembic/versions/57_allow_multiple_connectors_per_type.py index bd2fccf72..25558f42e 100644 --- a/surfsense_backend/alembic/versions/57_allow_multiple_connectors_per_type.py +++ b/surfsense_backend/alembic/versions/57_allow_multiple_connectors_per_type.py @@ -7,6 +7,7 @@ Create Date: 2026-01-06 12:00:00.000000 """ from collections.abc import Sequence + from alembic import op # revision identifiers, used by Alembic. @@ -17,6 +18,7 @@ depends_on: str | Sequence[str] | None = None from sqlalchemy import text + def upgrade() -> None: connection = op.get_bind() constraint_exists = connection.execute( @@ -31,9 +33,10 @@ def upgrade() -> None: op.drop_constraint( "uq_searchspace_user_connector_type", "search_source_connectors", - type_="unique" + type_="unique", ) + def downgrade() -> None: connection = op.get_bind() constraint_exists = connection.execute( @@ -48,6 +51,5 @@ def downgrade() -> None: op.create_unique_constraint( "uq_searchspace_user_connector_type", "search_source_connectors", - ["search_space_id", "user_id", "connector_type"] + ["search_space_id", "user_id", "connector_type"], ) - diff --git a/surfsense_backend/alembic/versions/58_unique_connector_name_per_space_user.py b/surfsense_backend/alembic/versions/58_unique_connector_name_per_space_user.py index b840af267..7c35ab1d8 100644 --- a/surfsense_backend/alembic/versions/58_unique_connector_name_per_space_user.py +++ b/surfsense_backend/alembic/versions/58_unique_connector_name_per_space_user.py @@ -8,6 +8,7 @@ Create Date: 2026-01-06 14:00:00.000000 """ from collections.abc import Sequence + from alembic import op revision: str = "58" @@ -17,6 +18,7 @@ depends_on: str | Sequence[str] | None = None from sqlalchemy import text + def upgrade() -> None: connection = op.get_bind() constraint_exists = connection.execute( @@ -31,9 +33,10 @@ def upgrade() -> None: op.create_unique_constraint( "uq_searchspace_user_connector_name", "search_source_connectors", - ["search_space_id", "user_id", "name"] + ["search_space_id", "user_id", "name"], ) + def downgrade() -> None: connection = op.get_bind() constraint_exists = connection.execute( @@ -48,6 +51,5 @@ def downgrade() -> None: op.drop_constraint( "uq_searchspace_user_connector_name", "search_source_connectors", - type_="unique" + type_="unique", ) - diff --git a/surfsense_backend/app/connectors/airtable_connector.py b/surfsense_backend/app/connectors/airtable_connector.py index 30a366cdd..8264f4bfa 100644 --- a/surfsense_backend/app/connectors/airtable_connector.py +++ b/surfsense_backend/app/connectors/airtable_connector.py @@ -414,7 +414,9 @@ async def fetch_airtable_user_email(access_token: str) -> str | None: logger.debug(f"Fetched Airtable user email: {email}") return email - logger.warning(f"Failed to fetch Airtable user info: {response.status_code}") + logger.warning( + f"Failed to fetch Airtable user info: {response.status_code}" + ) return None except Exception as e: diff --git a/surfsense_backend/app/routes/airtable_add_connector_route.py b/surfsense_backend/app/routes/airtable_add_connector_route.py index 5fa8180bf..5efa63e59 100644 --- a/surfsense_backend/app/routes/airtable_add_connector_route.py +++ b/surfsense_backend/app/routes/airtable_add_connector_route.py @@ -11,19 +11,21 @@ from fastapi.responses import RedirectResponse from pydantic import ValidationError from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select from app.config import config +from app.connectors.airtable_connector import fetch_airtable_user_email from app.db import ( SearchSourceConnector, SearchSourceConnectorType, User, get_async_session, ) -from app.connectors.airtable_connector import fetch_airtable_user_email from app.schemas.airtable_auth_credentials import AirtableAuthCredentialsBase from app.users import current_active_user -from app.utils.connector_naming import check_duplicate_connector, generate_unique_connector_name +from app.utils.connector_naming import ( + check_duplicate_connector, + generate_unique_connector_name, +) from app.utils.oauth_security import OAuthStateManager, TokenEncryption logger = logging.getLogger(__name__) @@ -279,7 +281,6 @@ async def airtable_callback( user_email = await fetch_airtable_user_email(access_token) - # Calculate expiration time (UTC, tz-aware) expires_at = None if token_json.get("expires_in"): diff --git a/surfsense_backend/app/routes/confluence_add_connector_route.py b/surfsense_backend/app/routes/confluence_add_connector_route.py index 56abf62ce..6c5830b17 100644 --- a/surfsense_backend/app/routes/confluence_add_connector_route.py +++ b/surfsense_backend/app/routes/confluence_add_connector_route.py @@ -14,7 +14,6 @@ from fastapi.responses import RedirectResponse from pydantic import ValidationError from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select from app.config import config from app.db import ( @@ -25,12 +24,12 @@ from app.db import ( ) from app.schemas.atlassian_auth_credentials import AtlassianAuthCredentialsBase from app.users import current_active_user -from app.utils.oauth_security import OAuthStateManager, TokenEncryption from app.utils.connector_naming import ( check_duplicate_connector, extract_identifier_from_credentials, generate_unique_connector_name, ) +from app.utils.oauth_security import OAuthStateManager, TokenEncryption logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/routes/discord_add_connector_route.py b/surfsense_backend/app/routes/discord_add_connector_route.py index 0bda191c6..1d8b40fcf 100644 --- a/surfsense_backend/app/routes/discord_add_connector_route.py +++ b/surfsense_backend/app/routes/discord_add_connector_route.py @@ -14,7 +14,6 @@ from fastapi.responses import RedirectResponse from pydantic import ValidationError from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select from app.config import config from app.db import ( @@ -25,12 +24,12 @@ from app.db import ( ) from app.schemas.discord_auth_credentials import DiscordAuthCredentialsBase from app.users import current_active_user -from app.utils.oauth_security import OAuthStateManager, TokenEncryption from app.utils.connector_naming import ( check_duplicate_connector, extract_identifier_from_credentials, generate_unique_connector_name, ) +from app.utils.oauth_security import OAuthStateManager, TokenEncryption logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/routes/google_calendar_add_connector_route.py b/surfsense_backend/app/routes/google_calendar_add_connector_route.py index a1292fa43..08e5c2f04 100644 --- a/surfsense_backend/app/routes/google_calendar_add_connector_route.py +++ b/surfsense_backend/app/routes/google_calendar_add_connector_route.py @@ -12,7 +12,6 @@ from google_auth_oauthlib.flow import Flow from pydantic import ValidationError from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select from app.config import config from app.connectors.google_gmail_connector import fetch_google_user_email @@ -23,7 +22,10 @@ from app.db import ( get_async_session, ) from app.users import current_active_user -from app.utils.connector_naming import check_duplicate_connector, generate_unique_connector_name +from app.utils.connector_naming import ( + check_duplicate_connector, + generate_unique_connector_name, +) from app.utils.oauth_security import OAuthStateManager, TokenEncryption logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/routes/google_drive_add_connector_route.py b/surfsense_backend/app/routes/google_drive_add_connector_route.py index 30a46a618..e15aed762 100644 --- a/surfsense_backend/app/routes/google_drive_add_connector_route.py +++ b/surfsense_backend/app/routes/google_drive_add_connector_route.py @@ -37,7 +37,10 @@ from app.db import ( get_async_session, ) from app.users import current_active_user -from app.utils.connector_naming import check_duplicate_connector, generate_unique_connector_name +from app.utils.connector_naming import ( + check_duplicate_connector, + generate_unique_connector_name, +) from app.utils.oauth_security import OAuthStateManager, TokenEncryption # Relax token scope validation for Google OAuth diff --git a/surfsense_backend/app/routes/google_gmail_add_connector_route.py b/surfsense_backend/app/routes/google_gmail_add_connector_route.py index 9919894f3..19fa019ce 100644 --- a/surfsense_backend/app/routes/google_gmail_add_connector_route.py +++ b/surfsense_backend/app/routes/google_gmail_add_connector_route.py @@ -12,7 +12,6 @@ from google_auth_oauthlib.flow import Flow from pydantic import ValidationError from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select from app.config import config from app.connectors.google_gmail_connector import fetch_google_user_email @@ -23,7 +22,10 @@ from app.db import ( get_async_session, ) from app.users import current_active_user -from app.utils.connector_naming import check_duplicate_connector, generate_unique_connector_name +from app.utils.connector_naming import ( + check_duplicate_connector, + generate_unique_connector_name, +) from app.utils.oauth_security import OAuthStateManager, TokenEncryption logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/routes/jira_add_connector_route.py b/surfsense_backend/app/routes/jira_add_connector_route.py index 744cf7fd4..fb66f4da7 100644 --- a/surfsense_backend/app/routes/jira_add_connector_route.py +++ b/surfsense_backend/app/routes/jira_add_connector_route.py @@ -15,7 +15,6 @@ from fastapi.responses import RedirectResponse from pydantic import ValidationError from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select from app.config import config from app.db import ( @@ -26,12 +25,12 @@ from app.db import ( ) from app.schemas.atlassian_auth_credentials import AtlassianAuthCredentialsBase from app.users import current_active_user -from app.utils.oauth_security import OAuthStateManager, TokenEncryption from app.utils.connector_naming import ( check_duplicate_connector, extract_identifier_from_credentials, generate_unique_connector_name, ) +from app.utils.oauth_security import OAuthStateManager, TokenEncryption logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/routes/linear_add_connector_route.py b/surfsense_backend/app/routes/linear_add_connector_route.py index ce5cdbfb3..fc9501bfb 100644 --- a/surfsense_backend/app/routes/linear_add_connector_route.py +++ b/surfsense_backend/app/routes/linear_add_connector_route.py @@ -14,19 +14,21 @@ from fastapi.responses import RedirectResponse from pydantic import ValidationError from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select from app.config import config +from app.connectors.linear_connector import fetch_linear_organization_name from app.db import ( SearchSourceConnector, SearchSourceConnectorType, User, get_async_session, ) -from app.connectors.linear_connector import fetch_linear_organization_name from app.schemas.linear_auth_credentials import LinearAuthCredentialsBase from app.users import current_active_user -from app.utils.connector_naming import check_duplicate_connector, generate_unique_connector_name +from app.utils.connector_naming import ( + check_duplicate_connector, + generate_unique_connector_name, +) from app.utils.oauth_security import OAuthStateManager, TokenEncryption logger = logging.getLogger(__name__) @@ -454,4 +456,4 @@ async def refresh_linear_token( logger.error(f"Failed to refresh Linear token: {e!s}", exc_info=True) raise HTTPException( status_code=500, detail=f"Failed to refresh Linear token: {e!s}" - ) from e \ No newline at end of file + ) from e diff --git a/surfsense_backend/app/routes/notion_add_connector_route.py b/surfsense_backend/app/routes/notion_add_connector_route.py index c0331b4bc..aac821793 100644 --- a/surfsense_backend/app/routes/notion_add_connector_route.py +++ b/surfsense_backend/app/routes/notion_add_connector_route.py @@ -14,7 +14,6 @@ from fastapi.responses import RedirectResponse from pydantic import ValidationError from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select from app.config import config from app.db import ( @@ -25,12 +24,12 @@ from app.db import ( ) from app.schemas.notion_auth_credentials import NotionAuthCredentialsBase from app.users import current_active_user -from app.utils.oauth_security import OAuthStateManager, TokenEncryption from app.utils.connector_naming import ( check_duplicate_connector, extract_identifier_from_credentials, generate_unique_connector_name, ) +from app.utils.oauth_security import OAuthStateManager, TokenEncryption logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/routes/slack_add_connector_route.py b/surfsense_backend/app/routes/slack_add_connector_route.py index 6da7e5d24..62d2ccaaa 100644 --- a/surfsense_backend/app/routes/slack_add_connector_route.py +++ b/surfsense_backend/app/routes/slack_add_connector_route.py @@ -14,7 +14,6 @@ from fastapi.responses import RedirectResponse from pydantic import ValidationError from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select from app.config import config from app.db import ( diff --git a/surfsense_backend/app/utils/connector_naming.py b/surfsense_backend/app/utils/connector_naming.py index 17b791c34..f9f1fdd21 100644 --- a/surfsense_backend/app/utils/connector_naming.py +++ b/surfsense_backend/app/utils/connector_naming.py @@ -31,7 +31,9 @@ BASE_NAME_FOR_TYPE = { def get_base_name_for_type(connector_type: SearchSourceConnectorType) -> str: """Get a friendly display name for a connector type.""" - return BASE_NAME_FOR_TYPE.get(connector_type, connector_type.replace("_", " ").title()) + return BASE_NAME_FOR_TYPE.get( + connector_type, connector_type.replace("_", " ").title() + ) def extract_identifier_from_credentials( @@ -178,9 +180,10 @@ async def generate_unique_connector_name( return f"{base} - {identifier}" # Fallback: use counter for uniqueness - count = await count_connectors_of_type(session, connector_type, search_space_id, user_id) + count = await count_connectors_of_type( + session, connector_type, search_space_id, user_id + ) if count == 0: return base return f"{base} ({count + 1})" -