From e649b5ac46d1ef29ac8339052c856b85552e7b02 Mon Sep 17 00:00:00 2001 From: Mawoka Date: Sat, 2 Apr 2022 20:51:17 +0200 Subject: [PATCH] :sparkles: Files get deleted, if quiz gets deleted --- classquiz/config.py | 3 +++ classquiz/kahoot_importer/import_quiz.py | 6 ++---- classquiz/routers/quiz.py | 17 ++++++++++++++--- classquiz/routers/storage.py | 5 +---- classquiz/storage/__init__.py | 8 +++++++- classquiz/storage/deta_storage.py | 9 +++------ classquiz/storage/local_storage.py | 17 ++++++++++++++--- 7 files changed, 44 insertions(+), 21 deletions(-) diff --git a/classquiz/config.py b/classquiz/config.py index e025fdf..df29cd4 100644 --- a/classquiz/config.py +++ b/classquiz/config.py @@ -1,6 +1,7 @@ import redis.asyncio as redis_lib from functools import lru_cache from pydantic import BaseSettings, RedisDsn, PostgresDsn +from classquiz.storage import Storage class Settings(BaseSettings): @@ -43,3 +44,5 @@ def settings() -> Settings: redis: redis_lib.client.Redis = redis_lib.Redis().from_url(settings().redis) +storage: Storage = Storage(backend=settings().storage_backend, deta_key=settings().deta_project_key, + deta_id=settings().deta_project_id, storage_path=settings().storage_path) diff --git a/classquiz/kahoot_importer/import_quiz.py b/classquiz/kahoot_importer/import_quiz.py index 301b856..4c8710c 100644 --- a/classquiz/kahoot_importer/import_quiz.py +++ b/classquiz/kahoot_importer/import_quiz.py @@ -6,8 +6,7 @@ from aiohttp import ClientSession, FormData import html from classquiz.db.models import Quiz, QuizAnswer, QuizQuestion, User from classquiz.kahoot_importer.get import get as get_quiz -from classquiz.config import settings -from classquiz.storage import Storage +from classquiz.config import settings, storage settings = settings() @@ -65,8 +64,7 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str: return "quiz not found" quiz_questions: list[dict] = [] quiz_id = uuid.uuid4() - storage = Storage(backend=settings.storage_backend, deta_key=settings.deta_project_key, - deta_id=settings.deta_project_id, storage_path=settings.storage_path) + for q in quiz.kahoot.questions: answers: list[QuizAnswer] = [] diff --git a/classquiz/routers/quiz.py b/classquiz/routers/quiz.py index 9399cc3..3a3f681 100644 --- a/classquiz/routers/quiz.py +++ b/classquiz/routers/quiz.py @@ -9,8 +9,9 @@ from fastapi.responses import JSONResponse import re from classquiz.auth import get_current_user, get_current_user_optional -from classquiz.config import redis, settings +from classquiz.config import redis, settings, storage from classquiz.db.models import Quiz, QuizInput, User, PlayGame + settings = settings() router = APIRouter() @@ -108,7 +109,17 @@ async def delete_quiz(quiz_id: str, user: User = Depends(get_current_user)): except ValueError: raise HTTPException(status_code=400, detail="badly formed quiz id") 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"}) - else: - return await quiz.delete() + pics_to_delete = [] + pic_name_regex = re.compile("^.*/(.{36}--.{36})$") + for question in quiz.questions: + try: + if question["image"] is not None: + if not str(question["image"]).startswith("https://i.imgur.com/"): + pics_to_delete.append(pic_name_regex.match(question["image"]).group(1)) + except KeyError: + pass + await storage.delete(pics_to_delete) + return await quiz.delete() diff --git a/classquiz/routers/storage.py b/classquiz/routers/storage.py index e2a403f..14fafdc 100644 --- a/classquiz/routers/storage.py +++ b/classquiz/routers/storage.py @@ -1,8 +1,7 @@ from fastapi import APIRouter, HTTPException from fastapi.responses import StreamingResponse -from classquiz.config import settings +from classquiz.config import settings, storage import re -from classquiz.storage import Storage settings = settings() @@ -13,8 +12,6 @@ file_regex = r"^[a-z0-9]{8}-[a-z0-9-]{27}--[a-z0-9-]{36}$" @router.get('/download/{file_name}') async def download_file(file_name: str): - storage = Storage(backend=settings.storage_backend, deta_key=settings.deta_project_key, - deta_id=settings.deta_project_id, storage_path=settings.storage_path) if not re.match(file_regex, file_name): raise HTTPException(status_code=400, detail="Invalid file name") diff --git a/classquiz/storage/__init__.py b/classquiz/storage/__init__.py index 4d3e63a..f03bf44 100644 --- a/classquiz/storage/__init__.py +++ b/classquiz/storage/__init__.py @@ -8,7 +8,7 @@ class Storage: self.backend = backend self.deta_key = deta_key self.deta_id = deta_id - self.deta_base_url = f"https://drive.deta.sh/v1/{deta_id}/classquiz" + self.deta_base_url = f"https://drive.deta.sh/v1/{deta_id}/classquiz1" self.deta_instance = DetaStorage(deta_base_url=self.deta_base_url, deta_key=self.deta_key, deta_id=self.deta_id) self.local_instance = LocalStorage(base_path=storage_path) if backend == "deta": @@ -32,3 +32,9 @@ class Storage: return await self.deta_instance.upload(file=file_data, file_name=file_name) elif self.backend == "local": return await self.local_instance.write_file(file_name=file_name, data=file_data) + + async def delete(self, file_names: [str]) -> None: + if self.backend == "deta": + return await self.deta_instance.delete(file_names=file_names) + elif self.backend == "local": + return await self.local_instance.delete_file(file_names=file_names) diff --git a/classquiz/storage/deta_storage.py b/classquiz/storage/deta_storage.py index 5921abd..ffe0b2f 100644 --- a/classquiz/storage/deta_storage.py +++ b/classquiz/storage/deta_storage.py @@ -1,9 +1,6 @@ -from classquiz.config import settings from aiohttp import ClientSession from io import BytesIO -settings = settings() - class DetaStorage: def __init__(self, deta_base_url: str, deta_id: str, deta_key: str): @@ -42,10 +39,10 @@ class DetaStorage: else: raise Exception("Upload failed") - async def delete(self, file_name: [str]) -> None: + async def delete(self, file_names: [str]) -> None: async with ClientSession(headers=self.headers) as session: - async with session.delete(f"{self.deta_url}/files/delete", data={"names": file_name}) as response: + async with session.delete(f"{self.deta_url}/files", json={"names": file_names}) as response: if response.status == 200: return None else: - raise Exception("Delete failed") \ No newline at end of file + raise Exception("Delete failed") diff --git a/classquiz/storage/local_storage.py b/classquiz/storage/local_storage.py index f07598a..4ae76f8 100644 --- a/classquiz/storage/local_storage.py +++ b/classquiz/storage/local_storage.py @@ -1,17 +1,28 @@ import io import os import aiofiles +import aiofiles.os class LocalStorage: def __init__(self, base_path: str): self.base_path = base_path - async def get_file(self, file_name: str) -> io.BytesIO: - async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode='rb') as f: - return io.BytesIO(await f.read()) + async def get_file(self, file_name: str) -> io.BytesIO | None: + try: + async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode='rb') as f: + return io.BytesIO(await f.read()) + except FileNotFoundError: + return None async def write_file(self, file_name: str, data: bytes) -> None: async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode='wb') as f: await f.write(data) + async def delete_file(self, file_names: [str]) -> None: + for i in file_names: + try: + await aiofiles.os.remove(os.path.join(self.base_path, i)) + except FileNotFoundError: + pass + return None