From b9e6e725b67c4bf4341a98e35dd168f2a7b0b2e2 Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Tue, 5 May 2026 14:54:08 +0200 Subject: [PATCH] feat(ST-abeu63oz): admin products-actions (create, update, archive, delete, addMember, removeMember) - Zod-schema adminProductSchema (name, description, repo_url, definition_of_done, auto_pr, owner_user_id) - adminCreateProductAction: owner-validatie, prisma.product.create - adminUpdateProductAction: zelfde schema zonder owner_user_id - adminArchiveProductAction: toggle archived-vlag - adminDeleteProductAction: hard delete (cascade) - adminAddMemberAction: upsert ProductMember - adminRemoveMemberAction: deleteMany ProductMember --- actions/admin/products.ts | 86 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 actions/admin/products.ts diff --git a/actions/admin/products.ts b/actions/admin/products.ts new file mode 100644 index 0000000..b6f4ad0 --- /dev/null +++ b/actions/admin/products.ts @@ -0,0 +1,86 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { z } from 'zod' +import { prisma } from '@/lib/prisma' +import { requireAdmin } from '@/lib/auth-guard' + +const adminProductSchema = z.object({ + name: z.string().min(1).max(100), + description: z.string().optional(), + repo_url: z.string().url().optional().or(z.literal('')), + definition_of_done: z.string().min(1), + auto_pr: z.boolean().default(false), + owner_user_id: z.string().cuid(), +}) + +const adminProductUpdateSchema = adminProductSchema.omit({ owner_user_id: true }) + +export async function adminCreateProductAction(data: unknown) { + await requireAdmin() + + const parsed = adminProductSchema.safeParse(data) + if (!parsed.success) throw new Error(parsed.error.message) + + const owner = await prisma.user.findUnique({ where: { id: parsed.data.owner_user_id } }) + if (!owner) throw new Error('Eigenaar niet gevonden') + + await prisma.product.create({ + data: { + user_id: parsed.data.owner_user_id, + name: parsed.data.name, + description: parsed.data.description, + repo_url: parsed.data.repo_url || null, + definition_of_done: parsed.data.definition_of_done, + auto_pr: parsed.data.auto_pr, + }, + }) + revalidatePath('/admin/products') +} + +export async function adminUpdateProductAction(productId: string, data: unknown) { + await requireAdmin() + + const parsed = adminProductUpdateSchema.safeParse(data) + if (!parsed.success) throw new Error(parsed.error.message) + + await prisma.product.update({ + where: { id: productId }, + data: { + name: parsed.data.name, + description: parsed.data.description, + repo_url: parsed.data.repo_url || null, + definition_of_done: parsed.data.definition_of_done, + auto_pr: parsed.data.auto_pr, + }, + }) + revalidatePath('/admin/products') +} + +export async function adminArchiveProductAction(productId: string, archived: boolean) { + await requireAdmin() + await prisma.product.update({ where: { id: productId }, data: { archived } }) + revalidatePath('/admin/products') +} + +export async function adminDeleteProductAction(productId: string) { + await requireAdmin() + await prisma.product.delete({ where: { id: productId } }) + revalidatePath('/admin/products') +} + +export async function adminAddMemberAction(productId: string, userId: string) { + await requireAdmin() + await prisma.productMember.upsert({ + where: { product_id_user_id: { product_id: productId, user_id: userId } }, + create: { product_id: productId, user_id: userId }, + update: {}, + }) + revalidatePath(`/admin/products/${productId}`) +} + +export async function adminRemoveMemberAction(productId: string, userId: string) { + await requireAdmin() + await prisma.productMember.deleteMany({ where: { product_id: productId, user_id: userId } }) + revalidatePath(`/admin/products/${productId}`) +}