@oneuptime/common 7.0.4066 → 7.0.4078

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 (33) hide show
  1. package/Server/API/StatusPageAPI.ts +426 -0
  2. package/Server/EnvironmentConfig.ts +7 -0
  3. package/Server/Services/OnCallDutyPolicyScheduleService.ts +1 -1
  4. package/Server/Utils/StartServer.ts +34 -5
  5. package/Types/Email/EmailTemplateType.ts +1 -0
  6. package/UI/Components/Button/DropdownButton.tsx +93 -0
  7. package/UI/Components/Forms/BasicForm.tsx +14 -2
  8. package/UI/Components/Forms/BasicModelForm.tsx +8 -1
  9. package/UI/Components/Forms/Fields/FormField.tsx +32 -18
  10. package/UI/Components/Forms/ModelForm.tsx +13 -3
  11. package/UI/Components/Forms/Types/Field.ts +7 -1
  12. package/UI/Images/favicon/status-green.png +0 -0
  13. package/build/dist/Server/API/StatusPageAPI.js +320 -36
  14. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  15. package/build/dist/Server/EnvironmentConfig.js +2 -1
  16. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  17. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +1 -1
  18. package/build/dist/Server/Utils/StartServer.js +16 -4
  19. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  20. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  21. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  22. package/build/dist/UI/Components/Button/DropdownButton.js +20 -0
  23. package/build/dist/UI/Components/Button/DropdownButton.js.map +1 -0
  24. package/build/dist/UI/Components/Forms/BasicForm.js +8 -2
  25. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  26. package/build/dist/UI/Components/Forms/BasicModelForm.js +1 -1
  27. package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
  28. package/build/dist/UI/Components/Forms/Fields/FormField.js +23 -18
  29. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  30. package/build/dist/UI/Components/Forms/ModelForm.js +2 -2
  31. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  32. package/package.json +2 -2
  33. package/UI/webpack-middleware.js +0 -65
@@ -72,13 +72,134 @@ import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
72
72
  import { Green } from "../../Types/BrandColors";
73
73
  import UptimeUtil from "../../Utils/Uptime/UptimeUtil";
