- 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
176 lines
5.3 KiB
JavaScript
176 lines
5.3 KiB
JavaScript
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`);
|
||
});
|