101:async def google_start_impl(user, client: dict = None): 102- """Shared implementation: generate state, persist, return Google's authorization URL. 103- `client` may be pre-resolved (e.g. the user's imported OAuthClientConfig row); 104- when omitted the server-level configuration is used (honest 400 if absent).""" 105- if client is None: 106- try: 107- client = gmail_service.resolve_client_config() 108- except gmail_service.GmailOauthError as e: 109- # Be honest: no client config -> no authorization URL (never fabricate one) 110- raise HTTPException(400, f"Google OAuth is not configured: {e}") 111- state = gmail_service.generate_state() 112- if _oauth_redis is not None: 113- _oauth_redis.setex(f"oauthstate:{state}", 600, json.dumps({"user_id": user.id})) 114- else: 115- _pending_oauth_states[state] = {"user_id": user.id} 116- auth_url = gmail_service.build_authorization_url(client, state) 117- return {"authorization_url": auth_url} 118- 119- 120-_pending_oauth_states: dict = {} 121- 122- 123-@router.get("/google/start") 124-async def google_start(request: Request, user=Depends(get_current_user)): 125- return await google_start_impl(user) 126- 127- 128-@router.get("/google/callback") 129-async def google_callback(request: Request, code: str = "", state: str = "", error: str = "", 130- db: AsyncSession = Depends(get_db)): 131- """Handle Google's response: verify state, exchange code for tokens, store encrypted.""" 132- from app.models import GmailAccount, OAuthClientConfig, OAuthToken 133- 134- if error: 135- return HTMLResponse(f"