feat(backend): Add legacy token handling in NotionHistoryConnector and log warnings for legacy usage in indexing

This commit is contained in:
Anish Sarkar 2026-01-28 22:53:34 +05:30
parent b3f553802c
commit c6d25ed7d8
2 changed files with 72 additions and 9 deletions

View file

@ -77,6 +77,8 @@ class NotionHistoryConnector:
self._pages_with_skipped_content: list[str] = [] self._pages_with_skipped_content: list[str] = []
# Optional callback to notify about retry progress (for user notifications) # Optional callback to notify about retry progress (for user notifications)
self._on_retry_callback: RetryCallbackType | None = None self._on_retry_callback: RetryCallbackType | None = None
# Track if using legacy integration token (for upgrade notification)
self._using_legacy_token: bool = False
def set_retry_callback(self, callback: RetryCallbackType | None) -> None: def set_retry_callback(self, callback: RetryCallbackType | None) -> None:
""" """
@ -119,6 +121,18 @@ class NotionHistoryConnector:
config_data = connector.config.copy() config_data = connector.config.copy()
# Check for legacy integration token format first
# (for connectors created before OAuth was implemented)
legacy_token = config_data.get("NOTION_INTEGRATION_TOKEN")
raw_access_token = config_data.get("access_token")
# Validate that we have some form of token
if not raw_access_token and not legacy_token:
raise ValueError(
"Notion integration not properly connected. "
"Please remove and re-add the Notion connector."
)
# Decrypt credentials if they are encrypted # Decrypt credentials if they are encrypted
token_encrypted = config_data.get("_token_encrypted", False) token_encrypted = config_data.get("_token_encrypted", False)
if token_encrypted and config.SECRET_KEY: if token_encrypted and config.SECRET_KEY:
@ -143,13 +157,38 @@ class NotionHistoryConnector:
f"Failed to decrypt Notion credentials for connector {self._connector_id}: {e!s}" f"Failed to decrypt Notion credentials for connector {self._connector_id}: {e!s}"
) )
raise ValueError( raise ValueError(
f"Failed to decrypt Notion credentials: {e!s}" "Notion credentials could not be decrypted. "
"Please remove and re-add the Notion connector."
) from e ) from e
# Handle legacy format: convert NOTION_INTEGRATION_TOKEN to access_token
if not config_data.get("access_token") and legacy_token:
config_data["access_token"] = legacy_token
self._using_legacy_token = True
logger.info(
f"Using legacy NOTION_INTEGRATION_TOKEN for connector {self._connector_id}"
)
# Final validation: ensure we have a valid access_token after all processing
final_token = config_data.get("access_token")
if not final_token or (isinstance(final_token, str) and not final_token.strip()):
raise ValueError(
"Notion access token is invalid or empty. "
"Please remove and re-add the Notion connector."
)
try: try:
self._credentials = NotionAuthCredentialsBase.from_dict(config_data) self._credentials = NotionAuthCredentialsBase.from_dict(config_data)
except KeyError as e:
raise ValueError(
f"Notion credentials are incomplete (missing {e}). "
"Please reconnect your Notion account."
) from e
except Exception as e: except Exception as e:
raise ValueError(f"Invalid Notion credentials: {e!s}") from e raise ValueError(
f"Notion credentials format error: {e!s}. "
"Please reconnect your Notion account."
) from e
# Check if token is expired and refreshable # Check if token is expired and refreshable
if self._credentials.is_expired and self._credentials.is_refreshable: if self._credentials.is_expired and self._credentials.is_refreshable:
@ -356,6 +395,15 @@ class NotionHistoryConnector:
""" """
return len(self._pages_with_skipped_content) return len(self._pages_with_skipped_content)
def is_using_legacy_token(self) -> bool:
"""
Check if connector is using legacy integration token format.
Returns:
True if using legacy NOTION_INTEGRATION_TOKEN, False if using OAuth
"""
return self._using_legacy_token
def _record_skipped_content(self, page_title: str): def _record_skipped_content(self, page_title: str):
""" """
Record that a page had unsupported content skipped. Record that a page had unsupported content skipped.

View file

@ -176,6 +176,13 @@ async def index_notion_pages(
logger.info( logger.info(
f"{pages_with_skipped_content} pages had Notion AI content skipped (not available via API)" f"{pages_with_skipped_content} pages had Notion AI content skipped (not available via API)"
) )
# Check if using legacy integration token and log warning
if notion_client.is_using_legacy_token():
logger.warning(
f"Connector {connector_id} is using legacy integration token. "
"Recommend reconnecting with OAuth."
)
except Exception as e: except Exception as e:
await task_logger.log_task_failure( await task_logger.log_task_failure(
log_entry, log_entry,
@ -471,8 +478,8 @@ async def index_notion_pages(
# Add user-friendly message about skipped Notion AI content # Add user-friendly message about skipped Notion AI content
if pages_with_skipped_ai_content > 0: if pages_with_skipped_ai_content > 0:
result_message += ( result_message += (
f" Audio transcriptions and AI summaries from Notion aren't accessible " " Audio transcriptions and AI summaries from Notion aren't accessible "
f"via their API — all other content was saved." "via their API - all other content was saved."
) )
# Log success # Log success
@ -496,18 +503,26 @@ async def index_notion_pages(
# Clean up the async client # Clean up the async client
await notion_client.close() await notion_client.close()
# Return user-friendly message about skipped AI content (if any) # Build user-friendly notification messages
# This will be shown in the notification to inform users # This will be shown in the notification to inform users
user_notification_message = None notification_parts = []
if pages_with_skipped_ai_content > 0: if pages_with_skipped_ai_content > 0:
user_notification_message = ( notification_parts.append(
"Some Notion AI content couldn't be synced (Notion API limitation)" "Some Notion AI content couldn't be synced (API limitation)"
) )
if notion_client.is_using_legacy_token():
notification_parts.append(
"Using legacy token. Reconnect with OAuth for better reliability."
)
user_notification_message = " ".join(notification_parts) if notification_parts else None
return ( return (
total_processed, total_processed,
user_notification_message, user_notification_message,
) # Return message about skipped AI content if any )
except SQLAlchemyError as db_error: except SQLAlchemyError as db_error:
await session.rollback() await session.rollback()