from services.database import generate_unique_code from services.io import add_fast_api_root from generateur.generateur_main import generate_from_path, parseGeneratorOut, parseOut from database.exercices.models import Exercice from database.room.crud import CorrigedChallenge, change_correction, corrige_challenge, create_parcours_db, delete_parcours_db, create_room_db, get_member_dep, check_room, serialize_room, update_parcours_db, get_parcours, get_room, check_admin, get_exercices, get_challenge, get_correction, create_tmp_correction, create_challenge, change_challenge from pydantic import BaseModel from typing import Any, Callable, Dict, List, Optional from fastapi import APIRouter, Depends, WebSocket, status, Query, Body from config import ALGORITHM, SECRET_KEY from database.auth.crud import get_user_from_clientId_db from database.auth.models import User from database.db import get_session from sqlmodel import Session, col, select from database.room.models import Challenge, ChallengeRead, Challenges, CorrigedGeneratorOut, Member, Note, Parcours, ParcoursCreate, ParcoursRead, ParcoursReadShort, ParsedGeneratorOut, Room, RoomConnectionInfos, RoomCreate, RoomAndMember, RoomInfo, TmpCorrection from routes.room.consumer import RoomConsumer from routes.room.manager import RoomManager from services.auth import get_current_user_optional from fastapi.exceptions import HTTPException from database.auth.crud import get_user_from_token from services.websocket import Consumer from services.misc import noteOn20, stripKeyDict router = APIRouter(tags=["room"]) manager = RoomManager() def get_manager(): return manager @router.post('/room', response_model=RoomConnectionInfos) def create_room(room: RoomCreate, username: Optional[str] = Query(default=None, max_length=20), user: User | None = Depends(get_current_user_optional), db: Session = Depends(get_session)): room_obj = create_room_db(room=room, user=user, username=username, db=db) return {'room': room_obj['room'].id_code, "member": getattr(room_obj['member'].anonymous, "reconnect_code", None)} @router.get('/room/{room_id}', response_model=RoomInfo) def get_room_route(room: Room = Depends(get_room), member: Member = Depends(get_member_dep), db: Session = Depends(get_session)): return serialize_room(room, member, db) @router.post('/room/{room_id}/parcours', response_model=ParcoursRead) async def create_parcours(*, parcours: ParcoursCreate, room_id: str, member: Member = Depends(check_admin), m: RoomManager = Depends(get_manager), db: Session = Depends(get_session)): parcours_obj = create_parcours_db(parcours, member.room_id, db) if type(parcours_obj) == str: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=parcours_obj) await m.broadcast({"type": "add_parcours", "data": {"parcours": ParcoursReadShort(**parcours_obj.dict(exclude_unset=True)).dict()}}, room_id) return parcours_obj @router.get('/room/{room_id}/parcours/{parcours_id}', response_model=ParcoursRead) async def get_parcours_route(*, parcours: Parcours = Depends(get_parcours), member: Member = Depends(get_member_dep), db: Session = Depends(get_session)): if member.is_admin == False: return {**parcours.dict(), "challenges": [Challenges(**{**chall.dict(), "challenger": member.id_code, "canCorrige": chall.data != []}) for chall in parcours.challenges if chall.challenger.id_code == member.id_code]} if member.is_admin == True: return {**parcours.dict(), "challenges": [Challenges(**{**chall.dict(), "challenger": member.id_code, "canCorrige": chall.data != []}) for chall in parcours.challenges]} @router.put('/room/{room_id}/parcours/{parcours_id}', response_model=ParcoursRead, dependencies=[Depends(check_admin)]) async def update_parcours(*, room_id: str, parcours: ParcoursCreate, parcours_old: Parcours = Depends(get_parcours), m: RoomManager = Depends(get_manager), db: Session = Depends(get_session)): parcours_obj = update_parcours_db(parcours, parcours_old, db) if type(parcours_obj) == str: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=parcours_obj) short = ParcoursReadShort( **parcours_obj.dict(exclude_unset=True), id_code=parcours_obj.id_code) await m.broadcast({"type": "update_parcours", "data": {"parcours": short.dict()}}, room_id) return parcours_obj @router.delete('/room/{room_id}/parcours/{parcours_id}', dependencies=[Depends(check_admin)]) async def delete_parcours(parcours: Parcours = Depends(get_parcours), m: RoomManager = Depends(get_manager), db: Session = Depends(get_session)): delete_parcours_db(parcours, db) await m.broadcast({"type": "del_parcours", "data": {"parcours_id": parcours.id_code}}, parcours.room.id_code) return "ok" class Exos(BaseModel): exercice: Exercice quantity: int @router.get('/room/{room_id}/challenge/{parcours_id}') def challenge_route(parcours_id: str, exercices: List[Exos] = Depends(get_exercices), member: Member = Depends(get_member_dep), db: Session = Depends(get_session)): correction = [parseGeneratorOut(generate_from_path(add_fast_api_root( e['exercice'].exo_source), e['quantity'], "web")) for e in exercices] sending = [[{**c, 'inputs': [stripKeyDict(i, "correction") for i in c['inputs']]} for c in e] for e in correction] tmpCorr = create_tmp_correction(correction, parcours_id, member, db) return {'challenge': sending, "id_code": tmpCorr.id_code} @router.post('/room/{room_id}/challenge/{parcours_id}/{correction_id}', response_model=ChallengeRead) async def send_challenge(*, challenge: List[List[ParsedGeneratorOut]], correction: TmpCorrection = Depends(get_correction), time: int = Body(), db: Session = Depends(get_session), m: RoomManager = Depends(get_manager),): parcours = correction.parcours member = correction.member data = corrige_challenge(challenge, correction) if data is None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={"challenge_error":"Object does not correspond to correction"}) chall = create_challenge(**data, challenger=member, parcours=parcours, time=time, db=db) await m.broadcast({"type": "challenge", "data": Challenges(**{**chall.dict(), "challenger": member.id_code, "canCorrige": chall.data != []}).dict()}, parcours.id_code) db.delete(correction) db.commit() return chall @router.get('/room/{room_id}/challenge/{parcours_id}/{challenge_id}', response_model=ChallengeRead, dependencies=[Depends(get_member_dep)]) async def challenge_read(*, challenge: Challenge = Depends(get_challenge)): return challenge @router.put('/room/{room_id}/challenge/{parcours_id}/{challenge_id}', response_model=ChallengeRead, dependencies=[Depends(check_admin)]) async def corrige(*, correction: List[List[CorrigedGeneratorOut]], challenge: Challenge = Depends(get_challenge), db: Session = Depends(get_session), m: RoomManager = Depends(get_manager),): parcours = challenge.parcours member = challenge.challenger data = change_correction(correction, challenge) if data is None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={"correction_error": "Object does not correspond to challenge"}) challenge = change_challenge(challenge, data, db) await m.broadcast({"type": "challenge_change", "data": Challenges(**{**challenge.dict(), "challenger": member.id_code, "canCorrige": challenge.data != []}).dict()}, parcours.id_code) return challenge @router.websocket('/ws/room/{room_id}') async def room_ws(ws: WebSocket, room: Room | None = Depends(check_room), db: Session = Depends(get_session), m: RoomManager = Depends(get_manager)): if room is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail='Room not found') consumer = RoomConsumer(ws=ws, room=room, manager=m, db=db) await consumer.run()