✨ Fixed multiple things
This commit is contained in:
@@ -40,6 +40,7 @@ minio = "*"
|
|||||||
xxhash = "*"
|
xxhash = "*"
|
||||||
arq = "*"
|
arq = "*"
|
||||||
thumbhash-python = "==1.0.0"
|
thumbhash-python = "==1.0.0"
|
||||||
|
python-magic = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
coverage = "*"
|
coverage = "*"
|
||||||
|
|||||||
Generated
+9
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "dc7977c3d6c0c72f41144ddb7bc0821c1bd34c856cbf2a93077c809e6b7a9048"
|
"sha256": "c3de18e25234c98fd1b33d4df4464fd6e05a485416c76eb6c4f64e78c497af64"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -1357,6 +1357,14 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.3.0"
|
"version": "==3.3.0"
|
||||||
},
|
},
|
||||||
|
"python-magic": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b",
|
||||||
|
"sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.4.27"
|
||||||
|
},
|
||||||
"python-multipart": {
|
"python-multipart": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132",
|
"sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132",
|
||||||
|
|||||||
+11
-10
@@ -8,6 +8,7 @@ from datetime import datetime
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import ormar
|
import ormar
|
||||||
|
from ormar import ReferentialAction
|
||||||
from pydantic import BaseModel, Json, validator
|
from pydantic import BaseModel, Json, validator
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from . import metadata, database
|
from . import metadata, database
|
||||||
@@ -58,7 +59,7 @@ class FidoCredentials(ormar.Model):
|
|||||||
id: bytes = ormar.LargeBinary(max_length=256)
|
id: bytes = ormar.LargeBinary(max_length=256)
|
||||||
public_key: bytes = ormar.LargeBinary(max_length=256)
|
public_key: bytes = ormar.LargeBinary(max_length=256)
|
||||||
sign_count: int = ormar.Integer()
|
sign_count: int = ormar.Integer()
|
||||||
user: Optional[User] = ormar.ForeignKey(User)
|
user: Optional[User] = ormar.ForeignKey(User, ondelete=ReferentialAction.CASCADE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "fido_credentials"
|
tablename = "fido_credentials"
|
||||||
@@ -68,7 +69,7 @@ class FidoCredentials(ormar.Model):
|
|||||||
|
|
||||||
class ApiKey(ormar.Model):
|
class ApiKey(ormar.Model):
|
||||||
key: str = ormar.String(max_length=48, min_length=48, primary_key=True)
|
key: str = ormar.String(max_length=48, min_length=48, primary_key=True)
|
||||||
user: Optional[User] = ormar.ForeignKey(User)
|
user: Optional[User] = ormar.ForeignKey(User, ondelete=ReferentialAction.CASCADE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "api_keys"
|
tablename = "api_keys"
|
||||||
@@ -82,7 +83,7 @@ class UserSession(ormar.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4())
|
id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4())
|
||||||
user: Optional[User] = ormar.ForeignKey(User)
|
user: Optional[User] = ormar.ForeignKey(User, ondelete=ReferentialAction.CASCADE)
|
||||||
session_key: str = ormar.String(unique=True, max_length=64)
|
session_key: str = ormar.String(unique=True, max_length=64)
|
||||||
created_at: datetime = ormar.DateTime(default=datetime.now())
|
created_at: datetime = ormar.DateTime(default=datetime.now())
|
||||||
ip_address: str = ormar.String(max_length=100, nullable=True)
|
ip_address: str = ormar.String(max_length=100, nullable=True)
|
||||||
@@ -181,7 +182,7 @@ class Quiz(ormar.Model):
|
|||||||
description: str = ormar.Text(nullable=True)
|
description: str = ormar.Text(nullable=True)
|
||||||
created_at: datetime = ormar.DateTime(default=datetime.now())
|
created_at: datetime = ormar.DateTime(default=datetime.now())
|
||||||
updated_at: datetime = ormar.DateTime(default=datetime.now())
|
updated_at: datetime = ormar.DateTime(default=datetime.now())
|
||||||
user_id: uuid.UUID = ormar.ForeignKey(User)
|
user_id: uuid.UUID = ormar.ForeignKey(User, ondelete=ReferentialAction.CASCADE)
|
||||||
questions: Json[list[QuizQuestion]] = ormar.JSON(nullable=False)
|
questions: Json[list[QuizQuestion]] = ormar.JSON(nullable=False)
|
||||||
imported_from_kahoot: Optional[bool] = ormar.Boolean(default=False, nullable=True)
|
imported_from_kahoot: Optional[bool] = ormar.Boolean(default=False, nullable=True)
|
||||||
cover_image: Optional[str] = ormar.Text(nullable=True, unique=False)
|
cover_image: Optional[str] = ormar.Text(nullable=True, unique=False)
|
||||||
@@ -307,8 +308,8 @@ class GameInLobby(BaseModel):
|
|||||||
|
|
||||||
class GameResults(ormar.Model):
|
class GameResults(ormar.Model):
|
||||||
id: uuid.UUID = ormar.UUID(primary_key=True)
|
id: uuid.UUID = ormar.UUID(primary_key=True)
|
||||||
quiz: uuid.UUID | Quiz = ormar.ForeignKey(Quiz)
|
quiz: uuid.UUID | Quiz = ormar.ForeignKey(Quiz, ondelete=ReferentialAction.CASCADE)
|
||||||
user: uuid.UUID | User = ormar.ForeignKey(User)
|
user: uuid.UUID | User = ormar.ForeignKey(User, ondelete=ReferentialAction.CASCADE)
|
||||||
timestamp: datetime = ormar.DateTime(default=datetime.now(), nullable=False)
|
timestamp: datetime = ormar.DateTime(default=datetime.now(), nullable=False)
|
||||||
player_count: int = ormar.Integer(nullable=False, default=0)
|
player_count: int = ormar.Integer(nullable=False, default=0)
|
||||||
note: str | None = ormar.Text(nullable=True)
|
note: str | None = ormar.Text(nullable=True)
|
||||||
@@ -334,7 +335,7 @@ class QuizTivity(ormar.Model):
|
|||||||
id: uuid.UUID = ormar.UUID(primary_key=True)
|
id: uuid.UUID = ormar.UUID(primary_key=True)
|
||||||
title: str = ormar.Text(nullable=False)
|
title: str = ormar.Text(nullable=False)
|
||||||
created_at: datetime = ormar.DateTime(nullable=False, server_default=func.now())
|
created_at: datetime = ormar.DateTime(nullable=False, server_default=func.now())
|
||||||
user: User | None = ormar.ForeignKey(User)
|
user: User | None = ormar.ForeignKey(User, ondelete=ReferentialAction.CASCADE)
|
||||||
pages: list[QuizTivityPage] = ormar.JSON(nullable=False)
|
pages: list[QuizTivityPage] = ormar.JSON(nullable=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -347,8 +348,8 @@ class QuizTivityShare(ormar.Model):
|
|||||||
id: uuid.UUID = ormar.UUID(primary_key=True)
|
id: uuid.UUID = ormar.UUID(primary_key=True)
|
||||||
name: str | None = ormar.Text(nullable=True)
|
name: str | None = ormar.Text(nullable=True)
|
||||||
expire_at: datetime | None = ormar.DateTime(nullable=True)
|
expire_at: datetime | None = ormar.DateTime(nullable=True)
|
||||||
quiztivity: QuizTivity | None = ormar.ForeignKey(QuizTivity)
|
quiztivity: QuizTivity | None = ormar.ForeignKey(QuizTivity, ondelete=ReferentialAction.CASCADE)
|
||||||
user: User | None = ormar.ForeignKey(User)
|
user: User | None = ormar.ForeignKey(User, ondelete=ReferentialAction.CASCADE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
tablename = "quiztivityshares"
|
tablename = "quiztivityshares"
|
||||||
@@ -386,7 +387,7 @@ class StorageItem(ormar.Model):
|
|||||||
uploaded_at: datetime = ormar.DateTime(nullable=False, default=datetime.now())
|
uploaded_at: datetime = ormar.DateTime(nullable=False, default=datetime.now())
|
||||||
mime_type: str = ormar.Text(nullable=False)
|
mime_type: str = ormar.Text(nullable=False)
|
||||||
hash: bytes | None = ormar.LargeBinary(nullable=True, min_length=16, max_length=16)
|
hash: bytes | None = ormar.LargeBinary(nullable=True, min_length=16, max_length=16)
|
||||||
user: User | None = ormar.ForeignKey(User)
|
user: User | None = ormar.ForeignKey(User, ondelete=ReferentialAction.SET_NULL)
|
||||||
size: int = ormar.BigInteger(nullable=False)
|
size: int = ormar.BigInteger(nullable=False)
|
||||||
storage_path: str | None = ormar.Text(nullable=True)
|
storage_path: str | None = ormar.Text(nullable=True)
|
||||||
deleted_at: datetime | None = ormar.DateTime(nullable=True, default=None)
|
deleted_at: datetime | None = ormar.DateTime(nullable=True, default=None)
|
||||||
|
|||||||
@@ -65,12 +65,15 @@ async def generate_spreadsheet(quiz_results: dict, quiz: Quiz, player_fields: di
|
|||||||
worksheet.write(i + 1, 1, question["time"])
|
worksheet.write(i + 1, 1, question["time"])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with ClientSession() as session, session.get(question["image"]) as response:
|
async with ClientSession() as session, session.get(
|
||||||
img_data = BytesIO(await response.read())
|
f"{settings.root_address}/api/v1/storage/download/{question['image']}"
|
||||||
worksheet.insert_image(i + 1, 2, question["image"], {"image_data": img_data})
|
) as response:
|
||||||
image = Image.open(img_data)
|
if "image" in response.headers.get("Content-Type"):
|
||||||
worksheet.set_row(i + 1, image.height)
|
img_data = BytesIO(await response.read())
|
||||||
worksheet.set_column(2, 2, image.width)
|
worksheet.insert_image(i + 1, 2, question["image"], {"image_data": img_data})
|
||||||
|
image = Image.open(img_data)
|
||||||
|
worksheet.set_row(i + 1, image.height)
|
||||||
|
worksheet.set_column(2, 2, image.width)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
answer_amount = len(answer_data)
|
answer_amount = len(answer_data)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -10,10 +11,11 @@ from aiohttp import ClientSession
|
|||||||
from fastapi import APIRouter, Depends, File, UploadFile, HTTPException
|
from fastapi import APIRouter, Depends, File, UploadFile, HTTPException
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from classquiz.auth import get_current_user
|
from classquiz.auth import get_current_user
|
||||||
from classquiz.config import storage, settings
|
from classquiz.config import storage, settings, arq
|
||||||
from classquiz.db.models import Quiz, User
|
from classquiz.db.models import Quiz, User, StorageItem
|
||||||
import gzip
|
import gzip
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import magic
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
settings = settings()
|
settings = settings()
|
||||||
@@ -49,7 +51,9 @@ async def export_quiz(quiz_id: uuid.UUID, user: User = Depends(get_current_user)
|
|||||||
for image_key in image_urls.keys():
|
for image_key in image_urls.keys():
|
||||||
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(image_urls[image_key]) as resp:
|
async with ClientSession() as session, session.get(
|
||||||
|
f"{settings.root_address}/api/v1/storage/download/{image_urls[image_key]}"
|
||||||
|
) as resp:
|
||||||
image_data = await resp.read()
|
image_data = await resp.read()
|
||||||
bin_data = bin_data + image_data
|
bin_data = bin_data + image_data
|
||||||
|
|
||||||
@@ -68,6 +72,8 @@ async def export_quiz(quiz_id: uuid.UUID, user: User = Depends(get_current_user)
|
|||||||
|
|
||||||
@router.post("/")
|
@router.post("/")
|
||||||
async def import_quiz(file: UploadFile = File(), user: User = Depends(get_current_user)):
|
async def import_quiz(file: UploadFile = File(), user: User = Depends(get_current_user)):
|
||||||
|
if user.storage_used > settings.free_storage_limit:
|
||||||
|
raise HTTPException(status_code=409, detail="Storage limit reached")
|
||||||
data = await file.read()
|
data = await file.read()
|
||||||
[split_data, images] = data.split(quiz_delimiter)
|
[split_data, images] = data.split(quiz_delimiter)
|
||||||
decompressed_quiz = gzip.decompress(split_data)
|
decompressed_quiz = gzip.decompress(split_data)
|
||||||
@@ -75,15 +81,32 @@ async def import_quiz(file: UploadFile = File(), user: User = Depends(get_curren
|
|||||||
image_splits = images.split(image_delimiter)
|
image_splits = images.split(image_delimiter)
|
||||||
quiz_id = uuid.uuid4()
|
quiz_id = uuid.uuid4()
|
||||||
image_urls = {}
|
image_urls = {}
|
||||||
|
print(len(data))
|
||||||
for image_split in image_splits:
|
for image_split in image_splits:
|
||||||
res = image_split.split(image_index_delimiter)
|
res = image_split.split(image_index_delimiter)
|
||||||
if len(res) != 2:
|
if len(res) != 2:
|
||||||
continue
|
continue
|
||||||
[index, image_data] = res
|
[index, image_data] = res
|
||||||
|
print(len(image_data))
|
||||||
|
img_data = io.BytesIO(image_data)
|
||||||
|
mime_type = magic.from_buffer(img_data.read(2048), mime=True)
|
||||||
|
print(mime_type)
|
||||||
index = int(index.decode("utf-8"))
|
index = int(index.decode("utf-8"))
|
||||||
image_name = f"{quiz_id}--{uuid.uuid4()}"
|
file_id = uuid.uuid4()
|
||||||
await storage.upload(file_name=image_name, file_data=image_data)
|
file_obj = StorageItem(
|
||||||
image = f"{settings.root_address}/api/v1/storage/download/{image_name}"
|
id=file_id,
|
||||||
|
uploaded_at=datetime.now(),
|
||||||
|
mime_type=mime_type,
|
||||||
|
hash=None,
|
||||||
|
user=user,
|
||||||
|
size=0,
|
||||||
|
deleted_at=None,
|
||||||
|
alt_text=None,
|
||||||
|
)
|
||||||
|
await storage.upload(file_name=file_id.hex, file_data=img_data, mime_type=mime_type)
|
||||||
|
await file_obj.save()
|
||||||
|
await arq.enqueue_job("calculate_hash", file_id.hex)
|
||||||
|
image = file_id.hex
|
||||||
image_urls[index] = image
|
image_urls[index] = image
|
||||||
quiz_dict["created_at"] = datetime.fromisoformat(quiz_dict["created_at"])
|
quiz_dict["created_at"] = datetime.fromisoformat(quiz_dict["created_at"])
|
||||||
quiz_dict["updated_at"] = datetime.fromisoformat(quiz_dict["updated_at"])
|
quiz_dict["updated_at"] = datetime.fromisoformat(quiz_dict["updated_at"])
|
||||||
|
|||||||
@@ -209,7 +209,9 @@ async def delete_quiz(quiz_id: str, user: User = Depends(get_current_user)):
|
|||||||
for question in quiz.questions:
|
for question in quiz.questions:
|
||||||
try:
|
try:
|
||||||
if question["image"] is not None and not str(question["image"]).startswith("https://i.imgur.com/"):
|
if question["image"] is not None and not str(question["image"]).startswith("https://i.imgur.com/"):
|
||||||
pics_to_delete.append(pic_name_regex.match(question["image"]).group(1))
|
old_image_to_delete = pic_name_regex.match(question["image"])
|
||||||
|
if old_image_to_delete is not None:
|
||||||
|
pics_to_delete.append(old_image_to_delete.group(1))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
if len(pics_to_delete) != 0:
|
if len(pics_to_delete) != 0:
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ async def upload_file(file: UploadFile = File(), user: User = Depends(get_curren
|
|||||||
deleted_at=None,
|
deleted_at=None,
|
||||||
alt_text=None,
|
alt_text=None,
|
||||||
)
|
)
|
||||||
await storage.upload(file_name=file_id.hex, file_data=file.file)
|
await storage.upload(file_name=file_id.hex, file_data=file.file, mime_type=file.content_type)
|
||||||
await file_obj.save()
|
await file_obj.save()
|
||||||
await arq.enqueue_job("calculate_hash", file_id.hex)
|
await arq.enqueue_job("calculate_hash", file_id.hex)
|
||||||
return PublicStorageItem.from_db_model(file_obj)
|
return PublicStorageItem.from_db_model(file_obj)
|
||||||
@@ -157,7 +157,9 @@ async def upload_raw_file(request: Request, user: User = Depends(get_current_use
|
|||||||
alt_text=None,
|
alt_text=None,
|
||||||
)
|
)
|
||||||
# https://github.com/VirusTotal/vt-py/issues/119#issuecomment-1261246867
|
# https://github.com/VirusTotal/vt-py/issues/119#issuecomment-1261246867
|
||||||
await storage.upload(file_name=file_id.hex, file_data=data_file._file)
|
await storage.upload(
|
||||||
|
file_name=file_id.hex, file_data=data_file._file, mime_type=request.headers.get("Content-Type")
|
||||||
|
)
|
||||||
await file_obj.save()
|
await file_obj.save()
|
||||||
await arq.enqueue_job("calculate_hash", file_id.hex)
|
await arq.enqueue_job("calculate_hash", file_id.hex)
|
||||||
return PublicStorageItem.from_db_model(file_obj)
|
return PublicStorageItem.from_db_model(file_obj)
|
||||||
|
|||||||
@@ -227,6 +227,7 @@
|
|||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
<MediaComponent
|
<MediaComponent
|
||||||
src={quiz_data.questions[selected_question].image}
|
src={quiz_data.questions[selected_question].image}
|
||||||
|
muted={false}
|
||||||
css_classes="max-h-[20vh] object-cover mx-auto mb-8 w-auto"
|
css_classes="max-h-[20vh] object-cover mx-auto mb-8 w-auto"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,11 +5,17 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
export let src: string;
|
export let src: string;
|
||||||
export let css_classes = 'max-h-64 h-auto w-auto';
|
export let css_classes = 'max-h-64 h-auto w-auto';
|
||||||
|
export let muted = true;
|
||||||
let type: 'img' | 'video' | undefined = undefined;
|
let type: 'img' | 'video' | undefined = undefined;
|
||||||
|
|
||||||
const get_media = async () => {
|
const get_media = async () => {
|
||||||
|
if (!browser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const res = await fetch(`/api/v1/storage/info/${src}`);
|
const res = await fetch(`/api/v1/storage/info/${src}`);
|
||||||
console.log('Headers', res.headers);
|
console.log('Headers', res.headers);
|
||||||
const fileType = res.headers.get('Content-Type');
|
const fileType = res.headers.get('Content-Type');
|
||||||
@@ -46,13 +52,11 @@
|
|||||||
<video
|
<video
|
||||||
class={css_classes}
|
class={css_classes}
|
||||||
disablepictureinpicture
|
disablepictureinpicture
|
||||||
disableremoteplayback
|
|
||||||
x-webkit-airplay="deny"
|
x-webkit-airplay="deny"
|
||||||
controls
|
controls
|
||||||
autoplay
|
autoplay
|
||||||
loop
|
loop
|
||||||
controlslist="nofullscreen,noremoteplayback"
|
{muted}
|
||||||
muted
|
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
>
|
>
|
||||||
<source src="/api/v1/storage/download/{src}" />
|
<source src="/api/v1/storage/download/{src}" />
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
import Spinner from '$lib/Spinner.svelte';
|
import Spinner from '$lib/Spinner.svelte';
|
||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||||
|
import MediaComponent from '$lib/editor/MediaComponent.svelte';
|
||||||
|
|
||||||
export let question: Question;
|
export let question: Question;
|
||||||
|
|
||||||
@@ -93,10 +94,9 @@
|
|||||||
<h1 class="text-3xl text-center">{@html question.question}</h1>
|
<h1 class="text-3xl text-center">{@html question.question}</h1>
|
||||||
{#if question.image !== null}
|
{#if question.image !== null}
|
||||||
<div>
|
<div>
|
||||||
<img
|
<MediaComponent
|
||||||
src="/api/v1/storage/download/{question.image}"
|
src={question.image}
|
||||||
class="max-h-[40vh] object-cover mx-auto mb-8 w-auto"
|
css_classes="max-h-[40vh] object-cover mx-auto mb-8 w-auto"
|
||||||
alt="Content for Question"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -187,7 +187,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex relative my-2">
|
<div class="flex relative my-2">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<BrownButton disabled={stats.progress !== 1} on:click={upload_video}>
|
<BrownButton disabled={status !== Status.CompressDone} on:click={upload_video}>
|
||||||
Upload {file_size_in_mi ? `${file_size_in_mi.toFixed(2)}Mi` : ''}</BrownButton
|
Upload {file_size_in_mi ? `${file_size_in_mi.toFixed(2)}Mi` : ''}</BrownButton
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Spinner from '$lib/Spinner.svelte';
|
import Spinner from '$lib/Spinner.svelte';
|
||||||
import GrayButton from '$lib/components/buttons/gray.svelte';
|
import GrayButton from '$lib/components/buttons/gray.svelte';
|
||||||
|
import MediaComponent from '$lib/editor/MediaComponent.svelte';
|
||||||
|
|
||||||
const tippy = createTippy({
|
const tippy = createTippy({
|
||||||
arrow: true,
|
arrow: true,
|
||||||
@@ -189,10 +190,11 @@
|
|||||||
<!-- </label>-->
|
<!-- </label>-->
|
||||||
{#if question.image}
|
{#if question.image}
|
||||||
<span>
|
<span>
|
||||||
<img
|
<MediaComponent
|
||||||
class="pl-8"
|
css_classes="mx-auto"
|
||||||
src="/api/v1/storage/download/{question.image}"
|
src={question.image}
|
||||||
alt="Not provided"
|
alt="Not provided"
|
||||||
|
muted={true}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
"""Added DeleteAction to models
|
||||||
|
|
||||||
|
Revision ID: a1841b1918b3
|
||||||
|
Revises: 89c4b5d547aa
|
||||||
|
Create Date: 2023-06-09 13:13:00.865587
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import ormar
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "a1841b1918b3"
|
||||||
|
down_revision = "89c4b5d547aa"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint("fk_api_keys_users_id_user", "api_keys", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_api_keys_users_id_user", "api_keys", "users", ["user"], ["id"], ondelete="CASCADE")
|
||||||
|
op.drop_constraint("fk_fido_credentials_users_id_user", "fido_credentials", type_="foreignkey")
|
||||||
|
op.create_foreign_key(
|
||||||
|
"fk_fido_credentials_users_id_user", "fido_credentials", "users", ["user"], ["id"], ondelete="CASCADE"
|
||||||
|
)
|
||||||
|
op.drop_constraint("fk_game_results_quiz_id_quiz", "game_results", type_="foreignkey")
|
||||||
|
op.drop_constraint("fk_game_results_users_id_user", "game_results", type_="foreignkey")
|
||||||
|
op.create_foreign_key(
|
||||||
|
"fk_game_results_users_id_user", "game_results", "users", ["user"], ["id"], ondelete="CASCADE"
|
||||||
|
)
|
||||||
|
op.create_foreign_key("fk_game_results_quiz_id_quiz", "game_results", "quiz", ["quiz"], ["id"], ondelete="CASCADE")
|
||||||
|
op.drop_constraint("fk_quiz_users_id_user_id", "quiz", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_quiz_users_id_user_id", "quiz", "users", ["user_id"], ["id"], ondelete="CASCADE")
|
||||||
|
op.drop_constraint("fk_quiztivitys_users_id_user", "quiztivitys", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_quiztivitys_users_id_user", "quiztivitys", "users", ["user"], ["id"], ondelete="CASCADE")
|
||||||
|
op.drop_constraint("fk_quiztivityshares_quiztivitys_id_quiztivity", "quiztivityshares", type_="foreignkey")
|
||||||
|
op.drop_constraint("fk_quiztivityshares_users_id_user", "quiztivityshares", type_="foreignkey")
|
||||||
|
op.create_foreign_key(
|
||||||
|
"fk_quiztivityshares_quiztivitys_id_quiztivity",
|
||||||
|
"quiztivityshares",
|
||||||
|
"quiztivitys",
|
||||||
|
["quiztivity"],
|
||||||
|
["id"],
|
||||||
|
ondelete="CASCADE",
|
||||||
|
)
|
||||||
|
op.create_foreign_key(
|
||||||
|
"fk_quiztivityshares_users_id_user", "quiztivityshares", "users", ["user"], ["id"], ondelete="CASCADE"
|
||||||
|
)
|
||||||
|
op.drop_constraint("fk_storage_items_users_id_user", "storage_items", type_="foreignkey")
|
||||||
|
op.create_foreign_key(
|
||||||
|
"fk_storage_items_users_id_user", "storage_items", "users", ["user"], ["id"], ondelete="SET NULL"
|
||||||
|
)
|
||||||
|
op.drop_constraint("fk_user_sessions_users_id_user", "user_sessions", type_="foreignkey")
|
||||||
|
op.create_foreign_key(
|
||||||
|
"fk_user_sessions_users_id_user", "user_sessions", "users", ["user"], ["id"], ondelete="CASCADE"
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint("fk_user_sessions_users_id_user", "user_sessions", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_user_sessions_users_id_user", "user_sessions", "users", ["user"], ["id"])
|
||||||
|
op.drop_constraint("fk_storage_items_users_id_user", "storage_items", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_storage_items_users_id_user", "storage_items", "users", ["user"], ["id"])
|
||||||
|
op.drop_constraint("fk_quiztivityshares_users_id_user", "quiztivityshares", type_="foreignkey")
|
||||||
|
op.drop_constraint("fk_quiztivityshares_quiztivitys_id_quiztivity", "quiztivityshares", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_quiztivityshares_users_id_user", "quiztivityshares", "users", ["user"], ["id"])
|
||||||
|
op.create_foreign_key(
|
||||||
|
"fk_quiztivityshares_quiztivitys_id_quiztivity", "quiztivityshares", "quiztivitys", ["quiztivity"], ["id"]
|
||||||
|
)
|
||||||
|
op.drop_constraint("fk_quiztivitys_users_id_user", "quiztivitys", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_quiztivitys_users_id_user", "quiztivitys", "users", ["user"], ["id"])
|
||||||
|
op.drop_constraint("fk_quiz_users_id_user_id", "quiz", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_quiz_users_id_user_id", "quiz", "users", ["user_id"], ["id"])
|
||||||
|
op.drop_constraint("fk_game_results_quiz_id_quiz", "game_results", type_="foreignkey")
|
||||||
|
op.drop_constraint("fk_game_results_users_id_user", "game_results", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_game_results_users_id_user", "game_results", "users", ["user"], ["id"])
|
||||||
|
op.create_foreign_key("fk_game_results_quiz_id_quiz", "game_results", "quiz", ["quiz"], ["id"])
|
||||||
|
op.drop_constraint("fk_fido_credentials_users_id_user", "fido_credentials", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_fido_credentials_users_id_user", "fido_credentials", "users", ["user"], ["id"])
|
||||||
|
op.drop_constraint("fk_api_keys_users_id_user", "api_keys", type_="foreignkey")
|
||||||
|
op.create_foreign_key("fk_api_keys_users_id_user", "api_keys", "users", ["user"], ["id"])
|
||||||
|
# ### end Alembic commands ###
|
||||||
Reference in New Issue
Block a user