From 8d28f6d77b3294b387c170626c5f0c3e32e811df Mon Sep 17 00:00:00 2001 From: patrick-motsch Date: Sun, 8 Feb 2026 14:26:01 +0100 Subject: [PATCH] fiixed feature instance role access --- .../automation/interfaceFeatureAutomation.py | 19 +- .../automation/routeFeatureAutomation.py | 28 +- .../features/chatbot/routeFeatureChatbot.py | 14 +- .../routeFeatureChatplayground.py | 14 +- .../neutralization/routeFeatureNeutralizer.py | 16 +- .../realEstate/routeFeatureRealEstate.py | 58 +-- .../features/trustee/routeFeatureTrustee.py | 232 +++++------ modules/routes/routeAdmin.py | 8 +- modules/routes/routeAdminAutomationEvents.py | 4 +- modules/routes/routeAdminFeatures.py | 34 +- modules/routes/routeAdminRbacExport.py | 4 +- modules/routes/routeAdminRbacRoles.py | 24 +- modules/routes/routeAdminRbacRules.py | 34 +- .../routes/routeAdminUserAccessOverview.py | 6 +- modules/routes/routeAttributes.py | 4 +- modules/routes/routeBilling.py | 32 +- modules/routes/routeDataConnections.py | 14 +- modules/routes/routeDataFiles.py | 14 +- modules/routes/routeDataMandates.py | 18 +- modules/routes/routeDataPrompts.py | 10 +- modules/routes/routeDataUsers.py | 18 +- modules/routes/routeDataWorkflows.py | 24 +- modules/routes/routeGdpr.py | 8 +- modules/routes/routeInvitations.py | 10 +- modules/routes/routeMessaging.py | 28 +- modules/routes/routeNotifications.py | 16 +- modules/routes/routeSecurityAdmin.py | 20 +- modules/routes/routeSecurityGoogle.py | 8 +- modules/routes/routeSecurityLocal.py | 16 +- modules/routes/routeSecurityMsft.py | 12 +- modules/routes/routeSystem.py | 2 +- modules/workflows/automation/mainWorkflow.py | 14 +- scripts/migrate_async_to_sync.py | 377 ++++++++++++++++++ 33 files changed, 764 insertions(+), 376 deletions(-) create mode 100644 scripts/migrate_async_to_sync.py diff --git a/modules/features/automation/interfaceFeatureAutomation.py b/modules/features/automation/interfaceFeatureAutomation.py index f88c3973..770d5eb0 100644 --- a/modules/features/automation/interfaceFeatureAutomation.py +++ b/modules/features/automation/interfaceFeatureAutomation.py @@ -88,7 +88,9 @@ class AutomationObjects: permissions = self.rbac.getUserPermissions( user=self.currentUser, context=AccessRuleContext.DATA, - item=objectKey + item=objectKey, + mandateId=self.mandateId, + featureInstanceId=self.featureInstanceId ) accessLevel = getattr(permissions, action, AccessLevel.NONE) @@ -373,6 +375,21 @@ class AutomationObjects: logger.error(f"Error creating automation definition: {str(e)}") raise + def _saveExecutionLog(self, automationId: str, executionLogs: List[Dict[str, Any]]) -> None: + """ + Save execution logs to an automation definition WITHOUT RBAC check. + + This is a system-level operation: when a user executes an automation, + the execution log must be saved regardless of whether the user has + 'update' permission on the AutomationDefinition. The user already + proved they have execute/read access by loading the automation. + """ + try: + self.db.recordModify(AutomationDefinition, automationId, {"executionLogs": executionLogs}) + logger.debug(f"Saved execution log for automation {automationId}") + except Exception as e: + logger.warning(f"Could not save execution log for automation {automationId}: {e}") + def updateAutomationDefinition(self, automationId: str, automationData: Dict[str, Any]) -> AutomationDefinition: """Updates an automation definition, then triggers sync.""" try: diff --git a/modules/features/automation/routeFeatureAutomation.py b/modules/features/automation/routeFeatureAutomation.py index d9c0b758..d39a3358 100644 --- a/modules/features/automation/routeFeatureAutomation.py +++ b/modules/features/automation/routeFeatureAutomation.py @@ -42,7 +42,7 @@ router = APIRouter( @router.get("", response_model=PaginatedResponse[AutomationDefinition]) @limiter.limit("30/minute") -async def get_automations( +def get_automations( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), context: RequestContext = Depends(getRequestContext) @@ -107,7 +107,7 @@ async def get_automations( @router.post("", response_model=AutomationDefinition) @limiter.limit("10/minute") -async def create_automation( +def create_automation( request: Request, automation: AutomationDefinition, context: RequestContext = Depends(getRequestContext) @@ -128,7 +128,7 @@ async def create_automation( ) @router.get("/attributes", response_model=Dict[str, Any]) -async def get_automation_attributes( +def get_automation_attributes( request: Request ) -> Dict[str, Any]: """Get attribute definitions for AutomationDefinition model""" @@ -137,7 +137,7 @@ async def get_automation_attributes( @router.get("/actions") @limiter.limit("30/minute") -async def get_available_actions( +def get_available_actions( request: Request, context: RequestContext = Depends(getRequestContext) ) -> JSONResponse: @@ -230,7 +230,7 @@ async def get_available_actions( @router.get("/{automationId}", response_model=AutomationDefinition) @limiter.limit("30/minute") -async def get_automation( +def get_automation( request: Request, automationId: str = Path(..., description="Automation ID"), context: RequestContext = Depends(getRequestContext) @@ -257,7 +257,7 @@ async def get_automation( @router.put("/{automationId}", response_model=AutomationDefinition) @limiter.limit("10/minute") -async def update_automation( +def update_automation( request: Request, automationId: str = Path(..., description="Automation ID"), automation: AutomationDefinition = Body(...), @@ -285,7 +285,7 @@ async def update_automation( @router.patch("/{automationId}/status") @limiter.limit("30/minute") -async def update_automation_status( +def update_automation_status( request: Request, automationId: str = Path(..., description="Automation ID"), active: bool = Body(..., embed=True), @@ -326,7 +326,7 @@ async def update_automation_status( @router.delete("/{automationId}") @limiter.limit("10/minute") -async def delete_automation( +def delete_automation( request: Request, automationId: str = Path(..., description="Automation ID"), context: RequestContext = Depends(getRequestContext) @@ -407,7 +407,7 @@ templateAttributes = getModelAttributeDefinitions(AutomationTemplate) @templateRouter.get("", response_model=PaginatedResponse[AutomationTemplate]) @limiter.limit("30/minute") -async def get_db_templates( +def get_db_templates( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), context: RequestContext = Depends(getRequestContext) @@ -470,7 +470,7 @@ async def get_db_templates( @templateRouter.get("/attributes", response_model=Dict[str, Any]) -async def get_template_attributes( +def get_template_attributes( request: Request ) -> Dict[str, Any]: """Get attribute definitions for AutomationTemplate model""" @@ -479,7 +479,7 @@ async def get_template_attributes( @templateRouter.get("/{templateId}") @limiter.limit("30/minute") -async def get_db_template( +def get_db_template( request: Request, templateId: str = Path(..., description="Template ID"), context: RequestContext = Depends(getRequestContext) @@ -511,7 +511,7 @@ async def get_db_template( @templateRouter.post("") @limiter.limit("10/minute") -async def create_db_template( +def create_db_template( request: Request, templateData: Dict[str, Any] = Body(...), context: RequestContext = Depends(getRequestContext) @@ -542,7 +542,7 @@ async def create_db_template( @templateRouter.put("/{templateId}") @limiter.limit("10/minute") -async def update_db_template( +def update_db_template( request: Request, templateId: str = Path(..., description="Template ID"), templateData: Dict[str, Any] = Body(...), @@ -574,7 +574,7 @@ async def update_db_template( @templateRouter.delete("/{templateId}") @limiter.limit("10/minute") -async def delete_db_template( +def delete_db_template( request: Request, templateId: str = Path(..., description="Template ID"), context: RequestContext = Depends(getRequestContext) diff --git a/modules/features/chatbot/routeFeatureChatbot.py b/modules/features/chatbot/routeFeatureChatbot.py index 62dc02f9..d8b4dd70 100644 --- a/modules/features/chatbot/routeFeatureChatbot.py +++ b/modules/features/chatbot/routeFeatureChatbot.py @@ -55,7 +55,7 @@ def _getServiceChat(context: RequestContext, instanceId: Optional[str] = None): ) -async def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str: +def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str: """ Validate that the user has access to the feature instance. Returns the mandateId for the instance. @@ -124,7 +124,7 @@ async def stream_chatbot_start( - Query parameter takes precedence if both are provided """ # Validate instance access - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) event_manager = get_event_manager() @@ -323,7 +323,7 @@ async def stop_chatbot( ) -> ChatWorkflow: """Stops a running chatbot workflow.""" # Validate instance access - await _validateInstanceAccess(instanceId, context) + _validateInstanceAccess(instanceId, context) try: # Get chatbot interface with instance context @@ -392,7 +392,7 @@ async def stop_chatbot( # to prevent "threads" from being matched as a workflowId @router.get("/{instanceId}/threads") @limiter.limit("120/minute") -async def get_chatbot_threads( +def get_chatbot_threads( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), workflowId: Optional[str] = Query(None, description="Optional workflow ID to get details and chat data for a specific thread"), @@ -406,7 +406,7 @@ async def get_chatbot_threads( - If workflowId is not provided: Returns a paginated list of all workflows """ # Validate instance access - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) try: interfaceDbChat = _getServiceChat(context, instanceId) @@ -523,7 +523,7 @@ async def get_chatbot_threads( # NOTE: This catch-all route MUST be defined AFTER more specific routes like /threads @router.delete("/{instanceId}/{workflowId}", response_model=Dict[str, Any]) @limiter.limit("120/minute") -async def delete_chatbot( +def delete_chatbot( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), workflowId: str = Path(..., description="ID of the workflow to delete"), @@ -531,7 +531,7 @@ async def delete_chatbot( ) -> Dict[str, Any]: """Deletes a chatbot workflow and its associated data.""" # Validate instance access - if user has access to instance, they can delete their workflows - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) try: # Get service center diff --git a/modules/features/chatplayground/routeFeatureChatplayground.py b/modules/features/chatplayground/routeFeatureChatplayground.py index cedd05d6..ce1611ea 100644 --- a/modules/features/chatplayground/routeFeatureChatplayground.py +++ b/modules/features/chatplayground/routeFeatureChatplayground.py @@ -41,7 +41,7 @@ def _getServiceChat(context: RequestContext, featureInstanceId: str = None): ) -async def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str: +def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str: """ Validate that user has access to the feature instance. @@ -93,7 +93,7 @@ async def start_workflow( """ try: # Validate access and get mandate ID - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) # Start or continue workflow workflow = await chatStart( @@ -129,7 +129,7 @@ async def stop_workflow( """Stops a running workflow.""" try: # Validate access and get mandate ID - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) # Stop workflow (pass featureInstanceId for proper RBAC filtering) workflow = await chatStop( @@ -154,7 +154,7 @@ async def stop_workflow( # Unified Chat Data Endpoint for Polling @router.get("/{instanceId}/{workflowId}/chatData") @limiter.limit("120/minute") -async def get_workflow_chat_data( +def get_workflow_chat_data( request: Request, instanceId: str = Path(..., description="Feature instance ID"), workflowId: str = Path(..., description="ID of the workflow"), @@ -167,7 +167,7 @@ async def get_workflow_chat_data( """ try: # Validate access - await _validateInstanceAccess(instanceId, context) + _validateInstanceAccess(instanceId, context) # Get service with feature instance context chatInterface = _getServiceChat(context, featureInstanceId=instanceId) @@ -198,7 +198,7 @@ async def get_workflow_chat_data( # Get workflows for this instance @router.get("/{instanceId}/workflows") @limiter.limit("120/minute") -async def get_workflows( +def get_workflows( request: Request, instanceId: str = Path(..., description="Feature instance ID"), page: int = Query(1, ge=1, description="Page number"), @@ -210,7 +210,7 @@ async def get_workflows( """ try: # Validate access - await _validateInstanceAccess(instanceId, context) + _validateInstanceAccess(instanceId, context) # Get service with feature instance context chatInterface = _getServiceChat(context, featureInstanceId=instanceId) diff --git a/modules/features/neutralization/routeFeatureNeutralizer.py b/modules/features/neutralization/routeFeatureNeutralizer.py index be262e47..33b9a00d 100644 --- a/modules/features/neutralization/routeFeatureNeutralizer.py +++ b/modules/features/neutralization/routeFeatureNeutralizer.py @@ -29,7 +29,7 @@ router = APIRouter( @router.get("/config", response_model=DataNeutraliserConfig) @limiter.limit("30/minute") -async def get_neutralization_config( +def get_neutralization_config( request: Request, context: RequestContext = Depends(getRequestContext) ) -> DataNeutraliserConfig: @@ -62,7 +62,7 @@ async def get_neutralization_config( @router.post("/config", response_model=DataNeutraliserConfig) @limiter.limit("10/minute") -async def save_neutralization_config( +def save_neutralization_config( request: Request, config_data: Dict[str, Any] = Body(...), context: RequestContext = Depends(getRequestContext) @@ -83,7 +83,7 @@ async def save_neutralization_config( @router.post("/neutralize-text", response_model=Dict[str, Any]) @limiter.limit("20/minute") -async def neutralize_text( +def neutralize_text( request: Request, text_data: Dict[str, Any] = Body(...), context: RequestContext = Depends(getRequestContext) @@ -115,7 +115,7 @@ async def neutralize_text( @router.post("/resolve-text", response_model=Dict[str, str]) @limiter.limit("20/minute") -async def resolve_text( +def resolve_text( request: Request, text_data: Dict[str, str] = Body(...), context: RequestContext = Depends(getRequestContext) @@ -146,7 +146,7 @@ async def resolve_text( @router.get("/attributes", response_model=List[DataNeutralizerAttributes]) @limiter.limit("30/minute") -async def get_neutralization_attributes( +def get_neutralization_attributes( request: Request, fileId: Optional[str] = Query(None, description="Filter by file ID"), context: RequestContext = Depends(getRequestContext) @@ -199,7 +199,7 @@ async def process_sharepoint_files( @router.post("/batch-process", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def batch_process_files( +def batch_process_files( request: Request, files_data: List[Dict[str, Any]] = Body(...), context: RequestContext = Depends(getRequestContext) @@ -228,7 +228,7 @@ async def batch_process_files( @router.get("/stats", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def get_neutralization_stats( +def get_neutralization_stats( request: Request, context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: @@ -248,7 +248,7 @@ async def get_neutralization_stats( @router.delete("/attributes/{fileId}", response_model=Dict[str, str]) @limiter.limit("10/minute") -async def cleanup_file_attributes( +def cleanup_file_attributes( request: Request, fileId: str = Path(..., description="File ID to cleanup attributes for"), context: RequestContext = Depends(getRequestContext) diff --git a/modules/features/realEstate/routeFeatureRealEstate.py b/modules/features/realEstate/routeFeatureRealEstate.py index 09f28f13..18fecd73 100644 --- a/modules/features/realEstate/routeFeatureRealEstate.py +++ b/modules/features/realEstate/routeFeatureRealEstate.py @@ -83,7 +83,7 @@ def _parsePagination(pagination: Optional[str]) -> Optional[PaginationParams]: return None -async def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str: +def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str: """ Validate that the user has access to the feature instance. Returns the mandateId for the instance. @@ -132,14 +132,14 @@ _REALESTATE_ENTITY_MODELS = { @router.get("/{instanceId}/attributes/{entityType}", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def get_entity_attributes( +def get_entity_attributes( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), entityType: str = Path(..., description="Entity type (e.g., Projekt, Parzelle)"), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Get attribute definitions for a Real Estate entity. Used by FormGeneratorTable.""" - await _validateInstanceAccess(instanceId, context) + _validateInstanceAccess(instanceId, context) if entityType not in _REALESTATE_ENTITY_MODELS: raise HTTPException( status_code=404, @@ -163,13 +163,13 @@ async def get_entity_attributes( @router.get("/{instanceId}/projects/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_project_options( +def get_project_options( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), context: RequestContext = Depends(getRequestContext) ) -> List[Dict[str, Any]]: """Get project options for select dropdowns. Returns: [{ value, label }]""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -179,13 +179,13 @@ async def get_project_options( @router.get("/{instanceId}/parcels/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_parcel_options( +def get_parcel_options( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), context: RequestContext = Depends(getRequestContext) ) -> List[Dict[str, Any]]: """Get parcel options for select dropdowns. Returns: [{ value, label }]""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -197,14 +197,14 @@ async def get_parcel_options( @router.get("/{instanceId}/projects", response_model=PaginatedResponse[Projekt]) @limiter.limit("30/minute") -async def get_projects( +def get_projects( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams"), context: RequestContext = Depends(getRequestContext) ) -> PaginatedResponse[Projekt]: """Get all projects for a feature instance with optional pagination.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -241,14 +241,14 @@ async def get_projects( @router.get("/{instanceId}/projects/{projectId}", response_model=Projekt) @limiter.limit("30/minute") -async def get_project_by_id( +def get_project_by_id( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), projectId: str = Path(..., description="Project ID"), context: RequestContext = Depends(getRequestContext) ) -> Projekt: """Get a single project by ID.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -260,14 +260,14 @@ async def get_project_by_id( @router.post("/{instanceId}/projects", response_model=Projekt) @limiter.limit("30/minute") -async def create_project( +def create_project( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), data: Dict[str, Any] = Body(...), context: RequestContext = Depends(getRequestContext) ) -> Projekt: """Create a new project.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -284,7 +284,7 @@ async def create_project( @router.put("/{instanceId}/projects/{projectId}", response_model=Projekt) @limiter.limit("30/minute") -async def update_project( +def update_project( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), projectId: str = Path(..., description="Project ID"), @@ -292,7 +292,7 @@ async def update_project( context: RequestContext = Depends(getRequestContext) ) -> Projekt: """Update a project.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -307,14 +307,14 @@ async def update_project( @router.delete("/{instanceId}/projects/{projectId}", status_code=status.HTTP_204_NO_CONTENT) @limiter.limit("30/minute") -async def delete_project( +def delete_project( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), projectId: str = Path(..., description="Project ID"), context: RequestContext = Depends(getRequestContext) ) -> None: """Delete a project.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -329,14 +329,14 @@ async def delete_project( @router.get("/{instanceId}/parcels", response_model=PaginatedResponse[Parzelle]) @limiter.limit("30/minute") -async def get_parcels( +def get_parcels( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams"), context: RequestContext = Depends(getRequestContext) ) -> PaginatedResponse[Parzelle]: """Get all parcels for a feature instance with optional pagination.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -373,14 +373,14 @@ async def get_parcels( @router.get("/{instanceId}/parcels/{parcelId}", response_model=Parzelle) @limiter.limit("30/minute") -async def get_parcel_by_id( +def get_parcel_by_id( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), parcelId: str = Path(..., description="Parcel ID"), context: RequestContext = Depends(getRequestContext) ) -> Parzelle: """Get a single parcel by ID.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -392,14 +392,14 @@ async def get_parcel_by_id( @router.post("/{instanceId}/parcels", response_model=Parzelle) @limiter.limit("30/minute") -async def create_parcel( +def create_parcel( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), data: Dict[str, Any] = Body(...), context: RequestContext = Depends(getRequestContext) ) -> Parzelle: """Create a new parcel.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -416,7 +416,7 @@ async def create_parcel( @router.put("/{instanceId}/parcels/{parcelId}", response_model=Parzelle) @limiter.limit("30/minute") -async def update_parcel( +def update_parcel( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), parcelId: str = Path(..., description="Parcel ID"), @@ -424,7 +424,7 @@ async def update_parcel( context: RequestContext = Depends(getRequestContext) ) -> Parzelle: """Update a parcel.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -439,14 +439,14 @@ async def update_parcel( @router.delete("/{instanceId}/parcels/{parcelId}", status_code=status.HTTP_204_NO_CONTENT) @limiter.limit("30/minute") -async def delete_parcel( +def delete_parcel( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), parcelId: str = Path(..., description="Parcel ID"), context: RequestContext = Depends(getRequestContext) ) -> None: """Delete a parcel.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getRealEstateInterface( context.user, mandateId=mandateId, featureInstanceId=instanceId ) @@ -549,7 +549,7 @@ async def process_command( @router.get("/tables", response_model=Dict[str, Any]) @limiter.limit("120/minute") -async def get_available_tables( +def get_available_tables( request: Request, context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: @@ -645,7 +645,7 @@ async def get_available_tables( @router.get("/table/{table}", response_model=PaginatedResponse[Any]) @limiter.limit("120/minute") -async def get_table_data( +def get_table_data( request: Request, table: str = Path(..., description="Table name (Projekt, Parzelle, Dokument, Gemeinde, Kanton, Land)"), pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), diff --git a/modules/features/trustee/routeFeatureTrustee.py b/modules/features/trustee/routeFeatureTrustee.py index 43706a10..408871c5 100644 --- a/modules/features/trustee/routeFeatureTrustee.py +++ b/modules/features/trustee/routeFeatureTrustee.py @@ -66,7 +66,7 @@ def _parsePagination(pagination: Optional[str]) -> Optional[PaginationParams]: return None -async def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str: +def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str: """ Validate that the user has access to the feature instance. Returns the mandateId for the instance. @@ -134,7 +134,7 @@ _TRUSTEE_ENTITY_MODELS = { @router.get("/{instanceId}/attributes/{entityType}") @limiter.limit("30/minute") -async def get_entity_attributes( +def get_entity_attributes( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), entityType: str = Path(..., description="Entity type (e.g., TrusteeDocument)"), @@ -145,7 +145,7 @@ async def get_entity_attributes( Used by FormGeneratorTable for dynamic column generation. """ # Validate instance access - await _validateInstanceAccess(instanceId, context) + _validateInstanceAccess(instanceId, context) # Check if entity type is valid if entityType not in _TRUSTEE_ENTITY_MODELS: @@ -182,7 +182,7 @@ async def get_entity_attributes( @router.get("/mime-types/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_mime_type_options( +def get_mime_type_options( request: Request, context: RequestContext = Depends(getRequestContext) ) -> List[Dict[str, Any]]: @@ -217,13 +217,13 @@ async def get_mime_type_options( @router.get("/{instanceId}/organisations/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_organisation_options( +def get_organisation_options( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), context: RequestContext = Depends(getRequestContext) ) -> List[Dict[str, Any]]: """Get organisation options for select dropdowns. Returns: [{ value, label }]""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.getAllOrganisations(None) items = result.items if hasattr(result, 'items') else result @@ -232,13 +232,13 @@ async def get_organisation_options( @router.get("/{instanceId}/roles/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_role_options( +def get_role_options( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), context: RequestContext = Depends(getRequestContext) ) -> List[Dict[str, Any]]: """Get role options for select dropdowns. Returns: [{ value, label }]""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.getAllRoles(None) items = result.items if hasattr(result, 'items') else result @@ -247,7 +247,7 @@ async def get_role_options( @router.get("/{instanceId}/contracts/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_contract_options( +def get_contract_options( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), organisationId: Optional[str] = Query(None, description="Optional: Filter by organisation ID"), @@ -261,7 +261,7 @@ async def get_contract_options( Returns: [{ value, label }] """ - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) if organisationId: @@ -277,13 +277,13 @@ async def get_contract_options( @router.get("/{instanceId}/documents/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_document_options( +def get_document_options( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), context: RequestContext = Depends(getRequestContext) ) -> List[Dict[str, Any]]: """Get document options for select dropdowns. Returns: [{ id, value, label }]""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.getAllDocuments(None) items = result.items if hasattr(result, 'items') else result @@ -293,13 +293,13 @@ async def get_document_options( @router.get("/{instanceId}/positions/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_position_options( +def get_position_options( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), context: RequestContext = Depends(getRequestContext) ) -> List[Dict[str, Any]]: """Get position options for select dropdowns. Returns: [{ id, value, label }]""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.getAllPositions(None) items = result.items if hasattr(result, 'items') else result @@ -326,14 +326,14 @@ async def get_position_options( @router.get("/{instanceId}/organisations", response_model=PaginatedResponse[TrusteeOrganisation]) @limiter.limit("30/minute") -async def get_organisations( +def get_organisations( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams"), context: RequestContext = Depends(getRequestContext) ) -> PaginatedResponse[TrusteeOrganisation]: """Get all organisations for a feature instance with optional pagination.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) paginationParams = _parsePagination(pagination) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) @@ -356,14 +356,14 @@ async def get_organisations( @router.get("/{instanceId}/organisations/{orgId}", response_model=TrusteeOrganisation) @limiter.limit("30/minute") -async def get_organisation( +def get_organisation( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), orgId: str = Path(..., description="Organisation ID"), context: RequestContext = Depends(getRequestContext) ) -> TrusteeOrganisation: """Get a single organisation by ID.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) org = interface.getOrganisation(orgId) @@ -374,14 +374,14 @@ async def get_organisation( @router.post("/{instanceId}/organisations", response_model=TrusteeOrganisation, status_code=201) @limiter.limit("10/minute") -async def create_organisation( +def create_organisation( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), data: TrusteeOrganisation = Body(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteeOrganisation: """Create a new organisation.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.createOrganisation(data.model_dump()) @@ -392,7 +392,7 @@ async def create_organisation( @router.put("/{instanceId}/organisations/{orgId}", response_model=TrusteeOrganisation) @limiter.limit("10/minute") -async def update_organisation( +def update_organisation( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), orgId: str = Path(..., description="Organisation ID"), @@ -400,7 +400,7 @@ async def update_organisation( context: RequestContext = Depends(getRequestContext) ) -> TrusteeOrganisation: """Update an organisation.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getOrganisation(orgId) @@ -415,14 +415,14 @@ async def update_organisation( @router.delete("/{instanceId}/organisations/{orgId}") @limiter.limit("10/minute") -async def delete_organisation( +def delete_organisation( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), orgId: str = Path(..., description="Organisation ID"), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Delete an organisation.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getOrganisation(orgId) @@ -439,14 +439,14 @@ async def delete_organisation( @router.get("/{instanceId}/roles", response_model=PaginatedResponse[TrusteeRole]) @limiter.limit("30/minute") -async def get_roles( +def get_roles( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), pagination: Optional[str] = Query(None), context: RequestContext = Depends(getRequestContext) ) -> PaginatedResponse[TrusteeRole]: """Get all roles with optional pagination.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) paginationParams = _parsePagination(pagination) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) @@ -469,14 +469,14 @@ async def get_roles( @router.get("/{instanceId}/roles/{roleId}", response_model=TrusteeRole) @limiter.limit("30/minute") -async def get_role( +def get_role( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), roleId: str = Path(..., description="Role ID"), context: RequestContext = Depends(getRequestContext) ) -> TrusteeRole: """Get a single role by ID.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) role = interface.getRole(roleId) @@ -487,14 +487,14 @@ async def get_role( @router.post("/{instanceId}/roles", response_model=TrusteeRole, status_code=201) @limiter.limit("10/minute") -async def create_role( +def create_role( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), data: TrusteeRole = Body(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteeRole: """Create a new role (sysadmin only).""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.createRole(data.model_dump()) @@ -505,7 +505,7 @@ async def create_role( @router.put("/{instanceId}/roles/{roleId}", response_model=TrusteeRole) @limiter.limit("10/minute") -async def update_role( +def update_role( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), roleId: str = Path(...), @@ -513,7 +513,7 @@ async def update_role( context: RequestContext = Depends(getRequestContext) ) -> TrusteeRole: """Update a role (sysadmin only).""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getRole(roleId) @@ -528,14 +528,14 @@ async def update_role( @router.delete("/{instanceId}/roles/{roleId}") @limiter.limit("10/minute") -async def delete_role( +def delete_role( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), roleId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Delete a role (sysadmin only, fails if in use).""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getRole(roleId) @@ -552,14 +552,14 @@ async def delete_role( @router.get("/{instanceId}/access", response_model=PaginatedResponse[TrusteeAccess]) @limiter.limit("30/minute") -async def get_all_access( +def get_all_access( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), pagination: Optional[str] = Query(None), context: RequestContext = Depends(getRequestContext) ) -> PaginatedResponse[TrusteeAccess]: """Get all access records with optional pagination.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) paginationParams = _parsePagination(pagination) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) @@ -582,14 +582,14 @@ async def get_all_access( @router.get("/{instanceId}/access/{accessId}", response_model=TrusteeAccess) @limiter.limit("30/minute") -async def get_access( +def get_access( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), accessId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteeAccess: """Get a single access record by ID.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) access = interface.getAccess(accessId) @@ -600,14 +600,14 @@ async def get_access( @router.get("/{instanceId}/access/organisation/{orgId}", response_model=List[TrusteeAccess]) @limiter.limit("30/minute") -async def get_access_by_organisation( +def get_access_by_organisation( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), orgId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> List[TrusteeAccess]: """Get all access records for an organisation.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) return interface.getAccessByOrganisation(orgId) @@ -615,14 +615,14 @@ async def get_access_by_organisation( @router.get("/{instanceId}/access/user/{userId}", response_model=List[TrusteeAccess]) @limiter.limit("30/minute") -async def get_access_by_user( +def get_access_by_user( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), userId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> List[TrusteeAccess]: """Get all access records for a user.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) return interface.getAccessByUser(userId) @@ -630,14 +630,14 @@ async def get_access_by_user( @router.post("/{instanceId}/access", response_model=TrusteeAccess, status_code=201) @limiter.limit("10/minute") -async def create_access( +def create_access( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), data: TrusteeAccess = Body(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteeAccess: """Create a new access record.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.createAccess(data.model_dump()) @@ -648,7 +648,7 @@ async def create_access( @router.put("/{instanceId}/access/{accessId}", response_model=TrusteeAccess) @limiter.limit("10/minute") -async def update_access( +def update_access( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), accessId: str = Path(...), @@ -656,7 +656,7 @@ async def update_access( context: RequestContext = Depends(getRequestContext) ) -> TrusteeAccess: """Update an access record.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getAccess(accessId) @@ -671,14 +671,14 @@ async def update_access( @router.delete("/{instanceId}/access/{accessId}") @limiter.limit("10/minute") -async def delete_access( +def delete_access( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), accessId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Delete an access record.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getAccess(accessId) @@ -695,14 +695,14 @@ async def delete_access( @router.get("/{instanceId}/contracts", response_model=PaginatedResponse[TrusteeContract]) @limiter.limit("30/minute") -async def get_contracts( +def get_contracts( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), pagination: Optional[str] = Query(None), context: RequestContext = Depends(getRequestContext) ) -> PaginatedResponse[TrusteeContract]: """Get all contracts with optional pagination.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) paginationParams = _parsePagination(pagination) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) @@ -725,14 +725,14 @@ async def get_contracts( @router.get("/{instanceId}/contracts/{contractId}", response_model=TrusteeContract) @limiter.limit("30/minute") -async def get_contract( +def get_contract( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), contractId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteeContract: """Get a single contract by ID.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) contract = interface.getContract(contractId) @@ -743,14 +743,14 @@ async def get_contract( @router.get("/{instanceId}/contracts/organisation/{orgId}", response_model=List[TrusteeContract]) @limiter.limit("30/minute") -async def get_contracts_by_organisation( +def get_contracts_by_organisation( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), orgId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> List[TrusteeContract]: """Get all contracts for an organisation.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) return interface.getContractsByOrganisation(orgId) @@ -758,14 +758,14 @@ async def get_contracts_by_organisation( @router.post("/{instanceId}/contracts", response_model=TrusteeContract, status_code=201) @limiter.limit("10/minute") -async def create_contract( +def create_contract( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), data: TrusteeContract = Body(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteeContract: """Create a new contract.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.createContract(data.model_dump()) @@ -776,7 +776,7 @@ async def create_contract( @router.put("/{instanceId}/contracts/{contractId}", response_model=TrusteeContract) @limiter.limit("10/minute") -async def update_contract( +def update_contract( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), contractId: str = Path(...), @@ -784,7 +784,7 @@ async def update_contract( context: RequestContext = Depends(getRequestContext) ) -> TrusteeContract: """Update a contract (organisationId is immutable).""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getContract(contractId) @@ -799,14 +799,14 @@ async def update_contract( @router.delete("/{instanceId}/contracts/{contractId}") @limiter.limit("10/minute") -async def delete_contract( +def delete_contract( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), contractId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Delete a contract.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getContract(contractId) @@ -823,14 +823,14 @@ async def delete_contract( @router.get("/{instanceId}/documents", response_model=PaginatedResponse[TrusteeDocument]) @limiter.limit("30/minute") -async def get_documents( +def get_documents( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), pagination: Optional[str] = Query(None), context: RequestContext = Depends(getRequestContext) ) -> PaginatedResponse[TrusteeDocument]: """Get all documents (metadata only) with optional pagination.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) paginationParams = _parsePagination(pagination) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) @@ -853,14 +853,14 @@ async def get_documents( @router.get("/{instanceId}/documents/{documentId}", response_model=TrusteeDocument) @limiter.limit("30/minute") -async def get_document( +def get_document( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), documentId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteeDocument: """Get document metadata by ID.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) doc = interface.getDocument(documentId) @@ -871,14 +871,14 @@ async def get_document( @router.get("/{instanceId}/documents/{documentId}/data") @limiter.limit("10/minute") -async def get_document_data( +def get_document_data( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), documentId: str = Path(...), context: RequestContext = Depends(getRequestContext) ): """Download document binary data.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) doc = interface.getDocument(documentId) @@ -898,14 +898,14 @@ async def get_document_data( @router.get("/{instanceId}/documents/contract/{contractId}", response_model=List[TrusteeDocument]) @limiter.limit("30/minute") -async def get_documents_by_contract( +def get_documents_by_contract( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), contractId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> List[TrusteeDocument]: """Get all documents for a contract.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) return interface.getDocumentsByContract(contractId) @@ -919,7 +919,7 @@ async def create_document( context: RequestContext = Depends(getRequestContext) ) -> TrusteeDocument: """Create a new document. Accepts JSON body with optional base64-encoded documentData.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) # Parse JSON body body = await request.json() @@ -959,7 +959,7 @@ async def upload_document( context: RequestContext = Depends(getRequestContext) ) -> TrusteeDocument: """Upload a document with multipart/form-data.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) # Read file content fileContent = await file.read() @@ -980,7 +980,7 @@ async def upload_document( @router.put("/{instanceId}/documents/{documentId}", response_model=TrusteeDocument) @limiter.limit("10/minute") -async def update_document( +def update_document( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), documentId: str = Path(...), @@ -988,7 +988,7 @@ async def update_document( context: RequestContext = Depends(getRequestContext) ) -> TrusteeDocument: """Update document metadata.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getDocument(documentId) @@ -1003,14 +1003,14 @@ async def update_document( @router.delete("/{instanceId}/documents/{documentId}") @limiter.limit("10/minute") -async def delete_document( +def delete_document( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), documentId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Delete a document.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getDocument(documentId) @@ -1027,14 +1027,14 @@ async def delete_document( @router.get("/{instanceId}/positions", response_model=PaginatedResponse[TrusteePosition]) @limiter.limit("30/minute") -async def get_positions( +def get_positions( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), pagination: Optional[str] = Query(None), context: RequestContext = Depends(getRequestContext) ) -> PaginatedResponse[TrusteePosition]: """Get all positions with optional pagination.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) paginationParams = _parsePagination(pagination) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) @@ -1057,14 +1057,14 @@ async def get_positions( @router.get("/{instanceId}/positions/{positionId}", response_model=TrusteePosition) @limiter.limit("30/minute") -async def get_position( +def get_position( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), positionId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteePosition: """Get a single position by ID.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) position = interface.getPosition(positionId) @@ -1075,14 +1075,14 @@ async def get_position( @router.get("/{instanceId}/positions/contract/{contractId}", response_model=List[TrusteePosition]) @limiter.limit("30/minute") -async def get_positions_by_contract( +def get_positions_by_contract( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), contractId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> List[TrusteePosition]: """Get all positions for a contract.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) return interface.getPositionsByContract(contractId) @@ -1090,14 +1090,14 @@ async def get_positions_by_contract( @router.get("/{instanceId}/positions/organisation/{orgId}", response_model=List[TrusteePosition]) @limiter.limit("30/minute") -async def get_positions_by_organisation( +def get_positions_by_organisation( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), orgId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> List[TrusteePosition]: """Get all positions for an organisation.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) return interface.getPositionsByOrganisation(orgId) @@ -1105,14 +1105,14 @@ async def get_positions_by_organisation( @router.post("/{instanceId}/positions", response_model=TrusteePosition, status_code=201) @limiter.limit("10/minute") -async def create_position( +def create_position( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), data: TrusteePosition = Body(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteePosition: """Create a new position.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.createPosition(data.model_dump()) @@ -1123,7 +1123,7 @@ async def create_position( @router.put("/{instanceId}/positions/{positionId}", response_model=TrusteePosition) @limiter.limit("10/minute") -async def update_position( +def update_position( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), positionId: str = Path(...), @@ -1131,7 +1131,7 @@ async def update_position( context: RequestContext = Depends(getRequestContext) ) -> TrusteePosition: """Update a position.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getPosition(positionId) @@ -1146,14 +1146,14 @@ async def update_position( @router.delete("/{instanceId}/positions/{positionId}") @limiter.limit("10/minute") -async def delete_position( +def delete_position( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), positionId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Delete a position.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getPosition(positionId) @@ -1170,7 +1170,7 @@ async def delete_position( @router.get("/{instanceId}/position-documents") @limiter.limit("30/minute") -async def get_position_documents( +def get_position_documents( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), pagination: Optional[str] = Query(None), @@ -1180,7 +1180,7 @@ async def get_position_documents( Each item includes _permissions: { canUpdate, canDelete } for row-level permission UI. """ - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) paginationParams = _parsePagination(pagination) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) @@ -1203,14 +1203,14 @@ async def get_position_documents( @router.get("/{instanceId}/position-documents/{linkId}", response_model=TrusteePositionDocument) @limiter.limit("30/minute") -async def get_position_document( +def get_position_document( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), linkId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteePositionDocument: """Get a single position-document link by ID.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) link = interface.getPositionDocument(linkId) @@ -1221,14 +1221,14 @@ async def get_position_document( @router.get("/{instanceId}/position-documents/position/{positionId}", response_model=List[TrusteePositionDocument]) @limiter.limit("30/minute") -async def get_documents_for_position( +def get_documents_for_position( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), positionId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> List[TrusteePositionDocument]: """Get all document links for a position.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) return interface.getDocumentsForPosition(positionId) @@ -1236,14 +1236,14 @@ async def get_documents_for_position( @router.get("/{instanceId}/position-documents/document/{documentId}", response_model=List[TrusteePositionDocument]) @limiter.limit("30/minute") -async def get_positions_for_document( +def get_positions_for_document( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), documentId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> List[TrusteePositionDocument]: """Get all position links for a document.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) return interface.getPositionsForDocument(documentId) @@ -1251,14 +1251,14 @@ async def get_positions_for_document( @router.post("/{instanceId}/position-documents", response_model=TrusteePositionDocument, status_code=201) @limiter.limit("10/minute") -async def create_position_document( +def create_position_document( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), data: TrusteePositionDocument = Body(...), context: RequestContext = Depends(getRequestContext) ) -> TrusteePositionDocument: """Create a new position-document link.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.createPositionDocument(data.model_dump()) @@ -1269,7 +1269,7 @@ async def create_position_document( @router.put("/{instanceId}/position-documents/{linkId}", response_model=TrusteePositionDocument) @limiter.limit("10/minute") -async def update_position_document( +def update_position_document( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), linkId: str = Path(...), @@ -1277,7 +1277,7 @@ async def update_position_document( context: RequestContext = Depends(getRequestContext) ) -> TrusteePositionDocument: """Update a position-document link.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) result = interface.updatePositionDocument(linkId, data.model_dump(exclude_unset=True)) @@ -1288,14 +1288,14 @@ async def update_position_document( @router.delete("/{instanceId}/position-documents/{linkId}") @limiter.limit("10/minute") -async def delete_position_document( +def delete_position_document( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), linkId: str = Path(...), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Delete a position-document link.""" - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) interface = getInterface(context.user, mandateId=mandateId, featureInstanceId=instanceId) existing = interface.getPositionDocument(linkId) @@ -1314,14 +1314,14 @@ async def delete_position_document( from modules.datamodels.datamodelRbac import Role, AccessRule, AccessRuleContext -async def _validateInstanceAdmin(instanceId: str, context: RequestContext) -> str: +def _validateInstanceAdmin(instanceId: str, context: RequestContext) -> str: """ Validate that the user has admin access to the feature instance. Returns the mandateId if authorized. This checks for the RESOURCE permission 'instance-roles.manage'. """ - mandateId = await _validateInstanceAccess(instanceId, context) + mandateId = _validateInstanceAccess(instanceId, context) # SysAdmin always has access if context.user.isSysAdmin: @@ -1350,7 +1350,7 @@ async def _validateInstanceAdmin(instanceId: str, context: RequestContext) -> st @router.get("/{instanceId}/instance-roles", response_model=PaginatedResponse) @limiter.limit("30/minute") -async def get_instance_roles( +def get_instance_roles( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), context: RequestContext = Depends(getRequestContext) @@ -1359,7 +1359,7 @@ async def get_instance_roles( Get all roles for this feature instance. Requires feature admin permission. """ - mandateId = await _validateInstanceAdmin(instanceId, context) + mandateId = _validateInstanceAdmin(instanceId, context) rootInterface = getRootInterface() @@ -1374,14 +1374,14 @@ async def get_instance_roles( @router.get("/{instanceId}/instance-roles/{roleId}", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def get_instance_role( +def get_instance_role( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), roleId: str = Path(..., description="Role ID"), context: RequestContext = Depends(getRequestContext) ) -> Dict[str, Any]: """Get a specific instance role.""" - mandateId = await _validateInstanceAdmin(instanceId, context) + mandateId = _validateInstanceAdmin(instanceId, context) rootInterface = getRootInterface() role = rootInterface.getRole(roleId) @@ -1398,7 +1398,7 @@ async def get_instance_role( @router.get("/{instanceId}/instance-roles/{roleId}/rules", response_model=PaginatedResponse) @limiter.limit("30/minute") -async def get_instance_role_rules( +def get_instance_role_rules( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), roleId: str = Path(..., description="Role ID"), @@ -1408,7 +1408,7 @@ async def get_instance_role_rules( Get all AccessRules for a specific instance role. Requires feature admin permission. """ - mandateId = await _validateInstanceAdmin(instanceId, context) + mandateId = _validateInstanceAdmin(instanceId, context) rootInterface = getRootInterface() @@ -1428,7 +1428,7 @@ async def get_instance_role_rules( @router.post("/{instanceId}/instance-roles/{roleId}/rules", response_model=Dict[str, Any], status_code=201) @limiter.limit("10/minute") -async def create_instance_role_rule( +def create_instance_role_rule( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), roleId: str = Path(..., description="Role ID"), @@ -1439,7 +1439,7 @@ async def create_instance_role_rule( Create a new AccessRule for an instance role. Requires feature admin permission. """ - mandateId = await _validateInstanceAdmin(instanceId, context) + mandateId = _validateInstanceAdmin(instanceId, context) rootInterface = getRootInterface() @@ -1477,7 +1477,7 @@ async def create_instance_role_rule( @router.put("/{instanceId}/instance-roles/{roleId}/rules/{ruleId}", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def update_instance_role_rule( +def update_instance_role_rule( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), roleId: str = Path(..., description="Role ID"), @@ -1490,7 +1490,7 @@ async def update_instance_role_rule( Only view, read, create, update, delete can be changed. Requires feature admin permission. """ - mandateId = await _validateInstanceAdmin(instanceId, context) + mandateId = _validateInstanceAdmin(instanceId, context) rootInterface = getRootInterface() @@ -1530,7 +1530,7 @@ async def update_instance_role_rule( @router.delete("/{instanceId}/instance-roles/{roleId}/rules/{ruleId}") @limiter.limit("10/minute") -async def delete_instance_role_rule( +def delete_instance_role_rule( request: Request, instanceId: str = Path(..., description="Feature Instance ID"), roleId: str = Path(..., description="Role ID"), @@ -1541,7 +1541,7 @@ async def delete_instance_role_rule( Delete an AccessRule for an instance role. Requires feature admin permission. """ - mandateId = await _validateInstanceAdmin(instanceId, context) + mandateId = _validateInstanceAdmin(instanceId, context) rootInterface = getRootInterface() diff --git a/modules/routes/routeAdmin.py b/modules/routes/routeAdmin.py index 878dbd66..ed5bf42c 100644 --- a/modules/routes/routeAdmin.py +++ b/modules/routes/routeAdmin.py @@ -33,7 +33,7 @@ router.mount( @router.get("/") @limiter.limit("30/minute") -async def root(request: Request) -> Dict[str, str]: +def root(request: Request) -> Dict[str, str]: """API status endpoint""" # Validate required configuration values allowedOrigins = APP_CONFIG.get("APP_ALLOWED_ORIGINS") @@ -51,7 +51,7 @@ async def root(request: Request) -> Dict[str, str]: @router.get("/api/environment") @limiter.limit("30/minute") -async def get_environment( +def get_environment( request: Request, currentUser: Dict[str, Any] = Depends(getCurrentUser) ) -> Dict[str, str]: """Get environment configuration for frontend""" @@ -82,13 +82,13 @@ async def get_environment( @router.options("/{fullPath:path}") @limiter.limit("60/minute") -async def options_route(request: Request, fullPath: str) -> Response: +def options_route(request: Request, fullPath: str) -> Response: return Response(status_code=200) @router.get("/favicon.ico") @limiter.limit("30/minute") -async def favicon(request: Request) -> FileResponse: +def favicon(request: Request) -> FileResponse: favicon_path = staticFolder / "favicon.ico" if not favicon_path.exists(): raise HTTPException(status_code=404, detail="Favicon not found") diff --git a/modules/routes/routeAdminAutomationEvents.py b/modules/routes/routeAdminAutomationEvents.py index e8bb9291..7765d621 100644 --- a/modules/routes/routeAdminAutomationEvents.py +++ b/modules/routes/routeAdminAutomationEvents.py @@ -33,7 +33,7 @@ router = APIRouter( @router.get("") @limiter.limit("30/minute") -async def get_all_automation_events( +def get_all_automation_events( request: Request, currentUser: User = Depends(requireSysAdmin) ) -> List[Dict[str, Any]]: @@ -107,7 +107,7 @@ async def sync_all_automation_events( @router.post("/{eventId}/remove") @limiter.limit("10/minute") -async def remove_event( +def remove_event( request: Request, eventId: str = Path(..., description="Event ID to remove"), currentUser: User = Depends(requireSysAdmin) diff --git a/modules/routes/routeAdminFeatures.py b/modules/routes/routeAdminFeatures.py index 87582b9e..3adc8025 100644 --- a/modules/routes/routeAdminFeatures.py +++ b/modules/routes/routeAdminFeatures.py @@ -67,7 +67,7 @@ class SyncRolesResult(BaseModel): @router.get("/", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def list_features( +def list_features( request: Request, context: RequestContext = Depends(getRequestContext) ) -> List[Dict[str, Any]]: @@ -105,7 +105,7 @@ class FeaturesMyResponse(BaseModel): @router.get("/my", response_model=FeaturesMyResponse) @limiter.limit("60/minute") -async def get_my_feature_instances( +def get_my_feature_instances( request: Request, context: RequestContext = Depends(getRequestContext) ) -> FeaturesMyResponse: @@ -332,7 +332,7 @@ def _mergeAccessLevel(current: str, new: str) -> str: @router.post("/", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def create_feature( +def create_feature( request: Request, code: str = Query(..., description="Unique feature code"), label: Dict[str, str] = None, @@ -387,7 +387,7 @@ async def create_feature( @router.get("/instances", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def list_feature_instances( +def list_feature_instances( request: Request, featureCode: Optional[str] = Query(None, description="Filter by feature code"), context: RequestContext = Depends(getRequestContext) @@ -429,7 +429,7 @@ async def list_feature_instances( @router.get("/instances/{instanceId}", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def get_feature_instance( +def get_feature_instance( request: Request, instanceId: str, context: RequestContext = Depends(getRequestContext) @@ -473,7 +473,7 @@ async def get_feature_instance( @router.post("/instances", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def create_feature_instance( +def create_feature_instance( request: Request, data: FeatureInstanceCreate, context: RequestContext = Depends(getRequestContext) @@ -540,7 +540,7 @@ async def create_feature_instance( @router.delete("/instances/{instanceId}", response_model=Dict[str, str]) @limiter.limit("10/minute") -async def delete_feature_instance( +def delete_feature_instance( request: Request, instanceId: str, context: RequestContext = Depends(getRequestContext) @@ -605,7 +605,7 @@ class FeatureInstanceUpdate(BaseModel): @router.put("/instances/{instanceId}", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def updateFeatureInstance( +def updateFeatureInstance( request: Request, instanceId: str, data: FeatureInstanceUpdate, @@ -682,7 +682,7 @@ async def updateFeatureInstance( @router.post("/instances/{instanceId}/sync-roles", response_model=SyncRolesResult) @limiter.limit("10/minute") -async def sync_instance_roles( +def sync_instance_roles( request: Request, instanceId: str, addOnly: bool = Query(True, description="Only add missing roles, don't remove extras"), @@ -749,7 +749,7 @@ async def sync_instance_roles( @router.get("/templates/roles", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def list_template_roles( +def list_template_roles( request: Request, featureCode: Optional[str] = Query(None, description="Filter by feature code"), sysAdmin: User = Depends(requireSysAdmin) @@ -779,7 +779,7 @@ async def list_template_roles( @router.post("/templates/roles", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def create_template_role( +def create_template_role( request: Request, roleLabel: str = Query(..., description="Role label (e.g., 'admin', 'viewer')"), featureCode: str = Query(..., description="Feature code this role belongs to"), @@ -864,7 +864,7 @@ class FeatureInstanceUserUpdate(BaseModel): @router.get("/instances/{instanceId}/users", response_model=List[FeatureInstanceUserResponse]) @limiter.limit("60/minute") -async def list_feature_instance_users( +def list_feature_instance_users( request: Request, instanceId: str, context: RequestContext = Depends(getRequestContext) @@ -942,7 +942,7 @@ async def list_feature_instance_users( @router.post("/instances/{instanceId}/users", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def add_user_to_feature_instance( +def add_user_to_feature_instance( request: Request, instanceId: str, data: FeatureInstanceUserCreate, @@ -1043,7 +1043,7 @@ async def add_user_to_feature_instance( @router.delete("/instances/{instanceId}/users/{userId}", response_model=Dict[str, str]) @limiter.limit("30/minute") -async def remove_user_from_feature_instance( +def remove_user_from_feature_instance( request: Request, instanceId: str, userId: str, @@ -1121,7 +1121,7 @@ async def remove_user_from_feature_instance( @router.put("/instances/{instanceId}/users/{userId}/roles", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def update_feature_instance_user_roles( +def update_feature_instance_user_roles( request: Request, instanceId: str, userId: str, @@ -1216,7 +1216,7 @@ async def update_feature_instance_user_roles( @router.get("/instances/{instanceId}/available-roles", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_feature_instance_available_roles( +def get_feature_instance_available_roles( request: Request, instanceId: str, context: RequestContext = Depends(getRequestContext) @@ -1280,7 +1280,7 @@ async def get_feature_instance_available_roles( @router.get("/{featureCode}", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def get_feature( +def get_feature( request: Request, featureCode: str, context: RequestContext = Depends(getRequestContext) diff --git a/modules/routes/routeAdminRbacExport.py b/modules/routes/routeAdminRbacExport.py index 28caf8c8..d22a6ba7 100644 --- a/modules/routes/routeAdminRbacExport.py +++ b/modules/routes/routeAdminRbacExport.py @@ -72,7 +72,7 @@ class RbacImportResult(BaseModel): @router.get("/export/global", response_model=RbacExportData) @limiter.limit("10/minute") -async def export_global_rbac( +def export_global_rbac( request: Request, sysAdmin: User = Depends(requireSysAdmin) ) -> RbacExportData: @@ -281,7 +281,7 @@ async def import_global_rbac( @router.get("/export/mandate", response_model=RbacExportData) @limiter.limit("10/minute") -async def export_mandate_rbac( +def export_mandate_rbac( request: Request, includeFeatureInstances: bool = True, context: RequestContext = Depends(getRequestContext) diff --git a/modules/routes/routeAdminRbacRoles.py b/modules/routes/routeAdminRbacRoles.py index 75e00cd5..97830991 100644 --- a/modules/routes/routeAdminRbacRoles.py +++ b/modules/routes/routeAdminRbacRoles.py @@ -68,7 +68,7 @@ router = APIRouter( @router.get("/", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def list_roles( +def list_roles( request: Request, currentUser: User = Depends(requireSysAdmin) ) -> List[Dict[str, Any]]: @@ -113,7 +113,7 @@ async def list_roles( @router.get("/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_role_options( +def get_role_options( request: Request, currentUser: User = Depends(requireSysAdmin) ) -> List[Dict[str, Any]]: @@ -154,7 +154,7 @@ async def get_role_options( @router.post("/", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def create_role( +def create_role( request: Request, role: Role = Body(...), currentUser: User = Depends(requireSysAdmin) @@ -198,7 +198,7 @@ async def create_role( @router.get("/{roleId}", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def get_role( +def get_role( request: Request, roleId: str = Path(..., description="Role ID"), currentUser: User = Depends(requireSysAdmin) @@ -242,7 +242,7 @@ async def get_role( @router.put("/{roleId}", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def update_role( +def update_role( request: Request, roleId: str = Path(..., description="Role ID"), role: Role = Body(...), @@ -290,7 +290,7 @@ async def update_role( @router.delete("/{roleId}", response_model=Dict[str, str]) @limiter.limit("30/minute") -async def delete_role( +def delete_role( request: Request, roleId: str = Path(..., description="Role ID"), currentUser: User = Depends(requireSysAdmin) @@ -334,7 +334,7 @@ async def delete_role( @router.get("/users", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def list_users_with_roles( +def list_users_with_roles( request: Request, roleLabel: Optional[str] = Query(None, description="Filter by role label"), mandateId: Optional[str] = Query(None, description="Filter by mandate ID (via UserMandate)"), @@ -396,7 +396,7 @@ async def list_users_with_roles( @router.get("/users/{userId}", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def get_user_roles( +def get_user_roles( request: Request, userId: str = Path(..., description="User ID"), currentUser: User = Depends(requireSysAdmin) @@ -446,7 +446,7 @@ async def get_user_roles( @router.put("/users/{userId}/roles", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def update_user_roles( +def update_user_roles( request: Request, userId: str = Path(..., description="User ID"), newRoleLabels: List[str] = Body(..., description="List of role labels to assign"), @@ -540,7 +540,7 @@ async def update_user_roles( @router.post("/users/{userId}/roles/{roleLabel}", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def add_user_role( +def add_user_role( request: Request, userId: str = Path(..., description="User ID"), roleLabel: str = Path(..., description="Role label to add"), @@ -619,7 +619,7 @@ async def add_user_role( @router.delete("/users/{userId}/roles/{roleLabel}", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def remove_user_role( +def remove_user_role( request: Request, userId: str = Path(..., description="User ID"), roleLabel: str = Path(..., description="Role label to remove"), @@ -693,7 +693,7 @@ async def remove_user_role( @router.get("/roles/{roleLabel}/users", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_users_with_role( +def get_users_with_role( request: Request, roleLabel: str = Path(..., description="Role label"), mandateId: Optional[str] = Query(None, description="Filter by mandate ID (via UserMandate)"), diff --git a/modules/routes/routeAdminRbacRules.py b/modules/routes/routeAdminRbacRules.py index 82cc13d7..1feb64a2 100644 --- a/modules/routes/routeAdminRbacRules.py +++ b/modules/routes/routeAdminRbacRules.py @@ -35,7 +35,7 @@ router = APIRouter( @router.get("/permissions", response_model=UserPermissions) @limiter.limit("300/minute") # Raised from 60 - sidebar checks many pages individually -async def get_permissions( +def get_permissions( request: Request, context: str = Query(..., description="Context type: DATA, UI, or RESOURCE"), item: Optional[str] = Query(None, description="Item identifier (table name, UI path, or resource path)"), @@ -101,7 +101,7 @@ async def get_permissions( @router.get("/permissions/all", response_model=Dict[str, Any]) @limiter.limit("120/minute") # Raised from 30 - optimized endpoint for bulk permission fetch -async def get_all_permissions( +def get_all_permissions( request: Request, context: Optional[str] = Query(None, description="Context type: UI or RESOURCE (if not provided, returns both)"), reqContext: RequestContext = Depends(getRequestContext) @@ -293,7 +293,7 @@ async def get_all_permissions( @router.get("/rules", response_model=PaginatedResponse) @limiter.limit("30/minute") -async def get_access_rules( +def get_access_rules( request: Request, roleLabel: Optional[str] = Query(None, description="Filter by role label"), context: Optional[str] = Query(None, description="Filter by context (DATA, UI, RESOURCE)"), @@ -382,7 +382,7 @@ async def get_access_rules( @router.get("/rules/by-role/{roleId}", response_model=PaginatedResponse) @limiter.limit("30/minute") -async def get_access_rules_by_role( +def get_access_rules_by_role( request: Request, roleId: str = Path(..., description="Role ID to get rules for"), currentUser: User = Depends(requireSysAdmin) @@ -420,7 +420,7 @@ async def get_access_rules_by_role( @router.get("/rules/{ruleId}", response_model=dict) @limiter.limit("30/minute") -async def get_access_rule( +def get_access_rule( request: Request, ruleId: str = Path(..., description="Access rule ID"), currentUser: User = Depends(requireSysAdmin) @@ -462,7 +462,7 @@ async def get_access_rule( @router.post("/rules", response_model=dict) @limiter.limit("30/minute") -async def create_access_rule( +def create_access_rule( request: Request, accessRuleData: dict = Body(..., description="Access rule data"), currentUser: User = Depends(requireSysAdmin) @@ -528,7 +528,7 @@ async def create_access_rule( @router.put("/rules/{ruleId}", response_model=dict) @limiter.limit("30/minute") -async def update_access_rule( +def update_access_rule( request: Request, ruleId: str = Path(..., description="Access rule ID"), accessRuleData: dict = Body(..., description="Updated access rule data"), @@ -611,7 +611,7 @@ async def update_access_rule( @router.delete("/rules/{ruleId}") @limiter.limit("30/minute") -async def delete_access_rule( +def delete_access_rule( request: Request, ruleId: str = Path(..., description="Access rule ID"), currentUser: User = Depends(requireSysAdmin) @@ -669,7 +669,7 @@ async def delete_access_rule( @router.get("/roles", response_model=PaginatedResponse) @limiter.limit("60/minute") -async def list_roles( +def list_roles( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), includeTemplates: bool = Query(False, description="Include feature template roles"), @@ -838,7 +838,7 @@ async def list_roles( @router.get("/roles/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_role_options( +def get_role_options( request: Request, currentUser: User = Depends(requireSysAdmin) ) -> List[Dict[str, Any]]: @@ -879,7 +879,7 @@ async def get_role_options( @router.post("/roles", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def create_role( +def create_role( request: Request, role: Role = Body(...), currentUser: User = Depends(requireSysAdmin) @@ -928,7 +928,7 @@ async def create_role( @router.get("/roles/{roleId}", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def get_role( +def get_role( request: Request, roleId: str = Path(..., description="Role ID"), currentUser: User = Depends(requireSysAdmin) @@ -975,7 +975,7 @@ async def get_role( @router.put("/roles/{roleId}", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def update_role( +def update_role( request: Request, roleId: str = Path(..., description="Role ID"), role: Role = Body(...), @@ -1028,7 +1028,7 @@ async def update_role( @router.delete("/roles/{roleId}", response_model=Dict[str, str]) @limiter.limit("30/minute") -async def delete_role( +def delete_role( request: Request, roleId: str = Path(..., description="Role ID"), currentUser: User = Depends(requireSysAdmin) @@ -1078,7 +1078,7 @@ async def delete_role( @router.get("/catalog/objects", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def getCatalogObjects( +def getCatalogObjects( request: Request, context: Optional[str] = Query(None, description="Filter by context (DATA, UI, RESOURCE)"), featureCode: Optional[str] = Query(None, description="Filter by feature code"), @@ -1170,7 +1170,7 @@ async def getCatalogObjects( @router.get("/catalog/stats", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def getCatalogStats( +def getCatalogStats( request: Request, currentUser: User = Depends(requireSysAdmin) ) -> Dict[str, Any]: @@ -1200,7 +1200,7 @@ async def getCatalogStats( @router.post("/cleanup/duplicate-rules", response_model=dict) @limiter.limit("5/minute") -async def cleanup_duplicate_access_rules( +def cleanup_duplicate_access_rules( request: Request, dryRun: bool = Query(True, description="If true, only report duplicates without deleting"), currentUser: User = Depends(requireSysAdmin) diff --git a/modules/routes/routeAdminUserAccessOverview.py b/modules/routes/routeAdminUserAccessOverview.py index 372e2193..b330d57e 100644 --- a/modules/routes/routeAdminUserAccessOverview.py +++ b/modules/routes/routeAdminUserAccessOverview.py @@ -69,7 +69,7 @@ def _getRoleScopePriority(scope: str) -> int: @router.get("/users", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def listUsersForOverview( +def listUsersForOverview( request: Request, currentUser: User = Depends(requireSysAdmin) ) -> List[Dict[str, Any]]: @@ -112,7 +112,7 @@ async def listUsersForOverview( @router.get("/{userId}", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def getUserAccessOverview( +def getUserAccessOverview( request: Request, userId: str = Path(..., description="User ID to get access overview for"), mandateId: Optional[str] = Query(None, description="Filter by mandate ID"), @@ -410,7 +410,7 @@ async def getUserAccessOverview( @router.get("/{userId}/effective-permissions", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def getEffectivePermissions( +def getEffectivePermissions( request: Request, userId: str = Path(..., description="User ID"), mandateId: str = Query(..., description="Mandate ID context"), diff --git a/modules/routes/routeAttributes.py b/modules/routes/routeAttributes.py index 10f93ce6..e877e512 100644 --- a/modules/routes/routeAttributes.py +++ b/modules/routes/routeAttributes.py @@ -22,7 +22,7 @@ router = APIRouter( @router.get("/{entityType}", response_model=AttributeResponse) @limiter.limit("30/minute") -async def get_entity_attributes( +def get_entity_attributes( request: Request, entityType: str = Path(..., description="Type of entity (e.g. prompt)") ) -> AttributeResponse: @@ -76,7 +76,7 @@ async def get_entity_attributes( @router.options("/{entityType}") @limiter.limit("60/minute") -async def options_entity_attributes( +def options_entity_attributes( request: Request, entityType: str = Path(..., description="Type of entity (e.g. prompt)") ) -> Response: diff --git a/modules/routes/routeBilling.py b/modules/routes/routeBilling.py index bd47c791..26133704 100644 --- a/modules/routes/routeBilling.py +++ b/modules/routes/routeBilling.py @@ -164,7 +164,7 @@ router = APIRouter( @router.get("/balance", response_model=List[BillingBalanceResponse]) @limiter.limit("60/minute") -async def getBalance( +def getBalance( request: Request, ctx: RequestContext = Depends(getRequestContext) ): @@ -189,7 +189,7 @@ async def getBalance( @router.get("/balance/{targetMandateId}", response_model=BillingBalanceResponse) @limiter.limit("60/minute") -async def getBalanceForMandate( +def getBalanceForMandate( request: Request, targetMandateId: str = Path(..., description="Mandate ID"), ctx: RequestContext = Depends(getRequestContext) @@ -230,7 +230,7 @@ async def getBalanceForMandate( @router.get("/transactions", response_model=List[TransactionResponse]) @limiter.limit("30/minute") -async def getTransactions( +def getTransactions( request: Request, limit: int = Query(default=50, ge=1, le=500), offset: int = Query(default=0, ge=0), @@ -276,7 +276,7 @@ async def getTransactions( @router.get("/statistics/{period}", response_model=UsageReportResponse) @limiter.limit("30/minute") -async def getStatistics( +def getStatistics( request: Request, period: str = Path(..., description="Period: 'day', 'month', or 'year'"), year: int = Query(..., description="Year"), @@ -361,7 +361,7 @@ async def getStatistics( @router.get("/providers", response_model=List[str]) @limiter.limit("60/minute") -async def getAllowedProviders( +def getAllowedProviders( request: Request, ctx: RequestContext = Depends(getRequestContext) ): @@ -388,7 +388,7 @@ async def getAllowedProviders( @router.get("/admin/settings/{targetMandateId}", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def getSettingsAdmin( +def getSettingsAdmin( request: Request, targetMandateId: str = Path(..., description="Mandate ID"), ctx: RequestContext = Depends(getRequestContext), @@ -415,7 +415,7 @@ async def getSettingsAdmin( @router.post("/admin/settings/{targetMandateId}", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def createOrUpdateSettings( +def createOrUpdateSettings( request: Request, targetMandateId: str = Path(..., description="Mandate ID"), settingsUpdate: BillingSettingsUpdate = Body(...), @@ -462,7 +462,7 @@ async def createOrUpdateSettings( @router.post("/admin/credit/{targetMandateId}", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def addCredit( +def addCredit( request: Request, targetMandateId: str = Path(..., description="Mandate ID"), creditRequest: CreditAddRequest = Body(...), @@ -526,7 +526,7 @@ async def addCredit( @router.get("/admin/accounts/{targetMandateId}", response_model=List[AccountSummary]) @limiter.limit("30/minute") -async def getAccounts( +def getAccounts( request: Request, targetMandateId: str = Path(..., description="Mandate ID"), ctx: RequestContext = Depends(getRequestContext), @@ -572,7 +572,7 @@ class MandateUserSummary(BaseModel): @router.get("/admin/users/{targetMandateId}", response_model=List[MandateUserSummary]) @limiter.limit("30/minute") -async def getUsersForMandate( +def getUsersForMandate( request: Request, targetMandateId: str = Path(..., description="Mandate ID"), ctx: RequestContext = Depends(getRequestContext), @@ -627,7 +627,7 @@ async def getUsersForMandate( @router.get("/admin/transactions/{targetMandateId}", response_model=List[TransactionResponse]) @limiter.limit("30/minute") -async def getTransactionsAdmin( +def getTransactionsAdmin( request: Request, targetMandateId: str = Path(..., description="Mandate ID"), limit: int = Query(default=100, ge=1, le=1000), @@ -669,7 +669,7 @@ async def getTransactionsAdmin( @router.get("/view/mandates/balances", response_model=List[MandateBalanceResponse]) @limiter.limit("30/minute") -async def getMandateViewBalances( +def getMandateViewBalances( request: Request, ctx: RequestContext = Depends(getRequestContext), _admin = Depends(requireSysAdmin) @@ -691,7 +691,7 @@ async def getMandateViewBalances( @router.get("/view/mandates/transactions", response_model=List[TransactionResponse]) @limiter.limit("30/minute") -async def getMandateViewTransactions( +def getMandateViewTransactions( request: Request, limit: int = Query(default=100, ge=1, le=1000), ctx: RequestContext = Depends(getRequestContext), @@ -734,7 +734,7 @@ async def getMandateViewTransactions( @router.get("/view/users/balances", response_model=List[UserBalanceResponse]) @limiter.limit("30/minute") -async def getUserViewBalances( +def getUserViewBalances( request: Request, ctx: RequestContext = Depends(getRequestContext) ): @@ -793,7 +793,7 @@ class ViewStatisticsResponse(BaseModel): @router.get("/view/statistics") @limiter.limit("30/minute") -async def getUserViewStatistics( +def getUserViewStatistics( request: Request, period: str = Query(default="month", description="Period: 'day' or 'month'"), year: int = Query(default=None, description="Year"), @@ -962,7 +962,7 @@ async def getUserViewStatistics( @router.get("/view/users/transactions", response_model=PaginatedResponse[UserTransactionResponse]) @limiter.limit("30/minute") -async def getUserViewTransactions( +def getUserViewTransactions( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), ctx: RequestContext = Depends(getRequestContext) diff --git a/modules/routes/routeDataConnections.py b/modules/routes/routeDataConnections.py index 95bbd014..099b04c2 100644 --- a/modules/routes/routeDataConnections.py +++ b/modules/routes/routeDataConnections.py @@ -84,7 +84,7 @@ router = APIRouter( @router.get("/statuses/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_connection_status_options( +def get_connection_status_options( request: Request, currentUser: User = Depends(getCurrentUser) ) -> List[Dict[str, Any]]: @@ -100,7 +100,7 @@ async def get_connection_status_options( @router.get("/authorities/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_auth_authority_options( +def get_auth_authority_options( request: Request, currentUser: User = Depends(getCurrentUser) ) -> List[Dict[str, Any]]: @@ -288,7 +288,7 @@ async def get_connections( @router.post("/", response_model=UserConnection) @limiter.limit("10/minute") -async def create_connection( +def create_connection( request: Request, connection_data: Dict[str, Any] = Body(...), currentUser: User = Depends(getCurrentUser) @@ -344,7 +344,7 @@ async def create_connection( @router.put("/{connectionId}", response_model=UserConnection) @limiter.limit("10/minute") -async def update_connection( +def update_connection( request: Request, connectionId: str = Path(..., description="The ID of the connection to update"), connection_data: Dict[str, Any] = Body(...), @@ -416,7 +416,7 @@ async def update_connection( @router.post("/{connectionId}/connect") @limiter.limit("10/minute") -async def connect_service( +def connect_service( request: Request, connectionId: str = Path(..., description="The ID of the connection to connect"), currentUser: User = Depends(getCurrentUser) @@ -482,7 +482,7 @@ async def connect_service( @router.post("/{connectionId}/disconnect") @limiter.limit("10/minute") -async def disconnect_service( +def disconnect_service( request: Request, connectionId: str = Path(..., description="The ID of the connection to disconnect"), currentUser: User = Depends(getCurrentUser) @@ -532,7 +532,7 @@ async def disconnect_service( @router.delete("/{connectionId}") @limiter.limit("10/minute") -async def delete_connection( +def delete_connection( request: Request, connectionId: str = Path(..., description="The ID of the connection to delete"), currentUser: User = Depends(getCurrentUser) diff --git a/modules/routes/routeDataFiles.py b/modules/routes/routeDataFiles.py index 1a84b7e4..49d7e365 100644 --- a/modules/routes/routeDataFiles.py +++ b/modules/routes/routeDataFiles.py @@ -37,7 +37,7 @@ router = APIRouter( @router.get("/list", response_model=PaginatedResponse[FileItem]) @limiter.limit("30/minute") -async def get_files( +def get_files( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), currentUser: User = Depends(getCurrentUser) @@ -168,7 +168,7 @@ async def upload_file( @router.get("/{fileId}", response_model=FileItem) @limiter.limit("30/minute") -async def get_file( +def get_file( request: Request, fileId: str = Path(..., description="ID of the file"), currentUser: User = Depends(getCurrentUser) @@ -214,7 +214,7 @@ async def get_file( @router.put("/{fileId}", response_model=FileItem) @limiter.limit("10/minute") -async def update_file( +def update_file( request: Request, fileId: str = Path(..., description="ID of the file to update"), file_info: Dict[str, Any] = Body(...), @@ -262,7 +262,7 @@ async def update_file( @router.delete("/{fileId}", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def delete_file( +def delete_file( request: Request, fileId: str = Path(..., description="ID of the file to delete"), currentUser: User = Depends(getCurrentUser) @@ -289,7 +289,7 @@ async def delete_file( @router.get("/stats", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def get_file_stats( +def get_file_stats( request: Request, currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: @@ -327,7 +327,7 @@ async def get_file_stats( @router.get("/{fileId}/download") @limiter.limit("30/minute") -async def download_file( +def download_file( request: Request, fileId: str = Path(..., description="ID of the file to download"), currentUser: User = Depends(getCurrentUser) @@ -375,7 +375,7 @@ async def download_file( @router.get("/{fileId}/preview", response_model=FilePreview) @limiter.limit("30/minute") -async def preview_file( +def preview_file( request: Request, fileId: str = Path(..., description="ID of the file to preview"), currentUser: User = Depends(getCurrentUser) diff --git a/modules/routes/routeDataMandates.py b/modules/routes/routeDataMandates.py index 38877a9f..8d2c4a2b 100644 --- a/modules/routes/routeDataMandates.py +++ b/modules/routes/routeDataMandates.py @@ -76,7 +76,7 @@ router = APIRouter( @router.get("/", response_model=PaginatedResponse[Mandate]) @limiter.limit("30/minute") -async def get_mandates( +def get_mandates( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), currentUser: User = Depends(requireSysAdmin) @@ -140,7 +140,7 @@ async def get_mandates( @router.get("/{mandateId}", response_model=Mandate) @limiter.limit("30/minute") -async def get_mandate( +def get_mandate( request: Request, mandateId: str = Path(..., description="ID of the mandate"), currentUser: User = Depends(requireSysAdmin) @@ -171,7 +171,7 @@ async def get_mandate( @router.post("/", response_model=Mandate) @limiter.limit("10/minute") -async def create_mandate( +def create_mandate( request: Request, mandateData: dict = Body(..., description="Mandate data with at least 'name' field"), currentUser: User = Depends(requireSysAdmin) @@ -224,7 +224,7 @@ async def create_mandate( @router.put("/{mandateId}", response_model=Mandate) @limiter.limit("10/minute") -async def update_mandate( +def update_mandate( request: Request, mandateId: str = Path(..., description="ID of the mandate to update"), mandateData: dict = Body(..., description="Mandate update data"), @@ -270,7 +270,7 @@ async def update_mandate( @router.delete("/{mandateId}", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def delete_mandate( +def delete_mandate( request: Request, mandateId: str = Path(..., description="ID of the mandate to delete"), currentUser: User = Depends(requireSysAdmin) @@ -324,7 +324,7 @@ async def delete_mandate( @router.get("/{targetMandateId}/users") @limiter.limit("60/minute") -async def list_mandate_users( +def list_mandate_users( request: Request, targetMandateId: str = Path(..., description="ID of the mandate"), pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), @@ -493,7 +493,7 @@ async def list_mandate_users( @router.post("/{targetMandateId}/users", response_model=UserMandateResponse) @limiter.limit("30/minute") -async def add_user_to_mandate( +def add_user_to_mandate( request: Request, targetMandateId: str = Path(..., description="ID of the mandate"), data: UserMandateCreate = Body(...), @@ -603,7 +603,7 @@ async def add_user_to_mandate( @router.delete("/{targetMandateId}/users/{targetUserId}", response_model=Dict[str, str]) @limiter.limit("30/minute") -async def remove_user_from_mandate( +def remove_user_from_mandate( request: Request, targetMandateId: str = Path(..., description="ID of the mandate"), targetUserId: str = Path(..., description="ID of the user to remove"), @@ -681,7 +681,7 @@ async def remove_user_from_mandate( @router.put("/{targetMandateId}/users/{targetUserId}/roles", response_model=UserMandateResponse) @limiter.limit("30/minute") -async def update_user_roles_in_mandate( +def update_user_roles_in_mandate( request: Request, targetMandateId: str = Path(..., description="ID of the mandate"), targetUserId: str = Path(..., description="ID of the user"), diff --git a/modules/routes/routeDataPrompts.py b/modules/routes/routeDataPrompts.py index 48902e66..4aad221d 100644 --- a/modules/routes/routeDataPrompts.py +++ b/modules/routes/routeDataPrompts.py @@ -27,7 +27,7 @@ router = APIRouter( @router.get("", response_model=PaginatedResponse[Prompt]) @limiter.limit("30/minute") -async def get_prompts( +def get_prompts( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), currentUser: User = Depends(getCurrentUser) @@ -83,7 +83,7 @@ async def get_prompts( @router.post("", response_model=Prompt) @limiter.limit("10/minute") -async def create_prompt( +def create_prompt( request: Request, prompt: Prompt, currentUser: User = Depends(getCurrentUser) @@ -98,7 +98,7 @@ async def create_prompt( @router.get("/{promptId}", response_model=Prompt) @limiter.limit("30/minute") -async def get_prompt( +def get_prompt( request: Request, promptId: str = Path(..., description="ID of the prompt"), currentUser: User = Depends(getCurrentUser) @@ -118,7 +118,7 @@ async def get_prompt( @router.put("/{promptId}", response_model=Prompt) @limiter.limit("10/minute") -async def update_prompt( +def update_prompt( request: Request, promptId: str = Path(..., description="ID of the prompt to update"), promptData: Prompt = Body(...), @@ -154,7 +154,7 @@ async def update_prompt( @router.delete("/{promptId}", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def delete_prompt( +def delete_prompt( request: Request, promptId: str = Path(..., description="ID of the prompt to delete"), currentUser: User = Depends(getCurrentUser) diff --git a/modules/routes/routeDataUsers.py b/modules/routes/routeDataUsers.py index 5e78d12a..b269e57e 100644 --- a/modules/routes/routeDataUsers.py +++ b/modules/routes/routeDataUsers.py @@ -153,7 +153,7 @@ router = APIRouter( @router.get("/options", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def get_user_options( +def get_user_options( request: Request, context: RequestContext = Depends(getRequestContext) ) -> List[Dict[str, Any]]: @@ -190,7 +190,7 @@ async def get_user_options( @router.get("/", response_model=PaginatedResponse[User]) @limiter.limit("30/minute") -async def get_users( +def get_users( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), context: RequestContext = Depends(getRequestContext) @@ -304,7 +304,7 @@ async def get_users( @router.get("/{userId}", response_model=User) @limiter.limit("30/minute") -async def get_user( +def get_user( request: Request, userId: str = Path(..., description="ID of the user"), context: RequestContext = Depends(getRequestContext) @@ -356,7 +356,7 @@ class CreateUserRequest(BaseModel): @router.post("", response_model=User) @limiter.limit("10/minute") -async def create_user( +def create_user( request: Request, userData: CreateUserRequest = Body(...), context: RequestContext = Depends(getRequestContext) @@ -396,7 +396,7 @@ async def create_user( @router.put("/{userId}", response_model=User) @limiter.limit("10/minute") -async def update_user( +def update_user( request: Request, userId: str = Path(..., description="ID of the user to update"), userData: User = Body(...), @@ -438,7 +438,7 @@ async def update_user( @router.post("/{userId}/reset-password") @limiter.limit("5/minute") -async def reset_user_password( +def reset_user_password( request: Request, userId: str = Path(..., description="ID of the user to reset password for"), newPassword: str = Body(..., embed=True), @@ -535,7 +535,7 @@ async def reset_user_password( @router.post("/change-password") @limiter.limit("5/minute") -async def change_password( +def change_password( request: Request, currentPassword: str = Body(..., embed=True), newPassword: str = Body(..., embed=True), @@ -614,7 +614,7 @@ async def change_password( @router.post("/{userId}/send-password-link") @limiter.limit("10/minute") -async def send_password_link( +def send_password_link( request: Request, userId: str = Path(..., description="ID of the user to send password setup link"), frontendUrl: str = Body(..., embed=True), @@ -749,7 +749,7 @@ Falls Sie diese Anforderung nicht erwartet haben, kontaktieren Sie bitte Ihren A @router.delete("/{userId}", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def delete_user( +def delete_user( request: Request, userId: str = Path(..., description="ID of the user to delete"), context: RequestContext = Depends(getRequestContext) diff --git a/modules/routes/routeDataWorkflows.py b/modules/routes/routeDataWorkflows.py index 80ca5986..88b41009 100644 --- a/modules/routes/routeDataWorkflows.py +++ b/modules/routes/routeDataWorkflows.py @@ -50,7 +50,7 @@ def getServiceChat(currentUser: User): # Consolidated endpoint for getting all workflows @router.get("/", response_model=PaginatedResponse[ChatWorkflow]) @limiter.limit("120/minute") -async def get_workflows( +def get_workflows( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), currentUser: User = Depends(getCurrentUser) @@ -123,7 +123,7 @@ async def get_workflows( @router.get("/{workflowId}", response_model=ChatWorkflow) @limiter.limit("120/minute") -async def get_workflow( +def get_workflow( request: Request, workflowId: str = Path(..., description="ID of the workflow"), currentUser: User = Depends(getCurrentUser) @@ -152,7 +152,7 @@ async def get_workflow( @router.put("/{workflowId}", response_model=ChatWorkflow) @limiter.limit("120/minute") -async def update_workflow( +def update_workflow( request: Request, workflowId: str = Path(..., description="ID of the workflow to update"), workflowData: Dict[str, Any] = Body(...), @@ -200,7 +200,7 @@ async def update_workflow( # API Endpoint for workflow status @router.get("/{workflowId}/status", response_model=ChatWorkflow) @limiter.limit("120/minute") -async def get_workflow_status( +def get_workflow_status( request: Request, workflowId: str = Path(..., description="ID of the workflow"), currentUser: User = Depends(getCurrentUser) @@ -274,7 +274,7 @@ async def stop_workflow( # API Endpoint for workflow logs with selective data transfer @router.get("/{workflowId}/logs", response_model=PaginatedResponse[ChatLog]) @limiter.limit("120/minute") -async def get_workflow_logs( +def get_workflow_logs( request: Request, workflowId: str = Path(..., description="ID of the workflow"), logId: Optional[str] = Query(None, description="Optional log ID to get only newer logs (legacy selective data transfer)"), @@ -365,7 +365,7 @@ async def get_workflow_logs( # API Endpoint for workflow messages with selective data transfer @router.get("/{workflowId}/messages", response_model=PaginatedResponse[ChatMessage]) @limiter.limit("120/minute") -async def get_workflow_messages( +def get_workflow_messages( request: Request, workflowId: str = Path(..., description="ID of the workflow"), messageId: Optional[str] = Query(None, description="Optional message ID to get only newer messages (legacy selective data transfer)"), @@ -457,7 +457,7 @@ async def get_workflow_messages( # State 11: Workflow Reset/Deletion endpoint @router.delete("/{workflowId}", response_model=Dict[str, Any]) @limiter.limit("120/minute") -async def delete_workflow( +def delete_workflow( request: Request, workflowId: str = Path(..., description="ID of the workflow to delete"), currentUser: User = Depends(getCurrentUser) @@ -516,7 +516,7 @@ async def delete_workflow( @router.delete("/{workflowId}/messages/{messageId}", response_model=Dict[str, Any]) @limiter.limit("120/minute") -async def delete_workflow_message( +def delete_workflow_message( request: Request, workflowId: str = Path(..., description="ID of the workflow"), messageId: str = Path(..., description="ID of the message to delete"), @@ -566,7 +566,7 @@ async def delete_workflow_message( @router.delete("/{workflowId}/messages/{messageId}/files/{fileId}", response_model=Dict[str, Any]) @limiter.limit("120/minute") -async def delete_file_from_message( +def delete_file_from_message( request: Request, workflowId: str = Path(..., description="ID of the workflow"), messageId: str = Path(..., description="ID of the message"), @@ -615,7 +615,7 @@ async def delete_file_from_message( @router.get("/actions", response_model=Dict[str, Any]) @limiter.limit("120/minute") -async def get_all_actions( +def get_all_actions( request: Request, currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: @@ -685,7 +685,7 @@ async def get_all_actions( @router.get("/actions/{method}", response_model=Dict[str, Any]) @limiter.limit("120/minute") -async def get_method_actions( +def get_method_actions( request: Request, method: str = Path(..., description="Method name (e.g., 'outlook', 'sharepoint')"), currentUser: User = Depends(getCurrentUser) @@ -768,7 +768,7 @@ async def get_method_actions( @router.get("/actions/{method}/{action}", response_model=Dict[str, Any]) @limiter.limit("120/minute") -async def get_action_schema( +def get_action_schema( request: Request, method: str = Path(..., description="Method name (e.g., 'outlook', 'sharepoint')"), action: str = Path(..., description="Action name (e.g., 'readEmails', 'uploadDocument')"), diff --git a/modules/routes/routeGdpr.py b/modules/routes/routeGdpr.py index af0c7199..abc39b1f 100644 --- a/modules/routes/routeGdpr.py +++ b/modules/routes/routeGdpr.py @@ -74,7 +74,7 @@ class DeletionResult(BaseModel): @router.get("/data-export", response_model=DataExportResponse) @limiter.limit("5/minute") -async def export_user_data( +def export_user_data( request: Request, currentUser: User = Depends(getCurrentUser) ) -> DataExportResponse: @@ -215,7 +215,7 @@ async def export_user_data( @router.get("/data-portability") @limiter.limit("5/minute") -async def export_portable_data( +def export_portable_data( request: Request, currentUser: User = Depends(getCurrentUser) ) -> JSONResponse: @@ -296,7 +296,7 @@ async def export_portable_data( @router.delete("/", response_model=DeletionResult) @limiter.limit("1/hour") -async def delete_account( +def delete_account( request: Request, confirmDeletion: bool = False, currentUser: User = Depends(getCurrentUser) @@ -391,7 +391,7 @@ async def delete_account( @router.get("/consent-info", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def get_consent_info( +def get_consent_info( request: Request, currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: diff --git a/modules/routes/routeInvitations.py b/modules/routes/routeInvitations.py index 6a53fb38..095b84fb 100644 --- a/modules/routes/routeInvitations.py +++ b/modules/routes/routeInvitations.py @@ -94,7 +94,7 @@ class InvitationValidation(BaseModel): @router.post("/", response_model=InvitationResponse) @limiter.limit("30/minute") -async def create_invitation( +def create_invitation( request: Request, data: InvitationCreate, context: RequestContext = Depends(getRequestContext) @@ -300,7 +300,7 @@ async def create_invitation( @router.get("/", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def list_invitations( +def list_invitations( request: Request, includeUsed: bool = Query(False, description="Include already used invitations"), includeExpired: bool = Query(False, description="Include expired invitations"), @@ -379,7 +379,7 @@ async def list_invitations( @router.delete("/{invitationId}", response_model=Dict[str, str]) @limiter.limit("30/minute") -async def revoke_invitation( +def revoke_invitation( request: Request, invitationId: str, context: RequestContext = Depends(getRequestContext) @@ -458,7 +458,7 @@ async def revoke_invitation( @router.get("/validate/{token}", response_model=InvitationValidation) @limiter.limit("30/minute") -async def validate_invitation( +def validate_invitation( request: Request, token: str ) -> InvitationValidation: @@ -562,7 +562,7 @@ async def validate_invitation( @router.post("/accept/{token}", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def accept_invitation( +def accept_invitation( request: Request, token: str, currentUser: User = Depends(getCurrentUser) diff --git a/modules/routes/routeMessaging.py b/modules/routes/routeMessaging.py index 419e9ae6..223181e0 100644 --- a/modules/routes/routeMessaging.py +++ b/modules/routes/routeMessaging.py @@ -38,7 +38,7 @@ router = APIRouter( @router.get("/subscriptions", response_model=PaginatedResponse[MessagingSubscription]) @limiter.limit("60/minute") -async def get_subscriptions( +def get_subscriptions( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), currentUser: User = Depends(getCurrentUser) @@ -79,7 +79,7 @@ async def get_subscriptions( @router.post("/subscriptions", response_model=MessagingSubscription) @limiter.limit("60/minute") -async def create_subscription( +def create_subscription( request: Request, subscription: MessagingSubscription, currentUser: User = Depends(getCurrentUser) @@ -95,7 +95,7 @@ async def create_subscription( @router.get("/subscriptions/{subscriptionId}", response_model=MessagingSubscription) @limiter.limit("60/minute") -async def get_subscription( +def get_subscription( request: Request, subscriptionId: str = Path(..., description="ID of the subscription"), currentUser: User = Depends(getCurrentUser) @@ -115,7 +115,7 @@ async def get_subscription( @router.put("/subscriptions/{subscriptionId}", response_model=MessagingSubscription) @limiter.limit("60/minute") -async def update_subscription( +def update_subscription( request: Request, subscriptionId: str = Path(..., description="ID of the subscription to update"), subscriptionData: MessagingSubscription = Body(...), @@ -145,7 +145,7 @@ async def update_subscription( @router.delete("/subscriptions/{subscriptionId}", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def delete_subscription( +def delete_subscription( request: Request, subscriptionId: str = Path(..., description="ID of the subscription to delete"), currentUser: User = Depends(getCurrentUser) @@ -174,7 +174,7 @@ async def delete_subscription( @router.get("/subscriptions/{subscriptionId}/registrations", response_model=PaginatedResponse[MessagingSubscriptionRegistration]) @limiter.limit("60/minute") -async def get_subscription_registrations( +def get_subscription_registrations( request: Request, subscriptionId: str = Path(..., description="ID of the subscription"), pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), @@ -219,7 +219,7 @@ async def get_subscription_registrations( @router.post("/subscriptions/{subscriptionId}/subscribe", response_model=MessagingSubscriptionRegistration) @limiter.limit("60/minute") -async def subscribe_user( +def subscribe_user( request: Request, subscriptionId: str = Path(..., description="ID of the subscription"), channel: MessagingChannel = Body(..., embed=True), @@ -241,7 +241,7 @@ async def subscribe_user( @router.delete("/subscriptions/{subscriptionId}/unsubscribe", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def unsubscribe_user( +def unsubscribe_user( request: Request, subscriptionId: str = Path(..., description="ID of the subscription"), channel: MessagingChannel = Body(..., embed=True), @@ -267,7 +267,7 @@ async def unsubscribe_user( @router.get("/registrations", response_model=PaginatedResponse[MessagingSubscriptionRegistration]) @limiter.limit("60/minute") -async def get_my_registrations( +def get_my_registrations( request: Request, pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), currentUser: User = Depends(getCurrentUser) @@ -311,7 +311,7 @@ async def get_my_registrations( @router.put("/registrations/{registrationId}", response_model=MessagingSubscriptionRegistration) @limiter.limit("60/minute") -async def update_registration( +def update_registration( request: Request, registrationId: str = Path(..., description="ID of the registration to update"), registrationData: MessagingSubscriptionRegistration = Body(...), @@ -341,7 +341,7 @@ async def update_registration( @router.delete("/registrations/{registrationId}", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def delete_registration( +def delete_registration( request: Request, registrationId: str = Path(..., description="ID of the registration to delete"), currentUser: User = Depends(getCurrentUser) @@ -376,7 +376,7 @@ def _getTriggerKey(request: Request) -> str: @router.post("/trigger/{subscriptionId}", response_model=MessagingSubscriptionExecutionResult) @limiter.limit("60/minute", key_func=_getTriggerKey) -async def trigger_subscription( +def trigger_subscription( request: Request, subscriptionId: str = Path(..., description="ID of the subscription to trigger"), eventParameters: Dict[str, Any] = Body(...), @@ -439,7 +439,7 @@ def _hasTriggerPermission(context: RequestContext) -> bool: @router.get("/deliveries", response_model=PaginatedResponse[MessagingDelivery]) @limiter.limit("60/minute") -async def get_deliveries( +def get_deliveries( request: Request, subscriptionId: Optional[str] = Query(None, description="Filter by subscription ID"), pagination: Optional[str] = Query(None, description="JSON-encoded PaginationParams object"), @@ -485,7 +485,7 @@ async def get_deliveries( @router.get("/deliveries/{deliveryId}", response_model=MessagingDelivery) @limiter.limit("60/minute") -async def get_delivery( +def get_delivery( request: Request, deliveryId: str = Path(..., description="ID of the delivery"), currentUser: User = Depends(getCurrentUser) diff --git a/modules/routes/routeNotifications.py b/modules/routes/routeNotifications.py index 4fc09ac4..00e9bbcd 100644 --- a/modules/routes/routeNotifications.py +++ b/modules/routes/routeNotifications.py @@ -120,7 +120,7 @@ def createInvitationNotification( @router.get("", response_model=List[Dict[str, Any]]) @limiter.limit("60/minute") -async def getNotifications( +def getNotifications( request: Request, currentUser: User = Depends(getCurrentUser), status: Optional[str] = None, @@ -161,7 +161,7 @@ async def getNotifications( @router.get("/unread-count", response_model=UnreadCountResponse) @limiter.limit("120/minute") -async def getUnreadCount( +def getUnreadCount( request: Request, currentUser: User = Depends(getCurrentUser) ) -> UnreadCountResponse: @@ -190,7 +190,7 @@ async def getUnreadCount( @router.put("/{notificationId}/read", response_model=Dict[str, Any]) @limiter.limit("60/minute") -async def markAsRead( +def markAsRead( request: Request, notificationId: str, currentUser: User = Depends(getCurrentUser) @@ -241,7 +241,7 @@ async def markAsRead( @router.put("/mark-all-read", response_model=Dict[str, Any]) @limiter.limit("10/minute") -async def markAllAsRead( +def markAllAsRead( request: Request, currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: @@ -283,7 +283,7 @@ async def markAllAsRead( @router.post("/{notificationId}/action", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def executeAction( +def executeAction( request: Request, notificationId: str, actionRequest: NotificationActionRequest, @@ -332,7 +332,7 @@ async def executeAction( actionResult = None if notification.get("type") == NotificationType.INVITATION.value: - actionResult = await _handleInvitationAction( + actionResult = _handleInvitationAction( notification=notification, actionId=actionRequest.actionId, currentUser=currentUser, @@ -370,7 +370,7 @@ async def executeAction( ) -async def _handleInvitationAction( +def _handleInvitationAction( notification: Dict[str, Any], actionId: str, currentUser: User, @@ -488,7 +488,7 @@ async def _handleInvitationAction( @router.delete("/{notificationId}", response_model=Dict[str, Any]) @limiter.limit("30/minute") -async def deleteNotification( +def deleteNotification( request: Request, notificationId: str, currentUser: User = Depends(getCurrentUser) diff --git a/modules/routes/routeSecurityAdmin.py b/modules/routes/routeSecurityAdmin.py index 75490eac..6a01cd9a 100644 --- a/modules/routes/routeSecurityAdmin.py +++ b/modules/routes/routeSecurityAdmin.py @@ -97,7 +97,7 @@ def _getDatabaseConnector(databaseName: str, userId: str = None) -> DatabaseConn @router.get("/tokens") @limiter.limit("30/minute") -async def list_tokens( +def list_tokens( request: Request, currentUser: User = Depends(requireSysAdmin), userId: Optional[str] = None, @@ -137,7 +137,7 @@ async def list_tokens( @router.post("/tokens/revoke/user") @limiter.limit("30/minute") -async def revoke_tokens_by_user( +def revoke_tokens_by_user( request: Request, currentUser: User = Depends(requireSysAdmin), payload: Dict[str, Any] = Body(...) @@ -172,7 +172,7 @@ async def revoke_tokens_by_user( @router.post("/tokens/revoke/session") @limiter.limit("30/minute") -async def revoke_tokens_by_session( +def revoke_tokens_by_session( request: Request, currentUser: User = Depends(requireSysAdmin), payload: Dict[str, Any] = Body(...) @@ -208,7 +208,7 @@ async def revoke_tokens_by_session( @router.post("/tokens/revoke/id") @limiter.limit("30/minute") -async def revoke_token_by_id( +def revoke_token_by_id( request: Request, currentUser: User = Depends(requireSysAdmin), payload: Dict[str, Any] = Body(...) @@ -235,7 +235,7 @@ async def revoke_token_by_id( @router.post("/tokens/revoke/mandate") @limiter.limit("10/minute") -async def revoke_tokens_by_mandate( +def revoke_tokens_by_mandate( request: Request, currentUser: User = Depends(requireSysAdmin), payload: Dict[str, Any] = Body(...) @@ -280,7 +280,7 @@ async def revoke_tokens_by_mandate( @router.get("/logs/{log_name}") @limiter.limit("60/minute") -async def download_log( +def download_log( request: Request, currentUser: User = Depends(requireSysAdmin), log_name: str = "poweron" @@ -309,7 +309,7 @@ async def download_log( @router.get("/databases") @limiter.limit("10/minute") -async def list_databases( +def list_databases( request: Request, currentUser: User = Depends(requireSysAdmin) ) -> Dict[str, Any]: @@ -327,7 +327,7 @@ async def list_databases( @router.get("/databases/{database_name}/tables") @limiter.limit("30/minute") -async def get_database_tables( +def get_database_tables( request: Request, database_name: str, currentUser: User = Depends(requireSysAdmin) @@ -356,7 +356,7 @@ async def get_database_tables( @router.post("/databases/{database_name}/tables/{table_name}/drop") @limiter.limit("10/minute") -async def drop_table( +def drop_table( request: Request, database_name: str, table_name: str, @@ -404,7 +404,7 @@ async def drop_table( @router.post("/databases/drop") @limiter.limit("5/minute") -async def drop_database( +def drop_database( request: Request, currentUser: User = Depends(requireSysAdmin), payload: Dict[str, Any] = Body(...) diff --git a/modules/routes/routeSecurityGoogle.py b/modules/routes/routeSecurityGoogle.py index 4ee634ed..cfaddc22 100644 --- a/modules/routes/routeSecurityGoogle.py +++ b/modules/routes/routeSecurityGoogle.py @@ -93,7 +93,7 @@ SCOPES = [ ] @router.get("/config") -async def get_config(): +def get_config(): """Debug endpoint to check Google OAuth configuration""" return { "client_id": CLIENT_ID, @@ -109,7 +109,7 @@ async def get_config(): @router.get("/login") @limiter.limit("5/minute") -async def login( +def login( request: Request, state: str = Query("login", description="State parameter to distinguish between login and connection flows"), connectionId: Optional[str] = Query(None, description="Connection ID for connection flow") @@ -589,7 +589,7 @@ async def auth_callback(code: str, state: str, request: Request, response: Respo @router.get("/me", response_model=User) @limiter.limit("30/minute") -async def get_current_user( +def get_current_user( request: Request, currentUser: User = Depends(getCurrentUser) ) -> User: @@ -605,7 +605,7 @@ async def get_current_user( @router.post("/logout") @limiter.limit("10/minute") -async def logout( +def logout( request: Request, currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: diff --git a/modules/routes/routeSecurityLocal.py b/modules/routes/routeSecurityLocal.py index 5f132833..46d00152 100644 --- a/modules/routes/routeSecurityLocal.py +++ b/modules/routes/routeSecurityLocal.py @@ -89,7 +89,7 @@ router = APIRouter( @router.post("/login") @limiter.limit("30/minute") -async def login( +def login( request: Request, response: Response, formData: OAuth2PasswordRequestForm = Depends(), @@ -242,7 +242,7 @@ async def login( @router.post("/register") @limiter.limit("10/minute") -async def register_user( +def register_user( request: Request, userData: User = Body(...), frontendUrl: str = Body(..., embed=True) @@ -381,7 +381,7 @@ Falls Sie sich nicht registriert haben, können Sie diese E-Mail ignorieren.""" @router.get("/me", response_model=User) @limiter.limit("30/minute") -async def read_user_me( +def read_user_me( request: Request, currentUser: User = Depends(getCurrentUser) ) -> User: @@ -397,7 +397,7 @@ async def read_user_me( @router.post("/refresh") @limiter.limit("60/minute") -async def refresh_token( +def refresh_token( request: Request, response: Response ) -> Dict[str, Any]: @@ -472,7 +472,7 @@ async def refresh_token( @router.post("/logout") @limiter.limit("30/minute") -async def logout(request: Request, response: Response, currentUser: User = Depends(getCurrentUser)) -> JSONResponse: +def logout(request: Request, response: Response, currentUser: User = Depends(getCurrentUser)) -> JSONResponse: """Logout from local authentication""" try: # Get user interface with current user context @@ -541,7 +541,7 @@ async def logout(request: Request, response: Response, currentUser: User = Depen @router.get("/available") @limiter.limit("10/minute") -async def check_username_availability( +def check_username_availability( request: Request, username: str, authenticationAuthority: str = "local" @@ -573,7 +573,7 @@ async def check_username_availability( @router.post("/password-reset-request") @limiter.limit("5/minute") -async def password_reset_request( +def password_reset_request( request: Request, username: str = Body(..., embed=True), frontendUrl: str = Body(..., embed=True) @@ -653,7 +653,7 @@ Falls Sie diese Anforderung nicht gestellt haben, können Sie diese E-Mail ignor @router.post("/password-reset") @limiter.limit("10/minute") -async def password_reset( +def password_reset( request: Request, token: str = Body(..., embed=True), password: str = Body(..., embed=True) diff --git a/modules/routes/routeSecurityMsft.py b/modules/routes/routeSecurityMsft.py index 0abb2f56..338e2e33 100644 --- a/modules/routes/routeSecurityMsft.py +++ b/modules/routes/routeSecurityMsft.py @@ -66,7 +66,7 @@ SCOPES = [ @router.get("/login") @limiter.limit("5/minute") -async def login( +def login( request: Request, state: str = Query("login", description="State parameter to distinguish between login and connection flows"), connectionId: Optional[str] = Query(None, description="Connection ID for connection flow") @@ -138,7 +138,7 @@ async def login( @router.get("/adminconsent") @limiter.limit("5/minute") -async def adminconsent(request: Request) -> RedirectResponse: +def adminconsent(request: Request) -> RedirectResponse: """Initiate Microsoft Admin Consent flow. An Azure AD admin must visit this URL once to grant consent for the entire tenant. @@ -161,7 +161,7 @@ async def adminconsent(request: Request) -> RedirectResponse: ) @router.get("/adminconsent/callback") -async def adminconsent_callback( +def adminconsent_callback( admin_consent: Optional[str] = Query(None), tenant: Optional[str] = Query(None), error: Optional[str] = Query(None), @@ -603,7 +603,7 @@ async def auth_callback(code: str, state: str, request: Request, response: Respo @router.get("/me", response_model=User) @limiter.limit("30/minute") -async def get_current_user( +def get_current_user( request: Request, currentUser: User = Depends(getCurrentUser) ) -> User: @@ -619,7 +619,7 @@ async def get_current_user( @router.post("/logout") @limiter.limit("10/minute") -async def logout( +def logout( request: Request, currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: @@ -655,7 +655,7 @@ async def logout( @router.post("/cleanup") @limiter.limit("5/minute") -async def cleanup_expired_tokens( +def cleanup_expired_tokens( request: Request, currentUser: User = Depends(getCurrentUser) ) -> Dict[str, Any]: diff --git a/modules/routes/routeSystem.py b/modules/routes/routeSystem.py index 04e14063..3c8cdd3d 100644 --- a/modules/routes/routeSystem.py +++ b/modules/routes/routeSystem.py @@ -409,7 +409,7 @@ def _formatBlockItem(item: Dict[str, Any], language: str) -> Dict[str, Any]: @navigationRouter.get("/navigation") @limiter.limit("60/minute") -async def get_navigation( +def get_navigation( request: Request, language: str = Query("de", description="Language for labels (en, de, fr)"), reqContext: RequestContext = Depends(getRequestContext) diff --git a/modules/workflows/automation/mainWorkflow.py b/modules/workflows/automation/mainWorkflow.py index e63f7932..172fc977 100644 --- a/modules/workflows/automation/mainWorkflow.py +++ b/modules/workflows/automation/mainWorkflow.py @@ -177,17 +177,14 @@ async def executeAutomation(automationId: str, services) -> ChatWorkflow: workflow = services.interfaceDbChat.updateWorkflow(workflow.id, {"name": workflowName}) logger.info(f"Set workflow {workflow.id} name to: {workflowName}") - # Update automation with execution log + # Save execution log (bypasses RBAC — system operation, not a user edit) executionLogs = list(automation.executionLogs or []) executionLogs.append(executionLog) # Keep only last 50 executions if len(executionLogs) > 50: executionLogs = executionLogs[-50:] - services.interfaceDbAutomation.updateAutomationDefinition( - automationId, - {"executionLogs": executionLogs} - ) + services.interfaceDbAutomation._saveExecutionLog(automationId, executionLogs) return workflow except Exception as e: @@ -195,7 +192,7 @@ async def executeAutomation(automationId: str, services) -> ChatWorkflow: executionLog["status"] = "error" executionLog["messages"].append(f"Error: {str(e)}") - # Update automation with execution log even on error + # Save execution log even on error (bypasses RBAC — system operation) try: automation = services.interfaceDbAutomation.getAutomationDefinition(automationId) if automation: @@ -203,10 +200,7 @@ async def executeAutomation(automationId: str, services) -> ChatWorkflow: executionLogs.append(executionLog) if len(executionLogs) > 50: executionLogs = executionLogs[-50:] - services.interfaceDbAutomation.updateAutomationDefinition( - automationId, - {"executionLogs": executionLogs} - ) + services.interfaceDbAutomation._saveExecutionLog(automationId, executionLogs) except Exception as logError: logger.error(f"Error saving execution log: {str(logError)}") diff --git a/scripts/migrate_async_to_sync.py b/scripts/migrate_async_to_sync.py new file mode 100644 index 00000000..d0f8ef67 --- /dev/null +++ b/scripts/migrate_async_to_sync.py @@ -0,0 +1,377 @@ +#!/usr/bin/env python3 +""" +Migration Script: Convert async def → def for route handlers that don't need async. + +This fixes the event-loop blocking issue where synchronous psycopg2 DB operations +inside async def routes block the entire uvicorn event loop, preventing concurrent +request handling. + +FastAPI behavior: +- `async def` routes → run directly on the event loop (blocks if sync code inside) +- `def` routes → run in a thread pool automatically (non-blocking) + +Usage: + python scripts/migrate_async_to_sync.py --dry-run # Preview changes + python scripts/migrate_async_to_sync.py # Apply changes + +Author: Auto-generated migration script +""" + +import os +import re +import sys +import argparse +from pathlib import Path +from typing import Dict, List, Set, Tuple + +# Base directory +GATEWAY_DIR = Path(__file__).parent.parent +ROUTES_DIR = GATEWAY_DIR / "modules" / "routes" +FEATURES_DIR = GATEWAY_DIR / "modules" / "features" +AUTH_DIR = GATEWAY_DIR / "modules" / "auth" + + +# ============================================================================= +# Configuration: Functions that MUST stay async +# ============================================================================= + +# Key: relative file path from gateway dir +# Value: set of function names that must remain async def +_MUST_STAY_ASYNC: Dict[str, Set[str]] = { + # --- routes/ --- + "modules/routes/routeAdminAutomationEvents.py": { + "sync_all_automation_events", # await syncAutomationEvents(...) + }, + "modules/routes/routeAdminRbacExport.py": { + "import_global_rbac", # await file.read() + "import_mandate_rbac", # await file.read() + }, + "modules/routes/routeDataConnections.py": { + "get_connections", # await token_refresh_service.refresh_expired_tokens(...) + }, + "modules/routes/routeDataFiles.py": { + "upload_file", # await file.read() + }, + "modules/routes/routeDataWorkflows.py": { + "stop_workflow", # await chatStop(...) + }, + # These files have many genuinely async routes (httpx, external APIs) -- keep ALL async: + "modules/routes/routeRealEstate.py": "__ALL__", + "modules/routes/routeSharepoint.py": "__ALL__", + "modules/routes/routeVoiceGoogle.py": "__ALL__", + # Partial keeps in security routes (httpx.AsyncClient, request.json()): + "modules/routes/routeSecurityGoogle.py": { + "verify_google_token", # await client.get(...) + "auth_callback", # await verify_google_token(...), await client.get(...) + "verify_token", # await verify_google_token(...) + "refresh_token", # await request.json() + }, + "modules/routes/routeSecurityMsft.py": { + "auth_callback", # await client.get(...) + "refresh_token", # await request.json() + }, + # --- features/ --- + "modules/features/automation/routeFeatureAutomation.py": { + "execute_automation_route", # await executeAutomation(...) + }, + "modules/features/chatbot/routeFeatureChatbot.py": { + "stream_chatbot_start", # await chatProcess(...), contains async event_stream generator + "event_stream", # await request.is_disconnected(), await asyncio.wait_for(...) + "stop_chatbot", # await event_manager.emit_event(...) + }, + "modules/features/chatplayground/routeFeatureChatplayground.py": { + "start_workflow", # await chatStart(...) + "stop_workflow", # await chatStop(...) + }, + "modules/features/neutralization/routeFeatureNeutralizer.py": { + "process_sharepoint_files", # await service.processSharepointFiles(...) + }, + "modules/features/realestate/routeFeatureRealEstate.py": { + "process_command", # await processNaturalLanguageCommand(...) + "create_table_record", # await create_project_with_parcel_data(...) + "search_parcel", # await connector.search_parcel(...), connector._query_building_layer(...) + "add_parcel_to_project", # await connector.search_parcel(...) + }, + "modules/features/trustee/routeFeatureTrustee.py": { + "create_document", # await request.json() + "upload_document", # await file.read() + }, +} + +# Files to skip entirely (all routes must stay async) +_SKIP_FILES: Set[str] = { + "modules/routes/routeRealEstate.py", + "modules/routes/routeSharepoint.py", + "modules/routes/routeVoiceGoogle.py", +} + +# Helper functions that are fake-async (async def but no await inside) +# These will be converted from async def -> def +_FAKE_ASYNC_HELPERS: Dict[str, Set[str]] = { + "modules/features/chatplayground/routeFeatureChatplayground.py": {"_validateInstanceAccess"}, + "modules/features/trustee/routeFeatureTrustee.py": {"_validateInstanceAccess", "_validateInstanceAdmin"}, + "modules/features/realestate/routeFeatureRealEstate.py": {"_validateInstanceAccess"}, + "modules/features/chatbot/routeFeatureChatbot.py": {"_validateInstanceAccess"}, + "modules/routes/routeNotifications.py": {"_handleInvitationAction"}, +} + +# Calls to these functions should have 'await' removed after they become sync +_REMOVE_AWAIT_CALLS: Set[str] = { + "_validateInstanceAccess", + "_validateInstanceAdmin", + "_handleInvitationAction", +} + + +# ============================================================================= +# Migration Logic +# ============================================================================= + +def _getRelativePath(filePath: Path) -> str: + """Get path relative to gateway dir.""" + try: + return str(filePath.relative_to(GATEWAY_DIR)).replace("\\", "/") + except ValueError: + return str(filePath) + + +def _shouldSkipFile(relPath: str) -> bool: + """Check if file should be skipped entirely.""" + return relPath in _SKIP_FILES or _MUST_STAY_ASYNC.get(relPath) == "__ALL__" + + +def _mustStayAsync(relPath: str, funcName: str) -> bool: + """Check if a specific function must stay async.""" + keepSet = _MUST_STAY_ASYNC.get(relPath, set()) + if keepSet == "__ALL__": + return True + return funcName in keepSet + + +def _isFakeAsyncHelper(relPath: str, funcName: str) -> bool: + """Check if a function is a fake-async helper that should be converted.""" + helpers = _FAKE_ASYNC_HELPERS.get(relPath, set()) + return funcName in helpers + + +def _processFile(filePath: Path, dryRun: bool = True) -> Dict[str, any]: + """Process a single file and convert async def → def where appropriate.""" + relPath = _getRelativePath(filePath) + + if _shouldSkipFile(relPath): + return {"file": relPath, "skipped": True, "reason": "all routes must stay async"} + + with open(filePath, "r", encoding="utf-8") as f: + originalContent = f.read() + + content = originalContent + changes = [] + + # Step 1: Find all async def functions and convert eligible ones + # Pattern matches: async def function_name( + asyncDefPattern = re.compile(r'^(\s*)async def (\w+)\s*\(', re.MULTILINE) + + convertedFunctions = set() + + for match in asyncDefPattern.finditer(originalContent): + indent = match.group(1) + funcName = match.group(2) + + # Check if this function must stay async + if _mustStayAsync(relPath, funcName): + changes.append(f" KEEP async: {funcName} (must stay async)") + continue + + # Convert async def → def + convertedFunctions.add(funcName) + changes.append(f" CONVERT: async def {funcName} -> def {funcName}") + + # Apply conversions + for funcName in convertedFunctions: + # Replace "async def funcName(" with "def funcName(" + # Be careful to match the exact function definition + pattern = re.compile( + r'^(\s*)async def ' + re.escape(funcName) + r'\s*\(', + re.MULTILINE + ) + content = pattern.sub( + lambda m: f'{m.group(1)}def {funcName}(', + content + ) + + # Step 2: Remove 'await' from calls to converted functions + # This handles: await _validateInstanceAccess(...) → _validateInstanceAccess(...) + # And also: result = await someConvertedFunc(...) → result = someConvertedFunc(...) + for funcName in _REMOVE_AWAIT_CALLS: + if funcName in convertedFunctions or _isFakeAsyncHelper(relPath, funcName): + awaitPattern = re.compile( + r'(\s*)(.*)await\s+' + re.escape(funcName) + r'\s*\(', + re.MULTILINE + ) + newContent = awaitPattern.sub( + lambda m: f'{m.group(1)}{m.group(2)}{funcName}(', + content + ) + if newContent != content: + changes.append(f" REMOVE await: await {funcName}(...) -> {funcName}(...)") + content = newContent + + # Step 3: Check for any remaining 'await' in converted functions + # This catches cases where a converted function still has await calls + remainingAwaits = [] + lines = content.split('\n') + currentFunc = None + funcIndent = 0 + + for i, line in enumerate(lines): + # Track current function + defMatch = re.match(r'^(\s*)def (\w+)\s*\(', line) + asyncDefMatch = re.match(r'^(\s*)async def (\w+)\s*\(', line) + + if defMatch and defMatch.group(2) in convertedFunctions: + currentFunc = defMatch.group(2) + funcIndent = len(defMatch.group(1)) + elif defMatch or asyncDefMatch: + currentFunc = None + elif currentFunc and line.strip() and not line[0].isspace(): + currentFunc = None + + # Check for remaining awaits in converted functions + if currentFunc and 'await ' in line: + remainingAwaits.append(f" WARNING: Remaining 'await' in {currentFunc} at line {i+1}: {line.strip()}") + + # Build result + result = { + "file": relPath, + "skipped": False, + "convertedCount": len(convertedFunctions), + "convertedFunctions": sorted(convertedFunctions), + "changes": changes, + "warnings": remainingAwaits, + "modified": content != originalContent, + } + + # Write file if not dry run and content changed + if not dryRun and content != originalContent: + with open(filePath, "w", encoding="utf-8") as f: + f.write(content) + result["written"] = True + else: + result["written"] = False + + return result + + +def _discoverRouteFiles() -> List[Path]: + """Discover all route files to process.""" + files = [] + + # Standard routes + if ROUTES_DIR.exists(): + for f in sorted(ROUTES_DIR.glob("route*.py")): + files.append(f) + + # Feature routes + if FEATURES_DIR.exists(): + for f in sorted(FEATURES_DIR.glob("*/routeFeature*.py")): + files.append(f) + + return files + + +def _main(): + parser = argparse.ArgumentParser( + description="Migrate async def → def for FastAPI routes with sync DB operations" + ) + parser.add_argument( + "--dry-run", + action="store_true", + default=False, + help="Preview changes without writing files (default: apply changes)" + ) + parser.add_argument( + "--file", + type=str, + default=None, + help="Process only a specific file (relative to gateway dir)" + ) + args = parser.parse_args() + + dryRun = args.dry_run + + print("=" * 70) + print(f" FastAPI Route Migration: async def -> def") + print(f" Mode: {'DRY RUN (preview only)' if dryRun else 'APPLY CHANGES'}") + print("=" * 70) + print() + + # Discover files + if args.file: + targetFile = GATEWAY_DIR / args.file.replace("/", os.sep) + if not targetFile.exists(): + print(f"ERROR: File not found: {targetFile}") + sys.exit(1) + files = [targetFile] + else: + files = _discoverRouteFiles() + + print(f"Found {len(files)} route files to analyze\n") + + totalConverted = 0 + totalWarnings = 0 + totalModified = 0 + allResults = [] + + for filePath in files: + result = _processFile(filePath, dryRun=dryRun) + allResults.append(result) + + if result.get("skipped"): + print(f"[SKIP] {result['file']} - SKIPPED ({result.get('reason', '')})") + continue + + converted = result.get("convertedCount", 0) + warnings = result.get("warnings", []) + modified = result.get("modified", False) + + if converted == 0 and not warnings: + continue + + totalConverted += converted + totalWarnings += len(warnings) + if modified: + totalModified += 1 + + status = "[DONE] WRITTEN" if result.get("written") else ("[PLAN] WOULD WRITE" if modified else "---") + print(f"{status} {result['file']} ({converted} functions)") + + for change in result.get("changes", []): + print(f" {change}") + + for warning in warnings: + print(f" [WARN] {warning}") + + print() + + # Summary + print("=" * 70) + print(f" SUMMARY") + print(f" Files analyzed: {len(files)}") + print(f" Files modified: {totalModified}") + print(f" Functions converted: {totalConverted}") + print(f" Warnings: {totalWarnings}") + if dryRun: + print(f"\n This was a DRY RUN. Run without --dry-run to apply changes.") + else: + print(f"\n Changes applied. Restart the server to take effect.") + print("=" * 70) + + # Return exit code based on warnings + if totalWarnings > 0: + print(f"\n[WARN] There are {totalWarnings} warnings - review before deploying!") + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(_main())