// ...existing code... // Top-level error handlers for debugging process.on('uncaughtException', err => { console.error('Uncaught Exception:', err); }); process.on('unhandledRejection', err => { console.error('Unhandled Rejection:', err); }); const express = require('express'); const bodyParser = require('body-parser'); const nodemailer = require('nodemailer'); const smtpConfig = require('./functions/smtpConfig'); const cors = require('cors'); const admin = require('firebase-admin'); let serviceAccount; try { serviceAccount = require('./serviceAccount.json'); } catch (err) { console.error('Could not load serviceAccount.json:', err); } function initializeFirebaseAdmin() { if (admin.apps && admin.apps.length > 0) { console.log('Firebase Admin already initialized.'); return; } try { if (!serviceAccount) throw new Error('serviceAccount.json not loaded'); admin.initializeApp({ credential: admin.credential.cert(serviceAccount) }); console.log('Firebase Admin initialized.'); } catch (err) { console.error('Error initializing Firebase Admin:', err); if (err.stack) { console.error('Stack trace:', err.stack); } throw err; } } initializeFirebaseAdmin(); const app = express(); app.use(bodyParser.json()); app.use(cors()); console.log('Express app initialized.'); // API endpoint: Get all events from Firestore, with optional city and category filters app.get('/events', async (req, res) => { try { const { city, category } = req.query; let query = admin.firestore().collection('activities'); // If category is provided, filter by genre/type (case-insensitive) if (category && category.toLowerCase() !== 'all') { query = query.where('genre', '==', category); } const snapshot = await query.get(); let events = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); // If city is provided, filter by city (case-insensitive, partial match) if (city && city.toLowerCase() !== 'anywhere') { const cityLower = city.toLowerCase(); events = events.filter(event => (event.city && event.city.toLowerCase().includes(cityLower)) || (event.venue && event.venue.toLowerCase().includes(cityLower)) ); } // Map Ticketmaster-style events to normalized structure for frontend events = events.map(event => ({ id: event.id, title: event.title || event.name || '', artist: event.artist || (event.classifications && event.classifications[0]?.genre?.name) || '', image: event.image || (event.images && event.images[0]?.url) || '', date: event.date || (event.dates && event.dates.start?.localDate) || '', time: event.time || (event.dates && event.dates.start?.localTime) || '', venue: event.venue || (event._embedded?.venues && event._embedded.venues[0]?.name) || '', city: event.city || (event._embedded?.venues && event._embedded.venues[0]?.city?.name) || '', url: event.url || '', info: event.info || '', })); res.json({ activities: events }); } catch (err) { console.error('Error fetching events:', err); res.status(500).json({ error: 'Failed to fetch events' }); } }); // Helper: Get personalized activities for a user async function getPersonalizedActivities(userId) { try { const userRef = admin.firestore().collection('users').doc(userId); const userDoc = await userRef.get(); if (!userDoc.exists) return []; const userData = userDoc.data(); let activitiesQuery = admin.firestore().collection('activities'); if (userData.favorites && userData.favorites.length > 0) { activitiesQuery = activitiesQuery.where('artist', 'in', userData.favorites); } const snapshot = await activitiesQuery.get(); return snapshot.docs.map(doc => doc.data()); } catch (err) { console.error('Error fetching personalized activities:', err); return []; } } // Send email async function sendEmail(to, subject, html) { const transporter = nodemailer.createTransport(smtpConfig); const mailOptions = { from: smtpConfig.auth.user, to, subject, html, }; try { const info = await transporter.sendMail(mailOptions); console.log('Email sent:', info); return info; } catch (error) { console.error('Error sending email:', error); throw error; } } // Manual trigger endpoint app.post('/send-weekly-mail', async (req, res) => { console.log('Received /send-weekly-mail request:', req.body); const { email } = req.body; if (email !== 'egelibilim87@gmail.com') { console.warn('Forbidden email attempt:', email); return res.status(403).send('Forbidden'); } try { let userDoc, userId; const usersSnapshot = await admin.firestore().collection('users').where('email', '==', email).get(); if (!usersSnapshot.empty) { userDoc = usersSnapshot.docs[0]; userId = userDoc.id; console.log('User found in Firestore:', email); } else { // Try to get user from Firebase Auth try { const userRecord = await admin.auth().getUserByEmail(email); userId = userRecord.uid; console.log('User found in Firebase Auth:', email); } catch (authErr) { console.warn('User not found in Firestore or Auth for email:', email); return res.status(404).send('User not found'); } } // Fetch activities after userId is set const activities = await getPersonalizedActivities(userId); const userRef = admin.firestore().collection('users').doc(userId); const userSnap = await userRef.get(); const userData = userSnap.exists ? userSnap.data() : {}; const userCity = userData.city || ''; // Filter activities by user's city let filtered = activities.filter(act => act.city && act.city.toLowerCase() === userCity.toLowerCase()); // Sort by relevance (example: prioritize favorites, then by date) if (userData.favorites && userData.favorites.length > 0) { filtered = filtered.sort((a, b) => { const aFav = userData.favorites.includes(a.artist) ? 1 : 0; const bFav = userData.favorites.includes(b.artist) ? 1 : 0; return bFav - aFav || new Date(a.date) - new Date(b.date); }); } else { filtered = filtered.sort((a, b) => new Date(a.date) - new Date(b.date)); } // Limit to 15 events filtered = filtered.slice(0, 15); if (filtered.length === 0) { console.log('No activities to send for user:', email); return res.status(200).send('No activities to send'); } // Build modern HTML email with greeting and responsive grid let html = `

Hello ${userData.name || ''}! Here are some activities you might get interested in.

`; filtered.forEach(act => { html += `
${act.title}

${act.title}

${act.artist || act.genre || ''}
${act.date}${act.time ? ' - ' + act.time : ''}
${act.venue || ''}
`; }); html += `
You are receiving this email because of your activity on sway.app.
`; console.log(`Attempting to send email to ${email}...`); try { await sendEmail(email, 'Your Weekly Activities', html); console.log(`Email successfully sent to ${email}`); return res.status(200).send('Weekly mail sent'); } catch (mailErr) { console.error(`Failed to send email to ${email}:`, mailErr); return res.status(500).send('Error sending mail'); } } catch (err) { console.error('Manual mail error:', err); return res.status(500).send('Error sending mail'); } }); // Event page route for sharing app.get('/event/:id', async (req, res) => { const eventId = req.params.id; try { const eventSnap = await admin.firestore().collection('activities').doc(eventId).get(); if (!eventSnap.exists) { return res.status(404).send('

Event not found

'); } const act = eventSnap.data(); res.send(` ${act.title} - Sway
${act.title}

${act.title}

${act.artist || act.genre || ''}
${act.date}${act.time ? ' - ' + act.time : ''}
${act.venue || ''}
Share this event: https://api.swayapp.digital/event/${act.id}
`); } catch (err) { res.status(500).send('

Error loading event

'); } }); app.get('/', (req, res) => { const fs = require('fs'); const path = require('path'); const filePath = path.join(__dirname, 'server.js'); fs.readFile(filePath, 'utf8', (err, data) => { if (err) { res.status(500).send('Error reading server.js'); } else { res.set('Content-Type', 'text/plain'); res.send(data); } }); }); const PORT = process.env.PORT || 4000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });