Merge remote-tracking branch 'origin/master'

This commit is contained in:
Mawoka
2023-06-15 15:24:34 +02:00
32 changed files with 127 additions and 91 deletions
+1 -2
View File
@@ -71,6 +71,7 @@ class Settings(BaseSettings):
async def initialize_arq(): async def initialize_arq():
# skipcq: PYL-W0603
global arq global arq
arq = await create_pool(RedisSettings.from_dsn(settings.redis)) arq = await create_pool(RedisSettings.from_dsn(settings.redis))
@@ -80,8 +81,6 @@ def settings() -> Settings:
return Settings() return Settings()
# asyncio.run(initialize_arq())
pool = redis_lib.ConnectionPool().from_url(settings().redis) pool = redis_lib.ConnectionPool().from_url(settings().redis)
redis: redis_base_lib.client.Redis = redis_lib.Redis(connection_pool=pool) redis: redis_base_lib.client.Redis = redis_lib.Redis(connection_pool=pool)
+3 -3
View File
@@ -297,7 +297,7 @@ class GameInLobby(BaseModel):
game_id: uuid.UUID game_id: uuid.UUID
# # skipcq: PY-W0069
# class UserProfileLinks(ormar.Model): # class UserProfileLinks(ormar.Model):
# id: int = ormar.Integer(primary_key=True, autoincrement=True) # id: int = ormar.Integer(primary_key=True, autoincrement=True)
# user: Optional[User] = ormar.ForeignKey(User) # user: Optional[User] = ormar.ForeignKey(User)
@@ -471,5 +471,5 @@ class PrivateStorageItem(PublicStorageItem):
class UpdateStorageItem(BaseModel): class UpdateStorageItem(BaseModel):
filename: str filename: str | None
alt_text: str alt_text: str | None
-7
View File
@@ -41,10 +41,3 @@ class QuizTivityPage(BaseModel):
title: str | None title: str | None
type: QuizTivityTypes type: QuizTivityTypes
data: Pdf | Memory | Markdown data: Pdf | Memory | Markdown
# @validator("type")
# def match_type_to_data_type(cls, v, values, **kwargs):
# print(values)
# if TYPE_CLASS_LIST[v] != type(values["data"]):
# raise ValueError("Specified Type doesn't match real data type")
# pass
+2 -1
View File
@@ -88,8 +88,9 @@ async def auth(request: Request, response: Response):
google_uid=user_data.sub.hex, google_uid=user_data.sub.hex,
avatar=gzipped_user_avatar(), avatar=gzipped_user_avatar(),
) )
# skipcq: PYL-W0703
except Exception as e: except Exception as e:
if type(e) == asyncpg.exceptions.UniqueViolationError: if type(e) is asyncpg.exceptions.UniqueViolationError:
error = True error = True
counter = 1 counter = 1
while error: while error:
+2 -1
View File
@@ -116,8 +116,9 @@ async def auth(request: Request, response: Response):
auth_type=UserAuthTypes.GITHUB, auth_type=UserAuthTypes.GITHUB,
avatar=gzipped_user_avatar(), avatar=gzipped_user_avatar(),
) )
# skipcq: PYL-W0703
except Exception as e: except Exception as e:
if type(e) == asyncpg.exceptions.UniqueViolationError: if type(e) is asyncpg.exceptions.UniqueViolationError:
error = True error = True
counter = 1 counter = 1
while error: while error:
+2 -1
View File
@@ -90,8 +90,9 @@ async def auth(request: Request, response: Response):
google_uid=user_data.sub, google_uid=user_data.sub,
avatar=gzipped_user_avatar(), avatar=gzipped_user_avatar(),
) )
# skipcq: PYL-W0703
except Exception as e: except Exception as e:
if type(e) == asyncpg.exceptions.UniqueViolationError: if type(e) is asyncpg.exceptions.UniqueViolationError:
error = True error = True
counter = 1 counter = 1
while error: while error:
+1
View File
@@ -80,6 +80,7 @@ async def get_customized_avatar(
clothe_color=clothe_color, clothe_color=clothe_color,
clothe_graphic_type=clothe_graphic_type, clothe_graphic_type=clothe_graphic_type,
).render_svg() ).render_svg()
# skipcq: PY-W0069
# print(f"skin_color: {len(AvatarItemsAsList.skin_color)},") # print(f"skin_color: {len(AvatarItemsAsList.skin_color)},")
# print(f"hair_color: {len(AvatarItemsAsList.hair_color)},") # print(f"hair_color: {len(AvatarItemsAsList.hair_color)},")
# print(f"facial_hair_type: {len(AvatarItemsAsList.facial_hair_type)},") # print(f"facial_hair_type: {len(AvatarItemsAsList.facial_hair_type)},")
+1 -1
View File
@@ -109,7 +109,7 @@ button_to_index_map = {"b": 0, "g": 1, "y": 2, "r": 3}
@router.websocket("/socket/{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:
await ws.close(code=status.WS_1001_GOING_AWAY) await ws.close(code=status.WS_1001_GOING_AWAY)
print("Client {} already exists.".format(id)) print("Client {} already exists.".format(id))
return return
+1 -1
View File
@@ -26,7 +26,7 @@ def generate_code() -> str:
"r", "r",
] # Capital stands for long press, lowercase letter for short press ] # Capital stands for long press, lowercase letter for short press
resulting_code = "" resulting_code = ""
for i in range(specified_length): for _ in range(specified_length):
resulting_code += random.choice(buttons) resulting_code += random.choice(buttons)
return resulting_code return resulting_code
-3
View File
@@ -106,9 +106,6 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
if quiz_input.cover_image == "": if quiz_input.cover_image == "":
quiz_input.cover_image = None quiz_input.cover_image = None
# if quiz_input.background_image is None and old_quiz_data.background_image is not None:
# mark_image_for_deletion(quiz_input.background_image)
if quiz_input.cover_image is not None and not check_image_string(quiz_input.cover_image)[0]: if quiz_input.cover_image is not None and not check_image_string(quiz_input.cover_image)[0]:
raise HTTPException(status_code=400, detail="image url is not valid") raise HTTPException(status_code=400, detail="image url is not valid")
+1 -2
View File
@@ -46,9 +46,8 @@ async def export_quiz(quiz_id: uuid.UUID, user: User = Depends(get_current_user)
quiz_dict["updated_at"] = quiz_dict["updated_at"].isoformat() quiz_dict["updated_at"] = quiz_dict["updated_at"].isoformat()
quiz_json = json.dumps(quiz_dict) quiz_json = json.dumps(quiz_dict)
bin_data = gzip.compress(quiz_json.encode("utf-8"), compresslevel=9) bin_data = gzip.compress(quiz_json.encode("utf-8"), compresslevel=9)
# bin_data = quiz_json.encode("utf-8")
bin_data = bin_data + quiz_delimiter bin_data = bin_data + quiz_delimiter
for image_key in image_urls.keys(): for image_key in image_urls:
bin_data = bin_data + image_delimiter + str(image_key).encode("utf-8") + image_index_delimiter bin_data = bin_data + image_delimiter + str(image_key).encode("utf-8") + image_index_delimiter
image_data = None image_data = None
async with ClientSession() as session, session.get( async with ClientSession() as session, session.get(
-8
View File
@@ -134,8 +134,6 @@ async def get_live_game_data(
@router.get("/user_count") @router.get("/user_count")
async def get_game_user_count(game_pin: str, api_key: str, as_string: bool = False, as_array: bool = False): async def get_game_user_count(game_pin: str, api_key: str, as_string: bool = False, as_array: bool = False):
# if redis_res is None:
# raise HTTPException(status_code=404, detail="Game not found")
user_id = await check_api_key(api_key) user_id = await check_api_key(api_key)
redis_res = await redis.get(f"game_session:{game_pin}") redis_res = await redis.get(f"game_session:{game_pin}")
if redis_res is None: if redis_res is None:
@@ -149,12 +147,6 @@ async def get_game_user_count(game_pin: str, api_key: str, as_string: bool = Fal
return {"players": {"count": player_count}} return {"players": {"count": player_count}}
# class _LivePlayersReturn(BaseModel):
# # players: list[GamePlayer | None]
# answers: list[GameAnswer1 | None]
# players: list[GamePlayer | None]
@router.get( @router.get(
"/players", "/players",
) )
+1 -1
View File
@@ -149,7 +149,7 @@ class StepInput(BaseModel):
@router.post("/step/{step_id}") @router.post("/step/{step_id}")
async def step_1(session_id: str, data: StepInput, request: Request, response: Response, step_id: int): async def step_1_endpoint(session_id: str, data: StepInput, request: Request, response: Response, step_id: int):
if step_id < 0 or step_id > 2: if step_id < 0 or step_id > 2:
raise HTTPException(status_code=401) raise HTTPException(status_code=401)
redis_res = await redis.get(f"login_session:{session_id}") redis_res = await redis.get(f"login_session:{session_id}")
+1 -1
View File
@@ -83,7 +83,7 @@ def generate_code() -> str:
"r", "r",
] # Capital stands for long press, lowercase letter for short press ] # Capital stands for long press, lowercase letter for short press
resulting_code = "" resulting_code = ""
for i in range(specified_length): for _ in range(specified_length):
resulting_code += random.choice(buttons) resulting_code += random.choice(buttons)
return resulting_code return resulting_code
+1
View File
@@ -49,6 +49,7 @@ async def set_note(id: UUID, data: _SetNoteInput, user: User = Depends(get_curre
return await res.update() return await res.update()
# skipcq: PYL-W0105
""" """
@router.get("/export/{result_id}", response_class=StreamingResponse) @router.get("/export/{result_id}", response_class=StreamingResponse)
async def export_result(result_id: UUID, user: User = Depends(get_current_user)): async def export_result(result_id: UUID, user: User = Depends(get_current_user)):
+4 -1
View File
@@ -197,6 +197,10 @@ async def update_image_data(
file_data = await StorageItem.objects.get_or_none(id=file_id, user=user, deleted_at=None) file_data = await StorageItem.objects.get_or_none(id=file_id, user=user, deleted_at=None)
if file_data is None: if file_data is None:
raise HTTPException(status_code=404, detail="File not found") raise HTTPException(status_code=404, detail="File not found")
if data.alt_text == "":
data.alt_text = None
if data.filename == "":
data.filename = None
file_data.filename = data.filename file_data.filename = data.filename
file_data.alt_text = data.alt_text file_data.alt_text = data.alt_text
await file_data.update() await file_data.update()
@@ -221,7 +225,6 @@ async def list_images(
raise HTTPException(status_code=404, detail="No items found") raise HTTPException(status_code=404, detail="No items found")
return_items: list[PrivateStorageItem] = [] return_items: list[PrivateStorageItem] = []
for item in storage_items: for item in storage_items:
# print(item.quizzes)
return_items.append(PrivateStorageItem.from_db_model(item)) return_items.append(PrivateStorageItem.from_db_model(item))
return return_items return return_items
-1
View File
@@ -79,7 +79,6 @@ async def create_user(user: RouteUser, background_task: BackgroundTasks) -> User
if len(user.username) == 32: if len(user.username) == 32:
return JSONResponse({"details": "Username mustn't be 32 characters long"}, 400) return JSONResponse({"details": "Username mustn't be 32 characters long"}, 400)
await user.save() await user.save()
# print(settings.skip_email_verification)
if settings.skip_email_verification: if settings.skip_email_verification:
user.verify_key = None user.verify_key = None
user.verified = True user.verified = True
+2 -5
View File
@@ -253,7 +253,7 @@ class ReturnQuestion(QuizQuestion):
if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswerWithoutSolution: if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswerWithoutSolution:
raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE") raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE")
if values["type"] == QuizQuestionType.VOTING and type(v[0]) != VotingQuizAnswer: if values["type"] == QuizQuestionType.VOTING and type(v[0]) != VotingQuizAnswer:
# print("Answer must be from type VotingQuizAnswer if type is VOTING") # skipcq: PTC-W0047
pass pass
return v return v
@@ -262,7 +262,6 @@ class ReturnQuestion(QuizQuestion):
async def set_question_number(sid, data: str): async def set_question_number(sid, data: str):
# data is just a number (as a str) of the question # data is just a number (as a str) of the question
session = await sio.get_session(sid) session = await sio.get_session(sid)
# print("set_question_number", data, session)
if session["admin"]: if session["admin"]:
game_pin = session["game_pin"] game_pin = session["game_pin"]
game_data = PlayGame.parse_raw(await redis.get(f"game:{session['game_pin']}")) game_data = PlayGame.parse_raw(await redis.get(f"game:{session['game_pin']}"))
@@ -286,7 +285,6 @@ async def set_question_number(sid, data: str):
temp_return["type"] = game_data.questions[int(float(data))].type temp_return["type"] = game_data.questions[int(float(data))].type
if temp_return["type"] == QuizQuestionType.ORDER: if temp_return["type"] == QuizQuestionType.ORDER:
random.shuffle(temp_return["answers"]) random.shuffle(temp_return["answers"])
# print("emitting")
await sio.emit( await sio.emit(
"set_question_number", "set_question_number",
{ {
@@ -375,8 +373,6 @@ async def submit_answer(sid: str, data: dict):
answers = await redis.get(f"game_session:{session['game_pin']}:{data.question_index}") answers = await redis.get(f"game_session:{session['game_pin']}:{data.question_index}")
diff = (time_q_started - now).total_seconds() * 1000 # - timedelta(milliseconds=latency) diff = (time_q_started - now).total_seconds() * 1000 # - timedelta(milliseconds=latency)
# print(abs(diff) - latency, latency, abs(diff))
score = 0 score = 0
if answer_right: if answer_right:
score = calculate_score( score = calculate_score(
@@ -394,6 +390,7 @@ async def submit_answer(sid: str, data: dict):
answers, game_pin=session["game_pin"], data=answer_data, q_index=int(float(data.question_index)) answers, game_pin=session["game_pin"], data=answer_data, q_index=int(float(data.question_index))
) )
player_count = await redis.scard(f"game_session:{session['game_pin']}:players") player_count = await redis.scard(f"game_session:{session['game_pin']}:players")
await sio.emit("player_answer", {})
if len(answers.__root__) == player_count: if len(answers.__root__) == player_count:
# await sio.emit( # await sio.emit(
# "question_results", # "question_results",
+3 -2
View File
@@ -27,9 +27,10 @@ class LocalStorage:
except FileNotFoundError: except FileNotFoundError:
yield None yield None
async def upload(self, file_name: str, data: BinaryIO, mime_type: str | None = None) -> None: # skipcq: PYL-W0613
async def upload(self, file_name: str, file: BinaryIO, mime_type: str | None = None) -> None:
async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode="wb") as f: async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode="wb") as f:
await aioshutil_copyfileobj(data, f) await aioshutil_copyfileobj(file, f)
async def delete(self, file_names: [str]) -> None: async def delete(self, file_names: [str]) -> None:
for i in file_names: for i in file_names:
+1 -3
View File
@@ -103,15 +103,13 @@ class S3Storage:
+ "Signature=" + "Signature="
+ signature + signature
) )
# if expiry is not None:
# authorization_header += f", Expires={expiry}"
# Send the request with the authorization header # Send the request with the authorization header
headers = {"x-amz-date": amz_date, "Authorization": authorization_header} headers = {"x-amz-date": amz_date, "Authorization": authorization_header}
request_url = self.base_url + path + "?" + canonical_querystring request_url = self.base_url + path + "?" + canonical_querystring
return headers, request_url return headers, request_url
# skipcq: PYL-W0613
async def upload(self, file: BinaryIO, file_name: str, mime_type: str | None = "application/octet-stream") -> None: async def upload(self, file: BinaryIO, file_name: str, mime_type: str | None = "application/octet-stream") -> None:
headers, url = self._generate_aws_signature_v4(method="PUT", path=f"/{file_name}") headers, url = self._generate_aws_signature_v4(method="PUT", path=f"/{file_name}")
async with ClientSession() as session, session.put(url, headers=headers, data=file) as resp: async with ClientSession() as session, session.put(url, headers=headers, data=file) as resp:
+4 -4
View File
@@ -16,6 +16,7 @@ from classquiz.storage.errors import DeletionFailedError
from thumbhash import image_to_thumbhash from thumbhash import image_to_thumbhash
# skipcq: PYL-W0613
async def clean_editor_images_up(ctx): async def clean_editor_images_up(ctx):
print("Cleaning images up") print("Cleaning images up")
edit_sessions = await redis.smembers("edit_sessions") edit_sessions = await redis.smembers("edit_sessions")
@@ -52,10 +53,12 @@ async def calculate_hash(ctx, file_id_as_str: str):
try: try:
if 0 < file_data.size < 20_970_000: # greater than 0 but smaller than 20mbytes if 0 < file_data.size < 20_970_000: # greater than 0 but smaller than 20mbytes
file_data.thumbhash = image_to_thumbhash(file) file_data.thumbhash = image_to_thumbhash(file)
# skipcq: PYL-W0703
except Exception: except Exception:
pass pass
hash_obj = xxhash.xxh3_128() hash_obj = xxhash.xxh3_128()
# skipcq: PY-W0069
# assert hash_obj.block_size == 64 # assert hash_obj.block_size == 64
while chunk := file.read(6400): while chunk := file.read(6400):
hash_obj.update(chunk) hash_obj.update(chunk)
@@ -70,6 +73,7 @@ async def calculate_hash(ctx, file_id_as_str: str):
await user.update() await user.update()
# skipcq: PYL-W0613
async def quiz_update(ctx, old_quiz: Quiz, quiz_id: uuid.UUID): async def quiz_update(ctx, old_quiz: Quiz, quiz_id: uuid.UUID):
new_quiz: Quiz = await Quiz.objects.get(id=quiz_id) new_quiz: Quiz = await Quiz.objects.get(id=quiz_id)
old_images = extract_image_ids_from_quiz(old_quiz) old_images = extract_image_ids_from_quiz(old_quiz)
@@ -83,8 +87,6 @@ async def quiz_update(ctx, old_quiz: Quiz, quiz_id: uuid.UUID):
removed_images = list(set(old_images) - set(new_images)) removed_images = list(set(old_images) - set(new_images))
added_images = list(set(new_images) - set(old_images)) added_images = list(set(new_images) - set(old_images))
change_made = False change_made = False
# print("added:", added_images)
# print("removed:", removed_images)
for image in removed_images: for image in removed_images:
if "--" in image: if "--" in image:
await storage.delete([image]) await storage.delete([image])
@@ -92,7 +94,6 @@ async def quiz_update(ctx, old_quiz: Quiz, quiz_id: uuid.UUID):
item = await StorageItem.objects.get_or_none(id=uuid.UUID(image)) item = await StorageItem.objects.get_or_none(id=uuid.UUID(image))
if item is None: if item is None:
continue continue
# print("removed item")
try: try:
await new_quiz.storageitems.remove(item) await new_quiz.storageitems.remove(item)
except ormar.exceptions.NoMatch: except ormar.exceptions.NoMatch:
@@ -103,7 +104,6 @@ async def quiz_update(ctx, old_quiz: Quiz, quiz_id: uuid.UUID):
item = await StorageItem.objects.get_or_none(id=uuid.UUID(image)) item = await StorageItem.objects.get_or_none(id=uuid.UUID(image))
if item is None: if item is None:
continue continue
# print("added item")
await new_quiz.storageitems.add(item) await new_quiz.storageitems.add(item)
change_made = True change_made = True
if change_made: if change_made:
+1 -1
View File
@@ -8,7 +8,7 @@
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json", "check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . '!src/lib/i18n/locales/*.json' && eslint --ignore-path .gitignore .", "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . '!src/lib/i18n/locales/*.json' '!pnpm-lock.yaml' && eslint --ignore-path .gitignore .",
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. . '!src/lib/i18n/locales/*.json'", "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. . '!src/lib/i18n/locales/*.json'",
"run:prod": "node index.js", "run:prod": "node index.js",
"translations-scan": "i18next-scanner --config i18next-scanner.config.engine.cjs src/**/*.svelte" "translations-scan": "i18next-scanner --config i18next-scanner.config.engine.cjs src/**/*.svelte"
+25 -13
View File
@@ -31,6 +31,7 @@
let shown_question_now: number; let shown_question_now: number;
let final_results_clicked = false; let final_results_clicked = false;
let timer_interval; let timer_interval;
let answer_count = 0;
export let control_visible: boolean; export let control_visible: boolean;
export let player_scores; export let player_scores;
@@ -53,6 +54,7 @@
shown_question_now = data.question_index; shown_question_now = data.question_index;
timer_res = quiz_data.questions[data.question_index].time; timer_res = quiz_data.questions[data.question_index].time;
selected_question = selected_question + 1; selected_question = selected_question + 1;
answer_count = 0;
timer(timer_res); timer(timer_res);
}); });
const get_question_results = () => { const get_question_results = () => {
@@ -95,6 +97,10 @@
} }
}); });
socket.on('player_answer', (_) => {
answer_count += 1;
});
const timer = (time: string) => { const timer = (time: string) => {
let seconds = Number(time); let seconds = Number(time);
timer_interval = setInterval(() => { timer_interval = setInterval(() => {
@@ -179,13 +185,13 @@
{/if} {/if}
{:else} {:else}
<!-- <button <!-- <button
on:click={() => { on:click={() => {
set_question_number(selected_question + 1); set_question_number(selected_question + 1);
}} }}
class='admin-button' class='admin-button'
>Next Question ({selected_question + 2} >Next Question ({selected_question + 2}
) )
</button>--> </button>-->
{/if} {/if}
</div> </div>
</div> </div>
@@ -215,12 +221,18 @@
{@html quiz_data.questions[selected_question].question} {@html quiz_data.questions[selected_question].question}
</h1> </h1>
<!-- <span class='text-center py-2 text-lg'>{$t('admin_page.time_left')}: {timer_res}</span>--> <!-- <span class='text-center py-2 text-lg'>{$t('admin_page.time_left')}: {timer_res}</span>-->
<div class="mx-auto my-2"> <div class="grid grid-cols-3 my-2">
<CircularTimer <span />
bind:text={timer_res} <div class="m-auto">
bind:progress={circular_progress} <CircularTimer
color="#ef4444" bind:text={timer_res}
/> bind:progress={circular_progress}
color="#ef4444"
/>
</div>
<p class="m-auto text-3xl">
{$t('admin_page.answers_submitted', { answer_count: answer_count })}
</p>
</div> </div>
</div> </div>
{#if quiz_data.questions[selected_question].image !== null} {#if quiz_data.questions[selected_question].image !== null}
@@ -4,6 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
// Stolen from https://svelte.dev/repl/c6a402704224403f96a3db56c2f48dfc?version=3.55.0 // Stolen from https://svelte.dev/repl/c6a402704224403f96a3db56c2f48dfc?version=3.55.0
// skipcq: JS-0119
let intersectionObserver; let intersectionObserver;
function ensureIntersectionObserver() { function ensureIntersectionObserver() {
+2
View File
@@ -42,10 +42,12 @@ export const mint = async (
// eslint-disable-next-line no-constant-condition // eslint-disable-next-line no-constant-condition
while (true) { while (true) {
// skipcq: JS-0003
const data = new TextEncoder().encode(`${challenge}:${counter.toString(16)}`); const data = new TextEncoder().encode(`${challenge}:${counter.toString(16)}`);
const hashBuffer = await crypto.subtle.digest('SHA-1', data); const hashBuffer = await crypto.subtle.digest('SHA-1', data);
const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashArray = Array.from(new Uint8Array(hashBuffer));
const digest = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); const digest = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
// skipcq: JS-0050
if (digest.slice(0, hex_digits) == zeros) { if (digest.slice(0, hex_digits) == zeros) {
result = counter.toString(16); result = counter.toString(16);
break; break;
+10 -9
View File
@@ -10,8 +10,8 @@ export const invertColor = (hexTripletColor: string): string => {
let color_int = parseInt(color, 16); // convert to integer let color_int = parseInt(color, 16); // convert to integer
color_int = 0xffffff ^ color_int; // invert three bytes color_int = 0xffffff ^ color_int; // invert three bytes
color = color_int.toString(16); // convert to hex color = color_int.toString(16); // convert to hex
color = ('000000' + color).slice(-6); // pad with leading zeros color = `000000${color}`.slice(-6); // pad with leading zeros
color = '#' + color; // prepend # color = `#${color}`; // prepend #
return color; return color;
}; };
@@ -20,13 +20,6 @@ export const calculate_score = (q_time: number, time_taken: number): number => {
}; };
export type RGB = [number, number, number]; export type RGB = [number, number, number];
export const getContrast = (foregroundColor: RGB, backgroundColor: RGB) => {
const foregroundLuminance = getLuminance(foregroundColor);
const backgroundLuminance = getLuminance(backgroundColor);
return backgroundLuminance < foregroundLuminance
? (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05)
: (foregroundLuminance + 0.05) / (backgroundLuminance + 0.05);
};
export const getLuminance = (rgb: RGB): number => { export const getLuminance = (rgb: RGB): number => {
const [r, g, b] = rgb.map((v) => { const [r, g, b] = rgb.map((v) => {
@@ -36,6 +29,14 @@ export const getLuminance = (rgb: RGB): number => {
return r * 0.2126 + g * 0.7152 + b * 0.0722; return r * 0.2126 + g * 0.7152 + b * 0.0722;
}; };
export const getContrast = (foregroundColor: RGB, backgroundColor: RGB) => {
const foregroundLuminance = getLuminance(foregroundColor);
const backgroundLuminance = getLuminance(backgroundColor);
return backgroundLuminance < foregroundLuminance
? (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05)
: (foregroundLuminance + 0.05) / (backgroundLuminance + 0.05);
};
export const getRgbColorFromHex = (hex: string): RGB => { export const getRgbColorFromHex = (hex: string): RGB => {
hex = hex.slice(1); hex = hex.slice(1);
const value = parseInt(hex, 16); const value = parseInt(hex, 16);
+7 -3
View File
@@ -181,7 +181,10 @@
"progress": "Progress", "progress": "Progress",
"speed": "Speed", "speed": "Speed",
"upload": "Upload", "upload": "Upload",
"files_library": "Files Library" "files_library": "Files Library",
"answer_plural": "Answers",
"yes": "Yes",
"no": "no"
}, },
"editor": { "editor": {
"time_in_seconds": "Time in seconds", "time_in_seconds": "Time in seconds",
@@ -229,7 +232,8 @@
"next_question": "Next Question ({{question}})", "next_question": "Next Question ({{question}})",
"show_results": "Show results", "show_results": "Show results",
"stop_time_and_solutions": "Stop time and show solutions", "stop_time_and_solutions": "Stop time and show solutions",
"enter_answer_into_field": "Enter your answer into the input field!" "enter_answer_into_field": "Enter your answer into the input field!",
"answers_submitted": "{{answer_count}} Answers submitted"
}, },
"password_reset_page": { "password_reset_page": {
"reset_password": "Reset password" "reset_password": "Reset password"
@@ -407,7 +411,7 @@
"caption": "Caption: {{caption}}", "caption": "Caption: {{caption}}",
"filename": "Filename: {{filename}}", "filename": "Filename: {{filename}}",
"uploaded": "Uploaded: {{date}}", "uploaded": "Uploaded: {{date}}",
"Imported": "Imported: {{yes_or_no}}", "imported": "Imported: {{yes_or_no}}",
"edit_details": "Edit details", "edit_details": "Edit details",
"delete_image": "Delete image", "delete_image": "Delete image",
"edit_the_image": "Edit the image", "edit_the_image": "Edit the image",
+36 -2
View File
@@ -175,7 +175,13 @@
"quiz": "Cuestionario", "quiz": "Cuestionario",
"quiztivity": "Quiztivity", "quiztivity": "Quiztivity",
"next": "Siguiente", "next": "Siguiente",
"check_choice": "Comprobar la elección" "check_choice": "Comprobar la elección",
"video": "Vídeo",
"library": "Biblioteca",
"progress": "Progreso",
"speed": "Velocidad",
"upload": "Subir",
"files_library": "Biblioteca de archivos"
}, },
"admin_page": { "admin_page": {
"export_results": "Exportar resultados", "export_results": "Exportar resultados",
@@ -277,7 +283,11 @@
"search_for_own_quizzes": "Busca tus propios quizzes" "search_for_own_quizzes": "Busca tus propios quizzes"
}, },
"uploader": { "uploader": {
"add_image": "Añadir una imagen" "add_image": "Añadir una imagen",
"select_upload_type": "Seleccione el tipo de carga",
"upload_a_video": "Cargar un vídeo",
"upload_video_popup_notice": "La ventana emergente está abierta; échale un vistazo para obtener más información",
"upload_video": "Subir un vídeo"
}, },
"avatar_settings": { "avatar_settings": {
"skin_color": "Color de la piel", "skin_color": "Color de la piel",
@@ -384,5 +394,29 @@
"popover": { "popover": {
"copied_to_clipboard": "¡Copiado al portapapeles!" "copied_to_clipboard": "¡Copiado al portapapeles!"
} }
},
"public_user_page": {
"no_original_quizzes": "Este usuario no tiene cuestionarios originales",
"joined_on": "Ingresó el {{date}}"
},
"file_dashboard": {
"not_available": "No disponible",
"missing": "¡DESAPARECIDO!",
"unset": "Desactivar",
"size": "Tamaño: {{size}} Mib",
"uploaded": "Subido: {{date}}",
"Imported": "Importado: {{yes_or_no}}",
"edit_details": "Editar los detalles",
"delete_image": "Borrar la imagen",
"edit_the_image": "Editar la imagen",
"filename_word": "Nombre del archivo",
"alt_text": "Texto alternativo / Leyenda",
"caption": "Leyenda: {{caption}}",
"filename": "Nombre del archivo: {{filename}}",
"storage_usage": "Has utilizado {{used}} Mib de {{total}} MiB del almacenamiento. Eso equivale al {{percent}}% de tu almacenamiento."
},
"video_uploader": {
"time_elapsed": "Tiempo transcurrido",
"time_remaining": "Duración restante"
} }
} }
+1 -6
View File
@@ -4,6 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
// skipcq: JS-C1003
import * as yup from 'yup'; import * as yup from 'yup';
export const ABCDQuestionSchema = yup export const ABCDQuestionSchema = yup
@@ -69,23 +70,17 @@ export const dataSchema = yup.object({
answers: yup.lazy((v) => { answers: yup.lazy((v) => {
if (Array.isArray(v)) { if (Array.isArray(v)) {
if (typeof v[0].right === 'boolean') { if (typeof v[0].right === 'boolean') {
console.log('ABCDQuestionSchema');
return ABCDQuestionSchema; return ABCDQuestionSchema;
} else if (typeof v[0].case_sensitive === 'boolean') { } else if (typeof v[0].case_sensitive === 'boolean') {
console.log('TextQuestionSchema');
return TextQuestionSchema; return TextQuestionSchema;
} else if (v[0].id !== undefined) { } else if (v[0].id !== undefined) {
console.log('OrderQuestionSchema');
return VotingQuestionSchema; return VotingQuestionSchema;
} else if (v[0].answer !== undefined) { } else if (v[0].answer !== undefined) {
console.log('VotingQuestionSchema');
return VotingQuestionSchema; return VotingQuestionSchema;
} }
} else if (typeof v === 'string' || v instanceof String) { } else if (typeof v === 'string' || v instanceof String) {
console.log('StringQuestionSchema');
return yup.string().required("The slide mustn't be empty").nullable(); return yup.string().required("The slide mustn't be empty").nullable();
} else { } else {
console.log('RangeQuestionSchema');
return RangeQuestionSchema; return RangeQuestionSchema;
} }
}) })
+1 -3
View File
@@ -7,7 +7,5 @@ import type { PageLoad } from './$types';
export const load = (async ({ fetch }) => { export const load = (async ({ fetch }) => {
const res = await fetch('/api/v1/storage/list'); const res = await fetch('/api/v1/storage/list');
if (res.ok) { return { files: await res.json() };
return { files: await res.json() };
}
}) satisfies PageLoad; }) satisfies PageLoad;
@@ -86,18 +86,20 @@ def upgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
session = Session(bind=conn) session = Session(bind=conn)
all_cover_images = session.execute("SELECT cover_image, id from quiz where cover_image is not null;") all_cover_images = session.execute("SELECT cover_image, id from quiz where cover_image is not null;")
stmt = text("UPDATE quiz SET cover_image = :n WHERE id=:i;")
for cover_image, id in all_cover_images: for cover_image, id in all_cover_images:
try: try:
new_cover_image = re.search(magic_regex, cover_image).group(1) new_cover_image = re.search(magic_regex, cover_image).group(1)
session.execute(f"UPDATE quiz SET cover_image = '{new_cover_image}' WHERE id='{id}';") session.execute(stmt, {"n": new_cover_image, "i": id})
except AttributeError: except AttributeError:
continue continue
all_background_images = session.execute("SELECT background_image, id from quiz where background_image is not null;") all_background_images = session.execute("SELECT background_image, id from quiz where background_image is not null;")
stmt = text("UPDATE quiz SET cover_image = :n WHERE id=:i;")
for bg_image, id in all_background_images: for bg_image, id in all_background_images:
try: try:
new_bg_image = re.search(magic_regex, bg_image).group(1) new_bg_image = re.search(magic_regex, bg_image).group(1)
session.execute(f"UPDATE quiz SET cover_image = '{new_bg_image}' WHERE id='{id}';") session.execute(stmt, {"n": new_bg_image, "i": id})
except AttributeError: except AttributeError:
continue continue
s = text("UPDATE quiz SET questions = :q WHERE id=:i;") s = text("UPDATE quiz SET questions = :q WHERE id=:i;")
@@ -138,23 +140,26 @@ def downgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
session = Session(bind=conn) session = Session(bind=conn)
all_cover_images = session.execute("SELECT cover_image, id from quiz where cover_image is not null;") all_cover_images = session.execute("SELECT cover_image, id from quiz where cover_image is not null;")
stmt = text("UPDATE quiz SET cover_image = :n WHERE id=:i;")
for cover_image, id in all_cover_images: for cover_image, id in all_cover_images:
new_cover_image = f"{settings.root_address}/api/v1/storage/download/{cover_image}" new_cover_image = f"{settings.root_address}/api/v1/storage/download/{cover_image}"
# print(new_cover_image, id) # print(new_cover_image, id)
session.execute(f"UPDATE quiz SET cover_image = '{new_cover_image}' WHERE id='{id}';") session.execute(stmt, {"n": new_cover_image, "i": id})
all_background_images = session.execute("SELECT background_image, id from quiz where background_image is not null;") all_background_images = session.execute("SELECT background_image, id from quiz where background_image is not null;")
stmt = text("UPDATE quiz SET cover_image = :n WHERE id=:i;")
for bg_image, id in all_background_images: for bg_image, id in all_background_images:
new_bg_image = f"{settings.root_address}/api/v1/storage/download/{bg_image}" new_bg_image = f"{settings.root_address}/api/v1/storage/download/{bg_image}"
# print(new_cover_image, id) # print(new_cover_image, id)
session.execute(f"UPDATE quiz SET cover_image = '{new_bg_image}' WHERE id='{id}';") session.execute(stmt, {"n": new_bg_image, "i": id})
all_questions = session.execute("SELECT questions, id from quiz;") all_questions = session.execute("SELECT questions, id from quiz;")
stmt = text("UPDATE quiz SET questions = :r WHERE id=:i;")
question_image_regex = r"(?=.{36}--.{36})" question_image_regex = r"(?=.{36}--.{36})"
for question, id in all_questions: for question, id in all_questions:
question_as_json = json.dumps(question) question_as_json = json.dumps(question)
result = re.sub(question_image_regex, f"{settings.root_address}/api/v1/storage/download/", question_as_json) result = re.sub(question_image_regex, f"{settings.root_address}/api/v1/storage/download/", question_as_json)
session.execute(f"UPDATE quiz SET questions = '{result}' WHERE id='{id}';") session.execute(stmt, {"r": result, "i": id})
## ADDED STORAGE ITEM ## ADDED STORAGE ITEM
op.drop_table("storageitems_quiztivitys") op.drop_table("storageitems_quiztivitys")
+2 -1
View File
@@ -4,6 +4,7 @@
import random import random
import time import time
import sys
import socketio import socketio
@@ -63,4 +64,4 @@ if __name__ == "__main__":
try: try:
__main__() __main__()
except KeyboardInterrupt: except KeyboardInterrupt:
exit(1) sys.exit(1)