Coverage for apps/kwai-api/src/kwai_api/v1/auth/endpoints/user_invitations.py: 71%

85 statements  

« prev     ^ index     » next       coverage.py v7.11.0, 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 kwai_bc_identity.delete_user_invitation import ( 

7 DeleteUserInvitation, 

8 DeleteUserInvitationCommand, 

9) 

10from kwai_bc_identity.get_invitations import GetInvitations, GetInvitationsCommand 

11from kwai_bc_identity.get_user_invitation import ( 

12 GetUserInvitation, 

13 GetUserInvitationCommand, 

14) 

15from kwai_bc_identity.invite_user import InviteUser, InviteUserCommand 

16from kwai_bc_identity.recreate_user_invitation import ( 

17 RecreateUserInvitation, 

18 RecreateUserInvitationCommand, 

19) 

20from kwai_bc_identity.update_user_invitation import ( 

21 UpdateUserInvitation, 

22 UpdateUserInvitationCommand, 

23) 

24from kwai_bc_identity.user_invitations.user_invitation_db_repository import ( 

25 UserInvitationDbRepository, 

26) 

27from kwai_bc_identity.user_invitations.user_invitation_repository import ( 

28 UserInvitationNotFoundException, 

29) 

30from kwai_bc_identity.users.user import UserEntity 

31from kwai_bc_identity.users.user_db_repository import UserDbRepository 

32from kwai_core.db.database import Database 

33from kwai_core.db.uow import UnitOfWork 

34from kwai_core.domain.exceptions import UnprocessableException 

35from kwai_core.domain.value_objects.email_address import InvalidEmailException 

36from kwai_core.events.publisher import Publisher 

37from kwai_core.json_api import PaginationModel 

38from loguru import logger 

39 

40from kwai_api.dependencies import create_database, get_current_user, get_publisher 

41from kwai_api.v1.auth.presenters import ( 

42 JsonApiUserInvitationPresenter, 

43 JsonApiUserInvitationsPresenter, 

44) 

45from kwai_api.v1.auth.schemas.user_invitation import ( 

46 UserInvitationDocument, 

47 UserInvitationsDocument, 

48) 

49 

50 

51router = APIRouter() 

52 

53 

54@router.post( 

55 "/invitations/{uuid}", 

56 summary="Recreate a user invitation", 

57 status_code=status.HTTP_201_CREATED, 

58 responses={ 

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

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

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

62 }, 

63) 

64async def recreate_user_invitation( 

65 uuid: str, 

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

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

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

69) -> UserInvitationDocument: 

70 """Recreate a user invitation. 

71 

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

73 will expire. 

74 """ 

75 command = RecreateUserInvitationCommand(uuid=uuid) 

76 presenter = JsonApiUserInvitationPresenter() 

77 try: 

78 async with UnitOfWork(db): 

79 await RecreateUserInvitation( 

80 user, 

81 UserDbRepository(db), 

82 UserInvitationDbRepository(db), 

83 presenter, 

84 publisher, 

85 ).execute(command) 

86 except UnprocessableException as ex: 

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

88 raise HTTPException( 

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

90 ) from ex 

91 

92 return presenter.get_document() 

93 

94 

95@router.patch( 

96 "/invitations/{id}", 

97 summary="Update a user invitation", 

98) 

99async def update_user_invitation( 

100 id: str, 

101 user_invitation_document: UserInvitationDocument, 

102 db=Depends(create_database), 

103 user: UserEntity = Depends(get_current_user), 

104) -> UserInvitationDocument: 

105 """Update a user invitation.""" 

106 command = UpdateUserInvitationCommand( 

107 uuid=id, 

108 revoked=user_invitation_document.data.attributes.revoked, 

109 remark=user_invitation_document.data.attributes.remark, 

110 ) 

111 presenter = JsonApiUserInvitationPresenter() 

112 try: 

113 async with UnitOfWork(db): 

