feat(teacher): teacher section (classes, roster, progress, guardians, assignments, resources, control, messaging, exams)#274
Conversation
…, assignments, resources, control, messaging, exams + security hardening Adds the full teacher section on top of the existing student experience, following the project's DDD/"Hermes" conventions (repository/service/router per domain; ORM models in data/models; thin routers in gateway/http). Features - Classrooms: create / list / open / delete + dashboard (E1) - Roster: enrol existing students + email invitations with accept flow (E2) - Progress: read-only per-student and per-class activity view (E3) - Guardians: parent link / invite / contact (E4) - Assignments: author / grade / submit + status tracker + file attachments (E7) - Resources: class-materials library + reusable assignment templates (E8) - Control: realtime screen lock/unlock, offline replay, tab-away telemetry, and an always-on "X/Y online now" presence indicator (3s poll) (E6) - Messaging: teacher <-> student 1:1 with unread + realtime delivery (E9) - Proctored exams: timed full-screen shell, grace-warning auto-submit, live proctoring (E10) Security & robustness - Pydantic Field bounds (length / range) on every request model - Server-side upload MIME allowlist + streamed size cap - Pagination (limit/offset, max 100) on list endpoints - Baseline security headers + SPA-safe CSP on every response - Per-caller rate limiting on sensitive mutating routes (production only) - Centralised ownership/enrolment checks (anti-IDOR); ORM-only data access Docs - docs/teacher/teacher-section-user-guide.md (+ screenshots) - docs/teacher/security.md New bounded contexts: classrooms, guardians, assignments, messaging, exams. Full backend test suite green (372 passing).
|
Hi @Ilyas-ek Documentation review — Strong work overall — this is some of the most thorough documentation I've seen on this project. The security doc especially is excellent. A few gaps to address:
Please address the missing guide sections (1 and 3) before approval. Great work! |
Review — feat(teacher): teacher sectionThanks for this contribution — the scope is impressive and the overall architecture is solid. Tested locally after a clean database reset. Several issues found, ranging from blocking to non-blocking. 1. 🔴 Blocking —
|
…ew feedback - students/[sid] page: fetch the real GET /students directory instead of mock data - remove sampleData.ts (leftover dev artifact) and its only import - MIGRATION.md: document schema strategy (teacher section = new tables only; no-Alembic caveat) - user guide: add Control offline-replay + tab-away detail, a proctored-exam / live-proctoring walkthrough, and teacher-side Guardians steps - add docs/teacher/technical-reference.md (endpoints + data models)
|
@Oumaima-elkhoummassi Thanks! All three addressed:
|
|
@baaki-hicham Thanks for the thorough testing — the sampleData catch was a real one. #1 / #3 (sampleData): Fixed. /teacher/students/[sid] now calls the real GET /students directory API (name, activity, supports, guardian count, last-active) with per-class links into the detailed view, and I've deleted sampleData.ts entirely — no remaining references. #2 (schema on upgrade): Added a schema-strategy note to MIGRATION.md. One clarification after checking the diff: this PR adds only new tables and no columns to any pre-existing table, so create_all() creates them cleanly on an existing DB with no data loss — a clean upstream→this-PR upgrade shouldn't hit missing-column errors. The IndexError you saw was most likely from a database that already held an older version of the teacher tables. That said, you're right that the project has no migration framework, so I've documented the reset-on-schema-change caveat and flagged Alembic as the proper long-term fix. |
… visibility - ExamShell: one warning per acknowledged cycle — leaving again while the warning is unacknowledged no longer adds warnings, it only restarts the same grace countdown; in-flight guard stops visibility+fullscreen double-reports - max_violations = N now means the N-th warning terminates (was N+1) - terminated exams surface as auto_submitted (yellow badge) in the student feed and the teacher's per-student breakdown, even when no answer was recovered (never falls back to "To do"/"pending") - finished exams cannot be restarted (422) and an ended exam's submission cannot be replaced; the shell's single post-termination auto-submit still lands - student page shows a read-only "exam ended" panel instead of the Start gate - teachers can grade a terminated exam with no recovered copy (empty submission is created to hold the grade; "No answer recovered" note in UI) - i18n: EN/FR/AR keys for the new student-facing strings - tests: 7 new/updated covering the schedule, feed statuses, retake guards, replacement guard, and grading empty terminations
|
Salam @Ilyas-ek, 1. Class material upload can be used for stored XSS The upload endpoint only checks the file's declared content-type against an allowlist ( Worth noting: the assignment attachments and message attachments in this same PR do this correctly — they use Fix: narrow 2. Rate limiter doesn't actually limit per user — everyone shares one bucket The per-user key is built from Fix: key on something unique per user — decode the token and use the |
|
@Ilyas-ek This PR doesn't follow the project's domain-boundary rule CLAUDE.md is explicit about this: every new domain has to go under one of six boundaries — For comparison, #266 (also adding classroom-management backend code) does this correctly — |
There was a problem hiding this comment.
Pull request overview
This PR introduces a classroom-aware “teacher section” across the SvelteKit UI and FastAPI backend by adding new bounded contexts (classrooms, guardians, assignments, resources, messaging, exams), new teacher/student routes, and cross-cutting security/robustness improvements (pagination, upload restrictions, security headers, optional production rate limiting).
Changes:
- Add teacher-facing UI routes/pages (dashboard, classes, students, progress, resources, messaging) and student-facing routes for resources/messages plus invite-accept flow.
- Add backend bounded contexts + routers for assignments/resources/messaging/exams/guardians, including realtime events for messaging and screen-lock telemetry.
- Add security/robustness layers: pagination dependency, security headers middleware, optional production rate limiting, and upload MIME/size enforcement.
Reviewed changes
Copilot reviewed 100 out of 115 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/routes/teacher/students/+page.svelte | Route wrapper for teacher Students page. |
| ui/src/routes/teacher/students/[sid]/+page.svelte | Teacher per-student directory detail page. |
| ui/src/routes/teacher/settings/+page.svelte | Reuses existing Settings page under teacher routes. |
| ui/src/routes/teacher/resources/+page.svelte | Route wrapper for teacher Resources library. |
| ui/src/routes/teacher/progress/+page.svelte | Route wrapper for teacher Progress page. |
| ui/src/routes/teacher/messages/+page.svelte | Route wrapper for teacher Messages page. |
| ui/src/routes/teacher/dashboard/+page.svelte | Route wrapper for teacher Dashboard page. |
| ui/src/routes/teacher/classes/create/+page.svelte | Route wrapper for teacher class creation flow. |
| ui/src/routes/teacher/classes/+page.svelte | Route wrapper for teacher classes list. |
| ui/src/routes/teacher/classes/[id]/students/[sid]/+page.svelte | Route wrapper for class-scoped student progress. |
| ui/src/routes/teacher/+page.svelte | Teacher entry route redirects to dashboard after role check. |
| ui/src/routes/student/resources/+page.svelte | Route wrapper for student resources page. |
| ui/src/routes/student/messages/+page.svelte | Switches student messages route to StudentMessages component. |
| ui/src/routes/invite/[token]/+page.svelte | Invitation acceptance UI flow. |
| ui/src/routes/+layout.svelte | Adds realtime monitor lock syncing + away telemetry handling + overlay mount. |
| ui/src/lib/stores/index.ts | Adds monitor lock/away + unread messaging stores. |
| ui/src/lib/components/teacher/pages/Students.svelte | Teacher students directory page with filtering/search. |
| ui/src/lib/components/teacher/pages/StudentProgress.svelte | Teacher class-scoped student progress view + guardians modal entry. |
| ui/src/lib/components/teacher/pages/Resources.svelte | Teacher library view for materials + assignment templates. |
| ui/src/lib/components/teacher/pages/Progress.svelte | Teacher progress rollup across classes/students. |
| ui/src/lib/components/teacher/pages/Dashboard.svelte | Teacher dashboard with stats + class cards. |
| ui/src/lib/components/teacher/pages/Classes.svelte | Teacher classes list with search + empty state. |
| ui/src/lib/components/teacher/elements/UploadMaterialModal.svelte | Modal for uploading class materials. |
| ui/src/lib/components/teacher/elements/TemplateModal.svelte | Modal for creating assignment templates. |
| ui/src/lib/components/teacher/elements/SectionScaffold.svelte | Shared scaffold for teacher section headers/empty states. |
| ui/src/lib/components/teacher/elements/InviteModal.svelte | Modal for sending student/parent invitations. |
| ui/src/lib/components/teacher/elements/GuardiansModal.svelte | Modal for viewing/inviting guardian links. |
| ui/src/lib/components/teacher/elements/GradeModal.svelte | Modal for grading student submissions. |
| ui/src/lib/components/teacher/elements/ClassCard.svelte | Reusable card UI for a classroom. |
| ui/src/lib/components/teacher/elements/AddStudentModal.svelte | Modal for enrol vs invite student by email. |
| ui/src/lib/components/student/pages/StudentResources.svelte | Student materials view aggregated across enrolled classes. |
| ui/src/lib/components/student/elements/Sidebar.svelte | Adds role-aware nav items + unread message badge wiring. |
| ui/src/lib/components/student/elements/Navbar.svelte | Makes navbar role-aware for settings/profile links + greeting. |
| ui/src/lib/components/MonitorLock.svelte | Fullscreen/overlay lock + away/presence reporting hooks. |
| ui/src/lib/apis/resources/index.ts | Frontend API client for resources + assignment templates. |
| ui/src/lib/apis/messaging/index.ts | Frontend API client for conversations/messages + attachments. |
| ui/src/lib/apis/exams/index.ts | Frontend API client for exam config/session/proctoring. |
| ui/src/lib/apis/assignments/index.ts | Frontend API client for teacher + student assignment flows. |
| tests/test_rate_limit.py | Unit tests for sliding-window limiter logic. |
| tests/conftest.py | Reduces bcrypt rounds in tests to speed up suite. |
| resources/service.py | Resources domain service (materials + templates + template→assignment). |
| resources/repository.py | Resources repositories for materials/templates. |
| resources/init.py | Resources bounded-context package marker. |
| MIGRATION.md | Documents “create_all only” strategy and teacher-table implications. |
| messaging/service.py | Messaging domain service for 1:1 teacher↔student conversations. |
| messaging/repository.py | Messaging repositories for conversations/participants/messages. |
| messaging/init.py | Messaging bounded-context package marker. |
| guardians/service.py | Guardians domain service for parent↔student link lifecycle. |
| guardians/repository.py | Guardians repository queries. |
| guardians/init.py | Guardians bounded-context package marker. |
| gateway/realtime/socket.py | Adds new realtime emit helpers (monitor, messaging, exam violation). |
| gateway/http/routers/resources.py | HTTP router for materials/templates + uploads/downloads. |
| gateway/http/routers/messaging.py | HTTP router for conversations/messages + realtime delivery. |
| gateway/http/routers/exams.py | HTTP router for exam config/proctoring + student exam lifecycle. |
| gateway/http/routers/auth.py | Tightens signup role self-selection (prevents self-assign admin). |
| gateway/http/routers/assignments.py | HTTP router for teacher assignments + student feed/submission. |
| gateway/http/rate_limit.py | In-memory sliding-window limiter middleware for sensitive endpoints. |
| gateway/http/dependencies.py | Adds require_teacher, service factories, and Pagination dependency. |
| gateway/http/app.py | Registers new routers + security headers + conditional rate limiting. |
| exams/repository.py | Exam repositories for config/session/violations. |
| exams/init.py | Exams bounded-context package marker. |
| docs/teacher/technical-reference.md | API/model reference for teacher section. |
| docs/teacher/teacher-section-user-guide.md | End-user guide for teacher/student/parent flows. |
| docs/teacher/security.md | Security controls documentation for the teacher section. |
| docs/teacher/screenshots/README.md | Screenshot manifest for teacher-section docs. |
| data/repositories/base.py | Adds commit=False option to create() for batched units of work. |
| data/models/resource.py | Adds ORM models for class resources + assignment templates. |
| data/models/monitor.py | Adds ORM models for monitor lock state + away events. |
| data/models/message.py | Adds ORM models for conversations/participants/messages. |
| data/models/guardian.py | Adds ORM model for guardian links. |
| data/models/exam.py | Adds ORM models for exam configs/sessions/violations. |
| data/models/classroom.py | Adds ORM models for classrooms/enrollments/invitations. |
| data/models/assignment.py | Adds ORM models for assignments/submissions. |
| data/models/init.py | Registers new ORM models for metadata import/creation. |
| config/settings.py | Adds material MIME allowlist configuration. |
| classrooms/repository.py | Adds repositories for classrooms/enrollments/invitations/monitor state. |
| classrooms/init.py | Classrooms bounded-context package marker. |
| assignments/repository.py | Adds repositories for assignments/submissions. |
| assignments/init.py | Assignments bounded-context package marker. |
| # Server-side MIME allowlist for teacher-uploaded class materials / attachments. | ||
| # Exact types plus the prefixes below (image/*, text/*). Keeps executables, scripts | ||
| # and other risky payloads out of the materials library. Tunable via env (comma-list). | ||
| ALLOWED_MATERIAL_MIME_PREFIXES = ("image/", "text/") | ||
| _default_material_mime = ( | ||
| "application/pdf," | ||
| "application/msword," | ||
| "application/vnd.openxmlformats-officedocument.wordprocessingml.document," | ||
| "application/vnd.ms-excel," | ||
| "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet," | ||
| "application/vnd.ms-powerpoint," | ||
| "application/vnd.openxmlformats-officedocument.presentationml.presentation," | ||
| "application/vnd.oasis.opendocument.text," | ||
| "application/vnd.oasis.opendocument.spreadsheet," | ||
| "application/vnd.oasis.opendocument.presentation," | ||
| "application/rtf,application/json,application/zip" | ||
| ) | ||
| ALLOWED_MATERIAL_MIME = frozenset( | ||
| t.strip() | ||
| for t in os.getenv("ALLOWED_MATERIAL_MIME", _default_material_mime).split(",") | ||
| if t.strip() | ||
| ) |
| contents = b"" | ||
| while True: | ||
| chunk = await file.read(64 * 1024) | ||
| if not chunk: | ||
| break | ||
| contents += chunk | ||
| if len(contents) > max_bytes: | ||
| raise _too_large | ||
| try: |
| return Response( | ||
| content=data, | ||
| media_type=content_type or "application/octet-stream", | ||
| headers={"Content-Disposition": f'inline; filename="{filename}"'}, | ||
| ) |
| data, content_type, filename = result | ||
| return Response( | ||
| content=data, | ||
| media_type=content_type, | ||
| headers={"Content-Disposition": f'attachment; filename="{filename}"'}, | ||
| ) |
| return Response( | ||
| content=data, | ||
| media_type=content_type, | ||
| headers={"Content-Disposition": f'attachment; filename="{filename}"'}, | ||
| ) |
| conversation = self.conversations.create( | ||
| id=str(uuid.uuid4()), created_at=datetime.utcnow(), last_message_at=None | ||
| ) | ||
| for uid in (initiator_id, recipient_id): | ||
| self.participants.create( | ||
| id=str(uuid.uuid4()), | ||
| conversation_id=conversation.id, | ||
| user_id=uid, | ||
| last_read_at=None, | ||
| ) |
… rate limiter, atomicity Addresses review findings (Eziane + Copilot) on the teacher section: - Stored XSS via class materials: downloads now force `attachment` (never inline), so an uploaded text/html file can't execute in the app's origin; the material MIME allowlist drops the broad `text/` prefix (which admitted text/html) in favour of explicit safe text types (plain/csv/markdown) - Content-Disposition header injection: filenames from uploads are sanitised (quotes/CR-LF/control chars stripped) before going into the header — shared gateway.http.attachments.attachment_response() now used by resources, assignments and messaging downloads - Rate limiter shared-bucket bug: `_client_key` keyed on a fixed JWT prefix, which is identical for every user (the encoded header), collapsing everyone into one bucket. Now keys on the token's `sub` claim (cookie or bearer), hashing the whole token as a fallback and IP when unauthenticated - Non-atomic conversation creation: messaging.start() created the conversation and both participant rows in three separate commits; now one unit of work - Upload no longer builds the file with O(n^2) `bytes` concatenation (bytearray) Tests: filename sanitiser + forced-download (test_attachments.py), per-user rate-limit key (regression for the shared bucket), HTML-material rejection and attachment disposition. Full backend suite green.
…ntent/) Addresses the domain-boundary review finding: the six new domains were created as top-level packages, but AGENTS.md requires `<boundary>/<domain>/`. Relocated with no behavioural change (imports rewritten, routes/models untouched): - classrooms → learning/classrooms (fills the reserved placeholder) - assignments → learning/assignments - exams → learning/exams - guardians → learning/guardians - messaging → learning/messaging - resources → content/resources (fills the reserved placeholder) Also folds the technical-reference bounded-context table onto the new paths. Full backend suite green; API paths and data models are unchanged.
This PR adds a complete teacher section on top of the existing student experience, turning Open TutorAI from an individual tutor into a classroom-aware platform. Teachers can organise classes, build rosters, monitor student progress, hand out and grade assignments (including timed proctored exams), share resources, message students, and control student screens live during class. Parents can be linked to a student as guardians. It also adds a layer of cross-cutting security hardening.
What's included
Teacher features:
Student-facing counterparts: assignments inbox (To Do / Submitted / Graded / Late / Missed), submission flow, full-screen exam shell, and the lock overlay.
Security & robustness (cross-cutting):
Fieldbounds (length / range) on every request modellimit/offset, max 100) on list endpointsNew bounded contexts:
classrooms,guardians,assignments,messaging,exams.How to test
pytest -q→ 372 passing.UI changes
Yes — new teacher and student screens. Screenshots live in
docs/teacher/screenshots/(roster, class creation, assignment grading, control/presence, exam shell, locked screen, messages, and more).Documentation
docs/teacher/teacher-section-user-guide.md— role-based how-to with screenshotsdocs/teacher/security.md— security controls referenceQuality
ruff-clean (lint + format); frontendprettierclean,eslint0 errors; i18n keys presentBreaking changes
None — additive only; no change to the existing student experience or data.
Known limitation
Session tokens are kept in
localStorage(inherited platform-wide). Moving to HttpOnly + Secure cookies (and the CSRF double-submit that becomes relevant with cookie auth) is a platform-wide auth change, tracked separately. Seedocs/teacher/security.md.