195 lines
6.4 KiB
Python
195 lines
6.4 KiB
Python
"""
|
||
邀请成员到 Workspace Use Case
|
||
"""
|
||
|
||
import secrets
|
||
from datetime import datetime, timedelta, timezone
|
||
from typing import Optional
|
||
from uuid import uuid4
|
||
|
||
from packages.adapters.smtp import get_email_service
|
||
from packages.domain.entities import (
|
||
InvitationStatus,
|
||
WorkspaceInvitation,
|
||
WorkspaceMemberRole,
|
||
)
|
||
|
||
|
||
class InviteMemberRequest:
|
||
"""邀请成员请求"""
|
||
|
||
def __init__(
|
||
self,
|
||
workspace_id: str,
|
||
inviter_user_id: str,
|
||
invitee_email: str,
|
||
role: str,
|
||
):
|
||
self.workspace_id = workspace_id
|
||
self.inviter_user_id = inviter_user_id
|
||
self.invitee_email = invitee_email.strip().lower()
|
||
self.role = role
|
||
|
||
|
||
class InviteMemberResponse:
|
||
"""邀请成员响应"""
|
||
|
||
def __init__(
|
||
self,
|
||
invitation_id: str,
|
||
invitee_email: str,
|
||
role: str,
|
||
expires_at: datetime,
|
||
):
|
||
self.invitation_id = invitation_id
|
||
self.invitee_email = invitee_email
|
||
self.role = role
|
||
self.expires_at = expires_at
|
||
|
||
|
||
class InviteMemberUseCase:
|
||
"""邀请成员用例"""
|
||
|
||
VALID_ROLES = [
|
||
WorkspaceMemberRole.ADMIN,
|
||
WorkspaceMemberRole.MEMBER,
|
||
WorkspaceMemberRole.VIEWER,
|
||
]
|
||
|
||
def __init__(
|
||
self,
|
||
workspace_repository,
|
||
workspace_member_repository,
|
||
workspace_invitation_repository,
|
||
user_repository,
|
||
base_url: str = "http://localhost:3000",
|
||
invitation_expire_days: int = 7,
|
||
email_service=None,
|
||
):
|
||
self.workspace_repository = workspace_repository
|
||
self.workspace_member_repository = workspace_member_repository
|
||
self.workspace_invitation_repository = workspace_invitation_repository
|
||
self.user_repository = user_repository
|
||
self.base_url = base_url
|
||
self.invitation_expire_days = invitation_expire_days
|
||
self.email_service = email_service or get_email_service()
|
||
|
||
def execute(self, request: InviteMemberRequest) -> tuple[Optional[InviteMemberResponse], Optional[str]]:
|
||
"""
|
||
执行邀请成员
|
||
|
||
Args:
|
||
request: 邀请请求
|
||
|
||
Returns:
|
||
(响应, 错误信息)
|
||
"""
|
||
try:
|
||
# 1. 验证输入
|
||
if not request.workspace_id:
|
||
return None, "Workspace ID is required"
|
||
|
||
if not request.inviter_user_id:
|
||
return None, "Inviter user ID is required"
|
||
|
||
if not request.invitee_email:
|
||
return None, "Invitee email is required"
|
||
|
||
if not request.role:
|
||
return None, "Role is required"
|
||
|
||
# 2. 验证角色(不能邀请 owner)
|
||
if request.role not in self.VALID_ROLES:
|
||
return None, f"Invalid role: {request.role}. Cannot invite as owner."
|
||
|
||
# 3. 验证 Workspace 存在
|
||
workspace = self.workspace_repository.find_by_id(request.workspace_id)
|
||
if not workspace:
|
||
return None, "Workspace not found"
|
||
|
||
# 4. 验证邀请人是成员且有权限(owner 或 admin)
|
||
inviter_member = self.workspace_member_repository.find_by_workspace_and_user(
|
||
request.workspace_id,
|
||
request.inviter_user_id,
|
||
)
|
||
if not inviter_member:
|
||
return None, "You are not a member of this workspace"
|
||
|
||
if inviter_member.role not in [
|
||
WorkspaceMemberRole.OWNER,
|
||
WorkspaceMemberRole.ADMIN,
|
||
]:
|
||
return None, "Only owners and admins can invite members"
|
||
|
||
# 5. 检查被邀请人是否已经是成员
|
||
invitee_user = self.user_repository.find_by_email(request.invitee_email)
|
||
if invitee_user:
|
||
existing_member = self.workspace_member_repository.find_by_workspace_and_user(
|
||
request.workspace_id,
|
||
invitee_user.id,
|
||
)
|
||
if existing_member:
|
||
return None, "User is already a member of this workspace"
|
||
|
||
# 6. 检查是否已有待处理的邀请
|
||
existing_invitation = self.workspace_invitation_repository.find_pending_by_workspace_and_email(
|
||
request.workspace_id,
|
||
request.invitee_email,
|
||
)
|
||
if existing_invitation:
|
||
return None, "An invitation has already been sent to this email"
|
||
|
||
# 7. 生成邀请令牌
|
||
invitation_token = secrets.token_urlsafe(32)
|
||
expires_at = datetime.now(timezone.utc) + timedelta(days=self.invitation_expire_days)
|
||
|
||
# 8. 创建邀请记录
|
||
invitation = WorkspaceInvitation(
|
||
id=uuid4().hex,
|
||
workspace_id=request.workspace_id,
|
||
inviter_user_id=request.inviter_user_id,
|
||
invitee_email=request.invitee_email,
|
||
role=request.role,
|
||
invitation_token=invitation_token,
|
||
status=InvitationStatus.PENDING,
|
||
expires_at=expires_at,
|
||
created_at=datetime.now(timezone.utc),
|
||
)
|
||
|
||
# 9. 保存邀请
|
||
self.workspace_invitation_repository.save(invitation)
|
||
|
||
# 10. 发送邀请邮件
|
||
invitation_url = f"{self.base_url}/invitations/{invitation_token}/accept"
|
||
|
||
try:
|
||
inviter = self.user_repository.find_by_id(request.inviter_user_id)
|
||
inviter_name = inviter.display_name if inviter else "Someone"
|
||
|
||
success, error = self.email_service.send_workspace_invitation_email(
|
||
to_email=request.invitee_email,
|
||
inviter_name=inviter_name,
|
||
workspace_name=workspace.name,
|
||
role=request.role,
|
||
invitation_url=invitation_url,
|
||
)
|
||
|
||
if not success:
|
||
print(f"Failed to send invitation email: {error}")
|
||
except Exception as e:
|
||
print(f"Email service error: {e}")
|
||
|
||
# 11. 返回响应
|
||
return (
|
||
InviteMemberResponse(
|
||
invitation_id=invitation.id,
|
||
invitee_email=invitation.invitee_email,
|
||
role=invitation.role,
|
||
expires_at=invitation.expires_at,
|
||
),
|
||
None,
|
||
)
|
||
|
||
except Exception as e:
|
||
return None, f"Failed to invite member: {str(e)}"
|