Skip to content

Course Service API

SVC-007 Student Route Proof

See Student Course Route Proof for the accepted bounded proof covering enrollment-gated student list/detail/progress, published lesson/material projection, static native route examples, and the remaining live/browser/default-route gate.

Current Endpoints

  • GET /healthz
  • GET /readyz
  • GET /v1
  • PUT /v1/courses/{courseId}/snapshot
  • GET /v1/courses
  • POST /v1/courses
  • GET /v1/courses/search-projections
  • GET /v1/courses/learning-catalog-events
  • GET /v1/courses/integration-contracts/question-exam
  • POST /v1/internal/courses/{courseId}/quiz-links/{referenceId}/attempt-context
  • GET /v1/courses/{courseId}
  • PATCH /v1/courses/{courseId}
  • DELETE /v1/courses/{courseId}
  • GET /v1/courses/{courseId}/sections
  • POST /v1/courses/{courseId}/sections
  • PATCH /v1/courses/{courseId}/sections/{sectionId}
  • DELETE /v1/courses/{courseId}/sections/{sectionId}
  • GET /v1/courses/{courseId}/lessons
  • POST /v1/courses/{courseId}/lessons
  • GET /v1/courses/{courseId}/lessons/{lessonId}
  • PATCH /v1/courses/{courseId}/lessons/{lessonId}
  • DELETE /v1/courses/{courseId}/lessons/{lessonId}
  • POST /v1/courses/{courseId}/lessons/{lessonId}/video
  • DELETE /v1/courses/{courseId}/lessons/{lessonId}/video
  • GET /v1/courses/{courseId}/lessons/{lessonId}/question-links
  • POST /v1/courses/{courseId}/lessons/{lessonId}/question-links
  • PATCH /v1/courses/{courseId}/lessons/{lessonId}/question-links/{referenceId}
  • DELETE /v1/courses/{courseId}/lessons/{lessonId}/question-links/{referenceId}
  • GET /v1/courses/{courseId}/materials
  • POST /v1/courses/{courseId}/materials
  • GET /v1/courses/{courseId}/materials/{materialId}
  • PATCH /v1/courses/{courseId}/materials/{materialId}
  • DELETE /v1/courses/{courseId}/materials/{materialId}
  • GET /v1/courses/{courseId}/quiz-links
  • POST /v1/courses/{courseId}/quiz-links
  • PATCH /v1/courses/{courseId}/quiz-links/{referenceId}
  • DELETE /v1/courses/{courseId}/quiz-links/{referenceId}
  • GET /v1/courses/{courseId}/enrollments
  • POST /v1/courses/{courseId}/enrollments
  • PATCH /v1/courses/{courseId}/enrollments/{studentId}
  • DELETE /v1/courses/{courseId}/enrollments/{studentId}
  • GET /v1/student/courses
  • GET /v1/student/courses/{courseId}
  • POST /v1/student/courses/{courseId}/lessons/{lessonId}/progress
  • POST /v1/student/courses/{courseId}/lessons/{lessonId}/video-progress
  • POST /v1/student/courses/{courseId}/materials/{materialId}/view
  • GET /v1/public/courses
  • GET /v1/public/courses/{slug}

Native Course Foundation

Phase 8 course-service starts with course catalog records, sections, lessons, material metadata, enrollments, and basic student progress. This is an internal /v1 foundation, not a public /api/courses*, /api/student/courses*, or /api/public/catalog* cutover.

