@oneuptime/common 9.5.6 → 9.5.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.
Files changed (105) hide show
  1. package/Models/DatabaseModels/AlertEpisode.ts +25 -0
  2. package/Models/DatabaseModels/AlertGroupingRule.ts +2 -2
  3. package/Models/DatabaseModels/IncidentEpisode.ts +25 -0
  4. package/Models/DatabaseModels/IncidentGroupingRule.ts +2 -2
  5. package/Models/DatabaseModels/Index.ts +2 -0
  6. package/Models/DatabaseModels/OpenSourceDeployment.ts +140 -0
  7. package/Models/DatabaseModels/UserPush.ts +2 -1
  8. package/Server/API/OpenSourceDeploymentAPI.ts +73 -0
  9. package/Server/API/UserPushAPI.ts +51 -4
  10. package/Server/EnvironmentConfig.ts +3 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1770237245070-MigrationName.ts +7 -7
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1770668054908-MigrationName.ts +26 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1770728946893-MigrationName.ts +47 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1770732721195-MigrationName.ts +27 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  16. package/Server/Middleware/UserAuthorization.ts +14 -9
  17. package/Server/Services/AlertEpisodeMemberService.ts +10 -20
  18. package/Server/Services/AlertEpisodeService.ts +3 -2
  19. package/Server/Services/AlertGroupingEngineService.ts +1 -0
  20. package/Server/Services/IncidentEpisodeMemberService.ts +12 -20
  21. package/Server/Services/IncidentEpisodeService.ts +65 -3
  22. package/Server/Services/IncidentGroupingEngineService.ts +1 -0
  23. package/Server/Services/Index.ts +2 -0
  24. package/Server/Services/OpenSourceDeploymentService.ts +10 -0
  25. package/Server/Services/PushNotificationService.ts +129 -27
  26. package/Server/Services/UserNotificationRuleService.ts +13 -3
  27. package/Server/Services/UserPushService.ts +2 -1
  28. package/Server/Utils/PushNotificationUtil.ts +56 -0
  29. package/Types/PushNotification/PushDeviceType.ts +7 -0
  30. package/Types/PushNotification/PushNotificationRequest.ts +3 -1
  31. package/UI/Components/Forms/BasicForm.tsx +8 -1
  32. package/UI/Components/Forms/Types/Field.ts +3 -0
  33. package/UI/Components/ModelDelete/ModelDelete.tsx +4 -1
  34. package/UI/Components/ModelDetail/CardModelDetail.tsx +4 -0
  35. package/UI/Components/ModelDetail/ModelDetail.tsx +4 -1
  36. package/UI/Components/Page/ModelPage.tsx +4 -1
  37. package/build/dist/Models/DatabaseModels/AlertEpisode.js +26 -0
  38. package/build/dist/Models/DatabaseModels/AlertEpisode.js.map +1 -1
  39. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js +2 -2
  40. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js.map +1 -1
  41. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +26 -0
  42. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  43. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js +2 -2
  44. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js.map +1 -1
  45. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  46. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  47. package/build/dist/Models/DatabaseModels/OpenSourceDeployment.js +167 -0
  48. package/build/dist/Models/DatabaseModels/OpenSourceDeployment.js.map +1 -0
  49. package/build/dist/Models/DatabaseModels/UserPush.js +2 -1
  50. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  51. package/build/dist/Server/API/OpenSourceDeploymentAPI.js +55 -0
  52. package/build/dist/Server/API/OpenSourceDeploymentAPI.js.map +1 -0
  53. package/build/dist/Server/API/UserPushAPI.js +34 -3
  54. package/build/dist/Server/API/UserPushAPI.js.map +1 -1
  55. package/build/dist/Server/EnvironmentConfig.js +1 -0
  56. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  57. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770237245070-MigrationName.js +7 -7
  58. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770237245070-MigrationName.js.map +1 -1
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770668054908-MigrationName.js +36 -0
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770668054908-MigrationName.js.map +1 -0
  61. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770728946893-MigrationName.js +22 -0
  62. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770728946893-MigrationName.js.map +1 -0
  63. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770732721195-MigrationName.js +16 -0
  64. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770732721195-MigrationName.js.map +1 -0
  65. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  66. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  67. package/build/dist/Server/Middleware/UserAuthorization.js +10 -4
  68. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  69. package/build/dist/Server/Services/AlertEpisodeMemberService.js +7 -13
  70. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  71. package/build/dist/Server/Services/AlertEpisodeService.js +3 -2
  72. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  73. package/build/dist/Server/Services/AlertGroupingEngineService.js +1 -0
  74. package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -1
  75. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +7 -13
  76. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  77. package/build/dist/Server/Services/IncidentEpisodeService.js +43 -3
  78. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  79. package/build/dist/Server/Services/IncidentGroupingEngineService.js +1 -0
  80. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  81. package/build/dist/Server/Services/Index.js +2 -0
  82. package/build/dist/Server/Services/Index.js.map +1 -1
  83. package/build/dist/Server/Services/OpenSourceDeploymentService.js +9 -0
  84. package/build/dist/Server/Services/OpenSourceDeploymentService.js.map +1 -0
  85. package/build/dist/Server/Services/PushNotificationService.js +77 -21
  86. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  87. package/build/dist/Server/Services/UserNotificationRuleService.js +12 -9
  88. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  89. package/build/dist/Server/Services/UserPushService.js +2 -1
  90. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  91. package/build/dist/Server/Utils/PushNotificationUtil.js +32 -8
  92. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  93. package/build/dist/Types/PushNotification/PushDeviceType.js +8 -0
  94. package/build/dist/Types/PushNotification/PushDeviceType.js.map +1 -0
  95. package/build/dist/UI/Components/Forms/BasicForm.js +3 -1
  96. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  97. package/build/dist/UI/Components/ModelDelete/ModelDelete.js +2 -1
  98. package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
  99. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  100. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  101. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +2 -1
  102. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  103. package/build/dist/UI/Components/Page/ModelPage.js +2 -1
  104. package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
  105. package/package.json +2 -1
