@oneuptime/common 9.5.2 → 9.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Alert.ts +1 -0
- package/Models/DatabaseModels/AlertEpisode.ts +1 -0
- package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/AlertStateTimeline.ts +1 -0
- package/Models/DatabaseModels/Incident.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +156 -0
- package/Models/DatabaseModels/IncidentEpisodeFeed.ts +2 -0
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +611 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +84 -0
- package/Models/DatabaseModels/IncidentGroupingRule.ts +36 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
- package/Models/DatabaseModels/Project.ts +2 -1
- package/Models/DatabaseModels/ProjectCallSMSConfig.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
- package/Models/DatabaseModels/StatusPage.ts +120 -0
- package/Server/API/IncidentEpisodePublicNoteAPI.ts +98 -0
- package/Server/API/StatusPageAPI.ts +1092 -45
- package/Server/Infrastructure/Postgres/SchemaMigrations/1770232207959-MigrationName.ts +181 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1770237245069-MigrationName.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/IncidentEpisodePublicNoteService.ts +254 -0
- package/Server/Services/IncidentEpisodeService.ts +26 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Utils/Monitor/MonitorIncident.ts +6 -0
- package/Types/Email/EmailTemplateType.ts +4 -0
- package/Types/Icon/IconProp.ts +172 -0
- package/Types/Monitor/CriteriaIncident.ts +2 -0
- package/Types/Permission.ts +40 -0
- package/Types/StatusPage/StatusPageSubscriberNotificationEventType.ts +5 -0
- package/UI/Components/Icon/Icon.tsx +1333 -1
- package/build/dist/Models/DatabaseModels/Alert.js +1 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertEpisode.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +1 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +161 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +626 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +86 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js +37 -0
- package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +2 -1
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +1 -0
- package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +126 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/IncidentEpisodePublicNoteAPI.js +68 -0
- package/build/dist/Server/API/IncidentEpisodePublicNoteAPI.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +874 -47
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770232207959-MigrationName.js +68 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770232207959-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770237245069-MigrationName.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770237245069-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +223 -0
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -0
- package/build/dist/Server/Services/IncidentEpisodeService.js +22 -0
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +5 -0
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +3 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Icon/IconProp.js +172 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaIncident.js +1 -0
- package/build/dist/Types/Monitor/CriteriaIncident.js.map +1 -1
- package/build/dist/Types/Permission.js +34 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js +4 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +502 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,6 +9,10 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
};
|
|
10
10
|
import UserMiddleware from "../Middleware/UserAuthorization";
|
|
11
11
|
import AcmeChallengeService from "../Services/AcmeChallengeService";
|
|
12
|
+
import IncidentEpisodeService from "../Services/IncidentEpisodeService";
|
|
13
|
+
import IncidentEpisodeMemberService from "../Services/IncidentEpisodeMemberService";
|
|
14
|
+
import IncidentEpisodePublicNoteService from "../Services/IncidentEpisodePublicNoteService";
|
|
15
|
+
import IncidentEpisodeStateTimelineService from "../Services/IncidentEpisodeStateTimelineService";
|
|
12
16
|
import IncidentPublicNoteService from "../Services/IncidentPublicNoteService";
|
|
13
17
|
import IncidentService from "../Services/IncidentService";
|
|
14
18
|
import IncidentStateService from "../Services/IncidentStateService";
|
|
@@ -49,6 +53,9 @@ import Phone from "../../Types/Phone";
|
|
|
49
53
|
import PositiveNumber from "../../Types/PositiveNumber";
|
|
50
54
|
import HashedString from "../../Types/HashedString";
|
|
51
55
|
import Incident from "../../Models/DatabaseModels/Incident";
|
|
56
|
+
import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
|
|
57
|
+
import IncidentEpisodePublicNote from "../../Models/DatabaseModels/IncidentEpisodePublicNote";
|
|
58
|
+
import IncidentEpisodeStateTimeline from "../../Models/DatabaseModels/IncidentEpisodeStateTimeline";
|
|
52
59
|
import IncidentPublicNote from "../../Models/DatabaseModels/IncidentPublicNote";
|
|
53
60
|
import IncidentState from "../../Models/DatabaseModels/IncidentState";
|
|
54
61
|
import IncidentStateTimeline from "../../Models/DatabaseModels/IncidentStateTimeline";
|
|
@@ -120,7 +127,7 @@ const resolveStatusPageIdOrThrow = async (statusPageIdOrDomain) => {
|
|
|
120
127
|
};
|
|
121
128
|
export default class StatusPageAPI extends BaseAPI {
|
|
122
129
|
constructor() {
|
|
123
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5;
|
|
130
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8;
|
|
124
131
|
super(StatusPage, StatusPageService);
|
|
125
132
|
// get title, description of the page. This is used for SEO.
|
|
126
133
|
this.router.get(`${(_a = new this.entityType().getCrudApiPath()) === null || _a === void 0 ? void 0 : _a.toString()}/seo/:statusPageIdOrDomain`, UserMiddleware.getUserMiddleware, async (req, res) => {
|
|
@@ -290,25 +297,34 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
290
297
|
}
|
|
291
298
|
});
|
|
292
299
|
this.router.get(`${(_f = new this.entityType()
|
|
293
|
-
.getCrudApiPath()) === null || _f === void 0 ? void 0 : _f.toString()}/incident/
|
|
300
|
+
.getCrudApiPath()) === null || _f === void 0 ? void 0 : _f.toString()}/incident-episode-public-note/attachment/:statusPageId/:episodeId/:noteId/:fileId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
294
301
|
try {
|
|
295
|
-
await this.
|
|
302
|
+
await this.getIncidentEpisodePublicNoteAttachment(req, res);
|
|
296
303
|
}
|
|
297
304
|
catch (err) {
|
|
298
305
|
next(err);
|
|
299
306
|
}
|
|
300
307
|
});
|
|
301
308
|
this.router.get(`${(_g = new this.entityType()
|
|
302
|
-
.getCrudApiPath()) === null || _g === void 0 ? void 0 : _g.toString()}/
|
|
309
|
+
.getCrudApiPath()) === null || _g === void 0 ? void 0 : _g.toString()}/incident/postmortem/attachment/:statusPageId/:incidentId/:fileId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
303
310
|
try {
|
|
304
|
-
await this.
|
|
311
|
+
await this.getIncidentPostmortemAttachment(req, res);
|
|
305
312
|
}
|
|
306
313
|
catch (err) {
|
|
307
314
|
next(err);
|
|
308
315
|
}
|
|
309
316
|
});
|
|
310
317
|
this.router.get(`${(_h = new this.entityType()
|
|
311
|
-
.getCrudApiPath()) === null || _h === void 0 ? void 0 : _h.toString()}/
|
|
318
|
+
.getCrudApiPath()) === null || _h === void 0 ? void 0 : _h.toString()}/scheduled-maintenance-public-note/attachment/:statusPageId/:scheduledMaintenanceId/:noteId/:fileId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
319
|
+
try {
|
|
320
|
+
await this.getScheduledMaintenancePublicNoteAttachment(req, res);
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
next(err);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
this.router.get(`${(_j = new this.entityType()
|
|
327
|
+
.getCrudApiPath()) === null || _j === void 0 ? void 0 : _j.toString()}/status-page-announcement/attachment/:statusPageId/:announcementId/:fileId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
312
328
|
try {
|
|
313
329
|
await this.getStatusPageAnnouncementAttachment(req, res);
|
|
314
330
|
}
|
|
@@ -317,8 +333,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
317
333
|
}
|
|
318
334
|
});
|
|
319
335
|
// embedded overall status badge api
|
|
320
|
-
this.router.get(`${(
|
|
321
|
-
.getCrudApiPath()) === null ||
|
|
336
|
+
this.router.get(`${(_k = new this.entityType()
|
|
337
|
+
.getCrudApiPath()) === null || _k === void 0 ? void 0 : _k.toString()}/badge/:statusPageId`, async (req, res) => {
|
|
322
338
|
var _a;
|
|
323
339
|
try {
|
|
324
340
|
const statusPageId = new ObjectID(req.params["statusPageId"]);
|
|
@@ -431,8 +447,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
431
447
|
}
|
|
432
448
|
});
|
|
433
449
|
// confirm subscription api
|
|
434
|
-
this.router.get(`${(
|
|
435
|
-
.getCrudApiPath()) === null ||
|
|
450
|
+
this.router.get(`${(_l = new this.entityType()
|
|
451
|
+
.getCrudApiPath()) === null || _l === void 0 ? void 0 : _l.toString()}/confirm-subscription/:statusPageSubscriberId`, async (req, res) => {
|
|
436
452
|
const token = req.query["verification-token"];
|
|
437
453
|
const statusPageSubscriberId = new ObjectID(req.params["statusPageSubscriberId"]);
|
|
438
454
|
const subscriber = await StatusPageSubscriberService.findOneBy({
|
|
@@ -469,8 +485,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
469
485
|
return Response.sendEmptySuccessResponse(req, res);
|
|
470
486
|
});
|
|
471
487
|
// CNAME verification api
|
|
472
|
-
this.router.get(`${(
|
|
473
|
-
.getCrudApiPath()) === null ||
|
|
488
|
+
this.router.get(`${(_m = new this.entityType()
|
|
489
|
+
.getCrudApiPath()) === null || _m === void 0 ? void 0 : _m.toString()}/cname-verification/:token`, async (req, res) => {
|
|
474
490
|
const host = req.get("host");
|
|
475
491
|
if (!host) {
|
|
476
492
|
throw new BadDataException("Host not found");
|
|
@@ -495,8 +511,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
495
511
|
return Response.sendEmptySuccessResponse(req, res);
|
|
496
512
|
});
|
|
497
513
|
// ACME Challenge Validation.
|
|
498
|
-
this.router.get(`${(
|
|
499
|
-
.getCrudApiPath()) === null ||
|
|
514
|
+
this.router.get(`${(_o = new this.entityType()
|
|
515
|
+
.getCrudApiPath()) === null || _o === void 0 ? void 0 : _o.toString()}/.well-known/acme-challenge/:token`, async (req, res) => {
|
|
500
516
|
const challenge = await AcmeChallengeService.findOneBy({
|
|
501
517
|
query: {
|
|
502
518
|
token: req.params["token"],
|
|
@@ -513,7 +529,7 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
513
529
|
}
|
|
514
530
|
return Response.sendTextResponse(req, res, challenge.challenge);
|
|
515
531
|
});
|
|
516
|
-
this.router.post(`${(
|
|
532
|
+
this.router.post(`${(_p = new this.entityType().getCrudApiPath()) === null || _p === void 0 ? void 0 : _p.toString()}/test-email-report`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
517
533
|
try {
|
|
518
534
|
const email = new Email(req.body["email"]);
|
|
519
535
|
const statusPageId = new ObjectID(req.body["statusPageId"].toString());
|
|
@@ -527,7 +543,7 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
527
543
|
next(err);
|
|
528
544
|
}
|
|
529
545
|
});
|
|
530
|
-
this.router.post(`${(
|
|
546
|
+
this.router.post(`${(_q = new this.entityType().getCrudApiPath()) === null || _q === void 0 ? void 0 : _q.toString()}/domain`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
531
547
|
try {
|
|
532
548
|
if (!req.body["domain"]) {
|
|
533
549
|
throw new BadDataException("domain is required in request body");
|
|
@@ -559,8 +575,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
559
575
|
next(err);
|
|
560
576
|
}
|
|
561
577
|
});
|
|
562
|
-
this.router.post(`${(
|
|
563
|
-
.getCrudApiPath()) === null ||
|
|
578
|
+
this.router.post(`${(_r = new this.entityType()
|
|
579
|
+
.getCrudApiPath()) === null || _r === void 0 ? void 0 : _r.toString()}/master-page/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
564
580
|
try {
|
|
565
581
|
const objectId = new ObjectID(req.params["statusPageId"]);
|
|
566
582
|
const select = {
|
|
@@ -677,8 +693,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
677
693
|
next(err);
|
|
678
694
|
}
|
|
679
695
|
});
|
|
680
|
-
this.router.post(`${(
|
|
681
|
-
.getCrudApiPath()) === null ||
|
|
696
|
+
this.router.post(`${(_s = new this.entityType()
|
|
697
|
+
.getCrudApiPath()) === null || _s === void 0 ? void 0 : _s.toString()}/master-password/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
682
698
|
try {
|
|
683
699
|
if (!req.params["statusPageId"]) {
|
|
684
700
|
throw new BadDataException("Status Page ID not found");
|
|
@@ -724,7 +740,7 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
724
740
|
next(err);
|
|
725
741
|
}
|
|
726
742
|
});
|
|
727
|
-
this.router.post(`${(
|
|
743
|
+
this.router.post(`${(_t = new this.entityType().getCrudApiPath()) === null || _t === void 0 ? void 0 : _t.toString()}/sso/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
728
744
|
try {
|
|
729
745
|
const objectId = new ObjectID(req.params["statusPageId"]);
|
|
730
746
|
const sso = await StatusPageSsoService.findBy({
|
|
@@ -751,8 +767,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
751
767
|
}
|
|
752
768
|
});
|
|
753
769
|
// Get all status page resources for subscriber to subscribe to.
|
|
754
|
-
this.router.post(`${(
|
|
755
|
-
.getCrudApiPath()) === null ||
|
|
770
|
+
this.router.post(`${(_u = new this.entityType()
|
|
771
|
+
.getCrudApiPath()) === null || _u === void 0 ? void 0 : _u.toString()}/resources/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
756
772
|
try {
|
|
757
773
|
const statusPageId = new ObjectID(req.params["statusPageId"]);
|
|
758
774
|
await this.checkHasReadAccess({
|
|
@@ -785,8 +801,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
785
801
|
next(err);
|
|
786
802
|
}
|
|
787
803
|
});
|
|
788
|
-
this.router.post(`${(
|
|
789
|
-
.getCrudApiPath()) === null ||
|
|
804
|
+
this.router.post(`${(_v = new this.entityType()
|
|
805
|
+
.getCrudApiPath()) === null || _v === void 0 ? void 0 : _v.toString()}/uptime/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
790
806
|
try {
|
|
791
807
|
// This reosurce ID can be of a status page resource OR a status page group.
|
|
792
808
|
const statusPageResourceId = new ObjectID(req.params["statusPageResourceId"]);
|
|
@@ -966,8 +982,9 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
966
982
|
next(err);
|
|
967
983
|
}
|
|
968
984
|
});
|
|
969
|
-
this.router.post(`${(
|
|
970
|
-
.getCrudApiPath()) === null ||
|
|
985
|
+
this.router.post(`${(_w = new this.entityType()
|
|
986
|
+
.getCrudApiPath()) === null || _w === void 0 ? void 0 : _w.toString()}/overview/:statusPageIdOrDomain`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
987
|
+
var _a;
|
|
971
988
|
try {
|
|
972
989
|
const statusPageId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
|
|
973
990
|
await this.checkHasReadAccess({
|
|
@@ -1105,6 +1122,260 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1105
1122
|
},
|
|
1106
1123
|
});
|
|
1107
1124
|
}
|
|
1125
|
+
// Fetch active episodes (similar to incidents)
|
|
1126
|
+
let activeEpisodes = [];
|
|
1127
|
+
let activeEpisodesJson = [];
|
|
1128
|
+
let episodePublicNotes = [];
|
|
1129
|
+
let episodeStateTimelines = [];
|
|
1130
|
+
if (statusPage.showEpisodesOnStatusPage &&
|
|
1131
|
+
monitorsOnStatusPage.length > 0) {
|
|
1132
|
+
// First, get incidents that have monitors on status page
|
|
1133
|
+
const incidentsForEpisodes = await IncidentService.findBy({
|
|
1134
|
+
query: {
|
|
1135
|
+
monitors: monitorsOnStatusPage,
|
|
1136
|
+
projectId: statusPage.projectId,
|
|
1137
|
+
},
|
|
1138
|
+
select: {
|
|
1139
|
+
_id: true,
|
|
1140
|
+
},
|
|
1141
|
+
skip: 0,
|
|
1142
|
+
limit: LIMIT_PER_PROJECT,
|
|
1143
|
+
props: {
|
|
1144
|
+
isRoot: true,
|
|
1145
|
+
},
|
|
1146
|
+
});
|
|
1147
|
+
const incidentIdsForEpisodes = incidentsForEpisodes.map((incident) => {
|
|
1148
|
+
return incident.id;
|
|
1149
|
+
});
|
|
1150
|
+
// Get episode members for these incidents
|
|
1151
|
+
let episodeMembers = [];
|
|
1152
|
+
if (incidentIdsForEpisodes.length > 0) {
|
|
1153
|
+
episodeMembers = await IncidentEpisodeMemberService.findBy({
|
|
1154
|
+
query: {
|
|
1155
|
+
incidentId: QueryHelper.any(incidentIdsForEpisodes),
|
|
1156
|
+
projectId: statusPage.projectId,
|
|
1157
|
+
},
|
|
1158
|
+
select: {
|
|
1159
|
+
incidentEpisodeId: true,
|
|
1160
|
+
incidentId: true,
|
|
1161
|
+
},
|
|
1162
|
+
skip: 0,
|
|
1163
|
+
limit: LIMIT_PER_PROJECT,
|
|
1164
|
+
props: {
|
|
1165
|
+
isRoot: true,
|
|
1166
|
+
},
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
// Get unique episode IDs
|
|
1170
|
+
const episodeIdsFromMembers = new Set();
|
|
1171
|
+
for (const member of episodeMembers) {
|
|
1172
|
+
if (member.incidentEpisodeId) {
|
|
1173
|
+
episodeIdsFromMembers.add(member.incidentEpisodeId.toString());
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
// Fetch active (unresolved) episodes
|
|
1177
|
+
if (episodeIdsFromMembers.size > 0) {
|
|
1178
|
+
const unresolvedIncidentStates = await IncidentStateService.getUnresolvedIncidentStates(statusPage.projectId, { isRoot: true });
|
|
1179
|
+
const unresolvedIncidentStateIds = unresolvedIncidentStates.map((state) => {
|
|
1180
|
+
return state.id;
|
|
1181
|
+
});
|
|
1182
|
+
let selectEpisodes = {
|
|
1183
|
+
createdAt: true,
|
|
1184
|
+
declaredAt: true,
|
|
1185
|
+
updatedAt: true,
|
|
1186
|
+
title: true,
|
|
1187
|
+
description: true,
|
|
1188
|
+
_id: true,
|
|
1189
|
+
episodeNumber: true,
|
|
1190
|
+
incidentSeverity: {
|
|
1191
|
+
name: true,
|
|
1192
|
+
color: true,
|
|
1193
|
+
},
|
|
1194
|
+
currentIncidentState: {
|
|
1195
|
+
name: true,
|
|
1196
|
+
color: true,
|
|
1197
|
+
_id: true,
|
|
1198
|
+
order: true,
|
|
1199
|
+
isCreatedState: true,
|
|
1200
|
+
isAcknowledgedState: true,
|
|
1201
|
+
isResolvedState: true,
|
|
1202
|
+
},
|
|
1203
|
+
incidentCount: true,
|
|
1204
|
+
};
|
|
1205
|
+
if (statusPage.showEpisodeLabelsOnStatusPage) {
|
|
1206
|
+
selectEpisodes = Object.assign(Object.assign({}, selectEpisodes), { labels: {
|
|
1207
|
+
name: true,
|
|
1208
|
+
color: true,
|
|
1209
|
+
} });
|
|
1210
|
+
}
|
|
1211
|
+
activeEpisodes = await IncidentEpisodeService.findBy({
|
|
1212
|
+
query: {
|
|
1213
|
+
_id: QueryHelper.any(Array.from(episodeIdsFromMembers).map((id) => {
|
|
1214
|
+
return new ObjectID(id);
|
|
1215
|
+
})),
|
|
1216
|
+
currentIncidentStateId: QueryHelper.any(unresolvedIncidentStateIds),
|
|
1217
|
+
isVisibleOnStatusPage: true,
|
|
1218
|
+
projectId: statusPage.projectId,
|
|
1219
|
+
},
|
|
1220
|
+
select: selectEpisodes,
|
|
1221
|
+
sort: {
|
|
1222
|
+
declaredAt: SortOrder.Descending,
|
|
1223
|
+
createdAt: SortOrder.Descending,
|
|
1224
|
+
},
|
|
1225
|
+
skip: 0,
|
|
1226
|
+
limit: LIMIT_PER_PROJECT,
|
|
1227
|
+
props: {
|
|
1228
|
+
isRoot: true,
|
|
1229
|
+
},
|
|
1230
|
+
});
|
|
1231
|
+
// Build episode monitors map
|
|
1232
|
+
if (activeEpisodes.length > 0) {
|
|
1233
|
+
// Collect all incident IDs from episode members for active episodes
|
|
1234
|
+
const activeEpisodeIds = new Set(activeEpisodes.map((e) => {
|
|
1235
|
+
return e.id.toString();
|
|
1236
|
+
}));
|
|
1237
|
+
const memberIncidentIds = [];
|
|
1238
|
+
for (const member of episodeMembers) {
|
|
1239
|
+
if (member.incidentEpisodeId &&
|
|
1240
|
+
activeEpisodeIds.has(member.incidentEpisodeId.toString()) &&
|
|
1241
|
+
member.incidentId &&
|
|
1242
|
+
!memberIncidentIds.some((id) => {
|
|
1243
|
+
return id.toString() === member.incidentId.toString();
|
|
1244
|
+
})) {
|
|
1245
|
+
memberIncidentIds.push(member.incidentId);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
// Fetch incidents with monitors
|
|
1249
|
+
let memberIncidents = [];
|
|
1250
|
+
if (memberIncidentIds.length > 0) {
|
|
1251
|
+
memberIncidents = await IncidentService.findBy({
|
|
1252
|
+
query: {
|
|
1253
|
+
_id: QueryHelper.any(memberIncidentIds),
|
|
1254
|
+
projectId: statusPage.projectId,
|
|
1255
|
+
},
|
|
1256
|
+
select: {
|
|
1257
|
+
_id: true,
|
|
1258
|
+
monitors: {
|
|
1259
|
+
_id: true,
|
|
1260
|
+
},
|
|
1261
|
+
},
|
|
1262
|
+
skip: 0,
|
|
1263
|
+
limit: LIMIT_PER_PROJECT,
|
|
1264
|
+
props: {
|
|
1265
|
+
isRoot: true,
|
|
1266
|
+
},
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
// Build incident -> monitors map
|
|
1270
|
+
const incidentMonitorsMap = new Map();
|
|
1271
|
+
for (const incident of memberIncidents) {
|
|
1272
|
+
const incidentIdStr = incident.id.toString();
|
|
1273
|
+
const monitorIds = (incident.monitors || [])
|
|
1274
|
+
.map((m) => {
|
|
1275
|
+
var _a, _b;
|
|
1276
|
+
return new ObjectID(((_a = m._id) === null || _a === void 0 ? void 0 : _a.toString()) || ((_b = m.id) === null || _b === void 0 ? void 0 : _b.toString()) || "");
|
|
1277
|
+
})
|
|
1278
|
+
.filter((id) => {
|
|
1279
|
+
return id.toString() !== "";
|
|
1280
|
+
});
|
|
1281
|
+
incidentMonitorsMap.set(incidentIdStr, monitorIds);
|
|
1282
|
+
}
|
|
1283
|
+
// Build episode -> monitors map
|
|
1284
|
+
const episodeMonitorsMap = new Map();
|
|
1285
|
+
for (const member of episodeMembers) {
|
|
1286
|
+
if (member.incidentEpisodeId &&
|
|
1287
|
+
member.incidentId &&
|
|
1288
|
+
activeEpisodeIds.has(member.incidentEpisodeId.toString())) {
|
|
1289
|
+
const episodeIdStr = member.incidentEpisodeId.toString();
|
|
1290
|
+
const incidentIdStr = member.incidentId.toString();
|
|
1291
|
+
if (!episodeMonitorsMap.has(episodeIdStr)) {
|
|
1292
|
+
episodeMonitorsMap.set(episodeIdStr, []);
|
|
1293
|
+
}
|
|
1294
|
+
const episodeMonitors = episodeMonitorsMap.get(episodeIdStr);
|
|
1295
|
+
const incidentMonitors = incidentMonitorsMap.get(incidentIdStr) || [];
|
|
1296
|
+
for (const monitorId of incidentMonitors) {
|
|
1297
|
+
if (!episodeMonitors.some((m) => {
|
|
1298
|
+
return m.toString() === monitorId.toString();
|
|
1299
|
+
})) {
|
|
1300
|
+
episodeMonitors.push(monitorId);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
// Serialize episodes and add monitors
|
|
1306
|
+
activeEpisodesJson = BaseModel.toJSONArray(activeEpisodes, IncidentEpisode);
|
|
1307
|
+
for (const episodeJson of activeEpisodesJson) {
|
|
1308
|
+
const episodeObj = episodeJson;
|
|
1309
|
+
const episodeId = (_a = episodeObj["_id"]) === null || _a === void 0 ? void 0 : _a.toString();
|
|
1310
|
+
if (episodeId) {
|
|
1311
|
+
const monitorIds = episodeMonitorsMap.get(episodeId) || [];
|
|
1312
|
+
episodeObj["monitors"] = monitorIds.map((id) => {
|
|
1313
|
+
return { _id: id.toString() };
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
// Get episode public notes
|
|
1318
|
+
const episodesOnStatusPage = activeEpisodes.map((episode) => {
|
|
1319
|
+
return episode.id;
|
|
1320
|
+
});
|
|
1321
|
+
if (episodesOnStatusPage.length > 0) {
|
|
1322
|
+
episodePublicNotes =
|
|
1323
|
+
await IncidentEpisodePublicNoteService.findBy({
|
|
1324
|
+
query: {
|
|
1325
|
+
incidentEpisodeId: QueryHelper.any(episodesOnStatusPage),
|
|
1326
|
+
projectId: statusPage.projectId,
|
|
1327
|
+
},
|
|
1328
|
+
select: {
|
|
1329
|
+
postedAt: true,
|
|
1330
|
+
note: true,
|
|
1331
|
+
incidentEpisodeId: true,
|
|
1332
|
+
attachments: {
|
|
1333
|
+
_id: true,
|
|
1334
|
+
name: true,
|
|
1335
|
+
},
|
|
1336
|
+
},
|
|
1337
|
+
sort: {
|
|
1338
|
+
postedAt: SortOrder.Descending,
|
|
1339
|
+
},
|
|
1340
|
+
skip: 0,
|
|
1341
|
+
limit: LIMIT_PER_PROJECT,
|
|
1342
|
+
props: {
|
|
1343
|
+
isRoot: true,
|
|
1344
|
+
},
|
|
1345
|
+
});
|
|
1346
|
+
// Get episode state timelines
|
|
1347
|
+
episodeStateTimelines =
|
|
1348
|
+
await IncidentEpisodeStateTimelineService.findBy({
|
|
1349
|
+
query: {
|
|
1350
|
+
incidentEpisodeId: QueryHelper.any(episodesOnStatusPage),
|
|
1351
|
+
projectId: statusPage.projectId,
|
|
1352
|
+
},
|
|
1353
|
+
select: {
|
|
1354
|
+
_id: true,
|
|
1355
|
+
createdAt: true,
|
|
1356
|
+
startsAt: true,
|
|
1357
|
+
incidentEpisodeId: true,
|
|
1358
|
+
incidentState: {
|
|
1359
|
+
name: true,
|
|
1360
|
+
color: true,
|
|
1361
|
+
isCreatedState: true,
|
|
1362
|
+
isAcknowledgedState: true,
|
|
1363
|
+
isResolvedState: true,
|
|
1364
|
+
},
|
|
1365
|
+
},
|
|
1366
|
+
sort: {
|
|
1367
|
+
startsAt: SortOrder.Descending,
|
|
1368
|
+
},
|
|
1369
|
+
skip: 0,
|
|
1370
|
+
limit: LIMIT_PER_PROJECT,
|
|
1371
|
+
props: {
|
|
1372
|
+
isRoot: true,
|
|
1373
|
+
},
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1108
1379
|
// check if status page has active announcement.
|
|
1109
1380
|
const today = OneUptimeDate.getCurrentDate();
|
|
1110
1381
|
let activeAnnouncements = [];
|
|
@@ -1303,6 +1574,9 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1303
1574
|
activeAnnouncements: BaseModel.toJSONArray(activeAnnouncements, StatusPageAnnouncement),
|
|
1304
1575
|
incidentPublicNotes: BaseModel.toJSONArray(incidentPublicNotes, IncidentPublicNote),
|
|
1305
1576
|
activeIncidents: BaseModel.toJSONArray(activeIncidents, Incident),
|
|
1577
|
+
activeEpisodes: activeEpisodesJson,
|
|
1578
|
+
episodePublicNotes: BaseModel.toJSONArray(episodePublicNotes, IncidentEpisodePublicNote),
|
|
1579
|
+
episodeStateTimelines: BaseModel.toJSONArray(episodeStateTimelines, IncidentEpisodeStateTimeline),
|
|
1306
1580
|
monitorStatusTimelines: BaseModel.toJSONArray(monitorStatusTimelines, MonitorStatusTimeline),
|
|
1307
1581
|
resourceGroups: BaseModel.toJSONArray(statusPageGroups, StatusPageGroup),
|
|
1308
1582
|
monitorStatuses: BaseModel.toJSONArray(monitorStatuses, MonitorStatus),
|
|
@@ -1319,8 +1593,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1319
1593
|
next(err);
|
|
1320
1594
|
}
|
|
1321
1595
|
});
|
|
1322
|
-
this.router.put(`${(
|
|
1323
|
-
.getCrudApiPath()) === null ||
|
|
1596
|
+
this.router.put(`${(_x = new this.entityType()
|
|
1597
|
+
.getCrudApiPath()) === null || _x === void 0 ? void 0 : _x.toString()}/update-subscription/:statusPageId/:subscriberId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1324
1598
|
try {
|
|
1325
1599
|
await this.subscribeToStatusPage(req);
|
|
1326
1600
|
return Response.sendEmptySuccessResponse(req, res);
|
|
@@ -1329,8 +1603,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1329
1603
|
next(err);
|
|
1330
1604
|
}
|
|
1331
1605
|
});
|
|
1332
|
-
this.router.post(`${(
|
|
1333
|
-
.getCrudApiPath()) === null ||
|
|
1606
|
+
this.router.post(`${(_y = new this.entityType()
|
|
1607
|
+
.getCrudApiPath()) === null || _y === void 0 ? void 0 : _y.toString()}/get-subscription/:statusPageId/:subscriberId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1334
1608
|
try {
|
|
1335
1609
|
const subscriber = await this.getSubscriber(req);
|
|
1336
1610
|
return Response.sendEntityResponse(req, res, subscriber, StatusPageSubscriber);
|
|
@@ -1339,8 +1613,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1339
1613
|
next(err);
|
|
1340
1614
|
}
|
|
1341
1615
|
});
|
|
1342
|
-
this.router.post(`${(
|
|
1343
|
-
.getCrudApiPath()) === null ||
|
|
1616
|
+
this.router.post(`${(_z = new this.entityType()
|
|
1617
|
+
.getCrudApiPath()) === null || _z === void 0 ? void 0 : _z.toString()}/subscribe/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1344
1618
|
try {
|
|
1345
1619
|
await this.subscribeToStatusPage(req);
|
|
1346
1620
|
return Response.sendEmptySuccessResponse(req, res);
|
|
@@ -1349,8 +1623,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1349
1623
|
next(err);
|
|
1350
1624
|
}
|
|
1351
1625
|
});
|
|
1352
|
-
this.router.post(`${(
|
|
1353
|
-
.getCrudApiPath()) === null ||
|
|
1626
|
+
this.router.post(`${(_0 = new this.entityType()
|
|
1627
|
+
.getCrudApiPath()) === null || _0 === void 0 ? void 0 : _0.toString()}/manage-subscription/:statusPageId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1354
1628
|
try {
|
|
1355
1629
|
await this.manageExistingSubscription(req);
|
|
1356
1630
|
return Response.sendEmptySuccessResponse(req, res);
|
|
@@ -1359,8 +1633,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1359
1633
|
next(err);
|
|
1360
1634
|
}
|
|
1361
1635
|
});
|
|
1362
|
-
this.router.post(`${(
|
|
1363
|
-
.getCrudApiPath()) === null ||
|
|
1636
|
+
this.router.post(`${(_1 = new this.entityType()
|
|
1637
|
+
.getCrudApiPath()) === null || _1 === void 0 ? void 0 : _1.toString()}/incidents/:statusPageIdOrDomain`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1364
1638
|
try {
|
|
1365
1639
|
const objectId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
|
|
1366
1640
|
const response = await this.getIncidents(objectId, null, req);
|
|
@@ -1370,8 +1644,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1370
1644
|
next(err);
|
|
1371
1645
|
}
|
|
1372
1646
|
});
|
|
1373
|
-
this.router.post(`${(
|
|
1374
|
-
.getCrudApiPath()) === null ||
|
|
1647
|
+
this.router.post(`${(_2 = new this.entityType()
|
|
1648
|
+
.getCrudApiPath()) === null || _2 === void 0 ? void 0 : _2.toString()}/scheduled-maintenance-events/:statusPageIdOrDomain`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1375
1649
|
try {
|
|
1376
1650
|
const objectId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
|
|
1377
1651
|
const response = await this.getScheduledMaintenanceEvents(objectId, null, req);
|
|
@@ -1381,8 +1655,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1381
1655
|
next(err);
|
|
1382
1656
|
}
|
|
1383
1657
|
});
|
|
1384
|
-
this.router.post(`${(
|
|
1385
|
-
.getCrudApiPath()) === null ||
|
|
1658
|
+
this.router.post(`${(_3 = new this.entityType()
|
|
1659
|
+
.getCrudApiPath()) === null || _3 === void 0 ? void 0 : _3.toString()}/announcements/:statusPageIdOrDomain`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1386
1660
|
try {
|
|
1387
1661
|
const objectId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
|
|
1388
1662
|
const response = await this.getAnnouncements(objectId, null, req);
|
|
@@ -1392,8 +1666,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1392
1666
|
next(err);
|
|
1393
1667
|
}
|
|
1394
1668
|
});
|
|
1395
|
-
this.router.post(`${(
|
|
1396
|
-
.getCrudApiPath()) === null ||
|
|
1669
|
+
this.router.post(`${(_4 = new this.entityType()
|
|
1670
|
+
.getCrudApiPath()) === null || _4 === void 0 ? void 0 : _4.toString()}/incidents/:statusPageIdOrDomain/:incidentId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1397
1671
|
try {
|
|
1398
1672
|
const objectId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
|
|
1399
1673
|
const incidentId = new ObjectID(req.params["incidentId"]);
|
|
@@ -1404,8 +1678,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1404
1678
|
next(err);
|
|
1405
1679
|
}
|
|
1406
1680
|
});
|
|
1407
|
-
this.router.post(`${(
|
|
1408
|
-
.getCrudApiPath()) === null ||
|
|
1681
|
+
this.router.post(`${(_5 = new this.entityType()
|
|
1682
|
+
.getCrudApiPath()) === null || _5 === void 0 ? void 0 : _5.toString()}/scheduled-maintenance-events/:statusPageIdOrDomain/:scheduledMaintenanceId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1409
1683
|
try {
|
|
1410
1684
|
const objectId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
|
|
1411
1685
|
const scheduledMaintenanceId = new ObjectID(req.params["scheduledMaintenanceId"]);
|
|
@@ -1416,8 +1690,8 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1416
1690
|
next(err);
|
|
1417
1691
|
}
|
|
1418
1692
|
});
|
|
1419
|
-
this.router.post(`${(
|
|
1420
|
-
.getCrudApiPath()) === null ||
|
|
1693
|
+
this.router.post(`${(_6 = new this.entityType()
|
|
1694
|
+
.getCrudApiPath()) === null || _6 === void 0 ? void 0 : _6.toString()}/announcements/:statusPageIdOrDomain/:announcementId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1421
1695
|
try {
|
|
1422
1696
|
const objectId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
|
|
1423
1697
|
const announcementId = new ObjectID(req.params["announcementId"]);
|
|
@@ -1428,6 +1702,30 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
1428
1702
|
next(err);
|
|
1429
1703
|
}
|
|
1430
1704
|
});
|
|
1705
|
+
// Episodes endpoints
|
|
1706
|
+
this.router.post(`${(_7 = new this.entityType()
|
|
1707
|
+
.getCrudApiPath()) === null || _7 === void 0 ? void 0 : _7.toString()}/episodes/:statusPageIdOrDomain`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1708
|
+
try {
|
|
1709
|
+
const objectId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
|
|
1710
|
+
const response = await this.getEpisodes(objectId, null, req);
|
|
1711
|
+
return Response.sendJsonObjectResponse(req, res, response);
|
|
1712
|
+
}
|
|
1713
|
+
catch (err) {
|
|
1714
|
+
next(err);
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
this.router.post(`${(_8 = new this.entityType()
|
|
1718
|
+
.getCrudApiPath()) === null || _8 === void 0 ? void 0 : _8.toString()}/episodes/:statusPageIdOrDomain/:episodeId`, UserMiddleware.getUserMiddleware, async (req, res, next) => {
|
|
1719
|
+
try {
|
|
1720
|
+
const objectId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
|
|
1721
|
+
const episodeId = new ObjectID(req.params["episodeId"]);
|
|
1722
|
+
const response = await this.getEpisodes(objectId, episodeId, req);
|
|
1723
|
+
return Response.sendJsonObjectResponse(req, res, response);
|
|
1724
|
+
}
|
|
1725
|
+
catch (err) {
|
|
1726
|
+
next(err);
|
|
1727
|
+
}
|
|
1728
|
+
});
|
|
1431
1729
|
}
|
|
1432
1730
|
async getScheduledMaintenanceEvents(statusPageId, scheduledMaintenanceId, req) {
|
|
1433
1731
|
await this.checkHasReadAccess({
|
|
@@ -2448,6 +2746,381 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
2448
2746
|
};
|
|
2449
2747
|
return response;
|
|
2450
2748
|
}
|
|
2749
|
+
async getEpisodes(statusPageId, episodeId, req) {
|
|
2750
|
+
var _a;
|
|
2751
|
+
await this.checkHasReadAccess({
|
|
2752
|
+
statusPageId: statusPageId,
|
|
2753
|
+
req: req,
|
|
2754
|
+
});
|
|
2755
|
+
const statusPage = await StatusPageService.findOneBy({
|
|
2756
|
+
query: {
|
|
2757
|
+
_id: statusPageId.toString(),
|
|
2758
|
+
},
|
|
2759
|
+
select: {
|
|
2760
|
+
_id: true,
|
|
2761
|
+
projectId: true,
|
|
2762
|
+
showEpisodeHistoryInDays: true,
|
|
2763
|
+
showEpisodesOnStatusPage: true,
|
|
2764
|
+
showEpisodeLabelsOnStatusPage: true,
|
|
2765
|
+
},
|
|
2766
|
+
props: {
|
|
2767
|
+
isRoot: true,
|
|
2768
|
+
},
|
|
2769
|
+
});
|
|
2770
|
+
if (!statusPage) {
|
|
2771
|
+
throw new BadDataException("Status Page not found");
|
|
2772
|
+
}
|
|
2773
|
+
if (!statusPage.showEpisodesOnStatusPage) {
|
|
2774
|
+
throw new BadDataException("Episodes are not enabled on this status page.");
|
|
2775
|
+
}
|
|
2776
|
+
// get monitors on status page.
|
|
2777
|
+
const statusPageResources = await StatusPageService.getStatusPageResources({
|
|
2778
|
+
statusPageId: statusPageId,
|
|
2779
|
+
});
|
|
2780
|
+
const { monitorsOnStatusPage, monitorsInGroup } = await StatusPageService.getMonitorIdsOnStatusPage({
|
|
2781
|
+
statusPageId: statusPageId,
|
|
2782
|
+
});
|
|
2783
|
+
const today = OneUptimeDate.getCurrentDate();
|
|
2784
|
+
const historyDays = OneUptimeDate.getSomeDaysAgo(statusPage.showEpisodeHistoryInDays || 14);
|
|
2785
|
+
/*
|
|
2786
|
+
* Get incidents that have monitors on this status page
|
|
2787
|
+
* Note: We don't filter by incident.isVisibleOnStatusPage here because
|
|
2788
|
+
* episode visibility is independent of incident visibility.
|
|
2789
|
+
* An episode should show if episode.isVisibleOnStatusPage is true,
|
|
2790
|
+
* regardless of whether its member incidents are visible.
|
|
2791
|
+
*/
|
|
2792
|
+
const incidentQuery = {
|
|
2793
|
+
monitors: monitorsOnStatusPage,
|
|
2794
|
+
projectId: statusPage.projectId,
|
|
2795
|
+
createdAt: QueryHelper.inBetween(historyDays, today),
|
|
2796
|
+
};
|
|
2797
|
+
let incidents = [];
|
|
2798
|
+
if (monitorsOnStatusPage.length > 0) {
|
|
2799
|
+
incidents = await IncidentService.findBy({
|
|
2800
|
+
query: incidentQuery,
|
|
2801
|
+
select: {
|
|
2802
|
+
_id: true,
|
|
2803
|
+
},
|
|
2804
|
+
skip: 0,
|
|
2805
|
+
limit: LIMIT_PER_PROJECT,
|
|
2806
|
+
props: {
|
|
2807
|
+
isRoot: true,
|
|
2808
|
+
},
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2811
|
+
const incidentIds = incidents.map((incident) => {
|
|
2812
|
+
return incident.id;
|
|
2813
|
+
});
|
|
2814
|
+
// Get episode members that link to these incidents
|
|
2815
|
+
let episodeMembers = [];
|
|
2816
|
+
if (incidentIds.length > 0) {
|
|
2817
|
+
episodeMembers = await IncidentEpisodeMemberService.findBy({
|
|
2818
|
+
query: {
|
|
2819
|
+
incidentId: QueryHelper.any(incidentIds),
|
|
2820
|
+
projectId: statusPage.projectId,
|
|
2821
|
+
},
|
|
2822
|
+
select: {
|
|
2823
|
+
incidentEpisodeId: true,
|
|
2824
|
+
incidentId: true,
|
|
2825
|
+
},
|
|
2826
|
+
skip: 0,
|
|
2827
|
+
limit: LIMIT_PER_PROJECT,
|
|
2828
|
+
props: {
|
|
2829
|
+
isRoot: true,
|
|
2830
|
+
},
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
// Get unique episode IDs
|
|
2834
|
+
const episodeIdsFromMembers = new Set();
|
|
2835
|
+
for (const member of episodeMembers) {
|
|
2836
|
+
if (member.incidentEpisodeId) {
|
|
2837
|
+
episodeIdsFromMembers.add(member.incidentEpisodeId.toString());
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
let episodeQuery = {
|
|
2841
|
+
_id: QueryHelper.any(Array.from(episodeIdsFromMembers).map((id) => {
|
|
2842
|
+
return new ObjectID(id);
|
|
2843
|
+
})),
|
|
2844
|
+
projectId: statusPage.projectId,
|
|
2845
|
+
isVisibleOnStatusPage: true,
|
|
2846
|
+
};
|
|
2847
|
+
if (episodeId) {
|
|
2848
|
+
episodeQuery = {
|
|
2849
|
+
_id: episodeId.toString(),
|
|
2850
|
+
projectId: statusPage.projectId,
|
|
2851
|
+
isVisibleOnStatusPage: true,
|
|
2852
|
+
};
|
|
2853
|
+
// When viewing a specific episode, also fetch its members directly
|
|
2854
|
+
const episodeMembersForSpecificEpisode = await IncidentEpisodeMemberService.findBy({
|
|
2855
|
+
query: {
|
|
2856
|
+
incidentEpisodeId: episodeId,
|
|
2857
|
+
projectId: statusPage.projectId,
|
|
2858
|
+
},
|
|
2859
|
+
select: {
|
|
2860
|
+
incidentEpisodeId: true,
|
|
2861
|
+
incidentId: true,
|
|
2862
|
+
},
|
|
2863
|
+
skip: 0,
|
|
2864
|
+
limit: LIMIT_PER_PROJECT,
|
|
2865
|
+
props: {
|
|
2866
|
+
isRoot: true,
|
|
2867
|
+
},
|
|
2868
|
+
});
|
|
2869
|
+
// Merge with existing episode members
|
|
2870
|
+
for (const member of episodeMembersForSpecificEpisode) {
|
|
2871
|
+
if (!episodeMembers.some((m) => {
|
|
2872
|
+
var _a, _b, _c, _d;
|
|
2873
|
+
return (((_a = m.incidentEpisodeId) === null || _a === void 0 ? void 0 : _a.toString()) ===
|
|
2874
|
+
((_b = member.incidentEpisodeId) === null || _b === void 0 ? void 0 : _b.toString()) &&
|
|
2875
|
+
((_c = m.incidentId) === null || _c === void 0 ? void 0 : _c.toString()) === ((_d = member.incidentId) === null || _d === void 0 ? void 0 : _d.toString()));
|
|
2876
|
+
})) {
|
|
2877
|
+
episodeMembers.push(member);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
// Get episodes
|
|
2882
|
+
let episodes = [];
|
|
2883
|
+
let selectEpisodes = {
|
|
2884
|
+
createdAt: true,
|
|
2885
|
+
declaredAt: true,
|
|
2886
|
+
updatedAt: true,
|
|
2887
|
+
title: true,
|
|
2888
|
+
description: true,
|
|
2889
|
+
_id: true,
|
|
2890
|
+
episodeNumber: true,
|
|
2891
|
+
incidentSeverity: {
|
|
2892
|
+
name: true,
|
|
2893
|
+
color: true,
|
|
2894
|
+
},
|
|
2895
|
+
currentIncidentState: {
|
|
2896
|
+
name: true,
|
|
2897
|
+
color: true,
|
|
2898
|
+
_id: true,
|
|
2899
|
+
order: true,
|
|
2900
|
+
isCreatedState: true,
|
|
2901
|
+
isAcknowledgedState: true,
|
|
2902
|
+
isResolvedState: true,
|
|
2903
|
+
},
|
|
2904
|
+
incidentCount: true,
|
|
2905
|
+
};
|
|
2906
|
+
if (statusPage.showEpisodeLabelsOnStatusPage) {
|
|
2907
|
+
selectEpisodes = Object.assign(Object.assign({}, selectEpisodes), { labels: {
|
|
2908
|
+
name: true,
|
|
2909
|
+
color: true,
|
|
2910
|
+
} });
|
|
2911
|
+
}
|
|
2912
|
+
if (episodeIdsFromMembers.size > 0 || episodeId) {
|
|
2913
|
+
episodes = await IncidentEpisodeService.findBy({
|
|
2914
|
+
query: episodeQuery,
|
|
2915
|
+
select: selectEpisodes,
|
|
2916
|
+
sort: {
|
|
2917
|
+
declaredAt: SortOrder.Descending,
|
|
2918
|
+
createdAt: SortOrder.Descending,
|
|
2919
|
+
},
|
|
2920
|
+
skip: 0,
|
|
2921
|
+
limit: LIMIT_PER_PROJECT,
|
|
2922
|
+
props: {
|
|
2923
|
+
isRoot: true,
|
|
2924
|
+
},
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2927
|
+
// If no specific episode, also fetch active (unresolved) episodes
|
|
2928
|
+
if (!episodeId && episodeIdsFromMembers.size > 0) {
|
|
2929
|
+
const unresolvedIncidentStates = await IncidentStateService.getUnresolvedIncidentStates(statusPage.projectId, {
|
|
2930
|
+
isRoot: true,
|
|
2931
|
+
});
|
|
2932
|
+
const unresolvedIncidentStateIds = unresolvedIncidentStates.map((state) => {
|
|
2933
|
+
return state.id;
|
|
2934
|
+
});
|
|
2935
|
+
const activeEpisodes = await IncidentEpisodeService.findBy({
|
|
2936
|
+
query: {
|
|
2937
|
+
_id: QueryHelper.any(Array.from(episodeIdsFromMembers).map((id) => {
|
|
2938
|
+
return new ObjectID(id);
|
|
2939
|
+
})),
|
|
2940
|
+
isVisibleOnStatusPage: true,
|
|
2941
|
+
currentIncidentStateId: QueryHelper.any(unresolvedIncidentStateIds),
|
|
2942
|
+
projectId: statusPage.projectId,
|
|
2943
|
+
},
|
|
2944
|
+
select: selectEpisodes,
|
|
2945
|
+
sort: {
|
|
2946
|
+
declaredAt: SortOrder.Descending,
|
|
2947
|
+
createdAt: SortOrder.Descending,
|
|
2948
|
+
},
|
|
2949
|
+
skip: 0,
|
|
2950
|
+
limit: LIMIT_PER_PROJECT,
|
|
2951
|
+
props: {
|
|
2952
|
+
isRoot: true,
|
|
2953
|
+
},
|
|
2954
|
+
});
|
|
2955
|
+
episodes = [...activeEpisodes, ...episodes];
|
|
2956
|
+
episodes = ArrayUtil.distinctByFieldName(episodes, "_id");
|
|
2957
|
+
}
|
|
2958
|
+
const episodesOnStatusPage = episodes.map((episode) => {
|
|
2959
|
+
return episode.id;
|
|
2960
|
+
});
|
|
2961
|
+
/*
|
|
2962
|
+
* Build a map of episode ID -> monitor IDs from episode members
|
|
2963
|
+
* Collect all unique incident IDs from episode members
|
|
2964
|
+
*/
|
|
2965
|
+
const memberIncidentIds = [];
|
|
2966
|
+
for (const member of episodeMembers) {
|
|
2967
|
+
if (member.incidentId &&
|
|
2968
|
+
!memberIncidentIds.some((id) => {
|
|
2969
|
+
return id.toString() === member.incidentId.toString();
|
|
2970
|
+
})) {
|
|
2971
|
+
memberIncidentIds.push(member.incidentId);
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
// Fetch incidents with their monitors
|
|
2975
|
+
let memberIncidents = [];
|
|
2976
|
+
if (memberIncidentIds.length > 0) {
|
|
2977
|
+
memberIncidents = await IncidentService.findBy({
|
|
2978
|
+
query: {
|
|
2979
|
+
_id: QueryHelper.any(memberIncidentIds),
|
|
2980
|
+
projectId: statusPage.projectId,
|
|
2981
|
+
},
|
|
2982
|
+
select: {
|
|
2983
|
+
_id: true,
|
|
2984
|
+
monitors: {
|
|
2985
|
+
_id: true,
|
|
2986
|
+
},
|
|
2987
|
+
},
|
|
2988
|
+
skip: 0,
|
|
2989
|
+
limit: LIMIT_PER_PROJECT,
|
|
2990
|
+
props: {
|
|
2991
|
+
isRoot: true,
|
|
2992
|
+
},
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
// Build a map of incident ID -> monitors
|
|
2996
|
+
const incidentMonitorsMap = new Map();
|
|
2997
|
+
for (const incident of memberIncidents) {
|
|
2998
|
+
const incidentIdStr = incident.id.toString();
|
|
2999
|
+
const monitorIds = (incident.monitors || [])
|
|
3000
|
+
.map((m) => {
|
|
3001
|
+
var _a, _b;
|
|
3002
|
+
return new ObjectID(((_a = m._id) === null || _a === void 0 ? void 0 : _a.toString()) || ((_b = m.id) === null || _b === void 0 ? void 0 : _b.toString()) || "");
|
|
3003
|
+
})
|
|
3004
|
+
.filter((id) => {
|
|
3005
|
+
return id.toString() !== "";
|
|
3006
|
+
});
|
|
3007
|
+
incidentMonitorsMap.set(incidentIdStr, monitorIds);
|
|
3008
|
+
}
|
|
3009
|
+
// Build episode monitors map from members and incident monitors
|
|
3010
|
+
const episodeMonitorsMap = new Map();
|
|
3011
|
+
for (const member of episodeMembers) {
|
|
3012
|
+
if (member.incidentEpisodeId && member.incidentId) {
|
|
3013
|
+
const episodeIdStr = member.incidentEpisodeId.toString();
|
|
3014
|
+
const incidentIdStr = member.incidentId.toString();
|
|
3015
|
+
if (!episodeMonitorsMap.has(episodeIdStr)) {
|
|
3016
|
+
episodeMonitorsMap.set(episodeIdStr, []);
|
|
3017
|
+
}
|
|
3018
|
+
const episodeMonitors = episodeMonitorsMap.get(episodeIdStr);
|
|
3019
|
+
const incidentMonitors = incidentMonitorsMap.get(incidentIdStr) || [];
|
|
3020
|
+
for (const monitorId of incidentMonitors) {
|
|
3021
|
+
if (!episodeMonitors.some((m) => {
|
|
3022
|
+
return m.toString() === monitorId.toString();
|
|
3023
|
+
})) {
|
|
3024
|
+
episodeMonitors.push(monitorId);
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
// Get public notes for episodes
|
|
3030
|
+
let episodePublicNotes = [];
|
|
3031
|
+
if (episodesOnStatusPage.length > 0) {
|
|
3032
|
+
episodePublicNotes = await IncidentEpisodePublicNoteService.findBy({
|
|
3033
|
+
query: {
|
|
3034
|
+
incidentEpisodeId: QueryHelper.any(episodesOnStatusPage),
|
|
3035
|
+
projectId: statusPage.projectId,
|
|
3036
|
+
},
|
|
3037
|
+
select: {
|
|
3038
|
+
postedAt: true,
|
|
3039
|
+
note: true,
|
|
3040
|
+
incidentEpisodeId: true,
|
|
3041
|
+
attachments: {
|
|
3042
|
+
_id: true,
|
|
3043
|
+
name: true,
|
|
3044
|
+
},
|
|
3045
|
+
},
|
|
3046
|
+
sort: {
|
|
3047
|
+
postedAt: SortOrder.Descending,
|
|
3048
|
+
},
|
|
3049
|
+
skip: 0,
|
|
3050
|
+
limit: LIMIT_PER_PROJECT,
|
|
3051
|
+
props: {
|
|
3052
|
+
isRoot: true,
|
|
3053
|
+
},
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
// Get state timelines for episodes
|
|
3057
|
+
let episodeStateTimelines = [];
|
|
3058
|
+
if (episodesOnStatusPage.length > 0) {
|
|
3059
|
+
episodeStateTimelines = await IncidentEpisodeStateTimelineService.findBy({
|
|
3060
|
+
query: {
|
|
3061
|
+
incidentEpisodeId: QueryHelper.any(episodesOnStatusPage),
|
|
3062
|
+
projectId: statusPage.projectId,
|
|
3063
|
+
},
|
|
3064
|
+
select: {
|
|
3065
|
+
_id: true,
|
|
3066
|
+
createdAt: true,
|
|
3067
|
+
startsAt: true,
|
|
3068
|
+
incidentEpisodeId: true,
|
|
3069
|
+
incidentState: {
|
|
3070
|
+
name: true,
|
|
3071
|
+
color: true,
|
|
3072
|
+
isCreatedState: true,
|
|
3073
|
+
isAcknowledgedState: true,
|
|
3074
|
+
isResolvedState: true,
|
|
3075
|
+
},
|
|
3076
|
+
},
|
|
3077
|
+
sort: {
|
|
3078
|
+
startsAt: SortOrder.Descending,
|
|
3079
|
+
},
|
|
3080
|
+
skip: 0,
|
|
3081
|
+
limit: LIMIT_PER_PROJECT,
|
|
3082
|
+
props: {
|
|
3083
|
+
isRoot: true,
|
|
3084
|
+
},
|
|
3085
|
+
});
|
|
3086
|
+
}
|
|
3087
|
+
// Get all incident states for this project
|
|
3088
|
+
const incidentStates = await IncidentStateService.findBy({
|
|
3089
|
+
query: {
|
|
3090
|
+
projectId: statusPage.projectId,
|
|
3091
|
+
},
|
|
3092
|
+
select: {
|
|
3093
|
+
isResolvedState: true,
|
|
3094
|
+
order: true,
|
|
3095
|
+
},
|
|
3096
|
+
limit: LIMIT_PER_PROJECT,
|
|
3097
|
+
skip: 0,
|
|
3098
|
+
props: {
|
|
3099
|
+
isRoot: true,
|
|
3100
|
+
},
|
|
3101
|
+
});
|
|
3102
|
+
// Serialize episodes and add monitors to each
|
|
3103
|
+
const episodesJson = BaseModel.toJSONArray(episodes, IncidentEpisode);
|
|
3104
|
+
for (const episodeJson of episodesJson) {
|
|
3105
|
+
const episodeObj = episodeJson;
|
|
3106
|
+
const episodeId = (_a = episodeObj["_id"]) === null || _a === void 0 ? void 0 : _a.toString();
|
|
3107
|
+
if (episodeId) {
|
|
3108
|
+
const monitorIds = episodeMonitorsMap.get(episodeId) || [];
|
|
3109
|
+
episodeObj["monitors"] = monitorIds.map((id) => {
|
|
3110
|
+
return { _id: id.toString() };
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
const response = {
|
|
3115
|
+
episodePublicNotes: BaseModel.toJSONArray(episodePublicNotes, IncidentEpisodePublicNote),
|
|
3116
|
+
incidentStates: BaseModel.toJSONArray(incidentStates, IncidentState),
|
|
3117
|
+
episodes: episodesJson,
|
|
3118
|
+
statusPageResources: BaseModel.toJSONArray(statusPageResources, StatusPageResource),
|
|
3119
|
+
episodeStateTimelines: BaseModel.toJSONArray(episodeStateTimelines, IncidentEpisodeStateTimeline),
|
|
3120
|
+
monitorsInGroup: JSONFunctions.serialize(monitorsInGroup),
|
|
3121
|
+
};
|
|
3122
|
+
return response;
|
|
3123
|
+
}
|
|
2451
3124
|
async getStatusPageResourcesAndTimelines(data) {
|
|
2452
3125
|
const objectId = data.statusPageId;
|
|
2453
3126
|
const statusPage = await StatusPageService.findOneBy({
|
|
@@ -2461,6 +3134,7 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
2461
3134
|
overviewPageDescription: true,
|
|
2462
3135
|
showIncidentLabelsOnStatusPage: true,
|
|
2463
3136
|
showScheduledEventLabelsOnStatusPage: true,
|
|
3137
|
+
showEpisodeLabelsOnStatusPage: true,
|
|
2464
3138
|
downtimeMonitorStatuses: {
|
|
2465
3139
|
_id: true,
|
|
2466
3140
|
},
|
|
@@ -2469,6 +3143,7 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
2469
3143
|
overallUptimePercentPrecision: true,
|
|
2470
3144
|
showAnnouncementsOnStatusPage: true,
|
|
2471
3145
|
showIncidentsOnStatusPage: true,
|
|
3146
|
+
showEpisodesOnStatusPage: true,
|
|
2472
3147
|
showScheduledMaintenanceEventsOnStatusPage: true,
|
|
2473
3148
|
},
|
|
2474
3149
|
props: {
|
|
@@ -3017,6 +3692,152 @@ export default class StatusPageAPI extends BaseAPI {
|
|
|
3017
3692
|
Response.setNoCacheHeaders(res);
|
|
3018
3693
|
return Response.sendFileResponse(req, res, attachment);
|
|
3019
3694
|
}
|
|
3695
|
+
async getIncidentEpisodePublicNoteAttachment(req, res) {
|
|
3696
|
+
var _a;
|
|
3697
|
+
const statusPageIdParam = req.params["statusPageId"];
|
|
3698
|
+
const episodeIdParam = req.params["episodeId"];
|
|
3699
|
+
const noteIdParam = req.params["noteId"];
|
|
3700
|
+
const fileIdParam = req.params["fileId"];
|
|
3701
|
+
if (!statusPageIdParam || !episodeIdParam || !noteIdParam || !fileIdParam) {
|
|
3702
|
+
throw new NotFoundException("Attachment not found");
|
|
3703
|
+
}
|
|
3704
|
+
let statusPageId;
|
|
3705
|
+
let episodeId;
|
|
3706
|
+
let noteId;
|
|
3707
|
+
let fileId;
|
|
3708
|
+
try {
|
|
3709
|
+
statusPageId = new ObjectID(statusPageIdParam);
|
|
3710
|
+
episodeId = new ObjectID(episodeIdParam);
|
|
3711
|
+
noteId = new ObjectID(noteIdParam);
|
|
3712
|
+
fileId = new ObjectID(fileIdParam);
|
|
3713
|
+
}
|
|
3714
|
+
catch (_b) {
|
|
3715
|
+
throw new NotFoundException("Attachment not found");
|
|
3716
|
+
}
|
|
3717
|
+
await this.checkHasReadAccess({
|
|
3718
|
+
statusPageId: statusPageId,
|
|
3719
|
+
req: req,
|
|
3720
|
+
});
|
|
3721
|
+
const statusPage = await StatusPageService.findOneBy({
|
|
3722
|
+
query: {
|
|
3723
|
+
_id: statusPageId.toString(),
|
|
3724
|
+
},
|
|
3725
|
+
select: {
|
|
3726
|
+
_id: true,
|
|
3727
|
+
projectId: true,
|
|
3728
|
+
showEpisodesOnStatusPage: true,
|
|
3729
|
+
},
|
|
3730
|
+
props: {
|
|
3731
|
+
isRoot: true,
|
|
3732
|
+
},
|
|
3733
|
+
});
|
|
3734
|
+
if (!statusPage || !statusPage.projectId) {
|
|
3735
|
+
throw new NotFoundException("Attachment not found");
|
|
3736
|
+
}
|
|
3737
|
+
if (!statusPage.showEpisodesOnStatusPage) {
|
|
3738
|
+
throw new NotFoundException("Attachment not found");
|
|
3739
|
+
}
|
|
3740
|
+
const { monitorsOnStatusPage } = await StatusPageService.getMonitorIdsOnStatusPage({
|
|
3741
|
+
statusPageId: statusPageId,
|
|
3742
|
+
});
|
|
3743
|
+
if (!monitorsOnStatusPage || monitorsOnStatusPage.length === 0) {
|
|
3744
|
+
throw new NotFoundException("Attachment not found");
|
|
3745
|
+
}
|
|
3746
|
+
// Get episode members (incidents) that are linked to monitors on the status page
|
|
3747
|
+
const episodeMembers = await IncidentEpisodeMemberService.findBy({
|
|
3748
|
+
query: {
|
|
3749
|
+
incidentEpisodeId: episodeId,
|
|
3750
|
+
projectId: statusPage.projectId,
|
|
3751
|
+
},
|
|
3752
|
+
select: {
|
|
3753
|
+
incidentId: true,
|
|
3754
|
+
},
|
|
3755
|
+
limit: LIMIT_PER_PROJECT,
|
|
3756
|
+
skip: 0,
|
|
3757
|
+
props: {
|
|
3758
|
+
isRoot: true,
|
|
3759
|
+
},
|
|
3760
|
+
});
|
|
3761
|
+
if (episodeMembers.length === 0) {
|
|
3762
|
+
throw new NotFoundException("Attachment not found");
|
|
3763
|
+
}
|
|
3764
|
+
const incidentIds = episodeMembers
|
|
3765
|
+
.map((member) => {
|
|
3766
|
+
return member.incidentId;
|
|
3767
|
+
})
|
|
3768
|
+
.filter((id) => {
|
|
3769
|
+
return Boolean(id);
|
|
3770
|
+
});
|
|
3771
|
+
// Check if any of the incidents are linked to monitors on the status page
|
|
3772
|
+
const incident = await IncidentService.findOneBy({
|
|
3773
|
+
query: {
|
|
3774
|
+
_id: QueryHelper.any(incidentIds),
|
|
3775
|
+
projectId: statusPage.projectId,
|
|
3776
|
+
isVisibleOnStatusPage: true,
|
|
3777
|
+
monitors: monitorsOnStatusPage,
|
|
3778
|
+
},
|
|
3779
|
+
select: {
|
|
3780
|
+
_id: true,
|
|
3781
|
+
},
|
|
3782
|
+
props: {
|
|
3783
|
+
isRoot: true,
|
|
3784
|
+
},
|
|
3785
|
+
});
|
|
3786
|
+
if (!incident) {
|
|
3787
|
+
throw new NotFoundException("Attachment not found");
|
|
3788
|
+
}
|
|
3789
|
+
// Verify the episode exists and is visible
|
|
3790
|
+
const episode = await IncidentEpisodeService.findOneBy({
|
|
3791
|
+
query: {
|
|
3792
|
+
_id: episodeId.toString(),
|
|
3793
|
+
projectId: statusPage.projectId,
|
|
3794
|
+
isVisibleOnStatusPage: true,
|
|
3795
|
+
},
|
|
3796
|
+
select: {
|
|
3797
|
+
_id: true,
|
|
3798
|
+
},
|
|
3799
|
+
props: {
|
|
3800
|
+
isRoot: true,
|
|
3801
|
+
},
|
|
3802
|
+
});
|
|
3803
|
+
if (!episode) {
|
|
3804
|
+
throw new NotFoundException("Attachment not found");
|
|
3805
|
+
}
|
|
3806
|
+
const episodePublicNote = await IncidentEpisodePublicNoteService.findOneBy({
|
|
3807
|
+
query: {
|
|
3808
|
+
_id: noteId.toString(),
|
|
3809
|
+
incidentEpisodeId: episodeId.toString(),
|
|
3810
|
+
projectId: statusPage.projectId,
|
|
3811
|
+
},
|
|
3812
|
+
select: {
|
|
3813
|
+
attachments: {
|
|
3814
|
+
_id: true,
|
|
3815
|
+
file: true,
|
|
3816
|
+
fileType: true,
|
|
3817
|
+
name: true,
|
|
3818
|
+
},
|
|
3819
|
+
},
|
|
3820
|
+
props: {
|
|
3821
|
+
isRoot: true,
|
|
3822
|
+
},
|
|
3823
|
+
});
|
|
3824
|
+
if (!episodePublicNote) {
|
|
3825
|
+
throw new NotFoundException("Attachment not found");
|
|
3826
|
+
}
|
|
3827
|
+
const attachment = (_a = episodePublicNote.attachments) === null || _a === void 0 ? void 0 : _a.find((file) => {
|
|
3828
|
+
const attachmentId = file._id
|
|
3829
|
+
? file._id.toString()
|
|
3830
|
+
: file.id
|
|
3831
|
+
? file.id.toString()
|
|
3832
|
+
: null;
|
|
3833
|
+
return attachmentId === fileId.toString();
|
|
3834
|
+
});
|
|
3835
|
+
if (!attachment || !attachment.file) {
|
|
3836
|
+
throw new NotFoundException("Attachment not found");
|
|
3837
|
+
}
|
|
3838
|
+
Response.setNoCacheHeaders(res);
|
|
3839
|
+
return Response.sendFileResponse(req, res, attachment);
|
|
3840
|
+
}
|
|
3020
3841
|
async checkHasReadAccess(data) {
|
|
3021
3842
|
const accessResult = await this.service.hasReadAccess({
|
|
3022
3843
|
statusPageId: data.statusPageId,
|
|
@@ -3064,6 +3885,12 @@ __decorate([
|
|
|
3064
3885
|
__metadata("design:paramtypes", [ObjectID, Object, Object]),
|
|
3065
3886
|
__metadata("design:returntype", Promise)
|
|
3066
3887
|
], StatusPageAPI.prototype, "getIncidents", null);
|
|
3888
|
+
__decorate([
|
|
3889
|
+
CaptureSpan(),
|
|
3890
|
+
__metadata("design:type", Function),
|
|
3891
|
+
__metadata("design:paramtypes", [ObjectID, Object, Object]),
|
|
3892
|
+
__metadata("design:returntype", Promise)
|
|
3893
|
+
], StatusPageAPI.prototype, "getEpisodes", null);
|
|
3067
3894
|
__decorate([
|
|
3068
3895
|
CaptureSpan(),
|
|
3069
3896
|
__metadata("design:type", Function),
|