working set for clean data references

This commit is contained in:
ValueOn AG 2025-05-21 19:38:06 +02:00
parent fee9bb0151
commit e6ca2bad17
20 changed files with 956 additions and 1113 deletions

View file

@ -12,27 +12,16 @@ class DatabaseConnector:
A connector for JSON-based data storage. A connector for JSON-based data storage.
Provides generic database operations without user/mandate filtering. Provides generic database operations without user/mandate filtering.
Stores tables as folders and records as individual files. Stores tables as folders and records as individual files.
Implements lazy loading for better performance.
""" """
def __init__(self, dbHost: str, dbDatabase: str, dbUser: str = None, dbPassword: str = None, def __init__(self, dbHost: str, dbDatabase: str, dbUser: str = None, dbPassword: str = None, userId: str = None):
_mandateId: str = None, _userId: str = None, skipInitialIdLookup: bool = False):
# Store the input parameters # Store the input parameters
self.dbHost = dbHost self.dbHost = dbHost
self.dbDatabase = dbDatabase self.dbDatabase = dbDatabase
self.dbUser = dbUser self.dbUser = dbUser
self.dbPassword = dbPassword self.dbPassword = dbPassword
# Check if context parameters are set # Set userId (default to empty string if None)
if _mandateId is None and _userId is None: self.userId = userId if userId is not None else ""
# Allow initialization with empty strings
self._mandateId = ''
self._userId = ''
else:
# Ensure both parameters are provided
if _mandateId is None or _userId is None:
raise ValueError("_mandateId and _userId must both be provided or both be None")
self._mandateId = _mandateId
self._userId = _userId
# Ensure the database directory exists # Ensure the database directory exists
self.dbFolder = os.path.join(self.dbHost, self.dbDatabase) self.dbFolder = os.path.join(self.dbHost, self.dbDatabase)
@ -46,27 +35,7 @@ class DatabaseConnector:
self._systemTableName = "_system" self._systemTableName = "_system"
self._initializeSystemTable() self._initializeSystemTable()
# If IDs are empty and we're not skipping lookup, try to use initial IDs logger.debug(f"Context: userId={self.userId}")
if not skipInitialIdLookup:
self._resolveInitialIds()
logger.debug(f"Context: _mandateId={self._mandateId}, _userId={self._userId}")
def _resolveInitialIds(self):
"""
Resolve initial IDs for mandate and user if they're empty.
"""
if not self._mandateId:
initialMandateId = self.getInitialId("mandates")
if initialMandateId is not None:
self._mandateId = initialMandateId
logger.info(f"Using initial _mandateId: {initialMandateId}")
if not self._userId:
initialUserId = self.getInitialId("users")
if initialUserId is not None:
self._userId = initialUserId
logger.info(f"Using initial _userId: {initialUserId}")
def _initializeSystemTable(self): def _initializeSystemTable(self):
"""Initializes the system table if it doesn't exist yet.""" """Initializes the system table if it doesn't exist yet."""
@ -315,14 +284,13 @@ class DatabaseConnector:
logger.error(f"Error saving metadata for table {table}: {e}") logger.error(f"Error saving metadata for table {table}: {e}")
return False return False
def updateContext(self, _mandateId: str, _userId: str) -> None: def updateContext(self, userId: str) -> None:
"""Updates the context of the database connector.""" """Updates the context of the database connector."""
if _mandateId is None or _userId is None: if userId is None:
raise ValueError("_mandateId and _userId must both be provided") raise ValueError("userId must be provided")
self._mandateId = _mandateId self.userId = userId
self._userId = _userId logger.info(f"Updated database context: userId={self.userId}")
logger.info(f"Updated database context: _mandateId={self._mandateId}, _userId={self._userId}")
# Clear cache to ensure fresh data with new context # Clear cache to ensure fresh data with new context
self._tablesCache = {} self._tablesCache = {}
@ -426,19 +394,14 @@ class DatabaseConnector:
recordData["id"] = str(recordData["id"]) recordData["id"] = str(recordData["id"])
# Add context fields # Add context fields
recordData["_mandateId"] = self._mandateId recordData["userId"] = self.userId
recordData["_userId"] = self._userId
# Update metadata # Add creation and modification tracking
if "recordIds" not in metadata:
metadata["recordIds"] = []
metadata["recordIds"].append(recordData["id"])
metadata["recordIds"].sort()
# Add creation timestamp
currentTime = self._getCurrentTimestamp() currentTime = self._getCurrentTimestamp()
recordData["_createdAt"] = currentTime recordData["_createdAt"] = currentTime
recordData["_modifiedAt"] = currentTime recordData["_modifiedAt"] = currentTime
recordData["_createdBy"] = self.userId
recordData["_modifiedBy"] = self.userId
# Save the record # Save the record
recordPath = self._getRecordPath(table, recordData["id"]) recordPath = self._getRecordPath(table, recordData["id"])
@ -532,8 +495,9 @@ class DatabaseConnector:
for key, value in recordData.items(): for key, value in recordData.items():
existingRecord[key] = value existingRecord[key] = value
# Update modified timestamp # Update modified timestamp and user
existingRecord["_modifiedAt"] = self._getCurrentTimestamp() existingRecord["_modifiedAt"] = self._getCurrentTimestamp()
existingRecord["_modifiedBy"] = self.userId
# Save the updated record # Save the updated record
recordPath = self._getRecordPath(table, recordId) recordPath = self._getRecordPath(table, recordId)

View file

@ -14,11 +14,11 @@ class GatewayAccess:
def __init__(self, currentUser: Dict[str, Any], db): def __init__(self, currentUser: Dict[str, Any], db):
"""Initialize with user context.""" """Initialize with user context."""
self.currentUser = currentUser self.currentUser = currentUser
self._mandateId = currentUser.get("_mandateId") self.mandateId = currentUser.get("mandateId")
self._userId = currentUser.get("id") self.userId = currentUser.get("id")
if not self._mandateId or not self._userId: if not self.mandateId or not self.userId:
raise ValueError("Invalid user context: _mandateId and id are required") raise ValueError("Invalid user context: mandateId and userId are required")
self.db = db self.db = db
@ -42,11 +42,11 @@ class GatewayAccess:
filtered_records = recordset # System admins see all records filtered_records = recordset # System admins see all records
elif userPrivilege == "admin": elif userPrivilege == "admin":
# Admins see records in their mandate # Admins see records in their mandate
filtered_records = [r for r in recordset if r.get("_mandateId") == self._mandateId] filtered_records = [r for r in recordset if r.get("mandateId","-") == self.mandateId]
else: # Regular users else: # Regular users
# Users only see records they own within their mandate # Users only see records they own within their mandate
filtered_records = [r for r in recordset filtered_records = [r for r in recordset
if r.get("_mandateId") == self._mandateId and r.get("_userId") == self._userId] if r.get("mandateId","-") == self.mandateId and r.get("_createdBy") == self.userId]
# Add access control attributes to each record # Add access control attributes to each record
for record in filtered_records: for record in filtered_records:
@ -96,15 +96,15 @@ class GatewayAccess:
record = records[0] record = records[0]
# Admins can modify anything in their mandate # Admins can modify anything in their mandate
if userPrivilege == "admin" and record.get("_mandateId") == self._mandateId: if userPrivilege == "admin" and record.get("mandateId","-") == self.mandateId:
# Exception: Can't modify Root mandate unless you are a sysadmin # Exception: Can't modify Root mandate unless you are a sysadmin
if table == "mandates" and record.get("initialid") and userPrivilege != "sysadmin": if table == "mandates" and record.get("initialid") and userPrivilege != "sysadmin":
return False return False
return True return True
# Users can only modify their own records # Users can only modify their own records
if (record.get("_mandateId") == self._mandateId and if (record.get("mandateId","-") == self.mandateId and
record.get("_userId") == self._userId): record.get("_createdBy") == self.userId):
return True return True
return False return False

View file

