'use server' import { revalidatePath } from 'next/cache' import { cookies } from 'next/headers' import { getIronSession } from 'iron-session' import { z } from 'zod' import type { Prisma } from '@prisma/client' import { prisma } from '@/lib/prisma' import { SessionData, sessionOptions } from '@/lib/session' import { productAccessFilter } from '@/lib/product-access' import { mergeSettings, parseUserSettings, type PendingSprintDraft, type UserSettings, } from '@/lib/user-settings' async function getSession() { return getIronSession(await cookies(), sessionOptions) } const StoryOverridesSchema = z.object({ add: z.array(z.string()), remove: z.array(z.string()), }).strict() const DraftSchema = z.object({ goal: z.string().min(1), startAt: z.string().date().optional(), endAt: z.string().date().optional(), pbiIntent: z.record(z.string(), z.enum(['all', 'none'])).default({}), storyOverrides: z.record(z.string(), StoryOverridesSchema).default({}), }).strict() const SetSchema = z.object({ productId: z.string().min(1), draft: DraftSchema, }) const ClearSchema = z.object({ productId: z.string().min(1), }) async function ensureProductAccess(userId: string, productId: string) { return prisma.product.findFirst({ where: { id: productId, ...productAccessFilter(userId) }, select: { id: true }, }) } async function readUserSettings(userId: string): Promise { const user = await prisma.user.findUnique({ where: { id: userId }, select: { settings: true }, }) return parseUserSettings(user?.settings) } async function writeUserSettings(userId: string, next: UserSettings) { await prisma.user.update({ where: { id: userId }, data: { settings: next as unknown as Prisma.InputJsonValue }, }) } export async function setPendingSprintDraftAction( productId: string, draft: PendingSprintDraft, ) { const session = await getSession() if (!session.userId) return { error: 'Niet ingelogd' } if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' } const parsed = SetSchema.safeParse({ productId, draft }) if (!parsed.success) { return { error: 'Ongeldige draft', issues: parsed.error.issues } } const product = await ensureProductAccess(session.userId, parsed.data.productId) if (!product) return { error: 'Product niet gevonden of niet toegankelijk' } const current = await readUserSettings(session.userId) const patch: Partial = { workflow: { pendingSprintDraft: { ...(current.workflow?.pendingSprintDraft ?? {}), [parsed.data.productId]: parsed.data.draft, }, }, } await writeUserSettings(session.userId, mergeSettings(current, patch)) revalidatePath('/', 'layout') return { success: true } } export async function clearPendingSprintDraftAction(productId: string) { const session = await getSession() if (!session.userId) return { error: 'Niet ingelogd' } if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' } const parsed = ClearSchema.safeParse({ productId }) if (!parsed.success) return { error: 'Ongeldig product-id' } const product = await ensureProductAccess(session.userId, parsed.data.productId) if (!product) return { error: 'Product niet gevonden of niet toegankelijk' } const current = await readUserSettings(session.userId) const existingMap = current.workflow?.pendingSprintDraft if (!existingMap || !(parsed.data.productId in existingMap)) { return { success: true } } const nextMap = { ...existingMap } delete nextMap[parsed.data.productId] const next: UserSettings = { ...current, workflow: { ...current.workflow, pendingSprintDraft: nextMap }, } await writeUserSettings(session.userId, next) revalidatePath('/', 'layout') return { success: true } }