@oneuptime/common 11.1.2 → 11.2.0

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.
Files changed (146) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/Incident.ts +55 -0
  3. package/Models/DatabaseModels/Index.ts +12 -0
  4. package/Models/DatabaseModels/IoTDevice.ts +690 -0
  5. package/Models/DatabaseModels/IoTFleet.ts +862 -0
  6. package/Models/DatabaseModels/IoTFleetLabelRule.ts +514 -0
  7. package/Models/DatabaseModels/IoTFleetOwnerRule.ts +596 -0
  8. package/Models/DatabaseModels/IoTFleetOwnerTeam.ts +485 -0
  9. package/Models/DatabaseModels/IoTFleetOwnerUser.ts +484 -0
  10. package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
  11. package/Server/API/IoTDeviceAPI.ts +128 -0
  12. package/Server/API/TelemetryAPI.ts +104 -35
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1782900000000-AddIoTFleetAndDeviceTables.ts +399 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1783000000000-AddIoTFleetActivityJoinTables.ts +97 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1783010000000-MigrationName.ts +979 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  17. package/Server/Services/IoTDeviceService.ts +356 -0
  18. package/Server/Services/IoTFleetLabelRuleEngineService.ts +198 -0
  19. package/Server/Services/IoTFleetLabelRuleService.ts +14 -0
  20. package/Server/Services/IoTFleetOwnerRuleEngineService.ts +216 -0
  21. package/Server/Services/IoTFleetOwnerRuleService.ts +14 -0
  22. package/Server/Services/IoTFleetOwnerTeamService.ts +10 -0
  23. package/Server/Services/IoTFleetOwnerUserService.ts +10 -0
  24. package/Server/Services/IoTFleetService.ts +376 -0
  25. package/Server/Services/OpenTelemetryIngestService.ts +16 -0
  26. package/Server/Services/StatusPageService.ts +69 -26
  27. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +36 -0
  28. package/Server/Services/TelemetryUsageBillingService.ts +18 -0
  29. package/Server/Services/TraceAggregationService.ts +174 -0
  30. package/Server/Utils/Monitor/MonitorAlert.ts +24 -4
  31. package/Server/Utils/Monitor/MonitorClusterContext.ts +26 -2
  32. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +4 -2
  33. package/Server/Utils/Monitor/MonitorIncident.ts +39 -5
  34. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +14 -0
  35. package/Server/Utils/Monitor/SeriesResourceLabels.ts +22 -0
  36. package/Server/Utils/Telemetry/IoTSnapshotScan.ts +408 -0
  37. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +36 -0
  38. package/Types/Icon/IconProp.ts +1 -0
  39. package/Types/Monitor/IotAlertTemplates.ts +593 -0
  40. package/Types/Monitor/IotMetricCatalog.ts +146 -0
  41. package/Types/Monitor/MonitorStep.ts +31 -0
  42. package/Types/Monitor/MonitorStepIoTMonitor.ts +65 -0
  43. package/Types/Monitor/MonitorType.ts +12 -1
  44. package/Types/Permission.ts +230 -0
  45. package/Types/StatusPage/StatusPageSubscriberNotificationEventType.ts +3 -0
  46. package/Types/Telemetry/ServiceType.ts +1 -0
  47. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
  48. package/UI/Components/Icon/Icon.tsx +18 -2
  49. package/UI/Components/Page/Page.tsx +1 -2
  50. package/build/dist/Models/DatabaseModels/Alert.js +54 -0
  51. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  52. package/build/dist/Models/DatabaseModels/Incident.js +54 -0
  53. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  54. package/build/dist/Models/DatabaseModels/Index.js +12 -0
  55. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  56. package/build/dist/Models/DatabaseModels/IoTDevice.js +722 -0
  57. package/build/dist/Models/DatabaseModels/IoTDevice.js.map +1 -0
  58. package/build/dist/Models/DatabaseModels/IoTFleet.js +882 -0
  59. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -0
  60. package/build/dist/Models/DatabaseModels/IoTFleetLabelRule.js +522 -0
  61. package/build/dist/Models/DatabaseModels/IoTFleetLabelRule.js.map +1 -0
  62. package/build/dist/Models/DatabaseModels/IoTFleetOwnerRule.js +603 -0
  63. package/build/dist/Models/DatabaseModels/IoTFleetOwnerRule.js.map +1 -0
  64. package/build/dist/Models/DatabaseModels/IoTFleetOwnerTeam.js +503 -0
  65. package/build/dist/Models/DatabaseModels/IoTFleetOwnerTeam.js.map +1 -0
  66. package/build/dist/Models/DatabaseModels/IoTFleetOwnerUser.js +502 -0
  67. package/build/dist/Models/DatabaseModels/IoTFleetOwnerUser.js.map +1 -0
  68. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
  69. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  70. package/build/dist/Server/API/IoTDeviceAPI.js +92 -0
  71. package/build/dist/Server/API/IoTDeviceAPI.js.map +1 -0
  72. package/build/dist/Server/API/TelemetryAPI.js +76 -27
  73. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  74. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782900000000-AddIoTFleetAndDeviceTables.js +187 -0
  75. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782900000000-AddIoTFleetAndDeviceTables.js.map +1 -0
  76. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783000000000-AddIoTFleetActivityJoinTables.js +48 -0
  77. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783000000000-AddIoTFleetActivityJoinTables.js.map +1 -0
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783010000000-MigrationName.js +340 -0
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783010000000-MigrationName.js.map +1 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  82. package/build/dist/Server/Services/IoTDeviceService.js +249 -0
  83. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -0
  84. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js +166 -0
  85. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js.map +1 -0
  86. package/build/dist/Server/Services/IoTFleetLabelRuleService.js +13 -0
  87. package/build/dist/Server/Services/IoTFleetLabelRuleService.js.map +1 -0
  88. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js +186 -0
  89. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js.map +1 -0
  90. package/build/dist/Server/Services/IoTFleetOwnerRuleService.js +13 -0
  91. package/build/dist/Server/Services/IoTFleetOwnerRuleService.js.map +1 -0
  92. package/build/dist/Server/Services/IoTFleetOwnerTeamService.js +9 -0
  93. package/build/dist/Server/Services/IoTFleetOwnerTeamService.js.map +1 -0
  94. package/build/dist/Server/Services/IoTFleetOwnerUserService.js +9 -0
  95. package/build/dist/Server/Services/IoTFleetOwnerUserService.js.map +1 -0
  96. package/build/dist/Server/Services/IoTFleetService.js +340 -0
  97. package/build/dist/Server/Services/IoTFleetService.js.map +1 -0
  98. package/build/dist/Server/Services/OpenTelemetryIngestService.js +26 -11
  99. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  100. package/build/dist/Server/Services/StatusPageService.js +57 -23
  101. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  102. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +34 -0
  103. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  104. package/build/dist/Server/Services/TelemetryUsageBillingService.js +13 -0
  105. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  106. package/build/dist/Server/Services/TraceAggregationService.js +136 -0
  107. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  108. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +37 -19
  109. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  110. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +27 -4
  111. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +1 -1
  112. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +4 -2
  113. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  114. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +39 -11
  115. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  116. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +8 -0
  117. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  118. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +17 -0
  119. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  120. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +300 -0
  121. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -0
  122. package/build/dist/Types/Icon/IconProp.js +1 -0
  123. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  124. package/build/dist/Types/Monitor/IotAlertTemplates.js +467 -0
  125. package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -0
  126. package/build/dist/Types/Monitor/IotMetricCatalog.js +108 -0
  127. package/build/dist/Types/Monitor/IotMetricCatalog.js.map +1 -0
  128. package/build/dist/Types/Monitor/MonitorStep.js +23 -0
  129. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  130. package/build/dist/Types/Monitor/MonitorStepIoTMonitor.js +32 -0
  131. package/build/dist/Types/Monitor/MonitorStepIoTMonitor.js.map +1 -0
  132. package/build/dist/Types/Monitor/MonitorType.js +11 -1
  133. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  134. package/build/dist/Types/Permission.js +204 -0
  135. package/build/dist/Types/Permission.js.map +1 -1
  136. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js +2 -0
  137. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js.map +1 -1
  138. package/build/dist/Types/Telemetry/ServiceType.js +1 -0
  139. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  140. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +3 -3
  141. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  142. package/build/dist/UI/Components/Icon/Icon.js +12 -2
  143. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  144. package/build/dist/UI/Components/Page/Page.js +1 -2
  145. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  146. package/package.json +1 -1
