@meyicloud/meyi-insight-server 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/incidentCategories.d.ts.map +1 -1
- package/dist/constants/incidentCategories.js +2 -1
- package/dist/constants/incidentCategories.js.map +1 -1
- package/dist/controllers/incident.controller.js +1 -1
- package/dist/controllers/incident.controller.js.map +1 -1
- package/dist/database/schema.d.ts +17 -0
- package/dist/database/schema.d.ts.map +1 -1
- package/dist/database/schema.js +2 -0
- package/dist/database/schema.js.map +1 -1
- package/dist/features/sla/domain/sla.evaluator.d.ts +1 -1
- package/dist/features/sla/domain/sla.evaluator.d.ts.map +1 -1
- package/dist/features/sla/domain/sla.evaluator.js +3 -2
- package/dist/features/sla/domain/sla.evaluator.js.map +1 -1
- package/dist/features/sources/cloudtrail/controller.d.ts.map +1 -1
- package/dist/features/sources/cloudtrail/controller.js +11 -1
- package/dist/features/sources/cloudtrail/controller.js.map +1 -1
- package/dist/migrations/run.d.ts.map +1 -1
- package/dist/migrations/run.js +1297 -1254
- package/dist/migrations/run.js.map +1 -1
- package/dist/models/alert.model.d.ts +7 -0
- package/dist/models/alert.model.d.ts.map +1 -1
- package/dist/models/alert.model.js +28 -1
- package/dist/models/alert.model.js.map +1 -1
- package/dist/models/incident.model.d.ts.map +1 -1
- package/dist/models/incident.model.js +1 -0
- package/dist/models/incident.model.js.map +1 -1
- package/dist/models/source.model.d.ts.map +1 -1
- package/dist/models/source.model.js +21 -7
- package/dist/models/source.model.js.map +1 -1
- package/dist/pipeline/AlarmPipelineWorker.d.ts.map +1 -1
- package/dist/pipeline/AlarmPipelineWorker.js +12 -9
- package/dist/pipeline/AlarmPipelineWorker.js.map +1 -1
- package/dist/pipeline/NormalizationService.d.ts.map +1 -1
- package/dist/pipeline/NormalizationService.js +5 -0
- package/dist/pipeline/NormalizationService.js.map +1 -1
- package/dist/pipeline/alarmQueue.d.ts +19 -1
- package/dist/pipeline/alarmQueue.d.ts.map +1 -1
- package/dist/pipeline/alarmQueue.js +93 -16
- package/dist/pipeline/alarmQueue.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -1
- package/dist/routes/index.js.map +1 -1
- package/dist/services/alert.service.d.ts +8 -0
- package/dist/services/alert.service.d.ts.map +1 -1
- package/dist/services/alert.service.js +20 -2
- package/dist/services/alert.service.js.map +1 -1
- package/dist/services/incident.service.d.ts +17 -1
- package/dist/services/incident.service.d.ts.map +1 -1
- package/dist/services/incident.service.js +48 -15
- package/dist/services/incident.service.js.map +1 -1
- package/dist/services/notification-provider.service.d.ts.map +1 -1
- package/dist/services/notification-provider.service.js +3 -1
- package/dist/services/notification-provider.service.js.map +1 -1
- package/package.json +75 -75
package/dist/migrations/run.js
CHANGED
|
@@ -22,146 +22,146 @@ export async function runMigrations(db) {
|
|
|
22
22
|
? defaultTokenTtlDaysRaw
|
|
23
23
|
: 30;
|
|
24
24
|
// Rename tables and columns if they exist from the old 'connector' naming convention
|
|
25
|
-
await db.execute(sql.raw(`
|
|
26
|
-
DO $$
|
|
27
|
-
BEGIN
|
|
28
|
-
-- Rename main table
|
|
29
|
-
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_connectors') THEN
|
|
30
|
-
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sources') THEN
|
|
31
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_connectors" RENAME TO "insight_sources"';
|
|
32
|
-
END IF;
|
|
33
|
-
END IF;
|
|
34
|
-
|
|
35
|
-
-- Rename CloudWatch setups
|
|
36
|
-
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_cloudwatch_connector_setups') THEN
|
|
37
|
-
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_cloudwatch_source_setups') THEN
|
|
38
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_cloudwatch_connector_setups" RENAME TO "insight_cloudwatch_source_setups"';
|
|
39
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_cloudwatch_source_setups" RENAME COLUMN "connector_id" TO "source_id"';
|
|
40
|
-
END IF;
|
|
41
|
-
END IF;
|
|
42
|
-
|
|
43
|
-
-- Rename CloudTrail setups
|
|
44
|
-
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_cloudtrail_connector_setups') THEN
|
|
45
|
-
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_cloudtrail_source_setups') THEN
|
|
46
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_cloudtrail_connector_setups" RENAME TO "insight_cloudtrail_source_setups"';
|
|
47
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_cloudtrail_source_setups" RENAME COLUMN "connector_id" TO "source_id"';
|
|
48
|
-
END IF;
|
|
49
|
-
END IF;
|
|
50
|
-
|
|
51
|
-
-- Rename Grafana setups
|
|
52
|
-
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_grafana_connector_setups') THEN
|
|
53
|
-
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_grafana_source_setups') THEN
|
|
54
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_grafana_connector_setups" RENAME TO "insight_grafana_source_setups"';
|
|
55
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_grafana_source_setups" RENAME COLUMN "connector_id" TO "source_id"';
|
|
56
|
-
END IF;
|
|
57
|
-
END IF;
|
|
58
|
-
|
|
59
|
-
-- Rename Prometheus setups
|
|
60
|
-
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_prometheus_connector_setups') THEN
|
|
61
|
-
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_prometheus_source_setups') THEN
|
|
62
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_prometheus_connector_setups" RENAME TO "insight_prometheus_source_setups"';
|
|
63
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_prometheus_source_setups" RENAME COLUMN "connector_id" TO "source_id"';
|
|
64
|
-
END IF;
|
|
65
|
-
END IF;
|
|
66
|
-
|
|
67
|
-
-- Rename Connector Health
|
|
68
|
-
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_connector_health') THEN
|
|
69
|
-
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_source_health') THEN
|
|
70
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_connector_health" RENAME TO "insight_source_health"';
|
|
71
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_source_health" RENAME COLUMN "connector_id" TO "source_id"';
|
|
72
|
-
END IF;
|
|
73
|
-
END IF;
|
|
74
|
-
|
|
75
|
-
-- Rename Webhook Failure Logs
|
|
76
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_webhook_failure_logs' AND column_name = 'connector_id') THEN
|
|
77
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_webhook_failure_logs" RENAME COLUMN "connector_id" TO "source_id"';
|
|
78
|
-
END IF;
|
|
79
|
-
|
|
80
|
-
-- Rename columns in alerts
|
|
81
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'connector_id') THEN
|
|
82
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_alerts" RENAME COLUMN "connector_id" TO "source_id"';
|
|
83
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_alerts" RENAME COLUMN "connector_name" TO "source_name"';
|
|
84
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_alerts" RENAME COLUMN "connector_type" TO "source_type"';
|
|
85
|
-
END IF;
|
|
86
|
-
|
|
87
|
-
-- Rename columns in configs
|
|
88
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_normalization_configs' AND column_name = 'connector_id') THEN
|
|
89
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_normalization_configs" RENAME COLUMN "connector_id" TO "source_id"';
|
|
90
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_normalization_configs" RENAME COLUMN "connector_type" TO "source_type"';
|
|
91
|
-
END IF;
|
|
92
|
-
|
|
93
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_dedup_configs' AND column_name = 'connector_id') THEN
|
|
94
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_dedup_configs" RENAME COLUMN "connector_id" TO "source_id"';
|
|
95
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_dedup_configs" RENAME COLUMN "connector_type" TO "source_type"';
|
|
96
|
-
END IF;
|
|
97
|
-
|
|
98
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_grouping_configs' AND column_name = 'connector_id') THEN
|
|
99
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_grouping_configs" RENAME COLUMN "connector_id" TO "source_id"';
|
|
100
|
-
EXECUTE 'ALTER TABLE "meyiconnect"."insight_grouping_configs" RENAME COLUMN "connector_type" TO "source_type"';
|
|
101
|
-
END IF;
|
|
102
|
-
|
|
103
|
-
END $$;
|
|
104
|
-
`));
|
|
105
|
-
await db.execute(sql.raw(`
|
|
106
|
-
DO $$
|
|
107
|
-
DECLARE
|
|
108
|
-
v_relkind "char";
|
|
109
|
-
BEGIN
|
|
110
|
-
SELECT c.relkind INTO v_relkind
|
|
111
|
-
FROM pg_class c
|
|
112
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
113
|
-
WHERE n.nspname = 'meyiconnect' AND c.relname = 'insight_tenants'
|
|
114
|
-
LIMIT 1;
|
|
115
|
-
|
|
116
|
-
IF v_relkind IS NULL THEN
|
|
117
|
-
CREATE TABLE "meyiconnect"."insight_tenants" (
|
|
118
|
-
"id" TEXT PRIMARY KEY,
|
|
119
|
-
"name" TEXT NOT NULL,
|
|
120
|
-
"is_default" BOOLEAN NOT NULL DEFAULT false,
|
|
121
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
122
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
123
|
-
);
|
|
124
|
-
${defaultTenantId ? `INSERT INTO "meyiconnect"."insight_tenants" ("id", "name", "is_default")
|
|
125
|
-
VALUES ('${defaultTenantSql}', 'Default tenant', true)
|
|
126
|
-
ON CONFLICT ("id") DO UPDATE SET "is_default" = true, "updated_at" = NOW();` : "NULL;"}
|
|
127
|
-
ELSIF v_relkind = 'r' THEN
|
|
128
|
-
${defaultTenantId ? `INSERT INTO "meyiconnect"."insight_tenants" ("id", "name", "is_default")
|
|
129
|
-
VALUES ('${defaultTenantSql}', 'Default tenant', true)
|
|
130
|
-
ON CONFLICT ("id") DO UPDATE SET "is_default" = true, "updated_at" = NOW();` : "NULL;"}
|
|
131
|
-
END IF;
|
|
132
|
-
END $$;
|
|
25
|
+
await db.execute(sql.raw(`
|
|
26
|
+
DO $$
|
|
27
|
+
BEGIN
|
|
28
|
+
-- Rename main table
|
|
29
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_connectors') THEN
|
|
30
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sources') THEN
|
|
31
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_connectors" RENAME TO "insight_sources"';
|
|
32
|
+
END IF;
|
|
33
|
+
END IF;
|
|
34
|
+
|
|
35
|
+
-- Rename CloudWatch setups
|
|
36
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_cloudwatch_connector_setups') THEN
|
|
37
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_cloudwatch_source_setups') THEN
|
|
38
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_cloudwatch_connector_setups" RENAME TO "insight_cloudwatch_source_setups"';
|
|
39
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_cloudwatch_source_setups" RENAME COLUMN "connector_id" TO "source_id"';
|
|
40
|
+
END IF;
|
|
41
|
+
END IF;
|
|
42
|
+
|
|
43
|
+
-- Rename CloudTrail setups
|
|
44
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_cloudtrail_connector_setups') THEN
|
|
45
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_cloudtrail_source_setups') THEN
|
|
46
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_cloudtrail_connector_setups" RENAME TO "insight_cloudtrail_source_setups"';
|
|
47
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_cloudtrail_source_setups" RENAME COLUMN "connector_id" TO "source_id"';
|
|
48
|
+
END IF;
|
|
49
|
+
END IF;
|
|
50
|
+
|
|
51
|
+
-- Rename Grafana setups
|
|
52
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_grafana_connector_setups') THEN
|
|
53
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_grafana_source_setups') THEN
|
|
54
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_grafana_connector_setups" RENAME TO "insight_grafana_source_setups"';
|
|
55
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_grafana_source_setups" RENAME COLUMN "connector_id" TO "source_id"';
|
|
56
|
+
END IF;
|
|
57
|
+
END IF;
|
|
58
|
+
|
|
59
|
+
-- Rename Prometheus setups
|
|
60
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_prometheus_connector_setups') THEN
|
|
61
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_prometheus_source_setups') THEN
|
|
62
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_prometheus_connector_setups" RENAME TO "insight_prometheus_source_setups"';
|
|
63
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_prometheus_source_setups" RENAME COLUMN "connector_id" TO "source_id"';
|
|
64
|
+
END IF;
|
|
65
|
+
END IF;
|
|
66
|
+
|
|
67
|
+
-- Rename Connector Health
|
|
68
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_connector_health') THEN
|
|
69
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'insight_source_health') THEN
|
|
70
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_connector_health" RENAME TO "insight_source_health"';
|
|
71
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_source_health" RENAME COLUMN "connector_id" TO "source_id"';
|
|
72
|
+
END IF;
|
|
73
|
+
END IF;
|
|
74
|
+
|
|
75
|
+
-- Rename Webhook Failure Logs
|
|
76
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_webhook_failure_logs' AND column_name = 'connector_id') THEN
|
|
77
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_webhook_failure_logs" RENAME COLUMN "connector_id" TO "source_id"';
|
|
78
|
+
END IF;
|
|
79
|
+
|
|
80
|
+
-- Rename columns in alerts
|
|
81
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'connector_id') THEN
|
|
82
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_alerts" RENAME COLUMN "connector_id" TO "source_id"';
|
|
83
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_alerts" RENAME COLUMN "connector_name" TO "source_name"';
|
|
84
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_alerts" RENAME COLUMN "connector_type" TO "source_type"';
|
|
85
|
+
END IF;
|
|
86
|
+
|
|
87
|
+
-- Rename columns in configs
|
|
88
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_normalization_configs' AND column_name = 'connector_id') THEN
|
|
89
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_normalization_configs" RENAME COLUMN "connector_id" TO "source_id"';
|
|
90
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_normalization_configs" RENAME COLUMN "connector_type" TO "source_type"';
|
|
91
|
+
END IF;
|
|
92
|
+
|
|
93
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_dedup_configs' AND column_name = 'connector_id') THEN
|
|
94
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_dedup_configs" RENAME COLUMN "connector_id" TO "source_id"';
|
|
95
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_dedup_configs" RENAME COLUMN "connector_type" TO "source_type"';
|
|
96
|
+
END IF;
|
|
97
|
+
|
|
98
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_grouping_configs' AND column_name = 'connector_id') THEN
|
|
99
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_grouping_configs" RENAME COLUMN "connector_id" TO "source_id"';
|
|
100
|
+
EXECUTE 'ALTER TABLE "meyiconnect"."insight_grouping_configs" RENAME COLUMN "connector_type" TO "source_type"';
|
|
101
|
+
END IF;
|
|
102
|
+
|
|
103
|
+
END $$;
|
|
104
|
+
`));
|
|
105
|
+
await db.execute(sql.raw(`
|
|
106
|
+
DO $$
|
|
107
|
+
DECLARE
|
|
108
|
+
v_relkind "char";
|
|
109
|
+
BEGIN
|
|
110
|
+
SELECT c.relkind INTO v_relkind
|
|
111
|
+
FROM pg_class c
|
|
112
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
113
|
+
WHERE n.nspname = 'meyiconnect' AND c.relname = 'insight_tenants'
|
|
114
|
+
LIMIT 1;
|
|
115
|
+
|
|
116
|
+
IF v_relkind IS NULL THEN
|
|
117
|
+
CREATE TABLE "meyiconnect"."insight_tenants" (
|
|
118
|
+
"id" TEXT PRIMARY KEY,
|
|
119
|
+
"name" TEXT NOT NULL,
|
|
120
|
+
"is_default" BOOLEAN NOT NULL DEFAULT false,
|
|
121
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
122
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
123
|
+
);
|
|
124
|
+
${defaultTenantId ? `INSERT INTO "meyiconnect"."insight_tenants" ("id", "name", "is_default")
|
|
125
|
+
VALUES ('${defaultTenantSql}', 'Default tenant', true)
|
|
126
|
+
ON CONFLICT ("id") DO UPDATE SET "is_default" = true, "updated_at" = NOW();` : "NULL;"}
|
|
127
|
+
ELSIF v_relkind = 'r' THEN
|
|
128
|
+
${defaultTenantId ? `INSERT INTO "meyiconnect"."insight_tenants" ("id", "name", "is_default")
|
|
129
|
+
VALUES ('${defaultTenantSql}', 'Default tenant', true)
|
|
130
|
+
ON CONFLICT ("id") DO UPDATE SET "is_default" = true, "updated_at" = NOW();` : "NULL;"}
|
|
131
|
+
END IF;
|
|
132
|
+
END $$;
|
|
133
133
|
`));
|
|
134
134
|
// insight_sources
|
|
135
|
-
await db.execute(sql.raw(`
|
|
136
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_sources" (
|
|
137
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
138
|
-
"name" TEXT NOT NULL,
|
|
139
|
-
"type" TEXT NOT NULL,
|
|
140
|
-
"webhook_token" TEXT NOT NULL,
|
|
141
|
-
"webhook_auth_token" TEXT,
|
|
142
|
-
"webhook_token_never_expires" BOOLEAN NOT NULL DEFAULT false,
|
|
143
|
-
"webhook_token_expires_at" TIMESTAMPTZ,
|
|
144
|
-
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
145
|
-
"description" TEXT,
|
|
146
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
147
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
148
|
-
)
|
|
149
|
-
`));
|
|
150
|
-
await db.execute(sql.raw(`
|
|
151
|
-
DO $$
|
|
152
|
-
BEGIN
|
|
153
|
-
IF EXISTS (
|
|
154
|
-
SELECT 1 FROM information_schema.columns
|
|
155
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sources' AND column_name = 'tenant_id' AND data_type = 'uuid'
|
|
156
|
-
) THEN
|
|
157
|
-
ALTER TABLE "meyiconnect"."insight_sources" ALTER COLUMN "tenant_id" TYPE TEXT USING "tenant_id"::TEXT;
|
|
158
|
-
ELSIF NOT EXISTS (
|
|
159
|
-
SELECT 1 FROM information_schema.columns
|
|
160
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sources' AND column_name = 'tenant_id'
|
|
161
|
-
) THEN
|
|
162
|
-
ALTER TABLE "meyiconnect"."insight_sources" ADD COLUMN "tenant_id" TEXT;
|
|
163
|
-
END IF;
|
|
164
|
-
END $$;
|
|
135
|
+
await db.execute(sql.raw(`
|
|
136
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_sources" (
|
|
137
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
138
|
+
"name" TEXT NOT NULL,
|
|
139
|
+
"type" TEXT NOT NULL,
|
|
140
|
+
"webhook_token" TEXT NOT NULL,
|
|
141
|
+
"webhook_auth_token" TEXT,
|
|
142
|
+
"webhook_token_never_expires" BOOLEAN NOT NULL DEFAULT false,
|
|
143
|
+
"webhook_token_expires_at" TIMESTAMPTZ,
|
|
144
|
+
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
145
|
+
"description" TEXT,
|
|
146
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
147
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
148
|
+
)
|
|
149
|
+
`));
|
|
150
|
+
await db.execute(sql.raw(`
|
|
151
|
+
DO $$
|
|
152
|
+
BEGIN
|
|
153
|
+
IF EXISTS (
|
|
154
|
+
SELECT 1 FROM information_schema.columns
|
|
155
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sources' AND column_name = 'tenant_id' AND data_type = 'uuid'
|
|
156
|
+
) THEN
|
|
157
|
+
ALTER TABLE "meyiconnect"."insight_sources" ALTER COLUMN "tenant_id" TYPE TEXT USING "tenant_id"::TEXT;
|
|
158
|
+
ELSIF NOT EXISTS (
|
|
159
|
+
SELECT 1 FROM information_schema.columns
|
|
160
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sources' AND column_name = 'tenant_id'
|
|
161
|
+
) THEN
|
|
162
|
+
ALTER TABLE "meyiconnect"."insight_sources" ADD COLUMN "tenant_id" TEXT;
|
|
163
|
+
END IF;
|
|
164
|
+
END $$;
|
|
165
165
|
`));
|
|
166
166
|
await backfill(`UPDATE "meyiconnect"."insight_sources" SET "tenant_id" = '${defaultTenantSql}' WHERE "tenant_id" IS NULL OR "tenant_id"::text = ''`);
|
|
167
167
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_sources_tenant_id_idx ON "meyiconnect"."insight_sources" ("tenant_id")`));
|
|
@@ -171,63 +171,63 @@ export async function runMigrations(db) {
|
|
|
171
171
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sources" ADD COLUMN IF NOT EXISTS "description" TEXT`));
|
|
172
172
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sources" ALTER COLUMN "webhook_token_expires_at" SET DEFAULT (NOW() + INTERVAL '${defaultTokenTtlDays} days')`));
|
|
173
173
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sources" DROP COLUMN IF EXISTS "config"`));
|
|
174
|
-
await db.execute(sql.raw(`
|
|
175
|
-
UPDATE "meyiconnect"."insight_sources"
|
|
176
|
-
SET "webhook_token" = gen_random_uuid()::TEXT
|
|
177
|
-
WHERE "webhook_token" IS NULL
|
|
174
|
+
await db.execute(sql.raw(`
|
|
175
|
+
UPDATE "meyiconnect"."insight_sources"
|
|
176
|
+
SET "webhook_token" = gen_random_uuid()::TEXT
|
|
177
|
+
WHERE "webhook_token" IS NULL
|
|
178
178
|
`));
|
|
179
179
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sources" ALTER COLUMN "webhook_token" SET NOT NULL`)).catch(() => { });
|
|
180
|
-
await db.execute(sql.raw(`
|
|
181
|
-
UPDATE "meyiconnect"."insight_sources"
|
|
182
|
-
SET "webhook_token_expires_at" = NOW() + INTERVAL '${defaultTokenTtlDays} days'
|
|
183
|
-
WHERE COALESCE("webhook_token_never_expires", FALSE) = FALSE
|
|
184
|
-
AND "webhook_token_expires_at" IS NULL
|
|
180
|
+
await db.execute(sql.raw(`
|
|
181
|
+
UPDATE "meyiconnect"."insight_sources"
|
|
182
|
+
SET "webhook_token_expires_at" = NOW() + INTERVAL '${defaultTokenTtlDays} days'
|
|
183
|
+
WHERE COALESCE("webhook_token_never_expires", FALSE) = FALSE
|
|
184
|
+
AND "webhook_token_expires_at" IS NULL
|
|
185
185
|
`));
|
|
186
|
-
await db.execute(sql.raw(`
|
|
187
|
-
CREATE UNIQUE INDEX IF NOT EXISTS insight_sources_webhook_token_unique
|
|
188
|
-
ON "meyiconnect"."insight_sources" ("webhook_token")
|
|
186
|
+
await db.execute(sql.raw(`
|
|
187
|
+
CREATE UNIQUE INDEX IF NOT EXISTS insight_sources_webhook_token_unique
|
|
188
|
+
ON "meyiconnect"."insight_sources" ("webhook_token")
|
|
189
189
|
`));
|
|
190
190
|
// CloudWatch source setup lives in a dedicated one-to-one table.
|
|
191
|
-
await db.execute(sql.raw(`
|
|
192
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_cloudwatch_source_setups" (
|
|
193
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
194
|
-
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
195
|
-
"aws_access_key_id" TEXT,
|
|
196
|
-
"aws_secret_access_key" TEXT,
|
|
197
|
-
"aws_session_token" TEXT,
|
|
198
|
-
"aws_region" TEXT,
|
|
199
|
-
"aws_stack_name" TEXT,
|
|
200
|
-
"aws_lambda_name" TEXT,
|
|
201
|
-
"aws_alarm_name_prefix" TEXT,
|
|
202
|
-
"aws_selected_alarm_names" JSONB DEFAULT '[]',
|
|
203
|
-
"aws_selected_metric_keys" JSONB DEFAULT '[]',
|
|
204
|
-
"aws_deployment_status" TEXT NOT NULL DEFAULT 'not_started',
|
|
205
|
-
"aws_deployment_status_reason" TEXT,
|
|
206
|
-
"aws_stack_id" TEXT,
|
|
207
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
208
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
209
|
-
)
|
|
191
|
+
await db.execute(sql.raw(`
|
|
192
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_cloudwatch_source_setups" (
|
|
193
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
194
|
+
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
195
|
+
"aws_access_key_id" TEXT,
|
|
196
|
+
"aws_secret_access_key" TEXT,
|
|
197
|
+
"aws_session_token" TEXT,
|
|
198
|
+
"aws_region" TEXT,
|
|
199
|
+
"aws_stack_name" TEXT,
|
|
200
|
+
"aws_lambda_name" TEXT,
|
|
201
|
+
"aws_alarm_name_prefix" TEXT,
|
|
202
|
+
"aws_selected_alarm_names" JSONB DEFAULT '[]',
|
|
203
|
+
"aws_selected_metric_keys" JSONB DEFAULT '[]',
|
|
204
|
+
"aws_deployment_status" TEXT NOT NULL DEFAULT 'not_started',
|
|
205
|
+
"aws_deployment_status_reason" TEXT,
|
|
206
|
+
"aws_stack_id" TEXT,
|
|
207
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
208
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
209
|
+
)
|
|
210
210
|
`));
|
|
211
211
|
await db.execute(sql.raw(`CREATE UNIQUE INDEX IF NOT EXISTS insight_cloudwatch_source_setups_source_unique ON "meyiconnect"."insight_cloudwatch_source_setups" ("source_id")`));
|
|
212
212
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_cloudwatch_source_setups_status_idx ON "meyiconnect"."insight_cloudwatch_source_setups" ("aws_deployment_status")`));
|
|
213
213
|
// CloudTrail source setup — dedicated one-to-one side-table (same pattern as cloudwatch).
|
|
214
|
-
await db.execute(sql.raw(`
|
|
215
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_cloudtrail_source_setups" (
|
|
216
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
217
|
-
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
218
|
-
"aws_region" TEXT,
|
|
219
|
-
"aws_stack_name" TEXT,
|
|
220
|
-
"aws_create_new_trail" BOOLEAN NOT NULL DEFAULT true,
|
|
221
|
-
"aws_deployment_status" TEXT NOT NULL DEFAULT 'not_started',
|
|
222
|
-
"aws_deployment_status_reason" TEXT,
|
|
223
|
-
"aws_stack_id" TEXT,
|
|
224
|
-
"aws_deployment_mode" TEXT DEFAULT 'Organizations',
|
|
225
|
-
"aws_target_ous" TEXT,
|
|
226
|
-
"aws_target_accounts" TEXT,
|
|
227
|
-
"aws_target_regions" TEXT,
|
|
228
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
229
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
230
|
-
)
|
|
214
|
+
await db.execute(sql.raw(`
|
|
215
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_cloudtrail_source_setups" (
|
|
216
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
217
|
+
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
218
|
+
"aws_region" TEXT,
|
|
219
|
+
"aws_stack_name" TEXT,
|
|
220
|
+
"aws_create_new_trail" BOOLEAN NOT NULL DEFAULT true,
|
|
221
|
+
"aws_deployment_status" TEXT NOT NULL DEFAULT 'not_started',
|
|
222
|
+
"aws_deployment_status_reason" TEXT,
|
|
223
|
+
"aws_stack_id" TEXT,
|
|
224
|
+
"aws_deployment_mode" TEXT DEFAULT 'Organizations',
|
|
225
|
+
"aws_target_ous" TEXT,
|
|
226
|
+
"aws_target_accounts" TEXT,
|
|
227
|
+
"aws_target_regions" TEXT,
|
|
228
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
229
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
230
|
+
)
|
|
231
231
|
`));
|
|
232
232
|
await db.execute(sql.raw(`CREATE UNIQUE INDEX IF NOT EXISTS insight_cloudtrail_source_setups_source_unique ON "meyiconnect"."insight_cloudtrail_source_setups" ("source_id")`));
|
|
233
233
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_cloudtrail_source_setups_status_idx ON "meyiconnect"."insight_cloudtrail_source_setups" ("aws_deployment_status")`));
|
|
@@ -236,209 +236,209 @@ export async function runMigrations(db) {
|
|
|
236
236
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_cloudtrail_source_setups" ADD COLUMN IF NOT EXISTS "aws_target_ous" TEXT`));
|
|
237
237
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_cloudtrail_source_setups" ADD COLUMN IF NOT EXISTS "aws_target_accounts" TEXT`));
|
|
238
238
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_cloudtrail_source_setups" ADD COLUMN IF NOT EXISTS "aws_target_regions" TEXT`));
|
|
239
|
-
await db.execute(sql.raw(`
|
|
240
|
-
DO $$
|
|
241
|
-
BEGIN
|
|
242
|
-
IF EXISTS (
|
|
243
|
-
SELECT 1
|
|
244
|
-
FROM information_schema.columns
|
|
245
|
-
WHERE table_schema = 'meyiconnect'
|
|
246
|
-
AND table_name = 'insight_sources'
|
|
247
|
-
AND column_name = 'aws_region'
|
|
248
|
-
) THEN
|
|
249
|
-
INSERT INTO "meyiconnect"."insight_cloudwatch_source_setups" (
|
|
250
|
-
"source_id",
|
|
251
|
-
"aws_access_key_id",
|
|
252
|
-
"aws_secret_access_key",
|
|
253
|
-
"aws_session_token",
|
|
254
|
-
"aws_region",
|
|
255
|
-
"aws_stack_name",
|
|
256
|
-
"aws_lambda_name",
|
|
257
|
-
"aws_alarm_name_prefix",
|
|
258
|
-
"aws_selected_alarm_names",
|
|
259
|
-
"aws_selected_metric_keys",
|
|
260
|
-
"aws_deployment_status",
|
|
261
|
-
"aws_deployment_status_reason",
|
|
262
|
-
"aws_stack_id",
|
|
263
|
-
"created_at",
|
|
264
|
-
"updated_at"
|
|
265
|
-
)
|
|
266
|
-
SELECT
|
|
267
|
-
"id",
|
|
268
|
-
"aws_access_key_id",
|
|
269
|
-
"aws_secret_access_key",
|
|
270
|
-
"aws_session_token",
|
|
271
|
-
"aws_region",
|
|
272
|
-
"aws_stack_name",
|
|
273
|
-
"aws_lambda_name",
|
|
274
|
-
"aws_alarm_name_prefix",
|
|
275
|
-
COALESCE("aws_selected_alarm_names", '[]'::jsonb),
|
|
276
|
-
COALESCE("aws_selected_metric_keys", '[]'::jsonb),
|
|
277
|
-
COALESCE("aws_deployment_status", 'not_started'),
|
|
278
|
-
"aws_deployment_status_reason",
|
|
279
|
-
"aws_stack_id",
|
|
280
|
-
NOW(),
|
|
281
|
-
NOW()
|
|
282
|
-
FROM "meyiconnect"."insight_sources"
|
|
283
|
-
WHERE "type" = 'cloudwatch'
|
|
284
|
-
AND (
|
|
285
|
-
"aws_access_key_id" IS NOT NULL OR
|
|
286
|
-
"aws_secret_access_key" IS NOT NULL OR
|
|
287
|
-
"aws_session_token" IS NOT NULL OR
|
|
288
|
-
"aws_region" IS NOT NULL OR
|
|
289
|
-
"aws_stack_name" IS NOT NULL OR
|
|
290
|
-
"aws_lambda_name" IS NOT NULL OR
|
|
291
|
-
"aws_alarm_name_prefix" IS NOT NULL OR
|
|
292
|
-
"aws_deployment_status" IS NOT NULL OR
|
|
293
|
-
"aws_stack_id" IS NOT NULL
|
|
294
|
-
)
|
|
295
|
-
ON CONFLICT ("source_id") DO UPDATE SET
|
|
296
|
-
"aws_access_key_id" = EXCLUDED."aws_access_key_id",
|
|
297
|
-
"aws_secret_access_key" = EXCLUDED."aws_secret_access_key",
|
|
298
|
-
"aws_session_token" = EXCLUDED."aws_session_token",
|
|
299
|
-
"aws_region" = EXCLUDED."aws_region",
|
|
300
|
-
"aws_stack_name" = EXCLUDED."aws_stack_name",
|
|
301
|
-
"aws_lambda_name" = EXCLUDED."aws_lambda_name",
|
|
302
|
-
"aws_alarm_name_prefix" = EXCLUDED."aws_alarm_name_prefix",
|
|
303
|
-
"aws_selected_alarm_names" = EXCLUDED."aws_selected_alarm_names",
|
|
304
|
-
"aws_selected_metric_keys" = EXCLUDED."aws_selected_metric_keys",
|
|
305
|
-
"aws_deployment_status" = EXCLUDED."aws_deployment_status",
|
|
306
|
-
"aws_deployment_status_reason" = EXCLUDED."aws_deployment_status_reason",
|
|
307
|
-
"aws_stack_id" = EXCLUDED."aws_stack_id",
|
|
308
|
-
"updated_at" = NOW();
|
|
309
|
-
END IF;
|
|
310
|
-
END $$;
|
|
311
|
-
`));
|
|
312
|
-
await db.execute(sql.raw(`
|
|
313
|
-
ALTER TABLE "meyiconnect"."insight_sources"
|
|
314
|
-
DROP COLUMN IF EXISTS "aws_access_key_id",
|
|
315
|
-
DROP COLUMN IF EXISTS "aws_secret_access_key",
|
|
316
|
-
DROP COLUMN IF EXISTS "aws_session_token",
|
|
317
|
-
DROP COLUMN IF EXISTS "aws_region",
|
|
318
|
-
DROP COLUMN IF EXISTS "aws_stack_name",
|
|
319
|
-
DROP COLUMN IF EXISTS "aws_lambda_name",
|
|
320
|
-
DROP COLUMN IF EXISTS "aws_alarm_name_prefix",
|
|
321
|
-
DROP COLUMN IF EXISTS "aws_selected_alarm_names",
|
|
322
|
-
DROP COLUMN IF EXISTS "aws_selected_metric_keys",
|
|
323
|
-
DROP COLUMN IF EXISTS "aws_deployment_status",
|
|
324
|
-
DROP COLUMN IF EXISTS "aws_deployment_status_reason",
|
|
325
|
-
DROP COLUMN IF EXISTS "aws_stack_id"
|
|
239
|
+
await db.execute(sql.raw(`
|
|
240
|
+
DO $$
|
|
241
|
+
BEGIN
|
|
242
|
+
IF EXISTS (
|
|
243
|
+
SELECT 1
|
|
244
|
+
FROM information_schema.columns
|
|
245
|
+
WHERE table_schema = 'meyiconnect'
|
|
246
|
+
AND table_name = 'insight_sources'
|
|
247
|
+
AND column_name = 'aws_region'
|
|
248
|
+
) THEN
|
|
249
|
+
INSERT INTO "meyiconnect"."insight_cloudwatch_source_setups" (
|
|
250
|
+
"source_id",
|
|
251
|
+
"aws_access_key_id",
|
|
252
|
+
"aws_secret_access_key",
|
|
253
|
+
"aws_session_token",
|
|
254
|
+
"aws_region",
|
|
255
|
+
"aws_stack_name",
|
|
256
|
+
"aws_lambda_name",
|
|
257
|
+
"aws_alarm_name_prefix",
|
|
258
|
+
"aws_selected_alarm_names",
|
|
259
|
+
"aws_selected_metric_keys",
|
|
260
|
+
"aws_deployment_status",
|
|
261
|
+
"aws_deployment_status_reason",
|
|
262
|
+
"aws_stack_id",
|
|
263
|
+
"created_at",
|
|
264
|
+
"updated_at"
|
|
265
|
+
)
|
|
266
|
+
SELECT
|
|
267
|
+
"id",
|
|
268
|
+
"aws_access_key_id",
|
|
269
|
+
"aws_secret_access_key",
|
|
270
|
+
"aws_session_token",
|
|
271
|
+
"aws_region",
|
|
272
|
+
"aws_stack_name",
|
|
273
|
+
"aws_lambda_name",
|
|
274
|
+
"aws_alarm_name_prefix",
|
|
275
|
+
COALESCE("aws_selected_alarm_names", '[]'::jsonb),
|
|
276
|
+
COALESCE("aws_selected_metric_keys", '[]'::jsonb),
|
|
277
|
+
COALESCE("aws_deployment_status", 'not_started'),
|
|
278
|
+
"aws_deployment_status_reason",
|
|
279
|
+
"aws_stack_id",
|
|
280
|
+
NOW(),
|
|
281
|
+
NOW()
|
|
282
|
+
FROM "meyiconnect"."insight_sources"
|
|
283
|
+
WHERE "type" = 'cloudwatch'
|
|
284
|
+
AND (
|
|
285
|
+
"aws_access_key_id" IS NOT NULL OR
|
|
286
|
+
"aws_secret_access_key" IS NOT NULL OR
|
|
287
|
+
"aws_session_token" IS NOT NULL OR
|
|
288
|
+
"aws_region" IS NOT NULL OR
|
|
289
|
+
"aws_stack_name" IS NOT NULL OR
|
|
290
|
+
"aws_lambda_name" IS NOT NULL OR
|
|
291
|
+
"aws_alarm_name_prefix" IS NOT NULL OR
|
|
292
|
+
"aws_deployment_status" IS NOT NULL OR
|
|
293
|
+
"aws_stack_id" IS NOT NULL
|
|
294
|
+
)
|
|
295
|
+
ON CONFLICT ("source_id") DO UPDATE SET
|
|
296
|
+
"aws_access_key_id" = EXCLUDED."aws_access_key_id",
|
|
297
|
+
"aws_secret_access_key" = EXCLUDED."aws_secret_access_key",
|
|
298
|
+
"aws_session_token" = EXCLUDED."aws_session_token",
|
|
299
|
+
"aws_region" = EXCLUDED."aws_region",
|
|
300
|
+
"aws_stack_name" = EXCLUDED."aws_stack_name",
|
|
301
|
+
"aws_lambda_name" = EXCLUDED."aws_lambda_name",
|
|
302
|
+
"aws_alarm_name_prefix" = EXCLUDED."aws_alarm_name_prefix",
|
|
303
|
+
"aws_selected_alarm_names" = EXCLUDED."aws_selected_alarm_names",
|
|
304
|
+
"aws_selected_metric_keys" = EXCLUDED."aws_selected_metric_keys",
|
|
305
|
+
"aws_deployment_status" = EXCLUDED."aws_deployment_status",
|
|
306
|
+
"aws_deployment_status_reason" = EXCLUDED."aws_deployment_status_reason",
|
|
307
|
+
"aws_stack_id" = EXCLUDED."aws_stack_id",
|
|
308
|
+
"updated_at" = NOW();
|
|
309
|
+
END IF;
|
|
310
|
+
END $$;
|
|
311
|
+
`));
|
|
312
|
+
await db.execute(sql.raw(`
|
|
313
|
+
ALTER TABLE "meyiconnect"."insight_sources"
|
|
314
|
+
DROP COLUMN IF EXISTS "aws_access_key_id",
|
|
315
|
+
DROP COLUMN IF EXISTS "aws_secret_access_key",
|
|
316
|
+
DROP COLUMN IF EXISTS "aws_session_token",
|
|
317
|
+
DROP COLUMN IF EXISTS "aws_region",
|
|
318
|
+
DROP COLUMN IF EXISTS "aws_stack_name",
|
|
319
|
+
DROP COLUMN IF EXISTS "aws_lambda_name",
|
|
320
|
+
DROP COLUMN IF EXISTS "aws_alarm_name_prefix",
|
|
321
|
+
DROP COLUMN IF EXISTS "aws_selected_alarm_names",
|
|
322
|
+
DROP COLUMN IF EXISTS "aws_selected_metric_keys",
|
|
323
|
+
DROP COLUMN IF EXISTS "aws_deployment_status",
|
|
324
|
+
DROP COLUMN IF EXISTS "aws_deployment_status_reason",
|
|
325
|
+
DROP COLUMN IF EXISTS "aws_stack_id"
|
|
326
326
|
`));
|
|
327
327
|
// Grafana source setup lives in a dedicated one-to-one table.
|
|
328
|
-
await db.execute(sql.raw(`
|
|
329
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_grafana_source_setups" (
|
|
330
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
331
|
-
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
332
|
-
"grafana_base_url" TEXT,
|
|
333
|
-
"grafana_contact_point_name" TEXT,
|
|
334
|
-
"grafana_account_token_encrypted" TEXT,
|
|
335
|
-
"grafana_auto_route" BOOLEAN NOT NULL DEFAULT false,
|
|
336
|
-
"grafana_matcher_label" TEXT,
|
|
337
|
-
"grafana_matcher_value" TEXT,
|
|
338
|
-
"grafana_policy_updated" BOOLEAN NOT NULL DEFAULT false,
|
|
339
|
-
"grafana_setup_status" TEXT NOT NULL DEFAULT 'not_started',
|
|
340
|
-
"grafana_setup_status_reason" TEXT,
|
|
341
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
342
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
343
|
-
)
|
|
328
|
+
await db.execute(sql.raw(`
|
|
329
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_grafana_source_setups" (
|
|
330
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
331
|
+
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
332
|
+
"grafana_base_url" TEXT,
|
|
333
|
+
"grafana_contact_point_name" TEXT,
|
|
334
|
+
"grafana_account_token_encrypted" TEXT,
|
|
335
|
+
"grafana_auto_route" BOOLEAN NOT NULL DEFAULT false,
|
|
336
|
+
"grafana_matcher_label" TEXT,
|
|
337
|
+
"grafana_matcher_value" TEXT,
|
|
338
|
+
"grafana_policy_updated" BOOLEAN NOT NULL DEFAULT false,
|
|
339
|
+
"grafana_setup_status" TEXT NOT NULL DEFAULT 'not_started',
|
|
340
|
+
"grafana_setup_status_reason" TEXT,
|
|
341
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
342
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
343
|
+
)
|
|
344
344
|
`));
|
|
345
345
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_grafana_source_setups" ADD COLUMN IF NOT EXISTS "grafana_account_token_encrypted" TEXT`));
|
|
346
346
|
await db.execute(sql.raw(`CREATE UNIQUE INDEX IF NOT EXISTS insight_grafana_source_setups_source_unique ON "meyiconnect"."insight_grafana_source_setups" ("source_id")`));
|
|
347
347
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_grafana_source_setups_status_idx ON "meyiconnect"."insight_grafana_source_setups" ("grafana_setup_status")`));
|
|
348
348
|
// Prometheus source setup lives in a dedicated one-to-one table.
|
|
349
|
-
await db.execute(sql.raw(`
|
|
350
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_prometheus_source_setups" (
|
|
351
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
352
|
-
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
353
|
-
"prometheus_environment" TEXT,
|
|
354
|
-
"prometheus_receiver_name" TEXT,
|
|
355
|
-
"prometheus_base_url" TEXT,
|
|
356
|
-
"alertmanager_base_url" TEXT,
|
|
357
|
-
"prometheus_setup_status" TEXT NOT NULL DEFAULT 'not_started',
|
|
358
|
-
"prometheus_setup_status_reason" TEXT,
|
|
359
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
360
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
361
|
-
)
|
|
349
|
+
await db.execute(sql.raw(`
|
|
350
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_prometheus_source_setups" (
|
|
351
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
352
|
+
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
353
|
+
"prometheus_environment" TEXT,
|
|
354
|
+
"prometheus_receiver_name" TEXT,
|
|
355
|
+
"prometheus_base_url" TEXT,
|
|
356
|
+
"alertmanager_base_url" TEXT,
|
|
357
|
+
"prometheus_setup_status" TEXT NOT NULL DEFAULT 'not_started',
|
|
358
|
+
"prometheus_setup_status_reason" TEXT,
|
|
359
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
360
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
361
|
+
)
|
|
362
362
|
`));
|
|
363
363
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_prometheus_source_setups" ADD COLUMN IF NOT EXISTS "prometheus_base_url" TEXT`));
|
|
364
364
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_prometheus_source_setups" ADD COLUMN IF NOT EXISTS "alertmanager_base_url" TEXT`));
|
|
365
365
|
await db.execute(sql.raw(`CREATE UNIQUE INDEX IF NOT EXISTS insight_prometheus_source_setups_source_unique ON "meyiconnect"."insight_prometheus_source_setups" ("source_id")`));
|
|
366
366
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_prometheus_source_setups_status_idx ON "meyiconnect"."insight_prometheus_source_setups" ("prometheus_setup_status")`));
|
|
367
367
|
// Source observability: one health row per source + failed webhook log.
|
|
368
|
-
await db.execute(sql.raw(`
|
|
369
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_source_health" (
|
|
370
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
371
|
-
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
372
|
-
"delivery_status" TEXT NOT NULL DEFAULT 'unknown',
|
|
373
|
-
"last_webhook_received_at" TIMESTAMPTZ,
|
|
374
|
-
"last_successful_webhook_at" TIMESTAMPTZ,
|
|
375
|
-
"last_failed_webhook_at" TIMESTAMPTZ,
|
|
376
|
-
"last_error_code" TEXT,
|
|
377
|
-
"last_error_message" TEXT,
|
|
378
|
-
"last_alert_id" UUID,
|
|
379
|
-
"success_count" INTEGER NOT NULL DEFAULT 0,
|
|
380
|
-
"failure_count" INTEGER NOT NULL DEFAULT 0,
|
|
381
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
382
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
383
|
-
)
|
|
368
|
+
await db.execute(sql.raw(`
|
|
369
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_source_health" (
|
|
370
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
371
|
+
"source_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
372
|
+
"delivery_status" TEXT NOT NULL DEFAULT 'unknown',
|
|
373
|
+
"last_webhook_received_at" TIMESTAMPTZ,
|
|
374
|
+
"last_successful_webhook_at" TIMESTAMPTZ,
|
|
375
|
+
"last_failed_webhook_at" TIMESTAMPTZ,
|
|
376
|
+
"last_error_code" TEXT,
|
|
377
|
+
"last_error_message" TEXT,
|
|
378
|
+
"last_alert_id" UUID,
|
|
379
|
+
"success_count" INTEGER NOT NULL DEFAULT 0,
|
|
380
|
+
"failure_count" INTEGER NOT NULL DEFAULT 0,
|
|
381
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
382
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
383
|
+
)
|
|
384
384
|
`));
|
|
385
385
|
await db.execute(sql.raw(`CREATE UNIQUE INDEX IF NOT EXISTS insight_source_health_source_unique ON "meyiconnect"."insight_source_health" ("source_id")`));
|
|
386
386
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_source_health_status_idx ON "meyiconnect"."insight_source_health" ("delivery_status")`));
|
|
387
|
-
await db.execute(sql.raw(`
|
|
388
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_webhook_failure_logs" (
|
|
389
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
390
|
-
"source_id" UUID REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE SET NULL,
|
|
391
|
-
"token_hash" TEXT,
|
|
392
|
-
"token_preview" TEXT,
|
|
393
|
-
"error_code" TEXT NOT NULL,
|
|
394
|
-
"error_message" TEXT NOT NULL,
|
|
395
|
-
"request_ip" TEXT,
|
|
396
|
-
"user_agent" TEXT,
|
|
397
|
-
"method" TEXT,
|
|
398
|
-
"path" TEXT,
|
|
399
|
-
"payload" JSONB,
|
|
400
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
401
|
-
)
|
|
387
|
+
await db.execute(sql.raw(`
|
|
388
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_webhook_failure_logs" (
|
|
389
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
390
|
+
"source_id" UUID REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE SET NULL,
|
|
391
|
+
"token_hash" TEXT,
|
|
392
|
+
"token_preview" TEXT,
|
|
393
|
+
"error_code" TEXT NOT NULL,
|
|
394
|
+
"error_message" TEXT NOT NULL,
|
|
395
|
+
"request_ip" TEXT,
|
|
396
|
+
"user_agent" TEXT,
|
|
397
|
+
"method" TEXT,
|
|
398
|
+
"path" TEXT,
|
|
399
|
+
"payload" JSONB,
|
|
400
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
401
|
+
)
|
|
402
402
|
`));
|
|
403
403
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_webhook_failure_logs_source_idx ON "meyiconnect"."insight_webhook_failure_logs" ("source_id")`));
|
|
404
404
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_webhook_failure_logs_error_idx ON "meyiconnect"."insight_webhook_failure_logs" ("error_code")`));
|
|
405
405
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_webhook_failure_logs_created_idx ON "meyiconnect"."insight_webhook_failure_logs" ("created_at")`));
|
|
406
406
|
// insight_projects
|
|
407
|
-
await db.execute(sql.raw(`
|
|
408
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_projects" (
|
|
409
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
410
|
-
"name" TEXT NOT NULL,
|
|
411
|
-
"tag" TEXT NOT NULL,
|
|
412
|
-
"description" TEXT,
|
|
413
|
-
"dependencies" JSONB DEFAULT '[]',
|
|
414
|
-
"aws_access_key_id" TEXT,
|
|
415
|
-
"aws_secret_access_key" TEXT,
|
|
416
|
-
"aws_region" TEXT,
|
|
417
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
418
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
419
|
-
)
|
|
420
|
-
`));
|
|
421
|
-
await db.execute(sql.raw(`
|
|
422
|
-
DO $$
|
|
423
|
-
BEGIN
|
|
424
|
-
IF EXISTS (
|
|
425
|
-
SELECT 1 FROM information_schema.columns
|
|
426
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_projects' AND column_name = 'tenant_id' AND data_type = 'uuid'
|
|
427
|
-
) THEN
|
|
428
|
-
ALTER TABLE "meyiconnect"."insight_projects" ALTER COLUMN "tenant_id" TYPE TEXT USING "tenant_id"::TEXT;
|
|
429
|
-
ELSIF NOT EXISTS (
|
|
430
|
-
SELECT 1 FROM information_schema.columns
|
|
431
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_projects' AND column_name = 'tenant_id'
|
|
432
|
-
) THEN
|
|
433
|
-
ALTER TABLE "meyiconnect"."insight_projects" ADD COLUMN "tenant_id" TEXT;
|
|
434
|
-
END IF;
|
|
435
|
-
END $$;
|
|
436
|
-
`));
|
|
437
|
-
await db.execute(sql.raw(`
|
|
438
|
-
ALTER TABLE "meyiconnect"."insight_projects"
|
|
439
|
-
ADD COLUMN IF NOT EXISTS "aws_access_key_id" TEXT,
|
|
440
|
-
ADD COLUMN IF NOT EXISTS "aws_secret_access_key" TEXT,
|
|
441
|
-
ADD COLUMN IF NOT EXISTS "aws_region" TEXT
|
|
407
|
+
await db.execute(sql.raw(`
|
|
408
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_projects" (
|
|
409
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
410
|
+
"name" TEXT NOT NULL,
|
|
411
|
+
"tag" TEXT NOT NULL,
|
|
412
|
+
"description" TEXT,
|
|
413
|
+
"dependencies" JSONB DEFAULT '[]',
|
|
414
|
+
"aws_access_key_id" TEXT,
|
|
415
|
+
"aws_secret_access_key" TEXT,
|
|
416
|
+
"aws_region" TEXT,
|
|
417
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
418
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
419
|
+
)
|
|
420
|
+
`));
|
|
421
|
+
await db.execute(sql.raw(`
|
|
422
|
+
DO $$
|
|
423
|
+
BEGIN
|
|
424
|
+
IF EXISTS (
|
|
425
|
+
SELECT 1 FROM information_schema.columns
|
|
426
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_projects' AND column_name = 'tenant_id' AND data_type = 'uuid'
|
|
427
|
+
) THEN
|
|
428
|
+
ALTER TABLE "meyiconnect"."insight_projects" ALTER COLUMN "tenant_id" TYPE TEXT USING "tenant_id"::TEXT;
|
|
429
|
+
ELSIF NOT EXISTS (
|
|
430
|
+
SELECT 1 FROM information_schema.columns
|
|
431
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_projects' AND column_name = 'tenant_id'
|
|
432
|
+
) THEN
|
|
433
|
+
ALTER TABLE "meyiconnect"."insight_projects" ADD COLUMN "tenant_id" TEXT;
|
|
434
|
+
END IF;
|
|
435
|
+
END $$;
|
|
436
|
+
`));
|
|
437
|
+
await db.execute(sql.raw(`
|
|
438
|
+
ALTER TABLE "meyiconnect"."insight_projects"
|
|
439
|
+
ADD COLUMN IF NOT EXISTS "aws_access_key_id" TEXT,
|
|
440
|
+
ADD COLUMN IF NOT EXISTS "aws_secret_access_key" TEXT,
|
|
441
|
+
ADD COLUMN IF NOT EXISTS "aws_region" TEXT
|
|
442
442
|
`));
|
|
443
443
|
await backfill(`UPDATE "meyiconnect"."insight_projects" SET "tenant_id" = '${defaultTenantSql}' WHERE "tenant_id" IS NULL OR "tenant_id"::text = ''`);
|
|
444
444
|
await db.execute(sql.raw(`DROP INDEX IF EXISTS "meyiconnect"."insight_projects_tag_unique"`)).catch(() => { });
|
|
@@ -447,54 +447,54 @@ export async function runMigrations(db) {
|
|
|
447
447
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_projects_tag_idx ON "meyiconnect"."insight_projects" ("tag")`));
|
|
448
448
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_projects_tenant_id_idx ON "meyiconnect"."insight_projects" ("tenant_id")`));
|
|
449
449
|
// insight_incidents
|
|
450
|
-
await db.execute(sql.raw(`
|
|
451
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_incidents" (
|
|
452
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
453
|
-
"alias_id" SERIAL,
|
|
454
|
-
"monitor_id" TEXT,
|
|
455
|
-
"monitor_name" TEXT,
|
|
456
|
-
"monitor_type" TEXT,
|
|
457
|
-
"monitor_url" TEXT,
|
|
458
|
-
"name" TEXT NOT NULL DEFAULT '',
|
|
459
|
-
"source" TEXT NOT NULL DEFAULT '',
|
|
460
|
-
"resource_name" TEXT NOT NULL DEFAULT '',
|
|
461
|
-
"resource_type" TEXT NOT NULL DEFAULT '',
|
|
462
|
-
"down_message" TEXT,
|
|
463
|
-
"project_tag" TEXT,
|
|
464
|
-
"down_time" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
465
|
-
"incident_down_time" TEXT NOT NULL DEFAULT '',
|
|
466
|
-
"resolved_at" TIMESTAMPTZ,
|
|
467
|
-
"analysis_status" TEXT NOT NULL DEFAULT 'pending',
|
|
468
|
-
"analysis_percent" INTEGER NOT NULL DEFAULT 0,
|
|
469
|
-
"analysis_triggered_at" TIMESTAMPTZ,
|
|
470
|
-
"analysis_completed_at" TIMESTAMPTZ,
|
|
471
|
-
"analysis_result" JSONB,
|
|
472
|
-
"remediation_steps" JSONB DEFAULT '[]',
|
|
473
|
-
"confidence_score" REAL,
|
|
474
|
-
"ai_model_used" TEXT,
|
|
475
|
-
"source_alert_id" UUID,
|
|
476
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
477
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
478
|
-
)
|
|
450
|
+
await db.execute(sql.raw(`
|
|
451
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_incidents" (
|
|
452
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
453
|
+
"alias_id" SERIAL,
|
|
454
|
+
"monitor_id" TEXT,
|
|
455
|
+
"monitor_name" TEXT,
|
|
456
|
+
"monitor_type" TEXT,
|
|
457
|
+
"monitor_url" TEXT,
|
|
458
|
+
"name" TEXT NOT NULL DEFAULT '',
|
|
459
|
+
"source" TEXT NOT NULL DEFAULT '',
|
|
460
|
+
"resource_name" TEXT NOT NULL DEFAULT '',
|
|
461
|
+
"resource_type" TEXT NOT NULL DEFAULT '',
|
|
462
|
+
"down_message" TEXT,
|
|
463
|
+
"project_tag" TEXT,
|
|
464
|
+
"down_time" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
465
|
+
"incident_down_time" TEXT NOT NULL DEFAULT '',
|
|
466
|
+
"resolved_at" TIMESTAMPTZ,
|
|
467
|
+
"analysis_status" TEXT NOT NULL DEFAULT 'pending',
|
|
468
|
+
"analysis_percent" INTEGER NOT NULL DEFAULT 0,
|
|
469
|
+
"analysis_triggered_at" TIMESTAMPTZ,
|
|
470
|
+
"analysis_completed_at" TIMESTAMPTZ,
|
|
471
|
+
"analysis_result" JSONB,
|
|
472
|
+
"remediation_steps" JSONB DEFAULT '[]',
|
|
473
|
+
"confidence_score" REAL,
|
|
474
|
+
"ai_model_used" TEXT,
|
|
475
|
+
"source_alert_id" UUID,
|
|
476
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
477
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
478
|
+
)
|
|
479
479
|
`));
|
|
480
480
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_incidents_monitor_id_idx ON "meyiconnect"."insight_incidents" ("monitor_id")`));
|
|
481
481
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_incidents_status_idx ON "meyiconnect"."insight_incidents" ("analysis_status")`));
|
|
482
482
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "alias_id" SERIAL`));
|
|
483
|
-
await db.execute(sql.raw(`
|
|
484
|
-
DO $$
|
|
485
|
-
BEGIN
|
|
486
|
-
IF EXISTS (
|
|
487
|
-
SELECT 1 FROM information_schema.columns
|
|
488
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_incidents' AND column_name = 'tenant_id' AND data_type = 'uuid'
|
|
489
|
-
) THEN
|
|
490
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "tenant_id" TYPE TEXT USING "tenant_id"::TEXT;
|
|
491
|
-
ELSIF NOT EXISTS (
|
|
492
|
-
SELECT 1 FROM information_schema.columns
|
|
493
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_incidents' AND column_name = 'tenant_id'
|
|
494
|
-
) THEN
|
|
495
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN "tenant_id" TEXT;
|
|
496
|
-
END IF;
|
|
497
|
-
END $$;
|
|
483
|
+
await db.execute(sql.raw(`
|
|
484
|
+
DO $$
|
|
485
|
+
BEGIN
|
|
486
|
+
IF EXISTS (
|
|
487
|
+
SELECT 1 FROM information_schema.columns
|
|
488
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_incidents' AND column_name = 'tenant_id' AND data_type = 'uuid'
|
|
489
|
+
) THEN
|
|
490
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "tenant_id" TYPE TEXT USING "tenant_id"::TEXT;
|
|
491
|
+
ELSIF NOT EXISTS (
|
|
492
|
+
SELECT 1 FROM information_schema.columns
|
|
493
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_incidents' AND column_name = 'tenant_id'
|
|
494
|
+
) THEN
|
|
495
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN "tenant_id" TEXT;
|
|
496
|
+
END IF;
|
|
497
|
+
END $$;
|
|
498
498
|
`));
|
|
499
499
|
await backfill(`UPDATE "meyiconnect"."insight_incidents" SET "tenant_id" = '${defaultTenantSql}' WHERE "tenant_id" IS NULL OR "tenant_id"::text = ''`);
|
|
500
500
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_incidents_tenant_id_idx ON "meyiconnect"."insight_incidents" ("tenant_id")`));
|
|
@@ -504,28 +504,28 @@ export async function runMigrations(db) {
|
|
|
504
504
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "resource_name" TEXT`));
|
|
505
505
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "resource_type" TEXT`));
|
|
506
506
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "down_time" TIMESTAMPTZ`));
|
|
507
|
-
await db.execute(sql.raw(`
|
|
508
|
-
DO $$
|
|
509
|
-
BEGIN
|
|
510
|
-
UPDATE "meyiconnect"."insight_incidents" SET "name" = COALESCE("monitor_name", 'Unknown') WHERE "name" IS NULL;
|
|
511
|
-
UPDATE "meyiconnect"."insight_incidents" SET "source" = 'Unknown' WHERE "source" IS NULL;
|
|
512
|
-
UPDATE "meyiconnect"."insight_incidents" SET "resource_name" = COALESCE("monitor_name", 'Unknown') WHERE "resource_name" IS NULL;
|
|
513
|
-
UPDATE "meyiconnect"."insight_incidents" SET "resource_type" = COALESCE("monitor_type", 'Unknown') WHERE "resource_type" IS NULL;
|
|
514
|
-
UPDATE "meyiconnect"."insight_incidents" SET "down_time" = "created_at" WHERE "down_time" IS NULL;
|
|
515
|
-
|
|
516
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "name" SET NOT NULL;
|
|
517
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "source" SET NOT NULL;
|
|
518
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "resource_name" SET NOT NULL;
|
|
519
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "resource_type" SET NOT NULL;
|
|
520
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "down_time" SET NOT NULL;
|
|
521
|
-
|
|
522
|
-
-- Remove NOT NULL from old columns if they exist
|
|
523
|
-
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_incidents' AND column_name = 'monitor_id') THEN
|
|
524
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "monitor_id" DROP NOT NULL;
|
|
525
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "monitor_name" DROP NOT NULL;
|
|
526
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "monitor_type" DROP NOT NULL;
|
|
527
|
-
END IF;
|
|
528
|
-
END $$;
|
|
507
|
+
await db.execute(sql.raw(`
|
|
508
|
+
DO $$
|
|
509
|
+
BEGIN
|
|
510
|
+
UPDATE "meyiconnect"."insight_incidents" SET "name" = COALESCE("monitor_name", 'Unknown') WHERE "name" IS NULL;
|
|
511
|
+
UPDATE "meyiconnect"."insight_incidents" SET "source" = 'Unknown' WHERE "source" IS NULL;
|
|
512
|
+
UPDATE "meyiconnect"."insight_incidents" SET "resource_name" = COALESCE("monitor_name", 'Unknown') WHERE "resource_name" IS NULL;
|
|
513
|
+
UPDATE "meyiconnect"."insight_incidents" SET "resource_type" = COALESCE("monitor_type", 'Unknown') WHERE "resource_type" IS NULL;
|
|
514
|
+
UPDATE "meyiconnect"."insight_incidents" SET "down_time" = "created_at" WHERE "down_time" IS NULL;
|
|
515
|
+
|
|
516
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "name" SET NOT NULL;
|
|
517
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "source" SET NOT NULL;
|
|
518
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "resource_name" SET NOT NULL;
|
|
519
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "resource_type" SET NOT NULL;
|
|
520
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "down_time" SET NOT NULL;
|
|
521
|
+
|
|
522
|
+
-- Remove NOT NULL from old columns if they exist
|
|
523
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'meyiconnect' AND table_name = 'insight_incidents' AND column_name = 'monitor_id') THEN
|
|
524
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "monitor_id" DROP NOT NULL;
|
|
525
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "monitor_name" DROP NOT NULL;
|
|
526
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "monitor_type" DROP NOT NULL;
|
|
527
|
+
END IF;
|
|
528
|
+
END $$;
|
|
529
529
|
`));
|
|
530
530
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "status" TEXT NOT NULL DEFAULT 'open'`));
|
|
531
531
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "analysis_percent" INTEGER NOT NULL DEFAULT 0`));
|
|
@@ -539,154 +539,154 @@ export async function runMigrations(db) {
|
|
|
539
539
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "account_id" TEXT`));
|
|
540
540
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "account_name" TEXT`));
|
|
541
541
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "total_cost" JSONB`));
|
|
542
|
-
await db.execute(sql.raw(`
|
|
543
|
-
UPDATE "meyiconnect"."insight_incidents" i
|
|
544
|
-
SET "account_name" = c."account_name"
|
|
545
|
-
FROM "meyiconnect"."connectors" c
|
|
546
|
-
WHERE i."account_name" IS NULL
|
|
547
|
-
AND i."account_id" IS NOT NULL
|
|
548
|
-
AND i."account_id" = c."account_id";
|
|
549
|
-
`));
|
|
550
|
-
await db.execute(sql.raw(`
|
|
551
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."incident_comments" (
|
|
552
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
553
|
-
"incident_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_incidents"("id") ON DELETE CASCADE,
|
|
554
|
-
"author_id" TEXT,
|
|
555
|
-
"author_name" TEXT,
|
|
556
|
-
"message" TEXT NOT NULL,
|
|
557
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
558
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
559
|
-
)
|
|
542
|
+
await db.execute(sql.raw(`
|
|
543
|
+
UPDATE "meyiconnect"."insight_incidents" i
|
|
544
|
+
SET "account_name" = c."account_name"
|
|
545
|
+
FROM "meyiconnect"."connectors" c
|
|
546
|
+
WHERE i."account_name" IS NULL
|
|
547
|
+
AND i."account_id" IS NOT NULL
|
|
548
|
+
AND i."account_id" = c."account_id";
|
|
549
|
+
`));
|
|
550
|
+
await db.execute(sql.raw(`
|
|
551
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."incident_comments" (
|
|
552
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
553
|
+
"incident_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_incidents"("id") ON DELETE CASCADE,
|
|
554
|
+
"author_id" TEXT,
|
|
555
|
+
"author_name" TEXT,
|
|
556
|
+
"message" TEXT NOT NULL,
|
|
557
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
558
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
559
|
+
)
|
|
560
560
|
`));
|
|
561
561
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS incident_comments_incident_id_idx ON "meyiconnect"."incident_comments" ("incident_id")`));
|
|
562
562
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS incident_comments_created_at_idx ON "meyiconnect"."incident_comments" ("created_at")`));
|
|
563
563
|
// insight_alerts
|
|
564
|
-
await db.execute(sql.raw(`
|
|
565
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_alerts" (
|
|
566
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
567
|
-
"tenant_id" TEXT,
|
|
568
|
-
"source_id" UUID,
|
|
569
|
-
"source_name" TEXT,
|
|
570
|
-
"source_type" TEXT,
|
|
571
|
-
"resource_id" TEXT NOT NULL,
|
|
572
|
-
"resource_name" TEXT NOT NULL,
|
|
573
|
-
"resource_type" TEXT NOT NULL,
|
|
574
|
-
"project_tag" TEXT,
|
|
575
|
-
"severity" TEXT NOT NULL DEFAULT 'high',
|
|
576
|
-
"alert_name" TEXT NOT NULL,
|
|
577
|
-
"description" TEXT,
|
|
578
|
-
"additional_configuration" JSONB,
|
|
579
|
-
"status" TEXT NOT NULL DEFAULT 'open',
|
|
580
|
-
"converted_incident_id" UUID,
|
|
581
|
-
"incident_id" UUID,
|
|
582
|
-
"alias_id" TEXT,
|
|
583
|
-
"assignee_id" UUID,
|
|
584
|
-
"down_time" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
585
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
586
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
587
|
-
"dup_count" INT NOT NULL DEFAULT 0,
|
|
588
|
-
"last_dup_at" TIMESTAMPTZ,
|
|
589
|
-
"fingerprint" TEXT,
|
|
590
|
-
"dedup_key" TEXT,
|
|
591
|
-
"pipeline_status" TEXT DEFAULT 'queued',
|
|
592
|
-
"group_id" UUID,
|
|
593
|
-
"raw_payload" JSONB
|
|
594
|
-
)
|
|
595
|
-
`));
|
|
596
|
-
await db.execute(sql.raw(`
|
|
597
|
-
DO $$
|
|
598
|
-
BEGIN
|
|
599
|
-
IF NOT EXISTS (
|
|
600
|
-
SELECT 1 FROM information_schema.columns
|
|
601
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'alert_name'
|
|
602
|
-
) THEN
|
|
603
|
-
ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN "alert_name" TEXT;
|
|
604
|
-
END IF;
|
|
605
|
-
|
|
606
|
-
IF EXISTS (
|
|
607
|
-
SELECT 1 FROM information_schema.columns
|
|
608
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'title'
|
|
609
|
-
) THEN
|
|
610
|
-
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "title" DROP NOT NULL;
|
|
611
|
-
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "title" SET DEFAULT 'Alert';
|
|
612
|
-
UPDATE "meyiconnect"."insight_alerts"
|
|
613
|
-
SET "alert_name" = COALESCE("alert_name", "title", 'Alert')
|
|
614
|
-
WHERE "alert_name" IS NULL;
|
|
615
|
-
ELSE
|
|
616
|
-
UPDATE "meyiconnect"."insight_alerts"
|
|
617
|
-
SET "alert_name" = COALESCE("alert_name", 'Alert')
|
|
618
|
-
WHERE "alert_name" IS NULL;
|
|
619
|
-
END IF;
|
|
620
|
-
|
|
621
|
-
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "alert_name" SET NOT NULL;
|
|
622
|
-
END $$;
|
|
564
|
+
await db.execute(sql.raw(`
|
|
565
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_alerts" (
|
|
566
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
567
|
+
"tenant_id" TEXT,
|
|
568
|
+
"source_id" UUID,
|
|
569
|
+
"source_name" TEXT,
|
|
570
|
+
"source_type" TEXT,
|
|
571
|
+
"resource_id" TEXT NOT NULL,
|
|
572
|
+
"resource_name" TEXT NOT NULL,
|
|
573
|
+
"resource_type" TEXT NOT NULL,
|
|
574
|
+
"project_tag" TEXT,
|
|
575
|
+
"severity" TEXT NOT NULL DEFAULT 'high',
|
|
576
|
+
"alert_name" TEXT NOT NULL,
|
|
577
|
+
"description" TEXT,
|
|
578
|
+
"additional_configuration" JSONB,
|
|
579
|
+
"status" TEXT NOT NULL DEFAULT 'open',
|
|
580
|
+
"converted_incident_id" UUID,
|
|
581
|
+
"incident_id" UUID,
|
|
582
|
+
"alias_id" TEXT,
|
|
583
|
+
"assignee_id" UUID,
|
|
584
|
+
"down_time" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
585
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
586
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
587
|
+
"dup_count" INT NOT NULL DEFAULT 0,
|
|
588
|
+
"last_dup_at" TIMESTAMPTZ,
|
|
589
|
+
"fingerprint" TEXT,
|
|
590
|
+
"dedup_key" TEXT,
|
|
591
|
+
"pipeline_status" TEXT DEFAULT 'queued',
|
|
592
|
+
"group_id" UUID,
|
|
593
|
+
"raw_payload" JSONB
|
|
594
|
+
)
|
|
595
|
+
`));
|
|
596
|
+
await db.execute(sql.raw(`
|
|
597
|
+
DO $$
|
|
598
|
+
BEGIN
|
|
599
|
+
IF NOT EXISTS (
|
|
600
|
+
SELECT 1 FROM information_schema.columns
|
|
601
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'alert_name'
|
|
602
|
+
) THEN
|
|
603
|
+
ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN "alert_name" TEXT;
|
|
604
|
+
END IF;
|
|
605
|
+
|
|
606
|
+
IF EXISTS (
|
|
607
|
+
SELECT 1 FROM information_schema.columns
|
|
608
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'title'
|
|
609
|
+
) THEN
|
|
610
|
+
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "title" DROP NOT NULL;
|
|
611
|
+
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "title" SET DEFAULT 'Alert';
|
|
612
|
+
UPDATE "meyiconnect"."insight_alerts"
|
|
613
|
+
SET "alert_name" = COALESCE("alert_name", "title", 'Alert')
|
|
614
|
+
WHERE "alert_name" IS NULL;
|
|
615
|
+
ELSE
|
|
616
|
+
UPDATE "meyiconnect"."insight_alerts"
|
|
617
|
+
SET "alert_name" = COALESCE("alert_name", 'Alert')
|
|
618
|
+
WHERE "alert_name" IS NULL;
|
|
619
|
+
END IF;
|
|
620
|
+
|
|
621
|
+
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "alert_name" SET NOT NULL;
|
|
622
|
+
END $$;
|
|
623
623
|
`));
|
|
624
624
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "project_tag" TEXT`));
|
|
625
|
-
await db.execute(sql.raw(`
|
|
626
|
-
DO $$
|
|
627
|
-
BEGIN
|
|
628
|
-
IF EXISTS (
|
|
629
|
-
SELECT 1 FROM information_schema.columns
|
|
630
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'tenant_id' AND data_type = 'uuid'
|
|
631
|
-
) THEN
|
|
632
|
-
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "tenant_id" TYPE TEXT USING "tenant_id"::TEXT;
|
|
633
|
-
ELSIF NOT EXISTS (
|
|
634
|
-
SELECT 1 FROM information_schema.columns
|
|
635
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'tenant_id'
|
|
636
|
-
) THEN
|
|
637
|
-
ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN "tenant_id" TEXT;
|
|
638
|
-
END IF;
|
|
639
|
-
END $$;
|
|
625
|
+
await db.execute(sql.raw(`
|
|
626
|
+
DO $$
|
|
627
|
+
BEGIN
|
|
628
|
+
IF EXISTS (
|
|
629
|
+
SELECT 1 FROM information_schema.columns
|
|
630
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'tenant_id' AND data_type = 'uuid'
|
|
631
|
+
) THEN
|
|
632
|
+
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "tenant_id" TYPE TEXT USING "tenant_id"::TEXT;
|
|
633
|
+
ELSIF NOT EXISTS (
|
|
634
|
+
SELECT 1 FROM information_schema.columns
|
|
635
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'tenant_id'
|
|
636
|
+
) THEN
|
|
637
|
+
ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN "tenant_id" TEXT;
|
|
638
|
+
END IF;
|
|
639
|
+
END $$;
|
|
640
640
|
`));
|
|
641
641
|
await backfill(`UPDATE "meyiconnect"."insight_alerts" SET "tenant_id" = '${defaultTenantSql}' WHERE "tenant_id" IS NULL OR "tenant_id"::text = ''`);
|
|
642
642
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_alerts_tenant_id_idx ON "meyiconnect"."insight_alerts" ("tenant_id")`));
|
|
643
643
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "alias_id" TEXT`));
|
|
644
644
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "additional_configuration" JSONB`));
|
|
645
645
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "incident_id" UUID`));
|
|
646
|
-
await db.execute(sql.raw(`
|
|
647
|
-
DO $$
|
|
648
|
-
BEGIN
|
|
649
|
-
IF NOT EXISTS (
|
|
650
|
-
SELECT 1 FROM information_schema.columns
|
|
651
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'down_time'
|
|
652
|
-
) THEN
|
|
653
|
-
ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN "down_time" TIMESTAMPTZ;
|
|
654
|
-
END IF;
|
|
655
|
-
|
|
656
|
-
IF EXISTS (
|
|
657
|
-
SELECT 1 FROM information_schema.columns
|
|
658
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'alert_time'
|
|
659
|
-
) THEN
|
|
660
|
-
UPDATE "meyiconnect"."insight_alerts"
|
|
661
|
-
SET "down_time" = COALESCE("down_time", "alert_time", NOW())
|
|
662
|
-
WHERE "down_time" IS NULL;
|
|
663
|
-
ELSE
|
|
664
|
-
UPDATE "meyiconnect"."insight_alerts"
|
|
665
|
-
SET "down_time" = COALESCE("down_time", NOW())
|
|
666
|
-
WHERE "down_time" IS NULL;
|
|
667
|
-
END IF;
|
|
668
|
-
|
|
669
|
-
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "down_time" SET NOT NULL;
|
|
670
|
-
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "down_time" SET DEFAULT NOW();
|
|
671
|
-
END $$;
|
|
672
|
-
`));
|
|
673
|
-
await db.execute(sql.raw(`
|
|
674
|
-
DO $$
|
|
675
|
-
BEGIN
|
|
676
|
-
IF EXISTS (
|
|
677
|
-
SELECT 1 FROM information_schema.columns
|
|
678
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'promoted_incident_id'
|
|
679
|
-
) THEN
|
|
680
|
-
IF NOT EXISTS (
|
|
681
|
-
SELECT 1 FROM information_schema.columns
|
|
682
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'converted_incident_id'
|
|
683
|
-
) THEN
|
|
684
|
-
ALTER TABLE "meyiconnect"."insight_alerts" RENAME COLUMN "promoted_incident_id" TO "converted_incident_id";
|
|
685
|
-
ELSE
|
|
686
|
-
ALTER TABLE "meyiconnect"."insight_alerts" DROP COLUMN "promoted_incident_id";
|
|
687
|
-
END IF;
|
|
688
|
-
END IF;
|
|
689
|
-
END $$;
|
|
646
|
+
await db.execute(sql.raw(`
|
|
647
|
+
DO $$
|
|
648
|
+
BEGIN
|
|
649
|
+
IF NOT EXISTS (
|
|
650
|
+
SELECT 1 FROM information_schema.columns
|
|
651
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'down_time'
|
|
652
|
+
) THEN
|
|
653
|
+
ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN "down_time" TIMESTAMPTZ;
|
|
654
|
+
END IF;
|
|
655
|
+
|
|
656
|
+
IF EXISTS (
|
|
657
|
+
SELECT 1 FROM information_schema.columns
|
|
658
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'alert_time'
|
|
659
|
+
) THEN
|
|
660
|
+
UPDATE "meyiconnect"."insight_alerts"
|
|
661
|
+
SET "down_time" = COALESCE("down_time", "alert_time", NOW())
|
|
662
|
+
WHERE "down_time" IS NULL;
|
|
663
|
+
ELSE
|
|
664
|
+
UPDATE "meyiconnect"."insight_alerts"
|
|
665
|
+
SET "down_time" = COALESCE("down_time", NOW())
|
|
666
|
+
WHERE "down_time" IS NULL;
|
|
667
|
+
END IF;
|
|
668
|
+
|
|
669
|
+
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "down_time" SET NOT NULL;
|
|
670
|
+
ALTER TABLE "meyiconnect"."insight_alerts" ALTER COLUMN "down_time" SET DEFAULT NOW();
|
|
671
|
+
END $$;
|
|
672
|
+
`));
|
|
673
|
+
await db.execute(sql.raw(`
|
|
674
|
+
DO $$
|
|
675
|
+
BEGIN
|
|
676
|
+
IF EXISTS (
|
|
677
|
+
SELECT 1 FROM information_schema.columns
|
|
678
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'promoted_incident_id'
|
|
679
|
+
) THEN
|
|
680
|
+
IF NOT EXISTS (
|
|
681
|
+
SELECT 1 FROM information_schema.columns
|
|
682
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'converted_incident_id'
|
|
683
|
+
) THEN
|
|
684
|
+
ALTER TABLE "meyiconnect"."insight_alerts" RENAME COLUMN "promoted_incident_id" TO "converted_incident_id";
|
|
685
|
+
ELSE
|
|
686
|
+
ALTER TABLE "meyiconnect"."insight_alerts" DROP COLUMN "promoted_incident_id";
|
|
687
|
+
END IF;
|
|
688
|
+
END IF;
|
|
689
|
+
END $$;
|
|
690
690
|
`));
|
|
691
691
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "converted_incident_id" UUID`));
|
|
692
692
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "assignee_id" UUID`));
|
|
@@ -694,61 +694,61 @@ export async function runMigrations(db) {
|
|
|
694
694
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "last_dup_at" TIMESTAMPTZ`));
|
|
695
695
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "account_id" TEXT`));
|
|
696
696
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "account_name" TEXT`));
|
|
697
|
-
await db.execute(sql.raw(`
|
|
698
|
-
UPDATE "meyiconnect"."insight_alerts" a
|
|
699
|
-
SET "account_name" = c."account_name"
|
|
700
|
-
FROM "meyiconnect"."connectors" c
|
|
701
|
-
WHERE a."account_name" IS NULL
|
|
702
|
-
AND a."account_id" IS NOT NULL
|
|
703
|
-
AND a."account_id" = c."account_id";
|
|
704
|
-
`));
|
|
705
|
-
await db.execute(sql.raw(`
|
|
706
|
-
UPDATE "meyiconnect"."insight_alerts"
|
|
707
|
-
SET "account_id" = COALESCE(
|
|
708
|
-
"additional_configuration"->>'accountId',
|
|
709
|
-
"raw_payload"->>'account',
|
|
710
|
-
"raw_payload"->>'accountId',
|
|
711
|
-
"raw_payload"->>'AWSAccountId',
|
|
712
|
-
"raw_payload"->'rawEvent'->>'account',
|
|
713
|
-
"raw_payload"->'detail'->>'account',
|
|
714
|
-
"raw_payload"->'detail'->>'recipientAccountId',
|
|
715
|
-
substring("resource_id" FROM 'arn:aws[a-z0-9-]*:cloudwatch:[^:]+:([0-9]{12}):alarm:')
|
|
716
|
-
)
|
|
717
|
-
WHERE "account_id" IS NULL
|
|
718
|
-
AND (
|
|
719
|
-
"additional_configuration"->>'accountId' IS NOT NULL
|
|
720
|
-
OR "raw_payload"->>'account' IS NOT NULL
|
|
721
|
-
OR "raw_payload"->>'accountId' IS NOT NULL
|
|
722
|
-
OR "raw_payload"->>'AWSAccountId' IS NOT NULL
|
|
723
|
-
OR "raw_payload"->'rawEvent'->>'account' IS NOT NULL
|
|
724
|
-
OR "raw_payload"->'detail'->>'account' IS NOT NULL
|
|
725
|
-
OR "raw_payload"->'detail'->>'recipientAccountId' IS NOT NULL
|
|
726
|
-
OR "resource_id" ~ '^arn:aws[a-z0-9-]*:cloudwatch:[^:]+:[0-9]{12}:alarm:'
|
|
727
|
-
)
|
|
728
|
-
`));
|
|
729
|
-
await db.execute(sql.raw(`
|
|
730
|
-
UPDATE "meyiconnect"."insight_alerts"
|
|
731
|
-
SET "additional_configuration" = jsonb_set(
|
|
732
|
-
COALESCE("additional_configuration", '{}'::jsonb),
|
|
733
|
-
'{tags}',
|
|
734
|
-
COALESCE("raw_payload"->'tags', "raw_payload"->'Tags', "raw_payload"->'detail'->'tags')
|
|
735
|
-
)
|
|
736
|
-
WHERE ("additional_configuration" IS NULL OR "additional_configuration"->'tags' IS NULL)
|
|
737
|
-
AND (
|
|
738
|
-
"raw_payload"->'tags' IS NOT NULL
|
|
739
|
-
OR "raw_payload"->'Tags' IS NOT NULL
|
|
740
|
-
OR "raw_payload"->'detail'->'tags' IS NOT NULL
|
|
741
|
-
)
|
|
697
|
+
await db.execute(sql.raw(`
|
|
698
|
+
UPDATE "meyiconnect"."insight_alerts" a
|
|
699
|
+
SET "account_name" = c."account_name"
|
|
700
|
+
FROM "meyiconnect"."connectors" c
|
|
701
|
+
WHERE a."account_name" IS NULL
|
|
702
|
+
AND a."account_id" IS NOT NULL
|
|
703
|
+
AND a."account_id" = c."account_id";
|
|
704
|
+
`));
|
|
705
|
+
await db.execute(sql.raw(`
|
|
706
|
+
UPDATE "meyiconnect"."insight_alerts"
|
|
707
|
+
SET "account_id" = COALESCE(
|
|
708
|
+
"additional_configuration"->>'accountId',
|
|
709
|
+
"raw_payload"->>'account',
|
|
710
|
+
"raw_payload"->>'accountId',
|
|
711
|
+
"raw_payload"->>'AWSAccountId',
|
|
712
|
+
"raw_payload"->'rawEvent'->>'account',
|
|
713
|
+
"raw_payload"->'detail'->>'account',
|
|
714
|
+
"raw_payload"->'detail'->>'recipientAccountId',
|
|
715
|
+
substring("resource_id" FROM 'arn:aws[a-z0-9-]*:cloudwatch:[^:]+:([0-9]{12}):alarm:')
|
|
716
|
+
)
|
|
717
|
+
WHERE "account_id" IS NULL
|
|
718
|
+
AND (
|
|
719
|
+
"additional_configuration"->>'accountId' IS NOT NULL
|
|
720
|
+
OR "raw_payload"->>'account' IS NOT NULL
|
|
721
|
+
OR "raw_payload"->>'accountId' IS NOT NULL
|
|
722
|
+
OR "raw_payload"->>'AWSAccountId' IS NOT NULL
|
|
723
|
+
OR "raw_payload"->'rawEvent'->>'account' IS NOT NULL
|
|
724
|
+
OR "raw_payload"->'detail'->>'account' IS NOT NULL
|
|
725
|
+
OR "raw_payload"->'detail'->>'recipientAccountId' IS NOT NULL
|
|
726
|
+
OR "resource_id" ~ '^arn:aws[a-z0-9-]*:cloudwatch:[^:]+:[0-9]{12}:alarm:'
|
|
727
|
+
)
|
|
728
|
+
`));
|
|
729
|
+
await db.execute(sql.raw(`
|
|
730
|
+
UPDATE "meyiconnect"."insight_alerts"
|
|
731
|
+
SET "additional_configuration" = jsonb_set(
|
|
732
|
+
COALESCE("additional_configuration", '{}'::jsonb),
|
|
733
|
+
'{tags}',
|
|
734
|
+
COALESCE("raw_payload"->'tags', "raw_payload"->'Tags', "raw_payload"->'detail'->'tags')
|
|
735
|
+
)
|
|
736
|
+
WHERE ("additional_configuration" IS NULL OR "additional_configuration"->'tags' IS NULL)
|
|
737
|
+
AND (
|
|
738
|
+
"raw_payload"->'tags' IS NOT NULL
|
|
739
|
+
OR "raw_payload"->'Tags' IS NOT NULL
|
|
740
|
+
OR "raw_payload"->'detail'->'tags' IS NOT NULL
|
|
741
|
+
)
|
|
742
742
|
`));
|
|
743
743
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_alerts_status_idx ON "meyiconnect"."insight_alerts" ("status")`));
|
|
744
744
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_alerts_source_idx ON "meyiconnect"."insight_alerts" ("source_id")`));
|
|
745
745
|
// insight_settings
|
|
746
|
-
await db.execute(sql.raw(`
|
|
747
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_settings" (
|
|
748
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
749
|
-
|
|
750
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
751
|
-
)
|
|
746
|
+
await db.execute(sql.raw(`
|
|
747
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_settings" (
|
|
748
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
749
|
+
|
|
750
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
751
|
+
)
|
|
752
752
|
`));
|
|
753
753
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_settings" ADD COLUMN IF NOT EXISTS "aws_bedrock_access_key_id" TEXT`));
|
|
754
754
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_settings" ADD COLUMN IF NOT EXISTS "aws_bedrock_secret_access_key" TEXT`));
|
|
@@ -757,19 +757,19 @@ export async function runMigrations(db) {
|
|
|
757
757
|
await backfill(`UPDATE "meyiconnect"."insight_settings" SET "tenant_id" = '${defaultTenantSql}' WHERE "tenant_id" IS NULL OR "tenant_id" = ''`);
|
|
758
758
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_settings_tenant_id_idx ON "meyiconnect"."insight_settings" ("tenant_id")`));
|
|
759
759
|
// notification_providers
|
|
760
|
-
await db.execute(sql.raw(`
|
|
761
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."notification_providers" (
|
|
762
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
763
|
-
"tenant_id" TEXT,
|
|
764
|
-
"provider_name" VARCHAR(50) NOT NULL,
|
|
765
|
-
"display_name" VARCHAR(100) NOT NULL,
|
|
766
|
-
"is_enabled" BOOLEAN NOT NULL DEFAULT false,
|
|
767
|
-
"config" JSONB,
|
|
768
|
-
"description" TEXT,
|
|
769
|
-
"icon" TEXT,
|
|
770
|
-
"created_at" TIMESTAMP DEFAULT NOW(),
|
|
771
|
-
"updated_at" TIMESTAMP DEFAULT NOW()
|
|
772
|
-
)
|
|
760
|
+
await db.execute(sql.raw(`
|
|
761
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."notification_providers" (
|
|
762
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
763
|
+
"tenant_id" TEXT,
|
|
764
|
+
"provider_name" VARCHAR(50) NOT NULL,
|
|
765
|
+
"display_name" VARCHAR(100) NOT NULL,
|
|
766
|
+
"is_enabled" BOOLEAN NOT NULL DEFAULT false,
|
|
767
|
+
"config" JSONB,
|
|
768
|
+
"description" TEXT,
|
|
769
|
+
"icon" TEXT,
|
|
770
|
+
"created_at" TIMESTAMP DEFAULT NOW(),
|
|
771
|
+
"updated_at" TIMESTAMP DEFAULT NOW()
|
|
772
|
+
)
|
|
773
773
|
`));
|
|
774
774
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."notification_providers" ADD COLUMN IF NOT EXISTS "tenant_id" TEXT`)).catch(() => { });
|
|
775
775
|
await backfill(`UPDATE "meyiconnect"."notification_providers" SET "tenant_id" = '${defaultTenantSql}' WHERE "tenant_id" IS NULL OR "tenant_id"::text = ''`).catch(() => { });
|
|
@@ -778,50 +778,50 @@ export async function runMigrations(db) {
|
|
|
778
778
|
await db.execute(sql.raw(`CREATE UNIQUE INDEX IF NOT EXISTS notification_providers_tenant_name_unique ON "meyiconnect"."notification_providers" ("tenant_id", "provider_name")`)).catch(() => { });
|
|
779
779
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS notification_providers_tenant_id_idx ON "meyiconnect"."notification_providers" ("tenant_id")`)).catch(() => { });
|
|
780
780
|
// incident_events — timestamped audit/timeline log (status changes, assigns, analysis, comments)
|
|
781
|
-
await db.execute(sql.raw(`
|
|
782
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."incident_events" (
|
|
783
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
784
|
-
"incident_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_incidents"("id") ON DELETE CASCADE,
|
|
785
|
-
"type" TEXT NOT NULL,
|
|
786
|
-
"actor" TEXT,
|
|
787
|
-
"message" TEXT,
|
|
788
|
-
"before_value" TEXT,
|
|
789
|
-
"after_value" TEXT,
|
|
790
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
791
|
-
)
|
|
781
|
+
await db.execute(sql.raw(`
|
|
782
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."incident_events" (
|
|
783
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
784
|
+
"incident_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_incidents"("id") ON DELETE CASCADE,
|
|
785
|
+
"type" TEXT NOT NULL,
|
|
786
|
+
"actor" TEXT,
|
|
787
|
+
"message" TEXT,
|
|
788
|
+
"before_value" TEXT,
|
|
789
|
+
"after_value" TEXT,
|
|
790
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
791
|
+
)
|
|
792
792
|
`));
|
|
793
793
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS incident_events_incident_id_idx ON "meyiconnect"."incident_events" ("incident_id")`));
|
|
794
794
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS incident_events_created_at_idx ON "meyiconnect"."incident_events" ("created_at")`));
|
|
795
795
|
// incident_logs
|
|
796
|
-
await db.execute(sql.raw(`
|
|
797
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."incident_logs" (
|
|
798
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
799
|
-
"incident_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_incidents"("id") ON DELETE CASCADE,
|
|
800
|
-
"ec2_details" JSONB,
|
|
801
|
-
"ec2_status_checks" JSONB,
|
|
802
|
-
"cloudwatch_metrics" JSONB,
|
|
803
|
-
"raw_logs" JSONB,
|
|
804
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
805
|
-
)
|
|
796
|
+
await db.execute(sql.raw(`
|
|
797
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."incident_logs" (
|
|
798
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
799
|
+
"incident_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_incidents"("id") ON DELETE CASCADE,
|
|
800
|
+
"ec2_details" JSONB,
|
|
801
|
+
"ec2_status_checks" JSONB,
|
|
802
|
+
"cloudwatch_metrics" JSONB,
|
|
803
|
+
"raw_logs" JSONB,
|
|
804
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
805
|
+
)
|
|
806
806
|
`));
|
|
807
807
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS incident_logs_incident_id_idx ON "meyiconnect"."incident_logs" ("incident_id")`));
|
|
808
808
|
// insight_sops
|
|
809
|
-
await db.execute(sql.raw(`
|
|
810
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_sops" (
|
|
811
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
812
|
-
"sop_id" TEXT NOT NULL,
|
|
813
|
-
"title" TEXT NOT NULL,
|
|
814
|
-
"alert_type" TEXT,
|
|
815
|
-
"service" TEXT,
|
|
816
|
-
"severity" TEXT,
|
|
817
|
-
"status" TEXT NOT NULL DEFAULT 'draft',
|
|
818
|
-
"description" TEXT,
|
|
819
|
-
"steps" TEXT,
|
|
820
|
-
"error_response" TEXT,
|
|
821
|
-
"created_by" TEXT DEFAULT 'Manual',
|
|
822
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
823
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
824
|
-
)
|
|
809
|
+
await db.execute(sql.raw(`
|
|
810
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_sops" (
|
|
811
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
812
|
+
"sop_id" TEXT NOT NULL,
|
|
813
|
+
"title" TEXT NOT NULL,
|
|
814
|
+
"alert_type" TEXT,
|
|
815
|
+
"service" TEXT,
|
|
816
|
+
"severity" TEXT,
|
|
817
|
+
"status" TEXT NOT NULL DEFAULT 'draft',
|
|
818
|
+
"description" TEXT,
|
|
819
|
+
"steps" TEXT,
|
|
820
|
+
"error_response" TEXT,
|
|
821
|
+
"created_by" TEXT DEFAULT 'Manual',
|
|
822
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
823
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
824
|
+
)
|
|
825
825
|
`));
|
|
826
826
|
await db.execute(sql.raw(`DROP INDEX IF EXISTS "meyiconnect"."insight_sops_sop_id_unique"`)).catch(() => { });
|
|
827
827
|
await db.execute(sql.raw(`DROP INDEX IF EXISTS insight_sops_sop_id_unique`)).catch(() => { });
|
|
@@ -831,46 +831,46 @@ export async function runMigrations(db) {
|
|
|
831
831
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sops" ADD COLUMN IF NOT EXISTS "error_response" TEXT`));
|
|
832
832
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sops" ADD COLUMN IF NOT EXISTS "source_id" UUID`));
|
|
833
833
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sops" ADD COLUMN IF NOT EXISTS "project_id" UUID`));
|
|
834
|
-
await db.execute(sql.raw(`
|
|
835
|
-
DO $$
|
|
836
|
-
BEGIN
|
|
837
|
-
IF EXISTS (
|
|
838
|
-
SELECT 1 FROM information_schema.columns
|
|
839
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sops' AND column_name = 'incident_id'
|
|
840
|
-
) THEN
|
|
841
|
-
IF NOT EXISTS (
|
|
842
|
-
SELECT 1 FROM information_schema.columns
|
|
843
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sops' AND column_name = 'alert_id'
|
|
844
|
-
) THEN
|
|
845
|
-
-- Drop old FK before renaming to ensure it points to the correct table
|
|
846
|
-
ALTER TABLE "meyiconnect"."insight_sops" DROP CONSTRAINT IF EXISTS insight_sops_incident_id_fkey;
|
|
847
|
-
ALTER TABLE "meyiconnect"."insight_sops" RENAME COLUMN "incident_id" TO "alert_id";
|
|
848
|
-
ELSE
|
|
849
|
-
ALTER TABLE "meyiconnect"."insight_sops" DROP COLUMN "incident_id";
|
|
850
|
-
END IF;
|
|
851
|
-
END IF;
|
|
852
|
-
END $$;
|
|
834
|
+
await db.execute(sql.raw(`
|
|
835
|
+
DO $$
|
|
836
|
+
BEGIN
|
|
837
|
+
IF EXISTS (
|
|
838
|
+
SELECT 1 FROM information_schema.columns
|
|
839
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sops' AND column_name = 'incident_id'
|
|
840
|
+
) THEN
|
|
841
|
+
IF NOT EXISTS (
|
|
842
|
+
SELECT 1 FROM information_schema.columns
|
|
843
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_sops' AND column_name = 'alert_id'
|
|
844
|
+
) THEN
|
|
845
|
+
-- Drop old FK before renaming to ensure it points to the correct table
|
|
846
|
+
ALTER TABLE "meyiconnect"."insight_sops" DROP CONSTRAINT IF EXISTS insight_sops_incident_id_fkey;
|
|
847
|
+
ALTER TABLE "meyiconnect"."insight_sops" RENAME COLUMN "incident_id" TO "alert_id";
|
|
848
|
+
ELSE
|
|
849
|
+
ALTER TABLE "meyiconnect"."insight_sops" DROP COLUMN "incident_id";
|
|
850
|
+
END IF;
|
|
851
|
+
END IF;
|
|
852
|
+
END $$;
|
|
853
853
|
`));
|
|
854
854
|
// Drop constraint anyway in case we renamed it but left the constraint intact previously
|
|
855
855
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sops" DROP CONSTRAINT IF EXISTS insight_sops_incident_id_fkey;`));
|
|
856
856
|
// Add column if it doesn't exist
|
|
857
857
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sops" ADD COLUMN IF NOT EXISTS "alert_id" UUID`));
|
|
858
858
|
// Explicitly add the new foreign key constraint if it doesn't exist
|
|
859
|
-
await db.execute(sql.raw(`
|
|
860
|
-
DO $$
|
|
861
|
-
BEGIN
|
|
862
|
-
IF NOT EXISTS (
|
|
863
|
-
SELECT 1 FROM information_schema.table_constraints
|
|
864
|
-
WHERE constraint_schema = 'meyiconnect' AND constraint_name = 'insight_sops_alert_id_fkey'
|
|
865
|
-
) THEN
|
|
866
|
-
-- Clear invalid alert_ids (which are actually old incident_ids) before adding the constraint
|
|
867
|
-
UPDATE "meyiconnect"."insight_sops"
|
|
868
|
-
SET alert_id = NULL
|
|
869
|
-
WHERE alert_id IS NOT NULL AND alert_id NOT IN (SELECT id FROM "meyiconnect"."insight_alerts");
|
|
870
|
-
|
|
871
|
-
ALTER TABLE "meyiconnect"."insight_sops" ADD CONSTRAINT insight_sops_alert_id_fkey FOREIGN KEY ("alert_id") REFERENCES "meyiconnect"."insight_alerts"("id") ON DELETE SET NULL;
|
|
872
|
-
END IF;
|
|
873
|
-
END $$;
|
|
859
|
+
await db.execute(sql.raw(`
|
|
860
|
+
DO $$
|
|
861
|
+
BEGIN
|
|
862
|
+
IF NOT EXISTS (
|
|
863
|
+
SELECT 1 FROM information_schema.table_constraints
|
|
864
|
+
WHERE constraint_schema = 'meyiconnect' AND constraint_name = 'insight_sops_alert_id_fkey'
|
|
865
|
+
) THEN
|
|
866
|
+
-- Clear invalid alert_ids (which are actually old incident_ids) before adding the constraint
|
|
867
|
+
UPDATE "meyiconnect"."insight_sops"
|
|
868
|
+
SET alert_id = NULL
|
|
869
|
+
WHERE alert_id IS NOT NULL AND alert_id NOT IN (SELECT id FROM "meyiconnect"."insight_alerts");
|
|
870
|
+
|
|
871
|
+
ALTER TABLE "meyiconnect"."insight_sops" ADD CONSTRAINT insight_sops_alert_id_fkey FOREIGN KEY ("alert_id") REFERENCES "meyiconnect"."insight_alerts"("id") ON DELETE SET NULL;
|
|
872
|
+
END IF;
|
|
873
|
+
END $$;
|
|
874
874
|
`));
|
|
875
875
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_sops" ADD COLUMN IF NOT EXISTS "alert_name" TEXT`));
|
|
876
876
|
await db.execute(sql.raw(`DROP INDEX IF EXISTS "meyiconnect"."insight_sops_alert_name_unique"`)).catch(() => { });
|
|
@@ -883,74 +883,74 @@ export async function runMigrations(db) {
|
|
|
883
883
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_sops_tenant_id_idx ON "meyiconnect"."insight_sops" ("tenant_id")`));
|
|
884
884
|
await db.execute(sql.raw(`CREATE UNIQUE INDEX IF NOT EXISTS insight_sops_tenant_sop_id_unique ON "meyiconnect"."insight_sops" ("tenant_id", "sop_id")`));
|
|
885
885
|
// incident_evidence
|
|
886
|
-
await db.execute(sql.raw(`
|
|
887
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."incident_evidence" (
|
|
888
|
-
"incident_id" UUID PRIMARY KEY REFERENCES "meyiconnect"."insight_incidents"("id") ON DELETE CASCADE,
|
|
889
|
-
"evidence_text" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
890
|
-
"investigation_findings" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
891
|
-
"rca_signals" JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
892
|
-
"tool_calls_made" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
893
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
894
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
895
|
-
)
|
|
886
|
+
await db.execute(sql.raw(`
|
|
887
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."incident_evidence" (
|
|
888
|
+
"incident_id" UUID PRIMARY KEY REFERENCES "meyiconnect"."insight_incidents"("id") ON DELETE CASCADE,
|
|
889
|
+
"evidence_text" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
890
|
+
"investigation_findings" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
891
|
+
"rca_signals" JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
892
|
+
"tool_calls_made" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
893
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
894
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
895
|
+
)
|
|
896
896
|
`));
|
|
897
897
|
// insight_workflows
|
|
898
|
-
await db.execute(sql.raw(`
|
|
899
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_workflows" (
|
|
900
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
901
|
-
"name" TEXT NOT NULL,
|
|
902
|
-
"description" TEXT,
|
|
903
|
-
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
904
|
-
"priority" INTEGER NOT NULL DEFAULT 0,
|
|
905
|
-
"conditions" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
906
|
-
"actions" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
907
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
908
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
909
|
-
)
|
|
910
|
-
`));
|
|
911
|
-
await db.execute(sql.raw(`
|
|
912
|
-
ALTER TABLE "meyiconnect"."insight_workflows"
|
|
913
|
-
ADD COLUMN IF NOT EXISTS "yaml_definition" TEXT NOT NULL DEFAULT ''
|
|
898
|
+
await db.execute(sql.raw(`
|
|
899
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_workflows" (
|
|
900
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
901
|
+
"name" TEXT NOT NULL,
|
|
902
|
+
"description" TEXT,
|
|
903
|
+
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
904
|
+
"priority" INTEGER NOT NULL DEFAULT 0,
|
|
905
|
+
"conditions" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
906
|
+
"actions" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
907
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
908
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
909
|
+
)
|
|
910
|
+
`));
|
|
911
|
+
await db.execute(sql.raw(`
|
|
912
|
+
ALTER TABLE "meyiconnect"."insight_workflows"
|
|
913
|
+
ADD COLUMN IF NOT EXISTS "yaml_definition" TEXT NOT NULL DEFAULT ''
|
|
914
914
|
`));
|
|
915
915
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_workflows" ADD COLUMN IF NOT EXISTS "tenant_id" TEXT`));
|
|
916
916
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_workflows" ADD COLUMN IF NOT EXISTS "source_id" UUID REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE`));
|
|
917
917
|
await backfill(`UPDATE "meyiconnect"."insight_workflows" SET "tenant_id" = '${defaultTenantSql}' WHERE "tenant_id" IS NULL OR "tenant_id" = ''`);
|
|
918
918
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_workflows_tenant_id_idx ON "meyiconnect"."insight_workflows" ("tenant_id")`));
|
|
919
|
-
await db.execute(sql.raw(`
|
|
920
|
-
ALTER TABLE "meyiconnect"."insight_workflows"
|
|
921
|
-
DROP COLUMN IF EXISTS "conditions"
|
|
919
|
+
await db.execute(sql.raw(`
|
|
920
|
+
ALTER TABLE "meyiconnect"."insight_workflows"
|
|
921
|
+
DROP COLUMN IF EXISTS "conditions"
|
|
922
922
|
`));
|
|
923
|
-
await db.execute(sql.raw(`
|
|
924
|
-
ALTER TABLE "meyiconnect"."insight_workflows"
|
|
925
|
-
DROP COLUMN IF EXISTS "actions"
|
|
923
|
+
await db.execute(sql.raw(`
|
|
924
|
+
ALTER TABLE "meyiconnect"."insight_workflows"
|
|
925
|
+
DROP COLUMN IF EXISTS "actions"
|
|
926
926
|
`));
|
|
927
|
-
await db.execute(sql.raw(`
|
|
928
|
-
ALTER TABLE "meyiconnect"."insight_workflows"
|
|
929
|
-
DROP COLUMN IF EXISTS "description"
|
|
927
|
+
await db.execute(sql.raw(`
|
|
928
|
+
ALTER TABLE "meyiconnect"."insight_workflows"
|
|
929
|
+
DROP COLUMN IF EXISTS "description"
|
|
930
930
|
`));
|
|
931
931
|
// insight_workflow_executions
|
|
932
|
-
await db.execute(sql.raw(`
|
|
933
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_workflow_executions" (
|
|
934
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
935
|
-
"workflow_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_workflows"("id") ON DELETE CASCADE,
|
|
936
|
-
"alert_id" UUID REFERENCES "meyiconnect"."insight_alerts"("id") ON DELETE SET NULL,
|
|
937
|
-
"status" TEXT NOT NULL DEFAULT 'pending',
|
|
938
|
-
"actions_executed" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
939
|
-
"error_message" TEXT,
|
|
940
|
-
"triggered_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
941
|
-
"completed_at" TIMESTAMPTZ
|
|
942
|
-
)
|
|
932
|
+
await db.execute(sql.raw(`
|
|
933
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_workflow_executions" (
|
|
934
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
935
|
+
"workflow_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_workflows"("id") ON DELETE CASCADE,
|
|
936
|
+
"alert_id" UUID REFERENCES "meyiconnect"."insight_alerts"("id") ON DELETE SET NULL,
|
|
937
|
+
"status" TEXT NOT NULL DEFAULT 'pending',
|
|
938
|
+
"actions_executed" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
939
|
+
"error_message" TEXT,
|
|
940
|
+
"triggered_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
941
|
+
"completed_at" TIMESTAMPTZ
|
|
942
|
+
)
|
|
943
943
|
`));
|
|
944
944
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_workflow_executions_workflow_id_idx ON "meyiconnect"."insight_workflow_executions" ("workflow_id")`));
|
|
945
945
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_workflow_executions_alert_id_idx ON "meyiconnect"."insight_workflow_executions" ("alert_id")`));
|
|
946
946
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_workflow_executions_status_idx ON "meyiconnect"."insight_workflow_executions" ("status")`));
|
|
947
947
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_workflow_executions" ADD COLUMN IF NOT EXISTS "tenant_id" TEXT`));
|
|
948
|
-
await db.execute(sql.raw(`
|
|
949
|
-
UPDATE "meyiconnect"."insight_workflow_executions" e
|
|
950
|
-
SET "tenant_id" = ${defaultTenantId ? `COALESCE(w."tenant_id", '${defaultTenantSql}')` : `w."tenant_id"`}
|
|
951
|
-
FROM "meyiconnect"."insight_workflows" w
|
|
952
|
-
WHERE e."workflow_id" = w."id"
|
|
953
|
-
AND (e."tenant_id" IS NULL OR e."tenant_id" = '')
|
|
948
|
+
await db.execute(sql.raw(`
|
|
949
|
+
UPDATE "meyiconnect"."insight_workflow_executions" e
|
|
950
|
+
SET "tenant_id" = ${defaultTenantId ? `COALESCE(w."tenant_id", '${defaultTenantSql}')` : `w."tenant_id"`}
|
|
951
|
+
FROM "meyiconnect"."insight_workflows" w
|
|
952
|
+
WHERE e."workflow_id" = w."id"
|
|
953
|
+
AND (e."tenant_id" IS NULL OR e."tenant_id" = '')
|
|
954
954
|
`));
|
|
955
955
|
await backfill(`UPDATE "meyiconnect"."insight_workflow_executions" SET "tenant_id" = '${defaultTenantSql}' WHERE "tenant_id" IS NULL OR "tenant_id" = ''`);
|
|
956
956
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_workflow_executions_tenant_id_idx ON "meyiconnect"."insight_workflow_executions" ("tenant_id")`));
|
|
@@ -962,40 +962,40 @@ export async function runMigrations(db) {
|
|
|
962
962
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alerts" ADD COLUMN IF NOT EXISTS "raw_payload" JSONB`));
|
|
963
963
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_alerts_pipeline_status_idx ON "meyiconnect"."insight_alerts" ("pipeline_status")`));
|
|
964
964
|
// ─── insight_provider_configs ────────────────────────────────────────────────
|
|
965
|
-
await db.execute(sql.raw(`
|
|
966
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_provider_configs" (
|
|
967
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
968
|
-
"tenant_id" TEXT,
|
|
969
|
-
"llm_provider_name" TEXT NOT NULL,
|
|
970
|
-
"llm_model_name" TEXT,
|
|
971
|
-
"llm_model_id" TEXT,
|
|
972
|
-
"llm_api_key" TEXT,
|
|
973
|
-
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
974
|
-
"auth_type" TEXT,
|
|
975
|
-
"aws_region" TEXT,
|
|
976
|
-
"aws_access_key_id" TEXT,
|
|
977
|
-
"aws_secret_access_key" TEXT,
|
|
978
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
979
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
980
|
-
)
|
|
965
|
+
await db.execute(sql.raw(`
|
|
966
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_provider_configs" (
|
|
967
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
968
|
+
"tenant_id" TEXT,
|
|
969
|
+
"llm_provider_name" TEXT NOT NULL,
|
|
970
|
+
"llm_model_name" TEXT,
|
|
971
|
+
"llm_model_id" TEXT,
|
|
972
|
+
"llm_api_key" TEXT,
|
|
973
|
+
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
974
|
+
"auth_type" TEXT,
|
|
975
|
+
"aws_region" TEXT,
|
|
976
|
+
"aws_access_key_id" TEXT,
|
|
977
|
+
"aws_secret_access_key" TEXT,
|
|
978
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
979
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
980
|
+
)
|
|
981
981
|
`));
|
|
982
982
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_provider_configs_tenant_id_idx ON "meyiconnect"."insight_provider_configs" ("tenant_id")`));
|
|
983
983
|
// ─── insight_normalization_configs ───────────────────────────────────────────
|
|
984
|
-
await db.execute(sql.raw(`
|
|
985
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_normalization_configs" (
|
|
986
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
987
|
-
"tenant_id" TEXT,
|
|
988
|
-
"name" TEXT NOT NULL,
|
|
989
|
-
"priority" INTEGER NOT NULL DEFAULT 0,
|
|
990
|
-
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
991
|
-
"source_id" UUID REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
992
|
-
"source_type" TEXT,
|
|
993
|
-
"field_mapping" TEXT NOT NULL DEFAULT '',
|
|
994
|
-
"yaml_definition" TEXT NOT NULL DEFAULT '',
|
|
995
|
-
"is_system" BOOLEAN NOT NULL DEFAULT false,
|
|
996
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
997
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
998
|
-
)
|
|
984
|
+
await db.execute(sql.raw(`
|
|
985
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_normalization_configs" (
|
|
986
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
987
|
+
"tenant_id" TEXT,
|
|
988
|
+
"name" TEXT NOT NULL,
|
|
989
|
+
"priority" INTEGER NOT NULL DEFAULT 0,
|
|
990
|
+
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
991
|
+
"source_id" UUID REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
992
|
+
"source_type" TEXT,
|
|
993
|
+
"field_mapping" TEXT NOT NULL DEFAULT '',
|
|
994
|
+
"yaml_definition" TEXT NOT NULL DEFAULT '',
|
|
995
|
+
"is_system" BOOLEAN NOT NULL DEFAULT false,
|
|
996
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
997
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
998
|
+
)
|
|
999
999
|
`));
|
|
1000
1000
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_normalization_configs" ADD COLUMN IF NOT EXISTS "is_system" BOOLEAN NOT NULL DEFAULT false`));
|
|
1001
1001
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_normalization_configs_priority_idx ON "meyiconnect"."insight_normalization_configs" ("priority")`));
|
|
@@ -1004,25 +1004,25 @@ export async function runMigrations(db) {
|
|
|
1004
1004
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_normalization_configs" ADD COLUMN IF NOT EXISTS "tenant_id" TEXT`));
|
|
1005
1005
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_normalization_configs_tenant_id_idx ON "meyiconnect"."insight_normalization_configs" ("tenant_id")`));
|
|
1006
1006
|
// ─── insight_dedup_configs ───────────────────────────────────────────────────
|
|
1007
|
-
await db.execute(sql.raw(`
|
|
1008
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_dedup_configs" (
|
|
1009
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1010
|
-
"tenant_id" TEXT,
|
|
1011
|
-
"name" TEXT NOT NULL,
|
|
1012
|
-
"priority" INTEGER NOT NULL DEFAULT 0,
|
|
1013
|
-
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
1014
|
-
"source_id" UUID REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
1015
|
-
"source_type" TEXT,
|
|
1016
|
-
"window_seconds" INTEGER NOT NULL DEFAULT 300,
|
|
1017
|
-
"key_fields" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
1018
|
-
"flap_threshold" INTEGER NOT NULL DEFAULT 4,
|
|
1019
|
-
"flap_window_seconds" INTEGER NOT NULL DEFAULT 600,
|
|
1020
|
-
"on_flap" TEXT NOT NULL DEFAULT 'none',
|
|
1021
|
-
"yaml_definition" TEXT NOT NULL DEFAULT '',
|
|
1022
|
-
"is_system" BOOLEAN NOT NULL DEFAULT false,
|
|
1023
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1024
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1025
|
-
)
|
|
1007
|
+
await db.execute(sql.raw(`
|
|
1008
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_dedup_configs" (
|
|
1009
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1010
|
+
"tenant_id" TEXT,
|
|
1011
|
+
"name" TEXT NOT NULL,
|
|
1012
|
+
"priority" INTEGER NOT NULL DEFAULT 0,
|
|
1013
|
+
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
1014
|
+
"source_id" UUID REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
1015
|
+
"source_type" TEXT,
|
|
1016
|
+
"window_seconds" INTEGER NOT NULL DEFAULT 300,
|
|
1017
|
+
"key_fields" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
1018
|
+
"flap_threshold" INTEGER NOT NULL DEFAULT 4,
|
|
1019
|
+
"flap_window_seconds" INTEGER NOT NULL DEFAULT 600,
|
|
1020
|
+
"on_flap" TEXT NOT NULL DEFAULT 'none',
|
|
1021
|
+
"yaml_definition" TEXT NOT NULL DEFAULT '',
|
|
1022
|
+
"is_system" BOOLEAN NOT NULL DEFAULT false,
|
|
1023
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1024
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1025
|
+
)
|
|
1026
1026
|
`));
|
|
1027
1027
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_dedup_configs" ADD COLUMN IF NOT EXISTS "is_system" BOOLEAN NOT NULL DEFAULT false`));
|
|
1028
1028
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_dedup_configs_priority_idx ON "meyiconnect"."insight_dedup_configs" ("priority")`));
|
|
@@ -1031,25 +1031,25 @@ export async function runMigrations(db) {
|
|
|
1031
1031
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_dedup_configs" ADD COLUMN IF NOT EXISTS "tenant_id" TEXT`));
|
|
1032
1032
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_dedup_configs_tenant_id_idx ON "meyiconnect"."insight_dedup_configs" ("tenant_id")`));
|
|
1033
1033
|
// ─── insight_grouping_configs ────────────────────────────────────────────────
|
|
1034
|
-
await db.execute(sql.raw(`
|
|
1035
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_grouping_configs" (
|
|
1036
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1037
|
-
"tenant_id" TEXT,
|
|
1038
|
-
"name" TEXT NOT NULL,
|
|
1039
|
-
"priority" INTEGER NOT NULL DEFAULT 0,
|
|
1040
|
-
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
1041
|
-
"source_id" UUID REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
1042
|
-
"source_type" TEXT,
|
|
1043
|
-
"group_by" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
1044
|
-
"timeout_seconds" INTEGER NOT NULL DEFAULT 600,
|
|
1045
|
-
"max_alarms" INTEGER NOT NULL DEFAULT 50,
|
|
1046
|
-
"incident_title_template" TEXT DEFAULT '{{count}} alerts on {{resourceName}}',
|
|
1047
|
-
"use_ai_generate" BOOLEAN NOT NULL DEFAULT false,
|
|
1048
|
-
"yaml_definition" TEXT NOT NULL DEFAULT '',
|
|
1049
|
-
"is_system" BOOLEAN NOT NULL DEFAULT false,
|
|
1050
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1051
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1052
|
-
)
|
|
1034
|
+
await db.execute(sql.raw(`
|
|
1035
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_grouping_configs" (
|
|
1036
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1037
|
+
"tenant_id" TEXT,
|
|
1038
|
+
"name" TEXT NOT NULL,
|
|
1039
|
+
"priority" INTEGER NOT NULL DEFAULT 0,
|
|
1040
|
+
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
1041
|
+
"source_id" UUID REFERENCES "meyiconnect"."insight_sources"("id") ON DELETE CASCADE,
|
|
1042
|
+
"source_type" TEXT,
|
|
1043
|
+
"group_by" JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
1044
|
+
"timeout_seconds" INTEGER NOT NULL DEFAULT 600,
|
|
1045
|
+
"max_alarms" INTEGER NOT NULL DEFAULT 50,
|
|
1046
|
+
"incident_title_template" TEXT DEFAULT '{{count}} alerts on {{resourceName}}',
|
|
1047
|
+
"use_ai_generate" BOOLEAN NOT NULL DEFAULT false,
|
|
1048
|
+
"yaml_definition" TEXT NOT NULL DEFAULT '',
|
|
1049
|
+
"is_system" BOOLEAN NOT NULL DEFAULT false,
|
|
1050
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1051
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1052
|
+
)
|
|
1053
1053
|
`));
|
|
1054
1054
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_grouping_configs" ADD COLUMN IF NOT EXISTS "is_system" BOOLEAN NOT NULL DEFAULT false`));
|
|
1055
1055
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_grouping_configs_priority_idx ON "meyiconnect"."insight_grouping_configs" ("priority")`));
|
|
@@ -1060,48 +1060,48 @@ export async function runMigrations(db) {
|
|
|
1060
1060
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_grouping_configs" ADD COLUMN IF NOT EXISTS "use_ai_generate" BOOLEAN NOT NULL DEFAULT false`));
|
|
1061
1061
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_grouping_configs" ADD COLUMN IF NOT EXISTS "topology_tag_mapping" TEXT`));
|
|
1062
1062
|
// ─── insight_alarm_groups ────────────────────────────────────────────────────
|
|
1063
|
-
await db.execute(sql.raw(`
|
|
1064
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_alarm_groups" (
|
|
1065
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1066
|
-
"tenant_id" TEXT,
|
|
1067
|
-
"group_key" TEXT NOT NULL,
|
|
1068
|
-
"config_id" UUID REFERENCES "meyiconnect"."insight_grouping_configs"("id") ON DELETE SET NULL,
|
|
1069
|
-
"status" TEXT NOT NULL DEFAULT 'buffering',
|
|
1070
|
-
"first_alarm_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1071
|
-
"last_alarm_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1072
|
-
"incident_id" UUID,
|
|
1073
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1074
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1075
|
-
)
|
|
1063
|
+
await db.execute(sql.raw(`
|
|
1064
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_alarm_groups" (
|
|
1065
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1066
|
+
"tenant_id" TEXT,
|
|
1067
|
+
"group_key" TEXT NOT NULL,
|
|
1068
|
+
"config_id" UUID REFERENCES "meyiconnect"."insight_grouping_configs"("id") ON DELETE SET NULL,
|
|
1069
|
+
"status" TEXT NOT NULL DEFAULT 'buffering',
|
|
1070
|
+
"first_alarm_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1071
|
+
"last_alarm_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1072
|
+
"incident_id" UUID,
|
|
1073
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1074
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1075
|
+
)
|
|
1076
1076
|
`));
|
|
1077
1077
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_alarm_groups_flush_idx ON "meyiconnect"."insight_alarm_groups" ("status", "last_alarm_at")`));
|
|
1078
1078
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_alarm_groups_group_key_idx ON "meyiconnect"."insight_alarm_groups" ("group_key")`));
|
|
1079
1079
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_alarm_groups" ADD COLUMN IF NOT EXISTS "tenant_id" TEXT`));
|
|
1080
1080
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_alarm_groups_tenant_id_idx ON "meyiconnect"."insight_alarm_groups" ("tenant_id")`));
|
|
1081
1081
|
// ─── insight_alarm_group_members (P1 junction table) ─────────────────────────
|
|
1082
|
-
await db.execute(sql.raw(`
|
|
1083
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_alarm_group_members" (
|
|
1084
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1085
|
-
"group_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_alarm_groups"("id") ON DELETE CASCADE,
|
|
1086
|
-
"alarm_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_alerts"("id") ON DELETE CASCADE,
|
|
1087
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1088
|
-
)
|
|
1082
|
+
await db.execute(sql.raw(`
|
|
1083
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_alarm_group_members" (
|
|
1084
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1085
|
+
"group_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_alarm_groups"("id") ON DELETE CASCADE,
|
|
1086
|
+
"alarm_id" UUID NOT NULL REFERENCES "meyiconnect"."insight_alerts"("id") ON DELETE CASCADE,
|
|
1087
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1088
|
+
)
|
|
1089
1089
|
`));
|
|
1090
1090
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_alarm_group_members_group_id_idx ON "meyiconnect"."insight_alarm_group_members" ("group_id")`));
|
|
1091
1091
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_alarm_group_members_alarm_id_idx ON "meyiconnect"."insight_alarm_group_members" ("alarm_id")`));
|
|
1092
1092
|
await db.execute(sql.raw(`CREATE UNIQUE INDEX IF NOT EXISTS insight_alarm_group_members_unique_idx ON "meyiconnect"."insight_alarm_group_members" ("group_id", "alarm_id")`));
|
|
1093
1093
|
// ─── insight_pipeline_events ─────────────────────────────────────────────────
|
|
1094
|
-
await db.execute(sql.raw(`
|
|
1095
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_pipeline_events" (
|
|
1096
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1097
|
-
"tenant_id" TEXT,
|
|
1098
|
-
"alarm_id" UUID REFERENCES "meyiconnect"."insight_alerts"("id") ON DELETE CASCADE,
|
|
1099
|
-
"stage" TEXT NOT NULL,
|
|
1100
|
-
"status" TEXT NOT NULL,
|
|
1101
|
-
"matched_config_id" UUID,
|
|
1102
|
-
"meta" JSONB,
|
|
1103
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1104
|
-
)
|
|
1094
|
+
await db.execute(sql.raw(`
|
|
1095
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_pipeline_events" (
|
|
1096
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1097
|
+
"tenant_id" TEXT,
|
|
1098
|
+
"alarm_id" UUID REFERENCES "meyiconnect"."insight_alerts"("id") ON DELETE CASCADE,
|
|
1099
|
+
"stage" TEXT NOT NULL,
|
|
1100
|
+
"status" TEXT NOT NULL,
|
|
1101
|
+
"matched_config_id" UUID,
|
|
1102
|
+
"meta" JSONB,
|
|
1103
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1104
|
+
)
|
|
1105
1105
|
`));
|
|
1106
1106
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_pipeline_events_alarm_id_idx ON "meyiconnect"."insight_pipeline_events" ("alarm_id")`));
|
|
1107
1107
|
await db.execute(sql.raw(`CREATE INDEX IF NOT EXISTS insight_pipeline_events_stage_idx ON "meyiconnect"."insight_pipeline_events" ("stage")`));
|
|
@@ -1117,46 +1117,46 @@ export async function runMigrations(db) {
|
|
|
1117
1117
|
if (configs.normalization) {
|
|
1118
1118
|
const parsed = yaml.load(configs.normalization.yamlDefinition);
|
|
1119
1119
|
const name = parsed.name || `${sourceType} Default Normalization`;
|
|
1120
|
-
await db.execute(sql.raw(`
|
|
1121
|
-
UPDATE "meyiconnect"."insight_normalization_configs"
|
|
1122
|
-
SET "field_mapping" = $token$${configs.normalization.fieldMapping}$token$,
|
|
1123
|
-
"yaml_definition" = $token$${configs.normalization.yamlDefinition}$token$
|
|
1124
|
-
WHERE "source_type" = '${sourceType}' AND "is_system" = true
|
|
1120
|
+
await db.execute(sql.raw(`
|
|
1121
|
+
UPDATE "meyiconnect"."insight_normalization_configs"
|
|
1122
|
+
SET "field_mapping" = $token$${configs.normalization.fieldMapping}$token$,
|
|
1123
|
+
"yaml_definition" = $token$${configs.normalization.yamlDefinition}$token$
|
|
1124
|
+
WHERE "source_type" = '${sourceType}' AND "is_system" = true
|
|
1125
1125
|
`));
|
|
1126
|
-
await db.execute(sql.raw(`
|
|
1127
|
-
INSERT INTO "meyiconnect"."insight_normalization_configs"
|
|
1128
|
-
("name", "priority", "is_active", "source_type", "field_mapping", "yaml_definition", "is_system")
|
|
1129
|
-
SELECT '${name}', 10, true, '${sourceType}', $token$${configs.normalization.fieldMapping}$token$, $token$${configs.normalization.yamlDefinition}$token$, true
|
|
1130
|
-
WHERE NOT EXISTS (
|
|
1131
|
-
SELECT 1 FROM "meyiconnect"."insight_normalization_configs"
|
|
1132
|
-
WHERE "source_type" = '${sourceType}' AND "is_system" = true
|
|
1133
|
-
)
|
|
1126
|
+
await db.execute(sql.raw(`
|
|
1127
|
+
INSERT INTO "meyiconnect"."insight_normalization_configs"
|
|
1128
|
+
("name", "priority", "is_active", "source_type", "field_mapping", "yaml_definition", "is_system")
|
|
1129
|
+
SELECT '${name}', 10, true, '${sourceType}', $token$${configs.normalization.fieldMapping}$token$, $token$${configs.normalization.yamlDefinition}$token$, true
|
|
1130
|
+
WHERE NOT EXISTS (
|
|
1131
|
+
SELECT 1 FROM "meyiconnect"."insight_normalization_configs"
|
|
1132
|
+
WHERE "source_type" = '${sourceType}' AND "is_system" = true
|
|
1133
|
+
)
|
|
1134
1134
|
`));
|
|
1135
1135
|
}
|
|
1136
1136
|
if (configs.dedup) {
|
|
1137
1137
|
const parsed = yaml.load(configs.dedup.yamlDefinition);
|
|
1138
1138
|
const name = parsed.name || `${sourceType} Default Deduplication`;
|
|
1139
|
-
await db.execute(sql.raw(`
|
|
1140
|
-
INSERT INTO "meyiconnect"."insight_dedup_configs"
|
|
1141
|
-
("name", "priority", "is_active", "source_type", "yaml_definition", "is_system")
|
|
1142
|
-
SELECT '${name}', 10, true, '${sourceType}', $token$${configs.dedup.yamlDefinition}$token$, true
|
|
1143
|
-
WHERE NOT EXISTS (
|
|
1144
|
-
SELECT 1 FROM "meyiconnect"."insight_dedup_configs"
|
|
1145
|
-
WHERE "source_type" = '${sourceType}' AND "is_system" = true
|
|
1146
|
-
)
|
|
1139
|
+
await db.execute(sql.raw(`
|
|
1140
|
+
INSERT INTO "meyiconnect"."insight_dedup_configs"
|
|
1141
|
+
("name", "priority", "is_active", "source_type", "yaml_definition", "is_system")
|
|
1142
|
+
SELECT '${name}', 10, true, '${sourceType}', $token$${configs.dedup.yamlDefinition}$token$, true
|
|
1143
|
+
WHERE NOT EXISTS (
|
|
1144
|
+
SELECT 1 FROM "meyiconnect"."insight_dedup_configs"
|
|
1145
|
+
WHERE "source_type" = '${sourceType}' AND "is_system" = true
|
|
1146
|
+
)
|
|
1147
1147
|
`));
|
|
1148
1148
|
}
|
|
1149
1149
|
if (configs.grouping) {
|
|
1150
1150
|
const parsed = yaml.load(configs.grouping.yamlDefinition);
|
|
1151
1151
|
const name = parsed.name || `${sourceType} Default Grouping`;
|
|
1152
|
-
await db.execute(sql.raw(`
|
|
1153
|
-
INSERT INTO "meyiconnect"."insight_grouping_configs"
|
|
1154
|
-
("name", "priority", "is_active", "source_type", "yaml_definition", "is_system")
|
|
1155
|
-
SELECT '${name}', 10, true, '${sourceType}', $token$${configs.grouping.yamlDefinition}$token$, true
|
|
1156
|
-
WHERE NOT EXISTS (
|
|
1157
|
-
SELECT 1 FROM "meyiconnect"."insight_grouping_configs"
|
|
1158
|
-
WHERE "source_type" = '${sourceType}' AND "is_system" = true
|
|
1159
|
-
)
|
|
1152
|
+
await db.execute(sql.raw(`
|
|
1153
|
+
INSERT INTO "meyiconnect"."insight_grouping_configs"
|
|
1154
|
+
("name", "priority", "is_active", "source_type", "yaml_definition", "is_system")
|
|
1155
|
+
SELECT '${name}', 10, true, '${sourceType}', $token$${configs.grouping.yamlDefinition}$token$, true
|
|
1156
|
+
WHERE NOT EXISTS (
|
|
1157
|
+
SELECT 1 FROM "meyiconnect"."insight_grouping_configs"
|
|
1158
|
+
WHERE "source_type" = '${sourceType}' AND "is_system" = true
|
|
1159
|
+
)
|
|
1160
1160
|
`));
|
|
1161
1161
|
}
|
|
1162
1162
|
}
|
|
@@ -1167,371 +1167,371 @@ export async function runMigrations(db) {
|
|
|
1167
1167
|
// ─── insight_connectors ────────────────────────────────────────────────────
|
|
1168
1168
|
// Denormalized connector table written by marketplace-service during
|
|
1169
1169
|
// onboarding. insight-server reads from this table for the Connectors page.
|
|
1170
|
-
await db.execute(sql.raw(`
|
|
1171
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_connectors" (
|
|
1172
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1173
|
-
"tenant_id" TEXT NOT NULL,
|
|
1174
|
-
"account_id" TEXT NOT NULL,
|
|
1175
|
-
"account_name" TEXT NOT NULL,
|
|
1176
|
-
"email" TEXT,
|
|
1177
|
-
"access_status" TEXT NOT NULL DEFAULT 'Active',
|
|
1178
|
-
"connection_status" TEXT NOT NULL DEFAULT 'PENDING',
|
|
1179
|
-
"connection_id" TEXT NOT NULL,
|
|
1180
|
-
"management_account_id" TEXT,
|
|
1181
|
-
"external_id" TEXT NOT NULL,
|
|
1182
|
-
"role_name" TEXT NOT NULL,
|
|
1183
|
-
"role_arn" TEXT NOT NULL,
|
|
1184
|
-
"selected" BOOLEAN NOT NULL DEFAULT false,
|
|
1185
|
-
"joined_method" TEXT,
|
|
1186
|
-
"connected_at" TIMESTAMPTZ,
|
|
1187
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1188
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1189
|
-
)
|
|
1190
|
-
`));
|
|
1191
|
-
await db.execute(sql.raw(`
|
|
1192
|
-
CREATE UNIQUE INDEX IF NOT EXISTS insight_connectors_tenant_account_unique
|
|
1193
|
-
ON "meyiconnect"."insight_connectors" ("tenant_id", "account_id")
|
|
1194
|
-
`));
|
|
1195
|
-
await db.execute(sql.raw(`
|
|
1196
|
-
CREATE INDEX IF NOT EXISTS insight_connectors_tenant_idx
|
|
1197
|
-
ON "meyiconnect"."insight_connectors" ("tenant_id")
|
|
1198
|
-
`));
|
|
1199
|
-
await db.execute(sql.raw(`
|
|
1200
|
-
CREATE INDEX IF NOT EXISTS insight_connectors_connection_idx
|
|
1201
|
-
ON "meyiconnect"."insight_connectors" ("connection_id")
|
|
1170
|
+
await db.execute(sql.raw(`
|
|
1171
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_connectors" (
|
|
1172
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1173
|
+
"tenant_id" TEXT NOT NULL,
|
|
1174
|
+
"account_id" TEXT NOT NULL,
|
|
1175
|
+
"account_name" TEXT NOT NULL,
|
|
1176
|
+
"email" TEXT,
|
|
1177
|
+
"access_status" TEXT NOT NULL DEFAULT 'Active',
|
|
1178
|
+
"connection_status" TEXT NOT NULL DEFAULT 'PENDING',
|
|
1179
|
+
"connection_id" TEXT NOT NULL,
|
|
1180
|
+
"management_account_id" TEXT,
|
|
1181
|
+
"external_id" TEXT NOT NULL,
|
|
1182
|
+
"role_name" TEXT NOT NULL,
|
|
1183
|
+
"role_arn" TEXT NOT NULL,
|
|
1184
|
+
"selected" BOOLEAN NOT NULL DEFAULT false,
|
|
1185
|
+
"joined_method" TEXT,
|
|
1186
|
+
"connected_at" TIMESTAMPTZ,
|
|
1187
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1188
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1189
|
+
)
|
|
1190
|
+
`));
|
|
1191
|
+
await db.execute(sql.raw(`
|
|
1192
|
+
CREATE UNIQUE INDEX IF NOT EXISTS insight_connectors_tenant_account_unique
|
|
1193
|
+
ON "meyiconnect"."insight_connectors" ("tenant_id", "account_id")
|
|
1194
|
+
`));
|
|
1195
|
+
await db.execute(sql.raw(`
|
|
1196
|
+
CREATE INDEX IF NOT EXISTS insight_connectors_tenant_idx
|
|
1197
|
+
ON "meyiconnect"."insight_connectors" ("tenant_id")
|
|
1198
|
+
`));
|
|
1199
|
+
await db.execute(sql.raw(`
|
|
1200
|
+
CREATE INDEX IF NOT EXISTS insight_connectors_connection_idx
|
|
1201
|
+
ON "meyiconnect"."insight_connectors" ("connection_id")
|
|
1202
1202
|
`));
|
|
1203
1203
|
// connectors.environment: the live table is "connectors" (created by onboarding); guard in case it
|
|
1204
1204
|
// doesn't exist yet. Onboarding's migrate.ts also adds this column, so either service may run first.
|
|
1205
|
-
await db.execute(sql.raw(`
|
|
1206
|
-
DO $$ BEGIN
|
|
1207
|
-
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'connectors') THEN
|
|
1208
|
-
ALTER TABLE "meyiconnect"."connectors" ADD COLUMN IF NOT EXISTS "environment" TEXT;
|
|
1209
|
-
END IF;
|
|
1210
|
-
END $$
|
|
1205
|
+
await db.execute(sql.raw(`
|
|
1206
|
+
DO $$ BEGIN
|
|
1207
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'meyiconnect' AND table_name = 'connectors') THEN
|
|
1208
|
+
ALTER TABLE "meyiconnect"."connectors" ADD COLUMN IF NOT EXISTS "environment" TEXT;
|
|
1209
|
+
END IF;
|
|
1210
|
+
END $$
|
|
1211
1211
|
`));
|
|
1212
|
-
await db.execute(sql.raw(`
|
|
1213
|
-
CREATE INDEX IF NOT EXISTS insight_connectors_access_status_idx
|
|
1214
|
-
ON "meyiconnect"."insight_connectors" ("access_status")
|
|
1212
|
+
await db.execute(sql.raw(`
|
|
1213
|
+
CREATE INDEX IF NOT EXISTS insight_connectors_access_status_idx
|
|
1214
|
+
ON "meyiconnect"."insight_connectors" ("access_status")
|
|
1215
1215
|
`));
|
|
1216
1216
|
// ─── insight_connector_sla_policies ───────────────────────────────────────
|
|
1217
1217
|
// Per-account SLA override (enable flag + acknowledge/resolve limits).
|
|
1218
|
-
await db.execute(sql.raw(`
|
|
1219
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_connector_sla_policies" (
|
|
1220
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1221
|
-
"tenant_id" TEXT NOT NULL,
|
|
1222
|
-
"account_id" TEXT NOT NULL,
|
|
1223
|
-
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
|
1224
|
-
"updated_by" TEXT,
|
|
1225
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1226
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1227
|
-
)
|
|
1228
|
-
`));
|
|
1229
|
-
await db.execute(sql.raw(`
|
|
1230
|
-
CREATE UNIQUE INDEX IF NOT EXISTS insight_connector_sla_policies_tenant_account_unique
|
|
1231
|
-
ON "meyiconnect"."insight_connector_sla_policies" ("tenant_id", "account_id")
|
|
1232
|
-
`));
|
|
1233
|
-
await db.execute(sql.raw(`
|
|
1234
|
-
CREATE INDEX IF NOT EXISTS insight_connector_sla_policies_tenant_idx
|
|
1235
|
-
ON "meyiconnect"."insight_connector_sla_policies" ("tenant_id")
|
|
1218
|
+
await db.execute(sql.raw(`
|
|
1219
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_connector_sla_policies" (
|
|
1220
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1221
|
+
"tenant_id" TEXT NOT NULL,
|
|
1222
|
+
"account_id" TEXT NOT NULL,
|
|
1223
|
+
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
|
1224
|
+
"updated_by" TEXT,
|
|
1225
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1226
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1227
|
+
)
|
|
1228
|
+
`));
|
|
1229
|
+
await db.execute(sql.raw(`
|
|
1230
|
+
CREATE UNIQUE INDEX IF NOT EXISTS insight_connector_sla_policies_tenant_account_unique
|
|
1231
|
+
ON "meyiconnect"."insight_connector_sla_policies" ("tenant_id", "account_id")
|
|
1232
|
+
`));
|
|
1233
|
+
await db.execute(sql.raw(`
|
|
1234
|
+
CREATE INDEX IF NOT EXISTS insight_connector_sla_policies_tenant_idx
|
|
1235
|
+
ON "meyiconnect"."insight_connector_sla_policies" ("tenant_id")
|
|
1236
1236
|
`));
|
|
1237
1237
|
// ─── insight_connector_sla_targets ────────────────────────────────────────
|
|
1238
1238
|
// Per-priority acknowledge/resolve limits for an account's SLA policy.
|
|
1239
|
-
await db.execute(sql.raw(`
|
|
1240
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_connector_sla_targets" (
|
|
1241
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1242
|
-
"tenant_id" TEXT NOT NULL,
|
|
1243
|
-
"account_id" TEXT NOT NULL,
|
|
1244
|
-
"incident_group" TEXT NOT NULL DEFAULT 'default',
|
|
1245
|
-
"priority" TEXT NOT NULL,
|
|
1246
|
-
"ack_minutes" INTEGER NOT NULL,
|
|
1247
|
-
"resolve_minutes" INTEGER,
|
|
1248
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1249
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1250
|
-
CONSTRAINT insight_connector_sla_targets_priority_chk CHECK (priority IN ('P1','P2','P3','P4')),
|
|
1251
|
-
CONSTRAINT insight_connector_sla_targets_group_chk
|
|
1252
|
-
CHECK (incident_group IN ('default','operations','security','cost')),
|
|
1253
|
-
CONSTRAINT insight_connector_sla_targets_limits_chk
|
|
1254
|
-
CHECK (ack_minutes BETWEEN 1 AND 43200 AND (resolve_minutes IS NULL OR (resolve_minutes >= ack_minutes AND resolve_minutes <= 43200)))
|
|
1255
|
-
)
|
|
1256
|
-
`));
|
|
1257
|
-
await db.execute(sql.raw(`
|
|
1258
|
-
CREATE INDEX IF NOT EXISTS insight_connector_sla_targets_tenant_idx
|
|
1259
|
-
ON "meyiconnect"."insight_connector_sla_targets" ("tenant_id")
|
|
1239
|
+
await db.execute(sql.raw(`
|
|
1240
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_connector_sla_targets" (
|
|
1241
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1242
|
+
"tenant_id" TEXT NOT NULL,
|
|
1243
|
+
"account_id" TEXT NOT NULL,
|
|
1244
|
+
"incident_group" TEXT NOT NULL DEFAULT 'default',
|
|
1245
|
+
"priority" TEXT NOT NULL,
|
|
1246
|
+
"ack_minutes" INTEGER NOT NULL,
|
|
1247
|
+
"resolve_minutes" INTEGER,
|
|
1248
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1249
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1250
|
+
CONSTRAINT insight_connector_sla_targets_priority_chk CHECK (priority IN ('P1','P2','P3','P4')),
|
|
1251
|
+
CONSTRAINT insight_connector_sla_targets_group_chk
|
|
1252
|
+
CHECK (incident_group IN ('default','operations','security','cost')),
|
|
1253
|
+
CONSTRAINT insight_connector_sla_targets_limits_chk
|
|
1254
|
+
CHECK (ack_minutes BETWEEN 1 AND 43200 AND (resolve_minutes IS NULL OR (resolve_minutes >= ack_minutes AND resolve_minutes <= 43200)))
|
|
1255
|
+
)
|
|
1256
|
+
`));
|
|
1257
|
+
await db.execute(sql.raw(`
|
|
1258
|
+
CREATE INDEX IF NOT EXISTS insight_connector_sla_targets_tenant_idx
|
|
1259
|
+
ON "meyiconnect"."insight_connector_sla_targets" ("tenant_id")
|
|
1260
1260
|
`));
|
|
1261
1261
|
// SLA per incident group (insight-server 1.1.0). Upgrades a 1.0.8 table in place:
|
|
1262
1262
|
// existing rows become the account default ('default' group), so evaluation is unchanged.
|
|
1263
|
-
await db.execute(sql.raw(`
|
|
1264
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_policies"
|
|
1265
|
-
ADD COLUMN IF NOT EXISTS "applies_to" TEXT[] NOT NULL DEFAULT '{operations,security,cost}'::text[]
|
|
1266
|
-
`));
|
|
1267
|
-
await db.execute(sql.raw(`
|
|
1268
|
-
DO $$ BEGIN
|
|
1269
|
-
IF NOT EXISTS (
|
|
1270
|
-
SELECT 1 FROM pg_constraint
|
|
1271
|
-
WHERE conname = 'insight_connector_sla_policies_applies_to_chk'
|
|
1272
|
-
AND conrelid = '"meyiconnect"."insight_connector_sla_policies"'::regclass
|
|
1273
|
-
) THEN
|
|
1274
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_policies" ADD CONSTRAINT insight_connector_sla_policies_applies_to_chk
|
|
1275
|
-
CHECK ("applies_to" <@ ARRAY['operations','security','cost']::text[]);
|
|
1276
|
-
END IF;
|
|
1277
|
-
END $$
|
|
1278
|
-
`));
|
|
1279
|
-
await db.execute(sql.raw(`
|
|
1280
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_targets"
|
|
1281
|
-
ADD COLUMN IF NOT EXISTS "incident_group" TEXT NOT NULL DEFAULT 'default'
|
|
1263
|
+
await db.execute(sql.raw(`
|
|
1264
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_policies"
|
|
1265
|
+
ADD COLUMN IF NOT EXISTS "applies_to" TEXT[] NOT NULL DEFAULT '{operations,security,cost}'::text[]
|
|
1266
|
+
`));
|
|
1267
|
+
await db.execute(sql.raw(`
|
|
1268
|
+
DO $$ BEGIN
|
|
1269
|
+
IF NOT EXISTS (
|
|
1270
|
+
SELECT 1 FROM pg_constraint
|
|
1271
|
+
WHERE conname = 'insight_connector_sla_policies_applies_to_chk'
|
|
1272
|
+
AND conrelid = '"meyiconnect"."insight_connector_sla_policies"'::regclass
|
|
1273
|
+
) THEN
|
|
1274
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_policies" ADD CONSTRAINT insight_connector_sla_policies_applies_to_chk
|
|
1275
|
+
CHECK ("applies_to" <@ ARRAY['operations','security','cost']::text[]);
|
|
1276
|
+
END IF;
|
|
1277
|
+
END $$
|
|
1278
|
+
`));
|
|
1279
|
+
await db.execute(sql.raw(`
|
|
1280
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_targets"
|
|
1281
|
+
ADD COLUMN IF NOT EXISTS "incident_group" TEXT NOT NULL DEFAULT 'default'
|
|
1282
1282
|
`));
|
|
1283
1283
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_connector_sla_targets" ALTER COLUMN "resolve_minutes" DROP NOT NULL`));
|
|
1284
|
-
await db.execute(sql.raw(`
|
|
1285
|
-
DO $$ BEGIN
|
|
1286
|
-
IF NOT EXISTS (
|
|
1287
|
-
SELECT 1 FROM pg_constraint
|
|
1288
|
-
WHERE conname = 'insight_connector_sla_targets_group_chk'
|
|
1289
|
-
AND conrelid = '"meyiconnect"."insight_connector_sla_targets"'::regclass
|
|
1290
|
-
) THEN
|
|
1291
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_targets" ADD CONSTRAINT insight_connector_sla_targets_group_chk
|
|
1292
|
-
CHECK ("incident_group" IN ('default','operations','security','cost'));
|
|
1293
|
-
END IF;
|
|
1294
|
-
-- Replace the 1.0.8 limits CHECK (resolve NOT NULL) with one that allows best effort (NULL resolve).
|
|
1295
|
-
IF EXISTS (
|
|
1296
|
-
SELECT 1 FROM pg_constraint
|
|
1297
|
-
WHERE conname = 'insight_connector_sla_targets_limits_chk'
|
|
1298
|
-
AND conrelid = '"meyiconnect"."insight_connector_sla_targets"'::regclass
|
|
1299
|
-
AND pg_get_constraintdef(oid) NOT ILIKE '%resolve_minutes IS NULL%'
|
|
1300
|
-
) THEN
|
|
1301
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_targets" DROP CONSTRAINT insight_connector_sla_targets_limits_chk;
|
|
1302
|
-
END IF;
|
|
1303
|
-
IF NOT EXISTS (
|
|
1304
|
-
SELECT 1 FROM pg_constraint
|
|
1305
|
-
WHERE conname = 'insight_connector_sla_targets_limits_chk'
|
|
1306
|
-
AND conrelid = '"meyiconnect"."insight_connector_sla_targets"'::regclass
|
|
1307
|
-
) THEN
|
|
1308
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_targets" ADD CONSTRAINT insight_connector_sla_targets_limits_chk
|
|
1309
|
-
CHECK (ack_minutes BETWEEN 1 AND 43200 AND (resolve_minutes IS NULL OR (resolve_minutes >= ack_minutes AND resolve_minutes <= 43200)));
|
|
1310
|
-
END IF;
|
|
1311
|
-
END $$
|
|
1284
|
+
await db.execute(sql.raw(`
|
|
1285
|
+
DO $$ BEGIN
|
|
1286
|
+
IF NOT EXISTS (
|
|
1287
|
+
SELECT 1 FROM pg_constraint
|
|
1288
|
+
WHERE conname = 'insight_connector_sla_targets_group_chk'
|
|
1289
|
+
AND conrelid = '"meyiconnect"."insight_connector_sla_targets"'::regclass
|
|
1290
|
+
) THEN
|
|
1291
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_targets" ADD CONSTRAINT insight_connector_sla_targets_group_chk
|
|
1292
|
+
CHECK ("incident_group" IN ('default','operations','security','cost'));
|
|
1293
|
+
END IF;
|
|
1294
|
+
-- Replace the 1.0.8 limits CHECK (resolve NOT NULL) with one that allows best effort (NULL resolve).
|
|
1295
|
+
IF EXISTS (
|
|
1296
|
+
SELECT 1 FROM pg_constraint
|
|
1297
|
+
WHERE conname = 'insight_connector_sla_targets_limits_chk'
|
|
1298
|
+
AND conrelid = '"meyiconnect"."insight_connector_sla_targets"'::regclass
|
|
1299
|
+
AND pg_get_constraintdef(oid) NOT ILIKE '%resolve_minutes IS NULL%'
|
|
1300
|
+
) THEN
|
|
1301
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_targets" DROP CONSTRAINT insight_connector_sla_targets_limits_chk;
|
|
1302
|
+
END IF;
|
|
1303
|
+
IF NOT EXISTS (
|
|
1304
|
+
SELECT 1 FROM pg_constraint
|
|
1305
|
+
WHERE conname = 'insight_connector_sla_targets_limits_chk'
|
|
1306
|
+
AND conrelid = '"meyiconnect"."insight_connector_sla_targets"'::regclass
|
|
1307
|
+
) THEN
|
|
1308
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_targets" ADD CONSTRAINT insight_connector_sla_targets_limits_chk
|
|
1309
|
+
CHECK (ack_minutes BETWEEN 1 AND 43200 AND (resolve_minutes IS NULL OR (resolve_minutes >= ack_minutes AND resolve_minutes <= 43200)));
|
|
1310
|
+
END IF;
|
|
1311
|
+
END $$
|
|
1312
1312
|
`));
|
|
1313
1313
|
// The 1.0.8 unique key (tenant, account, priority) would reject group overrides; replace it.
|
|
1314
|
-
await db.execute(sql.raw(`
|
|
1315
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_targets"
|
|
1316
|
-
DROP CONSTRAINT IF EXISTS insight_connector_sla_targets_tenant_account_priority_unique
|
|
1314
|
+
await db.execute(sql.raw(`
|
|
1315
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_targets"
|
|
1316
|
+
DROP CONSTRAINT IF EXISTS insight_connector_sla_targets_tenant_account_priority_unique
|
|
1317
1317
|
`));
|
|
1318
1318
|
await db.execute(sql.raw(`DROP INDEX IF EXISTS "meyiconnect"."insight_connector_sla_targets_tenant_account_priority_unique"`));
|
|
1319
|
-
await db.execute(sql.raw(`
|
|
1320
|
-
CREATE UNIQUE INDEX IF NOT EXISTS insight_connector_sla_targets_tenant_account_group_priority_unique
|
|
1321
|
-
ON "meyiconnect"."insight_connector_sla_targets" ("tenant_id", "account_id", "incident_group", "priority")
|
|
1319
|
+
await db.execute(sql.raw(`
|
|
1320
|
+
CREATE UNIQUE INDEX IF NOT EXISTS insight_connector_sla_targets_tenant_account_group_priority_unique
|
|
1321
|
+
ON "meyiconnect"."insight_connector_sla_targets" ("tenant_id", "account_id", "incident_group", "priority")
|
|
1322
1322
|
`));
|
|
1323
1323
|
// Upgrade from the single-limit policy: copy each account's limits to all four priorities (so behaviour is
|
|
1324
1324
|
// unchanged), then relax the old columns. They stay in place, unread, until a later release drops them.
|
|
1325
|
-
await db.execute(sql.raw(`
|
|
1326
|
-
DO $$ BEGIN
|
|
1327
|
-
IF EXISTS (
|
|
1328
|
-
SELECT 1 FROM information_schema.columns
|
|
1329
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_connector_sla_policies' AND column_name = 'ack_minutes'
|
|
1330
|
-
) THEN
|
|
1331
|
-
INSERT INTO "meyiconnect"."insight_connector_sla_targets" ("tenant_id", "account_id", "priority", "ack_minutes", "resolve_minutes")
|
|
1332
|
-
SELECT p."tenant_id", p."account_id", v."priority", p."ack_minutes", p."resolve_minutes"
|
|
1333
|
-
FROM "meyiconnect"."insight_connector_sla_policies" p
|
|
1334
|
-
CROSS JOIN (VALUES ('P1'), ('P2'), ('P3'), ('P4')) AS v("priority")
|
|
1335
|
-
WHERE p."ack_minutes" IS NOT NULL AND p."resolve_minutes" IS NOT NULL
|
|
1336
|
-
AND NOT EXISTS (
|
|
1337
|
-
SELECT 1 FROM "meyiconnect"."insight_connector_sla_targets" t
|
|
1338
|
-
WHERE t."tenant_id" = p."tenant_id" AND t."account_id" = p."account_id" AND t."priority" = v."priority"
|
|
1339
|
-
);
|
|
1340
|
-
|
|
1341
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_policies" ALTER COLUMN "ack_minutes" DROP NOT NULL;
|
|
1342
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_policies" ALTER COLUMN "resolve_minutes" DROP NOT NULL;
|
|
1343
|
-
ALTER TABLE "meyiconnect"."insight_connector_sla_policies" DROP CONSTRAINT IF EXISTS insight_connector_sla_policies_limits_chk;
|
|
1344
|
-
END IF;
|
|
1345
|
-
END $$
|
|
1325
|
+
await db.execute(sql.raw(`
|
|
1326
|
+
DO $$ BEGIN
|
|
1327
|
+
IF EXISTS (
|
|
1328
|
+
SELECT 1 FROM information_schema.columns
|
|
1329
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_connector_sla_policies' AND column_name = 'ack_minutes'
|
|
1330
|
+
) THEN
|
|
1331
|
+
INSERT INTO "meyiconnect"."insight_connector_sla_targets" ("tenant_id", "account_id", "priority", "ack_minutes", "resolve_minutes")
|
|
1332
|
+
SELECT p."tenant_id", p."account_id", v."priority", p."ack_minutes", p."resolve_minutes"
|
|
1333
|
+
FROM "meyiconnect"."insight_connector_sla_policies" p
|
|
1334
|
+
CROSS JOIN (VALUES ('P1'), ('P2'), ('P3'), ('P4')) AS v("priority")
|
|
1335
|
+
WHERE p."ack_minutes" IS NOT NULL AND p."resolve_minutes" IS NOT NULL
|
|
1336
|
+
AND NOT EXISTS (
|
|
1337
|
+
SELECT 1 FROM "meyiconnect"."insight_connector_sla_targets" t
|
|
1338
|
+
WHERE t."tenant_id" = p."tenant_id" AND t."account_id" = p."account_id" AND t."priority" = v."priority"
|
|
1339
|
+
);
|
|
1340
|
+
|
|
1341
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_policies" ALTER COLUMN "ack_minutes" DROP NOT NULL;
|
|
1342
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_policies" ALTER COLUMN "resolve_minutes" DROP NOT NULL;
|
|
1343
|
+
ALTER TABLE "meyiconnect"."insight_connector_sla_policies" DROP CONSTRAINT IF EXISTS insight_connector_sla_policies_limits_chk;
|
|
1344
|
+
END IF;
|
|
1345
|
+
END $$
|
|
1346
1346
|
`));
|
|
1347
1347
|
// ─── insight_job_queue ────────────────────────────────────────────────────
|
|
1348
|
-
await db.execute(sql.raw(`
|
|
1349
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_job_queue" (
|
|
1350
|
-
"id" SERIAL PRIMARY KEY,
|
|
1351
|
-
"job_type" TEXT NOT NULL,
|
|
1352
|
-
"payload" JSONB NOT NULL,
|
|
1353
|
-
"status" TEXT NOT NULL DEFAULT 'pending',
|
|
1354
|
-
"attempts" INTEGER NOT NULL DEFAULT 0,
|
|
1355
|
-
"max_attempts" INTEGER NOT NULL DEFAULT 3,
|
|
1356
|
-
"available_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1357
|
-
"leased_by" TEXT,
|
|
1358
|
-
"leased_at" TIMESTAMPTZ,
|
|
1359
|
-
"last_error" TEXT,
|
|
1360
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1361
|
-
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1362
|
-
)
|
|
1363
|
-
`));
|
|
1364
|
-
await db.execute(sql.raw(`
|
|
1365
|
-
CREATE INDEX IF NOT EXISTS insight_job_queue_fetch_idx
|
|
1366
|
-
ON "meyiconnect"."insight_job_queue" ("job_type", "status", "available_at")
|
|
1348
|
+
await db.execute(sql.raw(`
|
|
1349
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."insight_job_queue" (
|
|
1350
|
+
"id" SERIAL PRIMARY KEY,
|
|
1351
|
+
"job_type" TEXT NOT NULL,
|
|
1352
|
+
"payload" JSONB NOT NULL,
|
|
1353
|
+
"status" TEXT NOT NULL DEFAULT 'pending',
|
|
1354
|
+
"attempts" INTEGER NOT NULL DEFAULT 0,
|
|
1355
|
+
"max_attempts" INTEGER NOT NULL DEFAULT 3,
|
|
1356
|
+
"available_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1357
|
+
"leased_by" TEXT,
|
|
1358
|
+
"leased_at" TIMESTAMPTZ,
|
|
1359
|
+
"last_error" TEXT,
|
|
1360
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1361
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1362
|
+
)
|
|
1363
|
+
`));
|
|
1364
|
+
await db.execute(sql.raw(`
|
|
1365
|
+
CREATE INDEX IF NOT EXISTS insight_job_queue_fetch_idx
|
|
1366
|
+
ON "meyiconnect"."insight_job_queue" ("job_type", "status", "available_at")
|
|
1367
1367
|
`));
|
|
1368
1368
|
// ─── generic tags migration & project_tag removal ─────────────────────────
|
|
1369
|
-
await db.execute(sql.raw(`
|
|
1370
|
-
ALTER TABLE "meyiconnect"."insight_alerts"
|
|
1371
|
-
ADD COLUMN IF NOT EXISTS "tags" JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
1372
|
-
`));
|
|
1373
|
-
await db.execute(sql.raw(`
|
|
1374
|
-
ALTER TABLE "meyiconnect"."insight_incidents"
|
|
1375
|
-
ADD COLUMN IF NOT EXISTS "tags" JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
1376
|
-
`));
|
|
1377
|
-
await db.execute(sql.raw(`
|
|
1378
|
-
DO $$
|
|
1379
|
-
BEGIN
|
|
1380
|
-
IF EXISTS (
|
|
1381
|
-
SELECT 1 FROM information_schema.columns
|
|
1382
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'project_tag'
|
|
1383
|
-
) THEN
|
|
1384
|
-
UPDATE "meyiconnect"."insight_alerts"
|
|
1385
|
-
SET "tags" = COALESCE("tags", '{}'::jsonb)
|
|
1386
|
-
|| CASE WHEN "additional_configuration"->'tags' IS NOT NULL THEN ("additional_configuration"->'tags') ELSE '{}'::jsonb END
|
|
1387
|
-
|| CASE WHEN "project_tag" IS NOT NULL AND "project_tag" != '' THEN jsonb_build_object('project', "project_tag") ELSE '{}'::jsonb END;
|
|
1388
|
-
|
|
1389
|
-
DROP INDEX IF EXISTS "meyiconnect"."insight_alerts_project_tag_idx";
|
|
1390
|
-
ALTER TABLE "meyiconnect"."insight_alerts" DROP COLUMN "project_tag";
|
|
1391
|
-
END IF;
|
|
1392
|
-
|
|
1393
|
-
IF EXISTS (
|
|
1394
|
-
SELECT 1 FROM information_schema.columns
|
|
1395
|
-
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_incidents' AND column_name = 'project_tag'
|
|
1396
|
-
) THEN
|
|
1397
|
-
UPDATE "meyiconnect"."insight_incidents"
|
|
1398
|
-
SET "tags" = COALESCE("tags", '{}'::jsonb)
|
|
1399
|
-
|| CASE WHEN "project_tag" IS NOT NULL AND "project_tag" != '' THEN jsonb_build_object('project', "project_tag") ELSE '{}'::jsonb END;
|
|
1400
|
-
|
|
1401
|
-
ALTER TABLE "meyiconnect"."insight_incidents" DROP COLUMN "project_tag";
|
|
1402
|
-
END IF;
|
|
1403
|
-
END $$;
|
|
1404
|
-
`));
|
|
1405
|
-
await db.execute(sql.raw(`
|
|
1406
|
-
CREATE INDEX IF NOT EXISTS insight_alerts_tags_idx
|
|
1407
|
-
ON "meyiconnect"."insight_alerts" USING GIN ("tags");
|
|
1408
|
-
`));
|
|
1409
|
-
await db.execute(sql.raw(`
|
|
1410
|
-
CREATE INDEX IF NOT EXISTS insight_incidents_tags_idx
|
|
1411
|
-
ON "meyiconnect"."insight_incidents" USING GIN ("tags");
|
|
1369
|
+
await db.execute(sql.raw(`
|
|
1370
|
+
ALTER TABLE "meyiconnect"."insight_alerts"
|
|
1371
|
+
ADD COLUMN IF NOT EXISTS "tags" JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
1372
|
+
`));
|
|
1373
|
+
await db.execute(sql.raw(`
|
|
1374
|
+
ALTER TABLE "meyiconnect"."insight_incidents"
|
|
1375
|
+
ADD COLUMN IF NOT EXISTS "tags" JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
1376
|
+
`));
|
|
1377
|
+
await db.execute(sql.raw(`
|
|
1378
|
+
DO $$
|
|
1379
|
+
BEGIN
|
|
1380
|
+
IF EXISTS (
|
|
1381
|
+
SELECT 1 FROM information_schema.columns
|
|
1382
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_alerts' AND column_name = 'project_tag'
|
|
1383
|
+
) THEN
|
|
1384
|
+
UPDATE "meyiconnect"."insight_alerts"
|
|
1385
|
+
SET "tags" = COALESCE("tags", '{}'::jsonb)
|
|
1386
|
+
|| CASE WHEN "additional_configuration"->'tags' IS NOT NULL THEN ("additional_configuration"->'tags') ELSE '{}'::jsonb END
|
|
1387
|
+
|| CASE WHEN "project_tag" IS NOT NULL AND "project_tag" != '' THEN jsonb_build_object('project', "project_tag") ELSE '{}'::jsonb END;
|
|
1388
|
+
|
|
1389
|
+
DROP INDEX IF EXISTS "meyiconnect"."insight_alerts_project_tag_idx";
|
|
1390
|
+
ALTER TABLE "meyiconnect"."insight_alerts" DROP COLUMN "project_tag";
|
|
1391
|
+
END IF;
|
|
1392
|
+
|
|
1393
|
+
IF EXISTS (
|
|
1394
|
+
SELECT 1 FROM information_schema.columns
|
|
1395
|
+
WHERE table_schema = 'meyiconnect' AND table_name = 'insight_incidents' AND column_name = 'project_tag'
|
|
1396
|
+
) THEN
|
|
1397
|
+
UPDATE "meyiconnect"."insight_incidents"
|
|
1398
|
+
SET "tags" = COALESCE("tags", '{}'::jsonb)
|
|
1399
|
+
|| CASE WHEN "project_tag" IS NOT NULL AND "project_tag" != '' THEN jsonb_build_object('project', "project_tag") ELSE '{}'::jsonb END;
|
|
1400
|
+
|
|
1401
|
+
ALTER TABLE "meyiconnect"."insight_incidents" DROP COLUMN "project_tag";
|
|
1402
|
+
END IF;
|
|
1403
|
+
END $$;
|
|
1404
|
+
`));
|
|
1405
|
+
await db.execute(sql.raw(`
|
|
1406
|
+
CREATE INDEX IF NOT EXISTS insight_alerts_tags_idx
|
|
1407
|
+
ON "meyiconnect"."insight_alerts" USING GIN ("tags");
|
|
1408
|
+
`));
|
|
1409
|
+
await db.execute(sql.raw(`
|
|
1410
|
+
CREATE INDEX IF NOT EXISTS insight_incidents_tags_idx
|
|
1411
|
+
ON "meyiconnect"."insight_incidents" USING GIN ("tags");
|
|
1412
1412
|
`));
|
|
1413
1413
|
// ai_usage_log — one row per AI run (an RCA investigation, or a SOP
|
|
1414
1414
|
// generation), carrying the token/cost totals insight-ai already computes
|
|
1415
1415
|
// via app/costing/calculator.py but previously only logged.
|
|
1416
|
-
await db.execute(sql.raw(`
|
|
1417
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_usage_log" (
|
|
1418
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1419
|
-
"tenant_id" TEXT NOT NULL,
|
|
1420
|
-
"call_type" TEXT NOT NULL,
|
|
1421
|
-
"incident_id" UUID,
|
|
1422
|
-
"incident_name" TEXT,
|
|
1423
|
-
"sop_id" TEXT,
|
|
1424
|
-
"agent_job_id" UUID,
|
|
1425
|
-
"models" JSONB,
|
|
1426
|
-
"total_input_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1427
|
-
"total_output_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1428
|
-
"total_cache_read_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1429
|
-
"total_cache_write_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1430
|
-
"total_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1431
|
-
"total_cost" NUMERIC(18,6),
|
|
1432
|
-
"currency" TEXT NOT NULL DEFAULT 'USD',
|
|
1433
|
-
"priced" BOOLEAN NOT NULL DEFAULT false,
|
|
1434
|
-
"reason" TEXT,
|
|
1435
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1436
|
-
);
|
|
1437
|
-
`));
|
|
1438
|
-
await db.execute(sql.raw(`
|
|
1439
|
-
CREATE INDEX IF NOT EXISTS ai_usage_log_tenant_id_idx
|
|
1440
|
-
ON "meyiconnect"."ai_usage_log" ("tenant_id");
|
|
1441
|
-
`));
|
|
1442
|
-
await db.execute(sql.raw(`
|
|
1443
|
-
CREATE INDEX IF NOT EXISTS ai_usage_log_incident_id_idx
|
|
1444
|
-
ON "meyiconnect"."ai_usage_log" ("incident_id");
|
|
1416
|
+
await db.execute(sql.raw(`
|
|
1417
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_usage_log" (
|
|
1418
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1419
|
+
"tenant_id" TEXT NOT NULL,
|
|
1420
|
+
"call_type" TEXT NOT NULL,
|
|
1421
|
+
"incident_id" UUID,
|
|
1422
|
+
"incident_name" TEXT,
|
|
1423
|
+
"sop_id" TEXT,
|
|
1424
|
+
"agent_job_id" UUID,
|
|
1425
|
+
"models" JSONB,
|
|
1426
|
+
"total_input_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1427
|
+
"total_output_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1428
|
+
"total_cache_read_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1429
|
+
"total_cache_write_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1430
|
+
"total_tokens" INTEGER NOT NULL DEFAULT 0,
|
|
1431
|
+
"total_cost" NUMERIC(18,6),
|
|
1432
|
+
"currency" TEXT NOT NULL DEFAULT 'USD',
|
|
1433
|
+
"priced" BOOLEAN NOT NULL DEFAULT false,
|
|
1434
|
+
"reason" TEXT,
|
|
1435
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1436
|
+
);
|
|
1437
|
+
`));
|
|
1438
|
+
await db.execute(sql.raw(`
|
|
1439
|
+
CREATE INDEX IF NOT EXISTS ai_usage_log_tenant_id_idx
|
|
1440
|
+
ON "meyiconnect"."ai_usage_log" ("tenant_id");
|
|
1441
|
+
`));
|
|
1442
|
+
await db.execute(sql.raw(`
|
|
1443
|
+
CREATE INDEX IF NOT EXISTS ai_usage_log_incident_id_idx
|
|
1444
|
+
ON "meyiconnect"."ai_usage_log" ("incident_id");
|
|
1445
1445
|
`));
|
|
1446
1446
|
// report_id links call_type='cost_report' rows (written by
|
|
1447
1447
|
// insight-cost-server from the cost-ai-analyser's status.json) back to
|
|
1448
1448
|
// meyiconnect.cost_ai_reports. insight-cost-server's schema installer adds
|
|
1449
1449
|
// the same column, so either host can migrate first.
|
|
1450
|
-
await db.execute(sql.raw(`
|
|
1451
|
-
ALTER TABLE "meyiconnect"."ai_usage_log" ADD COLUMN IF NOT EXISTS "report_id" TEXT;
|
|
1450
|
+
await db.execute(sql.raw(`
|
|
1451
|
+
ALTER TABLE "meyiconnect"."ai_usage_log" ADD COLUMN IF NOT EXISTS "report_id" TEXT;
|
|
1452
1452
|
`));
|
|
1453
1453
|
// The AI usage API (src/features/ai-usage) reads a tenant's rows by time window.
|
|
1454
|
-
await db.execute(sql.raw(`
|
|
1455
|
-
CREATE INDEX IF NOT EXISTS ai_usage_log_tenant_created_idx
|
|
1456
|
-
ON "meyiconnect"."ai_usage_log" ("tenant_id", "created_at" DESC);
|
|
1454
|
+
await db.execute(sql.raw(`
|
|
1455
|
+
CREATE INDEX IF NOT EXISTS ai_usage_log_tenant_created_idx
|
|
1456
|
+
ON "meyiconnect"."ai_usage_log" ("tenant_id", "created_at" DESC);
|
|
1457
1457
|
`));
|
|
1458
1458
|
// The re-pricing job (src/features/ai-pricing) scans recent unpriced rows across tenants.
|
|
1459
|
-
await db.execute(sql.raw(`
|
|
1460
|
-
CREATE INDEX IF NOT EXISTS ai_usage_log_unpriced_idx
|
|
1461
|
-
ON "meyiconnect"."ai_usage_log" ("created_at", "id") WHERE "priced" = false;
|
|
1459
|
+
await db.execute(sql.raw(`
|
|
1460
|
+
CREATE INDEX IF NOT EXISTS ai_usage_log_unpriced_idx
|
|
1461
|
+
ON "meyiconnect"."ai_usage_log" ("created_at", "id") WHERE "priced" = false;
|
|
1462
1462
|
`));
|
|
1463
1463
|
// ─── AI model pricing (src/features/ai-pricing, docs: insight-ai/docs/ai-model-pricing-plan.md) ─
|
|
1464
1464
|
// Platform-level list prices for every AI provider, filled daily from LiteLLM's
|
|
1465
1465
|
// public price file plus manual overrides. Not tenant-scoped. insight-cost-server's
|
|
1466
1466
|
// schema installer mirrors ai_model_pricing and ai_model_aliases identically (it
|
|
1467
1467
|
// reads them), so either host can migrate first.
|
|
1468
|
-
await db.execute(sql.raw(`
|
|
1469
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_model_pricing" (
|
|
1470
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1471
|
-
"provider" TEXT NOT NULL,
|
|
1472
|
-
"model_key" TEXT NOT NULL,
|
|
1473
|
-
"region" TEXT NOT NULL DEFAULT '*',
|
|
1474
|
-
"input_per_million" NUMERIC(18,6) NOT NULL CHECK ("input_per_million" >= 0),
|
|
1475
|
-
"output_per_million" NUMERIC(18,6) NOT NULL CHECK ("output_per_million" >= 0),
|
|
1476
|
-
"cache_read_per_million" NUMERIC(18,6) CHECK ("cache_read_per_million" >= 0),
|
|
1477
|
-
"cache_write_per_million" NUMERIC(18,6) CHECK ("cache_write_per_million" >= 0),
|
|
1478
|
-
"currency" TEXT NOT NULL DEFAULT 'USD',
|
|
1479
|
-
"effective_from" TIMESTAMPTZ NOT NULL,
|
|
1480
|
-
"effective_to" TIMESTAMPTZ,
|
|
1481
|
-
"source" TEXT NOT NULL,
|
|
1482
|
-
"source_ref" TEXT,
|
|
1483
|
-
"verified_at" TIMESTAMPTZ,
|
|
1484
|
-
"updated_by" TEXT,
|
|
1485
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1486
|
-
CHECK ("effective_to" IS NULL OR "effective_to" > "effective_from")
|
|
1487
|
-
);
|
|
1488
|
-
`));
|
|
1489
|
-
await db.execute(sql.raw(`
|
|
1490
|
-
CREATE UNIQUE INDEX IF NOT EXISTS ai_model_pricing_current_uq
|
|
1491
|
-
ON "meyiconnect"."ai_model_pricing" ("provider", "model_key", "region") WHERE "effective_to" IS NULL;
|
|
1492
|
-
`));
|
|
1493
|
-
await db.execute(sql.raw(`
|
|
1494
|
-
CREATE INDEX IF NOT EXISTS ai_model_pricing_lookup_idx
|
|
1495
|
-
ON "meyiconnect"."ai_model_pricing" ("provider", "model_key", "region", "effective_from" DESC);
|
|
1496
|
-
`));
|
|
1497
|
-
await db.execute(sql.raw(`
|
|
1498
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_model_aliases" (
|
|
1499
|
-
"provider" TEXT NOT NULL,
|
|
1500
|
-
"alias" TEXT NOT NULL,
|
|
1501
|
-
"model_key" TEXT NOT NULL,
|
|
1502
|
-
PRIMARY KEY ("provider", "alias")
|
|
1503
|
-
);
|
|
1504
|
-
`));
|
|
1505
|
-
await db.execute(sql.raw(`
|
|
1506
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_pricing_sync_state" (
|
|
1507
|
-
"source" TEXT PRIMARY KEY,
|
|
1508
|
-
"source_ref" TEXT,
|
|
1509
|
-
"etag" TEXT,
|
|
1510
|
-
"last_checked_at" TIMESTAMPTZ,
|
|
1511
|
-
"last_changed_at" TIMESTAMPTZ,
|
|
1512
|
-
"last_repriced_at" TIMESTAMPTZ,
|
|
1513
|
-
"last_error" TEXT
|
|
1514
|
-
);
|
|
1515
|
-
`));
|
|
1516
|
-
await db.execute(sql.raw(`
|
|
1517
|
-
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_model_pricing_review" (
|
|
1518
|
-
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1519
|
-
"provider" TEXT NOT NULL,
|
|
1520
|
-
"model_key" TEXT NOT NULL,
|
|
1521
|
-
"region" TEXT NOT NULL,
|
|
1522
|
-
"current" JSONB,
|
|
1523
|
-
"proposed" JSONB NOT NULL,
|
|
1524
|
-
"reason" TEXT NOT NULL,
|
|
1525
|
-
"status" TEXT NOT NULL DEFAULT 'pending' CHECK ("status" IN ('pending','applied','rejected')),
|
|
1526
|
-
"source_ref" TEXT,
|
|
1527
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1528
|
-
"decided_by" TEXT,
|
|
1529
|
-
"decided_at" TIMESTAMPTZ
|
|
1530
|
-
);
|
|
1531
|
-
`));
|
|
1532
|
-
await db.execute(sql.raw(`
|
|
1533
|
-
CREATE INDEX IF NOT EXISTS ai_model_pricing_review_pending_idx
|
|
1534
|
-
ON "meyiconnect"."ai_model_pricing_review" ("provider", "model_key", "region") WHERE "status" = 'pending';
|
|
1468
|
+
await db.execute(sql.raw(`
|
|
1469
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_model_pricing" (
|
|
1470
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1471
|
+
"provider" TEXT NOT NULL,
|
|
1472
|
+
"model_key" TEXT NOT NULL,
|
|
1473
|
+
"region" TEXT NOT NULL DEFAULT '*',
|
|
1474
|
+
"input_per_million" NUMERIC(18,6) NOT NULL CHECK ("input_per_million" >= 0),
|
|
1475
|
+
"output_per_million" NUMERIC(18,6) NOT NULL CHECK ("output_per_million" >= 0),
|
|
1476
|
+
"cache_read_per_million" NUMERIC(18,6) CHECK ("cache_read_per_million" >= 0),
|
|
1477
|
+
"cache_write_per_million" NUMERIC(18,6) CHECK ("cache_write_per_million" >= 0),
|
|
1478
|
+
"currency" TEXT NOT NULL DEFAULT 'USD',
|
|
1479
|
+
"effective_from" TIMESTAMPTZ NOT NULL,
|
|
1480
|
+
"effective_to" TIMESTAMPTZ,
|
|
1481
|
+
"source" TEXT NOT NULL,
|
|
1482
|
+
"source_ref" TEXT,
|
|
1483
|
+
"verified_at" TIMESTAMPTZ,
|
|
1484
|
+
"updated_by" TEXT,
|
|
1485
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1486
|
+
CHECK ("effective_to" IS NULL OR "effective_to" > "effective_from")
|
|
1487
|
+
);
|
|
1488
|
+
`));
|
|
1489
|
+
await db.execute(sql.raw(`
|
|
1490
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ai_model_pricing_current_uq
|
|
1491
|
+
ON "meyiconnect"."ai_model_pricing" ("provider", "model_key", "region") WHERE "effective_to" IS NULL;
|
|
1492
|
+
`));
|
|
1493
|
+
await db.execute(sql.raw(`
|
|
1494
|
+
CREATE INDEX IF NOT EXISTS ai_model_pricing_lookup_idx
|
|
1495
|
+
ON "meyiconnect"."ai_model_pricing" ("provider", "model_key", "region", "effective_from" DESC);
|
|
1496
|
+
`));
|
|
1497
|
+
await db.execute(sql.raw(`
|
|
1498
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_model_aliases" (
|
|
1499
|
+
"provider" TEXT NOT NULL,
|
|
1500
|
+
"alias" TEXT NOT NULL,
|
|
1501
|
+
"model_key" TEXT NOT NULL,
|
|
1502
|
+
PRIMARY KEY ("provider", "alias")
|
|
1503
|
+
);
|
|
1504
|
+
`));
|
|
1505
|
+
await db.execute(sql.raw(`
|
|
1506
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_pricing_sync_state" (
|
|
1507
|
+
"source" TEXT PRIMARY KEY,
|
|
1508
|
+
"source_ref" TEXT,
|
|
1509
|
+
"etag" TEXT,
|
|
1510
|
+
"last_checked_at" TIMESTAMPTZ,
|
|
1511
|
+
"last_changed_at" TIMESTAMPTZ,
|
|
1512
|
+
"last_repriced_at" TIMESTAMPTZ,
|
|
1513
|
+
"last_error" TEXT
|
|
1514
|
+
);
|
|
1515
|
+
`));
|
|
1516
|
+
await db.execute(sql.raw(`
|
|
1517
|
+
CREATE TABLE IF NOT EXISTS "meyiconnect"."ai_model_pricing_review" (
|
|
1518
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1519
|
+
"provider" TEXT NOT NULL,
|
|
1520
|
+
"model_key" TEXT NOT NULL,
|
|
1521
|
+
"region" TEXT NOT NULL,
|
|
1522
|
+
"current" JSONB,
|
|
1523
|
+
"proposed" JSONB NOT NULL,
|
|
1524
|
+
"reason" TEXT NOT NULL,
|
|
1525
|
+
"status" TEXT NOT NULL DEFAULT 'pending' CHECK ("status" IN ('pending','applied','rejected')),
|
|
1526
|
+
"source_ref" TEXT,
|
|
1527
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
1528
|
+
"decided_by" TEXT,
|
|
1529
|
+
"decided_at" TIMESTAMPTZ
|
|
1530
|
+
);
|
|
1531
|
+
`));
|
|
1532
|
+
await db.execute(sql.raw(`
|
|
1533
|
+
CREATE INDEX IF NOT EXISTS ai_model_pricing_review_pending_idx
|
|
1534
|
+
ON "meyiconnect"."ai_model_pricing_review" ("provider", "model_key", "region") WHERE "status" = 'pending';
|
|
1535
1535
|
`));
|
|
1536
1536
|
// ─── insight_incidents.priority ───────────────────────────────────────────
|
|
1537
1537
|
// P1..P4. The default is set before the backfill so rows inserted meanwhile (e.g. by a server that
|
|
@@ -1539,34 +1539,34 @@ export async function runMigrations(db) {
|
|
|
1539
1539
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "priority" TEXT`));
|
|
1540
1540
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "priority" SET DEFAULT 'P2'`));
|
|
1541
1541
|
// Backfill: priority tag, else severity tag, else the linked alert's severity, else P2.
|
|
1542
|
-
await db.execute(sql.raw(`
|
|
1543
|
-
UPDATE "meyiconnect"."insight_incidents" i
|
|
1544
|
-
SET "priority" = COALESCE(
|
|
1545
|
-
CASE UPPER(BTRIM(i."tags"->>'priority'))
|
|
1546
|
-
WHEN 'P1' THEN 'P1' WHEN 'P2' THEN 'P2' WHEN 'P3' THEN 'P3' WHEN 'P4' THEN 'P4'
|
|
1547
|
-
END,
|
|
1548
|
-
CASE LOWER(BTRIM(i."tags"->>'severity'))
|
|
1549
|
-
WHEN 'critical' THEN 'P1' WHEN 'high' THEN 'P2' WHEN 'medium' THEN 'P3' WHEN 'low' THEN 'P4' WHEN 'info' THEN 'P4'
|
|
1550
|
-
END,
|
|
1551
|
-
CASE LOWER(BTRIM((SELECT a."severity" FROM "meyiconnect"."insight_alerts" a WHERE a."id" = i."source_alert_id")))
|
|
1552
|
-
WHEN 'critical' THEN 'P1' WHEN 'high' THEN 'P2' WHEN 'medium' THEN 'P3' WHEN 'low' THEN 'P4' WHEN 'info' THEN 'P4'
|
|
1553
|
-
END,
|
|
1554
|
-
'P2'
|
|
1555
|
-
)
|
|
1556
|
-
WHERE i."priority" IS NULL
|
|
1542
|
+
await db.execute(sql.raw(`
|
|
1543
|
+
UPDATE "meyiconnect"."insight_incidents" i
|
|
1544
|
+
SET "priority" = COALESCE(
|
|
1545
|
+
CASE UPPER(BTRIM(i."tags"->>'priority'))
|
|
1546
|
+
WHEN 'P1' THEN 'P1' WHEN 'P2' THEN 'P2' WHEN 'P3' THEN 'P3' WHEN 'P4' THEN 'P4'
|
|
1547
|
+
END,
|
|
1548
|
+
CASE LOWER(BTRIM(i."tags"->>'severity'))
|
|
1549
|
+
WHEN 'critical' THEN 'P1' WHEN 'high' THEN 'P2' WHEN 'medium' THEN 'P3' WHEN 'low' THEN 'P4' WHEN 'info' THEN 'P4'
|
|
1550
|
+
END,
|
|
1551
|
+
CASE LOWER(BTRIM((SELECT a."severity" FROM "meyiconnect"."insight_alerts" a WHERE a."id" = i."source_alert_id")))
|
|
1552
|
+
WHEN 'critical' THEN 'P1' WHEN 'high' THEN 'P2' WHEN 'medium' THEN 'P3' WHEN 'low' THEN 'P4' WHEN 'info' THEN 'P4'
|
|
1553
|
+
END,
|
|
1554
|
+
'P2'
|
|
1555
|
+
)
|
|
1556
|
+
WHERE i."priority" IS NULL
|
|
1557
1557
|
`));
|
|
1558
1558
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ALTER COLUMN "priority" SET NOT NULL`));
|
|
1559
|
-
await db.execute(sql.raw(`
|
|
1560
|
-
DO $$ BEGIN
|
|
1561
|
-
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'insight_incidents_priority_chk') THEN
|
|
1562
|
-
ALTER TABLE "meyiconnect"."insight_incidents" ADD CONSTRAINT insight_incidents_priority_chk
|
|
1563
|
-
CHECK ("priority" IN ('P1','P2','P3','P4'));
|
|
1564
|
-
END IF;
|
|
1565
|
-
END $$
|
|
1566
|
-
`));
|
|
1567
|
-
await db.execute(sql.raw(`
|
|
1568
|
-
CREATE INDEX IF NOT EXISTS insight_incidents_tenant_priority_idx
|
|
1569
|
-
ON "meyiconnect"."insight_incidents" ("tenant_id", "priority")
|
|
1559
|
+
await db.execute(sql.raw(`
|
|
1560
|
+
DO $$ BEGIN
|
|
1561
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'insight_incidents_priority_chk') THEN
|
|
1562
|
+
ALTER TABLE "meyiconnect"."insight_incidents" ADD CONSTRAINT insight_incidents_priority_chk
|
|
1563
|
+
CHECK ("priority" IN ('P1','P2','P3','P4'));
|
|
1564
|
+
END IF;
|
|
1565
|
+
END $$
|
|
1566
|
+
`));
|
|
1567
|
+
await db.execute(sql.raw(`
|
|
1568
|
+
CREATE INDEX IF NOT EXISTS insight_incidents_tenant_priority_idx
|
|
1569
|
+
ON "meyiconnect"."insight_incidents" ("tenant_id", "priority")
|
|
1570
1570
|
`));
|
|
1571
1571
|
// ─── insight_incidents: structured cost data (1.1.0) ──────────────────────
|
|
1572
1572
|
// external_ref: stable producer key; savings_monthly: sort key (USD/month); savings_detail: display range.
|
|
@@ -1574,29 +1574,72 @@ export async function runMigrations(db) {
|
|
|
1574
1574
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "savings_monthly" NUMERIC(14,2)`));
|
|
1575
1575
|
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "savings_detail" JSONB`));
|
|
1576
1576
|
// One OPEN incident per (tenant, source, external_ref). A resolved/closed one leaves room for a new incident.
|
|
1577
|
-
await createIndexConcurrently(db, "insight_incidents_tenant_source_ref_uq", `
|
|
1578
|
-
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS insight_incidents_tenant_source_ref_uq
|
|
1579
|
-
ON "meyiconnect"."insight_incidents" ("tenant_id", "source", "external_ref")
|
|
1580
|
-
WHERE "external_ref" IS NOT NULL AND "status" NOT IN ('resolved','closed')
|
|
1577
|
+
await createIndexConcurrently(db, "insight_incidents_tenant_source_ref_uq", `
|
|
1578
|
+
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS insight_incidents_tenant_source_ref_uq
|
|
1579
|
+
ON "meyiconnect"."insight_incidents" ("tenant_id", "source", "external_ref")
|
|
1580
|
+
WHERE "external_ref" IS NOT NULL AND "status" NOT IN ('resolved','closed')
|
|
1581
1581
|
`);
|
|
1582
1582
|
// Cost list order: priority, savings, newest.
|
|
1583
|
-
await createIndexConcurrently(db, "insight_incidents_cost_sort_idx", `
|
|
1584
|
-
CREATE INDEX CONCURRENTLY IF NOT EXISTS insight_incidents_cost_sort_idx
|
|
1585
|
-
ON "meyiconnect"."insight_incidents" ("tenant_id", "priority", "savings_monthly" DESC NULLS LAST, "created_at" DESC)
|
|
1583
|
+
await createIndexConcurrently(db, "insight_incidents_cost_sort_idx", `
|
|
1584
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS insight_incidents_cost_sort_idx
|
|
1585
|
+
ON "meyiconnect"."insight_incidents" ("tenant_id", "priority", "savings_monthly" DESC NULLS LAST, "created_at" DESC)
|
|
1586
1586
|
`);
|
|
1587
|
+
// ─── Incident lifecycle timestamps (1.1.8) ────────────────────────────────
|
|
1588
|
+
// resolved_at was never written before 1.1.8, so "Resolved today", MTTR and the resolve SLA had no data.
|
|
1589
|
+
// Both backfills take the status_change event when there is one, else the proxy the SLA evaluator already used.
|
|
1590
|
+
// They touch only rows still null, so after the first boot they find nothing.
|
|
1591
|
+
await db.execute(sql.raw(`ALTER TABLE "meyiconnect"."insight_incidents" ADD COLUMN IF NOT EXISTS "acked_at" TIMESTAMPTZ`));
|
|
1592
|
+
await db.execute(sql.raw(`
|
|
1593
|
+
UPDATE "meyiconnect"."insight_incidents" i
|
|
1594
|
+
SET resolved_at = COALESCE((
|
|
1595
|
+
SELECT max(e.created_at) FROM "meyiconnect"."incident_events" e
|
|
1596
|
+
WHERE e.incident_id = i.id AND e.type = 'status_change' AND e.after_value IN ('resolved', 'closed')
|
|
1597
|
+
), i.updated_at)
|
|
1598
|
+
WHERE i.resolved_at IS NULL AND i.status IN ('resolved', 'closed')
|
|
1599
|
+
`));
|
|
1600
|
+
await db.execute(sql.raw(`
|
|
1601
|
+
UPDATE "meyiconnect"."insight_incidents" i
|
|
1602
|
+
SET acked_at = COALESCE((
|
|
1603
|
+
SELECT min(e.created_at) FROM "meyiconnect"."incident_events" e
|
|
1604
|
+
WHERE e.incident_id = i.id AND e.type = 'status_change' AND e.before_value = 'open'
|
|
1605
|
+
), i.analysis_triggered_at, i.updated_at)
|
|
1606
|
+
WHERE i.acked_at IS NULL AND i.status <> 'open'
|
|
1607
|
+
`));
|
|
1608
|
+
// ─── CloudTrail deployment status (1.1.8) ─────────────────────────────────
|
|
1609
|
+
// The stack's status callback was rejected (403) before 1.1.8, so sources stayed not_started while sending
|
|
1610
|
+
// alerts. A source that has sent alerts is deployed.
|
|
1611
|
+
await db.execute(sql.raw(`
|
|
1612
|
+
UPDATE "meyiconnect"."insight_cloudtrail_source_setups" s
|
|
1613
|
+
SET aws_deployment_status = 'deployed',
|
|
1614
|
+
aws_deployment_status_reason = 'Alerts received from this source',
|
|
1615
|
+
updated_at = now()
|
|
1616
|
+
WHERE s.aws_deployment_status IN ('not_started', 'pending')
|
|
1617
|
+
AND EXISTS (SELECT 1 FROM "meyiconnect"."insight_alerts" a WHERE a.source_id = s.source_id)
|
|
1618
|
+
`));
|
|
1619
|
+
// ─── Alert account names (1.1.8) ──────────────────────────────────────────
|
|
1620
|
+
// Alerts from AWS payloads were stored with the account id only; take the name from the tenant's connector.
|
|
1621
|
+
await db.execute(sql.raw(`
|
|
1622
|
+
UPDATE "meyiconnect"."insight_alerts" a
|
|
1623
|
+
SET account_name = c.account_name
|
|
1624
|
+
FROM "meyiconnect"."connectors" c
|
|
1625
|
+
WHERE a.account_name IS NULL
|
|
1626
|
+
AND a.account_id IS NOT NULL
|
|
1627
|
+
AND c.account_id = a.account_id
|
|
1628
|
+
AND c.tenant_id::text = a.tenant_id::text
|
|
1629
|
+
`));
|
|
1587
1630
|
// ─── Retention indexes (1.1.0) ────────────────────────────────────────────
|
|
1588
1631
|
// Support the tenant-scoped, time-bounded batched purge. Built CONCURRENTLY so large tables stay writable.
|
|
1589
|
-
await createIndexConcurrently(db, "insight_alerts_tenant_created_idx", `
|
|
1590
|
-
CREATE INDEX CONCURRENTLY IF NOT EXISTS insight_alerts_tenant_created_idx
|
|
1591
|
-
ON "meyiconnect"."insight_alerts" ("tenant_id", "created_at")
|
|
1632
|
+
await createIndexConcurrently(db, "insight_alerts_tenant_created_idx", `
|
|
1633
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS insight_alerts_tenant_created_idx
|
|
1634
|
+
ON "meyiconnect"."insight_alerts" ("tenant_id", "created_at")
|
|
1592
1635
|
`);
|
|
1593
|
-
await createIndexConcurrently(db, "insight_incidents_tenant_status_resolved_idx", `
|
|
1594
|
-
CREATE INDEX CONCURRENTLY IF NOT EXISTS insight_incidents_tenant_status_resolved_idx
|
|
1595
|
-
ON "meyiconnect"."insight_incidents" ("tenant_id", "status", "resolved_at")
|
|
1636
|
+
await createIndexConcurrently(db, "insight_incidents_tenant_status_resolved_idx", `
|
|
1637
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS insight_incidents_tenant_status_resolved_idx
|
|
1638
|
+
ON "meyiconnect"."insight_incidents" ("tenant_id", "status", "resolved_at")
|
|
1596
1639
|
`);
|
|
1597
|
-
await createIndexConcurrently(db, "insight_workflow_executions_tenant_triggered_idx", `
|
|
1598
|
-
CREATE INDEX CONCURRENTLY IF NOT EXISTS insight_workflow_executions_tenant_triggered_idx
|
|
1599
|
-
ON "meyiconnect"."insight_workflow_executions" ("tenant_id", "triggered_at")
|
|
1640
|
+
await createIndexConcurrently(db, "insight_workflow_executions_tenant_triggered_idx", `
|
|
1641
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS insight_workflow_executions_tenant_triggered_idx
|
|
1642
|
+
ON "meyiconnect"."insight_workflow_executions" ("tenant_id", "triggered_at")
|
|
1600
1643
|
`);
|
|
1601
1644
|
}
|
|
1602
1645
|
function rowsOf(res) {
|
|
@@ -1609,12 +1652,12 @@ function rowsOf(res) {
|
|
|
1609
1652
|
* transaction (where CONCURRENTLY is not allowed), it falls back to a plain CREATE INDEX.
|
|
1610
1653
|
*/
|
|
1611
1654
|
async function createIndexConcurrently(db, name, concurrentDdl) {
|
|
1612
|
-
const lookup = await db.execute(sql `
|
|
1613
|
-
SELECT i.indisvalid AS valid
|
|
1614
|
-
FROM pg_class c
|
|
1615
|
-
JOIN pg_index i ON i.indexrelid = c.oid
|
|
1616
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1617
|
-
WHERE n.nspname = 'meyiconnect' AND c.relname = ${name}
|
|
1655
|
+
const lookup = await db.execute(sql `
|
|
1656
|
+
SELECT i.indisvalid AS valid
|
|
1657
|
+
FROM pg_class c
|
|
1658
|
+
JOIN pg_index i ON i.indexrelid = c.oid
|
|
1659
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1660
|
+
WHERE n.nspname = 'meyiconnect' AND c.relname = ${name}
|
|
1618
1661
|
`);
|
|
1619
1662
|
const existing = rowsOf(lookup)[0];
|
|
1620
1663
|
if (existing && (existing.valid === true || existing.valid === "t"))
|