Skip to content

Course Map JSON Import (#1152)

Structured import of a course map from our own export.json format β€” the first non-vision import path (PDF/image upload still routes through Gemini). A file produced by GET /api/course-maps/:id/export.json round-trips losslessly.

Endpoint

POST /api/course-maps/import (auth: bearer, owner = caller)

  • Body: a course-map document (the raw export.json content). Content-Type: application/json.
  • Validated against the published JSON Schema (@course-map/shared/schema/course-map.v1.json) via ajv, plus cross-reference checks (services/import-json.ts):
    • 400 INVALID_JSON β€” body isn’t JSON.
    • 400 SCHEMA_INVALID β€” fails the schema; details[] lists the violations.
    • 400 REFS_INVALID β€” duplicate course id, or an edge endpoint that isn’t a course on the map; details[] lists them.
  • On success: inserts a user-owned course_maps row (extraction_status = 'extracted', extraction_model = 'json-import', extraction_raw_json = the document), then fans out courses/edges through the shared persistExtraction path. Returns 201 with the CourseMapDto. A persistence failure rolls the row back (500 PERSIST_FAILED) so no empty map is left.

The map name is the document’s title (schema-required, length-bounded). The upload UI lets the user rename on import by overwriting title before posting.

Round-trip guarantee

composeCourseMapFromRows (read) and the courseInsertRows / edgeInsertRows mappings in extraction/persist.ts (write) are inverses, so export β†’ import β†’ re-export reproduces the same document. This is enforced by a round-trip test in __tests__/services/import-json.test.ts. Note the export shape doesn’t carry per-semester creditHours or notes (both derived/absent), so neither participates in the round-trip.

UI

apps/course-map/src/app/upload/page.tsx accepts .json alongside PDF/images; a JSON file is read client-side and POSTed to /import (no extraction step), then the user lands in the editor. importCourseMapJson in lib/api.ts folds the server’s details[] into the thrown error so schema problems are visible.

Scope

JSON only (lossless, schema-backed). CSV import is a possible follow-up (lossy β€” no edges or colors). HTML/PDF β€œimport” is the existing vision path.