fix(ideas): make nextIdeaCode self-correcting against counter drift (#200)
Fixes a P2002 unique constraint crash on (user_id, code) when idea_code_counter on the User is behind the actual codes in the ideas table (e.g. after direct DB inserts during development). After incrementing the counter the function now queries MAX(CAST(SUBSTRING(code FROM 6) AS INTEGER)) via raw SQL and takes max(counter, maxExisting + 1) as the next code. String MAX was not safe above IDEA-999, hence the numeric cast. If the counter lagged it is updated in-place to stay in sync. No schema change, no migration, no changes outside idea-code-server.ts. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d84cdf664f
commit
7bb252c528
1 changed files with 30 additions and 1 deletions
|
|
@ -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<string> {
|
||||
// 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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue