mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-30 21:59:46 +02:00
fix: restore duplicate check for non-OAuth connectors
This commit is contained in:
parent
f1a715e04e
commit
5f0013c109
7 changed files with 26 additions and 10 deletions
|
|
@ -16,7 +16,6 @@ from sqlalchemy.future import select
|
||||||
|
|
||||||
from app.config import config
|
from app.config import config
|
||||||
from app.db import SearchSourceConnector
|
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.schemas.linear_auth_credentials import LinearAuthCredentialsBase
|
||||||
from app.utils.oauth_security import TokenEncryption
|
from app.utils.oauth_security import TokenEncryption
|
||||||
|
|
||||||
|
|
@ -169,6 +168,9 @@ class LinearConnector:
|
||||||
f"Connector {self._connector_id} not found; cannot refresh token."
|
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
|
# Refresh token
|
||||||
connector = await refresh_linear_token(self._session, connector)
|
connector = await refresh_linear_token(self._session, connector)
|
||||||
|
|
||||||
|
|
@ -640,4 +642,3 @@ class LinearConnector:
|
||||||
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return iso_date
|
return iso_date
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,6 @@ async def airtable_callback(
|
||||||
status_code=400, detail="No access token received from Airtable"
|
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)
|
user_email = await fetch_airtable_user_email(access_token)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ async def calendar_callback(
|
||||||
creds = flow.credentials
|
creds = flow.credentials
|
||||||
creds_dict = json.loads(creds.to_json())
|
creds_dict = json.loads(creds.to_json())
|
||||||
|
|
||||||
# Fetch user email before encrypting credentials
|
# Fetch user email
|
||||||
user_email = fetch_google_user_email(creds)
|
user_email = fetch_google_user_email(creds)
|
||||||
|
|
||||||
# Encrypt sensitive credentials before storing
|
# Encrypt sensitive credentials before storing
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ async def drive_callback(
|
||||||
creds = flow.credentials
|
creds = flow.credentials
|
||||||
creds_dict = json.loads(creds.to_json())
|
creds_dict = json.loads(creds.to_json())
|
||||||
|
|
||||||
# Fetch user email before encrypting credentials
|
# Fetch user email
|
||||||
user_email = fetch_google_user_email(creds)
|
user_email = fetch_google_user_email(creds)
|
||||||
|
|
||||||
# Encrypt sensitive credentials before storing
|
# Encrypt sensitive credentials before storing
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ async def gmail_callback(
|
||||||
creds = flow.credentials
|
creds = flow.credentials
|
||||||
creds_dict = json.loads(creds.to_json())
|
creds_dict = json.loads(creds.to_json())
|
||||||
|
|
||||||
# Fetch user email before encrypting credentials
|
# Fetch user email
|
||||||
user_email = fetch_google_user_email(creds)
|
user_email = fetch_google_user_email(creds)
|
||||||
|
|
||||||
# Encrypt sensitive credentials before storing
|
# Encrypt sensitive credentials before storing
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ async def linear_callback(
|
||||||
status_code=400, detail="No access token received from Linear"
|
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)
|
org_name = await fetch_linear_organization_name(access_token)
|
||||||
|
|
||||||
# Calculate expiration time (UTC, tz-aware)
|
# Calculate expiration time (UTC, tz-aware)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ PUT /search-source-connectors/{connector_id} - Update a specific connector
|
||||||
DELETE /search-source-connectors/{connector_id} - Delete 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
|
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
|
import logging
|
||||||
|
|
@ -111,7 +112,7 @@ async def create_search_source_connector(
|
||||||
Create a new search source connector.
|
Create a new search source connector.
|
||||||
Requires CONNECTORS_CREATE permission.
|
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.
|
The config must contain the appropriate keys for the connector type.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -124,6 +125,21 @@ async def create_search_source_connector(
|
||||||
"You don't have permission to create connectors in this search space",
|
"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
|
# Prepare connector data
|
||||||
connector_data = connector.model_dump()
|
connector_data = connector.model_dump()
|
||||||
|
|
||||||
|
|
@ -169,7 +185,7 @@ async def create_search_source_connector(
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=409,
|
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
|
) from e
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue