diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e646728 --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +# SQLite database path +QUIZ_DB_PATH=./quiz.db + +# Required cookie signing secret +QUIZ_SECRET_KEY=change-me-to-a-random-secret + +# Required shared instructor password +QUIZ_ADMIN_PASSWORD=change-me + +# Uvicorn bind settings +QUIZ_HOST=127.0.0.1 +QUIZ_PORT=8001 + +# Public URL used for student join links and QR codes +QUIZ_PUBLIC_URL=https://quiz.ahkhan.me + +# Python logging level +QUIZ_LOG_LEVEL=INFO diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed2758e --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.venv/ +__pycache__/ +*.py[cod] +.pytest_cache/ +.coverage +htmlcov/ +.env +quiz.db +*.db +*.db-shm +*.db-wal diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..3245caf --- /dev/null +++ b/NOTES.md @@ -0,0 +1,3 @@ +# Notes + +Implementation notes will be filled in as the application lands. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba1548f --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Live In-Lecture Quiz Portal + +Development notes will be filled in as the implementation lands. diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..25d1bf6 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,3 @@ +"""Live quiz application package.""" + +__version__ = "0.1.0" diff --git a/app/auth.py b/app/auth.py new file mode 100644 index 0000000..e67d217 --- /dev/null +++ b/app/auth.py @@ -0,0 +1 @@ +"""Cookie signing helpers.""" diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..b3ef134 --- /dev/null +++ b/app/config.py @@ -0,0 +1 @@ +"""Application configuration.""" diff --git a/app/csv_export.py b/app/csv_export.py new file mode 100644 index 0000000..7e2565b --- /dev/null +++ b/app/csv_export.py @@ -0,0 +1 @@ +"""CSV export helpers.""" diff --git a/app/db.py b/app/db.py new file mode 100644 index 0000000..d910356 --- /dev/null +++ b/app/db.py @@ -0,0 +1 @@ +"""SQLite helpers.""" diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..cc506bf --- /dev/null +++ b/app/main.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI + + +def create_app() -> FastAPI: + app = FastAPI(title="Live In-Lecture Quiz Portal") + return app + + +app = create_app() diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..4294a13 --- /dev/null +++ b/app/models.py @@ -0,0 +1 @@ +"""Pydantic request and response models.""" diff --git a/app/pool.py b/app/pool.py new file mode 100644 index 0000000..0f89a46 --- /dev/null +++ b/app/pool.py @@ -0,0 +1 @@ +"""Question pool validation.""" diff --git a/app/room.py b/app/room.py new file mode 100644 index 0000000..278a10e --- /dev/null +++ b/app/room.py @@ -0,0 +1 @@ +"""In-process WebSocket room manager.""" diff --git a/app/routes_admin.py b/app/routes_admin.py new file mode 100644 index 0000000..345c6b7 --- /dev/null +++ b/app/routes_admin.py @@ -0,0 +1 @@ +"""Instructor routes.""" diff --git a/app/routes_student.py b/app/routes_student.py new file mode 100644 index 0000000..620704c --- /dev/null +++ b/app/routes_student.py @@ -0,0 +1 @@ +"""Student routes.""" diff --git a/app/scoring.py b/app/scoring.py new file mode 100644 index 0000000..f4be998 --- /dev/null +++ b/app/scoring.py @@ -0,0 +1 @@ +"""Score functions.""" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..437ba59 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,39 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "quiz" +version = "0.1.0" +description = "Live in-lecture quiz portal" +requires-python = ">=3.11" +dependencies = [ + "fastapi~=0.115", + "uvicorn[standard]~=0.34", + "aiosqlite~=0.20", + "itsdangerous~=2.2", + "python-multipart~=0.0.20", + "qrcode[pil]~=8.0", +] + +[project.optional-dependencies] +dev = [ + "pytest~=8.3", + "pytest-asyncio~=0.25", + "pytest-cov~=6.0", + "httpx~=0.28", +] + +[tool.setuptools] +packages = ["app"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] + +[tool.coverage.run] +source = ["app"] + +[tool.coverage.report] +show_missing = true +fail_under = 80 diff --git a/static/admin.html b/static/admin.html new file mode 100644 index 0000000..5945a9a --- /dev/null +++ b/static/admin.html @@ -0,0 +1,13 @@ + + + + + + Quiz Admin + + + +
+ + + diff --git a/static/admin.js b/static/admin.js new file mode 100644 index 0000000..c5fea96 --- /dev/null +++ b/static/admin.js @@ -0,0 +1,2 @@ +const app = document.querySelector("#admin-app"); +app.textContent = "Loading admin..."; diff --git a/static/observer.html b/static/observer.html new file mode 100644 index 0000000..e782cae --- /dev/null +++ b/static/observer.html @@ -0,0 +1,15 @@ + + + + + + Quiz Observer + + + +
+

