@terreno/api 0.15.0 → 0.15.2
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/CHANGELOG.md +21 -0
- package/dist/__tests__/versionCheckPlugin.test.js +83 -0
- package/dist/actions.d.ts +55 -0
- package/dist/actions.js +472 -0
- package/dist/actions.openApi.test.d.ts +1 -0
- package/dist/actions.openApi.test.js +252 -0
- package/dist/actions.test.d.ts +1 -0
- package/dist/actions.test.js +946 -0
- package/dist/api.d.ts +5 -0
- package/dist/api.js +4 -1
- package/dist/consentApp.js +118 -102
- package/dist/docLoader.d.ts +7 -0
- package/dist/docLoader.js +154 -0
- package/dist/docLoader.test.d.ts +1 -0
- package/dist/docLoader.test.js +137 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/permissions.d.ts +2 -2
- package/dist/permissions.js +11 -107
- package/dist/zodOpenApi.d.ts +2 -0
- package/dist/zodOpenApi.js +7 -0
- package/package.json +6 -3
- package/src/__tests__/versionCheckPlugin.test.ts +36 -0
- package/src/actions.openApi.test.ts +176 -0
- package/src/actions.test.ts +636 -0
- package/src/actions.ts +441 -0
- package/src/api.ts +14 -1
- package/src/consentApp.ts +80 -81
- package/src/docLoader.test.ts +58 -0
- package/src/docLoader.ts +77 -0
- package/src/index.ts +2 -0
- package/src/permissions.ts +4 -62
- package/src/zodOpenApi.ts +6 -0
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import express, { type NextFunction, type Request, type Response } from "express";
|
|
2
2
|
import mongoose, { type Document, type Model } from "mongoose";
|
|
3
|
+
import { type CollectionActionConfig, type InstanceActionConfig } from "./actions";
|
|
3
4
|
import { type User } from "./auth";
|
|
4
5
|
import { type ModelRouterValidationOptions } from "./openApiValidator";
|
|
5
6
|
import { type RESTPermissions } from "./permissions";
|
|
@@ -129,6 +130,10 @@ export interface ModelRouterOptions<T> {
|
|
|
129
130
|
maxLimit?: number;
|
|
130
131
|
/** Custom route setup function. Receives the router and optionally the full options (including openApi). */
|
|
131
132
|
endpoints?: (router: express.Router, options?: Partial<ModelRouterOptions<T>>) => void;
|
|
133
|
+
/** Named instance-scoped operations at `/:id/:actionName` (GET or POST). */
|
|
134
|
+
instanceActions?: Record<string, InstanceActionConfig<T, unknown, unknown, unknown>>;
|
|
135
|
+
/** Named collection-scoped operations at `/:actionName` (GET or POST). */
|
|
136
|
+
collectionActions?: Record<string, CollectionActionConfig<unknown, unknown, unknown>>;
|
|
132
137
|
/**
|
|
133
138
|
* Hook that runs after `transformer.transform` but before the object is created.
|
|
134
139
|
* Can update the body fields based on the request or the user.
|
package/dist/api.js
CHANGED
|
@@ -131,6 +131,7 @@ var express_1 = __importDefault(require("express"));
|
|
|
131
131
|
var cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
|
|
132
132
|
var luxon_1 = require("luxon");
|
|
133
133
|
var mongoose_1 = __importDefault(require("mongoose"));
|
|
134
|
+
var actions_1 = require("./actions");
|
|
134
135
|
var auth_1 = require("./auth");
|
|
135
136
|
var errors_1 = require("./errors");
|
|
136
137
|
var logger_1 = require("./logger");
|
|
@@ -334,7 +335,9 @@ function _buildModelRouter(model, options) {
|
|
|
334
335
|
var _this = this;
|
|
335
336
|
var _a;
|
|
336
337
|
var router = express_1.default.Router();
|
|
337
|
-
|
|
338
|
+
(0, actions_1.assertNoActionCollisions)(model, options);
|
|
339
|
+
(0, actions_1.registerActionRoutes)(router, model, options);
|
|
340
|
+
// User endpoints run after actions; actions win on path conflicts.
|
|
338
341
|
if (options.endpoints) {
|
|
339
342
|
options.endpoints(router, options);
|
|
340
343
|
}
|
package/dist/consentApp.js
CHANGED
|
@@ -64,6 +64,11 @@ var logger_1 = require("./logger");
|
|
|
64
64
|
var consentForm_1 = require("./models/consentForm");
|
|
65
65
|
var consentResponse_1 = require("./models/consentResponse");
|
|
66
66
|
var permissions_1 = require("./permissions");
|
|
67
|
+
var requireAdmin = function (user) {
|
|
68
|
+
if (!(user === null || user === void 0 ? void 0 : user.admin)) {
|
|
69
|
+
throw new errors_1.APIError({ status: 403, title: "Admin access required" });
|
|
70
|
+
}
|
|
71
|
+
};
|
|
67
72
|
var ConsentApp = /** @class */ (function () {
|
|
68
73
|
function ConsentApp(options) {
|
|
69
74
|
if (options === void 0) { options = {}; }
|
|
@@ -72,110 +77,126 @@ var ConsentApp = /** @class */ (function () {
|
|
|
72
77
|
ConsentApp.prototype.register = function (app) {
|
|
73
78
|
var _this = this;
|
|
74
79
|
var _a = this.options, auditTrail = _a.auditTrail, resolveConsentForms = _a.resolveConsentForms, aiConfig = _a.aiConfig;
|
|
75
|
-
|
|
80
|
+
var collectionActions = {};
|
|
81
|
+
if (aiConfig) {
|
|
82
|
+
collectionActions.generate = {
|
|
83
|
+
handler: function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
|
|
84
|
+
var typedBody, locale, content;
|
|
85
|
+
var _c;
|
|
86
|
+
var body = _b.body, user = _b.user;
|
|
87
|
+
return __generator(this, function (_d) {
|
|
88
|
+
switch (_d.label) {
|
|
89
|
+
case 0:
|
|
90
|
+
requireAdmin(user);
|
|
91
|
+
typedBody = body;
|
|
92
|
+
if (!typedBody.type) {
|
|
93
|
+
throw new errors_1.APIError({ status: 400, title: "type is required" });
|
|
94
|
+
}
|
|
95
|
+
if (!typedBody.description) {
|
|
96
|
+
throw new errors_1.APIError({ status: 400, title: "description is required" });
|
|
97
|
+
}
|
|
98
|
+
locale = (_c = typedBody.locale) !== null && _c !== void 0 ? _c : "en";
|
|
99
|
+
return [4 /*yield*/, aiConfig.generateContent({
|
|
100
|
+
description: typedBody.description,
|
|
101
|
+
locale: locale,
|
|
102
|
+
type: typedBody.type,
|
|
103
|
+
})];
|
|
104
|
+
case 1:
|
|
105
|
+
content = _d.sent();
|
|
106
|
+
logger_1.logger.info("ConsentForm content generated", { locale: locale, type: typedBody.type });
|
|
107
|
+
return [2 /*return*/, { content: content }];
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}); },
|
|
111
|
+
method: "POST",
|
|
112
|
+
permissions: [permissions_1.Permissions.IsAuthenticated],
|
|
113
|
+
summary: "Generate consent form content with AI",
|
|
114
|
+
};
|
|
115
|
+
collectionActions.translate = {
|
|
116
|
+
handler: function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
|
|
117
|
+
var typedBody, translated;
|
|
118
|
+
var body = _b.body, user = _b.user;
|
|
119
|
+
return __generator(this, function (_c) {
|
|
120
|
+
switch (_c.label) {
|
|
121
|
+
case 0:
|
|
122
|
+
requireAdmin(user);
|
|
123
|
+
typedBody = body;
|
|
124
|
+
if (!typedBody.content) {
|
|
125
|
+
throw new errors_1.APIError({ status: 400, title: "content is required" });
|
|
126
|
+
}
|
|
127
|
+
if (!typedBody.fromLocale) {
|
|
128
|
+
throw new errors_1.APIError({ status: 400, title: "fromLocale is required" });
|
|
129
|
+
}
|
|
130
|
+
if (!typedBody.toLocale) {
|
|
131
|
+
throw new errors_1.APIError({ status: 400, title: "toLocale is required" });
|
|
132
|
+
}
|
|
133
|
+
return [4 /*yield*/, aiConfig.translateContent({
|
|
134
|
+
content: typedBody.content,
|
|
135
|
+
fromLocale: typedBody.fromLocale,
|
|
136
|
+
toLocale: typedBody.toLocale,
|
|
137
|
+
})];
|
|
138
|
+
case 1:
|
|
139
|
+
translated = _c.sent();
|
|
140
|
+
logger_1.logger.info("ConsentForm content translated", {
|
|
141
|
+
fromLocale: typedBody.fromLocale,
|
|
142
|
+
toLocale: typedBody.toLocale,
|
|
143
|
+
});
|
|
144
|
+
return [2 /*return*/, { content: translated }];
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}); },
|
|
148
|
+
method: "POST",
|
|
149
|
+
permissions: [permissions_1.Permissions.IsAuthenticated],
|
|
150
|
+
summary: "Translate consent form content with AI",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
76
153
|
app.use("/consent-forms", (0, api_1.modelRouter)(consentForm_1.ConsentForm, {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
var
|
|
154
|
+
collectionActions: collectionActions,
|
|
155
|
+
instanceActions: {
|
|
156
|
+
publish: {
|
|
157
|
+
handler: function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
|
|
158
|
+
var form, newFormData, newForm;
|
|
159
|
+
var doc = _b.doc, user = _b.user;
|
|
82
160
|
return __generator(this, function (_c) {
|
|
83
161
|
switch (_c.label) {
|
|
84
162
|
case 0:
|
|
85
|
-
user
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
router.post("/translate", (0, auth_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
106
|
-
var user, _a, content, fromLocale, toLocale, translated;
|
|
107
|
-
return __generator(this, function (_b) {
|
|
108
|
-
switch (_b.label) {
|
|
109
|
-
case 0:
|
|
110
|
-
user = req.user;
|
|
111
|
-
if (!(user === null || user === void 0 ? void 0 : user.admin)) {
|
|
112
|
-
throw new errors_1.APIError({ status: 403, title: "Admin access required" });
|
|
113
|
-
}
|
|
114
|
-
_a = req.body, content = _a.content, fromLocale = _a.fromLocale, toLocale = _a.toLocale;
|
|
115
|
-
if (!content) {
|
|
116
|
-
throw new errors_1.APIError({ status: 400, title: "content is required" });
|
|
117
|
-
}
|
|
118
|
-
if (!fromLocale) {
|
|
119
|
-
throw new errors_1.APIError({ status: 400, title: "fromLocale is required" });
|
|
120
|
-
}
|
|
121
|
-
if (!toLocale) {
|
|
122
|
-
throw new errors_1.APIError({ status: 400, title: "toLocale is required" });
|
|
123
|
-
}
|
|
124
|
-
return [4 /*yield*/, aiConfig.translateContent({ content: content, fromLocale: fromLocale, toLocale: toLocale })];
|
|
163
|
+
requireAdmin(user);
|
|
164
|
+
form = doc;
|
|
165
|
+
newFormData = {
|
|
166
|
+
active: true,
|
|
167
|
+
agreeButtonText: form.agreeButtonText,
|
|
168
|
+
allowDecline: form.allowDecline,
|
|
169
|
+
captureSignature: form.captureSignature,
|
|
170
|
+
checkboxes: form.checkboxes,
|
|
171
|
+
content: form.content,
|
|
172
|
+
declineButtonText: form.declineButtonText,
|
|
173
|
+
defaultLocale: form.defaultLocale,
|
|
174
|
+
order: form.order,
|
|
175
|
+
required: form.required,
|
|
176
|
+
requireScrollToBottom: form.requireScrollToBottom,
|
|
177
|
+
slug: form.slug,
|
|
178
|
+
title: form.title,
|
|
179
|
+
type: form.type,
|
|
180
|
+
version: form.version + 1,
|
|
181
|
+
};
|
|
182
|
+
return [4 /*yield*/, consentForm_1.ConsentForm.create(newFormData)];
|
|
125
183
|
case 1:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
184
|
+
newForm = _c.sent();
|
|
185
|
+
return [4 /*yield*/, consentForm_1.ConsentForm.updateMany({ _id: { $ne: newForm._id }, slug: form.slug }, { active: false })];
|
|
186
|
+
case 2:
|
|
187
|
+
_c.sent();
|
|
188
|
+
logger_1.logger.info("ConsentForm published", {
|
|
189
|
+
newVersion: newForm.version,
|
|
190
|
+
slug: newForm.slug,
|
|
191
|
+
});
|
|
192
|
+
return [2 /*return*/, newForm];
|
|
129
193
|
}
|
|
130
194
|
});
|
|
131
|
-
}); }
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return __generator(this, function (_a) {
|
|
137
|
-
switch (_a.label) {
|
|
138
|
-
case 0:
|
|
139
|
-
user = req.user;
|
|
140
|
-
if (!(user === null || user === void 0 ? void 0 : user.admin)) {
|
|
141
|
-
throw new errors_1.APIError({ status: 403, title: "Admin access required" });
|
|
142
|
-
}
|
|
143
|
-
return [4 /*yield*/, consentForm_1.ConsentForm.findExactlyOne({ _id: req.params.id })];
|
|
144
|
-
case 1:
|
|
145
|
-
form = _a.sent();
|
|
146
|
-
newFormData = {
|
|
147
|
-
active: true,
|
|
148
|
-
agreeButtonText: form.agreeButtonText,
|
|
149
|
-
allowDecline: form.allowDecline,
|
|
150
|
-
captureSignature: form.captureSignature,
|
|
151
|
-
checkboxes: form.checkboxes,
|
|
152
|
-
content: form.content,
|
|
153
|
-
declineButtonText: form.declineButtonText,
|
|
154
|
-
defaultLocale: form.defaultLocale,
|
|
155
|
-
order: form.order,
|
|
156
|
-
required: form.required,
|
|
157
|
-
requireScrollToBottom: form.requireScrollToBottom,
|
|
158
|
-
slug: form.slug,
|
|
159
|
-
title: form.title,
|
|
160
|
-
type: form.type,
|
|
161
|
-
version: form.version + 1,
|
|
162
|
-
};
|
|
163
|
-
return [4 /*yield*/, consentForm_1.ConsentForm.create(newFormData)];
|
|
164
|
-
case 2:
|
|
165
|
-
newForm = _a.sent();
|
|
166
|
-
// Deactivate all other versions of the same slug
|
|
167
|
-
return [4 /*yield*/, consentForm_1.ConsentForm.updateMany({ _id: { $ne: newForm._id }, slug: form.slug }, { active: false })];
|
|
168
|
-
case 3:
|
|
169
|
-
// Deactivate all other versions of the same slug
|
|
170
|
-
_a.sent();
|
|
171
|
-
logger_1.logger.info("ConsentForm published", {
|
|
172
|
-
newVersion: newForm.version,
|
|
173
|
-
slug: newForm.slug,
|
|
174
|
-
});
|
|
175
|
-
return [2 /*return*/, res.json({ data: newForm })];
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
}); }));
|
|
195
|
+
}); },
|
|
196
|
+
method: "POST",
|
|
197
|
+
permissions: [permissions_1.Permissions.IsAuthenticated],
|
|
198
|
+
summary: "Publish a new version of a consent form",
|
|
199
|
+
},
|
|
179
200
|
},
|
|
180
201
|
permissions: {
|
|
181
202
|
create: [permissions_1.Permissions.IsAdmin],
|
|
@@ -273,12 +294,10 @@ var ConsentApp = /** @class */ (function () {
|
|
|
273
294
|
}
|
|
274
295
|
pendingForms = resolvedForms.filter(function (form) {
|
|
275
296
|
var formId = form._id.toString();
|
|
276
|
-
// Find responses for this form
|
|
277
297
|
var matchingResponses = existingResponses.filter(function (r) { return r.consentFormId.toString() === formId; });
|
|
278
298
|
if (matchingResponses.length === 0) {
|
|
279
299
|
return true;
|
|
280
300
|
}
|
|
281
|
-
// Check if any response matches the current form version
|
|
282
301
|
return !matchingResponses.some(function (r) { return r.formVersionSnapshot === form.version; });
|
|
283
302
|
});
|
|
284
303
|
filteredOutByResolverCount = Math.max(activeForms.length - resolvedForms.length, 0);
|
|
@@ -324,14 +343,12 @@ var ConsentApp = /** @class */ (function () {
|
|
|
324
343
|
if (!form.active) {
|
|
325
344
|
throw new errors_1.APIError({ status: 400, title: "Consent form is not active" });
|
|
326
345
|
}
|
|
327
|
-
// Validate signature requirement
|
|
328
346
|
if (form.captureSignature && agreed && !signature) {
|
|
329
347
|
throw new errors_1.APIError({
|
|
330
348
|
status: 400,
|
|
331
349
|
title: "Signature is required for this consent form",
|
|
332
350
|
});
|
|
333
351
|
}
|
|
334
|
-
// Validate required checkboxes
|
|
335
352
|
if (agreed && form.checkboxes.length > 0) {
|
|
336
353
|
values = checkboxValues !== null && checkboxValues !== void 0 ? checkboxValues : {};
|
|
337
354
|
for (i = 0; i < form.checkboxes.length; i++) {
|
|
@@ -429,7 +446,6 @@ var ConsentApp = /** @class */ (function () {
|
|
|
429
446
|
}
|
|
430
447
|
});
|
|
431
448
|
}); }));
|
|
432
|
-
// GET /consents/audit/:userId - admin audit trail for a specific user
|
|
433
449
|
if (auditTrail) {
|
|
434
450
|
router.get("/audit/:userId", (0, auth_1.authenticateMiddleware)(), (0, api_1.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
435
451
|
var user, responses, formIds, forms, formMap, auditEntries;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Model } from "mongoose";
|
|
2
|
+
import type { PopulatePath } from "./populate";
|
|
3
|
+
/**
|
|
4
|
+
* Loads a document by id or throws a 404 APIError.
|
|
5
|
+
* Matches permission middleware behavior including soft-delete metadata.
|
|
6
|
+
*/
|
|
7
|
+
export declare const loadDocOr404: <T>(model: Model<T>, id: string, populatePaths?: PopulatePath[]) => Promise<T>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
45
|
+
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);
|
|
46
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
47
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
48
|
+
function step(op) {
|
|
49
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
50
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
51
|
+
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;
|
|
52
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
53
|
+
switch (op[0]) {
|
|
54
|
+
case 0: case 1: t = op; break;
|
|
55
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
56
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
57
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
58
|
+
default:
|
|
59
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
60
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
61
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
62
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
63
|
+
if (t[2]) _.ops.pop();
|
|
64
|
+
_.trys.pop(); continue;
|
|
65
|
+
}
|
|
66
|
+
op = body.call(thisArg, _);
|
|
67
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
68
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
72
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
73
|
+
};
|
|
74
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
75
|
+
exports.loadDocOr404 = void 0;
|
|
76
|
+
var Sentry = __importStar(require("@sentry/bun"));
|
|
77
|
+
var mongoose_1 = __importDefault(require("mongoose"));
|
|
78
|
+
var api_1 = require("./api");
|
|
79
|
+
var errors_1 = require("./errors");
|
|
80
|
+
/**
|
|
81
|
+
* Loads a document by id or throws a 404 APIError.
|
|
82
|
+
* Matches permission middleware behavior including soft-delete metadata.
|
|
83
|
+
*/
|
|
84
|
+
var loadDocOr404 = function (model, id, populatePaths) { return __awaiter(void 0, void 0, void 0, function () {
|
|
85
|
+
var builtQuery, populatedQuery, data, error_1, hiddenDoc, error, reason, error;
|
|
86
|
+
return __generator(this, function (_a) {
|
|
87
|
+
switch (_a.label) {
|
|
88
|
+
case 0:
|
|
89
|
+
builtQuery = model.findById(id);
|
|
90
|
+
populatedQuery = (0, api_1.addPopulateToQuery)(
|
|
91
|
+
// biome-ignore lint/suspicious/noExplicitAny: Query types vary based on populate paths
|
|
92
|
+
builtQuery, populatePaths);
|
|
93
|
+
_a.label = 1;
|
|
94
|
+
case 1:
|
|
95
|
+
_a.trys.push([1, 3, , 4]);
|
|
96
|
+
return [4 /*yield*/, populatedQuery.exec()];
|
|
97
|
+
case 2:
|
|
98
|
+
data = (_a.sent());
|
|
99
|
+
return [3 /*break*/, 4];
|
|
100
|
+
case 3:
|
|
101
|
+
error_1 = _a.sent();
|
|
102
|
+
if ((0, errors_1.isAPIError)(error_1)) {
|
|
103
|
+
throw error_1;
|
|
104
|
+
}
|
|
105
|
+
throw new errors_1.APIError({
|
|
106
|
+
error: error_1,
|
|
107
|
+
status: 500,
|
|
108
|
+
title: "GET failed on ".concat(id),
|
|
109
|
+
});
|
|
110
|
+
case 4:
|
|
111
|
+
if (!!data) return [3 /*break*/, 6];
|
|
112
|
+
return [4 /*yield*/, model.collection.findOne({
|
|
113
|
+
_id: new mongoose_1.default.Types.ObjectId(id),
|
|
114
|
+
})];
|
|
115
|
+
case 5:
|
|
116
|
+
hiddenDoc = _a.sent();
|
|
117
|
+
if (!hiddenDoc) {
|
|
118
|
+
Sentry.captureMessage("Document ".concat(id, " not found for model ").concat(model.modelName));
|
|
119
|
+
error = new errors_1.APIError({
|
|
120
|
+
status: 404,
|
|
121
|
+
title: "Document ".concat(id, " not found for model ").concat(model.modelName),
|
|
122
|
+
});
|
|
123
|
+
error.meta = undefined;
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
reason = null;
|
|
127
|
+
if (hiddenDoc.deleted) {
|
|
128
|
+
reason = { deleted: "true" };
|
|
129
|
+
}
|
|
130
|
+
else if (hiddenDoc.disabled) {
|
|
131
|
+
reason = { disabled: "true" };
|
|
132
|
+
}
|
|
133
|
+
else if (hiddenDoc.archived) {
|
|
134
|
+
reason = { archived: "true" };
|
|
135
|
+
}
|
|
136
|
+
if (!reason) {
|
|
137
|
+
error = new errors_1.APIError({
|
|
138
|
+
status: 404,
|
|
139
|
+
title: "Document ".concat(id, " not found for model ").concat(model.modelName),
|
|
140
|
+
});
|
|
141
|
+
error.meta = undefined;
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
throw new errors_1.APIError({
|
|
145
|
+
disableExternalErrorTracking: true,
|
|
146
|
+
meta: reason,
|
|
147
|
+
status: 404,
|
|
148
|
+
title: "Document ".concat(id, " not found for model ").concat(model.modelName),
|
|
149
|
+
});
|
|
150
|
+
case 6: return [2 /*return*/, data];
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}); };
|
|
154
|
+
exports.loadDocOr404 = loadDocOr404;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
// biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
|
|
40
|
+
var bun_test_1 = require("bun:test");
|
|
41
|
+
var docLoader_1 = require("./docLoader");
|
|
42
|
+
var errors_1 = require("./errors");
|
|
43
|
+
(0, bun_test_1.describe)("loadDocOr404", function () {
|
|
44
|
+
(0, bun_test_1.it)("returns hidden reason metadata when document is deleted", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
45
|
+
var model, error_1, apiError;
|
|
46
|
+
return __generator(this, function (_a) {
|
|
47
|
+
switch (_a.label) {
|
|
48
|
+
case 0:
|
|
49
|
+
model = {
|
|
50
|
+
collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
51
|
+
return [2 /*return*/, ({ deleted: true })];
|
|
52
|
+
}); }); }) },
|
|
53
|
+
findById: (0, bun_test_1.mock)(function () { return ({ exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
54
|
+
return [2 /*return*/, null];
|
|
55
|
+
}); }); }) }); }),
|
|
56
|
+
modelName: "MockModel",
|
|
57
|
+
};
|
|
58
|
+
_a.label = 1;
|
|
59
|
+
case 1:
|
|
60
|
+
_a.trys.push([1, 3, , 4]);
|
|
61
|
+
return [4 /*yield*/, (0, docLoader_1.loadDocOr404)(model, "507f1f77bcf86cd799439011")];
|
|
62
|
+
case 2:
|
|
63
|
+
_a.sent();
|
|
64
|
+
bun_test_1.expect.unreachable("expected loadDocOr404 to throw");
|
|
65
|
+
return [3 /*break*/, 4];
|
|
66
|
+
case 3:
|
|
67
|
+
error_1 = _a.sent();
|
|
68
|
+
(0, bun_test_1.expect)(error_1).toBeInstanceOf(errors_1.APIError);
|
|
69
|
+
apiError = error_1;
|
|
70
|
+
(0, bun_test_1.expect)(apiError.status).toBe(404);
|
|
71
|
+
(0, bun_test_1.expect)(apiError.meta).toEqual({ deleted: "true" });
|
|
72
|
+
(0, bun_test_1.expect)(apiError.disableExternalErrorTracking).toBe(true);
|
|
73
|
+
return [3 /*break*/, 4];
|
|
74
|
+
case 4: return [2 /*return*/];
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}); });
|
|
78
|
+
(0, bun_test_1.it)("rethrows APIError from query execution without wrapping", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
79
|
+
var original, model;
|
|
80
|
+
return __generator(this, function (_a) {
|
|
81
|
+
switch (_a.label) {
|
|
82
|
+
case 0:
|
|
83
|
+
original = new errors_1.APIError({ status: 400, title: "validation failed" });
|
|
84
|
+
model = {
|
|
85
|
+
collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
86
|
+
return [2 /*return*/, null];
|
|
87
|
+
}); }); }) },
|
|
88
|
+
findById: (0, bun_test_1.mock)(function () { return ({
|
|
89
|
+
exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
90
|
+
return __generator(this, function (_a) {
|
|
91
|
+
throw original;
|
|
92
|
+
});
|
|
93
|
+
}); }),
|
|
94
|
+
}); }),
|
|
95
|
+
modelName: "MockModel",
|
|
96
|
+
};
|
|
97
|
+
return [4 /*yield*/, (0, bun_test_1.expect)((0, docLoader_1.loadDocOr404)(model, "507f1f77bcf86cd799439011")).rejects.toBe(original)];
|
|
98
|
+
case 1:
|
|
99
|
+
_a.sent();
|
|
100
|
+
return [2 /*return*/];
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}); });
|
|
104
|
+
(0, bun_test_1.it)("returns plain not found when document does not exist", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
105
|
+
var model, error_2, apiError;
|
|
106
|
+
return __generator(this, function (_a) {
|
|
107
|
+
switch (_a.label) {
|
|
108
|
+
case 0:
|
|
109
|
+
model = {
|
|
110
|
+
collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
111
|
+
return [2 /*return*/, null];
|
|
112
|
+
}); }); }) },
|
|
113
|
+
findById: (0, bun_test_1.mock)(function () { return ({ exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
114
|
+
return [2 /*return*/, null];
|
|
115
|
+
}); }); }) }); }),
|
|
116
|
+
modelName: "MockModel",
|
|
117
|
+
};
|
|
118
|
+
_a.label = 1;
|
|
119
|
+
case 1:
|
|
120
|
+
_a.trys.push([1, 3, , 4]);
|
|
121
|
+
return [4 /*yield*/, (0, docLoader_1.loadDocOr404)(model, "507f1f77bcf86cd799439011")];
|
|
122
|
+
case 2:
|
|
123
|
+
_a.sent();
|
|
124
|
+
bun_test_1.expect.unreachable("expected loadDocOr404 to throw");
|
|
125
|
+
return [3 /*break*/, 4];
|
|
126
|
+
case 3:
|
|
127
|
+
error_2 = _a.sent();
|
|
128
|
+
(0, bun_test_1.expect)(error_2).toBeInstanceOf(errors_1.APIError);
|
|
129
|
+
apiError = error_2;
|
|
130
|
+
(0, bun_test_1.expect)(apiError.status).toBe(404);
|
|
131
|
+
(0, bun_test_1.expect)(apiError.meta).toBeUndefined();
|
|
132
|
+
return [3 /*break*/, 4];
|
|
133
|
+
case 4: return [2 /*return*/];
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}); });
|
|
137
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./actions";
|
|
1
2
|
export * from "./api";
|
|
2
3
|
export * from "./auth";
|
|
3
4
|
export * from "./betterAuth";
|
|
@@ -36,3 +37,4 @@ export * from "./types/consentForm";
|
|
|
36
37
|
export * from "./types/consentResponse";
|
|
37
38
|
export * from "./utils";
|
|
38
39
|
export * from "./versionCheckPlugin";
|
|
40
|
+
export { z } from "./zodOpenApi";
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.z = void 0;
|
|
18
|
+
__exportStar(require("./actions"), exports);
|
|
17
19
|
__exportStar(require("./api"), exports);
|
|
18
20
|
__exportStar(require("./auth"), exports);
|
|
19
21
|
__exportStar(require("./betterAuth"), exports);
|
|
@@ -52,3 +54,5 @@ __exportStar(require("./types/consentForm"), exports);
|
|
|
52
54
|
__exportStar(require("./types/consentResponse"), exports);
|
|
53
55
|
__exportStar(require("./utils"), exports);
|
|
54
56
|
__exportStar(require("./versionCheckPlugin"), exports);
|
|
57
|
+
var zodOpenApi_1 = require("./zodOpenApi");
|
|
58
|
+
Object.defineProperty(exports, "z", { enumerable: true, get: function () { return zodOpenApi_1.z; } });
|