✨ Added storage_limit for user accounts
This commit is contained in:
@@ -50,6 +50,7 @@ class Settings(BaseSettings):
|
|||||||
github_client_secret: Optional[str]
|
github_client_secret: Optional[str]
|
||||||
custom_openid_provider: CustomOpenIDProvider | None = None
|
custom_openid_provider: CustomOpenIDProvider | None = None
|
||||||
telemetry_enabled: bool = True
|
telemetry_enabled: bool = True
|
||||||
|
free_storage_limit: int = 1074000000
|
||||||
|
|
||||||
# storage_backend
|
# storage_backend
|
||||||
storage_backend: str | None = "deta"
|
storage_backend: str | None = "deta"
|
||||||
|
|||||||
@@ -65,25 +65,6 @@ async def init_editor(edit: bool, quiz_id: Optional[UUID] = None, user: User = D
|
|||||||
return InitEditorResponse(token=edit_id)
|
return InitEditorResponse(token=edit_id)
|
||||||
|
|
||||||
|
|
||||||
class GetPowData(BaseModel):
|
|
||||||
data: str
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/pow", response_model=GetPowData)
|
|
||||||
async def get_pow_data(edit_id: str):
|
|
||||||
session_data = await redis.get(f"edit_session:{edit_id}")
|
|
||||||
if session_data is None:
|
|
||||||
raise HTTPException(status_code=401, detail="Edit ID not found!")
|
|
||||||
random_str = os.urandom(8).hex()
|
|
||||||
await redis.set(f"edit_session:{edit_id}:pow", random_str, ex=3800)
|
|
||||||
return GetPowData(data=random_str)
|
|
||||||
|
|
||||||
|
|
||||||
class UploadImageReturn(BaseModel):
|
|
||||||
id: str
|
|
||||||
pow_data: str
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/finish")
|
@router.post("/finish")
|
||||||
async def finish_edit(edit_id: str, quiz_input: QuizInput):
|
async def finish_edit(edit_id: str, quiz_input: QuizInput):
|
||||||
session_data = await redis.get(f"edit_session:{edit_id}")
|
session_data = await redis.get(f"edit_session:{edit_id}")
|
||||||
|
|||||||
@@ -11,17 +11,15 @@ from random import randint
|
|||||||
|
|
||||||
import ormar.exceptions
|
import ormar.exceptions
|
||||||
|
|
||||||
from classquiz.helpers import get_meili_data, generate_spreadsheet
|
from classquiz.helpers import generate_spreadsheet
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from fastapi.responses import JSONResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
from pydantic import ValidationError, BaseModel
|
from pydantic import ValidationError, BaseModel
|
||||||
import bleach
|
|
||||||
|
|
||||||
from classquiz.auth import get_current_user
|
from classquiz.auth import get_current_user
|
||||||
from classquiz.config import redis, settings, storage, meilisearch
|
from classquiz.config import redis, settings, storage, meilisearch
|
||||||
from classquiz.db.models import Quiz, QuizInput, User, PlayGame, GameInLobby, QuizQuestion
|
from classquiz.db.models import Quiz, User, PlayGame, GameInLobby, QuizQuestion
|
||||||
from classquiz.kahoot_importer.import_quiz import import_quiz
|
from classquiz.kahoot_importer.import_quiz import import_quiz
|
||||||
import html
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
settings = settings()
|
settings = settings()
|
||||||
@@ -29,33 +27,6 @@ settings = settings()
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/create", deprecated=True)
|
|
||||||
async def create_quiz_lol(quiz_input: QuizInput, user: User = Depends(get_current_user)):
|
|
||||||
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}$"
|
|
||||||
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))
|
|
||||||
for question in quiz_input.questions:
|
|
||||||
if question.image == "":
|
|
||||||
question.image = None
|
|
||||||
if (
|
|
||||||
question.image is not None
|
|
||||||
and not re.match(imgur_regex, question.image)
|
|
||||||
and not re.match(server_regex, question.image)
|
|
||||||
):
|
|
||||||
raise HTTPException(status_code=400, detail="image url is not valid")
|
|
||||||
if quiz_input.cover_image == "":
|
|
||||||
quiz_input.cover_image = None
|
|
||||||
|
|
||||||
if quiz_input.cover_image is not None and not bool(re.match(server_regex, quiz_input.cover_image)):
|
|
||||||
raise HTTPException(status_code=400, detail="image url is not valid")
|
|
||||||
quiz = Quiz(**quiz_input.dict(), user_id=user.id, id=uuid.uuid4())
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/get/{quiz_id}")
|
@router.get("/get/{quiz_id}")
|
||||||
async def get_quiz_from_id(quiz_id: str, user: User | None = Depends(get_current_user)):
|
async def get_quiz_from_id(quiz_id: str, user: User | None = Depends(get_current_user)):
|
||||||
try:
|
try:
|
||||||
@@ -213,54 +184,10 @@ async def get_quiz_list(user: User = Depends(get_current_user), page_size: int |
|
|||||||
raise HTTPException(status_code=400, detail="Invalid page(size). page(size) have to be greater than 0.")
|
raise HTTPException(status_code=400, detail="Invalid page(size). page(size) have to be greater than 0.")
|
||||||
|
|
||||||
|
|
||||||
@router.put("/update/{quiz_id}", deprecated=True)
|
|
||||||
async def update_quiz(quiz_id: str, quiz_input: QuizInput, user: User = Depends(get_current_user)):
|
|
||||||
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}}$"
|
|
||||||
for question in quiz_input.questions:
|
|
||||||
if question.image == "":
|
|
||||||
question.image = None
|
|
||||||
if (
|
|
||||||
question.image is not None
|
|
||||||
and not bool(re.match(server_regex, question.image))
|
|
||||||
and not bool(re.match(imgur_regex, question.image))
|
|
||||||
):
|
|
||||||
raise HTTPException(status_code=400, detail="image url is not valid")
|
|
||||||
try:
|
|
||||||
quiz_id = uuid.UUID(quiz_id)
|
|
||||||
except ValueError:
|
|
||||||
raise HTTPException(status_code=400, detail="badly formed quiz id")
|
|
||||||
# Check Cover-Image
|
|
||||||
|
|
||||||
if quiz_input.cover_image == "":
|
|
||||||
quiz_input.cover_image = None
|
|
||||||
|
|
||||||
if quiz_input.cover_image is not None and not bool(re.match(server_regex, quiz_input.cover_image)):
|
|
||||||
raise HTTPException(status_code=400, detail="image url is not valid")
|
|
||||||
|
|
||||||
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:
|
|
||||||
quiz_input.description = html.unescape(bleach.clean(quiz_input.description, tags=[], strip=True))
|
|
||||||
quiz_input.title = html.unescape(bleach.clean(quiz_input.title, tags=[], strip=True))
|
|
||||||
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))
|
|
||||||
if not quiz.public and quiz_input.public:
|
|
||||||
meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)])
|
|
||||||
quiz.title = quiz_input.title
|
|
||||||
quiz.cover_image = quiz_input.cover_image
|
|
||||||
quiz.public = quiz_input.public
|
|
||||||
quiz.description = quiz_input.description
|
|
||||||
quiz.updated_at = datetime.now()
|
|
||||||
quiz.questions = quiz_input.dict()["questions"]
|
|
||||||
|
|
||||||
return await quiz.update()
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/import/{quiz_id}")
|
@router.post("/import/{quiz_id}")
|
||||||
async def import_quiz_route(quiz_id: str, user: User = Depends(get_current_user)):
|
async def import_quiz_route(quiz_id: str, user: User = Depends(get_current_user)):
|
||||||
|
if user.storage_used > settings.free_storage_limit:
|
||||||
|
raise HTTPException(status_code=409, detail="Storage limit reached")
|
||||||
try:
|
try:
|
||||||
return await import_quiz(quiz_id, user)
|
return await import_quiz(quiz_id, user)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
|
|||||||
@@ -52,17 +52,11 @@ async def download_file(file_name: str):
|
|||||||
|
|
||||||
@router.post("/")
|
@router.post("/")
|
||||||
async def upload_file(file: UploadFile = File(), user: User = Depends(get_current_user)) -> PublicStorageItem:
|
async def upload_file(file: UploadFile = File(), user: User = Depends(get_current_user)) -> PublicStorageItem:
|
||||||
|
if user.storage_used > settings.free_storage_limit:
|
||||||
|
raise HTTPException(status_code=409, detail="Storage limit reached")
|
||||||
file_id = uuid4()
|
file_id = uuid4()
|
||||||
|
|
||||||
size = 0
|
size = 0
|
||||||
# if file.file.name is None:
|
|
||||||
# size = len(await file.read())
|
|
||||||
# print("MemorySize", size)
|
|
||||||
# else:
|
|
||||||
# f = file.file
|
|
||||||
# a = file.file.fileno()
|
|
||||||
# os.path.getsize(file.file.name)
|
|
||||||
# print("DiskSize", size, "name:", file.file.name, "a:", file.file.tell(), "size")
|
|
||||||
file_obj = StorageItem(
|
file_obj = StorageItem(
|
||||||
id=file_id,
|
id=file_id,
|
||||||
uploaded_at=datetime.now(),
|
uploaded_at=datetime.now(),
|
||||||
@@ -137,16 +131,16 @@ async def list_images(
|
|||||||
return return_items
|
return return_items
|
||||||
|
|
||||||
|
|
||||||
class ReturnStorageUsage(BaseModel):
|
class ReturnGetStorageLimit(BaseModel):
|
||||||
usage: int
|
limit: int
|
||||||
|
limit_reached: bool
|
||||||
|
used: int
|
||||||
|
|
||||||
|
|
||||||
@router.get("/usage")
|
@router.get("/limit")
|
||||||
async def get_storage_usage(user: User = Depends(get_current_user)) -> ReturnStorageUsage:
|
async def get_storage_limit(user: User = Depends(get_current_user)) -> ReturnGetStorageLimit:
|
||||||
files: list[StorageItem] = await StorageItem.objects.filter(user=user).filter(deleted_at=None).all()
|
user = await User.objects.get_or_none(id=user.id)
|
||||||
counted_size = 0
|
if user.storage_used > settings.free_storage_limit:
|
||||||
if len(files) == 0:
|
return ReturnGetStorageLimit(limit=settings.free_storage_limit, limit_reached=True, used=user.storage_used)
|
||||||
raise HTTPException(status_code=404, detail="No file found")
|
else:
|
||||||
for file in files:
|
return ReturnGetStorageLimit(limit=settings.free_storage_limit, limit_reached=False, used=user.storage_used)
|
||||||
counted_size += file.size
|
|
||||||
return ReturnStorageUsage(usage=counted_size)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user