@@ -543,6 +543,31 @@ export default class AlertEpisode extends BaseModel {
543
543
  })
544
544
  public resolvedAt?: Date = undefined;
545
545
 
546
+ @ColumnAccessControl({
547
+ create: [],
548
+ read: [
549
+ Permission.ProjectOwner,
550
+ Permission.ProjectAdmin,
551
+ Permission.ProjectMember,
552
+ Permission.ReadAlertEpisode,
553
+ Permission.ReadAllProjectResources,
554
+ ],
555
+ update: [],
556
+ })
557
+ @Index()
558
+ @TableColumn({
559
+ type: TableColumnType.Date,
560
+ title: "All Alerts Resolved At",
561
+ description:
562
+ "When all alerts in this episode were first detected as resolved. Used for resolve delay calculation.",
563
+ })
564
+ @Column({
565
+ type: ColumnType.Date,
566
+ nullable: true,
567
+ unique: false,
568
+ })
569
+ public allAlertsResolvedAt?: Date = undefined;
570
+
546
571
  @ColumnAccessControl({
547
572
  create: [
548
573
  Permission.ProjectOwner,
@@ -674,13 +674,13 @@ export default class AlertGroupingRule extends BaseModel {
674
674
  title: "Group By Monitor",
675
675
  description:
676
676
  "When enabled, alerts from different monitors will be grouped into separate episodes. When disabled, alerts from any monitor can be grouped together.",
677
- defaultValue: true,
677
+ defaultValue: false,
678
678
  isDefaultValueColumn: true,
679
679
  })
680
680
  @Column({
681
681
  type: ColumnType.Boolean,
682
682
  nullable: false,
683
- default: true,
683
+ default: false,
684
684
  })
685
685
  public groupByMonitor?: boolean = undefined;
686
686
 
@@ -542,6 +542,31 @@ export default class IncidentEpisode extends BaseModel {
542
542
  })
543
543
  public resolvedAt?: Date = undefined;
544
544
 
545
+ @ColumnAccessControl({
546
+ create: [],
547
+ read: [
548
+ Permission.ProjectOwner,
549
+ Permission.ProjectAdmin,
550
+ Permission.ProjectMember,
551
+ Permission.ReadIncidentEpisode,
552
+ Permission.ReadAllProjectResources,
553
+ ],
554
+ update: [],
555
+ })
556
+ @Index()
557
+ @TableColumn({
558
+ type: TableColumnType.Date,
559
+ title: "All Incidents Resolved At",
560
+ description:
561
+ "When all incidents in this episode were first detected as resolved. Used for resolve delay calculation.",
562
+ })
563
+ @Column({
564
+ type: ColumnType.Date,
565
+ nullable: true,
566
+ unique: false,
567
+ })
568
+ public allIncidentsResolvedAt?: Date = undefined;
569
+
545
570
  @ColumnAccessControl({
546
571
  create: [
547
572
  Permission.ProjectOwner,
@@ -678,13 +678,13 @@ export default class IncidentGroupingRule extends BaseModel {
678
678
  title: "Group By Monitor",
679
679
  description:
680
680
  "When enabled, incidents from different monitors will be grouped into separate episodes. When disabled, incidents from any monitor can be grouped together.",
681
- defaultValue: true,
681
+ defaultValue: false,
682
682
  isDefaultValueColumn: true,
683
683
  })
684
684
  @Column({
685
685
  type: ColumnType.Boolean,
686
686
  nullable: false,
687
- default: true,
687
+ default: false,
688
688
  })
689
689
  public groupByMonitor?: boolean = undefined;
690
690
 
@@ -98,6 +98,7 @@ import ProjectSmtpConfig from "./ProjectSmtpConfig";
98
98
  import ProjectSSO from "./ProjectSso";
99
99
  import PromoCode from "./PromoCode";
100
100
  import EnterpriseLicense from "./EnterpriseLicense";
101
+ import OpenSourceDeployment from "./OpenSourceDeployment";
101
102
  import Reseller from "./Reseller";
102
103
  import ResellerPlan from "./ResellerPlan";
103
104
  // ScheduledMaintenances
@@ -411,6 +412,7 @@ const AllModelTypes: Array<{
411
412
 
412
413
  PromoCode,
413
414
  EnterpriseLicense,
415
+ OpenSourceDeployment,
414
416
 
415
417
  GlobalConfig,
416
418
 
@@ -0,0 +1,140 @@
1
+ import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
2
+ import Route from "../../Types/API/Route";
3
+ import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
4
+ import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
5
+ import ColumnLength from "../../Types/Database/ColumnLength";
6
+ import ColumnType from "../../Types/Database/ColumnType";
7
+ import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
8
+ import TableColumn from "../../Types/Database/TableColumn";
9
+ import TableColumnType from "../../Types/Database/TableColumnType";
10
+ import TableMetadata from "../../Types/Database/TableMetadata";
11
+ import IconProp from "../../Types/Icon/IconProp";
12
+ import { Column, Entity } from "typeorm";
13
+
14
+ @TableAccessControl({
15
+ create: [],
16
+ read: [],
17
+ update: [],
18
+ delete: [],
19
+ })
20
+ @CrudApiEndpoint(new Route("/open-source-deployment"))
21
+ @TableMetadata({
22
+ tableName: "OpenSourceDeployment",
23
+ singularName: "Open Source Deployment",
24
+ pluralName: "Open Source Deployments",
25
+ icon: IconProp.Globe,
26
+ tableDescription:
27
+ "Open source deployment registrations from self-hosted instances.",
28
+ })
29
+ @Entity({
30
+ name: "OpenSourceDeployment",
31
+ })
32
+ export default class OpenSourceDeployment extends BaseModel {
33
+ @ColumnAccessControl({
34
+ create: [],
35
+ read: [],
36
+ update: [],
37
+ })
38
+ @TableColumn({
39
+ required: true,
40
+ type: TableColumnType.Email,
41
+ title: "Email",
42
+ description: "Email address of the user who registered.",
43
+ })
44
+ @Column({
45
+ nullable: false,
46
+ type: ColumnType.Email,
47
+ length: ColumnLength.Email,
48
+ })
49
+ public email?: string = undefined;
50
+
51
+ @ColumnAccessControl({
52
+ create: [],
53
+ read: [],
54
+ update: [],
55
+ })
56
+ @TableColumn({
57
+ required: true,
58
+ type: TableColumnType.ShortText,
59
+ title: "Name",
60
+ description: "Full name of the user who registered.",
61
+ })
62
+ @Column({
63
+ nullable: false,
64
+ type: ColumnType.ShortText,
65
+ length: ColumnLength.ShortText,
66
+ })
67
+ public name?: string = undefined;
68
+
69
+ @ColumnAccessControl({
70
+ create: [],
71
+ read: [],
72
+ update: [],
73
+ })
74
+ @TableColumn({
75
+ required: false,
76
+ type: TableColumnType.ShortText,
77
+ title: "Company Name",
78
+ description: "Company name of the user who registered.",
79
+ })
80
+ @Column({
81
+ nullable: true,
82
+ type: ColumnType.ShortText,
83
+ length: ColumnLength.ShortText,
84
+ })
85
+ public companyName?: string = undefined;
86
+
87
+ @ColumnAccessControl({
88
+ create: [],
89
+ read: [],
90
+ update: [],
91
+ })
92
+ @TableColumn({
93
+ required: false,
94
+ type: TableColumnType.Phone,
95
+ title: "Company Phone Number",
96
+ description: "Phone number of the user who registered.",
97
+ })
98
+ @Column({
99
+ nullable: true,
100
+ type: ColumnType.Phone,
101
+ length: ColumnLength.Phone,
102
+ })
103
+ public companyPhoneNumber?: string = undefined;
104
+
105
+ @ColumnAccessControl({
106
+ create: [],
107
+ read: [],
108
+ update: [],
109
+ })
110
+ @TableColumn({
111
+ required: true,
112
+ type: TableColumnType.ShortText,
113
+ title: "Version",
114
+ description: "OneUptime version of the self-hosted instance.",
115
+ })
116
+ @Column({
117
+ nullable: false,
118
+ type: ColumnType.ShortText,
119
+ length: ColumnLength.ShortText,
120
+ })
121
+ public oneuptimeVersion?: string = undefined;
122
+
123
+ @ColumnAccessControl({
124
+ create: [],
125
+ read: [],
126
+ update: [],
127
+ })
128
+ @TableColumn({
129
+ required: false,
130
+ type: TableColumnType.ShortText,
131
+ title: "Instance URL",
132
+ description: "URL of the self-hosted instance.",
133
+ })
134
+ @Column({
135
+ nullable: true,
136
+ type: ColumnType.ShortText,
137
+ length: ColumnLength.ShortText,
138
+ })
139
+ public instanceUrl?: string = undefined;
140
+ }
@@ -16,6 +16,7 @@ import TenantColumn from "../../Types/Database/TenantColumn";
16
16
  import IconProp from "../../Types/Icon/IconProp";
17
17
  import ObjectID from "../../Types/ObjectID";
18
18
  import Permission from "../../Types/Permission";
19
+ import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
19
20
  import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
20
21
 
21
22
  @TenantColumn("projectId")
@@ -122,7 +123,7 @@ class UserPush extends BaseModel {
122
123
  unique: false,
123
124
  nullable: false,
124
125
  })
125
- public deviceType?: "web" = "web" as const; // Only web support for now
126
+ public deviceType?: PushDeviceType = undefined;
126
127
 
127
128
  @ColumnAccessControl({
128
129
  create: [Permission.CurrentUser],
@@ -0,0 +1,73 @@
1
+ import OpenSourceDeployment from "../../Models/DatabaseModels/OpenSourceDeployment";
2
+ import { JSONObject } from "../../Types/JSON";
3
+ import URL from "../../Types/API/URL";
4
+ import API from "../../Utils/API";
5
+ import OpenSourceDeploymentService, {
6
+ Service as OpenSourceDeploymentServiceType,
7
+ } from "../Services/OpenSourceDeploymentService";
8
+ import { OpenSourceDeploymentWebhookUrl } from "../EnvironmentConfig";
9
+ import logger from "../Utils/Logger";
10
+ import Response from "../Utils/Response";
11
+ import {
12
+ ExpressRequest,
13
+ ExpressResponse,
14
+ NextFunction,
15
+ } from "../Utils/Express";
16
+ import BaseAPI from "./BaseAPI";
17
+
18
+ export default class OpenSourceDeploymentAPI extends BaseAPI<
19
+ OpenSourceDeployment,
20
+ OpenSourceDeploymentServiceType
21
+ > {
22
+ public constructor() {
23
+ super(OpenSourceDeployment, OpenSourceDeploymentService);
24
+
25
+ this.router.post(
26
+ `${new this.entityType().getCrudApiPath()?.toString()}/register`,
27
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
28
+ try {
29
+ const body: JSONObject = req.body;
30
+
31
+ const deployment: OpenSourceDeployment = new OpenSourceDeployment();
32
+
33
+ deployment.email = (body["email"] as string) || "";
34
+ deployment.name = (body["name"] as string) || "";
35
+ deployment.companyName = (body["companyName"] as string) || "";
36
+ deployment.companyPhoneNumber =
37
+ (body["companyPhoneNumber"] as string) || "";
38
+ deployment.oneuptimeVersion =
39
+ (body["oneuptimeVersion"] as string) || "unknown";
40
+ deployment.instanceUrl = (body["instanceUrl"] as string) || "";
41
+
42
+ await OpenSourceDeploymentService.create({
43
+ data: deployment,
44
+ props: {
45
+ isRoot: true,
46
+ },
47
+ });
48
+
49
+ if (OpenSourceDeploymentWebhookUrl) {
50
+ API.post({
51
+ url: URL.fromString(OpenSourceDeploymentWebhookUrl),
52
+ data: {
53
+ email: deployment.email?.toString() || "",
54
+ name: deployment.name?.toString() || "",
55
+ companyName: deployment.companyName?.toString() || "",
56
+ companyPhoneNumber:
57
+ deployment.companyPhoneNumber?.toString() || "",
58
+ oneuptimeVersion: deployment.oneuptimeVersion?.toString() || "",
59
+ instanceUrl: deployment.instanceUrl?.toString() || "",
60
+ },
61
+ }).catch((err: Error) => {
62
+ logger.error(err);
63
+ });
64
+ }
65
+
66
+ return Response.sendEmptySuccessResponse(req, res);
67
+ } catch (err) {
68
+ next(err);
69
+ }
70
+ },
71
+ );
72
+ }
73
+ }
@@ -14,6 +14,7 @@ import Response from "../Utils/Response";
14
14
  import BaseAPI from "./BaseAPI";
15
15
  import BadDataException from "../../Types/Exception/BadDataException";
16
16
  import ObjectID from "../../Types/ObjectID";
17
+ import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
17
18
  import UserPush from "../../Models/DatabaseModels/UserPush";
18
19
  import PushNotificationMessage from "../../Types/PushNotification/PushNotificationMessage";
19
20
 
@@ -39,11 +40,17 @@ export default class UserPushAPI extends BaseAPI<
39
40
  );
40
41
  }
41
42
 
42
- if (!req.body.deviceType || req.body.deviceType !== "web") {
43
+ const validDeviceTypes: string[] = Object.values(PushDeviceType);
44
+ if (
45
+ !req.body.deviceType ||
46
+ !validDeviceTypes.includes(req.body.deviceType)
47
+ ) {
43
48
  return Response.sendErrorResponse(
44
49
  req,
45
50
  res,
46
- new BadDataException("Only web device type is supported"),
51
+ new BadDataException(
52
+ "Device type must be one of: " + validDeviceTypes.join(", "),
53
+ ),
47
54
  );
48
55
  }
49
56
 
@@ -86,7 +93,7 @@ export default class UserPushAPI extends BaseAPI<
86
93
  userPush.deviceToken = req.body.deviceToken;
87
94
  userPush.deviceType = req.body.deviceType;
88
95
  userPush.deviceName = req.body.deviceName || "Unknown Device";
89
- userPush.isVerified = true; // For web push, we consider it verified immediately
96
+ userPush.isVerified = true; // Web, iOS, and Android devices are verified immediately
90
97
 
91
98
  const savedDevice: UserPush = await this.service.create({
92
99
  data: userPush,
@@ -105,6 +112,46 @@ export default class UserPushAPI extends BaseAPI<
105
112
  },
106
113
  );
107
114
 
115
+ this.router.post(
116
+ `/user-push/unregister`,
117
+ UserMiddleware.getUserMiddleware,
118
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
119
+ try {
120
+ req = req as OneUptimeRequest;
121
+
122
+ if (!req.body.deviceToken) {
123
+ return Response.sendErrorResponse(
124
+ req,
125
+ res,
126
+ new BadDataException("Device token is required"),
127
+ );
128
+ }
129
+
130
+ const userId: ObjectID = (req as OneUptimeRequest).userAuthorization!
131
+ .userId!;
132
+
133
+ await this.service.deleteBy({
134
+ query: {
135
+ userId: userId,
136
+ deviceToken: req.body.deviceToken,
137
+ },
138
+ limit: 100,
139
+ skip: 0,
140
+ props: {
141
+ isRoot: true,
142
+ },
143
+ });
144
+
145
+ return Response.sendJsonObjectResponse(req, res, {
146
+ success: true,
147
+ message: "Device unregistered successfully",
148
+ });
149
+ } catch (error) {
150
+ return next(error);
151
+ }
152
+ },
153
+ );
154
+
108
155
  this.router.post(
109
156
  `/user-push/:deviceId/test-notification`,
110
157
  UserMiddleware.getUserMiddleware,
@@ -186,7 +233,7 @@ export default class UserPushAPI extends BaseAPI<
186
233
  },
187
234
  ],
188
235
  message: testMessage,
189
- deviceType: device.deviceType!,
236
+ deviceType: device.deviceType! as PushDeviceType,
190
237
  },
