pdf-quiz-generator/backend/app/routers/public.py
Daniel 77c09db057 feat: the objective is not optional, and the site can count itself
Choosing what you are studying for has no way past it now but to answer.
It decides which questions exist, how relevance is weighted and what
readiness measures against, so an account that never answered it was
being shown the whole bank by accident rather than by choice.

What is guarded instead is asking a question that cannot be answered: if
the list of objectives fails to load, or there are none, nothing is shown
at all. A modal with no options in it is not a question, it is a locked
door.

GET /api/public/stats, unauthenticated, so the landing page can state what
there is rather than what someone typed into the markup months ago — a
number written into a page goes stale the week after and nothing breaks
to say so. Counts only, and only of published material: how much there
is, never what it is, so there is nothing here to walk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
2026-09-12 06:40:48 +02:00

43 lines
1.6 KiB
Python

"""The few facts the site may state about itself before anybody signs in.
Counts only, and only of published material. No titles, no ids, nothing that
could be walked to enumerate the bank — a landing page needs to say how much
there is, not what it is.
Unauthenticated on purpose: it is read by the page a stranger lands on.
"""
from fastapi import APIRouter, Depends
from sqlalchemy import func
from sqlalchemy.orm import Session
from app.database import get_db
from app.models.article import Article
from app.models.exam import Exam
from app.models.question import Question
from app.models.question_category import QuestionCategory
router = APIRouter()
@router.get("/stats")
def public_stats(db: Session = Depends(get_db)):
"""What the landing page says there is, taken from what there is.
A number written into the markup is a number that goes stale the week
after it is written, and nobody notices because nothing breaks.
"""
questions = db.query(func.count(Question.id)).filter(
Question.deleted_at.is_(None)).scalar() or 0
topics = db.query(func.count(QuestionCategory.id)).scalar() or 0
systems = db.query(func.count(func.distinct(QuestionCategory.system_id))).filter(
QuestionCategory.system_id.isnot(None)).scalar() or 0
articles = db.query(func.count(Article.id)).filter(
Article.status == "published").scalar() or 0
exams = db.query(func.count(Exam.id)).filter(Exam.is_active == 1).scalar() or 0
return {
"questions": questions,
"topics": topics,
"systems": systems,
"articles": articles,
"exams": exams,
}