import json import os import tempfile from pathlib import Path from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import Flow from googleapiclient.discovery import build SCOPES = [ "https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/drive.file", ] TOKEN_FILENAME = "google_token.json" DATA_DIR = Path("/data") FALLBACK_DATA_DIR = Path(tempfile.gettempdir()) / "tcc2_agent" class GoogleOAuthError(RuntimeError): pass class GoogleAuthRequiredError(GoogleOAuthError): pass def _resolve_data_dir(): preferred_dir = os.environ.get("GOOGLE_TOKEN_DIR", "").strip() candidates = [Path(preferred_dir)] if preferred_dir else [] candidates.extend([DATA_DIR, FALLBACK_DATA_DIR]) for directory in candidates: try: directory.mkdir(parents=True, exist_ok=True) return directory except OSError: continue raise GoogleOAuthError( "Nao foi possivel criar um diretorio para armazenar o token OAuth." ) def get_token_path(): return _resolve_data_dir() / TOKEN_FILENAME def _client_config(): client_id = os.environ.get("GOOGLE_CLIENT_ID", "").strip() client_secret = os.environ.get("GOOGLE_CLIENT_SECRET", "").strip() redirect_uri = os.environ.get("GOOGLE_REDIRECT_URI", "").strip() missing = [ name for name, value in { "GOOGLE_CLIENT_ID": client_id, "GOOGLE_CLIENT_SECRET": client_secret, "GOOGLE_REDIRECT_URI": redirect_uri, }.items() if not value ] if missing: raise GoogleOAuthError("Variaveis OAuth ausentes: " + ", ".join(missing)) return { "web": { "client_id": client_id, "client_secret": client_secret, "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "redirect_uris": [redirect_uri], } } def build_flow(state=None): flow = Flow.from_client_config(_client_config(), scopes=SCOPES, state=state) flow.redirect_uri = os.environ.get("GOOGLE_REDIRECT_URI") return flow def get_authorization_url(): flow = build_flow() authorization_url, state = flow.authorization_url( access_type="offline", prompt="consent", include_granted_scopes="true", ) return authorization_url, state # importante retornar state def save_credentials(credentials): if not credentials.refresh_token: raise GoogleOAuthError( "Refresh token nao recebido. Revogue o acesso e autorize novamente." ) token_path = get_token_path() token_path.write_text(credentials.to_json(), encoding="utf-8") def load_credentials(): token_path = get_token_path() if not token_path.exists(): return None try: payload = json.loads(token_path.read_text(encoding="utf-8")) credentials = Credentials.from_authorized_user_info(payload, SCOPES) except Exception: return None if credentials.expired and credentials.refresh_token: try: credentials.refresh(Request()) save_credentials(credentials) except Exception: return None if not credentials.valid: return None return credentials def get_sheets_service(): credentials = load_credentials() if credentials is None: raise GoogleAuthRequiredError( "Google Sheets nao autorizado. Acesse /auth/google primeiro." ) return build("sheets", "v4", credentials=credentials, cache_discovery=False) def append_feedback_to_sheet(feedback_data): spreadsheet_id = os.environ.get("GOOGLE_SPREADSHEET_ID", "").strip() if not spreadsheet_id: raise GoogleOAuthError("GOOGLE_SPREADSHEET_ID nao configurado.") service = get_sheets_service() fields = [ "timestamp", "search_id", "query", "rank", "product_id", "product_name", "categoria_inferida", "categoria_produto", "rating", "is_helpful", "feedback", "motivo", "score_final", "score_semantico", "bonus_lexical", "penalidade_feedback", "user_message", "note", ] row = [[str(feedback_data.get(field, "") or "") for field in fields]] try: return service.spreadsheets().values().append( spreadsheetId=spreadsheet_id, range="Feedback!A1:R1", valueInputOption="USER_ENTERED", insertDataOption="INSERT_ROWS", body={"values": row}, ).execute() except Exception as e: raise GoogleOAuthError(f"Erro ao enviar para Google Sheets: {e}")