@ -14,6 +14,7 @@ from passlib.context import CryptContext
from modules.connectors.connectorDbJson import DatabaseConnector from modules.connectors.connectorDbJson import DatabaseConnector
from modules.shared.configuration import APP_CONFIG from modules.shared.configuration import APP_CONFIG
from modules.interfaces.gatewayAccess import GatewayAccess from modules.interfaces.gatewayAccess import GatewayAccess
from modules.interfaces.gatewayModel import User, Mandate, UserInDB
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -31,59 +32,38 @@ class GatewayInterface:
Manages users and mandates. Manages users and mandates.
""" """
def __init__(self, currentUser: Dict[str, Any]): def __init__(self):
"""Initializes the Gateway Interface with user context.""" """Initializes the Gateway Interface."""
# Ensure valid DB
self.currentUser = currentUser
self._mandateId = currentUser.get("_mandateId")
self._userId = currentUser.get("id")
if not self._mandateId or not self._userId:
raise ValueError("Invalid initial context: _mandateId and id are required")
# Initialize database # Initialize database
self._initializeDatabase() self._initializeDatabase()
# Initialize standard records if needed # Initialize standard records if needed
self._initRecords() self._initRecords()
# Set user context
if currentUser.get("id") == "-1":
logger.debug(f"Initializing GatewayInterface with Root User")
self.currentUser = currentUser
self._mandateId = currentUser.get("_mandateId")
self._userId = currentUser.get("id")
self._initializeDatabase()
mandateId = self.getInitialId("mandates")
userId = self.getInitialId("users")
currentUser = {
"_mandateId": mandateId,
"id": userId
}
logger.debug(f"Initializing GatewayInterface with rootUser={currentUser}")
else:
logger.debug(f"Initializing GatewayInterface with currentUser={currentUser}")
self.currentUser = currentUser
self._mandateId = currentUser.get("_mandateId")
self._userId = currentUser.get("id")
if not self._mandateId or not self._userId: # Initialize variables
raise ValueError("Invalid user context: _mandateId and id are required") self.currentUser = None
self.userId = None
self.access = None # Will be set when user context is provided
def setUserContext(self, currentUser: Dict[str, Any]):
"""Sets the user context for the interface."""
if not currentUser:
logger.info("Initializing interface without user context")
return
self.currentUser = currentUser
self.userId = currentUser.get("id")
if not self.userId:
raise ValueError("Invalid user context: id is required")
# Add language settings # Add language settings
self.userLanguage = currentUser.get("language", "en") # Default user language self.userLanguage = currentUser.get("language", "en") # Default user language
# Initialize database # Initialize access control with user context
self._initializeDatabase()
# Initialize access control
self.access = GatewayAccess(self.currentUser, self.db) self.access = GatewayAccess(self.currentUser, self.db)
logger.debug(f"User context set: userId={self.userId}")
def _initializeDatabase(self): def _initializeDatabase(self):
"""Initializes the database connection.""" """Initializes the database connection."""
@ -101,44 +81,17 @@ class GatewayInterface:
dbHost=dbHost, dbHost=dbHost,
dbDatabase=dbDatabase, dbDatabase=dbDatabase,
dbUser=dbUser, dbUser=dbUser,
dbPassword=dbPassword, dbPassword=dbPassword
_mandateId=self._mandateId,
_userId=self._userId
) )
# Set context
self.db.updateContext(self._mandateId, self._userId)
logger.info("Database initialized successfully") logger.info("Database initialized successfully")
except Exception as e: except Exception as e:
logger.error(f"Failed to initialize database: {str(e)}") logger.error(f"Failed to initialize database: {str(e)}")
raise raise
def _getCurrentUserInfo(self) -> Optional[Dict[str, Any]]:
"""Returns information about the current user."""
if not self._userId:
return None
users = self.db.getRecordset("users", recordFilter={"id": self._userId})
if users:
return users[0]
return None
def _initRecords(self): def _initRecords(self):
"""Initializes standard records in the database if they don't exist."""
self._initRootMandate() self._initRootMandate()
# Update database context with new IDs
if self._mandateId and self._userId:
self.db.updateContext(self._mandateId, self._userId)
self._initAdminUser() self._initAdminUser()
# Update database context with new IDs
if self._mandateId and self._userId:
self.db.updateContext(self._mandateId, self._userId)
# Reload user information with new context
self.currentUser = self._getCurrentUserInfo()
def _initRootMandate(self): def _initRootMandate(self):
"""Creates the Root mandate if it doesn't exist.""" """Creates the Root mandate if it doesn't exist."""
@ -157,7 +110,7 @@ class GatewayInterface:
self.db._registerInitialId("mandates", createdMandate['id']) self.db._registerInitialId("mandates", createdMandate['id'])
# Update mandate context # Update mandate context
self._mandateId = createdMandate['id'] self.currentUser["mandateId"] = createdMandate['id']
def _initAdminUser(self): def _initAdminUser(self):
"""Creates the Admin user if it doesn't exist.""" """Creates the Admin user if it doesn't exist."""
@ -166,7 +119,7 @@ class GatewayInterface:
if existingUserId is None or not users: if existingUserId is None or not users:
logger.info("Creating Admin user") logger.info("Creating Admin user")
adminUser = { adminUser = {
"_mandateId": self._mandateId, "mandateId": self.getInitialId("mandates"),
"username": "admin", "username": "admin",
"email": "admin@example.com", "email": "admin@example.com",
"fullName": "Administrator", "fullName": "Administrator",
@ -183,8 +136,9 @@ class GatewayInterface:
self.db._registerInitialId("users", createdUser['id']) self.db._registerInitialId("users", createdUser['id'])
# Update user context # Update user context
self._userId = createdUser['id'] self.currentUser = createdUser
self.userId = createdUser.get("id")
def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]: def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
""" """
Unified user access management function that filters data based on user privileges Unified user access management function that filters data based on user privileges
@ -223,41 +177,43 @@ class GatewayInterface:
def _verifyPassword(self, plainPassword: str, hashedPassword: str) -> bool: def _verifyPassword(self, plainPassword: str, hashedPassword: str) -> bool:
"""Checks if the password matches the hash.""" """Checks if the password matches the hash."""
return pwdContext.verify(plainPassword, hashedPassword) return pwdContext.verify(plainPassword, hashedPassword)
def _getCurrentTimestamp(self) -> str:
"""Returns the current timestamp in ISO format."""
from datetime import datetime
return datetime.now().isoformat()
# Mandate methods # Mandate methods
def getAllMandates(self) -> List[Dict[str, Any]]: def getAllMandates(self) -> List[Mandate]:
"""Returns mandates based on user access level.""" """Returns mandates based on user access level."""
allMandates = self.db.getRecordset("mandates") allMandates = self.db.getRecordset("mandates")
return self._uam("mandates", allMandates) filteredMandates = self._uam("mandates", allMandates)
return [Mandate(**mandate) for mandate in filteredMandates]
def getMandate(self, mandateId: str) -> Optional[Dict[str, Any]]: def getMandate(self, mandateId: str) -> Optional[Mandate]:
"""Returns a mandate by ID if user has access.""" """Returns a mandate by ID if user has access."""
mandates = self.db.getRecordset("mandates", recordFilter={"id": mandateId}) mandates = self.db.getRecordset("mandates", recordFilter={"id": mandateId})
if not mandates: if not mandates:
return None return None
filteredMandates = self._uam("mandates", mandates) filteredMandates = self._uam("mandates", mandates)
return filteredMandates[0] if filteredMandates else None if not filteredMandates:
return None
return Mandate(**filteredMandates[0])
def createMandate(self, name: str, language: str = "en") -> Dict[str, Any]: def createMandate(self, name: str, language: str = "en") -> Mandate:
"""Creates a new mandate if user has permission.""" """Creates a new mandate if user has permission."""
if not self._canModify("mandates"): if not self._canModify("mandates"):
raise PermissionError("No permission to create mandates") raise PermissionError("No permission to create mandates")
mandateData = { # Create and validate mandate data using Pydantic model
"name": name, mandateData = Mandate(
"language": language name=name,
} language=language
)
return self.db.recordCreate("mandates", mandateData) # Convert to dict for database storage
created = self.db.recordCreate("mandates", mandateData.model_dump())
return Mandate(**created)
def updateMandate(self, mandateId: str, mandateData: Dict[str, Any]) -> Dict[str, Any]: def updateMandate(self, mandateId: str, mandateData: Dict[str, Any]) -> Mandate:
"""Updates a mandate if user has access.""" """Updates a mandate if user has access."""
# Check if the mandate exists and user has access # Check if the mandate exists and user has access
mandate = self.getMandate(mandateId) mandate = self.getMandate(mandateId)
@ -267,8 +223,16 @@ class GatewayInterface:
if not self._canModify("mandates", mandateId): if not self._canModify("mandates", mandateId):
raise PermissionError(f"No permission to update mandate {mandateId}") raise PermissionError(f"No permission to update mandate {mandateId}")
# Validate update data using Pydantic model
try:
# Create a new Mandate instance with existing data plus updates
updatedMandate = Mandate(**{**mandate.model_dump(), **mandateData})
except Exception as e:
raise ValueError(f"Invalid mandate data: {str(e)}")
# Update the mandate # Update the mandate
return self.db.recordModify("mandates", mandateId, mandateData) updated = self.db.recordModify("mandates", mandateId, updatedMandate.model_dump())
return Mandate(**updated)
def deleteMandate(self, mandateId: str) -> bool: def deleteMandate(self, mandateId: str) -> bool:
""" """
@ -307,7 +271,7 @@ class GatewayInterface:
# User methods # User methods
def getAllUsers(self) -> List[Dict[str, Any]]: def getAllUsers(self) -> List[User]:
"""Returns users based on user access level.""" """Returns users based on user access level."""
allUsers = self.db.getRecordset("users") allUsers = self.db.getRecordset("users")
filteredUsers = self._uam("users", allUsers) filteredUsers = self._uam("users", allUsers)
@ -317,17 +281,12 @@ class GatewayInterface:
if "hashedPassword" in user: if "hashedPassword" in user:
del user["hashedPassword"] del user["hashedPassword"]
return filteredUsers return [User(**user) for user in filteredUsers]
def getUsersByMandate(self, _mandateId: str) -> List[Dict[str, Any]]: def getUsersByMandate(self, mandateId: str) -> List[User]:
"""Returns users for a specific mandate if user has access.""" """Returns users for a specific mandate if user has access."""
# First check if user has access to the mandate
mandate = self.getMandate(_mandateId)
if not mandate:
return []
# Get users for this mandate # Get users for this mandate
users = self.db.getRecordset("users", recordFilter={"_mandateId": _mandateId}) users = self.db.getRecordset("users", recordFilter={"mandateId": mandateId})
filteredUsers = self._uam("users", users) filteredUsers = self._uam("users", users)
# Remove password hashes # Remove password hashes
@ -335,9 +294,9 @@ class GatewayInterface:
if "hashedPassword" in user: if "hashedPassword" in user:
del user["hashedPassword"] del user["hashedPassword"]
return filteredUsers return [User(**user) for user in filteredUsers]
def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]: def getUserByUsername(self, username: str) -> Optional[User]:
"""Returns a user by username.""" """Returns a user by username."""
try: try:
# Get users table # Get users table
@ -350,7 +309,7 @@ class GatewayInterface:
if user.get("username") == username: if user.get("username") == username:
logger.info(f"Found user with username {username}") logger.info(f"Found user with username {username}")
logger.debug(f"User fields: {list(user.keys())}") logger.debug(f"User fields: {list(user.keys())}")
return user return User(**user)
logger.info(f"No user found with username {username}") logger.info(f"No user found with username {username}")
return None return None
@ -359,9 +318,9 @@ class GatewayInterface:
logger.error(f"Error getting user by username: {str(e)}") logger.error(f"Error getting user by username: {str(e)}")
return None return None
def getUser(self, _userId: str) -> Optional[Dict[str, Any]]: def getUser(self, userId: str) -> Optional[User]:
"""Returns a user by ID if user has access.""" """Returns a user by ID if user has access."""
users = self.db.getRecordset("users", recordFilter={"_userId": _userId}) users = self.db.getRecordset("users", recordFilter={"id": userId})
if not users: if not users:
return None return None
@ -375,13 +334,13 @@ class GatewayInterface:
if "hashedPassword" in user: if "hashedPassword" in user:
userCopy = user.copy() userCopy = user.copy()
del userCopy["hashedPassword"] del userCopy["hashedPassword"]
return userCopy return User(**userCopy)
return user return User(**user)
def createUser(self, username: str, password: str = None, email: str = None, fullName: str = None, def createUser(self, username: str, password: str = None, email: str = None, fullName: str = None,
language: str = "en", _mandateId: int = None, disabled: bool = False, language: str = "en", disabled: bool = False,
privilege: str = "user", authenticationAuthority: str = "local") -> Dict[str, Any]: privilege: str = "user", authenticationAuthority: str = "local") -> User:
"""Create a new user""" """Create a new user"""
try: try:
# Validate username # Validate username
@ -390,7 +349,7 @@ class GatewayInterface:
# Check if user already exists with the same authentication authority # Check if user already exists with the same authentication authority
existingUser = self.getUserByUsername(username) existingUser = self.getUserByUsername(username)
if existingUser and existingUser.get("authenticationAuthority") == authenticationAuthority: if existingUser and existingUser.authenticationAuthority == authenticationAuthority:
raise ValueError(f"Username '{username}' already exists with {authenticationAuthority} authentication") raise ValueError(f"Username '{username}' already exists with {authenticationAuthority} authentication")
# Validate password for local authentication # Validate password for local authentication
@ -400,24 +359,21 @@ class GatewayInterface:
if len(password) < 8: if len(password) < 8:
raise ValueError("Password must be at least 8 characters long") raise ValueError("Password must be at least 8 characters long")
# Create user data # Create user data using UserInDB model
userData = { userData = UserInDB(
"username": username, username=username,
"email": email, email=email,
"fullName": fullName, fullName=fullName,
"language": language, language=language,
"_mandateId": _mandateId or self._mandateId, mandateId=self.currentUser.get("mandateId"),
"disabled": disabled, disabled=disabled,
"privilege": privilege, privilege=privilege,
"authenticationAuthority": authenticationAuthority authenticationAuthority=authenticationAuthority,
} hashedPassword=self._getPasswordHash(password) if authenticationAuthority == "local" else None
)
# Add password hash for local authentication
if authenticationAuthority == "local":
userData["hashedPassword"] = self._getPasswordHash(password)
# Create user record # Create user record
createdRecord = self.db.recordCreate("users", userData) createdRecord = self.db.recordCreate("users", userData.model_dump(exclude_none=True))
if not createdRecord or not createdRecord.get("id"): if not createdRecord or not createdRecord.get("id"):
raise ValueError("Failed to create user record") raise ValueError("Failed to create user record")
@ -425,7 +381,7 @@ class GatewayInterface:
createdUser = self.db.getRecordset("users", recordFilter={"id": createdRecord["id"]}) createdUser = self.db.getRecordset("users", recordFilter={"id": createdRecord["id"]})
if not createdUser or len(createdUser) == 0: if not createdUser or len(createdUser) == 0:
# Try to get user by username as fallback # Try to get user by username as fallback
createdUser = self.db.getRecordset("users", recordFilter={"username": userData["username"]}) createdUser = self.db.getRecordset("users", recordFilter={"username": userData.username})
if not createdUser or len(createdUser) == 0: if not createdUser or len(createdUser) == 0:
raise ValueError("Failed to retrieve created user") raise ValueError("Failed to retrieve created user")
@ -433,7 +389,7 @@ class GatewayInterface:
if hasattr(self.db, '_tablesCache') and "users" in self.db._tablesCache: if hasattr(self.db, '_tablesCache') and "users" in self.db._tablesCache:
del self.db._tablesCache["users"] del self.db._tablesCache["users"]
return createdUser[0] return User(**createdUser[0])
except ValueError as e: except ValueError as e:
logger.error(f"Error creating user: {str(e)}") logger.error(f"Error creating user: {str(e)}")
@ -442,7 +398,7 @@ class GatewayInterface:
logger.error(f"Unexpected error creating user: {str(e)}") logger.error(f"Unexpected error creating user: {str(e)}")
raise ValueError(f"Failed to create user: {str(e)}") raise ValueError(f"Failed to create user: {str(e)}")
def authenticateUser(self, username: str, password: str = None) -> Optional[Dict[str, Any]]: def authenticateUser(self, username: str, password: str = None) -> Optional[User]:
"""Authenticates a user by username and password.""" """Authenticates a user by username and password."""
# Clear the users table from cache and reload it # Clear the users table from cache and reload it
if "users" in self.db._tablesCache: if "users" in self.db._tablesCache:
@ -455,16 +411,18 @@ class GatewayInterface:
raise ValueError("Benutzer nicht gefunden") raise ValueError("Benutzer nicht gefunden")
# Check if the user is disabled # Check if the user is disabled
if user.get("disabled", False): if user.disabled:
raise ValueError("Benutzer ist deaktiviert") raise ValueError("Benutzer ist deaktiviert")
# Handle authentication based on authority # Handle authentication based on authority
auth_authority = user.get("authenticationAuthority", "local") auth_authority = user.authenticationAuthority
if auth_authority == "local": if auth_authority == "local":
if not password: if not password:
raise ValueError("Passwort ist erforderlich") raise ValueError("Passwort ist erforderlich")
if not self._verifyPassword(password, user.get("hashedPassword", "")): # Get the full user record with password hash for verification
userWithPassword = UserInDB(**self.db.getRecordset("users", recordFilter={"id": user.id})[0])
if not self._verifyPassword(password, userWithPassword.hashedPassword):
raise ValueError("Falsches Passwort") raise ValueError("Falsches Passwort")
elif auth_authority == "microsoft": elif auth_authority == "microsoft":
# For Microsoft users, we don't verify the password here # For Microsoft users, we don't verify the password here
@ -473,26 +431,21 @@ class GatewayInterface:
else: else:
raise ValueError(f"Unbekannte Authentifizierungsmethode: {auth_authority}") raise ValueError(f"Unbekannte Authentifizierungsmethode: {auth_authority}")
# Create a copy without password hash return user
authenticatedUser = {**user}
if "hashedPassword" in authenticatedUser:
del authenticatedUser["hashedPassword"]
return authenticatedUser
def updateUser(self, _userId: str, userData: Dict[str, Any]) -> Dict[str, Any]: def updateUser(self, userId: str, userData: Dict[str, Any]) -> User:
"""Updates a user if current user has permission.""" """Updates a user if current user has permission."""
# Check if the user exists and current user has access # Check if the user exists and current user has access
user = self.getUser(_userId) user = self.getUser(userId)
if not user: if not user:
# Try to get the raw user record for admin access check # Try to get the raw user record for admin access check
users = self.db.getRecordset("users", recordFilter={"_userId": _userId}) users = self.db.getRecordset("users", recordFilter={"id": userId})
if not users: if not users:
raise ValueError(f"User with ID {_userId} not found") raise ValueError(f"User with ID {userId} not found")
# Check if current user is admin/sysadmin # Check if current user is admin/sysadmin
if not self._canModify("users", _userId): if not self._canModify("users", userId):
raise PermissionError(f"No permission to update user {_userId}") raise PermissionError(f"No permission to update user {userId}")
user = users[0] user = users[0]
@ -510,140 +463,83 @@ class GatewayInterface:
userData["hashedPassword"] = self._getPasswordHash(userData["password"]) userData["hashedPassword"] = self._getPasswordHash(userData["password"])
del userData["password"] del userData["password"]
try:
# Create a new UserInDB instance with existing data plus updates
updatedUser = UserInDB(**{**user.model_dump(), **userData})
except Exception as e:
raise ValueError(f"Invalid user data: {str(e)}")
# Update the user # Update the user
updatedUser = self.db.recordModify("users", _userId, userData) updated = self.db.recordModify("users", userId, updatedUser.model_dump(exclude_none=True))
# Remove password hash from the response # Return User model without password hash
if "hashedPassword" in updatedUser: return User(**updated)
del updatedUser["hashedPassword"]
return updatedUser
def disableUser(self, _userId: str) -> Dict[str, Any]: def disableUser(self, userId: str) -> User:
"""Disables a user if current user has permission.""" """Disables a user if current user has permission."""
return self.updateUser(_userId, {"disabled": True}) return self.updateUser(userId, {"disabled": True})
def enableUser(self, _userId: str) -> Dict[str, Any]: def enableUser(self, userId: str) -> User:
"""Enables a user if current user has permission.""" """Enables a user if current user has permission."""
return self.updateUser(_userId, {"disabled": False}) return self.updateUser(userId, {"disabled": False})
def _deleteUserReferencedData(self, _userId: str) -> None: def _deleteUserReferencedData(self, userId: str) -> None:
"""Deletes all data associated with a user.""" """Deletes all data associated with a user."""
# Delete user attributes # Delete user attributes
try: try:
attributes = self.db.getRecordset("attributes", recordFilter={"_userId": _userId}) attributes = self.db.getRecordset("attributes", recordFilter={"createdBy": userId})
for attribute in attributes: for attribute in attributes:
self.db.recordDelete("attributes", attribute["id"]) self.db.recordDelete("attributes", attribute["id"])
except Exception as e: except Exception as e:
logger.error(f"Error deleting attributes for user {_userId}: {e}") logger.error(f"Error deleting attributes for user {userId}: {e}")
logger.info(f"All referenced data for user {_userId} has been deleted") logger.info(f"All referenced data for user {userId} has been deleted")
def deleteUser(self, _userId: str) -> bool: def deleteUser(self, userId: str) -> bool:
"""Deletes a user and all associated data if current user has permission.""" """Deletes a user and all associated data if current user has permission."""
# Check if the user exists # Check if the user exists
users = self.db.getRecordset("users", recordFilter={"_userId": _userId}) users = self.db.getRecordset("users", recordFilter={"id": userId})
if not users: if not users:
return False return False
# Check if current user has permission # Check if current user has permission
if not self._canModify("users", _userId): if not self._canModify("users", userId):
raise PermissionError(f"No permission to delete user {_userId}") raise PermissionError(f"No permission to delete user {userId}")
# Check if it's the initial user # Check if it's the initial user
initialUserId = self.getInitialId("users") initialUserId = self.getInitialId("users")
if initialUserId is not None and _userId == initialUserId: if initialUserId is not None and userId == initialUserId:
logger.warning("Attempt to delete the Root Admin was prevented") logger.warning("Attempt to delete the Root Admin was prevented")
return False return False
# Delete all data associated with the user # Delete all data associated with the user
self._deleteUserReferencedData(_userId) self._deleteUserReferencedData(userId)
# Delete the user # Delete the user
success = self.db.recordDelete("users", _userId) success = self.db.recordDelete("users", userId)
if success: if success:
logger.info(f"User with ID {_userId} was successfully deleted") logger.info(f"User with ID {userId} was successfully deleted")
else: else:
logger.error(f"Error deleting user with ID {_userId}") logger.error(f"Error deleting user with ID {userId}")
return success return success
# Microsoft Login def getInterface(currentUser: Dict[str, Any] = None) -> 'GatewayInterface':
def getMsftToken(self) -> Optional[Dict[str, Any]]:
"""Get Microsoft token data for the current user from database"""
try:
# Get token from database using current user's mandateId and userId
tokens = self.db.getRecordset("msftTokens", recordFilter={
"_mandateId": self._mandateId,
"_userId": self._userId
})
if tokens and len(tokens) > 0:
token_data = json.loads(tokens[0]["token_data"])
logger.debug(f"Retrieved Microsoft token for user {self._userId}")
return token_data
else:
logger.debug(f"No Microsoft token found for user {self._userId}")
return None
except Exception as e:
logger.error(f"Error retrieving Microsoft token: {str(e)}")
return None
def saveMsftToken(self, token_data: Dict[str, Any]) -> bool:
"""Save Microsoft token data for the current user to database"""
try:
# Check if token already exists
tokens = self.db.getRecordset("msftTokens", recordFilter={
"_mandateId": self._mandateId,
"_userId": self._userId
})
if tokens and len(tokens) > 0:
# Update existing token
token_id = tokens[0]["id"]
updated_data = {
"token_data": json.dumps(token_data),
"updated_at": datetime.now().isoformat()
}
self.db.recordModify("msftTokens", token_id, updated_data)
logger.debug(f"Updated Microsoft token for user {self._userId}")
else:
# Create new token with UUID
new_token = {
"_mandateId": self._mandateId,
"_userId": self._userId,
"token_data": json.dumps(token_data),
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat()
}
self.db.recordCreate("msftTokens", new_token)
logger.debug(f"Saved new Microsoft token for user {self._userId}")
return True
except Exception as e:
logger.error(f"Error saving Microsoft token: {str(e)}")
return False
def getInterface(currentUser: Dict[str, Any]) -> 'GatewayInterface':
""" """
Returns a GatewayInterface instance for the current user. Returns a GatewayInterface instance.
Handles initialization of database and records. If currentUser is provided, initializes with user context.
Otherwise, returns an instance with only database access.
""" """
mandateId = currentUser.get("_mandateId")
userId = currentUser.get("id")
if not mandateId or not userId:
raise ValueError("Invalid user context: _mandateId and id are required")
# Create context key
contextKey = f"{mandateId}_{userId}"
# Create new instance if not exists # Create new instance if not exists
if contextKey not in _gatewayInterfaces: if "default" not in _gatewayInterfaces:
_gatewayInterfaces[contextKey] = GatewayInterface(currentUser) _gatewayInterfaces["default"] = GatewayInterface()
return _gatewayInterfaces[contextKey] interface = _gatewayInterfaces["default"]
if currentUser:
interface.setUserContext(currentUser)
else:
logger.info("Returning interface without user context")
return interface

View file

