Files
botfights/server/src/db/schema.ts
T
DorianandClaude Opus 4.6 ecac2bf246 feat: season framework with 2-week auto-rotation
Create seasons engine with getCurrentSeason(), getSeasonLeaderboard(),
and resetSeasonElo(). 20 Bitcoin-themed season names. Add currentSeason
column to fights table, set on fight creation. Elo soft-reset formula:
finalElo * 0.6 + 1200 * 0.4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:13:44 +00:00

112 lines
4.8 KiB
TypeScript

import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core'
export const bots = sqliteTable('bots', {
id: text('id').primaryKey(),
name: text('name').notNull().unique(),
webhookUrl: text('webhook_url').notNull(),
avatarSeed: text('avatar_seed').notNull(),
archetype: text('archetype').notNull().default('standard'),
secretHash: text('secret_hash').notNull(),
publicKey: text('public_key'),
profilePicUrl: text('profile_pic_url'),
eloRating: real('elo_rating').notNull().default(1200),
wins: integer('wins').notNull().default(0),
losses: integer('losses').notNull().default(0),
winStreak: integer('win_streak').notNull().default(0),
bestStreak: integer('best_streak').notNull().default(0),
tier: integer('tier').notNull().default(0),
isActive: integer('is_active', { mode: 'boolean' }).notNull().default(true),
lastFightAt: text('last_fight_at'),
consecutiveErrors: integer('consecutive_errors').notNull().default(0),
lastErrorAt: text('last_error_at'),
customization: text('customization'),
satsWon: integer('sats_won').notNull().default(0),
satsWagered: integer('sats_wagered').notNull().default(0),
hasWallet: integer('has_wallet', { mode: 'boolean' }).notNull().default(false),
zapsReceived: integer('zaps_received').notNull().default(0),
botType: text('bot_type', { enum: ['regular', 'mock', 'classic'] }).notNull().default('regular'),
createdAt: text('created_at').notNull(),
})
export const fights = sqliteTable('fights', {
id: text('id').primaryKey(),
botAId: text('bot_a_id').notNull().references(() => bots.id),
botBId: text('bot_b_id').notNull().references(() => bots.id),
arena: text('arena').notNull(),
status: text('status', { enum: ['scheduled', 'live', 'finished', 'cancelled'] }).notNull().default('scheduled'),
winnerId: text('winner_id').references(() => bots.id),
botAHp: integer('bot_a_hp').notNull().default(200),
botBHp: integer('bot_b_hp').notNull().default(200),
totalRounds: integer('total_rounds').notNull().default(0),
scheduledAt: text('scheduled_at'),
startedAt: text('started_at'),
endedAt: text('ended_at'),
mode: text('mode', { enum: ['free', 'ranked'] }).notNull().default('free'),
potSats: integer('pot_sats').notNull().default(0),
payoutStatus: text('payout_status', { enum: ['pending', 'paid', 'failed'] }),
currentSeason: text('current_season'),
createdAt: text('created_at').notNull(),
})
export const rounds = sqliteTable('rounds', {
id: text('id').primaryKey(),
fightId: text('fight_id').notNull().references(() => fights.id),
roundNumber: integer('round_number').notNull(),
challengeType: text('challenge_type').notNull(),
challengeData: text('challenge_data').notNull(),
botAResponse: text('bot_a_response'),
botATimeMs: integer('bot_a_time_ms'),
botAScore: real('bot_a_score'),
botBResponse: text('bot_b_response'),
botBTimeMs: integer('bot_b_time_ms'),
botBScore: real('bot_b_score'),
winnerId: text('winner_id').references(() => bots.id),
narration: text('narration'),
createdAt: text('created_at').notNull(),
})
export const payments = sqliteTable('payments', {
id: text('id').primaryKey(),
fightId: text('fight_id').references(() => fights.id),
botId: text('bot_id').notNull().references(() => bots.id),
direction: text('direction', { enum: ['in', 'out'] }).notNull(),
amountSats: integer('amount_sats').notNull(),
method: text('method', { enum: ['lightning', 'cashu'] }).notNull(),
status: text('status', {
enum: ['pending', 'confirmed', 'failed', 'refunded'],
}).notNull().default('pending'),
invoice: text('invoice'),
preimage: text('preimage'),
cashuToken: text('cashu_token'),
errorReason: text('error_reason'),
createdAt: text('created_at').notNull(),
confirmedAt: text('confirmed_at'),
refundedAt: text('refunded_at'),
})
export const walletConnections = sqliteTable('wallet_connections', {
id: text('id').primaryKey(),
botId: text('bot_id').notNull().unique().references(() => bots.id),
method: text('method', { enum: ['nwc', 'lnaddress', 'cashu_mint'] }).notNull(),
connectionData: text('connection_data').notNull(),
createdAt: text('created_at').notNull(),
lastUsedAt: text('last_used_at'),
})
export const bets = sqliteTable('bets', {
id: text('id').primaryKey(),
fightId: text('fight_id').notNull().references(() => fights.id),
bettorPubkey: text('bettor_pubkey').notNull(),
botId: text('bot_id').notNull().references(() => bots.id),
amountSats: integer('amount_sats').notNull(),
oddsAtPlacement: real('odds_at_placement').notNull(),
cashuToken: text('cashu_token').notNull(),
status: text('status', {
enum: ['pending', 'locked', 'won', 'lost', 'refunded', 'paid'],
}).notNull().default('pending'),
payoutSats: integer('payout_sats'),
payoutToken: text('payout_token'),
createdAt: text('created_at').notNull(),
settledAt: text('settled_at'),
})