Skip to content

feat(teacher): teacher section (classes, roster, progress, guardians, assignments, resources, control, messaging, exams)#274

Open
Ilyas-ek wants to merge 6 commits into
Open-TutorAi:mainfrom
Ilyas-ek:feat/teacher-section
Open

feat(teacher): teacher section (classes, roster, progress, guardians, assignments, resources, control, messaging, exams)#274
Ilyas-ek wants to merge 6 commits into
Open-TutorAi:mainfrom
Ilyas-ek:feat/teacher-section

Conversation

@Ilyas-ek

Copy link
Copy Markdown

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:

  • Classes — create, list, open, delete, dashboard
  • Roster — enrol existing students + email invitations with an accept flow
  • Progress — read-only per-student and per-class activity
  • Guardians — link, invite, and contact a student's parent
  • Assignments — author, grade, submit, status tracker, file attachments
  • Resources — class-materials library + reusable assignment templates
  • Control — real-time screen lock/unlock, offline replay, tab-away telemetry, and an always-on "X/Y online now" presence indicator
  • Messaging — 1:1 teacher↔student with unread counts and live delivery
  • Proctored exams — timed full-screen shell, grace-warning auto-submit, live proctoring

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):

  • 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 + an SPA-safe CSP on every response
  • Per-caller rate limiting on sensitive routes (production only)
  • Centralised ownership/enrolment checks (anti-IDOR); ORM-only data access

New bounded contexts: classrooms, guardians, assignments, messaging, exams.

How to test

  1. Sign up as a teacher → create a class → enrol or invite a student → create an assignment (optionally a proctored exam) → upload a resource.
  2. On a second account/device, sign in as the student, open the assignment, and submit text + a file.
  3. Back as the teacher, grade and return the submission; open the Control tab, confirm the student shows online, then Lock / Unlock their screen.
  4. Backend suite: 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 screenshots
  • docs/teacher/security.md — security controls reference

Quality

  • Full backend suite green (372 passing)
  • Backend ruff-clean (lint + format); frontend prettier clean, eslint 0 errors; i18n keys present

Breaking 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. See docs/teacher/security.md.

Ilyas-ek and others added 2 commits June 26, 2026 15:26
…, 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).
@Dakir-Ai Dakir-Ai self-requested a review June 27, 2026 08:30
@Oumaima-elkhoummassi

Copy link
Copy Markdown

Hi @Ilyas-ek

Documentation review — teacher-section-user-guide.md and security.md checked.

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:

  1. The PR description mentions "offline replay, tab-away telemetry" (Control) and "grace-warning auto-submit, live proctoring" (Exams), but the user guide doesn't explain these - the Control section only covers lock/ unlock and the away marker, and the exam section doesn't describe what the teacher sees/does during live proctoring. Could you add these?

  2. There's no technical reference doc (API endpoints, data models) for the teacher section — only security.md and the user guide exist. This documentation would help anyone integrating with or maintaining this code later.

  3. The "Guardians" feature (link/invite/contact a parent) is listed as a teacher tab but never explained step by step from the teacher's side -only the parent's perspective gets a short section. Could you add the teacher-facing steps?

Please address the missing guide sections (1 and 3) before approval. Great work!

@baaki-hicham

Copy link
Copy Markdown

Review — feat(teacher): teacher section

Thanks 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 — sampleData.ts imported in production code

ui/src/routes/teacher/students/[sid]/+page.svelte imports and uses fake data instead of calling the real API:

import { sampleStudents } from '$lib/components/teacher/sampleData';
$: student = sampleStudents.find((s) => s.id === $page.params.sid);

Since sampleStudents only contains fictional UUIDs, find() will always return undefined for any real student. The page will always show "Student" as the name, 0 supports, "none" for guardians, and "coming soon" for progress — regardless of which student is clicked.

The real student detail API already exists (GET /classrooms/{id}/students/{student_id}/progress) and works correctly (confirmed via the class-scoped student page at /teacher/classes/[id]/students/[sid]).

Suggestion: replace sampleData with a real API call, following the same pattern as StudentProgress.svelte, which already works correctly. sampleData.ts should be removed entirely before merge — it has no place in production code.


2. 🔴 Blocking — Database schema mismatch causes 500 Internal Server Error on existing installations

When this PR is merged into an existing installation (where the database was created before this PR), the new SQLAlchemy models (Classroom, Assignment, Message, Exam, etc.) add many new columns that don't exist in the existing SQLite tables.

This causes:

IndexError: tuple index out of range

Confirmed via traceback:

File "data/repositories/base.py", line 37, in get_by_id
    return self.session.query(self.model).filter(self.model.id == id).first()
IndexError: tuple index out of range

The error is intermittent (some routes return 200, others 500) because SQLAlchemy's create_all() creates missing tables but never alters existing ones.

Any user upgrading from a previous version will hit this on every classroom, assignment, or messaging route.

Confirmed fix: deleting var/tutor.db and restarting resolves the issue on a clean install. But this is not acceptable for real users with existing data.

Suggestion: document a clear migration strategy. Since the project doesn't use Alembic, the minimum acceptable solution is a prominent warning in the PR description and MIGRATION.md that existing installations must reset their database, with an explanation of what data will be lost. A proper Alembic migration would be the ideal long-term fix.