114 await UpdateUserInvitation( 

115 UserInvitationDbRepository(db), presenter 

116 ).execute(command) 

117 except InvalidEmailException as exc: 

118 raise HTTPException( 

119 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, 

120 detail="Invalid email address", 

121 ) from exc 

122 except UnprocessableException as ex: 

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

124 raise HTTPException( 

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

126 ) from ex 

127 

128 return presenter.get_document() 

129 

130 

131@router.post( 

132 "/invitations", 

133 summary="Create a user invitation", 

134 status_code=status.HTTP_201_CREATED, 

135 responses={ 

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

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

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

139 }, 

140) 

141async def create_user_invitation( 

142 user_invitation_document: UserInvitationDocument, 

143 db=Depends(create_database), 

144 user: UserEntity = Depends(get_current_user), 

145 publisher=Depends(get_publisher), 

146) -> UserInvitationDocument: 

147 """Create a user invitation. 

148 

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

150 status code. 

151 """ 

152 command = InviteUserCommand( 

153 first_name=user_invitation_document.data.attributes.first_name, 

154 last_name=user_invitation_document.data.attributes.last_name, 

155 email=user_invitation_document.data.attributes.email, 

156 remark=user_invitation_document.data.attributes.remark, 

157 ) 

158 presenter = JsonApiUserInvitationPresenter() 

159 try: 

160 async with UnitOfWork(db): 

161 await InviteUser( 

162 user, 

163 UserDbRepository(db), 

164 UserInvitationDbRepository(db), 

165 presenter, 

166 publisher, 

167 ).execute(command) 

168 except InvalidEmailException as exc: 

169 raise HTTPException( 

170 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, 

171 detail="Invalid email address", 

172 ) from exc 

173 except UnprocessableException as ex: 

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

175 raise HTTPException( 

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

177 ) from ex 

178 

179 return presenter.get_document() 

180 

181 

182@router.delete( 

183 "/invitations/{uuid}", 

184 summary="Delete a user invitation", 

185 status_code=status.HTTP_200_OK, 

186 responses={ 

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

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

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

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

191 }, 

192) 

193async def delete_user_invitation( 

194 uuid: str, 

195 db=Depends(create_database), 

196 user: UserEntity = Depends(get_current_user), 

197) -> None: 

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

199 command = DeleteUserInvitationCommand(uuid=uuid) 

200 try: 

201 async with UnitOfWork(db): 

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

203 except UserInvitationNotFoundException as ex: 

204 raise HTTPException( 

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

206 ) from ex 

207 except ValueError as ex: 

208 raise HTTPException( 

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

210 ) from ex 

211 

212 

213@router.get( 

214 "/invitations", 

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

216 responses={ 

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

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

219 }, 

220) 

221async def get_user_invitations( 

222 pagination: PaginationModel = Depends(PaginationModel), 

223 db=Depends(create_database), 

224 user: UserEntity = Depends(get_current_user), 

225) -> UserInvitationsDocument: 

226 """Get all user invitations. 

227 

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

229 """ 

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

231 presenter = JsonApiUserInvitationsPresenter() 

232 await GetInvitations(UserInvitationDbRepository(db), presenter).execute(command) 

233 

234 return presenter.get_document() 

235 

236 

237@router.get( 

238 "/invitations/{uuid}", 

239 summary="Get a user invitation", 

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

241) 

242async def get_user_invitation( 

243 uuid: str, 

244 db=Depends(create_database), 

245 user: UserEntity = Depends(get_current_user), 

246) -> UserInvitationDocument: 

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

248 command = GetUserInvitationCommand(uuid=uuid) 

249 presenter = JsonApiUserInvitationPresenter() 

250 try: 

251 await GetUserInvitation(UserInvitationDbRepository(db), presenter).execute( 

252 command 

253 ) 

254 except UserInvitationNotFoundException as ex: 

255 raise HTTPException( 

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

257 ) from ex 

258 

259 return presenter.get_document()