import { MiiGender, MiiPlatform } from "@prisma/client"; import { z } from "zod"; // profanity censoring bypasses the regex in some of these but I think it's funny export const querySchema = z .string() .trim() .min(2, { error: "Search query must be at least 2 characters long" }) .max(64, { error: "Search query cannot be more than 64 characters long" }) .regex(/^[a-zA-Z0-9-_. ']+$/, { error: "Search query can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.", }); // Miis export const nameSchema = z .string() .trim() .min(2, { error: "Name must be at least 2 characters long" }) .max(64, { error: "Name cannot be more than 64 characters long" }) .regex(/^[a-zA-Z0-9-_. ']+$/, { error: "Name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.", }); export const tagsSchema = z .array( z .string() .min(2, { error: "Tags must be at least 2 characters long" }) .max(20, { error: "Tags cannot be more than 20 characters long" }) .regex(/^[a-z0-9-_]+$/, { error: "Tags can only contain lowercase letters, numbers, dashes, and underscores.", }), ) .min(1, { error: "There must be at least 1 tag" }) .max(8, { error: "There cannot be more than 8 tags" }); export const idSchema = z.coerce.number({ error: "ID must be a number" }).int({ error: "ID must be an integer" }).positive({ error: "ID must be valid" }); export const searchSchema = z.object({ q: querySchema.optional(), sort: z.enum(["likes", "newest", "oldest", "random"], { error: "Sort must be either 'likes', 'newest', 'oldest', or 'random'" }).default("newest"), tags: z .string() .optional() .transform((value) => value ?.split(",") .map((tag) => tag.trim()) .filter((tag) => tag.length > 0), ), exclude: z .string() .optional() .transform((value) => value ?.split(",") .map((tag) => tag.trim()) .filter((tag) => tag.length > 0), ), platform: z.enum(MiiPlatform, { error: "Platform must be either 'THREE_DS', or 'SWITCH'" }).optional(), gender: z.enum(MiiGender, { error: "Gender must be either 'MALE', 'FEMALE', or 'NONBINARY' if on Switch platform" }).optional(), allowCopying: z.coerce.boolean({ error: "Allow Copying must be either true or false" }).optional(), // todo: incorporate tagsSchema // Pages limit: z.coerce .number({ error: "Limit must be a number" }) .int({ error: "Limit must be an integer" }) .min(1, { error: "Limit must be at least 1" }) .max(100, { error: "Limit cannot be more than 100" }) .optional(), page: z.coerce.number({ error: "Page must be a number" }).int({ error: "Page must be an integer" }).min(1, { error: "Page must be at least 1" }).optional(), // Random sort seed: z.coerce.number({ error: "Seed must be a number" }).int({ error: "Seed must be an integer" }).optional(), }); // Account Info export const usernameSchema = z .string() .trim() .min(3, "Username must be at least 3 characters long") .max(20, "Username cannot be more than 20 characters long") .regex(/^[a-zA-Z0-9_]+$/, "Username can only contain letters, numbers, and underscores"); export const displayNameSchema = z .string() .trim() .min(2, { error: "Display name must be at least 2 characters long" }) .max(64, { error: "Display name cannot be more than 64 characters long" }) .regex(/^[a-zA-Z0-9-_. ']+$/, { error: "Display name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.", }); const colorSchema = z.number().int().min(0).max(107).optional(); const geometrySchema = z.number().int().min(-5).max(5).optional(); export const switchMiiInstructionsSchema = z .object({ head: z.object({ type: z.number().int().min(1).max(16).optional(), skinColor: z.number().int().min(0).max(121).optional() }).optional(), hair: z .object({ setType: z.number().int().min(0).max(25).optional(), bangsType: z.number().int().min(0).max(25).optional(), backType: z.number().int().min(0).max(25).optional(), color: colorSchema, subColor: colorSchema, style: z.number().int().min(0).max(3).optional(), isFlipped: z.boolean().optional(), }) .optional(), eyebrows: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, rotation: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), eyes: z .object({ eyesType: z.number().int().min(0).max(25).optional(), eyelashesTop: z.number().int().min(0).max(6).optional(), eyelashesBottom: z.number().int().min(0).max(25).optional(), eyelidTop: z.number().int().min(0).max(2).optional(), eyelidBottom: z.number().int().min(0).max(25).optional(), eyeliner: z.number().int().min(0).max(25).optional(), pupil: z.number().int().min(0).max(9).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, rotation: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), nose: z .object({ type: z.number().int().min(0).max(25).optional(), height: geometrySchema, size: geometrySchema, }) .optional(), lips: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, rotation: geometrySchema, size: geometrySchema, stretch: geometrySchema, hasLipstick: z.boolean().optional(), }) .optional(), ears: z .object({ type: z.number().int().min(0).max(4).optional(), height: geometrySchema, size: geometrySchema, }) .optional(), glasses: z .object({ type: z.number().int().min(0).max(25).optional(), ringColor: colorSchema, shadesColor: colorSchema, height: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), other: z .object({ wrinkles1: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), wrinkles2: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), beard: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), moustache: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), goatee: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), mole: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), eyeShadow: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), blush: z .object({ type: z.number().int().min(0).max(25).optional(), color: colorSchema, height: geometrySchema, distance: geometrySchema, size: geometrySchema, stretch: geometrySchema, }) .optional(), }) .optional(), height: z.number().int().min(0).max(100).optional(), weight: z.number().int().min(0).max(100).optional(), datingPreferences: z.array(z.enum(MiiGender)).optional(), voice: z .object({ speed: z.number().int().min(0).max(100).optional(), pitch: z.number().int().min(0).max(100).optional(), depth: z.number().int().min(0).max(100).optional(), delivery: z.number().int().min(0).max(100).optional(), tone: z.number().int().min(1).max(6).optional(), }) .optional(), personality: z .object({ movement: z.number().int().min(1).max(8).optional(), speech: z.number().int().min(1).max(8).optional(), energy: z.number().int().min(1).max(8).optional(), thinking: z.number().int().min(1).max(8).optional(), overall: z.number().int().min(1).max(8).optional(), }) .optional(), }) .optional();