diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 91c41ee..00b8f63 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -2,6 +2,7 @@ from fastapi import APIRouter from .chats_routes import router as chats_router from .documents_routes import router as documents_router +from .google_calendar_add_connector_route import router as google_oauth_router from .llm_config_routes import router as llm_config_router from .logs_routes import router as logs_router from .podcasts_routes import router as podcasts_router @@ -15,5 +16,6 @@ router.include_router(documents_router) router.include_router(podcasts_router) router.include_router(chats_router) router.include_router(search_source_connectors_router) +router.include_router(google_oauth_router) router.include_router(llm_config_router) router.include_router(logs_router) diff --git a/surfsense_backend/app/routes/google_calendar_connector_route.py b/surfsense_backend/app/routes/google_calendar_add_connector_route.py similarity index 55% rename from surfsense_backend/app/routes/google_calendar_connector_route.py rename to surfsense_backend/app/routes/google_calendar_add_connector_route.py index d4eee5d..d78d6dd 100644 --- a/surfsense_backend/app/routes/google_calendar_connector_route.py +++ b/surfsense_backend/app/routes/google_calendar_add_connector_route.py @@ -2,16 +2,19 @@ import base64 import json +from sqlite3 import IntegrityError from uuid import UUID +from venv import logger from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import RedirectResponse from google_auth_oauthlib.flow import Flow +from jsonschema import ValidationError from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from app.config import config -from app.db import GoogleCalendarAccount, User, get_async_session +from app.db import SearchSourceConnector, User, get_async_session from app.users import current_active_user router = APIRouter() @@ -41,7 +44,7 @@ def get_google_flow(): ) from e -@router.get("/auth/google/calendar/connector/init/") +@router.get("/auth/google/calendar/connector/add/") async def connect_calendar(space_id: int, user: User = Depends(current_active_user)): try: if not space_id: @@ -90,31 +93,57 @@ async def calendar_callback( flow.fetch_token(code=code) creds = flow.credentials - token = creds.token - refresh_token = creds.refresh_token + creds_dict = json.loads(creds.to_json()) - existing = await session.scalar( - select(GoogleCalendarAccount).where( - GoogleCalendarAccount.user_id == user_id - ) - ) - if existing: - existing.access_token = token - existing.refresh_token = refresh_token or existing.refresh_token - else: - session.add( - GoogleCalendarAccount( - user_id=user_id, - access_token=token, - refresh_token=refresh_token, + try: + # Check if a connector with the same type already exists for this user + result = await session.execute( + select(SearchSourceConnector).filter( + SearchSourceConnector.user_id == user_id, + SearchSourceConnector.connector_type == "GOOGLE_CALENDAR_CONNECTOR", ) ) + existing_connector = result.scalars().first() + if existing_connector: + raise HTTPException( + status_code=409, + detail="A GOOGLE_CALENDAR_CONNECTOR connector already exists. Each user can have only one connector of each type.", + ) + db_connector = SearchSourceConnector( + name="Google Calendar Connector", + connector_type="GOOGLE_CALENDAR_CONNECTOR", + config=creds_dict, + user_id=user_id, + is_indexable=True, + ) + session.add(db_connector) + await session.commit() + await session.refresh(db_connector) + return RedirectResponse( + f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/add/google-calendar-connector?success=true" + ) + except ValidationError as e: + await session.rollback() + raise HTTPException( + status_code=422, detail=f"Validation error: {e!s}" + ) from e + except IntegrityError as e: + await session.rollback() + raise HTTPException( + status_code=409, + detail=f"Integrity error: A connector with this type already exists. {e!s}", + ) from e + except HTTPException: + await session.rollback() + raise + except Exception as e: + logger.error(f"Failed to create search source connector: {e!s}") + await session.rollback() + raise HTTPException( + status_code=500, + detail=f"Failed to create search source connector: {e!s}", + ) from e - await session.commit() - - return RedirectResponse( - f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/add/google-calendar-connector?success=true" - ) except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to complete Google OAuth: {e!s}" diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py index f91bd9f..a65f1cf 100644 --- a/surfsense_backend/app/routes/search_source_connectors_routes.py +++ b/surfsense_backend/app/routes/search_source_connectors_routes.py @@ -40,6 +40,7 @@ from app.tasks.connectors_indexing_tasks import ( index_confluence_pages, index_discord_messages, index_github_repos, + index_google_calendar_events, index_jira_issues, index_linear_issues, index_notion_pages, @@ -489,6 +490,24 @@ async def index_connector_content( ) response_message = "ClickUp indexing started in the background." + elif ( + connector.connector_type + == SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR + ): + # Run indexing in background + logger.info( + f"Triggering Google Calendar indexing for connector {connector_id} into search space {search_space_id} from {indexing_from} to {indexing_to}" + ) + background_tasks.add_task( + run_google_calendar_indexing_with_new_session, + connector_id, + search_space_id, + str(user.id), + indexing_from, + indexing_to, + ) + response_message = "Google Calendar indexing started in the background." + elif connector.connector_type == SearchSourceConnectorType.DISCORD_CONNECTOR: # Run indexing in background logger.info( @@ -1034,3 +1053,63 @@ async def run_clickup_indexing( exc_info=True, ) # Optionally update status in DB to indicate failure + + +# Add new helper functions for Google Calendar indexing +async def run_google_calendar_indexing_with_new_session( + connector_id: int, + search_space_id: int, + user_id: str, + start_date: str, + end_date: str, +): + """Wrapper to run Google Calendar indexing with its own database session.""" + logger.info( + f"Background task started: Indexing Google Calendar connector {connector_id} into space {search_space_id} from {start_date} to {end_date}" + ) + async with async_session_maker() as session: + await run_google_calendar_indexing( + session, connector_id, search_space_id, user_id, start_date, end_date + ) + logger.info( + f"Background task finished: Indexing Google Calendar connector {connector_id}" + ) + + +async def run_google_calendar_indexing( + session: AsyncSession, + connector_id: int, + search_space_id: int, + user_id: str, + start_date: str, + end_date: str, +): + """Runs the Google Calendar indexing task and updates the timestamp.""" + try: + indexed_count, error_message = await index_google_calendar_events( + session, + connector_id, + search_space_id, + user_id, + start_date, + end_date, + update_last_indexed=False, + ) + if error_message: + logger.error( + f"Google Calendar indexing failed for connector {connector_id}: {error_message}" + ) + # Optionally update status in DB to indicate failure + else: + logger.info( + f"Google Calendar indexing successful for connector {connector_id}. Indexed {indexed_count} documents." + ) + # Update the last indexed timestamp only on success + await update_connector_last_indexed(session, connector_id) + await session.commit() # Commit timestamp update + except Exception as e: + logger.error( + f"Critical error in run_google_calendar_indexing for connector {connector_id}: {e}", + exc_info=True, + ) + # Optionally update status in DB to indicate failure