generateur_v3/backend/api/routes/room/routes.py
2023-02-28 11:22:14 +01:00

259 lines
13 KiB
Python

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()