from typing import List from fastapi import APIRouter, Depends, HTTPException, status from app.api.deps import ( get_current_user, get_training_repository, get_user_repository, require_superuser, ) from app.core.database.repository import TrainingRepository from app.core.database.user_repository import UserRepository from app.core.dto.training import TrainingDTO from app.core.dto.user import UserDTO, UserRole from app.core.parsers.obsidian import parse_training_data from app.schemas.trainings import TrainingUpdate router = APIRouter() async def _check_participant( repository: UserRepository, user_id: int, role: UserRole ) -> None: user = await repository.get(user_id) if user is None or user.role != role: raise HTTPException( status_code=422, detail=f"User {user_id} is not an existing {role.value}" ) async def _get_editable_training( training_id: int, user: UserDTO, repository: TrainingRepository ) -> TrainingDTO: """A training the user may change: their own (as the trainee), or any for a superuser. The trainer can only see the training. A training the user can't see at all is a 404. """ training = await repository.get(training_id, viewer=user) if training is None: raise HTTPException(status_code=404, detail="Training not found") if not (user.is_superuser or training.trainee_id == user.id): raise HTTPException(status_code=403, detail="Only the trainee can change a training") return training @router.get("/obsidian/", dependencies=[Depends(require_superuser)]) async def obsidian_trainings_list(): return {"data": parse_training_data()} @router.get("/", response_model=List[TrainingDTO]) async def get_trainings_list( user: UserDTO = Depends(get_current_user), repository: TrainingRepository = Depends(get_training_repository), ): """Trainings the user takes part in (all of them for a superuser).""" return await repository.list(viewer=user) @router.get("/{training_id}/", response_model=TrainingDTO) async def get_training( training_id: int, user: UserDTO = Depends(get_current_user), repository: TrainingRepository = Depends(get_training_repository), ): training = await repository.get(training_id, viewer=user) if training is None: raise HTTPException(status_code=404, detail="Training not found") return training @router.post("/", response_model=TrainingDTO, status_code=status.HTTP_201_CREATED) async def create_training( payload: TrainingUpdate, user: UserDTO = Depends(get_current_user), repository: TrainingRepository = Depends(get_training_repository), user_repository: UserRepository = Depends(get_user_repository), ): """Create a training. Only a superuser may create it for another trainee.""" if payload.trainee_id != user.id: if not user.is_superuser: raise HTTPException( status_code=403, detail="Only a superuser can create a training for another user" ) await _check_participant(user_repository, payload.trainee_id, UserRole.trainee) if payload.trainer_id is not None: await _check_participant(user_repository, payload.trainer_id, UserRole.trainer) training_id = await repository.save(TrainingDTO.model_validate(payload.model_dump())) return await repository.get(training_id) @router.put("/{training_id}/", response_model=TrainingDTO) async def update_training( training_id: int, payload: TrainingUpdate, user: UserDTO = Depends(get_current_user), repository: TrainingRepository = Depends(get_training_repository), user_repository: UserRepository = Depends(get_user_repository), ): """Replace the training: its date, trainee, trainer, exercises and approaches.""" current = await _get_editable_training(training_id, user, repository) if payload.trainee_id != current.trainee_id: if not user.is_superuser: raise HTTPException( status_code=403, detail="Only a superuser can change the trainee" ) await _check_participant(user_repository, payload.trainee_id, UserRole.trainee) if payload.trainer_id is not None: await _check_participant(user_repository, payload.trainer_id, UserRole.trainer) training = TrainingDTO.model_validate(payload.model_dump()) if not await repository.replace(training_id, training): raise HTTPException(status_code=404, detail="Training not found") return await repository.get(training_id) @router.delete("/{training_id}/") async def delete_training( training_id: int, user: UserDTO = Depends(get_current_user), repository: TrainingRepository = Depends(get_training_repository), ): await _get_editable_training(training_id, user, repository) await repository.delete([training_id]) return {"status": "ok"}