gohorsejobs/seeder-api/src/server.js
Yamamoto 5155fa853d fix: Auth URL race, Seeder Reset 500, and UI confirmation modal
- auth.ts: await initConfig() before refreshSession to fix localhost fallback
- server.js: optional chaining req.body?.password for reset endpoint
- seeder/page.tsx: replace confirm() with elegant AlertDialog for reset
2026-01-03 14:21:29 -03:00

176 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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`);
});