@terreno/api 0.7.2 → 0.8.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.
Files changed (50) hide show
  1. package/dist/__tests__/{versionCheck.test.js → versionCheckPlugin.test.js} +2 -2
  2. package/dist/api.d.ts +4 -2
  3. package/dist/api.js +7 -2
  4. package/dist/consentApp.d.ts +33 -0
  5. package/dist/consentApp.js +484 -0
  6. package/dist/consentApp.test.d.ts +1 -0
  7. package/dist/consentApp.test.js +1132 -0
  8. package/dist/index.d.ts +6 -0
  9. package/dist/index.js +6 -0
  10. package/dist/models/consentForm.d.ts +2 -0
  11. package/dist/models/consentForm.js +115 -0
  12. package/dist/models/consentResponse.d.ts +2 -0
  13. package/dist/models/consentResponse.js +73 -0
  14. package/dist/models/versionConfig.d.ts +1 -1
  15. package/dist/openApiValidator.js +2 -0
  16. package/dist/populate.d.ts +1 -0
  17. package/dist/populate.js +53 -13
  18. package/dist/syncConsents.d.ts +67 -0
  19. package/dist/syncConsents.js +334 -0
  20. package/dist/syncConsents.test.d.ts +1 -0
  21. package/dist/syncConsents.test.js +249 -0
  22. package/dist/terrenoApp.js +6 -5
  23. package/dist/terrenoPlugin.d.ts +1 -1
  24. package/dist/types/consentForm.d.ts +32 -0
  25. package/dist/types/consentForm.js +2 -0
  26. package/dist/types/consentResponse.d.ts +23 -0
  27. package/dist/types/consentResponse.js +2 -0
  28. package/dist/vendor/wesleytodd-openapi/lib/generate-doc.js +1 -1
  29. package/dist/versionCheckPlugin.d.ts +2 -0
  30. package/dist/versionCheckPlugin.js +3 -6
  31. package/package.json +1 -1
  32. package/src/__tests__/{versionCheck.test.ts → versionCheckPlugin.test.ts} +2 -2
  33. package/src/api.ts +11 -4
  34. package/src/consentApp.test.ts +749 -0
  35. package/src/consentApp.ts +463 -0
  36. package/src/index.ts +6 -0
  37. package/src/models/consentForm.ts +123 -0
  38. package/src/models/consentResponse.ts +78 -0
  39. package/src/models/versionConfig.ts +1 -1
  40. package/src/openApiValidator.ts +2 -0
  41. package/src/populate.ts +33 -0
  42. package/src/syncConsents.test.ts +124 -0
  43. package/src/syncConsents.ts +263 -0
  44. package/src/terrenoApp.ts +6 -6
  45. package/src/terrenoPlugin.ts +1 -1
  46. package/src/types/consentForm.ts +41 -0
  47. package/src/types/consentResponse.ts +34 -0
  48. package/src/vendor/wesleytodd-openapi/lib/generate-doc.js +1 -1
  49. package/src/versionCheckPlugin.ts +5 -6
  50. /package/dist/__tests__/{versionCheck.test.d.ts → versionCheckPlugin.test.d.ts} +0 -0
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ /**
3
+ * Sync consent form definitions from code to the database.
4
+ *
5
+ * Compares the provided definitions (keyed by slug) against what's in the database
6
+ * and creates, updates, or deactivates forms to match. When content changes, a new
7
+ * version is published so users are prompted to re-consent.
8
+ */
9
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
10
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
11
+ return new (P || (P = Promise))(function (resolve, reject) {
12
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
13
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
14
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
15
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
16
+ });
17
+ };
18
+ var __generator = (this && this.__generator) || function (thisArg, body) {
19
+ 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);
20
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
21
+ function verb(n) { return function (v) { return step([n, v]); }; }
22
+ function step(op) {
23
+ if (f) throw new TypeError("Generator is already executing.");
24
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
25
+ 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;
26
+ if (y = 0, t) op = [op[0] & 2, t.value];
27
+ switch (op[0]) {
28
+ case 0: case 1: t = op; break;
29
+ case 4: _.label++; return { value: op[1], done: false };
30
+ case 5: _.label++; y = op[1]; op = [0]; continue;
31
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
32
+ default:
33
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
34
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
35
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
36
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
37
+ if (t[2]) _.ops.pop();
38
+ _.trys.pop(); continue;
39
+ }
40
+ op = body.call(thisArg, _);
41
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
42
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
43
+ }
44
+ };
45
+ var __read = (this && this.__read) || function (o, n) {
46
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
47
+ if (!m) return o;
48
+ var i = m.call(o), r, ar = [], e;
49
+ try {
50
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
51
+ }
52
+ catch (error) { e = { error: error }; }
53
+ finally {
54
+ try {
55
+ if (r && !r.done && (m = i["return"])) m.call(i);
56
+ }
57
+ finally { if (e) throw e.error; }
58
+ }
59
+ return ar;
60
+ };
61
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
62
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
63
+ if (ar || !(i in from)) {
64
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
65
+ ar[i] = from[i];
66
+ }
67
+ }
68
+ return to.concat(ar || Array.prototype.slice.call(from));
69
+ };
70
+ var __values = (this && this.__values) || function(o) {
71
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
72
+ if (m) return m.call(o);
73
+ if (o && typeof o.length === "number") return {
74
+ next: function () {
75
+ if (o && i >= o.length) o = void 0;
76
+ return { value: o && o[i++], done: !o };
77
+ }
78
+ };
79
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
80
+ };
81
+ Object.defineProperty(exports, "__esModule", { value: true });
82
+ exports.syncConsents = void 0;
83
+ var logger_1 = require("./logger");
84
+ var consentForm_1 = require("./models/consentForm");
85
+ var contentEqual = function (a, b) {
86
+ var aKeys = __spreadArray([], __read(a.keys()), false).sort();
87
+ var bKeys = Object.keys(b).sort();
88
+ if (aKeys.length !== bKeys.length) {
89
+ return false;
90
+ }
91
+ return aKeys.every(function (key, i) { return key === bKeys[i] && a.get(key) === b[key]; });
92
+ };
93
+ var formFieldsMatch = function (existing, def) {
94
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
95
+ if (existing.title !== def.title) {
96
+ return false;
97
+ }
98
+ if (existing.type !== def.type) {
99
+ return false;
100
+ }
101
+ if (existing.order !== ((_a = def.order) !== null && _a !== void 0 ? _a : 0)) {
102
+ return false;
103
+ }
104
+ if (existing.required !== ((_b = def.required) !== null && _b !== void 0 ? _b : true)) {
105
+ return false;
106
+ }
107
+ if (existing.requireScrollToBottom !== ((_c = def.requireScrollToBottom) !== null && _c !== void 0 ? _c : false)) {
108
+ return false;
109
+ }
110
+ if (existing.captureSignature !== ((_d = def.captureSignature) !== null && _d !== void 0 ? _d : false)) {
111
+ return false;
112
+ }
113
+ if (existing.agreeButtonText !== ((_e = def.agreeButtonText) !== null && _e !== void 0 ? _e : "I Agree")) {
114
+ return false;
115
+ }
116
+ if (existing.allowDecline !== ((_f = def.allowDecline) !== null && _f !== void 0 ? _f : false)) {
117
+ return false;
118
+ }
119
+ if (existing.declineButtonText !== ((_g = def.declineButtonText) !== null && _g !== void 0 ? _g : "Decline")) {
120
+ return false;
121
+ }
122
+ if (existing.defaultLocale !== ((_h = def.defaultLocale) !== null && _h !== void 0 ? _h : "en")) {
123
+ return false;
124
+ }
125
+ if (!contentEqual(existing.content, def.content)) {
126
+ return false;
127
+ }
128
+ var existingCheckboxes = (_j = existing.checkboxes) !== null && _j !== void 0 ? _j : [];
129
+ var defCheckboxes = (_k = def.checkboxes) !== null && _k !== void 0 ? _k : [];
130
+ if (existingCheckboxes.length !== defCheckboxes.length) {
131
+ return false;
132
+ }
133
+ for (var i = 0; i < existingCheckboxes.length; i++) {
134
+ var ec = existingCheckboxes[i];
135
+ var dc = defCheckboxes[i];
136
+ if (ec.label !== dc.label || ec.required !== ((_l = dc.required) !== null && _l !== void 0 ? _l : false)) {
137
+ return false;
138
+ }
139
+ if (((_m = ec.confirmationPrompt) !== null && _m !== void 0 ? _m : undefined) !== ((_o = dc.confirmationPrompt) !== null && _o !== void 0 ? _o : undefined)) {
140
+ return false;
141
+ }
142
+ }
143
+ return true;
144
+ };
145
+ /**
146
+ * Sync consent form definitions to the database.
147
+ *
148
+ * @param definitions - Map of slug to consent form definition
149
+ * @param options - Sync options
150
+ * @returns Summary of what was created, updated, deactivated, or unchanged
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * import {syncConsents} from "@terreno/api";
155
+ *
156
+ * await syncConsents({
157
+ * "terms-of-service": {
158
+ * title: "Terms of Service",
159
+ * type: "terms",
160
+ * content: {"en": "# Terms\n...", "es": "# Términos\n..."},
161
+ * required: true,
162
+ * order: 1,
163
+ * },
164
+ * "privacy-policy": {
165
+ * title: "Privacy Policy",
166
+ * type: "privacy",
167
+ * content: {"en": "# Privacy\n..."},
168
+ * order: 2,
169
+ * },
170
+ * });
171
+ * ```
172
+ */
173
+ var syncConsents = function (definitions_1) {
174
+ var args_1 = [];
175
+ for (var _i = 1; _i < arguments.length; _i++) {
176
+ args_1[_i - 1] = arguments[_i];
177
+ }
178
+ return __awaiter(void 0, __spreadArray([definitions_1], __read(args_1), false), void 0, function (definitions, options) {
179
+ var _a, deactivateRemoved, _b, dryRun, result, slugs, activeForms, activeBySlug, slugs_1, slugs_1_1, slug, def, existing, newVersion, e_1_1, activeBySlug_1, activeBySlug_1_1, _c, slug, form, e_2_1, summary;
180
+ var e_1, _d, e_2, _e;
181
+ var _f, _g, _h, _j;
182
+ if (options === void 0) { options = {}; }
183
+ return __generator(this, function (_k) {
184
+ switch (_k.label) {
185
+ case 0:
186
+ _a = options.deactivateRemoved, deactivateRemoved = _a === void 0 ? false : _a, _b = options.dryRun, dryRun = _b === void 0 ? false : _b;
187
+ result = {
188
+ created: [],
189
+ deactivated: [],
190
+ unchanged: [],
191
+ updated: [],
192
+ };
193
+ slugs = Object.keys(definitions);
194
+ return [4 /*yield*/, consentForm_1.ConsentForm.find({ active: true })];
195
+ case 1:
196
+ activeForms = _k.sent();
197
+ activeBySlug = new Map(activeForms.map(function (f) { return [f.slug, f]; }));
198
+ _k.label = 2;
199
+ case 2:
200
+ _k.trys.push([2, 12, 13, 14]);
201
+ slugs_1 = __values(slugs), slugs_1_1 = slugs_1.next();
202
+ _k.label = 3;
203
+ case 3:
204
+ if (!!slugs_1_1.done) return [3 /*break*/, 11];
205
+ slug = slugs_1_1.value;
206
+ def = definitions[slug];
207
+ existing = activeBySlug.get(slug);
208
+ if (!!existing) return [3 /*break*/, 6];
209
+ // No active form for this slug — create version 1
210
+ logger_1.logger.info("syncConsents: creating \"".concat(slug, "\""), { dryRun: dryRun });
211
+ if (!!dryRun) return [3 /*break*/, 5];
212
+ return [4 /*yield*/, consentForm_1.ConsentForm.create({
213
+ active: true,
214
+ agreeButtonText: def.agreeButtonText,
215
+ allowDecline: def.allowDecline,
216
+ captureSignature: def.captureSignature,
217
+ checkboxes: def.checkboxes,
218
+ content: new Map(Object.entries(def.content)),
219
+ declineButtonText: def.declineButtonText,
220
+ defaultLocale: def.defaultLocale,
221
+ order: (_f = def.order) !== null && _f !== void 0 ? _f : 0,
222
+ required: (_g = def.required) !== null && _g !== void 0 ? _g : true,
223
+ requireScrollToBottom: def.requireScrollToBottom,
224
+ slug: slug,
225
+ title: def.title,
226
+ type: def.type,
227
+ version: 1,
228
+ })];
229
+ case 4:
230
+ _k.sent();
231
+ _k.label = 5;
232
+ case 5:
233
+ result.created.push(slug);
234
+ return [3 /*break*/, 10];
235
+ case 6:
236
+ if (formFieldsMatch(existing, def)) {
237
+ result.unchanged.push(slug);
238
+ return [3 /*break*/, 10];
239
+ }
240
+ newVersion = existing.version + 1;
241
+ logger_1.logger.info("syncConsents: updating \"".concat(slug, "\" v").concat(existing.version, " -> v").concat(newVersion), {
242
+ dryRun: dryRun,
243
+ });
244
+ if (!!dryRun) return [3 /*break*/, 9];
245
+ return [4 /*yield*/, consentForm_1.ConsentForm.create({
246
+ active: true,
247
+ agreeButtonText: def.agreeButtonText,
248
+ allowDecline: def.allowDecline,
249
+ captureSignature: def.captureSignature,
250
+ checkboxes: def.checkboxes,
251
+ content: new Map(Object.entries(def.content)),
252
+ declineButtonText: def.declineButtonText,
253
+ defaultLocale: def.defaultLocale,
254
+ order: (_h = def.order) !== null && _h !== void 0 ? _h : 0,
255
+ required: (_j = def.required) !== null && _j !== void 0 ? _j : true,
256
+ requireScrollToBottom: def.requireScrollToBottom,
257
+ slug: slug,
258
+ title: def.title,
259
+ type: def.type,
260
+ version: newVersion,
261
+ })];
262
+ case 7:
263
+ _k.sent();
264
+ return [4 /*yield*/, consentForm_1.ConsentForm.updateMany({ _id: { $ne: undefined }, slug: slug, version: { $lt: newVersion } }, { active: false })];
265
+ case 8:
266
+ _k.sent();
267
+ _k.label = 9;
268
+ case 9:
269
+ result.updated.push(slug);
270
+ _k.label = 10;
271
+ case 10:
272
+ slugs_1_1 = slugs_1.next();
273
+ return [3 /*break*/, 3];
274
+ case 11: return [3 /*break*/, 14];
275
+ case 12:
276
+ e_1_1 = _k.sent();
277
+ e_1 = { error: e_1_1 };
278
+ return [3 /*break*/, 14];
279
+ case 13:
280
+ try {
281
+ if (slugs_1_1 && !slugs_1_1.done && (_d = slugs_1.return)) _d.call(slugs_1);
282
+ }
283
+ finally { if (e_1) throw e_1.error; }
284
+ return [7 /*endfinally*/];
285
+ case 14:
286
+ if (!deactivateRemoved) return [3 /*break*/, 23];
287
+ _k.label = 15;
288
+ case 15:
289
+ _k.trys.push([15, 21, 22, 23]);
290
+ activeBySlug_1 = __values(activeBySlug), activeBySlug_1_1 = activeBySlug_1.next();
291
+ _k.label = 16;
292
+ case 16:
293
+ if (!!activeBySlug_1_1.done) return [3 /*break*/, 20];
294
+ _c = __read(activeBySlug_1_1.value, 2), slug = _c[0], form = _c[1];
295
+ if (!!definitions[slug]) return [3 /*break*/, 19];
296
+ logger_1.logger.info("syncConsents: deactivating \"".concat(slug, "\""), { dryRun: dryRun });
297
+ if (!!dryRun) return [3 /*break*/, 18];
298
+ return [4 /*yield*/, consentForm_1.ConsentForm.updateMany({ slug: slug }, { active: false })];
299
+ case 17:
300
+ _k.sent();
301
+ _k.label = 18;
302
+ case 18:
303
+ result.deactivated.push(slug);
304
+ _k.label = 19;
305
+ case 19:
306
+ activeBySlug_1_1 = activeBySlug_1.next();
307
+ return [3 /*break*/, 16];
308
+ case 20: return [3 /*break*/, 23];
309
+ case 21:
310
+ e_2_1 = _k.sent();
311
+ e_2 = { error: e_2_1 };
312
+ return [3 /*break*/, 23];
313
+ case 22:
314
+ try {
315
+ if (activeBySlug_1_1 && !activeBySlug_1_1.done && (_e = activeBySlug_1.return)) _e.call(activeBySlug_1);
316
+ }
317
+ finally { if (e_2) throw e_2.error; }
318
+ return [7 /*endfinally*/];
319
+ case 23:
320
+ summary = [
321
+ result.created.length > 0 ? "created: ".concat(result.created.join(", ")) : null,
322
+ result.updated.length > 0 ? "updated: ".concat(result.updated.join(", ")) : null,
323
+ result.deactivated.length > 0 ? "deactivated: ".concat(result.deactivated.join(", ")) : null,
324
+ result.unchanged.length > 0 ? "unchanged: ".concat(result.unchanged.join(", ")) : null,
325
+ ]
326
+ .filter(Boolean)
327
+ .join(" | ");
328
+ logger_1.logger.info("syncConsents: ".concat(dryRun ? "[DRY RUN] " : "").concat(summary || "nothing to do"));
329
+ return [2 /*return*/, result];
330
+ }
331
+ });
332
+ });
333
+ };
334
+ exports.syncConsents = syncConsents;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,249 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ var bun_test_1 = require("bun:test");
51
+ var consentForm_1 = require("./models/consentForm");
52
+ var syncConsents_1 = require("./syncConsents");
53
+ var tests_1 = require("./tests");
54
+ var baseDef = {
55
+ content: { en: "# Terms\nPlease agree." },
56
+ order: 1,
57
+ required: true,
58
+ title: "Terms of Service",
59
+ type: "terms",
60
+ };
61
+ (0, bun_test_1.describe)("syncConsents", function () {
62
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
63
+ return __generator(this, function (_a) {
64
+ switch (_a.label) {
65
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
66
+ case 1:
67
+ _a.sent();
68
+ return [4 /*yield*/, consentForm_1.ConsentForm.deleteMany({})];
69
+ case 2:
70
+ _a.sent();
71
+ return [2 /*return*/];
72
+ }
73
+ });
74
+ }); });
75
+ (0, bun_test_1.afterEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
76
+ return __generator(this, function (_a) {
77
+ switch (_a.label) {
78
+ case 0: return [4 /*yield*/, consentForm_1.ConsentForm.deleteMany({})];
79
+ case 1:
80
+ _a.sent();
81
+ return [2 /*return*/];
82
+ }
83
+ });
84
+ }); });
85
+ (0, bun_test_1.it)("creates a new consent form when none exists", function () { return __awaiter(void 0, void 0, void 0, function () {
86
+ var result, forms;
87
+ return __generator(this, function (_a) {
88
+ switch (_a.label) {
89
+ case 0: return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: baseDef })];
90
+ case 1:
91
+ result = _a.sent();
92
+ (0, bun_test_1.expect)(result.created).toEqual(["terms"]);
93
+ (0, bun_test_1.expect)(result.updated).toHaveLength(0);
94
+ (0, bun_test_1.expect)(result.unchanged).toHaveLength(0);
95
+ return [4 /*yield*/, consentForm_1.ConsentForm.find({ slug: "terms" })];
96
+ case 2:
97
+ forms = _a.sent();
98
+ (0, bun_test_1.expect)(forms).toHaveLength(1);
99
+ (0, bun_test_1.expect)(forms[0].active).toBe(true);
100
+ (0, bun_test_1.expect)(forms[0].version).toBe(1);
101
+ (0, bun_test_1.expect)(forms[0].title).toBe("Terms of Service");
102
+ return [2 /*return*/];
103
+ }
104
+ });
105
+ }); });
106
+ (0, bun_test_1.it)("leaves unchanged forms alone", function () { return __awaiter(void 0, void 0, void 0, function () {
107
+ var result, forms;
108
+ return __generator(this, function (_a) {
109
+ switch (_a.label) {
110
+ case 0: return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: baseDef })];
111
+ case 1:
112
+ _a.sent();
113
+ return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: baseDef })];
114
+ case 2:
115
+ result = _a.sent();
116
+ (0, bun_test_1.expect)(result.unchanged).toEqual(["terms"]);
117
+ (0, bun_test_1.expect)(result.created).toHaveLength(0);
118
+ (0, bun_test_1.expect)(result.updated).toHaveLength(0);
119
+ return [4 /*yield*/, consentForm_1.ConsentForm.find({ slug: "terms" })];
120
+ case 3:
121
+ forms = _a.sent();
122
+ (0, bun_test_1.expect)(forms).toHaveLength(1);
123
+ (0, bun_test_1.expect)(forms[0].version).toBe(1);
124
+ return [2 /*return*/];
125
+ }
126
+ });
127
+ }); });
128
+ (0, bun_test_1.it)("publishes a new version when content changes", function () { return __awaiter(void 0, void 0, void 0, function () {
129
+ var updated, result, forms;
130
+ return __generator(this, function (_a) {
131
+ switch (_a.label) {
132
+ case 0: return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: baseDef })];
133
+ case 1:
134
+ _a.sent();
135
+ updated = __assign(__assign({}, baseDef), { content: { en: "# Updated Terms\nNew content." } });
136
+ return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: updated })];
137
+ case 2:
138
+ result = _a.sent();
139
+ (0, bun_test_1.expect)(result.updated).toEqual(["terms"]);
140
+ return [4 /*yield*/, consentForm_1.ConsentForm.find({ slug: "terms" }).sort({ version: 1 })];
141
+ case 3:
142
+ forms = _a.sent();
143
+ (0, bun_test_1.expect)(forms).toHaveLength(2);
144
+ (0, bun_test_1.expect)(forms[0].active).toBe(false);
145
+ (0, bun_test_1.expect)(forms[0].version).toBe(1);
146
+ (0, bun_test_1.expect)(forms[1].active).toBe(true);
147
+ (0, bun_test_1.expect)(forms[1].version).toBe(2);
148
+ return [2 /*return*/];
149
+ }
150
+ });
151
+ }); });
152
+ (0, bun_test_1.it)("publishes a new version when title changes", function () { return __awaiter(void 0, void 0, void 0, function () {
153
+ var updated, result, active;
154
+ return __generator(this, function (_a) {
155
+ switch (_a.label) {
156
+ case 0: return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: baseDef })];
157
+ case 1:
158
+ _a.sent();
159
+ updated = __assign(__assign({}, baseDef), { title: "Updated Terms" });
160
+ return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: updated })];
161
+ case 2:
162
+ result = _a.sent();
163
+ (0, bun_test_1.expect)(result.updated).toEqual(["terms"]);
164
+ return [4 /*yield*/, consentForm_1.ConsentForm.findOne({ active: true, slug: "terms" })];
165
+ case 3:
166
+ active = _a.sent();
167
+ (0, bun_test_1.expect)(active === null || active === void 0 ? void 0 : active.version).toBe(2);
168
+ (0, bun_test_1.expect)(active === null || active === void 0 ? void 0 : active.title).toBe("Updated Terms");
169
+ return [2 /*return*/];
170
+ }
171
+ });
172
+ }); });
173
+ (0, bun_test_1.it)("deactivates removed forms when deactivateRemoved is true", function () { return __awaiter(void 0, void 0, void 0, function () {
174
+ var result, privacy;
175
+ return __generator(this, function (_a) {
176
+ switch (_a.label) {
177
+ case 0: return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ privacy: __assign(__assign({}, baseDef), { title: "Privacy", type: "privacy" }), terms: baseDef })];
178
+ case 1:
179
+ _a.sent();
180
+ return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: baseDef }, { deactivateRemoved: true })];
181
+ case 2:
182
+ result = _a.sent();
183
+ (0, bun_test_1.expect)(result.deactivated).toEqual(["privacy"]);
184
+ (0, bun_test_1.expect)(result.unchanged).toEqual(["terms"]);
185
+ return [4 /*yield*/, consentForm_1.ConsentForm.findOne({ slug: "privacy" })];
186
+ case 3:
187
+ privacy = _a.sent();
188
+ (0, bun_test_1.expect)(privacy === null || privacy === void 0 ? void 0 : privacy.active).toBe(false);
189
+ return [2 /*return*/];
190
+ }
191
+ });
192
+ }); });
193
+ (0, bun_test_1.it)("does not deactivate removed forms by default", function () { return __awaiter(void 0, void 0, void 0, function () {
194
+ var result, privacy;
195
+ return __generator(this, function (_a) {
196
+ switch (_a.label) {
197
+ case 0: return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ privacy: __assign(__assign({}, baseDef), { title: "Privacy", type: "privacy" }), terms: baseDef })];
198
+ case 1:
199
+ _a.sent();
200
+ return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: baseDef })];
201
+ case 2:
202
+ result = _a.sent();
203
+ (0, bun_test_1.expect)(result.deactivated).toHaveLength(0);
204
+ return [4 /*yield*/, consentForm_1.ConsentForm.findOne({ slug: "privacy" })];
205
+ case 3:
206
+ privacy = _a.sent();
207
+ (0, bun_test_1.expect)(privacy === null || privacy === void 0 ? void 0 : privacy.active).toBe(true);
208
+ return [2 /*return*/];
209
+ }
210
+ });
211
+ }); });
212
+ (0, bun_test_1.it)("does not write to the database in dry run mode", function () { return __awaiter(void 0, void 0, void 0, function () {
213
+ var result, forms;
214
+ return __generator(this, function (_a) {
215
+ switch (_a.label) {
216
+ case 0: return [4 /*yield*/, (0, syncConsents_1.syncConsents)({ terms: baseDef }, { dryRun: true })];
217
+ case 1:
218
+ result = _a.sent();
219
+ (0, bun_test_1.expect)(result.created).toEqual(["terms"]);
220
+ return [4 /*yield*/, consentForm_1.ConsentForm.find({ slug: "terms" })];
221
+ case 2:
222
+ forms = _a.sent();
223
+ (0, bun_test_1.expect)(forms).toHaveLength(0);
224
+ return [2 /*return*/];
225
+ }
226
+ });
227
+ }); });
228
+ (0, bun_test_1.it)("handles multiple forms in a single sync", function () { return __awaiter(void 0, void 0, void 0, function () {
229
+ var result, forms;
230
+ return __generator(this, function (_a) {
231
+ switch (_a.label) {
232
+ case 0: return [4 /*yield*/, (0, syncConsents_1.syncConsents)({
233
+ privacy: __assign(__assign({}, baseDef), { order: 2, title: "Privacy Policy", type: "privacy" }),
234
+ terms: baseDef,
235
+ })];
236
+ case 1:
237
+ result = _a.sent();
238
+ (0, bun_test_1.expect)(result.created.sort()).toEqual(["privacy", "terms"]);
239
+ return [4 /*yield*/, consentForm_1.ConsentForm.find({}).sort({ order: 1 })];
240
+ case 2:
241
+ forms = _a.sent();
242
+ (0, bun_test_1.expect)(forms).toHaveLength(2);
243
+ (0, bun_test_1.expect)(forms[0].slug).toBe("terms");
244
+ (0, bun_test_1.expect)(forms[1].slug).toBe("privacy");
245
+ return [2 /*return*/];
246
+ }
247
+ });
248
+ }); });
249
+ });
@@ -312,7 +312,6 @@ var TerrenoApp = /** @class */ (function () {
312
312
  if (process.env.ENABLE_SWAGGER === "true") {
313
313
  app.use("/swagger", oapi.swaggerui());
314
314
  }
315
- (0, auth_1.addMeRoutes)(app, options.userModel, options.authOptions);
316
315
  // GitHub OAuth
317
316
  if (options.githubAuth) {
318
317
  (0, githubAuth_1.setupGitHubAuth)(app, options.userModel, options.githubAuth);
@@ -327,10 +326,11 @@ var TerrenoApp = /** @class */ (function () {
327
326
  for (var _f = __values(this.registrations), _g = _f.next(); !_g.done; _g = _f.next()) {
328
327
  var registration = _g.value;
329
328
  if (this.isModelRouterRegistration(registration)) {
330
- app.use(registration.path, registration.router);
329
+ var router = registration._buildWithOpenApi(oapi);
330
+ app.use(registration.path, router);
331
331
  }
332
332
  else {
333
- registration.register(app);
333
+ registration.register(app, oapi);
334
334
  }
335
335
  }
336
336
  }
@@ -341,8 +341,9 @@ var TerrenoApp = /** @class */ (function () {
341
341
  }
342
342
  finally { if (e_2) throw e_2.error; }
343
343
  }
344
- // Inject openApi into model router options for registered routers
345
- // The openApi middleware handles this via the oapi instance already mounted on the app
344
+ // /auth/me must be registered after plugins so that session middleware
345
+ // (e.g. Better Auth) has a chance to populate req.user first.
346
+ (0, auth_1.addMeRoutes)(app, options.userModel, options.authOptions);
346
347
  Sentry.setupExpressErrorHandler(app);
347
348
  // Error middleware
348
349
  app.use(errors_1.apiUnauthorizedMiddleware);
@@ -34,5 +34,5 @@ export interface TerrenoPlugin {
34
34
  *
35
35
  * @param app - The Express application instance to register with
36
36
  */
37
- register(app: express.Application): void;
37
+ register(app: express.Application, openApi?: unknown): void;
38
38
  }
@@ -0,0 +1,32 @@
1
+ import type mongoose from "mongoose";
2
+ import type { FindExactlyOnePlugin, FindOneOrNonePlugin } from "../plugins";
3
+ export interface ConsentFormCheckbox {
4
+ label: string;
5
+ required: boolean;
6
+ confirmationPrompt?: string;
7
+ }
8
+ export type ConsentFormType = "agreement" | "privacy" | "hipaa" | "research" | "terms" | "custom";
9
+ export type ConsentFormMethods = {};
10
+ export type ConsentFormStatics = FindExactlyOnePlugin<ConsentFormDocument> & FindOneOrNonePlugin<ConsentFormDocument>;
11
+ export type ConsentFormModel = mongoose.Model<ConsentFormDocument, object, ConsentFormMethods> & ConsentFormStatics;
12
+ export interface ConsentFormDocument extends mongoose.Document {
13
+ _id: mongoose.Types.ObjectId;
14
+ title: string;
15
+ slug: string;
16
+ version: number;
17
+ order: number;
18
+ type: ConsentFormType;
19
+ content: Map<string, string>;
20
+ defaultLocale: string;
21
+ active: boolean;
22
+ captureSignature: boolean;
23
+ requireScrollToBottom: boolean;
24
+ checkboxes: ConsentFormCheckbox[];
25
+ agreeButtonText: string;
26
+ allowDecline: boolean;
27
+ declineButtonText: string;
28
+ required: boolean;
29
+ created: Date;
30
+ updated: Date;
31
+ deleted: boolean;
32
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });