From 08e0dd22f07ae9c24b89c3537cae917fb7234cef Mon Sep 17 00:00:00 2001 From: Janpeter Visser Date: Fri, 24 Apr 2026 22:07:45 +0200 Subject: [PATCH] Add server action pattern documentation --- patterns/server-action.md | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 patterns/server-action.md diff --git a/patterns/server-action.md b/patterns/server-action.md new file mode 100644 index 0000000..f076dff --- /dev/null +++ b/patterns/server-action.md @@ -0,0 +1,53 @@ +# Patroon: Server Action + +Altijd in `actions/[domein].ts`. Nooit inline in page.tsx. + +```ts +'use server' + +import { revalidatePath } from 'next/cache' +import { getIronSession } from 'iron-session' +import { cookies } from 'next/headers' +import { z } from 'zod' +import { prisma } from '@/lib/prisma' +import { SessionData, sessionOptions } from '@/lib/session' + +const schema = z.object({ + productId: z.string().cuid(), + title: z.string().min(1).max(200), + priority: z.number().int().min(1).max(4), +}) + +export async function createPbi(formData: FormData) { + // 1. Auth + const session = await getIronSession(await cookies(), sessionOptions) + if (!session.userId) return { error: 'Niet ingelogd' } + if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' } + + // 2. Validatie + const parsed = schema.safeParse({ + productId: formData.get('productId'), + title: formData.get('title'), + priority: Number(formData.get('priority')), + }) + if (!parsed.success) return { error: parsed.error.flatten().fieldErrors } + + // 3. Eigenaarschap controleren + const product = await prisma.product.findFirst({ + where: { id: parsed.data.productId, user_id: session.userId } + }) + if (!product) return { error: 'Product niet gevonden' } + + // 4. Schrijven + const last = await prisma.pbi.findFirst({ + where: { product_id: parsed.data.productId, priority: parsed.data.priority }, + orderBy: { sort_order: 'desc' }, + }) + const pbi = await prisma.pbi.create({ + data: { ...parsed.data, product_id: parsed.data.productId, sort_order: (last?.sort_order ?? 0) + 1.0 }, + }) + + revalidatePath(`/products/${parsed.data.productId}`) + return { success: true, pbi } +} +```