@oneuptime/common 7.0.5017 → 7.0.5022

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 (28) hide show
  1. package/Models/DatabaseModels/StatusPage.ts +39 -0
  2. package/Models/DatabaseModels/StatusPageSubscriber.ts +59 -0
  3. package/Server/API/StatusPageAPI.ts +48 -3
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/1755775040650-MigrationName.ts +29 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1755778495455-MigrationName.ts +23 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1755778934927-MigrationName.ts +23 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  8. package/Server/Services/StatusPageSubscriberService.ts +67 -9
  9. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +113 -373
  10. package/build/dist/Models/DatabaseModels/StatusPage.js +40 -0
  11. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  12. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +61 -0
  13. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  14. package/build/dist/Server/API/StatusPageAPI.js +27 -3
  15. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  16. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755775040650-MigrationName.js +16 -0
  17. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755775040650-MigrationName.js.map +1 -0
  18. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755778495455-MigrationName.js +14 -0
  19. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755778495455-MigrationName.js.map +1 -0
  20. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755778934927-MigrationName.js +14 -0
  21. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755778934927-MigrationName.js.map +1 -0
  22. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  23. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  24. package/build/dist/Server/Services/StatusPageSubscriberService.js +50 -9
  25. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  26. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +93 -302
  27. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  28. package/package.json +1 -1
@@ -1179,6 +1179,45 @@ export default class StatusPage extends BaseModel {
1179
1179
  })
1180
1180
  public enableSlackSubscribers?: boolean = undefined;
1181
1181
 
