Coverage for apps/kwai-api/src/kwai_api/v1/trainings/endpoints.py: 86%

86 statements  

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

1"""Module for endpoints for trainings.""" 

2 

3from datetime import datetime 

4from typing import Annotated 

5 

6from fastapi import APIRouter, Depends, HTTPException, Query, status 

7from kwai_bc_identity.users.user import UserEntity 

8from kwai_bc_training.coaches.coach_db_repository import CoachDbRepository 

9from kwai_bc_training.coaches.coach_repository import CoachNotFoundException 

10from kwai_bc_training.create_training import CreateTraining, CreateTrainingCommand 

11from kwai_bc_training.delete_training import DeleteTraining, DeleteTrainingCommand 

12from kwai_bc_training.get_training import GetTraining, GetTrainingCommand 

13from kwai_bc_training.get_trainings import GetTrainings, GetTrainingsCommand 

14from kwai_bc_training.teams.team_db_repository import TeamDbRepository 

15from kwai_bc_training.training_command import Coach 

16from kwai_bc_training.trainings.training_db_repository import TrainingDbRepository 

17from kwai_bc_training.trainings.training_repository import TrainingNotFoundException 

18from kwai_bc_training.trainings.training_schedule_db_repository import ( 

19 TrainingScheduleDbRepository, 

20) 

21from kwai_bc_training.trainings.training_schedule_repository import ( 

22 TrainingScheduleNotFoundException, 

23) 

24from kwai_bc_training.update_training import UpdateTraining, UpdateTrainingCommand 

25from kwai_core.db.database import Database 

26from kwai_core.db.uow import UnitOfWork 

27from kwai_core.domain.use_case import TextCommand 

28from kwai_core.domain.value_objects.owner import Owner 

29from kwai_core.json_api import PaginationModel 

30from pydantic import BaseModel, Field 

31 

32from kwai_api.dependencies import create_database, get_current_user 

33from kwai_api.v1.trainings.coaches.schemas import TrainingCoachResource 

34from kwai_api.v1.trainings.presenters import ( 

35 JsonApiTrainingPresenter, 

36 JsonApiTrainingsDocumentPresenter, 

37) 

38from kwai_api.v1.trainings.schemas import TrainingDocument, TrainingsDocument 

39from kwai_api.v1.trainings.security_dependencies import check_permission 

40 

41 

42router = APIRouter() 

43 

44 

45class TrainingsFilterModel(BaseModel): 

46 """Define the JSON:API filter for trainings.""" 

47 

48 year: int | None = Field(Query(default=None, alias="filter[year]")) 

49 month: int | None = Field(Query(default=None, alias="filter[month]")) 

50 start: datetime | None = Field(Query(default=None, alias="filter[start]")) 

51 end: datetime | None = Field(Query(default=None, alias="filter[end]")) 

52 active: bool = Field(Query(default=True, alias="filter[active]")) 

53 coach: int | None = Field(Query(default=None, alias="filter[coach]")) 

54 schedule: int | None = Field(Query(default=None, alias="filter[schedule]")) 

55 

56 

57@router.get( 

58 "/trainings", 

59 responses={ 

60 status.HTTP_404_NOT_FOUND: { 

61 "description": "Coach or Training schedule was not found." 

62 }, 

63 status.HTTP_403_FORBIDDEN: { 

64 "description": "You must be an administrator or coach." 

65 }, 

66 }, 

67 dependencies=[Depends(check_permission)], 

68) 

69async def get_trainings( 

70 pagination: Annotated[PaginationModel, Depends(PaginationModel)], 

71 trainings_filter: Annotated[TrainingsFilterModel, Depends(TrainingsFilterModel)], 

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

73) -> TrainingsDocument: 

74 """Get all trainings. 

75 

76 Only an administrator or coach is allowed to access this endpoint. 

77 """ 

78 command = GetTrainingsCommand( 

79 offset=pagination.offset or 0, 

80 limit=pagination.limit, 

81 year=trainings_filter.year, 

82 month=trainings_filter.month, 

83 start=trainings_filter.start, 

84 end=trainings_filter.end, 

85 active=trainings_filter.active, 

86 coach=trainings_filter.coach, 

87 schedule=trainings_filter.schedule, 

88 ) 

89 presenter = JsonApiTrainingsDocumentPresenter() 

90 try: 

91 await GetTrainings( 

92 TrainingDbRepository(db), 

93 CoachDbRepository(db), 

94 TrainingScheduleDbRepository(db), 

95 presenter, 

96 ).execute(command) 

97 except TrainingScheduleNotFoundException as ex: 

98 raise HTTPException( 

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

100 ) from ex 

101 except CoachNotFoundException as ex: 

102 raise HTTPException( 

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

104 ) from ex 

105 

106 return presenter.get_document() 

107 

108 

109@router.get( 

110 "/trainings/{training_id}", 

111 responses={status.HTTP_404_NOT_FOUND: {"description": "Training was not found."}}, 

112 dependencies=[Depends(check_permission)], 

113) 

114async def get_training( 

115 training_id: int, 

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

117) -> TrainingDocument: 

118 """Get the training with the given id.""" 

119 command = GetTrainingCommand(id=training_id) 

120 presenter = JsonApiTrainingPresenter() 

121 try: 

