- 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>
108 lines
2.8 KiB
JavaScript
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;
|