mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
chore: ran both frontend and backend linting
This commit is contained in:
parent
6ea6e752f6
commit
c7c5caf559
15 changed files with 177 additions and 94 deletions
|
|
@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
|
|||
class ConfluenceHistoryConnector:
|
||||
"""
|
||||
Confluence connector with OAuth support and automatic token refresh.
|
||||
|
||||
|
||||
This connector uses OAuth 2.0 access tokens to authenticate with the
|
||||
Confluence API. It automatically refreshes expired tokens when needed.
|
||||
Also supports legacy API token authentication for backward compatibility.
|
||||
|
|
@ -81,8 +81,10 @@ class ConfluenceHistoryConnector:
|
|||
config_data = connector.config.copy()
|
||||
|
||||
# Check if using OAuth or legacy API token
|
||||
is_oauth = config_data.get("_token_encrypted", False) or config_data.get("access_token")
|
||||
|
||||
is_oauth = config_data.get("_token_encrypted", False) or config_data.get(
|
||||
"access_token"
|
||||
)
|
||||
|
||||
if is_oauth:
|
||||
# OAuth 2.0 authentication
|
||||
# Decrypt credentials if they are encrypted
|
||||
|
|
@ -93,12 +95,16 @@ class ConfluenceHistoryConnector:
|
|||
|
||||
# Decrypt sensitive fields
|
||||
if config_data.get("access_token"):
|
||||
config_data["access_token"] = token_encryption.decrypt_token(
|
||||
config_data["access_token"]
|
||||
config_data["access_token"] = (
|
||||
token_encryption.decrypt_token(
|
||||
config_data["access_token"]
|
||||
)
|
||||
)
|
||||
if config_data.get("refresh_token"):
|
||||
config_data["refresh_token"] = token_encryption.decrypt_token(
|
||||
config_data["refresh_token"]
|
||||
config_data["refresh_token"] = (
|
||||
token_encryption.decrypt_token(
|
||||
config_data["refresh_token"]
|
||||
)
|
||||
)
|
||||
|
||||
logger.info(
|
||||
|
|
@ -113,13 +119,19 @@ class ConfluenceHistoryConnector:
|
|||
) from e
|
||||
|
||||
try:
|
||||
self._credentials = AtlassianAuthCredentialsBase.from_dict(config_data)
|
||||
self._credentials = AtlassianAuthCredentialsBase.from_dict(
|
||||
config_data
|
||||
)
|
||||
# Store cloud_id and base_url for API calls (with backward compatibility for site_url)
|
||||
self._cloud_id = config_data.get("cloud_id")
|
||||
self._base_url = config_data.get("base_url") or config_data.get("site_url")
|
||||
self._base_url = config_data.get("base_url") or config_data.get(
|
||||
"site_url"
|
||||
)
|
||||
self._use_oauth = True
|
||||
except Exception as e:
|
||||
raise ValueError(f"Invalid Confluence OAuth credentials: {e!s}") from e
|
||||
raise ValueError(
|
||||
f"Invalid Confluence OAuth credentials: {e!s}"
|
||||
) from e
|
||||
else:
|
||||
# Legacy API token authentication
|
||||
self._legacy_email = config_data.get("CONFLUENCE_EMAIL")
|
||||
|
|
@ -127,11 +139,21 @@ class ConfluenceHistoryConnector:
|
|||
self._base_url = config_data.get("CONFLUENCE_BASE_URL")
|
||||
self._use_oauth = False
|
||||
|
||||
if not self._legacy_email or not self._legacy_api_token or not self._base_url:
|
||||
raise ValueError("Confluence credentials not found in connector config")
|
||||
if (
|
||||
not self._legacy_email
|
||||
or not self._legacy_api_token
|
||||
or not self._base_url
|
||||
):
|
||||
raise ValueError(
|
||||
"Confluence credentials not found in connector config"
|
||||
)
|
||||
|
||||
# Check if token is expired and refreshable (only for OAuth)
|
||||
if self._use_oauth and self._credentials.is_expired and self._credentials.is_refreshable:
|
||||
if (
|
||||
self._use_oauth
|
||||
and self._credentials.is_expired
|
||||
and self._credentials.is_refreshable
|
||||
):
|
||||
try:
|
||||
logger.info(
|
||||
f"Confluence token expired for connector {self._connector_id}, refreshing..."
|
||||
|
|
@ -170,7 +192,9 @@ class ConfluenceHistoryConnector:
|
|||
self._credentials = AtlassianAuthCredentialsBase.from_dict(config_data)
|
||||
self._cloud_id = config_data.get("cloud_id")
|
||||
# Handle backward compatibility: check both base_url and site_url
|
||||
self._base_url = config_data.get("base_url") or config_data.get("site_url")
|
||||
self._base_url = config_data.get("base_url") or config_data.get(
|
||||
"site_url"
|
||||
)
|
||||
|
||||
# Invalidate cached client so it's recreated with new token
|
||||
if self._http_client:
|
||||
|
|
@ -230,10 +254,10 @@ class ConfluenceHistoryConnector:
|
|||
if not self._use_oauth:
|
||||
# For legacy auth, use the base_url directly
|
||||
return self._base_url or ""
|
||||
|
||||
|
||||
if not self._cloud_id:
|
||||
raise ValueError("Cloud ID not available. Cannot construct API URL.")
|
||||
|
||||
|
||||
# Use the Atlassian API format: https://api.atlassian.com/ex/confluence/{cloudid}
|
||||
return f"https://api.atlassian.com/ex/confluence/{self._cloud_id}"
|
||||
|
||||
|
|
@ -261,11 +285,12 @@ class ConfluenceHistoryConnector:
|
|||
# For now, we'll use the legacy client's make_api_request method
|
||||
# But since it's sync, we'll need to wrap it
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(
|
||||
None, client.make_api_request, endpoint, params
|
||||
)
|
||||
|
||||
|
||||
# OAuth flow
|
||||
token = await self._get_valid_token()
|
||||
base_url = await self._get_base_url()
|
||||
|
|
@ -446,7 +471,9 @@ class ConfluenceHistoryConnector:
|
|||
if cursor:
|
||||
params["cursor"] = cursor
|
||||
|
||||
result = await self._make_api_request(f"pages/{page_id}/{comment_type}", params)
|
||||
result = await self._make_api_request(
|
||||
f"pages/{page_id}/{comment_type}", params
|
||||
)
|
||||
|
||||
if not isinstance(result, dict) or "results" not in result:
|
||||
break # No comments or invalid response
|
||||
|
|
@ -495,6 +522,7 @@ class ConfluenceHistoryConnector:
|
|||
await self._get_valid_token()
|
||||
# ConfluenceConnector.get_pages_by_date_range is synchronous
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(
|
||||
None,
|
||||
|
|
@ -504,7 +532,7 @@ class ConfluenceHistoryConnector:
|
|||
space_ids,
|
||||
include_comments,
|
||||
)
|
||||
|
||||
|
||||
# OAuth flow
|
||||
all_pages = []
|
||||
|
||||
|
|
@ -562,4 +590,3 @@ class ConfluenceHistoryConnector:
|
|||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Async context manager exit."""
|
||||
await self.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -283,11 +283,11 @@ class JiraConnector:
|
|||
# Query issues that were either created OR updated within the date range
|
||||
# Use end_date + 1 day with < operator to include the full end date
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
# Parse end_date and add 1 day for inclusive end date
|
||||
end_date_obj = datetime.strptime(end_date, "%Y-%m-%d")
|
||||
end_date_next = (end_date_obj + timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
# Check both created and updated dates to catch all relevant issues
|
||||
# Use 'created' and 'updated' (standard JQL field names)
|
||||
date_filter = (
|
||||
|
|
@ -297,9 +297,7 @@ class JiraConnector:
|
|||
|
||||
jql = f"{date_filter} ORDER BY created DESC"
|
||||
if project_key:
|
||||
jql = (
|
||||
f'project = "{project_key}" AND ({date_filter}) ORDER BY created DESC'
|
||||
)
|
||||
jql = f'project = "{project_key}" AND ({date_filter}) ORDER BY created DESC'
|
||||
|
||||
# Define fields to retrieve
|
||||
fields = [
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
|
|||
class JiraHistoryConnector:
|
||||
"""
|
||||
Jira connector with OAuth support and automatic token refresh.
|
||||
|
||||
|
||||
This connector uses OAuth 2.0 access tokens to authenticate with the
|
||||
Jira API. It automatically refreshes expired tokens when needed.
|
||||
Also supports legacy API token authentication for backward compatibility.
|
||||
|
|
@ -80,8 +80,10 @@ class JiraHistoryConnector:
|
|||
config_data = connector.config.copy()
|
||||
|
||||
# Check if using OAuth or legacy API token
|
||||
is_oauth = config_data.get("_token_encrypted", False) or config_data.get("access_token")
|
||||
|
||||
is_oauth = config_data.get("_token_encrypted", False) or config_data.get(
|
||||
"access_token"
|
||||
)
|
||||
|
||||
if is_oauth:
|
||||
# OAuth 2.0 authentication
|
||||
if not config.SECRET_KEY:
|
||||
|
|
@ -118,7 +120,9 @@ class JiraHistoryConnector:
|
|||
) from e
|
||||
|
||||
try:
|
||||
self._credentials = AtlassianAuthCredentialsBase.from_dict(config_data)
|
||||
self._credentials = AtlassianAuthCredentialsBase.from_dict(
|
||||
config_data
|
||||
)
|
||||
self._cloud_id = config_data.get("cloud_id")
|
||||
self._base_url = config_data.get("base_url")
|
||||
self._use_oauth = True
|
||||
|
|
@ -131,11 +135,19 @@ class JiraHistoryConnector:
|
|||
self._base_url = config_data.get("JIRA_BASE_URL")
|
||||
self._use_oauth = False
|
||||
|
||||
if not self._legacy_email or not self._legacy_api_token or not self._base_url:
|
||||
if (
|
||||
not self._legacy_email
|
||||
or not self._legacy_api_token
|
||||
or not self._base_url
|
||||
):
|
||||
raise ValueError("Jira credentials not found in connector config")
|
||||
|
||||
# Check if token is expired and refreshable (only for OAuth)
|
||||
if self._use_oauth and self._credentials.is_expired and self._credentials.is_refreshable:
|
||||
if (
|
||||
self._use_oauth
|
||||
and self._credentials.is_expired
|
||||
and self._credentials.is_refreshable
|
||||
):
|
||||
try:
|
||||
logger.info(
|
||||
f"Jira token expired for connector {self._connector_id}, refreshing..."
|
||||
|
|
@ -206,7 +218,7 @@ class JiraHistoryConnector:
|
|||
if self._use_oauth:
|
||||
# Ensure we have valid token (will refresh if needed)
|
||||
await self._get_valid_token()
|
||||
|
||||
|
||||
self._jira_client = JiraConnector(
|
||||
base_url=self._base_url,
|
||||
access_token=self._credentials.access_token,
|
||||
|
|
@ -230,7 +242,7 @@ class JiraHistoryConnector:
|
|||
access_token=self._credentials.access_token,
|
||||
cloud_id=self._cloud_id,
|
||||
)
|
||||
|
||||
|
||||
return self._jira_client
|
||||
|
||||
async def get_issues_by_date_range(
|
||||
|
|
@ -256,10 +268,10 @@ class JiraHistoryConnector:
|
|||
# Ensure token is valid (will refresh if needed)
|
||||
if self._use_oauth:
|
||||
await self._get_valid_token()
|
||||
|
||||
|
||||
# Get client with valid credentials
|
||||
client = await self._get_jira_client()
|
||||
|
||||
|
||||
# JiraConnector methods are synchronous, so we call them directly
|
||||
# Token refresh has already been handled above
|
||||
return client.get_issues_by_date_range(
|
||||
|
|
@ -317,4 +329,3 @@ class JiraHistoryConnector:
|
|||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Async context manager exit."""
|
||||
await self.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from .airtable_add_connector_route import (
|
|||
router as airtable_add_connector_router,
|
||||
)
|
||||
from .circleback_webhook_route import router as circleback_webhook_router
|
||||
from .confluence_add_connector_route import router as confluence_add_connector_router
|
||||
from .discord_add_connector_route import router as discord_add_connector_router
|
||||
from .documents_routes import router as documents_router
|
||||
from .editor_routes import router as editor_router
|
||||
|
|
@ -17,7 +18,6 @@ from .google_gmail_add_connector_route import (
|
|||
router as google_gmail_add_connector_router,
|
||||
)
|
||||
from .jira_add_connector_route import router as jira_add_connector_router
|
||||
from .confluence_add_connector_route import router as confluence_add_connector_router
|
||||
from .linear_add_connector_route import router as linear_add_connector_router
|
||||
from .logs_routes import router as logs_router
|
||||
from .luma_add_connector_route import router as luma_add_connector_router
|
||||
|
|
|
|||
|
|
@ -87,7 +87,9 @@ async def connect_confluence(space_id: int, user: User = Depends(current_active_
|
|||
raise HTTPException(status_code=400, detail="space_id is required")
|
||||
|
||||
if not config.ATLASSIAN_CLIENT_ID:
|
||||
raise HTTPException(status_code=500, detail="Atlassian OAuth not configured.")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Atlassian OAuth not configured."
|
||||
)
|
||||
|
||||
if not config.SECRET_KEY:
|
||||
raise HTTPException(
|
||||
|
|
@ -113,7 +115,9 @@ async def connect_confluence(space_id: int, user: User = Depends(current_active_
|
|||
|
||||
auth_url = f"{AUTHORIZATION_URL}?{urlencode(auth_params)}"
|
||||
|
||||
logger.info(f"Generated Confluence OAuth URL for user {user.id}, space {space_id}")
|
||||
logger.info(
|
||||
f"Generated Confluence OAuth URL for user {user.id}, space {space_id}"
|
||||
)
|
||||
return {"auth_url": auth_url}
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -216,7 +220,9 @@ async def confluence_callback(
|
|||
error_detail = token_response.text
|
||||
try:
|
||||
error_json = token_response.json()
|
||||
error_detail = error_json.get("error_description", error_json.get("error", error_detail))
|
||||
error_detail = error_json.get(
|
||||
"error_description", error_json.get("error", error_detail)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
raise HTTPException(
|
||||
|
|
@ -252,7 +258,9 @@ async def confluence_callback(
|
|||
break
|
||||
|
||||
if not cloud_id:
|
||||
logger.warning("Could not determine Confluence cloud ID from accessible resources")
|
||||
logger.warning(
|
||||
"Could not determine Confluence cloud ID from accessible resources"
|
||||
)
|
||||
|
||||
# Calculate expiration time (UTC, tz-aware)
|
||||
expires_at = None
|
||||
|
|
@ -409,7 +417,9 @@ async def refresh_confluence_token(
|
|||
error_detail = token_response.text
|
||||
try:
|
||||
error_json = token_response.json()
|
||||
error_detail = error_json.get("error_description", error_json.get("error", error_detail))
|
||||
error_detail = error_json.get(
|
||||
"error_description", error_json.get("error", error_detail)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
raise HTTPException(
|
||||
|
|
@ -431,7 +441,8 @@ async def refresh_confluence_token(
|
|||
|
||||
if not access_token:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="No access token received from Confluence refresh"
|
||||
status_code=400,
|
||||
detail="No access token received from Confluence refresh",
|
||||
)
|
||||
|
||||
# Update credentials object with encrypted tokens
|
||||
|
|
@ -449,7 +460,9 @@ async def refresh_confluence_token(
|
|||
credentials.cloud_id = connector.config.get("cloud_id")
|
||||
if not credentials.base_url:
|
||||
# Check both base_url and site_url for backward compatibility
|
||||
credentials.base_url = connector.config.get("base_url") or connector.config.get("site_url")
|
||||
credentials.base_url = connector.config.get(
|
||||
"base_url"
|
||||
) or connector.config.get("site_url")
|
||||
|
||||
# Update connector config with encrypted tokens
|
||||
credentials_dict = credentials.to_dict()
|
||||
|
|
@ -458,7 +471,9 @@ async def refresh_confluence_token(
|
|||
await session.commit()
|
||||
await session.refresh(connector)
|
||||
|
||||
logger.info(f"Successfully refreshed Confluence token for connector {connector.id}")
|
||||
logger.info(
|
||||
f"Successfully refreshed Confluence token for connector {connector.id}"
|
||||
)
|
||||
|
||||
return connector
|
||||
except HTTPException:
|
||||
|
|
@ -468,4 +483,3 @@ async def refresh_confluence_token(
|
|||
raise HTTPException(
|
||||
status_code=500, detail=f"Failed to refresh Confluence token: {e!s}"
|
||||
) from e
|
||||
|
||||
|
|
|
|||
|
|
@ -217,7 +217,9 @@ async def discord_callback(
|
|||
error_detail = token_response.text
|
||||
try:
|
||||
error_json = token_response.json()
|
||||
error_detail = error_json.get("error_description", error_json.get("error", error_detail))
|
||||
error_detail = error_json.get(
|
||||
"error_description", error_json.get("error", error_detail)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
raise HTTPException(
|
||||
|
|
@ -263,7 +265,9 @@ async def discord_callback(
|
|||
|
||||
# Store the bot token from config and OAuth metadata
|
||||
connector_config = {
|
||||
"bot_token": token_encryption.encrypt_token(bot_token), # Use bot token from config
|
||||
"bot_token": token_encryption.encrypt_token(
|
||||
bot_token
|
||||
), # Use bot token from config
|
||||
"oauth_access_token": token_encryption.encrypt_token(oauth_access_token)
|
||||
if oauth_access_token
|
||||
else None, # Store OAuth token for reference
|
||||
|
|
@ -356,7 +360,7 @@ async def refresh_discord_token(
|
|||
) -> SearchSourceConnector:
|
||||
"""
|
||||
Refresh the Discord OAuth tokens for a connector.
|
||||
|
||||
|
||||
Note: Bot tokens from config don't expire, but OAuth access tokens might.
|
||||
This function refreshes OAuth tokens if needed, but always uses bot token from config.
|
||||
|
||||
|
|
@ -400,7 +404,9 @@ async def refresh_discord_token(
|
|||
f"No refresh token available for connector {connector.id}. Using bot token from config."
|
||||
)
|
||||
# Update bot token from config (in case it was changed)
|
||||
credentials.bot_token = token_encryption.encrypt_token(config.DISCORD_BOT_TOKEN)
|
||||
credentials.bot_token = token_encryption.encrypt_token(
|
||||
config.DISCORD_BOT_TOKEN
|
||||
)
|
||||
credentials_dict = credentials.to_dict()
|
||||
credentials_dict["_token_encrypted"] = True
|
||||
connector.config = credentials_dict
|
||||
|
|
@ -428,7 +434,9 @@ async def refresh_discord_token(
|
|||
error_detail = token_response.text
|
||||
try:
|
||||
error_json = token_response.json()
|
||||
error_detail = error_json.get("error_description", error_json.get("error", error_detail))
|
||||
error_detail = error_json.get(
|
||||
"error_description", error_json.get("error", error_detail)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
# If refresh fails, bot token from config is still valid
|
||||
|
|
@ -437,7 +445,9 @@ async def refresh_discord_token(
|
|||
"Using bot token from config."
|
||||
)
|
||||
# Update bot token from config
|
||||
credentials.bot_token = token_encryption.encrypt_token(config.DISCORD_BOT_TOKEN)
|
||||
credentials.bot_token = token_encryption.encrypt_token(
|
||||
config.DISCORD_BOT_TOKEN
|
||||
)
|
||||
credentials.refresh_token = None # Clear invalid refresh token
|
||||
credentials_dict = credentials.to_dict()
|
||||
credentials_dict["_token_encrypted"] = True
|
||||
|
|
@ -463,7 +473,7 @@ async def refresh_discord_token(
|
|||
|
||||
# Always use bot token from config (bot tokens don't expire)
|
||||
credentials.bot_token = token_encryption.encrypt_token(config.DISCORD_BOT_TOKEN)
|
||||
|
||||
|
||||
# Update OAuth tokens if available
|
||||
if oauth_access_token:
|
||||
# Store OAuth access token for reference
|
||||
|
|
@ -493,7 +503,9 @@ async def refresh_discord_token(
|
|||
await session.commit()
|
||||
await session.refresh(connector)
|
||||
|
||||
logger.info(f"Successfully refreshed Discord OAuth tokens for connector {connector.id}")
|
||||
logger.info(
|
||||
f"Successfully refreshed Discord OAuth tokens for connector {connector.id}"
|
||||
)
|
||||
|
||||
return connector
|
||||
except HTTPException:
|
||||
|
|
@ -506,4 +518,3 @@ async def refresh_discord_token(
|
|||
raise HTTPException(
|
||||
status_code=500, detail=f"Failed to refresh Discord tokens: {e!s}"
|
||||
) from e
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ async def connect_jira(space_id: int, user: User = Depends(current_active_user))
|
|||
raise HTTPException(status_code=400, detail="space_id is required")
|
||||
|
||||
if not config.ATLASSIAN_CLIENT_ID:
|
||||
raise HTTPException(status_code=500, detail="Atlassian OAuth not configured.")
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Atlassian OAuth not configured."
|
||||
)
|
||||
|
||||
if not config.SECRET_KEY:
|
||||
raise HTTPException(
|
||||
|
|
@ -215,7 +217,9 @@ async def jira_callback(
|
|||
error_detail = token_response.text
|
||||
try:
|
||||
error_json = token_response.json()
|
||||
error_detail = error_json.get("error_description", error_json.get("error", error_detail))
|
||||
error_detail = error_json.get(
|
||||
"error_description", error_json.get("error", error_detail)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
raise HTTPException(
|
||||
|
|
@ -254,9 +258,7 @@ async def jira_callback(
|
|||
|
||||
# Filter for Jira instances (resources with type "jira" or id field)
|
||||
jira_instances = [
|
||||
r
|
||||
for r in resources
|
||||
if r.get("id") and (r.get("name") or r.get("url"))
|
||||
r for r in resources if r.get("id") and (r.get("name") or r.get("url"))
|
||||
]
|
||||
|
||||
if not jira_instances:
|
||||
|
|
@ -270,7 +272,7 @@ async def jira_callback(
|
|||
jira_instance = jira_instances[0]
|
||||
cloud_id = jira_instance["id"]
|
||||
base_url = jira_instance.get("url")
|
||||
|
||||
|
||||
# If URL is not provided, construct it from cloud_id
|
||||
if not base_url:
|
||||
# Try to extract from name or construct default format
|
||||
|
|
@ -433,7 +435,9 @@ async def refresh_jira_token(
|
|||
error_detail = token_response.text
|
||||
try:
|
||||
error_json = token_response.json()
|
||||
error_detail = error_json.get("error_description", error_json.get("error", error_detail))
|
||||
error_detail = error_json.get(
|
||||
"error_description", error_json.get("error", error_detail)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from pydantic import BaseModel, field_validator
|
|||
class AtlassianAuthCredentialsBase(BaseModel):
|
||||
"""
|
||||
Base model for Atlassian OAuth 2.0 credentials.
|
||||
|
||||
|
||||
Used for both Jira and Confluence connectors since they share
|
||||
the same Atlassian OAuth infrastructure and token structure.
|
||||
"""
|
||||
|
|
@ -84,4 +84,3 @@ class AtlassianAuthCredentialsBase(BaseModel):
|
|||
if isinstance(v, datetime):
|
||||
return v if v.tzinfo else v.replace(tzinfo=UTC)
|
||||
return v
|
||||
|
||||
|
|
|
|||
|
|
@ -73,4 +73,3 @@ class DiscordAuthCredentialsBase(BaseModel):
|
|||
if isinstance(v, datetime):
|
||||
return v if v.tzinfo else v.replace(tzinfo=UTC)
|
||||
return v
|
||||
|
||||
|
|
|
|||
|
|
@ -90,9 +90,11 @@ async def index_confluence_pages(
|
|||
{"stage": "client_initialization"},
|
||||
)
|
||||
|
||||
confluence_client: ConfluenceHistoryConnector | None = ConfluenceHistoryConnector(
|
||||
session=session,
|
||||
connector_id=connector_id,
|
||||
confluence_client: ConfluenceHistoryConnector | None = (
|
||||
ConfluenceHistoryConnector(
|
||||
session=session,
|
||||
connector_id=connector_id,
|
||||
)
|
||||
)
|
||||
|
||||
# Calculate date range
|
||||
|
|
@ -421,11 +423,11 @@ async def index_confluence_pages(
|
|||
logger.info(
|
||||
f"Confluence indexing completed: {documents_indexed} new pages, {documents_skipped} skipped"
|
||||
)
|
||||
|
||||
|
||||
# Close the client connection
|
||||
if confluence_client:
|
||||
await confluence_client.close()
|
||||
|
||||
|
||||
return (
|
||||
total_processed,
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -69,7 +69,9 @@ async def index_discord_messages(
|
|||
|
||||
try:
|
||||
# Normalize date parameters - handle 'undefined' strings from frontend
|
||||
if start_date and (start_date.lower() == "undefined" or start_date.strip() == ""):
|
||||
if start_date and (
|
||||
start_date.lower() == "undefined" or start_date.strip() == ""
|
||||
):
|
||||
start_date = None
|
||||
if end_date and (end_date.lower() == "undefined" or end_date.strip() == ""):
|
||||
end_date = None
|
||||
|
|
@ -118,12 +120,13 @@ async def index_discord_messages(
|
|||
elif has_legacy:
|
||||
# Backward compatibility: use legacy token format
|
||||
discord_token = connector.config.get("DISCORD_BOT_TOKEN")
|
||||
|
||||
|
||||
# Decrypt token if it's encrypted (legacy tokens might be encrypted)
|
||||
token_encrypted = connector.config.get("_token_encrypted", False)
|
||||
if token_encrypted and config.SECRET_KEY and discord_token:
|
||||
try:
|
||||
from app.utils.oauth_security import TokenEncryption
|
||||
|
||||
token_encryption = TokenEncryption(config.SECRET_KEY)
|
||||
discord_token = token_encryption.decrypt_token(discord_token)
|
||||
logger.info(
|
||||
|
|
@ -135,7 +138,7 @@ async def index_discord_messages(
|
|||
"Trying to use token as-is (might be unencrypted)."
|
||||
)
|
||||
# Continue with token as-is - might be unencrypted legacy token
|
||||
|
||||
|
||||
discord_client = DiscordConnector(token=discord_token)
|
||||
else:
|
||||
await task_logger.log_task_failure(
|
||||
|
|
@ -210,11 +213,16 @@ async def index_discord_messages(
|
|||
f"Date parsing error: {e!s}",
|
||||
{"error_type": "InvalidDateFormat", "start_date": start_date},
|
||||
)
|
||||
return 0, f"Invalid start_date format: {start_date}. Expected YYYY-MM-DD format."
|
||||
return (
|
||||
0,
|
||||
f"Invalid start_date format: {start_date}. Expected YYYY-MM-DD format.",
|
||||
)
|
||||
|
||||
try:
|
||||
end_date_iso = (
|
||||
datetime.strptime(end_date, "%Y-%m-%d").replace(tzinfo=UTC).isoformat()
|
||||
datetime.strptime(end_date, "%Y-%m-%d")
|
||||
.replace(tzinfo=UTC)
|
||||
.isoformat()
|
||||
)
|
||||
except ValueError as e:
|
||||
await task_logger.log_task_failure(
|
||||
|
|
@ -223,7 +231,10 @@ async def index_discord_messages(
|
|||
f"Date parsing error: {e!s}",
|
||||
{"error_type": "InvalidDateFormat", "end_date": end_date},
|
||||
)
|
||||
return 0, f"Invalid end_date format: {end_date}. Expected YYYY-MM-DD format."
|
||||
return (
|
||||
0,
|
||||
f"Invalid end_date format: {end_date}. Expected YYYY-MM-DD format.",
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Indexing Discord messages from {start_date_iso} to {end_date_iso}"
|
||||
|
|
@ -384,8 +395,10 @@ async def index_discord_messages(
|
|||
)
|
||||
|
||||
# Check if document with this unique identifier already exists
|
||||
existing_document = await check_document_by_unique_identifier(
|
||||
session, unique_identifier_hash
|
||||
existing_document = (
|
||||
await check_document_by_unique_identifier(
|
||||
session, unique_identifier_hash
|
||||
)
|
||||
)
|
||||
|
||||
if existing_document:
|
||||
|
|
@ -406,8 +419,10 @@ async def index_discord_messages(
|
|||
chunks = await create_document_chunks(
|
||||
combined_document_string
|
||||
)
|
||||
doc_embedding = config.embedding_model_instance.embed(
|
||||
combined_document_string
|
||||
doc_embedding = (
|
||||
config.embedding_model_instance.embed(
|
||||
combined_document_string
|
||||
)
|
||||
)
|
||||
|
||||
# Update existing document
|
||||
|
|
@ -429,7 +444,9 @@ async def index_discord_messages(
|
|||
|
||||
# Delete old chunks and add new ones
|
||||
existing_document.chunks = chunks
|
||||
existing_document.updated_at = get_current_timestamp()
|
||||
existing_document.updated_at = (
|
||||
get_current_timestamp()
|
||||
)
|
||||
|
||||
documents_indexed += 1
|
||||
logger.info(
|
||||
|
|
@ -439,7 +456,9 @@ async def index_discord_messages(
|
|||
|
||||
# Document doesn't exist - create new one
|
||||
# Process chunks
|
||||
chunks = await create_document_chunks(combined_document_string)
|
||||
chunks = await create_document_chunks(
|
||||
combined_document_string
|
||||
)
|
||||
doc_embedding = config.embedding_model_instance.embed(
|
||||
combined_document_string
|
||||
)
|
||||
|
|
|
|||
|
|
@ -95,9 +95,7 @@ async def index_jira_issues(
|
|||
|
||||
# Create connector with session and connector_id for internal refresh
|
||||
# Token refresh will happen automatically when needed
|
||||
jira_client = JiraHistoryConnector(
|
||||
session=session, connector_id=connector_id
|
||||
)
|
||||
jira_client = JiraHistoryConnector(session=session, connector_id=connector_id)
|
||||
|
||||
# Calculate date range
|
||||
# Handle "undefined" strings from frontend
|
||||
|
|
@ -395,10 +393,10 @@ async def index_jira_issues(
|
|||
logger.info(
|
||||
f"JIRA indexing completed: {documents_indexed} new issues, {documents_skipped} skipped"
|
||||
)
|
||||
|
||||
|
||||
# Clean up the connector
|
||||
await jira_client.close()
|
||||
|
||||
|
||||
return (
|
||||
total_processed,
|
||||
None,
|
||||
|
|
@ -435,4 +433,4 @@ async def index_jira_issues(
|
|||
await jira_client.close()
|
||||
except Exception:
|
||||
pass
|
||||
return 0, f"Failed to index JIRA issues: {e!s}"
|
||||
return 0, f"Failed to index JIRA issues: {e!s}"
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ export const ConfluenceConfig: FC<ConfluenceConfigProps> = ({
|
|||
|
||||
// For OAuth connectors, show simple info message
|
||||
if (isOAuth) {
|
||||
const siteUrl = (connector.config?.base_url as string) || (connector.config?.site_url as string) || "Unknown";
|
||||
const siteUrl =
|
||||
(connector.config?.base_url as string) || (connector.config?.site_url as string) || "Unknown";
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* OAuth Info */}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ export const DiscordConfig: FC<DiscordConfigProps> = () => {
|
|||
<div className="text-xs sm:text-sm">
|
||||
<p className="font-medium text-xs sm:text-sm">Add Bot to Servers</p>
|
||||
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
|
||||
Before indexing, make sure the Discord bot has been added to the servers (guilds) you want to
|
||||
index. The bot can only access messages from servers it's been added to. Use the OAuth
|
||||
authorization flow to add the bot to your servers.
|
||||
Before indexing, make sure the Discord bot has been added to the servers (guilds) you
|
||||
want to index. The bot can only access messages from servers it's been added to. Use the
|
||||
OAuth authorization flow to add the bot to your servers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -449,13 +449,13 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
|
|||
case "JIRA_CONNECTOR": {
|
||||
// Check if this is an OAuth connector (has access_token or _token_encrypted flag)
|
||||
const isJiraOAuth = !!(originalConfig.access_token || originalConfig._token_encrypted);
|
||||
|
||||
|
||||
if (isJiraOAuth) {
|
||||
// OAuth connectors don't allow editing credentials through the form
|
||||
// Only allow name changes, which are handled separately
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Legacy API token connector - allow editing credentials
|
||||
if (
|
||||
formData.JIRA_BASE_URL !== originalConfig.JIRA_BASE_URL ||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue