import { z } from 'zod'; import { Request, Response, NextFunction } from 'express'; /** * Schema for creating a new user * - email: required, must be valid email format * - password: required, minimum 8 characters * - name: required (first_name + last_name combined or separate) * - role_id: required, must be valid UUID * - is_active: optional, defaults to true */ export const createUserSchema = z.object({ email: z .string({ required_error: 'Email is required' }) .email('Invalid email format') .transform((val) => val.toLowerCase().trim()), password: z .string({ required_error: 'Password is required' }) .min(8, 'Password must be at least 8 characters'), first_name: z .string({ required_error: 'First name is required' }) .min(1, 'First name cannot be empty') .max(100, 'First name cannot exceed 100 characters'), last_name: z .string({ required_error: 'Last name is required' }) .min(1, 'Last name cannot be empty') .max(100, 'Last name cannot exceed 100 characters'), role_id: z .number({ required_error: 'Role ID is required' }) .int('Role ID must be an integer') .positive('Role ID must be a positive number'), is_active: z.boolean().default(true), }); /** * Schema for updating a user * All fields are optional * Password has different rules (not allowed in regular update) */ export const updateUserSchema = z.object({ email: z .string() .email('Invalid email format') .transform((val) => val.toLowerCase().trim()) .optional(), first_name: z .string() .min(1, 'First name cannot be empty') .max(100, 'First name cannot exceed 100 characters') .optional(), last_name: z .string() .min(1, 'Last name cannot be empty') .max(100, 'Last name cannot exceed 100 characters') .optional(), role_id: z .number() .int('Role ID must be an integer') .positive('Role ID must be a positive number') .optional(), is_active: z.boolean().optional(), }); /** * Schema for changing password * - current_password: required * - new_password: required, minimum 8 characters */ export const changePasswordSchema = z.object({ current_password: z .string({ required_error: 'Current password is required' }) .min(1, 'Current password cannot be empty'), new_password: z .string({ required_error: 'New password is required' }) .min(8, 'New password must be at least 8 characters'), }); /** * Type definitions derived from schemas */ export type CreateUserInput = z.infer; export type UpdateUserInput = z.infer; export type ChangePasswordInput = z.infer; /** * Generic validation middleware factory * Creates a middleware that validates request body against a Zod schema * @param schema - Zod schema to validate against */ export function validate(schema: T) { return (req: Request, res: Response, next: NextFunction): void => { const result = schema.safeParse(req.body); if (!result.success) { const errors = result.error.errors.map((err) => ({ field: err.path.join('.'), message: err.message, })); res.status(400).json({ success: false, error: 'Validation failed', errors, }); return; } // Replace body with validated and typed data req.body = result.data; next(); }; } /** * Pre-configured validation middlewares */ export const validateCreateUser = validate(createUserSchema); export const validateUpdateUser = validate(updateUserSchema); export const validateChangePassword = validate(changePasswordSchema);