chore: import DH V2 D5.6.4 production baseline
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
const newPermissions = [
|
||||
'inspection_reports.read',
|
||||
'inspection_reports.generate',
|
||||
] as const;
|
||||
|
||||
const rolePermissionValues = `
|
||||
('admin', 'inspection_reports.read'),
|
||||
('director', 'inspection_reports.read'),
|
||||
('supervisor', 'inspection_reports.read'),
|
||||
('inspector', 'inspection_reports.read'),
|
||||
('inspector', 'inspection_reports.generate'),
|
||||
('auditor', 'inspection_reports.read')
|
||||
`;
|
||||
|
||||
function quoteIdentifier(identifier: string): string {
|
||||
return `"${identifier.replaceAll('"', '""')}"`;
|
||||
}
|
||||
|
||||
export class PhaseD5315DocumentCenter1788026400000 implements MigrationInterface {
|
||||
name = 'PhaseD5315DocumentCenter1788026400000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE inspection_reports (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
visit_id uuid NOT NULL,
|
||||
act_id uuid NOT NULL,
|
||||
report_year integer NOT NULL,
|
||||
report_number integer NOT NULL,
|
||||
code varchar(24) NOT NULL,
|
||||
status varchar(24) NOT NULL DEFAULT 'FROZEN',
|
||||
pdf_status varchar(24) NOT NULL DEFAULT 'PENDING',
|
||||
title varchar(220) NOT NULL,
|
||||
act_version integer NOT NULL,
|
||||
act_closure_sha256 char(64) NOT NULL,
|
||||
frozen_sha256 char(64) NOT NULL,
|
||||
frozen_snapshot jsonb NOT NULL,
|
||||
generated_at timestamptz NOT NULL,
|
||||
generated_by uuid NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_inspection_reports_visit UNIQUE (visit_id),
|
||||
CONSTRAINT uq_inspection_reports_act UNIQUE (act_id),
|
||||
CONSTRAINT uq_inspection_reports_year_number UNIQUE (report_year, report_number),
|
||||
CONSTRAINT uq_inspection_reports_code UNIQUE (code),
|
||||
CONSTRAINT chk_inspection_reports_number CHECK (report_year >= 2000 AND report_number > 0),
|
||||
CONSTRAINT chk_inspection_reports_status CHECK (status IN ('FROZEN', 'CANCELLED')),
|
||||
CONSTRAINT chk_inspection_reports_pdf_status CHECK (pdf_status IN ('PENDING', 'READY', 'FAILED')),
|
||||
CONSTRAINT chk_inspection_reports_hashes CHECK (
|
||||
act_closure_sha256 ~ '^[0-9a-f]{64}$'
|
||||
AND frozen_sha256 ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
CONSTRAINT chk_inspection_reports_snapshot CHECK (jsonb_typeof(frozen_snapshot) = 'object'),
|
||||
CONSTRAINT fk_inspection_reports_visit FOREIGN KEY (visit_id)
|
||||
REFERENCES inspection_visits(id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_inspection_reports_act FOREIGN KEY (act_id)
|
||||
REFERENCES inspection_acts(id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_inspection_reports_generated_by FOREIGN KEY (generated_by)
|
||||
REFERENCES users(id) ON DELETE RESTRICT
|
||||
)
|
||||
`);
|
||||
await queryRunner.query('CREATE INDEX idx_inspection_reports_generated_at ON inspection_reports (generated_at DESC)');
|
||||
await queryRunner.query('CREATE INDEX idx_inspection_reports_status ON inspection_reports (status, pdf_status)');
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE FUNCTION dhv2_guard_inspection_report_frozen()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF NEW.visit_id IS DISTINCT FROM OLD.visit_id
|
||||
OR NEW.act_id IS DISTINCT FROM OLD.act_id
|
||||
OR NEW.report_year IS DISTINCT FROM OLD.report_year
|
||||
OR NEW.report_number IS DISTINCT FROM OLD.report_number
|
||||
OR NEW.code IS DISTINCT FROM OLD.code
|
||||
OR NEW.title IS DISTINCT FROM OLD.title
|
||||
OR NEW.act_version IS DISTINCT FROM OLD.act_version
|
||||
OR NEW.act_closure_sha256 IS DISTINCT FROM OLD.act_closure_sha256
|
||||
OR NEW.frozen_sha256 IS DISTINCT FROM OLD.frozen_sha256
|
||||
OR NEW.frozen_snapshot IS DISTINCT FROM OLD.frozen_snapshot
|
||||
OR NEW.generated_at IS DISTINCT FROM OLD.generated_at
|
||||
OR NEW.generated_by IS DISTINCT FROM OLD.generated_by
|
||||
OR NEW.created_at IS DISTINCT FROM OLD.created_at
|
||||
THEN
|
||||
RAISE EXCEPTION 'El contenido congelado del informe es inmutable';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE TRIGGER trg_inspection_reports_frozen
|
||||
BEFORE UPDATE ON inspection_reports
|
||||
FOR EACH ROW EXECUTE FUNCTION dhv2_guard_inspection_report_frozen()
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
INSERT INTO permissions (code, description)
|
||||
VALUES
|
||||
('inspection_reports.read', 'Consultar informes de inspección y documentos pendientes de emisión'),
|
||||
('inspection_reports.generate', 'Solicitar la emisión y congelado de un informe desde una inspección cerrada')
|
||||
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 applicationRole = quoteIdentifier(appRole);
|
||||
await queryRunner.query(`
|
||||
GRANT SELECT, INSERT, UPDATE ON TABLE inspection_reports TO ${applicationRole}
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
REVOKE DELETE ON TABLE inspection_reports FROM ${applicationRole}
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP TRIGGER trg_inspection_reports_frozen ON inspection_reports');
|
||||
await queryRunner.query('DROP FUNCTION dhv2_guard_inspection_report_frozen()');
|
||||
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 TABLE inspection_reports');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user