Files
classroomio/packages/utils/src/validation/course/course.ts
T

337 lines
11 KiB
TypeScript
Raw Normal View History

import * as z from 'zod';
import { ALLOWED_CONTENT_TYPES, ALLOWED_DOCUMENT_TYPES } from '../constants';
export const ZCourseClone = z.object({
title: z.string().min(1),
description: z.string().optional(),
slug: z.string().min(1),
organizationId: z.string().min(1)
});
export type TCourseClone = z.infer<typeof ZCourseClone>;
export const ZCourseCloneParam = z.object({
courseId: z.string().min(1)
});
export type TCourseCloneParam = z.infer<typeof ZCourseCloneParam>;
2026-03-09 11:18:22 +01:00
export const ZCourseGetParam = z.object({
courseId: z.string().min(1)
});
export type TCourseGetParam = z.infer<typeof ZCourseGetParam>;
export const ZCourseGetQuery = z.object({
slug: z.string().optional()
});
export type TCourseGetQuery = z.infer<typeof ZCourseGetQuery>;
export const ZCourseGetBySlugParam = z.object({
slug: z.string().min(1)
});
export type TCourseGetBySlugParam = z.infer<typeof ZCourseGetBySlugParam>;
export const ZCourseEnrollParam = z.object({
courseId: z.string().min(1)
});
export type TCourseEnrollParam = z.infer<typeof ZCourseEnrollParam>;
export const ZCourseEnrollBody = z.object({
inviteToken: z.string().min(1).optional()
});
export type TCourseEnrollBody = z.infer<typeof ZCourseEnrollBody>;
export const ZCourseDownloadParam = z.object({
courseId: z.string().min(1)
});
export type TCourseDownloadParam = z.infer<typeof ZCourseDownloadParam>;
export const ZCertificateDownload = z.object({
theme: z.string().min(1),
studentName: z.string().min(1),
courseName: z.string().min(1),
courseDescription: z.string().min(1),
orgName: z.string().min(1),
orgLogoUrl: z.string().url().optional(),
facilitator: z.string().optional()
});
export type TCertificateDownload = z.infer<typeof ZCertificateDownload>;
export const ZCourseDownloadContent = z.object({
courseTitle: z.string().min(1),
orgName: z.string().min(1),
orgTheme: z.string().min(1),
lessons: z.array(
z.object({
lessonTitle: z.string().min(1),
lessonNumber: z.string().min(1),
lessonNote: z.string(),
slideUrl: z.string().url().optional(),
video: z.array(z.string().url()).optional()
})
)
});
export type TCourseDownloadContent = z.infer<typeof ZCourseDownloadContent>;
export const ZLessonDownloadContent = z.object({
title: z.string().min(1),
number: z.string().min(1),
orgName: z.string().min(1),
note: z.string(),
slideUrl: z.string().url().optional(),
video: z.array(z.string().url()).optional(),
courseTitle: z.string().min(1)
});
export type TLessonDownloadContent = z.infer<typeof ZLessonDownloadContent>;
export const ZCoursePresignUrlUpload = z.object({
fileName: z.string().min(1),
fileType: z.enum(ALLOWED_CONTENT_TYPES)
});
export type TCoursePresignUrlUpload = z.infer<typeof ZCoursePresignUrlUpload>;
export const ZCourseDocumentPresignUrlUpload = z.object({
fileName: z.string().min(1),
fileType: z.enum(ALLOWED_DOCUMENT_TYPES)
});
export type TCourseDocumentPresignUrlUpload = z.infer<typeof ZCourseDocumentPresignUrlUpload>;
export const ZCourseDownloadPresignedUrl = z.object({
keys: z.array(z.string().min(1)).min(1)
});
export type TCourseDownloadPresignedUrl = z.infer<typeof ZCourseDownloadPresignedUrl>;
2026-03-09 11:18:22 +01:00
export const ZCourseContentUpdateItem = z.object({
id: z.string().min(1),
type: z.enum(['LESSON', 'EXERCISE']),
isUnlocked: z.boolean().optional(),
order: z.number().int().min(0).optional(),
sectionId: z.string().nullable().optional()
});
export type TCourseContentUpdateItem = z.infer<typeof ZCourseContentUpdateItem>;
export const ZCourseContentUpdate = z.object({
items: z.array(ZCourseContentUpdateItem).min(1)
});
export type TCourseContentUpdate = z.infer<typeof ZCourseContentUpdate>;
2026-03-18 23:39:12 +01:00
export const ZCourseContentReorderSection = z.object({
id: z.string().min(1),
order: z.number().int().min(0)
});
export type TCourseContentReorderSection = z.infer<typeof ZCourseContentReorderSection>;
export const ZCourseContentReorderItem = ZCourseContentUpdateItem.omit({
isUnlocked: true
});
export type TCourseContentReorderItem = z.infer<typeof ZCourseContentReorderItem>;
export const ZCourseContentReorderBase = z.object({
sections: z.array(ZCourseContentReorderSection).min(1).optional(),
items: z.array(ZCourseContentReorderItem).min(1).optional()
});
export type TCourseContentReorderBase = z.infer<typeof ZCourseContentReorderBase>;
export const ZCourseContentReorder = ZCourseContentReorderBase.superRefine((data, ctx) => {
if (!data.sections?.length && !data.items?.length) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['sections'],
message: 'Provide sections or items to reorder'
});
}
const sectionIds = new Set<string>();
data.sections?.forEach((section, index) => {
if (sectionIds.has(section.id)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['sections', index, 'id'],
message: 'Section IDs must be unique'
});
}
sectionIds.add(section.id);
});
const itemKeys = new Set<string>();
data.items?.forEach((item, index) => {
if (item.order === undefined && item.sectionId === undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['items', index],
message: 'Each item must provide order or sectionId'
});
}
const itemKey = `${item.type}:${item.id}`;
if (itemKeys.has(itemKey)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['items', index, 'id'],
message: 'Item IDs must be unique per content type'
});
}
itemKeys.add(itemKey);
});
});
export type TCourseContentReorder = z.infer<typeof ZCourseContentReorder>;
2026-03-09 11:18:22 +01:00
export const ZCourseContentDeleteItem = z.object({
id: z.string().min(1),
type: z.enum(['LESSON', 'EXERCISE'])
});
export type TCourseContentDeleteItem = z.infer<typeof ZCourseContentDeleteItem>;
export const ZCourseContentDelete = z
.object({
sectionId: z.string().min(1).optional(),
items: z.array(ZCourseContentDeleteItem).min(1).optional()
})
.refine((data) => Boolean(data.sectionId) !== Boolean(data.items), {
message: 'Provide either sectionId or items',
path: ['sectionId']
});
export type TCourseContentDelete = z.infer<typeof ZCourseContentDelete>;
export const ZCourseCreate = z.object({
title: z.string().min(1),
description: z.string().min(1),
type: z.enum(['LIVE_CLASS', 'SELF_PACED']),
organizationId: z.string().min(1)
});
export type TCourseCreate = z.infer<typeof ZCourseCreate>;
2026-03-18 23:39:12 +01:00
export const ZCourseReward = z.object({
show: z.boolean(),
description: z.string()
});
export const ZCourseInstructor = z.object({
name: z.string(),
role: z.string(),
coursesNo: z.number(),
description: z.string(),
imgUrl: z.string()
});
export const ZCourseCertificate = z.object({
templateUrl: z.string()
});
export const ZCourseReview = z.object({
id: z.number(),
hide: z.boolean(),
name: z.string(),
avatar_url: z.string(),
rating: z.number(),
created_at: z.number(),
description: z.string()
});
export const ZCourseLessonTabsOrder = z.array(
z.object({
id: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]),
name: z.string()
})
);
2026-03-09 11:18:22 +01:00
// Course metadata schema matching the database structure
export const ZCourseMetadata = z.object({
requirements: z.string().optional(),
description: z.string().optional(),
goals: z.string().optional(),
videoUrl: z.string().optional(),
showDiscount: z.boolean().optional(),
discount: z.number().optional(),
paymentLink: z.string().optional(),
2026-03-18 23:39:12 +01:00
reward: ZCourseReward.optional(),
instructor: ZCourseInstructor.optional(),
certificate: ZCourseCertificate.optional(),
reviews: z.array(ZCourseReview).optional(),
lessonTabsOrder: ZCourseLessonTabsOrder.optional(),
2026-03-09 11:18:22 +01:00
grading: z.boolean().optional(),
lessonDownload: z.boolean().optional(),
allowNewStudent: z.boolean(),
sectionDisplay: z.record(z.string(), z.boolean()).optional(),
isContentGroupingEnabled: z.boolean().optional()
});
export type TCourseMetadata = z.infer<typeof ZCourseMetadata>;
2026-03-18 23:39:12 +01:00
export const ZCourseLandingPageMetadataUpdate = z.object({
requirements: z.string().optional(),
description: z.string().optional(),
goals: z.string().optional(),
videoUrl: z.string().optional(),
showDiscount: z.boolean().optional(),
discount: z.number().optional(),
paymentLink: z.string().optional(),
reward: ZCourseReward.partial().optional(),
instructor: ZCourseInstructor.partial().optional(),
certificate: ZCourseCertificate.partial().optional(),
reviews: z.array(ZCourseReview).optional(),
lessonTabsOrder: ZCourseLessonTabsOrder.optional(),
grading: z.boolean().optional(),
lessonDownload: z.boolean().optional(),
allowNewStudent: z.boolean().optional(),
sectionDisplay: z.record(z.string(), z.boolean()).optional(),
isContentGroupingEnabled: z.boolean().optional()
});
export type TCourseLandingPageMetadataUpdate = z.infer<typeof ZCourseLandingPageMetadataUpdate>;
2026-03-09 11:18:22 +01:00
export const ZCourseUpdate = z.object({
title: z.string().min(1).optional(),
description: z.string().min(1).optional(),
type: z.enum(['LIVE_CLASS', 'SELF_PACED']).optional(),
logo: z.string().optional(),
slug: z.string().optional(),
isPublished: z.boolean().optional(),
overview: z.string().optional(),
metadata: ZCourseMetadata.optional(),
2026-03-18 23:39:12 +01:00
cost: z.number().int().min(0).optional(),
currency: z.enum(['NGN', 'USD']).optional(),
2026-03-09 11:18:22 +01:00
isCertificateDownloadable: z.boolean().optional(),
certificateTheme: z.string().optional(),
tagIds: z.array(z.uuid()).max(100).optional()
});
export type TCourseUpdate = z.infer<typeof ZCourseUpdate>;
export const ZCourseUpdateParam = z.object({
courseId: z.string().min(1)
});
export type TCourseUpdateParam = z.infer<typeof ZCourseUpdateParam>;
2026-03-18 23:39:12 +01:00
export const ZCourseLandingPageUpdate = z.object({
title: z.string().min(1).optional(),
description: z.string().min(1).optional(),
overview: z.string().optional(),
cost: z.number().int().min(0).optional(),
currency: z.enum(['NGN', 'USD']).optional(),
imageUrl: z.url().optional(),
generateImage: z.boolean().optional(),
imageQuery: z.string().min(1).max(120).optional(),
metadata: ZCourseLandingPageMetadataUpdate.optional()
});
export type TCourseLandingPageUpdate = z.infer<typeof ZCourseLandingPageUpdate>;
2026-03-09 11:18:22 +01:00
export const ZCourseDeleteParam = z.object({
courseId: z.string().min(1)
});
export type TCourseDeleteParam = z.infer<typeof ZCourseDeleteParam>;
export const ZCourseProgressParam = z.object({
courseId: z.string().min(1)
});
export type TCourseProgressParam = z.infer<typeof ZCourseProgressParam>;
export const ZCourseProgressQuery = z.object({
profileId: z.string().uuid()
});
export type TCourseProgressQuery = z.infer<typeof ZCourseProgressQuery>;
export const ZCourseUserAnalyticsParam = z.object({
courseId: z.string().min(1),
userId: z.string().uuid()
});
export type TCourseUserAnalyticsParam = z.infer<typeof ZCourseUserAnalyticsParam>;