🚧 Added code for new ClassQuizController
This commit is contained in:
@@ -318,3 +318,19 @@ class GameResults(ormar.Model):
|
|||||||
tablename = "game_results"
|
tablename = "game_results"
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
|
|
||||||
|
class Controllers(ormar.Model):
|
||||||
|
id: uuid.UUID = ormar.UUID(primary_key=True)
|
||||||
|
user: uuid.UUID | User = ormar.ForeignKey(User)
|
||||||
|
secret_key: str = ormar.String(nullable=False, max_length=24, min_length=24)
|
||||||
|
player_name: str = ormar.Text(nullable=False)
|
||||||
|
last_seen: datetime | None = ormar.DateTime(nullable=True)
|
||||||
|
first_seen: datetime | None = ormar.DateTime(nullable=True)
|
||||||
|
name: str = ormar.Text(nullable=False)
|
||||||
|
os_version: str | None = ormar.Text(nullable=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
tablename = "controllers"
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# 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/.
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
def generate_code(specified_length: int) -> str:
|
||||||
|
buttons = [
|
||||||
|
"B",
|
||||||
|
"b",
|
||||||
|
"G",
|
||||||
|
"g",
|
||||||
|
"Y",
|
||||||
|
"y",
|
||||||
|
"R",
|
||||||
|
"r",
|
||||||
|
] # Capital stands for long press, lowercase letter for short press
|
||||||
|
resulting_code = ""
|
||||||
|
for i in range(specified_length):
|
||||||
|
resulting_code += random.choice(buttons)
|
||||||
|
return resulting_code
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# 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/.
|
||||||
|
|
||||||
|
# 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/.
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from classquiz.config import redis
|
||||||
|
from classquiz.db.models import PlayGame, Controllers
|
||||||
|
from classquiz.routers.box_controller.embedded.socket import router as socket_router
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
router.include_router(socket_router, prefix="/socket")
|
||||||
|
|
||||||
|
|
||||||
|
class JoinGameInput(BaseModel):
|
||||||
|
id: uuid.UUID
|
||||||
|
secret_key: str
|
||||||
|
code: str
|
||||||
|
|
||||||
|
|
||||||
|
class JoinGameResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/join")
|
||||||
|
async def join_game(data: JoinGameInput):
|
||||||
|
controller = await Controllers.objects.get_or_none(id=data.id, secret_key=data.secret_key)
|
||||||
|
game_pin = await redis.get(f"game:cqc:code:{data.code}")
|
||||||
|
if game_pin is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Game not found")
|
||||||
|
game = await redis.get(f"game:{game_pin}")
|
||||||
|
game = PlayGame.parse_raw(game)
|
||||||
|
# Check if game is already running
|
||||||
|
if game.started:
|
||||||
|
raise HTTPException(status_code=400, detail="Game started already")
|
||||||
|
# check if username already exists
|
||||||
|
if await redis.get(f"game_session:{game_pin}:players:{controller.player_name}") is not None:
|
||||||
|
raise HTTPException(status_code=409, detail="Username already exists")
|
||||||
|
|
||||||
|
player_id = os.urandom(5).hex()
|
||||||
|
await redis.set(f"game:cqc:player:{player_id}", controller.player_name)
|
||||||
|
return JoinGameResponse(id=f"{player_id}:{game_pin}")
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterWithCodeInput(BaseModel):
|
||||||
|
code: str
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterWithCodeResponse(BaseModel):
|
||||||
|
id: uuid.UUID
|
||||||
|
secret_key: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/register")
|
||||||
|
async def register_with_code(data: RegisterWithCodeInput) -> RegisterWithCodeResponse:
|
||||||
|
c_id = await redis.get(f"controller_setup:{data.code}")
|
||||||
|
if c_id is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Code not found")
|
||||||
|
await redis.delete(f"controller_setup:{data.code}")
|
||||||
|
c_id = uuid.UUID(c_id)
|
||||||
|
controller = await Controllers.objects.get(id=c_id)
|
||||||
|
return RegisterWithCodeResponse(id=controller.id, secret_key=controller.secret_key)
|
||||||
+4
-32
@@ -2,7 +2,6 @@
|
|||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# 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/.
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
import enum
|
import enum
|
||||||
import os
|
|
||||||
import typing
|
import typing
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -15,34 +14,6 @@ from classquiz.socket_server import sio, calculate_score, set_answer
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
class JoinGameInput(BaseModel):
|
|
||||||
code: str
|
|
||||||
name: str
|
|
||||||
|
|
||||||
|
|
||||||
class JoinGameResponse(BaseModel):
|
|
||||||
id: str
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/join")
|
|
||||||
async def join_game(data: JoinGameInput):
|
|
||||||
game_pin = await redis.get(f"game:cqc:code:{data.code}")
|
|
||||||
if game_pin is None:
|
|
||||||
raise HTTPException(status_code=404, detail="Game not found")
|
|
||||||
game = await redis.get(f"game:{game_pin}")
|
|
||||||
game = PlayGame.parse_raw(game)
|
|
||||||
# Check if game is already running
|
|
||||||
if game.started:
|
|
||||||
raise HTTPException(status_code=400, detail="Game started already")
|
|
||||||
# check if username already exists
|
|
||||||
if await redis.get(f"game_session:{game_pin}:players:{data.name}") is not None:
|
|
||||||
raise HTTPException(status_code=409, detail="Username already exists")
|
|
||||||
|
|
||||||
player_id = os.urandom(5).hex()
|
|
||||||
await redis.set(f"game:cqc:player:{player_id}", data.name)
|
|
||||||
return JoinGameResponse(id=f"{player_id}:{game_pin}")
|
|
||||||
|
|
||||||
|
|
||||||
class SubmitAnswerInput(BaseModel):
|
class SubmitAnswerInput(BaseModel):
|
||||||
answer: int
|
answer: int
|
||||||
|
|
||||||
@@ -91,6 +62,9 @@ async def submit_answer_fn(data_answer: int, game_pin: str, player_id: str, now:
|
|||||||
await sio.emit("everyone_answered", {})
|
await sio.emit("everyone_answered", {})
|
||||||
|
|
||||||
|
|
||||||
|
button_to_index_map = {"b": 0, "g": 2, "y": 1, "r": 3}
|
||||||
|
|
||||||
|
|
||||||
class WebSocketTypes(enum.Enum):
|
class WebSocketTypes(enum.Enum):
|
||||||
ButtonPress = "bp"
|
ButtonPress = "bp"
|
||||||
Error = "e"
|
Error = "e"
|
||||||
@@ -103,10 +77,8 @@ class WebSocketRequest(BaseModel):
|
|||||||
|
|
||||||
wss_clients = {}
|
wss_clients = {}
|
||||||
|
|
||||||
button_to_index_map = {"b": 0, "g": 1, "y": 2, "r": 3}
|
|
||||||
|
|
||||||
|
@router.websocket("/{id}")
|
||||||
@router.websocket("/socket/{id}")
|
|
||||||
async def websocket_endpoint(ws: WebSocket, game_id: str, id: str):
|
async def websocket_endpoint(ws: WebSocket, game_id: str, id: str):
|
||||||
try:
|
try:
|
||||||
if id in wss_clients.keys():
|
if id in wss_clients.keys():
|
||||||
@@ -1,52 +1,77 @@
|
|||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# 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
|
# 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/.
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
import random
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from classquiz.auth import get_current_user
|
from classquiz.auth import get_current_user
|
||||||
from classquiz.db.models import User, PlayGame
|
from classquiz.db.models import User, Controllers
|
||||||
from classquiz.config import redis
|
from classquiz.config import redis
|
||||||
|
from classquiz.helpers.box_controller import generate_code
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
def generate_code() -> str:
|
class SetControllerUpInput(BaseModel):
|
||||||
specified_length = 6
|
player_name: str | None
|
||||||
buttons = [
|
name: str
|
||||||
"B",
|
|
||||||
"b",
|
|
||||||
"G",
|
|
||||||
"g",
|
|
||||||
"Y",
|
|
||||||
"y",
|
|
||||||
"R",
|
|
||||||
"r",
|
|
||||||
] # Capital stands for long press, lowercase letter for short press
|
|
||||||
resulting_code = ""
|
|
||||||
for i in range(specified_length):
|
|
||||||
resulting_code += random.choice(buttons)
|
|
||||||
return resulting_code
|
|
||||||
|
|
||||||
|
|
||||||
class ActivateCqbForCurrentQuizInput(BaseModel):
|
class SetControllerUpResponse(BaseModel):
|
||||||
game_pin: int
|
|
||||||
|
|
||||||
|
|
||||||
class ActivateCqbForCurrentQuizResponse(BaseModel):
|
|
||||||
code: str
|
code: str
|
||||||
|
id: uuid.UUID
|
||||||
|
|
||||||
|
|
||||||
@router.post("/activate-for-quiz", response_model=ActivateCqbForCurrentQuizResponse)
|
@router.post("/setup")
|
||||||
async def activate_cqc_for_current_quiz(data: ActivateCqbForCurrentQuizInput, user: User = Depends(get_current_user)):
|
async def set_controller_up(
|
||||||
redis_res = await redis.get(f"game:{data.game_pin}")
|
input_data: SetControllerUpInput, user: User = Depends(get_current_user)
|
||||||
if redis_res is None:
|
) -> SetControllerUpResponse:
|
||||||
raise HTTPException(status_code=404, detail="Game not found")
|
code = generate_code(10)
|
||||||
game_data = PlayGame.parse_raw(redis_res)
|
if input_data.player_name is None:
|
||||||
if game_data.user_id != user.id:
|
input_data.player_name = user.username
|
||||||
raise HTTPException(status_code=401, detail="The quiz wasn't started by you")
|
data = Controllers(
|
||||||
code = generate_code()
|
id=uuid.uuid4(),
|
||||||
await redis.set(f"game:cqc:code:{code}", game_data.game_pin, ex=3600)
|
user=user,
|
||||||
return ActivateCqbForCurrentQuizResponse(code=code)
|
secret_key=os.urandom(12).hex(),
|
||||||
|
player_name=input_data.player_name,
|
||||||
|
last_seen=None,
|
||||||
|
first_seen=None,
|
||||||
|
name=input_data.name,
|
||||||
|
os_version=None,
|
||||||
|
)
|
||||||
|
await data.save()
|
||||||
|
await redis.set(f"controller_setup:{code}", data.id.hex, ex=900)
|
||||||
|
return SetControllerUpResponse(code=code, id=data.id)
|
||||||
|
|
||||||
|
|
||||||
|
GetControllerResponse = Controllers.get_pydantic(exclude={"secret_key", "user"})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/controller")
|
||||||
|
async def get_controller(id: uuid.UUID, user: User = Depends(get_current_user)) -> GetControllerResponse:
|
||||||
|
controller = await Controllers.objects.get_or_none(id=id, user=user.id)
|
||||||
|
if controller is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Controller not found")
|
||||||
|
return GetControllerResponse(**controller.dict())
|
||||||
|
|
||||||
|
|
||||||
|
class ModifyControllerInput(BaseModel):
|
||||||
|
id: uuid.UUID
|
||||||
|
player_name: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/modify")
|
||||||
|
async def modify_controller(
|
||||||
|
data: ModifyControllerInput, user: User = Depends(get_current_user)
|
||||||
|
) -> GetControllerResponse:
|
||||||
|
controller = await Controllers.objects.get_or_none(id=data.id, user=user.id)
|
||||||
|
if controller is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Controller not found")
|
||||||
|
controller.player_name = data.player_name
|
||||||
|
controller.name = data.name
|
||||||
|
await controller.update()
|
||||||
|
return GetControllerResponse(**controller.dict())
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import random
|
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -20,6 +19,7 @@ import bleach
|
|||||||
from classquiz.auth import get_current_user
|
from classquiz.auth import get_current_user
|
||||||
from classquiz.config import redis, settings, storage, meilisearch
|
from classquiz.config import redis, settings, storage, meilisearch
|
||||||
from classquiz.db.models import Quiz, QuizInput, User, PlayGame, GameInLobby, QuizQuestion
|
from classquiz.db.models import Quiz, QuizInput, User, PlayGame, GameInLobby, QuizQuestion
|
||||||
|
from classquiz.helpers.box_controller import generate_code
|
||||||
from classquiz.kahoot_importer.import_quiz import import_quiz
|
from classquiz.kahoot_importer.import_quiz import import_quiz
|
||||||
import html
|
import html
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@@ -99,24 +99,6 @@ async def get_public_quiz(quiz_id: str):
|
|||||||
return PublicQuizResponse(**quiz.dict())
|
return PublicQuizResponse(**quiz.dict())
|
||||||
|
|
||||||
|
|
||||||
def generate_code() -> str:
|
|
||||||
specified_length = 6
|
|
||||||
buttons = [
|
|
||||||
"B",
|
|
||||||
"b",
|
|
||||||
"G",
|
|
||||||
"g",
|
|
||||||
"Y",
|
|
||||||
"y",
|
|
||||||
"R",
|
|
||||||
"r",
|
|
||||||
] # Capital stands for long press, lowercase letter for short press
|
|
||||||
resulting_code = ""
|
|
||||||
for i in range(specified_length):
|
|
||||||
resulting_code += random.choice(buttons)
|
|
||||||
return resulting_code
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/start/{quiz_id}")
|
@router.post("/start/{quiz_id}")
|
||||||
async def start_quiz(
|
async def start_quiz(
|
||||||
quiz_id: str,
|
quiz_id: str,
|
||||||
@@ -160,7 +142,7 @@ async def start_quiz(
|
|||||||
)
|
)
|
||||||
code = None
|
code = None
|
||||||
if cqcs_enabled:
|
if cqcs_enabled:
|
||||||
code = generate_code()
|
code = generate_code(6)
|
||||||
await redis.set(f"game:cqc:code:{code}", game_pin, ex=3600)
|
await redis.set(f"game:cqc:code:{code}", game_pin, ex=3600)
|
||||||
await redis.set(f"game:{str(game.game_pin)}", game.json(), ex=18000)
|
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_pin:{user.id}:{quiz_id}", game_pin, ex=18000)
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""Added controllers table
|
||||||
|
|
||||||
|
Revision ID: b9ca06dfa179
|
||||||
|
Revises: 7afe98d04169
|
||||||
|
Create Date: 2023-04-03 13:16:49.585163
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import ormar
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "b9ca06dfa179"
|
||||||
|
down_revision = "7afe98d04169"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"controllers",
|
||||||
|
sa.Column("id", ormar.fields.sqlalchemy_uuid.CHAR(32), nullable=False),
|
||||||
|
sa.Column("user", ormar.fields.sqlalchemy_uuid.CHAR(32), nullable=True),
|
||||||
|
sa.Column("secret_key", sa.String(length=24), nullable=False),
|
||||||
|
sa.Column("player_name", sa.Text(), nullable=False),
|
||||||
|
sa.Column("last_seen", sa.DateTime(), nullable=True),
|
||||||
|
sa.Column("first_seen", sa.DateTime(), nullable=True),
|
||||||
|
sa.Column("name", sa.Text(), nullable=False),
|
||||||
|
sa.Column("os_version", sa.Text(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(["user"], ["users.id"], name="fk_controllers_users_id_user"),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table("controllers")
|
||||||
|
# ### end Alembic commands ###
|
||||||
Reference in New Issue
Block a user