dataflow/api/routes/records.js
Paul Trowbridge 89a70bdf7e Split transformed column; add override management; show all override keys in panel
- transformed now stores only rule additions (not merged data+overrides)
- View dynamically computes data || transformed || overrides at query time
- New DB functions: set/clear/bulk_set_record_overrides
- Records panel now includes source-wide override keys so party/reason etc.
  appear even on records that don't have them set yet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 11:00:24 -04:00

125 lines
4.8 KiB
JavaScript

/**
* Records Routes
* Query and manage imported records
*/
const express = require('express');
const { lit } = require('../lib/sql');
module.exports = (pool) => {
const router = express.Router();
// List records for a source
router.get('/source/:source_name', async (req, res, next) => {
try {
const { limit = 100, offset = 0, transformed_only } = req.query;
const result = await pool.query(
`SELECT * FROM list_records(${lit(req.params.source_name)}, ${lit(parseInt(limit))}, ${lit(parseInt(offset))}, ${lit(transformed_only === 'true')})`
);
res.json(result.rows);
} catch (err) {
next(err);
}
});
// Get single record
router.get('/:id', async (req, res, next) => {
try {
const result = await pool.query(`SELECT * FROM get_record(${lit(parseInt(req.params.id))})`);
if (result.rows.length === 0) return res.status(404).json({ error: 'Record not found' });
res.json(result.rows[0]);
} catch (err) {
next(err);
}
});
// Search records
router.post('/search', async (req, res, next) => {
try {
const { source_name, query, limit = 100 } = req.body;
if (!source_name || !query) {
return res.status(400).json({ error: 'Missing required fields: source_name, query' });
}
const result = await pool.query(
`SELECT * FROM search_records(${lit(source_name)}, ${lit(query)}, ${lit(parseInt(limit))})`
);
res.json(result.rows);
} catch (err) {
next(err);
}
});
// Set overrides for all selected records
router.post('/bulk-overrides', async (req, res, next) => {
try {
const { source_name, record_ids, overrides } = req.body;
if (!source_name || !Array.isArray(record_ids) || record_ids.length === 0 || !overrides || typeof overrides !== 'object')
return res.status(400).json({ error: 'source_name, record_ids array, and overrides object required' });
const idList = record_ids.map(id => parseInt(id)).join(',');
const result = await pool.query(
`SELECT bulk_set_record_overrides(${lit(source_name)}, ARRAY[${idList}]::int[], ${lit(overrides)}) as result`
);
res.json(result.rows[0].result);
} catch (err) {
next(err);
}
});
// Set overrides for a record
router.put('/:id/overrides', async (req, res, next) => {
try {
const { overrides } = req.body;
if (!overrides || typeof overrides !== 'object')
return res.status(400).json({ error: 'overrides object required' });
const result = await pool.query(
`SELECT set_record_overrides(${lit(parseInt(req.params.id))}, ${lit(overrides)}) as rec`
);
if (!result.rows[0].rec) return res.status(404).json({ error: 'Record not found' });
res.json(result.rows[0].rec);
} catch (err) {
next(err);
}
});
// Clear overrides and reprocess that record to restore computed values
router.delete('/:id/overrides', async (req, res, next) => {
try {
const result = await pool.query(
`SELECT clear_record_overrides(${lit(parseInt(req.params.id))}) as rec`
);
if (!result.rows[0].rec) return res.status(404).json({ error: 'Record not found' });
const { source_name } = result.rows[0].rec;
await pool.query(
`SELECT apply_transformations(${lit(source_name)}, ARRAY[${lit(parseInt(req.params.id))}::int], true)`
);
const updated = await pool.query(`SELECT * FROM get_record(${lit(parseInt(req.params.id))})`);
res.json(updated.rows[0]);
} catch (err) {
next(err);
}
});
// Delete record
router.delete('/:id', async (req, res, next) => {
try {
const result = await pool.query(`SELECT * FROM delete_record(${lit(parseInt(req.params.id))})`);
if (result.rows.length === 0) return res.status(404).json({ error: 'Record not found' });
res.json({ success: true, deleted: result.rows[0].id });
} catch (err) {
next(err);
}
});
// Delete all records for a source
router.delete('/source/:source_name/all', async (req, res, next) => {
try {
const result = await pool.query(`SELECT * FROM delete_source_records(${lit(req.params.source_name)})`);
res.json({ success: true, deleted_count: result.rows[0].deleted_count });
} catch (err) {
next(err);
}
});
return router;
};