✨ New Remote-Control page
This commit is contained in:
@@ -27,6 +27,7 @@ from classquiz.routers import (
|
||||
eximport,
|
||||
login,
|
||||
sitemap,
|
||||
remote,
|
||||
)
|
||||
from classquiz.socket_server import sio
|
||||
from classquiz.helpers import meilisearch_init, telemetry_ping, bg_tasks
|
||||
@@ -80,6 +81,7 @@ async def auth_middleware_wrapper(request: Request, call_next):
|
||||
return await rememberme_middleware(request, call_next)
|
||||
|
||||
|
||||
app.include_router(remote.router, tags=["remote"], prefix="/api/v1/remote", include_in_schema=True)
|
||||
app.include_router(login.router, tags=["auth"], prefix="/api/v1/login", include_in_schema=True)
|
||||
|
||||
app.add_middleware(SessionMiddleware, secret_key=settings.secret_key)
|
||||
@@ -96,4 +98,5 @@ app.include_router(
|
||||
app.include_router(editor.router, tags=["editor"], prefix="/api/v1/editor", include_in_schema=True)
|
||||
app.include_router(eximport.router, tags=["export", "import"], prefix="/api/v1/eximport", include_in_schema=True)
|
||||
app.include_router(sitemap.router, tags=["sitemap"], prefix="/api/v1/sitemap", include_in_schema=True)
|
||||
|
||||
app.mount("/", ASGIApp(sio))
|
||||
|
||||
@@ -240,3 +240,9 @@ class GameSession(BaseModel):
|
||||
class UpdatePassword(BaseModel):
|
||||
old_password: str
|
||||
new_password: str
|
||||
|
||||
|
||||
class GameInLobby(BaseModel):
|
||||
game_pin: str
|
||||
quiz_title: str
|
||||
game_id: uuid.UUID
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel, validator
|
||||
from classquiz.config import settings, redis
|
||||
@@ -155,17 +157,21 @@ async def get_game_user_count(game_pin: str, api_key: str, as_string: bool = Fal
|
||||
@router.get(
|
||||
"/players",
|
||||
)
|
||||
async def get_game_session(game_pin: str, api_key: str):
|
||||
async def get_game_session(game_pin: str, api_key: str | None = None, game_id: uuid.UUID | None = None):
|
||||
if game_id is None and api_key is None:
|
||||
raise HTTPException(status_code=401, detail="API-Key and Quiz-ID are missing")
|
||||
user_id = await check_api_key(api_key)
|
||||
redis_res = await redis.get(f"game_session:{game_pin}")
|
||||
if redis_res is None:
|
||||
game_pin = await redis.get(f"game_pin:{user_id}:{game_pin}")
|
||||
redis_res = await redis.get(f"game_session:{game_pin}")
|
||||
if redis_res is None or user_id is None:
|
||||
if redis_res is None:
|
||||
raise HTTPException(status_code=404, detail="Game not found or API key not found")
|
||||
data = GameSession.parse_raw(redis_res)
|
||||
if user_id is None and data.game_id != str(game_id):
|
||||
raise HTTPException(status_code=401, detail="Game not found or API key not found")
|
||||
game = PlayGame.parse_raw(await redis.get(f"game:{game_pin}"))
|
||||
if game.user_id != user_id:
|
||||
if game.user_id != user_id and data.game_id != str(game_id):
|
||||
raise HTTPException(status_code=404, detail="Game not found or API key not found")
|
||||
for i in range(0, len(game.questions)):
|
||||
res = await redis.get(f"game_session:{game_pin}:{i}")
|
||||
@@ -195,7 +201,7 @@ async def set_next_question(game_pin: str, question_number: int, api_key: str):
|
||||
if game_data.user_id != user_id:
|
||||
raise HTTPException(status_code=404, detail="Game not found or API key not found")
|
||||
game_data.current_question = question_number
|
||||
await redis.set(f"game:{game_pin}", game_data.json())
|
||||
await redis.set(f"game:{game_pin}", game_data.json(), ex=18000)
|
||||
await sio.emit(
|
||||
"set_question_number",
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ import bleach
|
||||
|
||||
from classquiz.auth import get_current_user
|
||||
from classquiz.config import redis, settings, storage, meilisearch
|
||||
from classquiz.db.models import Quiz, QuizInput, User, PlayGame
|
||||
from classquiz.db.models import Quiz, QuizInput, User, PlayGame, GameInLobby
|
||||
from classquiz.kahoot_importer.import_quiz import import_quiz
|
||||
import html
|
||||
import urllib.parse
|
||||
@@ -129,6 +129,11 @@ async def start_quiz(
|
||||
)
|
||||
await redis.set(f"game:{str(game.game_pin)}", game.json(), ex=18000)
|
||||
await redis.set(f"game_pin:{user.id}:{quiz_id}", game_pin, ex=18000)
|
||||
await redis.set(
|
||||
f"game_in_lobby:{user.id.hex}",
|
||||
GameInLobby(game_id=game.game_id, game_pin=str(game_pin), quiz_title=quiz.title).json(),
|
||||
ex=900,
|
||||
)
|
||||
return {**quiz.dict(exclude={"id"}), **game.dict(exclude={"questions"})}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from classquiz.auth import get_current_user
|
||||
from classquiz.db.models import User, GameInLobby
|
||||
from classquiz.config import redis
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/game_waiting")
|
||||
async def get_game_in_lobby(user: User = Depends(get_current_user)):
|
||||
game_in_lobby_raw = await redis.get(f"game_in_lobby:{user.id.hex}")
|
||||
if game_in_lobby_raw is None:
|
||||
raise HTTPException(status_code=404, detail="No game waiting")
|
||||
game_in_lobby = GameInLobby.parse_raw(game_in_lobby_raw)
|
||||
return game_in_lobby
|
||||
@@ -116,7 +116,7 @@ async def join_game(sid: str, data: dict):
|
||||
)
|
||||
redis_res = await redis.get(f"game_session:{data.game_pin}")
|
||||
redis_res = GameSession.parse_raw(redis_res)
|
||||
await redis.set(f"game_session:{data.game_pin}:players:{data.username}", sid, ex=18000)
|
||||
await redis.set(f"game_session:{data.game_pin}:players:{data.username}", sid, ex=7200)
|
||||
await redis.sadd(f"game_session:{data.game_pin}:players", GamePlayer(username=data.username, sid=sid).json())
|
||||
if data.custom_field == "":
|
||||
data.custom_field = None
|
||||
@@ -131,7 +131,7 @@ async def join_game(sid: str, data: dict):
|
||||
await sio.emit(
|
||||
"player_joined",
|
||||
{"username": data.username, "sid": sid},
|
||||
room=redis_res.admin,
|
||||
room=f"admin:{data.game_pin}",
|
||||
)
|
||||
# +++ Time-Sync +++
|
||||
encrypted_datetime = fernet.encrypt(datetime.now().isoformat().encode("utf-8")).decode("utf-8")
|
||||
@@ -146,7 +146,8 @@ async def start_game(sid: str, _data: dict):
|
||||
if session["admin"]:
|
||||
game_data = PlayGame.parse_raw(await redis.get(f"game:{session['game_pin']}"))
|
||||
game_data.started = True
|
||||
await redis.set(f"game:{session['game_pin']}", game_data.json())
|
||||
await redis.set(f"game:{session['game_pin']}", game_data.json(), ex=7200)
|
||||
await redis.delete(f"game_in_lobby:{game_data.user_id.hex}")
|
||||
await sio.emit("start_game", room=session["game_pin"])
|
||||
|
||||
|
||||
@@ -169,7 +170,7 @@ async def register_as_admin(sid: str, data: dict):
|
||||
await redis.set(
|
||||
f"game_session:{game_pin}",
|
||||
GameSession(admin=sid, game_id=game_id, answers=[]).json(),
|
||||
ex=18000,
|
||||
ex=7200,
|
||||
)
|
||||
|
||||
await sio.emit(
|
||||
@@ -180,7 +181,9 @@ async def register_as_admin(sid: str, data: dict):
|
||||
async with sio.session(sid) as session:
|
||||
session["game_pin"] = game_pin
|
||||
session["admin"] = True
|
||||
session["remote"] = False
|
||||
sio.enter_room(sid, game_pin)
|
||||
sio.enter_room(sid, f"admin:{data.game_pin}")
|
||||
else:
|
||||
await sio.emit("already_registered_as_admin", room=sid)
|
||||
|
||||
@@ -227,8 +230,8 @@ async def set_question_number(sid, data: str):
|
||||
game_pin = session["game_pin"]
|
||||
game_data = PlayGame.parse_raw(await redis.get(f"game:{session['game_pin']}"))
|
||||
game_data.current_question = int(float(data))
|
||||
await redis.set(f"game:{session['game_pin']}", game_data.json())
|
||||
await redis.set(f"game:{session['game_pin']}:current_time", datetime.now().isoformat())
|
||||
await redis.set(f"game:{session['game_pin']}", game_data.json(), ex=7200)
|
||||
await redis.set(f"game:{session['game_pin']}:current_time", datetime.now().isoformat(), ex=7200)
|
||||
temp_return = game_data.dict(include={"questions"})["questions"][int(float(data))]
|
||||
if game_data.questions[int(float(data))].type == QuizQuestionType.SLIDE:
|
||||
return
|
||||
@@ -322,7 +325,7 @@ async def submit_answer(sid: str, data: dict):
|
||||
)
|
||||
]
|
||||
).json(),
|
||||
ex=18000,
|
||||
ex=7200,
|
||||
)
|
||||
else:
|
||||
answers = _AnswerDataList.parse_raw(answers)
|
||||
@@ -338,7 +341,7 @@ async def submit_answer(sid: str, data: dict):
|
||||
await redis.set(
|
||||
f"game_session:{session['game_pin']}:{data.question_index}",
|
||||
answers.json(),
|
||||
ex=18000,
|
||||
ex=7200,
|
||||
)
|
||||
answers = _AnswerDataList.parse_raw(await redis.get(f"game_session:{session['game_pin']}:{data.question_index}"))
|
||||
player_count = await redis.scard(f"game_session:{session['game_pin']}:players")
|
||||
@@ -349,7 +352,9 @@ async def submit_answer(sid: str, data: dict):
|
||||
# room=session["game_pin"],
|
||||
# )
|
||||
await sio.emit("everyone_answered", {})
|
||||
# await redis.set(f"game_data:{session['game_pin']}", json.dumps(data))
|
||||
|
||||
|
||||
# await redis.set(f"game_data:{session['game_pin']}", json.dumps(data))
|
||||
|
||||
|
||||
@sio.event
|
||||
@@ -363,14 +368,14 @@ async def get_final_results(sid: str, _data: dict):
|
||||
|
||||
|
||||
@sio.event
|
||||
async def get_export_token(sid):
|
||||
async def get_export_token(sid: str):
|
||||
session = await sio.get_session(sid)
|
||||
if not session["admin"]:
|
||||
return
|
||||
game_data = PlayGame(**json.loads(await redis.get(f"game:{session['game_pin']}")))
|
||||
results = await generate_final_results(game_data, session["game_pin"])
|
||||
token = os.urandom(32).hex()
|
||||
await redis.set(f"export_token:{token}", json.dumps(results))
|
||||
await redis.set(f"export_token:{token}", json.dumps(results), ex=7200)
|
||||
await sio.emit("export_token", token, room=sid)
|
||||
|
||||
|
||||
@@ -407,7 +412,6 @@ async def kick_player(sid: str, data: dict):
|
||||
return
|
||||
|
||||
session: dict = await sio.get_session(sid)
|
||||
print(sid)
|
||||
if not session["admin"]:
|
||||
return
|
||||
|
||||
@@ -417,3 +421,46 @@ async def kick_player(sid: str, data: dict):
|
||||
)
|
||||
sio.leave_room(player_sid, session["game_pin"])
|
||||
await sio.emit("kick", room=player_sid)
|
||||
|
||||
|
||||
class _RegisterAsRemoteInput(BaseModel):
|
||||
game_pin: str
|
||||
game_id: str
|
||||
|
||||
|
||||
@sio.event
|
||||
async def register_as_remote(sid: str, data: dict):
|
||||
try:
|
||||
data = _RegisterAsRemoteInput(**data)
|
||||
except ValidationError as e:
|
||||
await sio.emit("error", room=sid)
|
||||
print(e)
|
||||
return
|
||||
await sio.emit(
|
||||
"registered_as_admin",
|
||||
{"game_id": data.game_id, "game": await redis.get(f"game:{data.game_pin}")},
|
||||
room=sid,
|
||||
)
|
||||
await sio.emit("control_visibility", {"visible": False}, room=f"admin:{data.game_pin}")
|
||||
async with sio.session(sid) as session:
|
||||
session["game_pin"] = data.game_pin
|
||||
session["admin"] = True
|
||||
session["remote"] = True
|
||||
sio.enter_room(sid, data.game_pin)
|
||||
sio.enter_room(sid, f"admin:{data.game_pin}")
|
||||
|
||||
|
||||
class _SetControlVisibilityInput(BaseModel):
|
||||
visible: bool
|
||||
|
||||
|
||||
@sio.event
|
||||
async def set_control_visibility(sid: str, data: dict):
|
||||
try:
|
||||
data = _SetControlVisibilityInput(**data)
|
||||
except ValidationError as e:
|
||||
await sio.emit("error", room=sid)
|
||||
print(e)
|
||||
return
|
||||
session: dict = await sio.get_session(sid)
|
||||
await sio.emit("control_visibility", {"visible": data.visible}, room=f"admin:{session['game_pin']}")
|
||||
|
||||
Reference in New Issue
Block a user