@@ -856,51 +856,92 @@ router.post(
856
856
  const sampledKeys: Array<string> = facetKeys.filter(
857
857
  (key: string): boolean => {
858
858
  return (
859
- !ResourceFacetResolver.isResourceFacet(key) && key !== "statusCode"
859
+ !ResourceFacetResolver.isResourceFacet(key) &&
860
+ key !== "statusCode" &&
861
+ // isRootSpan / hasException are counted exactly below, not sampled.
862
+ key !== "isRootSpan" &&
863
+ key !== "hasException"
860
864
  );
861
865
  },
862
866
  );
863
867
 
864
- let facets: Record<string, Array<TraceFacetValue>> = {};
865
- if (sampledKeys.length > 0) {
866
- try {
867
- facets = await TraceAggregationService.getFacetValuesFromSample({
868
- ...multiRequest,
869
- facetKeys: sampledKeys,
870
- });
871
- } catch {
872
- facets = Object.fromEntries(
873
- sampledKeys.map((key: string): [string, Array<TraceFacetValue>] => {
874
- return [key, []];
875
- }),
876
- );
877
- }
878
- }
879
-
880
868
  const needsAccurateCounts: boolean =
881
869
  facetKeys.includes("statusCode") ||
882
870
  facetKeys.some((key: string): boolean => {
883
871
  return ResourceFacetResolver.isResourceFacet(key);
884
872
  });
873
+ const wantsRootSpan: boolean = facetKeys.includes("isRootSpan");
874
+ const wantsHasException: boolean = facetKeys.includes("hasException");
875
+
876
+ const emptyAccurate: {
877
+ serviceCounts: Map<string, number>;
878
+ statusCounts: Map<string, number>;
879
+ } = {
880
+ serviceCounts: new Map<string, number>(),
881
+ statusCounts: new Map<string, number>(),
882
+ };
885
883
 
886
- let serviceCounts: Map<string, number> = new Map<string, number>();
887
- let statusCounts: Map<string, number> = new Map<string, number>();
888
- if (needsAccurateCounts) {
889
- try {
890
- const accurate: {
891
- serviceCounts: Map<string, number>;
892
- statusCounts: Map<string, number>;
893
- } =
894
- await TraceAggregationService.getResourceFacetCounts(multiRequest);
895
- serviceCounts = accurate.serviceCounts;
896
- statusCounts = accurate.statusCounts;
897
- } catch {
898
- /*
899
- * Degrade gracefully: resource facets still enumerate via Postgres
900
- * (count 0), statusCode falls back to empty.
901
- */
902
- }
903
- }
884
+ /*
885
+ * Run the independent count queries concurrently. They share no state and
886
+ * were previously awaited one after another, so their latencies added up.
887
+ * getHasExceptionCounts in particular is a base-table GROUP BY
888
+ * (hasException is not a proj_hist_by_minute key), so overlapping it with
889
+ * the projection-backed sample / resource / root-span queries keeps it
890
+ * off the critical path. Each query keeps its own degrade-to-empty catch.
891
+ */
892
+ const [sampledFacets, accurate, rootSpanCounts, exceptionCounts]: [
893
+ Record<string, Array<TraceFacetValue>>,
894
+ {
895
+ serviceCounts: Map<string, number>;
896
+ statusCounts: Map<string, number>;
897
+ },
898
+ { rootCount: number; nonRootCount: number } | null,
899
+ { withExceptionCount: number; withoutExceptionCount: number } | null,
900
+ ] = await Promise.all([
901
+ sampledKeys.length > 0
902
+ ? TraceAggregationService.getFacetValuesFromSample({
903
+ ...multiRequest,
904
+ facetKeys: sampledKeys,
905
+ }).catch((): Record<string, Array<TraceFacetValue>> => {
906
+ return Object.fromEntries(
907
+ sampledKeys.map(
908
+ (key: string): [string, Array<TraceFacetValue>] => {
909
+ return [key, []];
910
+ },
911
+ ),
912
+ );
913
+ })
914
+ : Promise.resolve({} as Record<string, Array<TraceFacetValue>>),
915
+ needsAccurateCounts
916
+ ? TraceAggregationService.getResourceFacetCounts(multiRequest).catch(
917
+ () => {
918
+ /*
919
+ * Degrade gracefully: resource facets still enumerate via
920
+ * Postgres (count 0), statusCode falls back to empty.
921
+ */
922
+ return emptyAccurate;
923
+ },
924
+ )
925
+ : Promise.resolve(emptyAccurate),
926
+ wantsRootSpan
927
+ ? TraceAggregationService.getRootSpanCounts(multiRequest).catch(
928
+ () => {
929
+ return null;
930
+ },
931
+ )
932
+ : Promise.resolve(null),
933
+ wantsHasException
934
+ ? TraceAggregationService.getHasExceptionCounts(multiRequest).catch(
935
+ () => {
936
+ return null;
937
+ },
938
+ )
939
+ : Promise.resolve(null),
940
+ ]);
941
+
942
+ const facets: Record<string, Array<TraceFacetValue>> = sampledFacets;
943
+ const serviceCounts: Map<string, number> = accurate.serviceCounts;
944
+ const statusCounts: Map<string, number> = accurate.statusCounts;
904
945
 
905
946
  if (facetKeys.includes("statusCode")) {
906
947
  facets["statusCode"] = Array.from(statusCounts.entries())
@@ -913,6 +954,34 @@ router.post(
913
954
  .slice(0, limit);
914
955
  }
915
956
 
957
+ /*
958
+ * Span-type facet: exact root vs non-root counts off the projection,
959
+ * computed ignoring rootOnly so both buckets survive (see
960
+ * getRootSpanCounts). Backs the "Span Type" sidebar choice.
961
+ */
962
+ if (wantsRootSpan) {
963
+ facets["isRootSpan"] = rootSpanCounts
964
+ ? [
965
+ { value: "true", count: rootSpanCounts.rootCount },
966
+ { value: "false", count: rootSpanCounts.nonRootCount },
967
+ ]
968
+ : [];
969
+ }
970
+
971
+ /*
972
+ * Has-exception facet: exact counts (exception spans are rare and the
973
+ * recent-N sample would under-report or miss them — see
974
+ * getHasExceptionCounts).
975
+ */
976
+ if (wantsHasException) {
977
+ facets["hasException"] = exceptionCounts
978
+ ? [
979
+ { value: "true", count: exceptionCounts.withExceptionCount },
980
+ { value: "false", count: exceptionCounts.withoutExceptionCount },
981
+ ]
982
+ : [];
983
+ }
984
+
916
985
  /*
917
986
  * Replace resource-facet results with the Postgres source-of-truth list
918
987
  * (filtered by facetSearchText and enriched with displayName). Every
@@ -0,0 +1,399 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ /*
4
+ * IoT monitoring product (cloned from the Proxmox infra product):
5
+ * one parent fleet table plus a child device-inventory table. A row per
6
+ * monitored IoTFleet, auto-discovered at OTel ingest from the
7
+ * `iot.fleet.name` resource attribute (the `name` column is the join key —
8
+ * like ProxmoxCluster there is no separate identifier column) or manually
9
+ * registered. IoTDevice rows are written by the metrics-ingest snapshot scan
10
+ * under isRoot and are read-only to users.
11
+ *
12
+ * Unlike the historical two-migration Proxmox split (table create + V2
13
+ * hardening), everything ships in this one migration: the IoTFleet table
14
+ * carries the DB-level UNIQUE(projectId, name) race-defense index from the
15
+ * start, alongside the owner/label rule engine tables, the owner join
16
+ * models, and the IoTDevice inventory table with its unique identity index.
17
+ *
18
+ * Columns/indexes/FKs are derived from the model decorators in
19
+ * Common/Models/DatabaseModels/{IoTFleet,IoTDevice,IoTFleetOwnerTeam,
20
+ * IoTFleetOwnerUser,IoTFleetOwnerRule,IoTFleetLabelRule}.ts.
21
+ *
22
+ * Abbreviations to stay under Postgres's 63-char identifier limit:
23
+ * ifor = IoTFleetOwnerRule, iflr = IoTFleetLabelRule.
24
+ */
25
+ export class AddIoTFleetAndDeviceTables1782900000000
26
+ implements MigrationInterface
27
+ {
28
+ public name = "AddIoTFleetAndDeviceTables1782900000000";
29
+
30
+ public async up(queryRunner: QueryRunner): Promise<void> {
31
+ // IoT fleet table.
32
+ await queryRunner.query(
33
+ `CREATE TABLE "IoTFleet" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "otelCollectorStatus" character varying(100) DEFAULT 'disconnected', "agentVersion" character varying(100), "lastSeenAt" TIMESTAMP WITH TIME ZONE, "deviceCount" integer DEFAULT '0', "onlineDeviceCount" integer DEFAULT '0', "isArchived" boolean NOT NULL DEFAULT false, "archivedAt" TIMESTAMP WITH TIME ZONE, "archivedByUserId" uuid, "retainTelemetryDataForDays" integer, "telemetryRetentionConfig" jsonb, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_iot_fleet_id" PRIMARY KEY ("_id"))`,
34
+ );
35
+ await queryRunner.query(
36
+ `CREATE INDEX "IDX_iot_fleet_projectId" ON "IoTFleet" ("projectId")`,
37
+ );
38
+ await queryRunner.query(
39
+ `CREATE INDEX "IDX_iot_fleet_name" ON "IoTFleet" ("name")`,
40
+ );
41
+ await queryRunner.query(
42
+ `CREATE UNIQUE INDEX "IDX_iot_fleet_slug" ON "IoTFleet" ("slug")`,
43
+ );
44
+ await queryRunner.query(
45
+ `CREATE INDEX "IDX_iot_fleet_isArchived" ON "IoTFleet" ("projectId", "isArchived")`,
46
+ );
47
+ await queryRunner.query(
48
+ `CREATE UNIQUE INDEX "IDX_iot_fleet_projectId_name" ON "IoTFleet" ("projectId", "name")`,
49
+ );
50
+ await queryRunner.query(
51
+ `ALTER TABLE "IoTFleet" ADD CONSTRAINT "FK_iot_fleet_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
52
+ );
53
+ await queryRunner.query(
54
+ `ALTER TABLE "IoTFleet" ADD CONSTRAINT "FK_iot_fleet_archivedByUserId" FOREIGN KEY ("archivedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
55
+ );
56
+ await queryRunner.query(
57
+ `ALTER TABLE "IoTFleet" ADD CONSTRAINT "FK_iot_fleet_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
58
+ );
59
+ await queryRunner.query(
60
+ `ALTER TABLE "IoTFleet" ADD CONSTRAINT "FK_iot_fleet_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
61
+ );
62
+
63
+ // IoT fleet label join table.
64
+ await queryRunner.query(
65
+ `CREATE TABLE "IoTFleetLabel" ("iotFleetId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_iot_fleet_label" PRIMARY KEY ("iotFleetId", "labelId"))`,
66
+ );
67
+ await queryRunner.query(
68
+ `CREATE INDEX "IDX_iot_fleet_label_iotFleetId" ON "IoTFleetLabel" ("iotFleetId")`,
69
+ );
70
+ await queryRunner.query(
71
+ `CREATE INDEX "IDX_iot_fleet_label_labelId" ON "IoTFleetLabel" ("labelId")`,
72
+ );
73
+ await queryRunner.query(
74
+ `ALTER TABLE "IoTFleetLabel" ADD CONSTRAINT "FK_iot_fleet_label_iotFleetId" FOREIGN KEY ("iotFleetId") REFERENCES "IoTFleet"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
75
+ );
76
+ await queryRunner.query(
77
+ `ALTER TABLE "IoTFleetLabel" ADD CONSTRAINT "FK_iot_fleet_label_labelId" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
78
+ );
79
+
80
+ /*
81
+ * ------------------------------------------------------------------
82
+ * Owner/label rule engine tables.
83
+ * ------------------------------------------------------------------
84
+ */
85
+
86
+ // IoTFleetOwnerRule
87
+ await queryRunner.query(
88
+ `CREATE TABLE "IoTFleetOwnerRule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "isEnabled" boolean NOT NULL DEFAULT true, "notifyOwners" boolean NOT NULL DEFAULT true, "iotFleetNamePattern" character varying(500), "iotFleetDescriptionPattern" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_iot_fleet_owner_rule" PRIMARY KEY ("_id"))`,
89
+ );
90
+ await queryRunner.query(
91
+ `CREATE INDEX "IDX_iot_fleet_owner_rule_projectId" ON "IoTFleetOwnerRule" ("projectId")`,
92
+ );
93
+ await queryRunner.query(
94
+ `CREATE INDEX "IDX_iot_fleet_owner_rule_name" ON "IoTFleetOwnerRule" ("name")`,
95
+ );
96
+ await queryRunner.query(
97
+ `CREATE INDEX "IDX_iot_fleet_owner_rule_isEnabled" ON "IoTFleetOwnerRule" ("isEnabled")`,
98
+ );
99
+ await queryRunner.query(
100
+ `ALTER TABLE "IoTFleetOwnerRule" ADD CONSTRAINT "FK_iot_fleet_owner_rule_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
101
+ );
102
+ await queryRunner.query(
103
+ `ALTER TABLE "IoTFleetOwnerRule" ADD CONSTRAINT "FK_iot_fleet_owner_rule_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
104
+ );
105
+ await queryRunner.query(
106
+ `ALTER TABLE "IoTFleetOwnerRule" ADD CONSTRAINT "FK_iot_fleet_owner_rule_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
107
+ );
108
+
109
+ // IoTFleetLabelRule
110
+ await queryRunner.query(
111
+ `CREATE TABLE "IoTFleetLabelRule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "isEnabled" boolean NOT NULL DEFAULT true, "iotFleetNamePattern" character varying(500), "iotFleetDescriptionPattern" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_iot_fleet_label_rule" PRIMARY KEY ("_id"))`,
112
+ );
113
+ await queryRunner.query(
114
+ `CREATE INDEX "IDX_iot_fleet_label_rule_projectId" ON "IoTFleetLabelRule" ("projectId")`,
115
+ );
116
+ await queryRunner.query(
117
+ `CREATE INDEX "IDX_iot_fleet_label_rule_name" ON "IoTFleetLabelRule" ("name")`,
118
+ );
119
+ await queryRunner.query(
120
+ `CREATE INDEX "IDX_iot_fleet_label_rule_isEnabled" ON "IoTFleetLabelRule" ("isEnabled")`,
121
+ );
122
+ await queryRunner.query(
123
+ `ALTER TABLE "IoTFleetLabelRule" ADD CONSTRAINT "FK_iot_fleet_label_rule_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
124
+ );
125
+ await queryRunner.query(
126
+ `ALTER TABLE "IoTFleetLabelRule" ADD CONSTRAINT "FK_iot_fleet_label_rule_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
127
+ );
128
+ await queryRunner.query(
129
+ `ALTER TABLE "IoTFleetLabelRule" ADD CONSTRAINT "FK_iot_fleet_label_rule_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
130
+ );
131
+
132
+ /*
133
+ * ------------------------------------------------------------------
134
+ * Rule matcher/action join tables.
135
+ * Abbreviations: ifor = IoTFleetOwnerRule, iflr = IoTFleetLabelRule.
136
+ * ------------------------------------------------------------------
137
+ */
138
+
139
+ // IoTFleetOwnerRuleIoTFleetLabel (matcher labels)
140
+ await queryRunner.query(
141
+ `CREATE TABLE "IoTFleetOwnerRuleIoTFleetLabel" ("iotFleetOwnerRuleId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_ifor_fleet_label" PRIMARY KEY ("iotFleetOwnerRuleId", "labelId"))`,
142
+ );
143
+ await queryRunner.query(
144
+ `CREATE INDEX "IDX_ifor_fleet_label_ruleId" ON "IoTFleetOwnerRuleIoTFleetLabel" ("iotFleetOwnerRuleId")`,
145
+ );
146
+ await queryRunner.query(
147
+ `CREATE INDEX "IDX_ifor_fleet_label_labelId" ON "IoTFleetOwnerRuleIoTFleetLabel" ("labelId")`,
148
+ );
149
+ await queryRunner.query(
150
+ `ALTER TABLE "IoTFleetOwnerRuleIoTFleetLabel" ADD CONSTRAINT "FK_ifor_fleet_label_ruleId" FOREIGN KEY ("iotFleetOwnerRuleId") REFERENCES "IoTFleetOwnerRule"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
151
+ );
152
+ await queryRunner.query(
153
+ `ALTER TABLE "IoTFleetOwnerRuleIoTFleetLabel" ADD CONSTRAINT "FK_ifor_fleet_label_labelId" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
154
+ );
155
+
156
+ // IoTFleetOwnerRuleOwnerUser
157
+ await queryRunner.query(
158
+ `CREATE TABLE "IoTFleetOwnerRuleOwnerUser" ("iotFleetOwnerRuleId" uuid NOT NULL, "userId" uuid NOT NULL, CONSTRAINT "PK_ifor_owner_user" PRIMARY KEY ("iotFleetOwnerRuleId", "userId"))`,
159
+ );
160
+ await queryRunner.query(
161
+ `CREATE INDEX "IDX_ifor_owner_user_ruleId" ON "IoTFleetOwnerRuleOwnerUser" ("iotFleetOwnerRuleId")`,
162
+ );
163
+ await queryRunner.query(
164
+ `CREATE INDEX "IDX_ifor_owner_user_userId" ON "IoTFleetOwnerRuleOwnerUser" ("userId")`,
165
+ );
166
+ await queryRunner.query(
167
+ `ALTER TABLE "IoTFleetOwnerRuleOwnerUser" ADD CONSTRAINT "FK_ifor_owner_user_ruleId" FOREIGN KEY ("iotFleetOwnerRuleId") REFERENCES "IoTFleetOwnerRule"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
168
+ );
169
+ await queryRunner.query(
170
+ `ALTER TABLE "IoTFleetOwnerRuleOwnerUser" ADD CONSTRAINT "FK_ifor_owner_user_userId" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
171
+ );
172
+
173
+ // IoTFleetOwnerRuleOwnerTeam
174
+ await queryRunner.query(
175
+ `CREATE TABLE "IoTFleetOwnerRuleOwnerTeam" ("iotFleetOwnerRuleId" uuid NOT NULL, "teamId" uuid NOT NULL, CONSTRAINT "PK_ifor_owner_team" PRIMARY KEY ("iotFleetOwnerRuleId", "teamId"))`,
176
+ );
177
+ await queryRunner.query(
178
+ `CREATE INDEX "IDX_ifor_owner_team_ruleId" ON "IoTFleetOwnerRuleOwnerTeam" ("iotFleetOwnerRuleId")`,
179
+ );
180
+ await queryRunner.query(
181
+ `CREATE INDEX "IDX_ifor_owner_team_teamId" ON "IoTFleetOwnerRuleOwnerTeam" ("teamId")`,
182
+ );
183
+ await queryRunner.query(
184
+ `ALTER TABLE "IoTFleetOwnerRuleOwnerTeam" ADD CONSTRAINT "FK_ifor_owner_team_ruleId" FOREIGN KEY ("iotFleetOwnerRuleId") REFERENCES "IoTFleetOwnerRule"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
185
+ );
186
+ await queryRunner.query(
187
+ `ALTER TABLE "IoTFleetOwnerRuleOwnerTeam" ADD CONSTRAINT "FK_ifor_owner_team_teamId" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
188
+ );
189
+
190
+ // IoTFleetLabelRuleIoTFleetLabel (matcher labels)
191
+ await queryRunner.query(
192
+ `CREATE TABLE "IoTFleetLabelRuleIoTFleetLabel" ("iotFleetLabelRuleId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_iflr_fleet_label" PRIMARY KEY ("iotFleetLabelRuleId", "labelId"))`,
193
+ );
194
+ await queryRunner.query(
195
+ `CREATE INDEX "IDX_iflr_fleet_label_ruleId" ON "IoTFleetLabelRuleIoTFleetLabel" ("iotFleetLabelRuleId")`,
196
+ );
197
+ await queryRunner.query(
198
+ `CREATE INDEX "IDX_iflr_fleet_label_labelId" ON "IoTFleetLabelRuleIoTFleetLabel" ("labelId")`,
199
+ );
200
+ await queryRunner.query(
201
+ `ALTER TABLE "IoTFleetLabelRuleIoTFleetLabel" ADD CONSTRAINT "FK_iflr_fleet_label_ruleId" FOREIGN KEY ("iotFleetLabelRuleId") REFERENCES "IoTFleetLabelRule"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
202
+ );
203
+ await queryRunner.query(
204
+ `ALTER TABLE "IoTFleetLabelRuleIoTFleetLabel" ADD CONSTRAINT "FK_iflr_fleet_label_labelId" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
205
+ );
206
+
207
+ // IoTFleetLabelRuleLabelToAdd (action labels)
208
+ await queryRunner.query(
209
+ `CREATE TABLE "IoTFleetLabelRuleLabelToAdd" ("iotFleetLabelRuleId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_iflr_label_to_add" PRIMARY KEY ("iotFleetLabelRuleId", "labelId"))`,
210
+ );
211
+ await queryRunner.query(
212
+ `CREATE INDEX "IDX_iflr_label_to_add_ruleId" ON "IoTFleetLabelRuleLabelToAdd" ("iotFleetLabelRuleId")`,
213
+ );
214
+ await queryRunner.query(
215
+ `CREATE INDEX "IDX_iflr_label_to_add_labelId" ON "IoTFleetLabelRuleLabelToAdd" ("labelId")`,
216
+ );
217
+ await queryRunner.query(
218
+ `ALTER TABLE "IoTFleetLabelRuleLabelToAdd" ADD CONSTRAINT "FK_iflr_label_to_add_ruleId" FOREIGN KEY ("iotFleetLabelRuleId") REFERENCES "IoTFleetLabelRule"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
219
+ );
220
+ await queryRunner.query(
221
+ `ALTER TABLE "IoTFleetLabelRuleLabelToAdd" ADD CONSTRAINT "FK_iflr_label_to_add_labelId" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
222
+ );
223
+
224
+ /*
225
+ * ------------------------------------------------------------------
226
+ * Owner join models (the prerequisite for the Owners page and
227
+ * useResourceOwners facets).
228
+ * ------------------------------------------------------------------
229
+ */
230
+
231
+ // IoTFleetOwnerUser
232
+ await queryRunner.query(
233
+ `CREATE TABLE "IoTFleetOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "iotFleetId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_iot_fleet_owner_user" PRIMARY KEY ("_id"))`,
234
+ );
235
+ await queryRunner.query(
236
+ `CREATE INDEX "IDX_iot_fleet_owner_user_projectId" ON "IoTFleetOwnerUser" ("projectId")`,
237
+ );
238
+ await queryRunner.query(
239
+ `CREATE INDEX "IDX_iot_fleet_owner_user_userId" ON "IoTFleetOwnerUser" ("userId")`,
240
+ );
241
+ await queryRunner.query(
242
+ `CREATE INDEX "IDX_iot_fleet_owner_user_iotFleetId" ON "IoTFleetOwnerUser" ("iotFleetId")`,
243
+ );
244
+ await queryRunner.query(
245
+ `CREATE INDEX "IDX_iot_fleet_owner_user_isOwnerNotified" ON "IoTFleetOwnerUser" ("isOwnerNotified")`,
246
+ );
247
+ await queryRunner.query(
248
+ `ALTER TABLE "IoTFleetOwnerUser" ADD CONSTRAINT "FK_iot_fleet_owner_user_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
249
+ );
250
+ await queryRunner.query(
251
+ `ALTER TABLE "IoTFleetOwnerUser" ADD CONSTRAINT "FK_iot_fleet_owner_user_userId" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
252
+ );
253
+ await queryRunner.query(
254
+ `ALTER TABLE "IoTFleetOwnerUser" ADD CONSTRAINT "FK_iot_fleet_owner_user_iotFleetId" FOREIGN KEY ("iotFleetId") REFERENCES "IoTFleet"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
255
+ );
256
+ await queryRunner.query(
257
+ `ALTER TABLE "IoTFleetOwnerUser" ADD CONSTRAINT "FK_iot_fleet_owner_user_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
258
+ );
259
+ await queryRunner.query(
260
+ `ALTER TABLE "IoTFleetOwnerUser" ADD CONSTRAINT "FK_iot_fleet_owner_user_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
261
+ );
262
+
263
+ // IoTFleetOwnerTeam
264
+ await queryRunner.query(
265
+ `CREATE TABLE "IoTFleetOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "iotFleetId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_iot_fleet_owner_team" PRIMARY KEY ("_id"))`,
266
+ );
267
+ await queryRunner.query(
268
+ `CREATE INDEX "IDX_iot_fleet_owner_team_projectId" ON "IoTFleetOwnerTeam" ("projectId")`,
269
+ );
270
+ await queryRunner.query(
271
+ `CREATE INDEX "IDX_iot_fleet_owner_team_teamId" ON "IoTFleetOwnerTeam" ("teamId")`,
272
+ );
273
+ await queryRunner.query(
274
+ `CREATE INDEX "IDX_iot_fleet_owner_team_iotFleetId" ON "IoTFleetOwnerTeam" ("iotFleetId")`,
275
+ );
276
+ await queryRunner.query(
277
+ `CREATE INDEX "IDX_iot_fleet_owner_team_isOwnerNotified" ON "IoTFleetOwnerTeam" ("isOwnerNotified")`,
278
+ );
279
+ await queryRunner.query(
280
+ `ALTER TABLE "IoTFleetOwnerTeam" ADD CONSTRAINT "FK_iot_fleet_owner_team_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
281
+ );
282
+ await queryRunner.query(
283
+ `ALTER TABLE "IoTFleetOwnerTeam" ADD CONSTRAINT "FK_iot_fleet_owner_team_teamId" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
284
+ );
285
+ await queryRunner.query(
286
+ `ALTER TABLE "IoTFleetOwnerTeam" ADD CONSTRAINT "FK_iot_fleet_owner_team_iotFleetId" FOREIGN KEY ("iotFleetId") REFERENCES "IoTFleet"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
287
+ );
288
+ await queryRunner.query(
289
+ `ALTER TABLE "IoTFleetOwnerTeam" ADD CONSTRAINT "FK_iot_fleet_owner_team_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
290
+ );
291
+ await queryRunner.query(
292
+ `ALTER TABLE "IoTFleetOwnerTeam" ADD CONSTRAINT "FK_iot_fleet_owner_team_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
293
+ );
294
+
295
+ /*
296
+ * ------------------------------------------------------------------
297
+ * IoT device inventory table (read-only to users, written by the
298
+ * metrics-ingest snapshot scan under isRoot). externalId is the
299
+ * `device.id` datapoint label; kind is the device class.
300
+ * ------------------------------------------------------------------
301
+ */
302
+ await queryRunner.query(
303
+ `CREATE TABLE "IoTDevice" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "iotFleetId" uuid NOT NULL, "kind" character varying(100) NOT NULL, "externalId" character varying(100) NOT NULL, "name" character varying(100), "deviceType" character varying(100), "firmwareVersion" character varying(100), "isUp" boolean, "uptimeSeconds" integer, "latestCpuPercent" numeric, "latestMemoryBytes" bigint, "maxMemoryBytes" bigint, "latestMemoryPercent" numeric, "latestBatteryPercent" numeric, "latestSignalStrengthDbm" numeric, "latestTemperatureCelsius" numeric, "metricsUpdatedAt" TIMESTAMP WITH TIME ZONE, "lastSeenAt" TIMESTAMP WITH TIME ZONE NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_iot_device_id" PRIMARY KEY ("_id"))`,
304
+ );
305
+ await queryRunner.query(
306
+ `CREATE INDEX "IDX_iot_device_projectId" ON "IoTDevice" ("projectId")`,
307
+ );
308
+ await queryRunner.query(
309
+ `CREATE INDEX "IDX_iot_device_iotFleetId" ON "IoTDevice" ("iotFleetId")`,
310
+ );
311
+ await queryRunner.query(
312
+ `CREATE UNIQUE INDEX "IDX_iot_device_identity" ON "IoTDevice" ("projectId", "iotFleetId", "kind", "externalId")`,
313
+ );
314
+ await queryRunner.query(
315
+ `ALTER TABLE "IoTDevice" ADD CONSTRAINT "FK_iot_device_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
316
+ );
317
+ await queryRunner.query(
318
+ `ALTER TABLE "IoTDevice" ADD CONSTRAINT "FK_iot_device_iotFleetId" FOREIGN KEY ("iotFleetId") REFERENCES "IoTFleet"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
319
+ );
320
+ await queryRunner.query(
321
+ `ALTER TABLE "IoTDevice" ADD CONSTRAINT "FK_iot_device_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
322
+ );
323
+ await queryRunner.query(
324
+ `ALTER TABLE "IoTDevice" ADD CONSTRAINT "FK_iot_device_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
325
+ );
326
+ }
327
+
328
+ public async down(queryRunner: QueryRunner): Promise<void> {
329
+ // IoT device inventory table.
330
+ await queryRunner.query(
331
+ `ALTER TABLE "IoTDevice" DROP CONSTRAINT "FK_iot_device_deletedByUserId"`,
332
+ );
333
+ await queryRunner.query(
334
+ `ALTER TABLE "IoTDevice" DROP CONSTRAINT "FK_iot_device_createdByUserId"`,
335
+ );
336
+ await queryRunner.query(
337
+ `ALTER TABLE "IoTDevice" DROP CONSTRAINT "FK_iot_device_iotFleetId"`,
338
+ );
339
+ await queryRunner.query(
340
+ `ALTER TABLE "IoTDevice" DROP CONSTRAINT "FK_iot_device_projectId"`,
341
+ );
342
+ await queryRunner.query(`DROP INDEX "public"."IDX_iot_device_identity"`);
343
+ await queryRunner.query(`DROP INDEX "public"."IDX_iot_device_iotFleetId"`);
344
+ await queryRunner.query(`DROP INDEX "public"."IDX_iot_device_projectId"`);
345
+ await queryRunner.query(`DROP TABLE "IoTDevice"`);
346
+
347
+ // Owner join models.
348
+ await queryRunner.query(`DROP TABLE "IoTFleetOwnerTeam"`);
349
+ await queryRunner.query(`DROP TABLE "IoTFleetOwnerUser"`);
350
+
351
+ // Rule matcher/action join tables.
352
+ await queryRunner.query(`DROP TABLE "IoTFleetLabelRuleLabelToAdd"`);
353
+ await queryRunner.query(`DROP TABLE "IoTFleetLabelRuleIoTFleetLabel"`);
354
+ await queryRunner.query(`DROP TABLE "IoTFleetOwnerRuleOwnerTeam"`);
355
+ await queryRunner.query(`DROP TABLE "IoTFleetOwnerRuleOwnerUser"`);
356
+ await queryRunner.query(`DROP TABLE "IoTFleetOwnerRuleIoTFleetLabel"`);
357
+
358
+ // Rule tables.
359
+ await queryRunner.query(`DROP TABLE "IoTFleetLabelRule"`);
360
+ await queryRunner.query(`DROP TABLE "IoTFleetOwnerRule"`);
361
+
362
+ // IoT fleet label join table.
363
+ await queryRunner.query(
364
+ `ALTER TABLE "IoTFleetLabel" DROP CONSTRAINT "FK_iot_fleet_label_labelId"`,
365
+ );
366
+ await queryRunner.query(
367
+ `ALTER TABLE "IoTFleetLabel" DROP CONSTRAINT "FK_iot_fleet_label_iotFleetId"`,
368
+ );
369
+ await queryRunner.query(
370
+ `DROP INDEX "public"."IDX_iot_fleet_label_labelId"`,
371
+ );
372
+ await queryRunner.query(
373
+ `DROP INDEX "public"."IDX_iot_fleet_label_iotFleetId"`,
374
+ );
375
+ await queryRunner.query(`DROP TABLE "IoTFleetLabel"`);
376
+
377
+ // IoT fleet table.
378
+ await queryRunner.query(
379
+ `ALTER TABLE "IoTFleet" DROP CONSTRAINT "FK_iot_fleet_deletedByUserId"`,
380
+ );
381
+ await queryRunner.query(
382
+ `ALTER TABLE "IoTFleet" DROP CONSTRAINT "FK_iot_fleet_createdByUserId"`,
383
+ );
384
+ await queryRunner.query(
385
+ `ALTER TABLE "IoTFleet" DROP CONSTRAINT "FK_iot_fleet_archivedByUserId"`,
386
+ );
387
+ await queryRunner.query(
388
+ `ALTER TABLE "IoTFleet" DROP CONSTRAINT "FK_iot_fleet_projectId"`,
389
+ );
390
+ await queryRunner.query(
391
+ `DROP INDEX "public"."IDX_iot_fleet_projectId_name"`,
392
+ );
393
+ await queryRunner.query(`DROP INDEX "public"."IDX_iot_fleet_isArchived"`);
394
+ await queryRunner.query(`DROP INDEX "public"."IDX_iot_fleet_slug"`);
395
+ await queryRunner.query(`DROP INDEX "public"."IDX_iot_fleet_name"`);
396
+ await queryRunner.query(`DROP INDEX "public"."IDX_iot_fleet_projectId"`);
397
+ await queryRunner.query(`DROP TABLE "IoTFleet"`);
398
+ }
399
+ }