import * as z from 'zod'; import { ZCourseMetadata } from '../course/course'; const ZSupportedLocale = z.enum(['en', 'hi', 'fr', 'pt', 'de', 'vi', 'ru', 'es', 'pl', 'da']); const LESSON_BODY_HTML_DESCRIPTION = 'Lesson body HTML only. Do not include the lesson title. Do not use h1 or h2 anywhere in lessonLanguages[].content. Start headings at h3 because that is the highest heading level allowed in lesson content.'; export const ZCourseImportWarning = z.object({ code: z.string().min(1), message: z.string().min(1), severity: z.enum(['info', 'warning', 'error']) }); export type TCourseImportWarning = z.infer; export const ZCourseImportSourceReference = z.object({ type: z.enum(['prompt', 'pdf', 'course']), label: z.string().min(1), pageStart: z.number().int().min(1).optional(), pageEnd: z.number().int().min(1).optional() }); export type TCourseImportSourceReference = z.infer; export const ZCourseImportDraftCourse = z.object({ title: z.string().min(1), description: z.string().min(1), type: z.enum(['LIVE_CLASS', 'SELF_PACED']), locale: ZSupportedLocale.default('en'), metadata: ZCourseMetadata.optional() }); export type TCourseImportDraftCourse = z.infer; export const ZCourseImportDraftSection = z.object({ externalId: z.string().min(1), title: z.string().min(1), order: z.number().int().min(0) }); export type TCourseImportDraftSection = z.infer; export const ZCourseImportDraftLesson = z.object({ externalId: z.string().min(1), sectionExternalId: z.string().min(1), title: z.string().min(1), order: z.number().int().min(0), isUnlocked: z.boolean().optional(), public: z.boolean().optional() }); export type TCourseImportDraftLesson = z.infer; export const ZCourseImportDraftLessonLanguage = z.object({ lessonExternalId: z.string().min(1), locale: ZSupportedLocale, content: z.string().min(1).describe(LESSON_BODY_HTML_DESCRIPTION) }); export type TCourseImportDraftLessonLanguage = z.infer; export const ZCourseImportDraftPayload = z .object({ course: ZCourseImportDraftCourse, tags: z.array(z.string().trim().min(1).max(80)).max(100).default([]), sections: z.array(ZCourseImportDraftSection).min(1), lessons: z.array(ZCourseImportDraftLesson).min(1), lessonLanguages: z.array(ZCourseImportDraftLessonLanguage).min(1), exercises: z.array(z.record(z.string(), z.unknown())).optional(), sourceReferences: z.array(ZCourseImportSourceReference).optional(), warnings: z.array(ZCourseImportWarning).default([]) }) .superRefine((value, ctx) => { const normalizedTags = new Set(); value.tags.forEach((tag, index) => { const tagKey = tag.trim().toLowerCase(); if (normalizedTags.has(tagKey)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['tags', index], message: 'Draft tags must be unique' }); } normalizedTags.add(tagKey); }); const sectionIds = new Set(); value.sections.forEach((section, index) => { if (sectionIds.has(section.externalId)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['sections', index, 'externalId'], message: 'Section externalId must be unique' }); } sectionIds.add(section.externalId); }); const lessonIds = new Set(); value.lessons.forEach((lesson, index) => { if (lessonIds.has(lesson.externalId)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['lessons', index, 'externalId'], message: 'Lesson externalId must be unique' }); } lessonIds.add(lesson.externalId); if (!sectionIds.has(lesson.sectionExternalId)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['lessons', index, 'sectionExternalId'], message: 'Lesson sectionExternalId must reference an existing section' }); } }); value.lessonLanguages.forEach((lessonLanguage, index) => { if (!lessonIds.has(lessonLanguage.lessonExternalId)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['lessonLanguages', index, 'lessonExternalId'], message: 'Lesson language must reference an existing lesson' }); } }); }); export type TCourseImportDraftPayload = z.infer; export const ZCourseImportDraftCreate = z.object({ sourceType: z.enum(['prompt', 'pdf', 'course']), idempotencyKey: z.string().min(1).optional(), summary: z.record(z.string(), z.unknown()).optional(), sourceArtifacts: z.array(z.record(z.string(), z.unknown())).optional(), draft: ZCourseImportDraftPayload }); export type TCourseImportDraftCreate = z.infer; export const ZCourseImportCourseParam = z.object({ courseId: z.string().min(1) }); export type TCourseImportCourseParam = z.infer; export const ZCourseImportDraftCreateFromCourse = z.object({ courseId: z.string().min(1), idempotencyKey: z.string().min(1).optional(), summary: z.record(z.string(), z.unknown()).optional(), sourceArtifacts: z.array(z.record(z.string(), z.unknown())).optional() }); export type TCourseImportDraftCreateFromCourse = z.infer; export const ZCourseImportDraftGetParam = z.object({ draftId: z.string().uuid() }); export type TCourseImportDraftGetParam = z.infer; export const ZCourseImportDraftUpdate = z.object({ summary: z.record(z.string(), z.unknown()).optional(), sourceArtifacts: z.array(z.record(z.string(), z.unknown())).optional(), warnings: z.array(ZCourseImportWarning).optional(), draft: ZCourseImportDraftPayload.optional() }); export type TCourseImportDraftUpdate = z.infer; export const ZCourseImportDraftPublish = z.object({ title: z.string().min(1).optional(), description: z.string().min(1).optional(), type: z.enum(['LIVE_CLASS', 'SELF_PACED']).optional(), metadata: ZCourseMetadata.optional(), bannerImageUrl: z.string().url().optional(), bannerImageQuery: z.string().min(1).max(120).optional(), generateBannerImage: z.boolean().optional() }); export type TCourseImportDraftPublish = z.infer; export const ZCourseImportDraftPublishToCourse = ZCourseImportDraftPublish.extend({ courseId: z.string().min(1) }); export type TCourseImportDraftPublishToCourse = z.infer;