fix: restore duplicate check for non-OAuth connectors

This commit is contained in:
CREDO23 2026-01-07 13:33:05 +02:00
parent f1a715e04e
commit 5f0013c109
7 changed files with 26 additions and 10 deletions

View file

@ -16,7 +16,6 @@ from sqlalchemy.future import select
from app.config import config
from app.db import SearchSourceConnector
from app.routes.linear_add_connector_route import refresh_linear_token
from app.schemas.linear_auth_credentials import LinearAuthCredentialsBase
from app.utils.oauth_security import TokenEncryption
@ -169,6 +168,9 @@ class LinearConnector:
f"Connector {self._connector_id} not found; cannot refresh token."
)
# Lazy import to avoid circular dependency
from app.routes.linear_add_connector_route import refresh_linear_token
# Refresh token
connector = await refresh_linear_token(self._session, connector)
@ -640,4 +642,3 @@ class LinearConnector:
return dt.strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
return iso_date

View file

@ -277,7 +277,6 @@ async def airtable_callback(
status_code=400, detail="No access token received from Airtable"
)
# Fetch user email before encrypting credentials
user_email = await fetch_airtable_user_email(access_token)

View file

@ -174,7 +174,7 @@ async def calendar_callback(
creds = flow.credentials
creds_dict = json.loads(creds.to_json())
# Fetch user email before encrypting credentials
# Fetch user email
user_email = fetch_google_user_email(creds)
# Encrypt sensitive credentials before storing

View file

@ -229,7 +229,7 @@ async def drive_callback(
creds = flow.credentials
creds_dict = json.loads(creds.to_json())
# Fetch user email before encrypting credentials
# Fetch user email
user_email = fetch_google_user_email(creds)
# Encrypt sensitive credentials before storing

View file

@ -205,7 +205,7 @@ async def gmail_callback(
creds = flow.credentials
creds_dict = json.loads(creds.to_json())
# Fetch user email before encrypting credentials
# Fetch user email
user_email = fetch_google_user_email(creds)
# Encrypt sensitive credentials before storing

View file

@ -242,7 +242,7 @@ async def linear_callback(
status_code=400, detail="No access token received from Linear"
)
# Fetch organization name before encrypting credentials
# Fetch organization name
org_name = await fetch_linear_organization_name(access_token)
# Calculate expiration time (UTC, tz-aware)

View file

@ -7,7 +7,8 @@ PUT /search-source-connectors/{connector_id} - Update a specific connector
DELETE /search-source-connectors/{connector_id} - Delete a specific connector
POST /search-source-connectors/{connector_id}/index - Index content from a connector to a search space
Note: Each search space can have multiple connectors of the same type per user (uniqueness is no longer enforced, you may connect several accounts of the same type).
Note: OAuth connectors (Gmail, Drive, Slack, etc.) support multiple accounts per search space.
Non-OAuth connectors (BookStack, GitHub, etc.) are limited to one per search space.
"""
import logging
@ -111,7 +112,7 @@ async def create_search_source_connector(
Create a new search source connector.
Requires CONNECTORS_CREATE permission.
Each search space can have multiple connectors of the same type (e.g., multiple Gmail, Slack, etc. accounts).
Each search space can have only one connector of each type (based on search_space_id and connector_type).
The config must contain the appropriate keys for the connector type.
"""
try:
@ -124,6 +125,21 @@ async def create_search_source_connector(
"You don't have permission to create connectors in this search space",
)
# Check if a connector with the same type already exists for this search space
# (for non-OAuth connectors that don't support multiple accounts)
result = await session.execute(
select(SearchSourceConnector).filter(
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.connector_type == connector.connector_type,
)
)
existing_connector = result.scalars().first()
if existing_connector:
raise HTTPException(
status_code=409,
detail=f"A connector with type {connector.connector_type} already exists in this search space.",
)
# Prepare connector data
connector_data = connector.model_dump()
@ -169,7 +185,7 @@ async def create_search_source_connector(
await session.rollback()
raise HTTPException(
status_code=409,
detail=f"Integrity error: {e!s}",
detail=f"Integrity error: A connector with this type already exists in this search space. {e!s}",
) from e
except HTTPException:
await session.rollback()