const express = require('express'); const { MongoClient } = require('mongodb'); const cors = require('cors'); const path = require('path'); const app = express(); const PORT = process.env.PORT || 3001; // Middleware app.use(cors()); app.use(express.json()); // Serve static files from React build app.use(express.static(path.join(__dirname, '..', 'dist'))); // MongoDB connection - using host.docker.internal for Docker networking const mongoHost = process.env.MONGO_HOST || 'host.docker.internal'; const uri = `mongodb://admin:MongoPass2024!@${mongoHost}:27017/?authSource=admin`; const client = new MongoClient(uri); let db; let amigosCollection; const clients = new Set(); function sseHeaders(res) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders && res.flushHeaders(); } function sendEvent(res, event, data) { if (event) res.write(`event: ${event}\n`); res.write(`data: ${JSON.stringify(data)}\n\n`); } function broadcast(event, data) { for (const res of clients) { try { sendEvent(res, event, data); } catch (_) { // ignore write errors; client will be removed on close } } } // Connect to MongoDB async function connectDB() { try { await client.connect(); console.log('Connected to MongoDB'); db = client.db('testdb'); amigosCollection = db.collection('amigos'); } catch (error) { console.error('Error connecting to MongoDB:', error); // Retry connection after 5 seconds setTimeout(connectDB, 5000); } } // API Routes // Get all amigos app.get('/api/amigos', async (req, res) => { try { if (!amigosCollection) { return res.status(503).json({ error: 'Database not connected' }); } const amigos = await amigosCollection.find({}).toArray(); res.json(amigos); } catch (error) { res.status(500).json({ error: error.message }); } }); // Add new amigo app.post('/api/amigos', async (req, res) => { try { if (!amigosCollection) { return res.status(503).json({ error: 'Database not connected' }); } const { nombre } = req.body; if (!nombre) { return res.status(400).json({ error: 'Nombre es requerido' }); } const result = await amigosCollection.insertOne({ nombre }); res.status(201).json({ message: 'Amigo agregado exitosamente', id: result.insertedId, nombre }); // Notify SSE clients about the new friend broadcast('amigoAdded', { id: result.insertedId, nombre }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Server-Sent Events for realtime updates app.get('/api/events', async (req, res) => { sseHeaders(res); clients.add(res); // Send initial snapshot try { if (amigosCollection) { const amigos = await amigosCollection.find({}).toArray(); sendEvent(res, 'init', { amigos }); } else { sendEvent(res, 'init', { amigos: [] }); } } catch (_) { sendEvent(res, 'init', { amigos: [] }); } req.on('close', () => { clients.delete(res); try { res.end(); } catch (_) {} }); }); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'ok', mongodb: amigosCollection ? 'connected' : 'disconnected' }); }); // Serve React app for all other routes app.use((req, res) => { res.sendFile(path.join(__dirname, '..', 'dist', 'index.html')); }); // Start server connectDB().then(() => { app.listen(PORT, '0.0.0.0', () => { console.log(`Server running on http://0.0.0.0:${PORT}`); console.log(`MongoDB host: ${mongoHost}`); }); });