- Complete project structure with Next.js frontend - GraphQL API backend with Apollo Server - Portal application with NextAuth - Crossplane Proxmox provider - GitOps configurations - CI/CD pipelines - Testing infrastructure (Vitest, Jest, Go tests) - Error handling and monitoring - Security hardening - UI component library - Documentation
83 lines
1.9 KiB
TypeScript
83 lines
1.9 KiB
TypeScript
/**
|
|
* Zod validation schemas
|
|
*/
|
|
|
|
import { z } from 'zod'
|
|
|
|
/**
|
|
* Common validation schemas
|
|
*/
|
|
export const emailSchema = z.string().email('Invalid email address')
|
|
|
|
export const urlSchema = z.string().url('Invalid URL')
|
|
|
|
export const nonEmptyStringSchema = z.string().min(1, 'This field is required')
|
|
|
|
/**
|
|
* Resource validation schemas
|
|
*/
|
|
export const resourceNameSchema = z
|
|
.string()
|
|
.min(1, 'Resource name is required')
|
|
.max(63, 'Resource name must be 63 characters or less')
|
|
.regex(
|
|
/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/,
|
|
'Resource name must be lowercase alphanumeric with hyphens'
|
|
)
|
|
|
|
export const memorySchema = z
|
|
.string()
|
|
.regex(/^\d+[KMGT]i?$/, 'Memory must be in format like 4Gi, 8Mi, etc.')
|
|
|
|
export const diskSizeSchema = z
|
|
.string()
|
|
.regex(/^\d+[KMGT]i?$/, 'Disk size must be in format like 50Gi, 100Mi, etc.')
|
|
|
|
export const cpuSchema = z.number().int().min(1).max(128)
|
|
|
|
/**
|
|
* VM creation schema
|
|
*/
|
|
export const vmCreateSchema = z.object({
|
|
name: resourceNameSchema,
|
|
node: nonEmptyStringSchema,
|
|
cpu: cpuSchema,
|
|
memory: memorySchema,
|
|
disk: diskSizeSchema,
|
|
storage: nonEmptyStringSchema,
|
|
network: nonEmptyStringSchema,
|
|
image: nonEmptyStringSchema,
|
|
site: nonEmptyStringSchema,
|
|
userData: z.string().optional(),
|
|
sshKeys: z.array(z.string()).optional(),
|
|
})
|
|
|
|
export type VMCreateInput = z.infer<typeof vmCreateSchema>
|
|
|
|
/**
|
|
* Form validation utilities
|
|
*/
|
|
export function validateForm<T>(schema: z.ZodSchema<T>, data: unknown): {
|
|
success: boolean
|
|
data?: T
|
|
errors?: Record<string, string[]>
|
|
} {
|
|
const result = schema.safeParse(data)
|
|
|
|
if (result.success) {
|
|
return { success: true, data: result.data }
|
|
}
|
|
|
|
const errors: Record<string, string[]> = {}
|
|
result.error.errors.forEach((error) => {
|
|
const path = error.path.join('.')
|
|
if (!errors[path]) {
|
|
errors[path] = []
|
|
}
|
|
errors[path].push(error.message)
|
|
})
|
|
|
|
return { success: false, errors }
|
|
}
|
|
|