Legacy evidence:

  • node-platform/apps/api/src/modules/courses/courses.controller.ts:42-334 maps teacher/admin course CRUD, section, lesson, video, material, enrollment, question, quiz, and report routes.
  • node-platform/apps/api/src/modules/courses/courses.controller.ts:336-386 maps student course list/detail/mastery/recommendation/purchase/progress routes.
  • node-platform/apps/api/src/modules/courses/courses.service.ts:23-169 delegates course routes to AppDataService and wallet purchase.
  • node-platform/apps/api/src/modules/app-data/app-data.courses-support.ts:41-160 defines manager checks, active enrollment checks, topic validation, and response expansion.
  • node-platform/apps/api/src/modules/app-data/app-data.courses-catalog.ts:41-262 implements legacy course list/detail/create/update/archive and section CRUD.
  • node-platform/apps/api/src/modules/app-data/app-data.courses-lessons.ts:41-477 implements lesson/material CRUD, video metadata attachment, and cleanup behavior.
  • node-platform/apps/api/src/modules/app-data/app-data.courses-enrollment.ts:41-475 implements enrollment CRUD, student course projection, lesson progress, video progress, and material views.
  • node-platform/apps/api/src/modules/app-data/app-data.courses-assessment.ts:41-708 implements question links, quizzes, report, mastery, and recommendation; these are deferred from P8-005.
  • node-platform/apps/api/src/modules/app-data/app-data.courses-files.ts:41-119 implements video/material content file access; streaming remains document-service/gateway work.
  • node-platform/apps/api/src/modules/app-data/app-data.public.ts:19-392 exposes public course home/catalog/detail projections.
  • node-platform/apps/api/prisma/schema.prisma:920-1188 defines Course, CourseSection, CourseLesson, CourseMaterial, CourseEnrollment, progress/view tables, question links, and quizzes.
  • node-platform/packages/shared/src/index.ts:1278-1379 defines course, section, lesson, material, enrollment, and progress validation schemas.
  • node-platform/apps/web/components/course/course-crud-client.tsx:52-307 consumes teacher/admin course CRUD, status, archive, and report routes.
  • node-platform/apps/web/components/course/course-management-client.tsx:108-316 consumes course detail, sections, lessons, materials, video, question links, quizzes, and enrollments.
  • node-platform/apps/web/components/course/student-course-client.tsx:131-170 consumes student course detail, recommendation, lesson/video progress, and material views.

