Add Export CSV button to Players page

- Exports all players with full subscription data
- Includes: discord_id, minecraft_username, minecraft_uuid, is_staff,
  tier_level, tier_name, status, mrr_value, is_lifetime,
  stripe_customer_id, created_at, updated_at
- Downloads as firefrost-players-YYYY-MM-DD.csv
- Properly escapes CSV values with quotes

Chronicler #69
This commit is contained in:
Claude
2026-04-08 08:41:17 +00:00
parent a13d9a2c66
commit 795020b55c
2 changed files with 59 additions and 0 deletions

View File

@@ -8,6 +8,64 @@ router.get('/', async (req, res) => {
res.render('admin/players/index', { title: 'Player Management', tiers: TIER_INFO });
});
// Export all players as CSV
router.get('/export', async (req, res) => {
try {
const { rows: players } = await db.query(`
SELECT
s.discord_id,
u.minecraft_username,
u.minecraft_uuid,
COALESCE(u.is_staff, false) as is_staff,
s.tier_level,
s.status,
s.mrr_value,
s.is_lifetime,
s.stripe_customer_id,
s.created_at,
s.updated_at
FROM subscriptions s
LEFT JOIN users u ON s.discord_id = u.discord_id
ORDER BY s.updated_at DESC
`);
// Build CSV
const headers = ['discord_id', 'minecraft_username', 'minecraft_uuid', 'is_staff', 'tier_level', 'tier_name', 'status', 'mrr_value', 'is_lifetime', 'stripe_customer_id', 'created_at', 'updated_at'];
const csvRows = [headers.join(',')];
for (const player of players) {
const tierName = TIER_INFO[player.tier_level]?.name || 'Unknown';
const row = [
player.discord_id || '',
player.minecraft_username || '',
player.minecraft_uuid || '',
player.is_staff ? 'true' : 'false',
player.tier_level || '',
tierName,
player.status || '',
player.mrr_value || '0',
player.is_lifetime ? 'true' : 'false',
player.stripe_customer_id || '',
player.created_at ? new Date(player.created_at).toISOString() : '',
player.updated_at ? new Date(player.updated_at).toISOString() : ''
].map(val => `"${String(val).replace(/"/g, '""')}"`);
csvRows.push(row.join(','));
}
const csv = csvRows.join('\n');
const filename = `firefrost-players-${new Date().toISOString().split('T')[0]}.csv`;
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.send(csv);
} catch (error) {
console.error('Export error:', error);
res.status(500).send('Error exporting players');
}
});
// HTMX Endpoint for the table body (Handles pagination, sorting, searching)
router.get('/table', async (req, res) => {
const page = parseInt(req.query.page) || 1;

View File

@@ -9,6 +9,7 @@
hx-target="#player-table-body">
<div class="space-x-2">
<a href="/admin/players/export" class="bg-gray-100 dark:bg-gray-700 px-4 py-2 rounded-md text-sm hover:bg-gray-200 dark:hover:bg-gray-600 inline-block">📤 Export CSV</a>
<button class="bg-gray-100 dark:bg-gray-700 px-4 py-2 rounded-md text-sm hover:bg-gray-200 dark:hover:bg-gray-600">📥 Import CSV</button>
</div>
</div>