/** * Photo Helpers * Utilities for normalizing and validating photo data from different sources */ import type { PhotoItem, NormalizedPhoto, PhotoDataSource } from '@/types/photos'; import type { PhotoSubmissionItem } from '@/types/photo-submissions'; /** * Type guard: Check if data is a photo submission item */ export function isPhotoSubmissionItem(data: any): data is PhotoSubmissionItem { return ( data && typeof data === 'object' && 'cloudflare_image_id' in data && 'cloudflare_image_url' in data && 'order_index' in data ); } /** * Type guard: Check if content is a review with photos */ export function isReviewWithPhotos(content: any): boolean { return ( content && typeof content === 'object' && Array.isArray(content.photos) && content.photos.length > 0 && content.photos[0]?.url ); } /** * Normalize photo data from any source to PhotoItem[] */ export function normalizePhotoData(source: PhotoDataSource): PhotoItem[] { switch (source.type) { case 'review': return source.photos.map((photo, index) => ({ id: `review-${index}`, url: photo.url, filename: photo.filename || `Review photo ${index + 1}`, caption: photo.caption, size: photo.size, type: photo.type, })); case 'submission_jsonb': return source.photos.map((photo, index) => ({ id: `jsonb-${index}`, url: photo.url, filename: photo.filename || `Photo ${index + 1}`, caption: photo.caption, title: photo.title, size: photo.size, type: photo.type, })); case 'submission_items': return source.items.map((item) => ({ id: item.id, url: item.cloudflare_image_url, filename: item.filename || `Photo ${item.order_index + 1}`, caption: item.caption, title: item.title, date_taken: item.date_taken, })); default: return []; } } /** * Convert PhotoSubmissionItem[] to NormalizedPhoto[] */ export function normalizePhotoSubmissionItems( items: PhotoSubmissionItem[] ): NormalizedPhoto[] { return items.map((item) => ({ id: item.id, url: item.cloudflare_image_url, filename: item.filename || `Photo ${item.order_index + 1}`, caption: item.caption || undefined, title: item.title || undefined, date_taken: item.date_taken || undefined, order_index: item.order_index, })); } /** * Validate photo URL is from Cloudflare Images * Supports both old imagedelivery.net and new CDN URLs */ export function isValidCloudflareUrl(url: string): boolean { try { const urlObj = new URL(url); return urlObj.hostname.includes('imagedelivery.net') || urlObj.hostname === 'cdn.thrillwiki.com'; } catch { return false; } } /** * Generate photo alt text from available metadata */ export function generatePhotoAlt(photo: PhotoItem | NormalizedPhoto): string { if (photo.title) return photo.title; if (photo.caption) return photo.caption; return photo.filename || 'Photo'; }