feat: Implement photo processing logic

This commit is contained in:
gpt-engineer-app[bot]
2025-09-30 15:40:49 +00:00
parent 14c399f293
commit 7bbf67156b
5 changed files with 702 additions and 53 deletions

View File

@@ -186,7 +186,7 @@ export async function approveSubmissionItems(
entityId = await createRideModel(item.item_data, dependencyMap);
break;
case 'photo':
entityId = await approvePhotos(item.item_data, dependencyMap);
entityId = await approvePhotos(item.item_data, dependencyMap, userId, item.submission_id);
break;
}
@@ -396,18 +396,157 @@ async function createRideModel(data: any, dependencyMap: Map<string, string>): P
return model.id;
}
async function approvePhotos(data: any, dependencyMap: Map<string, string>): Promise<string> {
async function approvePhotos(data: any, dependencyMap: Map<string, string>, userId: string, submissionId: string): Promise<string> {
// Photos are already uploaded to Cloudflare
// Resolve dependencies for entity associations
const resolvedData = resolveDependencies(data, dependencyMap);
// For now, return the first photo URL
// In the future, this could create photo records in a dedicated table
if (resolvedData.photos && Array.isArray(resolvedData.photos) && resolvedData.photos.length > 0) {
return resolvedData.photos[0].url;
if (!resolvedData.photos || !Array.isArray(resolvedData.photos) || resolvedData.photos.length === 0) {
throw new Error('No photos found in submission');
}
const { entity_id, context, park_id, ride_id, company_id } = resolvedData;
return '';
// Determine entity_id and entity_type
let finalEntityId = entity_id;
let entityType = context;
// Support legacy field names
if (!finalEntityId) {
if (park_id) {
finalEntityId = park_id;
entityType = 'park';
} else if (ride_id) {
finalEntityId = ride_id;
entityType = 'ride';
} else if (company_id) {
finalEntityId = company_id;
// Need to determine company type from database
}
}
if (!finalEntityId || !entityType) {
throw new Error('Missing entity_id or context in photo submission');
}
// Insert photos into the photos table
const photosToInsert = resolvedData.photos.map((photo: any, index: number) => {
// Extract CloudFlare image ID from URL if not provided
let cloudflareImageId = photo.cloudflare_image_id;
if (!cloudflareImageId && photo.url) {
// URL format: https://imagedelivery.net/{account_hash}/{image_id}/{variant}
const urlParts = photo.url.split('/');
cloudflareImageId = urlParts[urlParts.length - 2];
}
return {
cloudflare_image_id: cloudflareImageId,
cloudflare_image_url: photo.url,
entity_type: entityType,
entity_id: finalEntityId,
title: photo.title || resolvedData.title,
caption: photo.caption,
photographer_credit: photo.photographer_credit,
date_taken: photo.date || photo.date_taken,
order_index: photo.order !== undefined ? photo.order : index,
is_featured: index === 0, // First photo is featured by default
submission_id: submissionId,
submitted_by: userId,
approved_by: userId,
approved_at: new Date().toISOString(),
};
});
const { data: insertedPhotos, error } = await supabase
.from('photos')
.insert(photosToInsert)
.select();
if (error) {
console.error('Error inserting photos:', error);
throw new Error(`Database error: ${error.message}`);
}
// Update entity's featured image if this is the first photo
if (insertedPhotos && insertedPhotos.length > 0) {
const firstPhoto = insertedPhotos[0];
await updateEntityFeaturedImage(
entityType,
finalEntityId,
firstPhoto.cloudflare_image_url,
firstPhoto.cloudflare_image_id
);
}
// Return the first photo URL for backwards compatibility
return resolvedData.photos[0].url;
}
/**
* Update entity's featured image fields
*/
async function updateEntityFeaturedImage(
entityType: string,
entityId: string,
imageUrl: string,
imageId: string
): Promise<void> {
try {
// Update based on entity type
if (entityType === 'park') {
const { data: existingPark } = await supabase
.from('parks')
.select('card_image_url')
.eq('id', entityId)
.single();
if (existingPark && !existingPark.card_image_url) {
await supabase
.from('parks')
.update({
card_image_url: imageUrl,
card_image_id: imageId,
updated_at: new Date().toISOString(),
})
.eq('id', entityId);
}
} else if (entityType === 'ride') {
const { data: existingRide } = await supabase
.from('rides')
.select('card_image_url')
.eq('id', entityId)
.single();
if (existingRide && !existingRide.card_image_url) {
await supabase
.from('rides')
.update({
card_image_url: imageUrl,
card_image_id: imageId,
updated_at: new Date().toISOString(),
})
.eq('id', entityId);
}
} else if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(entityType)) {
const { data: existingCompany } = await supabase
.from('companies')
.select('logo_url')
.eq('id', entityId)
.single();
if (existingCompany && !existingCompany.logo_url) {
await supabase
.from('companies')
.update({
logo_url: imageUrl,
updated_at: new Date().toISOString(),
})
.eq('id', entityId);
}
}
} catch (error) {
console.error(`Error updating ${entityType} featured image:`, error);
}
}
/**