122 await GetTraining(TrainingDbRepository(db), presenter).execute(command) 

123 except TrainingNotFoundException as ex: 

124 raise HTTPException( 

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

126 ) from ex 

127 

128 return presenter.get_document() 

129 

130 

131@router.post( 

132 "/trainings", 

133 status_code=status.HTTP_201_CREATED, 

134 dependencies=[Depends(check_permission)], 

135) 

136async def create_training( 

137 resource: TrainingDocument, 

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

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

140) -> TrainingDocument: 

141 """Create a new training.""" 

142 coaches = [ 

143 coach for coach in resource.included if isinstance(coach, TrainingCoachResource) 

144 ] 

145 command = CreateTrainingCommand( 

146 start_date=resource.data.attributes.event.start_date, 

147 end_date=resource.data.attributes.event.end_date, 

148 active=resource.data.attributes.event.active, 

149 cancelled=resource.data.attributes.event.cancelled, 

150 texts=[ 

151 TextCommand( 

152 locale=text.locale, 

153 format=text.format, 

154 title=text.title, 

155 summary=text.original_summary or "", 

156 content=text.original_content or "", 

157 ) 

158 for text in resource.data.attributes.texts 

159 ], 

160 coaches=[ 

161 Coach( 

162 uuid=coach.id, 

163 head=coach.attributes.head, 

164 present=coach.attributes.present, 

165 payed=coach.attributes.payed, 

166 ) 

167 for coach in coaches 

168 ], 

169 teams=[int(team.id) for team in resource.data.relationships.teams.data], 

170 schedule=None 

171 if resource.data.relationships.schedule.data is None 

172 or resource.data.relationships.schedule.data.id is None 

173 else int(resource.data.relationships.schedule.data.id), 

174 location=resource.data.attributes.event.location, 

175 remark=resource.data.attributes.remark, 

176 ) 

177 

178 presenter = JsonApiTrainingPresenter() 

179 async with UnitOfWork(db): 

180 try: 

181 await CreateTraining( 

182 TrainingDbRepository(db), 

183 TrainingScheduleDbRepository(db), 

184 CoachDbRepository(db), 

185 TeamDbRepository(db), 

186 Owner(id=user.id, uuid=user.uuid, name=user.name), 

187 presenter, 

188 ).execute(command) 

189 except ValueError as ve: 

190 raise HTTPException( 

191 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(ve) 

192 ) from ve 

193 

194 return presenter.get_document() 

195 

196 

197@router.patch( 

198 "/trainings/{training_id}", 

199 responses={status.HTTP_404_NOT_FOUND: {"description": "Training was not found."}}, 

200 dependencies=[Depends(check_permission)], 

201) 

202async def update_training( 

203 training_id: int, 

204 resource: TrainingDocument, 

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

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

207) -> TrainingDocument: 

208 """Update a training.""" 

209 coaches = [ 

210 coach for coach in resource.included if isinstance(coach, TrainingCoachResource) 

211 ] 

212 command = UpdateTrainingCommand( 

213 id=training_id, 

214 start_date=resource.data.attributes.event.start_date, 

215 end_date=resource.data.attributes.event.end_date, 

216 active=resource.data.attributes.event.active, 

217 cancelled=resource.data.attributes.event.cancelled, 

218 texts=[ 

219 TextCommand( 

220 locale=text.locale, 

221 format=text.format, 

222 title=text.title, 

223 summary=text.original_summary or "", 

224 content=text.original_content or "", 

225 ) 

226 for text in resource.data.attributes.texts 

227 ], 

228 coaches=[ 

229 Coach( 

230 uuid=coach.id, 

231 head=coach.attributes.head, 

232 present=coach.attributes.present, 

233 payed=coach.attributes.payed, 

234 ) 

235 for coach in coaches 

236 ], 

237 teams=[int(team.id) for team in resource.data.relationships.teams.data], 

238 schedule=None 

239 if resource.data.relationships.schedule.data is None 

240 else int(resource.data.relationships.schedule.data.id), 

241 location=resource.data.attributes.event.location, 

242 remark=resource.data.attributes.remark, 

243 ) 

244 

245 presenter = JsonApiTrainingPresenter() 

246 async with UnitOfWork(db): 

247 try: 

248 await UpdateTraining( 

249 TrainingDbRepository(db), 

250 TrainingScheduleDbRepository(db), 

251 CoachDbRepository(db), 

252 TeamDbRepository(db), 

253 Owner(id=user.id, uuid=user.uuid, name=user.name), 

254 presenter, 

255 ).execute(command) 

256 except TrainingNotFoundException as ex: 

257 raise HTTPException( 

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

259 ) from ex 

260 except ValueError as ve: 

261 raise HTTPException( 

262 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(ve) 

263 ) from ve 

264 

265 return presenter.get_document() 

266 

267 

268@router.delete( 

269 "/trainings/{training_id}", 

270 responses={status.HTTP_404_NOT_FOUND: {"description": "Training was not found."}}, 

271 dependencies=[Depends(check_permission)], 

272) 

273async def delete_training( 

274 training_id: int, 

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

276) -> None: 

277 """Delete a training schedule.""" 

278 command = DeleteTrainingCommand(id=training_id) 

279 async with UnitOfWork(db): 

280 await DeleteTraining(TrainingDbRepository(db)).execute(command)