from typing import List, Optional from fastapi import APIRouter, Depends, WebSocket, status, Query, Body from fastapi.exceptions import HTTPException from pydantic import BaseModel from sqlmodel import Session, select from database.auth.models import User from database.db import get_session from database.exercices.models import Exercice from database.room.crud import delete_room_db, getUsername, getMemberValidated from database.room.crud import serialize_parcours_short, 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, serialize_parcours, getTops, getAvgRank, getRank, \ getAvgTops, ChallengerFromChallenge, getMemberAvgRank, getMemberRank, getMemberChallenges from database.room.models import Challenge, ChallengeRead, Challenges, ParcoursReadUpdate, ChallengeInfo, Member, \ Parcours, ParcoursCreate, ParcoursRead, ParcoursReadShort, Room, RoomConnectionInfos, \ RoomCreate, RoomInfo, TmpCorrection, CorrigedData, CorrectionData from generateur.generateur_main import generate_from_path, parseGeneratorOut from routes.room.consumer import RoomConsumer from routes.room.manager import RoomManager from services.auth import get_current_user_optional from services.io import add_fast_api_root from services.misc import 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.delete('/room/{room_id}', dependencies=[Depends(check_admin)]) async def delete_room(room: Room = Depends(get_room), m: RoomManager = Depends(get_manager), db: Session = Depends(get_session)): delete_room_db(room, db) await m.broadcast({"type": "deleted"}, room.id_code) return {"message": "ok"} @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 serialize_parcours(parcours_obj, member, db) @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)): return serialize_parcours(parcours, member, db) @router.put('/room/{room_id}/parcours/{parcours_id}', response_model=ParcoursRead) async def update_parcours(*, room_id: str, parcours: ParcoursCreate, member: Member = Depends(check_admin), parcours_old: Parcours = Depends(get_parcours), m: RoomManager = Depends(get_manager), db: Session = Depends(get_session)): parcours_obj, update_challenges = update_parcours_db( parcours, parcours_old, db) if type(parcours_obj) == str: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=parcours_obj) await m.broadcast(lambda m: {"type": "update_parcours", "data": {"parcours": serialize_parcours_short(parcours_obj, m, db).dict()}}, room_id) await m.broadcast({"type": "edit_parcours", "data": { "parcours": ParcoursReadUpdate(**parcours_obj.dict(), update_challenges=update_challenges).dict()}}, parcours_old.id_code) await m.broadcast( lambda m: {"type": "update_challenges", "data": {"challenger": {"id_code": m.id_code, "name": getUsername(m), "validated": getMemberValidated(m, parcours_obj, db)}, "challenges": [Challenges( **{**chall.dict(), "canCorrige": chall.data != []}).dict() for chall in getMemberChallenges(m, parcours_obj, db)]}}, parcours_old.id_code, conditions=[lambda m: m.member.id_code != member.id_code]) return serialize_parcours(parcours_obj, member, db) @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: Parcours = Depends(get_parcours), 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] infos = [{"name": e["exercice"].name, "consigne": e['exercice'].consigne, "id_code": e['exercice'].id_code} for e in exercices] sending = [{"exo": ei, "data": [{**c, 'inputs': [stripKeyDict(i, "correction") for i in c['inputs']]} for c in e]} for e, ei in zip(correction, infos)] corriged = [{"exo": ei, "data": e} for e, ei in zip(correction, infos)] tmpCorr = create_tmp_correction(corriged, parcours.id_code, member, db) return {'challenge': sending, "id_code": tmpCorr.id_code, 'parcours': {"name": parcours.name, 'time': parcours.time, "max_mistakes": parcours.max_mistakes, 'id_code': parcours.id_code}} @router.post('/room/{room_id}/challenge/{parcours_id}/{correction_id}', response_model=ChallengeRead) async def send_challenge(*, challenge: List[CorrectionData], 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, challenger = create_challenge(**data, challenger=member, parcours=parcours, time=time, db=db) await m.broadcast({"type": "challenge", "data": ChallengeInfo( challenger={"name": member.user.username if member.user_id != None else member.anonymous.username, "id_code": member.id_code}, challenges=[Challenges(**{**chall.dict(), "canCorrige": chall.data != [], })]).dict()}, parcours.id_code, conditions=[lambda c: c.member.is_admin or c.member.id_code == member.id_code]) # TODO : Envoyer que à ceux d'après await m.broadcast(lambda m: {"type": "newRanks", "data": {"rank": getMemberRank(m, correction.parcours, db), "avgRank": getMemberAvgRank(m, correction.parcours, db)}}, parcours.id_code) await m.send_to(parcours.room.id_code, member.id_code, {"type": "parcours_stats", "data": { "parcours": ParcoursReadShort(name=parcours.name, best_note=challenger.best, validated=challenger.validated, id_code=parcours.id_code, avg=challenger.avg).dict()}}) rank, avgRank = getRank( challenger, parcours, db), getAvgRank(challenger, parcours, db) if rank <= 3 or avgRank <= 3: await m.broadcast({"type": "newTops", "data": { "tops": getTops(correction.parcours, db), "avgTops": getAvgTops(correction.parcours, db), }}, parcours.id_code) db.delete(correction) returnValue = {**chall.dict()} db.commit() return returnValue # return {**chall.dict(), 'validated': chall.mistakes <= correction.parcours.max_mistakes} class ParcoursInfo(BaseModel): name: str time: int # validate: int id_code: str class Chall(BaseModel): challenge: Challenge parcours: ParcoursInfo # response_model=ChallengeRead @router.get('/room/{room_id}/correction/{challenge_id}', dependencies=[Depends(get_member_dep)]) async def challenge_read(*, challenge: Challenge = Depends(get_challenge), db: Session = Depends(get_session)): parcours = challenge.parcours member = db.exec(select(Member).where( Member.id == challenge.challenger_mid)).first() challenger = ChallengerFromChallenge(challenge, db) obj = member.user if member.user_id is not None else member.anonymous return {**ChallengeRead(**challenge.dict()).dict(), "challenger": {"name": obj.username}, 'parcours': {"name": parcours.name, 'time': parcours.time, "max_mistakes": parcours.max_mistakes, 'id_code': parcours.id_code}} # response_model=ChallengeRead @router.put('/room/{room_id}/correction/{challenge_id}', dependencies=[Depends(check_admin)]) async def corrige(*, correction: List[CorrigedData] = Body(), challenge: Challenge = Depends(get_challenge), db: Session = Depends(get_session), m: RoomManager = Depends(get_manager), ): 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"}) parcours = challenge.parcours member = db.exec(select(Member).where( Member.id == challenge.challenger_mid)).first() challenge, challenger = change_challenge(challenge, data, db) obj = member.user if member.user_id is not None else member.anonymous await m.broadcast(lambda m: {"type": "newRanks", "data": {"rank": getMemberRank(m, parcours, db), "avgRank": getMemberAvgRank(m, parcours, db)}}, parcours.id_code) rank, avgRank = getRank( challenger, parcours, db), getAvgRank(challenger, parcours, db) if rank <= 3 or avgRank <= 3: await m.broadcast({"type": "newTops", "data": { "tops": getTops(parcours, db), "avgTops": getAvgTops(parcours, db), }}, parcours.id_code) await m.broadcast({"type": "challenge_change", "data": {"challenge": Challenges(**challenge.dict()).dict(), "member": member.id_code, "validated": challenger.validated}}, parcours.id_code, conditions=[lambda m: m.member.is_admin or m.member.id_code == member.id_code]) return {**ChallengeRead(**challenge.dict()).dict(), "challenger": {"name": obj.username}} # return {**ChallengeRead.from_orm(challenge).dict(), "challenger": {"name": obj.username, }} ws_router = APIRouter(tags=["room"]) @ws_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)): consumer = RoomConsumer(ws=ws, room=room, manager=m, db=db) await consumer.run()