211 lines
6.9 KiB
Python
211 lines
6.9 KiB
Python
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
|
|
|
|
from typing import Dict, Any
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
from datetime import timedelta
|
|
import pathlib
|
|
|
|
from modules.configuration import APP_CONFIG
|
|
|
|
def initLogging():
|
|
# Get log level from config (default to INFO if not found)
|
|
logLevelName = APP_CONFIG.get("APP_LOGGING_LOG_LEVEL", "WARNING")
|
|
logLevel = getattr(logging, logLevelName)
|
|
|
|
# Configure handlers based on config
|
|
handlers = []
|
|
|
|
# Add console handler if enabled
|
|
if APP_CONFIG.get("APP_LOGGING_CONSOLE_ENABLED", True):
|
|
consoleHandler = logging.StreamHandler()
|
|
handlers.append(consoleHandler)
|
|
|
|
# Add file handler if enabled
|
|
if APP_CONFIG.get("APP_LOGGING_FILE_ENABLED", True):
|
|
logFile = APP_CONFIG.get("APP_LOGGING_LOG_FILE", "app.log")
|
|
rotationSize = int(APP_CONFIG.get("APP_LOGGING_ROTATION_SIZE", 10485760)) # Default: 10MB
|
|
backupCount = int(APP_CONFIG.get("APP_LOGGING_BACKUP_COUNT", 5))
|
|
|
|
fileHandler = RotatingFileHandler(
|
|
logFile,
|
|
maxBytes=rotationSize,
|
|
backupCount=backupCount
|
|
)
|
|
handlers.append(fileHandler)
|
|
|
|
# Configure the logger
|
|
logging.basicConfig(
|
|
level=logLevel,
|
|
format=APP_CONFIG.get("APP_LOGGING_FORMAT", "%(asctime)s - %(levelname)s - %(name)s - %(message)s"),
|
|
datefmt=APP_CONFIG.get("APP_LOGGING_DATE_FORMAT", "%Y-%m-%d %H:%M:%S"),
|
|
handlers=handlers
|
|
)
|
|
|
|
# Silence noisy third-party libraries - use the same level as the root logger
|
|
noisyLoggers = ["httpx", "urllib3", "asyncio", "fastapi.security.oauth2"]
|
|
for loggerName in noisyLoggers:
|
|
logging.getLogger(loggerName).setLevel(logLevel)
|
|
|
|
|
|
# Initialize logging
|
|
initLogging()
|
|
logger = logging.getLogger(__name__)
|
|
instanceLabel = APP_CONFIG.get("APP_ENV_LABEL")
|
|
|
|
# Import auth module
|
|
from modules.auth import (
|
|
createAccessToken,
|
|
getCurrentActiveUser,
|
|
getUserContext,
|
|
ACCESS_TOKEN_EXPIRE_MINUTES
|
|
)
|
|
|
|
# Import models - import generically for INITIALIZATION
|
|
import modules.gatewayModel as gatewayModel
|
|
from modules.gatewayInterface import getGatewayInterface
|
|
gateway = getGatewayInterface()
|
|
|
|
|
|
# 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")
|
|
|
|
# Parse CORS origins from environment variable
|
|
def get_allowed_origins():
|
|
origins_str = APP_CONFIG.get("APP_ALLOWED_ORIGINS", "http://localhost:8080")
|
|
# Split by comma and strip whitespace
|
|
origins = [origin.strip() for origin in origins_str.split(",")]
|
|
logger.info(f"CORS allowed origins: {origins}")
|
|
return origins
|
|
|
|
# START APP
|
|
app = FastAPI(
|
|
title="PowerOn | Data Platform API",
|
|
description=f"Backend API for the Multi-Agent Platform by ValueOn AG ({instanceLabel})",
|
|
lifespan=lifespan
|
|
)
|
|
|
|
# CORS configuration using environment variables
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins= get_allowed_origins(), # ["http://localhost:8080","http://localhost:8081"], #get_allowed_origins(),
|
|
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
|
|
baseDir = pathlib.Path(__file__).parent
|
|
staticFolder = baseDir / "static"
|
|
os.makedirs(staticFolder, exist_ok=True)
|
|
|
|
# Mount static files with proper configuration
|
|
app.mount("/static", StaticFiles(directory=str(staticFolder), html=True), name="static")
|
|
|
|
# Add favicon route
|
|
@app.get("/favicon.ico")
|
|
async def favicon():
|
|
return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
|
|
|
|
# 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 getTest():
|
|
return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
|
|
|
|
@app.options("/{fullPath:path}", tags=["General"])
|
|
async def optionsRoute(fullPath: str):
|
|
return Response(status_code=200)
|
|
|
|
@app.get("/api/environment", tags=["General"])
|
|
async def get_environment():
|
|
"""Get environment configuration for frontend"""
|
|
return {
|
|
"apiBaseUrl": APP_CONFIG.get("APP_API_URL", ""),
|
|
"environment": APP_CONFIG.get("APP_ENV", "development"),
|
|
"instanceLabel": APP_CONFIG.get("APP_ENV_LABEL", "Development"),
|
|
# Add other environment variables the frontend might need
|
|
}
|
|
|
|
# Token endpoint for login
|
|
@app.post("/api/token", response_model=gatewayModel.Token, tags=["General"])
|
|
async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
|
|
# Initialize Gateway interface without context
|
|
gateway = getGatewayInterface()
|
|
|
|
# Authenticate user
|
|
user = gateway.authenticateUser(formData.username, formData.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
|
|
accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
accessToken = createAccessToken(
|
|
data={
|
|
"sub": user["username"],
|
|
"mandateId": user["mandateId"]
|
|
},
|
|
expiresDelta=accessTokenExpires
|
|
)
|
|
|
|
return {"accessToken": accessToken, "tokenType": "bearer"}
|
|
|
|
# Get user info
|
|
@app.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
|
|
async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
|
|
return currentUser
|
|
|
|
# Include all routers
|
|
from routes.routeAttributes import router as attributesRouter
|
|
app.include_router(attributesRouter)
|
|
|
|
gateway = getGatewayInterface()
|
|
|
|
from routes.routeMandates import router as mandateRouter
|
|
app.include_router(mandateRouter)
|
|
|
|
gateway = getGatewayInterface()
|
|
|
|
|
|
from routes.routeUsers import router as userRouter
|
|
app.include_router(userRouter)
|
|
|
|
from routes.routeFiles import router as fileRouter
|
|
app.include_router(fileRouter)
|
|
|
|
from routes.routePrompts import router as promptRouter
|
|
app.include_router(promptRouter)
|
|
|
|
from routes.routeWorkflows import router as workflowRouter
|
|
app.include_router(workflowRouter)
|
|
|
|
from routes.routeMsft import router as msftRouter
|
|
app.include_router(msftRouter)
|
|
|
|
#if __name__ == "__main__":
|
|
# uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
|