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, getRootInterface, ACCESS_TOKEN_EXPIRE_MINUTES ) import modules.interfaces.gatewayModel as gatewayModel from modules.interfaces.gatewayInterface import getInterface 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", tags=["General"]) 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 get_test(): return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}" @router.options("/{fullPath:path}", tags=["General"]) async def options_route(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 login_for_access_token(formData: OAuth2PasswordRequestForm = Depends()): # Create a new gateway interface instance with admin context myInterface = getRootInterface() try: # Authenticate user user = myInterface.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 "authenticationAuthority": user.get("authenticationAuthority", "local") # Add auth authority }, expiresDelta=accessTokenExpires ) logger.info(f"User {user['username']} successfully logged in with context: _mandateId={user['_mandateId']}, _userId={user['id']}, auth={user.get('authenticationAuthority', 'local')}") 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 read_user_me(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): return currentUser @router.post("/api/users/register", response_model=Dict[str, Any], tags=["General"]) async def register_user(userData: Dict[str, Any]): """Register a new user.""" try: logger.debug("Received registration request") # Create a new gateway interface instance with admin context myInterface = getRootInterface() # 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 in same mandate as admin user userData = { "username": userData["username"], "password": userData["password"], "email": userData.get("email"), "fullName": userData.get("fullName"), "language": userData.get("language", "en"), "disabled": False, "privilege": "user" } # Create the user try: createdUser = myInterface.createUser(**userData) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) # Verify the user was created if not createdUser: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create user" ) return createdUser except HTTPException: raise except Exception as e: logger.error(f"Error in user registration: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Registration failed: {str(e)}" ) @router.get("/api/user/available", response_model=Dict[str, Any], tags=["General"]) async def check_username_availability( username: str, authenticationAuthority: str = "local" ): """Check if a username is available for registration""" try: # Create a new gateway interface instance with root context myInterface = getRootInterface() # Check if user exists existingUser = myInterface.getUserByUsername(username) if not existingUser: return {"available": True} # If user exists, check authentication authority if existingUser.get("authenticationAuthority") == authenticationAuthority: return { "available": False, "message": f"Username already exists with {authenticationAuthority} authentication" } else: return { "available": True, "message": f"Username exists but with different authentication authority" } except Exception as e: logger.error(f"Error checking username availability: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to check username availability: {str(e)}" )