/** * Rules Routes * Manage transformation rules */ const express = require('express'); const { lit } = require('../lib/sql'); module.exports = (pool) => { const router = express.Router(); // List all rules for a source router.get('/source/:source_name', async (req, res, next) => { try { const result = await pool.query(`SELECT * FROM list_rules(${lit(req.params.source_name)})`); res.json(result.rows); } catch (err) { next(err); } }); // Preview an ad-hoc pattern against real records (no saved rule needed) router.get('/preview', async (req, res, next) => { try { const { source, field, pattern, flags, function_type = 'extract', replace_value = '', limit = 20 } = req.query; if (!source || !field || !pattern) { return res.status(400).json({ error: 'source, field, and pattern are required' }); } const result = await pool.query( `SELECT * FROM preview_rule(${lit(source)}, ${lit(field)}, ${lit(pattern)}, ${lit(flags || '')}, ${lit(function_type)}, ${lit(replace_value)}, ${lit(parseInt(limit))})` ); res.json(result.rows); } catch (err) { next(err); } }); // Test a rule against real records router.get('/:id/test', async (req, res, next) => { try { const { limit = 20 } = req.query; const result = await pool.query( `SELECT * FROM test_rule(${lit(parseInt(req.params.id))}, ${lit(parseInt(limit))})` ); if (result.rows.length === 0) return res.status(404).json({ error: 'Rule not found' }); res.json(result.rows[0]); } catch (err) { next(err); } }); // Get single rule router.get('/:id', async (req, res, next) => { try { const result = await pool.query(`SELECT * FROM get_rule(${lit(parseInt(req.params.id))})`); if (result.rows.length === 0) return res.status(404).json({ error: 'Rule not found' }); res.json(result.rows[0]); } catch (err) { next(err); } }); // Create rule router.post('/', async (req, res, next) => { try { const { source_name, name, field, pattern, output_field, function_type, flags, replace_value, enabled, retain, sequence } = req.body; if (!source_name || !name || !field || !pattern || !output_field) { return res.status(400).json({ error: 'Missing required fields: source_name, name, field, pattern, output_field' }); } if (function_type && !['extract', 'replace'].includes(function_type)) { return res.status(400).json({ error: 'function_type must be "extract" or "replace"' }); } const result = await pool.query( `SELECT * FROM create_rule(${lit(source_name)}, ${lit(name)}, ${lit(field)}, ${lit(pattern)}, ${lit(output_field)}, ${lit(function_type || 'extract')}, ${lit(flags || '')}, ${lit(replace_value || '')}, ${lit(enabled !== false)}, ${lit(retain === true)}, ${lit(sequence || 0)})` ); res.status(201).json(result.rows[0]); } catch (err) { if (err.code === '23505') return res.status(409).json({ error: 'Rule already exists for this source' }); if (err.code === '23503') return res.status(404).json({ error: 'Source not found' }); next(err); } }); // Update rule router.put('/:id', async (req, res, next) => { try { const { name, field, pattern, output_field, function_type, flags, replace_value, enabled, retain, sequence } = req.body; if (function_type && !['extract', 'replace'].includes(function_type)) { return res.status(400).json({ error: 'function_type must be "extract" or "replace"' }); } const n = (v) => v !== undefined ? lit(v) : 'NULL'; const result = await pool.query( `SELECT * FROM update_rule(${lit(parseInt(req.params.id))}, ${n(name)}, ${n(field)}, ${n(pattern)}, ${n(output_field)}, ${n(function_type)}, ${n(flags)}, ${n(replace_value)}, ${n(enabled)}, ${n(retain)}, ${n(sequence)})` ); if (result.rows.length === 0) return res.status(404).json({ error: 'Rule not found' }); res.json(result.rows[0]); } catch (err) { next(err); } }); // Delete rule router.delete('/:id', async (req, res, next) => { try { const result = await pool.query(`SELECT * FROM delete_rule(${lit(parseInt(req.params.id))})`); if (result.rows.length === 0) return res.status(404).json({ error: 'Rule not found' }); res.json({ success: true, deleted: result.rows[0] }); } catch (err) { next(err); } }); return router; };