diff --git a/actions/pbis.ts b/actions/pbis.ts index d98b85b..ec09e07 100644 --- a/actions/pbis.ts +++ b/actions/pbis.ts @@ -7,7 +7,8 @@ import { z } from 'zod' import { prisma } from '@/lib/prisma' import { SessionData, sessionOptions } from '@/lib/session' import { getAccessibleProduct } from '@/lib/product-access' -import { generateNextPbiCode, isValidCode, MAX_CODE_LENGTH, normalizeCode } from '@/lib/code' +import { isValidCode, MAX_CODE_LENGTH, normalizeCode } from '@/lib/code' +import { generateNextPbiCode } from '@/lib/code-server' async function getSession() { return getIronSession(await cookies(), sessionOptions) diff --git a/actions/stories.ts b/actions/stories.ts index 35acb1b..32f1751 100644 --- a/actions/stories.ts +++ b/actions/stories.ts @@ -8,7 +8,8 @@ import { prisma } from '@/lib/prisma' import { SessionData, sessionOptions } from '@/lib/session' import { getAccessibleProduct, productAccessFilter } from '@/lib/product-access' import { requireProductWriter } from '@/lib/auth' -import { generateNextStoryCode, isValidCode, MAX_CODE_LENGTH, normalizeCode } from '@/lib/code' +import { isValidCode, MAX_CODE_LENGTH, normalizeCode } from '@/lib/code' +import { generateNextStoryCode } from '@/lib/code-server' async function getSession() { return getIronSession(await cookies(), sessionOptions) diff --git a/lib/code-server.ts b/lib/code-server.ts new file mode 100644 index 0000000..f78f4fa --- /dev/null +++ b/lib/code-server.ts @@ -0,0 +1,35 @@ +import { prisma } from '@/lib/prisma' + +const STORY_AUTO_RE = /^ST-(\d+)$/ +const PBI_AUTO_RE = /^PBI-(\d+)$/ + +function nextSequential(existing: (string | null)[], pattern: RegExp): number { + let max = 0 + for (const c of existing) { + if (!c) continue + const m = c.match(pattern) + if (m) { + const n = Number.parseInt(m[1], 10) + if (!Number.isNaN(n) && n > max) max = n + } + } + return max + 1 +} + +export async function generateNextStoryCode(productId: string): Promise { + const stories = await prisma.story.findMany({ + where: { product_id: productId }, + select: { code: true }, + }) + const next = nextSequential(stories.map((s) => s.code), STORY_AUTO_RE) + return `ST-${String(next).padStart(3, '0')}` +} + +export async function generateNextPbiCode(productId: string): Promise { + const pbis = await prisma.pbi.findMany({ + where: { product_id: productId }, + select: { code: true }, + }) + const next = nextSequential(pbis.map((p) => p.code), PBI_AUTO_RE) + return `PBI-${next}` +} diff --git a/lib/code.ts b/lib/code.ts index 6f1fb8c..1ce387a 100644 --- a/lib/code.ts +++ b/lib/code.ts @@ -1,7 +1,5 @@ -import { prisma } from '@/lib/prisma' - -const STORY_AUTO_RE = /^ST-(\d+)$/ -const PBI_AUTO_RE = /^PBI-(\d+)$/ +// Pure helpers — safe to import from client components. +// DB-backed helpers (generateNextStoryCode/PbiCode) live in lib/code-server.ts. const VALID_CODE_RE = /^[A-Za-z0-9._-]+$/ @@ -17,37 +15,6 @@ export function normalizeCode(input: string | null | undefined): string | null { return trimmed === '' ? null : trimmed } -function nextSequential(existing: (string | null)[], pattern: RegExp): number { - let max = 0 - for (const c of existing) { - if (!c) continue - const m = c.match(pattern) - if (m) { - const n = Number.parseInt(m[1], 10) - if (!Number.isNaN(n) && n > max) max = n - } - } - return max + 1 -} - -export async function generateNextStoryCode(productId: string): Promise { - const stories = await prisma.story.findMany({ - where: { product_id: productId }, - select: { code: true }, - }) - const next = nextSequential(stories.map((s) => s.code), STORY_AUTO_RE) - return `ST-${String(next).padStart(3, '0')}` -} - -export async function generateNextPbiCode(productId: string): Promise { - const pbis = await prisma.pbi.findMany({ - where: { product_id: productId }, - select: { code: true }, - }) - const next = nextSequential(pbis.map((p) => p.code), PBI_AUTO_RE) - return `PBI-${next}` -} - export function deriveTaskCode(storyCode: string | null, indexOneBased: number): string | null { if (!storyCode) return null return `${storyCode}.${indexOneBased}`