geocrop-platform./apps/nextgen/.harness/changelogs/2026-07-17-p1-ux-3-meet-cla...

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 WHATWG URL with http/https protocol. Rejects everything else including javascript:, data:, file:, ftp:, unparseable strings, and non-strings.
  • normalizeHttpUrl(value) — trims and returns the canonical URL, or null when invalid. Used by the controller so a bad paste gets persisted as null rather than junk.

server/src/controllers/teacher.controller.js (modified)

  • POST /api/teacher/lesson-plans now accepts meet_url and classroom_url. Each is run through normalizeHttpUrl; 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-plans already uses SELECT 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/... and https://classroom.google.com/...).
  • The lesson-plan submission now passes meet_url and classroom_url through the createLessonPlan call.

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; returns null for 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 run28 passed, 1 pre-existing failure (paynow-webhook.test.js "paid_amount undefined" — same failure as on dev; not caused by this diff).
  • node src/database/init.js against a fresh data/school.db → both columns added (PRAGMA table_info(lesson_plans) shows meet_url:TEXT and classroom_url:TEXT).
  • Re-running init on the already-migrated DB → "already up to date" — migration is idempotent.
  • node --check clean on every modified file.