[OND211-2329]: Updatd API's to add users to a team, remove users from a team and accept/reject team joining invitation.
This commit is contained in:
parent
ed7b44f2b8
commit
060ec782fc
1 changed files with 31 additions and 29 deletions
|
|
@ -14,7 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
from flask import Response, request
|
from flask import Response, request
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
|
@ -445,7 +445,7 @@ def tenant_list():
|
||||||
|
|
||||||
@manager.route("/update-request/<tenant_id>", methods=["PUT"]) # noqa: F821
|
@manager.route("/update-request/<tenant_id>", methods=["PUT"]) # noqa: F821
|
||||||
@login_required
|
@login_required
|
||||||
def update_request(tenant_id):
|
def update_request(tenant_id: str) -> Response:
|
||||||
"""
|
"""
|
||||||
Accept or reject a team invitation. User must have INVITE role.
|
Accept or reject a team invitation. User must have INVITE role.
|
||||||
Takes an 'accept' boolean in the request body to accept (true) or reject (false) the invitation.
|
Takes an 'accept' boolean in the request body to accept (true) or reject (false) the invitation.
|
||||||
|
|
@ -505,8 +505,8 @@ def update_request(tenant_id):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get accept boolean from request body
|
# Get accept boolean from request body
|
||||||
req = request.json or {}
|
req: Dict[str, Any] = request.json if request.json is not None else {}
|
||||||
accept = req.get("accept")
|
accept: Optional[bool] = req.get("accept")
|
||||||
|
|
||||||
# Validate accept parameter
|
# Validate accept parameter
|
||||||
if accept is None:
|
if accept is None:
|
||||||
|
|
@ -525,7 +525,7 @@ def update_request(tenant_id):
|
||||||
|
|
||||||
if accept:
|
if accept:
|
||||||
# Accept invitation - update role from INVITE to the specified role
|
# Accept invitation - update role from INVITE to the specified role
|
||||||
role = UserTenantRole.NORMAL.value
|
role: str = UserTenantRole.NORMAL.value
|
||||||
|
|
||||||
# Update role from INVITE to the specified role (defaults to NORMAL)
|
# Update role from INVITE to the specified role (defaults to NORMAL)
|
||||||
UserTenantService.filter_update(
|
UserTenantService.filter_update(
|
||||||
|
|
@ -547,7 +547,7 @@ def update_request(tenant_id):
|
||||||
@manager.route('/<tenant_id>/users/add', methods=['POST']) # noqa: F821
|
@manager.route('/<tenant_id>/users/add', methods=['POST']) # noqa: F821
|
||||||
@login_required
|
@login_required
|
||||||
@validate_request("users")
|
@validate_request("users")
|
||||||
def add_users(tenant_id):
|
def add_users(tenant_id: str) -> Response:
|
||||||
"""
|
"""
|
||||||
Send invitations to one or more users to join a team. Only OWNER or ADMIN can send invitations.
|
Send invitations to one or more users to join a team. Only OWNER or ADMIN can send invitations.
|
||||||
Users must accept the invitation before they are added to the team.
|
Users must accept the invitation before they are added to the team.
|
||||||
|
|
@ -620,8 +620,8 @@ def add_users(tenant_id):
|
||||||
code=RetCode.PERMISSION_ERROR
|
code=RetCode.PERMISSION_ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
req = request.json
|
req: Dict[str, Any] = request.json if request.json is not None else {}
|
||||||
users_input = req.get("users", [])
|
users_input: List[Union[str, Dict[str, Any]]] = req.get("users", [])
|
||||||
|
|
||||||
if not isinstance(users_input, list) or len(users_input) == 0:
|
if not isinstance(users_input, list) or len(users_input) == 0:
|
||||||
return get_json_result(
|
return get_json_result(
|
||||||
|
|
@ -630,11 +630,13 @@ def add_users(tenant_id):
|
||||||
code=RetCode.ARGUMENT_ERROR
|
code=RetCode.ARGUMENT_ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
added_users = []
|
added_users: List[Dict[str, Any]] = []
|
||||||
failed_users = []
|
failed_users: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
for user_input in users_input:
|
for user_input in users_input:
|
||||||
# Handle both string (email) and object formats
|
# Handle both string (email) and object formats
|
||||||
|
email: Optional[str] = None
|
||||||
|
role: str = UserTenantRole.NORMAL.value
|
||||||
if isinstance(user_input, str):
|
if isinstance(user_input, str):
|
||||||
email = user_input
|
email = user_input
|
||||||
role = UserTenantRole.NORMAL.value
|
role = UserTenantRole.NORMAL.value
|
||||||
|
|
@ -665,7 +667,7 @@ def add_users(tenant_id):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Find user by email
|
# Find user by email
|
||||||
invite_users = UserService.query(email=email)
|
invite_users: List[Any] = UserService.query(email=email)
|
||||||
if not invite_users:
|
if not invite_users:
|
||||||
failed_users.append({
|
failed_users.append({
|
||||||
"email": email,
|
"email": email,
|
||||||
|
|
@ -673,12 +675,12 @@ def add_users(tenant_id):
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
user_id_to_add = invite_users[0].id
|
user_id_to_add: str = invite_users[0].id
|
||||||
|
|
||||||
# Check if user is already in the team
|
# Check if user is already in the team
|
||||||
existing_user_tenants = UserTenantService.query(user_id=user_id_to_add, tenant_id=tenant_id)
|
existing_user_tenants: List[Any] = UserTenantService.query(user_id=user_id_to_add, tenant_id=tenant_id)
|
||||||
if existing_user_tenants:
|
if existing_user_tenants:
|
||||||
existing_role = existing_user_tenants[0].role
|
existing_role: Any = existing_user_tenants[0].role
|
||||||
if existing_role in [UserTenantRole.NORMAL, UserTenantRole.ADMIN]:
|
if existing_role in [UserTenantRole.NORMAL, UserTenantRole.ADMIN]:
|
||||||
failed_users.append({
|
failed_users.append({
|
||||||
"email": email,
|
"email": email,
|
||||||
|
|
@ -696,7 +698,7 @@ def add_users(tenant_id):
|
||||||
# Update invitation - keep INVITE role, user needs to accept again
|
# Update invitation - keep INVITE role, user needs to accept again
|
||||||
# Note: The intended role will be applied when user accepts via /agree endpoint
|
# Note: The intended role will be applied when user accepts via /agree endpoint
|
||||||
# For now, we'll store it by updating the invitation (user will need to accept)
|
# For now, we'll store it by updating the invitation (user will need to accept)
|
||||||
usr = invite_users[0].to_dict()
|
usr: Dict[str, Any] = invite_users[0].to_dict()
|
||||||
usr = {k: v for k, v in usr.items() if k in ["id", "avatar", "email", "nickname"]}
|
usr = {k: v for k, v in usr.items() if k in ["id", "avatar", "email", "nickname"]}
|
||||||
usr["role"] = "invite" # Still pending acceptance
|
usr["role"] = "invite" # Still pending acceptance
|
||||||
usr["intended_role"] = role # Store intended role for reference
|
usr["intended_role"] = role # Store intended role for reference
|
||||||
|
|
@ -720,7 +722,7 @@ def add_users(tenant_id):
|
||||||
# Send invitation email if configured
|
# Send invitation email if configured
|
||||||
if smtp_mail_server and settings.SMTP_CONF:
|
if smtp_mail_server and settings.SMTP_CONF:
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
user_name = ""
|
user_name: str = ""
|
||||||
_, user = UserService.get_by_id(current_user.id)
|
_, user = UserService.get_by_id(current_user.id)
|
||||||
if user:
|
if user:
|
||||||
user_name = user.nickname
|
user_name = user.nickname
|
||||||
|
|
@ -730,7 +732,7 @@ def add_users(tenant_id):
|
||||||
daemon=True
|
daemon=True
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
usr = invite_users[0].to_dict()
|
usr: Dict[str, Any] = invite_users[0].to_dict()
|
||||||
usr = {k: v for k, v in usr.items() if k in ["id", "avatar", "email", "nickname"]}
|
usr = {k: v for k, v in usr.items() if k in ["id", "avatar", "email", "nickname"]}
|
||||||
usr["role"] = "invite" # User is invited, not yet added
|
usr["role"] = "invite" # User is invited, not yet added
|
||||||
usr["intended_role"] = role # Role they will get after acceptance
|
usr["intended_role"] = role # Role they will get after acceptance
|
||||||
|
|
@ -743,7 +745,7 @@ def add_users(tenant_id):
|
||||||
"error": f"Failed to add user: {str(e)}"
|
"error": f"Failed to add user: {str(e)}"
|
||||||
})
|
})
|
||||||
|
|
||||||
result = {
|
result: Dict[str, List[Dict[str, Any]]] = {
|
||||||
"added": added_users,
|
"added": added_users,
|
||||||
"failed": failed_users
|
"failed": failed_users
|
||||||
}
|
}
|
||||||
|
|
@ -769,7 +771,7 @@ def add_users(tenant_id):
|
||||||
@manager.route('/<tenant_id>/users/remove', methods=['POST']) # noqa: F821
|
@manager.route('/<tenant_id>/users/remove', methods=['POST']) # noqa: F821
|
||||||
@login_required
|
@login_required
|
||||||
@validate_request("user_ids")
|
@validate_request("user_ids")
|
||||||
def remove_users(tenant_id):
|
def remove_users(tenant_id: str) -> Response:
|
||||||
"""
|
"""
|
||||||
Remove one or more users from a team. Only OWNER or ADMIN can remove users.
|
Remove one or more users from a team. Only OWNER or ADMIN can remove users.
|
||||||
Owners cannot be removed. Supports both single user and bulk operations.
|
Owners cannot be removed. Supports both single user and bulk operations.
|
||||||
|
|
@ -830,8 +832,8 @@ def remove_users(tenant_id):
|
||||||
code=RetCode.PERMISSION_ERROR
|
code=RetCode.PERMISSION_ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
req = request.json
|
req: Dict[str, Any] = request.json if request.json is not None else {}
|
||||||
user_ids = req.get("user_ids", [])
|
user_ids: List[str] = req.get("user_ids", [])
|
||||||
|
|
||||||
if not isinstance(user_ids, list) or len(user_ids) == 0:
|
if not isinstance(user_ids, list) or len(user_ids) == 0:
|
||||||
return get_json_result(
|
return get_json_result(
|
||||||
|
|
@ -840,12 +842,12 @@ def remove_users(tenant_id):
|
||||||
code=RetCode.ARGUMENT_ERROR
|
code=RetCode.ARGUMENT_ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
removed_users = []
|
removed_users: List[Dict[str, str]] = []
|
||||||
failed_users = []
|
failed_users: List[Dict[str, str]] = []
|
||||||
|
|
||||||
# Get all admins/owners for validation (check if removing would leave team without admin/owner)
|
# Get all admins/owners for validation (check if removing would leave team without admin/owner)
|
||||||
all_user_tenants = UserTenantService.query(tenant_id=tenant_id)
|
all_user_tenants: List[Any] = UserTenantService.query(tenant_id=tenant_id)
|
||||||
admin_owner_ids = {
|
admin_owner_ids: Set[str] = {
|
||||||
ut.user_id for ut in all_user_tenants
|
ut.user_id for ut in all_user_tenants
|
||||||
if ut.role in [UserTenantRole.OWNER, UserTenantRole.ADMIN] and ut.status == StatusEnum.VALID.value
|
if ut.role in [UserTenantRole.OWNER, UserTenantRole.ADMIN] and ut.status == StatusEnum.VALID.value
|
||||||
}
|
}
|
||||||
|
|
@ -878,7 +880,7 @@ def remove_users(tenant_id):
|
||||||
|
|
||||||
# Prevent removing yourself if you're the only admin
|
# Prevent removing yourself if you're the only admin
|
||||||
if user_id == current_user.id and user_tenant.role == UserTenantRole.ADMIN:
|
if user_id == current_user.id and user_tenant.role == UserTenantRole.ADMIN:
|
||||||
remaining_admins = admin_owner_ids - {user_id}
|
remaining_admins: Set[str] = admin_owner_ids - {user_id}
|
||||||
if len(remaining_admins) == 0:
|
if len(remaining_admins) == 0:
|
||||||
failed_users.append({
|
failed_users.append({
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
|
|
@ -893,8 +895,8 @@ def remove_users(tenant_id):
|
||||||
])
|
])
|
||||||
|
|
||||||
# Get user info for response
|
# Get user info for response
|
||||||
user = UserService.filter_by_id(user_id)
|
user: Optional[Any] = UserService.filter_by_id(user_id)
|
||||||
user_email = user.email if user else "Unknown"
|
user_email: str = user.email if user else "Unknown"
|
||||||
|
|
||||||
removed_users.append({
|
removed_users.append({
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
|
|
@ -908,7 +910,7 @@ def remove_users(tenant_id):
|
||||||
"error": f"Failed to remove user: {str(e)}"
|
"error": f"Failed to remove user: {str(e)}"
|
||||||
})
|
})
|
||||||
|
|
||||||
result = {
|
result: Dict[str, List[Dict[str, str]]] = {
|
||||||
"removed": removed_users,
|
"removed": removed_users,
|
||||||
"failed": failed_users
|
"failed": failed_users
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue