chore: import DH V2 D5.6.4 production baseline
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
const newPermissions = [
|
||||
'asset_types.read',
|
||||
'asset_types.manage',
|
||||
'assets.read',
|
||||
'assets.create',
|
||||
'assets.update',
|
||||
'assets.change_status',
|
||||
] as const;
|
||||
|
||||
const rolePermissionValues = `
|
||||
('admin', 'asset_types.read'),
|
||||
('admin', 'asset_types.manage'),
|
||||
('admin', 'assets.read'),
|
||||
('admin', 'assets.create'),
|
||||
('admin', 'assets.update'),
|
||||
('admin', 'assets.change_status'),
|
||||
('director', 'asset_types.read'),
|
||||
('director', 'assets.read'),
|
||||
('director', 'assets.change_status'),
|
||||
('supervisor', 'asset_types.read'),
|
||||
('supervisor', 'assets.read'),
|
||||
('supervisor', 'assets.create'),
|
||||
('supervisor', 'assets.update'),
|
||||
('supervisor', 'assets.change_status'),
|
||||
('inspector', 'asset_types.read'),
|
||||
('inspector', 'assets.read'),
|
||||
('inspector', 'assets.create'),
|
||||
('inspector', 'assets.update'),
|
||||
('auditor', 'asset_types.read'),
|
||||
('auditor', 'assets.read')
|
||||
`;
|
||||
|
||||
function quoteIdentifier(identifier: string): string {
|
||||
return `"${identifier.replaceAll('"', '""')}"`;
|
||||
}
|
||||
|
||||
export class PhaseB1AssetMaster1786636800000 implements MigrationInterface {
|
||||
name = 'PhaseB1AssetMaster1786636800000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TYPE asset_information_status AS ENUM (
|
||||
'DRAFT', 'PENDING_SURVEY', 'SURVEYED', 'VALIDATED',
|
||||
'OBSERVED', 'OUTDATED', 'INACTIVE'
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE TYPE asset_attribute_data_type AS ENUM (
|
||||
'TEXT', 'NUMBER', 'BOOLEAN', 'DATE', 'DATETIME', 'SELECT'
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE asset_types (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code varchar(80) NOT NULL,
|
||||
name varchar(160) NOT NULL,
|
||||
description text NOT NULL DEFAULT '',
|
||||
can_be_root boolean NOT NULL DEFAULT false,
|
||||
is_active boolean NOT NULL DEFAULT true,
|
||||
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by uuid,
|
||||
updated_by uuid,
|
||||
CONSTRAINT fk_asset_types_created_by FOREIGN KEY (created_by)
|
||||
REFERENCES users(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_asset_types_updated_by FOREIGN KEY (updated_by)
|
||||
REFERENCES users(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(
|
||||
'CREATE UNIQUE INDEX uq_asset_types_code_ci ON asset_types (LOWER(code))',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX idx_asset_types_is_active ON asset_types (is_active)',
|
||||
);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE asset_type_parent_rules (
|
||||
child_type_id uuid NOT NULL,
|
||||
parent_type_id uuid NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_asset_type_parent_rules
|
||||
PRIMARY KEY (child_type_id, parent_type_id),
|
||||
CONSTRAINT chk_asset_type_parent_rules_not_self
|
||||
CHECK (child_type_id <> parent_type_id),
|
||||
CONSTRAINT fk_asset_type_parent_rules_child FOREIGN KEY (child_type_id)
|
||||
REFERENCES asset_types(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_asset_type_parent_rules_parent FOREIGN KEY (parent_type_id)
|
||||
REFERENCES asset_types(id) ON DELETE RESTRICT
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX idx_asset_type_parent_rules_parent
|
||||
ON asset_type_parent_rules (parent_type_id)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE asset_attribute_definitions (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
asset_type_id uuid NOT NULL,
|
||||
code varchar(80) NOT NULL,
|
||||
name varchar(160) NOT NULL,
|
||||
data_type asset_attribute_data_type NOT NULL,
|
||||
is_required boolean NOT NULL DEFAULT false,
|
||||
is_active boolean NOT NULL DEFAULT true,
|
||||
unit varchar(40),
|
||||
options jsonb,
|
||||
sort_order integer NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_asset_attribute_sort_order CHECK (sort_order >= 0),
|
||||
CONSTRAINT chk_asset_attribute_select_options CHECK (
|
||||
(data_type = 'SELECT' AND options IS NOT NULL AND jsonb_typeof(options) = 'array')
|
||||
OR (data_type <> 'SELECT' AND options IS NULL)
|
||||
),
|
||||
CONSTRAINT fk_asset_attribute_definitions_type FOREIGN KEY (asset_type_id)
|
||||
REFERENCES asset_types(id) ON DELETE RESTRICT
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE UNIQUE INDEX uq_asset_attribute_definitions_code_ci
|
||||
ON asset_attribute_definitions (asset_type_id, LOWER(code))
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX idx_asset_attribute_definitions_type
|
||||
ON asset_attribute_definitions (asset_type_id, sort_order)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX idx_asset_attribute_definitions_active
|
||||
ON asset_attribute_definitions (is_active)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE assets (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
asset_type_id uuid NOT NULL,
|
||||
parent_id uuid,
|
||||
code varchar(120) NOT NULL,
|
||||
name varchar(200) NOT NULL,
|
||||
description text,
|
||||
information_status asset_information_status NOT NULL DEFAULT 'DRAFT',
|
||||
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by uuid,
|
||||
updated_by uuid,
|
||||
CONSTRAINT chk_assets_not_self_parent CHECK (id <> parent_id),
|
||||
CONSTRAINT fk_assets_type FOREIGN KEY (asset_type_id)
|
||||
REFERENCES asset_types(id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_assets_parent FOREIGN KEY (parent_id)
|
||||
REFERENCES assets(id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_assets_created_by FOREIGN KEY (created_by)
|
||||
REFERENCES users(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_assets_updated_by FOREIGN KEY (updated_by)
|
||||
REFERENCES users(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(
|
||||
'CREATE UNIQUE INDEX uq_assets_code_ci ON assets (LOWER(code))',
|
||||
);
|
||||
await queryRunner.query('CREATE INDEX idx_assets_type_id ON assets (asset_type_id)');
|
||||
await queryRunner.query('CREATE INDEX idx_assets_parent_id ON assets (parent_id)');
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX idx_assets_information_status ON assets (information_status)',
|
||||
);
|
||||
await queryRunner.query(`
|
||||
CREATE FUNCTION enforce_asset_hierarchy_rules()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW.parent_id IS NULL THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM asset_types
|
||||
WHERE id = NEW.asset_type_id AND can_be_root = true
|
||||
) THEN
|
||||
RAISE EXCEPTION USING
|
||||
ERRCODE = '23514',
|
||||
MESSAGE = 'asset type does not allow root assets';
|
||||
END IF;
|
||||
ELSE
|
||||
IF NEW.parent_id = NEW.id THEN
|
||||
RAISE EXCEPTION USING
|
||||
ERRCODE = '23514',
|
||||
MESSAGE = 'asset cannot be its own parent';
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM assets parent
|
||||
INNER JOIN asset_type_parent_rules rule
|
||||
ON rule.parent_type_id = parent.asset_type_id
|
||||
AND rule.child_type_id = NEW.asset_type_id
|
||||
WHERE parent.id = NEW.parent_id
|
||||
) THEN
|
||||
RAISE EXCEPTION USING
|
||||
ERRCODE = '23514',
|
||||
MESSAGE = 'asset parent type is not allowed';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
WITH RECURSIVE ancestors AS (
|
||||
SELECT id, parent_id FROM assets WHERE id = NEW.parent_id
|
||||
UNION ALL
|
||||
SELECT parent.id, parent.parent_id
|
||||
FROM assets parent
|
||||
INNER JOIN ancestors current ON parent.id = current.parent_id
|
||||
)
|
||||
SELECT 1 FROM ancestors WHERE id = NEW.id
|
||||
) THEN
|
||||
RAISE EXCEPTION USING
|
||||
ERRCODE = '23514',
|
||||
MESSAGE = 'asset hierarchy cycle detected';
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END
|
||||
$$
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE TRIGGER trg_assets_hierarchy_rules
|
||||
BEFORE INSERT OR UPDATE OF asset_type_id, parent_id ON assets
|
||||
FOR EACH ROW EXECUTE FUNCTION enforce_asset_hierarchy_rules()
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE asset_attribute_values (
|
||||
asset_id uuid NOT NULL,
|
||||
definition_id uuid NOT NULL,
|
||||
value jsonb NOT NULL,
|
||||
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_by uuid,
|
||||
CONSTRAINT pk_asset_attribute_values PRIMARY KEY (asset_id, definition_id),
|
||||
CONSTRAINT fk_asset_attribute_values_asset FOREIGN KEY (asset_id)
|
||||
REFERENCES assets(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_asset_attribute_values_definition FOREIGN KEY (definition_id)
|
||||
REFERENCES asset_attribute_definitions(id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_asset_attribute_values_updated_by FOREIGN KEY (updated_by)
|
||||
REFERENCES users(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX idx_asset_attribute_values_definition
|
||||
ON asset_attribute_values (definition_id)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE FUNCTION enforce_asset_attribute_type_match()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF (
|
||||
SELECT asset.asset_type_id IS DISTINCT FROM definition.asset_type_id
|
||||
FROM assets asset, asset_attribute_definitions definition
|
||||
WHERE asset.id = NEW.asset_id AND definition.id = NEW.definition_id
|
||||
) THEN
|
||||
RAISE EXCEPTION USING
|
||||
ERRCODE = '23514',
|
||||
MESSAGE = 'attribute definition does not belong to asset type';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END
|
||||
$$
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE TRIGGER trg_asset_attribute_type_match
|
||||
BEFORE INSERT OR UPDATE OF asset_id, definition_id ON asset_attribute_values
|
||||
FOR EACH ROW EXECUTE FUNCTION enforce_asset_attribute_type_match()
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
INSERT INTO permissions (code, description)
|
||||
VALUES
|
||||
('asset_types.read', 'Consultar tipos y atributos del Maestro de Activos'),
|
||||
('asset_types.manage', 'Administrar tipos, jerarquías permitidas y atributos'),
|
||||
('assets.read', 'Listar y consultar activos'),
|
||||
('assets.create', 'Crear activos'),
|
||||
('assets.update', 'Editar activos y sus atributos'),
|
||||
('assets.change_status', 'Cambiar el estado de información de activos')
|
||||
ON CONFLICT (code) DO UPDATE SET description = EXCLUDED.description
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
WITH mapping(role_code, permission_code) AS (VALUES ${rolePermissionValues})
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT role.id, permission.id
|
||||
FROM mapping
|
||||
INNER JOIN roles role ON role.code = mapping.role_code
|
||||
INNER JOIN permissions permission ON permission.code = mapping.permission_code
|
||||
ON CONFLICT (role_id, permission_id) DO NOTHING
|
||||
`);
|
||||
|
||||
const appRole = process.env.DB_APP_USER;
|
||||
if (!appRole) throw new Error('Missing required environment variable: DB_APP_USER');
|
||||
const roleRows = (await queryRunner.query(
|
||||
'SELECT 1 FROM pg_roles WHERE rolname = $1',
|
||||
[appRole],
|
||||
)) as unknown[];
|
||||
if (roleRows.length !== 1) throw new Error('Configured DB_APP_USER does not exist');
|
||||
const applicationRole = quoteIdentifier(appRole);
|
||||
await queryRunner.query(`
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE
|
||||
asset_types,
|
||||
asset_type_parent_rules,
|
||||
asset_attribute_definitions,
|
||||
assets,
|
||||
asset_attribute_values
|
||||
TO ${applicationRole}
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
WITH mapping(role_code, permission_code) AS (VALUES ${rolePermissionValues})
|
||||
DELETE FROM role_permissions role_permission
|
||||
USING roles role, permissions permission, mapping
|
||||
WHERE role_permission.role_id = role.id
|
||||
AND role_permission.permission_id = permission.id
|
||||
AND role.code = mapping.role_code
|
||||
AND permission.code = mapping.permission_code
|
||||
`);
|
||||
await queryRunner.query(
|
||||
`DELETE FROM permissions WHERE code = ANY($1::varchar[])
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM role_permissions WHERE permission_id = permissions.id
|
||||
)`,
|
||||
[newPermissions],
|
||||
);
|
||||
await queryRunner.query('DROP TRIGGER trg_asset_attribute_type_match ON asset_attribute_values');
|
||||
await queryRunner.query('DROP FUNCTION enforce_asset_attribute_type_match');
|
||||
await queryRunner.query('DROP TABLE asset_attribute_values');
|
||||
await queryRunner.query('DROP TRIGGER trg_assets_hierarchy_rules ON assets');
|
||||
await queryRunner.query('DROP FUNCTION enforce_asset_hierarchy_rules');
|
||||
await queryRunner.query('DROP TABLE assets');
|
||||
await queryRunner.query('DROP TABLE asset_attribute_definitions');
|
||||
await queryRunner.query('DROP TABLE asset_type_parent_rules');
|
||||
await queryRunner.query('DROP TABLE asset_types');
|
||||
await queryRunner.query('DROP TYPE asset_attribute_data_type');
|
||||
await queryRunner.query('DROP TYPE asset_information_status');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user