@terreno/api 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +170 -0
  3. package/biome.jsonc +22 -0
  4. package/bunfig.toml +4 -0
  5. package/dist/api.d.ts +227 -0
  6. package/dist/api.js +1024 -0
  7. package/dist/api.test.d.ts +1 -0
  8. package/dist/api.test.js +2143 -0
  9. package/dist/auth.d.ts +50 -0
  10. package/dist/auth.js +512 -0
  11. package/dist/auth.test.d.ts +1 -0
  12. package/dist/auth.test.js +778 -0
  13. package/dist/errors.d.ts +75 -0
  14. package/dist/errors.js +216 -0
  15. package/dist/example.d.ts +1 -0
  16. package/dist/example.js +118 -0
  17. package/dist/expressServer.d.ts +35 -0
  18. package/dist/expressServer.js +436 -0
  19. package/dist/index.d.ts +14 -0
  20. package/dist/index.js +30 -0
  21. package/dist/logger.d.ts +23 -0
  22. package/dist/logger.js +249 -0
  23. package/dist/middleware.d.ts +10 -0
  24. package/dist/middleware.js +52 -0
  25. package/dist/notifiers/googleChatNotifier.d.ts +5 -0
  26. package/dist/notifiers/googleChatNotifier.js +130 -0
  27. package/dist/notifiers/googleChatNotifier.test.d.ts +1 -0
  28. package/dist/notifiers/googleChatNotifier.test.js +260 -0
  29. package/dist/notifiers/index.d.ts +3 -0
  30. package/dist/notifiers/index.js +19 -0
  31. package/dist/notifiers/slackNotifier.d.ts +5 -0
  32. package/dist/notifiers/slackNotifier.js +130 -0
  33. package/dist/notifiers/slackNotifier.test.d.ts +1 -0
  34. package/dist/notifiers/slackNotifier.test.js +259 -0
  35. package/dist/notifiers/zoomNotifier.d.ts +34 -0
  36. package/dist/notifiers/zoomNotifier.js +181 -0
  37. package/dist/notifiers/zoomNotifier.test.d.ts +1 -0
  38. package/dist/notifiers/zoomNotifier.test.js +370 -0
  39. package/dist/openApi.d.ts +60 -0
  40. package/dist/openApi.js +441 -0
  41. package/dist/openApi.test.d.ts +1 -0
  42. package/dist/openApi.test.js +445 -0
  43. package/dist/openApiBuilder.d.ts +419 -0
  44. package/dist/openApiBuilder.js +424 -0
  45. package/dist/openApiBuilder.test.d.ts +1 -0
  46. package/dist/openApiBuilder.test.js +509 -0
  47. package/dist/openApiEtag.d.ts +7 -0
  48. package/dist/openApiEtag.js +38 -0
  49. package/dist/permissions.d.ts +26 -0
  50. package/dist/permissions.js +331 -0
  51. package/dist/permissions.test.d.ts +1 -0
  52. package/dist/permissions.test.js +413 -0
  53. package/dist/plugins.d.ts +67 -0
  54. package/dist/plugins.js +315 -0
  55. package/dist/plugins.test.d.ts +1 -0
  56. package/dist/plugins.test.js +639 -0
  57. package/dist/populate.d.ts +14 -0
  58. package/dist/populate.js +315 -0
  59. package/dist/populate.test.d.ts +1 -0
  60. package/dist/populate.test.js +133 -0
  61. package/dist/response.d.ts +0 -0
  62. package/dist/response.js +1 -0
  63. package/dist/tests/bunSetup.d.ts +1 -0
  64. package/dist/tests/bunSetup.js +297 -0
  65. package/dist/tests/index.d.ts +1 -0
  66. package/dist/tests/index.js +17 -0
  67. package/dist/tests.d.ts +99 -0
  68. package/dist/tests.js +273 -0
  69. package/dist/transformers.d.ts +25 -0
  70. package/dist/transformers.js +217 -0
  71. package/dist/transformers.test.d.ts +1 -0
  72. package/dist/transformers.test.js +370 -0
  73. package/dist/utils.d.ts +11 -0
  74. package/dist/utils.js +143 -0
  75. package/dist/utils.test.d.ts +1 -0
  76. package/dist/utils.test.js +14 -0
  77. package/index.ts +1 -0
  78. package/package.json +88 -0
  79. package/src/__snapshots__/openApi.test.ts.snap +4814 -0
  80. package/src/__snapshots__/openApiBuilder.test.ts.snap +1485 -0
  81. package/src/api.test.ts +1661 -0
  82. package/src/api.ts +1036 -0
  83. package/src/auth.test.ts +550 -0
  84. package/src/auth.ts +408 -0
  85. package/src/errors.ts +225 -0
  86. package/src/example.ts +99 -0
  87. package/src/express.d.ts +5 -0
  88. package/src/expressServer.ts +387 -0
  89. package/src/index.ts +14 -0
  90. package/src/logger.ts +190 -0
  91. package/src/middleware.ts +18 -0
  92. package/src/notifiers/googleChatNotifier.test.ts +114 -0
  93. package/src/notifiers/googleChatNotifier.ts +47 -0
  94. package/src/notifiers/index.ts +3 -0
  95. package/src/notifiers/slackNotifier.test.ts +113 -0
  96. package/src/notifiers/slackNotifier.ts +55 -0
  97. package/src/notifiers/zoomNotifier.test.ts +207 -0
  98. package/src/notifiers/zoomNotifier.ts +111 -0
  99. package/src/openApi.test.ts +331 -0
  100. package/src/openApi.ts +494 -0
  101. package/src/openApiBuilder.test.ts +442 -0
  102. package/src/openApiBuilder.ts +636 -0
  103. package/src/openApiEtag.ts +40 -0
  104. package/src/permissions.test.ts +219 -0
  105. package/src/permissions.ts +228 -0
  106. package/src/plugins.test.ts +390 -0
  107. package/src/plugins.ts +289 -0
  108. package/src/populate.test.ts +65 -0
  109. package/src/populate.ts +258 -0
  110. package/src/response.ts +0 -0
  111. package/src/tests/bunSetup.ts +234 -0
  112. package/src/tests/index.ts +1 -0
  113. package/src/tests.ts +218 -0
  114. package/src/transformers.test.ts +202 -0
  115. package/src/transformers.ts +170 -0
  116. package/src/utils.test.ts +14 -0
  117. package/src/utils.ts +47 -0
  118. package/tsconfig.json +60 -0
  119. package/types.d.ts +17 -0
