72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
import { z, type ZodTypeAny } from 'astro/zod'
|
|
import { round } from 'lodash-es'
|
|
|
|
const addZodPipe = (schema: ZodTypeAny, zodPipe?: ZodTypeAny) => {
|
|
return zodPipe ? schema.pipe(zodPipe) : schema
|
|
}
|
|
|
|
/**
|
|
* The difference between this and `z.coerce.number()` is that an empty string won't be coerced to 0.
|
|
*
|
|
* If you don't accept 0, just use `z.coerce.number().int().positive()` instead.
|
|
*/
|
|
export const zodCohercedNumber = (zodPipe?: ZodTypeAny) =>
|
|
addZodPipe(z.number().or(z.string().nonempty()), zodPipe)
|
|
|
|
export const zodUrlOptionalProtocol = z.preprocess(
|
|
(input) => {
|
|
if (typeof input !== 'string') return input
|
|
const cleanInput = input.trim().replace(/\/$/, '')
|
|
return !/^\w+:\/\//i.test(cleanInput) ? `https://${cleanInput}` : cleanInput
|
|
},
|
|
z.string().refine((value) => /^(https?):\/\/(?=.*\.[a-z0-9]{2,})[^\s$.?#].[^\s]*$/i.test(value), {
|
|
message: 'Invalid URL',
|
|
})
|
|
)
|
|
|
|
const stringToArrayFactory = (delimiter: RegExp | string = ',') => {
|
|
return <T>(input: T) =>
|
|
typeof input !== 'string'
|
|
? (input ?? undefined)
|
|
: input
|
|
.split(delimiter)
|
|
.map((item) => item.trim())
|
|
.filter((item) => item !== '')
|
|
}
|
|
|
|
export const stringListOfUrlsSchema = z.preprocess(
|
|
stringToArrayFactory(/[\s,\n]+/),
|
|
z.array(zodUrlOptionalProtocol).default([])
|
|
)
|
|
|
|
export const stringListOfUrlsSchemaRequired = z.preprocess(
|
|
stringToArrayFactory(/[\s,\n]+/),
|
|
z.array(zodUrlOptionalProtocol).min(1)
|
|
)
|
|
|
|
export const MAX_IMAGE_SIZE = 5 * 1024 * 1024 // 5MB
|
|
|
|
export const ACCEPTED_IMAGE_TYPES = [
|
|
'image/svg+xml',
|
|
'image/png',
|
|
'image/jpeg',
|
|
'image/avif',
|
|
'image/webp',
|
|
] as const satisfies string[]
|
|
|
|
export const imageFileSchema = z
|
|
.instanceof(File)
|
|
.optional()
|
|
.nullable()
|
|
.transform((file) => (!file || file.size === 0 || !file.name ? undefined : file))
|
|
.refine(
|
|
(file) => !file || file.size <= MAX_IMAGE_SIZE,
|
|
`Max image size is ${round(MAX_IMAGE_SIZE / 1024 / 1024, 3).toLocaleString()}MB.`
|
|
)
|
|
.refine(
|
|
(file) => !file || ACCEPTED_IMAGE_TYPES.some((type) => file.type === type),
|
|
'Only SVG, PNG, JPG, AVIF, WebP formats are supported.'
|
|
)
|
|
|
|
export const imageFileSchemaRequired = imageFileSchema.refine((file) => !!file, 'Required')
|