Native contract:

  • PUT /v1/courses/{courseId}/snapshot upserts service-owned course data from legacy backfill, including nested sections, lessons, materials, and enrollments when supplied.
  • GET /v1/courses supports q, status, organizationId, instructorId, subjectId, visibility, page, and limit; X-Actor-Id/X-Actor-Role scopes non-admin reads.
  • POST /v1/courses creates a course with status DRAFT by default. Teachers default to themselves as instructor; admins must provide an instructor id until profile-service adapters can choose a fallback.
  • GET/PATCH/DELETE /v1/courses/{courseId} preserve detail, partial update, and archive semantics. Delete sets status=ARCHIVED and clears featuredAt.
  • Section routes preserve course-scoped create/update/delete and orderIndex ordering.
  • Lesson routes preserve course-scoped create/update/delete, optional section/topic ids, preview, published to publishedAt, content fields, video metadata, and deletion cleanup of lesson-owned materials/progress.
  • Material routes preserve metadata CRUD, optional lesson binding, preview, published to publishedAt, and deletion cleanup of material views. File bytes remain document-service/object-storage work.
  • Lesson-question routes are ADMIN/owning-TEACHER management APIs only. They store a question-bank public id, placement role/order, and an optional copied title. A repeated (lessonId,questionId) save updates that placement without duplicating the reference. They never fetch question text, answers, solutions, taxonomy, or usage state from another database.
  • Quiz routes are ADMIN/owning-TEACHER management APIs only. They store an exam-service public id, optional same-course section/lesson placement, type/required score/order, and an optional copied title. A repeated (courseId,examId) save updates that placement without duplicating the reference. Lesson deletion cascades question references and unbinds quiz references; section deletion unbinds quiz section placement. These references are intentionally absent from student/public detail projections.
  • POST /v1/internal/courses/{courseId}/quiz-links/{referenceId}/attempt-context is an internal Gateway-only decision, not a learner route. It validates the authenticated STUDENT has active enrollment, the Course is published, the linked lesson is published and topic-bound, and the placement's local examId matches the already resolved Exam runtime. Its bounded result carries only course/reference/lesson/student/organization ids, examId, and Course's validation timestamp. The adapter uses examId only to bind the internal handoff; Attempt discards it before persisting the analytics courseContext snapshot. Browser request data can identify a selected Course quiz placement but cannot assert this owner decision.
  • Enrollment routes preserve upsert by (courseId,studentId), status, paid amount, expiration, grant actor, and note.
  • A successful enrollment grant optionally emits best-effort analytics event course.enrollment.granted.v1 to analytics-service POST /v1/analytics/events when ANALYTICS_SERVICE_URL or ANALYTICS_SERVICE_BASE_URL is configured. The event uses sourceService=course-service, sourceEventId=course_enrollment:{enrollmentId}:granted, entity=CourseEnrollment, and a bounded payload with course/student/status, paid amount, expiration, and grant actor. Analytics delivery failure does not roll back the enrollment write.
  • RecordLessonProgress never calls Analytics on the learner request path. Migration 000007_course_learning_progress_outbox.sql writes a durable, replayable course.lesson_progress.saved.v1 record for organization-scoped progress in the same Course database operation. Each mutable saved state has its own opaque source event id and a bounded payload containing only course, lesson, student, status, percent, and timestamps; it excludes lesson content, media URLs, storage keys, and profile PII.
  • RecordVideoProgress and RecordMaterialView optionally emit course.video_progress.saved.v1 and course.material_view.saved.v1 with timestamped replay ids for mutable rows. Payloads include progress counters and display metadata only; they exclude lesson content, media URLs, storage keys, file URLs, and profile PII.
  • Student course routes preserve active enrollment checks, published-course requirement, published lesson/material filtering, lesson progress status derivation, video progress auto-completing lessons at 95 percent, and material view increments. Lesson-attached materials are returned only when the owning lesson is visible to the student.
  • Public course routes expose published/public/slugged course projections for gateway adapters. Preview materials attached to lessons are returned only when the owning lesson is also part of the public preview projection.
  • GET /v1/courses/search-projections exposes copied COURSE search documents for search-service rebuilds. It uses the same course list filters and actor scope as GET /v1/courses; allOrgs=1 is honored only for admin actors. When status is omitted, archived courses are included so full rebuilds preserve archive state.
  • GET /v1/courses/learning-catalog-events is the direct owner-service replay and backfill feed for Course learning-catalog snapshots, not a public or default Gateway route. It is restricted to an ADMIN service identity, scoped to one organization unless an unscoped service identity explicitly uses allOrgs=1, and uses the afterOccurredAt plus afterSourceEventId cursor pair. The optional 000006 Course outbox delivers the same immutable snapshots to Analytics without polling this route; delivery only records an input event and never materializes Analytics projections by itself.
  • 000005_course_learning_catalog_events.sql records an immutable course.learning_catalog.changed.v1 event in the same Course transaction as course publish/unpublish, lesson topic/order/publish changes, and quiz placement changes. The event has a per-course/per-organization catalog revision and emits an UPSERT only for a published course; all other states produce a tombstone. It contains only published lessons with a topic id and quiz references placed on those lessons. It excludes lesson content, question data, answer keys, file/media URLs, profile data, and course-level or topicless quiz placements that cannot safely contribute to mastery.
  • 000003_course_search_projection_outbox.sql also writes a durable, service-owned search.projection.changed.v1 envelope for copied COURSE state. Parent course changes and count-affecting lesson, material, enrollment, and lesson-progress mutations advance a per-course/per-organization revision in the same transaction. An organization move emits a DELETE for the old search scope and an UPSERT for the new one. Archive remains an UPSERT with ARCHIVED; only physical deletion produces a DELETE tombstone.
  • The copied event contains searchable course fields and aggregate counts only. It excludes lesson/material raw content, video/view state, student ids, file URLs, and storage keys. No public API endpoint or Gateway route is added.
  • 000004_course_question_exam_references.sql owns the two local reference tables and advances the course search projection when their counts change. The event exposes aggregate questionLinkCount and quizCount only; it does not include external ids or copied titles.
  • The dispatcher is disabled by default. Set COURSE_SEARCH_PROJECTION_EVENT_TRANSPORT=nats plus COURSE_SEARCH_PROJECTION_EVENTS_NATS_URL (or SEARCH_PROJECTION_EVENTS_NATS_URL/NATS_URL) to enable it. Optional COURSE_SEARCH_PROJECTION_EVENT_NATS_SUBJECT, SEARCH_PROJECTION_EVENTS_NATS_STREAM, and COURSE_SEARCH_PROJECTION_OUTBOX_{POLL_INTERVAL_MS,BATCH_SIZE,MAX_ATTEMPTS,RETRY_DELAY_MS,LEASE_MS} tune the local publisher; a Postgres store is required.

Search projection response shape:

json
{
  "success": true,
  "data": {
    "sourceService": "course-service",
    "entityType": "COURSE",
    "documents": [],
    "nextCursor": null,
    "hasMore": false,
    "meta": {
      "page": 1,
      "limit": 20,
      "total": 0,
      "totalPages": 1
    }
  },
  "message": "OK"
}

Search-service stores these copied rows in its own index tables and must not read course-service tables directly.

