import * as z from 'zod'; import { blockedSubdomain } from '@cio/utils/constants'; export const ZGetOrganizations = z.object({ siteName: z.string().min(1).optional(), customDomain: z.string().min(1).optional(), /** * Accepts a real boolean (server-side direct calls) or the URL-encoded `'true'`/`'false'` * string Hono RPC produces when serializing query params. */ isCustomDomainVerified: z .union([z.boolean(), z.literal('true').transform(() => true), z.literal('false').transform(() => false)]) .optional() }); export type TGetOrganizations = z.infer; export const ZGetCoursesBySiteName = z.object({ siteName: z.string().min(1), tags: z.string().optional(), types: z.string().optional(), search: z.string().trim().optional(), pricing: z.enum(['free', 'paid']).optional(), page: z.coerce.number().int().min(1).optional(), limit: z.coerce.number().int().min(1).max(100).optional() }); export type TGetCoursesBySiteName = z.infer; export const ZGetOrganizationCoursesQuery = z.object({ page: z.coerce.number().int().min(1).default(1), limit: z.coerce.number().int().min(1).max(100).default(20), search: z.string().trim().optional(), tags: z.string().optional() }); export type TGetOrganizationCoursesQuery = z.infer; export const ZGetOrgSetup = z.object({ siteName: z.string().min(1) }); export type TGetOrgSetup = z.infer; export const ZCreateOrgPlan = z.object({ orgId: z.uuid(), planName: z.enum(['EARLY_ADOPTER', 'ENTERPRISE', 'BASIC']), subscriptionId: z.string().min(1), triggeredBy: z.number().int().positive(), payload: z.record(z.string(), z.unknown()) }); export type TCreateOrgPlan = z.infer; export const ZUpdateOrgPlan = z.object({ subscriptionId: z.string().min(1), payload: z.record(z.string(), z.unknown()) }); export type TUpdateOrgPlan = z.infer; export const ZCancelOrgPlan = z.object({ subscriptionId: z.string().min(1), payload: z.record(z.string(), z.unknown()) }); export type TCancelOrgPlan = z.infer; export const ZCreateOrganization = z.object({ name: z .string() .min(5) .refine((val) => !/^[-]|[-]$/.test(val), { message: 'Organization name cannot start or end with a hyphen' }), siteName: z .string() .min(5) .refine((val) => !/^[-]|[-]$/.test(val), { message: 'Site name cannot start or end with a hyphen' }) .refine((val) => !blockedSubdomain.includes(val), { message: 'Sitename already exists.' }) }); export type TCreateOrganization = z.infer; export const ZUpdateOrganization = z.object({ name: z .string() .min(5) .refine((val) => !/^[-]|[-]$/.test(val), { message: 'Organization name cannot start or end with a hyphen' }) .optional(), avatarUrl: z.url().optional(), favicon: z.union([z.url(), z.null()]).optional(), theme: z.string().optional(), landingpage: z.record(z.string(), z.unknown()).optional(), siteName: z.string().min(1).optional(), customDomain: z.string().nullable().optional(), isCustomDomainVerified: z.boolean().optional(), customization: z.record(z.string(), z.unknown()).optional(), disableSignup: z.boolean().optional(), disableSignupMessage: z.string().optional(), disableEmailPassword: z.boolean().optional(), disableGoogleAuth: z.boolean().optional(), /** Nested settings (stored in organization.settings JSONB). signup.inviteOnly = true means invite-only signup. */ settings: z .object({ signup: z .object({ inviteOnly: z.boolean().optional() }) .optional(), internalEnrollmentOnly: z.boolean().optional(), emailNotifications: z .object({ newStudent: z.boolean().optional(), newSubmission: z.boolean().optional(), gradingResult: z.boolean().optional(), newsfeed: z.boolean().optional(), quizAssigned: z.boolean().optional(), cohortReminder: z.boolean().optional(), session: z.boolean().optional(), enrollmentWelcome: z.boolean().optional(), courseCompletion: z.boolean().optional() }) .optional() }) .optional() }); export type TUpdateOrganization = z.infer; export const ZInviteTeamMembers = z.object({ emails: z.array(z.string().email()).min(1).max(50), roleId: z.number().int().positive() }); export type TInviteTeamMembers = z.infer; export const ZRemoveTeamMember = z.object({ memberId: z.coerce.number().int().positive() }); export type TRemoveTeamMember = z.infer; export const ZCreateLinkInvite = z.object({ roleId: z.number().int().positive() }); export type TCreateLinkInvite = z.infer; export const ZToggleLinkInvite = z.object({ isRevoked: z.boolean() }); export type TToggleLinkInvite = z.infer; export const ZGetUserAnalytics = z.object({ userId: z.uuid() }); export type TGetUserAnalytics = z.infer;