Files
thrilltrack-explorer/src-old/lib/submissionValidation.ts

208 lines
5.4 KiB
TypeScript

/**
* Client-side validation for entity submissions
* Prevents missing required fields before database calls
*/
export interface ValidationResult {
valid: boolean;
missingFields: string[];
errorMessage?: string;
}
export interface SlugValidationResult extends ValidationResult {
suggestedSlug?: string;
}
/**
* Validates slug format matching database constraints
* Pattern: lowercase alphanumeric with hyphens only
* No consecutive hyphens, no leading/trailing hyphens
*/
export function validateSlugFormat(slug: string): SlugValidationResult {
if (!slug) {
return {
valid: false,
missingFields: ['slug'],
errorMessage: 'Slug is required'
};
}
// Must match DB regex: ^[a-z0-9]+(-[a-z0-9]+)*$
const slugRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/;
if (!slugRegex.test(slug)) {
const suggested = slug
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
return {
valid: false,
missingFields: ['slug'],
errorMessage: 'Slug must be lowercase alphanumeric with hyphens only (no spaces or special characters)',
suggestedSlug: suggested
};
}
// Length constraints
if (slug.length < 2) {
return {
valid: false,
missingFields: ['slug'],
errorMessage: 'Slug too short (minimum 2 characters)'
};
}
if (slug.length > 100) {
return {
valid: false,
missingFields: ['slug'],
errorMessage: 'Slug too long (maximum 100 characters)'
};
}
// Reserved slugs that could conflict with routes
const reserved = [
'admin', 'api', 'auth', 'new', 'edit', 'delete', 'create',
'update', 'null', 'undefined', 'settings', 'profile', 'login',
'logout', 'signup', 'dashboard', 'moderator', 'moderation'
];
if (reserved.includes(slug)) {
return {
valid: false,
missingFields: ['slug'],
errorMessage: `'${slug}' is a reserved slug and cannot be used`,
suggestedSlug: `${slug}-1`
};
}
return { valid: true, missingFields: [] };
}
/**
* Validates required fields for park creation
*/
export function validateParkCreateFields(data: any): ValidationResult {
const missingFields: string[] = [];
if (!data.name?.trim()) missingFields.push('name');
if (!data.slug?.trim()) missingFields.push('slug');
if (!data.park_type) missingFields.push('park_type');
if (!data.status) missingFields.push('status');
if (missingFields.length > 0) {
return {
valid: false,
missingFields,
errorMessage: `Missing required fields for park creation: ${missingFields.join(', ')}`
};
}
// Validate slug format
if (data.slug?.trim()) {
const slugValidation = validateSlugFormat(data.slug.trim());
if (!slugValidation.valid) {
return slugValidation;
}
}
return { valid: true, missingFields: [] };
}
/**
* Validates required fields for ride creation
*/
export function validateRideCreateFields(data: any): ValidationResult {
const missingFields: string[] = [];
if (!data.name?.trim()) missingFields.push('name');
if (!data.slug?.trim()) missingFields.push('slug');
if (!data.category) missingFields.push('category');
if (!data.status) missingFields.push('status');
if (missingFields.length > 0) {
return {
valid: false,
missingFields,
errorMessage: `Missing required fields for ride creation: ${missingFields.join(', ')}`
};
}
// Validate slug format
if (data.slug?.trim()) {
const slugValidation = validateSlugFormat(data.slug.trim());
if (!slugValidation.valid) {
return slugValidation;
}
}
return { valid: true, missingFields: [] };
}
/**
* Validates required fields for company creation
*/
export function validateCompanyCreateFields(data: any): ValidationResult {
const missingFields: string[] = [];
if (!data.name?.trim()) missingFields.push('name');
if (!data.slug?.trim()) missingFields.push('slug');
if (!data.company_type) missingFields.push('company_type');
if (missingFields.length > 0) {
return {
valid: false,
missingFields,
errorMessage: `Missing required fields for company creation: ${missingFields.join(', ')}`
};
}
// Validate slug format
if (data.slug?.trim()) {
const slugValidation = validateSlugFormat(data.slug.trim());
if (!slugValidation.valid) {
return slugValidation;
}
}
return { valid: true, missingFields: [] };
}
/**
* Validates required fields for ride model creation
*/
export function validateRideModelCreateFields(data: any): ValidationResult {
const missingFields: string[] = [];
if (!data.name?.trim()) missingFields.push('name');
if (!data.slug?.trim()) missingFields.push('slug');
if (!data.manufacturer_id) missingFields.push('manufacturer_id');
if (!data.category) missingFields.push('category');
if (missingFields.length > 0) {
return {
valid: false,
missingFields,
errorMessage: `Missing required fields for ride model creation: ${missingFields.join(', ')}`
};
}
// Validate slug format
if (data.slug?.trim()) {
const slugValidation = validateSlugFormat(data.slug.trim());
if (!slugValidation.valid) {
return slugValidation;
}
}
return { valid: true, missingFields: [] };
}
/**
* Helper to throw validation error if validation fails
*/
export function assertValid(result: ValidationResult): void {
if (!result.valid) {
throw new Error(result.errorMessage);
}
}