Quiz Observer

+

This read-only view is reserved for a future classroom display.

+
+ + diff --git a/static/quiz.js b/static/quiz.js new file mode 100644 index 0000000..598e3bf --- /dev/null +++ b/static/quiz.js @@ -0,0 +1,2 @@ +const app = document.querySelector("#app"); +app.textContent = "Loading quiz..."; diff --git a/static/student.html b/static/student.html new file mode 100644 index 0000000..51e2509 --- /dev/null +++ b/static/student.html @@ -0,0 +1,13 @@ + + + + + + Quiz + + + +
+ + + diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..93959ce --- /dev/null +++ b/static/style.css @@ -0,0 +1,17 @@ +:root { + color-scheme: light dark; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +body { + margin: 0; + min-height: 100vh; + background: #f6f7f9; + color: #18212f; +} + +.shell { + max-width: 960px; + margin: 0 auto; + padding: 24px; +} diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..18ef0e3 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1 @@ +"""Test fixtures.""" diff --git a/tests/test_api_admin.py b/tests/test_api_admin.py new file mode 100644 index 0000000..ecef4fa --- /dev/null +++ b/tests/test_api_admin.py @@ -0,0 +1,2 @@ +def test_placeholder_api_admin(): + assert True diff --git a/tests/test_api_student.py b/tests/test_api_student.py new file mode 100644 index 0000000..a278bbd --- /dev/null +++ b/tests/test_api_student.py @@ -0,0 +1,2 @@ +def test_placeholder_api_student(): + assert True diff --git a/tests/test_auth.py b/tests/test_auth.py new file mode 100644 index 0000000..feda49c --- /dev/null +++ b/tests/test_auth.py @@ -0,0 +1,2 @@ +def test_placeholder_auth(): + assert True diff --git a/tests/test_csv_export.py b/tests/test_csv_export.py new file mode 100644 index 0000000..51781bc --- /dev/null +++ b/tests/test_csv_export.py @@ -0,0 +1,2 @@ +def test_placeholder_csv_export(): + assert True diff --git a/tests/test_late_join.py b/tests/test_late_join.py new file mode 100644 index 0000000..8fdbb5f --- /dev/null +++ b/tests/test_late_join.py @@ -0,0 +1,2 @@ +def test_placeholder_late_join(): + assert True diff --git a/tests/test_load_simulation.py b/tests/test_load_simulation.py new file mode 100644 index 0000000..8445559 --- /dev/null +++ b/tests/test_load_simulation.py @@ -0,0 +1,2 @@ +def test_placeholder_load_simulation(): + assert True diff --git a/tests/test_pool.py b/tests/test_pool.py new file mode 100644 index 0000000..f7f323c --- /dev/null +++ b/tests/test_pool.py @@ -0,0 +1,2 @@ +def test_placeholder_pool(): + assert True diff --git a/tests/test_reconnect.py b/tests/test_reconnect.py new file mode 100644 index 0000000..d26e061 --- /dev/null +++ b/tests/test_reconnect.py @@ -0,0 +1,2 @@ +def test_placeholder_reconnect(): + assert True diff --git a/tests/test_scoring.py b/tests/test_scoring.py new file mode 100644 index 0000000..7547cc9 --- /dev/null +++ b/tests/test_scoring.py @@ -0,0 +1,2 @@ +def test_placeholder_scoring(): + assert True diff --git a/tests/test_state_machine.py b/tests/test_state_machine.py new file mode 100644 index 0000000..898c13d --- /dev/null +++ b/tests/test_state_machine.py @@ -0,0 +1,2 @@ +def test_placeholder_state_machine(): + assert True diff --git a/tests/test_ws_admin.py b/tests/test_ws_admin.py new file mode 100644 index 0000000..5226ea7 --- /dev/null +++ b/tests/test_ws_admin.py @@ -0,0 +1,2 @@ +def test_placeholder_ws_admin(): + assert True diff --git a/tests/test_ws_student.py b/tests/test_ws_student.py new file mode 100644 index 0000000..5350a6b --- /dev/null +++ b/tests/test_ws_student.py @@ -0,0 +1,2 @@ +def test_placeholder_ws_student(): + assert True