const express = require('express'); const fs = require('fs').promises; const path = require('path'); const app = express(); const PORT = process.env.PORT || 3000; const DATA_DIR = process.env.DATA_DIR || './data'; const REGISTRATIONS_FILE = path.join(DATA_DIR, 'registrations.json'); // Middleware app.use(express.json()); app.use(express.static('.')); // Ensure data directory exists async function ensureDataDir() { try { await fs.mkdir(DATA_DIR, { recursive: true }); try { await fs.access(REGISTRATIONS_FILE); } catch { await fs.writeFile(REGISTRATIONS_FILE, '[]'); } } catch (error) { console.error('Error creating data directory:', error); } } // Load registrations async function loadRegistrations() { try { const data = await fs.readFile(REGISTRATIONS_FILE, 'utf8'); return JSON.parse(data); } catch { return []; } } // Save registrations async function saveRegistrations(registrations) { await fs.writeFile(REGISTRATIONS_FILE, JSON.stringify(registrations, null, 2)); } // Registration endpoint app.post('/api/register', async (req, res) => { try { const { firstName, lastName, email, location, role, interests, contribute, message } = req.body; // Validation if (!firstName || !lastName || !email) { return res.status(400).json({ error: 'First name, last name, and email are required' }); } if (!email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) { return res.status(400).json({ error: 'Please provide a valid email address' }); } // Load existing registrations const registrations = await loadRegistrations(); // Check for duplicate email if (registrations.some(r => r.email.toLowerCase() === email.toLowerCase())) { return res.status(400).json({ error: 'This email is already registered' }); } // Create new registration const registration = { id: Date.now().toString(36) + Math.random().toString(36).substr(2), firstName: firstName.trim(), lastName: lastName.trim(), email: email.toLowerCase().trim(), location: location?.trim() || '', role: role || '', interests: interests || [], contribute: contribute || '', message: message?.trim() || '', registeredAt: new Date().toISOString(), ipAddress: req.ip || req.connection.remoteAddress }; // Save registration registrations.push(registration); await saveRegistrations(registrations); console.log(`New registration: ${registration.firstName} ${registration.lastName} <${registration.email}>`); res.json({ success: true, message: 'Registration successful', id: registration.id }); } catch (error) { console.error('Registration error:', error); res.status(500).json({ error: 'An error occurred. Please try again.' }); } }); // Admin endpoint to view registrations (protected by simple token) app.get('/api/registrations', async (req, res) => { const token = req.headers['x-admin-token'] || req.query.token; const adminToken = process.env.ADMIN_TOKEN || 'worldplay-admin-2026'; if (token !== adminToken) { return res.status(401).json({ error: 'Unauthorized' }); } try { const registrations = await loadRegistrations(); res.json({ count: registrations.length, registrations: registrations.map(r => ({ ...r, ipAddress: undefined // Don't expose IP in admin view })) }); } catch (error) { res.status(500).json({ error: 'Failed to load registrations' }); } }); // Export registrations as CSV app.get('/api/registrations/export', async (req, res) => { const token = req.headers['x-admin-token'] || req.query.token; const adminToken = process.env.ADMIN_TOKEN || 'worldplay-admin-2026'; if (token !== adminToken) { return res.status(401).json({ error: 'Unauthorized' }); } try { const registrations = await loadRegistrations(); const headers = ['ID', 'First Name', 'Last Name', 'Email', 'Location', 'Role', 'Interests', 'Contribute', 'Message', 'Registered At']; const rows = registrations.map(r => [ r.id, r.firstName, r.lastName, r.email, r.location, r.role, Array.isArray(r.interests) ? r.interests.join('; ') : r.interests, r.contribute, r.message.replace(/"/g, '""'), r.registeredAt ]); const csv = [ headers.join(','), ...rows.map(row => row.map(cell => `"${cell}"`).join(',')) ].join('\n'); res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', 'attachment; filename=worldplay-registrations.csv'); res.send(csv); } catch (error) { res.status(500).json({ error: 'Failed to export registrations' }); } }); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Stats endpoint app.get('/api/stats', async (req, res) => { try { const registrations = await loadRegistrations(); const stats = { totalRegistrations: registrations.length, byRole: {}, byInterest: {}, byContribute: {} }; registrations.forEach(r => { // Count by role if (r.role) { stats.byRole[r.role] = (stats.byRole[r.role] || 0) + 1; } // Count by interest if (Array.isArray(r.interests)) { r.interests.forEach(interest => { stats.byInterest[interest] = (stats.byInterest[interest] || 0) + 1; }); } // Count by contribute if (r.contribute) { stats.byContribute[r.contribute] = (stats.byContribute[r.contribute] || 0) + 1; } }); res.json(stats); } catch (error) { res.status(500).json({ error: 'Failed to calculate stats' }); } }); // Start server ensureDataDir().then(() => { app.listen(PORT, '0.0.0.0', () => { console.log(`WORLDPLAY server running on port ${PORT}`); console.log(`Admin token: ${process.env.ADMIN_TOKEN || 'worldplay-admin-2026'}`); }); });