import mongoose from "mongoose";
import { z } from "zod";
/**
* Zod validation schemas for investigation endpoints
*/
// Vector3 validation schema
/**
* Vector3 validation schema
* @category Schemas
* @returns {z.ZodObject<{ x: z.ZodNumber, y: z.ZodNumber, z: z.ZodNumber }>} The Vector3 schema
*/
const Vector3Schema = z.object({
x: z.number(),
y: z.number(),
z: z.number(),
});
// Interaction validation schema
const InteractionSchema = z.object({
name: z.string().min(1).max(100),
});
// ViewComponent validation schema
const ViewComponentSchema = z.object({
mesh: z.string().min(1),
material: z.string().min(1),
texture: z.string().min(1),
interactions: z.array(InteractionSchema),
});
// RigidBodyComponent validation schema
const RigidBodyComponentSchema = z.object({
mass: z.number().min(0),
velocity: Vector3Schema,
angularVelocity: Vector3Schema,
drag: z.number().min(0),
angularDrag: z.number().min(0),
useGravity: z.boolean(),
isKinematic: z.boolean(),
interactions: z.array(InteractionSchema),
});
// FluidRigidBodyComponent validation schema
const FluidRigidBodyComponentSchema = z.object({
density: z.number().min(0),
viscosity: z.number().min(0),
surfaceTension: z.number().min(0),
particleSize: z.number().min(0),
simulationMethod: z.enum(["SPH", "Grid"]),
maxParticles: z.number().min(1),
interactions: z.array(InteractionSchema),
});
// CollisionMeshComponent validation schema
const CollisionMeshComponentSchema = z.object({
type: z.enum(["Box", "Sphere", "Capsule", "Mesh", "Convex"]),
size: Vector3Schema,
isTrigger: z.boolean(),
material: z.string().min(1),
interactions: z.array(InteractionSchema),
});
// TemperatureComponent validation schema
const TemperatureComponentSchema = z.object({
value: z.number(),
unit: z.enum(["C", "F", "K"]),
conductivity: z.number().min(0),
specificHeat: z.number().min(0),
interactions: z.array(InteractionSchema),
});
// StateOfMatterComponent validation schema
const StateOfMatterComponentSchema = z.object({
currentState: z.enum(["Solid", "Liquid", "Gas", "Plasma"]),
meltingPoint: z.number(),
boilingPoint: z.number(),
sublimationPoint: z.number().optional(),
stateChangeObjects: z.object({
solid: z.string().optional(),
liquid: z.string().optional(),
gas: z.string().optional(),
plasma: z.string().optional(),
}),
interactions: z.array(InteractionSchema),
});
// ShatterComponent validation schema
const ShatterComponentSchema = z.object({
health: z.number().min(0),
maxHealth: z.number().min(0),
shatterThreshold: z.number().min(0),
shatterEffect: z.enum(["Visual", "Vaporize", "Fragment"]),
fragmentCount: z.number().min(0).optional(),
fragmentObject: z.string().optional(),
interactions: z.array(InteractionSchema),
});
// HardAttachComponent validation schema
const HardAttachComponentSchema = z.object({
attachedObjects: z.array(z.string()),
attachmentStrength: z.number().min(0),
canDetach: z.boolean(),
detachForce: z.number().min(0),
interactions: z.array(InteractionSchema),
});
// FillComponent validation schema
const FillComponentSchema = z.object({
maxVolume: z.number().min(0),
currentVolume: z.number().min(0),
currentLiquid: z.string().optional(),
fillRate: z.number().min(0),
drainRate: z.number().min(0),
hasOverflow: z.boolean(),
interactions: z.array(InteractionSchema),
});
// Object validation schema
export const ObjectSchema = z.object({
id: z.string().min(1),
name: z.string().min(1).max(200),
position: Vector3Schema,
rotation: Vector3Schema,
size: z.number().min(0),
view: ViewComponentSchema,
rigidBody: RigidBodyComponentSchema.optional(),
fluidRigidBody: FluidRigidBodyComponentSchema.optional(),
collisionMesh: CollisionMeshComponentSchema.optional(),
temperature: TemperatureComponentSchema.optional(),
stateOfMatter: StateOfMatterComponentSchema.optional(),
shatter: ShatterComponentSchema.optional(),
hardAttach: HardAttachComponentSchema.optional(),
fill: FillComponentSchema.optional(),
});
// Editable field payload schema - supports both simple value and {value, aiGeneratedValue} format
const EditableFieldStringSchema = z.union([
z.string(),
z.object({
value: z.string(),
aiGeneratedValue: z.string().nullable().optional(),
}),
]);
const EditableFieldBooleanSchema = z.union([
z.boolean(),
z.object({
value: z.boolean(),
aiGeneratedValue: z.boolean().nullable().optional(),
}),
]);
// Simple Step validation schema (for API input)
// Supports both simple values and {value, aiGeneratedValue} format
export const SimpleStepSchema = z.object({
title: EditableFieldStringSchema.nullable().optional(),
desiredOutcome: EditableFieldStringSchema.nullable().optional(),
alternativeOutcome: EditableFieldStringSchema.nullable().optional(),
name: z.string().max(200).optional(), // Legacy field, kept for backward compatibility
descriptionEn: EditableFieldStringSchema.nullable().optional(),
skippable: EditableFieldBooleanSchema.nullable().optional(),
skippable_after_marking: EditableFieldBooleanSchema.nullable().optional(),
});
// Step validation schema (for database storage with EditableField)
// export const StepSchema = z.object({
// title: z.string().min(1).optional(),
// desiredOutcome: z.string().min(1).optional(),
// alternativeOutcome: z.string().min(1).optional(),
// name: z.string().min(1).max(200).optional(),
// descriptionEn: z.string().min(1).max(2000).optional(),
// skippable: EditableFieldSchema.optional(),
// skippable_after_marking: EditableFieldSchema.optional(),
// });
const createVectorSchema = z.object({
x: z.number().optional(),
y: z.number().optional(),
z: z.number().optional(),
});
const CreateObjectSchema = z.object({
name: z.string().min(0),
objectId: z.string(),
position: createVectorSchema.optional(),
rotation: createVectorSchema.optional(),
size: z.number().optional(),
});
// Update Object schema - supports {value, aiGeneratedValue} format
const UpdateObjectFieldSchema = z
.union([
z.string(),
z.object({
value: z.string(),
aiGeneratedValue: z.string().nullable().optional(),
}),
])
.nullable()
.optional();
const UpdateObjectSchema = z.object({
name: UpdateObjectFieldSchema,
objectId: UpdateObjectFieldSchema,
position: createVectorSchema.optional(),
rotation: createVectorSchema.optional(),
size: z.number().optional(),
});
// API Input validation schema (simple values for creating investigation)
export const CreateInvestigationSchema = z.object({
// Basic fields (all optional as requested)
curriculum: z.string().optional(),
grade: z.string().optional(),
title: z.string().optional(),
unitNumberAndTitle: z.string().optional(),
lessonNumberAndTitle: z.string().optional(),
ngss: z.string().optional(),
day: z.string().optional(),
objectives: z.string().optional(),
// Additional content fields
analyticalFacts: z.string().optional(),
goals: z.string().optional(),
// Complex nested fields
steps: z.array(SimpleStepSchema).optional(),
objects: z.array(CreateObjectSchema).optional(),
});
// Query parameters validation schema for GET investigations
export const GetInvestigationsQuerySchema = z.object({
limit: z.coerce.number().int().min(1).max(100).optional(),
offset: z.coerce.number().int().min(0).optional(),
sortBy: z.enum(["createdAt", "updatedAt", "title", "views"]).optional(),
sortOrder: z.enum(["asc", "desc"]).optional(),
search: z.string().min(1).optional(),
});
const ObjectIdParamSchema = z.string().refine((val) => mongoose.Types.ObjectId.isValid(val), {
message: "Invalid MongoDB ObjectId format",
});
// Query parameters validation schema for GET single investigation
export const GetInvestigationParamsSchema = z.object({
id: ObjectIdParamSchema,
});
// Params validation schema for DELETE single investigation
export const DeleteInvestigationParamsSchema = z.object({
id: ObjectIdParamSchema,
});
// Update investigation params validation schema with MongoDB ObjectId validation
export const UpdateInvestigationParamsSchema = z.object({
id: z
.string()
.min(1)
.regex(/^[0-9a-fA-F]{24}$/, "Invalid MongoDB ObjectId format"),
});
// Step update validation schema for individual step field updates
// All fields are required when steps array is provided
export const StepUpdateSchema = z.object({
index: z.number().int().min(0),
key: z.string().min(1),
value: z.union([z.string(), z.number(), z.boolean(), z.null()]), // Properly typed union
});
// Object update validation schema
export const ObjectUpdateSchema = {
isContradicting: z.boolean(),
targetFieldName: z.string().nullable(),
contradictionReason: z.string().nullable(),
value: z.array(z.object({
name: z.string(),
position: Vector3Schema,
rotation: Vector3Schema,
size: z.number(),
view: z.object({
mesh: z.string(),
material: z.string(),
texture: z.string(),
}),
})),
};
// New update investigation validation schema for the new API format
export const UpdateInvestigationSchema = z.object({
// String fields (curriculum, gradeAndUnit, title, correspondence, objectives, discussionTopics, analyticalFacts, introAndGoals, references)
curriculum: z.string().optional(),
title: z.string().optional(),
objectives: z.string().optional(),
analyticalFacts: z.string().optional(),
goals: z.string().optional(),
grade: z.string().optional(),
unitNumberAndTitle: z.string().optional(),
lessonNumberAndTitle: z.string().optional(),
ngss: z.string().optional(),
day: z.string().optional(),
// Step updates (array of step objects where each field can be simple value or {value, aiGeneratedValue})
steps: z.array(SimpleStepSchema).optional(),
objects: z.array(UpdateObjectSchema).optional(),
});
// Get field history params validation schema
export const GetFieldHistoryParamsSchema = z.object({
id: ObjectIdParamSchema,
fieldPath: z.string().min(1),
});
// Get field history query validation schema
export const GetFieldHistoryQuerySchema = z.object({
limit: z.coerce.number().int().min(1).max(50).optional(),
});
export const GetInvestigationVersionsParamsSchema = z.object({
id: ObjectIdParamSchema,
});
export const GetInvestigationVersionsQuerySchema = z.object({
limit: z.coerce.number().int().min(1).max(50).optional(),
});
// Regenerate other fields params validation schema
export const RegenerateOtherFieldsParamsSchema = z.object({
id: ObjectIdParamSchema,
});
// Regenerate other fields body validation schema
export const RegenerateOtherFieldsBodySchema = z.object({
fieldName: z.union([
// Block fields
z.enum([
"title",
"curriculum",
"unitNumberAndTitle",
"grade",
"lessonNumberAndTitle",
"objectives",
"ngss",
"analyticalFacts",
"goals",
"day",
]),
// Step fields (format: steps.{index}.{field})
z.string().regex(/^steps\.\d+\.(title|descriptionEn|desiredOutcome|alternativeOutcome)$/),
// Object fields (format: objects.{index}.name)
z.union([z.literal("name"), z.string().regex(/^objects\.\d+\.name$/)]),
]),
});
// Resolve contradiction params validation schema
export const ResolveContradictionParamsSchema = z.object({
id: ObjectIdParamSchema,
});
// Resolve contradiction body validation schema
export const ResolveContradictionBodySchema = z.object({
fieldName: z.union([
// Block fields
z.enum([
"title",
"curriculum",
"unitNumberAndTitle",
"grade",
"lessonNumberAndTitle",
"objectives",
"ngss",
"analyticalFacts",
"goals",
"day",
]),
// Step fields (format: steps.{index}.{field})
z.string().regex(/^steps\.\d+\.(title|descriptionEn|desiredOutcome|alternativeOutcome)$/),
// Object fields (format: objects.{index}.name)
z.union([z.literal("name"), z.string().regex(/^objects\.\d+\.name$/)]),
]),
});
Source