chore: import DH V2 D5.6.4 production baseline

This commit is contained in:
DH V2
2026-09-05 10:12:35 -03:00
commit 82213e72f5
757 changed files with 84218 additions and 0 deletions
@@ -0,0 +1,234 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
const newPermissions = [
'inspection_evidence.read',
'inspection_evidence.create',
'inspection_communications.read',
'inspection_communications.create',
] as const;
const rolePermissionValues = `
('admin', 'inspection_evidence.read'),
('admin', 'inspection_evidence.create'),
('admin', 'inspection_communications.read'),
('admin', 'inspection_communications.create'),
('director', 'inspection_evidence.read'),
('director', 'inspection_evidence.create'),
('director', 'inspection_communications.read'),
('director', 'inspection_communications.create'),
('supervisor', 'inspection_evidence.read'),
('supervisor', 'inspection_evidence.create'),
('supervisor', 'inspection_communications.read'),
('supervisor', 'inspection_communications.create'),
('inspector', 'inspection_evidence.read'),
('inspector', 'inspection_evidence.create'),
('inspector', 'inspection_communications.read'),
('inspector', 'inspection_communications.create'),
('auditor', 'inspection_evidence.read'),
('auditor', 'inspection_communications.read')
`;
function quoteIdentifier(identifier: string): string {
return `"${identifier.replaceAll('"', '""')}"`;
}
export class PhaseD4FindingEvidenceCommunications1787241600000 implements MigrationInterface {
name = 'PhaseD4FindingEvidenceCommunications1787241600000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE inspection_finding_communications (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
finding_id uuid NOT NULL,
direction varchar(20) NOT NULL,
channel varchar(20) NOT NULL,
type varchar(32) NOT NULL,
occurred_at timestamptz NOT NULL,
subject varchar(250) NOT NULL,
details text,
contact_name varchar(200),
contact_email varchar(320),
created_by uuid,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_inspection_finding_communications_id_finding
UNIQUE (id, finding_id),
CONSTRAINT chk_inspection_finding_communications_direction CHECK (
direction IN ('INBOUND', 'OUTBOUND', 'INTERNAL')
),
CONSTRAINT chk_inspection_finding_communications_channel CHECK (
channel IN ('EMAIL', 'IN_PERSON', 'PHONE', 'LETTER', 'SYSTEM', 'OTHER')
),
CONSTRAINT chk_inspection_finding_communications_type CHECK (
type IN ('COMPANY_RESPONSE', 'AUTHORITY_NOTICE', 'FOLLOW_UP', 'OTHER')
),
CONSTRAINT chk_inspection_finding_communications_subject CHECK (
LENGTH(TRIM(subject)) > 0
),
CONSTRAINT chk_inspection_finding_company_response_direction CHECK (
type <> 'COMPANY_RESPONSE' OR direction = 'INBOUND'
),
CONSTRAINT fk_inspection_finding_communications_finding FOREIGN KEY (finding_id)
REFERENCES inspection_findings(id) ON DELETE RESTRICT,
CONSTRAINT fk_inspection_finding_communications_created_by FOREIGN KEY (created_by)
REFERENCES users(id) ON DELETE SET NULL
)
`);
await queryRunner.query(`
CREATE INDEX idx_inspection_finding_communications_timeline
ON inspection_finding_communications (finding_id, occurred_at DESC)
`);
await queryRunner.query(`
CREATE TABLE inspection_finding_evidence (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
finding_id uuid NOT NULL,
communication_id uuid,
kind varchar(20) NOT NULL,
purpose varchar(40) NOT NULL,
original_name varchar(255) NOT NULL,
stored_name varchar(80) NOT NULL,
mime_type varchar(100) NOT NULL,
size_bytes bigint NOT NULL,
sha256 char(64) NOT NULL,
title varchar(200),
description text,
captured_at timestamptz,
latitude numeric(9, 6),
longitude numeric(9, 6),
accuracy_m numeric(12, 3),
device_label varchar(200),
source varchar(20) NOT NULL,
uploaded_by uuid,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_inspection_finding_evidence_stored_name UNIQUE (stored_name),
CONSTRAINT chk_inspection_finding_evidence_kind CHECK (
kind IN ('PHOTO', 'DOCUMENT')
),
CONSTRAINT chk_inspection_finding_evidence_purpose CHECK (
purpose IN (
'OBSERVATION', 'COMPANY_RESPONSE',
'COMMUNICATION_ATTACHMENT', 'OTHER_DOCUMENT'
)
),
CONSTRAINT chk_inspection_finding_evidence_source CHECK (
source IN ('WEB', 'ANDROID', 'IMPORT')
),
CONSTRAINT chk_inspection_finding_evidence_mime CHECK (
mime_type IN ('image/jpeg', 'image/png', 'image/webp', 'application/pdf')
),
CONSTRAINT chk_inspection_finding_evidence_size CHECK (
size_bytes > 0 AND size_bytes <= 15728640
),
CONSTRAINT chk_inspection_finding_evidence_sha256 CHECK (
sha256 ~ '^[0-9a-f]{64}$'
),
CONSTRAINT chk_inspection_finding_evidence_coordinates CHECK (
(latitude IS NULL AND longitude IS NULL)
OR (
latitude IS NOT NULL AND longitude IS NOT NULL
AND latitude BETWEEN -90 AND 90
AND longitude BETWEEN -180 AND 180
)
),
CONSTRAINT chk_inspection_finding_evidence_accuracy CHECK (
accuracy_m IS NULL OR accuracy_m >= 0
),
CONSTRAINT chk_inspection_finding_evidence_kind_mime CHECK (
(kind = 'PHOTO' AND mime_type IN ('image/jpeg', 'image/png', 'image/webp'))
OR (kind = 'DOCUMENT' AND mime_type = 'application/pdf')
),
CONSTRAINT chk_inspection_finding_company_response_pdf CHECK (
purpose <> 'COMPANY_RESPONSE'
OR (
kind = 'DOCUMENT'
AND mime_type = 'application/pdf'
AND communication_id IS NOT NULL
)
),
CONSTRAINT chk_inspection_finding_attachment_link CHECK (
purpose <> 'COMMUNICATION_ATTACHMENT' OR communication_id IS NOT NULL
),
CONSTRAINT fk_inspection_finding_evidence_finding FOREIGN KEY (finding_id)
REFERENCES inspection_findings(id) ON DELETE RESTRICT,
CONSTRAINT fk_inspection_finding_evidence_communication
FOREIGN KEY (communication_id, finding_id)
REFERENCES inspection_finding_communications(id, finding_id) ON DELETE RESTRICT,
CONSTRAINT fk_inspection_finding_evidence_uploaded_by FOREIGN KEY (uploaded_by)
REFERENCES users(id) ON DELETE SET NULL
)
`);
await queryRunner.query(`
CREATE INDEX idx_inspection_finding_evidence_finding_created
ON inspection_finding_evidence (finding_id, created_at DESC)
`);
await queryRunner.query(`
CREATE INDEX idx_inspection_finding_evidence_sha256
ON inspection_finding_evidence (sha256)
`);
await queryRunner.query(`
CREATE INDEX idx_inspection_finding_evidence_communication
ON inspection_finding_evidence (communication_id)
`);
await queryRunner.query(`
INSERT INTO permissions (code, description)
VALUES
('inspection_evidence.read', 'Consultar evidencias y documentos de hallazgos'),
('inspection_evidence.create', 'Incorporar evidencias y documentos de hallazgos'),
('inspection_communications.read', 'Consultar comunicaciones de hallazgos'),
('inspection_communications.create', 'Registrar comunicaciones de hallazgos')
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 ON TABLE
inspection_finding_communications,
inspection_finding_evidence
TO ${applicationRole}
`);
await queryRunner.query(`
REVOKE UPDATE, DELETE ON TABLE
inspection_finding_communications,
inspection_finding_evidence
FROM ${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 TABLE inspection_finding_evidence');
await queryRunner.query('DROP TABLE inspection_finding_communications');
}
}