Bug fixes (double submission, escaping of ><! in answers, further redis docs
This commit is contained in:
@@ -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 = []
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user