All files / src/lib/validation support-schemas.ts

100% Statements 290/290
100% Branches 4/4
100% Functions 0/0
100% Lines 290/290

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 2911x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
/**
 * Zod validation schemas for Customer Support System
 */
 
import { z } from 'zod';
 
// Enum schemas
export const TicketStatusSchema = z.enum([
  'OPEN',
  'IN_PROGRESS',
  'AWAITING_CUSTOMER',
  'AWAITING_AGENT',
  'RESOLVED',
  'CLOSED',
  'CANCELLED',
]);
 
export const TicketPrioritySchema = z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']);
 
export const TicketCategorySchema = z.enum([
  'ORDER_ISSUE',
  'PRODUCT_INQUIRY',
  'SHIPPING',
  'RETURNS_REFUNDS',
  'PAYMENT',
  'ACCOUNT',
  'TECHNICAL',
  'FEEDBACK',
  'OTHER',
]);
 
export const MessageSenderTypeSchema = z.enum(['CUSTOMER', 'AGENT', 'SYSTEM', 'BOT']);
 
// Create Ticket Schema
export const CreateTicketSchema = z.object({
  customerEmail: z
    .string()
    .min(1, 'Email is required')
    .email('Invalid email address')
    .max(255, 'Email must be less than 255 characters'),
  customerName: z
    .string()
    .min(1, 'Name is required')
    .max(100, 'Name must be less than 100 characters'),
  subject: z
    .string()
    .min(5, 'Subject must be at least 5 characters')
    .max(200, 'Subject must be less than 200 characters'),
  description: z
    .string()
    .min(20, 'Description must be at least 20 characters')
    .max(5000, 'Description must be less than 5000 characters'),
  category: TicketCategorySchema,
  priority: TicketPrioritySchema,
  orderId: z.number().int().positive().optional(),
  productId: z.number().int().positive().optional()});
 
// Update Ticket Schema (Admin)
export const UpdateTicketSchema = z.object({
  subject: z
    .string()
    .min(5, 'Subject must be at least 5 characters')
    .max(200, 'Subject must be less than 200 characters')
    .optional(),
  description: z
    .string()
    .min(20, 'Description must be at least 20 characters')
    .max(5000, 'Description must be less than 5000 characters')
    .optional(),
  category: TicketCategorySchema.optional(),
  priority: TicketPrioritySchema.optional(),
  status: TicketStatusSchema.optional(),
  assignedToId: z.number().int().positive().nullable().optional(),
  tags: z.string().max(500, 'Tags must be less than 500 characters').nullable().optional(),
  internalNotes: z
    .string()
    .max(5000, 'Internal notes must be less than 5000 characters')
    .nullable()
    .optional()});
 
// Assign Ticket Schema
export const AssignTicketSchema = z.object({
  assignedToId: z.number().int().positive().nullable()});
 
// Create Message Schema
export const CreateMessageSchema = z.object({
  content: z
    .string()
    .min(1, 'Message cannot be empty')
    .max(10000, 'Message must be less than 10000 characters'),
  isInternal: z.boolean().optional().default(false)});
 
// Create Article Schema
export const CreateArticleSchema = z.object({
  title: z
    .string()
    .min(5, 'Title must be at least 5 characters')
    .max(200, 'Title must be less than 200 characters'),
  slug: z
    .string()
    .min(3, 'Slug must be at least 3 characters')
    .max(200, 'Slug must be less than 200 characters')
    .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Slug must be URL-friendly (lowercase, hyphens only)'),
  content: z
    .string()
    .min(50, 'Content must be at least 50 characters')
    .max(50000, 'Content must be less than 50000 characters'),
  summary: z
    .string()
    .min(20, 'Summary must be at least 20 characters')
    .max(500, 'Summary must be less than 500 characters'),
  category: z
    .string()
    .min(1, 'Category is required')
    .max(100, 'Category must be less than 100 characters'),
  keywords: z
    .string()
    .min(1, 'Keywords are required')
    .max(500, 'Keywords must be less than 500 characters'),
  isPublished: z.boolean().optional().default(false)});
 
// Update Article Schema
export const UpdateArticleSchema = z.object({
  title: z
    .string()
    .min(5, 'Title must be at least 5 characters')
    .max(200, 'Title must be less than 200 characters')
    .optional(),
  slug: z
    .string()
    .min(3, 'Slug must be at least 3 characters')
    .max(200, 'Slug must be less than 200 characters')
    .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Slug must be URL-friendly (lowercase, hyphens only)')
    .optional(),
  content: z
    .string()
    .min(50, 'Content must be at least 50 characters')
    .max(50000, 'Content must be less than 50000 characters')
    .optional(),
  summary: z
    .string()
    .min(20, 'Summary must be at least 20 characters')
    .max(500, 'Summary must be less than 500 characters')
    .optional(),
  category: z
    .string()
    .min(1, 'Category is required')
    .max(100, 'Category must be less than 100 characters')
    .optional(),
  keywords: z
    .string()
    .min(1, 'Keywords are required')
    .max(500, 'Keywords must be less than 500 characters')
    .optional(),
  isPublished: z.boolean().optional()});
 
