New table compliance_item_history stores an append-only audit trail of changes to resolution_date and remediation_plan. The current values remain on compliance_items for fast VCL reporting queries (no double-counting). Backend: - Migration: creates compliance_item_history with indexes - PATCH /items/:hostname/metadata: records old→new in history before updating, accepts optional change_reason field (max 500 chars) - GET /items/:hostname: returns history array (last 10 entries, newest first) - POST /vcl/bulk-commit: records history for each changed field per hostname Frontend: - ComplianceDetailPanel: added change reason input below Save button - Added Change History section showing field changes with timestamps, usernames, old→new values, and reasons - Re-fetches detail after save to show updated history immediately Tests updated to match new transaction-based PATCH flow.
45 lines
1.4 KiB
JavaScript
45 lines
1.4 KiB
JavaScript
const pool = require('../db');
|
|
|
|
async function run() {
|
|
console.log('Starting compliance_item_history migration...');
|
|
try {
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS compliance_item_history (
|
|
id SERIAL PRIMARY KEY,
|
|
hostname TEXT NOT NULL,
|
|
field_name TEXT NOT NULL CHECK (field_name IN ('resolution_date', 'remediation_plan')),
|
|
old_value TEXT,
|
|
new_value TEXT,
|
|
change_reason TEXT,
|
|
changed_by TEXT NOT NULL,
|
|
changed_at TIMESTAMPTZ DEFAULT NOW()
|
|
)
|
|
`);
|
|
console.log('✓ compliance_item_history table created (or already exists)');
|
|
|
|
await pool.query(`
|
|
CREATE INDEX IF NOT EXISTS idx_compliance_history_hostname_field
|
|
ON compliance_item_history(hostname, field_name)
|
|
`);
|
|
console.log('✓ hostname/field_name index created');
|
|
|
|
await pool.query(`
|
|
CREATE INDEX IF NOT EXISTS idx_compliance_history_changed_at
|
|
ON compliance_item_history(changed_at)
|
|
`);
|
|
console.log('✓ changed_at index created');
|
|
|
|
console.log('Migration complete.');
|
|
} catch (err) {
|
|
console.error('Migration failed:', err.message);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
module.exports = { run };
|
|
|
|
// Self-execute when run directly
|
|
if (require.main === module) {
|
|
run().then(() => process.exit(0)).catch(() => process.exit(1));
|
|
}
|