""" API Router pentru Telegram Bot Integration Furnizează endpoint-uri pentru autentificare, linking și export rapoarte pentru Telegram bot """ from fastapi import APIRouter, Body, Depends, HTTPException, Request from typing import List, Optional, Dict, Any # import sys # Removed - no longer needed import os import secrets import string import httpx from datetime import datetime, timedelta from pydantic import BaseModel, Field from shared.auth.dependencies import get_current_user from shared.auth.models import CurrentUser from shared.auth.jwt_handler import jwt_handler from shared.database.oracle_pool import oracle_pool # Telegram bot internal API URL (running on same server) TELEGRAM_BOT_INTERNAL_API = os.getenv("TELEGRAM_BOT_INTERNAL_API", "http://localhost:8000/api/telegram") router = APIRouter(redirect_slashes=False) # ==================== Schemas ==================== class GenerateCodeRequest(BaseModel): """Request pentru generarea unui cod de linking""" telegram_user_id: int = Field(description="ID-ul utilizatorului Telegram") telegram_username: Optional[str] = Field(default=None, description="Username-ul Telegram") telegram_first_name: Optional[str] = Field(default=None, description="Prenumele utilizatorului") telegram_last_name: Optional[str] = Field(default=None, description="Numele utilizatorului") class GenerateCodeResponse(BaseModel): """Response pentru generarea unui cod de linking""" linking_code: str = Field(description="Codul de linking generat (8 caractere)") expires_at: datetime = Field(description="Data și ora expirării codului") expires_in_minutes: int = Field(description="Minutele până la expirare") class VerifyUserRequest(BaseModel): """ Request pentru verificarea utilizatorului în Oracle Suportă 2 flow-uri: 1. Auto-linking (recomandat): doar linking_code și oracle_username - Bot-ul verifică codul în SQLite, extrage oracle_username - Backend face lookup în Oracle fără verificare parolă - Codul valid este proof-of-authorization 2. Full verification (opțional): username, password, linking_code - Verificare completă cu parolă în Oracle """ linking_code: str = Field(description="Codul de linking de la /generate-code") oracle_username: Optional[str] = Field(default=None, description="Username Oracle (pentru auto-linking)") username: Optional[str] = Field(default=None, description="Username pentru verificare completă") password: Optional[str] = Field(default=None, description="Parolă pentru verificare completă") server_id: Optional[str] = Field(default=None, description="Oracle server ID (pentru multi-server mode)") class VerifyUserResponse(BaseModel): """Response pentru verificarea utilizatorului""" success: bool = Field(description="True dacă verificarea a avut succes") access_token: Optional[str] = Field(default=None, description="JWT access token") refresh_token: Optional[str] = Field(default=None, description="JWT refresh token") user: Optional[Dict[str, Any]] = Field(default=None, description="Detalii utilizator") message: str = Field(description="Mesaj de status") class RefreshTokenRequest(BaseModel): """Request pentru refresh JWT token""" refresh_token: str = Field(description="Refresh token-ul obținut la autentificare") class RefreshTokenResponse(BaseModel): """Response pentru refresh token""" access_token: str = Field(description="Noul JWT access token") expires_in: int = Field(description="Timpul de expirare în secunde") token_type: str = Field(default="bearer", description="Tipul token-ului") class ExportReportRequest(BaseModel): """Request pentru exportul unui raport""" company_id: int = Field(description="ID-ul firmei") report_type: str = Field(description="Tipul raportului (invoices, payments, dashboard)") format: str = Field(default="excel", description="Formatul exportului (excel, pdf, csv)") filters: Optional[Dict[str, Any]] = Field(default=None, description="Filtre pentru raport") class ExportReportResponse(BaseModel): """Response pentru exportul raportului""" success: bool = Field(description="True dacă exportul a avut succes") file_url: Optional[str] = Field(default=None, description="URL-ul fișierului generat") file_name: Optional[str] = Field(default=None, description="Numele fișierului generat") file_size_bytes: Optional[int] = Field(default=None, description="Mărimea fișierului în bytes") message: str = Field(description="Mesaj de status") class VerifyEmailRequest(BaseModel): """Request pentru verificarea email-ului în Oracle""" email: str = Field(description="Adresa de email Oracle") server_id: Optional[str] = Field(default=None, description="Oracle server ID (pentru multi-server mode)") class VerifyEmailResponse(BaseModel): """Response pentru verificarea email-ului""" success: bool = Field(description="True dacă email-ul există și este activ") username: Optional[str] = Field(default=None, description="Username-ul Oracle asociat") message: str = Field(description="Mesaj de status") class TelegramEmailLoginRequest(BaseModel): """Request pentru autentificare prin email + parolă""" email: str = Field(description="Adresa de email Oracle") password: str = Field(description="Parola Oracle") telegram_user_id: int = Field(description="ID-ul utilizatorului Telegram") session_token: str = Field(description="Token de sesiune pentru preveni spoofing") server_id: Optional[str] = Field(default=None, description="Oracle server ID (pentru multi-server mode)") class TelegramEmailLoginResponse(BaseModel): """Response pentru autentificare prin email + parolă""" success: bool = Field(description="True dacă autentificarea a avut succes") access_token: Optional[str] = Field(default=None, description="JWT access token") refresh_token: Optional[str] = Field(default=None, description="JWT refresh token") token_type: str = Field(default="bearer", description="Tipul token-ului") user_id: Optional[int] = Field(default=None, description="ID-ul utilizatorului Oracle") username: Optional[str] = Field(default=None, description="Username-ul Oracle") companies: List[Dict[str, Any]] = Field(default_factory=list, description="Lista companiilor") message: str = Field(description="Mesaj de status") class SwitchServerRequest(BaseModel): """Request pentru schimbarea serverului Oracle""" oracle_username: str = Field(description="Username Oracle al utilizatorului curent") new_server_id: str = Field(description="ID-ul noului server Oracle") oracle_password: Optional[str] = Field(default=None, description="Parola Oracle pe noul server (obligatorie dacă servere diferite au parole diferite)") class SwitchServerResponse(BaseModel): """Response pentru schimbarea serverului Oracle""" success: bool = Field(description="True dacă schimbarea a reușit") access_token: Optional[str] = Field(default=None, description="Noul JWT access token") refresh_token: Optional[str] = Field(default=None, description="Noul JWT refresh token") message: str = Field(description="Mesaj de status") # ==================== Helper Functions ==================== # Rate limiting storage (in-memory) from collections import defaultdict _endpoint_rate_limits = defaultdict(list) def check_endpoint_rate_limit( identifier: str, max_attempts: int = 5, window_minutes: int = 5 ) -> bool: """Backend rate limiting for sensitive endpoints""" now = datetime.now() cutoff = now - timedelta(minutes=window_minutes) # Clean old attempts _endpoint_rate_limits[identifier] = [ attempt for attempt in _endpoint_rate_limits[identifier] if attempt > cutoff ] # Check limit if len(_endpoint_rate_limits[identifier]) >= max_attempts: return False # Add attempt _endpoint_rate_limits[identifier].append(now) return True def verify_session_token( telegram_user_id: int, email: str, token: str ) -> bool: """ Verify session token from bot to prevent user ID spoofing Token format: user_id:email:signature """ import hashlib try: parts = token.split(":") if len(parts) != 3: return False token_user_id, token_email, signature = parts # Verify user ID and email match if int(token_user_id) != telegram_user_id or token_email != email: return False # Verify signature secret = os.getenv("AUTH_SESSION_SECRET", "change-me-in-production") payload = f"{telegram_user_id}:{email}:{secret}" expected_signature = hashlib.sha256(payload.encode()).hexdigest()[:16] if signature != expected_signature: return False return True except Exception: return False def generate_linking_code(length: int = 8) -> str: """ Generează un cod alfanumeric aleatoriu pentru linking Args: length: Lungimea codului (default: 8) Returns: Codul generat (uppercase alphanumeric) """ alphabet = string.ascii_uppercase + string.digits # Exclude caractere care pot fi confundate: 0, O, I, 1 alphabet = alphabet.replace('0', '').replace('O', '').replace('I', '').replace('1', '') return ''.join(secrets.choice(alphabet) for _ in range(length)) async def get_oracle_user_by_username(username: str, server_id: Optional[str] = None) -> Optional[Dict[str, Any]]: """ Obține informații despre utilizator din Oracle FĂRĂ verificare parolă. Folosit pentru auto-linking când utilizatorul a fost deja autentificat prin generarea unui linking code valid în aplicația web. Args: username: Username-ul utilizatorului Oracle server_id: ID-ul serverului Oracle (pentru multi-server mode) Returns: Dict cu informații despre utilizator sau None dacă nu există """ try: async with oracle_pool.get_connection(server_id) as connection: with connection.cursor() as cursor: # Obține detalii utilizator cursor.execute(""" SELECT ID_UTIL, UTILIZATOR FROM UTILIZATORI WHERE UPPER(UTILIZATOR) = :username """, {'username': username.upper()}) user_row = cursor.fetchone() if not user_row: return None user_id = user_row[0] actual_username = user_row[1] # Obține companiile utilizatorului cursor.execute(""" SELECT A.ID_FIRMA, A.FIRMA FROM V_NOM_FIRME A WHERE A.ID_FIRMA IN ( SELECT ID_FIRMA FROM VDEF_UTIL_FIRME WHERE ID_PROGRAM = 2 AND ID_UTIL = :user_id ) ORDER BY A.FIRMA """, {'user_id': user_id}) companies_result = cursor.fetchall() companies = [str(row[0]) for row in companies_result] return { 'user_id': user_id, 'username': actual_username, 'companies': companies, 'permissions': ['read', 'reports'] } except Exception as e: print(f"Error getting Oracle user by username: {e}") return None async def verify_oracle_user(username: str, password: str, server_id: Optional[str] = None) -> Optional[Dict[str, Any]]: """ Verifică utilizatorul în Oracle folosind pack_drepturi.verificautilizator Args: username: Username-ul utilizatorului password: Parola utilizatorului server_id: ID-ul serverului Oracle (pentru multi-server mode) Returns: Dict cu informații despre utilizator sau None dacă verificarea eșuează """ try: async with oracle_pool.get_connection(server_id) as connection: with connection.cursor() as cursor: # Verifică autentificarea cursor.execute(""" SELECT pack_drepturi.verificautilizator(:username, :password) FROM DUAL """, { 'username': username.upper(), 'password': password }) result = cursor.fetchone() verification_result = result[0] if result else -1 if verification_result == -1: return None # Obține detalii utilizator cursor.execute(""" SELECT ID_UTIL, UTILIZATOR FROM UTILIZATORI WHERE UPPER(UTILIZATOR) = :username """, {'username': username.upper()}) user_row = cursor.fetchone() if not user_row: return None user_id = user_row[0] # Obține companiile utilizatorului cursor.execute(""" SELECT A.ID_FIRMA, A.FIRMA FROM V_NOM_FIRME A WHERE A.ID_FIRMA IN ( SELECT ID_FIRMA FROM VDEF_UTIL_FIRME WHERE ID_PROGRAM = 2 AND ID_UTIL = :user_id ) ORDER BY A.FIRMA """, {'user_id': user_id}) companies_result = cursor.fetchall() companies = [str(row[0]) for row in companies_result] return { 'user_id': user_id, 'username': username, 'companies': companies, 'permissions': ['read', 'reports'] } except Exception as e: print(f"Error verifying Oracle user: {e}") return None # ==================== Endpoints ==================== @router.post("/auth/generate-code", response_model=GenerateCodeResponse) async def generate_linking_code_endpoint( request: Request, current_user: CurrentUser = Depends(get_current_user) ): """ Generează un cod de linking pentru conectarea unui utilizator Telegram Flow: 1. Utilizatorul autentificat în aplicație solicită un cod 2. Se generează un cod unic de 8 caractere 3. Codul este trimis la Telegram bot pentru salvare în SQLite cu TTL de 15 minute 4. Utilizatorul introduce codul în Telegram bot pentru linking Note: - Acest endpoint necesită autentificare JWT (utilizatorul trebuie să fie logat în aplicație) - Codul expiră după 15 minute - Fiecare request generează un cod nou (codurile vechi devin invalide) - Nu este nevoie de telegram_user_id în acest moment (utilizatorul nu e încă conectat la Telegram) - server_id este extras automat din JWT (setat de auth middleware în request.state) """ try: # Extrage server_id din JWT (setat de auth middleware) server_id = getattr(request.state, 'server_id', None) # Generează cod unic linking_code = generate_linking_code() # Setează expirarea la 15 minute expires_at = datetime.utcnow() + timedelta(minutes=15) expires_in_minutes = 15 # Salvează codul în database-ul Telegram bot (SQLite) via internal API try: async with httpx.AsyncClient(timeout=5.0) as client: save_code_response = await client.post( f"{TELEGRAM_BOT_INTERNAL_API}/internal/save-code", json={ "code": linking_code, "telegram_user_id": 0, # Not known yet (user hasn't linked) "oracle_username": current_user.username, "expires_in_minutes": expires_in_minutes, "server_id": server_id } ) # Accept both 200 (OK) and 201 (Created) as success if save_code_response.status_code not in [200, 201]: raise HTTPException( status_code=500, detail=f"Failed to save code to Telegram bot: {save_code_response.text}" ) except httpx.TimeoutException: raise HTTPException( status_code=503, detail="Telegram bot service is not responding. Please try again later." ) except httpx.ConnectError: raise HTTPException( status_code=503, detail="Cannot connect to Telegram bot service. Please contact administrator." ) return GenerateCodeResponse( linking_code=linking_code, expires_at=expires_at, expires_in_minutes=expires_in_minutes ) except HTTPException: raise except Exception as e: raise HTTPException( status_code=500, detail=f"Eroare la generarea codului de linking: {str(e)}" ) @router.post("/auth/verify-user", response_model=VerifyUserResponse) async def verify_user_endpoint(request: VerifyUserRequest): """ Verifică utilizatorul în Oracle și returnează JWT tokens Suportă 2 flow-uri de autentificare: Flow A - Auto-linking (RECOMANDAT): 1. Bot verifică linking_code în SQLite (code valid = user s-a autentificat în web app) 2. Bot extrage oracle_username din cod 3. Bot trimite: {linking_code, oracle_username} 4. Backend face lookup în Oracle (FĂRĂ verificare parolă) 5. Backend generează și returnează JWT tokens Flow B - Full verification (OPȚIONAL): 1. Bot cere username și parolă de la user în Telegram 2. Bot trimite: {linking_code, username, password} 3. Backend verifică credențialele în Oracle 4. Backend generează și returnează JWT tokens Note: - Acest endpoint NU necesită autentificare JWT (este public pentru bot) - Flow A oferă UX superior (fără re-introducere parolă) - Linking code-ul valid este proof-of-authorization """ try: # Flow A: Auto-linking (oracle_username provided, no password) if request.oracle_username and not request.password: user_data = await get_oracle_user_by_username(request.oracle_username, server_id=request.server_id) if not user_data: return VerifyUserResponse( success=False, message=f"Utilizatorul {request.oracle_username} nu există în Oracle" ) # Flow B: Full verification (username + password provided) elif request.username and request.password: user_data = await verify_oracle_user(request.username, request.password, server_id=request.server_id) if not user_data: return VerifyUserResponse( success=False, message="Username sau parolă incorectă" ) # Invalid request (missing required fields) else: return VerifyUserResponse( success=False, message="Trebuie furnizat fie oracle_username (auto-linking) fie username+password (verificare completă)" ) # Generează JWT tokens (cu server_id pentru multi-server mode) access_token = jwt_handler.create_access_token( username=user_data['username'], companies=user_data['companies'], user_id=user_data['user_id'], permissions=user_data['permissions'], server_id=request.server_id ) refresh_token = jwt_handler.create_refresh_token( username=user_data['username'], user_id=user_data['user_id'], server_id=request.server_id ) return VerifyUserResponse( success=True, access_token=access_token, refresh_token=refresh_token, user={ 'user_id': user_data['user_id'], 'username': user_data['username'], 'companies': user_data['companies'], 'permissions': user_data['permissions'] }, message="Autentificare reușită" ) except Exception as e: raise HTTPException( status_code=500, detail=f"Eroare la verificarea utilizatorului: {str(e)}" ) @router.post("/auth/refresh-token", response_model=RefreshTokenResponse) async def refresh_token_endpoint(request: RefreshTokenRequest): """ Refresh-uiește un JWT access token folosind refresh token-ul Acest endpoint este folosit de Telegram bot pentru a obține un nou access token când cel curent expiră, fără a solicita din nou username/password. Flow: 1. Botul Telegram detectează că access token-ul a expirat 2. Trimite refresh token-ul la acest endpoint 3. Se validează refresh token-ul și se generează un nou access token 4. Botul stochează noul access token în SQLite Note: - Refresh token-ul este valid 7 zile (vs 30 minute pentru access token) - Dacă refresh token-ul expiră, utilizatorul trebuie să se re-autentifice """ try: # Verifică refresh token-ul token_data = jwt_handler.verify_token(request.refresh_token) if not token_data or token_data.token_type != "refresh": raise HTTPException( status_code=401, detail="Refresh token invalid sau expirat" ) # Obține companiile actualizate din Oracle (folosind server_id din refresh token) async with oracle_pool.get_connection(token_data.server_id) as connection: with connection.cursor() as cursor: cursor.execute(""" SELECT A.ID_FIRMA FROM V_NOM_FIRME A WHERE A.ID_FIRMA IN ( SELECT ID_FIRMA FROM VDEF_UTIL_FIRME WHERE ID_PROGRAM = 2 AND ID_UTIL = :user_id ) ORDER BY A.FIRMA """, {'user_id': token_data.user_id}) companies_result = cursor.fetchall() companies = [str(row[0]) for row in companies_result] # Generează nou access token (păstrează server_id din refresh token) new_access_token = jwt_handler.create_access_token( username=token_data.username, companies=companies, user_id=token_data.user_id, permissions=token_data.permissions, server_id=token_data.server_id ) return RefreshTokenResponse( access_token=new_access_token, expires_in=jwt_handler.access_token_expire_minutes * 60, token_type="bearer" ) except HTTPException: raise except Exception as e: raise HTTPException( status_code=500, detail=f"Eroare la refresh token: {str(e)}" ) @router.post("/auth/verify-email", response_model=VerifyEmailResponse) async def verify_email_endpoint(request: VerifyEmailRequest): """ Verify if email exists in Oracle UTILIZATORI table (PUBLIC endpoint) This is a PUBLIC endpoint used by the telegram bot during email authentication. Returns username if email exists and user is active. Security: Generic error messages to prevent email enumeration. """ try: async with oracle_pool.get_connection(request.server_id) as connection: with connection.cursor() as cursor: # Query to find username by email cursor.execute(""" SELECT UTILIZATOR FROM CONTAFIN_ORACLE.UTILIZATORI WHERE UPPER(EMAIL) = UPPER(:email) AND INACTIV = 0 AND STERS = 0 """, {"email": request.email}) row = cursor.fetchone() if row: username = row[0] return VerifyEmailResponse( success=True, username=username, message="Email verificat cu succes" ) else: # Generic message (no enumeration) return VerifyEmailResponse( success=False, username=None, message="Email invalid sau inactiv" ) except Exception as e: # Generic error message (no details exposed) return VerifyEmailResponse( success=False, username=None, message="Eroare la verificarea email-ului" ) @router.post("/auth/login-with-email", response_model=TelegramEmailLoginResponse) async def login_with_email_endpoint(request: TelegramEmailLoginRequest): """ Telegram email + password authentication endpoint Security features: - Rate limiting: 5 attempts per 5 minutes - Session token verification (prevent user ID spoofing) - Generic error messages (no username/email enumeration) - Password verification in Oracle (not stored) """ # 1. Rate limiting rate_limit_key = f"email_login_{request.telegram_user_id}" if not check_endpoint_rate_limit(rate_limit_key, max_attempts=5, window_minutes=5): raise HTTPException( status_code=429, detail="Prea multe încercări. Te rugăm să aștepți 5 minute." ) # 2. Verify session token (prevent user ID spoofing) if not verify_session_token( request.telegram_user_id, request.email, request.session_token ): raise HTTPException( status_code=401, detail="Sesiune invalidă. Te rugăm să reîncepi autentificarea." ) try: async with oracle_pool.get_connection(request.server_id) as connection: with connection.cursor() as cursor: # 3. Find username by email cursor.execute(""" SELECT ID_UTIL, UTILIZATOR, INACTIV, STERS FROM CONTAFIN_ORACLE.UTILIZATORI WHERE UPPER(EMAIL) = UPPER(:email) """, {"email": request.email}) user_row = cursor.fetchone() # SECURITY: Generic error message (no email enumeration) if not user_row: raise HTTPException( status_code=401, detail="Credențiale invalide" # Generic message ) user_id, username, inactiv, sters = user_row # Check if user is active (INACTIV=0 means active, STERS=0 means not deleted) if inactiv != 0 or sters != 0: raise HTTPException( status_code=401, detail="Credențiale invalide" # Generic message ) # 4. Verify password via Oracle stored procedure # NOTE: This procedure returns a verification code, NOT the user_id! # Returns -1 if authentication fails, any other value means success cursor.execute(""" SELECT pack_drepturi.verificautilizator(:username, :password) FROM DUAL """, { "username": username.upper(), # IMPORTANT: Oracle usernames are uppercase "password": request.password }) verification_result = cursor.fetchone()[0] # SECURITY: Generic error message (no username leak) if verification_result == -1: raise HTTPException( status_code=401, detail="Credențiale invalide" # Generic message ) # 5. Get user companies cursor.execute(""" SELECT A.ID_FIRMA, A.FIRMA FROM V_NOM_FIRME A WHERE A.ID_FIRMA IN ( SELECT ID_FIRMA FROM VDEF_UTIL_FIRME WHERE ID_PROGRAM = 2 AND ID_UTIL = :user_id ) ORDER BY A.FIRMA """, {'user_id': user_id}) companies_result = cursor.fetchall() companies = [ {"id": str(row[0]), "name": row[1]} for row in companies_result ] company_ids = [str(row[0]) for row in companies_result] # 6. Get user permissions (default for Telegram) permissions = ['read', 'reports'] # 7. Generate JWT tokens (with server_id for multi-server routing) access_token = jwt_handler.create_access_token( username=username, user_id=user_id, companies=company_ids, permissions=permissions, server_id=request.server_id ) refresh_token = jwt_handler.create_refresh_token( username=username, user_id=user_id, server_id=request.server_id ) return TelegramEmailLoginResponse( success=True, access_token=access_token, refresh_token=refresh_token, user_id=user_id, username=username, companies=companies, message="Autentificare reușită" ) except HTTPException: raise except Exception as e: print(f"Error in login_with_email: {e}") raise HTTPException( status_code=500, detail="Eroare internă. Te rugăm să încerci din nou mai târziu." ) @router.post("/auth/switch-server", response_model=SwitchServerResponse) async def switch_server_endpoint( request: SwitchServerRequest, current_user: CurrentUser = Depends(get_current_user) ): """ Schimbă serverul Oracle activ fără re-autentificare. Flux: 1. Verifică că oracle_username din request == username din JWT (anti-impersonare) 2. Verifică că utilizatorul există pe noul server (fără parolă, prin get_oracle_user_by_username) 3. Generează JWT nou cu noul server_id 4. Returnează tokenurile noi Securitate: endpoint protejat cu Bearer JWT valid (Depends(get_current_user)). """ # Anti-impersonare: utilizatorul poate schimba serverul doar pentru propriul cont if request.oracle_username.upper() != current_user.username.upper(): raise HTTPException(status_code=403, detail="Acces interzis: username nepotrivit") # Verifică că utilizatorul există pe noul server user_data = await get_oracle_user_by_username(request.oracle_username, request.new_server_id) if not user_data: return SwitchServerResponse( success=False, message=f"Utilizatorul nu există pe serverul {request.new_server_id}" ) # Dacă parola e furnizată, verifică-o pe noul server înainte de a emite JWT if request.oracle_password: try: async with oracle_pool.get_connection(request.new_server_id) as connection: with connection.cursor() as cursor: cursor.execute(""" SELECT pack_drepturi.verificautilizator(:username, :password) FROM DUAL """, { "username": request.oracle_username.upper(), "password": request.oracle_password }) verification_result = cursor.fetchone()[0] if verification_result == -1: return SwitchServerResponse( success=False, message="Parolă incorectă pentru acest server" ) except Exception as e: logger.error(f"Password verification error during server switch: {e}") return SwitchServerResponse( success=False, message="Eroare la verificarea parolei pe noul server" ) # Generează JWT nou cu noul server_id access_token = jwt_handler.create_access_token( username=user_data['username'], companies=user_data['companies'], user_id=user_data['user_id'], permissions=user_data['permissions'], server_id=request.new_server_id ) refresh_token = jwt_handler.create_refresh_token( username=user_data['username'], user_id=user_data['user_id'], server_id=request.new_server_id ) return SwitchServerResponse( success=True, access_token=access_token, refresh_token=refresh_token, message="Server schimbat cu succes" ) @router.post("/export", response_model=ExportReportResponse) async def export_report_endpoint( request: ExportReportRequest, current_user: CurrentUser = Depends(get_current_user) ): """ Exportă un raport în format Excel, PDF sau CSV Acest endpoint este folosit de Telegram bot pentru a genera rapoarte și a le trimite utilizatorului. Flow: 1. Botul trimite cerere de export cu parametrii raportului 2. Se validează că utilizatorul are acces la firma specificată 3. Se generează raportul în formatul solicitat 4. Se returnează URL-ul sau conținutul fișierului Tipuri de rapoarte suportate: - invoices: Facturi (cu filtre: dată, status, client) - payments: Încasări (cu filtre: dată, metodă plată) - dashboard: Statistici dashboard (rezumat) Formate suportate: - excel: XLSX (cel mai complet) - pdf: PDF (pentru printing) - csv: CSV (pentru import în alte sisteme) Note: - Utilizatorul trebuie să aibă acces la firma specificată - Fișierele generate sunt temporare (șterse după 1 oră) """ try: # Verifică accesul la firmă company_id_str = str(request.company_id) if company_id_str not in current_user.companies: raise HTTPException( status_code=403, detail=f"Nu aveți acces la firma {request.company_id}" ) # TODO: Implementare export în funcție de report_type și format # Deocamdată returnăm un placeholder return ExportReportResponse( success=True, file_url=f"/api/telegram/downloads/report_{request.report_type}_{request.company_id}.{request.format}", file_name=f"raport_{request.report_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.{request.format}", file_size_bytes=0, message=f"Raport {request.report_type} generat cu succes în format {request.format}" ) except HTTPException: raise except Exception as e: raise HTTPException( status_code=500, detail=f"Eroare la generarea raportului: {str(e)}" ) @router.get("/health") async def telegram_health_check(): """ Health check pentru routerul Telegram Verifică conectivitatea la Oracle și disponibilitatea serviciilor """ try: async with oracle_pool.get_connection() as connection: with connection.cursor() as cursor: cursor.execute("SELECT 1 FROM DUAL") return { "status": "healthy", "service": "telegram-router", "database": "connected", "timestamp": datetime.utcnow().isoformat() } except Exception as e: return { "status": "degraded", "service": "telegram-router", "database": f"error: {str(e)}", "timestamp": datetime.utcnow().isoformat() }