dataflow/api/server.js
Paul Trowbridge eb50704ca0 Add React UI and backend enhancements for dataflow
- Add full React + Vite UI (src/pages: Sources, Rules, Mappings, Records, Import)
- Sidebar layout with source selector persisted to localStorage
- Sources: unified field table with Dedup/In-view checkboxes, CSV suggest, generate dfv view
- Rules: extract/replace function types, regex flags, input field picklist, test results
- Mappings: unmapped values with sample records, inline key/value editor, edit existing mappings
- Records: expanded row shows per-rule extraction and mapping output breakdown
- Import: drag-drop CSV, transform/reprocess buttons, import history
- Backend: add flags/function_type to rules, get_unmapped_values with samples, generate_source_view, fields endpoint, reprocess endpoint
- database/functions.sql: apply_transformations supports replace mode and flags; generate_source_view builds typed dfv views
- Server bound to 0.0.0.0, SPA fallback for client-side routing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 00:35:33 -04:00

108 lines
2.8 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 }));
// 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;