187 lines
5.2 KiB
TypeScript
187 lines
5.2 KiB
TypeScript
/**
|
|
* Zod Validation Schemas for API endpoints
|
|
* Centralized validation logic for type-safety and security
|
|
*/
|
|
|
|
import { z } from 'zod';
|
|
|
|
// ==========================================
|
|
// QR Code Schemas
|
|
// ==========================================
|
|
|
|
export const qrStyleSchema = z.object({
|
|
fgColor: z.string().regex(/^#[0-9A-F]{6}$/i, 'Invalid foreground color format').optional(),
|
|
bgColor: z.string().regex(/^#[0-9A-F]{6}$/i, 'Invalid background color format').optional(),
|
|
cornerStyle: z.enum(['square', 'rounded']).optional(),
|
|
size: z.number().min(100).max(1000).optional(),
|
|
});
|
|
|
|
export const createQRSchema = z.object({
|
|
title: z.string()
|
|
.min(1, 'Title is required')
|
|
.max(100, 'Title must be less than 100 characters'),
|
|
|
|
content: z.record(z.any()), // Accept any object structure for content
|
|
|
|
isStatic: z.boolean().optional(),
|
|
|
|
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT'], {
|
|
errorMap: () => ({ message: 'Invalid content type' })
|
|
}),
|
|
|
|
tags: z.array(z.string()).optional(),
|
|
|
|
style: z.object({
|
|
foregroundColor: z.string().optional(),
|
|
backgroundColor: z.string().optional(),
|
|
cornerStyle: z.enum(['square', 'rounded']).optional(),
|
|
size: z.number().optional(),
|
|
}).optional(),
|
|
});
|
|
|
|
export const updateQRSchema = z.object({
|
|
title: z.string()
|
|
.min(1, 'Title is required')
|
|
.max(100, 'Title must be less than 100 characters')
|
|
.optional(),
|
|
|
|
content: z.string()
|
|
.min(1, 'Content is required')
|
|
.max(5000, 'Content must be less than 5000 characters')
|
|
.optional(),
|
|
|
|
style: qrStyleSchema.optional(),
|
|
|
|
isActive: z.boolean().optional(),
|
|
});
|
|
|
|
export const bulkQRSchema = z.object({
|
|
qrs: z.array(
|
|
z.object({
|
|
title: z.string().min(1).max(100),
|
|
content: z.string().min(1).max(5000),
|
|
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT']),
|
|
})
|
|
).min(1, 'At least one QR code is required')
|
|
.max(100, 'Maximum 100 QR codes per bulk creation'),
|
|
});
|
|
|
|
// ==========================================
|
|
// Authentication Schemas
|
|
// ==========================================
|
|
|
|
export const loginSchema = z.object({
|
|
email: z.string()
|
|
.email('Invalid email format')
|
|
.toLowerCase(),
|
|
|
|
password: z.string()
|
|
.min(1, 'Password is required'),
|
|
});
|
|
|
|
export const signupSchema = z.object({
|
|
name: z.string()
|
|
.min(2, 'Name must be at least 2 characters')
|
|
.max(100, 'Name must be less than 100 characters')
|
|
.trim(),
|
|
|
|
email: z.string()
|
|
.email('Invalid email format')
|
|
.toLowerCase()
|
|
.trim(),
|
|
|
|
password: z.string()
|
|
.min(8, 'Password must be at least 8 characters')
|
|
.max(100, 'Password must be less than 100 characters'),
|
|
// Password complexity rules removed for easier testing
|
|
});
|
|
|
|
export const forgotPasswordSchema = z.object({
|
|
email: z.string()
|
|
.email('Invalid email format')
|
|
.toLowerCase()
|
|
.trim(),
|
|
});
|
|
|
|
export const resetPasswordSchema = z.object({
|
|
token: z.string().min(1, 'Reset token is required'),
|
|
password: z.string()
|
|
.min(8, 'Password must be at least 8 characters')
|
|
.max(100, 'Password must be less than 100 characters'),
|
|
// Password complexity rules removed for easier testing
|
|
});
|
|
|
|
// ==========================================
|
|
// Settings Schemas
|
|
// ==========================================
|
|
|
|
export const updateProfileSchema = z.object({
|
|
name: z.string()
|
|
.min(2, 'Name must be at least 2 characters')
|
|
.max(100, 'Name must be less than 100 characters')
|
|
.trim(),
|
|
});
|
|
|
|
export const changePasswordSchema = z.object({
|
|
currentPassword: z.string()
|
|
.min(1, 'Current password is required'),
|
|
|
|
newPassword: z.string()
|
|
.min(8, 'Password must be at least 8 characters')
|
|
.max(100, 'Password must be less than 100 characters'),
|
|
// Password complexity rules removed for easier testing
|
|
});
|
|
|
|
// ==========================================
|
|
// Stripe Schemas
|
|
// ==========================================
|
|
|
|
export const createCheckoutSchema = z.object({
|
|
priceId: z.string().min(1, 'Price ID is required'),
|
|
});
|
|
|
|
// ==========================================
|
|
// Newsletter Schemas
|
|
// ==========================================
|
|
|
|
export const newsletterSubscribeSchema = z.object({
|
|
email: z.string()
|
|
.email('Invalid email format')
|
|
.toLowerCase()
|
|
.trim()
|
|
.max(255, 'Email must be less than 255 characters'),
|
|
});
|
|
|
|
// ==========================================
|
|
// Helper: Format Zod Errors
|
|
// ==========================================
|
|
|
|
export function formatZodError(error: z.ZodError) {
|
|
return {
|
|
error: 'Validation failed',
|
|
details: error.errors.map(err => ({
|
|
field: err.path.join('.'),
|
|
message: err.message,
|
|
})),
|
|
};
|
|
}
|
|
|
|
// ==========================================
|
|
// Helper: Validate with Zod
|
|
// ==========================================
|
|
|
|
export async function validateRequest<T>(
|
|
schema: z.ZodSchema<T>,
|
|
data: unknown
|
|
): Promise<{ success: true; data: T } | { success: false; error: any }> {
|
|
try {
|
|
const validatedData = schema.parse(data);
|
|
return { success: true, data: validatedData };
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return { success: false, error: formatZodError(error) };
|
|
}
|
|
return { success: false, error: { error: 'Invalid request data' } };
|
|
}
|
|
}
|