✨ Finished new editor and deprecated old endpoints
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import asyncio
|
||||
import html
|
||||
import re
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
import asyncpg.exceptions
|
||||
import bleach
|
||||
import pydantic
|
||||
from fastapi import APIRouter, File, Form, UploadFile, HTTPException, BackgroundTasks, Depends
|
||||
@@ -16,6 +18,9 @@ from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from classquiz.helpers import get_meili_data
|
||||
from classquiz.storage.errors import DeletionFailedError
|
||||
|
||||
settings = settings()
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -83,8 +88,41 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput):
|
||||
session_data = EditSessionData.parse_raw(session_data)
|
||||
quiz_input.title = html.unescape(bleach.clean(quiz_input.title, tags=[], strip=True))
|
||||
quiz_input.description = html.unescape(bleach.clean(quiz_input.description, tags=[], strip=True))
|
||||
image_id_regex = r"^.{36}--.{36}$"
|
||||
imgur_regex = r"^https://i\.imgur\.com\/.{7}.(jpg|png|gif)$"
|
||||
server_regex = rf"^{re.escape(settings.root_address)}/api/v1/storage/download/.{{36}}--.{{36}}$"
|
||||
extract_file_name_re = r"^.*/api/v1/storage/download/(.{36}--.{36})$"
|
||||
images_to_delete = []
|
||||
old_quiz_data = await Quiz.objects.get_or_none(id=session_data.quiz_id, user_id=session_data.user_id)
|
||||
|
||||
def mark_image_for_deletion(new: str | None, index: int, old_quiz: Quiz | None):
|
||||
if old_quiz is None:
|
||||
return
|
||||
print(new, old_quiz.questions[index]["image"])
|
||||
if new == old_quiz.questions[index]["image"]:
|
||||
return
|
||||
else:
|
||||
images_to_delete.append(old_quiz.questions[index]["image"])
|
||||
|
||||
for i, question in enumerate(quiz_input.questions):
|
||||
image = question.image
|
||||
if image == "":
|
||||
question.image = 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)):
|
||||
mark_image_for_deletion(question.image, i, old_quiz_data)
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Image URL(s) aren't valid!")
|
||||
print(images_to_delete)
|
||||
if session_data.edit:
|
||||
quiz = await Quiz.objects.get_or_none(id=session_data.quiz_id, user_id=session_data.user_id)
|
||||
quiz = old_quiz_data
|
||||
meilisearch.index(settings.meilisearch_index).update_documents([await get_meili_data(quiz)])
|
||||
if quiz.public and not quiz_input.public:
|
||||
meilisearch.index(settings.meilisearch_index).delete_document(str(quiz.id))
|
||||
@@ -95,10 +133,19 @@ 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"]
|
||||
return await quiz.save()
|
||||
for image in images_to_delete:
|
||||
if image is not None:
|
||||
try:
|
||||
await storage.delete(re.search(extract_file_name_re, image).group(1))
|
||||
except DeletionFailedError:
|
||||
pass
|
||||
return await quiz.update()
|
||||
else:
|
||||
quiz = Quiz(**quiz_input.dict(), user_id=session_data.user_id, id=session_data.quiz_id)
|
||||
await redis.delete("global_quiz_count")
|
||||
if quiz_input.public:
|
||||
meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)])
|
||||
return await quiz.save()
|
||||
try:
|
||||
return await quiz.save()
|
||||
except asyncpg.exceptions.UniqueViolationError:
|
||||
raise HTTPException(status_code=400, detail="The quiz already exists")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from io import BytesIO
|
||||
from classquiz.storage.errors import DeletionFailedError, SavingFailedError, DownloadingFailedError
|
||||
|
||||
from aiohttp import ClientSession
|
||||
|
||||
@@ -26,7 +27,7 @@ class DetaStorage:
|
||||
elif response.status == 404:
|
||||
return None
|
||||
else:
|
||||
raise Exception("Download failed")
|
||||
raise DownloadingFailedError
|
||||
|
||||
async def upload(self, file: bytes, file_name: str) -> None:
|
||||
"""
|
||||
@@ -40,7 +41,7 @@ class DetaStorage:
|
||||
if response.status == 201:
|
||||
return None
|
||||
else:
|
||||
raise Exception("Upload failed")
|
||||
raise SavingFailedError
|
||||
|
||||
async def delete(self, file_names: [str]) -> None:
|
||||
async with ClientSession(headers=self.headers) as session, session.delete(
|
||||
@@ -49,4 +50,4 @@ class DetaStorage:
|
||||
if response.status == 200:
|
||||
return None
|
||||
else:
|
||||
raise Exception("Delete failed")
|
||||
raise DeletionFailedError
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
class DeletionFailedError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SavingFailedError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadingFailedError(Exception):
|
||||
pass
|
||||
Reference in New Issue
Block a user