|
# retoor <retoor@molodetz.nl>
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime, timezone
|
|
|
|
from devplacepy.database import conditional_update_row, get_table
|
|
from devplacepy.utils import is_admin
|
|
|
|
|
|
class QuizError(Exception):
|
|
pass
|
|
|
|
|
|
def _now() -> datetime:
|
|
return datetime.now(timezone.utc)
|
|
|
|
|
|
def _iso(value: datetime | None = None) -> str:
|
|
return (value or _now()).isoformat()
|
|
|
|
|
|
def _quizzes():
|
|
return get_table("quizzes")
|
|
|
|
|
|
def _questions():
|
|
return get_table("quiz_questions")
|
|
|
|
|
|
def _options():
|
|
return get_table("quiz_options")
|
|
|
|
|
|
def _attempts():
|
|
return get_table("quiz_attempts")
|
|
|
|
|
|
def _answers():
|
|
return get_table("quiz_answers")
|
|
|
|
|
|
def born_live(fields: dict) -> dict:
|
|
stamp = _iso()
|
|
return {
|
|
**fields,
|
|
"created_at": fields.get("created_at") or stamp,
|
|
"updated_at": stamp,
|
|
"deleted_at": None,
|
|
"deleted_by": None,
|
|
}
|
|
|
|
|
|
def load_json(raw, fallback):
|
|
if isinstance(raw, (list, dict)):
|
|
return raw
|
|
try:
|
|
parsed = json.loads(raw or "")
|
|
except (TypeError, ValueError):
|
|
return fallback
|
|
return parsed if isinstance(parsed, type(fallback)) else fallback
|
|
|
|
|
|
def dump_json(value) -> str:
|
|
return json.dumps(value, ensure_ascii=False)
|
|
|
|
|
|
def get_quiz(quiz_uid: str) -> dict | None:
|
|
if not quiz_uid:
|
|
return None
|
|
return _quizzes().find_one(uid=quiz_uid, deleted_at=None)
|
|
|
|
|
|
def is_quiz_owner(quiz: dict | None, user: dict | None) -> bool:
|
|
return bool(quiz and user and quiz.get("user_uid") == user.get("uid"))
|
|
|
|
|
|
def can_view_quiz(quiz: dict | None, user: dict | None) -> bool:
|
|
if not quiz:
|
|
return False
|
|
if quiz.get("status") == "published":
|
|
return True
|
|
return is_quiz_owner(quiz, user) or is_admin(user)
|
|
|
|
|
|
def guard_editable(quiz_uid: str) -> dict:
|
|
rows = conditional_update_row(
|
|
"quizzes",
|
|
quiz_uid,
|
|
"content_version = COALESCE(content_version, 0) + 1",
|
|
"status = 'draft' AND deleted_at IS NULL",
|
|
{},
|
|
)
|
|
if rows:
|
|
return get_quiz(quiz_uid) or {}
|
|
quiz = get_quiz(quiz_uid)
|
|
if not quiz:
|
|
raise QuizError("Quiz not found")
|
|
raise QuizError("A published quiz can no longer be edited")
|
|
|
|
|
|
__all__ = [
|
|
"QuizError",
|
|
"born_live",
|
|
"can_view_quiz",
|
|
"conditional_update_row",
|
|
"dump_json",
|
|
"get_quiz",
|
|
"guard_editable",
|
|
"is_quiz_owner",
|
|
"load_json",
|
|
"_answers",
|
|
"_attempts",
|
|
"_iso",
|
|
"_now",
|
|
"_options",
|
|
"_questions",
|
|
"_quizzes",
|
|
]
|