// Large-course mode API — cancel a course generation run. // // POST /api/courses/:courseId/cancel // // Aborts the full pipeline (and any module-regeneration cascade) and cooperatively // cancels the in-flight classroom job of the current module. Succeeded modules // are kept; the course status flips to `cancelled` once the run settles. import { type NextRequest } from 'next/server'; import { apiError, apiSuccess, API_ERROR_CODES } from '@/lib/server/api-response'; import { createLogger } from '@/lib/logger'; import { isValidCourseId, readCourseRecordReconciled } from '@/lib/course-framework/store'; import { cancelCourseGenerationJob, isCourseGenerationRunning, } from '@/lib/course-framework/runner'; import { requireOpsAccess } from '@/lib/server/ops-access'; import { proxyLargeCourseControlToEngine } from '@/lib/makelore-runtime/ops-engine-control'; const log = createLogger('Course Cancel API'); export async function POST( request: NextRequest, { params }: { params: Promise<{ courseId: string }> }, ) { const denied = await requireOpsAccess(request); 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, course: record, action: 'cancel', }); if (proxied) return proxied; if (!isCourseGenerationRunning(courseId)) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 409, 'Course generation is not running'); } cancelCourseGenerationJob(courseId); log.info(`Course cancellation requested: ${courseId}`); return apiSuccess({ courseId, message: 'Cancellation requested' }); } catch (error) { log.error('Course cancel failed:', error); return apiError(API_ERROR_CODES.INTERNAL_ERROR, 500, 'Failed to cancel course'); } }