d7fb7b7a7b
Build & Deploy / Build & Push Docker Image (push) Failing after 47s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / API Unit Tests (push) Failing after 5m4s
Test / Marketplace Unit Tests (push) Failing after 4m55s
Test / Admin Unit Tests (push) Successful in 9m37s
Test / Dashboard Unit Tests (push) Successful in 9m37s
Test / API Integration Tests (push) Successful in 9m54s
156 lines
4.9 KiB
TypeScript
156 lines
4.9 KiB
TypeScript
import { z } from 'zod'
|
|
import { sanitizeAndFormat, getMaxLength } from './inputValidation'
|
|
import type { FieldType } from './inputValidation'
|
|
|
|
// ─── Regex constants ───────────────────────────────────────────
|
|
|
|
const GLOBAL_ALLOWED = /^[a-zA-Z0-9@\-/\s]*$/
|
|
|
|
const EMAIL_REGEX = /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/
|
|
|
|
/** Morocco phone: (+212|00212|0) + 5/6/7 + 8 digits (optional spaces) */
|
|
const MA_PHONE_REGEX = /^(?:(?:\+|00)212|0)\s?[5-7](?:\s?\d){8}$/
|
|
|
|
// ─── Phone sanitization ───────────────────────────────────────
|
|
|
|
function sanitizePhone(raw: string): string {
|
|
let out = ''
|
|
for (const ch of raw) {
|
|
if (/[\d\s+]/.test(ch)) out += ch
|
|
}
|
|
return out.trim()
|
|
}
|
|
|
|
// ─── Required fields ───────────────────────────────────────────
|
|
|
|
function applyFieldRules(fieldType: FieldType) {
|
|
const maxLength = getMaxLength(fieldType)
|
|
return z
|
|
.string()
|
|
.min(1, { message: 'This field is required' })
|
|
.trim()
|
|
.transform((val: string) => sanitizeAndFormat(val, fieldType))
|
|
.pipe(
|
|
z.string().refine(
|
|
(val: string) => val.length <= maxLength,
|
|
{ message: 'Maximum ' + maxLength + ' characters allowed' }
|
|
)
|
|
)
|
|
}
|
|
|
|
// ─── Optional fields ───────────────────────────────────────────
|
|
|
|
function applyOptionalFieldRules(fieldType: FieldType) {
|
|
const maxLength = getMaxLength(fieldType)
|
|
return z
|
|
.string()
|
|
.optional()
|
|
.transform((val) => {
|
|
if (val === undefined || val === null || val.trim() === '') return undefined
|
|
return sanitizeAndFormat(val, fieldType)
|
|
})
|
|
.pipe(
|
|
z.string().optional().refine(
|
|
(val) => val === undefined || val.length <= maxLength,
|
|
{ message: 'Maximum ' + maxLength + ' characters allowed' }
|
|
)
|
|
)
|
|
}
|
|
|
|
// ─── Exported helpers ──────────────────────────────────────────
|
|
|
|
export function textField(fieldType: FieldType) {
|
|
return applyFieldRules(fieldType)
|
|
}
|
|
export function optionalTextField(fieldType: FieldType) {
|
|
return applyOptionalFieldRules(fieldType)
|
|
}
|
|
export function titleCaseAllField(fieldType: FieldType) {
|
|
return applyFieldRules(fieldType)
|
|
}
|
|
export function optionalTitleCaseAllField(fieldType: FieldType) {
|
|
return applyOptionalFieldRules(fieldType)
|
|
}
|
|
export function upperField(fieldType: FieldType) {
|
|
return applyFieldRules(fieldType)
|
|
}
|
|
export function optionalUpperField(fieldType: FieldType) {
|
|
return applyOptionalFieldRules(fieldType)
|
|
}
|
|
export function carTextField(fieldType: FieldType) {
|
|
return applyFieldRules(fieldType)
|
|
}
|
|
export function optionalCarTextField(fieldType: FieldType) {
|
|
return applyOptionalFieldRules(fieldType)
|
|
}
|
|
export function zipField() {
|
|
return applyFieldRules('zipCode')
|
|
}
|
|
export function optionalZipField() {
|
|
return applyOptionalFieldRules('zipCode')
|
|
}
|
|
|
|
// ─── Email fields ──────────────────────────────────────────────
|
|
|
|
export function emailField() {
|
|
return z
|
|
.string()
|
|
.min(1, { message: 'Email is required' })
|
|
.trim()
|
|
.transform((val: string) => sanitizeAndFormat(val, 'email'))
|
|
.pipe(
|
|
z.string().refine(
|
|
(val: string) => EMAIL_REGEX.test(val),
|
|
{ message: 'Please enter a valid email address' }
|
|
)
|
|
)
|
|
}
|
|
|
|
export function optionalEmailField() {
|
|
return z
|
|
.string()
|
|
.optional()
|
|
.transform((val) => {
|
|
if (val === undefined || val === null || val.trim() === '') return undefined
|
|
return sanitizeAndFormat(val, 'email')
|
|
})
|
|
.pipe(
|
|
z.string().optional().refine(
|
|
(val) => val === undefined || EMAIL_REGEX.test(val),
|
|
{ message: 'Please enter a valid email address' }
|
|
)
|
|
)
|
|
}
|
|
|
|
// ─── Phone fields ──────────────────────────────────────────────
|
|
|
|
export function phoneField() {
|
|
return z
|
|
.string()
|
|
.min(1, { message: 'Phone number is required' })
|
|
.trim()
|
|
.transform((val: string) => sanitizePhone(val))
|
|
.pipe(
|
|
z.string().refine(
|
|
(val: string) => MA_PHONE_REGEX.test(val),
|
|
{ message: 'Please enter a valid Morocco phone number' }
|
|
)
|
|
)
|
|
}
|
|
|
|
export function optionalPhoneField() {
|
|
return z
|
|
.string()
|
|
.optional()
|
|
.transform((val) => {
|
|
if (val === undefined || val === null || val.trim() === '') return undefined
|
|
return sanitizePhone(val)
|
|
})
|
|
.pipe(
|
|
z.string().optional().refine(
|
|
(val) => val === undefined || MA_PHONE_REGEX.test(val),
|
|
{ message: 'Please enter a valid Morocco phone number' }
|
|
)
|
|
)
|
|
}
|