LiveQuiz is a real-time feedback tool for classrooms. It allows instructors to launch quick quizzes during lectures to verify student comprehension immediately. The goal is to create an active learning loop between the teacher and students.
In the current domain model, Lecture is the aggregate root. Quiz interactions are modeled as lecture behavior:
- instructors create ordered questions and unlock them when needed,
- students advance through the oldest unlocked question they have not submitted yet,
- submissions keep full history while the latest attempt is canonical.
This project serves as a comprehensive learning exercise for Java Spring Boot and Angular.
- Manual Coding: The user writes every line of code. The AI's role is to explain concepts and guide, not to do the work.
- Deep Understanding: No "magic" code. Every annotation, configuration, and pattern must be understood.
- Architecture: Strict adherence to Domain-Driven Design (DDD) and Clean Architecture principles.
- JWT authentication with instructor/student roles
- Backend teacher registry for instructor role classification (feature-flagged rollout)
- Postgres-backed instructor email/password authentication with bcrypt hashes
- Instructor multi-lecture workflow (list, create, open by route, refresh-safe context)
- Lecture creation and ordered question management
- Question unlock flow (
unlock specificandunlock next) - Instructor analytics and per-question student answer history drilldown
- Instructor-generated invite flow (6-char code + QR-friendly token URL, max 24h)
- Student route-driven journey (lecture list, room re-entry, token deep-link join)
- Student return authentication via school-email magic link (
request-login-> secure link -> JWT) - Explicit invite error semantics (
INVITE_NOT_FOUND,INVITE_REVOKED,INVITE_EXPIRED) - Student progression API for "next pending unlocked question"
- Submission cooldown policy with
429rejection and retry metadata
- Students can request a return link from
/auth/loginusing their@ynov.comemail. - Backend endpoint:
POST /api/auth/students/request-login(generic non-enumerating response). - Link consumption endpoint:
POST /api/auth/students/verify-emailreturns a student JWT. - For
REGISTERED_UNVERIFIEDstudents, the same link both verifies email and signs them in.
This repository is a monorepo containing:
backend/: Java Spring Boot applicationfrontend/: Angular application
- Java 17+
- Bun (preferred for frontend scripts) and Node.js runtime
- Docker Desktop (for Postgres and RabbitMQ local services)
Use this when you want to run fast without Postgres.
- Start backend with in-memory profile:
cd backendSPRING_PROFILES_ACTIVE=in-memory ./mvnw spring-boot:run
- Install frontend dependencies:
cd ../frontendbun install
- Run frontend:
bun run start
- Open app:
- Frontend:
http://localhost:4200 - Backend health:
http://localhost:8080/api/health
- Frontend:
Default in-memory instructor login:
- Email:
instructor@ynov.com - Password:
password
Use this when you want the default backend profile and Flyway migrations.
- Start infra services:
cd backenddocker compose up -d
- Set required JWT secret (required outside in-memory profiles):
export LIVEQUIZ_JWT_SECRET='replace-with-a-long-random-secret'
- (Optional) Override DB/Rabbit config if needed:
SPRING_DATASOURCE_URLSPRING_DATASOURCE_USERNAMESPRING_DATASOURCE_PASSWORDSPRING_RABBITMQ_HOSTSPRING_RABBITMQ_PORT
- Run backend:
./mvnw spring-boot:run
- Run frontend (separate terminal):
cd ../frontendbun installbun run start
The root docker-compose.yml runs frontend, backend, Postgres, and RabbitMQ as containers.
- Create a
.env.productionfile at repository root with required values (DB, RabbitMQ, JWT secret, mail settings). - Start stack:
docker compose up -d --build
- Access app:
- Frontend:
http://localhost
- Frontend:
There are two layers involved in instructor access:
- Authentication account (login credentials)
- Teacher classification (who should receive
INSTRUCTORin JWT)
In postgres profile, instructor credentials come from DB table instructor_accounts.
From backend/:
- Interactive prompt (recommended):
./scripts/hash-password.sh - Stdin mode (safe for automation):
printf '%s\n' 'MyStrongPassword123!' | ./scripts/hash-password.sh
The command prints a bcrypt hash you can copy into SQL.
INSERT INTO instructor_accounts (email, password_hash, active, created_at, updated_at)
VALUES ('teacher@ynov.com', '<PASTE_BCRYPT_HASH_HERE>', TRUE, NOW(), NOW())
ON CONFLICT (email)
DO UPDATE SET password_hash = EXCLUDED.password_hash, active = EXCLUDED.active, updated_at = NOW();- Default mode (
LIVEQUIZ_TEACHER_ROLE_CLASSIFICATION_ENABLED=false):- Login role comes from legacy Spring authorities.
- Registry mode (
LIVEQUIZ_TEACHER_ROLE_CLASSIFICATION_ENABLED=true):- Login role is resolved from
teacher_identitiestable. - If a principal is missing or inactive in registry, role falls back to
STUDENT.
- Login role is resolved from
Set environment variable before starting backend:
export LIVEQUIZ_TEACHER_ROLE_CLASSIFICATION_ENABLED=true
principal_id must match the instructor login email (normalized lowercase).
INSERT INTO teacher_identities (principal_id, active, created_at, updated_at)
VALUES ('teacher@ynov.com', TRUE, NOW(), NOW())
ON CONFLICT (principal_id)
DO UPDATE SET active = EXCLUDED.active, updated_at = NOW();If Postgres runs in Docker from backend/docker-compose.yml, you can execute:
docker exec -it livequiz-db psql -U user -d livequiz
Then run the SQL above.
UPDATE teacher_identities
SET active = FALSE, updated_at = NOW()
WHERE principal_id = 'teacher@ynov.com';Optional: disable login account too.
UPDATE instructor_accounts
SET active = FALSE, updated_at = NOW()
WHERE email = 'teacher@ynov.com';- Backend unit tests:
cd backend && ./mvnw test - Backend integration + verify:
cd backend && ./mvnw verify - Frontend tests:
cd frontend && bun run test - Frontend build:
cd frontend && bun run build