Appearance
Classroom Service API
SVC-006 Route Guard Proof
See Classroom Public Route Guard Proof for the accepted bounded proof of tenant-scoped native reads, owner-local profile/organization references, gateway identity headers, and the unchanged legacy default route state. Public /api/classrooms* promotion remains gated.
Non-default Classroom Read Candidate
The named candidate tables deploy/gateway/routes.classroom-read-native-example.json and deploy/gateway/routes.classroom-read-native-localhost-example.json expose only authenticated ADMIN/TEACHER reads:
GET /api/classrooms->GET /v1/classroomsGET /api/classrooms/{classroomId}->GET /v1/classrooms/{classroomId}
Both require organization context and run before the broad legacy classroom fallback in the candidate table. routes.json remains unchanged, so no default caller uses this mapping. Candidate verification, including a memory-backed Gateway runtime smoke, is documented in Classroom Read Runtime Smoke. Keep the default table active to rollback.
Current Endpoints
GET /healthzGET /readyzGET /v1GET /v1/admin/classrooms/summaryPUT /v1/classrooms/{classroomId}/snapshotGET /v1/classroomsPOST /v1/classroomsGET /v1/classrooms/{classroomId}PATCH /v1/classrooms/{classroomId}PATCH /v1/classrooms/{classroomId}/statusDELETE /v1/classrooms/{classroomId}GET /v1/classrooms/{classroomId}/delete-impactDELETE /v1/classrooms/{classroomId}/purgeGET /v1/classrooms/{classroomId}/membersPOST /v1/classrooms/{classroomId}/membersPUT /v1/classrooms/{classroomId}/members/{userId}DELETE /v1/classrooms/{classroomId}/members/{userId}GET /v1/classrooms/{classroomId}/studentsPOST /v1/classrooms/{classroomId}/studentsDELETE /v1/classrooms/{classroomId}/students/{studentId}POST /v1/classrooms/{classroomId}/joinPOST /v1/classrooms/{classroomId}/notificationsGET /v1/classrooms/{classroomId}/progress
Native Classroom Foundation
Phase 8 classroom-service starts with classroom and classroom-member ownership. This is an internal /v1 foundation, not a public /api/classrooms* or /api/admin/classrooms* cutover.
Legacy evidence:
node-platform/apps/api/src/modules/classrooms/classrooms.controller.ts:32-204maps legacy classroom list/detail/create/update/status/delete/join/student/progress routes.node-platform/apps/api/src/modules/classrooms/classrooms.controller.ts:803-1430maps admin classroom list/create/update/detail/member/purge routes.node-platform/apps/api/src/modules/app-data/app-data.classrooms-core.ts:38-520implements legacy classroom list/detail/create/update/status rules, teacher membership creation, manager checks, and response counters.node-platform/apps/api/src/modules/app-data/app-data.classrooms-support.ts:50-150defines classroom manager, teacher-managed-classroom, reference, and membership assertions.node-platform/apps/api/src/modules/app-data/app-data.public.ts:707-980implements delete impact, soft archive vs hard delete, purge, join, remove student, and student list behavior.node-platform/apps/api/src/modules/app-data/app-data.progress-overview.ts:17-48implements classroom progress as a composition of classroom detail, student members, assignments, attempts, and weak topics.node-platform/apps/api/prisma/schema.prisma:551-604definesClassroomandClassroomMemberwith uniquecode, uniquepublicSlug, unique(classroomId,userId), andACTIVE/ARCHIVEDstatus.node-platform/packages/shared/src/index.ts:593-610andnode-platform/packages/shared/src/index.ts:1160-1178define public slug, visibility, grade-level, and classroom input validation.node-platform/apps/web/components/classroom/classroom-crud-client.tsx:168-336consumes/classrooms, status, delete-impact, delete, and admin purge.node-platform/apps/web/app/join/class/[code]/join-class-link-client.tsx:30-39consumes/classrooms/{code}/join.
Native contract:
GET /v1/admin/classrooms/summaryreturnssourceService=classroom-service, classroom counts by status and visibility, featured classroom count, and classroom-member row counts by role for admin dashboard/source-map adapters. It is read-only and does not query IAM, profile-service, organization-service/school-service, exam-service, or attempt-service databases.PUT /v1/classrooms/{classroomId}/snapshotupserts service-owned rows from legacy backfill, including timestamps, status, visibility, counters, and teacher/member data when supplied.GET /v1/classroomssupportsorganizationId,teacherId,studentId,status,visibility,q,page, andlimit;X-Actor-Id/X-Actor-Rolescopes non-admin reads.POST /v1/classroomscreates a classroom, generates a unique uppercase join code, and upserts the primary teacher as aTEACHERmember.GET /v1/classrooms/{classroomId}accepts id or code, preserving the legacy join-link lookup path.PATCH /v1/classrooms/{classroomId}preserves partial update rules for name, description, summary, cover image, visibility, public slug, featured flag, grade level, subject id, and teacher id.PATCH /v1/classrooms/{classroomId}/statussetsACTIVEorARCHIVED.DELETE /v1/classrooms/{classroomId}hard deletes only when there is no owned usage beyond the teacher membership; otherwise it archives and returns{deleted:false, archived:true}.GET /v1/classrooms/{classroomId}/delete-impactreturns service-owned counts immediately and zeroes deferred lesson/material/assignment/tuition counters until those slices are native.DELETE /v1/classrooms/{classroomId}/purgehard deletes the service-owned classroom and members for internal/admin-gateway-only adapters; public gateway promotion still requires external RBAC and rollback evidence.- Member routes preserve upsert by
(classroomId,userId), role updates, student join by id or code, student member listing, and primary-teacher reassignment when admin removes a teacher member. POST /v1/classrooms/{classroomId}/notificationsis an internal quick classroom notification producer. It requiresclientRequestId,title, andcontent, loads currentSTUDENTclassroom members from classroom-service storage, and best-effort emits notification-servicePOST /v1/events/notificationwithsourceService=classroom-service,sourceEventId=classroom:{classroomId}:notification:{clientRequestId},type=classroom.notification.sent.v1, andpreferenceType=CLASSROOM_NOTIFICATION. WhenPROFILE_SERVICE_URLorPROFILE_SERVICE_BASE_URLis configured, classroom-service resolves parent account ids through profile-service/v1/internal/profiles/parent-recipients/resolveand emits a separate replay-safe parent event withsourceEventId=classroom:{classroomId}:notification:{clientRequestId}:parents,type=classroom.notification.parent.sent.v1, andpreferenceType=CLASSROOM_NOTIFICATION_PARENT. The response reports total, student, and parent accepted counts plus whether parent resolution completed and whether notification-service publish succeeded.GET /v1/classrooms/{classroomId}/progressreturns the classroom, current student member rows,assignedExamCountfrom the classroom snapshot/read-model counter, zero completion rate, and empty weak topics until exam/attempt/analytics adapters provide native data.
Analytics event handoff:
- When
ANALYTICS_SERVICE_URLorANALYTICS_SERVICE_BASE_URLis configured, successful native membership mutations best-effort publishclassroom.membership.snapshot.v1to analytics-servicePOST /v1/analytics/events. - Event envelope:
sourceService=classroom-service,entity=Classroom,entityId={classroomId}, andsourceEventId=classroom:{classroomId}:membership-snapshot:{occurredAtRFC3339Nano}. - Payloads reload the classroom and current member refs after the mutation and include
schemaVersion,classroomId,organizationId,teacherId,status,subjectId,gradeLevel, trigger metadata,memberCount,studentCount,teacherCount, andmembers[{userId,role,joinedAt}]. MemberfullNameandemailare intentionally excluded so analytics does not store profile PII from classroom membership events. - Delivery is best-effort: disabled or failing analytics-service delivery does not roll back the classroom membership write.
- When
NOTIFICATION_SERVICE_URLorNOTIFICATION_SERVICE_BASE_URLis configured, the quick classroom notification producer sends inbox events to notification-service. Parent fanout is additionally gated byPROFILE_SERVICE_URLorPROFILE_SERVICE_BASE_URL; resolver failure does not roll back the student event. Event payloads include classroom id, organization id, preference key, and recipient count only; they exclude memberfullName,email, phone, parent-student links, and profile data.
Authorization model:
- Native service accepts
X-Actor-Id,X-Actor-Role, andX-Organization-Id;X-User-IdandX-User-Roleare accepted as fallbacks for gateway-injected headers. Gateway remains responsible for JWT verification and public route RBAC. ADMINmay manage any classroom in the requested organization scope.TEACHERmay manage classrooms whereteacherIdequals the actor or where the actor has aTEACHERclassroom member row.TEACHERcannot create or transfer a classroom to another teacher.STUDENTlist/detail access is membership-scoped and join may only add the actor as a student.PARENTcannot read or manage classroom-service foundation routes.
Envelope:
json
{
"success": true,
"data": {
"id": "class_local_math_6",
"name": "Toan 6",
"code": "A1B2C3",
"status": "ACTIVE",
"visibility": "PRIVATE",
"gradeLevel": 6,
"subjectId": "math",
"teacherId": "teacher_1",
"studentCount": 0,
"_count": {
"members": 1,
"lessons": 0,
"materials": 0,
"assignments": 0
}
},
"message": "OK"
}Database:
services/classroom-service/migrations/000002_classrooms.sqlcreatesclassroomsandclassroom_members.classrooms.organization_id,unit_id,subject_id,teacher_id, andcreated_by_idstore public ids from other services; classroom-service does not query other service databases.classrooms.codeis unique and stores the legacy uppercase join code.classrooms.public_sluguses a partial unique index so null slugs are allowed.classroom_membersenforces unique(classroom_id,user_id)and stores only user ids plus role.
Validation queries:
sql
SELECT id, legacy_id, organization_id, name, code, status, teacher_id
FROM classrooms
ORDER BY created_at DESC
LIMIT 20;
SELECT classroom_id, user_id, role, joined_at
FROM classroom_members
ORDER BY joined_at DESC
LIMIT 20;
SELECT c.id, c.code, count(m.id) AS member_count
FROM classrooms c
LEFT JOIN classroom_members m ON m.classroom_id = c.id
GROUP BY c.id, c.code
ORDER BY c.id;Rollback for this native slice:
- Keep
/api/classrooms*,/api/admin/classrooms*, and/api/classrooms/:id/notifyrouted to legacy. - Disable gateway callers for
/v1/classrooms*. - Unset
ANALYTICS_SERVICE_URL/ANALYTICS_SERVICE_BASE_URLto disable the membership analytics producer without changing classroom write behavior. - Unset
NOTIFICATION_SERVICE_URL/NOTIFICATION_SERVICE_BASE_URLto disable the quick classroom notification producer without changing classroom roster behavior. - Unset
PROFILE_SERVICE_URL/PROFILE_SERVICE_BASE_URLto disable parent recipient resolution while leaving student classroom notifications available. - Run
make test-classroom-route-guardafter route-table edits to prove public/admin classroom routes still stay legacy-proxied. - Keep lessons, materials, announcements, tuition, exam assignments, dashboard, leaderboard, and public catalog routes on legacy until their dedicated slices land.
- Drop classroom-service local
classroom_membersandclassroomstables with the migration down step if local test data must be reset.
Non-goals for P8-004:
- Public gateway adapter/cutover for
/api/classrooms*or/api/admin/classrooms*. - Lesson, material, video, announcement, tuition, dashboard, leaderboard, exam, or exam-assignment ownership.
- Subject, teacher, organization, and student existence checks against other service databases.
- Full admin search hydration for teacher, organization, and subject names; gateway/user/school/question-bank adapters can hydrate later.
- Derived analytics, weak topics, attempt scores, and assignment completion.
- Exam attempt grading, answer analysis, weak-topic computation, and question ownership remain outside classroom-service.