pdf-quiz-generator/backend/manage.py
Daniel 47ba213ae3 Major platform update: pgvector search, multi-provider TTS, settings page, CLI
Features:
- Hybrid semantic + keyword quiz search (pgvector HNSW + PostgreSQL ILIKE)
- AWS Bedrock Titan Embed V2 embeddings via LiteLLM proxy (0.71 cosine sim)
- Multi-provider TTS: OpenAI, AWS Polly (neural), ElevenLabs, Google Cloud TTS
- Unified Settings page (profile, theme, Nextcloud integration, admin shortcuts)
- Good morning/afternoon greeting on dashboard
- manage.py CLI: reset-password, list-users, reembed
- Email verification enforced: register no longer returns JWT for unverified users
- Quiz search with debounced input, semantic/keyword/title modes, highlighted snippets
- TTS button: loading/playing states, voice selector locked during playback
- TTS auto-stops when navigating between questions
- Footer added; mobile quiz nav overflow fixed; markdown theme body selector fixed
- OpenAI Alloy as default TTS voice; favicon added
- SMTP configured via smtp2go; password reset rate limiting (3/hour)
- PostgreSQL upgraded to pgvector/pgvector:pg16

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:03:10 +02:00

100 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""PedQuiz management CLI.
Usage (inside container):
python manage.py reset-password <email> <new-password>
python manage.py list-users
python manage.py reembed # Regenerate all question embeddings
"""
import argparse
import os
import sys
# Ensure app is importable
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def reset_password(email: str, new_password: str):
from app.database import SessionLocal
from app.models.user import User
from app.utils.auth import get_password_hash
if len(new_password) < 8:
print("Error: Password must be at least 8 characters")
sys.exit(1)
db = SessionLocal()
try:
user = db.query(User).filter(User.email == email).first()
if not user:
print(f"Error: No user found with email '{email}'")
sys.exit(1)
user.hashed_password = get_password_hash(new_password)
db.commit()
print(f"Password reset for {user.email} (name={user.name!r}, role={user.role})")
finally:
db.close()
def list_users():
from app.database import SessionLocal
from app.models.user import User
from app.models.email_verification import EmailVerification
db = SessionLocal()
try:
users = db.query(User).order_by(User.created_at).all()
print(f"{'ID':>4} {'Email':40} {'Role':12} {'Verified':8} Name")
print("-" * 90)
for u in users:
v = db.query(EmailVerification).filter(EmailVerification.user_id == u.id).first()
verified = "yes" if (v and v.verified_at) else "no"
print(f"{u.id:>4} {u.email:40} {u.role:12} {verified:8} {u.name}")
finally:
db.close()
def reembed():
"""Regenerate embeddings for all questions using current embedding model."""
from app.database import SessionLocal
from app.models.question import Question
from app.services import embedding_service
db = SessionLocal()
try:
questions = db.query(Question).all()
print(f"Re-embedding {len(questions)} questions...")
ok = fail = 0
for q in questions:
q.embedding = None # force regeneration
if embedding_service.embed_question(q):
ok += 1
else:
fail += 1
db.commit()
print(f"Done: {ok} succeeded, {fail} failed")
finally:
db.close()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="PedQuiz management CLI")
sub = parser.add_subparsers(dest="command")
rp = sub.add_parser("reset-password", help="Reset a user password")
rp.add_argument("email", help="User email address")
rp.add_argument("password", help="New password (min 8 chars)")
sub.add_parser("list-users", help="List all users with verification status")
sub.add_parser("reembed", help="Regenerate all question embeddings")
args = parser.parse_args()
if args.command == "reset-password":
reset_password(args.email, args.password)
elif args.command == "list-users":
list_users()
elif args.command == "reembed":
reembed()
else:
parser.print_help()