You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
senzapaura_es/scripts/estudio/administrar.mjs

100 lines
4.3 KiB

import pg from 'pg';
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { TABLAS_FUENTE, huella, leerFuente } from './fuente.mjs';
const accion = process.argv[2] ?? 'ver';
if (!['ver', 'exportar', 'activar', 'desactivar'].includes(accion))
throw Error('Usa ver, exportar, activar o desactivar.');
const c = new pg.Client({ connectionString: process.env.DATABASE_URL });
await c.connect();
const tablas = [
'estudio_control',
'estudio_documento',
'estudio_vinculo',
'estudio_historial',
'estudio_archivo',
'estudio_trabajo',
'estudio_pista_publicada',
'estudio_alias',
'estudio_migracion'
];
const q = (s) => '"' + s.replaceAll('"', '""') + '"';
try {
const {
rows: [s]
} = await c.query('SELECT current_schema() AS esquema');
const {
rows: [control]
} = await c.query('SELECT * FROM estudio_control WHERE id=1');
if (accion === 'ver') {
console.log(`Modo: ${control.modo}. Esquema: ${s.esquema}.`);
const { rows } = await c.query(
'SELECT tipo,count(*)::int AS total FROM estudio_documento GROUP BY tipo ORDER BY tipo'
);
console.table(rows);
console.log(
`Fuente ${control.huella_fuente && huella(await leerFuente(c)) === control.huella_fuente ? 'sin cambios desde la importación' : 'modificada o pendiente de comprobación'}.`
);
} else if (accion === 'exportar') {
const destino = resolve(process.argv[3] ?? `exports/estudio-${Date.now()}.json`);
const datos = {};
await c.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
for (const t of tablas) datos[t] = (await c.query(`SELECT * FROM ${t}`)).rows;
await c.query('COMMIT');
mkdirSync(dirname(destino), { recursive: true });
writeFileSync(
destino,
JSON.stringify(
{ version: 1, esquema: s.esquema, fecha: new Date().toISOString(), datos },
null,
2
),
{ flag: 'wx' }
);
console.log(`Exportación conservada en ${destino}`);
} else if (accion === 'activar') {
if (control.modo === 'activo') throw Error('Este almacén ya está activo.');
const respaldo = process.argv[3];
if (!respaldo) throw Error('Exporta primero y pasa la ruta del respaldo a activar.');
const copia = JSON.parse(readFileSync(respaldo, 'utf8'));
if (copia.version !== 1 || copia.esquema !== s.esquema)
throw Error('El respaldo no corresponde a este almacén.');
await c.query('BEGIN');
await c.query("SELECT pg_advisory_xact_lock(hashtext('estudio:publicar'))");
for (const t of [...TABLAS_FUENTE, 'estudio_documento'])
await c.query(`LOCK TABLE ${q(t)} IN SHARE ROW EXCLUSIVE MODE`);
const actuales = (await c.query('SELECT * FROM estudio_documento')).rows;
if (huella({ documentos: actuales }) !== huella({ documentos: copia.datos.estudio_documento }))
throw Error('El almacén cambió después de exportarlo. Genera un respaldo nuevo.');
if (!control.huella_fuente || huella(await leerFuente(c)) !== control.huella_fuente)
throw Error(
'El catálogo original cambió desde la importación. Concilia esos cambios antes de activar.'
);
const funcion = `${q(s.esquema)}.estudio_guardar_escritor`;
await c.query(`CREATE OR REPLACE FUNCTION ${funcion}() RETURNS trigger LANGUAGE plpgsql SET search_path=${q(s.esquema)},pg_temp AS $guard$
BEGIN IF current_setting('senzapaura.editor',true) IS DISTINCT FROM 'estudio' AND EXISTS(SELECT 1 FROM estudio_control WHERE id=1 AND modo='activo') THEN RAISE EXCEPTION 'La edición del catálogo se realiza desde Estudio'; END IF; IF TG_OP='DELETE' THEN RETURN OLD; END IF; RETURN NEW; END; $guard$`);
for (const t of TABLAS_FUENTE) {
await c.query(`DROP TRIGGER IF EXISTS estudio_escritor_unico ON ${q(t)}`);
await c.query(
`CREATE TRIGGER estudio_escritor_unico BEFORE INSERT OR UPDATE OR DELETE ON ${q(t)} FOR EACH ROW EXECUTE FUNCTION ${funcion}()`
);
}
await c.query("UPDATE estudio_control SET modo='activo' WHERE id=1");
await c.query('COMMIT');
console.log(
'Estudio activado. El catálogo tiene un único escritor; las compras conservan sus tablas y contratos.'
);
} else {
await c.query("UPDATE estudio_control SET modo='preparacion' WHERE id=1");
console.log(
'Publicación desde Estudio suspendida. Los datos ya publicados se conservan; no se han revertido ni eliminado.'
);
}
} catch (e) {
await c.query('ROLLBACK').catch(() => {});
console.error(e.message);
process.exitCode = 1;
} finally {
await c.end();
}

Powered by TurnKey Linux.