Coverage for src/kwai/api/v1/auth/endpoints/user_invitations.py: 72%

69 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2024-01-01 00:00 +0000

1"""Module that implements invitations endpoints.""" 

2 

3from typing import Annotated 

4 

5from fastapi import APIRouter, Depends, HTTPException, status 

6from loguru import logger 

7 

8from kwai.api.dependencies import create_database, get_current_user, get_publisher 

9from kwai.api.v1.auth.schemas.user_invitation import UserInvitationDocument 

10from kwai.core.db.database import Database 

11from kwai.core.db.uow import UnitOfWork 

12from kwai.core.domain.exceptions import UnprocessableException 

13from kwai.core.domain.value_objects.email_address import InvalidEmailException 

14from kwai.core.events.publisher import Publisher 

15from kwai.core.json_api import Meta, PaginationModel 

16from kwai.modules.identity.delete_user_invitation import ( 

17 DeleteUserInvitation, 

18 DeleteUserInvitationCommand, 

19) 

20from kwai.modules.identity.get_invitations import GetInvitations, GetInvitationsCommand 

21from kwai.modules.identity.get_user_invitation import ( 

22 GetUserInvitation, 

23 GetUserInvitationCommand, 

24) 

25from kwai.modules.identity.invite_user import InviteUser, InviteUserCommand 

26from kwai.modules.identity.recreate_user_invitation import ( 

27 RecreateUserInvitation, 

28 RecreateUserInvitationCommand, 

29) 

30from kwai.modules.identity.user_invitations.user_invitation_db_repository import ( 

31 UserInvitationDbRepository, 

32) 

33from kwai.modules.identity.user_invitations.user_invitation_repository import ( 

34 UserInvitationNotFoundException, 

35) 

36from kwai.modules.identity.users.user import UserEntity 

37from kwai.modules.identity.users.user_db_repository import UserDbRepository 

38 

39 

40router = APIRouter() 

41 

42 

43@router.post( 

44 "/invitations/{uuid}", 

45 summary="Recreate a user invitation", 

46 status_code=status.HTTP_201_CREATED, 

47 responses={ 

48 201: {"description": "User invitation is created"}, 

49 401: {"description": "Not authorized."}, 

50 422: {"description": "User invitation could not be created"}, 

51 }, 

52) 

53async def recreate_user_invitation( 

54 uuid: str, 

55 db: Annotated[Database, Depends(create_database)], 

56 user: Annotated[UserEntity, Depends(get_current_user)], 

57 publisher: Annotated[Publisher, Depends(get_publisher)], 

58) -> UserInvitationDocument: 

59 """Recreate a user invitation. 

60 

61 Use this API for resending a user invitation. The existing invitation 

62 will expire. 

63 """ 

64 command = RecreateUserInvitationCommand(uuid=uuid) 

65 try: 

66 async with UnitOfWork(db): 

67 invitation = await RecreateUserInvitation( 

68 user, UserDbRepository(db), UserInvitationDbRepository(db), publisher 

69 ).execute(command) 

70 except UnprocessableException as ex: 

71 logger.warning(f"User invitation could not be processed: {ex}") 

72 raise HTTPException( 

73 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(ex) 

74 ) from ex 

75 

76 return UserInvitationDocument.create(invitation) 

77 

78 

79@router.post( 

80 "/invitations", 

81 summary="Create a user invitation", 

82 status_code=status.HTTP_201_CREATED, 

83 responses={ 

84 201: {"description": "User invitation is created"}, 

85 401: {"description": "Not authorized."}, 

86 422: {"description": "User invitation could not be created"}, 

87 }, 

88) 

89async def create_user_invitation( 

90 user_invitation_document: UserInvitationDocument, 

91 db=Depends(create_database), 

92 user: UserEntity = Depends(get_current_user), 

93 publisher=Depends(get_publisher), 

94) -> UserInvitationDocument: 

95 """Create a user invitation. 

96 

97 A wrong email address or a still pending user invitation will result in a 422 

98 status code. 

99 """ 

