const express = require('express'); const { authenticateToken } = require('../middleware/auth'); const db = require('../database'); const { stringify } = require('csv-stringify/sync'); const { parse } = require('csv-parse/sync'); const router = express.Router(); // Get all games with optional filters router.get('/', (req, res) => { try { const { enabled, minPlayers, maxPlayers, playerCount, drawing, length, familyFriendly, pack } = req.query; let query = 'SELECT * FROM games WHERE 1=1'; const params = []; if (enabled !== undefined) { query += ' AND enabled = ?'; params.push(enabled === 'true' ? 1 : 0); } if (playerCount) { const count = parseInt(playerCount); query += ' AND min_players <= ? AND max_players >= ?'; params.push(count, count); } if (drawing === 'only') { query += ' AND game_type = ?'; params.push('Drawing'); } else if (drawing === 'exclude') { query += ' AND (game_type != ? OR game_type IS NULL)'; params.push('Drawing'); } if (length) { if (length === 'short') { query += ' AND (length_minutes <= 15 OR length_minutes IS NULL)'; } else if (length === 'medium') { query += ' AND length_minutes > 15 AND length_minutes <= 25'; } else if (length === 'long') { query += ' AND length_minutes > 25'; } } if (familyFriendly !== undefined) { query += ' AND family_friendly = ?'; params.push(familyFriendly === 'true' ? 1 : 0); } if (pack) { query += ' AND pack_name = ?'; params.push(pack); } query += ' ORDER BY pack_name, title'; const stmt = db.prepare(query); const games = stmt.all(...params); res.json(games); } catch (error) { res.status(500).json({ error: error.message }); } }); // Get single game by ID router.get('/:id', (req, res) => { try { const game = db.prepare('SELECT * FROM games WHERE id = ?').get(req.params.id); if (!game) { return res.status(404).json({ error: 'Game not found' }); } res.json(game); } catch (error) { res.status(500).json({ error: error.message }); } }); // Create new game (admin only) router.post('/', authenticateToken, (req, res) => { try { const { pack_name, title, min_players, max_players, length_minutes, has_audience, family_friendly, game_type, secondary_type } = req.body; if (!pack_name || !title || !min_players || !max_players) { return res.status(400).json({ error: 'Missing required fields' }); } const stmt = db.prepare(` INSERT INTO games ( pack_name, title, min_players, max_players, length_minutes, has_audience, family_friendly, game_type, secondary_type ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `); const result = stmt.run( pack_name, title, min_players, max_players, length_minutes || null, has_audience ? 1 : 0, family_friendly ? 1 : 0, game_type || null, secondary_type || null ); const newGame = db.prepare('SELECT * FROM games WHERE id = ?').get(result.lastInsertRowid); res.status(201).json(newGame); } catch (error) { res.status(500).json({ error: error.message }); } }); // Update game (admin only) router.put('/:id', authenticateToken, (req, res) => { try { const { pack_name, title, min_players, max_players, length_minutes, has_audience, family_friendly, game_type, secondary_type, enabled } = req.body; const stmt = db.prepare(` UPDATE games SET pack_name = COALESCE(?, pack_name), title = COALESCE(?, title), min_players = COALESCE(?, min_players), max_players = COALESCE(?, max_players), length_minutes = ?, has_audience = COALESCE(?, has_audience), family_friendly = COALESCE(?, family_friendly), game_type = ?, secondary_type = ?, enabled = COALESCE(?, enabled) WHERE id = ? `); const result = stmt.run( pack_name || null, title || null, min_players || null, max_players || null, length_minutes !== undefined ? length_minutes : null, has_audience !== undefined ? (has_audience ? 1 : 0) : null, family_friendly !== undefined ? (family_friendly ? 1 : 0) : null, game_type !== undefined ? game_type : null, secondary_type !== undefined ? secondary_type : null, enabled !== undefined ? (enabled ? 1 : 0) : null, req.params.id ); if (result.changes === 0) { return res.status(404).json({ error: 'Game not found' }); } const updatedGame = db.prepare('SELECT * FROM games WHERE id = ?').get(req.params.id); res.json(updatedGame); } catch (error) { res.status(500).json({ error: error.message }); } }); // Delete game (admin only) router.delete('/:id', authenticateToken, (req, res) => { try { const stmt = db.prepare('DELETE FROM games WHERE id = ?'); const result = stmt.run(req.params.id); if (result.changes === 0) { return res.status(404).json({ error: 'Game not found' }); } res.json({ message: 'Game deleted successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Toggle game enabled status (admin only) router.patch('/:id/toggle', authenticateToken, (req, res) => { try { const game = db.prepare('SELECT enabled FROM games WHERE id = ?').get(req.params.id); if (!game) { return res.status(404).json({ error: 'Game not found' }); } const newStatus = game.enabled === 1 ? 0 : 1; db.prepare('UPDATE games SET enabled = ? WHERE id = ?').run(newStatus, req.params.id); const updatedGame = db.prepare('SELECT * FROM games WHERE id = ?').get(req.params.id); res.json(updatedGame); } catch (error) { res.status(500).json({ error: error.message }); } }); // Get list of unique pack names router.get('/meta/packs', (req, res) => { try { const packs = db.prepare(` SELECT pack_name, COUNT(*) as game_count, SUM(enabled) as enabled_count FROM games GROUP BY pack_name ORDER BY pack_name `).all(); res.json(packs); } catch (error) { res.status(500).json({ error: error.message }); } }); // Toggle entire pack (admin only) router.patch('/packs/:name/toggle', authenticateToken, (req, res) => { try { const { enabled } = req.body; if (enabled === undefined) { return res.status(400).json({ error: 'enabled status required' }); } const stmt = db.prepare('UPDATE games SET enabled = ? WHERE pack_name = ?'); const result = stmt.run(enabled ? 1 : 0, req.params.name); res.json({ message: `Pack ${enabled ? 'enabled' : 'disabled'} successfully`, gamesAffected: result.changes }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Export games to CSV (admin only) router.get('/export/csv', authenticateToken, (req, res) => { try { const games = db.prepare('SELECT * FROM games ORDER BY pack_name, title').all(); const csvData = games.map(game => ({ 'Game Pack': game.pack_name, 'Game Title': game.title, 'Min. Players': game.min_players, 'Max. Players': game.max_players, 'Length': game.length_minutes ? `${game.length_minutes} minutes` : '????', 'Audience': game.has_audience ? 'Yes' : 'No', 'Family Friendly?': game.family_friendly ? 'Yes' : 'No', 'Game Type': game.game_type || '', 'Secondary Type': game.secondary_type || '' })); const csv = stringify(csvData, { header: true }); res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', 'attachment; filename=games-export.csv'); res.send(csv); } catch (error) { res.status(500).json({ error: error.message }); } }); // Import games from CSV (admin only) router.post('/import/csv', authenticateToken, (req, res) => { try { const { csvData, mode } = req.body; // mode: 'append' or 'replace' if (!csvData) { return res.status(400).json({ error: 'CSV data required' }); } const records = parse(csvData, { columns: true, skip_empty_lines: true, trim: true }); if (mode === 'replace') { db.prepare('DELETE FROM games').run(); } const insert = db.prepare(` INSERT INTO games ( pack_name, title, min_players, max_players, length_minutes, has_audience, family_friendly, game_type, secondary_type, enabled ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1) `); const insertMany = db.transaction((games) => { for (const game of games) { insert.run( game['Game Pack'], game['Game Title'], parseInt(game['Min. Players']) || 1, parseInt(game['Max. Players']) || 8, parseLengthMinutes(game['Length']), parseBoolean(game['Audience']), parseBoolean(game['Family Friendly?']), game['Game Type'] || null, game['Secondary Type'] || null ); } }); insertMany(records); res.json({ message: `Successfully imported ${records.length} games`, count: records.length, mode }); } catch (error) { res.status(500).json({ error: error.message }); } }); function parseLengthMinutes(lengthStr) { if (!lengthStr || lengthStr === '????' || lengthStr === '?') { return null; } const match = lengthStr.match(/(\d+)/); return match ? parseInt(match[1]) : null; } function parseBoolean(value) { if (!value || value === '?' || value === '????') { return 0; } return value.toLowerCase() === 'yes' ? 1 : 0; } module.exports = router;