package/dist/api.js ADDED
@@ -0,0 +1,1024 @@
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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
47
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
48
+ return new (P || (P = Promise))(function (resolve, reject) {
49
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
50
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
51
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
52
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
53
+ });
54
+ };
55
+ var __generator = (this && this.__generator) || function (thisArg, body) {
56
+ 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);
57
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
58
+ function verb(n) { return function (v) { return step([n, v]); }; }
59
+ function step(op) {
60
+ if (f) throw new TypeError("Generator is already executing.");
61
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
62
+ 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;
63
+ if (y = 0, t) op = [op[0] & 2, t.value];
64
+ switch (op[0]) {
65
+ case 0: case 1: t = op; break;
66
+ case 4: _.label++; return { value: op[1], done: false };
67
+ case 5: _.label++; y = op[1]; op = [0]; continue;
68
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
69
+ default:
70
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
71
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
72
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
73
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
74
+ if (t[2]) _.ops.pop();
75
+ _.trys.pop(); continue;
76
+ }
77
+ op = body.call(thisArg, _);
78
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
79
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
80
+ }
81
+ };
82
+ var __values = (this && this.__values) || function(o) {
83
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
84
+ if (m) return m.call(o);
85
+ if (o && typeof o.length === "number") return {
86
+ next: function () {
87
+ if (o && i >= o.length) o = void 0;
88
+ return { value: o && o[i++], done: !o };
89
+ }
90
+ };
91
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
92
+ };
93
+ var __read = (this && this.__read) || function (o, n) {
94
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
95
+ if (!m) return o;
96
+ var i = m.call(o), r, ar = [], e;
97
+ try {
98
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
99
+ }
100
+ catch (error) { e = { error: error }; }
101
+ finally {
102
+ try {
103
+ if (r && !r.done && (m = i["return"])) m.call(i);
104
+ }
105
+ finally { if (e) throw e.error; }
106
+ }
107
+ return ar;
108
+ };
109
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
110
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
111
+ if (ar || !(i in from)) {
112
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
113
+ ar[i] = from[i];
114
+ }
115
+ }
116
+ return to.concat(ar || Array.prototype.slice.call(from));
117
+ };
118
+ var __importDefault = (this && this.__importDefault) || function (mod) {
119
+ return (mod && mod.__esModule) ? mod : { "default": mod };
120
+ };
121
+ Object.defineProperty(exports, "__esModule", { value: true });
122
+ exports.gooseRestRouter = exports.asyncHandler = void 0;
123
+ exports.addPopulateToQuery = addPopulateToQuery;
124
+ exports.getModel = getModel;
125
+ exports.modelRouter = modelRouter;
126
+ /**
127
+ * This is the doc comment for api.ts
128
+ *
129
+ * @packageDocumentation
130
+ */
131
+ var Sentry = __importStar(require("@sentry/node"));
132
+ var express_1 = __importDefault(require("express"));
133
+ var cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
134
+ var mongoose_1 = __importDefault(require("mongoose"));
135
+ var auth_1 = require("./auth");
136
+ var errors_1 = require("./errors");
137
+ var logger_1 = require("./logger");
138
+ var openApi_1 = require("./openApi");
139
+ var permissions_1 = require("./permissions");
140
+ var transformers_1 = require("./transformers");
141
+ var utils_1 = require("./utils");
142
+ function addPopulateToQuery(builtQuery, populatePaths) {
143
+ var e_1, _a;
144
+ var paths = populatePaths !== null && populatePaths !== void 0 ? populatePaths : [];
145
+ var query = builtQuery;
146
+ try {
147
+ for (var paths_1 = __values(paths), paths_1_1 = paths_1.next(); !paths_1_1.done; paths_1_1 = paths_1.next()) {
148
+ var populatePath = paths_1_1.value;
149
+ var path = populatePath.path;
150
+ var select = populatePath.fields;
151
+ query = builtQuery.populate({ path: path, select: select });
152
+ }
153
+ }
154
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
155
+ finally {
156
+ try {
157
+ if (paths_1_1 && !paths_1_1.done && (_a = paths_1.return)) _a.call(paths_1);
158
+ }
159
+ finally { if (e_1) throw e_1.error; }
160
+ }
161
+ return query;
162
+ }
163
+ // TODOS:
164
+ // Support bulk actions
165
+ // Support more complex query fields
166
+ // Rate limiting
167
+ // These are the query params that are reserved for pagination.
168
+ var PAGINATION_QUERY_PARAMS = ["limit", "page", "sort"];
169
+ // Add support for more complex queries.
170
+ var COMPLEX_QUERY_PARAMS = ["$and", "$or"];
171
+ // A function to decide which model to use. If no discriminators are provided,
172
+ // just returns the base model. If
173
+ function getModel(baseModel, body, options) {
174
+ var _a, _b;
175
+ var discriminatorKey = (_a = options === null || options === void 0 ? void 0 : options.discriminatorKey) !== null && _a !== void 0 ? _a : "__t";
176
+ var modelName = body === null || body === void 0 ? void 0 : body[discriminatorKey];
177
+ if (!modelName) {
178
+ return baseModel;
179
+ }
180
+ var model = (_b = baseModel.discriminators) === null || _b === void 0 ? void 0 : _b[modelName];
181
+ if (!model) {
182
+ throw new Error("Could not find discriminator model for key ".concat(modelName, ", baseModel: ").concat(baseModel));
183
+ }
184
+ return model;
185
+ }
186
+ // Ensures query params are allowed. Also checks nested query params when using $and/$or.
187
+ function checkQueryParamAllowed(queryParam, queryParamValue, queryFields) {
188
+ var e_2, _a, e_3, _b;
189
+ if (queryFields === void 0) { queryFields = []; }
190
+ // Check the values of each of the complex query params. We don't support recursive queries here,
191
+ // just one level of and/or
192
+ if (COMPLEX_QUERY_PARAMS.includes(queryParam)) {
193
+ try {
194
+ // Complex query of the form `$and: [{key1: value1}, {key2: value2}]`
195
+ for (var queryParamValue_1 = __values(queryParamValue), queryParamValue_1_1 = queryParamValue_1.next(); !queryParamValue_1_1.done; queryParamValue_1_1 = queryParamValue_1.next()) {
196
+ var subQuery = queryParamValue_1_1.value;
197
+ try {
198
+ for (var _c = (e_3 = void 0, __values(Object.keys(subQuery))), _d = _c.next(); !_d.done; _d = _c.next()) {
199
+ var subKey = _d.value;
200
+ checkQueryParamAllowed(subKey, subQuery[subKey], queryFields);
201
+ }
202
+ }
203
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
204
+ finally {
205
+ try {
206
+ if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
207
+ }
208
+ finally { if (e_3) throw e_3.error; }
209
+ }
210
+ }
211
+ }
212
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
213
+ finally {
214
+ try {
215
+ if (queryParamValue_1_1 && !queryParamValue_1_1.done && (_a = queryParamValue_1.return)) _a.call(queryParamValue_1);
216
+ }
217
+ finally { if (e_2) throw e_2.error; }
218
+ }
219
+ return;
220
+ }
221
+ if (!queryFields.includes(queryParam)) {
222
+ throw new errors_1.APIError({
223
+ status: 400,
224
+ title: "".concat(queryParam, " is not allowed as a query param."),
225
+ });
226
+ }
227
+ }
228
+ // Handles dot notation patches, creates a normal object to be used for updates.
229
+ // function flattenDotNotationPatch(data: any) {
230
+ // const result = {};
231
+ //
232
+ // for (const key in data) {
233
+ // if (data.hasOwnProperty(key)) {
234
+ // if (typeof data[key] === "object" && !key.includes(".")) {
235
+ // // If the value is an object and the key does not contain a dot, merge it
236
+ // merge(result, {[key]: data[key]});
237
+ // } else {
238
+ // // Otherwise, use _.set() to handle dot notation
239
+ // set(result, key, data[key]);
240
+ // }
241
+ // }
242
+ // }
243
+ //
244
+ // return result;
245
+ // }
246
+ /**
247
+ * Create a set of CRUD routes given a Mongoose model $baseModel and configuration options.
248
+ *
249
+ * @param baseModel A Mongoose Model
250
+ * @param options Options for configuring the REST API, such as permissions, transformers, and hooks.
251
+ */
252
+ function modelRouter(baseModel, options) {
253
+ var _this = this;
254
+ var _a;
255
+ var router = express_1.default.Router();
256
+ // Do before the other router options so endpoints take priority.
257
+ if (options.endpoints) {
258
+ options.endpoints(router);
259
+ }
260
+ var responseHandler = (_a = options.responseHandler) !== null && _a !== void 0 ? _a : transformers_1.defaultResponseHandler;
261
+ router.post("/", [
262
+ (0, auth_1.authenticateMiddleware)(options.allowAnonymous),
263
+ (0, openApi_1.createOpenApiMiddleware)(baseModel, options),
264
+ (0, permissions_1.permissionMiddleware)(baseModel, options),
265
+ ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
266
+ var model, body, error_1, data, error_2, populateQuery, error_3, error_4, serialized, error_5;
267
+ var _a;
268
+ return __generator(this, function (_b) {
269
+ switch (_b.label) {
270
+ case 0:
271
+ model = getModel(baseModel, (_a = req.body) === null || _a === void 0 ? void 0 : _a.__t, options);
272
+ try {
273
+ body = (0, transformers_1.transform)(options, req.body, "create", req.user);
274
+ }
275
+ catch (error) {
276
+ throw new errors_1.APIError({
277
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error),
278
+ error: error,
279
+ status: 400,
280
+ title: error.message,
281
+ });
282
+ }
283
+ if (!options.preCreate) return [3 /*break*/, 5];
284
+ _b.label = 1;
285
+ case 1:
286
+ _b.trys.push([1, 3, , 4]);
287
+ return [4 /*yield*/, options.preCreate(body, req)];
288
+ case 2:
289
+ body = _b.sent();
290
+ return [3 /*break*/, 4];
291
+ case 3:
292
+ error_1 = _b.sent();
293
+ if ((0, errors_1.isAPIError)(error_1)) {
294
+ throw error_1;
295
+ }
296
+ throw new errors_1.APIError({
297
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_1),
298
+ error: error_1,
299
+ status: 400,
300
+ title: "preCreate hook error: ".concat(error_1.message),
301
+ });
302
+ case 4:
303
+ if (body === undefined) {
304
+ throw new errors_1.APIError({
305
+ detail: "A body must be returned from preCreate",
306
+ status: 403,
307
+ title: "Create not allowed",
308
+ });
309
+ }
310
+ if (body === null) {
311
+ throw new errors_1.APIError({
312
+ detail: "preCreate hook returned null",
313
+ status: 403,
314
+ title: "Create not allowed",
315
+ });
316
+ }
317
+ _b.label = 5;
318
+ case 5:
319
+ if (body === undefined) {
320
+ throw new errors_1.APIError({
321
+ detail: "Body is undefined",
322
+ status: 400,
323
+ title: "Invalid request body",
324
+ });
325
+ }
326
+ _b.label = 6;
327
+ case 6:
328
+ _b.trys.push([6, 8, , 9]);
329
+ return [4 /*yield*/, model.create(body)];
330
+ case 7:
331
+ data = _b.sent();
332
+ return [3 /*break*/, 9];
333
+ case 8:
334
+ error_2 = _b.sent();
335
+ throw new errors_1.APIError({
336
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_2),
337
+ error: error_2,
338
+ status: 400,
339
+ title: error_2.message,
340
+ });
341
+ case 9:
342
+ if (!options.populatePaths) return [3 /*break*/, 13];
343
+ _b.label = 10;
344
+ case 10:
345
+ _b.trys.push([10, 12, , 13]);
346
+ populateQuery = model.findById(data._id);
347
+ populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
348
+ return [4 /*yield*/, populateQuery.exec()];
349
+ case 11:
350
+ data = _b.sent();
351
+ return [3 /*break*/, 13];
352
+ case 12:
353
+ error_3 = _b.sent();
354
+ throw new errors_1.APIError({
355
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_3),
356
+ error: error_3,
357
+ status: 400,
358
+ title: "Populate error: ".concat(error_3.message),
359
+ });
360
+ case 13:
361
+ if (!options.postCreate) return [3 /*break*/, 17];
362
+ _b.label = 14;
363
+ case 14:
364
+ _b.trys.push([14, 16, , 17]);
365
+ return [4 /*yield*/, options.postCreate(data, req)];
366
+ case 15:
367
+ _b.sent();
368
+ return [3 /*break*/, 17];
369
+ case 16:
370
+ error_4 = _b.sent();
371
+ throw new errors_1.APIError({
372
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_4),
373
+ error: error_4,
374
+ status: 400,
375
+ title: "postCreate hook error: ".concat(error_4.message),
376
+ });
377
+ case 17:
378
+ _b.trys.push([17, 19, , 20]);
379
+ return [4 /*yield*/, responseHandler(data, "create", req, options)];
380
+ case 18:
381
+ serialized = _b.sent();
382
+ return [2 /*return*/, res.status(201).json({ data: serialized })];
383
+ case 19:
384
+ error_5 = _b.sent();
385
+ throw new errors_1.APIError({
386
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_5),
387
+ error: error_5,
388
+ title: "responseHandler error: ".concat(error_5.message),
389
+ });
390
+ case 20: return [2 /*return*/];
391
+ }
392
+ });
393
+ }); }));
394
+ // TODO add rate limit
395
+ router.get("/", [
396
+ (0, auth_1.authenticateMiddleware)(options.allowAnonymous),
397
+ (0, permissions_1.permissionMiddleware)(baseModel, options),
398
+ (0, openApi_1.listOpenApiMiddleware)(baseModel, options),
399
+ ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
400
+ var model, query, _a, _b, queryParam, _c, _d, queryParam, queryFilter, error_6, limit, builtQuery, total, populatedQuery, data, error_7, serialized, error_8, more, msg;
401
+ var e_4, _e, e_5, _f;
402
+ var _g, _h, _j, _k, _l, _m;
403
+ return __generator(this, function (_o) {
404
+ switch (_o.label) {
405
+ case 0:
406
+ model = baseModel;
407
+ query = {};
408
+ try {
409
+ for (_a = __values(Object.keys((_g = options.defaultQueryParams) !== null && _g !== void 0 ? _g : [])), _b = _a.next(); !_b.done; _b = _a.next()) {
410
+ queryParam = _b.value;
411
+ query[queryParam] = (_h = options.defaultQueryParams) === null || _h === void 0 ? void 0 : _h[queryParam];
412
+ }
413
+ }
414
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
415
+ finally {
416
+ try {
417
+ if (_b && !_b.done && (_e = _a.return)) _e.call(_a);
418
+ }
419
+ finally { if (e_4) throw e_4.error; }
420
+ }
421
+ try {
422
+ for (_c = __values(Object.keys(req.query)), _d = _c.next(); !_d.done; _d = _c.next()) {
423
+ queryParam = _d.value;
424
+ if (PAGINATION_QUERY_PARAMS.includes(queryParam)) {
425
+ continue;
426
+ }
427
+ checkQueryParamAllowed(queryParam, req.query[queryParam], options.queryFields);
428
+ // Not sure if this is necessary or if mongoose does the right thing.
429
+ if (req.query[queryParam] === "true") {
430
+ query[queryParam] = true;
431
+ }
432
+ else if (req.query[queryParam] === "false") {
433
+ query[queryParam] = false;
434
+ }
435
+ else {
436
+ query[queryParam] = req.query[queryParam];
437
+ }
438
+ }
439
+ }
440
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
441
+ finally {
442
+ try {
443
+ if (_d && !_d.done && (_f = _c.return)) _f.call(_c);
444
+ }
445
+ finally { if (e_5) throw e_5.error; }
446
+ }
447
+ // Special operators. NOTE: these request Mongo Atlas.
448
+ if (req.query.$search) {
449
+ (_j = mongoose_1.default.connection.db) === null || _j === void 0 ? void 0 : _j.collection(model.collection.collectionName);
450
+ }
451
+ if (req.query.$autocomplete) {
452
+ (_k = mongoose_1.default.connection.db) === null || _k === void 0 ? void 0 : _k.collection(model.collection.collectionName);
453
+ }
454
+ if (!options.queryFilter) return [3 /*break*/, 5];
455
+ queryFilter = void 0;
456
+ _o.label = 1;
457
+ case 1:
458
+ _o.trys.push([1, 3, , 4]);
459
+ return [4 /*yield*/, options.queryFilter(req.user, query)];
460
+ case 2:
461
+ queryFilter = _o.sent();
462
+ return [3 /*break*/, 4];
463
+ case 3:
464
+ error_6 = _o.sent();
465
+ throw new errors_1.APIError({
466
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_6),
467
+ error: error_6,
468
+ status: 400,
469
+ title: "Query filter error: ".concat(error_6),
470
+ });
471
+ case 4:
472
+ // If the query filter returns null specifically, we know this is a query that shouldn't
473
+ // return any results.
474
+ if (queryFilter === null) {
475
+ return [2 /*return*/, res.json({ data: [] })];
476
+ }
477
+ query = __assign(__assign({}, query), queryFilter);
478
+ _o.label = 5;
479
+ case 5:
480
+ limit = (_l = options.defaultLimit) !== null && _l !== void 0 ? _l : 100;
481
+ if (Number(req.query.limit)) {
482
+ limit = Math.min(Number(req.query.limit), (_m = options.maxLimit) !== null && _m !== void 0 ? _m : 500);
483
+ }
484
+ if (query.period) {
485
+ // need to remove 'period' since it isn't part of any schemas but parsed and applied in
486
+ // queryFilter instead
487
+ query.period = undefined;
488
+ }
489
+ builtQuery = model.find(query).limit(limit + 1);
490
+ return [4 /*yield*/, model.countDocuments(query)];
491
+ case 6:
492
+ total = _o.sent();
493
+ if (req.query.page) {
494
+ if (Number(req.query.page) === 0 || Number.isNaN(Number(req.query.page))) {
495
+ throw new errors_1.APIError({
496
+ status: 400,
497
+ title: "Invalid page: ".concat(req.query.page),
498
+ });
499
+ }
500
+ builtQuery = builtQuery.skip((Number(req.query.page) - 1) * limit);
501
+ }
502
+ // Query param sort takes precedence over options.sort.
503
+ if (req.query.sort) {
504
+ builtQuery = builtQuery.sort(req.query.sort);
505
+ }
506
+ else if (options.sort) {
507
+ builtQuery = builtQuery.sort(options.sort);
508
+ }
509
+ populatedQuery = addPopulateToQuery(builtQuery, options.populatePaths);
510
+ _o.label = 7;
511
+ case 7:
512
+ _o.trys.push([7, 9, , 10]);
513
+ return [4 /*yield*/, populatedQuery.exec()];
514
+ case 8:
515
+ data = _o.sent();
516
+ return [3 /*break*/, 10];
517
+ case 9:
518
+ error_7 = _o.sent();
519
+ throw new errors_1.APIError({
520
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_7),
521
+ error: error_7,
522
+ title: "List error: ".concat(error_7.stack),
523
+ });
524
+ case 10:
525
+ _o.trys.push([10, 12, , 13]);
526
+ return [4 /*yield*/, responseHandler(data, "list", req, options)];
527
+ case 11:
528
+ serialized = _o.sent();
529
+ return [3 /*break*/, 13];
530
+ case 12:
531
+ error_8 = _o.sent();
532
+ throw new errors_1.APIError({
533
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_8),
534
+ error: error_8,
535
+ title: "responseHandler error: ".concat(error_8.message),
536
+ });
537
+ case 13:
538
+ try {
539
+ if (serialized && Array.isArray(serialized)) {
540
+ more = serialized.length === limit + 1 && serialized.length > 0;
541
+ if (more) {
542
+ // Slice off the extra document we fetched to determine if more is true or not.
543
+ serialized = serialized.slice(0, limit);
544
+ if (!req.query.page) {
545
+ msg = "More than ".concat(limit, " results returned for ").concat(model.collection.name, " without pagination, data may be silently truncated. req.query: ").concat(JSON.stringify(req.query));
546
+ logger_1.logger.warn(msg);
547
+ try {
548
+ Sentry.captureMessage(msg);
549
+ }
550
+ catch (error) {
551
+ logger_1.logger.error("Error capturing message: ".concat(error));
552
+ }
553
+ }
554
+ }
555
+ return [2 /*return*/, res.json({
556
+ data: serialized,
557
+ limit: limit,
558
+ more: more,
559
+ page: req.query.page,
560
+ total: total,
561
+ })];
562
+ }
563
+ return [2 /*return*/, res.json({ data: serialized })];
564
+ }
565
+ catch (error) {
566
+ throw new errors_1.APIError({
567
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error),
568
+ error: error,
569
+ title: "Serialization error: ".concat(error.message),
570
+ });
571
+ }
572
+ return [2 /*return*/];
573
+ }
574
+ });
575
+ }); }));
576
+ router.get("/:id", [
577
+ (0, auth_1.authenticateMiddleware)(options.allowAnonymous),
578
+ (0, openApi_1.getOpenApiMiddleware)(baseModel, options),
579
+ (0, permissions_1.permissionMiddleware)(baseModel, options),
580
+ ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
581
+ var data, serialized, error_9;
582
+ return __generator(this, function (_a) {
583
+ switch (_a.label) {
584
+ case 0:
585
+ data = req.obj;
586
+ _a.label = 1;
587
+ case 1:
588
+ _a.trys.push([1, 3, , 4]);
589
+ return [4 /*yield*/, responseHandler(data, "read", req, options)];
590
+ case 2:
591
+ serialized = _a.sent();
592
+ return [2 /*return*/, res.json({ data: serialized })];
593
+ case 3:
594
+ error_9 = _a.sent();
595
+ throw new errors_1.APIError({
596
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_9),
597
+ error: error_9,
598
+ title: "responseHandler error: ".concat(error_9.message),
599
+ });
600
+ case 4: return [2 /*return*/];
601
+ }
602
+ });
603
+ }); }));
604
+ router.put("/:id", (0, auth_1.authenticateMiddleware)(options.allowAnonymous), (0, exports.asyncHandler)(function (_req, _res) { return __awaiter(_this, void 0, void 0, function () {
605
+ return __generator(this, function (_a) {
606
+ // Patch is what we want 90% of the time
607
+ throw new errors_1.APIError({
608
+ title: "PUT is not supported.",
609
+ });
610
+ });
611
+ }); }));
612
+ router.patch("/:id", [
613
+ (0, auth_1.authenticateMiddleware)(options.allowAnonymous),
614
+ (0, openApi_1.patchOpenApiMiddleware)(baseModel, options),
615
+ (0, permissions_1.permissionMiddleware)(baseModel, options),
616
+ ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
617
+ var model, doc, body, error_10, prevDoc, error_11, populateQuery, error_12, serialized, error_13;
618
+ var _a;
619
+ return __generator(this, function (_b) {
620
+ switch (_b.label) {
621
+ case 0:
622
+ model = getModel(baseModel, req.body, options);
623
+ doc = req.obj;
624
+ try {
625
+ body = (0, transformers_1.transform)(options, req.body, "update", req.user);
626
+ }
627
+ catch (error) {
628
+ throw new errors_1.APIError({
629
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error),
630
+ error: error,
631
+ status: 403,
632
+ title: "PATCH failed on ".concat(req.params.id, " for user ").concat((_a = req.user) === null || _a === void 0 ? void 0 : _a.id, ": ").concat(error.message),
633
+ });
634
+ }
635
+ if (!options.preUpdate) return [3 /*break*/, 5];
636
+ _b.label = 1;
637
+ case 1:
638
+ _b.trys.push([1, 3, , 4]);
639
+ return [4 /*yield*/, options.preUpdate(body, req)];
640
+ case 2:
641
+ // TODO: Send flattened dot notation body to preUpdate, then merge the returned body
642
+ // with the original body, maintaining the dot notation. This way we don't have to write
643
+ // two preUpdate branches downstream, one looking at the dot notation style and
644
+ // one looking at normal object style.
645
+ body = _b.sent();
646
+ return [3 /*break*/, 4];
647
+ case 3:
648
+ error_10 = _b.sent();
649
+ if ((0, errors_1.isAPIError)(error_10)) {
650
+ throw error_10;
651
+ }
652
+ throw new errors_1.APIError({
653
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_10),
654
+ error: error_10,
655
+ status: 400,
656
+ title: "preUpdate hook error on ".concat(req.params.id, ": ").concat(error_10.message),
657
+ });
658
+ case 4:
659
+ if (body === undefined) {
660
+ throw new errors_1.APIError({
661
+ detail: "A body must be returned from preUpdate",
662
+ status: 403,
663
+ title: "Update not allowed",
664
+ });
665
+ }
666
+ if (body === null) {
667
+ throw new errors_1.APIError({
668
+ detail: "preUpdate hook on ".concat(req.params.id, " returned null"),
669
+ status: 403,
670
+ title: "Update not allowed",
671
+ });
672
+ }
673
+ _b.label = 5;
674
+ case 5:
675
+ prevDoc = (0, cloneDeep_1.default)(doc);
676
+ _b.label = 6;
677
+ case 6:
678
+ _b.trys.push([6, 8, , 9]);
679
+ doc.set(body);
680
+ return [4 /*yield*/, doc.save()];
681
+ case 7:
682
+ _b.sent();
683
+ return [3 /*break*/, 9];
684
+ case 8:
685
+ error_11 = _b.sent();
686
+ throw new errors_1.APIError({
687
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_11),
688
+ error: error_11,
689
+ status: 400,
690
+ title: "preUpdate hook save error on ".concat(req.params.id, ": ").concat(error_11.message),
691
+ });
692
+ case 9:
693
+ if (!options.populatePaths) return [3 /*break*/, 11];
694
+ populateQuery = model.findById(doc._id);
695
+ populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
696
+ return [4 /*yield*/, populateQuery.exec()];
697
+ case 10:
698
+ doc = _b.sent();
699
+ _b.label = 11;
700
+ case 11:
701
+ if (!options.postUpdate) return [3 /*break*/, 15];
702
+ _b.label = 12;
703
+ case 12:
704
+ _b.trys.push([12, 14, , 15]);
705
+ return [4 /*yield*/, options.postUpdate(doc, body, req, prevDoc)];
706
+ case 13:
707
+ _b.sent();
708
+ return [3 /*break*/, 15];
709
+ case 14:
710
+ error_12 = _b.sent();
711
+ throw new errors_1.APIError({
712
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_12),
713
+ error: error_12,
714
+ status: 400,
715
+ title: "postUpdate hook error on ".concat(req.params.id, ": ").concat(error_12.message),
716
+ });
717
+ case 15:
718
+ _b.trys.push([15, 17, , 18]);
719
+ return [4 /*yield*/, responseHandler(doc, "update", req, options)];
720
+ case 16:
721
+ serialized = _b.sent();
722
+ return [2 /*return*/, res.json({ data: serialized })];
723
+ case 17:
724
+ error_13 = _b.sent();
725
+ throw new errors_1.APIError({
726
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_13),
727
+ error: error_13,
728
+ title: "responseHandler error: ".concat(error_13.message),
729
+ });
730
+ case 18: return [2 /*return*/];
731
+ }
732
+ });
733
+ }); }));
734
+ router.delete("/:id", [
735
+ (0, auth_1.authenticateMiddleware)(options.allowAnonymous),
736
+ (0, openApi_1.deleteOpenApiMiddleware)(baseModel, options),
737
+ (0, permissions_1.permissionMiddleware)(baseModel, options),
738
+ ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
739
+ var model, doc, body, error_14, error_15, error_16;
740
+ return __generator(this, function (_a) {
741
+ switch (_a.label) {
742
+ case 0:
743
+ model = getModel(baseModel, req.body, options);
744
+ doc = req.obj;
745
+ if (!options.preDelete) return [3 /*break*/, 5];
746
+ body = void 0;
747
+ _a.label = 1;
748
+ case 1:
749
+ _a.trys.push([1, 3, , 4]);
750
+ return [4 /*yield*/, options.preDelete(doc, req)];
751
+ case 2:
752
+ body = _a.sent();
753
+ return [3 /*break*/, 4];
754
+ case 3:
755
+ error_14 = _a.sent();
756
+ if ((0, errors_1.isAPIError)(error_14)) {
757
+ throw error_14;
758
+ }
759
+ throw new errors_1.APIError({
760
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_14),
761
+ error: error_14,
762
+ status: 403,
763
+ title: "preDelete hook error on ".concat(req.params.id, ": ").concat(error_14.message),
764
+ });
765
+ case 4:
766
+ if (body === undefined) {
767
+ throw new errors_1.APIError({
768
+ detail: "A body must be returned from preDelete",
769
+ status: 403,
770
+ title: "Delete not allowed",
771
+ });
772
+ }
773
+ if (body === null) {
774
+ throw new errors_1.APIError({
775
+ detail: "preDelete hook for ".concat(req.params.id, " returned null"),
776
+ status: 403,
777
+ title: "Delete not allowed",
778
+ });
779
+ }
780
+ _a.label = 5;
781
+ case 5:
782
+ if (!(Object.keys(model.schema.paths).includes("deleted") &&
783
+ model.schema.paths.deleted.instance === "Boolean")) return [3 /*break*/, 7];
784
+ doc.deleted = true;
785
+ return [4 /*yield*/, doc.save()];
786
+ case 6:
787
+ _a.sent();
788
+ return [3 /*break*/, 10];
789
+ case 7:
790
+ _a.trys.push([7, 9, , 10]);
791
+ return [4 /*yield*/, doc.deleteOne()];
792
+ case 8:
793
+ _a.sent();
794
+ return [3 /*break*/, 10];
795
+ case 9:
796
+ error_15 = _a.sent();
797
+ throw new errors_1.APIError({
798
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_15),
799
+ error: error_15,
800
+ status: 400,
801
+ title: error_15.message,
802
+ });
803
+ case 10:
804
+ if (!options.postDelete) return [3 /*break*/, 14];
805
+ _a.label = 11;
806
+ case 11:
807
+ _a.trys.push([11, 13, , 14]);
808
+ return [4 /*yield*/, options.postDelete(req, doc)];
809
+ case 12:
810
+ _a.sent();
811
+ return [3 /*break*/, 14];
812
+ case 13:
813
+ error_16 = _a.sent();
814
+ throw new errors_1.APIError({
815
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_16),
816
+ error: error_16,
817
+ status: 400,
818
+ title: "postDelete hook error: ".concat(error_16.message),
819
+ });
820
+ case 14: return [2 /*return*/, res.status(204).json({})];
821
+ }
822
+ });
823
+ }); }));
824
+ function arrayOperation(req, res, operation) {
825
+ return __awaiter(this, void 0, void 0, function () {
826
+ var model, doc, prevDoc, field, array, index, body, error_17, error_18, error_19;
827
+ var _a;
828
+ var _b, _c;
829
+ return __generator(this, function (_d) {
830
+ switch (_d.label) {
831
+ case 0:
832
+ model = getModel(baseModel, req.body, options);
833
+ return [4 /*yield*/, (0, permissions_1.checkPermissions)("update", options.permissions.update, req.user)];
834
+ case 1:
835
+ if (!(_d.sent())) {
836
+ throw new errors_1.APIError({
837
+ status: 405,
838
+ title: "Access to PATCH on ".concat(model.modelName, " denied for ").concat((_b = req.user) === null || _b === void 0 ? void 0 : _b.id),
839
+ });
840
+ }
841
+ return [4 /*yield*/, model.findById(req.params.id)];
842
+ case 2:
843
+ doc = _d.sent();
844
+ prevDoc = (0, cloneDeep_1.default)(doc);
845
+ // We fail here because we might fetch the document without the __t but we'd be missing all the
846
+ // hooks.
847
+ if (!doc || (doc.__t && !req.body.__t)) {
848
+ throw new errors_1.APIError({
849
+ status: 404,
850
+ title: "Could not find document to PATCH: ".concat(req.params.id),
851
+ });
852
+ }
853
+ return [4 /*yield*/, (0, permissions_1.checkPermissions)("update", options.permissions.update, req.user, doc)];
854
+ case 3:
855
+ if (!(_d.sent())) {
856
+ throw new errors_1.APIError({
857
+ status: 403,
858
+ title: "Patch not allowed for user ".concat((_c = req.user) === null || _c === void 0 ? void 0 : _c.id, " on doc ").concat(doc._id),
859
+ });
860
+ }
861
+ // We apply the operation *before* the hooks. As far as the callers are concerned, this should
862
+ // be like PATCHing the field and replacing the whole thing.
863
+ if (operation !== "DELETE" && req.body[req.params.field] === undefined) {
864
+ throw new errors_1.APIError({
865
+ status: 400,
866
+ title: "Malformed body, array operations should have a single, top level key, got: ".concat(Object.keys(req.body).join(",")),
867
+ });
868
+ }
869
+ field = req.params.field;
870
+ array = __spreadArray([], __read(doc[field]), false);
871
+ if (operation === "POST") {
872
+ array.push(req.body[field]);
873
+ }
874
+ else if (operation === "PATCH" || operation === "DELETE") {
875
+ index = void 0;
876
+ if ((0, utils_1.isValidObjectId)(req.params.itemId)) {
877
+ index = array.findIndex(function (x) { return x.id === req.params.itemId; });
878
+ }
879
+ else {
880
+ index = array.findIndex(function (x) { return x === req.params.itemId; });
881
+ }
882
+ if (index === -1) {
883
+ throw new errors_1.APIError({
884
+ status: 404,
885
+ title: "Could not find ".concat(field, "/").concat(req.params.itemId),
886
+ });
887
+ }
888
+ // For PATCHing an item by ID, we need to merge the objects so we don't override the _id or
889
+ // other parts of the subdocument.
890
+ if (operation === "PATCH" && (0, utils_1.isValidObjectId)(req.params.itemId)) {
891
+ Object.assign(array[index], req.body[field]);
892
+ }
893
+ else if (operation === "PATCH") {
894
+ // For PATCHing a string array, we can replace the whole object.
895
+ array[index] = req.body[field];
896
+ }
897
+ else {
898
+ array.splice(index, 1);
899
+ }
900
+ }
901
+ else {
902
+ throw new errors_1.APIError({
903
+ status: 400,
904
+ title: "Invalid array operation: ".concat(operation),
905
+ });
906
+ }
907
+ body = (_a = {}, _a[field] = array, _a);
908
+ try {
909
+ body = (0, transformers_1.transform)(options, body, "update", req.user);
910
+ }
911
+ catch (error) {
912
+ throw new errors_1.APIError({
913
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error),
914
+ error: error,
915
+ status: 403,
916
+ title: error.message,
917
+ });
918
+ }
919
+ if (!options.preUpdate) return [3 /*break*/, 8];
920
+ _d.label = 4;
921
+ case 4:
922
+ _d.trys.push([4, 6, , 7]);
923
+ return [4 /*yield*/, options.preUpdate(body, req)];
924
+ case 5:
925
+ body = _d.sent();
926
+ return [3 /*break*/, 7];
927
+ case 6:
928
+ error_17 = _d.sent();
929
+ throw new errors_1.APIError({
930
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_17),
931
+ error: error_17,
932
+ status: 400,
933
+ title: "preUpdate hook error on ".concat(req.params.id, ": ").concat(error_17.message),
934
+ });
935
+ case 7:
936
+ if (body === undefined) {
937
+ throw new errors_1.APIError({
938
+ detail: "A body must be returned from preUpdate",
939
+ status: 403,
940
+ title: "Update not allowed",
941
+ });
942
+ }
943
+ if (body === null) {
944
+ throw new errors_1.APIError({
945
+ detail: "preUpdate hook on ".concat(req.params.id, " returned null"),
946
+ status: 403,
947
+ title: "Update not allowed",
948
+ });
949
+ }
950
+ _d.label = 8;
951
+ case 8:
952
+ _d.trys.push([8, 10, , 11]);
953
+ Object.assign(doc, body);
954
+ return [4 /*yield*/, doc.save()];
955
+ case 9:
956
+ _d.sent();
957
+ return [3 /*break*/, 11];
958
+ case 10:
959
+ error_18 = _d.sent();
960
+ throw new errors_1.APIError({
961
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_18),
962
+ error: error_18,
963
+ status: 400,
964
+ title: "PATCH Pre Update error on ".concat(req.params.id, ": ").concat(error_18.message),
965
+ });
966
+ case 11:
967
+ if (!options.postUpdate) return [3 /*break*/, 15];
968
+ _d.label = 12;
969
+ case 12:
970
+ _d.trys.push([12, 14, , 15]);
971
+ return [4 /*yield*/, options.postUpdate(doc, body, req, prevDoc)];
972
+ case 13:
973
+ _d.sent();
974
+ return [3 /*break*/, 15];
975
+ case 14:
976
+ error_19 = _d.sent();
977
+ throw new errors_1.APIError({
978
+ disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_19),
979
+ error: error_19,
980
+ status: 400,
981
+ title: "PATCH Post Update error on ".concat(req.params.id, ": ").concat(error_19.message),
982
+ });
983
+ case 15: return [2 /*return*/, res.json({ data: (0, transformers_1.serialize)(req, options, doc) })];
984
+ }
985
+ });
986
+ });
987
+ }
988
+ function arrayPost(req, res) {
989
+ return __awaiter(this, void 0, void 0, function () {
990
+ return __generator(this, function (_a) {
991
+ return [2 /*return*/, arrayOperation(req, res, "POST")];
992
+ });
993
+ });
994
+ }
995
+ function arrayPatch(req, res) {
996
+ return __awaiter(this, void 0, void 0, function () {
997
+ return __generator(this, function (_a) {
998
+ return [2 /*return*/, arrayOperation(req, res, "PATCH")];
999
+ });
1000
+ });
1001
+ }
1002
+ function arrayDelete(req, res) {
1003
+ return __awaiter(this, void 0, void 0, function () {
1004
+ return __generator(this, function (_a) {
1005
+ return [2 /*return*/, arrayOperation(req, res, "DELETE")];
1006
+ });
1007
+ });
1008
+ }
1009
+ // Set up routes for managing array fields. Check if there any array fields to add this for.
1010
+ if (Object.values(baseModel.schema.paths).find(function (config) { return config.instance === "Array"; })) {
1011
+ router.post("/:id/:field", (0, auth_1.authenticateMiddleware)(options.allowAnonymous), (0, exports.asyncHandler)(arrayPost));
1012
+ router.patch("/:id/:field/:itemId", (0, auth_1.authenticateMiddleware)(options.allowAnonymous), (0, exports.asyncHandler)(arrayPatch));
1013
+ router.delete("/:id/:field/:itemId", (0, auth_1.authenticateMiddleware)(options.allowAnonymous), (0, exports.asyncHandler)(arrayDelete));
1014
+ }
1015
+ router.use(errors_1.apiErrorMiddleware);
1016
+ return router;
1017
+ }
1018
+ // Since express doesn't handle async routes well, wrap them with this function.
1019
+ var asyncHandler = function (fn) { return function (req, res, next) {
1020
+ return Promise.resolve(fn(req, res, next)).catch(next);
1021
+ }; };
1022
+ exports.asyncHandler = asyncHandler;
1023
+ // For backwards compatibility with the old names.
1024
+ exports.gooseRestRouter = modelRouter;