from fastapi import APIRouter, HTTPException, Depends, Body, status, Response from fastapi.responses import FileResponse from fastapi.security import OAuth2PasswordRequestForm from fastapi.staticfiles import StaticFiles from typing import Dict, Any from datetime import timedelta import pathlib import os import logging from modules.shared.configuration import APP_CONFIG from modules.security.auth import ( createAccessToken, getCurrentActiveUser, getUserContext, ACCESS_TOKEN_EXPIRE_MINUTES ) import modules.interfaces.gatewayModel as gatewayModel from modules.interfaces.gatewayInterface import getGatewayInterface router = APIRouter() # Static folder setup - using absolute path from app root baseDir = pathlib.Path(__file__).parent.parent.parent # Go up to gateway root staticFolder = baseDir / "static" os.makedirs(staticFolder, exist_ok=True) # Mount static files router.mount("/static", StaticFiles(directory=str(staticFolder), html=True), name="static") logger = logging.getLogger(__name__) @router.get("/favicon.ico") async def favicon(): return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon") @router.get("/", tags=["General"]) async def root(): """API status endpoint""" return {"status": "online", "message": "Data Platform API is active"} @router.get("/api/test", tags=["General"]) async def getTest(): return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}" @router.options("/{fullPath:path}", tags=["General"]) async def optionsRoute(fullPath: str): return Response(status_code=200) @router.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 } @router.post("/api/token", response_model=gatewayModel.Token, tags=["General"]) async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()): # Get root mandate and admin user IDs adminGateway = getGatewayInterface() rootMandateId = adminGateway.getInitialId("mandates") adminUserId = adminGateway.getInitialId("users") if not rootMandateId or not adminUserId: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="System is not properly initialized with root mandate and admin user" ) # Create a new gateway interface instance with admin context adminGateway = getGatewayInterface(rootMandateId, adminUserId) try: # Authenticate user user = adminGateway.authenticateUser(formData.username, formData.password) # Create token with mandate ID and user ID accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) accessToken = createAccessToken( data={ "sub": user["username"], "_mandateId": str(user["_mandateId"]), # Ensure string "_userId": str(user["id"]) # Ensure string }, expiresDelta=accessTokenExpires ) logger.info(f"User {user['username']} successfully logged in with context: _mandateId={user['_mandateId']}, _userId={user['id']}") return {"accessToken": accessToken, "tokenType": "bearer"} except ValueError as e: # Handle authentication errors error_msg = str(e) logger.warning(f"Authentication failed for user {formData.username}: {error_msg}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=error_msg, headers={"WWW-Authenticate": "Bearer"}, ) except Exception as e: # Handle other errors error_msg = f"Login failed: {str(e)}" logger.error(f"Unexpected error during login for user {formData.username}: {error_msg}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error_msg ) @router.get("/api/user/me", response_model=Dict[str, Any], tags=["General"]) async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): return currentUser @router.post("/api/users/register", response_model=Dict[str, Any], tags=["General"]) async def registerUser(userData: Dict[str, Any]): """Register a new user.""" try: logger.info("Received registration request") logger.info(f"Raw userData type: {type(userData)}") logger.info(f"Raw userData content: {userData}") # Get root mandate and admin user IDs adminGateway = getGatewayInterface() rootMandateId = adminGateway.getInitialId("mandates") adminUserId = adminGateway.getInitialId("users") if not rootMandateId or not adminUserId: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="System is not properly initialized with root mandate and admin user" ) # Create a new gateway interface instance with admin context adminGateway = getGatewayInterface(rootMandateId, adminUserId) # Check required fields if not userData or not isinstance(userData, dict): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid user data format" ) if not userData.get("username") or not userData.get("password"): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Username and password are required" ) # Create user data with mandate ID userData = { "username": userData["username"], "password": userData["password"], "email": userData.get("email"), "fullName": userData.get("fullName"), "language": userData.get("language", "de"), "_mandateId": rootMandateId, "disabled": False, "privilege": "user" } # Create the user createdUser = adminGateway.createUser(**userData) if not createdUser: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create user" ) # Clear the users table from cache to ensure fresh data if hasattr(adminGateway.db, '_tablesCache') and "users" in adminGateway.db._tablesCache: del adminGateway.db._tablesCache["users"] # Return the created user (without password) if "hashedPassword" in createdUser: del createdUser["hashedPassword"] return createdUser except ValueError as e: logger.error(f"ValueError during registration: {str(e)}") raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) except PermissionError as e: logger.error(f"PermissionError during registration: {str(e)}") raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e) ) except Exception as e: logger.error(f"Error during user registration: {str(e)}") logger.error(f"Error type: {type(e)}") logger.error(f"Error details: {e.__dict__ if hasattr(e, '__dict__') else 'No details available'}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to register user" )