Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
136 lines
5.3 KiB
JavaScript
136 lines
5.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* ADR-0003 widget-embed Phase 1, frontend-backend meta-schema sync.
|
|
*
|
|
* Brings every existing per-document content table (`universal_tables` row
|
|
* with `table_type = 'document_content'`) in line with the new
|
|
* DOCUMENT_TABLE_COLUMNS template in
|
|
* `backend/routes/v3/documents/_helpers.js`:
|
|
*
|
|
* 1. `level` select column — config.options extended with
|
|
* ['atom', 'ticket', 'image', 'page_break', 'widget'] (was h1/h2/h3/text/divider
|
|
* only in the template; UI was already emitting the extras for a while, so
|
|
* existing rows may carry values the DB config didn't list — this realigns).
|
|
* 2. `widget_ref` relation column — added if missing, targets `widgets` table
|
|
* (target_table_id stored as the string 'widgets' — the existing relation
|
|
* UI resolves named targets for system tables; otherwise null).
|
|
* 3. `settings_override` text column — added if missing (stores JSON blob
|
|
* of preset-local overrides for an embedded widget).
|
|
*
|
|
* Idempotent: re-running finds existing columns / options and makes zero
|
|
* writes.
|
|
*
|
|
* Usage:
|
|
* node scripts/migrate-doc-tables-widget-level.js # dry-run
|
|
* node scripts/migrate-doc-tables-widget-level.js --apply
|
|
*/
|
|
|
|
import pg from 'pg';
|
|
const { Pool } = pg;
|
|
|
|
const APPLY = process.argv.includes('--apply');
|
|
|
|
const pool = new Pool({
|
|
host: process.env.POSTGRES_HOST || 'localhost',
|
|
port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
|
|
database: process.env.POSTGRES_DB || 'godcrm_prod',
|
|
user: process.env.POSTGRES_USER || 'godcrm',
|
|
password: process.env.POSTGRES_PASSWORD || 'godcrm_dev_2026',
|
|
});
|
|
|
|
const TARGET_LEVEL_OPTIONS = [
|
|
'h1', 'h2', 'h3', 'text', 'atom', 'ticket', 'image', 'divider', 'page_break', 'widget',
|
|
];
|
|
|
|
function log(...a) { console.log('[migrate-doc-tables-widget-level]', ...a); }
|
|
|
|
async function main() {
|
|
const docTables = (await pool.query(
|
|
`SELECT id, name, project_id FROM universal_tables
|
|
WHERE table_type = 'document_content' ORDER BY id`
|
|
)).rows;
|
|
|
|
log(`found ${docTables.length} document_content tables`);
|
|
|
|
let levelAligned = 0, widgetRefAdded = 0, settingsAdded = 0, untouched = 0;
|
|
|
|
for (const dt of docTables) {
|
|
const cols = (await pool.query(
|
|
`SELECT id, column_name, type, config, order_index
|
|
FROM table_columns WHERE table_id = $1 ORDER BY order_index, id`,
|
|
[dt.id]
|
|
)).rows;
|
|
|
|
let touchedThisTable = false;
|
|
|
|
// 1) level — extend select options
|
|
const levelCol = cols.find(c => c.column_name === 'level');
|
|
if (levelCol) {
|
|
const cfg = typeof levelCol.config === 'string'
|
|
? (levelCol.config ? JSON.parse(levelCol.config) : {})
|
|
: (levelCol.config || {});
|
|
const existingOpts = Array.isArray(cfg.options) ? cfg.options : [];
|
|
const merged = Array.from(new Set([...existingOpts, ...TARGET_LEVEL_OPTIONS]));
|
|
if (merged.length !== existingOpts.length) {
|
|
const newCfg = { ...cfg, options: merged };
|
|
log(` table ${dt.id} (${dt.name}): level options ${existingOpts.join(',')} → ${merged.join(',')}`);
|
|
if (APPLY) {
|
|
await pool.query(
|
|
`UPDATE table_columns SET config = $1 WHERE id = $2`,
|
|
[JSON.stringify(newCfg), levelCol.id]
|
|
);
|
|
}
|
|
levelAligned++;
|
|
touchedThisTable = true;
|
|
}
|
|
} else {
|
|
log(` table ${dt.id} (${dt.name}): WARN no 'level' column — skipping`);
|
|
}
|
|
|
|
// 2) widget_ref — relation
|
|
if (!cols.find(c => c.column_name === 'widget_ref')) {
|
|
const nextOrder = (cols.reduce((m, c) => Math.max(m, c.order_index || 0), 0)) + 1;
|
|
log(` table ${dt.id} (${dt.name}): add widget_ref (relation) order=${nextOrder}`);
|
|
if (APPLY) {
|
|
await pool.query(
|
|
`INSERT INTO table_columns (table_id, column_name, display_name, type, order_index, is_visible, config)
|
|
VALUES ($1, 'widget_ref', 'Widget Ref', 'relation', $2, 1, $3)`,
|
|
[dt.id, nextOrder, JSON.stringify({ relation_table: null, description: 'ADR-0003: embedded widget (widgets.id)' })]
|
|
);
|
|
}
|
|
widgetRefAdded++;
|
|
touchedThisTable = true;
|
|
}
|
|
|
|
// 3) settings_override — text (JSON blob)
|
|
if (!cols.find(c => c.column_name === 'settings_override')) {
|
|
const nextOrder = (cols.reduce((m, c) => Math.max(m, c.order_index || 0), 0)) + 2;
|
|
log(` table ${dt.id} (${dt.name}): add settings_override (text) order=${nextOrder}`);
|
|
if (APPLY) {
|
|
await pool.query(
|
|
`INSERT INTO table_columns (table_id, column_name, display_name, type, order_index, is_visible, config)
|
|
VALUES ($1, 'settings_override', 'Settings Override', 'text', $2, 0, $3)`,
|
|
[dt.id, nextOrder, JSON.stringify({ hidden_in_view: true, description: 'ADR-0003: JSON overrides for embedded widget' })]
|
|
);
|
|
}
|
|
settingsAdded++;
|
|
touchedThisTable = true;
|
|
}
|
|
|
|
if (!touchedThisTable) untouched++;
|
|
}
|
|
|
|
log('---');
|
|
log(`tables total : ${docTables.length}`);
|
|
log(`level extended : ${levelAligned}`);
|
|
log(`widget_ref added : ${widgetRefAdded}`);
|
|
log(`settings_override+ : ${settingsAdded}`);
|
|
log(`already up to date : ${untouched} ${APPLY ? '' : '(DRY RUN — use --apply to execute)'}`);
|
|
|
|
await pool.end();
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('[migrate-doc-tables-widget-level] FATAL:', err);
|
|
process.exit(1);
|
|
});
|