working set for clean data references

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

View file

@ -12,27 +12,16 @@ class DatabaseConnector:
A connector for JSON-based data storage.
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)

View file

@ -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

View file

@ -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

View file

@ -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"}),

View file

@ -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

View file

@ -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]
interface = _lucydomInterfaces["default"]
if currentUser:
interface.setUserContext(currentUser)
else:
logger.info("Returning interface without user context")
return interface

View file

@ -18,8 +18,21 @@ def getModelAttributes(modelClass):
class Label(BaseModel):
"""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

View file

@ -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)}")

View file

@ -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={})
}
}
# Response models for Microsoft routes
class MsftAuthStatus(BaseModel):
"""Response model for Microsoft authentication status"""
authenticated: bool
message: Optional[str] = None
user: Optional[MsftUserInfo] = None
class MsftTokenResponse(BaseModel):
"""Response model for Microsoft token"""
token: MsftToken
class MsftSaveTokenResponse(BaseModel):
"""Response model for saving Microsoft token"""
success: bool
message: str
token: Optional[MsftToken] = None

View file

@ -5,12 +5,40 @@ import inspect
import importlib
import 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(

View file

@ -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)

View file

@ -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)}"
)
)
@router.get("/favicon.ico", tags=["General"])
async def favicon():
return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")

View file

@ -1,9 +1,13 @@
from fastapi import APIRouter, HTTPException, Depends, Body, status
from 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,

View file

@ -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)}

View file

@ -1,15 +1,19 @@
from fastapi import APIRouter, HTTPException, Depends, Body, Query, Path
from fastapi import APIRouter, HTTPException, Depends, Body, Query, Path, Request
from typing import List, Dict, Any, Optional
from 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,

View file

@ -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(

View file

@ -10,18 +10,33 @@ from typing import List, Dict, Any, Optional
from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query, Response, status
from 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)}"
)

View file

@ -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(

View file

@ -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

View file

@ -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).