/** * Rules Routes * Manage transformation rules */ const express = require('express'); 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 rules WHERE source_name = $1 ORDER BY sequence, name', [req.params.source_name] ); 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 ruleResult = await pool.query( 'SELECT * FROM rules WHERE id = $1', [req.params.id] ); if (ruleResult.rows.length === 0) { return res.status(404).json({ error: 'Rule not found' }); } const rule = ruleResult.rows[0]; const pattern = (rule.flags ? `(?${rule.flags})` : '') + rule.pattern; const result = await pool.query( `SELECT id, data->>$1 AS raw_value, substring(data->>$1 FROM $2) AS extracted_value FROM records WHERE source_name = $3 AND data ? $1 ORDER BY id DESC LIMIT $4`, [rule.field, pattern, rule.source_name, parseInt(limit)] ); res.json({ rule: { id: rule.id, name: rule.name, field: rule.field, pattern: rule.pattern, output_field: rule.output_field }, results: result.rows }); } catch (err) { next(err); } }); // Get single rule router.get('/:id', async (req, res, next) => { try { const result = await pool.query( 'SELECT * FROM rules WHERE id = $1', [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, enabled, 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( `INSERT INTO rules (source_name, name, field, pattern, output_field, function_type, flags, enabled, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *`, [source_name, name, field, pattern, output_field, function_type || 'extract', flags || '', enabled !== false, sequence || 0] ); res.status(201).json(result.rows[0]); } catch (err) { if (err.code === '23505') { // Unique violation return res.status(409).json({ error: 'Rule already exists for this source' }); } if (err.code === '23503') { // Foreign key violation 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, enabled, 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 result = await pool.query( `UPDATE rules SET name = COALESCE($2, name), field = COALESCE($3, field), pattern = COALESCE($4, pattern), output_field = COALESCE($5, output_field), function_type = COALESCE($6, function_type), flags = COALESCE($7, flags), enabled = COALESCE($8, enabled), sequence = COALESCE($9, sequence) WHERE id = $1 RETURNING *`, [req.params.id, name, field, pattern, output_field, function_type, flags, enabled, 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( 'DELETE FROM rules WHERE id = $1 RETURNING id, name', [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; };