Advertisement
bebo231312312321

Untitled

Jun 30th, 2025
469
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. const { z } = require('zod');
  2.  
  3. // Base schemas for common types
  4. const ImageSchema = z.object({
  5.     src: z.string().nullable().optional(),
  6.     alt: z.string().nullable().optional(),
  7.     caption: z.string().nullable().optional(),
  8. });
  9.  
  10. const ContactSchema = z.object({
  11.     name: z.string().nullable().optional(),
  12.     position: z.string().nullable().optional(),
  13.     email: z.string().email('Invalid email format').nullable().optional(),
  14.     phone: z
  15.         .string()
  16.         .regex(/^\+?[\d\s\-\(\)]{8,}$/, 'Invalid phone format')
  17.         .nullable()
  18.         .optional(),
  19.     image: z.string().nullable().optional(),
  20.     isMainContact: z.boolean().nullable().optional(),
  21.     isTeamMember: z.boolean().nullable().optional(),
  22.     role: z.string().nullable().optional(),
  23. });
  24.  
  25. const SectionSchema = z.object({
  26.     titleSlug: z.string().nullable().optional(),
  27.     title: z.string().min(1, 'Section title is required').nullable().optional(),
  28.     content: z.any().nullable().optional(),
  29.     order: z.number().nullable().optional(),
  30.     images: z.array(ImageSchema).nullable().optional(),
  31. });
  32.  
  33. const SponsorSchema = z.object({
  34.     name: z.string().min(1, 'Sponsor name is required').nullable().optional(),
  35.     amount: z
  36.         .union([z.string(), z.number(), z.null()])
  37.         .transform((val) => {
  38.             if (val === null) return null;
  39.             if (typeof val === 'string') {
  40.                 const num = parseFloat(val);
  41.                 return isNaN(num) ? val : num;
  42.             }
  43.             return val;
  44.         })
  45.         .refine((val) => val === null || typeof val === 'number', 'Amount must be a valid number')
  46.         .refine((val) => val === null || val >= 0, 'Amount must be positive')
  47.         .refine((val) => val === null || val <= 999999999, 'Amount too large')
  48.         .nullable()
  49.         .optional(),
  50.     currency: z.string().nullable().optional(),
  51.     type: z.string().nullable().optional(),
  52.     visible: z.boolean().nullable().optional(),
  53.     logo: z.string().nullable().optional(),
  54.     website: z.string().url('Invalid website URL').nullable().optional(),
  55.     description: z.string().max(10000, 'Description too long').nullable().optional(),
  56. });
  57.  
  58. const PartnerSchema = z.object({
  59.     name: z.string().min(1, 'Partner name is required').nullable().optional(),
  60.     description: z.string().max(10000, 'Description too long').nullable().optional(),
  61.     website: z.string().url('Invalid website URL').nullable().optional(),
  62.     type: z.string().nullable().optional(),
  63.     visible: z.boolean().nullable().optional(),
  64.     logo: z.string().nullable().optional(),
  65. });
  66.  
  67. const DownloadMaterialSchema = z.object({
  68.     titleSlug: z.string().nullable().optional(),
  69.     title: z.string().min(1, 'Document title is required').nullable().optional(),
  70.     description: z.string().nullable().optional(),
  71.     fileType: z.enum(['pdf', 'docx']).nullable().optional(),
  72.     fileSize: z.string().nullable().optional(),
  73.     downloadUrl: z.string().nullable().optional(),
  74.     image: ImageSchema.nullable().optional(),
  75. });
  76.  
  77. const MilestoneSchema = z.object({
  78.     date: z.string().min(1, 'Milestone date is required').nullable().optional(),
  79.     description: z.string().min(5, 'Milestone description must be at least 5 characters').nullable().optional(),
  80. });
  81.  
  82. const KPISchema = z.object({
  83.     name: z.string().min(1, 'KPI name is required').nullable().optional(),
  84.     target: z.string().min(1, 'KPI target is required').nullable().optional(),
  85. });
  86.  
  87. const FAQSchema = z.object({
  88.     question: z.string().min(5, 'Question must be at least 5 characters').max(200, 'Question too long').nullable().optional(),
  89.     answer: z.string().min(10, 'Answer must be at least 10 characters').max(5000, 'Answer too long').nullable().optional(),
  90. });
  91.  
  92. const SocialMediaSchema = z.object({
  93.     facebook: z.string().url('Invalid Facebook URL').nullable().optional(),
  94.     instagram: z.string().url('Invalid Instagram URL').nullable().optional(),
  95.     linkedin: z.string().url('Invalid LinkedIn URL').nullable().optional(),
  96.     twitter: z.string().url('Invalid Twitter URL').nullable().optional(),
  97. });
  98.  
  99. const OrganizationSchema = z.object({
  100.     name: z.string().nullable().optional(),
  101.     address: z.string().nullable().optional(),
  102.     website: z.string().url('Invalid organization website URL').nullable().optional(),
  103. });
  104.  
  105. // Base schema with all fields optional for flexibility
  106. const BaseInitiativeSchema = z
  107.     .object({
  108.         // Basic info
  109.         slug: z
  110.             .string()
  111.             .min(3, 'Slug must be at least 3 characters')
  112.             .max(100, 'Slug must not exceed 100 characters')
  113.             .regex(/^[a-z0-9-]+$/, 'Slug can only contain lowercase letters, numbers, and hyphens')
  114.             .refine((slug) => !slug.startsWith('-') && !slug.endsWith('-'), 'Slug cannot start or end with hyphen')
  115.             .refine((slug) => !slug.includes('--'), 'Slug cannot contain consecutive hyphens')
  116.             .nullable()
  117.             .optional(),
  118.  
  119.         title: z.string().min(3, 'Title must be at least 3 characters').nullable().optional(),
  120.  
  121.         shortDescription: z.string().min(10, 'Short description must be at least 10 characters').nullable().optional(),
  122.  
  123.         detailedDescription: z
  124.             .any()
  125.             .refine((content) => {
  126.                 if (!content || content === null) return true;
  127.                 const textLength = JSON.stringify(content).length;
  128.                 return textLength <= 50000;
  129.             }, 'Detailed description too long')
  130.             .nullable()
  131.             .optional(),
  132.  
  133.         category: z.string().nullable().optional(),
  134.         customCategory: z.string().nullable().optional(),
  135.         priority: z.enum(['Low', 'Medium', 'High']).nullable().optional(),
  136.  
  137.         // Location
  138.         location: z
  139.             .object({
  140.                 address: z.string().nullable().optional(),
  141.                 coordinates: z
  142.                     .object({
  143.                         lat: z.number().nullable().optional(),
  144.                         lng: z.number().nullable().optional(),
  145.                     })
  146.                     .nullable()
  147.                     .optional(),
  148.             })
  149.             .refine(
  150.                 (data) => {
  151.                     if (!data) return true; // Allow null/undefined
  152.                     return data.address || (data.coordinates?.lat && data.coordinates?.lng);
  153.                 },
  154.                 {
  155.                     message: 'Location is required (either address or coordinates)',
  156.                 }
  157.             )
  158.             .nullable()
  159.             .optional(),
  160.  
  161.         // Status and campaign
  162.         status: z.enum(['in-progress', 'active', 'planned', 'completed']).nullable().optional(),
  163.         campaignStatus: z.enum(['open', 'closed']).nullable().optional(),
  164.  
  165.         // Dates and milestones
  166.         startDate: z.string().min(1, 'Start date is required').nullable().optional(),
  167.         endDate: z.string().nullable().optional(),
  168.         timestamp: z
  169.             .union([z.string(), z.date(), z.null()])
  170.             .transform((val) => {
  171.                 if (val === null) return null;
  172.                 if (typeof val === 'string') {
  173.                     const date = new Date(val);
  174.                     return isNaN(date.getTime()) ? val : date;
  175.                 }
  176.                 return val;
  177.             })
  178.             .refine((val) => val === null || val instanceof Date || val === undefined, 'Invalid date format')
  179.             .nullable()
  180.             .optional(),
  181.         duration: z.union([z.string(), z.number()]).nullable().optional(),
  182.         milestones: z.array(MilestoneSchema).nullable().optional(),
  183.  
  184.         // Target audience
  185.         targetAge: z.array(z.string()).min(1, 'Target age is required').nullable().optional(),
  186.         targetAudience: z.array(z.string()).nullable().optional(),
  187.         customAudience: z.string().min(5, 'Custom audience must be at least 5 characters').nullable().optional(),
  188.  
  189.         // Budget and funding
  190.         expectedBudget: z
  191.             .union([z.string(), z.number(), z.null()])
  192.             .transform((val) => {
  193.                 if (val === null) return null;
  194.                 if (typeof val === 'string') {
  195.                     const num = parseFloat(val);
  196.                     return isNaN(num) ? val : num;
  197.                 }
  198.                 return val;
  199.             })
  200.             .refine((val) => val === null || typeof val === 'number', 'Budget must be a valid number')
  201.             .refine((val) => val === null || val >= 0, 'Budget must be positive')
  202.             .refine((val) => val === null || val <= 999999999, 'Budget too large')
  203.             .nullable()
  204.             .optional(),
  205.         currency: z.enum(['BGN', 'EUR', 'USD', 'GBP']).nullable().optional(),
  206.         fundingSources: z.array(z.string()).nullable().optional(),
  207.  
  208.         // Organization and contact
  209.         organization: OrganizationSchema.nullable().optional(),
  210.         logo: z.string().nullable().optional(),
  211.         contactEmail: z.string().email('Invalid contact email').nullable().optional(),
  212.         contactPhone: z
  213.             .string()
  214.             .regex(/^\+?[\d\s\-\(\)]{8,}$/, 'Invalid contact phone')
  215.             .nullable()
  216.             .optional(),
  217.  
  218.         // Social media and content
  219.         socialMedia: SocialMediaSchema.nullable().optional(),
  220.         kpis: z.array(KPISchema).nullable().optional(),
  221.         expectedResults: z
  222.             .any()
  223.             .refine((content) => {
  224.                 if (!content || content === null) return true;
  225.                 const textLength = JSON.stringify(content).length;
  226.                 return textLength <= 10000;
  227.             }, 'Expected results too long')
  228.             .nullable()
  229.             .optional(),
  230.         progressReport: z
  231.             .any()
  232.             .refine((content) => {
  233.                 if (!content || content === null) return true;
  234.                 const textLength = JSON.stringify(content).length;
  235.                 return textLength <= 10000;
  236.             }, 'Progress report too long')
  237.             .nullable()
  238.             .optional(),
  239.         impactMetrics: z.array(z.any()).nullable().optional(),
  240.         testimonials: z.array(z.any()).nullable().optional(),
  241.         faq: z.array(FAQSchema).nullable().optional(),
  242.         tags: z
  243.             .array(z.string())
  244.             .max(20, 'Maximum 20 tags allowed')
  245.             .refine((tags) => tags.every((tag) => tag.length >= 2 && tag.length <= 30), {
  246.                 message: 'Each tag must be between 2 and 30 characters',
  247.             })
  248.             .nullable()
  249.             .optional(),
  250.         commentsEnabled: z.boolean().nullable().optional(),
  251.         isDraft: z.boolean().nullable().optional(),
  252.         gallery: z.array(z.any()).nullable().optional(),
  253.  
  254.         mainImage: z
  255.             .object({
  256.                 src: z.string().min(1, 'Main image is required').nullable().optional(),
  257.                 alt: z.string().nullable().optional(),
  258.                 caption: z.string().nullable().optional(),
  259.                 gallery: z.array(ImageSchema).nullable().optional(),
  260.             })
  261.             .nullable()
  262.             .optional(),
  263.  
  264.         contact: ContactSchema.nullable().optional(),
  265.         additionalContacts: z.array(ContactSchema).nullable().optional(),
  266.         responsible: ContactSchema.nullable().optional(),
  267.  
  268.         sections: z.array(SectionSchema).min(1, 'At least one section is required').nullable().optional(),
  269.  
  270.         sponsors: z.array(SponsorSchema).nullable().optional(),
  271.         partners: z.array(PartnerSchema).nullable().optional(),
  272.         downloadMaterials: z.array(DownloadMaterialSchema).nullable().optional(),
  273.         documents: z.array(DownloadMaterialSchema).nullable().optional(),
  274.     })
  275.     .refine(
  276.         (data) => {
  277.             if (data?.expectedBudget && !data?.currency) {
  278.                 return false;
  279.             }
  280.             return true;
  281.         },
  282.         {
  283.             message: 'Currency is required when budget is provided',
  284.             path: ['currency'],
  285.         }
  286.     )
  287.     .refine(
  288.         (data) => {
  289.             if (data?.startDate && data?.endDate) {
  290.                 return new Date(data.startDate) < new Date(data.endDate);
  291.             }
  292.             return true;
  293.         },
  294.         {
  295.             message: 'End date must be after start date',
  296.             path: ['endDate'],
  297.         }
  298.     );
  299.  
  300. const InitiativeSchema = BaseInitiativeSchema.refine(
  301.     (data) => {
  302.         const requiredFields = ['slug', 'title', 'shortDescription', 'mainImage', 'sections', 'targetAge', 'startDate'];
  303.         return requiredFields.every((field) => data[field] !== undefined && data[field] !== null && data[field] !== '');
  304.     },
  305.     {
  306.         message: 'Required fields missing for initiative creation',
  307.     }
  308. );
  309.  
  310. // For updates - everything is optional
  311. const UpdateInitiativeSchema = BaseInitiativeSchema;
  312.  
  313. // Pagination query parameters
  314. const PaginationQuerySchema = z.object({
  315.     page: z
  316.         .string()
  317.         .optional()
  318.         .transform((val) => {
  319.             const num = val ? parseInt(val) : 1;
  320.             return Math.max(1, num);
  321.         }),
  322.     limit: z
  323.         .string()
  324.         .optional()
  325.         .transform((val) => {
  326.             const num = val ? parseInt(val) : 6;
  327.             return Math.max(1, num);
  328.         }),
  329. });
  330.  
  331. module.exports = {
  332.     InitiativeSchema,
  333.     UpdateInitiativeSchema,
  334.     PaginationQuerySchema,
  335.     ContactSchema,
  336.     SectionSchema,
  337.     SponsorSchema,
  338.     PartnerSchema,
  339.     DownloadMaterialSchema,
  340.     MilestoneSchema,
  341.     KPISchema,
  342.     FAQSchema,
  343.     SocialMediaSchema,
  344.     OrganizationSchema,
  345.     ImageSchema,
  346. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement