dataflow/api/server.js
Paul Trowbridge ef6c6bbbb8 Add Stacks feature: multi-source union with running balance and calibration
- database/queries/stacks.sql: tables, functions for create/update/delete/calibrate/generate view
- api/routes/stacks.js: REST endpoints for stacks and stack sources
- api/server.js: register stacks router
- ui/src/api.js: stacks API methods
- ui/src/App.jsx: Stacks page route and nav entry
- ui/src/pages/Stacks.jsx: full UI for stack management, source mapping, calibration

Note: SQL deployment pending fix for balance_offset column and calibrate_balance signature

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 15:48:42 -04:00

114 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');
const stacksRoutes = require('./routes/stacks');
// 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));
app.use('/api/stacks', stacksRoutes(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;