diff --git a/lib/idea-code-server.ts b/lib/idea-code-server.ts index 9f26aed..819d4ed 100644 --- a/lib/idea-code-server.ts +++ b/lib/idea-code-server.ts @@ -7,6 +7,11 @@ // 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' @@ -17,10 +22,34 @@ 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 }, }) - return formatIdeaCode(u.idea_code_counter) + + // 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) }