Bug fixes (double submission, escaping of ><! in answers, further redis docs

This commit is contained in:
Mawoka
2025-10-25 16:17:32 +02:00
parent f3abe4d2c4
commit 8f2c832f9b
5 changed files with 59 additions and 11 deletions
+17 -5
View File
@@ -11,15 +11,27 @@ import asyncpg.exceptions
import bleach import bleach
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel from pydantic import BaseModel
import html
from classquiz.config import settings, redis, storage, meilisearch, ALLOWED_TAGS_FOR_QUIZ, arq from classquiz.config import (
settings,
redis,
storage,
meilisearch,
ALLOWED_TAGS_FOR_QUIZ,
arq,
)
from classquiz.db.models import Quiz, QuizInput, User, QuizQuestionType, StorageItem from classquiz.db.models import Quiz, QuizInput, User, QuizQuestionType, StorageItem
from classquiz.auth import get_current_user from classquiz.auth import get_current_user
import os import os
from datetime import datetime from datetime import datetime
from uuid import UUID from uuid import UUID
from classquiz.helpers import get_meili_data, check_image_string, extract_image_ids_from_quiz from classquiz.helpers import (
get_meili_data,
check_image_string,
extract_image_ids_from_quiz,
)
from classquiz.storage.errors import DeletionFailedError from classquiz.storage.errors import DeletionFailedError
settings = settings() settings = settings()
@@ -79,15 +91,15 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
quiz_input.background_color = bleach.clean(quiz_input.background_color, tags=[], strip=True) quiz_input.background_color = bleach.clean(quiz_input.background_color, tags=[], strip=True)
for i, question in enumerate(quiz_input.questions): for i, question in enumerate(quiz_input.questions):
if question.type == QuizQuestionType.ABCD: if question.type == QuizQuestionType.ABCD or question.type == QuizQuestionType.VOTING:
for i2, answer in enumerate(question.answers): for i2, answer in enumerate(question.answers):
if answer.color is not None: if answer.color is not None:
quiz_input.questions[i].answers[i2].color = bleach.clean(answer.color, tags=[], strip=True) quiz_input.questions[i].answers[i2].color = bleach.clean(answer.color, tags=[], strip=True)
if answer.answer == "": if answer.answer == "":
quiz_input.questions[i].answers[i2].answer = None quiz_input.questions[i].answers[i2].answer = None
if answer.answer is not None: if answer.answer is not None:
quiz_input.questions[i].answers[i2].answer = bleach.clean( quiz_input.questions[i].answers[i2].answer = html.unescape(
answer.answer, tags=ALLOWED_TAGS_FOR_QUIZ, strip=True bleach.clean(answer.answer, tags=ALLOWED_TAGS_FOR_QUIZ, strip=True)
) )
images_to_delete = [] images_to_delete = []
+11 -2
View File
@@ -25,7 +25,11 @@ from classquiz.db.models import (
from pydantic import BaseModel, ValidationError from pydantic import BaseModel, ValidationError
from datetime import datetime from datetime import datetime
from classquiz.socket_server.helpers import check_answer, check_captcha from classquiz.socket_server.helpers import (
check_answer,
check_captcha,
has_already_answered,
)
from .models import ( from .models import (
RejoinGameData, RejoinGameData,
JoinGameData, JoinGameData,
@@ -286,7 +290,12 @@ async def submit_answer(sid: str, data: dict):
return return
data.answer = str(data.answer) data.answer = str(data.answer)
session = await get_session(sid, sio) session = await get_session(sid, sio)
question_index = int(float(data.question_index))
game_data = await PlayGame.get_from_redis(session["game_pin"]) game_data = await PlayGame.get_from_redis(session["game_pin"])
already_answered = await has_already_answered(session["game_pin"], question_index, session["username"])
if already_answered:
await sio.emit("already_replied", room=sid)
return
(answer_right, answer) = check_answer(game_data, data) (answer_right, answer) = check_answer(game_data, data)
latency = int(float(session["ping"])) latency = int(float(session["ping"]))
time_q_started = datetime.fromisoformat(await redis.get(f"game:{session['game_pin']}:current_time")) time_q_started = datetime.fromisoformat(await redis.get(f"game:{session['game_pin']}:current_time"))
@@ -295,7 +304,7 @@ async def submit_answer(sid: str, data: dict):
if answer_right: if answer_right:
score = calculate_score( score = calculate_score(
abs(diff) - latency, abs(diff) - latency,
int(float(game_data.questions[int(float(data.question_index))].time)), int(float(game_data.questions[question_index].time)),
) )
if score > 1000: if score > 1000:
score = 1000 score = 1000
+10
View File
@@ -10,6 +10,7 @@ from classquiz.db.models import (
ABCDQuizAnswer, ABCDQuizAnswer,
VotingQuizAnswer, VotingQuizAnswer,
RangeQuizAnswer, RangeQuizAnswer,
AnswerDataList,
) )
from classquiz.socket_server.models import SubmitAnswerData from classquiz.socket_server.models import SubmitAnswerData
from .models import SubmitAnswerDataOrderType from .models import SubmitAnswerDataOrderType
@@ -131,3 +132,12 @@ def check_check_question(answer: str, answers: list[ABCDQuizAnswer]) -> bool:
if a.right: if a.right:
correct_string += str(i) correct_string += str(i)
return bool(correct_string == answer) return bool(correct_string == answer)
async def has_already_answered(game_pin: str, q_index: int, username: str) -> bool:
answers = await AnswerDataList.get_redis_or_empty(game_pin, q_index)
if answers is None:
return False
else:
answers = list(filter(lambda a: a.username == username, answers.root))
return len(answers) > 0
+7 -4
View File
@@ -82,6 +82,9 @@ SPDX-License-Identifier: MPL-2.0
}); });
const selectAnswer = (answer: string) => { const selectAnswer = (answer: string) => {
if (selected_answer !== undefined) {
return;
}
selected_answer = answer; selected_answer = answer;
//timer_res = '0'; //timer_res = '0';
socket.emit('submit_answer', { socket.emit('submit_answer', {
@@ -262,7 +265,7 @@ SPDX-License-Identifier: MPL-2.0
<input <input
type="text" type="text"
bind:value={text_input} bind:value={text_input}
disabled={selected_answer} disabled={selected_answer !== undefined}
class="bg-gray-50 focus:ring text-gray-900 rounded-lg focus:ring-blue-500 block w-full p-2 dark:bg-gray-700 dark:text-white dark:focus:ring-blue-500 outline-hidden transition text-center disabled:opacity-50 disabled:cursor-not-allowed" class="bg-gray-50 focus:ring text-gray-900 rounded-lg focus:ring-blue-500 block w-full p-2 dark:bg-gray-700 dark:text-white dark:focus:ring-blue-500 outline-hidden transition text-center disabled:opacity-50 disabled:cursor-not-allowed"
/> />
</div> </div>
@@ -271,7 +274,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-1/3"> <div class="w-1/3">
<BrownButton <BrownButton
type="button" type="button"
disabled={selected_answer} disabled={selected_answer !== undefined}
on:click={() => { on:click={() => {
selectAnswer(text_input); selectAnswer(text_input);
}} }}
@@ -367,7 +370,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="w-full mt-2"> <div class="w-full mt-2">
<BrownButton <BrownButton
type="button" type="button"
disabled={selected_answer} disabled={selected_answer !== undefined}
on:click={() => { on:click={() => {
select_complex_answer(question.answers); select_complex_answer(question.answers);
}}>{$t('words.submit')}</BrownButton }}>{$t('words.submit')}</BrownButton
@@ -389,7 +392,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex justify-center h-[5%]"> <div class="flex justify-center h-[5%]">
<div class="w-1/2"> <div class="w-1/2">
<BrownButton <BrownButton
disabled={!selected_answer} disabled={selected_answer !== undefined}
on:click={() => selectAnswer(selected_answer)} on:click={() => selectAnswer(selected_answer)}
>{$t('words.submit')} >{$t('words.submit')}
</BrownButton> </BrownButton>
+14
View File
@@ -134,3 +134,17 @@ data: `{PLAYER_NAME} = {CUSTOM_FIELD_VALUE}`
{cqc_code} is the code used to join with a **C**lass**Q**uiz**C**ontroller {cqc_code} is the code used to join with a **C**lass**Q**uiz**C**ontroller
Only holds the game-pin Only holds the game-pin
## game_in_lobby:{admin_user_id} [string]
Set if game is waiting for players to join for the admin to enable remote control
model: GameInLobby
example:
```json
{
"game_pin": "1234543",
"quiz_title": "Some random title",
"game_id": "uuid of game"
}
```