From 2ab9d14d3e3e994f5129d3762a1c6fd2f1a2139d Mon Sep 17 00:00:00 2001
From: Mawoka
Date: Thu, 8 Sep 2022 17:47:29 +0200
Subject: [PATCH] :sparkles: Added cover-image (Closes #103)
---
classquiz/db/models.py | 3 ++
classquiz/kahoot_importer/import_quiz.py | 7 ++++
classquiz/routers/editor.py | 8 ++++
classquiz/routers/quiz.py | 16 ++++++++
frontend/src/lib/dashboard/main_slider.svelte | 24 ++++++++++--
frontend/src/lib/editor.svelte | 26 ++++++++++++-
frontend/src/lib/editor/card.svelte | 18 +--------
frontend/src/lib/editor/settings-card.svelte | 37 +++++++++++++++++++
frontend/src/lib/editor/uploader.svelte | 12 ++++--
frontend/src/lib/play/title.svelte | 8 ++++
frontend/src/lib/quiz_types.ts | 2 +
frontend/src/routes/+page.svelte | 6 ++-
frontend/src/routes/play/+page.svelte | 6 ++-
.../src/routes/view/[quiz_id]/+page.svelte | 12 +++++-
.../cda6903dfc0c_added_cover_image.py | 30 +++++++++++++++
15 files changed, 186 insertions(+), 29 deletions(-)
create mode 100644 migrations/versions/cda6903dfc0c_added_cover_image.py
diff --git a/classquiz/db/models.py b/classquiz/db/models.py
index 8ce627c..461e5aa 100644
--- a/classquiz/db/models.py
+++ b/classquiz/db/models.py
@@ -99,6 +99,7 @@ class QuizInput(BaseModel):
public: bool = False
title: str
description: str
+ cover_image: str | None
questions: list[QuizQuestion]
@@ -112,6 +113,7 @@ class Quiz(ormar.Model):
user_id: uuid.UUID = ormar.ForeignKey(User)
questions: Json[list[QuizQuestion]] = ormar.JSON(nullable=False)
imported_from_kahoot: Optional[bool] = ormar.Boolean(default=False, nullable=True)
+ cover_image: Optional[str] = ormar.Text(nullable=True, unique=False)
class Meta:
tablename = "quiz"
@@ -154,6 +156,7 @@ class PlayGame(BaseModel):
game_pin: str
started: bool = False
captcha_enabled: bool = False
+ cover_image: str | None
class GamePlayer(BaseModel):
diff --git a/classquiz/kahoot_importer/import_quiz.py b/classquiz/kahoot_importer/import_quiz.py
index 3e0b5f7..4475920 100644
--- a/classquiz/kahoot_importer/import_quiz.py
+++ b/classquiz/kahoot_importer/import_quiz.py
@@ -55,6 +55,12 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
image=image,
).dict()
)
+ cover = None
+ if quiz.kahoot.cover != "":
+ image_bytes = await _download_image(quiz.kahoot.cover)
+ image_name = f"{quiz_id}--{uuid.uuid4()}"
+ await storage.upload(file_name=image_name, file_data=image_bytes)
+ cover = f"{settings.root_address}/api/v1/storage/download/{image_name}"
quiz_data = Quiz(
id=quiz_id,
public=True,
@@ -65,6 +71,7 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
user_id=user.id,
questions=json.dumps(quiz_questions),
imported_from_kahoot=True,
+ cover_image=cover,
)
meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz_data)])
return await quiz_data.save()
diff --git a/classquiz/routers/editor.py b/classquiz/routers/editor.py
index a9cb010..4699061 100644
--- a/classquiz/routers/editor.py
+++ b/classquiz/routers/editor.py
@@ -157,6 +157,13 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
mark_image_for_deletion(question.image, i, old_quiz_data)
else:
raise HTTPException(status_code=400, detail="Image URL(s) aren't valid!")
+
+ if quiz_input.cover_image == "":
+ quiz_input.cover_image = None
+
+ if quiz_input.cover_image is not None and not bool(re.match(server_regex, quiz_input.cover_image)):
+ raise HTTPException(status_code=400, detail="image url is not valid")
+
if session_data.edit:
quiz = old_quiz_data
meilisearch.index(settings.meilisearch_index).update_documents([await get_meili_data(quiz)])
@@ -169,6 +176,7 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
quiz.description = quiz_input.description
quiz.updated_at = datetime.now()
quiz.questions = quiz_input.dict()["questions"]
+ quiz.cover_image = quiz_input.cover_image
for image in images_to_delete:
if image is not None:
try:
diff --git a/classquiz/routers/quiz.py b/classquiz/routers/quiz.py
index d80f27a..b2439d4 100644
--- a/classquiz/routers/quiz.py
+++ b/classquiz/routers/quiz.py
@@ -42,6 +42,11 @@ async def create_quiz_lol(quiz_input: QuizInput, user: User = Depends(get_curren
and not re.match(server_regex, question.image)
):
raise HTTPException(status_code=400, detail="image url is not valid")
+ if quiz_input.cover_image == "":
+ quiz_input.cover_image = None
+
+ if quiz_input.cover_image is not None and not bool(re.match(server_regex, quiz_input.cover_image)):
+ raise HTTPException(status_code=400, detail="image url is not valid")
quiz = Quiz(**quiz_input.dict(), user_id=user.id, id=uuid.uuid4())
await redis.delete("global_quiz_count")
if quiz_input.public:
@@ -102,6 +107,7 @@ async def start_quiz(quiz_id: str, captcha_enabled: bool = True, user: User = De
title=quiz.title,
description=quiz.description,
captcha_enabled=captcha_enabled,
+ cover_image=quiz.cover_image,
)
await redis.set(f"game:{str(game.game_pin)}", (game.json()), ex=18000)
return {**quiz.dict(exclude={"id"}), **game.dict(exclude={"questions"})}
@@ -156,6 +162,15 @@ async def update_quiz(quiz_id: str, quiz_input: QuizInput, user: User = Depends(
quiz_id = uuid.UUID(quiz_id)
except ValueError:
raise HTTPException(status_code=400, detail="badly formed quiz id")
+ ## Check Cover-Image
+
+ print(quiz_input.cover_image)
+ if quiz_input.cover_image == "":
+ quiz_input.cover_image = None
+
+ if quiz_input.cover_image is not None and not bool(re.match(server_regex, quiz_input.cover_image)):
+ raise HTTPException(status_code=400, detail="image url is not valid")
+
quiz = await Quiz.objects.get_or_none(id=quiz_id, user_id=user.id)
if quiz is None:
return JSONResponse(status_code=404, content={"detail": "quiz not found"})
@@ -168,6 +183,7 @@ async def update_quiz(quiz_id: str, quiz_input: QuizInput, user: User = Depends(
if not quiz.public and quiz_input.public:
meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)])
quiz.title = quiz_input.title
+ quiz.cover_image = quiz_input.cover_image
quiz.public = quiz_input.public
quiz.description = quiz_input.description
quiz.updated_at = datetime.now()
diff --git a/frontend/src/lib/dashboard/main_slider.svelte b/frontend/src/lib/dashboard/main_slider.svelte
index b95aef8..a8ea7c6 100644
--- a/frontend/src/lib/dashboard/main_slider.svelte
+++ b/frontend/src/lib/dashboard/main_slider.svelte
@@ -71,9 +71,25 @@
>
{quiz.title}
-
- {quiz.description}
-
+
+
+ {quiz.description}
+
+ {#if quiz.cover_image}
+
+
+

+
+
+ {/if}
+
+
+
{quiz.questions.length}
{$t('words.question', { count: quiz.questions.length })}
+
{#if quiz.public}
{#if selected_question === -1}
-
+
{:else}
-
+
{/if}
diff --git a/frontend/src/lib/editor/card.svelte b/frontend/src/lib/editor/card.svelte
index ab05e41..c2f4650 100644
--- a/frontend/src/lib/editor/card.svelte
+++ b/frontend/src/lib/editor/card.svelte
@@ -10,7 +10,6 @@
import { reach } from 'yup';
import { dataSchema } from '$lib/yupSchemas';
import Spinner from '../Spinner.svelte';
- import { mint } from '$lib/hashcash';
import { createTippy } from 'svelte-tippy';
import { getLocalization } from '$lib/i18n';
@@ -26,25 +25,10 @@
export let selected_question: number;
export let edit_id: string;
export let pow_data;
- let pow_salt: string;
+ export let pow_salt: string;
let uppyOpen = false;
- const computePOW = async (salt: string) => {
- if (pow_salt === undefined) {
- return;
- }
- console.log('Computing POW');
- pow_data = await mint(salt, 16, '', 8, false);
- pow_salt = undefined;
- return;
- };
-
- $: {
- pow_salt;
- computePOW(pow_salt);
- }
-
/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/
const correctTimeInput = (_) => {
let time = data.questions[selected_question].time;
diff --git a/frontend/src/lib/editor/settings-card.svelte b/frontend/src/lib/editor/settings-card.svelte
index f9f3363..6e3f2be 100644
--- a/frontend/src/lib/editor/settings-card.svelte
+++ b/frontend/src/lib/editor/settings-card.svelte
@@ -6,9 +6,16 @@
@@ -42,6 +49,36 @@
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 h-20 resize-none dark:bg-gray-500"
/>
+
+ {#if data.cover_image != undefined && data.cover_image !== ''}
+
+

{
+ data.cover_image = '';
+ }}
+ />
+
+ {:else if pow_data === undefined}
+
+
+
+ {:else}
+ {#await import('$lib/editor/uploader.svelte')}
+
+ {:then c}
+
+ {/await}
+ {/if}
+ How does ClassQuiz even work?
+
-
+
{$t('index_page.get_a_quiz')}
-
+
{:else if gameData !== undefined && question_index === ''}
-
+
{:else if gameMeta.started && gameData !== undefined && question_index !== '' && answer_results === undefined}
{#key unique}
{quiz.description}
+ {#if quiz.cover_image}
+
+
+

+
+
+ {/if}
@@ -98,7 +109,6 @@
{#if question.image}
- {$t('words.image')}:
{/if}
diff --git a/migrations/versions/cda6903dfc0c_added_cover_image.py b/migrations/versions/cda6903dfc0c_added_cover_image.py
new file mode 100644
index 0000000..76f54f8
--- /dev/null
+++ b/migrations/versions/cda6903dfc0c_added_cover_image.py
@@ -0,0 +1,30 @@
+"""added cover_image
+
+Revision ID: cda6903dfc0c
+Revises: 3f63c0130bce
+Create Date: 2022-09-08 16:40:01.675020
+
+"""
+from alembic import op
+import sqlalchemy as sa
+import ormar
+
+
+# revision identifiers, used by Alembic.
+revision = "cda6903dfc0c"
+down_revision = "3f63c0130bce"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_unique_constraint(None, "instance_data", ["instance_id"])
+ op.add_column("quiz", sa.Column("cover_image", sa.Text(), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("quiz", "cover_image")
+ # ### end Alembic commands ###