import os os.environ["NUMEXPR_MAX_THREADS"] = "12" from fastapi import FastAPI, HTTPException, Depends, Body, status, Response from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, FileResponse from fastapi.staticfiles import StaticFiles from fastapi.security import OAuth2PasswordRequestForm from contextlib import asynccontextmanager import uvicorn from typing import Dict, Any import logging from logging.handlers import RotatingFileHandler from datetime import timedelta import pathlib from modules.configuration import APP_CONFIG from modules.gateway_interface import get_gateway_interface # Import auth module from modules.auth import ( create_access_token, get_current_active_user, get_user_context, ACCESS_TOKEN_EXPIRE_MINUTES ) # Import models - import generically for INITIALIZATION, even if dummy! import modules.gateway_model as gateway_model import modules.lucydom_interface as lucydom_model def init_logging(): # Get log level from config (default to INFO if not found) log_level_name = APP_CONFIG.get("APP_LOG_LEVEL", "INFO") log_level = getattr(logging, log_level_name) # Configure handlers based on config handlers = [] # Add console handler if enabled if APP_CONFIG.get("Logging_CONSOLE_ENABLED", True): console_handler = logging.StreamHandler() handlers.append(console_handler) # Add file handler if enabled if APP_CONFIG.get("Logging_FILE_ENABLED", True): log_file = APP_CONFIG.get("APP_LOG_FILE", "app.log") rotation_size = int(APP_CONFIG.get("Logging_ROTATION_SIZE", 10485760)) # Default: 10MB backup_count = int(APP_CONFIG.get("Logging_BACKUP_COUNT", 5)) file_handler = RotatingFileHandler( log_file, maxBytes=rotation_size, backupCount=backup_count ) handlers.append(file_handler) # Configure the logger logging.basicConfig( level=log_level, format=APP_CONFIG.get("Logging_FORMAT", "%(asctime)s - %(levelname)s - %(name)s - %(message)s"), datefmt=APP_CONFIG.get("Logging_DATE_FORMAT", "%Y-%m-%d %H:%M:%S"), handlers=handlers ) # Initialize logging init_logging() logger = logging.getLogger(__name__) instance_label = APP_CONFIG.get("APP_ENV_LABEL") # Define lifespan context manager for application startup/shutdown events @asynccontextmanager async def lifespan(app: FastAPI): # Startup logic (if any) logger.info("Application is starting up") yield # Shutdown logic logger.info("Application has been shut down") # START APP app = FastAPI( title="PowerOn | Data Platform API", description=f"Backend API for the Multi-Agent Platform by ValueOn AG ({instance_label})", lifespan=lifespan ) # CORS configuration for frontend requests app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:8080","https://poweron-lucyagents-xxx.germanywestcentral-01.azurewebsites.net"], allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["*"], expose_headers=["*"], max_age=86400 # Increased caching for preflight requests ) # Static folder for frontend - work with absolute path base_dir = pathlib.Path(__file__).parent static_folder = base_dir / "static" os.makedirs(static_folder, exist_ok=True) app.mount("/static", StaticFiles(directory=str(static_folder)), name="static") # Add a specific route for favicon.ico @app.get("/favicon.ico", include_in_schema=False) async def favicon(): favicon_path = static_folder / "favicon.ico" return FileResponse(str(favicon_path)) # General Elements @app.get("/", tags=["General"]) async def root(): """API status endpoint""" return {"status": "online", "message": "Data Platform API is active"} @app.get("/api/test", tags=["General"]) async def get_test(): return "OK 1.5" @app.options("/{full_path:path}", tags=["General"]) async def options_route(full_path: str): return Response(status_code=200) # Token endpoint for login @app.post("/api/token", response_model=gateway_model.Token, tags=["General"]) async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): # Initialize Gateway interface without context gateway = get_gateway_interface() # Authenticate user user = gateway.authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid username or password", headers={"WWW-Authenticate": "Bearer"}, ) # Create token with tenant ID access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={ "sub": user["username"], "mandate_id": user["mandate_id"] }, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"} # Get user info @app.get("/api/user/me", response_model=Dict[str, Any], tags=["General"]) async def read_user_me(current_user: Dict[str, Any] = Depends(get_current_active_user)): return current_user # Include all routers from routes.attributes import router as attributes_router app.include_router(attributes_router) from routes.mandates import router as mandate_router app.include_router(mandate_router) from routes.users import router as user_router app.include_router(user_router) from routes.files import router as file_router app.include_router(file_router) from routes.prompts import router as prompt_router app.include_router(prompt_router) from routes.workflows import router as workflow_router app.include_router(workflow_router) #if __name__ == "__main__": # uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)