From 3870f50ea5033766ac7676ca61386e3ed7c99b31 Mon Sep 17 00:00:00 2001 From: Hetavi Shah Date: Thu, 13 Nov 2025 17:01:57 +0530 Subject: [PATCH] [OND211-2329]: Added API to update team settings. --- api/apps/tenant_app.py | 289 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 267 insertions(+), 22 deletions(-) diff --git a/api/apps/tenant_app.py b/api/apps/tenant_app.py index 5def0595a..fc55b9e0c 100644 --- a/api/apps/tenant_app.py +++ b/api/apps/tenant_app.py @@ -61,6 +61,34 @@ def is_team_admin_or_owner(tenant_id: str, user_id: str) -> bool: return user_tenant.role in [UserTenantRole.OWNER, UserTenantRole.ADMIN] +def validate_model_id(user_id: str, model_id: Optional[str], model_type: str, context: str = "team") -> Optional[str]: + """ + Validate that a model ID has been added by the user. Returns error message if invalid, None if valid. + + Args: + user_id: The user ID to check models for + model_id: The model ID to validate (optional) + model_type: The type of model (e.g., "LLM", "Embedding", "ASR") + context: The context for the error message (e.g., "team", "creating a team", "updating the team") + + Returns: + Error message string if invalid, None if valid + """ + if model_id is None: + return None # Optional parameter, skip validation if not provided + + # Check if the model exists in TenantLLM for this user + model_config = TenantLLMService.get_api_key(user_id, model_id) + if not model_config: + return f"{model_type} model '{model_id}' has not been added. Please add the model first before {context}." + + # Check if the model is valid (status = "1") + if model_config.status != StatusEnum.VALID.value: + return f"{model_type} model '{model_id}' is not active. Please enable the model first." + + return None + + @manager.route("//user/list", methods=["GET"]) # noqa: F821 @login_required def user_list(tenant_id): @@ -285,47 +313,30 @@ def create_team() -> Response: # Validate that provided LLM models have been added by the user # Models are stored in TenantLLM with tenant_id = user_id - def validate_model_id(user_id: str, model_id: Optional[str], model_type: str) -> Optional[str]: - """Validate that a model ID has been added by the user. Returns error message if invalid, None if valid.""" - if model_id is None: - return None # Optional parameter, skip validation if not provided - - # Check if the model exists in TenantLLM for this user - model_config = TenantLLMService.get_api_key(user_id, model_id) - if not model_config: - return f"{model_type} model '{model_id}' has not been added. Please add the model first before creating a team." - - # Check if the model is valid (status = "1") - if model_config.status != StatusEnum.VALID.value: - return f"{model_type} model '{model_id}' is not active. Please enable the model first." - - return None - - # Validate all provided model IDs validation_errors = [] if llm_id is not None: - error = validate_model_id(owner_user_id, llm_id, "LLM") + error = validate_model_id(owner_user_id, llm_id, "LLM", "creating a team") if error: validation_errors.append(error) if embd_id is not None: - error = validate_model_id(owner_user_id, embd_id, "Embedding") + error = validate_model_id(owner_user_id, embd_id, "Embedding", "creating a team") if error: validation_errors.append(error) if asr_id is not None: - error = validate_model_id(owner_user_id, asr_id, "ASR") + error = validate_model_id(owner_user_id, asr_id, "ASR", "creating a team") if error: validation_errors.append(error) if img2txt_id is not None: - error = validate_model_id(owner_user_id, img2txt_id, "Image-to-text") + error = validate_model_id(owner_user_id, img2txt_id, "Image-to-text", "creating a team") if error: validation_errors.append(error) if rerank_id is not None: - error = validate_model_id(owner_user_id, rerank_id, "Rerank") + error = validate_model_id(owner_user_id, rerank_id, "Rerank", "creating a team") if error: validation_errors.append(error) @@ -443,6 +454,240 @@ def tenant_list(): return server_error_response(e) +@manager.route("/", methods=["PUT"]) # noqa: F821 +@login_required +def update_team(tenant_id: str) -> Response: + """ + Update team details. Only OWNER or ADMIN can update team information. + + --- + tags: + - Team + security: + - ApiKeyAuth: [] + parameters: + - in: path + name: tenant_id + required: true + type: string + description: Team ID + - in: body + name: body + required: true + schema: + type: object + properties: + name: + type: string + description: Team name (optional, max 100 characters). + llm_id: + type: string + description: LLM model ID (optional, must be added by the user). + embd_id: + type: string + description: Embedding model ID (optional, must be added by the user). + asr_id: + type: string + description: ASR model ID (optional, must be added by the user). + img2txt_id: + type: string + description: Image-to-text model ID (optional, must be added by the user). + rerank_id: + type: string + description: Rerank model ID (optional, must be added by the user). + tts_id: + type: string + description: TTS model ID (optional, must be added by the user). + parser_ids: + type: string + description: Document parser IDs (optional). + credit: + type: integer + description: Credit amount (optional). + responses: + 200: + description: Team updated successfully. + schema: + type: object + properties: + data: + type: object + description: Updated team information. + message: + type: string + description: Success message. + 400: + description: Invalid request. + 401: + description: Unauthorized. + 403: + description: Forbidden - not owner or admin. + 404: + description: Team not found. + """ + try: + # Check if current user is OWNER or ADMIN of the team + if not is_team_admin_or_owner(tenant_id, current_user.id): + return get_json_result( + data=False, + message='Only team owners or admins can update team details.', + code=RetCode.PERMISSION_ERROR + ) + + # Verify tenant exists + success, tenant = TenantService.get_by_id(tenant_id) + if not success or not tenant: + return get_json_result( + data=False, + message=f"Team with ID '{tenant_id}' not found.", + code=RetCode.DATA_ERROR + ) + + if request.json is None: + return get_json_result( + data=False, + message="Request body is required!", + code=RetCode.ARGUMENT_ERROR, + ) + + req: Dict[str, Any] = request.json + + # Extract update fields (all optional) + update_data: Dict[str, Any] = {} + + # Update team name if provided + if "name" in req: + team_name: str = req.get("name", "").strip() + if team_name: + if len(team_name) > 100: + return get_json_result( + data=False, + message="Team name must be 100 characters or less!", + code=RetCode.ARGUMENT_ERROR, + ) + update_data["name"] = team_name + else: + return get_json_result( + data=False, + message="Team name cannot be empty!", + code=RetCode.ARGUMENT_ERROR, + ) + + # Validate model IDs if provided (reuse validate_model_id function) + validation_errors: List[str] = [] + + llm_id: Optional[str] = req.get("llm_id") + if llm_id is not None: + error = validate_model_id(current_user.id, llm_id, "LLM", "updating the team") + if error: + validation_errors.append(error) + else: + update_data["llm_id"] = llm_id + + embd_id: Optional[str] = req.get("embd_id") + if embd_id is not None: + error = validate_model_id(current_user.id, embd_id, "Embedding", "updating the team") + if error: + validation_errors.append(error) + else: + update_data["embd_id"] = embd_id + + asr_id: Optional[str] = req.get("asr_id") + if asr_id is not None: + error = validate_model_id(current_user.id, asr_id, "ASR", "updating the team") + if error: + validation_errors.append(error) + else: + update_data["asr_id"] = asr_id + + img2txt_id: Optional[str] = req.get("img2txt_id") + if img2txt_id is not None: + error = validate_model_id(current_user.id, img2txt_id, "Image-to-text", "updating the team") + if error: + validation_errors.append(error) + else: + update_data["img2txt_id"] = img2txt_id + + rerank_id: Optional[str] = req.get("rerank_id") + if rerank_id is not None: + error = validate_model_id(current_user.id, rerank_id, "Rerank", "updating the team") + if error: + validation_errors.append(error) + else: + update_data["rerank_id"] = rerank_id + + tts_id: Optional[str] = req.get("tts_id") + if tts_id is not None: + error = validate_model_id(current_user.id, tts_id, "TTS", "updating the team") + if error: + validation_errors.append(error) + else: + update_data["tts_id"] = tts_id + + parser_ids: Optional[str] = req.get("parser_ids") + if parser_ids is not None: + update_data["parser_ids"] = parser_ids + + credit: Optional[int] = req.get("credit") + if credit is not None: + if not isinstance(credit, int) or credit < 0: + return get_json_result( + data=False, + message="Credit must be a non-negative integer!", + code=RetCode.ARGUMENT_ERROR, + ) + update_data["credit"] = credit + + if validation_errors: + return get_json_result( + data=False, + message="; ".join(validation_errors), + code=RetCode.DATA_ERROR, + ) + + # Check if there's anything to update + if not update_data: + return get_json_result( + data=False, + message="No fields provided to update.", + code=RetCode.ARGUMENT_ERROR, + ) + + # Update the tenant + TenantService.update_by_id(tenant_id, update_data) + + # Get updated tenant info + success, updated_tenant = TenantService.get_by_id(tenant_id) + if not success or not updated_tenant: + return get_json_result( + data=False, + message="Failed to retrieve updated team information.", + code=RetCode.EXCEPTION_ERROR, + ) + + # Return updated team info + team_data: Dict[str, Any] = { + "id": updated_tenant.id, + "name": updated_tenant.name, + "llm_id": updated_tenant.llm_id, + "embd_id": updated_tenant.embd_id, + "asr_id": updated_tenant.asr_id, + "img2txt_id": updated_tenant.img2txt_id, + "rerank_id": updated_tenant.rerank_id, + "tts_id": updated_tenant.tts_id, + "parser_ids": updated_tenant.parser_ids, + "credit": updated_tenant.credit, + } + + return get_json_result( + data=team_data, + message="Team updated successfully!", + ) + except Exception as e: + logging.exception(e) + return server_error_response(e) + + @manager.route("/update-request/", methods=["PUT"]) # noqa: F821 @login_required def update_request(tenant_id: str) -> Response: