diff --git a/supabase/functions/process-selective-approval/validation.ts b/supabase/functions/process-selective-approval/validation.ts index 55bf33c7..15dadabc 100644 --- a/supabase/functions/process-selective-approval/validation.ts +++ b/supabase/functions/process-selective-approval/validation.ts @@ -160,12 +160,25 @@ export function validateEntityDataStrict( if (!data.cloudflare_image_id) { result.blockingErrors.push('Image ID is required'); } + // Validate Cloudflare image ID format (standard UUID format) + if (data.cloudflare_image_id && !/^[a-zA-Z0-9-]{36}$/.test(data.cloudflare_image_id)) { + result.blockingErrors.push('Invalid Cloudflare image ID format'); + } if (!data.entity_type) { result.blockingErrors.push('Entity type is required'); } + // Validate entity type is one of the allowed types + const validPhotoEntityTypes = ['park', 'ride', 'company', 'ride_model']; + if (data.entity_type && !validPhotoEntityTypes.includes(data.entity_type)) { + result.blockingErrors.push(`Invalid entity type. Must be one of: ${validPhotoEntityTypes.join(', ')}`); + } if (!data.entity_id) { result.blockingErrors.push('Entity ID is required'); } + // Validate entity_id is a valid UUID + if (data.entity_id && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(data.entity_id)) { + result.blockingErrors.push('Entity ID must be a valid UUID'); + } if (data.caption && data.caption.length > 500) { result.blockingErrors.push('Caption must be less than 500 characters'); } @@ -218,12 +231,42 @@ export function validateEntityDataStrict( if (!data.event_date) { result.blockingErrors.push('Event date is required'); } + // Validate event date is not too far in the future (max 5 years) + if (data.event_date) { + const eventDate = new Date(data.event_date); + const maxFutureDate = new Date(); + maxFutureDate.setFullYear(maxFutureDate.getFullYear() + 5); + if (eventDate > maxFutureDate) { + result.blockingErrors.push('Event date cannot be more than 5 years in the future'); + } + // Also validate it's not absurdly old (before 1800) + const minDate = new Date('1800-01-01'); + if (eventDate < minDate) { + result.blockingErrors.push('Event date cannot be before year 1800'); + } + } + // For change events (name_change, location_change, status_change), require from/to values + const changeEventTypes = ['name_change', 'location_change', 'status_change', 'ownership_change']; + if (data.event_type && changeEventTypes.includes(data.event_type)) { + if (!data.from_value && !data.to_value) { + result.blockingErrors.push(`Change event (${data.event_type}) requires at least one of from_value or to_value`); + } + } if (!data.entity_type) { result.blockingErrors.push('Entity type is required'); } + // Validate entity type is one of the allowed types + const validTimelineEntityTypes = ['park', 'ride', 'company', 'ride_model']; + if (data.entity_type && !validTimelineEntityTypes.includes(data.entity_type)) { + result.blockingErrors.push(`Invalid entity type. Must be one of: ${validTimelineEntityTypes.join(', ')}`); + } if (!data.entity_id) { result.blockingErrors.push('Entity ID is required'); } + // Validate entity_id is a valid UUID + if (data.entity_id && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(data.entity_id)) { + result.blockingErrors.push('Entity ID must be a valid UUID'); + } break; }