- Express auth middleware checks Authorization: Basic header on all /api routes using bcrypt against LOGIN_USER/LOGIN_PASSWORD_HASH in .env - React login screen shown before app loads, stores credentials in memory, sends them with every API request, clears and returns to login on 401 - Logout button in sidebar header - manage.py option 9: set login credentials (bcrypt via node, writes to .env) - manage.py status shows whether login credentials are configured Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
3.0 KiB
JavaScript
112 lines
3.0 KiB
JavaScript
/**
|
|
* Dataflow API Server
|
|
* Simple REST API for data transformation
|
|
*/
|
|
|
|
require('dotenv').config();
|
|
const express = require('express');
|
|
const { Pool } = require('pg');
|
|
|
|
const app = express();
|
|
const PORT = process.env.API_PORT || 3000;
|
|
|
|
// Database connection
|
|
const pool = new Pool({
|
|
host: process.env.DB_HOST,
|
|
port: process.env.DB_PORT,
|
|
database: process.env.DB_NAME,
|
|
user: process.env.DB_USER,
|
|
password: process.env.DB_PASSWORD
|
|
});
|
|
|
|
// Middleware
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
// Auth — protect all /api routes (health and static UI are exempt)
|
|
const auth = require('./middleware/auth');
|
|
app.use('/api', auth);
|
|
|
|
// Serve UI
|
|
const path = require('path');
|
|
app.use(express.static(path.join(__dirname, '../public')));
|
|
|
|
// Set search path for all queries
|
|
pool.on('connect', (client) => {
|
|
client.query('SET search_path TO dataflow, public');
|
|
});
|
|
|
|
// Test database connection
|
|
pool.query('SELECT NOW()', (err, res) => {
|
|
if (err) {
|
|
console.error('Database connection error:', err);
|
|
process.exit(1);
|
|
}
|
|
console.log('✓ Database connected');
|
|
});
|
|
|
|
//------------------------------------------------------
|
|
// Routes
|
|
//------------------------------------------------------
|
|
|
|
// Import route modules
|
|
const sourcesRoutes = require('./routes/sources');
|
|
const rulesRoutes = require('./routes/rules');
|
|
const mappingsRoutes = require('./routes/mappings');
|
|
const recordsRoutes = require('./routes/records');
|
|
|
|
// Mount routes
|
|
app.use('/api/sources', sourcesRoutes(pool));
|
|
app.use('/api/rules', rulesRoutes(pool));
|
|
app.use('/api/mappings', mappingsRoutes(pool));
|
|
app.use('/api/records', recordsRoutes(pool));
|
|
|
|
// Health check
|
|
app.get('/health', (req, res) => {
|
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
});
|
|
|
|
// Root endpoint
|
|
app.get('/', (req, res) => {
|
|
res.json({
|
|
name: 'Dataflow API',
|
|
version: '0.1.0',
|
|
endpoints: {
|
|
sources: '/api/sources',
|
|
rules: '/api/rules',
|
|
mappings: '/api/mappings',
|
|
records: '/api/records',
|
|
health: '/health'
|
|
}
|
|
});
|
|
});
|
|
|
|
// Error handler
|
|
app.use((err, req, res, next) => {
|
|
console.error('Error:', err);
|
|
res.status(err.status || 500).json({
|
|
error: err.message || 'Internal server error',
|
|
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
|
|
});
|
|
});
|
|
|
|
// SPA fallback — serve index.html for any non-API route
|
|
app.use((req, res, next) => {
|
|
if (req.path.startsWith('/api')) return next();
|
|
res.sendFile(path.join(__dirname, '../public/index.html'));
|
|
});
|
|
|
|
// 404 handler (API routes only)
|
|
app.use((req, res) => {
|
|
res.status(404).json({ error: 'Endpoint not found' });
|
|
});
|
|
|
|
// Start server
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`✓ Dataflow API listening on port ${PORT}`);
|
|
console.log(` Health: http://localhost:${PORT}/health`);
|
|
console.log(` API: http://localhost:${PORT}/api/sources`);
|
|
});
|
|
|
|
module.exports = app;
|