feat: add sequential progression and video watch lesson completion

Gate student content by completion order, track per-video watch progress
with seek enforcement, and auto-complete lessons at the configured
threshold. Show ring progress in the lesson header, keep watch progress
on rewatch, and sync progression locks client-side so the next item
unlocks after completion.
This commit is contained in:
Rotimi Best
2026-06-11 14:48:34 +01:00
parent c8154409f5
commit 52d9bf18c3
94 changed files with 3499 additions and 564 deletions
+18
View File
@@ -36,6 +36,8 @@ import { courseMemberMiddleware } from '@api/middlewares/course-member';
import { courseMemberOrAutomationKeyMiddleware } from '@api/middlewares/course-member-or-automation-key';
import { assertMcpAutomationUsageAllowed, recordMcpAutomationUsage } from '@api/services/organization/automation-usage';
import { createSubmissionService, listExerciseSubmissionsOverview } from '@api/services/submission';
import { assertEnrolledStudentContentAccess } from '@api/services/course/access';
import { ContentType } from '@cio/utils/constants';
import { zValidator } from '@hono/zod-validator';
export const exerciseRouter = new Hono()
@@ -95,6 +97,15 @@ export const exerciseRouter = new Hono()
await assertMcpAutomationUsageAllowed(automationKey, 'get_course_exercise');
}
if (user?.id && !automationKey) {
await assertEnrolledStudentContentAccess({
courseId: c.req.param('courseId')!,
profileId: user.id,
contentId: exerciseId,
type: ContentType.Exercise
});
}
const exercise = await getExercise(exerciseId, undefined, user?.id);
if (automationKey?.type === 'mcp') {
@@ -260,6 +271,13 @@ export const exerciseRouter = new Hono()
return c.json({ success: false, error: 'User is not a member of this course' }, 403);
}
await assertEnrolledStudentContentAccess({
courseId,
profileId: user.id,
contentId: exerciseId,
type: ContentType.Exercise
});
const submission = await createSubmissionService(courseId, exerciseId, groupMemberId, answers);
return c.json({ success: true, data: submission }, 201);