From 4ae4edb033f0b6a51d0a93732eaaef0a3f2214a5 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Sun, 26 Apr 2026 16:19:15 +0200 Subject: [PATCH] feat(ST-352): add claimStoryAction, unclaimStoryAction, reassignStoryAction, claimAllUnassignedInActiveSprintAction Co-Authored-By: Claude Sonnet 4.6 --- actions/stories.ts | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/actions/stories.ts b/actions/stories.ts index efc32d3..1495f4d 100644 --- a/actions/stories.ts +++ b/actions/stories.ts @@ -7,6 +7,7 @@ import { z } from 'zod' import { prisma } from '@/lib/prisma' import { SessionData, sessionOptions } from '@/lib/session' import { getAccessibleProduct, productAccessFilter } from '@/lib/product-access' +import { requireProductWriter } from '@/lib/auth' async function getSession() { return getIronSession(await cookies(), sessionOptions) @@ -208,6 +209,95 @@ export async function getStoryLogsAction(storyId: string) { } } +export async function claimStoryAction(storyId: string, productId: string) { + try { + await requireProductWriter(productId) + } catch (e: unknown) { + return { error: e instanceof Error ? e.message : 'Geen toegang' } + } + + const session = await getSession() + const story = await prisma.story.findFirst({ where: { id: storyId, product_id: productId } }) + if (!story) return { error: 'Story niet gevonden' } + + await prisma.story.update({ where: { id: storyId }, data: { assignee_id: session.userId } }) + + revalidatePath(`/products/${productId}/sprint`) + revalidatePath('/solo') + return { success: true } +} + +export async function unclaimStoryAction(storyId: string, productId: string) { + try { + await requireProductWriter(productId) + } catch (e: unknown) { + return { error: e instanceof Error ? e.message : 'Geen toegang' } + } + + const story = await prisma.story.findFirst({ where: { id: storyId, product_id: productId } }) + if (!story) return { error: 'Story niet gevonden' } + + await prisma.story.update({ where: { id: storyId }, data: { assignee_id: null } }) + + revalidatePath(`/products/${productId}/sprint`) + revalidatePath('/solo') + return { success: true } +} + +export async function reassignStoryAction(storyId: string, productId: string, targetUserId: string) { + try { + await requireProductWriter(productId) + } catch (e: unknown) { + return { error: e instanceof Error ? e.message : 'Geen toegang' } + } + + // Validate target user is owner or member of the product + const product = await prisma.product.findFirst({ + where: { + id: productId, + OR: [ + { user_id: targetUserId }, + { members: { some: { user_id: targetUserId } } }, + ], + }, + }) + if (!product) return { error: 'Gebruiker is geen lid van dit product' } + + const story = await prisma.story.findFirst({ where: { id: storyId, product_id: productId } }) + if (!story) return { error: 'Story niet gevonden' } + + await prisma.story.update({ where: { id: storyId }, data: { assignee_id: targetUserId } }) + + revalidatePath(`/products/${productId}/sprint`) + revalidatePath('/solo') + return { success: true } +} + +export async function claimAllUnassignedInActiveSprintAction(productId: string) { + try { + await requireProductWriter(productId) + } catch (e: unknown) { + return { error: e instanceof Error ? e.message : 'Geen toegang' } + } + + const session = await getSession() + const userId = session.userId + + const sprint = await prisma.sprint.findFirst({ + where: { product_id: productId, status: 'ACTIVE' }, + }) + if (!sprint) return { error: 'Geen actieve sprint gevonden' } + + const result = await prisma.story.updateMany({ + where: { sprint_id: sprint.id, product_id: productId, assignee_id: null }, + data: { assignee_id: userId }, + }) + + revalidatePath(`/products/${productId}/sprint`) + revalidatePath('/solo') + return { success: true, count: result.count } +} + export async function reorderStoriesAction( pbiId: string, productId: string,