3.8 KiB
2026-07-17 — Google Meet / Classroom URLs on lesson_plans (Sprint A2 / P1-UX-3)
Branch: feat/p1-ux-3-meet-classroom
Worktree: .worktrees/feat-p1-ux-3-meet-classroom
Why
The UI/UX feedback PDF §3 (Virtual Classroom Integration) asks teachers to be able to "embed or link out to Google Meet sessions and Google Classroom environments directly from their lesson uploads." Until now, lesson plans had no place to carry those URLs.
Changes
server/src/database/migrations/knex/2026071700000008_lesson_plan_virtual_classroom.js (new)
Adds two optional columns to lesson_plans:
meet_url TEXT— Google Meet session URL for the lesson's live class.classroom_url TEXT— Google Classroom link for the assignment/unit.
Both default to NULL and are added via ALTER TABLE … ADD COLUMN. Each ADD is guarded by try/catch so re-applying the migration against a DB that already has the columns is a no-op. The down migration drops both columns (SQLite ≥ 3.35, supported by better-sqlite3).
server/src/utils/validateUrl.js (new)
Tiny URL-shape validator used by the controller:
isSafeHttpUrl(value)— returns true for null / undefined / empty (means "no link") and for strings that parse via WHATWGURLwithhttp/httpsprotocol. Rejects everything else includingjavascript:,data:,file:,ftp:, unparseable strings, and non-strings.normalizeHttpUrl(value)— trims and returns the canonical URL, ornullwhen invalid. Used by the controller so a bad paste gets persisted asnullrather than junk.
server/src/controllers/teacher.controller.js (modified)
- POST
/api/teacher/lesson-plansnow acceptsmeet_urlandclassroom_url. Each is run throughnormalizeHttpUrl; an invalid non-empty value returns 400 with the field name in the error. - INSERT statement extended to write both columns; existing rows are unaffected (NULL).
- OpenAPI block above the route documents both fields with examples.
- GET
/api/teacher/lesson-plansalready usesSELECT lp.*so the new columns ride along automatically.
client/src/pages/teacher/TeacherTools.tsx (modified)
Lesson-plan modal:
- Two new
<input type="url">fields with placeholders (https://meet.google.com/...andhttps://classroom.google.com/...). - The lesson-plan submission now passes
meet_urlandclassroom_urlthrough thecreateLessonPlancall.
server/tests/validate-url.test.js (new, 10 specs)
Vitest unit tests for the two helpers:
isSafeHttpUrl: null/undefined/empty valid; non-strings invalid; http/https valid;javascript:/ftp:/file:/data:invalid; unparseable strings invalid.normalizeHttpUrl: trims and returns canonical URL; returnsnullfor invalid input (not the original string); handles null/undefined/empty/non-string.
Out of scope (follow-up)
- Student-side "Join Class" surface. Per PDF §9, this lives inside the Course Scheme/Plan page ("Virtual Class Room button" on every lesson row) — that's P1-UX-7. The lesson_plans API now carries the data; P1-UX-7 renders it.
- A PUT/PATCH endpoint on lesson plans. TeacherTools currently does not edit lesson plans in place; if needed, the same validator applies.
- Rate-limiting the URL fields or logging them in audit_logs. Today the columns ride the existing audit story.
Verification
npx vitest run tests/validate-url.test.js→ 10 passed (15 ms).npx vitest run→ 28 passed, 1 pre-existing failure (paynow-webhook.test.js"paid_amount undefined" — same failure as ondev; not caused by this diff).node src/database/init.jsagainst a freshdata/school.db→ both columns added (PRAGMA table_info(lesson_plans)showsmeet_url:TEXTandclassroom_url:TEXT).- Re-running init on the already-migrated DB → "already up to date" — migration is idempotent.
node --checkclean on every modified file.