import { Request, Response } from 'express'; import { db } from '../database'; import { executeDraw } from '../services/draw'; import { retryPayoutService } from '../services/payout'; import { JackpotCycle, Payout } from '../types'; /** * GET /admin/cycles * List all cycles with optional filters */ export async function listCycles(req: Request, res: Response) { try { const { status, cycle_type, limit = 50, offset = 0 } = req.query; let query = `SELECT * FROM jackpot_cycles WHERE 1=1`; const params: any[] = []; let paramCount = 0; if (status) { paramCount++; query += ` AND status = $${paramCount}`; params.push(status); } if (cycle_type) { paramCount++; query += ` AND cycle_type = $${paramCount}`; params.push(cycle_type); } query += ` ORDER BY scheduled_at DESC LIMIT $${paramCount + 1} OFFSET $${paramCount + 2}`; params.push(limit, offset); const result = await db.query(query, params); const cycles = result.rows.map(c => ({ id: c.id, lottery_id: c.lottery_id, cycle_type: c.cycle_type, sequence_number: c.sequence_number, scheduled_at: c.scheduled_at.toISOString(), status: c.status, pot_total_sats: parseInt(c.pot_total_sats.toString()), pot_after_fee_sats: c.pot_after_fee_sats ? parseInt(c.pot_after_fee_sats.toString()) : null, winning_ticket_id: c.winning_ticket_id, winning_lightning_address: c.winning_lightning_address, })); return res.json({ version: '1.0', data: { cycles, total: cycles.length, }, }); } catch (error: any) { console.error('List cycles error:', error); return res.status(500).json({ version: '1.0', error: 'INTERNAL_ERROR', message: 'Failed to list cycles', }); } } /** * POST /admin/cycles/:id/run-draw * Manually trigger draw execution */ export async function runDrawManually(req: Request, res: Response) { try { const { id } = req.params; const result = await executeDraw(id); if (!result.success) { return res.status(400).json({ version: '1.0', error: result.error || 'DRAW_FAILED', message: result.message || 'Failed to execute draw', }); } return res.json({ version: '1.0', data: { cycle_id: id, winning_ticket_id: result.winningTicketId, pot_after_fee_sats: result.potAfterFeeSats, payout_status: result.payoutStatus, }, }); } catch (error: any) { console.error('Manual draw error:', error); return res.status(500).json({ version: '1.0', error: 'INTERNAL_ERROR', message: 'Failed to run draw', }); } } /** * GET /admin/payouts * List payouts with optional filters */ export async function listPayouts(req: Request, res: Response) { try { const { status, limit = 50, offset = 0 } = req.query; let query = `SELECT * FROM payouts WHERE 1=1`; const params: any[] = []; let paramCount = 0; if (status) { paramCount++; query += ` AND status = $${paramCount}`; params.push(status); } query += ` ORDER BY created_at DESC LIMIT $${paramCount + 1} OFFSET $${paramCount + 2}`; params.push(limit, offset); const result = await db.query(query, params); const payouts = result.rows.map(p => ({ id: p.id, lottery_id: p.lottery_id, cycle_id: p.cycle_id, ticket_id: p.ticket_id, lightning_address: p.lightning_address, amount_sats: parseInt(p.amount_sats.toString()), status: p.status, error_message: p.error_message, retry_count: p.retry_count, created_at: p.created_at.toISOString(), })); return res.json({ version: '1.0', data: { payouts, total: payouts.length, }, }); } catch (error: any) { console.error('List payouts error:', error); return res.status(500).json({ version: '1.0', error: 'INTERNAL_ERROR', message: 'Failed to list payouts', }); } } /** * POST /admin/payouts/:id/retry * Retry a failed payout */ export async function retryPayout(req: Request, res: Response) { try { const { id } = req.params; const result = await retryPayoutService(id); if (!result.success) { return res.status(400).json({ version: '1.0', error: result.error || 'RETRY_FAILED', message: result.message || 'Failed to retry payout', }); } return res.json({ version: '1.0', data: { payout_id: id, status: result.status, retry_count: result.retryCount, }, }); } catch (error: any) { console.error('Retry payout error:', error); return res.status(500).json({ version: '1.0', error: 'INTERNAL_ERROR', message: 'Failed to retry payout', }); } } /** * GET /admin/maintenance * Get current maintenance mode status */ export async function getMaintenanceStatus(req: Request, res: Response) { try { const result = await db.query( `SELECT value FROM system_settings WHERE key = 'maintenance_mode'` ); const pendingResult = await db.query( `SELECT value FROM system_settings WHERE key = 'maintenance_pending'` ); const messageResult = await db.query( `SELECT value FROM system_settings WHERE key = 'maintenance_message'` ); const enabled = result.rows[0]?.value === 'true'; const pending = pendingResult.rows[0]?.value === 'true'; const message = messageResult.rows[0]?.value || 'System is under maintenance. Please try again later.'; return res.json({ version: '1.0', data: { maintenance_mode: enabled, maintenance_pending: pending, message: message, }, }); } catch (error: any) { console.error('Get maintenance status error:', error); return res.status(500).json({ version: '1.0', error: 'INTERNAL_ERROR', message: 'Failed to get maintenance status', }); } } /** * POST /admin/maintenance * Enable or disable maintenance mode * When enabling, maintenance is set to "pending" and activates after current draw completes */ export async function setMaintenanceMode(req: Request, res: Response) { try { const { enabled, message, immediate } = req.body; if (typeof enabled !== 'boolean') { return res.status(400).json({ version: '1.0', error: 'INVALID_INPUT', message: 'enabled must be a boolean', }); } if (enabled) { if (immediate === true) { // Immediate activation (admin override) await db.query( `INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_mode', 'true', datetime('now')) ON CONFLICT(key) DO UPDATE SET value = 'true', updated_at = datetime('now')` ); await db.query( `INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_pending', 'false', datetime('now')) ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = datetime('now')` ); console.log('Maintenance mode ENABLED IMMEDIATELY'); } else { // Set pending - will activate after current draw completes await db.query( `INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_pending', 'true', datetime('now')) ON CONFLICT(key) DO UPDATE SET value = 'true', updated_at = datetime('now')` ); console.log('Maintenance mode PENDING (will activate after current draw)'); } } else { // Disable both active and pending maintenance await db.query( `INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_mode', 'false', datetime('now')) ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = datetime('now')` ); await db.query( `INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_pending', 'false', datetime('now')) ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = datetime('now')` ); console.log('Maintenance mode DISABLED'); } // Update message if provided if (message && typeof message === 'string') { await db.query( `INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_message', $1, datetime('now')) ON CONFLICT(key) DO UPDATE SET value = $1, updated_at = datetime('now')`, [message] ); } // Get current state const modeResult = await db.query(`SELECT value FROM system_settings WHERE key = 'maintenance_mode'`); const pendingResult = await db.query(`SELECT value FROM system_settings WHERE key = 'maintenance_pending'`); return res.json({ version: '1.0', data: { maintenance_mode: modeResult.rows[0]?.value === 'true', maintenance_pending: pendingResult.rows[0]?.value === 'true', message: message || 'System is under maintenance. Please try again later.', }, }); } catch (error: any) { console.error('Set maintenance mode error:', error); return res.status(500).json({ version: '1.0', error: 'INTERNAL_ERROR', message: 'Failed to set maintenance mode', }); } }