From f1a715e04e37fd793fa95abb1dfcbc3bc553a6e9 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 7 Jan 2026 13:13:19 +0200 Subject: [PATCH] refactor: move Linear OAuth utils to connector, use httpx.AsyncClient --- .../app/connectors/airtable_connector.py | 5 -- .../app/connectors/linear_connector.py | 48 +++++++++++++++ .../app/connectors/linear_oauth.py | 60 ------------------- .../app/routes/linear_add_connector_route.py | 4 +- 4 files changed, 50 insertions(+), 67 deletions(-) delete mode 100644 surfsense_backend/app/connectors/linear_oauth.py diff --git a/surfsense_backend/app/connectors/airtable_connector.py b/surfsense_backend/app/connectors/airtable_connector.py index ecbba7a19..30a366cdd 100644 --- a/surfsense_backend/app/connectors/airtable_connector.py +++ b/surfsense_backend/app/connectors/airtable_connector.py @@ -399,11 +399,6 @@ async def fetch_airtable_user_email(access_token: str) -> str | None: Returns: User's email address or None if fetch fails """ - import httpx - import logging - - logger = logging.getLogger(__name__) - try: async with httpx.AsyncClient() as client: response = await client.get( diff --git a/surfsense_backend/app/connectors/linear_connector.py b/surfsense_backend/app/connectors/linear_connector.py index 404f60e66..61980e7ba 100644 --- a/surfsense_backend/app/connectors/linear_connector.py +++ b/surfsense_backend/app/connectors/linear_connector.py @@ -9,6 +9,7 @@ import logging from datetime import datetime from typing import Any +import httpx import requests from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select @@ -21,6 +22,53 @@ from app.utils.oauth_security import TokenEncryption logger = logging.getLogger(__name__) +LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql" + +ORGANIZATION_QUERY = """ +query { + organization { + name + } +} +""" + + +async def fetch_linear_organization_name(access_token: str) -> str | None: + """ + Fetch organization/workspace name from Linear GraphQL API. + + Args: + access_token: The Linear OAuth access token + + Returns: + Organization name or None if fetch fails + """ + try: + async with httpx.AsyncClient() as client: + response = await client.post( + LINEAR_GRAPHQL_URL, + headers={ + "Authorization": access_token, + "Content-Type": "application/json", + }, + json={"query": ORGANIZATION_QUERY}, + timeout=10.0, + ) + + if response.status_code == 200: + data = response.json() + org_name = data.get("data", {}).get("organization", {}).get("name") + if org_name: + logger.debug(f"Fetched Linear organization name: {org_name}") + return org_name + + logger.warning(f"Failed to fetch Linear org info: {response.status_code}") + return None + + except Exception as e: + logger.warning(f"Error fetching Linear organization name: {e!s}") + return None + class LinearConnector: """Class for retrieving issues and comments from Linear.""" diff --git a/surfsense_backend/app/connectors/linear_oauth.py b/surfsense_backend/app/connectors/linear_oauth.py deleted file mode 100644 index 96336fe94..000000000 --- a/surfsense_backend/app/connectors/linear_oauth.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Linear OAuth Utilities. - -Provides functions for fetching user/organization info from Linear API. -Separated from linear_connector.py to avoid circular imports. -""" - -import logging - -import httpx - -logger = logging.getLogger(__name__) - -LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql" - -ORGANIZATION_QUERY = """ -query { - organization { - name - } -} -""" - - -async def fetch_linear_organization_name(access_token: str) -> str | None: - """ - Fetch organization/workspace name from Linear GraphQL API. - - Args: - access_token: The Linear OAuth access token - - Returns: - Organization name or None if fetch fails - """ - try: - async with httpx.AsyncClient() as client: - response = await client.post( - LINEAR_GRAPHQL_URL, - headers={ - "Authorization": access_token, - "Content-Type": "application/json", - }, - json={"query": ORGANIZATION_QUERY}, - timeout=10.0, - ) - - if response.status_code == 200: - data = response.json() - org_name = data.get("data", {}).get("organization", {}).get("name") - if org_name: - logger.debug(f"Fetched Linear organization name: {org_name}") - return org_name - - logger.warning(f"Failed to fetch Linear org info: {response.status_code}") - return None - - except Exception as e: - logger.warning(f"Error fetching Linear organization name: {e!s}") - return None - diff --git a/surfsense_backend/app/routes/linear_add_connector_route.py b/surfsense_backend/app/routes/linear_add_connector_route.py index ca1d09568..db79afdcb 100644 --- a/surfsense_backend/app/routes/linear_add_connector_route.py +++ b/surfsense_backend/app/routes/linear_add_connector_route.py @@ -23,7 +23,7 @@ from app.db import ( User, get_async_session, ) -from app.connectors.linear_oauth import fetch_linear_organization_name +from app.connectors.linear_connector import fetch_linear_organization_name from app.schemas.linear_auth_credentials import LinearAuthCredentialsBase from app.users import current_active_user from app.utils.connector_naming import check_duplicate_connector, generate_unique_connector_name @@ -454,4 +454,4 @@ async def refresh_linear_token( logger.error(f"Failed to refresh Linear token: {e!s}", exc_info=True) raise HTTPException( status_code=500, detail=f"Failed to refresh Linear token: {e!s}" - ) from e + ) from e \ No newline at end of file