191
238
  {
192
239
  isSensitive: false,
@@ -148,6 +148,9 @@ export const EncryptionSecret: ObjectID = new ObjectID(
148
148
  process.env["ENCRYPTION_SECRET"] || "secret",
149
149
  );
150
150
 
151
+ export const OpenSourceDeploymentWebhookUrl: string =
152
+ process.env["OPEN_SOURCE_DEPLOYMENT_WEBHOOK_URL"] || "";
153
+
151
154
  export const AirtableApiKey: string = process.env["AIRTABLE_API_KEY"] || "";
152
155
 
153
156
  export const AirtableBaseId: string = process.env["AIRTABLE_BASE_ID"] || "";
@@ -22,23 +22,23 @@ export class MigrationName1770237245070 implements MigrationInterface {
22
22
  );
23
23
 
24
24
  /*
25
- * Backfill counters from COUNT of each entity table (including soft-deleted rows)
26
- * Using JOIN-based updates instead of correlated subqueries for performance
25
+ * Backfill counters from MAX number of each entity table (per project)
26
+ * Using MAX instead of COUNT to correctly handle deleted rows
27
27
  */
28
28
  await queryRunner.query(
29
- `UPDATE "Project" SET "incidentCounter" = sub.cnt FROM (SELECT "projectId", COUNT(*) as cnt FROM "Incident" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
29
+ `UPDATE "Project" SET "incidentCounter" = sub.max_num FROM (SELECT "projectId", COALESCE(MAX("incidentNumber"), 0) as max_num FROM "Incident" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
30
30
  );
31
31
  await queryRunner.query(
32
- `UPDATE "Project" SET "alertCounter" = sub.cnt FROM (SELECT "projectId", COUNT(*) as cnt FROM "Alert" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
32
+ `UPDATE "Project" SET "alertCounter" = sub.max_num FROM (SELECT "projectId", COALESCE(MAX("alertNumber"), 0) as max_num FROM "Alert" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
33
33
  );
34
34
  await queryRunner.query(
35
- `UPDATE "Project" SET "scheduledMaintenanceCounter" = sub.cnt FROM (SELECT "projectId", COUNT(*) as cnt FROM "ScheduledMaintenance" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
35
+ `UPDATE "Project" SET "scheduledMaintenanceCounter" = sub.max_num FROM (SELECT "projectId", COALESCE(MAX("scheduledMaintenanceNumber"), 0) as max_num FROM "ScheduledMaintenance" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
36
36
  );
37
37
  await queryRunner.query(
38
- `UPDATE "Project" SET "incidentEpisodeCounter" = sub.cnt FROM (SELECT "projectId", COUNT(*) as cnt FROM "IncidentEpisode" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
38
+ `UPDATE "Project" SET "incidentEpisodeCounter" = sub.max_num FROM (SELECT "projectId", COALESCE(MAX("episodeNumber"), 0) as max_num FROM "IncidentEpisode" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
39
39
  );
40
40
  await queryRunner.query(
41
- `UPDATE "Project" SET "alertEpisodeCounter" = sub.cnt FROM (SELECT "projectId", COUNT(*) as cnt FROM "AlertEpisode" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
41
+ `UPDATE "Project" SET "alertEpisodeCounter" = sub.max_num FROM (SELECT "projectId", COALESCE(MAX("episodeNumber"), 0) as max_num FROM "AlertEpisode" GROUP BY "projectId") sub WHERE "Project"."_id" = sub."projectId"`,
42
42
  );
43
43
  }
44
44
 
@@ -0,0 +1,26 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+ import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan";
3
+
4
+ export class MigrationName1770668054908 implements MigrationInterface {
5
+ public name = "MigrationName1770668054908";
6
+
7
+ @CaptureSpan()
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "IncidentGroupingRule" ALTER COLUMN "groupByMonitor" SET DEFAULT false`,
11
+ );
12
+ await queryRunner.query(
13
+ `ALTER TABLE "AlertGroupingRule" ALTER COLUMN "groupByMonitor" SET DEFAULT false`,
14
+ );
15
+ }
16
+
17
+ @CaptureSpan()
18
+ public async down(queryRunner: QueryRunner): Promise<void> {
19
+ await queryRunner.query(
20
+ `ALTER TABLE "IncidentGroupingRule" ALTER COLUMN "groupByMonitor" SET DEFAULT true`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "AlertGroupingRule" ALTER COLUMN "groupByMonitor" SET DEFAULT true`,
24
+ );
25
+ }
26
+ }
@@ -0,0 +1,47 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1770728946893 implements MigrationInterface {
4
+ public name = "MigrationName1770728946893";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "IncidentEpisode" ADD "allIncidentsResolvedAt" TIMESTAMP WITH TIME ZONE`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "AlertEpisode" ADD "allAlertsResolvedAt" TIMESTAMP WITH TIME ZONE`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
15
+ );
16
+ await queryRunner.query(
17
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_0610406e5c436c20a5068b1006" ON "IncidentEpisode" ("allIncidentsResolvedAt") `,
21
+ );
22
+ await queryRunner.query(
23
+ `CREATE INDEX "IDX_ea5d1f899fe52445dd6e0d0d55" ON "AlertEpisode" ("allAlertsResolvedAt") `,
24
+ );
25
+ }
26
+
27
+ public async down(queryRunner: QueryRunner): Promise<void> {
28
+ await queryRunner.query(
29
+ `DROP INDEX "public"."IDX_ea5d1f899fe52445dd6e0d0d55"`,
30
+ );
31
+ await queryRunner.query(
32
+ `DROP INDEX "public"."IDX_0610406e5c436c20a5068b1006"`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "AlertEpisode" DROP COLUMN "allAlertsResolvedAt"`,
42
+ );
43
+ await queryRunner.query(
44
+ `ALTER TABLE "IncidentEpisode" DROP COLUMN "allIncidentsResolvedAt"`,
45
+ );
46
+ }
47
+ }
@@ -0,0 +1,27 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1770732721195 implements MigrationInterface {
4
+ public name = "MigrationName1770732721195";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "OpenSourceDeployment" ("_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, "email" character varying(100) NOT NULL, "name" character varying(100) NOT NULL, "companyName" character varying(100), "companyPhoneNumber" character varying(30), "oneuptimeVersion" character varying(100) NOT NULL, "instanceUrl" character varying(100), CONSTRAINT "PK_cf6728c16db1c0c1b89f8ffc6dd" PRIMARY KEY ("_id"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
15
+ );
16
+ }
17
+
18
+ public async down(queryRunner: QueryRunner): Promise<void> {
19
+ await queryRunner.query(
20
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
24
+ );
25
+ await queryRunner.query(`DROP TABLE "OpenSourceDeployment"`);
26
+ }
27
+ }
@@ -253,6 +253,9 @@ import { MigrationName1770232207959 } from "./1770232207959-MigrationName";
253
253
  import { MigrationName1770237245069 } from "./1770237245069-MigrationName";
254
254
  import { MigrationName1770237245070 } from "./1770237245070-MigrationName";
255
255
  import { MigrationName1770407024682 } from "./1770407024682-MigrationName";
256
+ import { MigrationName1770668054908 } from "./1770668054908-MigrationName";
257
+ import { MigrationName1770728946893 } from "./1770728946893-MigrationName";
258
+ import { MigrationName1770732721195 } from "./1770732721195-MigrationName";
256
259
 
257
260
  export default [
258
261
  InitialMigration,
@@ -510,4 +513,7 @@ export default [
510
513
  MigrationName1770237245069,
511
514
  MigrationName1770237245070,
512
515
  MigrationName1770407024682,
516
+ MigrationName1770668054908,
517
+ MigrationName1770728946893,
518
+ MigrationName1770732721195,
513
519
  ];
@@ -64,18 +64,23 @@ export default class UserMiddleware {
64
64
  public static getAccessTokenFromExpressRequest(
65
65
  req: ExpressRequest,
66
66
  ): string | undefined {
67
- let accessToken: string | undefined = undefined;
67
+ // 1. Try cookie (existing web dashboard flow)
68
+ const cookieToken: string | undefined =
69
+ CookieUtil.getCookieFromExpressRequest(req, CookieUtil.getUserTokenKey());
68
70
 
69
- if (
70
- CookieUtil.getCookieFromExpressRequest(req, CookieUtil.getUserTokenKey())
71
- ) {
72
- accessToken = CookieUtil.getCookieFromExpressRequest(
73
- req,
74
- CookieUtil.getUserTokenKey(),
75
- );
71
+ if (cookieToken) {
72
+ return cookieToken;
76
73
  }
77
74
 
78
- return accessToken;
75
+ // 2. Fallback: Check Authorization: Bearer <token> header (mobile app flow)
76
+ const authHeader: string | undefined = req.headers["authorization"] as
77
+ | string
78
+ | undefined;
79
+ if (authHeader && authHeader.startsWith("Bearer ")) {
80
+ return authHeader.substring(7);
81
+ }
82
+
83
+ return undefined;
79
84
  }
80
85
 
81
86
  @CaptureSpan()