mirror of
https://github.com/taogaetz/chefbible.git
synced 2025-12-06 11:47:24 -05:00
185 lines
6.6 KiB
TypeScript
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' } });
|
|
}
|
|
};
|