// Large-course mode API — resume course generation. // // POST /api/courses/:courseId/resume // // Continues whatever is unfinished: generates the framework when it does not // exist yet, otherwise runs the module pipeline (skipping succeeded modules). // No-op when everything already succeeded. import { after, type NextRequest } from 'next/server'; import { apiError, apiSuccess, API_ERROR_CODES } from '@/lib/server/api-response'; import { buildRequestOrigin } from '@/lib/server/classroom-storage'; import { createLogger } from '@/lib/logger'; import { isValidCourseId, readCourseRecordReconciled } from '@/lib/course-framework/store'; import { isCourseGenerationRunning, runCourseFrameworkGeneration, runCourseModuleGeneration, } from '@/lib/course-framework/runner'; import { requireOpsAccess } from '@/lib/server/ops-access'; import { CourseMutationInProgressError, isCoursePublishing, } from '@/lib/course-framework/publish-state'; import { proxyLargeCourseControlToEngine } from '@/lib/makelore-runtime/ops-engine-control'; import { assertLearningCourseMutable, FrozenLearningCourseMutationError, } from '@/lib/makelore-course/immutability'; const log = createLogger('Course Resume API'); export async function POST( req: NextRequest, { params }: { params: Promise<{ courseId: string }> }, ) { const denied = await requireOpsAccess(req); if (denied) return denied; try { const { courseId } = await params; if (!isValidCourseId(courseId)) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 400, `Invalid course id: ${courseId}`); } const record = await readCourseRecordReconciled(courseId); if (!record) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 404, `Course not found: ${courseId}`); } const proxied = await proxyLargeCourseControlToEngine({ request: req, course: record, action: 'resume', }); if (proxied) return proxied; await assertLearningCourseMutable(courseId, record); if (record.status === 'completed') { return apiError(API_ERROR_CODES.INVALID_REQUEST, 409, 'Course is already completed'); } if (isCourseGenerationRunning(courseId) || isCoursePublishing(courseId)) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 409, '该课程已有生成任务正在运行'); } const baseUrl = buildRequestOrigin(req); const hasFramework = !!record.framework; const run = hasFramework ? runCourseModuleGeneration(courseId, baseUrl) : runCourseFrameworkGeneration(courseId, baseUrl); after(() => run); return apiSuccess({ courseId, status: hasFramework ? 'generating' : 'framework_generating', message: hasFramework ? '模块生成已继续' : '框架生成已继续', detailUrl: `${baseUrl}/api/courses/${courseId}`, }); } catch (error) { if (error instanceof FrozenLearningCourseMutationError) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 409, error.message); } if (error instanceof CourseMutationInProgressError) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 409, error.message); } log.error('Course resume failed:', error); return apiError(API_ERROR_CODES.INTERNAL_ERROR, 500, 'Failed to resume course'); } }