100 command = InviteUserCommand( 

101 first_name=user_invitation_document.resource.attributes.first_name, 

102 last_name=user_invitation_document.resource.attributes.last_name, 

103 email=user_invitation_document.resource.attributes.email, 

104 remark=user_invitation_document.resource.attributes.remark, 

105 ) 

106 

107 try: 

108 async with UnitOfWork(db): 

109 invitation = await InviteUser( 

110 user, UserDbRepository(db), UserInvitationDbRepository(db), publisher 

111 ).execute(command) 

112 except InvalidEmailException as exc: 

113 raise HTTPException( 

114 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, 

115 detail="Invalid email address", 

116 ) from exc 

117 except UnprocessableException as ex: 

118 logger.warning(f"User invitation could not be processed: {ex}") 

119 raise HTTPException( 

120 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(ex) 

121 ) from ex 

122 

123 return UserInvitationDocument.create(invitation) 

124 

125 

126@router.delete( 

127 "/invitations/{uuid}", 

128 summary="Delete a user invitation", 

129 status_code=status.HTTP_200_OK, 

130 responses={ 

131 200: {"description": "User invitation is deleted."}, 

132 401: {"description": "Not authorized."}, 

133 404: {"description": "User invitation does not exist."}, 

134 422: {"description": "Invalid unique id passed for the user invitation."}, 

135 }, 

136) 

137async def delete_user_invitation( 

138 uuid: str, 

139 db=Depends(create_database), 

140 user: UserEntity = Depends(get_current_user), 

141) -> None: 

142 """Delete the user invitation with the given unique id.""" 

143 command = DeleteUserInvitationCommand(uuid=uuid) 

144 try: 

145 async with UnitOfWork(db): 

146 await DeleteUserInvitation(UserInvitationDbRepository(db)).execute(command) 

147 except UserInvitationNotFoundException as ex: 

148 raise HTTPException( 

149 status_code=status.HTTP_404_NOT_FOUND, detail=str(ex) 

150 ) from ex 

151 except ValueError as ex: 

152 raise HTTPException( 

153 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(ex) 

154 ) from ex 

155 

156 

157@router.get( 

158 "/invitations", 

159 summary="Get a list of user invitations", 

160 responses={ 

161 200: {"description": "Ok."}, 

162 401: {"description": "Not authorized."}, 

163 }, 

164) 

165async def get_user_invitations( 

166 pagination: PaginationModel = Depends(PaginationModel), 

167 db=Depends(create_database), 

168 user: UserEntity = Depends(get_current_user), 

169) -> UserInvitationDocument: 

170 """Get all user invitations. 

171 

172 Use the page[offset] and page[limit] query parameters to get a paginated result. 

173 """ 

174 command = GetInvitationsCommand(offset=pagination.offset, limit=pagination.limit) 

175 count, invitation_iterator = await GetInvitations( 

176 UserInvitationDbRepository(db) 

177 ).execute(command) 

178 

179 result = UserInvitationDocument( 

180 meta=Meta(count=count, limit=pagination.limit, offset=pagination.offset), 

181 data=[], 

182 ) 

183 

184 async for invitation in invitation_iterator: 

185 result.merge(UserInvitationDocument.create(invitation)) 

186 

187 return result 

188 

189 

190@router.get( 

191 "/invitations/{uuid}", 

192 summary="Get a user invitation", 

193 responses={200: {"description": "Ok."}, 401: {"description": "Not authorized."}}, 

194) 

195async def get_user_invitation( 

196 uuid: str, 

197 db=Depends(create_database), 

198 user: UserEntity = Depends(get_current_user), 

199) -> UserInvitationDocument: 

200 """Get the user invitation with the given unique id.""" 

201 command = GetUserInvitationCommand(uuid=uuid) 

202 try: 

203 invitation = await GetUserInvitation(UserInvitationDbRepository(db)).execute( 

204 command 

205 ) 

206 except UserInvitationNotFoundException as ex: 

207 raise HTTPException( 

208 status_code=status.HTTP_404_NOT_FOUND, detail=str(ex) 

209 ) from ex 

210 

211 return UserInvitationDocument.create(invitation)