From e6ca2bad171026cb8ba02a738c376324f8f16aa5 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Wed, 21 May 2025 19:38:06 +0200
Subject: [PATCH] working set for clean data references
---
modules/connectors/connectorDbJson.py | 66 +---
modules/interfaces/gatewayAccess.py | 18 +-
modules/interfaces/gatewayInterface.py | 384 +++++++++--------------
modules/interfaces/gatewayModel.py | 18 +-
modules/interfaces/lucydomAccess.py | 22 +-
modules/interfaces/lucydomInterface.py | 217 ++++++++-----
modules/interfaces/lucydomModel.py | 61 +++-
modules/interfaces/msftInterface.py | 60 ++--
modules/interfaces/msftModel.py | 19 +-
modules/routes/routeAttributes.py | 42 ++-
modules/routes/routeFiles.py | 87 +++---
modules/routes/routeGeneral.py | 156 +++-------
modules/routes/routeMandates.py | 73 ++---
modules/routes/routeMsft.py | 135 ++++----
modules/routes/routePrompts.py | 82 +++--
modules/routes/routeUsers.py | 101 +++---
modules/routes/routeWorkflows.py | 406 ++++++++-----------------
modules/security/auth.py | 15 +-
modules/workflow/workflowManager.py | 39 +--
notes/changelog.txt | 68 +++--
20 files changed, 956 insertions(+), 1113 deletions(-)
diff --git a/modules/connectors/connectorDbJson.py b/modules/connectors/connectorDbJson.py
index 93f06e42..18ebc48c 100644
--- a/modules/connectors/connectorDbJson.py
+++ b/modules/connectors/connectorDbJson.py
@@ -12,27 +12,16 @@ class DatabaseConnector:
A connector for JSON-based data storage.
Provides generic database operations without user/mandate filtering.
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,
- _mandateId: str = None, _userId: str = None, skipInitialIdLookup: bool = False):
+ def __init__(self, dbHost: str, dbDatabase: str, dbUser: str = None, dbPassword: str = None, userId: str = None):
# Store the input parameters
self.dbHost = dbHost
self.dbDatabase = dbDatabase
self.dbUser = dbUser
self.dbPassword = dbPassword
- # Check if context parameters are set
- if _mandateId is None and _userId is None:
- # 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
+ # Set userId (default to empty string if None)
+ self.userId = userId if userId is not None else ""
# Ensure the database directory exists
self.dbFolder = os.path.join(self.dbHost, self.dbDatabase)
@@ -46,27 +35,7 @@ class DatabaseConnector:
self._systemTableName = "_system"
self._initializeSystemTable()
- # If IDs are empty and we're not skipping lookup, try to use initial IDs
- 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}")
+ logger.debug(f"Context: userId={self.userId}")
def _initializeSystemTable(self):
"""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}")
return False
- def updateContext(self, _mandateId: str, _userId: str) -> None:
+ def updateContext(self, userId: str) -> None:
"""Updates the context of the database connector."""
- if _mandateId is None or _userId is None:
- raise ValueError("_mandateId and _userId must both be provided")
+ if userId is None:
+ raise ValueError("userId must be provided")
- self._mandateId = _mandateId
- self._userId = _userId
- logger.info(f"Updated database context: _mandateId={self._mandateId}, _userId={self._userId}")
+ self.userId = userId
+ logger.info(f"Updated database context: userId={self.userId}")
# Clear cache to ensure fresh data with new context
self._tablesCache = {}
@@ -426,19 +394,14 @@ class DatabaseConnector:
recordData["id"] = str(recordData["id"])
# Add context fields
- recordData["_mandateId"] = self._mandateId
- recordData["_userId"] = self._userId
+ recordData["userId"] = self.userId
- # Update metadata
- if "recordIds" not in metadata:
- metadata["recordIds"] = []
- metadata["recordIds"].append(recordData["id"])
- metadata["recordIds"].sort()
-
- # Add creation timestamp
+ # Add creation and modification tracking
currentTime = self._getCurrentTimestamp()
recordData["_createdAt"] = currentTime
recordData["_modifiedAt"] = currentTime
+ recordData["_createdBy"] = self.userId
+ recordData["_modifiedBy"] = self.userId
# Save the record
recordPath = self._getRecordPath(table, recordData["id"])
@@ -532,8 +495,9 @@ class DatabaseConnector:
for key, value in recordData.items():
existingRecord[key] = value
- # Update modified timestamp
+ # Update modified timestamp and user
existingRecord["_modifiedAt"] = self._getCurrentTimestamp()
+ existingRecord["_modifiedBy"] = self.userId
# Save the updated record
recordPath = self._getRecordPath(table, recordId)
diff --git a/modules/interfaces/gatewayAccess.py b/modules/interfaces/gatewayAccess.py
index e4aa78b1..3b031407 100644
--- a/modules/interfaces/gatewayAccess.py
+++ b/modules/interfaces/gatewayAccess.py
@@ -14,11 +14,11 @@ class GatewayAccess:
def __init__(self, currentUser: Dict[str, Any], db):
"""Initialize with user context."""
self.currentUser = currentUser
- self._mandateId = currentUser.get("_mandateId")
- self._userId = currentUser.get("id")
+ self.mandateId = currentUser.get("mandateId")
+ 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.mandateId or not self.userId:
+ raise ValueError("Invalid user context: mandateId and userId are required")
self.db = db
@@ -42,11 +42,11 @@ class GatewayAccess:
filtered_records = recordset # System admins see all records
elif userPrivilege == "admin":
# 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
# Users only see records they own within their mandate
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
for record in filtered_records:
@@ -96,15 +96,15 @@ class GatewayAccess:
record = records[0]
# 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
if table == "mandates" and record.get("initialid") and userPrivilege != "sysadmin":
return False
return True
# Users can only modify their own records
- if (record.get("_mandateId") == self._mandateId and
- record.get("_userId") == self._userId):
+ if (record.get("mandateId","-") == self.mandateId and
+ record.get("_createdBy") == self.userId):
return True
return False
diff --git a/modules/interfaces/gatewayInterface.py b/modules/interfaces/gatewayInterface.py
index d64337df..ea917b98 100644
--- a/modules/interfaces/gatewayInterface.py
+++ b/modules/interfaces/gatewayInterface.py
@@ -14,6 +14,7 @@ from passlib.context import CryptContext
from modules.connectors.connectorDbJson import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
from modules.interfaces.gatewayAccess import GatewayAccess
+from modules.interfaces.gatewayModel import User, Mandate, UserInDB
logger = logging.getLogger(__name__)
@@ -31,59 +32,38 @@ class GatewayInterface:
Manages users and mandates.
"""
- def __init__(self, currentUser: Dict[str, Any]):
- """Initializes the Gateway Interface with user context."""
-
- # 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")
-
+ def __init__(self):
+ """Initializes the Gateway Interface."""
# Initialize database
self._initializeDatabase()
-
+
# Initialize standard records if needed
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:
- raise ValueError("Invalid user context: _mandateId and id are required")
-
+ # Initialize variables
+ 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
self.userLanguage = currentUser.get("language", "en") # Default user language
- # Initialize database
- self._initializeDatabase()
-
- # Initialize access control
+ # Initialize access control with user context
self.access = GatewayAccess(self.currentUser, self.db)
+
+ logger.debug(f"User context set: userId={self.userId}")
def _initializeDatabase(self):
"""Initializes the database connection."""
@@ -101,44 +81,17 @@ class GatewayInterface:
dbHost=dbHost,
dbDatabase=dbDatabase,
dbUser=dbUser,
- dbPassword=dbPassword,
- _mandateId=self._mandateId,
- _userId=self._userId
+ dbPassword=dbPassword
)
- # Set context
- self.db.updateContext(self._mandateId, self._userId)
-
logger.info("Database initialized successfully")
except Exception as e:
logger.error(f"Failed to initialize database: {str(e)}")
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):
- """Initializes standard records in the database if they don't exist."""
-
self._initRootMandate()
- # Update database context with new IDs
- if self._mandateId and self._userId:
- self.db.updateContext(self._mandateId, self._userId)
-
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):
"""Creates the Root mandate if it doesn't exist."""
@@ -157,7 +110,7 @@ class GatewayInterface:
self.db._registerInitialId("mandates", createdMandate['id'])
# Update mandate context
- self._mandateId = createdMandate['id']
+ self.currentUser["mandateId"] = createdMandate['id']
def _initAdminUser(self):
"""Creates the Admin user if it doesn't exist."""
@@ -166,7 +119,7 @@ class GatewayInterface:
if existingUserId is None or not users:
logger.info("Creating Admin user")
adminUser = {
- "_mandateId": self._mandateId,
+ "mandateId": self.getInitialId("mandates"),
"username": "admin",
"email": "admin@example.com",
"fullName": "Administrator",
@@ -183,8 +136,9 @@ class GatewayInterface:
self.db._registerInitialId("users", createdUser['id'])
# 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]]:
"""
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:
"""Checks if the password matches the hash."""
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
- def getAllMandates(self) -> List[Dict[str, Any]]:
+ def getAllMandates(self) -> List[Mandate]:
"""Returns mandates based on user access level."""
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."""
mandates = self.db.getRecordset("mandates", recordFilter={"id": mandateId})
if not mandates:
return None
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."""
if not self._canModify("mandates"):
raise PermissionError("No permission to create mandates")
- mandateData = {
- "name": name,
- "language": language
- }
+ # Create and validate mandate data using Pydantic model
+ mandateData = Mandate(
+ 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."""
# Check if the mandate exists and user has access
mandate = self.getMandate(mandateId)
@@ -267,8 +223,16 @@ class GatewayInterface:
if not self._canModify("mandates", 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
- 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:
"""
@@ -307,7 +271,7 @@ class GatewayInterface:
# User methods
- def getAllUsers(self) -> List[Dict[str, Any]]:
+ def getAllUsers(self) -> List[User]:
"""Returns users based on user access level."""
allUsers = self.db.getRecordset("users")
filteredUsers = self._uam("users", allUsers)
@@ -317,17 +281,12 @@ class GatewayInterface:
if "hashedPassword" in user:
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."""
- # First check if user has access to the mandate
- mandate = self.getMandate(_mandateId)
- if not mandate:
- return []
-
# 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)
# Remove password hashes
@@ -335,9 +294,9 @@ class GatewayInterface:
if "hashedPassword" in user:
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."""
try:
# Get users table
@@ -350,7 +309,7 @@ class GatewayInterface:
if user.get("username") == username:
logger.info(f"Found user with username {username}")
logger.debug(f"User fields: {list(user.keys())}")
- return user
+ return User(**user)
logger.info(f"No user found with username {username}")
return None
@@ -359,9 +318,9 @@ class GatewayInterface:
logger.error(f"Error getting user by username: {str(e)}")
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."""
- users = self.db.getRecordset("users", recordFilter={"_userId": _userId})
+ users = self.db.getRecordset("users", recordFilter={"id": userId})
if not users:
return None
@@ -375,13 +334,13 @@ class GatewayInterface:
if "hashedPassword" in user:
userCopy = user.copy()
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,
- language: str = "en", _mandateId: int = None, disabled: bool = False,
- privilege: str = "user", authenticationAuthority: str = "local") -> Dict[str, Any]:
+ language: str = "en", disabled: bool = False,
+ privilege: str = "user", authenticationAuthority: str = "local") -> User:
"""Create a new user"""
try:
# Validate username
@@ -390,7 +349,7 @@ class GatewayInterface:
# Check if user already exists with the same authentication authority
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")
# Validate password for local authentication
@@ -400,24 +359,21 @@ class GatewayInterface:
if len(password) < 8:
raise ValueError("Password must be at least 8 characters long")
- # Create user data
- userData = {
- "username": username,
- "email": email,
- "fullName": fullName,
- "language": language,
- "_mandateId": _mandateId or self._mandateId,
- "disabled": disabled,
- "privilege": privilege,
- "authenticationAuthority": authenticationAuthority
- }
-
- # Add password hash for local authentication
- if authenticationAuthority == "local":
- userData["hashedPassword"] = self._getPasswordHash(password)
+ # Create user data using UserInDB model
+ userData = UserInDB(
+ username=username,
+ email=email,
+ fullName=fullName,
+ language=language,
+ mandateId=self.currentUser.get("mandateId"),
+ disabled=disabled,
+ privilege=privilege,
+ authenticationAuthority=authenticationAuthority,
+ hashedPassword=self._getPasswordHash(password) if authenticationAuthority == "local" else None
+ )
# 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"):
raise ValueError("Failed to create user record")
@@ -425,7 +381,7 @@ class GatewayInterface:
createdUser = self.db.getRecordset("users", recordFilter={"id": createdRecord["id"]})
if not createdUser or len(createdUser) == 0:
# 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:
raise ValueError("Failed to retrieve created user")
@@ -433,7 +389,7 @@ class GatewayInterface:
if hasattr(self.db, '_tablesCache') and "users" in self.db._tablesCache:
del self.db._tablesCache["users"]
- return createdUser[0]
+ return User(**createdUser[0])
except ValueError as e:
logger.error(f"Error creating user: {str(e)}")
@@ -442,7 +398,7 @@ class GatewayInterface:
logger.error(f"Unexpected error creating 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."""
# Clear the users table from cache and reload it
if "users" in self.db._tablesCache:
@@ -455,16 +411,18 @@ class GatewayInterface:
raise ValueError("Benutzer nicht gefunden")
# Check if the user is disabled
- if user.get("disabled", False):
+ if user.disabled:
raise ValueError("Benutzer ist deaktiviert")
# Handle authentication based on authority
- auth_authority = user.get("authenticationAuthority", "local")
+ auth_authority = user.authenticationAuthority
if auth_authority == "local":
if not password:
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")
elif auth_authority == "microsoft":
# For Microsoft users, we don't verify the password here
@@ -473,26 +431,21 @@ class GatewayInterface:
else:
raise ValueError(f"Unbekannte Authentifizierungsmethode: {auth_authority}")
- # Create a copy without password hash
- authenticatedUser = {**user}
- if "hashedPassword" in authenticatedUser:
- del authenticatedUser["hashedPassword"]
-
- return authenticatedUser
+ return user
- 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."""
# Check if the user exists and current user has access
- user = self.getUser(_userId)
+ user = self.getUser(userId)
if not user:
# 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:
- 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
- if not self._canModify("users", _userId):
- raise PermissionError(f"No permission to update user {_userId}")
+ if not self._canModify("users", userId):
+ raise PermissionError(f"No permission to update user {userId}")
user = users[0]
@@ -510,140 +463,83 @@ class GatewayInterface:
userData["hashedPassword"] = self._getPasswordHash(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
- 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
- if "hashedPassword" in updatedUser:
- del updatedUser["hashedPassword"]
-
- return updatedUser
+ # Return User model without password hash
+ return User(**updated)
- def disableUser(self, _userId: str) -> Dict[str, Any]:
+ def disableUser(self, userId: str) -> User:
"""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."""
- 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."""
# Delete user attributes
try:
- attributes = self.db.getRecordset("attributes", recordFilter={"_userId": _userId})
+ attributes = self.db.getRecordset("attributes", recordFilter={"createdBy": userId})
for attribute in attributes:
self.db.recordDelete("attributes", attribute["id"])
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."""
# Check if the user exists
- users = self.db.getRecordset("users", recordFilter={"_userId": _userId})
+ users = self.db.getRecordset("users", recordFilter={"id": userId})
if not users:
return False
# Check if current user has permission
- if not self._canModify("users", _userId):
- raise PermissionError(f"No permission to delete user {_userId}")
+ if not self._canModify("users", userId):
+ raise PermissionError(f"No permission to delete user {userId}")
# Check if it's the initial user
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")
return False
# Delete all data associated with the user
- self._deleteUserReferencedData(_userId)
+ self._deleteUserReferencedData(userId)
# Delete the user
- success = self.db.recordDelete("users", _userId)
+ success = self.db.recordDelete("users", userId)
if success:
- logger.info(f"User with ID {_userId} was successfully deleted")
+ logger.info(f"User with ID {userId} was successfully deleted")
else:
- logger.error(f"Error deleting user with ID {_userId}")
+ logger.error(f"Error deleting user with ID {userId}")
return success
- # Microsoft Login
-
- 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':
+def getInterface(currentUser: Dict[str, Any] = None) -> 'GatewayInterface':
"""
- Returns a GatewayInterface instance for the current user.
- Handles initialization of database and records.
+ Returns a GatewayInterface instance.
+ 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
- if contextKey not in _gatewayInterfaces:
- _gatewayInterfaces[contextKey] = GatewayInterface(currentUser)
+ if "default" not in _gatewayInterfaces:
+ _gatewayInterfaces["default"] = GatewayInterface()
- return _gatewayInterfaces[contextKey]
+ interface = _gatewayInterfaces["default"]
+
+ if currentUser:
+ interface.setUserContext(currentUser)
+ else:
+ logger.info("Returning interface without user context")
+
+ return interface
diff --git a/modules/interfaces/gatewayModel.py b/modules/interfaces/gatewayModel.py
index 12d3118c..f9059d3b 100644
--- a/modules/interfaces/gatewayModel.py
+++ b/modules/interfaces/gatewayModel.py
@@ -15,8 +15,21 @@ def getModelAttributes(modelClass):
class Label(BaseModel):
"""Label for an attribute or a class with support for multiple languages"""
- default: str
- translations: Dict[str, str] = {}
+ default: str = Field(..., description="Default label text")
+ 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):
"""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")
privilege: str = Field(description="Permission level") #sysadmin,admin,user
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(
default=Label(default="User", translations={"en": "User", "fr": "Utilisateur"}),
diff --git a/modules/interfaces/lucydomAccess.py b/modules/interfaces/lucydomAccess.py
index b2e4423b..f333115b 100644
--- a/modules/interfaces/lucydomAccess.py
+++ b/modules/interfaces/lucydomAccess.py
@@ -14,11 +14,11 @@ class LucydomAccess:
def __init__(self, currentUser: Dict[str, Any], db):
"""Initialize with user context."""
self.currentUser = currentUser
- self._mandateId = currentUser.get("_mandateId")
- self._userId = currentUser.get("id")
+ self.mandateId = currentUser.get("mandateId")
+ 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.mandateId or not self.userId:
+ raise ValueError("Invalid user context: mandateId and userId are required")
self.db = db
@@ -43,15 +43,15 @@ class LucydomAccess:
filtered_records = recordset # System admins see all records
elif userPrivilege == "admin":
# 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
# For prompts, users can see all prompts from their mandate
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:
# Users see only their records for other tables
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
for record in filtered_records:
@@ -113,13 +113,13 @@ class LucydomAccess:
record = records[0]
- # Admins can modify anything in their mandate
- if userPrivilege == "admin" and record.get("_mandateId") == self._mandateId:
+ # Admins can modify anything in their mandate, if mandate is specified for a record
+ if userPrivilege == "admin" and record.get("mandateId","-") == self.mandateId:
return True
# Regular users can only modify their own records
- if (record.get("_mandateId") == self._mandateId and
- record.get("_userId") == self._userId):
+ if (record.get("mandateId","-") == self.mandateId and
+ record.get("_createdBy") == self.userId):
return True
return False
diff --git a/modules/interfaces/lucydomInterface.py b/modules/interfaces/lucydomInterface.py
index b90ef652..90177b70 100644
--- a/modules/interfaces/lucydomInterface.py
+++ b/modules/interfaces/lucydomInterface.py
@@ -13,6 +13,10 @@ import hashlib
from modules.shared.mimeUtils import isTextMimeType
from modules.interfaces.lucydomAccess import LucydomAccess
+from modules.interfaces.lucydomModel import (
+ ChatWorkflow, ChatMessage, ChatLog, ChatStat,
+ ChatDocument, UserInputRequest
+)
# DYNAMIC PART: Connectors to the Interface
from modules.connectors.connectorDbJson import DatabaseConnector
@@ -48,40 +52,47 @@ class FileDeletionError(FileError):
class LucydomInterface:
"""
- Interface to the LucyDOM database.
- Uses the JSON connector for data access.
+ Interface to LucyDOM database and AI Connectors.
+ Uses the JSON connector for data access with added language support.
"""
- def __init__(self, currentUser: Dict[str, Any]):
- """Initializes the LucyDOM Interface with user context."""
- logger.debug(f"Initializing LucydomInterface with currentUser={currentUser}")
+ def __init__(self):
+ """Initializes the Lucydom Interface."""
+ # 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._mandateId = currentUser.get("_mandateId")
- self._userId = currentUser.get("id")
-
- if not self._mandateId or not self._userId:
- raise ValueError("Invalid user context: _mandateId and id are required")
+ self.userId = currentUser.get("id")
+ if not self.userId:
+ raise ValueError("Invalid user context: id is required")
+
# Add language settings
self.userLanguage = currentUser.get("language", "en") # Default user language
- # Initialize database
- self._initializeDatabase()
-
- # Initialize standard records
- self._initRecords()
-
+ # Initialize access control with user context
+ self.access = LucydomAccess(self.currentUser, self.db)
+
# Initialize AI service
self.aiService = ChatService()
- if not self.aiService:
- logger.warning("AI service not available during LucydomInterface initialization")
- # Initialize access control
- self.access = LucydomAccess(self.currentUser, self.db)
-
-
+ logger.debug(f"User context set: userId={self.userId}")
+
def _initializeDatabase(self):
"""Initializes the database connection."""
try:
@@ -98,14 +109,9 @@ class LucydomInterface:
dbHost=dbHost,
dbDatabase=dbDatabase,
dbUser=dbUser,
- dbPassword=dbPassword,
- _mandateId=self._mandateId,
- _userId=self._userId
+ dbPassword=dbPassword
)
- # Set context
- self.db.updateContext(self._mandateId, self._userId)
-
logger.info("Database initialized successfully")
except Exception as e:
logger.error(f"Failed to initialize database: {str(e)}")
@@ -163,7 +169,7 @@ class LucydomInterface:
# Create prompts
for promptData in standardPrompts:
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:
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."""
files = self.db.getRecordset("files", recordFilter={
"fileHash": fileHash,
- "_mandateId": self._mandateId,
- "_userId": self._userId
+ "mandateId": self.currentUser.get("mandateId"),
+ "_createdBy": self.currentUser.get("id")
})
if files:
return files[0]
@@ -357,8 +363,7 @@ class LucydomInterface:
raise PermissionError("No permission to create files")
fileData = {
- "_mandateId": self._mandateId,
- "_userId": self._userId,
+ "mandateId": self.currentUser.get("mandateId"),
"name": name,
"mimeType": mimeType,
"size": size,
@@ -675,35 +680,48 @@ class LucydomInterface:
allWorkflows = self.db.getRecordset("workflows")
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."""
- # Get workflows by _userId
- workflows = self.db.getRecordset("workflows", recordFilter={"_userId": _userId})
+ # Get workflows by userId
+ workflows = self.db.getRecordset("workflows", recordFilter={"_createdBy": userId})
# Apply access control
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."""
workflows = self.db.getRecordset("workflows", recordFilter={"id": workflowId})
if not workflows:
return None
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."""
if not self._canModify("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
currentTime = self._getCurrentTimestamp()
if "startedAt" not in workflowData:
@@ -711,10 +729,25 @@ class LucydomInterface:
if "lastActivity" not in workflowData:
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."""
# Check if the workflow exists and user has access
workflow = self.getWorkflow(workflowId)
@@ -727,8 +760,22 @@ class LucydomInterface:
# Set update time
workflowData["lastActivity"] = self._getCurrentTimestamp()
- # Update workflow
- return self.db.recordModify("workflows", workflowId, workflowData)
+ # Update workflow in database
+ 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:
"""Deletes a workflow if user has access."""
@@ -756,7 +803,7 @@ class LucydomInterface:
messages = self.db.getRecordset("workflowMessages", recordFilter={"workflowId": workflowId})
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."""
try:
# Check required fields
@@ -811,14 +858,28 @@ class LucydomInterface:
# Update workflow's messageIds if this is a new message
if createdMessage:
# 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
if createdMessage["id"] not in messageIds:
messageIds.append(createdMessage["id"])
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:
logger.error(f"Error creating workflow message: {str(e)}")
return None
@@ -1027,7 +1088,7 @@ class LucydomInterface:
# Get logs for this workflow
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."""
# Check workflow access
workflowId = logData.get("workflowId")
@@ -1065,7 +1126,18 @@ class LucydomInterface:
elif logData.get("type") == "warning":
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
@@ -1089,8 +1161,7 @@ class LucydomInterface:
# Extract only the database-relevant workflow fields
workflowDbData = {
"id": workflowId,
- "_mandateId": workflow.get("_mandateId", self._mandateId),
- "_userId": workflow.get("_userId", self._userId),
+ "mandateId": workflow.get("mandateId", self.currentUser.get("mandateId")),
"name": workflow.get("name", f"Workflow {workflowId}"),
"status": workflow.get("status", "completed"),
"startedAt": workflow.get("startedAt", self._getCurrentTimestamp()),
@@ -1220,23 +1291,21 @@ class LucydomInterface:
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.
- Handles initialization of database and records.
+ Returns a LucydomInterface instance.
+ 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
- if contextKey not in _lucydomInterfaces:
- _lucydomInterfaces[contextKey] = LucydomInterface(currentUser)
+ if "default" not in _lucydomInterfaces:
+ _lucydomInterfaces["default"] = LucydomInterface()
- return _lucydomInterfaces[contextKey]
\ No newline at end of file
+ interface = _lucydomInterfaces["default"]
+
+ if currentUser:
+ interface.setUserContext(currentUser)
+ else:
+ logger.info("Returning interface without user context")
+
+ return interface
\ No newline at end of file
diff --git a/modules/interfaces/lucydomModel.py b/modules/interfaces/lucydomModel.py
index 5c6e547b..d88500e4 100644
--- a/modules/interfaces/lucydomModel.py
+++ b/modules/interfaces/lucydomModel.py
@@ -18,8 +18,21 @@ def getModelAttributes(modelClass):
class Label(BaseModel):
"""Label for an attribute or a class with support for multiple languages"""
- default: str
- translations: Dict[str, str] = {}
+ default: str = Field(..., description="Default label text")
+ 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):
"""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")
content: str = Field(description="Content 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(
default=Label(default="Prompt", translations={"en": "Prompt", "fr": "Invite"}),
@@ -43,7 +57,8 @@ class Prompt(BaseModel):
fieldLabels: Dict[str, Label] = {
"id": Label(default="ID", translations={}),
"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")
fileHash: str = Field(description="Hash code for deduplication")
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(
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"}),
"fileSize": Label(default="Size", translations={"en": "Size", "fr": "Taille"}),
"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):
"""Statistics for performance and data usage"""
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")
tokenCount: Optional[int] = Field(None, description="Token count (for AI models)")
bytesSent: Optional[int] = Field(None, description="Bytes sent")
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):
"""Message object in the chat workflow"""
@@ -149,6 +182,26 @@ class ChatWorkflow(BaseModel):
logs: List[ChatLog] = Field(default=[], description="Log entries")
messages: List[ChatMessage] = Field(default=[], description="Message history")
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
diff --git a/modules/interfaces/msftInterface.py b/modules/interfaces/msftInterface.py
index 0e004b10..c864d483 100644
--- a/modules/interfaces/msftInterface.py
+++ b/modules/interfaces/msftInterface.py
@@ -7,7 +7,7 @@ import json
import requests
import base64
import msal
-from typing import Dict, Any, Optional, List
+from typing import Dict, Any, Optional, List, Tuple
from datetime import datetime, timedelta
import secrets
import os
@@ -111,7 +111,7 @@ class MsftInterface:
"""
return self.access.canModify(table, recordId)
- def getMsftToken(self) -> Optional[Dict[str, Any]]:
+ def getMsftToken(self) -> Optional[MsftToken]:
"""Get Microsoft token for current user"""
try:
tokens = self.db.getRecordset("msftTokens", recordFilter={
@@ -126,7 +126,7 @@ class MsftInterface:
if not filtered_tokens:
return None
- return filtered_tokens[0]
+ return MsftToken(**filtered_tokens[0])
except Exception as e:
logger.error(f"Error getting Microsoft token: {str(e)}")
return None
@@ -142,15 +142,21 @@ class MsftInterface:
token_data["_mandateId"] = self._mandateId
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
existing_token = self.getMsftToken()
if 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:
# Create new token record
- return self.db.recordCreate("msftTokens", token_data)
+ return self.db.recordCreate("msftTokens", token.model_dump())
except Exception as e:
logger.error(f"Error saving Microsoft token: {str(e)}")
@@ -164,13 +170,13 @@ class MsftInterface:
existing_token = self.getMsftToken()
if existing_token:
- return self.db.recordDelete("msftTokens", existing_token["id"])
+ return self.db.recordDelete("msftTokens", existing_token.id)
return True
except Exception as e:
logger.error(f"Error deleting Microsoft token: {str(e)}")
return False
- def getCurrentUserToken(self) -> tuple:
+ def getCurrentUserToken(self) -> Tuple[Optional[MsftUserInfo], Optional[str]]:
"""Get current user's Microsoft token and info"""
try:
token_data = self.getMsftToken()
@@ -178,19 +184,19 @@ class MsftInterface:
return None, None
# 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):
return None, None
token_data = self.getMsftToken()
- user_info = token_data.get("user_info")
+ user_info = token_data.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:
- token_data["user_info"] = user_info
- self.saveMsftToken(token_data)
+ token_data.user_info = user_info
+ 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:
logger.error(f"Error getting current user token: {str(e)}")
@@ -209,14 +215,14 @@ class MsftInterface:
logger.error(f"Error verifying token: {str(e)}")
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"""
try:
- if not token_data or not token_data.get("refresh_token"):
+ if not token_data or not token_data.refresh_token:
return False
result = self.msal_app.acquire_token_by_refresh_token(
- token_data["refresh_token"],
+ token_data.refresh_token,
scopes=self.scopes
)
@@ -224,11 +230,12 @@ class MsftInterface:
logger.error(f"Error refreshing token: {result.get('error')}")
return False
- token_data["access_token"] = result["access_token"]
+ # Update token data
+ token_data.access_token = result["access_token"]
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:
logger.error(f"Error refreshing token: {str(e)}")
@@ -364,8 +371,19 @@ class MsftInterface:
if not user_info:
return None
- token_response["user_info"] = user_info
- return token_response
+ # Create MsftToken instance
+ 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:
logger.error(f"Error handling auth callback: {str(e)}")
diff --git a/modules/interfaces/msftModel.py b/modules/interfaces/msftModel.py
index b3449f08..7b6f062a 100644
--- a/modules/interfaces/msftModel.py
+++ b/modules/interfaces/msftModel.py
@@ -70,4 +70,21 @@ class MsftUserInfo(BaseModel):
"name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}),
"email": Label(default="Email", translations={"en": "Email", "fr": "E-mail"}),
"id": Label(default="ID", translations={})
- }
\ No newline at end of file
+ }
+
+# 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
\ No newline at end of file
diff --git a/modules/routes/routeAttributes.py b/modules/routes/routeAttributes.py
index bc90e169..086d5a89 100644
--- a/modules/routes/routeAttributes.py
+++ b/modules/routes/routeAttributes.py
@@ -5,12 +5,40 @@ import inspect
import importlib
import os
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
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]:
"""Dynamically get all model classes from all model modules"""
modelClasses = {}
@@ -42,14 +70,20 @@ router = APIRouter(
responses={404: {"description": "Not found"}}
)
-@router.get("/{entityType}", response_model=List[AttributeDefinition])
+@router.get("/{entityType}", response_model=AttributeResponse)
async def get_entity_attributes(
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.
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
userLanguage = currentUser.get("language", "en")
@@ -69,7 +103,7 @@ async def get_entity_attributes(
attributes = getModelAttributes(modelClass, userLanguage)
# 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}")
async def options_entity_attributes(
diff --git a/modules/routes/routeFiles.py b/modules/routes/routeFiles.py
index d23d9a15..af42212d 100644
--- a/modules/routes/routeFiles.py
+++ b/modules/routes/routeFiles.py
@@ -6,18 +6,18 @@ from datetime import datetime, timezone
from dataclasses import dataclass
import io
-from modules.security.auth import getCurrentActiveUser
-from modules.shared.configuration import APP_CONFIG
+# Import auth module
+import modules.security.auth as auth
# Import interfaces
-from modules.interfaces.lucydomInterface import getInterface, FileError, FileNotFoundError, FileStorageError, FilePermissionError, FileDeletionError
-from modules.interfaces.lucydomModel import FileItem, getModelAttributes
+import modules.interfaces.lucydomInterface as lucydomInterface
+from modules.interfaces.lucydomModel import FileItem
# Configure logger
logger = logging.getLogger(__name__)
# Model attributes for FileItem
-fileAttributes = getModelAttributes(FileItem)
+fileAttributes = lucydomInterface.getModelAttributes(FileItem)
# Create router for file endpoints
router = APIRouter(
@@ -32,15 +32,15 @@ router = APIRouter(
}
)
-@router.get("", response_model=List[Dict[str, Any]])
-async def get_files(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
+@router.get("", response_model=List[FileItem])
+async def get_files(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
"""Get all available files"""
try:
- myInterface = getInterface(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get all files generically - only metadata, no binary data
- files = myInterface.getAllFiles()
- return files
+ files = interfaceLucydom.getAllFiles()
+ return [FileItem(**file) for file in files]
except Exception as e:
logger.error(f"Error retrieving files: {str(e)}")
raise HTTPException(
@@ -52,36 +52,36 @@ async def get_files(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser))
async def upload_file(
file: UploadFile = File(...),
workflowId: Optional[str] = Form(None),
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Upload a file"""
try:
- myInterface = getInterface(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Read file
fileContent = await file.read()
# 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:
raise HTTPException(
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
- fileMeta = myInterface.saveUploadedFile(fileContent, file.filename)
+ fileMeta = interfaceLucydom.saveUploadedFile(fileContent, file.filename)
# If workflowId is provided, update the file information
if workflowId:
updateData = {"workflowId": workflowId}
- myInterface.updateFile(fileMeta["id"], updateData)
+ interfaceLucydom.updateFile(fileMeta["id"], updateData)
fileMeta["workflowId"] = workflowId
# Successful response
return fileMeta
- except FileStorageError as e:
+ except lucydomInterface.FileStorageError as e:
logger.error(f"Error during file upload (storage): {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -97,14 +97,14 @@ async def upload_file(
@router.get("/{fileId}")
async def get_file(
fileId: str,
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Returns a file by its ID for download"""
try:
- myInterface = getInterface(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get file via LucyDOM interface from the database
- fileData = myInterface.downloadFile(fileId)
+ fileData = interfaceLucydom.downloadFile(fileId)
# Return file
headers = {
@@ -116,19 +116,19 @@ async def get_file(
headers=headers
)
- except FileNotFoundError as e:
+ except lucydomInterface.FileNotFoundError as e:
logger.warning(f"File not found: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
- except FilePermissionError as e:
+ except lucydomInterface.FilePermissionError as e:
logger.warning(f"No permission for file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
- except FileError as e:
+ except lucydomInterface.FileError as e:
logger.error(f"Error retrieving file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -141,20 +141,20 @@ async def get_file(
detail=f"Error retrieving file: {str(e)}"
)
-@router.put("/{file_id}")
+@router.put("/{file_id}", response_model=FileItem)
async def update_file(
file_id: str,
file_data: FileItem,
- current_user: Dict[str, Any] = Depends(getCurrentActiveUser)
+ current_user: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""
Update file metadata
"""
try:
- myInterface = getInterface(current_user)
+ interfaceLucydom = lucydomInterface.getInterface(current_user)
# Get the file from the database
- file = myInterface.getFile(file_id)
+ file = interfaceLucydom.getFile(file_id)
if not file:
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):
raise HTTPException(status_code=403, detail="Not authorized to update this file")
- # Update file metadata
- update_data = file_data.dict(exclude_unset=True)
- update_data["modified_at"] = datetime.now(timezone.utc)
+ # Convert FileItem to dict for interface
+ update_data = file_data.model_dump()
- # Update in database
- result = myInterface.updateFile(file_id, update_data)
+ # Update the file
+ result = interfaceLucydom.updateFile(file_id, update_data)
if not result:
raise HTTPException(status_code=500, detail="Failed to update file")
- # Get updated file
- updated_file = myInterface.getFile(file_id)
- return updated_file
+ # Get updated file and convert to FileItem
+ updated_file = interfaceLucydom.getFile(file_id)
+ return FileItem(**updated_file)
except HTTPException as he:
raise he
@@ -184,31 +183,31 @@ async def update_file(
@router.delete("/{fileId}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_file(
fileId: str,
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Deletes a file by its ID from the database"""
try:
- myInterface = getInterface(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Delete file via LucyDOM interface
- myInterface.deleteFile(fileId)
+ interfaceLucydom.deleteFile(fileId)
# Return successful deletion without content (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)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
- except FilePermissionError as e:
+ except lucydomInterface.FilePermissionError as e:
logger.warning(f"No permission to delete file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
- except FileDeletionError as e:
+ except lucydomInterface.FileDeletionError as e:
logger.error(f"Error deleting file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -223,14 +222,14 @@ async def delete_file(
@router.get("/stats", response_model=Dict[str, Any])
async def get_file_stats(
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Returns statistics about the stored files"""
try:
- myInterface = getInterface(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get all files - metadata only
- allFiles = myInterface.getAllFiles()
+ allFiles = interfaceLucydom.getAllFiles()
# Calculate statistics
totalFiles = len(allFiles)
diff --git a/modules/routes/routeGeneral.py b/modules/routes/routeGeneral.py
index 82f3c3f8..2268a3b5 100644
--- a/modules/routes/routeGeneral.py
+++ b/modules/routes/routeGeneral.py
@@ -7,21 +7,21 @@ from datetime import timedelta
import pathlib
import os
import logging
+from pathlib import Path as FilePath
from modules.shared.configuration import APP_CONFIG
-from modules.security.auth import (
- createAccessToken,
- getCurrentActiveUser,
- getRootInterface,
- ACCESS_TOKEN_EXPIRE_MINUTES
-)
+import modules.security.auth as auth
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
-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"
os.makedirs(staticFolder, exist_ok=True)
@@ -30,22 +30,14 @@ router.mount("/static", StaticFiles(directory=str(staticFolder), html=True), nam
logger = logging.getLogger(__name__)
-@router.get("/favicon.ico", tags=["General"])
-async def favicon():
- return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
-
@router.get("/", tags=["General"])
async def root():
"""API status endpoint"""
- return {"status": "online", "message": "Data Platform API is active"}
-
-@router.get("/api/test", tags=["General"])
-async def get_test():
- return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
-
-@router.options("/{fullPath:path}", tags=["General"])
-async def options_route(fullPath: str):
- return Response(status_code=200)
+ return {
+ "status": "online",
+ "message": "Data Platform API is active",
+ "allowedOrigins": f"Allowed origins are {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
+ }
@router.get("/api/environment", tags=["General"])
async def get_environment():
@@ -57,29 +49,22 @@ async def get_environment():
# 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"])
async def login_for_access_token(formData: OAuth2PasswordRequestForm = Depends()):
# Create a new gateway interface instance with admin context
- myInterface = getRootInterface()
+ interfaceRoot = auth.getRootInterface()
try:
# Authenticate user
- user = myInterface.authenticateUser(formData.username, formData.password)
+ user = interfaceRoot.authenticateUser(formData.username, formData.password)
- # Create token with mandate ID and user ID
- accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
- accessToken = createAccessToken(
- data={
- "sub": user["username"],
- "_mandateId": str(user["_mandateId"]), # Ensure string
- "_userId": str(user["id"]), # Ensure string
- "authenticationAuthority": user.get("authenticationAuthority", "local") # Add auth authority
- },
- expiresDelta=accessTokenExpires
- )
-
- logger.info(f"User {user['username']} successfully logged in with context: _mandateId={user['_mandateId']}, _userId={user['id']}, auth={user.get('authenticationAuthority', 'local')}")
- return {"accessToken": accessToken, "tokenType": "bearer"}
+ # Authenticate user and get token
+ token = interfaceRoot.authenticateAndGetToken(formData.username, formData.password)
+ return token
except ValueError as e:
# Handle authentication errors
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"])
-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
-@router.post("/api/users/register", response_model=Dict[str, Any], tags=["General"])
-async def register_user(userData: Dict[str, Any]):
+@router.post("/api/user/register", response_model=gatewayModel.User, tags=["General"])
+async def register_user(userData: gatewayModel.User):
"""Register a new user."""
try:
- logger.debug("Received registration request")
-
- # Create a new gateway interface instance with admin context
- myInterface = getRootInterface()
-
- # Check required fields
- if not userData or not isinstance(userData, dict):
- raise HTTPException(
- status_code=status.HTTP_400_BAD_REQUEST,
- detail="Invalid user data format"
- )
-
- if not userData.get("username") or not userData.get("password"):
- raise HTTPException(
- status_code=status.HTTP_400_BAD_REQUEST,
- detail="Username and password are required"
- )
-
- # Create user data in same mandate as admin user
- userData = {
- "username": userData["username"],
- "password": userData["password"],
- "email": userData.get("email"),
- "fullName": userData.get("fullName"),
- "language": userData.get("language", "en"),
- "disabled": False,
- "privilege": "user"
- }
-
- # Create the user
- try:
- createdUser = myInterface.createUser(**userData)
- except ValueError as e:
- raise HTTPException(
- status_code=status.HTTP_400_BAD_REQUEST,
- detail=str(e)
- )
-
- # Verify the user was created
- if not createdUser:
- raise HTTPException(
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
- detail="Failed to create user"
- )
-
- return createdUser
-
- except HTTPException:
- raise
+ interfaceRoot = auth.getRootInterface()
+ return interfaceRoot.registerUser(userData.model_dump())
+ except ValueError as e:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail=str(e)
+ )
except Exception as e:
- logger.error(f"Error in user registration: {str(e)}")
+ logger.error(f"Error registering user: {str(e)}")
raise HTTPException(
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"])
@@ -169,30 +112,15 @@ async def check_username_availability(
):
"""Check if a username is available for registration"""
try:
- # Create a new gateway interface instance with root context
- myInterface = getRootInterface()
-
- # Check if user exists
- existingUser = myInterface.getUserByUsername(username)
-
- if not existingUser:
- return {"available": True}
-
- # If user exists, check authentication authority
- if existingUser.get("authenticationAuthority") == authenticationAuthority:
- return {
- "available": False,
- "message": f"Username already exists with {authenticationAuthority} authentication"
- }
- else:
- return {
- "available": True,
- "message": f"Username exists but with different authentication authority"
- }
-
+ interfaceRoot = auth.getRootInterface()
+ return interfaceRoot.checkUsernameAvailability(username, authenticationAuthority)
except Exception as e:
logger.error(f"Error checking username availability: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to check username availability: {str(e)}"
- )
\ No newline at end of file
+ )
+
+@router.get("/favicon.ico", tags=["General"])
+async def favicon():
+ return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
diff --git a/modules/routes/routeMandates.py b/modules/routes/routeMandates.py
index aa98891a..d8bdea81 100644
--- a/modules/routes/routeMandates.py
+++ b/modules/routes/routeMandates.py
@@ -1,9 +1,13 @@
-from fastapi import APIRouter, HTTPException, Depends, Body, status
-from typing import Dict, Any, List
+from fastapi import APIRouter, HTTPException, Depends, Body, Path, Request
+from typing import List, Dict, Any, Optional
+from fastapi import status
import logging
-from modules.security.auth import getCurrentActiveUser
-from modules.interfaces.gatewayInterface import getInterface
+# Import auth module
+import modules.security.auth as auth
+
+# Import interfaces
+import modules.interfaces.gatewayInterface as gatewayInterface
from modules.interfaces.gatewayModel import Mandate, getModelAttributes
# Configure logger
@@ -19,11 +23,11 @@ router = APIRouter(
)
@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"""
try:
- myInterface = getInterface(currentUser)
- return myInterface.getMandates()
+ interfaceGateway = gatewayInterface.getInterface(currentUser)
+ return interfaceGateway.getMandates()
except Exception as e:
logger.error(f"Error getting mandates: {str(e)}")
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"])
async def get_mandate(
mandateId: str,
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Get a specific mandate by ID"""
try:
- myInterface = getInterface(currentUser)
- mandate = myInterface.getMandateById(mandateId)
+ interfaceGateway = gatewayInterface.getInterface(currentUser)
+ mandate = interfaceGateway.getMandateById(mandateId)
if not mandate:
raise HTTPException(
@@ -57,30 +61,17 @@ async def get_mandate(
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(
- mandateData: Dict[str, Any],
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ mandateData: Mandate,
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Create a new mandate"""
try:
- myInterface = 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]
+ interfaceGateway = gatewayInterface.getInterface(currentUser)
try:
- createdMandate = myInterface.createMandate(**filteredData)
+ createdMandate = interfaceGateway.createMandate(mandateData)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -103,33 +94,27 @@ async def create_mandate(
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(
mandateId: str,
- mandateData: Dict[str, Any],
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ mandateData: Mandate,
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Update an existing mandate"""
try:
- myInterface = getInterface(currentUser)
+ interfaceGateway = gatewayInterface.getInterface(currentUser)
# Check if mandate exists
- existingMandate = myInterface.getMandateById(mandateId)
+ existingMandate = interfaceGateway.getMandateById(mandateId)
if not existingMandate:
raise HTTPException(
status_code=status.HTTP_404_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
try:
- updatedMandate = myInterface.updateMandate(mandateId, **filteredData)
+ updatedMandate = interfaceGateway.updateMandate(mandateId, mandateData)
except ValueError as e:
raise HTTPException(
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"])
async def delete_mandate(
mandateId: str,
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Delete a mandate"""
try:
- myInterface = getInterface(currentUser)
+ interfaceGateway = gatewayInterface.getInterface(currentUser)
# Check if mandate exists
- existingMandate = myInterface.getMandateById(mandateId)
+ existingMandate = interfaceGateway.getMandateById(mandateId)
if not existingMandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -171,7 +156,7 @@ async def delete_mandate(
# Delete mandate
try:
- myInterface.deleteMandate(mandateId)
+ interfaceGateway.deleteMandate(mandateId)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
diff --git a/modules/routes/routeMsft.py b/modules/routes/routeMsft.py
index 12352d50..f34613d3 100644
--- a/modules/routes/routeMsft.py
+++ b/modules/routes/routeMsft.py
@@ -5,8 +5,18 @@ import json
from typing import Dict, Any, Optional, List
from datetime import datetime, timedelta
-from modules.security.auth import getCurrentActiveUser, createAccessToken, ACCESS_TOKEN_EXPIRE_MINUTES, getRootInterface
-from modules.interfaces.msftInterface import getInterface as getMsftInterface
+# Import auth module
+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
logger = logging.getLogger(__name__)
@@ -29,7 +39,7 @@ async def login():
"""Initiate Microsoft login for the current user"""
try:
# Get Microsoft interface
- msft = getMsftInterface({"_mandateId": "root", "id": "root"})
+ msft = msftInterface.getInterface({"_mandateId": "root", "id": "root"})
# Get login URL
auth_url = msft.initiateLogin()
@@ -54,7 +64,7 @@ async def auth_callback(code: str, state: str, request: Request):
"""Handle Microsoft OAuth callback"""
try:
# Get Microsoft interface
- msft = getMsftInterface({"_mandateId": "root", "id": "root"})
+ msft = msftInterface.getInterface({"_mandateId": "root", "id": "root"})
# Handle auth callback
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
- gateway = getRootInterface()
+ gateway = auth.getRootInterface()
# Check if user exists
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
- access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
- access_token = createAccessToken(
+ access_token_expires = timedelta(minutes=auth.ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = auth.createAccessToken(
data={
"sub": user["username"],
"_mandateId": str(user["_mandateId"]),
@@ -191,68 +201,97 @@ async def auth_callback(code: str, state: str, request: Request):
detail=f"Authentication failed: {str(e)}"
)
-@router.get("/status")
-async def auth_status(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
+@router.get("/status", response_model=MsftAuthStatus)
+async def auth_status(currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)):
"""Check Microsoft authentication status"""
try:
# Get Microsoft interface
- msft = getMsftInterface(currentUser)
+ msft = msftInterface.getInterface(currentUser)
# Get current user token and info
user_info, access_token = msft.getCurrentUserToken()
if not user_info or not access_token:
- return JSONResponse({
- "authenticated": False,
- "message": "Not authenticated with Microsoft"
- })
+ return MsftAuthStatus(
+ authenticated=False,
+ message="Not authenticated with Microsoft"
+ )
- return JSONResponse({
- "authenticated": True,
- "user": user_info
- })
+ # Convert user_info to MsftUserInfo model
+ user_info_model = MsftUserInfo(**user_info)
+
+ return MsftAuthStatus(
+ authenticated=True,
+ user=user_info_model
+ )
except Exception as e:
logger.error(f"Error checking authentication status: {str(e)}")
- return JSONResponse({
- "authenticated": False,
- "message": f"Error checking authentication status: {str(e)}"
- })
+ return MsftAuthStatus(
+ authenticated=False,
+ message=f"Error checking authentication status: {str(e)}"
+ )
-@router.post("/save-token")
-async def save_token(token_data: Dict[str, Any], currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
+@router.get("/token", response_model=MsftTokenResponse)
+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"""
try:
# Get Microsoft interface
- msft = getMsftInterface(currentUser)
+ msft = msftInterface.getInterface(currentUser)
# Save token
- success = msft.saveMsftToken(token_data)
+ success = msft.saveMsftToken(token_data.model_dump())
if success:
- return JSONResponse({
- "success": True,
- "message": "Token saved successfully"
- })
+ return MsftSaveTokenResponse(
+ success=True,
+ message="Token saved successfully",
+ token=token_data
+ )
else:
- return JSONResponse({
- "success": False,
- "message": "Failed to save token"
- })
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail="Failed to save token"
+ )
except Exception as e:
logger.error(f"Error saving token: {str(e)}")
- return JSONResponse({
- "success": False,
- "message": f"Error saving token: {str(e)}"
- })
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=f"Error saving token: {str(e)}"
+ )
@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"""
try:
# Get Microsoft interface
- msft = getMsftInterface(currentUser)
+ msft = msftInterface.getInterface(currentUser)
# Delete token
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,
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)}
diff --git a/modules/routes/routePrompts.py b/modules/routes/routePrompts.py
index 14810a2d..9239995a 100644
--- a/modules/routes/routePrompts.py
+++ b/modules/routes/routePrompts.py
@@ -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 fastapi import status
from datetime import datetime
+import logging
# Import auth module
-from modules.security.auth import getCurrentActiveUser
+import modules.security.auth as auth
-# Import interface
-from modules.interfaces.lucydomInterface import getInterface
+# Import interfaces
+import modules.interfaces.lucydomInterface as lucydomInterface
from modules.interfaces.lucydomModel import Prompt, getModelAttributes
+# Configure logger
+logger = logging.getLogger(__name__)
+
# Model attributes for Prompt
promptAttributes = getModelAttributes(Prompt)
@@ -20,83 +24,75 @@ router = APIRouter(
responses={404: {"description": "Not found"}}
)
-@router.get("", response_model=List[Dict[str, Any]])
+@router.get("", response_model=List[Prompt])
async def get_prompts(
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Get all prompts"""
- myInterface = getInterface(currentUser)
- return myInterface.getAllPrompts()
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
+ 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(
- prompt: Dict[str, Any] = Body(...),
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ prompt: Prompt,
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Create a new prompt"""
- myInterface = getInterface(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
- # Required fields with default values
- content = prompt.get("content", "")
- name = prompt.get("name", "New Prompt")
+ # Convert Prompt to dict for interface
+ prompt_data = prompt.model_dump()
# Create prompt
- newPrompt = myInterface.createPrompt(
- content=content,
- name=name
- )
+ newPrompt = interfaceLucydom.createPrompt(prompt_data)
# Set current time for createdAt if it exists in the model
if "createdAt" in promptAttributes and hasattr(newPrompt, "createdAt"):
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(
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"""
- myInterface = getInterface(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get prompt
- prompt = myInterface.getPrompt(promptId)
+ prompt = interfaceLucydom.getPrompt(promptId)
if not prompt:
raise HTTPException(
status_code=status.HTTP_404_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(
promptId: str = Path(..., description="ID of the prompt to update"),
- promptData: Dict[str, Any] = Body(...),
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ promptData: Prompt = Body(...),
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Update an existing prompt"""
- myInterface = getInterface(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Check if the prompt exists
- existingPrompt = myInterface.getPrompt(promptId)
+ existingPrompt = interfaceLucydom.getPrompt(promptId)
if not existingPrompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {promptId} not found"
)
- # Standard fields for update
- content = promptData.get("content")
- name = promptData.get("name")
+ # Convert Prompt to dict for interface
+ update_data = promptData.model_dump()
# Update prompt
- updatedPrompt = myInterface.updatePrompt(
- promptId=promptId,
- content=content,
- name=name
- )
+ updatedPrompt = interfaceLucydom.updatePrompt(promptId, update_data)
if not updatedPrompt:
raise HTTPException(
@@ -104,25 +100,25 @@ async def update_prompt(
detail="Error updating the prompt"
)
- return updatedPrompt
+ return Prompt(**updatedPrompt)
@router.delete("/{promptId}", response_model=Dict[str, Any])
async def delete_prompt(
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"""
- myInterface = getInterface(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Check if the prompt exists
- existingPrompt = myInterface.getPrompt(promptId)
+ existingPrompt = interfaceLucydom.getPrompt(promptId)
if not existingPrompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {promptId} not found"
)
- success = myInterface.deletePrompt(promptId)
+ success = interfaceLucydom.deletePrompt(promptId)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
diff --git a/modules/routes/routeUsers.py b/modules/routes/routeUsers.py
index b4dcb38b..dc4f9bd9 100644
--- a/modules/routes/routeUsers.py
+++ b/modules/routes/routeUsers.py
@@ -5,17 +5,17 @@ from datetime import datetime
import logging
# Import auth module
-from modules.security.auth import getCurrentActiveUser, getRootInterface
+import modules.security.auth as auth
# Import interfaces
-from modules.interfaces.gatewayInterface import getInterface
-from modules.interfaces.gatewayModel import User, getModelAttributes
+import modules.interfaces.gatewayInterface as gatewayInterface
+import modules.interfaces.gatewayModel as gatewayModel
# Configure logger
logger = logging.getLogger(__name__)
# Model attributes for User
-userAttributes = getModelAttributes(User)
+userAttributes = gatewayModel.getModelAttributes(gatewayModel.User)
router = APIRouter(
prefix="/api/users",
@@ -24,11 +24,11 @@ router = APIRouter(
)
@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"""
try:
- myInterface = getInterface(currentUser)
- return myInterface.getUsers()
+ interfaceGateway = gatewayInterface.getInterface(currentUser)
+ return interfaceGateway.getUsers()
except Exception as e:
logger.error(f"Error getting users: {str(e)}")
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"])
async def get_user(
userId: str,
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Get a specific user by ID"""
try:
- myInterface = getInterface(currentUser)
- user = myInterface.getUserById(userId)
+ interfaceGateway = gatewayInterface.getInterface(currentUser)
+ user = interfaceGateway.getUserById(userId)
if not user:
raise HTTPException(
@@ -62,31 +62,27 @@ async def get_user(
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(
- userData: Dict[str, Any],
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ userData: gatewayModel.User,
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Create a new user"""
try:
# Get admin user for user creation
- myInterface = 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]
+ interfaceRoot = auth.getRootInterface()
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:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -109,34 +105,28 @@ async def create_user(
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(
userId: str,
- userData: Dict[str, Any],
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ userData: gatewayModel.User,
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Update an existing user"""
try:
# Get admin user for user updates
- myInterface = getRootInterface()
+ interfaceRoot = auth.getRootInterface()
# Check if user exists
- existingUser = myInterface.getUserById(userId)
+ existingUser = interfaceRoot.getUserById(userId)
if not existingUser:
raise HTTPException(
status_code=status.HTTP_404_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
try:
- updatedUser = myInterface.updateUser(userId, **filteredData)
+ updatedUser = interfaceRoot.updateUser(userId, userData)
except ValueError as e:
raise HTTPException(
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"])
async def delete_user(
userId: str,
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Delete a user"""
try:
- # Get admin user for user deletion
- myInterface = getRootInterface()
-
- # 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)
- )
-
+ interfaceGateway = gatewayInterface.getInterface(currentUser)
+ interfaceGateway.deleteUser(userId)
return {"message": f"User {userId} deleted successfully"}
- except HTTPException:
- raise
+ except ValueError as e:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail=str(e)
+ )
except Exception as e:
logger.error(f"Error deleting user {userId}: {str(e)}")
raise HTTPException(
diff --git a/modules/routes/routeWorkflows.py b/modules/routes/routeWorkflows.py
index c9fa3bf1..35887b18 100644
--- a/modules/routes/routeWorkflows.py
+++ b/modules/routes/routeWorkflows.py
@@ -10,18 +10,33 @@ from typing import List, Dict, Any, Optional
from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query, Response, status
from datetime import datetime
+# Import auth module
+import modules.security.auth as auth
+
# Import interfaces
-from modules.interfaces.lucydomInterface import getInterface as getInterfaceLucydom
-from modules.interfaces.msftInterface import getInterface as getInterfaceMsft
-from modules.security.auth import getCurrentActiveUser
+import modules.interfaces.lucydomInterface as lucydomInterface
+import modules.interfaces.msftInterface as msftInterface
+
+# Import workflow manager
from modules.workflow.workflowManager import getWorkflowManager
# Import models
-from modules.interfaces import lucydomModel as Models
+from modules.interfaces.lucydomModel import (
+ ChatWorkflow,
+ ChatMessage,
+ ChatLog,
+ ChatStat,
+ ChatDocument,
+ UserInputRequest,
+ getModelAttributes
+)
# Configure logger
logger = logging.getLogger(__name__)
+# Model attributes for ChatWorkflow
+workflowAttributes = getModelAttributes(ChatWorkflow)
+
# Create router for workflow endpoints
router = APIRouter(
prefix="/api/workflows",
@@ -29,12 +44,33 @@ router = APIRouter(
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
-@router.post("/start", response_model=Dict[str, Any])
-async def startWorkflow(
+@router.post("/start", response_model=ChatWorkflow)
+async def start_workflow(
workflowId: Optional[str] = Query(None, description="Optional ID of the workflow to continue"),
- userInput: Models.UserInputRequest = Body(...),
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ userInput: UserInputRequest = Body(...),
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""
Starts a new workflow or continues an existing one.
@@ -42,27 +78,17 @@ async def startWorkflow(
"""
try:
# Get interface with current user context
- interfaceBase = getInterfaceLucydom(currentUser)
- interfaceMsft = getInterfaceMsft(currentUser)
-
- # Convert the user input to a dictionary
- userInputDict = {
- "prompt": userInput.prompt,
- "listFileId": userInput.listFileId
- }
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
+ interfaceMsft = msftInterface.getInterface(currentUser)
# Get workflow manager with interface
- workflowManager = await getWorkflowManager(interfaceBase, interfaceMsft)
+ workflowManager = await getWorkflowManager(interfaceLucydom, interfaceMsft)
# 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)
- return {
- "id": workflow.get("id"),
- "status": workflow.get("status", "running"),
- "message": "Workflow initialized and processing started"
- }
+ return ChatWorkflow(**workflow)
except Exception as e:
logger.error(f"Error starting workflow: {str(e)}", exc_info=True)
raise HTTPException(
@@ -71,19 +97,19 @@ async def startWorkflow(
)
# State 8: Workflow Stopped endpoint
-@router.post("/{workflowId}/stop", response_model=Dict[str, Any])
-async def stopWorkflow(
+@router.post("/{workflowId}/stop", response_model=ChatWorkflow)
+async def stop_workflow(
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."""
try:
# Get interface with current user context
- interfaceBase = getInterfaceLucydom(currentUser)
- interfaceMsft = getInterfaceMsft(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
+ interfaceMsft = msftInterface.getInterface(currentUser)
# Verify workflow exists and belongs to user
- workflow = interfaceBase.getWorkflow(workflowId)
+ workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -97,14 +123,10 @@ async def stopWorkflow(
)
# Stop the workflow
- workflowManager = await getWorkflowManager(interfaceBase, interfaceMsft)
+ workflowManager = await getWorkflowManager(interfaceLucydom, interfaceMsft)
stoppedWorkflow = await workflowManager.workflowStop(workflowId)
- return {
- "id": workflowId,
- "status": stoppedWorkflow.get("status", "stopped"),
- "message": "Workflow has been stopped"
- }
+ return ChatWorkflow(**stoppedWorkflow)
except HTTPException:
raise
except Exception as e:
@@ -116,17 +138,17 @@ async def stopWorkflow(
# State 11: Workflow Reset/Deletion endpoint
@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"),
- currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
+ currentUser: Dict[str, Any] = Depends(auth.getCurrentActiveUser)
):
"""Deletes a workflow and its associated data."""
try:
# Get interface with current user context
- interfaceBase = getInterfaceLucydom(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists
- workflow = interfaceBase.getWorkflow(workflowId)
+ workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -141,7 +163,7 @@ async def deleteWorkflow(
)
# Delete workflow
- success = interfaceBase.deleteWorkflow(workflowId)
+ success = interfaceLucydom.deleteWorkflow(workflowId)
if not success:
raise HTTPException(
@@ -162,57 +184,26 @@ async def deleteWorkflow(
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
-@router.get("/{workflowId}/status", response_model=Dict[str, Any])
-async def getWorkflowStatus(
+@router.get("/{workflowId}/status", response_model=ChatWorkflow)
+async def get_workflow_status(
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."""
try:
# Get interface with current user context
- interfaceBase = getInterfaceLucydom(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Retrieve workflow
- workflow = interfaceBase.getWorkflow(workflowId)
+ workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflowId} not found"
)
- # Create status response
- 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
+ return ChatWorkflow(**workflow)
except HTTPException:
raise
except Exception as e:
@@ -223,19 +214,19 @@ async def getWorkflowStatus(
)
# API Endpoint for workflow logs with selective data transfer
-@router.get("/{workflowId}/logs", response_model=List[Dict[str, Any]])
-async def getWorkflowLogs(
+@router.get("/{workflowId}/logs", response_model=List[ChatLog])
+async def get_workflow_logs(
workflowId: str = Path(..., description="ID of the workflow"),
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."""
try:
# Get interface with current user context
- interfaceBase = getInterfaceLucydom(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists
- workflow = interfaceBase.getWorkflow(workflowId)
+ workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -243,7 +234,7 @@ async def getWorkflowLogs(
)
# Get all logs
- allLogs = interfaceBase.getWorkflowLogs(workflowId)
+ allLogs = interfaceLucydom.getWorkflowLogs(workflowId)
# Apply selective data transfer if logId is provided
if logId:
@@ -251,9 +242,9 @@ async def getWorkflowLogs(
logIndex = next((i for i, log in enumerate(allLogs) if log.get("id") == logId), -1)
if logIndex >= 0:
# 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:
raise
except Exception as e:
@@ -264,19 +255,19 @@ async def getWorkflowLogs(
)
# API Endpoint for workflow messages with selective data transfer
-@router.get("/{workflowId}/messages", response_model=List[Dict[str, Any]])
-async def getWorkflowMessages(
+@router.get("/{workflowId}/messages", response_model=List[ChatMessage])
+async def get_workflow_messages(
workflowId: str = Path(..., description="ID of the workflow"),
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."""
try:
# Get admin user for workflow operations
- interfaceBase = getInterfaceLucydom(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists
- workflow = interfaceBase.getWorkflow(workflowId)
+ workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -284,7 +275,7 @@ async def getWorkflowMessages(
)
# Get all messages
- allMessages = interfaceBase.getWorkflowMessages(workflowId)
+ allMessages = interfaceLucydom.getWorkflowMessages(workflowId)
# Apply selective data transfer if messageId is provided
if messageId:
@@ -298,11 +289,11 @@ async def getWorkflowMessages(
message = next((msg for msg in allMessages if msg.get("id") == msgId), None)
if message:
filteredMessages.append(message)
- return filteredMessages
+ return [ChatMessage(**msg) for msg in filteredMessages]
# Sort messages by sequenceNo
allMessages.sort(key=lambda x: x.get("sequenceNo", 0))
- return allMessages
+ return [ChatMessage(**msg) for msg in allMessages]
except HTTPException:
raise
except Exception as e:
@@ -315,18 +306,18 @@ async def getWorkflowMessages(
# Document Management Endpoints
@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"),
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."""
try:
# Get admin user for workflow operations
- interfaceBase = getInterfaceLucydom(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists and belongs to user
- workflow = interfaceBase.getWorkflow(workflowId)
+ workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -334,7 +325,7 @@ async def deleteWorkflowMessage(
)
# Delete the message
- success = interfaceBase.deleteWorkflowMessage(workflowId, messageId)
+ success = interfaceLucydom.deleteWorkflowMessage(workflowId, messageId)
if not success:
raise HTTPException(
@@ -346,7 +337,7 @@ async def deleteWorkflowMessage(
messageIds = workflow.get("messageIds", [])
if messageId in messageIds:
messageIds.remove(messageId)
- interfaceBase.updateWorkflow(workflowId, {"messageIds": messageIds})
+ interfaceLucydom.updateWorkflow(workflowId, {"messageIds": messageIds})
return {
"workflowId": workflowId,
@@ -363,19 +354,19 @@ async def deleteWorkflowMessage(
)
@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"),
messageId: str = Path(..., description="ID of the message"),
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."""
try:
# Get admin user for workflow operations
- interfaceBase = getInterfaceLucydom(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Verify workflow exists and belongs to user
- workflow = interfaceBase.getWorkflow(workflowId)
+ workflow = interfaceLucydom.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -383,7 +374,7 @@ async def deleteFileFromMessage(
)
# Delete file reference from message
- success = interfaceBase.deleteFileFromMessage(workflowId, messageId, fileId)
+ success = interfaceLucydom.deleteFileFromMessage(workflowId, messageId, fileId)
if not success:
raise HTTPException(
@@ -408,18 +399,18 @@ async def deleteFileFromMessage(
# File preview and download routes
-@router.get("/files/{fileId}/preview", response_model=Dict[str, Any])
-async def previewFile(
+@router.get("/files/{fileId}/preview", response_model=ChatDocument)
+async def preview_file(
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."""
try:
# Get admin user for workflow operations
- interfaceBase = getInterfaceLucydom(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get file metadata
- file = interfaceBase.getFile(fileId)
+ file = interfaceLucydom.getFile(fileId)
if not file:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -427,7 +418,7 @@ async def previewFile(
)
# Get file data (limited for preview)
- fileData = interfaceBase.getFileData(fileId)
+ fileData = interfaceLucydom.getFileData(fileId)
if fileData is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -445,7 +436,7 @@ async def previewFile(
previewData = None
# 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]:
# Use the flag from the database
base64Encoded = fileDataEntries[0]["base64Encoded"]
@@ -475,17 +466,24 @@ async def previewFile(
previewData = base64.b64encode(fileData).decode('utf-8')
base64Encoded = True
- # Return file metadata with preview and base64Encoded flag
- return {
- "id": fileId,
- "name": file.get("name"),
- "mimeType": mimeType,
- "size": file.get("size"),
- "creationDate": file.get("creationDate"),
- "isPreviewable": isText or mimeType.startswith("image/"),
- "preview": previewData,
- "base64Encoded": base64Encoded
- }
+ # Create ChatDocument instance
+ return ChatDocument(
+ id=fileId,
+ fileId=fileId,
+ fileName=file.get("name"),
+ fileSize=file.get("size"),
+ mimeType=mimeType,
+ contents=[{
+ "sequenceNr": 1,
+ "name": file.get("name"),
+ "mimeType": mimeType,
+ "data": previewData,
+ "metadata": {
+ "base64Encoded": base64Encoded,
+ "isPreviewable": isText or mimeType.startswith("image/")
+ }
+ }]
+ )
except HTTPException:
raise
except Exception as e:
@@ -496,17 +494,17 @@ async def previewFile(
)
@router.get("/files/{fileId}/download")
-async def downloadFile(
+async def download_file(
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."""
try:
# Get admin user for workflow operations
- interfaceBase = getInterfaceLucydom(currentUser)
+ interfaceLucydom = lucydomInterface.getInterface(currentUser)
# Get file data
- fileInfo = interfaceBase.downloadFile(fileId)
+ fileInfo = interfaceLucydom.downloadFile(fileId)
if not fileInfo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -529,159 +527,3 @@ async def downloadFile(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
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)}"
- )
\ No newline at end of file
diff --git a/modules/security/auth.py b/modules/security/auth.py
index a8810282..7c7ad339 100644
--- a/modules/security/auth.py
+++ b/modules/security/auth.py
@@ -129,7 +129,20 @@ def getCurrentActiveUser(currentUser: Dict[str, Any] = Depends(_getCurrentUser))
def getRootInterface() -> Dict[str, Any]:
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:
logger.error(f"Error getting root access: {str(e)}")
raise HTTPException(
diff --git a/modules/workflow/workflowManager.py b/modules/workflow/workflowManager.py
index d3661d33..3e495de2 100644
--- a/modules/workflow/workflowManager.py
+++ b/modules/workflow/workflowManager.py
@@ -17,6 +17,7 @@ from modules.shared.mimeUtils import isTextMimeType
# Required imports
from modules.workflow.agentRegistry import getAgentRegistry
from modules.workflow.documentProcessor import getDocumentContents
+from modules.interfaces.lucydomInterface import UserInputRequest
# Configure logger
logger = logging.getLogger(__name__)
@@ -89,26 +90,26 @@ class WorkflowManager:
### Workflow State Machine Implementation
- async def workflowStart(self, userInput: Dict[str, Any], workflowId: Optional[str] = None) -> Dict[str, Any]:
- """
- Main entry point for starting or continuing a workflow (State 1: Workflow Initialization).
- Initializes a new workflow or loads an existing one based on workflowId.
-
- Args:
- userInput: User input with prompt and optional file list
- workflowId: Optional workflow ID to continue an existing workflow
+ async def workflowStart(self, userInput: UserInputRequest, workflowId: Optional[str] = None) -> Dict[str, Any]:
+ """Starts a new workflow or continues an existing one."""
+ try:
+ # Convert UserInputRequest to dict for processing
+ userInputDict = userInput.model_dump()
- Returns:
- Initialized workflow object with status "running"
- """
- # 1. Initialize workflow or load existing one
- workflow = self.workflowInit(workflowId)
- self.logAdd(workflow, "Starting workflow processing", level="info", progress=0)
-
- # Start asynchronous processing
- asyncio.create_task(self.workflowProcess(userInput, workflow))
-
- return workflow
+ # Initialize or load workflow
+ if workflowId:
+ workflow = self.service.base.getWorkflow(workflowId)
+ if not workflow:
+ raise ValueError(f"Workflow {workflowId} not found")
+ else:
+ workflow = self.workflowInit()
+
+ # Process the workflow
+ return await self.workflowProcess(userInputDict, workflow)
+
+ except Exception as e:
+ logger.error(f"Error in workflowStart: {str(e)}")
+ raise
### Forces exit
diff --git a/notes/changelog.txt b/notes/changelog.txt
index 43e107bc..a4235ca7 100644
--- a/notes/changelog.txt
+++ b/notes/changelog.txt
@@ -1,35 +1,32 @@
....................... 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:
- - check and ensurem that database is available
- - initializeDatabase
- - initializeRecords
+TODO: Interface assignment overall to adapt
-- each route, which needs authentication, has: "currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)" as parameter
- - 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
+TODO: Implement userid,mandateid change overall
- 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
! 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
@@ -106,6 +94,34 @@ Tools to transfer incl funds:
----------------------- 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:
- 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).