@terreno/announcements 57.4.0
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/LICENSE +21 -0
- package/README.md +50 -0
- package/biome.jsonc +20 -0
- package/bunfig.toml +2 -0
- package/dist/announcementsApp.d.ts +9 -0
- package/dist/announcementsApp.js +820 -0
- package/dist/help.d.ts +30 -0
- package/dist/help.js +72 -0
- package/dist/helpRoutes.d.ts +8 -0
- package/dist/helpRoutes.js +233 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +34 -0
- package/dist/models/announcement.d.ts +3 -0
- package/dist/models/announcement.js +214 -0
- package/dist/models/announcementAcknowledgement.d.ts +2 -0
- package/dist/models/announcementAcknowledgement.js +41 -0
- package/dist/models/announcementClickEvent.d.ts +3 -0
- package/dist/models/announcementClickEvent.js +56 -0
- package/dist/models/announcementImpression.d.ts +3 -0
- package/dist/models/announcementImpression.js +50 -0
- package/dist/overview.d.ts +14 -0
- package/dist/overview.js +310 -0
- package/dist/pending.d.ts +64 -0
- package/dist/pending.js +242 -0
- package/dist/types.d.ts +135 -0
- package/dist/types.js +2 -0
- package/package.json +57 -0
- package/src/announcementsApp.ts +722 -0
- package/src/help.ts +98 -0
- package/src/helpRoutes.ts +160 -0
- package/src/index.ts +45 -0
- package/src/models/announcement.ts +189 -0
- package/src/models/announcementAcknowledgement.ts +50 -0
- package/src/models/announcementClickEvent.ts +68 -0
- package/src/models/announcementImpression.ts +62 -0
- package/src/overview.ts +228 -0
- package/src/pending.ts +286 -0
- package/src/tests/announcementsApp.test.ts +844 -0
- package/src/tests/bunSetup.ts +9 -0
- package/src/tests/help.test.ts +29 -0
- package/src/tests/helpRoutes.test.ts +291 -0
- package/src/tests/index.test.ts +57 -0
- package/src/tests/overview.test.ts +351 -0
- package/src/tests/pending.test.ts +262 -0
- package/src/types.ts +157 -0
- package/tsconfig.json +33 -0
package/dist/help.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AcknowledgementPolicy, AnnouncementDocument, AnnouncementStatus } from "./types";
|
|
2
|
+
export interface AnnouncementHelpSummary {
|
|
3
|
+
archivedAt?: string;
|
|
4
|
+
excerpt: string;
|
|
5
|
+
id: string;
|
|
6
|
+
publishedAt?: string;
|
|
7
|
+
status: AnnouncementStatus;
|
|
8
|
+
title: string;
|
|
9
|
+
version: number;
|
|
10
|
+
}
|
|
11
|
+
export interface AnnouncementHelpDetail {
|
|
12
|
+
archivedAt?: string;
|
|
13
|
+
body: string;
|
|
14
|
+
expiresAt?: string;
|
|
15
|
+
id: string;
|
|
16
|
+
platforms: AnnouncementDocument["platforms"];
|
|
17
|
+
primaryAction?: AnnouncementDocument["primaryAction"];
|
|
18
|
+
priority: number;
|
|
19
|
+
publishAt?: string;
|
|
20
|
+
publishedAt?: string;
|
|
21
|
+
requiresAcknowledgement: boolean;
|
|
22
|
+
status: AnnouncementStatus;
|
|
23
|
+
title: string;
|
|
24
|
+
version: number;
|
|
25
|
+
}
|
|
26
|
+
export declare const buildHelpStatusFilter: (includeArchived: boolean) => AnnouncementStatus[];
|
|
27
|
+
export declare const excerptBody: (body: string, maxLength?: number) => string;
|
|
28
|
+
export declare const matchesHelpQueries: (doc: Pick<AnnouncementDocument, "body" | "title">, queries: string[]) => boolean;
|
|
29
|
+
export declare const toHelpSummary: (doc: AnnouncementDocument) => AnnouncementHelpSummary;
|
|
30
|
+
export declare const toHelpDetail: (doc: AnnouncementDocument, defaultAcknowledgementPolicy?: AcknowledgementPolicy) => AnnouncementHelpDetail;
|
package/dist/help.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toHelpDetail = exports.toHelpSummary = exports.matchesHelpQueries = exports.excerptBody = exports.buildHelpStatusFilter = void 0;
|
|
4
|
+
var pending_1 = require("./pending");
|
|
5
|
+
var buildHelpStatusFilter = function (includeArchived) {
|
|
6
|
+
if (includeArchived) {
|
|
7
|
+
return ["published", "archived"];
|
|
8
|
+
}
|
|
9
|
+
return ["published"];
|
|
10
|
+
};
|
|
11
|
+
exports.buildHelpStatusFilter = buildHelpStatusFilter;
|
|
12
|
+
var excerptBody = function (body, maxLength) {
|
|
13
|
+
if (maxLength === void 0) { maxLength = 240; }
|
|
14
|
+
var normalized = body.replace(/\s+/g, " ").trim();
|
|
15
|
+
if (normalized.length <= maxLength) {
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
return "".concat(normalized.slice(0, maxLength), "\u2026");
|
|
19
|
+
};
|
|
20
|
+
exports.excerptBody = excerptBody;
|
|
21
|
+
var matchesSingleQuery = function (doc, query) {
|
|
22
|
+
var normalized = query.trim().toLowerCase();
|
|
23
|
+
if (!normalized) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
var haystack = "".concat(doc.title, "\n").concat(doc.body).toLowerCase();
|
|
27
|
+
return haystack.includes(normalized);
|
|
28
|
+
};
|
|
29
|
+
var matchesHelpQueries = function (doc, queries) {
|
|
30
|
+
var activeQueries = queries.map(function (query) { return query.trim(); }).filter(Boolean);
|
|
31
|
+
if (activeQueries.length === 0) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return activeQueries.some(function (query) { return matchesSingleQuery(doc, query); });
|
|
35
|
+
};
|
|
36
|
+
exports.matchesHelpQueries = matchesHelpQueries;
|
|
37
|
+
var toHelpSummary = function (doc) {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
return ({
|
|
40
|
+
archivedAt: (_a = doc.archivedAt) === null || _a === void 0 ? void 0 : _a.toISOString(),
|
|
41
|
+
excerpt: (0, exports.excerptBody)(doc.body),
|
|
42
|
+
id: doc._id.toString(),
|
|
43
|
+
publishedAt: (_b = doc.publishedAt) === null || _b === void 0 ? void 0 : _b.toISOString(),
|
|
44
|
+
status: doc.status,
|
|
45
|
+
title: doc.title,
|
|
46
|
+
version: doc.version,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
exports.toHelpSummary = toHelpSummary;
|
|
50
|
+
var toHelpDetail = function (doc, defaultAcknowledgementPolicy) {
|
|
51
|
+
var _a, _b, _c, _d;
|
|
52
|
+
if (defaultAcknowledgementPolicy === void 0) { defaultAcknowledgementPolicy = "dismiss-only"; }
|
|
53
|
+
return ({
|
|
54
|
+
archivedAt: (_a = doc.archivedAt) === null || _a === void 0 ? void 0 : _a.toISOString(),
|
|
55
|
+
body: doc.body,
|
|
56
|
+
expiresAt: (_b = doc.expiresAt) === null || _b === void 0 ? void 0 : _b.toISOString(),
|
|
57
|
+
id: doc._id.toString(),
|
|
58
|
+
platforms: doc.platforms,
|
|
59
|
+
primaryAction: doc.primaryAction,
|
|
60
|
+
priority: doc.priority,
|
|
61
|
+
publishAt: (_c = doc.publishAt) === null || _c === void 0 ? void 0 : _c.toISOString(),
|
|
62
|
+
publishedAt: (_d = doc.publishedAt) === null || _d === void 0 ? void 0 : _d.toISOString(),
|
|
63
|
+
requiresAcknowledgement: (0, pending_1.requiresAcknowledgementForAnnouncement)({
|
|
64
|
+
announcement: doc,
|
|
65
|
+
defaultAcknowledgementPolicy: defaultAcknowledgementPolicy,
|
|
66
|
+
}),
|
|
67
|
+
status: doc.status,
|
|
68
|
+
title: doc.title,
|
|
69
|
+
version: doc.version,
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
exports.toHelpDetail = toHelpDetail;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AcknowledgementPolicy, MatchAudienceFunction } from "./types";
|
|
2
|
+
export declare const registerAnnouncementHelpRoutes: ({ app, basePath, defaultAcknowledgementPolicy, isStaff, matchAudience, }: {
|
|
3
|
+
app: import("express").Application;
|
|
4
|
+
basePath: string;
|
|
5
|
+
defaultAcknowledgementPolicy?: AcknowledgementPolicy;
|
|
6
|
+
isStaff?: (user: unknown) => boolean;
|
|
7
|
+
matchAudience?: MatchAudienceFunction;
|
|
8
|
+
}) => void;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
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);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
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;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
39
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
40
|
+
if (!m) return o;
|
|
41
|
+
var i = m.call(o), r, ar = [], e;
|
|
42
|
+
try {
|
|
43
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
44
|
+
}
|
|
45
|
+
catch (error) { e = { error: error }; }
|
|
46
|
+
finally {
|
|
47
|
+
try {
|
|
48
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
49
|
+
}
|
|
50
|
+
finally { if (e) throw e.error; }
|
|
51
|
+
}
|
|
52
|
+
return ar;
|
|
53
|
+
};
|
|
54
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
55
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
56
|
+
if (ar || !(i in from)) {
|
|
57
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
58
|
+
ar[i] = from[i];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
62
|
+
};
|
|
63
|
+
var __values = (this && this.__values) || function(o) {
|
|
64
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
65
|
+
if (m) return m.call(o);
|
|
66
|
+
if (o && typeof o.length === "number") return {
|
|
67
|
+
next: function () {
|
|
68
|
+
if (o && i >= o.length) o = void 0;
|
|
69
|
+
return { value: o && o[i++], done: !o };
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
73
|
+
};
|
|
74
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
75
|
+
exports.registerAnnouncementHelpRoutes = void 0;
|
|
76
|
+
var api_1 = require("@terreno/api");
|
|
77
|
+
var express_1 = require("express");
|
|
78
|
+
var help_1 = require("./help");
|
|
79
|
+
var announcement_1 = require("./models/announcement");
|
|
80
|
+
var pending_1 = require("./pending");
|
|
81
|
+
var defaultIsStaff = function (user) { return user.admin === true; };
|
|
82
|
+
var parseHelpQueries = function (req) {
|
|
83
|
+
var raw = req.query.q;
|
|
84
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
85
|
+
return [raw.trim()];
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(raw)) {
|
|
88
|
+
return raw.filter(function (value) { return typeof value === "string" && value.trim().length > 0; });
|
|
89
|
+
}
|
|
90
|
+
return [];
|
|
91
|
+
};
|
|
92
|
+
var parseIncludeArchived = function (req) {
|
|
93
|
+
var raw = req.query.includeArchived;
|
|
94
|
+
if (raw === "true" || raw === "1") {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
var parseLimit = function (req) {
|
|
100
|
+
var _a;
|
|
101
|
+
var parsed = Number((_a = req.query.limit) !== null && _a !== void 0 ? _a : 10);
|
|
102
|
+
return Math.min(Math.max(parsed, 1), 50);
|
|
103
|
+
};
|
|
104
|
+
var isAnnouncementHelpVisible = function (announcement) {
|
|
105
|
+
if (announcement.status === "archived") {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return (0, pending_1.isAnnouncementVisibleNow)({ announcement: announcement });
|
|
109
|
+
};
|
|
110
|
+
var sortHelpResults = function (docs) {
|
|
111
|
+
return __spreadArray([], __read(docs), false).sort(function (left, right) {
|
|
112
|
+
var _a, _b, _c, _d;
|
|
113
|
+
if (right.priority !== left.priority) {
|
|
114
|
+
return right.priority - left.priority;
|
|
115
|
+
}
|
|
116
|
+
var rightPublished = (_b = (_a = right.publishedAt) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : 0;
|
|
117
|
+
var leftPublished = (_d = (_c = left.publishedAt) === null || _c === void 0 ? void 0 : _c.getTime()) !== null && _d !== void 0 ? _d : 0;
|
|
118
|
+
return rightPublished - leftPublished;
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
var registerAnnouncementHelpRoutes = function (_a) {
|
|
122
|
+
var app = _a.app, basePath = _a.basePath, _b = _a.defaultAcknowledgementPolicy, defaultAcknowledgementPolicy = _b === void 0 ? "dismiss-only" : _b, _c = _a.isStaff, isStaff = _c === void 0 ? defaultIsStaff : _c, matchAudience = _a.matchAudience;
|
|
123
|
+
var audienceMatcher = matchAudience !== null && matchAudience !== void 0 ? matchAudience : (function () { return true; });
|
|
124
|
+
var matchesAnnouncementAudience = function (user, announcement) { return __awaiter(void 0, void 0, void 0, function () {
|
|
125
|
+
return __generator(this, function (_a) {
|
|
126
|
+
if (!(0, pending_1.matchAudienceByType)({ announcement: announcement, isStaff: isStaff, user: user })) {
|
|
127
|
+
return [2 /*return*/, false];
|
|
128
|
+
}
|
|
129
|
+
return [2 /*return*/, audienceMatcher(user, announcement)];
|
|
130
|
+
});
|
|
131
|
+
}); };
|
|
132
|
+
var router = (0, express_1.Router)();
|
|
133
|
+
router.get("/help/search", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
134
|
+
var user, queries, includeArchived, limit, queryVersion, statuses, candidates, matched, candidates_1, candidates_1_1, doc, matchesAudience, e_1_1, page;
|
|
135
|
+
var e_1, _a;
|
|
136
|
+
return __generator(this, function (_b) {
|
|
137
|
+
switch (_b.label) {
|
|
138
|
+
case 0:
|
|
139
|
+
user = req.user;
|
|
140
|
+
if (!user) {
|
|
141
|
+
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
142
|
+
}
|
|
143
|
+
queries = parseHelpQueries(req);
|
|
144
|
+
includeArchived = parseIncludeArchived(req);
|
|
145
|
+
limit = parseLimit(req);
|
|
146
|
+
queryVersion = (0, pending_1.parseQueryVersion)(req.query.version);
|
|
147
|
+
statuses = (0, help_1.buildHelpStatusFilter)(includeArchived);
|
|
148
|
+
return [4 /*yield*/, announcement_1.Announcement.find({ status: { $in: statuses } }).lean()];
|
|
149
|
+
case 1:
|
|
150
|
+
candidates = (_b.sent());
|
|
151
|
+
matched = [];
|
|
152
|
+
_b.label = 2;
|
|
153
|
+
case 2:
|
|
154
|
+
_b.trys.push([2, 7, 8, 9]);
|
|
155
|
+
candidates_1 = __values(candidates), candidates_1_1 = candidates_1.next();
|
|
156
|
+
_b.label = 3;
|
|
157
|
+
case 3:
|
|
158
|
+
if (!!candidates_1_1.done) return [3 /*break*/, 6];
|
|
159
|
+
doc = candidates_1_1.value;
|
|
160
|
+
if (!isAnnouncementHelpVisible(doc)) {
|
|
161
|
+
return [3 /*break*/, 5];
|
|
162
|
+
}
|
|
163
|
+
if (!(0, pending_1.passesMinBuildNumber)({ announcement: doc, queryVersion: queryVersion })) {
|
|
164
|
+
return [3 /*break*/, 5];
|
|
165
|
+
}
|
|
166
|
+
return [4 /*yield*/, matchesAnnouncementAudience(user, doc)];
|
|
167
|
+
case 4:
|
|
168
|
+
matchesAudience = _b.sent();
|
|
169
|
+
if (!matchesAudience) {
|
|
170
|
+
return [3 /*break*/, 5];
|
|
171
|
+
}
|
|
172
|
+
if (!(0, help_1.matchesHelpQueries)(doc, queries)) {
|
|
173
|
+
return [3 /*break*/, 5];
|
|
174
|
+
}
|
|
175
|
+
matched.push(doc);
|
|
176
|
+
_b.label = 5;
|
|
177
|
+
case 5:
|
|
178
|
+
candidates_1_1 = candidates_1.next();
|
|
179
|
+
return [3 /*break*/, 3];
|
|
180
|
+
case 6: return [3 /*break*/, 9];
|
|
181
|
+
case 7:
|
|
182
|
+
e_1_1 = _b.sent();
|
|
183
|
+
e_1 = { error: e_1_1 };
|
|
184
|
+
return [3 /*break*/, 9];
|
|
185
|
+
case 8:
|
|
186
|
+
try {
|
|
187
|
+
if (candidates_1_1 && !candidates_1_1.done && (_a = candidates_1.return)) _a.call(candidates_1);
|
|
188
|
+
}
|
|
189
|
+
finally { if (e_1) throw e_1.error; }
|
|
190
|
+
return [7 /*endfinally*/];
|
|
191
|
+
case 9:
|
|
192
|
+
page = sortHelpResults(matched).slice(0, limit).map(help_1.toHelpSummary);
|
|
193
|
+
return [2 /*return*/, res.json({ data: page, total: matched.length })];
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}); }));
|
|
197
|
+
router.get("/help/:id", (0, api_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
198
|
+
var user, includeArchived, queryVersion, statuses, announcement, matchesAudience;
|
|
199
|
+
return __generator(this, function (_a) {
|
|
200
|
+
switch (_a.label) {
|
|
201
|
+
case 0:
|
|
202
|
+
user = req.user;
|
|
203
|
+
if (!user) {
|
|
204
|
+
throw new api_1.APIError({ status: 401, title: "Authentication required" });
|
|
205
|
+
}
|
|
206
|
+
includeArchived = parseIncludeArchived(req);
|
|
207
|
+
queryVersion = (0, pending_1.parseQueryVersion)(req.query.version);
|
|
208
|
+
statuses = (0, help_1.buildHelpStatusFilter)(includeArchived);
|
|
209
|
+
return [4 /*yield*/, announcement_1.Announcement.findById(req.params.id).lean()];
|
|
210
|
+
case 1:
|
|
211
|
+
announcement = (_a.sent());
|
|
212
|
+
if (!announcement || !statuses.includes(announcement.status)) {
|
|
213
|
+
throw new api_1.APIError({ status: 404, title: "Update note not found" });
|
|
214
|
+
}
|
|
215
|
+
if (!isAnnouncementHelpVisible(announcement)) {
|
|
216
|
+
throw new api_1.APIError({ status: 404, title: "Update note not found" });
|
|
217
|
+
}
|
|
218
|
+
if (!(0, pending_1.passesMinBuildNumber)({ announcement: announcement, queryVersion: queryVersion })) {
|
|
219
|
+
throw new api_1.APIError({ status: 404, title: "Update note not found" });
|
|
220
|
+
}
|
|
221
|
+
return [4 /*yield*/, matchesAnnouncementAudience(user, announcement)];
|
|
222
|
+
case 2:
|
|
223
|
+
matchesAudience = _a.sent();
|
|
224
|
+
if (!matchesAudience) {
|
|
225
|
+
throw new api_1.APIError({ status: 404, title: "Update note not found" });
|
|
226
|
+
}
|
|
227
|
+
return [2 /*return*/, res.json({ data: (0, help_1.toHelpDetail)(announcement, defaultAcknowledgementPolicy) })];
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}); }));
|
|
231
|
+
app.use(basePath, router);
|
|
232
|
+
};
|
|
233
|
+
exports.registerAnnouncementHelpRoutes = registerAnnouncementHelpRoutes;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { AnnouncementsApp } from "./announcementsApp";
|
|
2
|
+
export type { AnnouncementHelpDetail, AnnouncementHelpSummary } from "./help";
|
|
3
|
+
export { buildHelpStatusFilter, excerptBody, matchesHelpQueries, toHelpDetail, toHelpSummary, } from "./help";
|
|
4
|
+
export { Announcement, toAnnouncementPublic } from "./models/announcement";
|
|
5
|
+
export { AnnouncementAcknowledgement } from "./models/announcementAcknowledgement";
|
|
6
|
+
export { AnnouncementClickEvent } from "./models/announcementClickEvent";
|
|
7
|
+
export { AnnouncementImpression } from "./models/announcementImpression";
|
|
8
|
+
export { isAnnouncementPendingForUser, isAnnouncementVisibleNow, isAnnouncementVisibleToUser, isInterruptDisplayMode, matchAudienceByType, parseQueryVersion, passesMinBuildNumber, requiresAcknowledgementForAnnouncement, resolveAcknowledgementPolicy, resolveAudienceType, resolveDisplayMode, selectPendingAnnouncements, sortAnnouncementsForQueue, } from "./pending";
|
|
9
|
+
export type { AcknowledgementPolicy, AnnouncementAudienceType, AnnouncementClickAction, AnnouncementDisplayMode, AnnouncementDocument, AnnouncementOverviewMetrics, AnnouncementOverviewResponse, AnnouncementOverviewRow, AnnouncementOverviewTotals, AnnouncementPlatform, AnnouncementPrimaryAction, AnnouncementPublic, AnnouncementsHelpOptions, AnnouncementsOptions, MatchAudienceFunction, } from "./types";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sortAnnouncementsForQueue = exports.selectPendingAnnouncements = exports.resolveDisplayMode = exports.resolveAudienceType = exports.resolveAcknowledgementPolicy = exports.requiresAcknowledgementForAnnouncement = exports.passesMinBuildNumber = exports.parseQueryVersion = exports.matchAudienceByType = exports.isInterruptDisplayMode = exports.isAnnouncementVisibleToUser = exports.isAnnouncementVisibleNow = exports.isAnnouncementPendingForUser = exports.AnnouncementImpression = exports.AnnouncementClickEvent = exports.AnnouncementAcknowledgement = exports.toAnnouncementPublic = exports.Announcement = exports.toHelpSummary = exports.toHelpDetail = exports.matchesHelpQueries = exports.excerptBody = exports.buildHelpStatusFilter = exports.AnnouncementsApp = void 0;
|
|
4
|
+
var announcementsApp_1 = require("./announcementsApp");
|
|
5
|
+
Object.defineProperty(exports, "AnnouncementsApp", { enumerable: true, get: function () { return announcementsApp_1.AnnouncementsApp; } });
|
|
6
|
+
var help_1 = require("./help");
|
|
7
|
+
Object.defineProperty(exports, "buildHelpStatusFilter", { enumerable: true, get: function () { return help_1.buildHelpStatusFilter; } });
|
|
8
|
+
Object.defineProperty(exports, "excerptBody", { enumerable: true, get: function () { return help_1.excerptBody; } });
|
|
9
|
+
Object.defineProperty(exports, "matchesHelpQueries", { enumerable: true, get: function () { return help_1.matchesHelpQueries; } });
|
|
10
|
+
Object.defineProperty(exports, "toHelpDetail", { enumerable: true, get: function () { return help_1.toHelpDetail; } });
|
|
11
|
+
Object.defineProperty(exports, "toHelpSummary", { enumerable: true, get: function () { return help_1.toHelpSummary; } });
|
|
12
|
+
var announcement_1 = require("./models/announcement");
|
|
13
|
+
Object.defineProperty(exports, "Announcement", { enumerable: true, get: function () { return announcement_1.Announcement; } });
|
|
14
|
+
Object.defineProperty(exports, "toAnnouncementPublic", { enumerable: true, get: function () { return announcement_1.toAnnouncementPublic; } });
|
|
15
|
+
var announcementAcknowledgement_1 = require("./models/announcementAcknowledgement");
|
|
16
|
+
Object.defineProperty(exports, "AnnouncementAcknowledgement", { enumerable: true, get: function () { return announcementAcknowledgement_1.AnnouncementAcknowledgement; } });
|
|
17
|
+
var announcementClickEvent_1 = require("./models/announcementClickEvent");
|
|
18
|
+
Object.defineProperty(exports, "AnnouncementClickEvent", { enumerable: true, get: function () { return announcementClickEvent_1.AnnouncementClickEvent; } });
|
|
19
|
+
var announcementImpression_1 = require("./models/announcementImpression");
|
|
20
|
+
Object.defineProperty(exports, "AnnouncementImpression", { enumerable: true, get: function () { return announcementImpression_1.AnnouncementImpression; } });
|
|
21
|
+
var pending_1 = require("./pending");
|
|
22
|
+
Object.defineProperty(exports, "isAnnouncementPendingForUser", { enumerable: true, get: function () { return pending_1.isAnnouncementPendingForUser; } });
|
|
23
|
+
Object.defineProperty(exports, "isAnnouncementVisibleNow", { enumerable: true, get: function () { return pending_1.isAnnouncementVisibleNow; } });
|
|
24
|
+
Object.defineProperty(exports, "isAnnouncementVisibleToUser", { enumerable: true, get: function () { return pending_1.isAnnouncementVisibleToUser; } });
|
|
25
|
+
Object.defineProperty(exports, "isInterruptDisplayMode", { enumerable: true, get: function () { return pending_1.isInterruptDisplayMode; } });
|
|
26
|
+
Object.defineProperty(exports, "matchAudienceByType", { enumerable: true, get: function () { return pending_1.matchAudienceByType; } });
|
|
27
|
+
Object.defineProperty(exports, "parseQueryVersion", { enumerable: true, get: function () { return pending_1.parseQueryVersion; } });
|
|
28
|
+
Object.defineProperty(exports, "passesMinBuildNumber", { enumerable: true, get: function () { return pending_1.passesMinBuildNumber; } });
|
|
29
|
+
Object.defineProperty(exports, "requiresAcknowledgementForAnnouncement", { enumerable: true, get: function () { return pending_1.requiresAcknowledgementForAnnouncement; } });
|
|
30
|
+
Object.defineProperty(exports, "resolveAcknowledgementPolicy", { enumerable: true, get: function () { return pending_1.resolveAcknowledgementPolicy; } });
|
|
31
|
+
Object.defineProperty(exports, "resolveAudienceType", { enumerable: true, get: function () { return pending_1.resolveAudienceType; } });
|
|
32
|
+
Object.defineProperty(exports, "resolveDisplayMode", { enumerable: true, get: function () { return pending_1.resolveDisplayMode; } });
|
|
33
|
+
Object.defineProperty(exports, "selectPendingAnnouncements", { enumerable: true, get: function () { return pending_1.selectPendingAnnouncements; } });
|
|
34
|
+
Object.defineProperty(exports, "sortAnnouncementsForQueue", { enumerable: true, get: function () { return pending_1.sortAnnouncementsForQueue; } });
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AcknowledgementPolicy, AnnouncementDocument, AnnouncementModel, AnnouncementPublic } from "../types";
|
|
2
|
+
export declare const Announcement: AnnouncementModel;
|
|
3
|
+
export declare const toAnnouncementPublic: (doc: AnnouncementDocument, defaultAcknowledgementPolicy?: AcknowledgementPolicy) => AnnouncementPublic;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
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);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
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;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
var _a;
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.toAnnouncementPublic = exports.Announcement = void 0;
|
|
44
|
+
var api_1 = require("@terreno/api");
|
|
45
|
+
var mongoose_1 = __importDefault(require("mongoose"));
|
|
46
|
+
var pending_1 = require("../pending");
|
|
47
|
+
var ALL_PLATFORMS = ["ios", "android", "web"];
|
|
48
|
+
var announcementSchema = new mongoose_1.default.Schema({
|
|
49
|
+
acknowledgementPolicy: {
|
|
50
|
+
description: "Whether users must acknowledge (required) or may dismiss with an impression only (dismiss-only). Omitted values resolve from the plugin defaultAcknowledgementPolicy at read time.",
|
|
51
|
+
enum: ["required", "dismiss-only"],
|
|
52
|
+
type: String,
|
|
53
|
+
},
|
|
54
|
+
archivedAt: {
|
|
55
|
+
description: "When the announcement was archived",
|
|
56
|
+
type: Date,
|
|
57
|
+
},
|
|
58
|
+
audience: {
|
|
59
|
+
default: {},
|
|
60
|
+
description: "Opaque targeting metadata consumed by matchAudience callback",
|
|
61
|
+
type: mongoose_1.default.Schema.Types.Mixed,
|
|
62
|
+
},
|
|
63
|
+
audienceType: {
|
|
64
|
+
default: "all",
|
|
65
|
+
description: "First-class audience targeting: staff, patient, or all. Composed with matchAudience via matchAudienceByType.",
|
|
66
|
+
enum: ["staff", "patient", "all"],
|
|
67
|
+
type: String,
|
|
68
|
+
},
|
|
69
|
+
body: {
|
|
70
|
+
description: "Markdown body shown in the announcement modal",
|
|
71
|
+
required: true,
|
|
72
|
+
trim: true,
|
|
73
|
+
type: String,
|
|
74
|
+
},
|
|
75
|
+
displayMode: {
|
|
76
|
+
default: "modal",
|
|
77
|
+
description: "Where the announcement appears: blocking modal, non-blocking banner, or feed-only changelog entry",
|
|
78
|
+
enum: ["modal", "banner", "feed"],
|
|
79
|
+
type: String,
|
|
80
|
+
},
|
|
81
|
+
expiresAt: {
|
|
82
|
+
description: "Optional expiry — hidden from pending/feed after this time",
|
|
83
|
+
type: Date,
|
|
84
|
+
},
|
|
85
|
+
minBuildNumber: {
|
|
86
|
+
description: "Optional minimum client build number. Hidden from pending, feed, and help when query version is a finite integer below this value",
|
|
87
|
+
min: 1,
|
|
88
|
+
type: Number,
|
|
89
|
+
},
|
|
90
|
+
platforms: {
|
|
91
|
+
default: ALL_PLATFORMS,
|
|
92
|
+
description: "Platforms that should receive this announcement",
|
|
93
|
+
enum: ALL_PLATFORMS,
|
|
94
|
+
type: [String],
|
|
95
|
+
},
|
|
96
|
+
primaryAction: {
|
|
97
|
+
label: {
|
|
98
|
+
description: "Button label for optional primary action",
|
|
99
|
+
type: String,
|
|
100
|
+
},
|
|
101
|
+
url: {
|
|
102
|
+
description: "Deep link or external URL opened by the primary action button",
|
|
103
|
+
type: String,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
priority: {
|
|
107
|
+
default: 0,
|
|
108
|
+
description: "Higher priority announcements appear first in the modal queue",
|
|
109
|
+
type: Number,
|
|
110
|
+
},
|
|
111
|
+
publishAt: {
|
|
112
|
+
description: "Optional scheduled publish time — hidden until this instant",
|
|
113
|
+
type: Date,
|
|
114
|
+
},
|
|
115
|
+
publishedAt: {
|
|
116
|
+
description: "When the announcement was first published",
|
|
117
|
+
type: Date,
|
|
118
|
+
},
|
|
119
|
+
status: {
|
|
120
|
+
default: "draft",
|
|
121
|
+
description: "Lifecycle status: draft, published, or archived",
|
|
122
|
+
enum: ["draft", "published", "archived"],
|
|
123
|
+
required: true,
|
|
124
|
+
type: String,
|
|
125
|
+
},
|
|
126
|
+
title: {
|
|
127
|
+
description: "Announcement title shown in modal and changelog feed",
|
|
128
|
+
required: true,
|
|
129
|
+
trim: true,
|
|
130
|
+
type: String,
|
|
131
|
+
},
|
|
132
|
+
version: {
|
|
133
|
+
default: 1,
|
|
134
|
+
description: "Content version — increments when published title/body changes",
|
|
135
|
+
min: 1,
|
|
136
|
+
type: Number,
|
|
137
|
+
},
|
|
138
|
+
}, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
|
|
139
|
+
announcementSchema.plugin(api_1.createdUpdatedPlugin);
|
|
140
|
+
announcementSchema.plugin(api_1.isDeletedPlugin);
|
|
141
|
+
announcementSchema.plugin(api_1.findExactlyOne);
|
|
142
|
+
announcementSchema.plugin(api_1.findOneOrNone);
|
|
143
|
+
announcementSchema.index({ publishAt: 1, status: 1 });
|
|
144
|
+
announcementSchema.index({ priority: -1, publishedAt: -1, status: 1 });
|
|
145
|
+
announcementSchema.pre("save", function bumpVersionOnPublishedEdit() {
|
|
146
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
147
|
+
var previous;
|
|
148
|
+
var _a;
|
|
149
|
+
return __generator(this, function (_b) {
|
|
150
|
+
switch (_b.label) {
|
|
151
|
+
case 0:
|
|
152
|
+
if (this.isNew) {
|
|
153
|
+
return [2 /*return*/];
|
|
154
|
+
}
|
|
155
|
+
if (this.status !== "published") {
|
|
156
|
+
return [2 /*return*/];
|
|
157
|
+
}
|
|
158
|
+
if (!this.isModified("title") && !this.isModified("body")) {
|
|
159
|
+
return [2 /*return*/];
|
|
160
|
+
}
|
|
161
|
+
return [4 /*yield*/, exports.Announcement.findById(this._id).select("title body status version")];
|
|
162
|
+
case 1:
|
|
163
|
+
previous = _b.sent();
|
|
164
|
+
if ((previous === null || previous === void 0 ? void 0 : previous.status) !== "published") {
|
|
165
|
+
return [2 /*return*/];
|
|
166
|
+
}
|
|
167
|
+
if (previous.title === this.title && previous.body === this.body) {
|
|
168
|
+
return [2 /*return*/];
|
|
169
|
+
}
|
|
170
|
+
this.version = ((_a = previous.version) !== null && _a !== void 0 ? _a : 1) + 1;
|
|
171
|
+
return [2 /*return*/];
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
announcementSchema.pre("validate", function validatePrimaryAction() {
|
|
177
|
+
var _a, _b;
|
|
178
|
+
var action = this.primaryAction;
|
|
179
|
+
if (!action) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
var hasLabel = Boolean((_a = action.label) === null || _a === void 0 ? void 0 : _a.trim());
|
|
183
|
+
var hasUrl = Boolean((_b = action.url) === null || _b === void 0 ? void 0 : _b.trim());
|
|
184
|
+
if (!hasLabel && !hasUrl) {
|
|
185
|
+
this.primaryAction = undefined;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!hasLabel || !hasUrl) {
|
|
189
|
+
throw new api_1.APIError({
|
|
190
|
+
status: 400,
|
|
191
|
+
title: "primaryAction requires both label and url when provided",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
exports.Announcement = (_a = mongoose_1.default.models.Announcement) !== null && _a !== void 0 ? _a : mongoose_1.default.model("Announcement", announcementSchema);
|
|
196
|
+
var toAnnouncementPublic = function (doc, defaultAcknowledgementPolicy) {
|
|
197
|
+
var _a;
|
|
198
|
+
if (defaultAcknowledgementPolicy === void 0) { defaultAcknowledgementPolicy = "dismiss-only"; }
|
|
199
|
+
return ({
|
|
200
|
+
body: doc.body,
|
|
201
|
+
displayMode: (0, pending_1.resolveDisplayMode)(doc),
|
|
202
|
+
id: doc._id.toString(),
|
|
203
|
+
primaryAction: doc.primaryAction,
|
|
204
|
+
priority: doc.priority,
|
|
205
|
+
publishedAt: (_a = doc.publishedAt) === null || _a === void 0 ? void 0 : _a.toISOString(),
|
|
206
|
+
requiresAcknowledgement: (0, pending_1.requiresAcknowledgementForAnnouncement)({
|
|
207
|
+
announcement: doc,
|
|
208
|
+
defaultAcknowledgementPolicy: defaultAcknowledgementPolicy,
|
|
209
|
+
}),
|
|
210
|
+
title: doc.title,
|
|
211
|
+
version: doc.version,
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
exports.toAnnouncementPublic = toAnnouncementPublic;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
var _a;
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.AnnouncementAcknowledgement = void 0;
|
|
8
|
+
var api_1 = require("@terreno/api");
|
|
9
|
+
var mongoose_1 = __importDefault(require("mongoose"));
|
|
10
|
+
var announcementAcknowledgementSchema = new mongoose_1.default.Schema({
|
|
11
|
+
acknowledgedAt: {
|
|
12
|
+
description: "When the user acknowledged this announcement version",
|
|
13
|
+
required: true,
|
|
14
|
+
type: Date,
|
|
15
|
+
},
|
|
16
|
+
announcementId: {
|
|
17
|
+
description: "Announcement that was acknowledged",
|
|
18
|
+
index: true,
|
|
19
|
+
ref: "Announcement",
|
|
20
|
+
required: true,
|
|
21
|
+
type: mongoose_1.default.Schema.Types.ObjectId,
|
|
22
|
+
},
|
|
23
|
+
userId: {
|
|
24
|
+
description: "User who acknowledged the announcement",
|
|
25
|
+
index: true,
|
|
26
|
+
ref: "User",
|
|
27
|
+
required: true,
|
|
28
|
+
type: mongoose_1.default.Schema.Types.ObjectId,
|
|
29
|
+
},
|
|
30
|
+
version: {
|
|
31
|
+
description: "Announcement version acknowledged by the user",
|
|
32
|
+
required: true,
|
|
33
|
+
type: Number,
|
|
34
|
+
},
|
|
35
|
+
}, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
|
|
36
|
+
announcementAcknowledgementSchema.plugin(api_1.createdUpdatedPlugin);
|
|
37
|
+
announcementAcknowledgementSchema.plugin(api_1.isDeletedPlugin);
|
|
38
|
+
announcementAcknowledgementSchema.plugin(api_1.findExactlyOne);
|
|
39
|
+
announcementAcknowledgementSchema.plugin(api_1.findOneOrNone);
|
|
40
|
+
announcementAcknowledgementSchema.index({ announcementId: 1, userId: 1, version: 1 }, { unique: true });
|
|
41
|
+
exports.AnnouncementAcknowledgement = (_a = mongoose_1.default.models.AnnouncementAcknowledgement) !== null && _a !== void 0 ? _a : mongoose_1.default.model("AnnouncementAcknowledgement", announcementAcknowledgementSchema);
|