erp-suite/apps/shared-libs/core/database/policies/migration-example.ts

153 lines
4.9 KiB
TypeScript

/**
* Migration Example: Applying Centralized RLS Policies
*
* This migration demonstrates how to replace vertical-specific RLS policies
* with the centralized shared library policies.
*
* BEFORE: Each vertical had duplicated RLS functions and policies
* AFTER: Use shared-libs centralized RLS policies
*/
import { Pool } from 'pg';
import {
applyCompleteRlsPolicies,
applyCompletePoliciesForSchema,
batchApplyRlsPolicies,
getSchemaRlsStatus,
} from '@erp-suite/core';
/**
* Migration UP: Apply RLS policies to all tables
*/
export async function up(pool: Pool): Promise<void> {
console.log('Starting RLS migration...');
// STEP 1: Install RLS helper functions (run once per database)
console.log('Installing RLS helper functions...');
// Note: This would be done via SQL file first:
// psql -d database -f apps/shared-libs/core/database/policies/rls-policies.sql
// STEP 2: Apply RLS to ERP Core tables
console.log('Applying RLS to ERP Core tables...');
const coreSchemas = [
{
schema: 'core',
tables: ['partners', 'addresses', 'product_categories', 'tags', 'sequences', 'attachments', 'notes'],
},
{
schema: 'inventory',
tables: ['products', 'warehouses', 'locations', 'lots', 'pickings', 'stock_moves'],
},
{
schema: 'sales',
tables: ['sales_orders', 'quotations', 'pricelists'],
},
{
schema: 'purchase',
tables: ['purchase_orders', 'rfqs', 'vendor_pricelists'],
},
{
schema: 'financial',
tables: ['accounts', 'invoices', 'payments', 'journal_entries'],
},
];
for (const { schema, tables } of coreSchemas) {
await applyCompletePoliciesForSchema(pool, schema, tables);
console.log(` Applied RLS to ${schema} schema (${tables.length} tables)`);
}
// STEP 3: Apply RLS to vertical-specific tables
console.log('Applying RLS to vertical tables...');
// Example: Construccion vertical
const construccionTables = [
{ schema: 'construction', table: 'projects', tenantColumn: 'constructora_id' },
{ schema: 'construction', table: 'estimates', tenantColumn: 'constructora_id' },
{ schema: 'construction', table: 'work_orders', tenantColumn: 'constructora_id' },
{ schema: 'hse', table: 'incidents', tenantColumn: 'constructora_id' },
];
await batchApplyRlsPolicies(pool, construccionTables);
console.log(` Applied RLS to construccion vertical (${construccionTables.length} tables)`);
// Example: Mecanicas-diesel vertical
const mecanicasTables = [
{ schema: 'mechanics', table: 'work_orders' },
{ schema: 'mechanics', table: 'vehicles' },
{ schema: 'mechanics', table: 'parts' },
];
await batchApplyRlsPolicies(pool, mecanicasTables);
console.log(` Applied RLS to mecanicas-diesel vertical (${mecanicasTables.length} tables)`);
// STEP 4: Verify RLS application
console.log('Verifying RLS policies...');
const coreStatus = await getSchemaRlsStatus(pool, 'core');
const enabledCount = coreStatus.filter(s => s.rlsEnabled).length;
console.log(` Core schema: ${enabledCount}/${coreStatus.length} tables have RLS enabled`);
console.log('RLS migration completed successfully!');
}
/**
* Migration DOWN: Remove RLS policies
*/
export async function down(pool: Pool): Promise<void> {
console.log('Rolling back RLS migration...');
// Import cleanup functions
const { dropAllRlsPolicies, disableRls } = await import('@erp-suite/core');
// Remove RLS from all tables
const schemas = ['core', 'inventory', 'sales', 'purchase', 'financial', 'construction', 'hse', 'mechanics'];
for (const schema of schemas) {
const tablesQuery = `
SELECT table_name
FROM information_schema.tables
WHERE table_schema = $1
AND table_type = 'BASE TABLE'
`;
const result = await pool.query(tablesQuery, [schema]);
for (const row of result.rows) {
const table = row.table_name;
try {
const count = await dropAllRlsPolicies(pool, schema, table);
if (count > 0) {
await disableRls(pool, schema, table);
console.log(` Removed RLS from ${schema}.${table} (${count} policies)`);
}
} catch (error) {
console.error(` Error removing RLS from ${schema}.${table}:`, error.message);
}
}
}
console.log('RLS migration rollback completed!');
}
/**
* Example usage in a migration framework
*/
export default {
up,
down,
};
// TypeORM migration class format
export class ApplyRlsPolicies1234567890123 {
public async up(queryRunner: any): Promise<void> {
// Get pool from queryRunner or create one
const pool = queryRunner.connection.driver.master;
await up(pool);
}
public async down(queryRunner: any): Promise<void> {
const pool = queryRunner.connection.driver.master;
await down(pool);
}
}