74
74
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
75
+ import URL from "Common/Types/API/URL";
76
+ import SmsService from "../Services/SmsService";
77
+ import ProjectCallSMSConfigService from "../Services/ProjectCallSMSConfigService";
78
+ import MailService from "../Services/MailService";
79
+ import EmailTemplateType from "../../Types/Email/EmailTemplateType";
80
+ import DatabaseConfig from "../DatabaseConfig";
81
+ import { FileRoute } from "../../ServiceRoute";
82
+ import ProjectSmtpConfigService from "../Services/ProjectSmtpConfigService";
75
83
  export default class StatusPageAPI extends BaseAPI {
76
84
  constructor() {
77
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
85
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
78
86
  super(StatusPage, StatusPageService);
87
+ // get title, description of the page. This is used for SEO.
88
+ this.router.get(`${(_a = new this.entityType().getCrudApiPath()) === null || _a === void 0 ? void 0 : _a.toString()}/seo/:statusPageIdOrDomain`, UserMiddleware.getUserMiddleware, async (req, res) => {
89
+ const statusPageIdOrDomain = req.params["statusPageIdOrDomain"];
90
+ let statusPageId = null;
91
+ if (statusPageIdOrDomain && statusPageIdOrDomain.includes(".")) {
92
+ // then this is a domain and not the status page id. We need to get the status page id from the domain.
93
+ const statusPageDomain = await StatusPageDomainService.findOneBy({
94
+ query: {
95
+ fullDomain: statusPageIdOrDomain,
96
+ domain: {
97
+ isVerified: true,
98
+ },
99
+ },
100
+ select: {
101
+ statusPageId: true,
102
+ },
103
+ props: {
104
+ isRoot: true,
105
+ },
106
+ });
107
+ if (!statusPageDomain || !statusPageDomain.statusPageId) {
108
+ return Response.sendErrorResponse(req, res, new NotFoundException("Status Page not found"));
109
+ }
110
+ statusPageId = statusPageDomain.statusPageId;
111
+ }
112
+ else {
113
+ // then this is a status page id. We need to get the status page id from the id.
114
+ try {
115
+ statusPageId = new ObjectID(statusPageIdOrDomain);
116
+ }
117
+ catch (err) {
118
+ return Response.sendErrorResponse(req, res, new NotFoundException("Status Page not found"));
119
+ }
120
+ }
121
+ const statusPage = await StatusPageService.findOneBy({
122
+ query: {
123
+ _id: statusPageId,
124
+ },
125
+ select: {
126
+ pageTitle: true,
127
+ pageDescription: true,
128
+ name: true,
129
+ },
130
+ props: {
131
+ isRoot: true,
132
+ },
133
+ });
134
+ if (!statusPage) {
135
+ return Response.sendErrorResponse(req, res, new NotFoundException("Status Page not found"));
136
+ }
137
+ return Response.sendJsonObjectResponse(req, res, {
138
+ title: statusPage.pageTitle || statusPage.name,
139
+ description: statusPage.pageDescription,
140
+ });
141
+ });
142
+ // favicon api.
143
+ this.router.get(`${(_b = new this.entityType().getCrudApiPath()) === null || _b === void 0 ? void 0 : _b.toString()}/favicon/:statusPageIdOrDomain`, async (req, res) => {
144
+ const statusPageIdOrDomain = req.params["statusPageIdOrDomain"];
145
+ let statusPageId = null;
146
+ if (statusPageIdOrDomain && statusPageIdOrDomain.includes(".")) {
147
+ // then this is a domain and not the status page id. We need to get the status page id from the domain.
148
+ const statusPageDomain = await StatusPageDomainService.findOneBy({
149
+ query: {
150
+ fullDomain: statusPageIdOrDomain,
151
+ domain: {
152
+ isVerified: true,
153
+ },
154
+ },
155
+ select: {
156
+ statusPageId: true,
157
+ },
158
+ props: {
159
+ isRoot: true,
160
+ },
161
+ });
162
+ if (!statusPageDomain || !statusPageDomain.statusPageId) {
163
+ return Response.sendErrorResponse(req, res, new NotFoundException("Status Page not found"));
164
+ }
165
+ statusPageId = statusPageDomain.statusPageId;
166
+ }
167
+ else {
168
+ // then this is a status page id. We need to get the status page id from the id.
169
+ try {
170
+ statusPageId = new ObjectID(statusPageIdOrDomain);
171
+ }
172
+ catch (err) {
173
+ return Response.sendErrorResponse(req, res, new NotFoundException("Status Page not found"));
174
+ }
175
+ }
176
+ const statusPage = await StatusPageService.findOneBy({
177
+ query: {
178
+ _id: statusPageId,
179
+ },
180
+ select: {
181
+ faviconFile: {
182
+ file: true,
183
+ _id: true,
184
+ type: true,
185
+ name: true,
186
+ },
187
+ },
188
+ props: {
189
+ isRoot: true,
190
+ },
191
+ });
192
+ if (!statusPage || !statusPage.faviconFile) {
193
+ logger.debug("Favicon file not found. Returning default favicon.");
194
+ // return default favicon.
195
+ return Response.sendFileByPath(req, res, `/usr/src/Common/UI/Images/favicon/status-green.png`);
196
+ }
197
+ logger.debug(`Favicon file found. Sending file: ${statusPage.faviconFile.name}`);
198
+ return Response.sendFileResponse(req, res, statusPage.faviconFile);
199
+ });
79
200
  // confirm subscription api
80
- this.router.get(`${(_a = new this.entityType()
81
- .getCrudApiPath()) === null || _a === void 0 ? void 0 : _a.toString()}/confirm-subscription/:statusPageSubscriberId`, async (req, res) => {
201
+ this.router.get(`${(_c = new this.entityType()
202
+ .getCrudApiPath()) === null || _c === void 0 ? void 0 : _c.toString()}/confirm-subscription/:statusPageSubscriberId`, async (req, res) => {
82
203
  const token = req.query["verification-token"];
83
204
  const statusPageSubscriberId = new ObjectID(req.params["statusPageSubscriberId"]);
84
205
  const subscriber = await StatusPageSubscriberService.findOneBy({
@@ -115,8 +236,8 @@ export default class StatusPageAPI extends BaseAPI {
115
236
  return Response.sendEmptySuccessResponse(req, res);
116
237
  });
117
238
  // CNAME verification api
118
- this.router.get(`${(_b = new this.entityType()
119
- .getCrudApiPath()) === null || _b === void 0 ? void 0 : _b.toString()}/cname-verification/:token`, async (req, res) => {
239
+ this.router.get(`${(_d = new this.entityType()
240
+ .getCrudApiPath()) === null || _d === void 0 ? void 0 : _d.toString()}/cname-verification/:token`, async (req, res) => {
120
241
  const host = req.get("host");
121
242
  if (!host) {
122
243
  throw new BadDataException("Host not found");
@@ -141,8 +262,8 @@ export default class StatusPageAPI extends BaseAPI {
141
262
  return Response.sendEmptySuccessResponse(req, res);
142
263
  });
143
264
  // ACME Challenge Validation.
144
- this.router.get(`${(_c = new this.entityType()
145
- .getCrudApiPath()) === null || _c === void 0 ? void 0 : _c.toString()}/.well-known/acme-challenge/:token`, async (req, res) => {
265
+ this.router.get(`${(_e = new this.entityType()
266
+ .getCrudApiPath()) === null || _e === void 0 ? void 0 : _e.toString()}/.well-known/acme-challenge/:token`, async (req, res) => {
146
267
  const challenge = await AcmeChallengeService.findOneBy({
147
268
  query: {
148
269
  token: req.params["token"],
@@ -159,7 +280,7 @@ export default class StatusPageAPI extends BaseAPI {
159
280
  }
160
281
  return Response.sendTextResponse(req, res, challenge.challenge);
161
282
  });
162
- this.router.post(`${(_d = new this.entityType().getCrudApiPath()) === null || _d === void 0 ? void 0 : _d.toString()}/test-email-report`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
283
+ this.router.post(`${(_f = new this.entityType().getCrudApiPath()) === null || _f === void 0 ? void 0 : _f.toString()}/test-email-report`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
163
284
  try {
164
285
  const email = new Email(req.body["email"]);
165
286
  const statusPageId = new ObjectID(req.body["statusPageId"].toString());
@@ -173,7 +294,7 @@ export default class StatusPageAPI extends BaseAPI {
173
294
  next(err);
174
295
  }
175
296
  });
176
- this.router.post(`${(_e = new this.entityType().getCrudApiPath()) === null || _e === void 0 ? void 0 : _e.toString()}/domain`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
297
+ this.router.post(`${(_g = new this.entityType().getCrudApiPath()) === null || _g === void 0 ? void 0 : _g.toString()}/domain`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
177
298
  try {
178
299
  if (!req.body["domain"]) {
179
300
  throw new BadDataException("domain is required in request body");
@@ -205,8 +326,8 @@ export default class StatusPageAPI extends BaseAPI {
205
326
  next(err);
206
327
  }
207
328
  });
208
- this.router.post(`${(_f = new this.entityType()
209
- .getCrudApiPath()) === null || _f === void 0 ? void 0 : _f.toString()}/master-page/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
329
+ this.router.post(`${(_h = new this.entityType()
330
+ .getCrudApiPath()) === null || _h === void 0 ? void 0 : _h.toString()}/master-page/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
210
331
  try {
211
332
  const objectId = new ObjectID(req.params["statusPageId"]);
212
333
  const select = {
@@ -320,7 +441,7 @@ export default class StatusPageAPI extends BaseAPI {
320
441
  next(err);
321
442
  }
322
443
  });
323
- this.router.post(`${(_g = new this.entityType().getCrudApiPath()) === null || _g === void 0 ? void 0 : _g.toString()}/sso/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
444
+ this.router.post(`${(_j = new this.entityType().getCrudApiPath()) === null || _j === void 0 ? void 0 : _j.toString()}/sso/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
324
445
  try {
325
446
  const objectId = new ObjectID(req.params["statusPageId"]);
326
447
  const sso = await StatusPageSsoService.findBy({
@@ -347,8 +468,8 @@ export default class StatusPageAPI extends BaseAPI {
347
468
  }
348
469
  });
349
470
  // Get all status page resources for subscriber to subscribe to.
350
- this.router.post(`${(_h = new this.entityType()
351
- .getCrudApiPath()) === null || _h === void 0 ? void 0 : _h.toString()}/resources/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
471
+ this.router.post(`${(_k = new this.entityType()
472
+ .getCrudApiPath()) === null || _k === void 0 ? void 0 : _k.toString()}/resources/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
352
473
  try {
353
474
  const objectId = new ObjectID(req.params["statusPageId"]);
354
475
  if (!(await this.service.hasReadAccess(objectId, await CommonAPI.getDatabaseCommonInteractionProps(req), req))) {
@@ -380,8 +501,8 @@ export default class StatusPageAPI extends BaseAPI {
380
501
  next(err);
381
502
  }
382
503
  });
383
- this.router.post(`${(_j = new this.entityType()
384
- .getCrudApiPath()) === null || _j === void 0 ? void 0 : _j.toString()}/uptime/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
504
+ this.router.post(`${(_l = new this.entityType()
505
+ .getCrudApiPath()) === null || _l === void 0 ? void 0 : _l.toString()}/uptime/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
385
506
  try {
386
507
  // This reosurce ID can be of a status page resource OR a status page group.
387
508
  const statusPageResourceId = new ObjectID(req.params["statusPageResourceId"]);
@@ -558,8 +679,8 @@ export default class StatusPageAPI extends BaseAPI {
558
679
  next(err);
559
680
  }
560
681
  });
561
- this.router.post(`${(_k = new this.entityType()
562
- .getCrudApiPath()) === null || _k === void 0 ? void 0 : _k.toString()}/overview/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
682
+ this.router.post(`${(_m = new this.entityType()
683
+ .getCrudApiPath()) === null || _m === void 0 ? void 0 : _m.toString()}/overview/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
563
684
  try {
564
685
  const objectId = new ObjectID(req.params["statusPageId"]);
565
686
  if (!(await this.service.hasReadAccess(objectId, await CommonAPI.getDatabaseCommonInteractionProps(req), req))) {
@@ -886,8 +1007,8 @@ export default class StatusPageAPI extends BaseAPI {
886
1007
  next(err);
887
1008
  }
888
1009
  });
889
- this.router.put(`${(_l = new this.entityType()
890
- .getCrudApiPath()) === null || _l === void 0 ? void 0 : _l.toString()}/update-subscription/:statusPageId/:subscriberId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1010
+ this.router.put(`${(_o = new this.entityType()
1011
+ .getCrudApiPath()) === null || _o === void 0 ? void 0 : _o.toString()}/update-subscription/:statusPageId/:subscriberId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
891
1012
  try {
892
1013
  await this.subscribeToStatusPage(req);
893
1014
  return Response.sendEmptySuccessResponse(req, res);
@@ -896,8 +1017,8 @@ export default class StatusPageAPI extends BaseAPI {
896
1017
  next(err);
897
1018
  }
898
1019
  });
899
- this.router.post(`${(_m = new this.entityType()
900
- .getCrudApiPath()) === null || _m === void 0 ? void 0 : _m.toString()}/get-subscription/:statusPageId/:subscriberId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1020
+ this.router.post(`${(_p = new this.entityType()
1021
+ .getCrudApiPath()) === null || _p === void 0 ? void 0 : _p.toString()}/get-subscription/:statusPageId/:subscriberId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
901
1022
  try {
902
1023
  const subscriber = await this.getSubscriber(req);
903
1024
  return Response.sendEntityResponse(req, res, subscriber, StatusPageSubscriber);
@@ -906,8 +1027,8 @@ export default class StatusPageAPI extends BaseAPI {
906
1027
  next(err);
907
1028
  }
908
1029
  });
909
- this.router.post(`${(_o = new this.entityType()
910
- .getCrudApiPath()) === null || _o === void 0 ? void 0 : _o.toString()}/subscribe/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1030
+ this.router.post(`${(_q = new this.entityType()
1031
+ .getCrudApiPath()) === null || _q === void 0 ? void 0 : _q.toString()}/subscribe/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
911
1032
  try {
912
1033
  await this.subscribeToStatusPage(req);
913
1034
  return Response.sendEmptySuccessResponse(req, res);
@@ -916,8 +1037,18 @@ export default class StatusPageAPI extends BaseAPI {
916
1037
  next(err);
917
1038
  }
918
1039
  });
919
- this.router.post(`${(_p = new this.entityType()
920
- .getCrudApiPath()) === null || _p === void 0 ? void 0 : _p.toString()}/incidents/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1040
+ this.router.post(`${(_r = new this.entityType()
1041
+ .getCrudApiPath()) === null || _r === void 0 ? void 0 : _r.toString()}/manage-subscription/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1042
+ try {
1043
+ await this.manageExistingSubscription(req);
1044
+ return Response.sendEmptySuccessResponse(req, res);
1045
+ }
1046
+ catch (err) {
1047
+ next(err);
1048
+ }
1049
+ });
1050
+ this.router.post(`${(_s = new this.entityType()
1051
+ .getCrudApiPath()) === null || _s === void 0 ? void 0 : _s.toString()}/incidents/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
921
1052
  try {
922
1053
  const objectId = new ObjectID(req.params["statusPageId"]);
923
1054
  const response = await this.getIncidents(objectId, null, await CommonAPI.getDatabaseCommonInteractionProps(req), req);
@@ -927,8 +1058,8 @@ export default class StatusPageAPI extends BaseAPI {
927
1058
  next(err);
928
1059
  }
929
1060
  });
930
- this.router.post(`${(_q = new this.entityType()
931
- .getCrudApiPath()) === null || _q === void 0 ? void 0 : _q.toString()}/scheduled-maintenance-events/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1061
+ this.router.post(`${(_t = new this.entityType()
1062
+ .getCrudApiPath()) === null || _t === void 0 ? void 0 : _t.toString()}/scheduled-maintenance-events/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
932
1063
  try {
933
1064
  const objectId = new ObjectID(req.params["statusPageId"]);
934
1065
  const response = await this.getScheduledMaintenanceEvents(objectId, null, await CommonAPI.getDatabaseCommonInteractionProps(req), req);
@@ -938,8 +1069,8 @@ export default class StatusPageAPI extends BaseAPI {
938
1069
  next(err);
939
1070
  }
940
1071
  });
941
- this.router.post(`${(_r = new this.entityType()
942
- .getCrudApiPath()) === null || _r === void 0 ? void 0 : _r.toString()}/announcements/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1072
+ this.router.post(`${(_u = new this.entityType()
1073
+ .getCrudApiPath()) === null || _u === void 0 ? void 0 : _u.toString()}/announcements/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
943
1074
  try {
944
1075
  const objectId = new ObjectID(req.params["statusPageId"]);
945
1076
  const response = await this.getAnnouncements(objectId, null, await CommonAPI.getDatabaseCommonInteractionProps(req), req);
@@ -949,8 +1080,8 @@ export default class StatusPageAPI extends BaseAPI {
949
1080
  next(err);
950
1081
  }
951
1082
  });
952
- this.router.post(`${(_s = new this.entityType()
953
- .getCrudApiPath()) === null || _s === void 0 ? void 0 : _s.toString()}/incidents/:statusPageId/:incidentId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1083
+ this.router.post(`${(_v = new this.entityType()
1084
+ .getCrudApiPath()) === null || _v === void 0 ? void 0 : _v.toString()}/incidents/:statusPageId/:incidentId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
954
1085
  try {
955
1086
  const objectId = new ObjectID(req.params["statusPageId"]);
956
1087
  const incidentId = new ObjectID(req.params["incidentId"]);
@@ -961,8 +1092,8 @@ export default class StatusPageAPI extends BaseAPI {
961
1092
  next(err);
962
1093
  }
963
1094
  });
964
- this.router.post(`${(_t = new this.entityType()
965
- .getCrudApiPath()) === null || _t === void 0 ? void 0 : _t.toString()}/scheduled-maintenance-events/:statusPageId/:scheduledMaintenanceId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1095
+ this.router.post(`${(_w = new this.entityType()
1096
+ .getCrudApiPath()) === null || _w === void 0 ? void 0 : _w.toString()}/scheduled-maintenance-events/:statusPageId/:scheduledMaintenanceId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
966
1097
  try {
967
1098
  const objectId = new ObjectID(req.params["statusPageId"]);
968
1099
  const scheduledMaintenanceId = new ObjectID(req.params["scheduledMaintenanceId"]);
@@ -973,8 +1104,8 @@ export default class StatusPageAPI extends BaseAPI {
973
1104
  next(err);
974
1105
  }
975
1106
  });
976
- this.router.post(`${(_u = new this.entityType()
977
- .getCrudApiPath()) === null || _u === void 0 ? void 0 : _u.toString()}/announcements/:statusPageId/:announcementId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
1107
+ this.router.post(`${(_x = new this.entityType()
1108
+ .getCrudApiPath()) === null || _x === void 0 ? void 0 : _x.toString()}/announcements/:statusPageId/:announcementId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
978
1109
  try {
979
1110
  const objectId = new ObjectID(req.params["statusPageId"]);
980
1111
  const announcementId = new ObjectID(req.params["announcementId"]);
@@ -1310,6 +1441,153 @@ export default class StatusPageAPI extends BaseAPI {
1310
1441
  };
1311
1442
  return response;
1312
1443
  }
1444
+ async manageExistingSubscription(req) {
1445
+ const objectId = new ObjectID(req.params["statusPageId"]);
1446
+ logger.debug(`Managing Existing Subscription for Status Page: ${objectId}`);
1447
+ if (!(await this.service.hasReadAccess(objectId, await CommonAPI.getDatabaseCommonInteractionProps(req), req))) {
1448
+ logger.debug(`No read access to status page with ID: ${objectId}`);
1449
+ throw new NotAuthenticatedException("You are not authenticated to access this status page");
1450
+ }
1451
+ const statusPage = await StatusPageService.findOneBy({
1452
+ query: {
1453
+ _id: objectId.toString(),
1454
+ },
1455
+ select: {
1456
+ _id: true,
1457
+ projectId: true,
1458
+ enableEmailSubscribers: true,
1459
+ enableSmsSubscribers: true,
1460
+ allowSubscribersToChooseResources: true,
1461
+ allowSubscribersToChooseEventTypes: true,
1462
+ showSubscriberPageOnStatusPage: true,
1463
+ },
1464
+ props: {
1465
+ isRoot: true,
1466
+ },
1467
+ });
1468
+ if (!statusPage) {
1469
+ logger.debug(`Status page not found with ID: ${objectId}`);
1470
+ throw new BadDataException("Status Page not found");
1471
+ }
1472
+ if (!statusPage.showSubscriberPageOnStatusPage) {
1473
+ logger.debug(`Subscriber page not enabled for status page with ID: ${objectId}`);
1474
+ throw new BadDataException("Subscribes not enabled for this status page.");
1475
+ }
1476
+ logger.debug(`Status page found: ${JSON.stringify(statusPage)}`);
1477
+ if (req.body.data["subscriberEmail"] &&
1478
+ !statusPage.enableEmailSubscribers) {
1479
+ logger.debug(`Email subscribers not enabled for status page with ID: ${objectId}`);
1480
+ throw new BadDataException("Email subscribers not enabled for this status page.");
1481
+ }
1482
+ if (req.body.data["subscriberPhone"] && !statusPage.enableSmsSubscribers) {
1483
+ logger.debug(`SMS subscribers not enabled for status page with ID: ${objectId}`);
1484
+ throw new BadDataException("SMS subscribers not enabled for this status page.");
1485
+ }
1486
+ // if no email or phone, throw error.
1487
+ if (!req.body.data["subscriberEmail"] &&
1488
+ !req.body.data["subscriberPhone"]) {
1489
+ logger.debug(`No email or phone provided for subscription to status page with ID: ${objectId}`);
1490
+ throw new BadDataException("Email or phone is required to subscribe to this status page.");
1491
+ }
1492
+ const email = req.body.data["subscriberEmail"]
1493
+ ? new Email(req.body.data["subscriberEmail"])
1494
+ : undefined;
1495
+ const phone = req.body.data["subscriberPhone"]
1496
+ ? new Phone(req.body.data["subscriberPhone"])
1497
+ : undefined;
1498
+ let statusPageSubscriber = null;
1499
+ if (email) {
1500
+ logger.debug(`Setting subscriber email: ${email}`);
1501
+ statusPageSubscriber = await StatusPageSubscriberService.findOneBy({
1502
+ query: {
1503
+ subscriberEmail: email,
1504
+ statusPageId: objectId,
1505
+ },
1506
+ select: {
1507
+ _id: true,
1508
+ subscriberEmail: true,
1509
+ },
1510
+ props: {
1511
+ isRoot: true,
1512
+ },
1513
+ });
1514
+ }
1515
+ if (phone) {
1516
+ logger.debug(`Setting subscriber phone: ${phone}`);
1517
+ statusPageSubscriber = await StatusPageSubscriberService.findOneBy({
1518
+ query: {
1519
+ subscriberPhone: phone,
1520
+ statusPageId: objectId,
1521
+ },
1522
+ select: {
1523
+ _id: true,
1524
+ subscriberPhone: true,
1525
+ },
1526
+ props: {
1527
+ isRoot: true,
1528
+ },
1529
+ });
1530
+ }
1531
+ if (!statusPageSubscriber) {
1532
+ // not found, return bad data
1533
+ logger.debug(`Subscriber not found for email: ${email} or phone: ${phone}`);
1534
+ let emailOrPhone = "email";
1535
+ if (phone) {
1536
+ emailOrPhone = "phone";
1537
+ }
1538
+ throw new BadDataException(`Subscription not found for this status page. Please make sure your ${emailOrPhone} is correct.`);
1539
+ }
1540
+ const statusPageURL = await StatusPageService.getStatusPageURL(objectId);
1541
+ const manageUrlink = StatusPageSubscriberService.getUnsubscribeLink(URL.fromString(statusPageURL), statusPageSubscriber.id).toString();
1542
+ const statusPages = await StatusPageSubscriberService.getStatusPagesToSendNotification([
1543
+ objectId,
1544
+ ]);
1545
+ for (const statusPage of statusPages) {
1546
+ // send email to subscriber or sms if phone is provided.
1547
+ if (email) {
1548
+ const host = await DatabaseConfig.getHost();
1549
+ const httpProtocol = await DatabaseConfig.getHttpProtocol();
1550
+ MailService.sendMail({
1551
+ toEmail: email,
1552
+ templateType: EmailTemplateType.ManageExistingStatusPageSubscriberSubscription,
1553
+ vars: {
1554
+ statusPageName: statusPage.name || "Status Page",
1555
+ statusPageUrl: statusPageURL,
1556
+ logoUrl: statusPage.logoFileId
1557
+ ? new URL(httpProtocol, host)
1558
+ .addRoute(FileRoute)
1559
+ .addRoute("/image/" + statusPage.logoFileId)
1560
+ .toString()
1561
+ : "",
1562
+ isPublicStatusPage: statusPage.isPublicStatusPage
1563
+ ? "true"
1564
+ : "false",
1565
+ subscriberEmailNotificationFooterText: statusPage.subscriberEmailNotificationFooterText || "",
1566
+ manageSubscriptionUrl: manageUrlink,
1567
+ },
1568
+ subject: "Manage your Subscription for" +
1569
+ (statusPage.name || "Status Page"),
1570
+ }, {
1571
+ mailServer: ProjectSmtpConfigService.toEmailServer(statusPage.smtpConfig),
1572
+ projectId: statusPage.projectId,
1573
+ });
1574
+ }
1575
+ if (phone) {
1576
+ const sms = {
1577
+ message: `You have selected to manage your subscription for the status page: ${statusPage.name}. You can manage your subscription here: ${manageUrlink}`,
1578
+ to: phone,
1579
+ };
1580
+ // send sms here.
1581
+ SmsService.sendSms(sms, {
1582
+ projectId: statusPage.projectId,
1583
+ customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig(statusPage.callSmsConfig),
1584
+ }).catch((err) => {
1585
+ logger.error(err);
1586
+ });
1587
+ }
1588
+ logger.debug(`Subscription management link sent to subscriber with ID: ${statusPageSubscriber.id}`);
1589
+ }
1590
+ }
1313
1591
  async subscribeToStatusPage(req) {
1314
1592
  const objectId = new ObjectID(req.params["statusPageId"]);
1315
1593
  logger.debug(`Subscribing to status page with ID: ${objectId}`);
@@ -1933,6 +2211,12 @@ __decorate([
1933
2211
  __metadata("design:paramtypes", [ObjectID, Object, Object, Object]),
1934
2212
  __metadata("design:returntype", Promise)
1935
2213
  ], StatusPageAPI.prototype, "getAnnouncements", null);
2214
+ __decorate([
2215
+ CaptureSpan(),
2216
+ __metadata("design:type", Function),
2217
+ __metadata("design:paramtypes", [Object]),
2218
+ __metadata("design:returntype", Promise)
2219
+ ], StatusPageAPI.prototype, "manageExistingSubscription", null);
1936
2220
  __decorate([
1937
2221
  CaptureSpan(),
1938
2222
  __metadata("design:type", Function),