Coverage for src/kwai/modules/identity/invite_user.py: 100%
38 statements
« prev ^ index » next coverage.py v7.7.1, created at 2024-01-01 00:00 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2024-01-01 00:00 +0000
1"""Module for the invite user use case."""
3from dataclasses import dataclass
5from kwai.core.domain.exceptions import UnprocessableException
6from kwai.core.domain.value_objects.email_address import EmailAddress
7from kwai.core.domain.value_objects.name import Name
8from kwai.core.domain.value_objects.timestamp import Timestamp
9from kwai.core.domain.value_objects.unique_id import UniqueId
10from kwai.core.events.publisher import Publisher
11from kwai.modules.identity.user_invitations.user_invitation import UserInvitationEntity
12from kwai.modules.identity.user_invitations.user_invitation_events import (
13 UserInvitationCreatedEvent,
14)
15from kwai.modules.identity.user_invitations.user_invitation_repository import (
16 UserInvitationRepository,
17)
18from kwai.modules.identity.users.user import UserEntity
19from kwai.modules.identity.users.user_repository import (
20 UserNotFoundException,
21 UserRepository,
22)
25@dataclass(kw_only=True, frozen=True, slots=True)
26class InviteUserCommand:
27 """Input for the use case.
29 [InviteUser][kwai.modules.identity.invite_user.InviteUser]
31 Attributes:
32 first_name: Firstname of the invited
33 last_name: Lastname of the invited
34 email: Email address of the invited
35 expiration_in_days: After how many days will the invitation expire?
36 remark: A remark about this invitation
37 """
39 first_name: str
40 last_name: str
41 email: str
42 expiration_in_days: int = 7
43 remark: str = ""
46class InviteUser:
47 """Invite user use case.
49 This use case will try to create an invitation for a user.
50 """
52 def __init__(
53 self,
54 user: UserEntity,
55 user_repo: UserRepository,
56 user_invitation_repo: UserInvitationRepository,
57 publisher: Publisher,
58 ):
59 """Initialize the use case.
61 Args:
62 user: The inviter.
63 user_repo: The repository to check if the email address is still free.
64 user_invitation_repo: The repository for creating an invitation.
65 publisher: A publisher for publishing the user invitation created event.
66 """
67 self._user = user
68 self._user_repo = user_repo
69 self._user_invitation_repo = user_invitation_repo
70 self._publisher = publisher
72 async def execute(self, command: InviteUserCommand) -> UserInvitationEntity:
73 """Execute the use case.
75 Args:
76 command: The input for this use case.
78 Returns:
79 A user invitation
81 Raises:
82 UnprocessableException: raised when the email address is already in use or
83 when there are still pending invitations for the email address.
84 """
85 email_address = EmailAddress(command.email)
86 try:
87 await self._user_repo.get_user_by_email(email_address)
88 raise UnprocessableException(f"{command.email} is already used.")
89 except UserNotFoundException:
90 pass
92 query = (
93 self._user_invitation_repo.create_query()
94 .filter_by_email(email_address)
95 .filter_active()
96 .filter_not_expired(Timestamp.create_now())
97 )
98 if await query.count() > 0:
99 raise UnprocessableException(
100 f"There are still pending invitations for {command.email}"
101 )
103 invitation = await self._user_invitation_repo.create(
104 UserInvitationEntity(
105 email=email_address,
106 name=Name(first_name=command.first_name, last_name=command.last_name),
107 uuid=UniqueId.generate(),
108 expired_at=Timestamp.create_with_delta(days=command.expiration_in_days),
109 user=self._user,
110 )
111 )
113 await self._publisher.publish(
114 UserInvitationCreatedEvent(uuid=str(invitation.uuid))
115 )
117 return invitation