chefbible/src/routes/recipe/[id]/edit/+server.ts

185 lines
6.6 KiB
TypeScript

import type { RequestHandler } from './$types';
import prisma from '$lib/server/prisma';
import { v2 as cloudinary } from 'cloudinary';
import { CLOUDINARY_URL } from '$env/static/private';
// Configure Cloudinary using the URL
if (!CLOUDINARY_URL) {
throw new Error('Missing CLOUDINARY_URL environment variable');
}
cloudinary.config({
url: CLOUDINARY_URL
});
export const POST: RequestHandler = async ({ request, params }) => {
const formData = await request.formData();
const name = (formData.get('name') as string | null)?.trim() ?? '';
const description = (formData.get('description') as string | null)?.trim() || null;
const instructions = (formData.get('instructions') as string | null)?.trim() || null;
const time = ((formData.get('time') as string | null)?.trim() || 'Medium') as string;
const station = ((formData.get('station') as string | null)?.trim() || 'Pans') as string;
const photo = formData.get('photo') as File | null;
const parsedIngredientsRaw = formData.get('parsedIngredients') as string | null;
let parsedIngredients: Array<{ name: string; quantity: number | null; unit: string | null; prep: string | null }> = [];
if (!name) {
return new Response(JSON.stringify({ type: 'error', message: 'Name is required' }), { status: 400 });
}
if (parsedIngredientsRaw) {
try {
parsedIngredients = JSON.parse(parsedIngredientsRaw);
} catch (error) {
console.error('Failed to parse ingredients JSON:', error);
return new Response(JSON.stringify({ type: 'error', message: 'Invalid ingredients format' }), { status: 400 });
}
}
let photoUrl: string | null = null;
// Handle photo upload if provided
if (photo && photo.size > 0) {
try {
// Convert File to buffer
const arrayBuffer = await photo.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
// Upload to Cloudinary
const result = await new Promise<{ secure_url: string }>((resolve, reject) => {
cloudinary.uploader.upload_stream(
{
folder: 'chef-bible/recipes',
resource_type: 'auto',
transformation: [
{ width: 800, height: 600, crop: 'limit' },
{ quality: 'auto:good' }
]
},
(error, result) => {
if (error) reject(error);
else if (result) resolve(result as { secure_url: string });
else reject(new Error('No result from Cloudinary'));
}
).end(buffer);
});
photoUrl = result.secure_url;
} catch (error) {
console.error('Failed to upload photo to Cloudinary:', error);
return new Response(JSON.stringify({ type: 'error', message: 'Failed to upload photo' }), { status: 500 });
}
}
// Get existing recipe to check if we need to preserve the existing photo
const existingRecipe = await prisma.recipe.findUnique({
where: { id: params.id }
});
if (!existingRecipe) {
return new Response(JSON.stringify({ type: 'error', message: 'Recipe not found' }), { status: 404 });
}
// Update recipe
const recipe = await prisma.recipe.update({
where: { id: params.id },
data: {
name,
description,
instructions,
photoUrl: photoUrl || existingRecipe.photoUrl, // Keep existing photo if no new one uploaded
time,
station
}
});
// Delete existing recipe ingredients
await prisma.recipeIngredient.deleteMany({
where: { recipeId: params.id }
});
if (parsedIngredients.length > 0) {
// Upsert ingredients first
const ingredientNames = parsedIngredients.map(i => i.name).filter(Boolean);
const existingIngredients = await prisma.ingredient.findMany({
where: { name: { in: ingredientNames } }
});
const existingNames = new Set(existingIngredients.map(i => i.name));
const createIngredients = ingredientNames
.filter(n => !existingNames.has(n))
.map(n => ({ name: n }));
if (createIngredients.length) {
await prisma.ingredient.createMany({
data: createIngredients
});
}
// Get all ingredients with ids
const allIngredients = await prisma.ingredient.findMany({
where: { name: { in: ingredientNames } }
});
const ingredientMap = new Map(allIngredients.map(i => [i.name, i.id]));
// Bulk create recipeIngredient
const recipeIngredientsData = parsedIngredients
.filter(i => i.name)
.map(i => ({
recipeId: recipe.id,
ingredientId: ingredientMap.get(i.name)!,
quantity: i.quantity,
unit: i.unit,
prep: i.prep
}));
if (recipeIngredientsData.length) {
await prisma.recipeIngredient.createMany({
data: recipeIngredientsData
});
}
}
return new Response(JSON.stringify({
type: 'success',
status: 200,
location: `/recipe/${recipe.id}`
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
};
export const DELETE: RequestHandler = async ({ params }) => {
try {
// Check if recipe exists
const existingRecipe = await prisma.recipe.findUnique({
where: { id: params.id }
});
if (!existingRecipe) {
return new Response(JSON.stringify({ type: 'error', message: 'Recipe not found' }), { status: 404 });
}
// Delete recipe ingredients first (due to foreign key constraints)
await prisma.recipeIngredient.deleteMany({
where: { recipeId: params.id }
});
// Delete the recipe
await prisma.recipe.delete({
where: { id: params.id }
});
return new Response(JSON.stringify({
type: 'success',
message: 'Recipe deleted successfully'
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
} catch (error) {
console.error('Error deleting recipe:', error);
return new Response(JSON.stringify({
type: 'error',
message: 'Failed to delete recipe'
}), { status: 500, headers: { 'Content-Type': 'application/json' } });
}
};