1182
+ @ColumnAccessControl({
1183
+ create: [
1184
+ Permission.ProjectOwner,
1185
+ Permission.ProjectAdmin,
1186
+ Permission.ProjectMember,
1187
+ Permission.CreateProjectStatusPage,
1188
+ ],
1189
+ read: [
1190
+ Permission.ProjectOwner,
1191
+ Permission.ProjectAdmin,
1192
+ Permission.ProjectMember,
1193
+ Permission.ReadProjectStatusPage,
1194
+ ],
1195
+ update: [
1196
+ Permission.ProjectOwner,
1197
+ Permission.ProjectAdmin,
1198
+ Permission.ProjectMember,
1199
+ Permission.EditProjectStatusPage,
1200
+ ],
1201
+ })
1202
+ @TableColumn({
1203
+ isDefaultValueColumn: true,
1204
+ type: TableColumnType.Boolean,
1205
+ title: "Enable Microsoft Teams Subscribers",
1206
+ description:
1207
+ "Can Microsoft Teams subscribers subscribe to this Status Page?",
1208
+ defaultValue: false,
1209
+ })
1210
+ @Column({
1211
+ type: ColumnType.Boolean,
1212
+ default: false,
1213
+ })
1214
+ @ColumnBillingAccessControl({
1215
+ read: PlanType.Free,
1216
+ update: PlanType.Scale,
1217
+ create: PlanType.Free,
1218
+ })
1219
+ public enableMicrosoftTeamsSubscribers?: boolean = undefined;
1220
+
1182
1221
  @ColumnAccessControl({
1183
1222
  create: [
1184
1223
  Permission.ProjectOwner,
@@ -379,6 +379,65 @@ export default class StatusPageSubscriber extends BaseModel {
379
379
  })
380
380
  public slackWorkspaceName?: string = undefined;
381
381
 
382
+ @ColumnAccessControl({
383
+ create: [
384
+ Permission.ProjectOwner,
385
+ Permission.ProjectAdmin,
386
+ Permission.ProjectMember,
387
+ Permission.CreateStatusPageSubscriber,
388
+ Permission.Public,
389
+ ],
390
+ read: [],
391
+ update: [],
392
+ })
393
+ @TableColumn({
394
+ required: false,
395
+ type: TableColumnType.LongURL,
396
+ title: "Microsoft Teams Incoming Webhook URL",
397
+ description:
398
+ "Microsoft Teams incoming webhook URL to send notifications to Teams channel",
399
+ })
400
+ @Column({
401
+ nullable: true,
402
+ type: ColumnType.LongURL,
403
+ transformer: URL.getDatabaseTransformer(),
404
+ })
405
+ public microsoftTeamsIncomingWebhookUrl?: URL = undefined;
406
+
407
+ @ColumnAccessControl({
408
+ create: [
409
+ Permission.ProjectOwner,
410
+ Permission.ProjectAdmin,
411
+ Permission.ProjectMember,
412
+ Permission.CreateStatusPageSubscriber,
413
+ Permission.Public,
414
+ ],
415
+ read: [
416
+ Permission.ProjectOwner,
417
+ Permission.ProjectAdmin,
418
+ Permission.ProjectMember,
419
+ Permission.ReadStatusPageSubscriber,
420
+ ],
421
+ update: [
422
+ Permission.ProjectOwner,
423
+ Permission.ProjectAdmin,
424
+ Permission.ProjectMember,
425
+ Permission.EditStatusPageSubscriber,
426
+ ],
427
+ })
428
+ @TableColumn({
429
+ required: false,
430
+ type: TableColumnType.VeryLongText,
431
+ title: "Microsoft Teams Workspace Name",
432
+ description:
433
+ "Name of the Microsoft Teams workspace for validation and identification",
434
+ })
435
+ @Column({
436
+ nullable: true,
437
+ type: ColumnType.VeryLongText,
438
+ })
439
+ public microsoftTeamsWorkspaceName?: string = undefined;
440
+
382
441
  @ColumnAccessControl({
383
442
  create: [
384
443
  Permission.ProjectOwner,
@@ -502,6 +502,7 @@ export default class StatusPageAPI extends BaseAPI<
502
502
  footerHTML: true,
503
503
  enableEmailSubscribers: true,
504
504
  enableSlackSubscribers: true,
505
+ enableMicrosoftTeamsSubscribers: true,
505
506
  enableSmsSubscribers: true,
506
507
  isPublicStatusPage: true,
507
508
  allowSubscribersToChooseResources: true,
@@ -2146,6 +2147,7 @@ export default class StatusPageAPI extends BaseAPI<
2146
2147
  projectId: true,
2147
2148
  enableEmailSubscribers: true,
2148
2149
  enableSlackSubscribers: true,
2150
+ enableMicrosoftTeamsSubscribers: true,
2149
2151
  enableSmsSubscribers: true,
2150
2152
  allowSubscribersToChooseResources: true,
2151
2153
  allowSubscribersToChooseEventTypes: true,
@@ -2419,6 +2421,7 @@ export default class StatusPageAPI extends BaseAPI<
2419
2421
  enableEmailSubscribers: true,
2420
2422
  enableSmsSubscribers: true,
2421
2423
  enableSlackSubscribers: true,
2424
+ enableMicrosoftTeamsSubscribers: true,
2422
2425
  allowSubscribersToChooseResources: true,
2423
2426
  allowSubscribersToChooseEventTypes: true,
2424
2427
  showSubscriberPageOnStatusPage: true,
@@ -2479,16 +2482,29 @@ export default class StatusPageAPI extends BaseAPI<
2479
2482
  );
2480
2483
  }
2481
2484
 
2485
+ if (
2486
+ req.body.data["microsoftTeamsWorkspaceName"] &&
2487
+ !statusPage.enableMicrosoftTeamsSubscribers
2488
+ ) {
2489
+ logger.debug(
2490
+ `Microsoft Teams subscribers not enabled for status page with ID: ${objectId}`,
2491
+ );
2492
+ throw new BadDataException(
2493
+ "Microsoft Teams subscribers not enabled for this status page.",
2494
+ );
2495
+ }
2496
+
2482
2497
  if (
2483
2498
  !req.body.data["subscriberEmail"] &&
2484
2499
  !req.body.data["subscriberPhone"] &&
2485
- !req.body.data["slackWorkspaceName"]
2500
+ !req.body.data["slackWorkspaceName"] &&
2501
+ !req.body.data["microsoftTeamsWorkspaceName"]
2486
2502
  ) {
2487
2503
  logger.debug(
2488
- `No email, phone, or slack workspace name provided for subscription to status page with ID: ${objectId}`,
2504
+ `No email, phone, slack workspace name, or Microsoft Teams workspace name provided for subscription to status page with ID: ${objectId}`,
2489
2505
  );
2490
2506
  throw new BadDataException(
2491
- "Email, phone or slack workspace name is required to subscribe to this status page.",
2507
+ "Email, phone, slack workspace name, or Microsoft Teams workspace name is required to subscribe to this status page.",
2492
2508
  );
2493
2509
  }
2494
2510
 
@@ -2512,6 +2528,18 @@ export default class StatusPageAPI extends BaseAPI<
2512
2528
  ? (req.body.data["slackWorkspaceName"] as string)
2513
2529
  : undefined;
2514
2530
 
2531
+ const microsoftTeamsIncomingWebhookUrl: string | undefined = req.body.data[
2532
+ "microsoftTeamsIncomingWebhookUrl"
2533
+ ]
2534
+ ? (req.body.data["microsoftTeamsIncomingWebhookUrl"] as string)
2535
+ : undefined;
2536
+
2537
+ const microsoftTeamsWorkspaceName: string | undefined = req.body.data[
2538
+ "microsoftTeamsWorkspaceName"
2539
+ ]
2540
+ ? (req.body.data["microsoftTeamsWorkspaceName"] as string)
2541
+ : undefined;
2542
+
2515
2543
  let statusPageSubscriber: StatusPageSubscriber | null = null;
2516
2544
 
2517
2545
  let isUpdate: boolean = false;
@@ -2570,6 +2598,23 @@ export default class StatusPageAPI extends BaseAPI<
2570
2598
  statusPageSubscriber.slackWorkspaceName = slackWorkspaceName;
2571
2599
  }
2572
2600
 
2601
+ if (microsoftTeamsIncomingWebhookUrl) {
2602
+ logger.debug(
2603
+ `Setting subscriber Microsoft Teams webhook: ${microsoftTeamsIncomingWebhookUrl}`,
2604
+ );
2605
+ statusPageSubscriber.microsoftTeamsIncomingWebhookUrl = URL.fromString(
2606
+ microsoftTeamsIncomingWebhookUrl,
2607
+ );
2608
+ }
2609
+
2610
+ if (microsoftTeamsWorkspaceName) {
2611
+ logger.debug(
2612
+ `Setting subscriber Microsoft Teams workspace name: ${microsoftTeamsWorkspaceName}`,
2613
+ );
2614
+ statusPageSubscriber.microsoftTeamsWorkspaceName =
2615
+ microsoftTeamsWorkspaceName;
2616
+ }
2617
+
2573
2618
  if (
2574
2619
  req.body.data["statusPageResources"] &&
2575
2620
  !statusPage.allowSubscribersToChooseResources
@@ -0,0 +1,29 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1755775040650 implements MigrationInterface {
4
+ public name = "MigrationName1755775040650";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "StatusPage" ADD "enableMicrosoftTeamsSubscribers" boolean NOT NULL DEFAULT false`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "StatusPageSubscriber" ADD "microsoftTeamsIncomingWebhookUrl" character varying`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "StatusPageSubscriber" ADD "microsoftTeamsWorkspaceName" character varying(100)`,
15
+ );
16
+ }
17
+
18
+ public async down(queryRunner: QueryRunner): Promise<void> {
19
+ await queryRunner.query(
20
+ `ALTER TABLE "StatusPageSubscriber" DROP COLUMN "microsoftTeamsWorkspaceName"`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "StatusPageSubscriber" DROP COLUMN "microsoftTeamsIncomingWebhookUrl"`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "StatusPage" DROP COLUMN "enableMicrosoftTeamsSubscribers"`,
27
+ );
28
+ }
29
+ }
@@ -0,0 +1,23 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1755778495455 implements MigrationInterface {
4
+ public name = "MigrationName1755778495455";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "StatusPageSubscriber" DROP COLUMN "microsoftTeamsWorkspaceName"`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "StatusPageSubscriber" ADD "microsoftTeamsWorkspaceName" text`,
12
+ );
13
+ }
14
+
15
+ public async down(queryRunner: QueryRunner): Promise<void> {
16
+ await queryRunner.query(
17
+ `ALTER TABLE "StatusPageSubscriber" DROP COLUMN "microsoftTeamsWorkspaceName"`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "StatusPageSubscriber" ADD "microsoftTeamsWorkspaceName" character varying(100)`,
21
+ );
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1755778934927 implements MigrationInterface {
4
+ public name = "MigrationName1755778934927";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "StatusPageSubscriber" DROP COLUMN "microsoftTeamsIncomingWebhookUrl"`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "StatusPageSubscriber" ADD "microsoftTeamsIncomingWebhookUrl" text`,
12
+ );
13
+ }
14
+
15
+ public async down(queryRunner: QueryRunner): Promise<void> {
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
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
21
+ );
22
+ }
23
+ }
@@ -158,6 +158,9 @@ import { MigrationName1755088852971 } from "./1755088852971-MigrationName";
158
158
  import { MigrationName1755093133870 } from "./1755093133870-MigrationName";
