import { Hono } from 'hono' import { db, schema } from '../db/index.js' import { eq } from 'drizzle-orm' import { toError } from '../lib/utils.js' import { isCreatorPubkey } from '../lib/constants.js' import { extractPubkeyFromAuth } from '../middleware/jwt.js' import { createTournament, joinTournament, startTournament, getTournamentBracket, listTournaments, } from '../engine/tournaments.js' import { createTournamentSchema, joinTournamentSchema, startTournamentSchema, formatZodError } from '../lib/validators.js' export const tournamentsRouter = new Hono() // List tournaments (optionally filter by status) tournamentsRouter.get('/', async (c) => { const status = c.req.query('status') as 'open' | 'active' | 'finished' | undefined const tournaments = listTournaments(status) return c.json({ tournaments }) }) // Get tournament bracket tournamentsRouter.get('/:id', async (c) => { const id = c.req.param('id') const bracket = getTournamentBracket(id) if (!bracket) return c.json({ error: 'Tournament not found' }, 404) return c.json(bracket) }) // Create a tournament (admin only — creator pubkey required) tournamentsRouter.post('/', async (c) => { const parsed = createTournamentSchema.safeParse(await c.req.json().catch(() => ({}))) if (!parsed.success) { return c.json({ error: formatZodError(parsed.error, { name: 'Tournament name required (1-100 chars)', size: 'Size must be 8, 16, or 32', }, 'Invalid tournament data') }, 400) } const body = parsed.data if (!isCreatorPubkey(body.pubkey)) { return c.json({ error: 'Only the creator can create tournaments' }, 403) } const format = body.format ?? 'single_elim' const size = body.size ?? 8 const id = createTournament(body.name, format, size, body.entrySats ?? 0) return c.json({ id, name: body.name, format, size }, 201) }) // Join a tournament (requires JWT auth to prove pubkey ownership) tournamentsRouter.post('/:id/join', async (c) => { const tournamentId = c.req.param('id') const parsed = joinTournamentSchema.safeParse(await c.req.json().catch(() => ({}))) if (!parsed.success) return c.json({ error: formatZodError(parsed.error, { pubkey: 'pubkey required' }, 'pubkey required') }, 400) const body = parsed.data // Verify caller owns the pubkey via JWT (prevents joining on behalf of others) const authedPubkey = extractPubkeyFromAuth(c.req.header('Authorization')) if (authedPubkey && authedPubkey !== body.pubkey) { return c.json({ error: 'Pubkey does not match authenticated session' }, 403) } if (!authedPubkey && process.env.NODE_ENV === 'production') { return c.json({ error: 'Authentication required' }, 401) } // Look up bot by pubkey const bot = db.select().from(schema.bots) .where(eq(schema.bots.publicKey, body.pubkey)) .get() if (!bot) return c.json({ error: 'Bot not found for this pubkey' }, 404) try { const entryId = joinTournament(tournamentId, bot.id, body.paymentId) return c.json({ entryId, botId: bot.id }) } catch (err) { const message = toError(err).message return c.json({ error: message }, 400) } }) // Start a tournament (admin only) tournamentsRouter.post('/:id/start', async (c) => { const tournamentId = c.req.param('id') const parsed = startTournamentSchema.safeParse(await c.req.json().catch(() => ({}))) if (!parsed.success) return c.json({ error: formatZodError(parsed.error, { pubkey: 'pubkey required' }, 'pubkey required') }, 400) const body = parsed.data if (!isCreatorPubkey(body.pubkey)) { return c.json({ error: 'Only the creator can start tournaments' }, 403) } try { startTournament(tournamentId) const bracket = getTournamentBracket(tournamentId) return c.json({ status: 'active', bracket }) } catch (err) { const message = toError(err).message return c.json({ error: message }, 400) } })