✨ Migrated from image urls to image uuids
This commit is contained in:
@@ -398,3 +398,35 @@ class StorageItem(ormar.Model):
|
||||
tablename = "storage_items"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
|
||||
class PublicStorageItem(BaseModel):
|
||||
id: uuid.UUID
|
||||
uploaded_at: datetime
|
||||
mime_type: str
|
||||
hash: str | None
|
||||
size: int
|
||||
deleted_at: datetime | None
|
||||
alt_text: str | None
|
||||
filename: str | None
|
||||
|
||||
@classmethod
|
||||
def from_db_model(cls, data: StorageItem):
|
||||
hash_data = None
|
||||
if data.hash is not None:
|
||||
hash_data = data.hash.hex()
|
||||
return cls(
|
||||
id=data.id,
|
||||
uploaded_at=data.uploaded_at,
|
||||
mime_type=data.mime_type,
|
||||
hash=hash_data,
|
||||
size=data.size,
|
||||
deleted_at=data.deleted_at,
|
||||
alt_text=data.alt_text,
|
||||
filename=data.filename,
|
||||
)
|
||||
|
||||
|
||||
class UpdateStorageItem(BaseModel):
|
||||
filename: str
|
||||
alt_text: str
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import asyncio
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
import ormar.exceptions
|
||||
@@ -175,3 +176,23 @@ def check_hashcash(data: str, input_data: str, claim_in: Optional[str] = "19") -
|
||||
return False
|
||||
some_error = [version == "1", claim == claim_in, res == input_data, ext == ""]
|
||||
return all(el is True for el in some_error)
|
||||
|
||||
|
||||
def check_image_string(image: str) -> (bool, uuid.UUID | None):
|
||||
# Valid formats: {uuid} and {uuid}--{uuid}
|
||||
try:
|
||||
parsed_uuid = uuid.UUID(image)
|
||||
return True, parsed_uuid
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
split_image = image.split("--")
|
||||
if len(split_image) != 2:
|
||||
return False, None
|
||||
|
||||
try:
|
||||
uuid.UUID(split_image[0])
|
||||
uuid.UUID(split_image[1])
|
||||
return True, None
|
||||
except ValueError:
|
||||
return False, None
|
||||
|
||||
@@ -48,8 +48,8 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
|
||||
if q.image is not None and q.image != "":
|
||||
image_bytes = await _download_image(q.image)
|
||||
image_name = f"{quiz_id}--{uuid.uuid4()}"
|
||||
image = await storage.upload(file_name=image_name, file_data=image_bytes)
|
||||
image = f"{settings.root_address}/api/v1/storage/download/{image_name}"
|
||||
await storage.upload(file_name=image_name, file_data=image_bytes)
|
||||
image = image_name
|
||||
for i, a in enumerate(q.choices):
|
||||
answers.append(
|
||||
(
|
||||
@@ -74,7 +74,7 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
|
||||
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}"
|
||||
cover = image_name
|
||||
quiz_data = Quiz(
|
||||
id=quiz_id,
|
||||
public=True,
|
||||
|
||||
+10
-49
@@ -4,24 +4,22 @@
|
||||
|
||||
import asyncio
|
||||
import html
|
||||
import re
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
import asyncpg.exceptions
|
||||
import bleach
|
||||
from fastapi import APIRouter, File, UploadFile, HTTPException, Depends
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from pydantic import BaseModel
|
||||
|
||||
from classquiz.config import settings, redis, storage, meilisearch, ALLOWED_TAGS_FOR_QUIZ, server_regex
|
||||
from classquiz.config import settings, redis, storage, meilisearch, ALLOWED_TAGS_FOR_QUIZ
|
||||
from classquiz.db.models import Quiz, QuizInput, User, QuizQuestionType
|
||||
import puremagic
|
||||
from classquiz.auth import get_current_user
|
||||
import os
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from classquiz.helpers import get_meili_data, check_hashcash
|
||||
from classquiz.helpers import get_meili_data, check_image_string
|
||||
from classquiz.storage.errors import DeletionFailedError
|
||||
|
||||
settings = settings()
|
||||
@@ -86,37 +84,6 @@ class UploadImageReturn(BaseModel):
|
||||
pow_data: str
|
||||
|
||||
|
||||
@router.post("/image", response_model=UploadImageReturn)
|
||||
async def upload_image(edit_id: str, pow_data: str, file: UploadFile = File()):
|
||||
session_data = await redis.get(f"edit_session:{edit_id}")
|
||||
pow_data_server = await redis.get(f"edit_session:{edit_id}:pow")
|
||||
uploaded_images = await redis.llen(f"edit_session:{edit_id}:images")
|
||||
if pow_data_server is None:
|
||||
raise HTTPException(status_code=401, detail="Edit ID not found!")
|
||||
if session_data is None:
|
||||
raise HTTPException(status_code=401, detail="Edit ID not found!")
|
||||
if uploaded_images == 0 and not check_hashcash(pow_data, pow_data_server, "8"):
|
||||
raise HTTPException(status_code=401, detail="Edit ID not found!")
|
||||
if uploaded_images != 0 and not check_hashcash(pow_data, pow_data_server, "8"):
|
||||
raise HTTPException(status_code=401, detail="Edit ID not found!")
|
||||
file_bytes = await file.read()
|
||||
if len(file_bytes) > 2000000:
|
||||
raise HTTPException(status_code=400, detail="File too large")
|
||||
try:
|
||||
pm_data = puremagic.magic_string(file_bytes)[0]
|
||||
except puremagic.PureError:
|
||||
raise HTTPException(status_code=400, detail="Image couldn't be identified!")
|
||||
if pm_data.extension not in allowed_image_extensions:
|
||||
raise HTTPException(status_code=400, detail="Image-type now allowed!")
|
||||
session_data = EditSessionData.parse_raw(session_data)
|
||||
file_name = f"{session_data.quiz_id}--{uuid.uuid4()}"
|
||||
await storage.upload(file_name=file_name, file_data=file_bytes)
|
||||
await redis.lpush(f"edit_session:{edit_id}:images", file_name)
|
||||
random_str = os.urandom(8).hex()
|
||||
await redis.set(f"edit_session:{edit_id}:pow", random_str, ex=3800)
|
||||
return UploadImageReturn(id=file_name, pow_data=random_str)
|
||||
|
||||
|
||||
@router.post("/finish")
|
||||
async def finish_edit(edit_id: str, quiz_input: QuizInput):
|
||||
session_data = await redis.get(f"edit_session:{edit_id}")
|
||||
@@ -141,10 +108,7 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
|
||||
quiz_input.questions[i].answers[i2].answer = html.unescape(
|
||||
bleach.clean(answer.answer, tags=ALLOWED_TAGS_FOR_QUIZ, strip=True)
|
||||
)
|
||||
image_id_regex = r"^.{36}--.{36}$"
|
||||
imgur_regex = r"^https://i\.imgur\.com\/.{7}.(jpg|png|gif)$"
|
||||
|
||||
extract_file_name_re = r"^.*/api/v1/storage/download/(.{36}--.{36})$"
|
||||
images_to_delete = []
|
||||
old_quiz_data: Quiz = await Quiz.objects.get_or_none(id=session_data.quiz_id, user_id=session_data.user_id)
|
||||
|
||||
@@ -167,15 +131,9 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
|
||||
)
|
||||
if image == "":
|
||||
question.image = None
|
||||
if image is None:
|
||||
mark_image_for_deletion(question.image, i, old_quiz_data)
|
||||
elif image is None:
|
||||
mark_image_for_deletion(question.image, i, old_quiz_data)
|
||||
elif bool(re.match(image_id_regex, question.image)):
|
||||
question.image = f"{settings.root_address}/api/v1/storage/download/{image}"
|
||||
mark_image_for_deletion(question.image, i, old_quiz_data)
|
||||
elif bool(re.match(imgur_regex, image)):
|
||||
mark_image_for_deletion(question.image, i, old_quiz_data)
|
||||
elif bool(re.match(server_regex, image)):
|
||||
elif check_image_string(quiz_input.cover_image)[0]:
|
||||
mark_image_for_deletion(question.image, i, old_quiz_data)
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Image URL(s) aren't valid!")
|
||||
@@ -186,7 +144,10 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
|
||||
# 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 bool(re.match(server_regex, quiz_input.cover_image)):
|
||||
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")
|
||||
|
||||
if quiz_input.background_image is not None and not check_image_string(quiz_input.background_image)[0]:
|
||||
raise HTTPException(status_code=400, detail="image url is not valid")
|
||||
|
||||
if session_data.edit:
|
||||
@@ -207,7 +168,7 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
|
||||
for image in images_to_delete:
|
||||
if image is not None:
|
||||
try:
|
||||
await storage.delete([re.search(extract_file_name_re, image).group(1)])
|
||||
await storage.delete([image])
|
||||
except DeletionFailedError:
|
||||
pass
|
||||
await redis.srem("edit_sessions", edit_id)
|
||||
|
||||
@@ -9,9 +9,9 @@ from fastapi.responses import StreamingResponse, RedirectResponse
|
||||
|
||||
from classquiz.auth import get_current_user
|
||||
from classquiz.config import settings, storage, arq
|
||||
from classquiz.db.models import User, StorageItem
|
||||
from classquiz.db.models import User, StorageItem, PublicStorageItem, UpdateStorageItem
|
||||
from classquiz.storage.errors import DownloadingFailedError
|
||||
from uuid import uuid4
|
||||
from uuid import uuid4, UUID
|
||||
|
||||
settings = settings()
|
||||
|
||||
@@ -45,7 +45,7 @@ async def download_file(file_name: str):
|
||||
|
||||
|
||||
@router.post("/")
|
||||
async def upload_file(file: UploadFile = File(), user: User = Depends(get_current_user)):
|
||||
async def upload_file(file: UploadFile = File(), user: User = Depends(get_current_user)) -> PublicStorageItem:
|
||||
file_id = uuid4()
|
||||
|
||||
size = 0
|
||||
@@ -71,3 +71,39 @@ async def upload_file(file: UploadFile = File(), user: User = Depends(get_curren
|
||||
await storage.upload(file_name=file_id.hex, file_data=file_data)
|
||||
await file_obj.save()
|
||||
await arq.enqueue_job("calculate_hash", file_id.hex)
|
||||
return PublicStorageItem.from_db_model(file_obj)
|
||||
|
||||
|
||||
@router.get("/meta/{file_id}")
|
||||
async def get_file_info(file_id: UUID, user: User = Depends(get_current_user)) -> PublicStorageItem:
|
||||
file_data = await StorageItem.objects.get_or_none(id=file_id, user=user, deleted_at=None)
|
||||
if file_data is None:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
return PublicStorageItem.from_db_model(file_data)
|
||||
|
||||
|
||||
@router.delete("/meta/{file_id}")
|
||||
async def mark_file_as_deleted(file_id: UUID, user: User = Depends(get_current_user)):
|
||||
file_data = await StorageItem.objects.get_or_none(id=file_id, user=user, deleted_at=None)
|
||||
if file_data is None:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
storage_path = file_data.storage_path
|
||||
if storage_path is None:
|
||||
storage_path = file_data.id.hex
|
||||
await storage.delete(storage_path)
|
||||
file_data.deleted_at = datetime.now()
|
||||
await file_data.update()
|
||||
return
|
||||
|
||||
|
||||
@router.put("/meta/{file_id}")
|
||||
async def update_image_data(
|
||||
file_id: UUID, data: UpdateStorageItem, user: User = Depends(get_current_user)
|
||||
) -> PublicStorageItem:
|
||||
file_data = await StorageItem.objects.get_or_none(id=file_id, user=user, deleted_at=None)
|
||||
if file_data is None:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
file_data.filename = data.filename
|
||||
file_data.alt_text = data.alt_text
|
||||
await file_data.update()
|
||||
return PublicStorageItem.from_db_model(file_data)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
import uuid
|
||||
|
||||
from arq.worker import Retry
|
||||
import xxhash
|
||||
|
||||
from classquiz.config import redis, storage
|
||||
@@ -36,7 +37,11 @@ async def calculate_hash(ctx, file_id_as_str: str):
|
||||
if file_data.storage_path is not None:
|
||||
file_path = file_data.storage_path
|
||||
file = SpooledTemporaryFile()
|
||||
file.write((await storage.download(file_path)).getbuffer().tobytes())
|
||||
file_bytes = await storage.download(file_path)
|
||||
if file_bytes is None:
|
||||
print("Retry raised!")
|
||||
raise Retry(defer=ctx["job_try"] * 10)
|
||||
file.write(file_bytes.getbuffer().tobytes())
|
||||
hash_obj = xxhash.xxh3_128()
|
||||
# assert hash_obj.block_size == 64
|
||||
while chunk := file.read(6400):
|
||||
|
||||
@@ -225,7 +225,8 @@
|
||||
{#if quiz_data.questions[selected_question].image !== null}
|
||||
<div>
|
||||
<img
|
||||
src={quiz_data.questions[selected_question].image}
|
||||
src="/api/v1/storage/download/{quiz_data.questions[selected_question]
|
||||
.image}"
|
||||
class="max-h-[20vh] object-cover mx-auto mb-8 w-auto"
|
||||
alt="Content for Question"
|
||||
/>
|
||||
@@ -320,7 +321,7 @@
|
||||
<div class="h-[30vh] m-auto w-auto mt-12">
|
||||
<img
|
||||
class="max-h-full max-w-full block"
|
||||
src={quiz_data.cover_image}
|
||||
src="/api/v1/storage/download/{quiz_data.cover_image}"
|
||||
alt="Not provided"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
<div class="h-[20vh] m-auto w-auto">
|
||||
<img
|
||||
class="max-h-full max-w-full block"
|
||||
src={quiz.cover_image}
|
||||
src="/api/v1/storage/download/{quiz.cover_image}"
|
||||
alt="Not provided"
|
||||
loading="lazy"
|
||||
/>
|
||||
@@ -302,7 +302,7 @@
|
||||
{#if visibleImages?.[i]?.[q]}
|
||||
<img
|
||||
class="max-h-full max-w-full block"
|
||||
src={question.image}
|
||||
src="/api/v1/storage/download/{question.image}"
|
||||
alt="Not provided"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -129,7 +129,8 @@
|
||||
</svg>
|
||||
</button>
|
||||
<img
|
||||
src={data.questions[selected_question].image}
|
||||
src="/api/v1/storage/download/{data.questions[selected_question]
|
||||
.image}"
|
||||
alt="not available"
|
||||
class="max-h-64 h-auto w-auto"
|
||||
/>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<div
|
||||
class="dark:bg-gray-700 h-full"
|
||||
style="background-repeat: no-repeat;background-size: 100% 100%;background-image: {data.background_image
|
||||
? `url("${data.background_image}")`
|
||||
? `url("/api/v1/storage/download/${data.background_image}")`
|
||||
: `unset`}"
|
||||
>
|
||||
<div class="flex justify-center pt-10 w-full">
|
||||
@@ -68,7 +68,7 @@
|
||||
{#if data.cover_image != undefined && data.cover_image !== ''}
|
||||
<div class="flex justify-center pt-10 w-full max-h-72 w-full">
|
||||
<img
|
||||
src={data.cover_image}
|
||||
src="/api/v1/storage/download/{data.cover_image}"
|
||||
alt="not available"
|
||||
class="max-h-72 h-auto w-auto"
|
||||
on:contextmenu|preventDefault={() => {
|
||||
|
||||
@@ -201,11 +201,11 @@
|
||||
{#if question.image}
|
||||
<div class="flex justify-center align-middle pb-0.5">
|
||||
<img
|
||||
src={question.image}
|
||||
src="/api/v1/storage/download/{question.image}"
|
||||
class="h-10 border rounded-lg"
|
||||
alt="Not available"
|
||||
use:tippy={{
|
||||
content: `<img src='${question.image}' alt='Not available' class='rounded'>`,
|
||||
content: `<img src='/api/v1/storage/download/${question.image}' alt='Not available' class='rounded'>`,
|
||||
allowHTML: true
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -45,33 +45,29 @@
|
||||
quality: 0.6
|
||||
})
|
||||
.use(XHRUpload, {
|
||||
endpoint: `/api/v1/editor/image?edit_id=${edit_id}&pow_data=${pow_data}`
|
||||
endpoint: `/api/v1/storage/`
|
||||
});
|
||||
const props = {
|
||||
inline: true,
|
||||
restrictions: {
|
||||
maxFileSize: 2_000_000,
|
||||
maxNumberOfFiles: 1,
|
||||
allowedFileTypes: ['.gif', '.jpg', '.jpeg', '.png', '.svg', '.webp']
|
||||
maxFileSize: 10_000_000,
|
||||
maxNumberOfFiles: 1
|
||||
// allowedFileTypes: ['.gif', '.jpg', '.jpeg', '.png', '.svg', '.webp']
|
||||
}
|
||||
};
|
||||
let image_id;
|
||||
uppy.on('upload-success', (file, response) => {
|
||||
image_id = response.body.id;
|
||||
pow_salt = response.body.pow_data;
|
||||
console.log(pow_salt, response.body);
|
||||
pow_data = undefined;
|
||||
});
|
||||
uppy.on('complete', (_) => {
|
||||
console.log(pow_data);
|
||||
if (selected_question === undefined) {
|
||||
data.cover_image = `${window.location.origin}/api/v1/storage/download/${image_id}`;
|
||||
data.cover_image = image_id;
|
||||
} else if (selected_question === -1) {
|
||||
data.background_image = `${window.location.origin}/api/v1/storage/download/${image_id}`;
|
||||
data.background_image = image_id;
|
||||
} else {
|
||||
data.questions[
|
||||
selected_question
|
||||
].image = `${window.location.origin}/api/v1/storage/download/${image_id}`;
|
||||
data.questions[selected_question].image = image_id;
|
||||
}
|
||||
console.log(selected_question, data);
|
||||
|
||||
|
||||
@@ -384,5 +384,9 @@
|
||||
"popover": {
|
||||
"copied_to_clipboard": "Copied to clipboard!"
|
||||
}
|
||||
},
|
||||
"public_user_page": {
|
||||
"joined_on": "Joined on {{date}}",
|
||||
"no_original_quizzes": "This user doesn't have any original quizzes"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
{#if question.image !== null && game_mode !== 'kahoot'}
|
||||
<div class="max-h-full">
|
||||
<img
|
||||
src={question.image}
|
||||
src="/api/v1/storage/download/{question.image}"
|
||||
class="object-cover mx-auto mb-8 max-h-[90%]"
|
||||
alt="Content for Question"
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
{#if cover_image}
|
||||
<div class="flex justify-center align-middle items-center">
|
||||
<div class="h-[30vh] m-auto w-auto mt-12">
|
||||
<img class="max-h-full max-w-full block" src={cover_image} alt="Not provided" />
|
||||
<img
|
||||
class="max-h-full max-w-full block"
|
||||
src="/api/v1/storage/download/{cover_image}"
|
||||
alt="Not provided"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
{#if question.image !== null}
|
||||
<div>
|
||||
<img
|
||||
src={question.image}
|
||||
src="/api/v1/storage/download/{question.image}"
|
||||
class="max-h-[40vh] object-cover mx-auto mb-8 w-auto"
|
||||
alt="Content for Question"
|
||||
/>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
{#if data.cover_image != undefined && data.cover_image !== ''}
|
||||
<div class="flex justify-center pt-10 w-full max-h-72 w-full">
|
||||
<img
|
||||
src={data.cover_image}
|
||||
src="/api/v1/storage/download/{data.cover_image}"
|
||||
alt="not available"
|
||||
class="max-h-72 h-auto w-auto"
|
||||
on:contextmenu|preventDefault={() => {
|
||||
|
||||
@@ -65,14 +65,7 @@ export const dataSchema = yup.object({
|
||||
yup.object({
|
||||
question: yup.string().required('A question-title is required').max(299),
|
||||
time: yup.number().required().positive('The time has to be positive'),
|
||||
image: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.matches(
|
||||
/^(http(|s):\/\/.*(|:)\d*\/api\/v1\/storage\/download\/.{36}--.{36}|https:\/\/i\.imgur\.com\/.{7}.(jpg|png|gif))$|^$/,
|
||||
"The image-url isn't valid"
|
||||
)
|
||||
.lowercase(),
|
||||
image: yup.string().nullable().lowercase(),
|
||||
answers: yup.lazy((v) => {
|
||||
if (Array.isArray(v)) {
|
||||
if (typeof v[0].right === 'boolean') {
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
<div class="hidden lg:flex w-auto h-full items-center relative">
|
||||
{#if quiz.cover_image}
|
||||
<img
|
||||
src={quiz.cover_image}
|
||||
src="/api/v1/storage/download/{quiz.cover_image}"
|
||||
alt="user provided"
|
||||
loading="lazy"
|
||||
class="shrink-0 max-w-full max-h-full absolute"
|
||||
|
||||
@@ -253,7 +253,8 @@
|
||||
{#if game_data.questions[selected_question].image !== null}
|
||||
<div>
|
||||
<img
|
||||
src={game_data.questions[selected_question].image}
|
||||
src="/api/v1/storage/download/{game_data.questions[selected_question]
|
||||
.image}"
|
||||
class="max-h-[20vh] object-cover mx-auto mb-8 w-auto"
|
||||
alt="Content for Question"
|
||||
/>
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<div class="h-[20vh] m-auto w-auto max-h-[18vh]">
|
||||
<img
|
||||
class="max-h-full max-w-full block"
|
||||
src={quiz.cover_image}
|
||||
src="/api/v1/storage/download/{quiz.cover_image}"
|
||||
alt="Not provided"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<div class="h-[15vh] m-auto w-auto my-3">
|
||||
<img
|
||||
class="max-h-full max-w-full block"
|
||||
src={quiz.cover_image}
|
||||
src="/api/v1/storage/download/{quiz.cover_image}"
|
||||
alt="Not provided"
|
||||
/>
|
||||
</div>
|
||||
@@ -189,7 +189,11 @@
|
||||
<!-- </label>-->
|
||||
{#if question.image}
|
||||
<span>
|
||||
<img class="pl-8" src={question.image} alt="Not provided" />
|
||||
<img
|
||||
class="pl-8"
|
||||
src="/api/v1/storage/download/{question.image}"
|
||||
alt="Not provided"
|
||||
/>
|
||||
</span>
|
||||
{/if}
|
||||
<p
|
||||
|
||||
@@ -31,15 +31,19 @@ def upgrade() -> None:
|
||||
session = Session(bind=conn)
|
||||
all_cover_images = session.execute("SELECT cover_image, id from quiz where cover_image is not null;")
|
||||
for cover_image, id in all_cover_images:
|
||||
new_cover_image = re.search(magic_regex, cover_image).group(1)
|
||||
# print(new_cover_image, id)
|
||||
session.execute(f"UPDATE quiz SET cover_image = '{new_cover_image}' WHERE id='{id}';")
|
||||
try:
|
||||
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}';")
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
all_background_images = session.execute("SELECT background_image, id from quiz where background_image is not null;")
|
||||
for bg_image, id in all_background_images:
|
||||
new_bg_image = re.search(magic_regex, bg_image).group(1)
|
||||
# print(new_cover_image, id)
|
||||
session.execute(f"UPDATE quiz SET cover_image = '{new_bg_image}' WHERE id='{id}';")
|
||||
try:
|
||||
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}';")
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
all_questions = session.execute("SELECT questions, id from quiz;")
|
||||
question_image_regex = rf"{settings.root_address}/api/v1/storage/download/(?=.{{36}}--.{{36}})"
|
||||
@@ -71,5 +75,5 @@ def downgrade() -> None:
|
||||
question_image_regex = r"(?=.{36}--.{36})"
|
||||
for question, id in all_questions:
|
||||
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}';")
|
||||
|
||||
Reference in New Issue
Block a user