@terreno/api 0.0.4 → 0.0.11-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +107 -0
- package/bunfig.toml +3 -2
- package/dist/api.d.ts +6 -17
- package/dist/api.js +44 -68
- package/dist/api.test.js +2051 -166
- package/dist/expressServer.d.ts +2 -2
- package/dist/openApi.d.ts +7 -7
- package/dist/openApiBuilder.d.ts +3 -3
- package/dist/permissions.d.ts +2 -2
- package/dist/permissions.js +17 -25
- package/dist/transformers.d.ts +4 -4
- package/dist/utils.test.js +169 -7
- package/package.json +3 -2
- package/src/api.test.ts +1736 -142
- package/src/api.ts +22 -64
- package/src/example.ts +2 -2
- package/src/expressServer.ts +2 -2
- package/src/openApi.test.ts +4 -4
- package/src/openApi.ts +7 -7
- package/src/openApiBuilder.test.ts +2 -2
- package/src/openApiBuilder.ts +4 -4
- package/src/permissions.ts +4 -14
- package/src/transformers.ts +4 -4
- package/src/utils.test.ts +189 -9
package/src/api.ts
CHANGED
|
@@ -70,7 +70,7 @@ export type RESTMethod = "list" | "create" | "read" | "update" | "delete";
|
|
|
70
70
|
* This is the main configuration.
|
|
71
71
|
* @param T - the base document type. This should not include Mongoose models, just the types of the object.
|
|
72
72
|
*/
|
|
73
|
-
export interface
|
|
73
|
+
export interface ModelRouterOptions<T> {
|
|
74
74
|
/**
|
|
75
75
|
* A group of method-level (create/read/update/delete/list) permissions.
|
|
76
76
|
* Determine if the user can perform the operation at all, and for read/update/delete methods,
|
|
@@ -239,18 +239,8 @@ export interface modelRouterOptions<T> {
|
|
|
239
239
|
value: (Document<any, any, any> & T) | (Document<any, any, any> & T)[],
|
|
240
240
|
method: "list" | "create" | "read" | "update" | "delete",
|
|
241
241
|
request: express.Request,
|
|
242
|
-
options:
|
|
242
|
+
options: ModelRouterOptions<T>
|
|
243
243
|
) => Promise<JSONValue>;
|
|
244
|
-
/**
|
|
245
|
-
* The discriminatorKey that you passed when creating the Mongoose models. Defaults to __t. See:
|
|
246
|
-
* https://mongoosejs.com/docs/discriminators.html If this key is provided,
|
|
247
|
-
* you must provide the same key as part of the top level of the body when making performing
|
|
248
|
-
* update or delete operations on this model.
|
|
249
|
-
* \{discriminatorKey: "__t"\}
|
|
250
|
-
*
|
|
251
|
-
* PATCH \{__t: "SuperUser", name: "Foo"\} // __t is required or there will be a 404 error.
|
|
252
|
-
*/
|
|
253
|
-
discriminatorKey?: string;
|
|
254
244
|
/**
|
|
255
245
|
* The OpenAPI generator for this server. This is used to generate the OpenAPI documentation.
|
|
256
246
|
*/
|
|
@@ -275,23 +265,6 @@ export interface modelRouterOptions<T> {
|
|
|
275
265
|
openApiExtraModelProperties?: any;
|
|
276
266
|
}
|
|
277
267
|
|
|
278
|
-
// A function to decide which model to use. If no discriminators are provided,
|
|
279
|
-
// just returns the base model. If
|
|
280
|
-
export function getModel(baseModel: Model<any>, body?: any, options?: modelRouterOptions<any>) {
|
|
281
|
-
const discriminatorKey = options?.discriminatorKey ?? "__t";
|
|
282
|
-
const modelName = body?.[discriminatorKey];
|
|
283
|
-
if (!modelName) {
|
|
284
|
-
return baseModel;
|
|
285
|
-
}
|
|
286
|
-
const model = baseModel.discriminators?.[modelName];
|
|
287
|
-
if (!model) {
|
|
288
|
-
throw new Error(
|
|
289
|
-
`Could not find discriminator model for key ${modelName}, baseModel: ${baseModel}`
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
return model;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
268
|
// Ensures query params are allowed. Also checks nested query params when using $and/$or.
|
|
296
269
|
function checkQueryParamAllowed(
|
|
297
270
|
queryParam: string,
|
|
@@ -337,15 +310,12 @@ function checkQueryParamAllowed(
|
|
|
337
310
|
// }
|
|
338
311
|
|
|
339
312
|
/**
|
|
340
|
-
* Create a set of CRUD routes given a Mongoose model
|
|
313
|
+
* Create a set of CRUD routes given a Mongoose model and configuration options.
|
|
341
314
|
*
|
|
342
|
-
* @param
|
|
315
|
+
* @param model A Mongoose Model
|
|
343
316
|
* @param options Options for configuring the REST API, such as permissions, transformers, and hooks.
|
|
344
317
|
*/
|
|
345
|
-
export function modelRouter<T>(
|
|
346
|
-
baseModel: Model<T>,
|
|
347
|
-
options: modelRouterOptions<T>
|
|
348
|
-
): express.Router {
|
|
318
|
+
export function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>): express.Router {
|
|
349
319
|
const router = express.Router();
|
|
350
320
|
|
|
351
321
|
// Do before the other router options so endpoints take priority.
|
|
@@ -359,12 +329,10 @@ export function modelRouter<T>(
|
|
|
359
329
|
"/",
|
|
360
330
|
[
|
|
361
331
|
authenticateMiddleware(options.allowAnonymous),
|
|
362
|
-
createOpenApiMiddleware(
|
|
363
|
-
permissionMiddleware(
|
|
332
|
+
createOpenApiMiddleware(model, options),
|
|
333
|
+
permissionMiddleware(model, options),
|
|
364
334
|
],
|
|
365
335
|
asyncHandler(async (req: Request, res: Response) => {
|
|
366
|
-
const model = getModel(baseModel, req.body?.__t, options);
|
|
367
|
-
|
|
368
336
|
let body: Partial<T> | (Partial<T> | undefined)[] | null | undefined;
|
|
369
337
|
try {
|
|
370
338
|
body = transform<T>(options, req.body, "create", req.user);
|
|
@@ -426,7 +394,7 @@ export function modelRouter<T>(
|
|
|
426
394
|
|
|
427
395
|
if (options.populatePaths) {
|
|
428
396
|
try {
|
|
429
|
-
let populateQuery = model.findById(data._id);
|
|
397
|
+
let populateQuery: any = model.findById(data._id);
|
|
430
398
|
populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
|
|
431
399
|
data = await populateQuery.exec();
|
|
432
400
|
} catch (error: any) {
|
|
@@ -469,13 +437,10 @@ export function modelRouter<T>(
|
|
|
469
437
|
"/",
|
|
470
438
|
[
|
|
471
439
|
authenticateMiddleware(options.allowAnonymous),
|
|
472
|
-
permissionMiddleware(
|
|
473
|
-
listOpenApiMiddleware(
|
|
440
|
+
permissionMiddleware(model, options),
|
|
441
|
+
listOpenApiMiddleware(model, options),
|
|
474
442
|
],
|
|
475
443
|
asyncHandler(async (req: Request, res: Response) => {
|
|
476
|
-
// For pure read queries, Mongoose will return the correct data with just the base model.
|
|
477
|
-
const model = baseModel;
|
|
478
|
-
|
|
479
444
|
let query: any = {};
|
|
480
445
|
for (const queryParam of Object.keys(options.defaultQueryParams ?? [])) {
|
|
481
446
|
query[queryParam] = options.defaultQueryParams?.[queryParam];
|
|
@@ -624,8 +589,8 @@ export function modelRouter<T>(
|
|
|
624
589
|
"/:id",
|
|
625
590
|
[
|
|
626
591
|
authenticateMiddleware(options.allowAnonymous),
|
|
627
|
-
getOpenApiMiddleware(
|
|
628
|
-
permissionMiddleware(
|
|
592
|
+
getOpenApiMiddleware(model, options),
|
|
593
|
+
permissionMiddleware(model, options),
|
|
629
594
|
],
|
|
630
595
|
asyncHandler(async (req: Request, res: Response) => {
|
|
631
596
|
const data: mongoose.Document & T = (req as any).obj;
|
|
@@ -658,12 +623,10 @@ export function modelRouter<T>(
|
|
|
658
623
|
"/:id",
|
|
659
624
|
[
|
|
660
625
|
authenticateMiddleware(options.allowAnonymous),
|
|
661
|
-
patchOpenApiMiddleware(
|
|
662
|
-
permissionMiddleware(
|
|
626
|
+
patchOpenApiMiddleware(model, options),
|
|
627
|
+
permissionMiddleware(model, options),
|
|
663
628
|
],
|
|
664
629
|
asyncHandler(async (req: Request, res: Response) => {
|
|
665
|
-
const model = getModel(baseModel, req.body, options);
|
|
666
|
-
|
|
667
630
|
let doc: mongoose.Document & T = (req as any).obj;
|
|
668
631
|
|
|
669
632
|
let body;
|
|
@@ -731,7 +694,7 @@ export function modelRouter<T>(
|
|
|
731
694
|
}
|
|
732
695
|
|
|
733
696
|
if (options.populatePaths) {
|
|
734
|
-
let populateQuery = model.findById(doc._id);
|
|
697
|
+
let populateQuery: any = model.findById(doc._id);
|
|
735
698
|
populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
|
|
736
699
|
doc = await populateQuery.exec();
|
|
737
700
|
}
|
|
@@ -766,12 +729,10 @@ export function modelRouter<T>(
|
|
|
766
729
|
"/:id",
|
|
767
730
|
[
|
|
768
731
|
authenticateMiddleware(options.allowAnonymous),
|
|
769
|
-
deleteOpenApiMiddleware(
|
|
770
|
-
permissionMiddleware(
|
|
732
|
+
deleteOpenApiMiddleware(model, options),
|
|
733
|
+
permissionMiddleware(model, options),
|
|
771
734
|
],
|
|
772
735
|
asyncHandler(async (req: Request, res: Response) => {
|
|
773
|
-
const model = getModel(baseModel, req.body, options);
|
|
774
|
-
|
|
775
736
|
const doc: mongoose.Document & T & {deleted?: boolean} = (req as any).obj;
|
|
776
737
|
|
|
777
738
|
if (options.preDelete) {
|
|
@@ -849,7 +810,6 @@ export function modelRouter<T>(
|
|
|
849
810
|
operation: "POST" | "PATCH" | "DELETE"
|
|
850
811
|
) {
|
|
851
812
|
// TODO Combine array operations and .patch(), as they are very similar.
|
|
852
|
-
const model = getModel(baseModel, req.body, options);
|
|
853
813
|
|
|
854
814
|
if (!(await checkPermissions("update", options.permissions.update, req.user))) {
|
|
855
815
|
throw new APIError({
|
|
@@ -861,9 +821,7 @@ export function modelRouter<T>(
|
|
|
861
821
|
const doc = await model.findById(req.params.id);
|
|
862
822
|
// Make a copy for passing pre-saved values to hooks.
|
|
863
823
|
const prevDoc = cloneDeep(doc);
|
|
864
|
-
|
|
865
|
-
// hooks.
|
|
866
|
-
if (!doc || (doc.__t && !req.body.__t)) {
|
|
824
|
+
if (!doc) {
|
|
867
825
|
throw new APIError({
|
|
868
826
|
status: 404,
|
|
869
827
|
title: `Could not find document to PATCH: ${req.params.id}`,
|
|
@@ -979,7 +937,7 @@ export function modelRouter<T>(
|
|
|
979
937
|
|
|
980
938
|
if (options.postUpdate) {
|
|
981
939
|
try {
|
|
982
|
-
await options.postUpdate(doc, body, req, prevDoc);
|
|
940
|
+
await options.postUpdate(doc as any, body, req, prevDoc as any);
|
|
983
941
|
} catch (error: any) {
|
|
984
942
|
throw new APIError({
|
|
985
943
|
disableExternalErrorTracking: getDisableExternalErrorTracking(error),
|
|
@@ -989,7 +947,7 @@ export function modelRouter<T>(
|
|
|
989
947
|
});
|
|
990
948
|
}
|
|
991
949
|
}
|
|
992
|
-
return res.json({data: serialize<T>(req, options, doc)});
|
|
950
|
+
return res.json({data: serialize<T>(req, options, doc as any)});
|
|
993
951
|
}
|
|
994
952
|
|
|
995
953
|
async function arrayPost(req: Request, res: Response) {
|
|
@@ -1004,7 +962,7 @@ export function modelRouter<T>(
|
|
|
1004
962
|
return arrayOperation(req, res, "DELETE");
|
|
1005
963
|
}
|
|
1006
964
|
// Set up routes for managing array fields. Check if there any array fields to add this for.
|
|
1007
|
-
if (Object.values(
|
|
965
|
+
if (Object.values(model.schema.paths).find((config: any) => config.instance === "Array")) {
|
|
1008
966
|
router.post(
|
|
1009
967
|
"/:id/:field",
|
|
1010
968
|
authenticateMiddleware(options.allowAnonymous),
|
|
@@ -1033,4 +991,4 @@ export const asyncHandler = (fn: any) => (req: Request, res: Response, next: Nex
|
|
|
1033
991
|
|
|
1034
992
|
// For backwards compatibility with the old names.
|
|
1035
993
|
export const gooseRestRouter = modelRouter;
|
|
1036
|
-
export type GooseRESTOptions<T> =
|
|
994
|
+
export type GooseRESTOptions<T> = ModelRouterOptions<T>;
|
package/src/example.ts
CHANGED
|
@@ -2,7 +2,7 @@ import express from "express";
|
|
|
2
2
|
import mongoose, {model, Schema} from "mongoose";
|
|
3
3
|
import passportLocalMongoose from "passport-local-mongoose";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {type ModelRouterOptions, modelRouter} from "./api";
|
|
6
6
|
import {addAuthRoutes, setupAuth} from "./auth";
|
|
7
7
|
import {setupServer} from "./expressServer";
|
|
8
8
|
import {logger} from "./logger";
|
|
@@ -68,7 +68,7 @@ function getBaseServer() {
|
|
|
68
68
|
setupAuth(app, UserModel as any);
|
|
69
69
|
addAuthRoutes(app, UserModel as any);
|
|
70
70
|
|
|
71
|
-
function addRoutes(router: express.Router, options?: Partial<
|
|
71
|
+
function addRoutes(router: express.Router, options?: Partial<ModelRouterOptions<any>>): void {
|
|
72
72
|
router.use(
|
|
73
73
|
"/food",
|
|
74
74
|
modelRouter(FoodModel, {
|
package/src/expressServer.ts
CHANGED
|
@@ -9,7 +9,7 @@ import onFinished from "on-finished";
|
|
|
9
9
|
import passport from "passport";
|
|
10
10
|
import qs from "qs";
|
|
11
11
|
|
|
12
|
-
import type {
|
|
12
|
+
import type {ModelRouterOptions} from "./api";
|
|
13
13
|
import {addAuthRoutes, addMeRoutes, setupAuth, type UserModel as UserMongooseModel} from "./auth";
|
|
14
14
|
import {apiErrorMiddleware, apiUnauthorizedMiddleware} from "./errors";
|
|
15
15
|
import {type LoggingOptions, logger, setupLogging} from "./logger";
|
|
@@ -41,7 +41,7 @@ export function setupEnvironment(): void {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export type AddRoutes = (router: Router, options?: Partial<
|
|
44
|
+
export type AddRoutes = (router: Router, options?: Partial<ModelRouterOptions<any>>) => void;
|
|
45
45
|
|
|
46
46
|
const logRequestsFinished = (req: any, res: any, startTime: bigint) => {
|
|
47
47
|
const options = (res.locals.loggingOptions ?? {}) as LoggingOptions;
|
package/src/openApi.test.ts
CHANGED
|
@@ -4,13 +4,13 @@ import type {Router} from "express";
|
|
|
4
4
|
import supertest from "supertest";
|
|
5
5
|
import type TestAgent from "supertest/lib/agent";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {type ModelRouterOptions, modelRouter} from "./api";
|
|
8
8
|
import {addAuthRoutes, setupAuth} from "./auth";
|
|
9
9
|
import {setupServer} from "./expressServer";
|
|
10
10
|
import {Permissions} from "./permissions";
|
|
11
11
|
import {FoodModel, setupDb, UserModel} from "./tests";
|
|
12
12
|
|
|
13
|
-
function getMessageSummaryOpenApiMiddleware(options: Partial<
|
|
13
|
+
function getMessageSummaryOpenApiMiddleware(options: Partial<ModelRouterOptions<any>>): any {
|
|
14
14
|
return options.openApi.path({
|
|
15
15
|
parameters: [
|
|
16
16
|
{
|
|
@@ -42,7 +42,7 @@ function getMessageSummaryOpenApiMiddleware(options: Partial<modelRouterOptions<
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function addRoutes(router: Router, options?: Partial<
|
|
45
|
+
function addRoutes(router: Router, options?: Partial<ModelRouterOptions<any>>): void {
|
|
46
46
|
router.use(
|
|
47
47
|
"/food",
|
|
48
48
|
modelRouter(FoodModel as any, {
|
|
@@ -176,7 +176,7 @@ describe("openApi", () => {
|
|
|
176
176
|
});
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
-
function addRoutesPopulate(router: Router, options?: Partial<
|
|
179
|
+
function addRoutesPopulate(router: Router, options?: Partial<ModelRouterOptions<any>>): void {
|
|
180
180
|
options?.openApi.component("schemas", "LimitedUser", {
|
|
181
181
|
properties: {
|
|
182
182
|
email: {
|
package/src/openApi.ts
CHANGED
|
@@ -3,7 +3,7 @@ import merge from "lodash/merge";
|
|
|
3
3
|
import type {Model} from "mongoose";
|
|
4
4
|
import m2s from "mongoose-to-swagger";
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type {ModelRouterOptions} from "./api";
|
|
7
7
|
import {logger} from "./logger";
|
|
8
8
|
import {getOpenApiSpecForModel} from "./populate";
|
|
9
9
|
|
|
@@ -112,7 +112,7 @@ function createAPIErrorComponent(openApi: any) {
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
export function getOpenApiMiddleware<T>(model: Model<T>, options: Partial<
|
|
115
|
+
export function getOpenApiMiddleware<T>(model: Model<T>, options: Partial<ModelRouterOptions<T>>) {
|
|
116
116
|
createAPIErrorComponent(options.openApi);
|
|
117
117
|
if (!options.openApi?.path) {
|
|
118
118
|
// Just log this once rather than for each middleware.
|
|
@@ -154,7 +154,7 @@ export function getOpenApiMiddleware<T>(model: Model<T>, options: Partial<modelR
|
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
export function listOpenApiMiddleware<T>(model: Model<T>, options: Partial<
|
|
157
|
+
export function listOpenApiMiddleware<T>(model: Model<T>, options: Partial<ModelRouterOptions<T>>) {
|
|
158
158
|
if (!options.openApi?.path) {
|
|
159
159
|
return noop;
|
|
160
160
|
}
|
|
@@ -319,7 +319,7 @@ export function listOpenApiMiddleware<T>(model: Model<T>, options: Partial<model
|
|
|
319
319
|
|
|
320
320
|
export function createOpenApiMiddleware<T>(
|
|
321
321
|
model: Model<T>,
|
|
322
|
-
options: Partial<
|
|
322
|
+
options: Partial<ModelRouterOptions<T>>
|
|
323
323
|
) {
|
|
324
324
|
if (!options.openApi?.path) {
|
|
325
325
|
return noop;
|
|
@@ -371,7 +371,7 @@ export function createOpenApiMiddleware<T>(
|
|
|
371
371
|
|
|
372
372
|
export function patchOpenApiMiddleware<T>(
|
|
373
373
|
model: Model<T>,
|
|
374
|
-
options: Partial<
|
|
374
|
+
options: Partial<ModelRouterOptions<T>>
|
|
375
375
|
) {
|
|
376
376
|
if (!options.openApi?.path) {
|
|
377
377
|
return noop;
|
|
@@ -423,7 +423,7 @@ export function patchOpenApiMiddleware<T>(
|
|
|
423
423
|
|
|
424
424
|
export function deleteOpenApiMiddleware<T>(
|
|
425
425
|
model: Model<T>,
|
|
426
|
-
options: Partial<
|
|
426
|
+
options: Partial<ModelRouterOptions<T>>
|
|
427
427
|
) {
|
|
428
428
|
if (!options.openApi?.path) {
|
|
429
429
|
return noop;
|
|
@@ -452,7 +452,7 @@ export function deleteOpenApiMiddleware<T>(
|
|
|
452
452
|
// This is a generic OpenAPI wrapper for a read that returns any object described by `properties`.
|
|
453
453
|
// Useful for endpoints that don't directly map to a model.
|
|
454
454
|
export function readOpenApiMiddleware<T>(
|
|
455
|
-
options: Partial<
|
|
455
|
+
options: Partial<ModelRouterOptions<T>>,
|
|
456
456
|
properties: any,
|
|
457
457
|
required: string[],
|
|
458
458
|
queryParameters: any
|
|
@@ -4,14 +4,14 @@ import type {Router} from "express";
|
|
|
4
4
|
import supertest from "supertest";
|
|
5
5
|
import type TestAgent from "supertest/lib/agent";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {type ModelRouterOptions, modelRouter} from "./api";
|
|
8
8
|
import {addAuthRoutes, setupAuth} from "./auth";
|
|
9
9
|
import {setupServer} from "./expressServer";
|
|
10
10
|
import {createOpenApiBuilder, OpenApiMiddlewareBuilder} from "./openApiBuilder";
|
|
11
11
|
import {Permissions} from "./permissions";
|
|
12
12
|
import {FoodModel, UserModel} from "./tests";
|
|
13
13
|
|
|
14
|
-
function addRoutesWithBuilder(router: Router, options?: Partial<
|
|
14
|
+
function addRoutesWithBuilder(router: Router, options?: Partial<ModelRouterOptions<any>>): void {
|
|
15
15
|
// Add a custom endpoint using the OpenApiMiddlewareBuilder
|
|
16
16
|
const statsMiddleware = createOpenApiBuilder(options ?? {})
|
|
17
17
|
.withTags(["Stats"])
|
package/src/openApiBuilder.ts
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import merge from "lodash/merge";
|
|
33
33
|
|
|
34
|
-
import type {
|
|
34
|
+
import type {ModelRouterOptions} from "./api";
|
|
35
35
|
import {logger} from "./logger";
|
|
36
36
|
import {defaultOpenApiErrorResponses} from "./openApi";
|
|
37
37
|
|
|
@@ -250,7 +250,7 @@ interface OpenApiConfig {
|
|
|
250
250
|
*/
|
|
251
251
|
export class OpenApiMiddlewareBuilder {
|
|
252
252
|
/** Router options containing OpenAPI configuration */
|
|
253
|
-
private options: Partial<
|
|
253
|
+
private options: Partial<ModelRouterOptions<any>>;
|
|
254
254
|
|
|
255
255
|
/** Accumulated OpenAPI configuration from builder methods */
|
|
256
256
|
private config: OpenApiConfig;
|
|
@@ -260,7 +260,7 @@ export class OpenApiMiddlewareBuilder {
|
|
|
260
260
|
*
|
|
261
261
|
* @param options - Router options containing the OpenAPI path configuration
|
|
262
262
|
*/
|
|
263
|
-
constructor(options: Partial<
|
|
263
|
+
constructor(options: Partial<ModelRouterOptions<any>>) {
|
|
264
264
|
this.options = options;
|
|
265
265
|
this.config = {
|
|
266
266
|
responses: {},
|
|
@@ -630,7 +630,7 @@ export class OpenApiMiddlewareBuilder {
|
|
|
630
630
|
* ```
|
|
631
631
|
*/
|
|
632
632
|
export function createOpenApiBuilder(
|
|
633
|
-
options: Partial<
|
|
633
|
+
options: Partial<ModelRouterOptions<any>>
|
|
634
634
|
): OpenApiMiddlewareBuilder {
|
|
635
635
|
return new OpenApiMiddlewareBuilder(options);
|
|
636
636
|
}
|
package/src/permissions.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type express from "express";
|
|
|
4
4
|
import type {NextFunction} from "express";
|
|
5
5
|
import mongoose, {type Model} from "mongoose";
|
|
6
6
|
|
|
7
|
-
import {addPopulateToQuery,
|
|
7
|
+
import {addPopulateToQuery, type ModelRouterOptions, type RESTMethod} from "./api";
|
|
8
8
|
import type {User} from "./auth";
|
|
9
9
|
import {APIError} from "./errors";
|
|
10
10
|
import {logger} from "./logger";
|
|
@@ -101,8 +101,8 @@ export async function checkPermissions<T>(
|
|
|
101
101
|
// finds the relevant object, checks the permissions, and attaches the object to the request as
|
|
102
102
|
// req.obj.
|
|
103
103
|
export function permissionMiddleware<T>(
|
|
104
|
-
|
|
105
|
-
options: Pick<
|
|
104
|
+
model: Model<T>,
|
|
105
|
+
options: Pick<ModelRouterOptions<T>, "permissions" | "populatePaths">
|
|
106
106
|
) {
|
|
107
107
|
return async (req: express.Request, _res: express.Response, next: NextFunction) => {
|
|
108
108
|
if (req.method === "OPTIONS") {
|
|
@@ -131,8 +131,6 @@ export function permissionMiddleware<T>(
|
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
const model = getModel(baseModel, req.body, options);
|
|
135
|
-
|
|
136
134
|
// All methods check for permissions.
|
|
137
135
|
if (!(await checkPermissions(method, options.permissions[method], req.user))) {
|
|
138
136
|
throw new APIError({
|
|
@@ -159,15 +157,7 @@ export function permissionMiddleware<T>(
|
|
|
159
157
|
title: `GET failed on ${req.params.id}`,
|
|
160
158
|
});
|
|
161
159
|
}
|
|
162
|
-
if (!data
|
|
163
|
-
// For discriminated models, return 404 without checking hidden state
|
|
164
|
-
if (["update", "delete"].includes(method) && data?.__t && !req.body?.__t) {
|
|
165
|
-
throw new APIError({
|
|
166
|
-
status: 404,
|
|
167
|
-
title: `Document ${req.params.id} not found for model ${model.modelName}`,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
160
|
+
if (!data) {
|
|
171
161
|
// Check if document exists but is hidden. Completely skip plugins.
|
|
172
162
|
const hiddenDoc = await model.collection.findOne({
|
|
173
163
|
_id: new mongoose.Types.ObjectId(req.params.id),
|
package/src/transformers.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type express from "express";
|
|
2
2
|
import type {Document} from "mongoose";
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type {ModelRouterOptions} from "./api";
|
|
5
5
|
import type {User} from "./auth";
|
|
6
6
|
import {APIError} from "./errors";
|
|
7
7
|
import {logger} from "./logger";
|
|
@@ -88,7 +88,7 @@ export function AdminOwnerTransformer<T>(options: {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
export function transform<T>(
|
|
91
|
-
options:
|
|
91
|
+
options: ModelRouterOptions<T>,
|
|
92
92
|
data: Partial<T> | Partial<T>[],
|
|
93
93
|
method: "create" | "update",
|
|
94
94
|
user?: User
|
|
@@ -112,7 +112,7 @@ export function transform<T>(
|
|
|
112
112
|
|
|
113
113
|
export function serialize<T>(
|
|
114
114
|
req: express.Request,
|
|
115
|
-
options:
|
|
115
|
+
options: ModelRouterOptions<T>,
|
|
116
116
|
data: (Document<any, any, any> & T) | (Document<any, any, any> & T)[]
|
|
117
117
|
) {
|
|
118
118
|
const serializeFn = (serializeData: Document<any, any, any> & T, serializeUser?: User) => {
|
|
@@ -153,7 +153,7 @@ export async function defaultResponseHandler<T>(
|
|
|
153
153
|
doc: (Document<any, any, any> & T) | (Document<any, any, any> & T)[] | null,
|
|
154
154
|
method: "list" | "create" | "read" | "update",
|
|
155
155
|
request: express.Request,
|
|
156
|
-
options:
|
|
156
|
+
options: ModelRouterOptions<T>
|
|
157
157
|
) {
|
|
158
158
|
if (!doc) {
|
|
159
159
|
return null;
|