Authorization model:

  • Native service accepts X-Actor-Id, X-Actor-Role, and X-Organization-Id; gateway remains responsible for JWT verification and public route RBAC.
  • ADMIN may manage all courses unless an organization filter is explicitly applied.
  • TEACHER may manage courses where instructorId or createdById equals the actor.
  • STUDENT may read course detail and record progress only with an active, unexpired enrollment and PUBLISHED course status.
  • PARENT is not part of the P8-005 course foundation.

Database:

  • services/course-service/migrations/000002_courses.sql creates courses, course_sections, course_lessons, course_materials, course_enrollments, course_lesson_progress, course_video_progress, and course_material_views; 000004_course_question_exam_references.sql adds only course-owned placement references.
  • Cross-service ids such as organization_id, subject_id, topic_id, instructor_id, created_by_id, uploaded_by_id, and student_id are stored as public ids; course-service does not query other service databases.
  • course_lessons.video_asset_id and material storage fields store document-service references only.
  • GET /v1/courses/integration-contracts/question-exam records the native management-only reference boundary. Lesson-question references store courseId, lessonId, questionId, role/order, and optional copied display title, while question-bank-service remains canonical for question body, answers, solutions, taxonomy, and usage counters through owner APIs such as GET /v1/questions/{id} and PUT /v1/questions/usage-counts. Quiz references store only courseId, optional same-course sectionId/lessonId, examId, type/required score/order, and optional copied title, while exam-service remains canonical for exam authoring, snapshots, grading rules, attempts, and runtime access through owner APIs such as GET /v1/exams/{id} and PUT|GET /v1/exams/{examId}/question-snapshots.
  • Owner API validation, Gateway/BFF adapters, learner quiz delivery, wallet purchase records, and canonical media metadata remain deferred. Course mastery/recommendation/report work is explicitly planned as the copied-owner contract in Course Learning Projection Contract: Analytics owns the read model, Course owns catalog/progress snapshots, and Attempt must carry a Course-validated context. An examId alone is not a valid course association.

Rollback for this native slice:

  • Keep /api/courses*, /api/student/courses*, and /api/public/catalog* routed to legacy.
  • Disable gateway callers for /v1/courses*, /v1/student/courses*, and /v1/public/courses*.
  • Keep default question-link/quiz routes, report, mastery, recommendation, wallet purchase, and file streaming on legacy until owner validation and dedicated Gateway/BFF adapters land.
  • Drop course-service local course tables with the migration down step if local test data must be reset.

Gateway rehearsal:

deploy/gateway/routes.student-courses-read-native-example.json and deploy/gateway/routes.student-courses-read-native-localhost-example.json provide non-default route-table rehearsals for student course reads. They route only:

  • GET /api/student/courses
  • GET /api/student/courses/{courseId}

to course-service with gateway auth, STUDENT role enforcement, and organization header injection. Recommendation, purchase, lesson progress, video progress, material view, teacher/admin course management, public catalog, broad student-course/course/public routes, and fallback stay legacy-proxied until the missing adapters are explicit.

Run make test-student-course-routes before any live or browser rehearsal.

Student-course progress gateway rehearsal:

deploy/gateway/routes.student-course-progress-native-example.json and deploy/gateway/routes.student-course-progress-native-localhost-example.json provide non-default route-table rehearsals for student course progress writes. They route only:

  • POST /api/student/courses/{courseId}/lessons/{lessonId}/progress
  • POST /api/student/courses/{courseId}/lessons/{lessonId}/video-progress
  • POST /api/student/courses/{courseId}/materials/{materialId}/view

to course-service with gateway auth, STUDENT role enforcement, and organization header injection. Student course list/detail are covered by the separate read rehearsal and remain legacy in this progress-only table. Mastery, recommendation, purchase, teacher/admin course management, public catalog, broad student-course/course/public routes, and fallback stay legacy-proxied until the missing adapters are explicit.

Run make test-student-course-progress-routes before any live or browser rehearsal.

Non-goals for P8-005:

  • Public gateway adapter/cutover.
  • Wallet purchase and AZ Credit consumption.
  • Public/Gateway question-link or quiz/exam delivery beyond the native management-only owner-boundary references.
  • Mastery recalculation, recommendations, and full course reports.
  • File byte streaming and signed URL generation.
  • Cross-service validation against school-service, profile-service, question-bank-service, exam-service, document-service, or wallet services.

Go-platform documentation is generated from repository Markdown.