import express from 'express'; import cors from 'cors'; import { resetDatabase, seedDatabase, seedDatabaseLite, seedDatabaseNoLocations } from './index.js'; import { pool } from './db.js'; // --- Console Interception for Streaming --- const originalLog = console.log; const originalError = console.error; let streamCallback = null; console.log = (...args) => { originalLog.apply(console, args); if (streamCallback) { const msg = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg) ).join(' '); streamCallback('INFO', msg); } }; console.error = (...args) => { originalError.apply(console, args); if (streamCallback) { const msg = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg) ).join(' '); streamCallback('ERROR', msg); } }; // ------------------------------------------ const app = express(); const port = process.env.PORT || 8080; app.use(cors()); app.use(express.json()); // Root Handler app.get('/', (req, res) => { res.json({ message: "🌱 GoHorseJobs Seeder API is running! (Node.js Mode)", version: "1.0.0", ip: req.ip, endpoints: { health: "/health", seed: "POST /seed", reset: "POST /reset", stream: "GET /seed/stream" } }); }); // Middleware to log requests app.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); }); // Health check app.get('/health', async (req, res) => { try { const result = await pool.query('SELECT 1'); res.json({ status: 'ok', database: 'connected', version: '1.0.0' }); } catch (error) { console.error('Health check failed:', error); res.status(500).json({ status: 'error', database: 'disconnected', error: error.message }); } }); // Seed endpoint // Options: type = 'full' | 'lite' | 'no-locations' // Seed endpoint with SSE app.get('/seed/stream', async (req, res) => { if (isSeeding) { // If already seeding, just tell them. // Ideally we would share the same stream but for simplicity we block new connections or just tell them to wait. // Actually, let's allow connecting to the *log stream* even if unrelated? No, let's keep it simple. // We will just expose an endpoint that TRIGGERS the seed AND streams the output. } // Set headers for SSE res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); const sendLog = (msg) => { // Handle "INFO" or "ERROR" prefix if passed from interceptor, or just raw string res.write(`data: ${JSON.stringify({ message: msg, timestamp: new Date().toISOString() })}\n\n`); }; if (isSeeding) { sendLog('âš ī¸ Seeding already in progress...'); // allow them to watch? For now blocking new triggers. return res.end(); } const type = req.query.type || 'full'; isSeeding = true; // Hook up the stream callback streamCallback = (level, text) => { const icon = level === 'ERROR' ? '❌' : 'â„šī¸'; sendLog(`${icon} ${text}`); }; // sendLog(`🚀 Starting manual seed (${type})...`); // This will be logged by console.log inside functions anyway try { if (type === 'lite') { await seedDatabaseLite(); } else if (type === 'no-locations') { await seedDatabaseNoLocations(); } else { await seedDatabase(); } res.write(`data: ${JSON.stringify({ type: 'done' })}\n\n`); } catch (error) { console.error('Manual seed fatal error:', error); // Captured by interceptor res.write(`data: ${JSON.stringify({ type: 'error', error: error.message })}\n\n`); } finally { isSeeding = false; streamCallback = null; // Detach res.end(); } }); // Keep legacy POST for compatibility if needed, using the new shared function logic app.post('/seed', async (req, res) => { // ... keep existing logic but warn it's deprecated? // Actually let's just keep it simple. if (isSeeding) return res.status(409).json({ error: 'Seeding in progress' }); isSeeding = true; res.json({ message: 'Seeding started' }); try { await seedDatabase(); } catch (e) { console.error(e); } finally { isSeeding = false; } }); // Reset endpoint app.post('/reset', async (req, res) => { if (isSeeding) { return res.status(409).json({ error: 'Seeding/Reset in progress' }); } const password = req.body?.password; if (process.env.SEED_PASSWORD && password !== process.env.SEED_PASSWORD) { return res.status(401).json({ error: 'Unauthorized' }); } isSeeding = true; res.json({ message: 'Reset started' }); try { console.log('🚀 Starting manual reset...'); await resetDatabase(); console.log('✅ Manual reset completed'); } catch (error) { console.error('❌ Manual reset failed:', error); } finally { isSeeding = false; } }); let isSeeding = false; app.listen(port, () => { console.log(`🌱 GoHorseJobs Seeder API listening on port ${port}`); console.log(` Health check: http://localhost:${port}/health`); });