diff --git a/lib/validations.ts b/lib/validations.ts index 749fb1ea1..744c84a47 100644 --- a/lib/validations.ts +++ b/lib/validations.ts @@ -529,17 +529,59 @@ export const streakParamsSchema = baseStreakParamsSchema.refine( } ); +const HEX_REGEX = /^([A-Fa-f0-9]{3,4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/; + export const githubParamsSchema = z.object({ - username: z - .string({ error: 'Missing "username" parameter' }) - .trim() - .min(1, { message: 'Username is required' }) - .max(39, { message: 'GitHub username cannot exceed 39 characters' }) - .regex(GITHUB_USERNAME_REGEX, { - message: 'Invalid GitHub username', - }), - refresh: z.string().optional().transform(toRefreshFlag), - bypassCache: z.string().optional().transform(toRefreshFlag), + // Preprocess leaves undefined untouched so we can distinguish missing vs empty string + username: z.preprocess( + (val) => (typeof val === 'string' ? val.trim() : val), + z + .custom() + .refine((val) => val !== undefined && val !== null, { + message: 'Missing "username" parameter', + }) + .transform((val) => String(val)) + .refine((val) => val.length > 0, { + message: 'Username is required', + }) + .refine((val) => val.length <= 39, { + message: 'GitHub username cannot exceed 39 characters', + }) + .refine((val) => validateGitHubUsername(val), { + message: 'Invalid GitHub username', + }) + ), + + bg: z + .string() + .optional() + .transform((val) => val?.replace('#', '') || 'ffffff') + .refine((val) => HEX_REGEX.test(val)) + .catch('ffffff'), + + accent: z + .string() + .optional() + .transform((val) => val?.replace('#', '') || 'ff6b35') + .refine((val) => HEX_REGEX.test(val)) + .catch('ff6b35'), + + width: z + .string() + .optional() + .transform((val) => (val ? parseInt(val, 10) : 400)) + .refine((val) => !isNaN(val) && val >= 100 && val <= 2000) + .catch(400), + + height: z + .string() + .optional() + .transform((val) => (val ? parseInt(val, 10) : 150)) + .refine((val) => !isNaN(val) && val >= 100 && val <= 2000) + .catch(150), + + refresh: z.preprocess((val) => val === 'true', z.boolean()).default(false), + bypassCache: z.preprocess((val) => val === 'true', z.boolean()).default(false), }); export const compareParamsSchema = z