we're about to port the chrome-extension. everything else mostly works

This commit is contained in:
cottongin
2025-10-30 13:27:55 -04:00
parent 2db707961c
commit db2a8abe66
29 changed files with 2490 additions and 562 deletions

View File

@@ -73,6 +73,91 @@ router.get('/', (req, res) => {
}
});
// Get all packs with their favor bias (MUST be before /:id)
router.get('/packs', (req, res) => {
try {
const packs = db.prepare('SELECT * FROM packs ORDER BY name').all();
res.json(packs);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get pack metadata (MUST be before /:id)
router.get('/meta/packs', (req, res) => {
try {
const packs = db.prepare(`
SELECT
pack_name as name,
COUNT(*) as total_count,
SUM(CASE WHEN enabled = 1 THEN 1 ELSE 0 END) as enabled_count,
SUM(play_count) as total_plays
FROM games
GROUP BY pack_name
ORDER BY pack_name
`).all();
res.json(packs);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Export games as CSV (MUST be before /:id)
router.get('/export/csv', authenticateToken, (req, res) => {
try {
const games = db.prepare('SELECT * FROM games ORDER BY pack_name, title').all();
const records = games.map(game => ({
'Pack Name': game.pack_name,
'Title': game.title,
'Min Players': game.min_players,
'Max Players': game.max_players,
'Length (minutes)': game.length_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(records, { header: true });
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename="jackbox-games.csv"');
res.send(csv);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Set favor bias for a pack (MUST be before /:id)
router.patch('/packs/:name/favor', authenticateToken, (req, res) => {
try {
const { favor_bias } = req.body;
// Validate favor_bias value
if (![1, -1, 0].includes(favor_bias)) {
return res.status(400).json({ error: 'favor_bias must be 1 (favor), -1 (disfavor), or 0 (neutral)' });
}
// Update pack favor bias
const packStmt = db.prepare('UPDATE packs SET favor_bias = ? WHERE name = ?');
const packResult = packStmt.run(favor_bias, req.params.name);
if (packResult.changes === 0) {
// Pack doesn't exist, create it
const insertStmt = db.prepare('INSERT INTO packs (name, favor_bias) VALUES (?, ?)');
insertStmt.run(req.params.name, favor_bias);
}
res.json({
message: 'Pack favor bias updated successfully',
favor_bias
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get single game by ID
router.get('/:id', (req, res) => {
try {
@@ -224,25 +309,6 @@ router.patch('/:id/toggle', authenticateToken, (req, res) => {
}
});
// 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 {
@@ -264,33 +330,6 @@ router.patch('/packs/:name/toggle', authenticateToken, (req, res) => {
}
});
// 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 {
@@ -360,5 +399,31 @@ function parseBoolean(value) {
return value.toLowerCase() === 'yes' ? 1 : 0;
}
// Set favor bias for a game (1 = favor, -1 = disfavor, 0 = neutral)
router.patch('/:id/favor', authenticateToken, (req, res) => {
try {
const { favor_bias } = req.body;
// Validate favor_bias value
if (![1, -1, 0].includes(favor_bias)) {
return res.status(400).json({ error: 'favor_bias must be 1 (favor), -1 (disfavor), or 0 (neutral)' });
}
const stmt = db.prepare('UPDATE games SET favor_bias = ? WHERE id = ?');
const result = stmt.run(favor_bias, req.params.id);
if (result.changes === 0) {
return res.status(404).json({ error: 'Game not found' });
}
res.json({
message: 'Favor bias updated successfully',
favor_bias
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;