Add Zod validation to all API endpoints and remove debug test route
Adds schema-based input validation across helcim, events, members, series, admin, and updates API endpoints. Removes the peer-support debug test endpoint. Adds validation test coverage.
This commit is contained in:
parent
e4813075b7
commit
025c1a180f
38 changed files with 1132 additions and 309 deletions
|
|
@ -64,6 +64,135 @@ export const paymentVerifySchema = z.object({
|
|||
customerId: z.string().min(1)
|
||||
})
|
||||
|
||||
// --- Helcim schemas ---
|
||||
|
||||
export const helcimCreatePlanSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
amount: z.union([z.string().min(1), z.number().positive()]),
|
||||
frequency: z.string().min(1).max(50),
|
||||
currency: z.string().max(10).optional()
|
||||
})
|
||||
|
||||
export const helcimCustomerSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
email: z.string().trim().toLowerCase().email(),
|
||||
circle: z.enum(['community', 'founder', 'practitioner']).optional(),
|
||||
contributionTier: z.enum(['0', '5', '15', '30', '50']).optional()
|
||||
})
|
||||
|
||||
export const helcimInitializePaymentSchema = z.object({
|
||||
amount: z.number().min(0).optional(),
|
||||
customerCode: z.string().max(200).optional(),
|
||||
metadata: z.object({
|
||||
type: z.string().max(100).optional(),
|
||||
eventTitle: z.string().max(500).optional(),
|
||||
eventId: z.string().max(200).optional()
|
||||
}).optional()
|
||||
})
|
||||
|
||||
export const helcimSubscriptionSchema = z.object({
|
||||
customerId: z.union([z.string().min(1), z.number()]),
|
||||
contributionTier: z.enum(['0', '5', '15', '30', '50']),
|
||||
customerCode: z.string().min(1).max(200),
|
||||
cardToken: z.string().max(500).optional()
|
||||
})
|
||||
|
||||
export const helcimUpdateBillingSchema = z.object({
|
||||
customerId: z.union([z.string().min(1), z.number()]),
|
||||
billingAddress: z.object({
|
||||
name: z.string().max(200).optional(),
|
||||
street: z.string().min(1).max(500),
|
||||
city: z.string().min(1).max(200),
|
||||
province: z.string().max(200).optional(),
|
||||
state: z.string().max(200).optional(),
|
||||
country: z.string().min(1).max(100),
|
||||
postalCode: z.string().min(1).max(20)
|
||||
})
|
||||
})
|
||||
|
||||
// --- Event ticket/registration schemas ---
|
||||
|
||||
export const ticketPurchaseSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
email: z.string().trim().toLowerCase().email(),
|
||||
transactionId: z.string().max(500).optional()
|
||||
})
|
||||
|
||||
export const ticketReserveSchema = z.object({
|
||||
email: z.string().trim().toLowerCase().email()
|
||||
})
|
||||
|
||||
export const ticketEligibilitySchema = z.object({
|
||||
email: z.string().trim().toLowerCase().email()
|
||||
})
|
||||
|
||||
export const waitlistSchema = z.object({
|
||||
name: z.string().max(200).optional(),
|
||||
email: z.string().trim().toLowerCase().email(),
|
||||
membershipLevel: z.string().max(100).optional()
|
||||
})
|
||||
|
||||
export const waitlistDeleteSchema = z.object({
|
||||
email: z.string().trim().toLowerCase().email()
|
||||
})
|
||||
|
||||
export const cancelRegistrationSchema = z.object({
|
||||
email: z.string().trim().toLowerCase().email()
|
||||
})
|
||||
|
||||
export const checkRegistrationSchema = z.object({
|
||||
email: z.string().trim().toLowerCase().email()
|
||||
})
|
||||
|
||||
export const guestRegisterSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
email: z.string().trim().toLowerCase().email()
|
||||
})
|
||||
|
||||
export const eventPaymentSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
email: z.string().trim().toLowerCase().email(),
|
||||
paymentToken: z.string().min(1).max(500)
|
||||
})
|
||||
|
||||
// --- Member schemas ---
|
||||
|
||||
export const updateContributionSchema = z.object({
|
||||
contributionTier: z.enum(['0', '5', '15', '30', '50'])
|
||||
})
|
||||
|
||||
export const peerSupportUpdateSchema = z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
skillTopics: z.array(z.string().max(200)).max(20).optional(),
|
||||
supportTopics: z.array(z.string().max(200)).max(20).optional(),
|
||||
availability: z.string().max(500).optional(),
|
||||
personalMessage: z.string().max(2000).optional(),
|
||||
slackUsername: z.string().max(200).optional()
|
||||
})
|
||||
|
||||
// --- Update schemas ---
|
||||
|
||||
export const updatePatchSchema = z.object({
|
||||
content: z.string().min(1).max(50000).optional(),
|
||||
images: z.array(z.string().url()).max(20).optional(),
|
||||
privacy: z.enum(['public', 'members', 'private']).optional(),
|
||||
commentsEnabled: z.boolean().optional()
|
||||
})
|
||||
|
||||
// --- Series ticket schemas ---
|
||||
|
||||
export const seriesTicketPurchaseSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
email: z.string().trim().toLowerCase().email(),
|
||||
paymentId: z.string().max(500).optional()
|
||||
})
|
||||
|
||||
export const seriesTicketEligibilitySchema = z.object({
|
||||
email: z.string().trim().toLowerCase().email()
|
||||
})
|
||||
|
||||
// --- Admin schemas ---
|
||||
|
||||
export const adminEventCreateSchema = z.object({
|
||||
title: z.string().min(1).max(500),
|
||||
description: z.string().min(1).max(50000),
|
||||
|
|
@ -94,3 +223,106 @@ export const adminEventCreateSchema = z.object({
|
|||
tags: z.array(z.string().max(100)).max(20).optional(),
|
||||
series: z.string().optional()
|
||||
})
|
||||
|
||||
export const adminEventUpdateSchema = z.object({
|
||||
title: z.string().min(1).max(500),
|
||||
description: z.string().min(1).max(50000),
|
||||
startDate: z.string().min(1),
|
||||
endDate: z.string().min(1),
|
||||
location: z.string().max(500).optional(),
|
||||
maxAttendees: z.number().int().positive().optional().nullable(),
|
||||
membersOnly: z.boolean().optional(),
|
||||
registrationDeadline: z.string().optional().nullable(),
|
||||
pricing: z.object({
|
||||
paymentRequired: z.boolean().optional(),
|
||||
isFree: z.boolean().optional(),
|
||||
publicPrice: z.number().min(0).optional()
|
||||
}).optional(),
|
||||
tickets: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
public: z.object({
|
||||
available: z.boolean().optional(),
|
||||
name: z.string().max(200).optional(),
|
||||
description: z.string().max(2000).optional(),
|
||||
price: z.number().min(0).optional(),
|
||||
quantity: z.number().int().positive().optional().nullable(),
|
||||
sold: z.number().int().min(0).optional(),
|
||||
earlyBirdPrice: z.number().min(0).optional().nullable(),
|
||||
earlyBirdDeadline: z.string().optional().nullable()
|
||||
}).optional()
|
||||
}).optional(),
|
||||
image: z.string().url().optional().nullable(),
|
||||
category: z.string().max(100).optional(),
|
||||
tags: z.array(z.string().max(100)).max(20).optional(),
|
||||
series: z.any().optional(),
|
||||
slug: z.string().max(500).optional()
|
||||
}).passthrough()
|
||||
|
||||
export const adminSeriesCreateSchema = z.object({
|
||||
id: z.string().min(1).max(200),
|
||||
title: z.string().min(1).max(500),
|
||||
description: z.string().min(1).max(50000),
|
||||
type: z.string().max(100).optional(),
|
||||
totalEvents: z.number().int().positive().optional().nullable()
|
||||
})
|
||||
|
||||
export const adminSeriesUpdateSchema = z.object({
|
||||
id: z.string().min(1).max(200),
|
||||
title: z.string().min(1).max(500),
|
||||
description: z.string().max(50000).optional(),
|
||||
type: z.string().max(100).optional(),
|
||||
totalEvents: z.number().int().positive().optional().nullable()
|
||||
})
|
||||
|
||||
export const adminSeriesItemUpdateSchema = z.object({
|
||||
title: z.string().min(1).max(500).optional(),
|
||||
description: z.string().max(50000).optional(),
|
||||
type: z.string().max(100).optional(),
|
||||
totalEvents: z.number().int().positive().optional().nullable(),
|
||||
isActive: z.boolean().optional()
|
||||
})
|
||||
|
||||
export const adminSeriesTicketsSchema = z.object({
|
||||
id: z.string().min(1).max(200),
|
||||
tickets: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
requiresSeriesTicket: z.boolean().optional(),
|
||||
allowIndividualEventTickets: z.boolean().optional(),
|
||||
currency: z.string().max(10).optional(),
|
||||
member: z.object({
|
||||
available: z.boolean().optional(),
|
||||
isFree: z.boolean().optional(),
|
||||
price: z.number().min(0).optional(),
|
||||
name: z.string().max(200).optional(),
|
||||
description: z.string().max(2000).optional(),
|
||||
circleOverrides: z.record(z.any()).optional()
|
||||
}).optional(),
|
||||
public: z.object({
|
||||
available: z.boolean().optional(),
|
||||
name: z.string().max(200).optional(),
|
||||
description: z.string().max(2000).optional(),
|
||||
price: z.number().min(0).optional(),
|
||||
quantity: z.number().int().positive().optional().nullable(),
|
||||
sold: z.number().int().min(0).optional(),
|
||||
reserved: z.number().int().min(0).optional(),
|
||||
earlyBirdPrice: z.number().min(0).optional().nullable(),
|
||||
earlyBirdDeadline: z.string().optional().nullable()
|
||||
}).optional(),
|
||||
capacity: z.object({
|
||||
total: z.number().int().positive().optional().nullable(),
|
||||
reserved: z.number().int().min(0).optional()
|
||||
}).optional(),
|
||||
waitlist: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
maxSize: z.number().int().positive().optional().nullable(),
|
||||
entries: z.array(z.any()).optional()
|
||||
}).optional()
|
||||
})
|
||||
})
|
||||
|
||||
export const adminMemberCreateSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
email: z.string().trim().toLowerCase().email(),
|
||||
circle: z.enum(['community', 'founder', 'practitioner']),
|
||||
contributionTier: z.enum(['0', '5', '15', '30', '50'])
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue