feat: omni-morph sprite swap, bullet time showboats, music tuning, server hardening
- Creator omni-morph now generates actual sprite sheets for morphed archetypes - 3 new Creator showboats: bullet time attack, ₿ throne summon, disco dance - Music: subtle tempo shift (+10 BPM max), longer phrases (8/16/24 bars), smoother crossfades, less chaotic hi-hat at high intensity - Server: security headers, body size limit, production error masking, CORS origin warning, graceful shutdown with drain - Payments: atomic consume (eliminates SELECT/UPDATE race), release reverts DB - Fight loop: round events for live TUI, retro displayPrompt - Frontend: pass pubkey in payment/queue requests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0acfa4417f
commit
e12fb94ae7
@@ -1,5 +1,5 @@
|
||||
import { nanoid } from 'nanoid'
|
||||
import { db, schema } from '../db/index.js'
|
||||
import { db, schema, sqlite } from '../db/index.js'
|
||||
import { eq, and, isNull, sql } from 'drizzle-orm'
|
||||
import { finalizeEvent, getPublicKey } from 'nostr-tools'
|
||||
import * as nip04 from 'nostr-tools/nip04'
|
||||
@@ -643,22 +643,13 @@ export async function consumePaymentForQueue(paymentId: string, botId: string):
|
||||
// Fast path: already consumed in this server lifetime
|
||||
if (consumedPayments.has(paymentId)) return false
|
||||
|
||||
// Verify payment belongs to this bot, is confirmed, inbound, and not linked to a fight
|
||||
const rows = await db.select({
|
||||
id: schema.payments.id,
|
||||
botId: schema.payments.botId,
|
||||
status: schema.payments.status,
|
||||
direction: schema.payments.direction,
|
||||
fightId: schema.payments.fightId,
|
||||
}).from(schema.payments).where(eq(schema.payments.id, paymentId)).limit(1)
|
||||
|
||||
if (rows.length === 0) return false
|
||||
const p = rows[0]
|
||||
if (p.botId !== botId) return false
|
||||
if (p.status !== 'confirmed') return false
|
||||
if (p.direction !== 'in') return false
|
||||
if (p.fightId !== null) return false // already linked to a fight
|
||||
// Atomic: only marks as consumed if ALL conditions met in a single UPDATE
|
||||
// Eliminates race window between SELECT check and later UPDATE
|
||||
const result = sqlite.prepare(
|
||||
`UPDATE payments SET status = 'consumed' WHERE id = ? AND bot_id = ? AND status = 'confirmed' AND direction = 'in' AND fight_id IS NULL`
|
||||
).run(paymentId, botId)
|
||||
|
||||
if (result.changes === 0) return false
|
||||
consumedPayments.add(paymentId)
|
||||
return true
|
||||
}
|
||||
@@ -680,6 +671,8 @@ export async function linkPaymentsToFight(fightId: string, paymentIds: string[])
|
||||
*/
|
||||
export function releasePayment(paymentId: string): void {
|
||||
consumedPayments.delete(paymentId)
|
||||
// Revert DB status so the payment can be re-consumed or refunded
|
||||
sqlite.prepare(`UPDATE payments SET status = 'confirmed' WHERE id = ? AND status = 'consumed'`).run(paymentId)
|
||||
}
|
||||
|
||||
export { ENTRY_FEE_SATS, POT_SATS }
|
||||
|
||||
Reference in New Issue
Block a user