159
159
  import { MigrationName1755109893911 } from "./1755109893911-MigrationName";
160
160
  import { MigrationName1755110936888 } from "./1755110936888-MigrationName";
161
+ import { MigrationName1755775040650 } from "./1755775040650-MigrationName";
162
+ import { MigrationName1755778495455 } from "./1755778495455-MigrationName";
163
+ import { MigrationName1755778934927 } from "./1755778934927-MigrationName";
161
164
 
162
165
  export default [
163
166
  InitialMigration,
@@ -320,4 +323,7 @@ export default [
320
323
  MigrationName1755093133870,
321
324
  MigrationName1755109893911,
322
325
  MigrationName1755110936888,
326
+ MigrationName1755775040650,
327
+ MigrationName1755778495455,
328
+ MigrationName1755778934927,
323
329
  ];
@@ -32,6 +32,7 @@ import PositiveNumber from "../../Types/PositiveNumber";
32
32
  import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
33
33
  import NumberUtil from "../../Utils/Number";
34
34
  import SlackUtil from "../Utils/Workspace/Slack/Slack";
35
+ import MicrosoftTeamsUtil from "../Utils/Workspace/MicrosoftTeams/MicrosoftTeams";
35
36
 
36
37
  export class Service extends DatabaseService<Model> {
37
38
  public constructor() {
@@ -221,6 +222,23 @@ export class Service extends DatabaseService<Model> {
221
222
  }
222
223
  }
223
224
 
225
+ // Validate Microsoft Teams webhook URL if provided
226
+ if (data.data.microsoftTeamsIncomingWebhookUrl) {
227
+ logger.debug(
228
+ `Microsoft Teams Incoming Webhook URL: ${data.data.microsoftTeamsIncomingWebhookUrl}`,
229
+ );
230
+ if (
231
+ !MicrosoftTeamsUtil.isValidMicrosoftTeamsIncomingWebhookUrl(
232
+ data.data.microsoftTeamsIncomingWebhookUrl,
233
+ )
234
+ ) {
235
+ logger.debug("Invalid Microsoft Teams Incoming Webhook URL.");
236
+ throw new BadDataException(
237
+ "Invalid Microsoft Teams Incoming Webhook URL.",
238
+ );
239
+ }
240
+ }
241
+
224
242
  data.data.subscriptionConfirmationToken = NumberUtil.getRandomNumber(
225
243
  100000,
226
244
  999999,
@@ -370,16 +388,55 @@ Stay informed about service availability! 🚀`;
370
388
 
371
389
  logger.debug(`Slack Message: ${slackMessage}`);
372
390
 
373
- try {
374
- await SlackUtil.sendMessageToChannelViaIncomingWebhook({
375
- url: URL.fromString(createdItem.slackIncomingWebhookUrl.toString()),
376
- text: SlackUtil.convertMarkdownToSlackRichText(slackMessage),
391
+ SlackUtil.sendMessageToChannelViaIncomingWebhook({
392
+ url: URL.fromString(createdItem.slackIncomingWebhookUrl.toString()),
393
+ text: SlackUtil.convertMarkdownToSlackRichText(slackMessage),
394
+ })
395
+ .then(() => {
396
+ logger.debug("Slack notification sent successfully.");
397
+ })
398
+ .catch((err: Error) => {
399
+ logger.error("Error sending Slack notification:");
400
+ logger.error(err);
401
+ });
402
+ }
403
+
404
+ // if Microsoft Teams incoming webhook is provided and sendYouHaveSubscribedMessage is true, then send a message to the Teams channel.
405
+ if (
406
+ createdItem.microsoftTeamsIncomingWebhookUrl &&
407
+ createdItem.sendYouHaveSubscribedMessage
408
+ ) {
409
+ logger.debug("Sending Microsoft Teams notification for new subscriber.");
410
+ const teamsMessage: string = `## 📢 New Subscription to ${statusPageName}
411
+
412
+ **You have successfully subscribed to receive status updates!**
413
+
414
+ 🔗 **Status Page:** [${statusPageName}](${statusPageURL})
415
+ 📧 **Manage Subscription:** [Update preferences or unsubscribe](${unsubscribeLink})
416
+
417
+ You will receive real-time notifications for:
418
+ • Incidents and outages
419
+ • Scheduled maintenance events
420
+ • Service announcements
421
+ • Status updates
422
+
423
+ Stay informed about service availability! 🚀`;
424
+
425
+ logger.debug(`Teams Message: ${teamsMessage}`);
426
+
427
+ MicrosoftTeamsUtil.sendMessageToChannelViaIncomingWebhook({
428
+ url: URL.fromString(
429
+ createdItem.microsoftTeamsIncomingWebhookUrl.toString(),
430
+ ),
431
+ text: teamsMessage,
432
+ })
433
+ .then(() => {
434
+ logger.debug("Microsoft Teams notification sent successfully.");
435
+ })
436
+ .catch((err: Error) => {
437
+ logger.error("Error sending Microsoft Teams notification:");
438
+ logger.error(err);
377
439
  });
378
- logger.debug("Slack notification sent successfully.");
379
- } catch (error) {
380
- logger.error("Error sending Slack notification:");
381
- logger.error(error);
382
- }
383
440
  }
384
441
 
385
442
  logger.debug("onCreateSuccess completed.");
@@ -691,6 +748,7 @@ Stay informed about service availability! 🚀`;
691
748
  subscriberPhone: true,
692
749
  subscriberWebhook: true,
693
750
  slackIncomingWebhookUrl: true,
751
+ microsoftTeamsIncomingWebhookUrl: true,
694
752
  isSubscribedToAllResources: true,
695
753
  statusPageResources: true,
696
754
  isSubscribedToAllEventTypes: true,