✨ Files get deleted, if quiz gets deleted
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import redis.asyncio as redis_lib
|
import redis.asyncio as redis_lib
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from pydantic import BaseSettings, RedisDsn, PostgresDsn
|
from pydantic import BaseSettings, RedisDsn, PostgresDsn
|
||||||
|
from classquiz.storage import Storage
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
@@ -43,3 +44,5 @@ def settings() -> Settings:
|
|||||||
|
|
||||||
|
|
||||||
redis: redis_lib.client.Redis = redis_lib.Redis().from_url(settings().redis)
|
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)
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ from aiohttp import ClientSession, FormData
|
|||||||
import html
|
import html
|
||||||
from classquiz.db.models import Quiz, QuizAnswer, QuizQuestion, User
|
from classquiz.db.models import Quiz, QuizAnswer, QuizQuestion, User
|
||||||
from classquiz.kahoot_importer.get import get as get_quiz
|
from classquiz.kahoot_importer.get import get as get_quiz
|
||||||
from classquiz.config import settings
|
from classquiz.config import settings, storage
|
||||||
from classquiz.storage import Storage
|
|
||||||
|
|
||||||
settings = settings()
|
settings = settings()
|
||||||
|
|
||||||
@@ -65,8 +64,7 @@ async def import_quiz(quiz_id: str, user: User) -> Quiz | str:
|
|||||||
return "quiz not found"
|
return "quiz not found"
|
||||||
quiz_questions: list[dict] = []
|
quiz_questions: list[dict] = []
|
||||||
quiz_id = uuid.uuid4()
|
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:
|
for q in quiz.kahoot.questions:
|
||||||
answers: list[QuizAnswer] = []
|
answers: list[QuizAnswer] = []
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ from fastapi.responses import JSONResponse
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from classquiz.auth import get_current_user, get_current_user_optional
|
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
|
from classquiz.db.models import Quiz, QuizInput, User, PlayGame
|
||||||
|
|
||||||
settings = settings()
|
settings = settings()
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -108,7 +109,17 @@ async def delete_quiz(quiz_id: str, user: User = Depends(get_current_user)):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
raise HTTPException(status_code=400, detail="badly formed quiz id")
|
raise HTTPException(status_code=400, detail="badly formed quiz id")
|
||||||
quiz = await Quiz.objects.get_or_none(id=quiz_id, user_id=user.id)
|
quiz = await Quiz.objects.get_or_none(id=quiz_id, user_id=user.id)
|
||||||
|
|
||||||
if quiz is None:
|
if quiz is None:
|
||||||
return JSONResponse(status_code=404, content={"detail": "quiz not found"})
|
return JSONResponse(status_code=404, content={"detail": "quiz not found"})
|
||||||
else:
|
pics_to_delete = []
|
||||||
return await quiz.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()
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from classquiz.config import settings
|
from classquiz.config import settings, storage
|
||||||
import re
|
import re
|
||||||
from classquiz.storage import Storage
|
|
||||||
|
|
||||||
settings = settings()
|
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}')
|
@router.get('/download/{file_name}')
|
||||||
async def download_file(file_name: str):
|
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):
|
if not re.match(file_regex, file_name):
|
||||||
raise HTTPException(status_code=400, detail="Invalid file name")
|
raise HTTPException(status_code=400, detail="Invalid file name")
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class Storage:
|
|||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.deta_key = deta_key
|
self.deta_key = deta_key
|
||||||
self.deta_id = deta_id
|
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.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)
|
self.local_instance = LocalStorage(base_path=storage_path)
|
||||||
if backend == "deta":
|
if backend == "deta":
|
||||||
@@ -32,3 +32,9 @@ class Storage:
|
|||||||
return await self.deta_instance.upload(file=file_data, file_name=file_name)
|
return await self.deta_instance.upload(file=file_data, file_name=file_name)
|
||||||
elif self.backend == "local":
|
elif self.backend == "local":
|
||||||
return await self.local_instance.write_file(file_name=file_name, data=file_data)
|
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)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
from classquiz.config import settings
|
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
settings = settings()
|
|
||||||
|
|
||||||
|
|
||||||
class DetaStorage:
|
class DetaStorage:
|
||||||
def __init__(self, deta_base_url: str, deta_id: str, deta_key: str):
|
def __init__(self, deta_base_url: str, deta_id: str, deta_key: str):
|
||||||
@@ -42,9 +39,9 @@ class DetaStorage:
|
|||||||
else:
|
else:
|
||||||
raise Exception("Upload failed")
|
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 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:
|
if response.status == 200:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import aiofiles
|
import aiofiles
|
||||||
|
import aiofiles.os
|
||||||
|
|
||||||
|
|
||||||
class LocalStorage:
|
class LocalStorage:
|
||||||
def __init__(self, base_path: str):
|
def __init__(self, base_path: str):
|
||||||
self.base_path = base_path
|
self.base_path = base_path
|
||||||
|
|
||||||
async def get_file(self, file_name: str) -> io.BytesIO:
|
async def get_file(self, file_name: str) -> io.BytesIO | None:
|
||||||
async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode='rb') as f:
|
try:
|
||||||
return io.BytesIO(await f.read())
|
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 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:
|
async with aiofiles.open(file=os.path.join(self.base_path, file_name), mode='wb') as f:
|
||||||
await f.write(data)
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user