// Create Canned Response Schema
export const CreateCannedResponseSchema = z.object({
  title: z
    .string()
    .min(3, 'Title must be at least 3 characters')
    .max(100, 'Title must be less than 100 characters'),
  shortcut: z
    .string()
    .min(2, 'Shortcut must be at least 2 characters')
    .max(50, 'Shortcut must be less than 50 characters')
    .regex(
      /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
      'Shortcut must be lowercase with hyphens only (e.g., "greeting", "refund-policy")'
    ),
  content: z
    .string()
    .min(10, 'Content must be at least 10 characters')
    .max(5000, 'Content must be less than 5000 characters'),
  category: z
    .string()
    .min(1, 'Category is required')
    .max(100, 'Category must be less than 100 characters')});
 
// Update Canned Response Schema
export const UpdateCannedResponseSchema = z.object({
  title: z
    .string()
    .min(3, 'Title must be at least 3 characters')
    .max(100, 'Title must be less than 100 characters')
    .optional(),
  shortcut: z
    .string()
    .min(2, 'Shortcut must be at least 2 characters')
    .max(50, 'Shortcut must be less than 50 characters')
    .regex(
      /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
      'Shortcut must be lowercase with hyphens only (e.g., "greeting", "refund-policy")'
    )
    .optional(),
  content: z
    .string()
    .min(10, 'Content must be at least 10 characters')
    .max(5000, 'Content must be less than 5000 characters')
    .optional(),
  category: z
    .string()
    .min(1, 'Category is required')
    .max(100, 'Category must be less than 100 characters')
    .optional()});
 
// Submit Survey Schema
export const SubmitSurveySchema = z.object({
  rating: z
    .number()
    .int()
    .min(1, 'Rating must be at least 1')
    .max(5, 'Rating must be at most 5'),
  feedback: z.string().max(2000, 'Feedback must be less than 2000 characters').optional(),
  wasHelpful: z.boolean().optional(),
  wouldRecommend: z.boolean().optional()});
 
// Chatbot Message Schema
export const ChatbotMessageSchema = z.object({
  message: z
    .string()
    .min(1, 'Message cannot be empty')
    .max(1000, 'Message must be less than 1000 characters')});
 
// Article Helpful Vote Schema
export const ArticleHelpfulSchema = z.object({
  helpful: z.boolean()});
 
// Ticket Filter Schema (for query params)
export const TicketFilterSchema = z.object({
  status: z.union([TicketStatusSchema, z.array(TicketStatusSchema)]).optional(),
  priority: z.union([TicketPrioritySchema, z.array(TicketPrioritySchema)]).optional(),
  category: z.union([TicketCategorySchema, z.array(TicketCategorySchema)]).optional(),
  assignedToId: z
    .string()
    .transform((val) => (val === 'null' ? null : parseInt(val, 10)))
    .pipe(z.number().int().positive().nullable())
    .optional(),
  userId: z.coerce.number().int().positive().optional(),
  search: z.string().max(200).optional(),
  dateFrom: z.coerce.date().optional(),
  dateTo: z.coerce.date().optional(),
  page: z.coerce.number().int().positive().optional().default(1),
  limit: z.coerce.number().int().min(1).max(100).optional().default(20),
  sortBy: z.enum(['createdAt', 'updatedAt', 'priority', 'status']).optional().default('createdAt'),
  sortOrder: z.enum(['asc', 'desc']).optional().default('desc')});
 
// Article Filter Schema (for query params)
export const ArticleFilterSchema = z.object({
  category: z.string().optional(),
  isPublished: z
    .string()
    .transform((val) => val === 'true')
    .optional(),
  search: z.string().max(200).optional(),
  page: z.coerce.number().int().positive().optional().default(1),
  limit: z.coerce.number().int().min(1).max(100).optional().default(20)});
 
// Bulk Action Schema
export const BulkTicketActionSchema = z.object({
  ticketIds: z.array(z.string()).min(1, 'At least one ticket must be selected'),
  action: z.enum(['close', 'resolve', 'assign', 'change_priority', 'change_status']),
  value: z.union([z.string(), z.number(), z.null()]).optional()});
 
// File Upload Schema
export const FileUploadSchema = z.object({
  fileName: z.string().min(1).max(255),
  fileType: z.string().min(1).max(100),
  fileSize: z
    .number()
    .int()
    .positive()
    .max(10 * 1024 * 1024, 'File size must be less than 10MB')});
 
// Type exports from schemas
export type CreateTicketInput = z.infer<typeof CreateTicketSchema>;
export type UpdateTicketInput = z.infer<typeof UpdateTicketSchema>;
export type AssignTicketInput = z.infer<typeof AssignTicketSchema>;
export type CreateMessageInput = z.infer<typeof CreateMessageSchema>;
export type CreateArticleInput = z.infer<typeof CreateArticleSchema>;
export type UpdateArticleInput = z.infer<typeof UpdateArticleSchema>;
export type CreateCannedResponseInput = z.infer<typeof CreateCannedResponseSchema>;
export type UpdateCannedResponseInput = z.infer<typeof UpdateCannedResponseSchema>;
export type SubmitSurveyInput = z.infer<typeof SubmitSurveySchema>;
export type ChatbotMessageInput = z.infer<typeof ChatbotMessageSchema>;
export type ArticleHelpfulInput = z.infer<typeof ArticleHelpfulSchema>;
export type TicketFilterInput = z.infer<typeof TicketFilterSchema>;
export type ArticleFilterInput = z.infer<typeof ArticleFilterSchema>;
export type BulkTicketActionInput = z.infer<typeof BulkTicketActionSchema>;
export type FileUploadInput = z.infer<typeof FileUploadSchema>;