209 lines
6.5 KiB
JavaScript
209 lines
6.5 KiB
JavaScript
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'}`);
|
|
});
|
|
});
|