Merge pull request #7 from valueonag/dev-patrick
access extracted from interface and all routes in separate modules
This commit is contained in:
commit
ab28684dc8
14 changed files with 392 additions and 1330 deletions
75
app.py
75
app.py
|
|
@ -102,7 +102,7 @@ app = FastAPI(
|
|||
# CORS configuration using environment variables
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins= get_allowed_origins(), # ["http://localhost:8080","http://localhost:8081"], #get_allowed_origins(),
|
||||
allow_origins= get_allowed_origins(),
|
||||
allow_credentials=True,
|
||||
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
allow_headers=["*"],
|
||||
|
|
@ -118,80 +118,16 @@ os.makedirs(staticFolder, exist_ok=True)
|
|||
# Mount static files with proper configuration
|
||||
app.mount("/static", StaticFiles(directory=str(staticFolder), html=True), name="static")
|
||||
|
||||
# Add favicon route
|
||||
@app.get("/favicon.ico")
|
||||
async def favicon():
|
||||
return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
|
||||
|
||||
# General Elements
|
||||
@app.get("/", tags=["General"])
|
||||
async def root():
|
||||
"""API status endpoint"""
|
||||
return {"status": "online", "message": "Data Platform API is active"}
|
||||
|
||||
@app.get("/api/test", tags=["General"])
|
||||
async def getTest():
|
||||
return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
|
||||
|
||||
@app.options("/{fullPath:path}", tags=["General"])
|
||||
async def optionsRoute(fullPath: str):
|
||||
return Response(status_code=200)
|
||||
|
||||
@app.get("/api/environment", tags=["General"])
|
||||
async def get_environment():
|
||||
"""Get environment configuration for frontend"""
|
||||
return {
|
||||
"apiBaseUrl": APP_CONFIG.get("APP_API_URL", ""),
|
||||
"environment": APP_CONFIG.get("APP_ENV", "development"),
|
||||
"instanceLabel": APP_CONFIG.get("APP_ENV_LABEL", "Development"),
|
||||
# Add other environment variables the frontend might need
|
||||
}
|
||||
|
||||
# Token endpoint for login
|
||||
@app.post("/api/token", response_model=gatewayModel.Token, tags=["General"])
|
||||
async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
|
||||
# Initialize Gateway interface without context
|
||||
gateway = getGatewayInterface()
|
||||
|
||||
# Authenticate user
|
||||
user = gateway.authenticateUser(formData.username, formData.password)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Create token with tenant ID
|
||||
accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
accessToken = createAccessToken(
|
||||
data={
|
||||
"sub": user["username"],
|
||||
"mandateId": user["mandateId"]
|
||||
},
|
||||
expiresDelta=accessTokenExpires
|
||||
)
|
||||
|
||||
return {"accessToken": accessToken, "tokenType": "bearer"}
|
||||
|
||||
# Get user info
|
||||
@app.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
|
||||
async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
|
||||
return currentUser
|
||||
|
||||
# Include all routers
|
||||
from routes.routeGeneral import router as generalRouter
|
||||
app.include_router(generalRouter)
|
||||
|
||||
from routes.routeAttributes import router as attributesRouter
|
||||
app.include_router(attributesRouter)
|
||||
|
||||
gateway = getGatewayInterface()
|
||||
|
||||
from routes.routeMandates import router as mandateRouter
|
||||
app.include_router(mandateRouter)
|
||||
|
||||
gateway = getGatewayInterface()
|
||||
|
||||
|
||||
from routes.routeUsers import router as userRouter
|
||||
app.include_router(userRouter)
|
||||
|
||||
|
|
@ -206,6 +142,3 @@ app.include_router(workflowRouter)
|
|||
|
||||
from routes.routeMsft import router as msftRouter
|
||||
app.include_router(msftRouter)
|
||||
|
||||
#if __name__ == "__main__":
|
||||
# uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
|
||||
|
|
|
|||
111
modules/gatewayAccess.py
Normal file
111
modules/gatewayAccess.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
"""
|
||||
Access control functions for the Gateway system.
|
||||
Manages user access and permissions.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
def _uam(currentUser: Dict[str, Any], table: str, recordset: List[Dict[str, Any]], mandateId: int, userId: int, db) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Unified user access management function that filters data based on user privileges
|
||||
and adds access control attributes.
|
||||
|
||||
Args:
|
||||
currentUser: Current user information dictionary
|
||||
table: Name of the table
|
||||
recordset: Recordset to filter based on access rules
|
||||
mandateId: Current mandate ID
|
||||
userId: Current user ID
|
||||
db: Database connector instance
|
||||
|
||||
Returns:
|
||||
Filtered recordset with access control attributes
|
||||
"""
|
||||
userPrivilege = currentUser.get("privilege", "user")
|
||||
filtered_records = []
|
||||
|
||||
# Apply filtering based on privilege
|
||||
if userPrivilege == "sysadmin":
|
||||
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") == mandateId]
|
||||
else: # Regular users
|
||||
# Users only see records they own within their mandate
|
||||
filtered_records = [r for r in recordset
|
||||
if r.get("mandateId") == mandateId and r.get("userId") == userId]
|
||||
|
||||
# Add access control attributes to each record
|
||||
for record in filtered_records:
|
||||
record_id = record.get("id")
|
||||
|
||||
# Set access control flags based on user permissions
|
||||
if table == "mandates":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not _canModify(currentUser, "mandates", record_id, mandateId, userId, db)
|
||||
record["_hideDelete"] = not _canModify(currentUser, "mandates", record_id, mandateId, userId, db)
|
||||
elif table == "users":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not _canModify(currentUser, "users", record_id, mandateId, userId, db)
|
||||
record["_hideDelete"] = not _canModify(currentUser, "users", record_id, mandateId, userId, db)
|
||||
else:
|
||||
# Default access control for other tables
|
||||
record["_hideView"] = False
|
||||
record["_hideEdit"] = not _canModify(currentUser, table, record_id, mandateId, userId, db)
|
||||
record["_hideDelete"] = not _canModify(currentUser, table, record_id, mandateId, userId, db)
|
||||
|
||||
return filtered_records
|
||||
|
||||
def _canModify(currentUser: Dict[str, Any], table: str, recordId: Optional[int] = None, mandateId: int = None, userId: int = None, db = None) -> bool:
|
||||
"""
|
||||
Checks if the current user can modify (create/update/delete) records in a table.
|
||||
|
||||
Args:
|
||||
currentUser: Current user information dictionary
|
||||
table: Name of the table
|
||||
recordId: Optional record ID for specific record check
|
||||
mandateId: Current mandate ID
|
||||
userId: Current user ID
|
||||
db: Database connector instance
|
||||
|
||||
Returns:
|
||||
Boolean indicating permission
|
||||
"""
|
||||
userPrivilege = currentUser.get("privilege", "user")
|
||||
|
||||
# System admins can modify anything
|
||||
if userPrivilege == "sysadmin":
|
||||
return True
|
||||
|
||||
# Check specific record permissions
|
||||
if recordId is not None:
|
||||
# Get the record to check ownership
|
||||
records = db.getRecordset(table, recordFilter={"id": recordId})
|
||||
if not records:
|
||||
return False
|
||||
|
||||
record = records[0]
|
||||
|
||||
# Admins can modify anything in their mandate
|
||||
if userPrivilege == "admin" and record.get("mandateId") == mandateId:
|
||||
# Exception: Can't modify Root mandate unless you are a sysadmin
|
||||
if table == "mandates" and recordId == 1 and userPrivilege != "sysadmin":
|
||||
return False
|
||||
return True
|
||||
|
||||
# Users can only modify their own records
|
||||
if (record.get("mandateId") == mandateId and
|
||||
record.get("userId") == userId):
|
||||
return True
|
||||
|
||||
return False
|
||||
else:
|
||||
# For general table modify permission (e.g., create)
|
||||
# Admins can create anything in their mandate
|
||||
if userPrivilege == "admin":
|
||||
return True
|
||||
|
||||
# Regular users can create most entities
|
||||
if table == "mandates":
|
||||
return False # Regular users can't create mandates
|
||||
return True
|
||||
|
|
@ -11,6 +11,7 @@ from passlib.context import CryptContext
|
|||
|
||||
from connectors.connectorDbJson import DatabaseConnector
|
||||
from modules.configuration import APP_CONFIG
|
||||
from modules.gatewayAccess import _uam, _canModify
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -133,40 +134,7 @@ class GatewayInterface:
|
|||
Returns:
|
||||
Filtered recordset with access control attributes
|
||||
"""
|
||||
userPrivilege = self.currentUser.get("privilege", "user")
|
||||
filtered_records = []
|
||||
|
||||
# Apply filtering based on privilege
|
||||
if userPrivilege == "sysadmin":
|
||||
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]
|
||||
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]
|
||||
|
||||
# Add access control attributes to each record
|
||||
for record in filtered_records:
|
||||
record_id = record.get("id")
|
||||
|
||||
# Set access control flags based on user permissions
|
||||
if table == "mandates":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("mandates", record_id)
|
||||
record["_hideDelete"] = not self._canModify("mandates", record_id)
|
||||
elif table == "users":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("users", record_id)
|
||||
record["_hideDelete"] = not self._canModify("users", record_id)
|
||||
else:
|
||||
# Default access control for other tables
|
||||
record["_hideView"] = False
|
||||
record["_hideEdit"] = not self._canModify(table, record_id)
|
||||
record["_hideDelete"] = not self._canModify(table, record_id)
|
||||
|
||||
return filtered_records
|
||||
return _uam(self.currentUser, table, recordset, self.mandateId, self.userId, self.db)
|
||||
|
||||
def _canModify(self, table: str, recordId: Optional[int] = None) -> bool:
|
||||
"""
|
||||
|
|
@ -179,44 +147,7 @@ class GatewayInterface:
|
|||
Returns:
|
||||
Boolean indicating permission
|
||||
"""
|
||||
userPrivilege = self.currentUser.get("privilege", "user")
|
||||
|
||||
# System admins can modify anything
|
||||
if userPrivilege == "sysadmin":
|
||||
return True
|
||||
|
||||
# Check specific record permissions
|
||||
if recordId is not None:
|
||||
# Get the record to check ownership
|
||||
records = self.db.getRecordset(table, recordFilter={"id": recordId})
|
||||
if not records:
|
||||
return False
|
||||
|
||||
record = records[0]
|
||||
|
||||
# Admins can modify anything in their mandate
|
||||
if userPrivilege == "admin" and record.get("mandateId") == self.mandateId:
|
||||
# Exception: Can't modify Root mandate unless you are a sysadmin
|
||||
if table == "mandates" and recordId == 1 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):
|
||||
return True
|
||||
|
||||
return False
|
||||
else:
|
||||
# For general table modify permission (e.g., create)
|
||||
# Admins can create anything in their mandate
|
||||
if userPrivilege == "admin":
|
||||
return True
|
||||
|
||||
# Regular users can create most entities
|
||||
if table == "mandates":
|
||||
return False # Regular users can't create mandates
|
||||
return True
|
||||
return _canModify(self.currentUser, table, recordId, self.mandateId, self.userId, self.db)
|
||||
|
||||
def getInitialId(self, table: str) -> Optional[int]:
|
||||
"""Returns the initial ID for a table."""
|
||||
|
|
|
|||
131
modules/lucydomAccess.py
Normal file
131
modules/lucydomAccess.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
"""
|
||||
Access control module for LucyDOM interface.
|
||||
Handles user access management and permission checks.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
class LucyDOMAccess:
|
||||
"""
|
||||
Access control class for LucyDOM interface.
|
||||
Handles user access management and permission checks.
|
||||
"""
|
||||
|
||||
def __init__(self, currentUser: Dict[str, Any], mandateId: int, userId: int):
|
||||
"""Initialize with user context."""
|
||||
self.currentUser = currentUser
|
||||
self.mandateId = mandateId
|
||||
self.userId = userId
|
||||
|
||||
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
|
||||
and adds access control attributes.
|
||||
|
||||
Args:
|
||||
table: Name of the table
|
||||
recordset: Recordset to filter based on access rules
|
||||
|
||||
Returns:
|
||||
Filtered recordset with access control attributes
|
||||
"""
|
||||
userPrivilege = self.currentUser.get("privilege", "user")
|
||||
filtered_records = []
|
||||
|
||||
# Apply filtering based on privilege
|
||||
if userPrivilege == "sysadmin":
|
||||
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]
|
||||
else: # Regular users
|
||||
# To see all prompts from mandate 0 and own
|
||||
if table == "prompts":
|
||||
filtered_records = [r for r in recordset if
|
||||
(r.get("mandateId") == self.mandateId and r.get("userId") == self.userId)
|
||||
or
|
||||
(r.get("mandateId") == 0)
|
||||
]
|
||||
else:
|
||||
# Users see only their records
|
||||
filtered_records = [r for r in recordset
|
||||
if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId]
|
||||
|
||||
# Add access control attributes to each record
|
||||
for record in filtered_records:
|
||||
record_id = record.get("id")
|
||||
|
||||
# Set access control flags based on user permissions
|
||||
if table == "prompts":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("prompts", record_id)
|
||||
record["_hideDelete"] = not self._canModify("prompts", record_id)
|
||||
elif table == "files":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("files", record_id)
|
||||
record["_hideDelete"] = not self._canModify("files", record_id)
|
||||
record["_hideDownload"] = not self._canModify("files", record_id)
|
||||
elif table == "workflows":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("workflows", record_id)
|
||||
record["_hideDelete"] = not self._canModify("workflows", record_id)
|
||||
elif table == "workflowMessages":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
|
||||
record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
|
||||
elif table == "workflowLogs":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
|
||||
record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
|
||||
else:
|
||||
# Default access control for other tables
|
||||
record["_hideView"] = False
|
||||
record["_hideEdit"] = not self._canModify(table, record_id)
|
||||
record["_hideDelete"] = not self._canModify(table, record_id)
|
||||
|
||||
return filtered_records
|
||||
|
||||
def _canModify(self, table: str, recordId: Optional[int] = None) -> bool:
|
||||
"""
|
||||
Checks if the current user can modify (create/update/delete) records in a table.
|
||||
|
||||
Args:
|
||||
table: Name of the table
|
||||
recordId: Optional record ID for specific record check
|
||||
|
||||
Returns:
|
||||
Boolean indicating permission
|
||||
"""
|
||||
userPrivilege = self.currentUser.get("privilege", "user")
|
||||
|
||||
# System admins can modify anything
|
||||
if userPrivilege == "sysadmin":
|
||||
return True
|
||||
|
||||
# For regular users and admins, check specific cases
|
||||
if recordId is not None:
|
||||
# Get the record to check ownership
|
||||
records = self.db.getRecordset(table, recordFilter={"id": recordId})
|
||||
if not records:
|
||||
return False
|
||||
|
||||
record = records[0]
|
||||
|
||||
# Admins can modify anything in their mandate
|
||||
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):
|
||||
return True
|
||||
|
||||
return False
|
||||
else:
|
||||
# For general modification permission (e.g., create)
|
||||
# Admins can create anything in their mandate
|
||||
if userPrivilege == "admin":
|
||||
return True
|
||||
|
||||
# Regular users can create in most tables
|
||||
return True
|
||||
|
|
@ -14,6 +14,7 @@ import hashlib
|
|||
import json
|
||||
|
||||
from modules.mimeUtils import isTextMimeType, determineContentEncoding
|
||||
from modules.lucydomAccess import LucyDOMAccess
|
||||
|
||||
# DYNAMIC PART: Connectors to the Interface
|
||||
from connectors.connectorDbJson import DatabaseConnector
|
||||
|
|
@ -74,6 +75,10 @@ class LucyDOMInterface:
|
|||
# Load user information
|
||||
self.currentUser = self._getCurrentUserInfo()
|
||||
|
||||
# Initialize access control
|
||||
self.access = LucyDOMAccess(self.currentUser, self.mandateId, self.userId)
|
||||
self.access.db = self.db # Share database connection
|
||||
|
||||
# Initialize standard database records if needed
|
||||
self._initRecords()
|
||||
|
||||
|
|
@ -161,117 +166,12 @@ class LucyDOMInterface:
|
|||
logger.info(f"Prompt '{promptData.get('name', 'Standard')}' was created with ID {createdPrompt['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
|
||||
and adds access control attributes.
|
||||
|
||||
Args:
|
||||
table: Name of the table
|
||||
recordset: Recordset to filter based on access rules
|
||||
|
||||
Returns:
|
||||
Filtered recordset with access control attributes
|
||||
"""
|
||||
userPrivilege = self.currentUser.get("privilege", "user")
|
||||
filtered_records = []
|
||||
|
||||
# Apply filtering based on privilege
|
||||
if userPrivilege == "sysadmin":
|
||||
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]
|
||||
else: # Regular users
|
||||
# To see all prompts from mandate 0 and own
|
||||
if table == "prompts":
|
||||
filtered_records = [r for r in recordset if
|
||||
(r.get("mandateId") == self.mandateId and r.get("userId") == self.userId)
|
||||
or
|
||||
(r.get("mandateId") == 0)
|
||||
]
|
||||
else:
|
||||
# Users see only their records
|
||||
filtered_records = [r for r in recordset
|
||||
if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId]
|
||||
|
||||
# Add access control attributes to each record
|
||||
for record in filtered_records:
|
||||
record_id = record.get("id")
|
||||
|
||||
# Set access control flags based on user permissions
|
||||
if table == "prompts":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("prompts", record_id)
|
||||
record["_hideDelete"] = not self._canModify("prompts", record_id)
|
||||
elif table == "files":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("files", record_id)
|
||||
record["_hideDelete"] = not self._canModify("files", record_id)
|
||||
record["_hideDownload"] = not self._canModify("files", record_id)
|
||||
elif table == "workflows":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("workflows", record_id)
|
||||
record["_hideDelete"] = not self._canModify("workflows", record_id)
|
||||
elif table == "workflowMessages":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
|
||||
record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
|
||||
elif table == "workflowLogs":
|
||||
record["_hideView"] = False # Everyone can view
|
||||
record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
|
||||
record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
|
||||
else:
|
||||
# Default access control for other tables
|
||||
record["_hideView"] = False
|
||||
record["_hideEdit"] = not self._canModify(table, record_id)
|
||||
record["_hideDelete"] = not self._canModify(table, record_id)
|
||||
|
||||
return filtered_records
|
||||
"""Delegate to access control module."""
|
||||
return self.access._uam(table, recordset)
|
||||
|
||||
def _canModify(self, table: str, recordId: Optional[int] = None) -> bool:
|
||||
"""
|
||||
Checks if the current user can modify (create/update/delete) records in a table.
|
||||
|
||||
Args:
|
||||
table: Name of the table
|
||||
recordId: Optional record ID for specific record check
|
||||
|
||||
Returns:
|
||||
Boolean indicating permission
|
||||
"""
|
||||
userPrivilege = self.currentUser.get("privilege", "user")
|
||||
|
||||
# System admins can modify anything
|
||||
if userPrivilege == "sysadmin":
|
||||
return True
|
||||
|
||||
# For regular users and admins, check specific cases
|
||||
if recordId is not None:
|
||||
# Get the record to check ownership
|
||||
records = self.db.getRecordset(table, recordFilter={"id": recordId})
|
||||
if not records:
|
||||
return False
|
||||
|
||||
record = records[0]
|
||||
|
||||
# Admins can modify anything in their mandate
|
||||
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):
|
||||
return True
|
||||
|
||||
return False
|
||||
else:
|
||||
# For general modification permission (e.g., create)
|
||||
# Admins can create anything in their mandate
|
||||
if userPrivilege == "admin":
|
||||
return True
|
||||
|
||||
# Regular users can create in most tables
|
||||
return True
|
||||
"""Delegate to access control module."""
|
||||
return self.access._canModify(table, recordId)
|
||||
|
||||
# Language support method
|
||||
|
||||
|
|
|
|||
|
|
@ -1,104 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Variables
|
||||
SUBSCRIPTION_ID="213596c9-34b2-4677-a712-45ed127cdae5"
|
||||
RESOURCE_GROUP="volucy-group"
|
||||
APP_NAME="poweron-gateway"
|
||||
DOMAIN_NAME="gateway.poweron-center.net"
|
||||
CERT_PASSWORD="TheSecurePass$(date +%s)" # Unique password with timestamp
|
||||
|
||||
# Login to Azure (uncomment if not already logged in)
|
||||
# az login
|
||||
|
||||
# Set subscription
|
||||
echo "Setting subscription..."
|
||||
az account set --subscription "$SUBSCRIPTION_ID"
|
||||
|
||||
# Create directory for certificate files
|
||||
mkdir -p cert-files
|
||||
cd cert-files
|
||||
|
||||
# Create OpenSSL config file with required extensions
|
||||
cat > openssl.cnf << EOF
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = req_ext
|
||||
[ req_distinguished_name ]
|
||||
countryName = Country Name (2 letter code)
|
||||
stateOrProvinceName = State or Province Name (full name)
|
||||
localityName = Locality Name (eg, city)
|
||||
organizationName = Organization Name (eg, company)
|
||||
commonName = Common Name (e.g. server FQDN)
|
||||
[ req_ext ]
|
||||
subjectAltName = @alt_names
|
||||
extendedKeyUsage = serverAuth
|
||||
[alt_names]
|
||||
DNS.1 = ${DOMAIN_NAME}
|
||||
EOF
|
||||
|
||||
# Generate private key
|
||||
openssl genrsa -out private.key 2048
|
||||
|
||||
# Create CSR with config file
|
||||
openssl req -new -key private.key -out request.csr -config openssl.cnf -subj "/C=US/ST=State/L=City/O=Organization/CN=${DOMAIN_NAME}"
|
||||
|
||||
# Generate self-signed certificate with extensions
|
||||
openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt \
|
||||
-extfile openssl.cnf -extensions req_ext
|
||||
|
||||
# Create PFX file
|
||||
openssl pkcs12 -export -out self-signed-cert.pfx -inkey private.key -in certificate.crt -passout pass:$CERT_PASSWORD
|
||||
|
||||
cd ..
|
||||
|
||||
# Upload certificate to App Service
|
||||
echo "Uploading certificate..."
|
||||
UPLOAD_RESULT=$(az webapp config ssl upload \
|
||||
--resource-group "$RESOURCE_GROUP" \
|
||||
--name "$APP_NAME" \
|
||||
--certificate-file "cert-files/self-signed-cert.pfx" \
|
||||
--certificate-password "$CERT_PASSWORD")
|
||||
|
||||
# Extract thumbprint from upload result
|
||||
CERT_THUMBPRINT=$(echo $UPLOAD_RESULT | jq -r '.thumbprint')
|
||||
|
||||
echo "Certificate uploaded successfully with thumbprint: $CERT_THUMBPRINT"
|
||||
|
||||
# If the thumbprint is empty, try to find it another way
|
||||
if [ -z "$CERT_THUMBPRINT" ] || [ "$CERT_THUMBPRINT" == "null" ]; then
|
||||
echo "Thumbprint not found in upload result. Trying to list certificates..."
|
||||
CERT_LIST=$(az webapp config ssl list --resource-group "$RESOURCE_GROUP")
|
||||
|
||||
# Look for the most recently uploaded certificate
|
||||
CERT_THUMBPRINT=$(echo $CERT_LIST | jq -r 'sort_by(.expirationDate) | reverse | .[0].thumbprint')
|
||||
|
||||
if [ -z "$CERT_THUMBPRINT" ] || [ "$CERT_THUMBPRINT" == "null" ]; then
|
||||
echo "Error: Could not find certificate thumbprint."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Using certificate thumbprint: $CERT_THUMBPRINT"
|
||||
|
||||
# Make sure the custom domain is added
|
||||
echo "Checking if custom domain exists..."
|
||||
DOMAIN_EXISTS=$(az webapp config hostname list --resource-group "$RESOURCE_GROUP" --webapp-name "$APP_NAME" | jq -r ".[] | select(.name==\"$DOMAIN_NAME\") | .name")
|
||||
|
||||
if [ -z "$DOMAIN_EXISTS" ]; then
|
||||
echo "Adding custom domain..."
|
||||
az webapp config hostname add \
|
||||
--resource-group "$RESOURCE_GROUP" \
|
||||
--webapp-name "$APP_NAME" \
|
||||
--hostname "$DOMAIN_NAME"
|
||||
fi
|
||||
|
||||
# Add IP-based SSL binding
|
||||
echo "Creating IP-based SSL binding..."
|
||||
az webapp config ssl bind \
|
||||
--resource-group "$RESOURCE_GROUP" \
|
||||
--name "$APP_NAME" \
|
||||
--certificate-thumbprint "$CERT_THUMBPRINT" \
|
||||
--ssl-type "IP"
|
||||
|
||||
echo "SSL binding completed. Your domain should now be secured."
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
agentDocumentation delivers a ".docx" file, but the content is a ".md" text markup file
|
||||
|
||||
access management to extract into separate modules "lucydomAccess.py" and "gatewayAccess.py". Here to move the functions from "*Interface.py", which define what access which role has.
|
||||
agentDocumentation to extract data per chapter
|
||||
|
||||
check data extraction tabelle im pdf
|
||||
|
||||
|
|
@ -22,8 +22,6 @@ sharepoint connector with document search, content search, content extraction
|
|||
|
||||
PRIO2:
|
||||
|
||||
sharepoint connector with document search, content search, content extraction
|
||||
|
||||
Split big files into content-parts
|
||||
|
||||
Integrate NDA Text as modal form - Data governance agreement by login with checkbox
|
||||
|
|
@ -36,11 +34,13 @@ Tools to transfer incl funds:
|
|||
- Google SERPAPI (shelly)
|
||||
- Anthropic Claude (valueon + shelly)
|
||||
- Cursor Pro
|
||||
- Mermaid
|
||||
|
||||
|
||||
|
||||
----------------------- DONE
|
||||
|
||||
|
||||
|
||||
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).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
Architectural Flow
|
||||
|
||||
workflow.js - Entry point that:
|
||||
|
||||
Creates the central state object
|
||||
Initializes API and UI components
|
||||
Connects components together
|
||||
Exposes required public functions
|
||||
|
||||
|
||||
workflow_state.js - State management that:
|
||||
|
||||
Maintains a single source of truth for workflow data
|
||||
Implements an observer pattern for state changes
|
||||
Handles state transitions and business logic
|
||||
Validates state updates
|
||||
|
||||
|
||||
workflow_api.js - API communication that:
|
||||
|
||||
Abstracts all API calls to backend services
|
||||
Manages polling for workflow status
|
||||
Processes API responses
|
||||
Tracks data transfer statistics
|
||||
Updates state with API results
|
||||
|
||||
|
||||
workflow_ui.js - UI layer that:
|
||||
|
||||
Renders UI based on current state
|
||||
Sets up event listeners for user interactions
|
||||
Handles DOM manipulation
|
||||
Controls layout adjustments
|
||||
Triggers state changes via user actions
|
||||
|
||||
|
||||
|
||||
Data Flow
|
||||
|
||||
User initiates action in the UI
|
||||
UI controller handles the action and calls relevant API methods
|
||||
API communicates with backend and updates state
|
||||
State notifies observers about changes
|
||||
UI reacts to state changes and updates display
|
||||
Polling continues until completed state is reached
|
||||
Final UI update happens when workflow completes
|
||||
|
|
@ -1,366 +0,0 @@
|
|||
# State Machine Documentation for Backend Chat Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
The Chat Workflow system implements a state machine that processes user inputs through a sequence of well-defined steps. The system orchestrates interactions between users, project managers, and specialized agents to produce final outputs.
|
||||
|
||||
## Core Objects
|
||||
|
||||
### Workflow Object
|
||||
```json
|
||||
{
|
||||
"id": "uuid-string",
|
||||
"mandateId": int,
|
||||
"userId": int,
|
||||
"name": "Workflow name",
|
||||
"startedAt": "ISO-datetime",
|
||||
"messages": [], // References to messages
|
||||
"messageIds": [], // List of message IDs
|
||||
"logs": [], // Log entries
|
||||
"dataStats": {}, // Performance metrics
|
||||
"currentRound": int, // Increments with each interaction
|
||||
"status": "string", // running, completed, failed, stopped
|
||||
"lastActivity": "ISO-datetime"
|
||||
}
|
||||
```
|
||||
|
||||
### Message Object
|
||||
```json
|
||||
{
|
||||
"id": "msg_uuid-string",
|
||||
"workflowId": "workflow-uuid",
|
||||
"role": "string", // user, assistant
|
||||
"agentName": "string", // Empty for user, agent name for assistant
|
||||
"content": "string", // The message text
|
||||
"documents": [], // List of document objects
|
||||
"timestamp": "ISO-datetime",
|
||||
"sequenceNo": int, // Position in conversation
|
||||
"status": "string" // first, step, last
|
||||
}
|
||||
```
|
||||
|
||||
### Log Entry Object
|
||||
```json
|
||||
{
|
||||
"id": "log_uuid-string",
|
||||
"workflowId": "workflow-uuid",
|
||||
"message": "string",
|
||||
"progress": int, // Optional, 0-100
|
||||
"type": "string", // info, warning, error
|
||||
"timestamp": "ISO-datetime",
|
||||
"agentName": "string", // Name of the agent that generated the log
|
||||
"status": "string" // current workflow status (running, completed, failed, stopped)
|
||||
}
|
||||
```
|
||||
|
||||
### Document Object
|
||||
```json
|
||||
{
|
||||
"id": "doc_uuid-string",
|
||||
"fileId": int,
|
||||
"name": "string", // Filename without extension
|
||||
"ext": "string", // File extension
|
||||
"data": "base64-encoded-string", // File contents
|
||||
"contents": [] // Extracted content items in text format
|
||||
}
|
||||
```
|
||||
|
||||
### Content Item Object
|
||||
```json
|
||||
{
|
||||
"sequenceNr": int, // Sequence in the document
|
||||
"name": "string",
|
||||
"ext": "string",
|
||||
"contentType": "string", // mime type
|
||||
"data": "string|base64", // Original content
|
||||
"dataExtracted": "string", // Optional AI-processed content based on extraction requirement
|
||||
"metadata": {
|
||||
"isText": boolean,
|
||||
"base64Encoded": boolean,
|
||||
"aiProcessed": boolean,
|
||||
// Optional metadata specific to content type
|
||||
},
|
||||
"summary": "string" // AI-generated static summary of the content
|
||||
}
|
||||
```
|
||||
|
||||
## State Machine Workflow
|
||||
|
||||
### 1. Workflow Initialization
|
||||
- **Trigger**: User message received via `/api/workflows/start` OR `/api/workflows/start?id=string`
|
||||
- **Input**: `UserInputRequest` with `prompt` and optional `listFileId`
|
||||
- **Process**:
|
||||
- If `id` existing and workflow exists for `id`==`workflowId`: Load workflow, increment `currentRound`, set status "running"
|
||||
- Else: Create new workflow with "currentRound"=1, status "running"
|
||||
- **Logs**: "Workflow initialized" or "Running workflow", progress 0%
|
||||
- **API Responses**:
|
||||
- Success: 200 OK with workflow ID
|
||||
- Error: 400 Bad Request if input invalid, 404 Not Found if workflow ID not found
|
||||
|
||||
### 2. Workflow Exception
|
||||
- **Trigger**:
|
||||
- User stopped workflow via API
|
||||
- An exception happened
|
||||
- **Process**:
|
||||
- If status=="stopped": Set workflow status to "stopped", add message with status "last", update lastActivity, stop execution immediately
|
||||
- If status=="failed": Set workflow status to "failed", add message with status "last", update lastActivity, stop execution immediately
|
||||
- Else: Continue normally
|
||||
- **Logs**: "Workflow failure reported", progress 100%
|
||||
- **API Responses**:
|
||||
- For stop request: 200 OK when workflow successfully stopped
|
||||
- For exceptions: 500 Internal Server Error with error details
|
||||
|
||||
### 3. User Message Processing
|
||||
- **Process**:
|
||||
- Transform user input into message object with documents, message status "first"
|
||||
- Extract contents from files using `getDocumentContents()`
|
||||
- Generate static summaries for each content item
|
||||
- **State Changes**:
|
||||
- Add user message to `workflow.messages` array
|
||||
- Add message ID to `workflow.messageIds` array
|
||||
- Update `workflow.lastActivity`
|
||||
- **Logs**: "Workflow processing started", progress 0%
|
||||
|
||||
### 4. Project Manager Analysis
|
||||
- **Process**:
|
||||
- Generate prompt for project manager AI
|
||||
- Project manager analyzes request and documents
|
||||
- Project manager generates work plan and response
|
||||
- **Outputs**:
|
||||
- `objFinalDocuments`: List of str "filename.ext" for expected final output documents
|
||||
- `objWorkplan`: List of agent tasks
|
||||
- `objUserResponse`: Text response to user
|
||||
- `userLanguage`: Detected language code (e.g. en)
|
||||
- **State Changes**:
|
||||
- Add assistant message with project manager response, status "step"
|
||||
- Set user language in mydom interface
|
||||
- **Logs**: "Analyzing request and planning work" (10%), "Planned outputs" (20%), "Work plan created" (25%)
|
||||
|
||||
### 5. Agent Execution
|
||||
- **Process** (For each task in workplan):
|
||||
- Prepare input documents for agent
|
||||
- Execute agent with standardized task object
|
||||
- Save produced documents
|
||||
- Create assistant message with agent response, status "step"
|
||||
- **Agent Task Object**:
|
||||
```json
|
||||
{
|
||||
"taskId": "uuid-string",
|
||||
"workflowId": "workflow-uuid",
|
||||
"prompt": "string",
|
||||
"inputDocuments": [], // list of documents including original document data and all content items data with original (attribute "data") and based on prompt (attribute "dataExtracted")
|
||||
"outputSpecifications": [
|
||||
{
|
||||
"label": "filename.ext",
|
||||
"description": "string"
|
||||
}
|
||||
],
|
||||
"context": {
|
||||
"workflowRound": int,
|
||||
"agentType": "string",
|
||||
"timestamp": "ISO-datetime",
|
||||
"language": "language-code"
|
||||
}
|
||||
}
|
||||
```
|
||||
- **Agent Result Object**:
|
||||
```json
|
||||
{
|
||||
"feedback": "string", // Text describing what the agent did
|
||||
"documents": [
|
||||
{
|
||||
"label": "filename.ext",
|
||||
"content": "string|binary" // Document contents
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- **State Changes**: Add assistant message for each agent with agentName set, status "step"
|
||||
- **Logs**: "Running task X/Y: agentName" with progress updates from 30% to 90%
|
||||
|
||||
### 6. Final Response Generation
|
||||
- **Process**:
|
||||
- Create final message reviewing promised and delivered documents
|
||||
- Add documents to workflow
|
||||
- **State Changes**: Add final assistant message from projectManager, status "last"
|
||||
- **Logs**: "Creating final response" (90%)
|
||||
|
||||
### 7. Workflow Completion
|
||||
- **Process**:
|
||||
- Finalize workflow and update status
|
||||
- **State Changes**:
|
||||
- Set workflow status to "completed"
|
||||
- Update `workflow.lastActivity`
|
||||
- **Logs**: "Workflow completed successfully" with progress 100%
|
||||
- **API Responses**:
|
||||
- A message with status "last" is included in the response
|
||||
- Status endpoint will return "completed"
|
||||
|
||||
### 8. Workflow Stopped
|
||||
- **Trigger**: `/api/workflows/{workflowId}/stop` endpoint called
|
||||
- **Process**:
|
||||
- Immediately interrupt workflow execution
|
||||
- Save current state and mark as stopped
|
||||
- **State Changes**:
|
||||
- Set workflow status to "stopped"
|
||||
- Update lastActivity timestamp
|
||||
- **Logs**: "Workflow stopped by user" with progress 100%
|
||||
- **API Responses**:
|
||||
- 200 OK with confirmation message
|
||||
|
||||
### 9. Workflow Failed
|
||||
- **Trigger**: Exception during workflow execution
|
||||
- **Process**:
|
||||
- Log error details
|
||||
- Set workflow status to "failed"
|
||||
- **State Changes**:
|
||||
- Set workflow status to "failed"
|
||||
- Update lastActivity timestamp
|
||||
- **Logs**: Detailed error message with progress 100%
|
||||
- **API Responses**:
|
||||
- Status endpoint will return "failed" with error context
|
||||
|
||||
### 10. Workflow Resumption
|
||||
- **Trigger**: `/api/workflows/start?id={workflowId}` endpoint called with existing workflow ID
|
||||
- **Process**:
|
||||
- Load existing workflow
|
||||
- Increment currentRound counter
|
||||
- Start processing from user message
|
||||
- **State Changes**:
|
||||
- Set status to "running"
|
||||
- Increment currentRound
|
||||
- Add new user message
|
||||
- **Logs**: "Resuming workflow, round {currentRound}" with progress 0%
|
||||
- **API Responses**:
|
||||
- Same as workflow initialization
|
||||
|
||||
### 11. Workflow Reset/Deletion
|
||||
- **Trigger**: `/api/workflows/{workflowId}` DELETE endpoint called
|
||||
- **Process**:
|
||||
- Remove all workflow data from storage
|
||||
- **State Changes**:
|
||||
- Workflow no longer exists in the system
|
||||
- **Logs**: Log in system log that workflow was deleted
|
||||
- **API Responses**:
|
||||
- 200 OK if successful
|
||||
- 404 Not Found if workflow didn't exist
|
||||
|
||||
## API Endpoints and Polling Support
|
||||
|
||||
### Main Workflow Endpoints
|
||||
- `POST /api/workflows/start?id=string`: Submit user input to start a new workflow, optional with workflow id to continue existing workflow
|
||||
- `POST /api/workflows/{workflowId}/stop`: Stop a running workflow: Immediately to set workflow status to "stopped"
|
||||
- `DELETE /api/workflows/{workflowId}`: Delete a workflow
|
||||
- `GET /api/workflows/{workflowId}/status`: Get workflow status (running, completed, failed, stopped)
|
||||
- `GET /api/workflows/{workflowId}/logs?id=string`: Get workflow logs, optional with log id to get only logs produced after and including log with log id
|
||||
- `GET /api/workflows/{workflowId}/messages?id=string`: Get workflow messages, optional with message id to get only messages produced after and including message with log id
|
||||
|
||||
### Document Management
|
||||
- `DELETE /api/workflows/{workflowId}/messages/{messageId}`: Delete a message
|
||||
- `DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}`: Remove file from message
|
||||
|
||||
### Backend Support for Frontend Polling
|
||||
|
||||
The backend implements efficient support for frontend polling mechanisms:
|
||||
|
||||
1. **Selective Data Transfer**:
|
||||
- Both `/logs` and `/messages` endpoints accept an optional `id` parameter
|
||||
- When provided, only records with IDs equal to or newer than the specified ID are returned
|
||||
- This minimizes data transfer and improves performance
|
||||
|
||||
2. **Log Storage**:
|
||||
- Each log entry includes timestamp, progress indicators, and status
|
||||
- Frontend can accurately track workflow progress and update UI accordingly
|
||||
- Logs are stored in chronological order with monotonically increasing IDs
|
||||
|
||||
3. **Message Handling**:
|
||||
- Messages include a status field ("first", "step", "last")
|
||||
- The "last" status indicates completion of the current workflow round
|
||||
- Frontend uses this to determine when to enable user input
|
||||
|
||||
4. **Status Endpoint**:
|
||||
- Lightweight endpoint that returns only the current workflow status
|
||||
- Used by frontend to detect state changes without transferring all data
|
||||
- Also includes lastActivity timestamp to detect stalled workflows
|
||||
|
||||
5. **Caching Layer**:
|
||||
- Backend implements caching for frequent polling requests
|
||||
- Reduces database load and improves response times
|
||||
- Cache invalidation occurs when workflow status changes
|
||||
|
||||
6. **Batch Processing**:
|
||||
- Large log or message sets are paginated automatically
|
||||
- Frontend receives data in manageable chunks
|
||||
- Prevents memory issues with long-running workflows
|
||||
|
||||
## Document Object Structure Clarification
|
||||
The Document Object contains both raw data and processed contents:
|
||||
- `data`: Contains the base64-encoded binary representation of the entire original file
|
||||
- `contents`: Contains an array of structured Content Item objects extracted from the original file
|
||||
|
||||
The relationship works as follows:
|
||||
1. When a file is uploaded, its binary data is stored in the `data` field
|
||||
2. The original file's complete data is always preserved in the document's `data` field
|
||||
3. The file is then processed by content extractors based on file type (PDF, image, text, etc.)
|
||||
4. Each logically separate piece of content is added to the `contents` array
|
||||
5. For text files, there might be just one content item
|
||||
6. For PDFs, there might be multiple content items (one per page or per embedded image)
|
||||
7. For complex documents, content items might represent different sections or formats
|
||||
8. Each content item contains its own `data` field with the specific extracted content; for agents convenience it contains the additional field `dataExtracted` with extracted data based on agents task prompt
|
||||
|
||||
This dual structure allows agents to:
|
||||
- Access the complete original file when needed
|
||||
- Work with pre-processed, extracted content for efficiency
|
||||
- Process specific sections of a document without loading the entire file
|
||||
|
||||
## State Transitions
|
||||
|
||||
```
|
||||
[null] → [running] // New workflow created
|
||||
[running] → [completed] // Workflow completes successfully
|
||||
[running] → [stopped] // User manually stops workflow
|
||||
[running] → [failed] // Error occurs during workflow
|
||||
[completed] → [running] // User continues workflow with new input (new round)
|
||||
[stopped] → [running] // User continues after manual stop (new round)
|
||||
[failed] → [running] // User retries workflow despite error (new round)
|
||||
[any] → [null] // Workflow deleted
|
||||
```
|
||||
|
||||
## Exception Handling
|
||||
|
||||
### Rules
|
||||
- Workflow status changes to "failed" on exceptions, all message and workflow generation exceptions to handle to ensure data consistency in the database
|
||||
- Errors are logged in workflow logs with type "error"
|
||||
- Produced project manager analysis output, inputs to agents, output from agents, workflow items, message items are all logged for debugging in the logger with type "debug"
|
||||
- HTTP exceptions are returned to the client with appropriate status codes
|
||||
- Failed agent tasks are recorded but don't stop the workflow
|
||||
|
||||
### Workflow Stop Conditions
|
||||
- User explicitly cancels the workflow via the stop endpoint
|
||||
|
||||
Action to take:
|
||||
- workflow to set to "stopped" status
|
||||
|
||||
### Workflow Failure Conditions
|
||||
- Unhandled exceptions in the main workflow execution path
|
||||
- Project manager analysis fails to generate a valid workplan
|
||||
- More than 50% of the agent tasks in the workplan fail to complete
|
||||
- Timeout exceeded (workflow runs longer than the configured maximum duration)
|
||||
- System resource limits exceeded (memory, CPU, etc.)
|
||||
|
||||
Action to take, when a workflow fails:
|
||||
- The last log entry will contain details about the failure reason
|
||||
- workflow to set to "failed" status
|
||||
|
||||
### Workflow Exception Checkpoints
|
||||
At the following points in the code the Workflow Execution routine is called:
|
||||
- Before adding or updating a message to the workflow
|
||||
- Before doing an API call
|
||||
|
||||
## Special Notes
|
||||
|
||||
1. **Document Processing**: Files uploaded by users are processed with content extraction to make them accessible to agents.
|
||||
2. **AI Language Support**: The system detects and adapts to the user's language.
|
||||
3. **Round Counting**: Each interaction increments the `currentRound` counter.
|
||||
4. **Agent Registry**: Agents are loaded dynamically and registered in the AgentRegistry.
|
||||
5. **Standardized Task Processing**: All agents implement the same task processing interface.
|
||||
|
|
@ -1,453 +0,0 @@
|
|||
# State Machine Documentation for Frontend Chat Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
The Chat Workflow frontend implements a state machine that manages user interactions through a well-defined sequence of states. This system coordinates the user interface components, handles file attachments, and manages communication with the backend to provide a seamless multi-agent chat experience.
|
||||
|
||||
## Core Objects
|
||||
|
||||
### Frontend Workflow State Object
|
||||
```json
|
||||
{
|
||||
"status": "string", // null or "running", "completed", "failed", "stopped"
|
||||
"workflowId": "string", // null or Unique workflow identifier
|
||||
"logs": [], // Log entries
|
||||
"chatMessages": [], // Chat messages
|
||||
"lastPolledLogId": "string", // ID of last polled log
|
||||
"lastPolledMessageId": "string", // ID of last polled message
|
||||
"dataStats": {
|
||||
"bytesSent": int, // Data sent to backend
|
||||
"bytesReceived": int, // Data received from backend
|
||||
"tokensUsed": float // Used tokens required for billing
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### User Input State Object
|
||||
```json
|
||||
{
|
||||
"promptText": "string", // Current user input text
|
||||
"additionalFiles": [], // List of file IDs to attach
|
||||
"domElements": {} // References to UI DOM elements
|
||||
}
|
||||
```
|
||||
|
||||
### Log Entry Object
|
||||
```json
|
||||
{
|
||||
"id": "log_timestamp",
|
||||
"message": "string",
|
||||
"progress": int, // Optional, 0-100
|
||||
"type": "string", // info, warning, error
|
||||
"timestamp": "ISO-datetime",
|
||||
"agentName": "string", // Name of the agent that generated the log
|
||||
"waiting": boolean, // Whether this log shows a waiting indicator
|
||||
"highlighted": boolean // Whether this log should be visually highlighted
|
||||
}
|
||||
```
|
||||
|
||||
### Chat Message Object
|
||||
```json
|
||||
{
|
||||
"id": "msg_type_timestamp",
|
||||
"role": "string", // user, assistant
|
||||
"agentName": "string",
|
||||
"content": "string",
|
||||
"documents": [], // List of document objects
|
||||
"timestamp": "ISO-datetime",
|
||||
"status": "string" // first, step, last ("workflowComplete" can be asked as `status`=="last")
|
||||
}
|
||||
```
|
||||
|
||||
### File Object
|
||||
```json
|
||||
{
|
||||
"id": "file_uuid-string",
|
||||
"name": "filename.ext",
|
||||
"size": int,
|
||||
"fileId": int,
|
||||
"contentType": "string", // mime type
|
||||
}
|
||||
```
|
||||
|
||||
## State Machine Workflow
|
||||
|
||||
### 1. Initial State
|
||||
- **State**: `null` (No workflow active)
|
||||
- **UI Elements**:
|
||||
- Empty chat container with placeholder
|
||||
- Prompt input field enabled
|
||||
- "Start" button enabled
|
||||
- "Stop" button hidden
|
||||
- File upload area enabled
|
||||
- Empty log panel
|
||||
- **User Actions**:
|
||||
- Enter prompt text
|
||||
- Add files via upload or drag & drop
|
||||
- Select pre-defined prompts from dropdown
|
||||
- Click Start button
|
||||
- **API Interactions**: None in this state
|
||||
|
||||
### 2. Prompt Preparation
|
||||
- **State**: `null` (transitioning to `running`)
|
||||
- **Triggers**:
|
||||
- User typing in prompt field (updates `userInputState.promptText`)
|
||||
- File uploads/drag & drop (adds to `userInputState.additionalFiles`)
|
||||
- Prompt selection from dropdown (sets `userInputState.promptText`)
|
||||
- **Process**:
|
||||
- Files are uploaded to backend via `uploadAndAddFile()`
|
||||
- Files appear in attachment list with name, size, remove option
|
||||
- Prompt visualization is updated with file count and names
|
||||
- **State Changes**:
|
||||
- `userInputState.additionalFiles` array updated
|
||||
- `userInputState.promptText` updated
|
||||
- **UI Updates**:
|
||||
- File attachment list rendered
|
||||
- Prompt preview area shows attached files
|
||||
- **API Interactions**:
|
||||
- `POST /api/files/upload`: For each file being attached, an API call is made to upload the file and get a file ID
|
||||
- Response contains file metadata which is stored in the frontend state
|
||||
|
||||
### 3. Workflow Initialization
|
||||
- **State**: `running`
|
||||
- **Trigger**: User clicks "Start" button or presses Enter
|
||||
- **Process**:
|
||||
- Validate user input (ensures non-empty prompt)
|
||||
- Set loading state (disable start button, show spinner)
|
||||
- Submit prompt and files
|
||||
- Receive workflow ID from backend
|
||||
- **State Changes**:
|
||||
- `status` → `running`
|
||||
- `workflowId` set to returned ID
|
||||
- `userInputState.additionalFiles` reset to empty array
|
||||
- `userInputState.promptText` reset to empty string
|
||||
- **UI Updates**:
|
||||
- Chat messages container shown
|
||||
- User message appears in chat
|
||||
- Log entry added: "Workflow started"
|
||||
- System chat message added: "Multi-Agent Chat has been started"
|
||||
- Start button becomes Send button, deactivated and with animation
|
||||
- Stop button becomes visible
|
||||
- Input field disabled temporarily
|
||||
- **API Interactions**:
|
||||
- `POST /api/workflows/start`: Submit user prompt and list of file IDs
|
||||
- Request payload: `{ prompt: string, fileIds: array }`
|
||||
- Response contains `workflowId` which is stored for future API calls
|
||||
- For continuing an existing workflow: `POST /api/workflows/start?id={workflowId}`
|
||||
|
||||
### 4. Polling for Updates
|
||||
- **State**: `running`
|
||||
- **Process**:
|
||||
- `pollWorkflowStatus()` initiated with workflow ID
|
||||
- `pollWorkflowLogs()` and `pollWorkflowMessages()` called periodically
|
||||
- Last log and message IDs tracked to request only new items
|
||||
- New logs and messages added to state
|
||||
- **State Changes**:
|
||||
- `logs` array updated with new log entries
|
||||
- `chatMessages` array updated with new messages
|
||||
- `lastPolledLogId` and `lastPolledMessageId` updated
|
||||
- `dataStats` updated with sent/received bytes and tokens
|
||||
- **UI Updates**:
|
||||
- Updated and new logs rendered in log panel
|
||||
- Updated and new chat messages rendered in chat area
|
||||
- Waiting animation shown on latest log entry
|
||||
- Data statistics counters updated
|
||||
- **API Interactions**:
|
||||
- `GET /api/workflows/{workflowId}/status`: Polls workflow status (every 2000ms)
|
||||
- `GET /api/workflows/{workflowId}/logs?id={lastPolledLogId}`: Gets only logs newer than the last polled log
|
||||
- `GET /api/workflows/{workflowId}/messages?id={lastPolledMessageId}`: Gets only messages newer than the last polled message
|
||||
- Polling continues until workflow status changes from "running" or a message with status "last" is received
|
||||
|
||||
### 5. Workflow Running
|
||||
- **State**: `running`
|
||||
- **Process**:
|
||||
- Backend processes user input through agent workflow
|
||||
- Frontend continuously polls for updates
|
||||
- Log entries show progress of agents
|
||||
- Chat messages display agent responses
|
||||
- **UI Elements**:
|
||||
- Stop button visible and enabled
|
||||
- Input field disabled
|
||||
- Log panel shows processing steps with progress indicators
|
||||
- Chat displays multi-agent conversation
|
||||
- **User Actions**:
|
||||
- Click Stop button to interrupt workflow
|
||||
- View file attachments in messages
|
||||
- Preview or download files
|
||||
- **API Interactions**:
|
||||
- Continued polling via endpoints described in State 4
|
||||
- `POST /api/workflows/{workflowId}/stop`: If user clicks Stop button
|
||||
|
||||
### 6. Workflow Completion
|
||||
- **State**: `completed`
|
||||
- **Trigger**: Backend returns workflow status "completed" or message with status "last"
|
||||
- **Process**:
|
||||
- Final message displayed, input enabled for new prompt
|
||||
- Polling stops
|
||||
- **State Changes**:
|
||||
- `status` → `completed`
|
||||
- **UI Updates**:
|
||||
- Stop button hidden
|
||||
- Start button enabled
|
||||
- Input field enabled and focused
|
||||
- Final log shows "Workflow completed"
|
||||
- **API Interactions**:
|
||||
- No further API calls until user inputs new prompt
|
||||
|
||||
### 7. Workflow Failure
|
||||
- **State**: `failed`
|
||||
- **Trigger**: Backend returns workflow status "failed"
|
||||
- **Process**:
|
||||
- Error message displayed, option to retry
|
||||
- Polling stops
|
||||
- **State Changes**:
|
||||
- `status` → `failed`
|
||||
- **UI Updates**:
|
||||
- Error indicators in UI
|
||||
- Retry option enabled
|
||||
- Input field enabled
|
||||
- **API Interactions**:
|
||||
- No further API calls until user initiates retry
|
||||
|
||||
### 8. Workflow Stopped
|
||||
- **State**: `stopped`
|
||||
- **Trigger**: User clicks Stop button or backend returns "stopped"
|
||||
- **Process**:
|
||||
- Workflow processing interrupted
|
||||
- Polling stops
|
||||
- **State Changes**:
|
||||
- `status` → `stopped`
|
||||
- **UI Updates**:
|
||||
- Resume option enabled
|
||||
- Start button enabled
|
||||
- Input field enabled
|
||||
- **API Interactions**:
|
||||
- No further API calls until user continues or resets
|
||||
|
||||
### 9. User Input Requested
|
||||
- **State**: Varies based on workflow state
|
||||
- **Trigger**: Received message with status "last" or workflow status not "running"
|
||||
- **Process**:
|
||||
- Special log entry added: "Waiting for user input to continue"
|
||||
- Input field enabled for user response
|
||||
- **State Changes**:
|
||||
- `status` → The status from the workflow
|
||||
- **UI Updates**:
|
||||
- Stop Polling
|
||||
- Waiting animation stopped
|
||||
- Input field enabled and focused
|
||||
- Send button enabled
|
||||
- Send button shows "Start" for new workflow or "Send" for continuation
|
||||
- Input field may show specific placeholder
|
||||
- Stop button hidden
|
||||
- **API Interactions**:
|
||||
- No API calls until user provides input
|
||||
|
||||
### 10. Continuation Preparation
|
||||
- **State**: `completed`, `failed`, or `stopped` (transitioning to `running`)
|
||||
- **Trigger**: User enters new input after previous workflow cycle
|
||||
- **Process**:
|
||||
- Prepare continuation with existing workflow ID
|
||||
- Similar to Prompt Preparation but preserves context
|
||||
- **State Changes**:
|
||||
- `userInputState.promptText` updated
|
||||
- `userInputState.additionalFiles` updated if new files added
|
||||
- **UI Updates**:
|
||||
- File attachment list updated if changed
|
||||
- Send button ready for continuation
|
||||
- **API Interactions**:
|
||||
- Same as Prompt Preparation if new files are added
|
||||
|
||||
### 11. Workflow Resumption
|
||||
- **State**: `running`
|
||||
- **Trigger**: User continues workflow after stop/failure/completion
|
||||
- **Process**:
|
||||
- Submit new input with existing workflow ID
|
||||
- **State Changes**:
|
||||
- `status` → `running`
|
||||
- New user message added
|
||||
- **UI Updates**:
|
||||
- Similar to Workflow Initialization but preserves history
|
||||
- Polling resumes
|
||||
- **API Interactions**:
|
||||
- `POST /api/workflows/start?id={workflowId}`: Sends continuation prompt with existing workflow ID
|
||||
- Polling endpoints resume as in State 4
|
||||
|
||||
### 12. Workflow Reset
|
||||
- **State**: `null`
|
||||
- **Trigger**: User clicks Reset button
|
||||
- **Process**:
|
||||
- All state data cleared
|
||||
- UI reset to initial state
|
||||
- **State Changes**:
|
||||
- `status` → `null`
|
||||
- `workflowId` → `null`
|
||||
- `logs` → `[]`
|
||||
- `chatMessages` → `[]`
|
||||
- **UI Updates**:
|
||||
- Empty chat state shown
|
||||
- Log panel cleared
|
||||
- Input field reset
|
||||
- Chat area cleared
|
||||
- File attachments removed
|
||||
- **API Interactions**:
|
||||
- Optional `DELETE /api/workflows/{workflowId}` to clean up server resources
|
||||
|
||||
## API Interaction Details
|
||||
|
||||
### Polling Implementation
|
||||
The frontend implements a sophisticated polling mechanism that efficiently retrieves only the new data:
|
||||
|
||||
1. **Initialization**:
|
||||
- When a workflow starts, the frontend stores the workflow ID
|
||||
- Initial polling begins immediately after receiving workflow ID
|
||||
|
||||
2. **Selective Data Retrieval**:
|
||||
- Frontend tracks `lastPolledLogId` and `lastPolledMessageId`
|
||||
- Each polling request includes the last ID to receive only newer items
|
||||
- This significantly reduces bandwidth and processing requirements
|
||||
|
||||
3. **Polling Schedule**:
|
||||
- Status polling: Every 2000ms while in `running` state
|
||||
- Logs polling: Every 1000ms while in `running` state
|
||||
- Messages polling: Every 1000ms while in `running` state
|
||||
- All polling stops when workflow status changes from `running`
|
||||
|
||||
4. **Retry Mechanism**:
|
||||
- Network failures trigger exponential backoff
|
||||
- Starting at 1000ms, doubling up to 16000ms max
|
||||
- After 5 consecutive failures, displays connection error
|
||||
|
||||
5. **Polling Suspension**:
|
||||
- Polling automatically pauses when browser tab is inactive
|
||||
- Resumes when tab becomes active again
|
||||
- Can be manually suspended with `suspendPolling()` during UI transitions
|
||||
|
||||
### API Endpoints Used
|
||||
|
||||
| Frontend Function | Backend Endpoint | Parameters | Description |
|
||||
|-------------------|------------------|------------|-------------|
|
||||
| `startWorkflow()` | `POST /api/workflows/start` | `{ prompt: string, fileIds: [] }` | Starts new workflow |
|
||||
| `continueWorkflow()` | `POST /api/workflows/start?id={workflowId}` | `{ prompt: string, fileIds: [] }` | Continues existing workflow |
|
||||
| `pollWorkflowStatus()` | `GET /api/workflows/{workflowId}/status` | None | Gets current workflow status |
|
||||
| `pollWorkflowLogs()` | `GET /api/workflows/{workflowId}/logs?id={lastLogId}` | Optional log ID | Gets logs newer than specified ID |
|
||||
| `pollWorkflowMessages()` | `GET /api/workflows/{workflowId}/messages?id={lastMessageId}` | Optional message ID | Gets messages newer than specified ID |
|
||||
| `stopWorkflow()` | `POST /api/workflows/{workflowId}/stop` | None | Interrupts running workflow |
|
||||
| `resetWorkflow()` | `DELETE /api/workflows/{workflowId}` | None | Cleans up workflow resources |
|
||||
| `uploadFile()` | `POST /api/files/upload` | FormData with file | Uploads file and returns file ID |
|
||||
| `deleteMessage()` | `DELETE /api/workflows/{workflowId}/messages/{messageId}` | None | Removes message from workflow |
|
||||
| `removeFileFromMessage()` | `DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}` | None | Removes file from message |
|
||||
|
||||
## Special Features
|
||||
|
||||
### File Handling
|
||||
1. **File Upload**:
|
||||
- Direct upload via button or drag & drop
|
||||
- FileId added to `userInputState.additionalFiles`
|
||||
- UI renders file in attachment list
|
||||
- Each file can be removed individually
|
||||
|
||||
2. **File Preview**:
|
||||
- Files in messages can be previewed
|
||||
- Preview shows content based on file type (text, image, PDF)
|
||||
- Files can be downloaded or copied to clipboard
|
||||
- Various file formats supported with appropriate visualizations
|
||||
|
||||
### Chat Message Rendering
|
||||
1. **Message Types**:
|
||||
- User messages (grey background)
|
||||
- Agent messages (white background)
|
||||
- Moderator questions (specially formatted)
|
||||
|
||||
2. **Message Features**:
|
||||
- Collapsible for long content
|
||||
- Symbol to delete message
|
||||
- File attachments with preview options
|
||||
- Markdown-like formatting support
|
||||
- Agent name and timestamp display
|
||||
|
||||
### Log Panel Features
|
||||
1. **Log Types**:
|
||||
- Info (blue)
|
||||
- Warning (orange)
|
||||
- Error (red)
|
||||
|
||||
2. **Log Features**:
|
||||
- Log items are read selectively from API: Only the last log entry and newer ones
|
||||
- Progress indicators for agent tasks; before a next log message is rendered, the last log message progress is set to 100%
|
||||
- Waiting animation for ongoing processes
|
||||
- Agent-specific highlighting
|
||||
- Collapsible detailed information, meaning the first n (e.g., 40) characters are by default displayed with "..." at the end. By clicking on "..." the additional part can be expanded/collapsed
|
||||
- Timestamp display
|
||||
|
||||
### Waiting States
|
||||
1. **Animation**:
|
||||
- Dots animation in log entries
|
||||
- Spinner in Send button during loading
|
||||
- Progress indicators in agent logs
|
||||
|
||||
2. **State Management**:
|
||||
- `waiting` flag in log objects
|
||||
- `waitingDotsInterval` controls animation
|
||||
- `setLoadingState()` manages UI element states
|
||||
|
||||
## State Transitions
|
||||
|
||||
```
|
||||
[null] → [running] // Initial prompt submission
|
||||
[running] → [running] // Ongoing polling updates
|
||||
[running] → [completed] // Workflow completes successfully
|
||||
[running] → [stopped] // User manually stops workflow
|
||||
[running] → [failed] // Error occurs during workflow
|
||||
[completed] → [running] // User continues workflow with new input
|
||||
[stopped] → [running] // User continues after manual stop
|
||||
[failed] → [running] // User retries workflow despite error
|
||||
[any] → [null] // User resets workflow
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Client-side Errors
|
||||
1. **Network Errors**:
|
||||
- Retry mechanism in polling functions
|
||||
- Exponential backoff for repeated failures
|
||||
- Informative error logs for debugging
|
||||
|
||||
2. **UI Errors**:
|
||||
- Validation before state changes
|
||||
- Fallback DOM element selection
|
||||
- Error boundaries for component isolation
|
||||
|
||||
### Backend Communication Errors
|
||||
1. **API Failures**:
|
||||
- Error logging in console
|
||||
- User-friendly error messages in UI
|
||||
- Error state with option to retry
|
||||
- Toast notifications for transient errors
|
||||
|
||||
2. **Data Inconsistencies**:
|
||||
- ID matching with fallback to partial matching
|
||||
- Multiple file reference methods
|
||||
- Content extraction fallbacks
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
1. **Module Structure**:
|
||||
- `workflow.js`: Main initialization and coordination
|
||||
- `workflowState.js`: State management
|
||||
- `workflowApi.js`: Backend communication
|
||||
- `workflowUi.js`: UI rendering
|
||||
- `workflowUtils.js`: Helper functions
|
||||
|
||||
2. **Key Functions**:
|
||||
- `workflowInit()`: Entry point for initialization
|
||||
- `workflowStatusUpdate()`: Core state transition function
|
||||
- `workflowStatusPoll()`: Main update loop
|
||||
- `workflowUserInput()`: Handles user input submission
|
||||
- `renderLogs()` and `renderMessages()`: UI update functions
|
||||
|
||||
3. **Performance Optimizations**:
|
||||
- Selective DOM updates
|
||||
- Scroll position preservation
|
||||
- Data size estimation for statistics
|
||||
- Conditional re-rendering
|
||||
48
notes/produce_diagrams.md
Normal file
48
notes/produce_diagrams.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
MERMAID DIAGRAM:
|
||||
|
||||
can you make chart "wiki/diagramm_komponenten.mermaid". produce an component diagram, based on current code in poweron/*
|
||||
if document existsadd missing components, remove obsolete components.
|
||||
|
||||
in box texts to use <br> instead of \n
|
||||
|
||||
for all subgraphs to to add path on a separate line to find the module in the code.
|
||||
|
||||
read all code modules caerfully to identify all components and their relations.
|
||||
|
||||
connectors without texts, only lines.
|
||||
|
||||
to add connector between frontend and backend (apiCalls.js -> app.py)
|
||||
|
||||
to connect app.py (Main application module) with the route*.py
|
||||
|
||||
to put all items of frontend into subgraph "Frontend"
|
||||
to put all items of gateway into subgraph "Gateway"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
to put following boxes to a dedicated subgraph within their existing subgraph:
|
||||
- workflowManager.py, workflowAgentsRegistry.py, documentProcessor.py, --> "Workflow"
|
||||
- mimeUtils.py, defAttributes.py, configuration.py, autho.py --> "Shared"
|
||||
- agent*.py --> "Agents"
|
||||
- workflow*.js --> "Workflow"
|
||||
- all *.js in js/modules/ not starting with workflow* --> "Administration"
|
||||
- formGeneric.js not to put to subgraph "Shared", but to a separated subgraph "Shared
|
||||
|
||||
to connect the main.js (main app in the frontend) to nativation.js, globalState.js, login.js, register.js, msftCall.js, config.js
|
||||
|
||||
to connect navigation.js to moduleLoader.js
|
||||
|
||||
to connect moduleLoader.js to workflow.js, and all *.js in js/modules/ not starting with workflow*
|
||||
|
||||
to connect all *.js in js/modules/ not starting with workflow* --> formGeneric.js
|
||||
|
||||
to connect fomrGeneric.js --> apiCalls.js
|
||||
|
||||
|
||||
to use underscores (e.g. Backend_Python, Workflow_Modules, etc.) for all subgraph titles.
|
||||
|
||||
if adding legend, then to give same colors like references to legend
|
||||
|
||||
|
|
@ -90,6 +90,7 @@ Für größere Installationen die JSON-basierte Datenbank ersetzen durch:
|
|||
### git permanent login with vs code
|
||||
git remote set-url origin https://valueon@github.com/valueonag/gateway
|
||||
git remote set-url origin https://valueon@github.com/valueonag/frontend_agents
|
||||
git remote set-url origin https://valueon@github.com/valueonag/wiki
|
||||
|
||||
## Lizenz
|
||||
|
||||
|
|
|
|||
105
notes/start.sh
105
notes/start.sh
|
|
@ -1,105 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Erkennen des Betriebssystems
|
||||
case "$(uname -s)" in
|
||||
CYGWIN*|MINGW*|MSYS*|Windows*)
|
||||
OS="Windows"
|
||||
VENV_PATH="gwserver/venv/Scripts"
|
||||
ACTIVATE_CMD="$VENV_PATH/activate"
|
||||
;;
|
||||
*)
|
||||
OS="Unix"
|
||||
VENV_PATH="gwserver/venv/bin"
|
||||
ACTIVATE_CMD="$VENV_PATH/activate"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Erkanntes Betriebssystem: $OS"
|
||||
|
||||
# Prüfen, ob Python installiert ist
|
||||
PYTHON_CMD=""
|
||||
if command -v python3 &>/dev/null; then
|
||||
PYTHON_CMD="python3"
|
||||
elif command -v python &>/dev/null; then
|
||||
PYTHON_CMD="python"
|
||||
else
|
||||
echo "Python ist nicht installiert. Bitte installieren Sie Python 3.8 oder höher."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Virtuelle Umgebung erstellen, falls sie nicht existiert
|
||||
if [ ! -d "gwserver/venv" ]; then
|
||||
echo "Erstelle virtuelle Python-Umgebung..."
|
||||
cd gwserver
|
||||
$PYTHON_CMD -m venv venv
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# Virtuelle Umgebung aktivieren
|
||||
echo "Aktiviere virtuelle Umgebung..."
|
||||
if [ "$OS" = "Windows" ]; then
|
||||
source "$ACTIVATE_CMD" 2>/dev/null || . "$ACTIVATE_CMD" 2>/dev/null || echo "Warnung: Aktivierung der virtuellen Umgebung fehlgeschlagen. Fahre fort..."
|
||||
else
|
||||
source "$ACTIVATE_CMD" 2>/dev/null || . "$ACTIVATE_CMD" 2>/dev/null || echo "Warnung: Aktivierung der virtuellen Umgebung fehlgeschlagen. Fahre fort..."
|
||||
fi
|
||||
|
||||
# Abhängigkeiten installieren
|
||||
echo "Installiere Abhängigkeiten..."
|
||||
pip install -r requirements.txt
|
||||
|
||||
# gwserver als Hintergrundprozess starten
|
||||
echo "Starte gwserver-Server..."
|
||||
|
||||
# START CORE
|
||||
cd gwserver
|
||||
if [ "$OS" = "Windows" ]; then
|
||||
# Windows: starte in einem separaten Prozess
|
||||
start uvicorn app:app --reload --host 0.0.0.0 --port 8000
|
||||
# Keine PID-Erfassung nötig unter Windows mit 'start'
|
||||
GWSERVER_PID=""
|
||||
else
|
||||
# Unix: starte im Hintergrund und erfasse PID
|
||||
uvicorn app:app --reload --host 0.0.0.0 --port 8000 &
|
||||
GWSERVER_PID=$!
|
||||
fi
|
||||
# END CORE
|
||||
|
||||
cd ..
|
||||
echo "gwserver API läuft auf: http://localhost:8000"
|
||||
echo "API-Dokumentation: http://localhost:8000/docs"
|
||||
echo "Drücke STRG+C, um Server zu beenden"
|
||||
|
||||
# Funktion zum Beenden der Server bei STRG+C
|
||||
cleanup() {
|
||||
echo -e "\nBeende Server..."
|
||||
if [ "$OS" = "Windows" ]; then
|
||||
# Windows: Taskkill für uvicorn
|
||||
taskkill //F //IM uvicorn.exe 2>/dev/null || echo "Konnte uvicorn nicht beenden"
|
||||
else
|
||||
# Unix: Verwende die erfasste PID
|
||||
if [ -n "$GWSERVER_PID" ]; then
|
||||
kill $GWSERVER_PID 2>/dev/null || echo "Konnte Prozess $GWSERVER_PID nicht beenden"
|
||||
fi
|
||||
fi
|
||||
echo "Server wurden beendet"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Signal-Handler für STRG+C
|
||||
trap cleanup SIGINT
|
||||
|
||||
# Warten auf Benutzeraktion
|
||||
if [ "$OS" = "Windows" ]; then
|
||||
# Unter Windows: Warte auf Eingabe
|
||||
read -p "Drücke Enter zum Beenden..." dummy
|
||||
cleanup
|
||||
else
|
||||
# Unter Unix: Warte auf Prozess
|
||||
if [ -n "$GWSERVER_PID" ]; then
|
||||
wait $GWSERVER_PID
|
||||
else
|
||||
# Fallback: einfach warten
|
||||
read -p "Drücke Enter zum Beenden..." dummy
|
||||
cleanup
|
||||
fi
|
||||
fi
|
||||
81
routes/routeGeneral.py
Normal file
81
routes/routeGeneral.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
from fastapi import APIRouter, HTTPException, Depends, Body, status, Response
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from typing import Dict, Any
|
||||
from datetime import timedelta
|
||||
import pathlib
|
||||
import os
|
||||
|
||||
from modules.configuration import APP_CONFIG
|
||||
from modules.auth import (
|
||||
createAccessToken,
|
||||
getCurrentActiveUser,
|
||||
getUserContext,
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
import modules.gatewayModel as gatewayModel
|
||||
from modules.gatewayInterface import getGatewayInterface
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Static folder for favicon
|
||||
baseDir = pathlib.Path(__file__).parent.parent
|
||||
staticFolder = baseDir / "static"
|
||||
|
||||
@router.get("/favicon.ico")
|
||||
async def favicon():
|
||||
return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon")
|
||||
|
||||
@router.get("/", tags=["General"])
|
||||
async def root():
|
||||
"""API status endpoint"""
|
||||
return {"status": "online", "message": "Data Platform API is active"}
|
||||
|
||||
@router.get("/api/test", tags=["General"])
|
||||
async def getTest():
|
||||
return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}"
|
||||
|
||||
@router.options("/{fullPath:path}", tags=["General"])
|
||||
async def optionsRoute(fullPath: str):
|
||||
return Response(status_code=200)
|
||||
|
||||
@router.get("/api/environment", tags=["General"])
|
||||
async def get_environment():
|
||||
"""Get environment configuration for frontend"""
|
||||
return {
|
||||
"apiBaseUrl": APP_CONFIG.get("APP_API_URL", ""),
|
||||
"environment": APP_CONFIG.get("APP_ENV", "development"),
|
||||
"instanceLabel": APP_CONFIG.get("APP_ENV_LABEL", "Development"),
|
||||
# Add other environment variables the frontend might need
|
||||
}
|
||||
|
||||
@router.post("/api/token", response_model=gatewayModel.Token, tags=["General"])
|
||||
async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
|
||||
# Initialize Gateway interface without context
|
||||
gateway = getGatewayInterface()
|
||||
|
||||
# Authenticate user
|
||||
user = gateway.authenticateUser(formData.username, formData.password)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Create token with tenant ID
|
||||
accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
accessToken = createAccessToken(
|
||||
data={
|
||||
"sub": user["username"],
|
||||
"mandateId": user["mandateId"]
|
||||
},
|
||||
expiresDelta=accessTokenExpires
|
||||
)
|
||||
|
||||
return {"accessToken": accessToken, "tokenType": "bearer"}
|
||||
|
||||
@router.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
|
||||
async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
|
||||
return currentUser
|
||||
Loading…
Reference in a new issue