@terreno/announcements 57.4.0 → 57.6.1
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/README.md +4 -4
- package/dist/announcementsApp.js +378 -639
- package/dist/help.js +38 -46
- package/dist/helpRoutes.js +80 -200
- package/dist/models/announcement.js +42 -95
- package/dist/models/announcementAcknowledgement.js +6 -6
- package/dist/models/announcementClickEvent.js +7 -9
- package/dist/models/announcementImpression.js +7 -9
- package/dist/overview.js +151 -300
- package/dist/pending.js +62 -140
- package/package.json +6 -6
- package/src/models/announcement.ts +6 -2
- package/src/models/announcementAcknowledgement.ts +5 -4
- package/src/models/announcementClickEvent.ts +5 -4
- package/src/models/announcementImpression.ts +5 -4
- package/src/tests/index.test.ts +43 -2
- package/tsconfig.json +6 -21
package/dist/announcementsApp.js
CHANGED
|
@@ -1,108 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __assign = (this && this.__assign) || function () {
|
|
3
|
-
__assign = Object.assign || function(t) {
|
|
4
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
-
s = arguments[i];
|
|
6
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
-
t[p] = s[p];
|
|
8
|
-
}
|
|
9
|
-
return t;
|
|
10
|
-
};
|
|
11
|
-
return __assign.apply(this, arguments);
|
|
12
|
-
};
|
|
13
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
-
});
|
|
21
|
-
};
|
|
22
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
24
|
-
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
-
function step(op) {
|
|
27
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
-
switch (op[0]) {
|
|
32
|
-
case 0: case 1: t = op; break;
|
|
33
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
-
default:
|
|
37
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
-
if (t[2]) _.ops.pop();
|
|
42
|
-
_.trys.pop(); continue;
|
|
43
|
-
}
|
|
44
|
-
op = body.call(thisArg, _);
|
|
45
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
var __values = (this && this.__values) || function(o) {
|
|
50
|
-
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
51
|
-
if (m) return m.call(o);
|
|
52
|
-
if (o && typeof o.length === "number") return {
|
|
53
|
-
next: function () {
|
|
54
|
-
if (o && i >= o.length) o = void 0;
|
|
55
|
-
return { value: o && o[i++], done: !o };
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
59
|
-
};
|
|
60
|
-
var __read = (this && this.__read) || function (o, n) {
|
|
61
|
-
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
62
|
-
if (!m) return o;
|
|
63
|
-
var i = m.call(o), r, ar = [], e;
|
|
64
|
-
try {
|
|
65
|
-
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
66
|
-
}
|
|
67
|
-
catch (error) { e = { error: error }; }
|
|
68
|
-
finally {
|
|
69
|
-
try {
|
|
70
|
-
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
71
|
-
}
|
|
72
|
-
finally { if (e) throw e.error; }
|
|
73
|
-
}
|
|
74
|
-
return ar;
|
|
75
|
-
};
|
|
76
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
77
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
78
|
-
if (ar || !(i in from)) {
|
|
79
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
80
|
-
ar[i] = from[i];
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
84
|
-
};
|
|
85
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
86
3
|
exports.AnnouncementsApp = void 0;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
var queryPlatform = req.query.platform;
|
|
4
|
+
const api_1 = require("@terreno/api");
|
|
5
|
+
const express_1 = require("express");
|
|
6
|
+
const luxon_1 = require("luxon");
|
|
7
|
+
const helpRoutes_1 = require("./helpRoutes");
|
|
8
|
+
const announcement_1 = require("./models/announcement");
|
|
9
|
+
const announcementAcknowledgement_1 = require("./models/announcementAcknowledgement");
|
|
10
|
+
const announcementClickEvent_1 = require("./models/announcementClickEvent");
|
|
11
|
+
const announcementImpression_1 = require("./models/announcementImpression");
|
|
12
|
+
const overview_1 = require("./overview");
|
|
13
|
+
const pending_1 = require("./pending");
|
|
14
|
+
const DEFAULT_BASE_PATH = "/announcements";
|
|
15
|
+
const defaultIsStaff = (user) => user.admin === true;
|
|
16
|
+
const parsePlatform = (req) => {
|
|
17
|
+
const queryPlatform = req.query.platform;
|
|
102
18
|
if ((0, announcementImpression_1.isValidPlatform)(queryPlatform)) {
|
|
103
19
|
return queryPlatform;
|
|
104
20
|
}
|
|
105
|
-
|
|
21
|
+
const userAgent = req.get("user-agent")?.toLowerCase() ?? "";
|
|
106
22
|
if (userAgent.includes("iphone") || userAgent.includes("ipad")) {
|
|
107
23
|
return "ios";
|
|
108
24
|
}
|
|
@@ -111,69 +27,35 @@ var parsePlatform = function (req) {
|
|
|
111
27
|
}
|
|
112
28
|
return "web";
|
|
113
29
|
};
|
|
114
|
-
|
|
115
|
-
if (!
|
|
30
|
+
const requireAdmin = (user) => {
|
|
31
|
+
if (!user?.admin) {
|
|
116
32
|
throw new api_1.APIError({ status: 403, title: "Admin access required" });
|
|
117
33
|
}
|
|
118
34
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
throw new api_1.APIError({ status: 403, title: "Admin access required" });
|
|
128
|
-
}
|
|
129
|
-
_d.label = 1;
|
|
130
|
-
case 1:
|
|
131
|
-
_d.trys.push([1, 6, 7, 8]);
|
|
132
|
-
permissions_1 = __values(permissions), permissions_1_1 = permissions_1.next();
|
|
133
|
-
_d.label = 2;
|
|
134
|
-
case 2:
|
|
135
|
-
if (!!permissions_1_1.done) return [3 /*break*/, 5];
|
|
136
|
-
permission = permissions_1_1.value;
|
|
137
|
-
return [4 /*yield*/, permission("list", user)];
|
|
138
|
-
case 3:
|
|
139
|
-
isAllowed = _d.sent();
|
|
140
|
-
if (!isAllowed) {
|
|
141
|
-
throw new api_1.APIError({ status: 403, title: "Admin access required" });
|
|
142
|
-
}
|
|
143
|
-
_d.label = 4;
|
|
144
|
-
case 4:
|
|
145
|
-
permissions_1_1 = permissions_1.next();
|
|
146
|
-
return [3 /*break*/, 2];
|
|
147
|
-
case 5: return [3 /*break*/, 8];
|
|
148
|
-
case 6:
|
|
149
|
-
e_1_1 = _d.sent();
|
|
150
|
-
e_1 = { error: e_1_1 };
|
|
151
|
-
return [3 /*break*/, 8];
|
|
152
|
-
case 7:
|
|
153
|
-
try {
|
|
154
|
-
if (permissions_1_1 && !permissions_1_1.done && (_c = permissions_1.return)) _c.call(permissions_1);
|
|
155
|
-
}
|
|
156
|
-
finally { if (e_1) throw e_1.error; }
|
|
157
|
-
return [7 /*endfinally*/];
|
|
158
|
-
case 8: return [2 /*return*/];
|
|
35
|
+
const requireOverviewAccess = async ({ permissions, user, }) => {
|
|
36
|
+
if (permissions.length === 0) {
|
|
37
|
+
throw new api_1.APIError({ status: 403, title: "Admin access required" });
|
|
38
|
+
}
|
|
39
|
+
for (const permission of permissions) {
|
|
40
|
+
const isAllowed = await permission("list", user);
|
|
41
|
+
if (!isAllowed) {
|
|
42
|
+
throw new api_1.APIError({ status: 403, title: "Admin access required" });
|
|
159
43
|
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
var userId = (_a = user._id) !== null && _a !== void 0 ? _a : user.id;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const getUserId = (user) => {
|
|
47
|
+
const userId = user._id ?? user.id;
|
|
165
48
|
if (!userId) {
|
|
166
49
|
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
167
50
|
}
|
|
168
51
|
return String(userId);
|
|
169
52
|
};
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
var url = (_d = (_c = announcement.primaryAction) === null || _c === void 0 ? void 0 : _c.url) === null || _d === void 0 ? void 0 : _d.trim();
|
|
53
|
+
const hasPrimaryAction = (announcement) => {
|
|
54
|
+
const label = announcement.primaryAction?.label?.trim();
|
|
55
|
+
const url = announcement.primaryAction?.url?.trim();
|
|
174
56
|
return Boolean(label && url);
|
|
175
57
|
};
|
|
176
|
-
|
|
58
|
+
const resolveClickPlatform = (req, bodyPlatform) => {
|
|
177
59
|
if (bodyPlatform !== undefined && bodyPlatform !== null) {
|
|
178
60
|
if (!(0, announcementImpression_1.isValidPlatform)(bodyPlatform)) {
|
|
179
61
|
throw new api_1.APIError({ status: 400, title: "Invalid platform" });
|
|
@@ -182,37 +64,29 @@ var resolveClickPlatform = function (req, bodyPlatform) {
|
|
|
182
64
|
}
|
|
183
65
|
return parsePlatform(req);
|
|
184
66
|
};
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
isStaff: isStaff,
|
|
197
|
-
matchAudience: matchAudience,
|
|
198
|
-
platform: platform,
|
|
199
|
-
queryVersion: queryVersion,
|
|
200
|
-
user: user,
|
|
201
|
-
})];
|
|
202
|
-
case 1:
|
|
203
|
-
visible = _c.sent();
|
|
204
|
-
if (!visible) {
|
|
205
|
-
throw new api_1.APIError({ status: 404, title: "Announcement not found" });
|
|
206
|
-
}
|
|
207
|
-
return [2 /*return*/, announcement];
|
|
208
|
-
}
|
|
67
|
+
const requireVisibleAnnouncement = async ({ announcement, isStaff, matchAudience, platform, queryVersion, user, }) => {
|
|
68
|
+
if (!announcement) {
|
|
69
|
+
throw new api_1.APIError({ status: 404, title: "Announcement not found" });
|
|
70
|
+
}
|
|
71
|
+
const visible = await (0, pending_1.isAnnouncementVisibleToUser)({
|
|
72
|
+
announcement,
|
|
73
|
+
isStaff,
|
|
74
|
+
matchAudience,
|
|
75
|
+
platform,
|
|
76
|
+
queryVersion,
|
|
77
|
+
user,
|
|
209
78
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
79
|
+
if (!visible) {
|
|
80
|
+
throw new api_1.APIError({ status: 404, title: "Announcement not found" });
|
|
81
|
+
}
|
|
82
|
+
return announcement;
|
|
83
|
+
};
|
|
84
|
+
class AnnouncementsApp {
|
|
85
|
+
options;
|
|
86
|
+
constructor(options) {
|
|
87
|
+
this.options = options ?? {};
|
|
214
88
|
}
|
|
215
|
-
|
|
89
|
+
adminContribution() {
|
|
216
90
|
return {
|
|
217
91
|
customScreens: [
|
|
218
92
|
{
|
|
@@ -297,480 +171,346 @@ var AnnouncementsApp = /** @class */ (function () {
|
|
|
297
171
|
},
|
|
298
172
|
],
|
|
299
173
|
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
174
|
+
}
|
|
175
|
+
register(app, openApi) {
|
|
176
|
+
const basePath = this.options.basePath ?? DEFAULT_BASE_PATH;
|
|
177
|
+
const defaultAcknowledgementPolicy = this.options.defaultAcknowledgementPolicy ?? "dismiss-only";
|
|
178
|
+
const overviewPermissions = this.options.adminOverviewPermissions ?? [api_1.Permissions.IsAdmin];
|
|
179
|
+
const matchAudience = this.options.matchAudience ?? (() => true);
|
|
180
|
+
const isStaff = this.options.isStaff ?? defaultIsStaff;
|
|
181
|
+
const matchesAnnouncementAudience = async (user, announcement) => {
|
|
182
|
+
if (!(0, pending_1.matchAudienceByType)({ announcement, isStaff, user })) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return matchAudience(user, announcement);
|
|
186
|
+
};
|
|
187
|
+
const routerOptions = {
|
|
188
|
+
...(openApi ? { openApi: openApi } : {}),
|
|
189
|
+
permissions: {
|
|
190
|
+
create: [api_1.Permissions.IsAdmin],
|
|
191
|
+
delete: [api_1.Permissions.IsAdmin],
|
|
192
|
+
list: [api_1.Permissions.IsAdmin],
|
|
193
|
+
read: [api_1.Permissions.IsAdmin],
|
|
194
|
+
update: [api_1.Permissions.IsAdmin],
|
|
195
|
+
...this.options.permissions,
|
|
196
|
+
},
|
|
197
|
+
queryFields: ["status", "priority", "title"],
|
|
198
|
+
sort: "-priority,-publishedAt",
|
|
199
|
+
};
|
|
200
|
+
const userRouter = (0, express_1.Router)();
|
|
201
|
+
userRouter.get("/pending", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(async (req, res) => {
|
|
202
|
+
const user = req.user;
|
|
203
|
+
if (!user) {
|
|
204
|
+
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
205
|
+
}
|
|
206
|
+
const userId = getUserId(user);
|
|
207
|
+
const platform = parsePlatform(req);
|
|
208
|
+
const queryVersion = (0, pending_1.parseQueryVersion)(req.query.version);
|
|
209
|
+
const published = (await announcement_1.Announcement.find({ status: "published" }).lean());
|
|
210
|
+
const audienceFiltered = [];
|
|
211
|
+
for (const announcement of published) {
|
|
212
|
+
const matches = await matchesAnnouncementAudience(user, announcement);
|
|
213
|
+
if (matches) {
|
|
214
|
+
audienceFiltered.push(announcement);
|
|
313
215
|
}
|
|
314
|
-
|
|
216
|
+
}
|
|
217
|
+
const acknowledgements = await announcementAcknowledgement_1.AnnouncementAcknowledgement.find({ userId }).select("announcementId version");
|
|
218
|
+
const impressions = await announcementImpression_1.AnnouncementImpression.find({ userId }).select("announcementId version");
|
|
219
|
+
const pending = (0, pending_1.selectPendingAnnouncements)({
|
|
220
|
+
acknowledgements: acknowledgements.map((ack) => ({
|
|
221
|
+
announcementId: ack.announcementId.toString(),
|
|
222
|
+
version: ack.version,
|
|
223
|
+
})),
|
|
224
|
+
announcements: audienceFiltered,
|
|
225
|
+
defaultAcknowledgementPolicy,
|
|
226
|
+
impressions: impressions.map((impression) => ({
|
|
227
|
+
announcementId: impression.announcementId.toString(),
|
|
228
|
+
version: impression.version,
|
|
229
|
+
})),
|
|
230
|
+
platform,
|
|
231
|
+
queryVersion,
|
|
315
232
|
});
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
user = req.user;
|
|
326
|
-
if (!user) {
|
|
327
|
-
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
328
|
-
}
|
|
329
|
-
userId = getUserId(user);
|
|
330
|
-
platform = parsePlatform(req);
|
|
331
|
-
queryVersion = (0, pending_1.parseQueryVersion)(req.query.version);
|
|
332
|
-
return [4 /*yield*/, announcement_1.Announcement.find({ status: "published" }).lean()];
|
|
333
|
-
case 1:
|
|
334
|
-
published = (_b.sent());
|
|
335
|
-
audienceFiltered = [];
|
|
336
|
-
_b.label = 2;
|
|
337
|
-
case 2:
|
|
338
|
-
_b.trys.push([2, 7, 8, 9]);
|
|
339
|
-
published_1 = __values(published), published_1_1 = published_1.next();
|
|
340
|
-
_b.label = 3;
|
|
341
|
-
case 3:
|
|
342
|
-
if (!!published_1_1.done) return [3 /*break*/, 6];
|
|
343
|
-
announcement = published_1_1.value;
|
|
344
|
-
return [4 /*yield*/, matchesAnnouncementAudience(user, announcement)];
|
|
345
|
-
case 4:
|
|
346
|
-
matches = _b.sent();
|
|
347
|
-
if (matches) {
|
|
348
|
-
audienceFiltered.push(announcement);
|
|
349
|
-
}
|
|
350
|
-
_b.label = 5;
|
|
351
|
-
case 5:
|
|
352
|
-
published_1_1 = published_1.next();
|
|
353
|
-
return [3 /*break*/, 3];
|
|
354
|
-
case 6: return [3 /*break*/, 9];
|
|
355
|
-
case 7:
|
|
356
|
-
e_2_1 = _b.sent();
|
|
357
|
-
e_2 = { error: e_2_1 };
|
|
358
|
-
return [3 /*break*/, 9];
|
|
359
|
-
case 8:
|
|
360
|
-
try {
|
|
361
|
-
if (published_1_1 && !published_1_1.done && (_a = published_1.return)) _a.call(published_1);
|
|
362
|
-
}
|
|
363
|
-
finally { if (e_2) throw e_2.error; }
|
|
364
|
-
return [7 /*endfinally*/];
|
|
365
|
-
case 9: return [4 /*yield*/, announcementAcknowledgement_1.AnnouncementAcknowledgement.find({ userId: userId }).select("announcementId version")];
|
|
366
|
-
case 10:
|
|
367
|
-
acknowledgements = _b.sent();
|
|
368
|
-
return [4 /*yield*/, announcementImpression_1.AnnouncementImpression.find({ userId: userId }).select("announcementId version")];
|
|
369
|
-
case 11:
|
|
370
|
-
impressions = _b.sent();
|
|
371
|
-
pending = (0, pending_1.selectPendingAnnouncements)({
|
|
372
|
-
acknowledgements: acknowledgements.map(function (ack) { return ({
|
|
373
|
-
announcementId: ack.announcementId.toString(),
|
|
374
|
-
version: ack.version,
|
|
375
|
-
}); }),
|
|
376
|
-
announcements: audienceFiltered,
|
|
377
|
-
defaultAcknowledgementPolicy: defaultAcknowledgementPolicy,
|
|
378
|
-
impressions: impressions.map(function (impression) { return ({
|
|
379
|
-
announcementId: impression.announcementId.toString(),
|
|
380
|
-
version: impression.version,
|
|
381
|
-
}); }),
|
|
382
|
-
platform: platform,
|
|
383
|
-
queryVersion: queryVersion,
|
|
384
|
-
});
|
|
385
|
-
current = pending[0]
|
|
386
|
-
? (0, announcement_1.toAnnouncementPublic)(pending[0], defaultAcknowledgementPolicy)
|
|
387
|
-
: null;
|
|
388
|
-
remainingCount = Math.max(pending.length - (current ? 1 : 0), 0);
|
|
389
|
-
api_1.logger.info("Pending announcements fetched", {
|
|
390
|
-
currentId: current === null || current === void 0 ? void 0 : current.id,
|
|
391
|
-
platform: platform,
|
|
392
|
-
remainingCount: remainingCount,
|
|
393
|
-
userId: userId,
|
|
394
|
-
});
|
|
395
|
-
return [2 /*return*/, res.json({ data: { current: current, remainingCount: remainingCount } })];
|
|
396
|
-
}
|
|
233
|
+
const current = pending[0]
|
|
234
|
+
? (0, announcement_1.toAnnouncementPublic)(pending[0], defaultAcknowledgementPolicy)
|
|
235
|
+
: null;
|
|
236
|
+
const remainingCount = Math.max(pending.length - (current ? 1 : 0), 0);
|
|
237
|
+
api_1.logger.info("Pending announcements fetched", {
|
|
238
|
+
currentId: current?.id,
|
|
239
|
+
platform,
|
|
240
|
+
remainingCount,
|
|
241
|
+
userId,
|
|
397
242
|
});
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
case 1:
|
|
416
|
-
published = (_d.sent());
|
|
417
|
-
visible = [];
|
|
418
|
-
_d.label = 2;
|
|
419
|
-
case 2:
|
|
420
|
-
_d.trys.push([2, 7, 8, 9]);
|
|
421
|
-
published_2 = __values(published), published_2_1 = published_2.next();
|
|
422
|
-
_d.label = 3;
|
|
423
|
-
case 3:
|
|
424
|
-
if (!!published_2_1.done) return [3 /*break*/, 6];
|
|
425
|
-
announcement = published_2_1.value;
|
|
426
|
-
return [4 /*yield*/, matchesAnnouncementAudience(user, announcement)];
|
|
427
|
-
case 4:
|
|
428
|
-
matchesAudience = _d.sent();
|
|
429
|
-
if (!matchesAudience) {
|
|
430
|
-
return [3 /*break*/, 5];
|
|
431
|
-
}
|
|
432
|
-
if (!(0, pending_1.isAnnouncementVisibleNow)({ announcement: announcement })) {
|
|
433
|
-
return [3 /*break*/, 5];
|
|
434
|
-
}
|
|
435
|
-
if (!(0, pending_1.matchesPlatform)({ announcement: announcement, platform: platform })) {
|
|
436
|
-
return [3 /*break*/, 5];
|
|
437
|
-
}
|
|
438
|
-
if (!(0, pending_1.passesMinBuildNumber)({ announcement: announcement, queryVersion: queryVersion })) {
|
|
439
|
-
return [3 /*break*/, 5];
|
|
440
|
-
}
|
|
441
|
-
visible.push(announcement);
|
|
442
|
-
_d.label = 5;
|
|
443
|
-
case 5:
|
|
444
|
-
published_2_1 = published_2.next();
|
|
445
|
-
return [3 /*break*/, 3];
|
|
446
|
-
case 6: return [3 /*break*/, 9];
|
|
447
|
-
case 7:
|
|
448
|
-
e_3_1 = _d.sent();
|
|
449
|
-
e_3 = { error: e_3_1 };
|
|
450
|
-
return [3 /*break*/, 9];
|
|
451
|
-
case 8:
|
|
452
|
-
try {
|
|
453
|
-
if (published_2_1 && !published_2_1.done && (_a = published_2.return)) _a.call(published_2);
|
|
454
|
-
}
|
|
455
|
-
finally { if (e_3) throw e_3.error; }
|
|
456
|
-
return [7 /*endfinally*/];
|
|
457
|
-
case 9:
|
|
458
|
-
sorted = visible.sort(function (left, right) {
|
|
459
|
-
var _a, _b, _c, _d;
|
|
460
|
-
if (right.priority !== left.priority) {
|
|
461
|
-
return right.priority - left.priority;
|
|
462
|
-
}
|
|
463
|
-
var rightPublished = (_b = (_a = right.publishedAt) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0;
|
|
464
|
-
var leftPublished = (_d = (_c = left.publishedAt) === null || _c === void 0 ? void 0 : _c.getTime()) !== null && _d !== void 0 ? _d : 0;
|
|
465
|
-
return rightPublished - leftPublished;
|
|
466
|
-
});
|
|
467
|
-
start = (page - 1) * limit;
|
|
468
|
-
pageItems = sorted
|
|
469
|
-
.slice(start, start + limit)
|
|
470
|
-
.map(function (announcement) { return (0, announcement_1.toAnnouncementPublic)(announcement, defaultAcknowledgementPolicy); });
|
|
471
|
-
return [2 /*return*/, res.json({
|
|
472
|
-
data: pageItems,
|
|
473
|
-
page: page,
|
|
474
|
-
total: sorted.length,
|
|
475
|
-
})];
|
|
243
|
+
return res.json({ data: { current, remainingCount } });
|
|
244
|
+
}));
|
|
245
|
+
userRouter.get("/feed", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(async (req, res) => {
|
|
246
|
+
const user = req.user;
|
|
247
|
+
if (!user) {
|
|
248
|
+
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
249
|
+
}
|
|
250
|
+
const platform = parsePlatform(req);
|
|
251
|
+
const queryVersion = (0, pending_1.parseQueryVersion)(req.query.version);
|
|
252
|
+
const limit = Math.min(Math.max(Number(req.query.limit ?? 20), 1), 100);
|
|
253
|
+
const page = Math.max(Number(req.query.page ?? 1), 1);
|
|
254
|
+
const published = (await announcement_1.Announcement.find({ status: "published" }).lean());
|
|
255
|
+
const visible = [];
|
|
256
|
+
for (const announcement of published) {
|
|
257
|
+
const matchesAudience = await matchesAnnouncementAudience(user, announcement);
|
|
258
|
+
if (!matchesAudience) {
|
|
259
|
+
continue;
|
|
476
260
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
userRouter.post("/:id/acknowledge", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
480
|
-
var user, userId, announcement, _a, existing;
|
|
481
|
-
var _b;
|
|
482
|
-
return __generator(this, function (_c) {
|
|
483
|
-
switch (_c.label) {
|
|
484
|
-
case 0:
|
|
485
|
-
user = req.user;
|
|
486
|
-
if (!user) {
|
|
487
|
-
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
488
|
-
}
|
|
489
|
-
userId = getUserId(user);
|
|
490
|
-
_a = requireVisibleAnnouncement;
|
|
491
|
-
_b = {};
|
|
492
|
-
return [4 /*yield*/, announcement_1.Announcement.findById(req.params.id)];
|
|
493
|
-
case 1: return [4 /*yield*/, _a.apply(void 0, [(_b.announcement = _c.sent(),
|
|
494
|
-
_b.isStaff = isStaff,
|
|
495
|
-
_b.matchAudience = matchAudience,
|
|
496
|
-
_b.platform = parsePlatform(req),
|
|
497
|
-
_b.queryVersion = (0, pending_1.parseQueryVersion)(req.query.version),
|
|
498
|
-
_b.user = user,
|
|
499
|
-
_b)])];
|
|
500
|
-
case 2:
|
|
501
|
-
announcement = _c.sent();
|
|
502
|
-
return [4 /*yield*/, (0, api_1.findOneOrNoneFor)(announcementAcknowledgement_1.AnnouncementAcknowledgement, {
|
|
503
|
-
announcementId: announcement._id,
|
|
504
|
-
userId: userId,
|
|
505
|
-
version: announcement.version,
|
|
506
|
-
})];
|
|
507
|
-
case 3:
|
|
508
|
-
existing = _c.sent();
|
|
509
|
-
if (!!existing) return [3 /*break*/, 5];
|
|
510
|
-
return [4 /*yield*/, announcementAcknowledgement_1.AnnouncementAcknowledgement.create({
|
|
511
|
-
acknowledgedAt: luxon_1.DateTime.utc().toJSDate(),
|
|
512
|
-
announcementId: announcement._id,
|
|
513
|
-
userId: userId,
|
|
514
|
-
version: announcement.version,
|
|
515
|
-
})];
|
|
516
|
-
case 4:
|
|
517
|
-
_c.sent();
|
|
518
|
-
_c.label = 5;
|
|
519
|
-
case 5: return [2 /*return*/, res.json({ data: { acknowledged: true } })];
|
|
261
|
+
if (!(0, pending_1.isAnnouncementVisibleNow)({ announcement })) {
|
|
262
|
+
continue;
|
|
520
263
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
userRouter.post("/:id/impression", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
524
|
-
var user, userId, bodyPlatform, platform, announcement, _a;
|
|
525
|
-
var _b;
|
|
526
|
-
var _c;
|
|
527
|
-
return __generator(this, function (_d) {
|
|
528
|
-
switch (_d.label) {
|
|
529
|
-
case 0:
|
|
530
|
-
user = req.user;
|
|
531
|
-
if (!user) {
|
|
532
|
-
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
533
|
-
}
|
|
534
|
-
userId = getUserId(user);
|
|
535
|
-
bodyPlatform = (_c = req.body) === null || _c === void 0 ? void 0 : _c.platform;
|
|
536
|
-
platform = (0, announcementImpression_1.isValidPlatform)(bodyPlatform) ? bodyPlatform : parsePlatform(req);
|
|
537
|
-
_a = requireVisibleAnnouncement;
|
|
538
|
-
_b = {};
|
|
539
|
-
return [4 /*yield*/, announcement_1.Announcement.findById(req.params.id)];
|
|
540
|
-
case 1: return [4 /*yield*/, _a.apply(void 0, [(_b.announcement = _d.sent(),
|
|
541
|
-
_b.isStaff = isStaff,
|
|
542
|
-
_b.matchAudience = matchAudience,
|
|
543
|
-
_b.platform = platform,
|
|
544
|
-
_b.queryVersion = (0, pending_1.parseQueryVersion)(req.query.version),
|
|
545
|
-
_b.user = user,
|
|
546
|
-
_b)])];
|
|
547
|
-
case 2:
|
|
548
|
-
announcement = _d.sent();
|
|
549
|
-
return [4 /*yield*/, announcementImpression_1.AnnouncementImpression.create({
|
|
550
|
-
announcementId: announcement._id,
|
|
551
|
-
platform: platform,
|
|
552
|
-
userId: userId,
|
|
553
|
-
version: announcement.version,
|
|
554
|
-
viewedAt: luxon_1.DateTime.utc().toJSDate(),
|
|
555
|
-
})];
|
|
556
|
-
case 3:
|
|
557
|
-
_d.sent();
|
|
558
|
-
return [2 /*return*/, res.json({ data: { recorded: true } })];
|
|
264
|
+
if (!(0, pending_1.matchesPlatform)({ announcement, platform })) {
|
|
265
|
+
continue;
|
|
559
266
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
userRouter.post("/:id/click", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
563
|
-
var user, userId, body, platform, announcement, _a;
|
|
564
|
-
var _b;
|
|
565
|
-
return __generator(this, function (_c) {
|
|
566
|
-
switch (_c.label) {
|
|
567
|
-
case 0:
|
|
568
|
-
user = req.user;
|
|
569
|
-
if (!user) {
|
|
570
|
-
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
571
|
-
}
|
|
572
|
-
userId = getUserId(user);
|
|
573
|
-
body = req.body;
|
|
574
|
-
platform = resolveClickPlatform(req, body.platform);
|
|
575
|
-
_a = requireVisibleAnnouncement;
|
|
576
|
-
_b = {};
|
|
577
|
-
return [4 /*yield*/, announcement_1.Announcement.findById(req.params.id)];
|
|
578
|
-
case 1: return [4 /*yield*/, _a.apply(void 0, [(_b.announcement = _c.sent(),
|
|
579
|
-
_b.isStaff = isStaff,
|
|
580
|
-
_b.matchAudience = matchAudience,
|
|
581
|
-
_b.platform = platform,
|
|
582
|
-
_b.queryVersion = (0, pending_1.parseQueryVersion)(req.query.version),
|
|
583
|
-
_b.user = user,
|
|
584
|
-
_b)])];
|
|
585
|
-
case 2:
|
|
586
|
-
announcement = _c.sent();
|
|
587
|
-
if (!(0, announcementClickEvent_1.isValidClickAction)(body.action)) {
|
|
588
|
-
throw new api_1.APIError({ status: 400, title: "Invalid click action" });
|
|
589
|
-
}
|
|
590
|
-
if (!hasPrimaryAction(announcement)) {
|
|
591
|
-
throw new api_1.APIError({ status: 400, title: "Announcement has no primary action" });
|
|
592
|
-
}
|
|
593
|
-
return [4 /*yield*/, announcementClickEvent_1.AnnouncementClickEvent.create({
|
|
594
|
-
action: body.action,
|
|
595
|
-
announcementId: announcement._id,
|
|
596
|
-
clickedAt: luxon_1.DateTime.utc().toJSDate(),
|
|
597
|
-
platform: platform,
|
|
598
|
-
userId: userId,
|
|
599
|
-
version: announcement.version,
|
|
600
|
-
})];
|
|
601
|
-
case 3:
|
|
602
|
-
_c.sent();
|
|
603
|
-
return [2 /*return*/, res.json({ data: { recorded: true } })];
|
|
267
|
+
if (!(0, pending_1.passesMinBuildNumber)({ announcement, queryVersion })) {
|
|
268
|
+
continue;
|
|
604
269
|
}
|
|
270
|
+
visible.push(announcement);
|
|
271
|
+
}
|
|
272
|
+
const sorted = visible.sort((left, right) => {
|
|
273
|
+
if (right.priority !== left.priority) {
|
|
274
|
+
return right.priority - left.priority;
|
|
275
|
+
}
|
|
276
|
+
const rightPublished = right.publishedAt?.getTime() ?? 0;
|
|
277
|
+
const leftPublished = left.publishedAt?.getTime() ?? 0;
|
|
278
|
+
return rightPublished - leftPublished;
|
|
605
279
|
});
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
280
|
+
const start = (page - 1) * limit;
|
|
281
|
+
const pageItems = sorted
|
|
282
|
+
.slice(start, start + limit)
|
|
283
|
+
.map((announcement) => (0, announcement_1.toAnnouncementPublic)(announcement, defaultAcknowledgementPolicy));
|
|
284
|
+
return res.json({
|
|
285
|
+
data: pageItems,
|
|
286
|
+
page,
|
|
287
|
+
total: sorted.length,
|
|
288
|
+
});
|
|
289
|
+
}));
|
|
290
|
+
userRouter.post("/:id/acknowledge", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(async (req, res) => {
|
|
291
|
+
const user = req.user;
|
|
292
|
+
if (!user) {
|
|
293
|
+
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
294
|
+
}
|
|
295
|
+
const userId = getUserId(user);
|
|
296
|
+
const announcement = await requireVisibleAnnouncement({
|
|
297
|
+
announcement: await announcement_1.Announcement.findById(req.params.id),
|
|
298
|
+
isStaff,
|
|
299
|
+
matchAudience,
|
|
300
|
+
platform: parsePlatform(req),
|
|
301
|
+
queryVersion: (0, pending_1.parseQueryVersion)(req.query.version),
|
|
302
|
+
user,
|
|
303
|
+
});
|
|
304
|
+
const existing = await (0, api_1.findOneOrNoneFor)(announcementAcknowledgement_1.AnnouncementAcknowledgement, {
|
|
305
|
+
announcementId: announcement._id,
|
|
306
|
+
userId,
|
|
307
|
+
version: announcement.version,
|
|
633
308
|
});
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
309
|
+
if (!existing) {
|
|
310
|
+
await announcementAcknowledgement_1.AnnouncementAcknowledgement.create({
|
|
311
|
+
acknowledgedAt: luxon_1.DateTime.utc().toJSDate(),
|
|
312
|
+
announcementId: announcement._id,
|
|
313
|
+
userId,
|
|
314
|
+
version: announcement.version,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return res.json({ data: { acknowledged: true } });
|
|
318
|
+
}));
|
|
319
|
+
userRouter.post("/:id/impression", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(async (req, res) => {
|
|
320
|
+
const user = req.user;
|
|
321
|
+
if (!user) {
|
|
322
|
+
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
323
|
+
}
|
|
324
|
+
const userId = getUserId(user);
|
|
325
|
+
const bodyPlatform = req.body?.platform;
|
|
326
|
+
const platform = (0, announcementImpression_1.isValidPlatform)(bodyPlatform) ? bodyPlatform : parsePlatform(req);
|
|
327
|
+
const announcement = await requireVisibleAnnouncement({
|
|
328
|
+
announcement: await announcement_1.Announcement.findById(req.params.id),
|
|
329
|
+
isStaff,
|
|
330
|
+
matchAudience,
|
|
331
|
+
platform,
|
|
332
|
+
queryVersion: (0, pending_1.parseQueryVersion)(req.query.version),
|
|
333
|
+
user,
|
|
334
|
+
});
|
|
335
|
+
await announcementImpression_1.AnnouncementImpression.create({
|
|
336
|
+
announcementId: announcement._id,
|
|
337
|
+
platform,
|
|
338
|
+
userId,
|
|
339
|
+
version: announcement.version,
|
|
340
|
+
viewedAt: luxon_1.DateTime.utc().toJSDate(),
|
|
341
|
+
});
|
|
342
|
+
return res.json({ data: { recorded: true } });
|
|
343
|
+
}));
|
|
344
|
+
userRouter.post("/:id/click", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(async (req, res) => {
|
|
345
|
+
const user = req.user;
|
|
346
|
+
if (!user) {
|
|
347
|
+
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
348
|
+
}
|
|
349
|
+
const userId = getUserId(user);
|
|
350
|
+
const body = req.body;
|
|
351
|
+
const platform = resolveClickPlatform(req, body.platform);
|
|
352
|
+
const announcement = await requireVisibleAnnouncement({
|
|
353
|
+
announcement: await announcement_1.Announcement.findById(req.params.id),
|
|
354
|
+
isStaff,
|
|
355
|
+
matchAudience,
|
|
356
|
+
platform,
|
|
357
|
+
queryVersion: (0, pending_1.parseQueryVersion)(req.query.version),
|
|
358
|
+
user,
|
|
359
|
+
});
|
|
360
|
+
if (!(0, announcementClickEvent_1.isValidClickAction)(body.action)) {
|
|
361
|
+
throw new api_1.APIError({ status: 400, title: "Invalid click action" });
|
|
362
|
+
}
|
|
363
|
+
if (!hasPrimaryAction(announcement)) {
|
|
364
|
+
throw new api_1.APIError({ status: 400, title: "Announcement has no primary action" });
|
|
365
|
+
}
|
|
366
|
+
await announcementClickEvent_1.AnnouncementClickEvent.create({
|
|
367
|
+
action: body.action,
|
|
368
|
+
announcementId: announcement._id,
|
|
369
|
+
clickedAt: luxon_1.DateTime.utc().toJSDate(),
|
|
370
|
+
platform,
|
|
371
|
+
userId,
|
|
372
|
+
version: announcement.version,
|
|
373
|
+
});
|
|
374
|
+
return res.json({ data: { recorded: true } });
|
|
375
|
+
}));
|
|
376
|
+
const adminRouter = (0, express_1.Router)();
|
|
377
|
+
const routeOpenApi = openApi ? { openApi: openApi } : undefined;
|
|
378
|
+
adminRouter.get("/config", [
|
|
379
|
+
(0, api_1.authenticateMiddleware)(),
|
|
380
|
+
...(routeOpenApi
|
|
381
|
+
? [
|
|
382
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
383
|
+
.withTags(["announcements"])
|
|
384
|
+
.withSummary("Get announcement plugin configuration")
|
|
385
|
+
.withResponse(200, {
|
|
386
|
+
data: {
|
|
647
387
|
properties: {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
displayMode: { type: "string" },
|
|
652
|
-
expiresAt: { type: "string" },
|
|
653
|
-
metrics: {
|
|
654
|
-
properties: {
|
|
655
|
-
acknowledgements: { type: "number" },
|
|
656
|
-
clicks: { type: "number" },
|
|
657
|
-
impressions: { type: "number" },
|
|
658
|
-
},
|
|
659
|
-
type: "object",
|
|
388
|
+
defaultAcknowledgementPolicy: {
|
|
389
|
+
description: '"required" or "dismiss-only"',
|
|
390
|
+
type: "string",
|
|
660
391
|
},
|
|
661
|
-
priority: { type: "number" },
|
|
662
|
-
publishedAt: { type: "string" },
|
|
663
|
-
status: { type: "string" },
|
|
664
|
-
title: { type: "string" },
|
|
665
|
-
version: { type: "number" },
|
|
666
392
|
},
|
|
667
393
|
type: "object",
|
|
668
394
|
},
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
395
|
+
})
|
|
396
|
+
.build(),
|
|
397
|
+
]
|
|
398
|
+
: []),
|
|
399
|
+
], (0, api_1.asyncHandler)(async (req, res) => {
|
|
400
|
+
requireAdmin(req.user);
|
|
401
|
+
return res.json({ data: { defaultAcknowledgementPolicy } });
|
|
402
|
+
}));
|
|
403
|
+
adminRouter.get("/overview", [
|
|
404
|
+
(0, api_1.authenticateMiddleware)(),
|
|
405
|
+
...(routeOpenApi
|
|
406
|
+
? [
|
|
407
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
408
|
+
.withTags(["announcements"])
|
|
409
|
+
.withSummary("Admin overview of announcements with aggregate metrics")
|
|
410
|
+
.withQueryParameter("page", { type: "number" }, { required: false })
|
|
411
|
+
.withQueryParameter("limit", { type: "number" }, { required: false })
|
|
412
|
+
.withResponse(200, {
|
|
413
|
+
data: {
|
|
414
|
+
items: {
|
|
415
|
+
properties: {
|
|
416
|
+
_id: { type: "string" },
|
|
417
|
+
acknowledgementPolicy: { type: "string" },
|
|
418
|
+
audienceType: { type: "string" },
|
|
419
|
+
displayMode: { type: "string" },
|
|
420
|
+
expiresAt: { type: "string" },
|
|
421
|
+
metrics: {
|
|
422
|
+
properties: {
|
|
423
|
+
acknowledgements: { type: "number" },
|
|
424
|
+
clicks: { type: "number" },
|
|
425
|
+
impressions: { type: "number" },
|
|
426
|
+
},
|
|
427
|
+
type: "object",
|
|
428
|
+
},
|
|
429
|
+
priority: { type: "number" },
|
|
430
|
+
publishedAt: { type: "string" },
|
|
431
|
+
status: { type: "string" },
|
|
432
|
+
title: { type: "string" },
|
|
433
|
+
version: { type: "number" },
|
|
434
|
+
},
|
|
435
|
+
type: "object",
|
|
436
|
+
},
|
|
437
|
+
type: "array",
|
|
684
438
|
},
|
|
685
|
-
type: "
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
switch (_a.label) {
|
|
713
|
-
case 0:
|
|
714
|
-
requireAdmin(req.user);
|
|
715
|
-
return [4 /*yield*/, announcement_1.Announcement.findById(req.params.id)];
|
|
716
|
-
case 1:
|
|
717
|
-
announcement = _a.sent();
|
|
718
|
-
if (!announcement) {
|
|
719
|
-
throw new api_1.APIError({ status: 404, title: "Announcement not found" });
|
|
720
|
-
}
|
|
721
|
-
if (announcement.status !== "draft") {
|
|
722
|
-
throw new api_1.APIError({
|
|
723
|
-
status: 400,
|
|
724
|
-
title: "Only draft announcements can be published",
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
announcement.status = "published";
|
|
728
|
-
announcement.publishedAt = luxon_1.DateTime.utc().toJSDate();
|
|
729
|
-
return [4 /*yield*/, announcement.save()];
|
|
730
|
-
case 2:
|
|
731
|
-
_a.sent();
|
|
732
|
-
api_1.logger.info("Announcement published", { announcementId: announcement._id.toString() });
|
|
733
|
-
return [2 /*return*/, res.json({ data: announcement })];
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
}); }));
|
|
737
|
-
adminRouter.post("/:id/archive", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
738
|
-
var announcement;
|
|
739
|
-
return __generator(this, function (_a) {
|
|
740
|
-
switch (_a.label) {
|
|
741
|
-
case 0:
|
|
742
|
-
requireAdmin(req.user);
|
|
743
|
-
return [4 /*yield*/, announcement_1.Announcement.findById(req.params.id)];
|
|
744
|
-
case 1:
|
|
745
|
-
announcement = _a.sent();
|
|
746
|
-
if (!announcement) {
|
|
747
|
-
throw new api_1.APIError({ status: 404, title: "Announcement not found" });
|
|
748
|
-
}
|
|
749
|
-
if (announcement.status !== "published") {
|
|
750
|
-
throw new api_1.APIError({
|
|
751
|
-
status: 400,
|
|
752
|
-
title: "Only published announcements can be archived",
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
announcement.status = "archived";
|
|
756
|
-
announcement.archivedAt = luxon_1.DateTime.utc().toJSDate();
|
|
757
|
-
return [4 /*yield*/, announcement.save()];
|
|
758
|
-
case 2:
|
|
759
|
-
_a.sent();
|
|
760
|
-
api_1.logger.info("Announcement archived", { announcementId: announcement._id.toString() });
|
|
761
|
-
return [2 /*return*/, res.json({ data: announcement })];
|
|
762
|
-
}
|
|
439
|
+
limit: { type: "number" },
|
|
440
|
+
more: { type: "boolean" },
|
|
441
|
+
page: { type: "number" },
|
|
442
|
+
total: { type: "number" },
|
|
443
|
+
totals: {
|
|
444
|
+
properties: {
|
|
445
|
+
acknowledgements: { type: "number" },
|
|
446
|
+
announcements: { type: "number" },
|
|
447
|
+
archived: { type: "number" },
|
|
448
|
+
clicks: { type: "number" },
|
|
449
|
+
draft: { type: "number" },
|
|
450
|
+
impressions: { type: "number" },
|
|
451
|
+
published: { type: "number" },
|
|
452
|
+
},
|
|
453
|
+
type: "object",
|
|
454
|
+
},
|
|
455
|
+
})
|
|
456
|
+
.build(),
|
|
457
|
+
]
|
|
458
|
+
: []),
|
|
459
|
+
], (0, api_1.asyncHandler)(async (req, res) => {
|
|
460
|
+
await requireOverviewAccess({ permissions: overviewPermissions, user: req.user });
|
|
461
|
+
const { limit, page } = (0, overview_1.parseOverviewPagination)(req.query);
|
|
462
|
+
const overview = await (0, overview_1.fetchAnnouncementOverview)({
|
|
463
|
+
defaultAcknowledgementPolicy,
|
|
464
|
+
limit,
|
|
465
|
+
page,
|
|
763
466
|
});
|
|
764
|
-
|
|
467
|
+
return res.json(overview);
|
|
468
|
+
}));
|
|
469
|
+
adminRouter.post("/:id/publish", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(async (req, res) => {
|
|
470
|
+
requireAdmin(req.user);
|
|
471
|
+
const announcement = await announcement_1.Announcement.findById(req.params.id);
|
|
472
|
+
if (!announcement) {
|
|
473
|
+
throw new api_1.APIError({ status: 404, title: "Announcement not found" });
|
|
474
|
+
}
|
|
475
|
+
if (announcement.status !== "draft") {
|
|
476
|
+
throw new api_1.APIError({
|
|
477
|
+
status: 400,
|
|
478
|
+
title: "Only draft announcements can be published",
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
announcement.status = "published";
|
|
482
|
+
announcement.publishedAt = luxon_1.DateTime.utc().toJSDate();
|
|
483
|
+
await announcement.save();
|
|
484
|
+
api_1.logger.info("Announcement published", { announcementId: announcement._id.toString() });
|
|
485
|
+
return res.json({ data: announcement });
|
|
486
|
+
}));
|
|
487
|
+
adminRouter.post("/:id/archive", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(async (req, res) => {
|
|
488
|
+
requireAdmin(req.user);
|
|
489
|
+
const announcement = await announcement_1.Announcement.findById(req.params.id);
|
|
490
|
+
if (!announcement) {
|
|
491
|
+
throw new api_1.APIError({ status: 404, title: "Announcement not found" });
|
|
492
|
+
}
|
|
493
|
+
if (announcement.status !== "published") {
|
|
494
|
+
throw new api_1.APIError({
|
|
495
|
+
status: 400,
|
|
496
|
+
title: "Only published announcements can be archived",
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
announcement.status = "archived";
|
|
500
|
+
announcement.archivedAt = luxon_1.DateTime.utc().toJSDate();
|
|
501
|
+
await announcement.save();
|
|
502
|
+
api_1.logger.info("Announcement archived", { announcementId: announcement._id.toString() });
|
|
503
|
+
return res.json({ data: announcement });
|
|
504
|
+
}));
|
|
765
505
|
app.use(basePath, userRouter);
|
|
766
506
|
app.use(basePath, adminRouter);
|
|
767
|
-
if (
|
|
507
|
+
if (this.options.help?.enabled) {
|
|
768
508
|
(0, helpRoutes_1.registerAnnouncementHelpRoutes)({
|
|
769
|
-
app
|
|
770
|
-
basePath
|
|
771
|
-
defaultAcknowledgementPolicy
|
|
772
|
-
isStaff
|
|
773
|
-
matchAudience
|
|
509
|
+
app,
|
|
510
|
+
basePath,
|
|
511
|
+
defaultAcknowledgementPolicy,
|
|
512
|
+
isStaff,
|
|
513
|
+
matchAudience,
|
|
774
514
|
});
|
|
775
515
|
}
|
|
776
516
|
app.use(basePath, (0, api_1.modelRouter)(announcement_1.Announcement, routerOptions));
|
|
@@ -813,8 +553,7 @@ var AnnouncementsApp = /** @class */ (function () {
|
|
|
813
553
|
{ fields: ["email", "name"], path: "userId" },
|
|
814
554
|
],
|
|
815
555
|
}));
|
|
816
|
-
api_1.logger.info("AnnouncementsApp registered", { basePath
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
}());
|
|
556
|
+
api_1.logger.info("AnnouncementsApp registered", { basePath, defaultAcknowledgementPolicy });
|
|
557
|
+
}
|
|
558
|
+
}
|
|
820
559
|
exports.AnnouncementsApp = AnnouncementsApp;
|