'use server' import { revalidatePath } from 'next/cache' import { cookies } from 'next/headers' import { getIronSession } from 'iron-session' import { z } from 'zod' import { prisma } from '@/lib/prisma' import { SessionData, sessionOptions } from '@/lib/session' import { getAccessibleProduct } from '@/lib/product-access' import { isValidCode, MAX_CODE_LENGTH, normalizeCode } from '@/lib/code' import { generateNextPbiCode } from '@/lib/code-server' async function getSession() { return getIronSession(await cookies(), sessionOptions) } const codeField = z.string().max(MAX_CODE_LENGTH).optional() const createPbiSchema = z.object({ productId: z.string(), code: codeField, title: z.string().min(1, 'Titel is verplicht').max(200), description: z.string().max(2000).optional(), priority: z.coerce.number().int().min(1).max(4), }) const updatePbiSchema = z.object({ id: z.string(), code: codeField, title: z.string().min(1, 'Titel is verplicht').max(200), description: z.string().max(2000).optional(), priority: z.coerce.number().int().min(1).max(4), }) export async function createPbiAction(_prevState: unknown, formData: FormData) { const session = await getSession() if (!session.userId) return { error: 'Niet ingelogd' } if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' } const parsed = createPbiSchema.safeParse({ productId: formData.get('productId'), code: (formData.get('code') as string) || undefined, title: formData.get('title'), description: formData.get('description') || undefined, priority: formData.get('priority'), }) if (!parsed.success) return { error: parsed.error.flatten().fieldErrors } const product = await getAccessibleProduct(parsed.data.productId, session.userId) if (!product) return { error: 'Product niet gevonden' } let code = normalizeCode(parsed.data.code) if (code !== null && !isValidCode(code)) { return { error: { code: ['Code mag alleen letters, cijfers, punten, koppeltekens of underscores bevatten'] } } } if (code === null) { code = await generateNextPbiCode(parsed.data.productId) } else { const dup = await prisma.pbi.findFirst({ where: { product_id: parsed.data.productId, code } }) if (dup) return { error: { code: ['Deze code is al in gebruik binnen dit product'] } } } const last = await prisma.pbi.findFirst({ where: { product_id: parsed.data.productId, priority: parsed.data.priority }, orderBy: { sort_order: 'desc' }, }) const sort_order = (last?.sort_order ?? 0) + 1.0 const pbi = await prisma.pbi.create({ data: { product_id: parsed.data.productId, code, title: parsed.data.title, description: parsed.data.description ?? null, priority: parsed.data.priority, sort_order, }, }) revalidatePath(`/products/${parsed.data.productId}`) return { success: true, pbi } } export async function updatePbiAction(_prevState: unknown, formData: FormData) { const session = await getSession() if (!session.userId) return { error: 'Niet ingelogd' } if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' } const parsed = updatePbiSchema.safeParse({ id: formData.get('id'), code: (formData.get('code') as string) || undefined, title: formData.get('title'), description: formData.get('description') || undefined, priority: formData.get('priority'), }) if (!parsed.success) return { error: parsed.error.flatten().fieldErrors } const pbi = await prisma.pbi.findFirst({ where: { id: parsed.data.id }, include: { product: true }, }) if (!pbi) return { error: 'PBI niet gevonden' } const accessible = await getAccessibleProduct(pbi.product_id, session.userId) if (!accessible) return { error: 'PBI niet gevonden' } const code = normalizeCode(parsed.data.code) if (code !== null && !isValidCode(code)) { return { error: { code: ['Code mag alleen letters, cijfers, punten, koppeltekens of underscores bevatten'] } } } if (code) { const dup = await prisma.pbi.findFirst({ where: { product_id: pbi.product_id, code, NOT: { id: parsed.data.id } }, }) if (dup) return { error: { code: ['Deze code is al in gebruik binnen dit product'] } } } await prisma.pbi.update({ where: { id: parsed.data.id }, data: { code, title: parsed.data.title, description: parsed.data.description ?? null, priority: parsed.data.priority, }, }) revalidatePath(`/products/${pbi.product_id}`) return { success: true } } export async function deletePbiAction(id: string) { const session = await getSession() if (!session.userId) return { error: 'Niet ingelogd' } if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' } const pbi = await prisma.pbi.findFirst({ where: { id }, include: { product: true }, }) if (!pbi) return { error: 'PBI niet gevonden' } const accessible = await getAccessibleProduct(pbi.product_id, session.userId) if (!accessible) return { error: 'PBI niet gevonden' } await prisma.pbi.delete({ where: { id } }) revalidatePath(`/products/${pbi.product_id}`) return { success: true } }