@ -15,8 +15,21 @@ def getModelAttributes(modelClass):
class Label(BaseModel): class Label(BaseModel):
"""Label for an attribute or a class with support for multiple languages""" """Label for an attribute or a class with support for multiple languages"""
default: str default: str = Field(..., description="Default label text")
translations: Dict[str, str] = {} translations: Dict[str, str] = Field(default_factory=dict, description="Translations for different languages")
class Config:
title = "Label"
description = "A label with support for multiple languages"
schema_extra = {
"example": {
"default": "User",
"translations": {
"en": "User",
"fr": "Utilisateur"
}
}
}
def getLabel(self, language: str = None): def getLabel(self, language: str = None):
"""Returns the label in the specified language, or the default value if not available""" """Returns the label in the specified language, or the default value if not available"""
@ -53,6 +66,7 @@ class User(BaseModel):
disabled: Optional[bool] = Field(False, description="Indicates whether the user is disabled") disabled: Optional[bool] = Field(False, description="Indicates whether the user is disabled")
privilege: str = Field(description="Permission level") #sysadmin,admin,user privilege: str = Field(description="Permission level") #sysadmin,admin,user
authenticationAuthority: str = Field(default="local", description="Authentication authority (local, microsoft)") authenticationAuthority: str = Field(default="local", description="Authentication authority (local, microsoft)")
mandateId: str = Field(description="ID of the mandate this user belongs to")
label: Label = Field( label: Label = Field(
default=Label(default="User", translations={"en": "User", "fr": "Utilisateur"}), default=Label(default="User", translations={"en": "User", "fr": "Utilisateur"}),

View file

@ -14,11 +14,11 @@ class LucydomAccess:
def __init__(self, currentUser: Dict[str, Any], db): def __init__(self, currentUser: Dict[str, Any], db):
"""Initialize with user context.""" """Initialize with user context."""
self.currentUser = currentUser self.currentUser = currentUser
self._mandateId = currentUser.get("_mandateId") self.mandateId = currentUser.get("mandateId")
self._userId = currentUser.get("id") self.userId = currentUser.get("id")
if not self._mandateId or not self._userId: if not self.mandateId or not self.userId:
raise ValueError("Invalid user context: _mandateId and id are required") raise ValueError("Invalid user context: mandateId and userId are required")
self.db = db self.db = db
@ -43,15 +43,15 @@ class LucydomAccess:
filtered_records = recordset # System admins see all records filtered_records = recordset # System admins see all records
elif userPrivilege == "admin": elif userPrivilege == "admin":
# Admins see records in their mandate # Admins see records in their mandate
filtered_records = [r for r in recordset if r.get("_mandateId") == self._mandateId] filtered_records = [r for r in recordset if r.get("mandateId","-") == self.mandateId]
else: # Regular users else: # Regular users
# For prompts, users can see all prompts from their mandate # For prompts, users can see all prompts from their mandate
if table == "prompts": if table == "prompts":
filtered_records = [r for r in recordset if r.get("_mandateId") == self._mandateId] filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId]
else: else:
# Users see only their records for other tables # Users see only their records for other tables
filtered_records = [r for r in recordset filtered_records = [r for r in recordset
if r.get("_mandateId") == self._mandateId and r.get("_userId") == self._userId] if r.get("mandateId","-") == self.mandateId and r.get("_createdBy") == self.userId]
# Add access control attributes to each record # Add access control attributes to each record
for record in filtered_records: for record in filtered_records:
@ -113,13 +113,13 @@ class LucydomAccess:
record = records[0] record = records[0]
# Admins can modify anything in their mandate # Admins can modify anything in their mandate, if mandate is specified for a record
if userPrivilege == "admin" and record.get("_mandateId") == self._mandateId: if userPrivilege == "admin" and record.get("mandateId","-") == self.mandateId:
return True return True
# Regular users can only modify their own records # Regular users can only modify their own records
if (record.get("_mandateId") == self._mandateId and if (record.get("mandateId","-") == self.mandateId and
record.get("_userId") == self._userId): record.get("_createdBy") == self.userId):
return True return True
return False return False

View file

@ -13,6 +13,10 @@ import hashlib
from modules.shared.mimeUtils import isTextMimeType from modules.shared.mimeUtils import isTextMimeType
from modules.interfaces.lucydomAccess import LucydomAccess from modules.interfaces.lucydomAccess import LucydomAccess
from modules.interfaces.lucydomModel import (
ChatWorkflow, ChatMessage, ChatLog, ChatStat,
ChatDocument, UserInputRequest
)
# DYNAMIC PART: Connectors to the Interface # DYNAMIC PART: Connectors to the Interface
from modules.connectors.connectorDbJson import DatabaseConnector from modules.connectors.connectorDbJson import DatabaseConnector
@ -48,40 +52,47 @@ class FileDeletionError(FileError):
class LucydomInterface: class LucydomInterface:
""" """
Interface to the LucyDOM database. Interface to LucyDOM database and AI Connectors.
Uses the JSON connector for data access. Uses the JSON connector for data access with added language support.
""" """
def __init__(self, currentUser: Dict[str, Any]): def __init__(self):
"""Initializes the LucyDOM Interface with user context.""" """Initializes the Lucydom Interface."""
logger.debug(f"Initializing LucydomInterface with currentUser={currentUser}") # Initialize database
self._initializeDatabase()
# Ensure valid user context # Initialize standard records if needed
self._initRecords()
# Initialize variables
self.currentUser = None
self.userId = None
self.access = None # Will be set when user context is provided
self.aiService = None # Will be set when user context is provided
def setUserContext(self, currentUser: Dict[str, Any]):
"""Sets the user context for the interface."""
if not currentUser:
logger.info("Initializing interface without user context")
return
self.currentUser = currentUser self.currentUser = currentUser
self._mandateId = currentUser.get("_mandateId") self.userId = currentUser.get("id")
self._userId = currentUser.get("id")
if not self._mandateId or not self._userId:
raise ValueError("Invalid user context: _mandateId and id are required")
if not self.userId:
raise ValueError("Invalid user context: id is required")
# Add language settings # Add language settings
self.userLanguage = currentUser.get("language", "en") # Default user language self.userLanguage = currentUser.get("language", "en") # Default user language
# Initialize database # Initialize access control with user context
self._initializeDatabase() self.access = LucydomAccess(self.currentUser, self.db)
# Initialize standard records
self._initRecords()
# Initialize AI service # Initialize AI service
self.aiService = ChatService() self.aiService = ChatService()
if not self.aiService:
logger.warning("AI service not available during LucydomInterface initialization")
# Initialize access control logger.debug(f"User context set: userId={self.userId}")
self.access = LucydomAccess(self.currentUser, self.db)
def _initializeDatabase(self): def _initializeDatabase(self):
"""Initializes the database connection.""" """Initializes the database connection."""
try: try:
@ -98,14 +109,9 @@ class LucydomInterface:
dbHost=dbHost, dbHost=dbHost,
dbDatabase=dbDatabase, dbDatabase=dbDatabase,
dbUser=dbUser, dbUser=dbUser,
dbPassword=dbPassword, dbPassword=dbPassword
_mandateId=self._mandateId,
_userId=self._userId
) )
# Set context
self.db.updateContext(self._mandateId, self._userId)
logger.info("Database initialized successfully") logger.info("Database initialized successfully")
except Exception as e: except Exception as e:
logger.error(f"Failed to initialize database: {str(e)}") logger.error(f"Failed to initialize database: {str(e)}")
@ -163,7 +169,7 @@ class LucydomInterface:
# Create prompts # Create prompts
for promptData in standardPrompts: for promptData in standardPrompts:
createdPrompt = self.db.recordCreate("prompts", promptData) createdPrompt = self.db.recordCreate("prompts", promptData)
logger.debug(f"Prompt '{promptData.get('name', 'Standard')}' was created with ID {createdPrompt['id']} and context mandate={createdPrompt.get('_mandateId')}, user={createdPrompt.get('_userId')}") logger.debug(f"Prompt '{promptData.get('name', 'Standard')}' was created with ID {createdPrompt['id']} and context mandate={createdPrompt.get('mandateId')}, user={createdPrompt.get('_createdBy')}")
else: else:
logger.debug("Prompts already exist, skipping creation") logger.debug("Prompts already exist, skipping creation")
@ -298,8 +304,8 @@ class LucydomInterface:
"""Checks if a file with the same hash already exists for the current user and mandate.""" """Checks if a file with the same hash already exists for the current user and mandate."""
files = self.db.getRecordset("files", recordFilter={ files = self.db.getRecordset("files", recordFilter={
"fileHash": fileHash, "fileHash": fileHash,
"_mandateId": self._mandateId, "mandateId": self.currentUser.get("mandateId"),
"_userId": self._userId "_createdBy": self.currentUser.get("id")
}) })
if files: if files:
return files[0] return files[0]
@ -357,8 +363,7 @@ class LucydomInterface:
raise PermissionError("No permission to create files") raise PermissionError("No permission to create files")
fileData = { fileData = {
"_mandateId": self._mandateId, "mandateId": self.currentUser.get("mandateId"),
"_userId": self._userId,
"name": name, "name": name,
"mimeType": mimeType, "mimeType": mimeType,
"size": size, "size": size,
@ -675,35 +680,48 @@ class LucydomInterface:
allWorkflows = self.db.getRecordset("workflows") allWorkflows = self.db.getRecordset("workflows")
return self._uam("workflows", allWorkflows) return self._uam("workflows", allWorkflows)
def getWorkflowsByUser(self, _userId: str) -> List[Dict[str, Any]]: def getWorkflowsByUser(self, userId: str) -> List[Dict[str, Any]]:
"""Returns workflows for a specific user if current user has access.""" """Returns workflows for a specific user if current user has access."""
# Get workflows by _userId # Get workflows by userId
workflows = self.db.getRecordset("workflows", recordFilter={"_userId": _userId}) workflows = self.db.getRecordset("workflows", recordFilter={"_createdBy": userId})
# Apply access control # Apply access control
return self._uam("workflows", workflows) return self._uam("workflows", workflows)
def getWorkflow(self, workflowId: str) -> Optional[Dict[str, Any]]: def getWorkflow(self, workflowId: str) -> Optional[ChatWorkflow]:
"""Returns a workflow by ID if user has access.""" """Returns a workflow by ID if user has access."""
workflows = self.db.getRecordset("workflows", recordFilter={"id": workflowId}) workflows = self.db.getRecordset("workflows", recordFilter={"id": workflowId})
if not workflows: if not workflows:
return None return None
filteredWorkflows = self._uam("workflows", workflows) filteredWorkflows = self._uam("workflows", workflows)
return filteredWorkflows[0] if filteredWorkflows else None if not filteredWorkflows:
return None
workflow = filteredWorkflows[0]
try:
# Validate workflow data against ChatWorkflow model
return ChatWorkflow(
id=workflow["id"],
status=workflow.get("status", "running"),
name=workflow.get("name"),
currentRound=workflow.get("currentRound", 1),
lastActivity=workflow.get("lastActivity", self._getCurrentTimestamp()),
startedAt=workflow.get("startedAt", self._getCurrentTimestamp()),
logs=[ChatLog(**log) for log in workflow.get("logs", [])],
messages=[ChatMessage(**msg) for msg in workflow.get("messages", [])],
stats=ChatStat(**workflow.get("dataStats", {})) if workflow.get("dataStats") else None,
mandateId=workflow.get("mandateId", self.currentUser.get("mandateId"))
)
except Exception as e:
logger.error(f"Error validating workflow data: {str(e)}")
return None
def createWorkflow(self, workflowData: Dict[str, Any]) -> Dict[str, Any]: def createWorkflow(self, workflowData: Dict[str, Any]) -> ChatWorkflow:
"""Creates a new workflow if user has permission.""" """Creates a new workflow if user has permission."""
if not self._canModify("workflows"): if not self._canModify("workflows"):
raise PermissionError("No permission to create workflows") raise PermissionError("No permission to create workflows")
# Make sure mandateId and userId are set
if "_mandateId" not in workflowData:
workflowData["_mandateId"] = self._mandateId
if "_userId" not in workflowData:
workflowData["_userId"] = self._userId
# Set timestamp if not present # Set timestamp if not present
currentTime = self._getCurrentTimestamp() currentTime = self._getCurrentTimestamp()
if "startedAt" not in workflowData: if "startedAt" not in workflowData:
@ -711,10 +729,25 @@ class LucydomInterface:
if "lastActivity" not in workflowData: if "lastActivity" not in workflowData:
workflowData["lastActivity"] = currentTime workflowData["lastActivity"] = currentTime
# Create workflow in database
created = self.db.recordCreate("workflows", workflowData)
return self.db.recordCreate("workflows", workflowData) # Convert to ChatWorkflow model
return ChatWorkflow(
id=created["id"],
status=created.get("status", "running"),
name=created.get("name"),
currentRound=created.get("currentRound", 1),
lastActivity=created.get("lastActivity", currentTime),
startedAt=created.get("startedAt", currentTime),
logs=[],
messages=[],
stats=ChatStat(**created.get("dataStats", {})) if created.get("dataStats") else None,
mandateId=created.get("mandateId", self.currentUser.get("mandateId"))
)
def updateWorkflow(self, workflowId: str, workflowData: Dict[str, Any]) -> Dict[str, Any]: def updateWorkflow(self, workflowId: str, workflowData: Dict[str, Any]) -> ChatWorkflow:
"""Updates a workflow if user has access.""" """Updates a workflow if user has access."""
# Check if the workflow exists and user has access # Check if the workflow exists and user has access
workflow = self.getWorkflow(workflowId) workflow = self.getWorkflow(workflowId)
@ -727,8 +760,22 @@ class LucydomInterface:
# Set update time # Set update time
workflowData["lastActivity"] = self._getCurrentTimestamp() workflowData["lastActivity"] = self._getCurrentTimestamp()
# Update workflow # Update workflow in database
return self.db.recordModify("workflows", workflowId, workflowData) updated = self.db.recordModify("workflows", workflowId, workflowData)
# Convert to ChatWorkflow model
return ChatWorkflow(
id=updated["id"],
status=updated.get("status", workflow.status),
name=updated.get("name", workflow.name),
currentRound=updated.get("currentRound", workflow.currentRound),
lastActivity=updated.get("lastActivity", workflow.lastActivity),
startedAt=updated.get("startedAt", workflow.startedAt),
logs=[ChatLog(**log) for log in updated.get("logs", workflow.logs)],
messages=[ChatMessage(**msg) for msg in updated.get("messages", workflow.messages)],
stats=ChatStat(**updated.get("dataStats", workflow.stats.dict() if workflow.stats else {})) if updated.get("dataStats") or workflow.stats else None,
mandateId=updated.get("mandateId", workflow.mandateId)
)
def deleteWorkflow(self, workflowId: str) -> bool: def deleteWorkflow(self, workflowId: str) -> bool:
"""Deletes a workflow if user has access.""" """Deletes a workflow if user has access."""
@ -756,7 +803,7 @@ class LucydomInterface:
messages = self.db.getRecordset("workflowMessages", recordFilter={"workflowId": workflowId}) messages = self.db.getRecordset("workflowMessages", recordFilter={"workflowId": workflowId})
return messages # No further filtering needed since workflow access is already checked return messages # No further filtering needed since workflow access is already checked
def createWorkflowMessage(self, messageData: Dict[str, Any]) -> Dict[str, Any]: def createWorkflowMessage(self, messageData: Dict[str, Any]) -> ChatMessage:
"""Creates a message for a workflow if user has access.""" """Creates a message for a workflow if user has access."""
try: try:
# Check required fields # Check required fields
@ -811,14 +858,28 @@ class LucydomInterface:
# Update workflow's messageIds if this is a new message # Update workflow's messageIds if this is a new message
if createdMessage: if createdMessage:
# Get current messageIds or initialize empty list # Get current messageIds or initialize empty list
messageIds = workflow.get("messageIds", []) messageIds = workflow.messageIds if hasattr(workflow, 'messageIds') else []
# Add the new message ID if not already in the list # Add the new message ID if not already in the list
if createdMessage["id"] not in messageIds: if createdMessage["id"] not in messageIds:
messageIds.append(createdMessage["id"]) messageIds.append(createdMessage["id"])
self.updateWorkflow(workflowId, {"messageIds": messageIds}) self.updateWorkflow(workflowId, {"messageIds": messageIds})
return createdMessage # Convert to ChatMessage model
return ChatMessage(
id=createdMessage["id"],
workflowId=createdMessage["workflowId"],
parentMessageId=createdMessage.get("parentMessageId"),
agentName=createdMessage.get("agentName"),
documents=[ChatDocument(**doc) for doc in createdMessage.get("documents", [])],
message=createdMessage.get("message"),
role=createdMessage.get("role", "assistant"),
status=createdMessage.get("status", "completed"),
sequenceNr=createdMessage.get("sequenceNo", 0),
startedAt=createdMessage.get("startedAt", self._getCurrentTimestamp()),
finishedAt=createdMessage.get("finishedAt"),
stats=ChatStat(**createdMessage.get("stats", {})) if createdMessage.get("stats") else None
)
except Exception as e: except Exception as e:
logger.error(f"Error creating workflow message: {str(e)}") logger.error(f"Error creating workflow message: {str(e)}")
return None return None
@ -1027,7 +1088,7 @@ class LucydomInterface:
# Get logs for this workflow # Get logs for this workflow
return self.db.getRecordset("workflowLogs", recordFilter={"workflowId": workflowId}) return self.db.getRecordset("workflowLogs", recordFilter={"workflowId": workflowId})
def createWorkflowLog(self, logData: Dict[str, Any]) -> Dict[str, Any]: def createWorkflowLog(self, logData: Dict[str, Any]) -> ChatLog:
"""Creates a log entry for a workflow if user has access.""" """Creates a log entry for a workflow if user has access."""
# Check workflow access # Check workflow access
workflowId = logData.get("workflowId") workflowId = logData.get("workflowId")
@ -1065,7 +1126,18 @@ class LucydomInterface:
elif logData.get("type") == "warning": elif logData.get("type") == "warning":
logData["progress"] = 50 # Default middle progress logData["progress"] = 50 # Default middle progress
return self.db.recordCreate("workflowLogs", logData) # Validate log data against ChatLog model
try:
log_model = ChatLog(**logData)
except Exception as e:
logger.error(f"Invalid log data: {str(e)}")
return None
# Create log in database
createdLog = self.db.recordCreate("workflowLogs", log_model.model_dump())
# Return validated ChatLog instance
return ChatLog(**createdLog)
# Workflow Management # Workflow Management
@ -1089,8 +1161,7 @@ class LucydomInterface:
# Extract only the database-relevant workflow fields # Extract only the database-relevant workflow fields
workflowDbData = { workflowDbData = {
"id": workflowId, "id": workflowId,
"_mandateId": workflow.get("_mandateId", self._mandateId), "mandateId": workflow.get("mandateId", self.currentUser.get("mandateId")),
"_userId": workflow.get("_userId", self._userId),
"name": workflow.get("name", f"Workflow {workflowId}"), "name": workflow.get("name", f"Workflow {workflowId}"),
"status": workflow.get("status", "completed"), "status": workflow.get("status", "completed"),
"startedAt": workflow.get("startedAt", self._getCurrentTimestamp()), "startedAt": workflow.get("startedAt", self._getCurrentTimestamp()),
@ -1220,23 +1291,21 @@ class LucydomInterface:
return None return None
def getInterface(currentUser: Dict[str, Any]) -> 'LucydomInterface': def getInterface(currentUser: Dict[str, Any] = None) -> 'LucydomInterface':
""" """
Returns a LucydomInterface instance for the current user. Returns a LucydomInterface instance.
Handles initialization of database and records. If currentUser is provided, initializes with user context.
Otherwise, returns an instance with only database access.
""" """
# Get user context
mandateId = currentUser.get("_mandateId")
userId = currentUser.get("id")
if not mandateId or not userId:
raise ValueError("Invalid user context: _mandateId and id are required")
# Create context key
contextKey = f"{mandateId}_{userId}"
# Create new instance if not exists # Create new instance if not exists
if contextKey not in _lucydomInterfaces: if "default" not in _lucydomInterfaces:
_lucydomInterfaces[contextKey] = LucydomInterface(currentUser) _lucydomInterfaces["default"] = LucydomInterface()
return _lucydomInterfaces[contextKey] interface = _lucydomInterfaces["default"]
if currentUser:
interface.setUserContext(currentUser)
else:
logger.info("Returning interface without user context")
return interface

View file

@ -18,8 +18,21 @@ def getModelAttributes(modelClass):
class Label(BaseModel): class Label(BaseModel):
"""Label for an attribute or a class with support for multiple languages""" """Label for an attribute or a class with support for multiple languages"""
default: str default: str = Field(..., description="Default label text")
translations: Dict[str, str] = {} translations: Dict[str, str] = Field(default_factory=dict, description="Translations for different languages")
class Config:
title = "Label"
description = "A label with support for multiple languages"
schema_extra = {
"example": {
"default": "Document",
"translations": {
"en": "Document",
"fr": "Document"
}
}
}
def getLabel(self, language: str = None): def getLabel(self, language: str = None):
"""Returns the label in the specified language, or the default value if not available""" """Returns the label in the specified language, or the default value if not available"""
@ -33,6 +46,7 @@ class Prompt(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the prompt") id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the prompt")
content: str = Field(description="Content of the prompt") content: str = Field(description="Content of the prompt")
name: str = Field(description="Display name of the prompt") name: str = Field(description="Display name of the prompt")
mandateId: str = Field(description="ID of the mandate this prompt belongs to")
label: Label = Field( label: Label = Field(
default=Label(default="Prompt", translations={"en": "Prompt", "fr": "Invite"}), default=Label(default="Prompt", translations={"en": "Prompt", "fr": "Invite"}),
@ -43,7 +57,8 @@ class Prompt(BaseModel):
fieldLabels: Dict[str, Label] = { fieldLabels: Dict[str, Label] = {
"id": Label(default="ID", translations={}), "id": Label(default="ID", translations={}),
"content": Label(default="Content", translations={"en": "Content", "fr": "Contenu"}), "content": Label(default="Content", translations={"en": "Content", "fr": "Contenu"}),
"name": Label(default="Name", translations={"en": "Label", "fr": "Nom"}) "name": Label(default="Name", translations={"en": "Label", "fr": "Nom"}),
"mandateId": Label(default="Mandate ID", translations={"en": "Mandate ID", "fr": "ID de mandat"})
} }
@ -55,6 +70,7 @@ class FileItem(BaseModel):
fileSize: int = Field(description="Size of the file in bytes") fileSize: int = Field(description="Size of the file in bytes")
fileHash: str = Field(description="Hash code for deduplication") fileHash: str = Field(description="Hash code for deduplication")
workflowId: Optional[str] = Field(None, description="ID of the associated workflow, if any") workflowId: Optional[str] = Field(None, description="ID of the associated workflow, if any")
mandateId: str = Field(description="ID of the mandate this file belongs to")
label: Label = Field( label: Label = Field(
default=Label(default="Data Object", translations={"en": "Data Object", "fr": "Objet de données"}), default=Label(default="Data Object", translations={"en": "Data Object", "fr": "Objet de données"}),
@ -68,7 +84,8 @@ class FileItem(BaseModel):
"fileName": Label(default="Filename", translations={"en": "fileName", "fr": "Nom de fichier"}), "fileName": Label(default="Filename", translations={"en": "fileName", "fr": "Nom de fichier"}),
"fileSize": Label(default="Size", translations={"en": "Size", "fr": "Taille"}), "fileSize": Label(default="Size", translations={"en": "Size", "fr": "Taille"}),
"fileHash": Label(default="File Hash", translations={"en": "Hash", "fr": "Hash"}), "fileHash": Label(default="File Hash", translations={"en": "Hash", "fr": "Hash"}),
"workflowId": Label(default="Workflow ID", translations={"en": "Workflow ID", "fr": "ID du workflow"}) "workflowId": Label(default="Workflow ID", translations={"en": "Workflow ID", "fr": "ID du workflow"}),
"mandateId": Label(default="Mandate ID", translations={"en": "Mandate ID", "fr": "ID de mandat"})
} }
@ -103,11 +120,27 @@ class ChatDocument(BaseModel):
class ChatStat(BaseModel): class ChatStat(BaseModel):
"""Statistics for performance and data usage""" """Statistics for performance and data usage"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the stats") id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the stats")
workflowId: str = Field(description="ID of the associated workflow")
processingTime: Optional[float] = Field(None, description="Processing time in seconds") processingTime: Optional[float] = Field(None, description="Processing time in seconds")
tokenCount: Optional[int] = Field(None, description="Token count (for AI models)") tokenCount: Optional[int] = Field(None, description="Token count (for AI models)")
bytesSent: Optional[int] = Field(None, description="Bytes sent") bytesSent: Optional[int] = Field(None, description="Bytes sent")
bytesReceived: Optional[int] = Field(None, description="Bytes received") bytesReceived: Optional[int] = Field(None, description="Bytes received")
label: Label = Field(
default=Label(default="Chat Statistics", translations={"en": "Chat Statistics", "fr": "Statistiques de chat"}),
description="Label for the class"
)
# Labels for attributes
fieldLabels: Dict[str, Label] = {
"id": Label(default="ID", translations={}),
"workflowId": Label(default="Workflow ID", translations={"en": "Workflow ID", "fr": "ID du workflow"}),
"processingTime": Label(default="Processing Time", translations={"en": "Processing Time", "fr": "Temps de traitement"}),
"tokenCount": Label(default="Token Count", translations={"en": "Token Count", "fr": "Nombre de tokens"}),
"bytesSent": Label(default="Bytes Sent", translations={"en": "Bytes Sent", "fr": "Octets envoyés"}),
"bytesReceived": Label(default="Bytes Received", translations={"en": "Bytes Received", "fr": "Octets reçus"})
}
class ChatMessage(BaseModel): class ChatMessage(BaseModel):
"""Message object in the chat workflow""" """Message object in the chat workflow"""
@ -149,6 +182,26 @@ class ChatWorkflow(BaseModel):
logs: List[ChatLog] = Field(default=[], description="Log entries") logs: List[ChatLog] = Field(default=[], description="Log entries")
messages: List[ChatMessage] = Field(default=[], description="Message history") messages: List[ChatMessage] = Field(default=[], description="Message history")
stats: Optional[ChatStat] = Field(None, description="Statistics") stats: Optional[ChatStat] = Field(None, description="Statistics")
mandateId: str = Field(description="ID of the mandate this workflow belongs to")
label: Label = Field(
default=Label(default="Chat Workflow", translations={"en": "Chat Workflow", "fr": "Workflow de chat"}),
description="Label for the class"
)
# Labels for attributes
fieldLabels: Dict[str, Label] = {
"id": Label(default="ID", translations={}),
"status": Label(default="Status", translations={"en": "Status", "fr": "Statut"}),
"name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}),
"currentRound": Label(default="Current Round", translations={"en": "Current Round", "fr": "Tour actuel"}),
"lastActivity": Label(default="Last Activity", translations={"en": "Last Activity", "fr": "Dernière activité"}),
"startedAt": Label(default="Started At", translations={"en": "Started At", "fr": "Démarré à"}),
"logs": Label(default="Logs", translations={"en": "Logs", "fr": "Journaux"}),
"messages": Label(default="Messages", translations={"en": "Messages", "fr": "Messages"}),
"stats": Label(default="Statistics", translations={"en": "Statistics", "fr": "Statistiques"}),
"mandateId": Label(default="Mandate ID", translations={"en": "Mandate ID", "fr": "ID de mandat"})
}
# AGENT AND TASK MODELS # AGENT AND TASK MODELS

View file

@ -7,7 +7,7 @@ import json
import requests import requests
import base64 import base64
import msal import msal
from typing import Dict, Any, Optional, List from typing import Dict, Any, Optional, List, Tuple
from datetime import datetime, timedelta from datetime import datetime, timedelta
import secrets import secrets
import os import os
@ -111,7 +111,7 @@ class MsftInterface:
""" """
return self.access.canModify(table, recordId) return self.access.canModify(table, recordId)
def getMsftToken(self) -> Optional[Dict[str, Any]]: def getMsftToken(self) -> Optional[MsftToken]:
"""Get Microsoft token for current user""" """Get Microsoft token for current user"""
try: try:
tokens = self.db.getRecordset("msftTokens", recordFilter={ tokens = self.db.getRecordset("msftTokens", recordFilter={
@ -126,7 +126,7 @@ class MsftInterface:
if not filtered_tokens: if not filtered_tokens:
return None return None
return filtered_tokens[0] return MsftToken(**filtered_tokens[0])
except Exception as e: except Exception as e:
logger.error(f"Error getting Microsoft token: {str(e)}") logger.error(f"Error getting Microsoft token: {str(e)}")
return None return None
@ -142,15 +142,21 @@ class MsftInterface:
token_data["_mandateId"] = self._mandateId token_data["_mandateId"] = self._mandateId
token_data["_userId"] = self._userId token_data["_userId"] = self._userId
# Validate token data using Pydantic model
try:
token = MsftToken(**token_data)
except Exception as e:
raise ValueError(f"Invalid token data: {str(e)}")
# Check if token already exists # Check if token already exists
existing_token = self.getMsftToken() existing_token = self.getMsftToken()
if existing_token: if existing_token:
# Update existing token # Update existing token
return self.db.recordUpdate("msftTokens", existing_token["id"], token_data) return self.db.recordModify("msftTokens", existing_token.id, token.model_dump())
else: else:
# Create new token record # Create new token record
return self.db.recordCreate("msftTokens", token_data) return self.db.recordCreate("msftTokens", token.model_dump())
except Exception as e: except Exception as e:
logger.error(f"Error saving Microsoft token: {str(e)}") logger.error(f"Error saving Microsoft token: {str(e)}")
@ -164,13 +170,13 @@ class MsftInterface:
existing_token = self.getMsftToken() existing_token = self.getMsftToken()
if existing_token: if existing_token:
return self.db.recordDelete("msftTokens", existing_token["id"]) return self.db.recordDelete("msftTokens", existing_token.id)
return True return True
except Exception as e: except Exception as e:
logger.error(f"Error deleting Microsoft token: {str(e)}") logger.error(f"Error deleting Microsoft token: {str(e)}")
return False return False
def getCurrentUserToken(self) -> tuple: def getCurrentUserToken(self) -> Tuple[Optional[MsftUserInfo], Optional[str]]:
"""Get current user's Microsoft token and info""" """Get current user's Microsoft token and info"""
try: try:
token_data = self.getMsftToken() token_data = self.getMsftToken()
@ -178,19 +184,19 @@ class MsftInterface:
return None, None return None, None
# Verify token is still valid # Verify token is still valid
if not self.verifyToken(token_data.get("access_token")): if not self.verifyToken(token_data.access_token):
if not self.refreshToken(token_data): if not self.refreshToken(token_data):
return None, None return None, None
token_data = self.getMsftToken() token_data = self.getMsftToken()
user_info = token_data.get("user_info") user_info = token_data.user_info
if not user_info: if not user_info:
user_info = self.getUserInfoFromToken(token_data.get("access_token")) user_info = self.getUserInfoFromToken(token_data.access_token)
if user_info: if user_info:
token_data["user_info"] = user_info token_data.user_info = user_info
self.saveMsftToken(token_data) self.saveMsftToken(token_data.model_dump())
return user_info, token_data.get("access_token") return MsftUserInfo(**user_info) if user_info else None, token_data.access_token
except Exception as e: except Exception as e:
logger.error(f"Error getting current user token: {str(e)}") logger.error(f"Error getting current user token: {str(e)}")
@ -209,14 +215,14 @@ class MsftInterface:
logger.error(f"Error verifying token: {str(e)}") logger.error(f"Error verifying token: {str(e)}")
return False return False
def refreshToken(self, token_data: Dict[str, Any]) -> bool: def refreshToken(self, token_data: MsftToken) -> bool:
"""Refresh the access token using the stored refresh token""" """Refresh the access token using the stored refresh token"""
try: try:
if not token_data or not token_data.get("refresh_token"): if not token_data or not token_data.refresh_token:
return False return False
result = self.msal_app.acquire_token_by_refresh_token( result = self.msal_app.acquire_token_by_refresh_token(
token_data["refresh_token"], token_data.refresh_token,
scopes=self.scopes scopes=self.scopes
) )
@ -224,11 +230,12 @@ class MsftInterface:
logger.error(f"Error refreshing token: {result.get('error')}") logger.error(f"Error refreshing token: {result.get('error')}")
return False return False
token_data["access_token"] = result["access_token"] # Update token data
token_data.access_token = result["access_token"]
if "refresh_token" in result: if "refresh_token" in result:
token_data["refresh_token"] = result["refresh_token"] token_data.refresh_token = result["refresh_token"]
return self.saveMsftToken(token_data) return self.saveMsftToken(token_data.model_dump())
except Exception as e: except Exception as e:
logger.error(f"Error refreshing token: {str(e)}") logger.error(f"Error refreshing token: {str(e)}")
@ -364,8 +371,19 @@ class MsftInterface:
if not user_info: if not user_info:
return None return None
token_response["user_info"] = user_info # Create MsftToken instance
return token_response token_data = MsftToken(
access_token=token_response["access_token"],
refresh_token=token_response.get("refresh_token", ""),
expires_in=token_response.get("expires_in", 0),
token_type=token_response.get("token_type", "bearer"),
expires_at=datetime.now().timestamp() + token_response.get("expires_in", 0),
user_info=user_info,
_mandateId=self._mandateId,
_userId=self._userId
)
return token_data.model_dump()
except Exception as e: except Exception as e:
logger.error(f"Error handling auth callback: {str(e)}") logger.error(f"Error handling auth callback: {str(e)}")

View file

@ -70,4 +70,21 @@ class MsftUserInfo(BaseModel):
"name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}), "name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}),
"email": Label(default="Email", translations={"en": "Email", "fr": "E-mail"}), "email": Label(default="Email", translations={"en": "Email", "fr": "E-mail"}),
"id": Label(default="ID", translations={}) "id": Label(default="ID", translations={})
} }
# Response models for Microsoft routes
class MsftAuthStatus(BaseModel):
"""Response model for Microsoft authentication status"""
authenticated: bool
message: Optional[str] = None
user: Optional[MsftUserInfo] = None
class MsftTokenResponse(BaseModel):
"""Response model for Microsoft token"""
token: MsftToken
class MsftSaveTokenResponse(BaseModel):
"""Response model for saving Microsoft token"""
success: bool
message: str
token: Optional[MsftToken] = None

View file

@ -5,12 +5,40 @@ import inspect
import importlib import importlib
import os import os
from pydantic import BaseModel from pydantic import BaseModel
import logging
from modules.security.auth import getCurrentActiveUser # Import auth module
import modules.security.auth as auth
# Import the attribute definition and helper functions # Import the attribute definition and helper functions
from modules.shared.defAttributes import AttributeDefinition, getModelAttributes from modules.shared.defAttributes import AttributeDefinition, getModelAttributes
# Configure logger
logger = logging.getLogger(__name__)
# Create a response model for better documentation
class AttributeResponse(BaseModel):
"""Response model for entity attributes"""
attributes: List[AttributeDefinition]
class Config:
schema_extra = {
"example": {
"attributes": [
{
"name": "username",
"label": "Username",
"type": "string",
"required": True,
"placeholder": "Please enter username",
"editable": True,
"visible": True,
"order": 0
}
]
}
}
def getModelClasses() -> Dict[str, Any]: def getModelClasses() -> Dict[str, Any]:
"""Dynamically get all model classes from all model modules""" """Dynamically get all model classes from all model modules"""
modelClasses = {} modelClasses = {}
@ -42,14 +70,20 @@ router = APIRouter(
responses={404: {"description": "Not found"}} responses={404: {"description": "Not found"}}
) )
@router.get("/{entityType}", response_model=List[AttributeDefinition]) @router.get("/{entityType}", response_model=AttributeResponse)
async def get_entity_attributes( async def get_entity_attributes(
entityType: str = Path(..., description="Type of entity (e.g. prompt)"), entityType: str = Path(..., description="Type of entity (e.g. prompt)"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
""" """
Retrieves the attribute definitions for a specific entity. Retrieves the attribute definitions for a specific entity.
This can be used for dynamic form generation. This can be used for dynamic form generation.
Parameters:
- entityType: The type of entity to get attributes for (e.g., 'user', 'prompt')
Returns:
- A list of attribute definitions that can be used to generate forms
""" """
# Determine preferred language of the user # Determine preferred language of the user
userLanguage = currentUser.get("language", "en") userLanguage = currentUser.get("language", "en")
@ -69,7 +103,7 @@ async def get_entity_attributes(
attributes = getModelAttributes(modelClass, userLanguage) attributes = getModelAttributes(modelClass, userLanguage)
# Return only visible attributes # Return only visible attributes
return [attr for attr in attributes if attr.visible] return AttributeResponse(attributes=[attr for attr in attributes if attr.visible])
@router.options("/{entityType}") @router.options("/{entityType}")
async def options_entity_attributes( async def options_entity_attributes(

View file

@ -6,18 +6,18 @@ from datetime import datetime, timezone
from dataclasses import dataclass from dataclasses import dataclass
import io import io
from modules.security.auth import getCurrentActiveUser # Import auth module
from modules.shared.configuration import APP_CONFIG import modules.security.auth as auth
# Import interfaces # Import interfaces
from modules.interfaces.lucydomInterface import getInterface, FileError, FileNotFoundError, FileStorageError, FilePermissionError, FileDeletionError import modules.interfaces.lucydomInterface as lucydomInterface
from modules.interfaces.lucydomModel import FileItem, getModelAttributes from modules.interfaces.lucydomModel import FileItem
# Configure logger # Configure logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Model attributes for FileItem # Model attributes for FileItem
fileAttributes = getModelAttributes(FileItem) fileAttributes = lucydomInterface.getModelAttributes(FileItem)
# Create router for file endpoints # Create router for file endpoints
router = APIRouter( router = APIRouter(
@ -32,15 +32,15 @@ router = APIRouter(
} }
) )
@router.get("", response_model=List[Dict[str, Any]]) @router.get("", response_model=List[FileItem])
async def get_files(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): async def get_files(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
"""Get all available files""" """Get all available files"""
try: try:
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get all files generically - only metadata, no binary data # Get all files generically - only metadata, no binary data
files = myInterface.getAllFiles() files = interfaceLucydom.getAllFiles()
return files return [FileItem(**file) for file in files]
except Exception as e: except Exception as e:
logger.error(f"Error retrieving files: {str(e)}") logger.error(f"Error retrieving files: {str(e)}")
raise HTTPException( raise HTTPException(
@ -52,36 +52,36 @@ async def get_files(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser))
async def upload_file( async def upload_file(
file: UploadFile = File(...), file: UploadFile = File(...),
workflowId: Optional[str] = Form(None), workflowId: Optional[str] = Form(None),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Upload a file""" """Upload a file"""
try: try:
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Read file # Read file
fileContent = await file.read() fileContent = await file.read()
# Check size limits # Check size limits
maxSize = int(APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # in bytes maxSize = int(lucydomInterface.APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # in bytes
if len(fileContent) > maxSize: if len(fileContent) > maxSize:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail=f"File too large. Maximum size: {APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB" detail=f"File too large. Maximum size: {lucydomInterface.APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
) )
# Save file via LucyDOM interface in the database # Save file via LucyDOM interface in the database
fileMeta = myInterface.saveUploadedFile(fileContent, file.filename) fileMeta = interfaceLucydom.saveUploadedFile(fileContent, file.filename)
# If workflowId is provided, update the file information # If workflowId is provided, update the file information
if workflowId: if workflowId:
updateData = {"workflowId": workflowId} updateData = {"workflowId": workflowId}
myInterface.updateFile(fileMeta["id"], updateData) interfaceLucydom.updateFile(fileMeta["id"], updateData)
fileMeta["workflowId"] = workflowId fileMeta["workflowId"] = workflowId
# Successful response # Successful response
return fileMeta return fileMeta
except FileStorageError as e: except lucydomInterface.FileStorageError as e:
logger.error(f"Error during file upload (storage): {str(e)}") logger.error(f"Error during file upload (storage): {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@ -97,14 +97,14 @@ async def upload_file(
@router.get("/{fileId}") @router.get("/{fileId}")
async def get_file( async def get_file(
fileId: str, fileId: str,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Returns a file by its ID for download""" """Returns a file by its ID for download"""
try: try:
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get file via LucyDOM interface from the database # Get file via LucyDOM interface from the database
fileData = myInterface.downloadFile(fileId) fileData = interfaceLucydom.downloadFile(fileId)
# Return file # Return file
headers = { headers = {
@ -116,19 +116,19 @@ async def get_file(
headers=headers headers=headers
) )
except FileNotFoundError as e: except lucydomInterface.FileNotFoundError as e:
logger.warning(f"File not found: {str(e)}") logger.warning(f"File not found: {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=str(e) detail=str(e)
) )
except FilePermissionError as e: except lucydomInterface.FilePermissionError as e:
logger.warning(f"No permission for file: {str(e)}") logger.warning(f"No permission for file: {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail=str(e) detail=str(e)
) )
except FileError as e: except lucydomInterface.FileError as e:
logger.error(f"Error retrieving file: {str(e)}") logger.error(f"Error retrieving file: {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@ -141,20 +141,20 @@ async def get_file(
detail=f"Error retrieving file: {str(e)}" detail=f"Error retrieving file: {str(e)}"
) )
@router.put("/{file_id}") @router.put("/{file_id}", response_model=FileItem)
async def update_file( async def update_file(
file_id: str, file_id: str,
file_data: FileItem, file_data: FileItem,
current_user: Dict[str, Any] = Depends(getCurrentActiveUser) current_user: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
""" """
Update file metadata Update file metadata
""" """
try: try:
myInterface = getInterface(current_user) interfaceLucydom = lucydomInterface.getInterface(current_user)
# Get the file from the database # Get the file from the database
file = myInterface.getFile(file_id) file = interfaceLucydom.getFile(file_id)
if not file: if not file:
raise HTTPException(status_code=404, detail="File not found") raise HTTPException(status_code=404, detail="File not found")
@ -162,18 +162,17 @@ async def update_file(
if file.get("userId", 0) != current_user.get("id", 0): if file.get("userId", 0) != current_user.get("id", 0):
raise HTTPException(status_code=403, detail="Not authorized to update this file") raise HTTPException(status_code=403, detail="Not authorized to update this file")
# Update file metadata # Convert FileItem to dict for interface
update_data = file_data.dict(exclude_unset=True) update_data = file_data.model_dump()
update_data["modified_at"] = datetime.now(timezone.utc)
# Update in database # Update the file
result = myInterface.updateFile(file_id, update_data) result = interfaceLucydom.updateFile(file_id, update_data)
if not result: if not result:
raise HTTPException(status_code=500, detail="Failed to update file") raise HTTPException(status_code=500, detail="Failed to update file")
# Get updated file # Get updated file and convert to FileItem
updated_file = myInterface.getFile(file_id) updated_file = interfaceLucydom.getFile(file_id)
return updated_file return FileItem(**updated_file)
except HTTPException as he: except HTTPException as he:
raise he raise he
@ -184,31 +183,31 @@ async def update_file(
@router.delete("/{fileId}", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/{fileId}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_file( async def delete_file(
fileId: str, fileId: str,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Deletes a file by its ID from the database""" """Deletes a file by its ID from the database"""
try: try:
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Delete file via LucyDOM interface # Delete file via LucyDOM interface
myInterface.deleteFile(fileId) interfaceLucydom.deleteFile(fileId)
# Return successful deletion without content (204 No Content) # Return successful deletion without content (204 No Content)
return Response(status_code=status.HTTP_204_NO_CONTENT) return Response(status_code=status.HTTP_204_NO_CONTENT)
except FileNotFoundError as e: except lucydomInterface.FileNotFoundError as e:
logger.warning(f"File not found: {str(e)}") logger.warning(f"File not found: {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=str(e) detail=str(e)
) )
except FilePermissionError as e: except lucydomInterface.FilePermissionError as e:
logger.warning(f"No permission to delete file: {str(e)}") logger.warning(f"No permission to delete file: {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail=str(e) detail=str(e)
) )
except FileDeletionError as e: except lucydomInterface.FileDeletionError as e:
logger.error(f"Error deleting file: {str(e)}") logger.error(f"Error deleting file: {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@ -223,14 +222,14 @@ async def delete_file(
@router.get("/stats", response_model=Dict[str, Any]) @router.get("/stats", response_model=Dict[str, Any])
async def get_file_stats( async def get_file_stats(
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Returns statistics about the stored files""" """Returns statistics about the stored files"""
try: try:
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get all files - metadata only # Get all files - metadata only
allFiles = myInterface.getAllFiles() allFiles = interfaceLucydom.getAllFiles()
# Calculate statistics # Calculate statistics
totalFiles = len(allFiles) totalFiles = len(allFiles)

View file

@ -7,21 +7,21 @@ from datetime import timedelta
import pathlib import pathlib
import os import os
import logging import logging
from pathlib import Path as FilePath
from modules.shared.configuration import APP_CONFIG from modules.shared.configuration import APP_CONFIG
from modules.security.auth import ( import modules.security.auth as auth
createAccessToken,
getCurrentActiveUser,
getRootInterface,
ACCESS_TOKEN_EXPIRE_MINUTES
)
import modules.interfaces.gatewayModel as gatewayModel import modules.interfaces.gatewayModel as gatewayModel
from modules.interfaces.gatewayInterface import getInterface import modules.interfaces.gatewayInterface as gatewayInterface
router = APIRouter() router = APIRouter(
prefix="",
tags=["General"],
responses={404: {"description": "Not found"}}
)
# Static folder setup - using absolute path from app root # Static folder setup - using absolute path from app root
baseDir = pathlib.Path(__file__).parent.parent.parent # Go up to gateway root baseDir = FilePath(__file__).parent.parent.parent # Go up to gateway root
staticFolder = baseDir / "static" staticFolder = baseDir / "static"
os.makedirs(staticFolder, exist_ok=True) os.makedirs(staticFolder, exist_ok=True)
@ -30,22 +30,14 @@ router.mount("/static", StaticFiles(directory=str(staticFolder), html=True), nam
logger = logging.getLogger(__name__) 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"]) @router.get("/", tags=["General"])
async def root(): async def root():
"""API status endpoint""" """API status endpoint"""
return {"status": "online", "message": "Data Platform API is active"} return {
"status": "online",
@router.get("/api/test", tags=["General"]) "message": "Data Platform API is active",
async def get_test(): "allowedOrigins": f"Allowed origins are {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
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"]) @router.get("/api/environment", tags=["General"])
async def get_environment(): async def get_environment():
@ -57,29 +49,22 @@ async def get_environment():
# Add other environment variables the frontend might need # Add other environment variables the frontend might need
} }
@router.options("/{fullPath:path}", tags=["General"])
async def options_route(fullPath: str):
return Response(status_code=200)
@router.post("/api/token", response_model=gatewayModel.Token, tags=["General"]) @router.post("/api/token", response_model=gatewayModel.Token, tags=["General"])
async def login_for_access_token(formData: OAuth2PasswordRequestForm = Depends()): async def login_for_access_token(formData: OAuth2PasswordRequestForm = Depends()):
# Create a new gateway interface instance with admin context # Create a new gateway interface instance with admin context
myInterface = getRootInterface() interfaceRoot = auth.getRootInterface()
try: try:
# Authenticate user # Authenticate user
user = myInterface.authenticateUser(formData.username, formData.password) user = interfaceRoot.authenticateUser(formData.username, formData.password)
# Create token with mandate ID and user ID # Authenticate user and get token
accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) token = interfaceRoot.authenticateAndGetToken(formData.username, formData.password)
accessToken = createAccessToken( return token
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: except ValueError as e:
# Handle authentication errors # Handle authentication errors
error_msg = str(e) error_msg = str(e)
@ -99,67 +84,25 @@ async def login_for_access_token(formData: OAuth2PasswordRequestForm = Depends()
) )
@router.get("/api/user/me", response_model=Dict[str, Any], tags=["General"]) @router.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
async def read_user_me(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): async def read_user_me(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
return currentUser return currentUser
@router.post("/api/users/register", response_model=Dict[str, Any], tags=["General"]) @router.post("/api/user/register", response_model=gatewayModel.User, tags=["General"])
async def register_user(userData: Dict[str, Any]): async def register_user(userData: gatewayModel.User):
"""Register a new user.""" """Register a new user."""
try: try:
logger.debug("Received registration request") interfaceRoot = auth.getRootInterface()
return interfaceRoot.registerUser(userData.model_dump())
# Create a new gateway interface instance with admin context except ValueError as e:
myInterface = getRootInterface() raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
# Check required fields detail=str(e)
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: except Exception as e:
logger.error(f"Error in user registration: {str(e)}") logger.error(f"Error registering user: {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Registration failed: {str(e)}" detail=f"Failed to register user: {str(e)}"
) )
@router.get("/api/user/available", response_model=Dict[str, Any], tags=["General"]) @router.get("/api/user/available", response_model=Dict[str, Any], tags=["General"])
@ -169,30 +112,15 @@ async def check_username_availability(
): ):
"""Check if a username is available for registration""" """Check if a username is available for registration"""
try: try:
# Create a new gateway interface instance with root context interfaceRoot = auth.getRootInterface()
myInterface = getRootInterface() return interfaceRoot.checkUsernameAvailability(username, authenticationAuthority)
# 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: except Exception as e:
logger.error(f"Error checking username availability: {str(e)}") logger.error(f"Error checking username availability: {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to check username availability: {str(e)}" detail=f"Failed to check username availability: {str(e)}"
) )
@router.get("/favicon.ico", tags=["General"])
async def favicon():
return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")

View file

@ -1,9 +1,13 @@
from fastapi import APIRouter, HTTPException, Depends, Body, status from fastapi import APIRouter, HTTPException, Depends, Body, Path, Request
from typing import Dict, Any, List from typing import List, Dict, Any, Optional
from fastapi import status
import logging import logging
from modules.security.auth import getCurrentActiveUser # Import auth module
from modules.interfaces.gatewayInterface import getInterface import modules.security.auth as auth
# Import interfaces
import modules.interfaces.gatewayInterface as gatewayInterface
from modules.interfaces.gatewayModel import Mandate, getModelAttributes from modules.interfaces.gatewayModel import Mandate, getModelAttributes
# Configure logger # Configure logger
@ -19,11 +23,11 @@ router = APIRouter(
) )
@router.get("/", response_model=List[Dict[str, Any]], tags=["Mandates"]) @router.get("/", response_model=List[Dict[str, Any]], tags=["Mandates"])
async def get_mandates(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): async def get_mandates(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
"""Get all mandates""" """Get all mandates"""
try: try:
myInterface = getInterface(currentUser) interfaceGateway = gatewayInterface.getInterface(currentUser)
return myInterface.getMandates() return interfaceGateway.getMandates()
except Exception as e: except Exception as e:
logger.error(f"Error getting mandates: {str(e)}") logger.error(f"Error getting mandates: {str(e)}")
raise HTTPException( raise HTTPException(
@ -34,12 +38,12 @@ async def get_mandates(currentUser: Dict[str, Any] = Depends(getCurrentActiveUse
@router.get("/{mandateId}", response_model=Dict[str, Any], tags=["Mandates"]) @router.get("/{mandateId}", response_model=Dict[str, Any], tags=["Mandates"])
async def get_mandate( async def get_mandate(
mandateId: str, mandateId: str,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Get a specific mandate by ID""" """Get a specific mandate by ID"""
try: try:
myInterface = getInterface(currentUser) interfaceGateway = gatewayInterface.getInterface(currentUser)
mandate = myInterface.getMandateById(mandateId) mandate = interfaceGateway.getMandateById(mandateId)
if not mandate: if not mandate:
raise HTTPException( raise HTTPException(
@ -57,30 +61,17 @@ async def get_mandate(
detail=f"Failed to get mandate: {str(e)}" detail=f"Failed to get mandate: {str(e)}"
) )
@router.post("/", response_model=Dict[str, Any], tags=["Mandates"]) @router.post("/", response_model=Mandate, tags=["Mandates"])
async def create_mandate( async def create_mandate(
mandateData: Dict[str, Any], mandateData: Mandate,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Create a new mandate""" """Create a new mandate"""
try: try:
myInterface = getInterface(currentUser) interfaceGateway = gatewayInterface.getInterface(currentUser)
# Check required fields
if not mandateData.get("name"):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Mandate name is required"
)
# Filter attributes based on model definition
filteredData = {}
for attr in mandateAttributes:
if attr in mandateData:
filteredData[attr] = mandateData[attr]
try: try:
createdMandate = myInterface.createMandate(**filteredData) createdMandate = interfaceGateway.createMandate(mandateData)
except ValueError as e: except ValueError as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
@ -103,33 +94,27 @@ async def create_mandate(
detail=f"Failed to create mandate: {str(e)}" detail=f"Failed to create mandate: {str(e)}"
) )
@router.put("/{mandateId}", response_model=Dict[str, Any], tags=["Mandates"]) @router.put("/{mandateId}", response_model=Mandate, tags=["Mandates"])
async def update_mandate( async def update_mandate(
mandateId: str, mandateId: str,
mandateData: Dict[str, Any], mandateData: Mandate,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Update an existing mandate""" """Update an existing mandate"""
try: try:
myInterface = getInterface(currentUser) interfaceGateway = gatewayInterface.getInterface(currentUser)
# Check if mandate exists # Check if mandate exists
existingMandate = myInterface.getMandateById(mandateId) existingMandate = interfaceGateway.getMandateById(mandateId)
if not existingMandate: if not existingMandate:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandate {mandateId} not found" detail=f"Mandate {mandateId} not found"
) )
# Filter attributes based on model definition
filteredData = {}
for attr in mandateAttributes:
if attr in mandateData:
filteredData[attr] = mandateData[attr]
# Update mandate data # Update mandate data
try: try:
updatedMandate = myInterface.updateMandate(mandateId, **filteredData) updatedMandate = interfaceGateway.updateMandate(mandateId, mandateData)
except ValueError as e: except ValueError as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
@ -155,14 +140,14 @@ async def update_mandate(
@router.delete("/{mandateId}", response_model=Dict[str, Any], tags=["Mandates"]) @router.delete("/{mandateId}", response_model=Dict[str, Any], tags=["Mandates"])
async def delete_mandate( async def delete_mandate(
mandateId: str, mandateId: str,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Delete a mandate""" """Delete a mandate"""
try: try:
myInterface = getInterface(currentUser) interfaceGateway = gatewayInterface.getInterface(currentUser)
# Check if mandate exists # Check if mandate exists
existingMandate = myInterface.getMandateById(mandateId) existingMandate = interfaceGateway.getMandateById(mandateId)
if not existingMandate: if not existingMandate:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -171,7 +156,7 @@ async def delete_mandate(
# Delete mandate # Delete mandate
try: try:
myInterface.deleteMandate(mandateId) interfaceGateway.deleteMandate(mandateId)
except ValueError as e: except ValueError as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,

View file

@ -5,8 +5,18 @@ import json
from typing import Dict, Any, Optional, List from typing import Dict, Any, Optional, List
from datetime import datetime, timedelta from datetime import datetime, timedelta
from modules.security.auth import getCurrentActiveUser, createAccessToken, ACCESS_TOKEN_EXPIRE_MINUTES, getRootInterface # Import auth module
from modules.interfaces.msftInterface import getInterface as getMsftInterface import modules.security.auth as auth
# Import interfaces
import modules.interfaces.msftInterface as msftInterface
from modules.interfaces.msftModel import (
MsftToken,
MsftUserInfo,
MsftAuthStatus,
MsftTokenResponse,
MsftSaveTokenResponse
)
# Configure logger # Configure logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -29,7 +39,7 @@ async def login():
"""Initiate Microsoft login for the current user""" """Initiate Microsoft login for the current user"""
try: try:
# Get Microsoft interface # Get Microsoft interface
msft = getMsftInterface({"_mandateId": "root", "id": "root"}) msft = msftInterface.getInterface({"_mandateId": "root", "id": "root"})
# Get login URL # Get login URL
auth_url = msft.initiateLogin() auth_url = msft.initiateLogin()
@ -54,7 +64,7 @@ async def auth_callback(code: str, state: str, request: Request):
"""Handle Microsoft OAuth callback""" """Handle Microsoft OAuth callback"""
try: try:
# Get Microsoft interface # Get Microsoft interface
msft = getMsftInterface({"_mandateId": "root", "id": "root"}) msft = msftInterface.getInterface({"_mandateId": "root", "id": "root"})
# Handle auth callback # Handle auth callback
token_response = msft.handleAuthCallback(code) token_response = msft.handleAuthCallback(code)
@ -82,7 +92,7 @@ async def auth_callback(code: str, state: str, request: Request):
) )
# Get gateway interface for user operations # Get gateway interface for user operations
gateway = getRootInterface() gateway = auth.getRootInterface()
# Check if user exists # Check if user exists
user = gateway.getUserByUsername(token_response["user_info"]["email"]) user = gateway.getUserByUsername(token_response["user_info"]["email"])
@ -135,8 +145,8 @@ async def auth_callback(code: str, state: str, request: Request):
) )
# Create backend token # Create backend token
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token_expires = timedelta(minutes=auth.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = createAccessToken( access_token = auth.createAccessToken(
data={ data={
"sub": user["username"], "sub": user["username"],
"_mandateId": str(user["_mandateId"]), "_mandateId": str(user["_mandateId"]),
@ -191,68 +201,97 @@ async def auth_callback(code: str, state: str, request: Request):
detail=f"Authentication failed: {str(e)}" detail=f"Authentication failed: {str(e)}"
) )
@router.get("/status") @router.get("/status", response_model=MsftAuthStatus)
async def auth_status(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): async def auth_status(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
"""Check Microsoft authentication status""" """Check Microsoft authentication status"""
try: try:
# Get Microsoft interface # Get Microsoft interface
msft = getMsftInterface(currentUser) msft = msftInterface.getInterface(currentUser)
# Get current user token and info # Get current user token and info
user_info, access_token = msft.getCurrentUserToken() user_info, access_token = msft.getCurrentUserToken()
if not user_info or not access_token: if not user_info or not access_token:
return JSONResponse({ return MsftAuthStatus(
"authenticated": False, authenticated=False,
"message": "Not authenticated with Microsoft" message="Not authenticated with Microsoft"
}) )
return JSONResponse({ # Convert user_info to MsftUserInfo model
"authenticated": True, user_info_model = MsftUserInfo(**user_info)
"user": user_info
}) return MsftAuthStatus(
authenticated=True,
user=user_info_model
)
except Exception as e: except Exception as e:
logger.error(f"Error checking authentication status: {str(e)}") logger.error(f"Error checking authentication status: {str(e)}")
return JSONResponse({ return MsftAuthStatus(
"authenticated": False, authenticated=False,
"message": f"Error checking authentication status: {str(e)}" message=f"Error checking authentication status: {str(e)}"
}) )
@router.post("/save-token") @router.get("/token", response_model=MsftTokenResponse)
async def save_token(token_data: Dict[str, Any], currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): async def get_token(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
"""Get Microsoft token for current user."""
try:
# Get Microsoft interface
msft = msftInterface.getInterface(currentUser)
# Get token
token_data = msft.getMsftToken()
if token_data:
return MsftTokenResponse(token=token_data)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="No token found"
)
except Exception as e:
logger.error(f"Error getting token: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
@router.post("/save-token", response_model=MsftSaveTokenResponse)
async def save_token(
token_data: MsftToken,
currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Save Microsoft token data from frontend""" """Save Microsoft token data from frontend"""
try: try:
# Get Microsoft interface # Get Microsoft interface
msft = getMsftInterface(currentUser) msft = msftInterface.getInterface(currentUser)
# Save token # Save token
success = msft.saveMsftToken(token_data) success = msft.saveMsftToken(token_data.model_dump())
if success: if success:
return JSONResponse({ return MsftSaveTokenResponse(
"success": True, success=True,
"message": "Token saved successfully" message="Token saved successfully",
}) token=token_data
)
else: else:
return JSONResponse({ raise HTTPException(
"success": False, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": "Failed to save token" detail="Failed to save token"
}) )
except Exception as e: except Exception as e:
logger.error(f"Error saving token: {str(e)}") logger.error(f"Error saving token: {str(e)}")
return JSONResponse({ raise HTTPException(
"success": False, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": f"Error saving token: {str(e)}" detail=f"Error saving token: {str(e)}"
}) )
@router.post("/logout") @router.post("/logout")
async def logout(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): async def logout(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
"""Logout from Microsoft""" """Logout from Microsoft"""
try: try:
# Get Microsoft interface # Get Microsoft interface
msft = getMsftInterface(currentUser) msft = msftInterface.getInterface(currentUser)
# Delete token # Delete token
success = msft.db.deleteToken(currentUser["id"]) success = msft.db.deleteToken(currentUser["id"])
@ -272,19 +311,3 @@ async def logout(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Logout failed: {str(e)}" detail=f"Logout failed: {str(e)}"
) )
@router.get("/token")
async def get_token(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
"""Get Microsoft token for current user."""
try:
# Get Microsoft interface
msft = getMsftInterface(currentUser)
# Get token
token = msft.getMsftToken()
if token:
return {"token": token}
return {"error": "No token found"}
except Exception as e:
logger.error(f"Error getting token: {str(e)}")
return {"error": str(e)}

View file

@ -1,15 +1,19 @@
from fastapi import APIRouter, HTTPException, Depends, Body, Query, Path from fastapi import APIRouter, HTTPException, Depends, Body, Query, Path, Request
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from fastapi import status from fastapi import status
from datetime import datetime from datetime import datetime
import logging
# Import auth module # Import auth module
from modules.security.auth import getCurrentActiveUser import modules.security.auth as auth
# Import interface # Import interfaces
from modules.interfaces.lucydomInterface import getInterface import modules.interfaces.lucydomInterface as lucydomInterface
from modules.interfaces.lucydomModel import Prompt, getModelAttributes from modules.interfaces.lucydomModel import Prompt, getModelAttributes
# Configure logger
logger = logging.getLogger(__name__)
# Model attributes for Prompt # Model attributes for Prompt
promptAttributes = getModelAttributes(Prompt) promptAttributes = getModelAttributes(Prompt)
@ -20,83 +24,75 @@ router = APIRouter(
responses={404: {"description": "Not found"}} responses={404: {"description": "Not found"}}
) )
@router.get("", response_model=List[Dict[str, Any]]) @router.get("", response_model=List[Prompt])
async def get_prompts( async def get_prompts(
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Get all prompts""" """Get all prompts"""
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
return myInterface.getAllPrompts() prompts = interfaceLucydom.getAllPrompts()
return [Prompt(**prompt) for prompt in prompts]
@router.post("", response_model=Dict[str, Any]) @router.post("", response_model=Prompt)
async def create_prompt( async def create_prompt(
prompt: Dict[str, Any] = Body(...), prompt: Prompt,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Create a new prompt""" """Create a new prompt"""
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Required fields with default values # Convert Prompt to dict for interface
content = prompt.get("content", "") prompt_data = prompt.model_dump()
name = prompt.get("name", "New Prompt")
# Create prompt # Create prompt
newPrompt = myInterface.createPrompt( newPrompt = interfaceLucydom.createPrompt(prompt_data)
content=content,
name=name
)
# Set current time for createdAt if it exists in the model # Set current time for createdAt if it exists in the model
if "createdAt" in promptAttributes and hasattr(newPrompt, "createdAt"): if "createdAt" in promptAttributes and hasattr(newPrompt, "createdAt"):
newPrompt["createdAt"] = datetime.now().isoformat() newPrompt["createdAt"] = datetime.now().isoformat()
return newPrompt return Prompt(**newPrompt)
@router.get("/{promptId}", response_model=Dict[str, Any]) @router.get("/{promptId}", response_model=Prompt)
async def get_prompt( async def get_prompt(
promptId: str = Path(..., description="ID of the prompt"), promptId: str = Path(..., description="ID of the prompt"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Get a specific prompt""" """Get a specific prompt"""
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get prompt # Get prompt
prompt = myInterface.getPrompt(promptId) prompt = interfaceLucydom.getPrompt(promptId)
if not prompt: if not prompt:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {promptId} not found" detail=f"Prompt with ID {promptId} not found"
) )
return prompt return Prompt(**prompt)
@router.put("/{promptId}", response_model=Dict[str, Any]) @router.put("/{promptId}", response_model=Prompt)
async def update_prompt( async def update_prompt(
promptId: str = Path(..., description="ID of the prompt to update"), promptId: str = Path(..., description="ID of the prompt to update"),
promptData: Dict[str, Any] = Body(...), promptData: Prompt = Body(...),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Update an existing prompt""" """Update an existing prompt"""
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Check if the prompt exists # Check if the prompt exists
existingPrompt = myInterface.getPrompt(promptId) existingPrompt = interfaceLucydom.getPrompt(promptId)
if not existingPrompt: if not existingPrompt:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {promptId} not found" detail=f"Prompt with ID {promptId} not found"
) )
# Standard fields for update # Convert Prompt to dict for interface
content = promptData.get("content") update_data = promptData.model_dump()
name = promptData.get("name")
# Update prompt # Update prompt
updatedPrompt = myInterface.updatePrompt( updatedPrompt = interfaceLucydom.updatePrompt(promptId, update_data)
promptId=promptId,
content=content,
name=name
)
if not updatedPrompt: if not updatedPrompt:
raise HTTPException( raise HTTPException(
@ -104,25 +100,25 @@ async def update_prompt(
detail="Error updating the prompt" detail="Error updating the prompt"
) )
return updatedPrompt return Prompt(**updatedPrompt)
@router.delete("/{promptId}", response_model=Dict[str, Any]) @router.delete("/{promptId}", response_model=Dict[str, Any])
async def delete_prompt( async def delete_prompt(
promptId: str = Path(..., description="ID of the prompt to delete"), promptId: str = Path(..., description="ID of the prompt to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Delete a prompt""" """Delete a prompt"""
myInterface = getInterface(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Check if the prompt exists # Check if the prompt exists
existingPrompt = myInterface.getPrompt(promptId) existingPrompt = interfaceLucydom.getPrompt(promptId)
if not existingPrompt: if not existingPrompt:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {promptId} not found" detail=f"Prompt with ID {promptId} not found"
) )
success = myInterface.deletePrompt(promptId) success = interfaceLucydom.deletePrompt(promptId)
if not success: if not success:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,

View file

@ -5,17 +5,17 @@ from datetime import datetime
import logging import logging
# Import auth module # Import auth module
from modules.security.auth import getCurrentActiveUser, getRootInterface import modules.security.auth as auth
# Import interfaces # Import interfaces
from modules.interfaces.gatewayInterface import getInterface import modules.interfaces.gatewayInterface as gatewayInterface
from modules.interfaces.gatewayModel import User, getModelAttributes import modules.interfaces.gatewayModel as gatewayModel
# Configure logger # Configure logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Model attributes for User # Model attributes for User
userAttributes = getModelAttributes(User) userAttributes = gatewayModel.getModelAttributes(gatewayModel.User)
router = APIRouter( router = APIRouter(
prefix="/api/users", prefix="/api/users",
@ -24,11 +24,11 @@ router = APIRouter(
) )
@router.get("/", response_model=List[Dict[str, Any]], tags=["Users"]) @router.get("/", response_model=List[Dict[str, Any]], tags=["Users"])
async def get_users(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): async def get_users(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
"""Get all users in the current mandate""" """Get all users in the current mandate"""
try: try:
myInterface = getInterface(currentUser) interfaceGateway = gatewayInterface.getInterface(currentUser)
return myInterface.getUsers() return interfaceGateway.getUsers()
except Exception as e: except Exception as e:
logger.error(f"Error getting users: {str(e)}") logger.error(f"Error getting users: {str(e)}")
raise HTTPException( raise HTTPException(
@ -39,12 +39,12 @@ async def get_users(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser))
@router.get("/{userId}", response_model=Dict[str, Any], tags=["Users"]) @router.get("/{userId}", response_model=Dict[str, Any], tags=["Users"])
async def get_user( async def get_user(
userId: str, userId: str,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Get a specific user by ID""" """Get a specific user by ID"""
try: try:
myInterface = getInterface(currentUser) interfaceGateway = gatewayInterface.getInterface(currentUser)
user = myInterface.getUserById(userId) user = interfaceGateway.getUserById(userId)
if not user: if not user:
raise HTTPException( raise HTTPException(
@ -62,31 +62,27 @@ async def get_user(
detail=f"Failed to get user: {str(e)}" detail=f"Failed to get user: {str(e)}"
) )
@router.post("/", response_model=Dict[str, Any], tags=["Users"]) @router.post("/", response_model=gatewayModel.User, tags=["Users"])
async def create_user( async def create_user(
userData: Dict[str, Any], userData: gatewayModel.User,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Create a new user""" """Create a new user"""
try: try:
# Get admin user for user creation # Get admin user for user creation
myInterface = getRootInterface() interfaceRoot = auth.getRootInterface()
# Check required fields
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"
)
# Filter attributes based on model definition
filteredData = {}
for attr in userAttributes:
if attr in userData:
filteredData[attr] = userData[attr]
try: try:
createdUser = myInterface.createUser(**filteredData) # Convert User model to dict and pass to createUser
createdUser = interfaceRoot.createUser(
username=userData.username,
email=userData.email,
fullName=userData.fullName,
language=userData.language,
disabled=userData.disabled,
privilege=userData.privilege,
authenticationAuthority=userData.authenticationAuthority
)
except ValueError as e: except ValueError as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
@ -109,34 +105,28 @@ async def create_user(
detail=f"Failed to create user: {str(e)}" detail=f"Failed to create user: {str(e)}"
) )
@router.put("/{userId}", response_model=Dict[str, Any], tags=["Users"]) @router.put("/{userId}", response_model=gatewayModel.User, tags=["Users"])
async def update_user( async def update_user(
userId: str, userId: str,
userData: Dict[str, Any], userData: gatewayModel.User,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Update an existing user""" """Update an existing user"""
try: try:
# Get admin user for user updates # Get admin user for user updates
myInterface = getRootInterface() interfaceRoot = auth.getRootInterface()
# Check if user exists # Check if user exists
existingUser = myInterface.getUserById(userId) existingUser = interfaceRoot.getUserById(userId)
if not existingUser: if not existingUser:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"User {userId} not found" detail=f"User {userId} not found"
) )
# Filter attributes based on model definition
filteredData = {}
for attr in userAttributes:
if attr in userData:
filteredData[attr] = userData[attr]
# Update user data # Update user data
try: try:
updatedUser = myInterface.updateUser(userId, **filteredData) updatedUser = interfaceRoot.updateUser(userId, userData)
except ValueError as e: except ValueError as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
@ -162,33 +152,18 @@ async def update_user(
@router.delete("/{userId}", response_model=Dict[str, Any], tags=["Users"]) @router.delete("/{userId}", response_model=Dict[str, Any], tags=["Users"])
async def delete_user( async def delete_user(
userId: str, userId: str,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Delete a user""" """Delete a user"""
try: try:
# Get admin user for user deletion interfaceGateway = gatewayInterface.getInterface(currentUser)
myInterface = getRootInterface() interfaceGateway.deleteUser(userId)
# Check if user exists
existingUser = myInterface.getUserById(userId)
if not existingUser:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User {userId} not found"
)
# Delete user
try:
myInterface.deleteUser(userId)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
return {"message": f"User {userId} deleted successfully"} return {"message": f"User {userId} deleted successfully"}
except HTTPException: except ValueError as e:
raise raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
except Exception as e: except Exception as e:
logger.error(f"Error deleting user {userId}: {str(e)}") logger.error(f"Error deleting user {userId}: {str(e)}")
raise HTTPException( raise HTTPException(

View file

@ -10,18 +10,33 @@ from typing import List, Dict, Any, Optional
from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query, Response, status from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query, Response, status
from datetime import datetime from datetime import datetime
# Import auth module
import modules.security.auth as auth
# Import interfaces # Import interfaces
from modules.interfaces.lucydomInterface import getInterface as getInterfaceLucydom import modules.interfaces.lucydomInterface as lucydomInterface
from modules.interfaces.msftInterface import getInterface as getInterfaceMsft import modules.interfaces.msftInterface as msftInterface
from modules.security.auth import getCurrentActiveUser
# Import workflow manager
from modules.workflow.workflowManager import getWorkflowManager from modules.workflow.workflowManager import getWorkflowManager
# Import models # Import models
from modules.interfaces import lucydomModel as Models from modules.interfaces.lucydomModel import (
ChatWorkflow,
ChatMessage,
ChatLog,
ChatStat,
ChatDocument,
UserInputRequest,
getModelAttributes
)
# Configure logger # Configure logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Model attributes for ChatWorkflow
workflowAttributes = getModelAttributes(ChatWorkflow)
# Create router for workflow endpoints # Create router for workflow endpoints
router = APIRouter( router = APIRouter(
prefix="/api/workflows", prefix="/api/workflows",
@ -29,12 +44,33 @@ router = APIRouter(
responses={404: {"description": "Not found"}} responses={404: {"description": "Not found"}}
) )
# API Endpoint for getting all workflows
@router.get("", response_model=List[ChatWorkflow])
async def list_workflows(
currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""List all workflows for the current user."""
try:
# Get interface with current user context
interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Retrieve workflows for the user
workflows = interfaceLucydom.getWorkflowsByUser(currentUser["id"])
return [ChatWorkflow(**workflow) for workflow in workflows]
except Exception as e:
logger.error(f"Error listing workflows: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error listing workflows: {str(e)}"
)
# State 1: Workflow Initialization endpoint # State 1: Workflow Initialization endpoint
@router.post("/start", response_model=Dict[str, Any]) @router.post("/start", response_model=ChatWorkflow)
async def startWorkflow( async def start_workflow(
workflowId: Optional[str] = Query(None, description="Optional ID of the workflow to continue"), workflowId: Optional[str] = Query(None, description="Optional ID of the workflow to continue"),
userInput: Models.UserInputRequest = Body(...), userInput: UserInputRequest = Body(...),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
""" """
Starts a new workflow or continues an existing one. Starts a new workflow or continues an existing one.
@ -42,27 +78,17 @@ async def startWorkflow(
""" """
try: try:
# Get interface with current user context # Get interface with current user context
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
interfaceMsft = getInterfaceMsft(currentUser) interfaceMsft = msftInterface.getInterface(currentUser)
# Convert the user input to a dictionary
userInputDict = {
"prompt": userInput.prompt,
"listFileId": userInput.listFileId
}
# Get workflow manager with interface # Get workflow manager with interface
workflowManager = await getWorkflowManager(interfaceBase, interfaceMsft) workflowManager = await getWorkflowManager(interfaceLucydom, interfaceMsft)
# Start or continue workflow using the workflow manager # Start or continue workflow using the workflow manager
workflow = await workflowManager.workflowStart(userInputDict, workflowId) workflow = await workflowManager.workflowStart(userInput.dict(), workflowId)
logger.info("User Input received. Answer:", workflow) logger.info("User Input received. Answer:", workflow)
return { return ChatWorkflow(**workflow)
"id": workflow.get("id"),
"status": workflow.get("status", "running"),
"message": "Workflow initialized and processing started"
}
except Exception as e: except Exception as e:
logger.error(f"Error starting workflow: {str(e)}", exc_info=True) logger.error(f"Error starting workflow: {str(e)}", exc_info=True)
raise HTTPException( raise HTTPException(
@ -71,19 +97,19 @@ async def startWorkflow(
) )
# State 8: Workflow Stopped endpoint # State 8: Workflow Stopped endpoint
@router.post("/{workflowId}/stop", response_model=Dict[str, Any]) @router.post("/{workflowId}/stop", response_model=ChatWorkflow)
async def stopWorkflow( async def stop_workflow(
workflowId: str = Path(..., description="ID of the workflow to stop"), workflowId: str = Path(..., description="ID of the workflow to stop"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Stops a running workflow.""" """Stops a running workflow."""
try: try:
# Get interface with current user context # Get interface with current user context
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
interfaceMsft = getInterfaceMsft(currentUser) interfaceMsft = msftInterface.getInterface(currentUser)
# Verify workflow exists and belongs to user # Verify workflow exists and belongs to user
workflow = interfaceBase.getWorkflow(workflowId) workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow: if not workflow:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -97,14 +123,10 @@ async def stopWorkflow(
) )
# Stop the workflow # Stop the workflow
workflowManager = await getWorkflowManager(interfaceBase, interfaceMsft) workflowManager = await getWorkflowManager(interfaceLucydom, interfaceMsft)
stoppedWorkflow = await workflowManager.workflowStop(workflowId) stoppedWorkflow = await workflowManager.workflowStop(workflowId)
return { return ChatWorkflow(**stoppedWorkflow)
"id": workflowId,
"status": stoppedWorkflow.get("status", "stopped"),
"message": "Workflow has been stopped"
}
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
@ -116,17 +138,17 @@ async def stopWorkflow(
# State 11: Workflow Reset/Deletion endpoint # State 11: Workflow Reset/Deletion endpoint
@router.delete("/{workflowId}", response_model=Dict[str, Any]) @router.delete("/{workflowId}", response_model=Dict[str, Any])
async def deleteWorkflow( async def delete_workflow(
workflowId: str = Path(..., description="ID of the workflow to delete"), workflowId: str = Path(..., description="ID of the workflow to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Deletes a workflow and its associated data.""" """Deletes a workflow and its associated data."""
try: try:
# Get interface with current user context # Get interface with current user context
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists # Verify workflow exists
workflow = interfaceBase.getWorkflow(workflowId) workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow: if not workflow:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -141,7 +163,7 @@ async def deleteWorkflow(
) )
# Delete workflow # Delete workflow
success = interfaceBase.deleteWorkflow(workflowId) success = interfaceLucydom.deleteWorkflow(workflowId)
if not success: if not success:
raise HTTPException( raise HTTPException(
@ -162,57 +184,26 @@ async def deleteWorkflow(
detail=f"Error deleting workflow: {str(e)}" detail=f"Error deleting workflow: {str(e)}"
) )
# API Endpoint for getting all workflows
@router.get("", response_model=List[Dict[str, Any]])
async def listWorkflows(
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""List all workflows for the current user."""
try:
# Get interface with current user context
interfaceBase = getInterfaceLucydom(currentUser)
# Retrieve workflows for the user
workflows = interfaceBase.getWorkflowsByUser(currentUser["id"])
return workflows
except Exception as e:
logger.error(f"Error listing workflows: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error listing workflows: {str(e)}"
)
# API Endpoint for workflow status # API Endpoint for workflow status
@router.get("/{workflowId}/status", response_model=Dict[str, Any]) @router.get("/{workflowId}/status", response_model=ChatWorkflow)
async def getWorkflowStatus( async def get_workflow_status(
workflowId: str = Path(..., description="ID of the workflow"), workflowId: str = Path(..., description="ID of the workflow"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Get the current status of a workflow.""" """Get the current status of a workflow."""
try: try:
# Get interface with current user context # Get interface with current user context
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Retrieve workflow # Retrieve workflow
workflow = interfaceBase.getWorkflow(workflowId) workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow: if not workflow:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflowId} not found" detail=f"Workflow with ID {workflowId} not found"
) )
# Create status response return ChatWorkflow(**workflow)
statusInfo = {
"id": workflow.get("id"),
"name": workflow.get("name"),
"status": workflow.get("status"),
"startedAt": workflow.get("startedAt"),
"lastActivity": workflow.get("lastActivity"),
"currentRound": workflow.get("currentRound", 1),
"dataStats": workflow.get("dataStats", {})
}
return statusInfo
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
@ -223,19 +214,19 @@ async def getWorkflowStatus(
) )
# API Endpoint for workflow logs with selective data transfer # API Endpoint for workflow logs with selective data transfer
@router.get("/{workflowId}/logs", response_model=List[Dict[str, Any]]) @router.get("/{workflowId}/logs", response_model=List[ChatLog])
async def getWorkflowLogs( async def get_workflow_logs(
workflowId: str = Path(..., description="ID of the workflow"), workflowId: str = Path(..., description="ID of the workflow"),
logId: Optional[str] = Query(None, description="Optional log ID to get only newer logs"), logId: Optional[str] = Query(None, description="Optional log ID to get only newer logs"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Get logs for a workflow with support for selective data transfer.""" """Get logs for a workflow with support for selective data transfer."""
try: try:
# Get interface with current user context # Get interface with current user context
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists # Verify workflow exists
workflow = interfaceBase.getWorkflow(workflowId) workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow: if not workflow:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -243,7 +234,7 @@ async def getWorkflowLogs(
) )
# Get all logs # Get all logs
allLogs = interfaceBase.getWorkflowLogs(workflowId) allLogs = interfaceLucydom.getWorkflowLogs(workflowId)
# Apply selective data transfer if logId is provided # Apply selective data transfer if logId is provided
if logId: if logId:
@ -251,9 +242,9 @@ async def getWorkflowLogs(
logIndex = next((i for i, log in enumerate(allLogs) if log.get("id") == logId), -1) logIndex = next((i for i, log in enumerate(allLogs) if log.get("id") == logId), -1)
if logIndex >= 0: if logIndex >= 0:
# Return only logs after the specified log # Return only logs after the specified log
return allLogs[logIndex + 1:] return [ChatLog(**log) for log in allLogs[logIndex + 1:]]
return allLogs return [ChatLog(**log) for log in allLogs]
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
@ -264,19 +255,19 @@ async def getWorkflowLogs(
) )
# API Endpoint for workflow messages with selective data transfer # API Endpoint for workflow messages with selective data transfer
@router.get("/{workflowId}/messages", response_model=List[Dict[str, Any]]) @router.get("/{workflowId}/messages", response_model=List[ChatMessage])
async def getWorkflowMessages( async def get_workflow_messages(
workflowId: str = Path(..., description="ID of the workflow"), workflowId: str = Path(..., description="ID of the workflow"),
messageId: Optional[str] = Query(None, description="Optional message ID to get only newer messages"), messageId: Optional[str] = Query(None, description="Optional message ID to get only newer messages"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Get messages for a workflow with support for selective data transfer.""" """Get messages for a workflow with support for selective data transfer."""
try: try:
# Get admin user for workflow operations # Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists # Verify workflow exists
workflow = interfaceBase.getWorkflow(workflowId) workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow: if not workflow:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -284,7 +275,7 @@ async def getWorkflowMessages(
) )
# Get all messages # Get all messages
allMessages = interfaceBase.getWorkflowMessages(workflowId) allMessages = interfaceLucydom.getWorkflowMessages(workflowId)
# Apply selective data transfer if messageId is provided # Apply selective data transfer if messageId is provided
if messageId: if messageId:
@ -298,11 +289,11 @@ async def getWorkflowMessages(
message = next((msg for msg in allMessages if msg.get("id") == msgId), None) message = next((msg for msg in allMessages if msg.get("id") == msgId), None)
if message: if message:
filteredMessages.append(message) filteredMessages.append(message)
return filteredMessages return [ChatMessage(**msg) for msg in filteredMessages]
# Sort messages by sequenceNo # Sort messages by sequenceNo
allMessages.sort(key=lambda x: x.get("sequenceNo", 0)) allMessages.sort(key=lambda x: x.get("sequenceNo", 0))
return allMessages return [ChatMessage(**msg) for msg in allMessages]
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
@ -315,18 +306,18 @@ async def getWorkflowMessages(
# Document Management Endpoints # Document Management Endpoints
@router.delete("/{workflowId}/messages/{messageId}", response_model=Dict[str, Any]) @router.delete("/{workflowId}/messages/{messageId}", response_model=Dict[str, Any])
async def deleteWorkflowMessage( async def delete_workflow_message(
workflowId: str = Path(..., description="ID of the workflow"), workflowId: str = Path(..., description="ID of the workflow"),
messageId: str = Path(..., description="ID of the message to delete"), messageId: str = Path(..., description="ID of the message to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Delete a message from a workflow.""" """Delete a message from a workflow."""
try: try:
# Get admin user for workflow operations # Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists and belongs to user # Verify workflow exists and belongs to user
workflow = interfaceBase.getWorkflow(workflowId) workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow: if not workflow:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -334,7 +325,7 @@ async def deleteWorkflowMessage(
) )
# Delete the message # Delete the message
success = interfaceBase.deleteWorkflowMessage(workflowId, messageId) success = interfaceLucydom.deleteWorkflowMessage(workflowId, messageId)
if not success: if not success:
raise HTTPException( raise HTTPException(
@ -346,7 +337,7 @@ async def deleteWorkflowMessage(
messageIds = workflow.get("messageIds", []) messageIds = workflow.get("messageIds", [])
if messageId in messageIds: if messageId in messageIds:
messageIds.remove(messageId) messageIds.remove(messageId)
interfaceBase.updateWorkflow(workflowId, {"messageIds": messageIds}) interfaceLucydom.updateWorkflow(workflowId, {"messageIds": messageIds})
return { return {
"workflowId": workflowId, "workflowId": workflowId,
@ -363,19 +354,19 @@ async def deleteWorkflowMessage(
) )
@router.delete("/{workflowId}/messages/{messageId}/files/{fileId}", response_model=Dict[str, Any]) @router.delete("/{workflowId}/messages/{messageId}/files/{fileId}", response_model=Dict[str, Any])
async def deleteFileFromMessage( async def delete_file_from_message(
workflowId: str = Path(..., description="ID of the workflow"), workflowId: str = Path(..., description="ID of the workflow"),
messageId: str = Path(..., description="ID of the message"), messageId: str = Path(..., description="ID of the message"),
fileId: str = Path(..., description="ID of the file to delete"), fileId: str = Path(..., description="ID of the file to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Delete a file reference from a message in a workflow.""" """Delete a file reference from a message in a workflow."""
try: try:
# Get admin user for workflow operations # Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists and belongs to user # Verify workflow exists and belongs to user
workflow = interfaceBase.getWorkflow(workflowId) workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow: if not workflow:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -383,7 +374,7 @@ async def deleteFileFromMessage(
) )
# Delete file reference from message # Delete file reference from message
success = interfaceBase.deleteFileFromMessage(workflowId, messageId, fileId) success = interfaceLucydom.deleteFileFromMessage(workflowId, messageId, fileId)
if not success: if not success:
raise HTTPException( raise HTTPException(
@ -408,18 +399,18 @@ async def deleteFileFromMessage(
# File preview and download routes # File preview and download routes
@router.get("/files/{fileId}/preview", response_model=Dict[str, Any]) @router.get("/files/{fileId}/preview", response_model=ChatDocument)
async def previewFile( async def preview_file(
fileId: str = Path(..., description="ID of the file to preview"), fileId: str = Path(..., description="ID of the file to preview"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Get file metadata and a preview of the file content.""" """Get file metadata and a preview of the file content."""
try: try:
# Get admin user for workflow operations # Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get file metadata # Get file metadata
file = interfaceBase.getFile(fileId) file = interfaceLucydom.getFile(fileId)
if not file: if not file:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -427,7 +418,7 @@ async def previewFile(
) )
# Get file data (limited for preview) # Get file data (limited for preview)
fileData = interfaceBase.getFileData(fileId) fileData = interfaceLucydom.getFileData(fileId)
if fileData is None: if fileData is None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -445,7 +436,7 @@ async def previewFile(
previewData = None previewData = None
# Get base64Encoded flag from database # Get base64Encoded flag from database
fileDataEntries = interfaceBase.db.getRecordset("fileData", recordFilter={"id": fileId}) fileDataEntries = interfaceLucydom.db.getRecordset("fileData", recordFilter={"id": fileId})
if fileDataEntries and "base64Encoded" in fileDataEntries[0]: if fileDataEntries and "base64Encoded" in fileDataEntries[0]:
# Use the flag from the database # Use the flag from the database
base64Encoded = fileDataEntries[0]["base64Encoded"] base64Encoded = fileDataEntries[0]["base64Encoded"]
@ -475,17 +466,24 @@ async def previewFile(
previewData = base64.b64encode(fileData).decode('utf-8') previewData = base64.b64encode(fileData).decode('utf-8')
base64Encoded = True base64Encoded = True
# Return file metadata with preview and base64Encoded flag # Create ChatDocument instance
return { return ChatDocument(
"id": fileId, id=fileId,
"name": file.get("name"), fileId=fileId,
"mimeType": mimeType, fileName=file.get("name"),
"size": file.get("size"), fileSize=file.get("size"),
"creationDate": file.get("creationDate"), mimeType=mimeType,
"isPreviewable": isText or mimeType.startswith("image/"), contents=[{
"preview": previewData, "sequenceNr": 1,
"base64Encoded": base64Encoded "name": file.get("name"),
} "mimeType": mimeType,
"data": previewData,
"metadata": {
"base64Encoded": base64Encoded,
"isPreviewable": isText or mimeType.startswith("image/")
}
}]
)
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
@ -496,17 +494,17 @@ async def previewFile(
) )
@router.get("/files/{fileId}/download") @router.get("/files/{fileId}/download")
async def downloadFile( async def download_file(
fileId: str = Path(..., description="ID of the file to download"), fileId: str = Path(..., description="ID of the file to download"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
): ):
"""Download a file.""" """Download a file."""
try: try:
# Get admin user for workflow operations # Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser) interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get file data # Get file data
fileInfo = interfaceBase.downloadFile(fileId) fileInfo = interfaceLucydom.downloadFile(fileId)
if not fileInfo: if not fileInfo:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
@ -529,159 +527,3 @@ async def downloadFile(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error downloading file: {str(e)}" detail=f"Error downloading file: {str(e)}"
) )
@router.get("/workflows", response_model=List[Dict[str, Any]])
async def getWorkflows(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
"""Get all workflows for the mandate."""
try:
# Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser)
# Get all workflows for the mandate
workflows = interfaceBase.getWorkflowsByMandate(currentUser.get("_mandateId"))
return workflows
except Exception as e:
logger.error(f"Error getting workflows: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error getting workflows: {str(e)}"
)
@router.post("/workflows", response_model=Dict[str, Any])
async def createWorkflow(
workflow: Dict[str, Any],
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Create a new workflow."""
try:
# Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser)
# Create workflow
newWorkflow = interfaceBase.createWorkflow(workflow)
return newWorkflow
except Exception as e:
logger.error(f"Error creating workflow: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error creating workflow: {str(e)}"
)
@router.get("/workflows/{workflowId}", response_model=Dict[str, Any])
async def getWorkflow(
workflowId: str = Path(..., description="ID of the workflow"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Get a specific workflow."""
try:
# Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser)
# Get workflow
workflow = interfaceBase.getWorkflow(workflowId)
if not workflow:
raise HTTPException(status_code=404, detail="Workflow not found")
return workflow
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting workflow: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error getting workflow: {str(e)}"
)
@router.put("/workflows/{workflowId}", response_model=Dict[str, Any])
async def updateWorkflow(
workflow: Dict[str, Any],
workflowId: str = Path(..., description="ID of the workflow to update"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Update an existing workflow."""
try:
# Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser)
# Get workflow
existingWorkflow = interfaceBase.getWorkflow(workflowId)
if not existingWorkflow:
raise HTTPException(status_code=404, detail="Workflow not found")
# Update workflow
updatedWorkflow = interfaceBase.updateWorkflow(workflowId, workflow)
return updatedWorkflow
except HTTPException:
raise
except Exception as e:
logger.error(f"Error updating workflow: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error updating workflow: {str(e)}"
)
@router.delete("/workflows/{workflowId}")
async def deleteWorkflow(
workflowId: str = Path(..., description="ID of the workflow to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Delete a workflow."""
try:
# Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser)
# Get workflow
workflow = interfaceBase.getWorkflow(workflowId)
if not workflow:
raise HTTPException(status_code=404, detail="Workflow not found")
# Delete workflow
success = interfaceBase.deleteWorkflow(workflowId)
if not success:
raise HTTPException(status_code=500, detail="Failed to delete workflow")
return {"status": "success"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error deleting workflow: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting workflow: {str(e)}"
)
@router.post("/workflows/{workflowId}/files/{fileId}")
async def addFileToWorkflow(
workflowId: str = Path(..., description="ID of the workflow"),
fileId: str = Path(..., description="ID of the file to add"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Add a file to a workflow."""
try:
# Get admin user for workflow operations
interfaceBase = getInterfaceLucydom(currentUser)
# Get workflow
workflow = interfaceBase.getWorkflow(workflowId)
if not workflow:
raise HTTPException(status_code=404, detail="Workflow not found")
# Get file
file = interfaceBase.getFile(fileId)
if not file:
raise HTTPException(status_code=404, detail="File not found")
# Add file to workflow
success = interfaceBase.addFileToWorkflow(workflowId, fileId)
if not success:
raise HTTPException(status_code=500, detail="Failed to add file to workflow")
return {"status": "success"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error adding file to workflow: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error adding file to workflow: {str(e)}"
)

View file

@ -129,7 +129,20 @@ def getCurrentActiveUser(currentUser: Dict[str, Any] = Depends(_getCurrentUser))
def getRootInterface() -> Dict[str, Any]: def getRootInterface() -> Dict[str, Any]:
try: try:
return getInterface(currentUser={"id": "-1", "_mandateId": "-1"}) # Get the initial user ID from the database
gateway = getInterface() # Initialize without user context
initialUserId = gateway.getInitialId("users")
if not initialUserId:
raise ValueError("No initial user ID found in database")
# Get the actual user record
gateway.setUserContext(initialUserId)
rootUser = gateway.getUser(initialUserId)
if not rootUser:
raise ValueError(f"Root user with ID {initialUserId} not found in database")
return getInterface(currentUser=rootUser)
except Exception as e: except Exception as e:
logger.error(f"Error getting root access: {str(e)}") logger.error(f"Error getting root access: {str(e)}")
raise HTTPException( raise HTTPException(

View file

@ -17,6 +17,7 @@ from modules.shared.mimeUtils import isTextMimeType
# Required imports # Required imports
from modules.workflow.agentRegistry import getAgentRegistry from modules.workflow.agentRegistry import getAgentRegistry
from modules.workflow.documentProcessor import getDocumentContents from modules.workflow.documentProcessor import getDocumentContents
from modules.interfaces.lucydomInterface import UserInputRequest
# Configure logger # Configure logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -89,26 +90,26 @@ class WorkflowManager:
### Workflow State Machine Implementation ### Workflow State Machine Implementation
async def workflowStart(self, userInput: Dict[str, Any], workflowId: Optional[str] = None) -> Dict[str, Any]: async def workflowStart(self, userInput: UserInputRequest, workflowId: Optional[str] = None) -> Dict[str, Any]:
""" """Starts a new workflow or continues an existing one."""
Main entry point for starting or continuing a workflow (State 1: Workflow Initialization). try:
Initializes a new workflow or loads an existing one based on workflowId. # Convert UserInputRequest to dict for processing
userInputDict = userInput.model_dump()
Args:
userInput: User input with prompt and optional file list
workflowId: Optional workflow ID to continue an existing workflow
Returns: # Initialize or load workflow
Initialized workflow object with status "running" if workflowId:
""" workflow = self.service.base.getWorkflow(workflowId)
# 1. Initialize workflow or load existing one if not workflow:
workflow = self.workflowInit(workflowId) raise ValueError(f"Workflow {workflowId} not found")
self.logAdd(workflow, "Starting workflow processing", level="info", progress=0) else:
workflow = self.workflowInit()
# Start asynchronous processing
asyncio.create_task(self.workflowProcess(userInput, workflow)) # Process the workflow
return await self.workflowProcess(userInputDict, workflow)
return workflow
except Exception as e:
logger.error(f"Error in workflowStart: {str(e)}")
raise
### Forces exit ### Forces exit

View file

@ -1,35 +1,32 @@
....................... TASKS ....................... TASKS
mandateid necessary??
cleanup: #####################
all routes to have mandateAttributes = getModelAttributes(Mandate) --> include
adapt workflow route, that handover is with myInterface, not with usersetc. CROSS-CHECK Wrkflow set
ERROR --- > when user logs in with "local" managed account and then logs in to msft account with "msft" authority, the userid is switched to the microsoft instance.
i want to refactor all routes route*.py to initiate in the same way. TODO: routeGeneral: To add User Model for user creation - or to pass to interface. to check!
- routes which need gatewayInterface, to import it as: from modules.interfaces.gatewayInterface import getInterface TODO: All routes not to use "*interface.py" modules for checks or data handling. Full data handling, access control, uam to be in the "*Interface.py" modules. This to adapt.
- routes which need lucydomInterface, to import it as: from modules.interfaces.lucydomInterface import getInterface TODO: Assign model classes for "create" and "update" functions. not to pass specific attributes to functions or routes
- gatewayInterface and lucydomInterface are automatically initialized, and do the following things: TODO: Interface assignment overall to adapt
- check and ensurem that database is available
- initializeDatabase
- initializeRecords
- each route, which needs authentication, has: "currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)" as parameter TODO: Implement userid,mandateid change overall
- it calls "myInterface = getInterface(currentUser)", to have an instance of the according interface with applied userid and mandateid to use
- in lucydomInterface there is also attached "aiService", an instance of connector_ai_openai
like this all routes have the same basic data and can do their work
can you start with route "prompt"? TODO: Workflow-sub modules and agents to include Models and adaptions
###################
@ -38,15 +35,6 @@ i want to refactor all routes route*.py to initiate in the same way.
for all created records
- to add _createdAt (datetime) and _modifiedAt (datetime), initially _createdAt=_modifiedAt
for all updated records
- to update attribute _modifiedAt
! function callAI() to ask with userPrompt,systemPrompt optional), not with json ! function callAI() to ask with userPrompt,systemPrompt optional), not with json
! in the taskplan to refer files always in context of user/mandate ! in the taskplan to refer files always in context of user/mandate
! userinput to handle with object AgentQuery --> when received in frontend to enhance for full object ! userinput to handle with object AgentQuery --> when received in frontend to enhance for full object
@ -106,6 +94,34 @@ Tools to transfer incl funds:
----------------------- DONE ----------------------- DONE
i want to refactor the user management in the backend through the user journey. currrently we have two problems: we always pass _userid and _mandate or id with _mandate from function to function, which blocks scaling. this is too complicated and non-logic.
to adapt the following:
1. The attributes _mandateid and _userid to be removed from @connectorDbJson.py. the attribute _userid to rename to "userId". this is the id of the user, who creates the record. This is the passed attribute instead of _userid and _mandate id., which is stored as userId. The default value to be "" (if None, then set to ""). All new created records get an additional "_createdBy" and "modifiedBy" attribute =self.userId. A modified record gets adapted "modifiedBy" attribute = "userId" when modified.
2.@gatewayModel.py to adapt class User: add mandateId. This is set to the same mandateId like the mandateId of the user, who creates the user.
3. @lucydomModel.py to adapt classes Prompt, FileItem, ChatWorkflow: add mandateId. This is set to the same mandateId like the mandateId of the user, who creates the user.
Also to add "workflowId" to ChatStat, it is missing there.
4. @gatewayInterface.py and @lucydomInterface.py to adapt according to the changes of point 1, 2, 3. Also to integrate their according "*Model.py" to use for record creation with correct attributes.
Also to separate class initiation and function call getInterface().
Class initiation without parameter userid and mandateid. Initialize database and records. Like this it is ensured, when the first function call happens to the class, it is initiated correctly. Initiate the module class automaitcally when module loading.
function getInterface(currentUser with default value = None) makes this:
- if currentUser is None, then only database is initialized (e.g. for refresh folders and files) and an empty object given back with logger info for databse refresh
- if currentUser is provided, then uses the id of the user for contextkey, creates ne instance of the class, gives self.user=currentUser to the class to have user context, initializes AI service self.aiService=ChatService(), initializes access control: self.access = LucydomAccess(self.currentUser, self.db)
- now to adapt code in the *Interface.py modules to use currentUser attributes. like this we have a proper object usage
- modules.interfaces.*Interface to import as module and not the functions. This ensure, that module is initiated when imported.
5. @auth.py : getRootInterface to call getInterface(rootUser), where rootUser is the user with initialId indatabase (use function for this)
FRONTEND: FRONTEND:
- login page and register page withoug fallback. they have mandatory to load their login.html or register.html pages to work (not html in the code). - login page and register page withoug fallback. they have mandatory to load their login.html or register.html pages to work (not html in the code).