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
« prev ^ index » next coverage.py v7.11.0, created at 2024-01-01 00:00 +0000
1"""Module for endpoints for trainings."""
3from datetime import datetime
4from typing import Annotated
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
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
42router = APIRouter()
45class TrainingsFilterModel(BaseModel):
46 """Define the JSON:API filter for trainings."""
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]"))
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.
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
106 return presenter.get_document()
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
128 return presenter.get_document()
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 )
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
194 return presenter.get_document()
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 )
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
265 return presenter.get_document()
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)