3. 🟡 Non-blocking — sampleData.ts is a leftover development artifact

Even if the import in [sid]/+page.svelte is fixed, ui/src/lib/components/teacher/sampleData.ts itself should not be merged into main.

It contains fictional students, classes, resources and templates that serve no purpose in production and could confuse future contributors.

Suggestion: delete the file entirely and remove any remaining imports before merge.


4. ℹ️ Note — PR is still in Draft status

Since this PR is marked as Draft, I understand it may not be ready for full review yet. The two blocking issues above (sampleData in production + schema mismatch) are the most important to address before it's marked Ready for Review.

…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)
@Ilyas-ek

Ilyas-ek commented Jul 1, 2026

Copy link
Copy Markdown
Author

@Oumaima-elkhoummassi Thanks! All three addressed:

  1. Expanded the Control section (offline replay + tab-away log detail) and added a Proctored exams walkthrough covering setup, grace-warning auto-submit, and the live-proctoring view.
  2. Added a teacher-side Guardians section (invite → pending/active → contact).
  3. Added docs/teacher/technical-reference.md — endpoints + data models for the whole teacher section.

@Ilyas-ek

Ilyas-ek commented Jul 1, 2026

Copy link
Copy Markdown
Author

@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
@Eziane

Eziane commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Salam @Ilyas-ek,
Reviewed the teacher section (big PR, went through it carefully). Found 2 real bugs worth fixing before merge:

1. Class material upload can be used for stored XSS
gateway/http/routers/resources.py — upload check ~line 79-87, download ~line 141-144

The upload endpoint only checks the file's declared content-type against an allowlist (config/settings.py line 60-61) — that content-type comes straight from the client, nothing verifies it matches the actual file. The allowlist includes the whole "text/" prefix, which covers text/html. So a teacher account can upload a file labeled text/html containing a <script> payload and it passes the check. Then when a student opens that class material, download_material serves it back with Content-Disposition: inline and that same attacker-chosen content-type — so the browser renders it as an HTML page and runs the script, inside the app's own origin. That's stored XSS, reachable by anyone who opens a class material.

Worth noting: the assignment attachments and message attachments in this same PR do this correctly — they use Content-Disposition: attachment (forces a download instead of rendering). Resources is the only one using inline.

Fix: narrow "text/" down to just the safe subset (plain/csv), or switch resources to attachment like the other two do, or actually inspect the file bytes instead of trusting the client's header.

2. Rate limiter doesn't actually limit per user — everyone shares one bucket
gateway/http/rate_limit.py, line 53 (_client_key)

The per-user key is built from auth[7:][:32] — the first 32 characters of the JWT after "Bearer ". Problem: the first ~36 characters of every JWT this app issues are just the encoded header ({"typ":"JWT","alg":"HS256"}), which is identical for every user's token. So this "per-user" key is actually the same string for everyone, and the limiter ends up being one shared bucket for the whole app instead of one per user. One person hitting the limit on /submit or /grade would rate-limit everyone else too. It only kicks in when DEBUG=false, so tests don't catch it — confirmed test_rate_limit.py only tests the counting logic, never the actual token→key part.

Fix: key on something unique per user — decode the token and use the sub claim, or hash the full token instead of slicing a fixed prefix.

@Eziane

Eziane commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

@Ilyas-ek
One more thing, caught while reviewing #266 for comparison

This PR doesn't follow the project's domain-boundary rule
classrooms/, assignments/, exams/, guardians/, messaging/, resources/ — all at the repo root

CLAUDE.md is explicit about this: every new domain has to go under one of six boundaries — accounts, learning, ai, content, governance, system — as <boundary>/<domain>/. This PR creates all six new domains as top-level packages instead, e.g. classrooms/service.py at the repo root instead of learning/classrooms/service.py. This isn't a style nitpick — it's called out as the project's own architectural rule, which the team's review checklist explicitly ranks above general best practices.

For comparison, #266 (also adding classroom-management backend code) does this correctly — learning/classrooms, learning/attendance, learning/announcements. Since both PRs touch overlapping ground, whoever merges second is reworking regardless — better to fix the placement here before merge than let six top-level packages become the accepted pattern for future domains.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread config/settings.py
Comment on lines +57 to +78
# 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()
)
Comment thread gateway/http/routers/resources.py Outdated
Comment on lines +94 to +102
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:
Comment thread gateway/http/routers/resources.py Outdated
Comment on lines +141 to +145
return Response(
content=data,
media_type=content_type or "application/octet-stream",
headers={"Content-Disposition": f'inline; filename="{filename}"'},
)
Comment thread gateway/http/routers/assignments.py Outdated
Comment on lines +64 to +69
data, content_type, filename = result
return Response(
content=data,
media_type=content_type,
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)
Comment thread gateway/http/routers/messaging.py Outdated
Comment on lines +117 to +121
return Response(
content=data,
media_type=content_type,
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)
Comment on lines +133 to +142
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,
)
Ilyas-ek added 2 commits July 4, 2026 14:30
… 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants