259 lines
13 KiB
Python
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()
|