// Atomic per-user idea-code generator (DB-side). // Schema: User.idea_code_counter Int @default(0) — increment-and-return via // Prisma `update` (which acquires a row-lock for the duration of the // transaction; concurrent calls serialize). Format: "IDEA-001", "IDEA-002", … // // Concurrency: vertrouwt op Postgres row-locking binnen Prisma `update`. // Geen aparte $transaction nodig voor enkelvoudige update — de update is // atomisch op één rij. Voor combineren met een idea.create wordt // nextIdeaCode aangeroepen binnen de bredere $transaction van de caller. // // Self-correcting: na de increment wordt de numerieke MAX van bestaande codes // opgevraagd via raw SQL (geen string-vergelijking — die faalt boven IDEA-999). // Als de counter achterloopt (bijv. na directe DB-inserts tijdens development) // wordt nextN = MAX+1 gebruikt en de counter direct bijgewerkt. import { prisma } from '@/lib/prisma' import { formatIdeaCode } from '@/lib/idea-code' import type { Prisma } from '@prisma/client' export async function nextIdeaCode( userId: string, client: Prisma.TransactionClient | typeof prisma = prisma, ): Promise { // Increment counter — acquires Postgres row lock, serializes concurrent calls. const u = await client.user.update({ where: { id: userId }, data: { idea_code_counter: { increment: 1 } }, select: { idea_code_counter: true }, }) // Numeric MAX guards against counter drift (e.g. ideas inserted directly in // DB without updating the counter). String MAX mis-sorts "IDEA-1000" < // "IDEA-999", so we cast to INTEGER in SQL. const rows = await (client as Prisma.TransactionClient).$queryRaw< [{ max_n: number | bigint | null }] >` SELECT MAX(CAST(SUBSTRING(code FROM 6) AS INTEGER)) AS max_n FROM ideas WHERE user_id = ${userId} ` const maxExisting = rows[0].max_n !== null ? Number(rows[0].max_n) : 0 const nextN = Math.max(u.idea_code_counter, maxExisting + 1) // Re-sync counter forward if it was behind the actual max. if (nextN !== u.idea_code_counter) { await client.user.update({ where: { id: userId }, data: { idea_code_counter: nextN }, select: { id: true }, }) } return formatIdeaCode(nextN) }