test7 / src /lib /validation.ts
simikkk's picture
Upload 86 files
e218aaf verified
Raw
History Blame Contribute Delete
4.44 kB
import { z } from 'zod';
// ─── Auth Schemas ─────────────────────────────────────────────────────────────
export const loginSchema = z.object({
email: z.string().email('Please enter a valid email address'),
password: z.string().min(1, 'Password is required'),
});
export const signupSchema = z
.object({
name: z
.string()
.min(2, 'Name must be at least 2 characters')
.max(100, 'Name must be at most 100 characters'),
email: z.string().email('Please enter a valid email address'),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.refine(
(pwd) => /[A-Z]/.test(pwd),
'Password must contain at least one uppercase letter'
)
.refine(
(pwd) => /[a-z]/.test(pwd),
'Password must contain at least one lowercase letter'
)
.refine(
(pwd) => /[0-9]/.test(pwd),
'Password must contain at least one number'
),
termsAccepted: z.literal(true, {
message: 'You must accept the terms and conditions',
}),
});
export const changePasswordSchema = z
.object({
currentPassword: z.string().min(1, 'Current password is required'),
newPassword: z
.string()
.min(8, 'Password must be at least 8 characters')
.refine(
(pwd) => /[A-Z]/.test(pwd),
'Password must contain at least one uppercase letter'
)
.refine(
(pwd) => /[a-z]/.test(pwd),
'Password must contain at least one lowercase letter'
)
.refine(
(pwd) => /[0-9]/.test(pwd),
'Password must contain at least one number'
),
confirmPassword: z.string().min(1, 'Please confirm your new password'),
})
.refine((data) => data.newPassword === data.confirmPassword, {
message: 'New passwords do not match',
path: ['confirmPassword'],
});
// ─── Upload Validation ────────────────────────────────────────────────────────
const ALLOWED_MIME_TYPES = [
'application/pdf',
'image/jpeg',
'image/png',
'image/tiff',
];
const ALLOWED_EXTENSIONS = ['pdf', 'jpg', 'jpeg', 'png', 'tiff', 'tif'];
const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20 MB
export function validateFile(file: File): string | null {
// Check file size
if (file.size > MAX_FILE_SIZE) {
return `File "${file.name}" exceeds the 20 MB size limit (${(file.size / 1024 / 1024).toFixed(1)} MB)`;
}
// Check MIME type
if (!ALLOWED_MIME_TYPES.includes(file.type)) {
return `File "${file.name}" has unsupported type "${file.type}". Allowed: PDF, JPEG, PNG, TIFF`;
}
// Check extension
const ext = file.name.split('.').pop()?.toLowerCase() ?? '';
if (!ALLOWED_EXTENSIONS.includes(ext)) {
return `File "${file.name}" has unsupported extension ".${ext}". Allowed: .pdf, .jpg, .jpeg, .png, .tiff, .tif`;
}
return null; // No error
}
// ─── Chat Schema ──────────────────────────────────────────────────────────────
export const chatSchema = z.object({
message: z
.string()
.min(1, 'Message cannot be empty')
.max(500, 'Message must be at most 500 characters'),
history: z
.array(
z.object({
role: z.enum(['user', 'assistant', 'system']),
content: z.string(),
})
)
.optional()
.default([]),
});
// ─── Invoice ID Schema ────────────────────────────────────────────────────────
export const invoiceIdSchema = z.object({
id: z.string().min(1, 'Invoice ID is required'),
});
// ─── Helper: Get Client IP ────────────────────────────────────────────────────
export function getClientIp(request: Request): string {
const forwarded = request.headers.get('x-forwarded-for');
if (forwarded) {
return forwarded.split(',')[0].trim();
}
const realIp = request.headers.get('x-real-ip');
if (realIp) return realIp.trim();
return 'unknown';
}