diff --git a/apps/web/app/(app)/recipes/[id]/page.tsx b/apps/web/app/(app)/recipes/[id]/page.tsx index 5834f66..f6a3271 100644 --- a/apps/web/app/(app)/recipes/[id]/page.tsx +++ b/apps/web/app/(app)/recipes/[id]/page.tsx @@ -339,6 +339,7 @@ export default async function RecipePage({ params }: Params) {
{recipe.title} {`${recipe.title} }; @@ -70,7 +74,11 @@ export async function PUT(req: NextRequest, { params }: Params) { const existing = await db.query.recipes.findFirst({ where: and(eq(recipes.id, id), eq(recipes.authorId, session!.user.id)), - with: { ingredients: { orderBy: (t, { asc }) => asc(t.order) }, steps: { orderBy: (t, { asc }) => asc(t.order) } }, + with: { + ingredients: { orderBy: (t, { asc }) => asc(t.order) }, + steps: { orderBy: (t, { asc }) => asc(t.order) }, + photos: true, + }, }); if (!existing) return NextResponse.json({ error: "Not found" }, { status: 404 }); @@ -162,8 +170,29 @@ export async function PUT(req: NextRequest, { params }: Params) { ); } } + + if (data.photos !== undefined) { + await tx.delete(recipePhotos).where(eq(recipePhotos.recipeId, id)); + if (data.photos.length > 0) { + await tx.insert(recipePhotos).values( + data.photos.map((photo, i) => ({ + id: crypto.randomUUID(), + recipeId: id, + storageKey: photo.key, + order: i, + isCover: photo.isCover, + })) + ); + } + } }); + if (data.photos !== undefined) { + const newKeys = new Set(data.photos.map((p) => p.key)); + const removedKeys = existing.photos.filter((p) => !newKeys.has(p.storageKey)).map((p) => p.storageKey); + await Promise.all(removedKeys.map((key) => deleteObject(key).catch(() => {}))); + } + const updated = await getOwnedRecipe(id, session!.user.id); void dispatchWebhook(session!.user.id, "recipe.updated", { id, title: updated?.title }); if (existing.visibility === "private" && data.visibility === "public") { diff --git a/apps/web/app/api/v1/recipes/route.ts b/apps/web/app/api/v1/recipes/route.ts index 0465ef3..b79d8f9 100644 --- a/apps/web/app/api/v1/recipes/route.ts +++ b/apps/web/app/api/v1/recipes/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { db, recipes, recipeIngredients, recipeSteps } from "@epicure/db"; +import { db, recipes, recipeIngredients, recipeSteps, recipePhotos } from "@epicure/db"; import { eq, desc, and } from "@epicure/db"; import { z } from "zod"; import { requireSessionOrApiKey } from "@/lib/api-auth"; @@ -43,6 +43,10 @@ const CreateRecipeSchema = z.object({ timerSeconds: z.number().int().min(0).max(86400).optional(), order: z.number().int().optional(), })).max(100).default([]), + photos: z.array(z.object({ + key: z.string().min(1).max(500), + isCover: z.boolean().default(false), + })).max(20).default([]), }); export async function GET(req: NextRequest) { @@ -135,6 +139,18 @@ export async function POST(req: NextRequest) { })) ); } + + if (data.photos.length > 0) { + await tx.insert(recipePhotos).values( + data.photos.map((photo, i) => ({ + id: crypto.randomUUID(), + recipeId: id, + storageKey: photo.key, + order: i, + isCover: photo.isCover, + })) + ); + } }); const recipe = await db.query.recipes.findFirst({ where: eq(recipes.id, id) }); diff --git a/apps/web/app/r/[id]/page.tsx b/apps/web/app/r/[id]/page.tsx index 08b3a0a..06248b4 100644 --- a/apps/web/app/r/[id]/page.tsx +++ b/apps/web/app/r/[id]/page.tsx @@ -127,7 +127,7 @@ export default async function PublicRecipePage({ params }: Params) { {cover && (
- {recipe.title} + {recipe.title}
)} diff --git a/apps/web/components/profile/profile-tabs.tsx b/apps/web/components/profile/profile-tabs.tsx index dfd09b7..b5131dd 100644 --- a/apps/web/components/profile/profile-tabs.tsx +++ b/apps/web/components/profile/profile-tabs.tsx @@ -147,6 +147,7 @@ export function ProfileTabs({
{photo.recipeTitle} Recipe photo {recipe.title} ({ key: p.key, isCover: p.isCover })), }; const url = isEdit ? `/api/v1/recipes/${id}` : "/api/v1/recipes"; diff --git a/apps/web/components/recipe/recipes-grid.tsx b/apps/web/components/recipe/recipes-grid.tsx index 496a54e..f697b8d 100644 --- a/apps/web/components/recipe/recipes-grid.tsx +++ b/apps/web/components/recipe/recipes-grid.tsx @@ -70,6 +70,7 @@ function RecipeThumb({ recipe, selectMode, selected, className }: { recipe: Reci {cover ? ( {recipe.title} Your cooked-it photo