299 lines
12 KiB
Python
299 lines
12 KiB
Python
from fastapi import APIRouter, Response, Depends, Request, Body
|
|
from fastapi.responses import FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
import os
|
|
import logging
|
|
from pathlib import Path as FilePath
|
|
from typing import Dict, Any, List
|
|
from fastapi import HTTPException, status
|
|
from datetime import datetime
|
|
|
|
from modules.shared.configuration import APP_CONFIG
|
|
from modules.security.auth import limiter, getCurrentUser
|
|
from modules.interfaces.interfaceAppModel import User
|
|
from modules.interfaces.interfaceAppObjects import getRootInterface
|
|
from modules.interfaces.interfaceChatObjects import getInterface as getChatInterface
|
|
from modules.interfaces.interfaceComponentObjects import getInterface as getComponentInterface
|
|
|
|
# Static folder setup - using absolute path from app root
|
|
baseDir = FilePath(__file__).parent.parent.parent # Go up to gateway root
|
|
staticFolder = baseDir / "static"
|
|
os.makedirs(staticFolder, exist_ok=True)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(
|
|
prefix="",
|
|
tags=["Administration"],
|
|
responses={404: {"description": "Not found"}}
|
|
)
|
|
|
|
# Mount static files
|
|
router.mount("/static", StaticFiles(directory=str(staticFolder), html=True), name="static")
|
|
|
|
def get_interface_for_database(database_name: str, currentUser: User):
|
|
"""
|
|
Get the appropriate interface based on database name.
|
|
|
|
Args:
|
|
database_name: Name of the database
|
|
currentUser: Current user for interface initialization
|
|
|
|
Returns:
|
|
Interface object for the specified database
|
|
|
|
Raises:
|
|
HTTPException: If database name is unknown or interface cannot be created
|
|
"""
|
|
# Get database names from configuration
|
|
appDbName = APP_CONFIG.get("DB_APP_DATABASE")
|
|
chatDbName = APP_CONFIG.get("DB_CHAT_DATABASE")
|
|
managementDbName = APP_CONFIG.get("DB_MANAGEMENT_DATABASE")
|
|
|
|
if not appDbName:
|
|
raise HTTPException(status_code=500, detail="DB_APP_DATABASE configuration is required")
|
|
|
|
# Map database names to their corresponding interfaces
|
|
if database_name == appDbName:
|
|
return getRootInterface()
|
|
elif chatDbName and database_name == chatDbName:
|
|
return getChatInterface(currentUser)
|
|
elif managementDbName and database_name == managementDbName:
|
|
return getComponentInterface(currentUser)
|
|
else:
|
|
available_dbs = [appDbName]
|
|
if chatDbName:
|
|
available_dbs.append(chatDbName)
|
|
if managementDbName:
|
|
available_dbs.append(managementDbName)
|
|
raise HTTPException(status_code=400, detail=f"Unknown database. Available: {', '.join(available_dbs)}")
|
|
|
|
@router.get("/")
|
|
@limiter.limit("30/minute")
|
|
async def root(request: Request) -> Dict[str, str]:
|
|
"""API status endpoint"""
|
|
# Validate required configuration values
|
|
allowedOrigins = APP_CONFIG.get("APP_ALLOWED_ORIGINS")
|
|
if not allowedOrigins:
|
|
raise HTTPException(status_code=500, detail="APP_ALLOWED_ORIGINS configuration is required")
|
|
|
|
return {
|
|
"status": "online",
|
|
"message": "Data Platform API is active",
|
|
"allowedOrigins": f"Allowed origins are {allowedOrigins}"
|
|
}
|
|
|
|
@router.get("/api/environment")
|
|
@limiter.limit("30/minute")
|
|
async def get_environment(request: Request, currentUser: Dict[str, Any] = Depends(getCurrentUser)) -> Dict[str, str]:
|
|
"""Get environment configuration for frontend"""
|
|
# Validate required configuration values
|
|
apiBaseUrl = APP_CONFIG.get("APP_API_URL")
|
|
if not apiBaseUrl:
|
|
raise HTTPException(status_code=500, detail="APP_API_URL configuration is required")
|
|
|
|
environment = APP_CONFIG.get("APP_ENV")
|
|
if not environment:
|
|
raise HTTPException(status_code=500, detail="APP_ENV configuration is required")
|
|
|
|
instanceLabel = APP_CONFIG.get("APP_ENV_LABEL")
|
|
if not instanceLabel:
|
|
raise HTTPException(status_code=500, detail="APP_ENV_LABEL configuration is required")
|
|
|
|
return {
|
|
"apiBaseUrl": apiBaseUrl,
|
|
"environment": environment,
|
|
"instanceLabel": instanceLabel,
|
|
# Add other environment variables the frontend might need
|
|
}
|
|
|
|
@router.options("/{fullPath:path}")
|
|
@limiter.limit("60/minute")
|
|
async def options_route(request: Request, fullPath: str) -> Response:
|
|
return Response(status_code=200)
|
|
|
|
@router.get("/favicon.ico")
|
|
@limiter.limit("30/minute")
|
|
async def favicon(request: Request) -> FileResponse:
|
|
return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
|
|
|
|
# ----------------------
|
|
# Log Management
|
|
# ----------------------
|
|
|
|
@router.get("/api/logs/app")
|
|
@limiter.limit("10/minute")
|
|
async def download_app_log(request: Request, currentUser: User = Depends(getCurrentUser)) -> FileResponse:
|
|
"""Download the current day's application log file"""
|
|
# Check if user has admin privileges
|
|
if not hasattr(currentUser, 'privilege') or currentUser.privilege not in ('admin', 'sysadmin'):
|
|
raise HTTPException(status_code=403, detail="Admin privileges required")
|
|
|
|
# Get log directory from config
|
|
logDir = APP_CONFIG.get("APP_LOGGING_LOG_DIR")
|
|
if not logDir:
|
|
raise HTTPException(status_code=500, detail="APP_LOGGING_LOG_DIR configuration is required")
|
|
|
|
if not os.path.isabs(logDir):
|
|
# If relative path, make it relative to the gateway directory
|
|
gatewayDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
logDir = os.path.join(gatewayDir, logDir)
|
|
|
|
# Get current date for log file
|
|
today = datetime.now().strftime("%Y%m%d")
|
|
logFile = os.path.join(logDir, f"log_app_{today}.log")
|
|
|
|
if not os.path.exists(logFile):
|
|
raise HTTPException(status_code=404, detail=f"Application log file for today not found: {logFile}")
|
|
|
|
return FileResponse(
|
|
path=logFile,
|
|
filename=f"log_app_{today}.log",
|
|
media_type="text/plain"
|
|
)
|
|
|
|
@router.get("/api/logs/audit")
|
|
@limiter.limit("10/minute")
|
|
async def download_audit_log(request: Request, currentUser: User = Depends(getCurrentUser)) -> FileResponse:
|
|
"""Download the current day's audit log file"""
|
|
# Check if user has admin privileges
|
|
if not hasattr(currentUser, 'privilege') or currentUser.privilege not in ('admin', 'sysadmin'):
|
|
raise HTTPException(status_code=403, detail="Admin privileges required")
|
|
|
|
# Get log directory from config
|
|
logDir = APP_CONFIG.get("APP_LOGGING_LOG_DIR")
|
|
if not logDir:
|
|
raise HTTPException(status_code=500, detail="APP_LOGGING_LOG_DIR configuration is required")
|
|
|
|
if not os.path.isabs(logDir):
|
|
# If relative path, make it relative to the gateway directory
|
|
gatewayDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
logDir = os.path.join(gatewayDir, logDir)
|
|
|
|
# Get current date for log file
|
|
today = datetime.now().strftime("%Y%m%d")
|
|
logFile = os.path.join(logDir, f"log_audit_{today}.log")
|
|
|
|
if not os.path.exists(logFile):
|
|
raise HTTPException(status_code=404, detail=f"Audit log file for today not found: {logFile}")
|
|
|
|
return FileResponse(
|
|
path=logFile,
|
|
filename=f"log_audit_{today}.log",
|
|
media_type="text/plain"
|
|
)
|
|
|
|
# ----------------------
|
|
# Database Management
|
|
# ----------------------
|
|
|
|
@router.get("/api/databases")
|
|
@limiter.limit("10/minute")
|
|
async def list_databases(request: Request, currentUser: User = Depends(getCurrentUser)) -> Dict[str, Any]:
|
|
"""List available databases"""
|
|
# Check if user has admin privileges
|
|
if not hasattr(currentUser, 'privilege') or currentUser.privilege not in ('admin', 'sysadmin'):
|
|
raise HTTPException(status_code=403, detail="Admin privileges required")
|
|
|
|
try:
|
|
# Get configured database names from configuration
|
|
databases = []
|
|
|
|
# App database - required configuration
|
|
appDb = APP_CONFIG.get("DB_APP_DATABASE")
|
|
if not appDb:
|
|
raise HTTPException(status_code=500, detail="DB_APP_DATABASE configuration is required")
|
|
databases.append(appDb)
|
|
|
|
# Chat database - optional configuration
|
|
chatDb = APP_CONFIG.get("DB_CHAT_DATABASE")
|
|
if chatDb and chatDb not in databases:
|
|
databases.append(chatDb)
|
|
|
|
# Management database - optional configuration
|
|
managementDb = APP_CONFIG.get("DB_MANAGEMENT_DATABASE")
|
|
if managementDb and managementDb not in databases:
|
|
databases.append(managementDb)
|
|
|
|
return {"databases": databases}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error listing databases: {e}")
|
|
raise HTTPException(status_code=500, detail="Failed to list databases")
|
|
|
|
@router.get("/api/databases/{database_name}/tables")
|
|
@limiter.limit("10/minute")
|
|
async def list_tables(
|
|
request: Request,
|
|
database_name: str,
|
|
currentUser: User = Depends(getCurrentUser)
|
|
) -> Dict[str, Any]:
|
|
"""List tables in a specific database"""
|
|
# Check if user has admin privileges
|
|
if not hasattr(currentUser, 'privilege') or currentUser.privilege not in ('admin', 'sysadmin'):
|
|
raise HTTPException(status_code=403, detail="Admin privileges required")
|
|
|
|
try:
|
|
# Get the appropriate interface based on database name
|
|
interface = get_interface_for_database(database_name, currentUser)
|
|
|
|
# Check if interface and database connection exist
|
|
if not interface or not interface.db:
|
|
raise HTTPException(status_code=500, detail="Database interface not available")
|
|
|
|
# Get tables from database
|
|
tables = interface.db.getTables()
|
|
|
|
return {"database": database_name, "tables": tables}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error listing tables for database {database_name}: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Failed to list tables for database {database_name}")
|
|
|
|
@router.post("/api/databases/{database_name}/tables/drop")
|
|
@limiter.limit("5/minute")
|
|
async def drop_table(
|
|
request: Request,
|
|
database_name: str,
|
|
currentUser: User = Depends(getCurrentUser),
|
|
payload: Dict[str, Any] = Body(...)
|
|
) -> Dict[str, Any]:
|
|
"""Drop a specific table from a database"""
|
|
# Check if user has admin privileges
|
|
if not hasattr(currentUser, 'privilege') or currentUser.privilege not in ('admin', 'sysadmin'):
|
|
raise HTTPException(status_code=403, detail="Admin privileges required")
|
|
|
|
table_name = payload.get("table")
|
|
if not table_name:
|
|
raise HTTPException(status_code=400, detail="Table name is required")
|
|
|
|
try:
|
|
# Get the appropriate interface based on database name
|
|
interface = get_interface_for_database(database_name, currentUser)
|
|
|
|
# Check if interface and database connection exist
|
|
if not interface or not interface.db:
|
|
raise HTTPException(status_code=500, detail="Database interface not available")
|
|
|
|
# Check if table exists
|
|
tables = interface.db.getTables()
|
|
if table_name not in tables:
|
|
raise HTTPException(status_code=404, detail=f"Table '{table_name}' not found in database '{database_name}'")
|
|
|
|
# Drop the table
|
|
with interface.db.connection.cursor() as cursor:
|
|
cursor.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE')
|
|
interface.db.connection.commit()
|
|
|
|
logger.warning(f"Admin drop_table executed by {currentUser.id}: dropped table '{table_name}' from database '{database_name}'")
|
|
return {"message": f"Table '{table_name}' dropped successfully from database '{database_name}'"}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error dropping table {table_name} from database {database_name}: {e}")
|
|
if 'interface' in locals() and interface.db.connection:
|
|
interface.db.connection.rollback()
|
|
raise HTTPException(status_code=500, detail=f"Failed to drop table '{table_name}' from database '{database_name}'")
|