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:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user