Joining a lesson
Schedule creates a LessonSession → student clicks Join → Django checks group + active subscription → issues short-lived JWT → browser connects to Jitsi directly. A bare room link is useless without a valid pass.
Modular monolith on Django. Video traffic never hits the application server. Billing, classrooms and homework stay isolated through explicit service boundaries.
Nginx, Daphne, PostgreSQL, Redis and Celery share the application host for year-one simplicity — with Celery resource limits so heavy jobs do not starve live lessons.
Browsers join Jitsi over WebRTC with short-lived JWT passes. Lesson peak load scales on the videobridge, not on Django.
Reports, file conversion, LLM assistance and ephemeral recorders run in Celery. External failures do not take the school offline.
Billing knows nothing about lesson content. Classroom knows nothing about money. Cross-module rules — “inactive subscription → no lesson entry” — go through owner services only.
accountsStudent, Parent, Teacher, Curator, Club instructor
schoolYears, semesters, grades 0–11, groups, shifts
schedulingLessons, consultations, later iCal export
coursesTopics, lessons, materials, cloning
classroomLessonSession, Jitsi JWT, chat, board
homeworkTests, uploads, submissions, review
gradesScores 1–12, attendance, student diary
billingTariffs, invoices, webhooks, debt cycle
clubsDirections, groups, separate billing
notificationsEmail + in-app notifications
integrationsPayments, R2, Anthropic
manage_panelOperational UI for admin / deputy / curator
Schedule creates a LessonSession → student clicks Join → Django checks group + active subscription → issues short-lived JWT → browser connects to Jitsi directly. A bare room link is useless without a valid pass.
Monthly invoice = tariff − discount + clubs → redirect to LiqPay/Fondy/Portmone → signed webhook → mark paid, renew subscription, notify. Overdue invoices open curator retention work.
Student uploads straight to Cloudflare R2 via a signed URL. The app stores a Submission reference and notifies the teacher — the application server never carries the file bytes.
Auto reminders on day 5 and 10. On day 12 the curator reviews a shortlist, unticks negotiated cases, restricts the rest. Lessons close; chat and pay stay open. Payment webhook unlocks instantly. Race-safe: the server re-checks invoice status inside a locked transaction before restricting anyone.
| Layer | Choice | Why |
|---|---|---|
| Application | Hetzner CAX31 (ARM) | Nginx + Daphne + Postgres + Redis + Celery |
| Video | Hetzner CAX41 Jitsi | ~12 rooms; second JVB for 20+ peak |
| Recording | Ephemeral Jibri fleet | Spun up before a shift, destroyed after; MVP records distance webinars only |
| Files | Cloudflare R2 | Materials, homework, selected recordings |
| Backups | Continuous WAL → R2 | Point-in-time recovery is mandatory — parent money is on the line |
| LLM | Anthropic API | Teacher content assistant, offline from the critical path |
Auth, roles, /manage/, academic structure, tariffs, payments, timetable, Jitsi rooms, journals, preschool start.
Parent cabinet, homework, debt cycle, clubs, flexible enrolment, distance mode, calendar, notifications.
Interactive board, selective recording, LLM assistant, progress analytics, CSV export for state reporting, iCal.