""" ROA Reports API - FastAPI Backend Aplicația principală pentru rapoarte facturi și încasări """ from fastapi import FastAPI, Depends from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager import sys import os from datetime import datetime from dotenv import load_dotenv # Încărcare environment variables din .env load_dotenv() # Configurare TNS_ADMIN pentru Oracle tns_path = os.path.join(os.path.dirname(__file__), '../../../../app') os.environ['TNS_ADMIN'] = tns_path # Adăugare path pentru shared modules sys.path.append(os.path.join(os.path.dirname(__file__), '../../../shared')) from database.oracle_pool import oracle_pool from auth.middleware import AuthenticationMiddleware # from auth.routes import create_auth_router # Fixed inline # Import routere locale from app.routers import invoices, dashboard, treasury, companies, telegram, cache, trial_balance # Auth endpoints pentru test from fastapi import APIRouter, HTTPException from pydantic import BaseModel from datetime import datetime, timedelta import jwt import logging logger = logging.getLogger(__name__) # JWT Setup JWT_SECRET = os.getenv("JWT_SECRET_KEY", "test-secret-key") JWT_ALGORITHM = "HS256" JWT_EXPIRE_MINUTES = 30 class LoginRequest(BaseModel): username: str password: str class LoginResponse(BaseModel): access_token: str refresh_token: str # Added refresh token token_type: str user: dict def create_auth_router(): """Create authentication router for testing""" auth_router = APIRouter(tags=["authentication"]) @auth_router.post("/login", response_model=LoginResponse) async def login(credentials: LoginRequest): """Autentificare utilizator prin Oracle database""" try: async with oracle_pool.get_connection() as connection: with connection.cursor() as cursor: # Call verificautilizator procedure using SELECT cursor.execute(""" SELECT pack_drepturi.verificautilizator(:username, :password) FROM DUAL """, { 'username': credentials.username.upper(), 'password': credentials.password }) result = cursor.fetchone() verification_result = result[0] if result else -1 # Check if authentication was successful if verification_result == -1: raise HTTPException(status_code=401, detail="Invalid username or password") # Get user companies - first get user ID from UTILIZATORI cursor.execute(""" SELECT ID_UTIL, UTILIZATOR FROM UTILIZATORI WHERE UPPER(UTILIZATOR) = :username """, {'username': credentials.username.upper()}) user_row = cursor.fetchone() if not user_row: raise HTTPException(status_code=401, detail="User not found in system") user_id = user_row[0] # Now get companies using the correct query structure 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() if not companies_result: # Don't fail login if no companies - let frontend show message companies = [] else: companies = [str(row[0]) for row in companies_result] # Create JWT token with all required fields now = datetime.utcnow() expire = now + timedelta(minutes=JWT_EXPIRE_MINUTES) token_data = { "username": credentials.username, # Changed from "sub" to "username" "user_id": user_id, # Include user_id from database "companies": companies, "permissions": ["read", "reports"], # Default permissions "exp": expire, "iat": now, # Added issued at time "type": "access" # Added token type } access_token = jwt.encode(token_data, JWT_SECRET, algorithm=JWT_ALGORITHM) # Create refresh token refresh_expire = now + timedelta(days=7) refresh_token_data = { "username": credentials.username, "user_id": user_id, "companies": companies, "permissions": ["read", "reports"], "exp": refresh_expire, "iat": now, "type": "refresh" } refresh_token = jwt.encode(refresh_token_data, JWT_SECRET, algorithm=JWT_ALGORITHM) return LoginResponse( access_token=access_token, refresh_token=refresh_token, # Include refresh token token_type="bearer", user={ "username": credentials.username, "user_id": user_id, # Include user_id "companies": companies, "permissions": ["read", "reports"] # Include permissions } ) except Exception as e: logger.error(f"Login error: {str(e)}") raise HTTPException(status_code=500, detail="Internal authentication error") return auth_router @asynccontextmanager async def lifespan(app: FastAPI): """Lifecycle events pentru aplicație""" # Startup - Initialize Oracle connection pool await oracle_pool.initialize() logger.info("[ROA Reports API] Oracle pool initialized") # Initialize cache system from app.cache import init_cache, run_baseline_benchmarks, init_event_monitor, get_cache from app.cache.config import CacheConfig try: cache_config = CacheConfig.from_env() await init_cache(cache_config) logger.info(f"[ROA Reports API] Cache initialized: type={cache_config.cache_type}, enabled={cache_config.enabled}") # Run baseline benchmarks (optional, based on config) if cache_config.benchmark_on_startup: logger.info("[ROA Reports API] Running baseline performance benchmarks...") benchmarks = await run_baseline_benchmarks() logger.info(f"[ROA Reports API] Benchmarks completed: {len(benchmarks)} types measured") # Initialize event monitor cache = get_cache() await init_event_monitor(cache, cache_config) if cache_config.auto_invalidate_enabled: logger.info("[ROA Reports API] Event-based auto-invalidation ENABLED") else: logger.info("[ROA Reports API] Event-based auto-invalidation DISABLED") except Exception as e: logger.error(f"[ROA Reports API] Cache initialization error: {e}", exc_info=True) logger.warning("[ROA Reports API] Continuing without cache") logger.info("[ROA Reports API] Started successfully") yield # Shutdown from app.cache import close_cache, get_event_monitor # Stop event monitor try: monitor = get_event_monitor() if monitor: await monitor.stop() logger.info("[ROA Reports API] Event monitor stopped") except Exception as e: logger.error(f"[ROA Reports API] Event monitor shutdown error: {e}") # Close cache try: await close_cache() logger.info("[ROA Reports API] Cache closed") except Exception as e: logger.error(f"[ROA Reports API] Cache shutdown error: {e}") await oracle_pool.close_pool() logger.info("[ROA Reports API] Stopped") app = FastAPI( title="ROA Reports API", description="API pentru rapoarte ERP - facturi, încasări și alte rapoarte financiare", version="1.0.0", # lifespan=lifespan # Using event handlers instead due to uvicorn compatibility issues ) # STARTUP EVENT HANDLER (alternative to lifespan) @app.on_event("startup") async def startup_event(): """Application startup - Initialize Oracle pool and cache""" print("=" * 80, flush=True) print("[STARTUP] Initializing Oracle pool...", flush=True) logger.critical("=" * 80) logger.critical("[STARTUP] Initializing Oracle pool...") await oracle_pool.initialize() print("[STARTUP] Oracle pool initialized", flush=True) logger.critical("[STARTUP] Oracle pool initialized") print("[STARTUP] Initializing cache system...", flush=True) logger.critical("[STARTUP] Initializing cache system...") from app.cache import init_cache, init_event_monitor, get_cache from app.cache.config import CacheConfig try: cache_config = CacheConfig.from_env() await init_cache(cache_config) print(f"[STARTUP] Cache initialized: type={cache_config.cache_type}, enabled={cache_config.enabled}", flush=True) logger.critical(f"[STARTUP] Cache initialized: type={cache_config.cache_type}, enabled={cache_config.enabled}") # Initialize event monitor cache = get_cache() await init_event_monitor(cache, cache_config) if cache_config.auto_invalidate_enabled: logger.info("[STARTUP] Event-based auto-invalidation ENABLED") else: logger.info("[STARTUP] Event-based auto-invalidation DISABLED") except Exception as e: logger.error(f"[STARTUP] Cache initialization error: {e}", exc_info=True) logger.warning("[STARTUP] Continuing without cache") logger.info("[STARTUP] ROA Reports API started successfully") logger.info("=" * 80) # SHUTDOWN EVENT HANDLER @app.on_event("shutdown") async def shutdown_event(): """Application shutdown - Cleanup resources""" logger.info("[SHUTDOWN] Stopping event monitor...") from app.cache import close_cache, get_event_monitor try: monitor = get_event_monitor() if monitor: await monitor.stop() logger.info("[SHUTDOWN] Event monitor stopped") except Exception as e: logger.error(f"[SHUTDOWN] Event monitor error: {e}") try: await close_cache() logger.info("[SHUTDOWN] Cache closed") except Exception as e: logger.error(f"[SHUTDOWN] Cache error: {e}") await oracle_pool.close_pool() logger.info("[SHUTDOWN] Oracle pool closed") logger.info("[SHUTDOWN] ROA Reports API stopped") # CORS pentru frontend Vue.js app.add_middleware( CORSMiddleware, allow_origins=["*"], # Allow all origins for production deployment allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Authentication middleware app.add_middleware( AuthenticationMiddleware, excluded_paths=[ "/", "/docs", "/health", "/api/auth/login", "/redoc", "/openapi.json", "/api/telegram/auth/verify-user", # Public endpoint for Telegram bot "/api/telegram/auth/verify-email", # Public endpoint for email verification (2FA flow) "/api/telegram/auth/login-with-email", # Public endpoint for email + password login (2FA flow) "/api/telegram/auth/refresh-token", # Public endpoint for token refresh "/api/telegram/health" # Health check for Telegram router ] ) # Include routere with /api prefix auth_router = create_auth_router() app.include_router(auth_router, prefix="/api/auth", tags=["authentication"]) app.include_router(companies.router, prefix="/api/companies", tags=["companies"]) app.include_router(invoices.router, prefix="/api/invoices", tags=["invoices"]) app.include_router(dashboard.router, prefix="/api/dashboard", tags=["dashboard"]) app.include_router(treasury.router, prefix="/api/treasury", tags=["treasury"]) app.include_router(telegram.router, prefix="/api/telegram", tags=["telegram"]) app.include_router(cache.router, prefix="/api", tags=["cache"]) app.include_router(trial_balance.router, prefix="/api/trial-balance", tags=["trial-balance"]) @app.get("/") async def root(): print("[MAIN DEBUG] Root endpoint accessed") return {"message": "ROA Reports API", "version": "1.0.0", "status": "running"} @app.get("/health") async def health_check(): # Test database connection try: async with oracle_pool.get_connection() as conn: with conn.cursor() as cursor: cursor.execute("SELECT 1 FROM DUAL") return {"api": "healthy", "database": "connected", "timestamp": datetime.utcnow().isoformat()} except Exception as e: return {"api": "healthy", "database": f"error: {str(e)}", "timestamp": datetime.utcnow().isoformat()}