from __future__ import annotations import time from typing import List, Optional, Union from fastapi import APIRouter, HTTPException, Query from app.models.schemas import ( GoogleApiIdsResponse, GoogleApiScopesResponse, GoogleScopeNamesEntry, GoogleScopeNamesListResponse, GoogleScopeNamesResponse, ) from app.services.google_scope_service import GoogleScopeError from .deps import GENERATED, REFERENCE, SOURCE, get_scope_service router = APIRouter() _VALID_PERMISSION_LEVELS = frozenset({"non-sensitive", "sensitive", "restricted"}) def _filters_echo( api_name: Optional[str], oauth_only: bool, no_scope: bool, search: Optional[str], scope_name: Optional[str], permission_level: Optional[List[str]], required: Optional[bool], recommended: Optional[bool], scope_name_only: bool, ids_only: bool, ) -> dict: return { "api_name": api_name, "oauth_only": oauth_only, "no_scope": no_scope, "search": search, "scope_name": scope_name, "permission_level": permission_level, "required": required, "recommended": recommended, "scope_name_only": scope_name_only, "ids_only": ids_only, } def _validate_permission_levels(permission_level: Optional[List[str]]) -> None: if permission_level: bad = [p for p in permission_level if p.strip().lower() not in _VALID_PERMISSION_LEVELS] if bad: raise HTTPException( status_code=422, detail=f"Invalid permission_level values: {', '.join(bad)}. " "Allowed: non-sensitive, sensitive, restricted.", ) @router.get("", response_model=Union[GoogleApiScopesResponse, GoogleScopeNamesResponse, GoogleScopeNamesListResponse, GoogleApiIdsResponse]) async def list_google_scopes( api_name: Optional[str] = Query(None, description="Filter to a single API id (e.g. gmail, youtube, drive)"), oauth_only: bool = Query(False, description="Only APIs that require OAuth scopes"), no_scope: bool = Query(False, description="Only APIs that do not require OAuth scopes"), search: Optional[str] = Query(None, description="Free-text search across API id/name and scope fields"), scope_name: Optional[str] = Query(None, description="Keep scopes whose name or URI contains this value"), permission_level: Optional[List[str]] = Query(None, description="Filter scopes by permission level (repeatable)"), required: Optional[bool] = Query(None, description="Filter scopes by required flag"), recommended: Optional[bool] = Query(None, description="Filter scopes by recommended flag"), scope_name_only: bool = Query(False, description="Return only scope names instead of full scope objects"), ids_only: bool = Query(False, description="Return only the matching API ids"), page: int = Query(1, ge=1, description="Page number"), page_size: int = Query(50, ge=1, le=200, description="Items per page"), ) -> Union[GoogleApiScopesResponse, GoogleScopeNamesResponse, GoogleScopeNamesListResponse, GoogleApiIdsResponse]: """List all Google integrations with their OAuth scopes and current selection. Supports combining every filter: ``api_name``, ``oauth_only``/``no_scope``, ``search``, ``scope_name``, ``permission_level``, ``required``, ``recommended``, ``scope_name_only``, ``ids_only`` and pagination. """ _validate_permission_levels(permission_level) started = time.perf_counter() service = get_scope_service() try: plans = await service.load_all_plans() state = await service.load_state() data = service.build_data(plans, state) except GoogleScopeError as exc: return GoogleApiScopesResponse( success=False, time_ms=round((time.perf_counter() - started) * 1000, 3), error=exc.message, ) filtered = service.filter_data( data, api_name=api_name, oauth_only=oauth_only, no_scope=no_scope, search=search, scope_name=scope_name, permission_level=permission_level, required=required, recommended=recommended, scope_name_only=scope_name_only, ) if ids_only: return GoogleApiIdsResponse( success=True, time_ms=round((time.perf_counter() - started) * 1000, 3), count=len(filtered), ids=[e["id"] for e in filtered], ) # Compact single-API shape for api_name + scope_name_only (see example). if scope_name_only and api_name: entry = next((e for e in filtered if e["id"] == api_name), None) if entry is None: raise HTTPException(status_code=404, detail=f"Unknown API id '{api_name}'") return GoogleScopeNamesResponse(api_name=entry["id"], scopes=entry["scopes"]) if scope_name_only: start = (page - 1) * page_size chunk = filtered[start : start + page_size] return GoogleScopeNamesListResponse( success=True, time_ms=round((time.perf_counter() - started) * 1000, 3), count=len(chunk), data=[GoogleScopeNamesEntry(api_name=e["id"], scopes=e["scopes"]) for e in chunk], ) total = len(filtered) total_pages = max(1, (total + page_size - 1) // page_size) start = (page - 1) * page_size chunk = filtered[start : start + page_size] return GoogleApiScopesResponse( success=True, time_ms=round((time.perf_counter() - started) * 1000, 3), count=len(chunk), total=total, page=page, page_size=page_size, total_pages=total_pages, generated=GENERATED, reference=REFERENCE, source=SOURCE, filters=_filters_echo( api_name=api_name, oauth_only=oauth_only, no_scope=no_scope, search=search, scope_name=scope_name, permission_level=permission_level, required=required, recommended=recommended, scope_name_only=scope_name_only, ids_only=ids_only, ), data=chunk, )