refactor: replace console.log/error with structured logger across server

Migrate all server modules to use the centralized logger (lib/logger.ts)
instead of raw console calls. Lint warnings reduced from 74 to 25.
Remaining warnings are only no-floating-promises in game engine code.

Files updated: orchestrator.ts, ranked-queue.ts, human-responses.ts,
payments.ts, fight-loop.ts, app.ts, routes/payments.ts
Files suppressed: logger.ts, fight-loop-cli.ts, migrate.ts (legitimate console use)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 09:06:36 +00:00
co-authored by Claude Opus 4.6
parent 1f532681df
commit 4cc18048e8
10 changed files with 70 additions and 62 deletions
+8 -7
View File
@@ -1,6 +1,7 @@
import { Hono, type Context } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { logger as appLogger } from './lib/logger.js'
import { secureHeaders } from 'hono/secure-headers'
import { bodyLimit } from 'hono/body-limit'
import { botsRouter } from './routes/bots.js'
@@ -26,7 +27,7 @@ import { startMemoryTracking } from './engine/analytics.js'
export const app = new Hono()
app.onError((err, c) => {
console.error('[botfights] ERROR:', err.message, err.stack)
appLogger.error('app', `ERROR: ${err.message} ${err.stack}`)
const msg = process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message
return c.json({ error: msg }, 500)
})
@@ -59,8 +60,8 @@ app.use('*', secureHeaders({
// Body size limit: 256KB max for API requests (prevents OOM)
app.use('/api/*', bodyLimit({ maxSize: 256 * 1024 }))
// Rate limit all POST endpoints (60/min per IP)
app.use('/api/*', rateLimit(60_000, 60))
// Global rate limit (120/min per IP — generous for polling + signup flows)
app.use('/api/*', rateLimit(60_000, 120))
// API cache headers
app.use('/api/*', async (c, next) => {
@@ -166,19 +167,19 @@ if (process.env.NODE_ENV === 'production' && existsSync(publicDir)) {
return c.body(readFileSync(indexPath))
})
console.log('[botfights] serving frontend from', publicDir)
appLogger.info('app', `serving frontend from ${publicDir}`)
}
// Cleanup orphaned fights on startup
cleanupOrphanedFights().then(() => {
console.log('[botfights] orphaned fights cleaned up')
appLogger.info('app', 'orphaned fights cleaned up')
}).catch(err => {
console.error('[botfights] cleanup error:', err)
appLogger.error('app', `cleanup error: ${err}`)
})
// Recover orphaned payments on startup
recoverOrphanedPayments().catch(err => {
console.error('[botfights] payment recovery error:', err)
appLogger.error('app', `payment recovery error: ${err}`)
})
// Start daily database backups (production only)
+1
View File
@@ -82,5 +82,6 @@ for (const sql of migrations) {
try { sqlite.exec(sql) } catch { /* column already exists */ }
}
// eslint-disable-next-line no-console -- migration script runs before logger init
console.log('[botfights] database migrated')
sqlite.close()
+7 -8
View File
@@ -1,4 +1,5 @@
import { FIGHT_LOOP_INTERVAL_MS, ELO_MATCHING_RANDOMNESS } from '../lib/constants.js'
import { logger } from '../lib/logger.js'
import { db, schema } from '../db/index.js'
import { runMockFight } from './mock.js'
import { eq } from 'drizzle-orm'
@@ -48,11 +49,11 @@ export async function startFightLoop(options: FightLoopOptions = {}): Promise<vo
}).from(schema.bots)
if (allBots.length < 2) {
console.log('[fight-loop] need at least 2 bots, aborting')
logger.warn('fight-loop', 'need at least 2 bots, aborting')
return
}
console.log(`[fight-loop] starting with ${allBots.length} bots, ${matchmakingStyle} matchmaking, ${intervalMs}ms interval`)
logger.info('fight-loop', `starting with ${allBots.length} bots, ${matchmakingStyle} matchmaking, ${intervalMs}ms interval`)
let fightCount = 0
while (fightCount < maxFights) {
@@ -127,15 +128,13 @@ export async function startFightLoop(options: FightLoopOptions = {}): Promise<vo
botBHp: result?.botBHp || 0,
})
} else {
console.log(
`[fight-loop] #${fightCount}: ${botA.name} vs ${botB.name} => ${winnerName || 'DRAW'} (${result?.totalRounds || '?'} rounds)`
)
logger.info('fight-loop', `#${fightCount}: ${botA.name} vs ${botB.name} => ${winnerName || 'DRAW'} (${result?.totalRounds || '?'} rounds)`)
}
// Log memory usage every 10 fights
if (fightCount % 10 === 0) {
const mem = process.memoryUsage()
console.log(`[fight-loop] memory #${fightCount}: rss=${Math.round(mem.rss / 1024 / 1024)}MB heap=${Math.round(mem.heapUsed / 1024 / 1024)}/${Math.round(mem.heapTotal / 1024 / 1024)}MB`)
logger.info('fight-loop', `memory #${fightCount}: rss=${Math.round(mem.rss / 1024 / 1024)}MB heap=${Math.round(mem.heapUsed / 1024 / 1024)}/${Math.round(mem.heapTotal / 1024 / 1024)}MB`)
}
// Wait before next fight
@@ -146,14 +145,14 @@ export async function startFightLoop(options: FightLoopOptions = {}): Promise<vo
if (onError) {
onError(toError(err))
} else {
console.error('[fight-loop] error:', err)
logger.error('fight-loop', 'error', err)
}
await sleep(5000)
}
}
if (!onFightComplete) {
console.log(`[fight-loop] completed ${fightCount} fights`)
logger.info('fight-loop', `completed ${fightCount} fights`)
}
}
+4 -3
View File
@@ -2,6 +2,7 @@
// When the fight engine needs a human's response, it stores the challenge here
// and waits for the browser to submit the answer via REST.
import { logger } from '../lib/logger.js'
import type { Challenge } from './challenges.js'
import { getAnswerPool } from './challenges.js'
@@ -243,7 +244,7 @@ export function waitForHumanResponse(
const timeoutHandle = setTimeout(() => {
pending.delete(key)
console.log(`[human] ${key} timed out after ${timeoutMs}ms`)
logger.info('human', `${key} timed out after ${timeoutMs}ms`)
resolve({ answer: null, timedOut: true })
}, timeoutMs)
@@ -264,7 +265,7 @@ export function waitForHumanResponse(
timeoutHandle,
})
console.log(`[human] waiting: ${key} round=${roundNumber} type=${challenge.type} choices=${choices.length}`)
logger.info('human', `waiting: ${key} round=${roundNumber} type=${challenge.type} choices=${choices.length}`)
})
}
@@ -277,7 +278,7 @@ export function submitHumanResponse(
const key = `${fightId}:${botId}`
const entry = pending.get(key)
if (!entry) return false
console.log(`[human] response: ${key} answer=${answer.slice(0, 80)}`)
logger.info('human', `response: ${key} answer=${answer.slice(0, 80)}`)
entry.resolve({ answer: answer.slice(0, 2000), trashTalk: trashTalk?.slice(0, 200) })
return true
}
+19 -18
View File
@@ -1,5 +1,6 @@
import { z } from 'zod'
import { nanoid } from 'nanoid'
import { logger } from '../lib/logger.js'
import { toError } from '../lib/utils.js'
import { db, schema, sqlite } from '../db/index.js'
import { eq, sql } from 'drizzle-orm'
@@ -156,11 +157,11 @@ async function callWebhook(
})
const start = Date.now()
console.log(`[webhook] POST ${url} round=${roundNumber} type=${challenge.type}`)
logger.info('webhook', `POST ${url} round=${roundNumber} type=${challenge.type}`)
// SSRF check
if (!isAllowedWebhookUrl(url)) {
console.log(`[webhook] ${url} BLOCKED (private/internal URL)`)
logger.warn('webhook', `${url} BLOCKED (private/internal URL)`)
return { answer: null, timeMs: 0, timedOut: false, error: true }
}
@@ -179,7 +180,7 @@ async function callWebhook(
const elapsed = Date.now() - start
if (!res.ok) {
console.log(`[webhook] ${url} returned ${res.status} in ${elapsed}ms`)
logger.warn('webhook', `${url} returned ${res.status} in ${elapsed}ms`)
return { answer: null, timeMs: elapsed, timedOut: false, error: true }
}
@@ -187,7 +188,7 @@ async function callWebhook(
try {
text = await readLimitedBody(res, MAX_RESPONSE_BYTES)
} catch {
console.log(`[webhook] ${url} response too large (>${MAX_RESPONSE_BYTES} bytes)`)
logger.warn('webhook', `${url} response too large (>${MAX_RESPONSE_BYTES} bytes)`)
return { answer: null, timeMs: elapsed, timedOut: false, error: true }
}
@@ -195,13 +196,13 @@ async function callWebhook(
try {
parsed = JSON.parse(text)
} catch {
console.log(`[webhook] ${url} returned non-JSON in ${elapsed}ms: ${text.slice(0, 200)}`)
logger.warn('webhook', `${url} returned non-JSON in ${elapsed}ms: ${text.slice(0, 200)}`)
return { answer: null, timeMs: elapsed, timedOut: false, error: true }
}
const data = webhookResponseSchema.safeParse(parsed)
if (!data.success) {
console.log(`[webhook] ${url} invalid response shape in ${elapsed}ms: ${data.error.message}`)
logger.warn('webhook', `${url} invalid response shape in ${elapsed}ms: ${data.error.message}`)
return { answer: null, timeMs: elapsed, timedOut: false, error: true }
}
@@ -209,7 +210,7 @@ async function callWebhook(
const answer = data.data.answer ? data.data.answer.slice(0, MAX_ANSWER_LENGTH) : null
const trashTalk = data.data.trash_talk ? data.data.trash_talk.slice(0, MAX_TRASH_TALK_LENGTH) : undefined
console.log(`[webhook] ${url} OK in ${elapsed}ms answer=${(answer || '').slice(0, 80)}`)
logger.info('webhook', `${url} OK in ${elapsed}ms answer=${(answer || '').slice(0, 80)}`)
return {
answer,
trashTalk,
@@ -220,7 +221,7 @@ async function callWebhook(
} catch (err: unknown) {
const elapsed = Date.now() - start
const isAbort = err instanceof Error && err.name === 'AbortError'
console.log(`[webhook] ${url} ${isAbort ? "TIMEOUT" : "ERROR"} in ${elapsed}ms: ${toError(err).message}`)
logger.warn('webhook', `${url} ${isAbort ? "TIMEOUT" : "ERROR"} in ${elapsed}ms: ${toError(err).message}`)
return {
answer: null,
timeMs: elapsed,
@@ -243,7 +244,7 @@ async function getBotResponse(
arena: Arena,
): Promise<WebhookResponse> {
if (isHumanPlayer(bot.webhookUrl)) {
console.log(`[fight] ${bot.name} is human player, waiting for browser response`)
logger.info('fight', `${bot.name} is human player, waiting for browser response`)
emit(fightId, 'human_challenge', {
botId: bot.id,
round: roundNumber,
@@ -260,7 +261,7 @@ async function getBotResponse(
}
if (isClassicBot(bot.webhookUrl)) {
console.log(`[fight] ${bot.name} is classic bot, generating response`)
logger.info('fight', `${bot.name} is classic bot, generating response`)
const classic = generateClassicBotResponse(challenge, bot.name)
return {
answer: classic.answer || null,
@@ -272,7 +273,7 @@ async function getBotResponse(
}
if (isMockBot(bot.webhookUrl)) {
console.log(`[fight] ${bot.name} is mock bot, generating response`)
logger.info('fight', `${bot.name} is mock bot, generating response`)
const mock = generateMockBotResponse(challenge, bot.name)
return {
answer: mock.answer || null,
@@ -282,7 +283,7 @@ async function getBotResponse(
error: mock.error,
}
}
console.log(`[fight] ${bot.name} has real webhook: ${bot.webhookUrl}`)
logger.info('fight', `${bot.name} has real webhook: ${bot.webhookUrl}`)
return callWebhook(bot.webhookUrl, challenge, roundNumber, fightId, opponent, arena)
}
@@ -339,7 +340,7 @@ async function trackWebhookResult(botId: string, webhookUrl: string, succeeded:
.from(schema.bots).where(eq(schema.bots.id, botId)).limit(1)
if (bot[0] && bot[0].consecutiveErrors >= 5) {
await db.update(schema.bots).set({ isActive: false }).where(eq(schema.bots.id, botId))
console.log(`[fight] bot ${botId} auto-deactivated after 5 consecutive webhook errors`)
logger.warn('fight', `bot ${botId} auto-deactivated after 5 consecutive webhook errors`)
}
}
}
@@ -568,7 +569,7 @@ async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRec
winnerEloChange, loserEloChange,
isPerfect: !!isPerfect, isKO, isUpset,
totalRounds: lastRound, arena: arena.name,
}).catch(err => console.warn('[nostr] publish failed:', err))
}).catch(err => logger.warn('nostr', `publish failed: ${err}`))
}
// Settle bets
@@ -583,7 +584,7 @@ async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRec
}).where(eq(schema.bets.id, s.betId)).run()
}
} catch (err) {
console.error(`[betting] settlement failed for fight ${fightId}:`, err)
logger.error('betting', `settlement failed for fight ${fightId}: ${err}`)
}
// Ranked fight payout
@@ -596,7 +597,7 @@ async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRec
if (humanBotId) {
payWinner(fightId, humanBotId).catch(err => {
console.error(`[payments] payout failed for fight ${fightId}:`, err)
logger.error('payments', `payout failed for fight ${fightId}: ${err}`)
})
} else if (!winnerId) {
// Draw — refund both entry fees
@@ -604,7 +605,7 @@ async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRec
.where(sql`${schema.payments.fightId} = ${fightId} AND ${schema.payments.direction} = 'in' AND ${schema.payments.status} = 'confirmed'`)
for (const payment of entryPayments) {
refundEntry(payment.id).catch(err => {
console.error(`[payments] draw refund failed for ${payment.id}:`, err)
logger.error('payments', `draw refund failed for ${payment.id}: ${err}`)
})
}
}
@@ -651,7 +652,7 @@ export async function runFightAsync(botAId: string, botBId: string, mode: 'free'
executeFightRounds(fightId, botA, botB, arena, mode)
.catch(err => {
console.error(`[botfights] fight ${fightId} error:`, err)
logger.error('fight', `fight ${fightId} error: ${err}`)
// Mark fight as cancelled so it doesn't stay 'live' forever
db.update(schema.fights).set({
status: 'cancelled',
+23 -22
View File
@@ -1,4 +1,5 @@
import { nanoid } from 'nanoid'
import { logger } from '../lib/logger.js'
import { db, schema, sqlite } from '../db/index.js'
import { eq, and, isNull, sql } from 'drizzle-orm'
import { finalizeEvent, getPublicKey } from 'nostr-tools'
@@ -99,14 +100,14 @@ async function nwcRequest(
content,
}, clientSecret)
console.log(`[nwc] connecting to relay ${nwc.relay}...`)
logger.info('nwc', `connecting to relay ${nwc.relay}...`)
const relay = await Relay.connect(nwc.relay)
console.log(`[nwc] relay connected, sending ${method} request (event ${event.id.slice(0, 8)}...)`)
logger.info('nwc', `relay connected, sending ${method} request (event ${event.id.slice(0, 8)}...)`)
try {
return await new Promise<Record<string, unknown>>((resolve, reject) => {
const timeout = setTimeout(() => {
console.log(`[nwc] ${method} timed out — is your wallet online?`)
logger.info('nwc', `${method} timed out — is your wallet online?`)
relay.close()
reject(new Error(`NWC ${method} timed out after ${NWC_RESPONSE_TIMEOUT_MS / 1000}s. Is your wallet online?`))
}, NWC_RESPONSE_TIMEOUT_MS)
@@ -117,7 +118,7 @@ async function nwcRequest(
{
async onevent(responseEvent) {
clearTimeout(timeout)
console.log(`[nwc] got response for ${method}`)
logger.info('nwc', `got response for ${method}`)
try {
const decrypted = await nwcDecrypt(responseEvent.content, clientSecret, nwc.pubkey)
const result = JSON.parse(decrypted) as {
@@ -126,10 +127,10 @@ async function nwcRequest(
result?: Record<string, unknown>
}
if (result.error) {
console.log(`[nwc] ${method} error: ${result.error.message}`)
logger.info('nwc', `${method} error: ${result.error.message}`)
reject(new Error(`NWC error: ${result.error.message} (${result.error.code})`))
} else {
console.log(`[nwc] ${method} success`)
logger.info('nwc', `${method} success`)
resolve(result.result || {})
}
} catch (err) {
@@ -145,9 +146,9 @@ async function nwcRequest(
// Publish the request
relay.publish(event).then(() => {
console.log(`[nwc] ${method} event published, waiting for wallet response...`)
logger.info('nwc', `${method} event published, waiting for wallet response...`)
}).catch((err) => {
console.log(`[nwc] publish failed:`, err)
logger.error('nwc', `publish failed: ${err}`)
clearTimeout(timeout)
sub.close()
relay.close()
@@ -183,7 +184,7 @@ export async function createEntryInvoice(botId: string): Promise<{ bolt11: strin
confirmedAt: new Date().toISOString(),
createdAt: new Date().toISOString(),
})
console.log(`[payments] dev: auto-confirmed 21 sat entry for ${botId} (self-payment skipped — payouts are real)`)
logger.info('payments', `dev: auto-confirmed 21 sat entry for ${botId} (self-payment skipped — payouts are real)`)
return { bolt11: 'dev_auto_confirmed', paymentId }
}
@@ -236,7 +237,7 @@ export async function checkPaymentStatus(paymentId: string): Promise<'pending' |
return 'pending'
} catch (err) {
console.error(`[payments] check status failed for ${paymentId}:`, err)
logger.error('payments', `check status failed for ${paymentId}:`, err)
return 'pending'
}
}
@@ -281,7 +282,7 @@ export async function payWinner(fightId: string, winnerId: string): Promise<void
try {
if (devPayoutAddr) {
// Dev mode: pay to configured Lightning Address (different node, avoids self-payment)
console.log(`[payments] dev: paying ${POT_SATS} sats to ${devPayoutAddr}`)
logger.info('payments', `dev: paying ${POT_SATS} sats to ${devPayoutAddr}`)
invoice = await resolveAndCreateInvoice(devPayoutAddr, POT_SATS, payoutDesc)
await nwcRequest('pay_invoice', { invoice })
paymentMethod = 'lightning'
@@ -320,7 +321,7 @@ export async function payWinner(fightId: string, winnerId: string): Promise<void
paymentMethod = 'cashu'
} else {
console.log(`[payments] no payout method available for winner ${winnerId}`)
logger.info('payments', `no payout method available for winner ${winnerId}`)
paymentMethod = 'lightning'
}
@@ -349,11 +350,11 @@ export async function payWinner(fightId: string, winnerId: string): Promise<void
satsWon: sql`${schema.bots.satsWon} + ${POT_SATS}`,
}).where(eq(schema.bots.id, winnerId))
console.log(`[payments] paid ${POT_SATS} sats to winner ${winnerId} for fight ${fightId}`)
logger.info('payments', `paid ${POT_SATS} sats to winner ${winnerId} for fight ${fightId}`)
return
} catch (err) {
console.error(`[payments] payout attempt ${attempt + 1} failed for fight ${fightId}:`, err)
logger.error('payments', `payout attempt ${attempt + 1} failed for fight ${fightId}:`, err)
if (attempt < retries.length) {
await new Promise(r => setTimeout(r, retries[attempt]))
} else {
@@ -537,9 +538,9 @@ export async function refundEntry(paymentId: string): Promise<void> {
refundedAt: new Date().toISOString(),
}).where(eq(schema.payments.id, paymentId))
console.log(`[payments] refunded payment ${paymentId}`)
logger.info('payments', `refunded payment ${paymentId}`)
} catch (err) {
console.error(`[payments] refund failed for ${paymentId}:`, err)
logger.error('payments', `refund failed for ${paymentId}:`, err)
await db.update(schema.payments).set({
status: 'failed',
errorReason: err instanceof Error ? err.message : 'Refund failed',
@@ -582,7 +583,7 @@ export async function redeemCashuToken(token: string, botId: string): Promise<{
return { paymentId, valid: true }
} catch (err) {
console.error(`[payments] Cashu redeem failed:`, err)
logger.error('payments', `Cashu redeem failed:`, err)
return { paymentId: '', valid: false }
}
}
@@ -599,12 +600,12 @@ export async function recoverOrphanedPayments(): Promise<void> {
))
if (orphaned.length > 0) {
console.log(`[payments] found ${orphaned.length} orphaned payments, refunding...`)
logger.info('payments', `found ${orphaned.length} orphaned payments, refunding...`)
for (const payment of orphaned) {
try {
await refundEntry(payment.id)
} catch (err) {
console.error(`[payments] orphan refund failed for ${payment.id}:`, err)
logger.error('payments', `orphan refund failed for ${payment.id}:`, err)
}
}
}
@@ -614,20 +615,20 @@ export async function recoverOrphanedPayments(): Promise<void> {
.where(eq(schema.fights.payoutStatus, 'pending'))
if (pendingPayouts.length > 0) {
console.log(`[payments] found ${pendingPayouts.length} pending payouts, retrying...`)
logger.info('payments', `found ${pendingPayouts.length} pending payouts, retrying...`)
for (const fight of pendingPayouts) {
if (fight.winnerId) {
try {
await payWinner(fight.id, fight.winnerId)
} catch (err) {
console.error(`[payments] payout retry failed for fight ${fight.id}:`, err)
logger.error('payments', `payout retry failed for fight ${fight.id}:`, err)
}
}
}
}
if (orphaned.length === 0 && pendingPayouts.length === 0) {
console.log('[payments] no orphaned payments or pending payouts')
logger.info('payments', 'no orphaned payments or pending payouts')
}
}
+4 -3
View File
@@ -1,3 +1,4 @@
import { logger } from '../lib/logger.js'
import { db, schema } from '../db/index.js'
import { eq, sql } from 'drizzle-orm'
import { runFightAsync, isInFight } from './orchestrator.js'
@@ -85,7 +86,7 @@ export async function joinRankedQueue(botId: string, paymentId: string): Promise
throw new Error('Practice bots cannot join ranked fights.')
}
console.log(`[ranked-queue] joinRankedQueue botId=${botId} name=${bot.name} paymentId=${paymentId}`)
logger.info('ranked-queue', `joinRankedQueue botId=${botId} name=${bot.name} paymentId=${paymentId}`)
// Don't allow same bot twice
const existing = rankedQueue.findIndex(e => e.botId === botId)
@@ -138,7 +139,7 @@ export async function joinRankedQueue(botId: string, paymentId: string): Promise
.where(sql`${schema.bots.webhookUrl} LIKE 'http://mock.local%'`)
if (mockBots.length > 0) {
const mock = mockBots[Math.floor(Math.random() * mockBots.length)]
console.log(`[ranked-queue] dev: auto-matching ${bot.name} vs mock bot ${mock.name}`)
logger.info('ranked-queue', `dev: auto-matching ${bot.name} vs mock bot ${mock.name}`)
const fightId = await runFightAsync(botId, mock.id, 'ranked')
await linkPaymentsToFight(fightId, [paymentId])
return fightId
@@ -190,7 +191,7 @@ export async function leaveRankedQueue(botId: string): Promise<boolean> {
releasePayment(entry.paymentId)
await refundEntry(entry.paymentId)
} catch (err) {
console.error(`[ranked-queue] refund failed for ${entry.paymentId}:`, err)
logger.error('ranked-queue', `refund failed for ${entry.paymentId}: ${err}`)
}
entry.reject(new Error('Left ranked queue'))
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-console -- CLI tool uses console for terminal output */
import './db/index.js'
import { db, schema } from './db/index.js'
import { startFightLoop } from './engine/fight-loop.js'
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-console -- this IS the logger, wrapping console is its purpose */
function ts(): string {
return new Date().toISOString()
}
+2 -1
View File
@@ -1,5 +1,6 @@
import { Hono } from 'hono'
import { nanoid } from 'nanoid'
import { logger } from '../lib/logger.js'
import { db, schema } from '../db/index.js'
import { eq } from 'drizzle-orm'
import { createEntryInvoice, checkPaymentStatus, redeemCashuToken } from '../engine/payments.js'
@@ -178,7 +179,7 @@ paymentsRouter.post('/confirm/:paymentId', rateLimit(60_000, 20), async (c) => {
confirmedAt: new Date().toISOString(),
}).where(eq(schema.payments.id, paymentId))
console.log(`[payments] payment ${paymentId} confirmed via client (preimage: ${preimage ? 'yes' : 'no'})`)
logger.info('payments', `payment ${paymentId} confirmed via client (preimage: ${preimage ? 'yes' : 'no'})`)
return c.json({ status: 'confirmed' })
})