dataflow/api/server.js
Paul Trowbridge 2c573a5eeb Add login authentication with Basic Auth
- 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>
2026-04-05 17:41:07 -04:00

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;