217 lines
9.0 KiB
TypeScript
217 lines
9.0 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
const newPermissions = [
|
|
'surveys.read_reports',
|
|
'surveys.capture',
|
|
'surveys.review',
|
|
] as const;
|
|
|
|
const rolePermissionValues = `
|
|
('admin', 'surveys.read_reports'),
|
|
('admin', 'surveys.capture'),
|
|
('admin', 'surveys.review'),
|
|
('director', 'surveys.read_reports'),
|
|
('director', 'surveys.review'),
|
|
('supervisor', 'surveys.read_reports'),
|
|
('supervisor', 'surveys.capture'),
|
|
('supervisor', 'surveys.review'),
|
|
('inspector', 'surveys.read_reports'),
|
|
('inspector', 'surveys.capture'),
|
|
('auditor', 'surveys.read_reports')
|
|
`;
|
|
|
|
function quoteIdentifier(identifier: string): string {
|
|
return `"${identifier.replaceAll('"', '""')}"`;
|
|
}
|
|
|
|
export class PhaseC2SurveyExecution1786896000000 implements MigrationInterface {
|
|
name = 'PhaseC2SurveyExecution1786896000000';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
ALTER TABLE survey_campaign_targets
|
|
DROP CONSTRAINT chk_survey_campaign_targets_status
|
|
`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE survey_campaign_targets
|
|
ADD CONSTRAINT chk_survey_campaign_targets_status CHECK (status IN (
|
|
'PENDING', 'IN_PROGRESS', 'SUBMITTED', 'COMPLETED', 'SKIPPED'
|
|
))
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE survey_target_reports (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
target_id uuid NOT NULL,
|
|
outcome varchar(32),
|
|
status varchar(24) NOT NULL DEFAULT 'DRAFT',
|
|
observed_at timestamptz,
|
|
latitude numeric(9, 6),
|
|
longitude numeric(9, 6),
|
|
accuracy_m numeric(12, 3),
|
|
notes text,
|
|
asset_version_at_submission integer,
|
|
submitted_at timestamptz,
|
|
submitted_by uuid,
|
|
reviewed_at timestamptz,
|
|
reviewed_by uuid,
|
|
review_notes text,
|
|
created_by uuid,
|
|
updated_by uuid,
|
|
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT uq_survey_target_reports_target_id UNIQUE (target_id),
|
|
CONSTRAINT chk_survey_target_reports_outcome CHECK (
|
|
outcome IS NULL OR outcome IN ('CONFIRMED', 'CHANGES_RECORDED', 'NOT_LOCATED')
|
|
),
|
|
CONSTRAINT chk_survey_target_reports_status CHECK (
|
|
status IN ('DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED')
|
|
),
|
|
CONSTRAINT chk_survey_target_reports_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_survey_target_reports_accuracy CHECK (
|
|
accuracy_m IS NULL OR (
|
|
accuracy_m >= 0 AND latitude IS NOT NULL AND longitude IS NOT NULL
|
|
)
|
|
),
|
|
CONSTRAINT fk_survey_target_reports_target FOREIGN KEY (target_id)
|
|
REFERENCES survey_campaign_targets(id) ON DELETE RESTRICT,
|
|
CONSTRAINT fk_survey_target_reports_submitted_by FOREIGN KEY (submitted_by)
|
|
REFERENCES users(id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_survey_target_reports_reviewed_by FOREIGN KEY (reviewed_by)
|
|
REFERENCES users(id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_survey_target_reports_created_by FOREIGN KEY (created_by)
|
|
REFERENCES users(id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_survey_target_reports_updated_by FOREIGN KEY (updated_by)
|
|
REFERENCES users(id) ON DELETE SET NULL
|
|
)
|
|
`);
|
|
await queryRunner.query(`CREATE INDEX idx_survey_target_reports_status ON survey_target_reports (status)`);
|
|
await queryRunner.query(`CREATE INDEX idx_survey_target_reports_submitted_by ON survey_target_reports (submitted_by)`);
|
|
await queryRunner.query(`CREATE INDEX idx_survey_target_reports_reviewed_by ON survey_target_reports (reviewed_by)`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE survey_target_report_media (
|
|
report_id uuid NOT NULL,
|
|
media_id uuid NOT NULL,
|
|
included boolean NOT NULL DEFAULT true,
|
|
added_by uuid,
|
|
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (report_id, media_id),
|
|
CONSTRAINT fk_survey_target_report_media_report FOREIGN KEY (report_id)
|
|
REFERENCES survey_target_reports(id) ON DELETE RESTRICT,
|
|
CONSTRAINT fk_survey_target_report_media_media FOREIGN KEY (media_id)
|
|
REFERENCES asset_media(id) ON DELETE RESTRICT,
|
|
CONSTRAINT fk_survey_target_report_media_added_by FOREIGN KEY (added_by)
|
|
REFERENCES users(id) ON DELETE SET NULL
|
|
)
|
|
`);
|
|
await queryRunner.query(`CREATE INDEX idx_survey_target_report_media_media_id ON survey_target_report_media (media_id)`);
|
|
await queryRunner.query(`CREATE INDEX idx_survey_target_report_media_included ON survey_target_report_media (report_id, included)`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE survey_target_report_versions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
report_id uuid NOT NULL,
|
|
version_number integer NOT NULL,
|
|
event varchar(24) NOT NULL,
|
|
snapshot jsonb NOT NULL,
|
|
actor_user_id uuid,
|
|
actor_username varchar(80),
|
|
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT uq_survey_target_report_versions_number UNIQUE (report_id, version_number),
|
|
CONSTRAINT chk_survey_target_report_versions_event CHECK (
|
|
event IN ('SUBMITTED', 'APPROVED', 'REJECTED')
|
|
),
|
|
CONSTRAINT fk_survey_target_report_versions_report FOREIGN KEY (report_id)
|
|
REFERENCES survey_target_reports(id) ON DELETE RESTRICT,
|
|
CONSTRAINT fk_survey_target_report_versions_actor FOREIGN KEY (actor_user_id)
|
|
REFERENCES users(id) ON DELETE SET NULL
|
|
)
|
|
`);
|
|
await queryRunner.query(`CREATE INDEX idx_survey_target_report_versions_created_at ON survey_target_report_versions (created_at)`);
|
|
|
|
await queryRunner.query(`
|
|
INSERT INTO permissions (code, description)
|
|
VALUES
|
|
('surveys.read_reports', 'Consultar capturas y versiones de relevamientos'),
|
|
('surveys.capture', 'Capturar y enviar relevamientos de campo'),
|
|
('surveys.review', 'Aprobar o rechazar relevamientos enviados')
|
|
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
|
|
survey_target_reports, survey_target_report_media
|
|
TO ${applicationRole}
|
|
`);
|
|
await queryRunner.query(`
|
|
GRANT SELECT, INSERT ON TABLE survey_target_report_versions
|
|
TO ${applicationRole}
|
|
`);
|
|
await queryRunner.query(`
|
|
REVOKE DELETE ON TABLE
|
|
survey_target_reports, survey_target_report_media, survey_target_report_versions
|
|
FROM ${applicationRole}
|
|
`);
|
|
await queryRunner.query(`
|
|
REVOKE UPDATE ON TABLE survey_target_report_versions
|
|
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 survey_target_report_versions`);
|
|
await queryRunner.query(`DROP TABLE survey_target_report_media`);
|
|
await queryRunner.query(`DROP TABLE survey_target_reports`);
|
|
await queryRunner.query(`
|
|
UPDATE survey_campaign_targets SET status = 'IN_PROGRESS' WHERE status = 'SUBMITTED'
|
|
`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE survey_campaign_targets
|
|
DROP CONSTRAINT chk_survey_campaign_targets_status
|
|
`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE survey_campaign_targets
|
|
ADD CONSTRAINT chk_survey_campaign_targets_status CHECK (status IN (
|
|
'PENDING', 'IN_PROGRESS', 'COMPLETED', 'SKIPPED'
|
|
))
|
|
`);
|
|
}
|
|
}
|