@decaf-ts/for-nest 0.11.2 → 0.12.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/README.md +274 -2
- package/dist/for-nest.cjs +1 -1
- package/dist/for-nest.cjs.map +1 -1
- package/dist/for-nest.js +1 -1
- package/dist/for-nest.js.map +1 -1
- package/lib/cjs/auth/AuthInterceptor.cjs +93 -0
- package/lib/cjs/auth/AuthInterceptor.cjs.map +1 -0
- package/lib/cjs/auth/DecafAuthHandler.cjs +37 -0
- package/lib/cjs/auth/DecafAuthHandler.cjs.map +1 -0
- package/lib/cjs/auth/DecafAuthModule.cjs +50 -0
- package/lib/cjs/auth/DecafAuthModule.cjs.map +1 -0
- package/lib/cjs/auth/constants.cjs +10 -0
- package/lib/cjs/auth/constants.cjs.map +1 -0
- package/lib/cjs/auth/decorators.cjs +28 -0
- package/lib/cjs/auth/decorators.cjs.map +1 -0
- package/lib/cjs/{ram → auth}/index.cjs +6 -2
- package/lib/cjs/auth/index.cjs.map +1 -0
- package/lib/cjs/bin/cli.cjs +1 -1
- package/lib/cjs/bin/cli.cjs.map +1 -1
- package/lib/cjs/cli-module.cjs +63 -6
- package/lib/cjs/cli-module.cjs.map +1 -1
- package/lib/cjs/constants.cjs +4 -5
- package/lib/cjs/constants.cjs.map +1 -1
- package/lib/cjs/controllers.cjs +15 -112
- package/lib/cjs/controllers.cjs.map +1 -1
- package/lib/cjs/core-module.cjs +7 -7
- package/lib/cjs/core-module.cjs.map +1 -1
- package/lib/cjs/decaf-model/DecafModelModule.cjs +27 -3
- package/lib/cjs/decaf-model/DecafModelModule.cjs.map +1 -1
- package/lib/cjs/decaf-model/FromModelController.cjs +618 -792
- package/lib/cjs/decaf-model/FromModelController.cjs.map +1 -1
- package/lib/cjs/decaf-model/decorators/ApiOperationFromModel.cjs +52 -15
- package/lib/cjs/decaf-model/decorators/ApiOperationFromModel.cjs.map +1 -1
- package/lib/cjs/decaf-model/decorators/ApiParamsFromModel.cjs +1 -1
- package/lib/cjs/decaf-model/decorators/ApiParamsFromModel.cjs.map +1 -1
- package/lib/cjs/decaf-model/decorators/DecafBody.cjs +1 -1
- package/lib/cjs/decaf-model/decorators/DecafBody.cjs.map +1 -1
- package/lib/cjs/decaf-model/decorators/DecafParams.cjs +1 -1
- package/lib/cjs/decaf-model/decorators/DecafParams.cjs.map +1 -1
- package/lib/cjs/decaf-model/decorators/controller-config.cjs +29 -0
- package/lib/cjs/decaf-model/decorators/controller-config.cjs.map +1 -0
- package/lib/cjs/decaf-model/decorators/decorators.cjs +4 -44
- package/lib/cjs/decaf-model/decorators/decorators.cjs.map +1 -1
- package/lib/cjs/decaf-model/decorators/expose.cjs +13 -0
- package/lib/cjs/decaf-model/decorators/expose.cjs.map +1 -0
- package/lib/cjs/decaf-model/decorators/index.cjs +2 -1
- package/lib/cjs/decaf-model/decorators/index.cjs.map +1 -1
- package/lib/cjs/decaf-model/decorators/types.cjs +1 -1
- package/lib/cjs/decaf-model/decorators/types.cjs.map +1 -1
- package/lib/cjs/decaf-model/decorators/utils.cjs +3 -2
- package/lib/cjs/decaf-model/decorators/utils.cjs.map +1 -1
- package/lib/cjs/decaf-model/index.cjs +27 -18
- package/lib/cjs/decaf-model/index.cjs.map +1 -1
- package/lib/cjs/decaf-model/types.cjs +1 -1
- package/lib/cjs/decaf-model/types.cjs.map +1 -1
- package/lib/cjs/decaf-model/utils.cjs +10 -4
- package/lib/cjs/decaf-model/utils.cjs.map +1 -1
- package/lib/cjs/decoration.cjs +1 -1
- package/lib/cjs/decoration.cjs.map +1 -1
- package/lib/cjs/decorators.cjs +93 -0
- package/lib/cjs/decorators.cjs.map +1 -0
- package/lib/cjs/events-module/DecafStreamModule.cjs +1 -1
- package/lib/cjs/events-module/DecafStreamModule.cjs.map +1 -1
- package/lib/cjs/events-module/EventsController.cjs +1 -1
- package/lib/cjs/events-module/EventsController.cjs.map +1 -1
- package/lib/cjs/events-module/constant.cjs +1 -1
- package/lib/cjs/events-module/constant.cjs.map +1 -1
- package/lib/cjs/events-module/index.cjs +1 -1
- package/lib/cjs/events-module/index.cjs.map +1 -1
- package/lib/cjs/events-module/utils.cjs +1 -1
- package/lib/cjs/events-module/utils.cjs.map +1 -1
- package/lib/cjs/factory/NestBootstraper.cjs +11 -4
- package/lib/cjs/factory/NestBootstraper.cjs.map +1 -1
- package/lib/cjs/factory/errors/cors.cjs +1 -1
- package/lib/cjs/factory/errors/cors.cjs.map +1 -1
- package/lib/cjs/factory/errors/index.cjs +1 -1
- package/lib/cjs/factory/errors/index.cjs.map +1 -1
- package/lib/cjs/factory/errors/throttling.cjs +1 -1
- package/lib/cjs/factory/errors/throttling.cjs.map +1 -1
- package/lib/cjs/factory/exceptions/DecafErrorFilter.cjs +105 -6
- package/lib/cjs/factory/exceptions/DecafErrorFilter.cjs.map +1 -1
- package/lib/cjs/factory/exceptions/decorators.cjs +4 -1
- package/lib/cjs/factory/exceptions/decorators.cjs.map +1 -1
- package/lib/cjs/factory/exceptions/index.cjs +1 -1
- package/lib/cjs/factory/exceptions/index.cjs.map +1 -1
- package/lib/cjs/factory/index.cjs +1 -1
- package/lib/cjs/factory/index.cjs.map +1 -1
- package/lib/cjs/factory/openapi/DtoBuilder.cjs +36 -5
- package/lib/cjs/factory/openapi/DtoBuilder.cjs.map +1 -1
- package/lib/cjs/factory/openapi/SwaggerBuilder.cjs +1 -1
- package/lib/cjs/factory/openapi/SwaggerBuilder.cjs.map +1 -1
- package/lib/cjs/factory/openapi/SwaggerCustomUI.cjs +1 -1
- package/lib/cjs/factory/openapi/SwaggerCustomUI.cjs.map +1 -1
- package/lib/cjs/factory/openapi/constants.cjs +1 -1
- package/lib/cjs/factory/openapi/constants.cjs.map +1 -1
- package/lib/cjs/factory/openapi/index.cjs +1 -1
- package/lib/cjs/factory/openapi/index.cjs.map +1 -1
- package/lib/cjs/index.cjs +6 -5
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/interceptors/DecafRequestHandlerInterceptor.cjs +24 -20
- package/lib/cjs/interceptors/DecafRequestHandlerInterceptor.cjs.map +1 -1
- package/lib/cjs/interceptors/index.cjs +2 -3
- package/lib/cjs/interceptors/index.cjs.map +1 -1
- package/lib/cjs/module.cjs +4 -1
- package/lib/cjs/module.cjs.map +1 -1
- package/lib/cjs/overrides/Adapter.cjs +1 -1
- package/lib/cjs/overrides/Adapter.cjs.map +1 -1
- package/lib/cjs/overrides/ModelBuilderExtensions.cjs +1 -1
- package/lib/cjs/overrides/ModelBuilderExtensions.cjs.map +1 -1
- package/lib/cjs/overrides/constants.cjs +1 -1
- package/lib/cjs/overrides/constants.cjs.map +1 -1
- package/lib/cjs/overrides/decoration.cjs +1 -1
- package/lib/cjs/overrides/decoration.cjs.map +1 -1
- package/lib/cjs/overrides/helpers.cjs +1 -1
- package/lib/cjs/overrides/helpers.cjs.map +1 -1
- package/lib/cjs/overrides/index.cjs +2 -1
- package/lib/cjs/overrides/index.cjs.map +1 -1
- package/lib/cjs/overrides/logging.cjs +5 -0
- package/lib/cjs/overrides/logging.cjs.map +1 -0
- package/lib/cjs/overrides/overrides.cjs +1 -1
- package/lib/cjs/overrides/overrides.cjs.map +1 -1
- package/lib/cjs/request/DecafAuthHandler.cjs +4 -38
- package/lib/cjs/request/DecafAuthHandler.cjs.map +1 -1
- package/lib/cjs/request/DecafHandlerExecutor.cjs +1 -1
- package/lib/cjs/request/DecafHandlerExecutor.cjs.map +1 -1
- package/lib/cjs/request/DecafRequestContext.cjs +12 -19
- package/lib/cjs/request/DecafRequestContext.cjs.map +1 -1
- package/lib/cjs/request/DecafResponseInterceptor.cjs +5 -3
- package/lib/cjs/request/DecafResponseInterceptor.cjs.map +1 -1
- package/lib/cjs/request/index.cjs +1 -1
- package/lib/cjs/request/index.cjs.map +1 -1
- package/lib/cjs/swagger-types.cjs +1 -1
- package/lib/cjs/swagger-types.cjs.map +1 -1
- package/lib/cjs/types.cjs +1 -1
- package/lib/cjs/types.cjs.map +1 -1
- package/lib/cjs/utils.cjs +1 -1
- package/lib/cjs/utils.cjs.map +1 -1
- package/lib/cjs/webhooks/DecafWebhookModule.cjs +4 -3
- package/lib/cjs/webhooks/DecafWebhookModule.cjs.map +1 -1
- package/lib/cjs/webhooks/controllers.cjs +49 -54
- package/lib/cjs/webhooks/controllers.cjs.map +1 -1
- package/lib/cjs/webhooks/index.cjs +1 -1
- package/lib/cjs/webhooks/index.cjs.map +1 -1
- package/lib/cjs/webhooks/types.cjs +1 -1
- package/lib/cjs/webhooks/types.cjs.map +1 -1
- package/lib/esm/auth/AuthInterceptor.js +84 -0
- package/lib/esm/auth/AuthInterceptor.js.map +1 -0
- package/lib/esm/auth/DecafAuthHandler.js +31 -0
- package/lib/esm/auth/DecafAuthHandler.js.map +1 -0
- package/lib/esm/auth/DecafAuthModule.js +42 -0
- package/lib/esm/auth/DecafAuthModule.js.map +1 -0
- package/lib/esm/auth/constants.js +6 -0
- package/lib/esm/auth/constants.js.map +1 -0
- package/lib/esm/auth/decorators.js +22 -0
- package/lib/esm/auth/decorators.js.map +1 -0
- package/lib/esm/auth/index.js +6 -0
- package/lib/esm/auth/index.js.map +1 -0
- package/lib/esm/cli-module.js +62 -5
- package/lib/esm/cli-module.js.map +1 -1
- package/lib/esm/constants.js +2 -3
- package/lib/esm/constants.js.map +1 -1
- package/lib/esm/controllers.js +15 -112
- package/lib/esm/controllers.js.map +1 -1
- package/lib/esm/core-module.js +6 -6
- package/lib/esm/core-module.js.map +1 -1
- package/lib/esm/decaf-model/DecafModelModule.js +26 -2
- package/lib/esm/decaf-model/DecafModelModule.js.map +1 -1
- package/lib/esm/decaf-model/FromModelController.js +622 -796
- package/lib/esm/decaf-model/FromModelController.js.map +1 -1
- package/lib/esm/decaf-model/decorators/ApiOperationFromModel.js +52 -14
- package/lib/esm/decaf-model/decorators/ApiOperationFromModel.js.map +1 -1
- package/lib/esm/decaf-model/decorators/controller-config.js +25 -0
- package/lib/esm/decaf-model/decorators/controller-config.js.map +1 -0
- package/lib/esm/decaf-model/decorators/decorators.js +1 -42
- package/lib/esm/decaf-model/decorators/decorators.js.map +1 -1
- package/lib/esm/decaf-model/decorators/expose.js +9 -0
- package/lib/esm/decaf-model/decorators/expose.js.map +1 -0
- package/lib/esm/decaf-model/decorators/index.js +1 -0
- package/lib/esm/decaf-model/decorators/index.js.map +1 -1
- package/lib/esm/decaf-model/decorators/utils.js +2 -1
- package/lib/esm/decaf-model/decorators/utils.js.map +1 -1
- package/lib/esm/decaf-model/index.js +10 -3
- package/lib/esm/decaf-model/index.js.map +1 -1
- package/lib/esm/decaf-model/utils.js +9 -3
- package/lib/esm/decaf-model/utils.js.map +1 -1
- package/lib/esm/decorators.js +87 -0
- package/lib/esm/decorators.js.map +1 -0
- package/lib/esm/events-module/utils.js.map +1 -1
- package/lib/esm/factory/NestBootstraper.js +10 -3
- package/lib/esm/factory/NestBootstraper.js.map +1 -1
- package/lib/esm/factory/exceptions/DecafErrorFilter.js +105 -7
- package/lib/esm/factory/exceptions/DecafErrorFilter.js.map +1 -1
- package/lib/esm/factory/exceptions/decorators.js +3 -0
- package/lib/esm/factory/exceptions/decorators.js.map +1 -1
- package/lib/esm/factory/openapi/DtoBuilder.js +35 -4
- package/lib/esm/factory/openapi/DtoBuilder.js.map +1 -1
- package/lib/esm/index.js +5 -4
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/interceptors/DecafRequestHandlerInterceptor.js +24 -20
- package/lib/esm/interceptors/DecafRequestHandlerInterceptor.js.map +1 -1
- package/lib/esm/interceptors/index.js +1 -2
- package/lib/esm/interceptors/index.js.map +1 -1
- package/lib/esm/module.js +3 -0
- package/lib/esm/module.js.map +1 -1
- package/lib/esm/overrides/index.js +1 -0
- package/lib/esm/overrides/index.js.map +1 -1
- package/lib/esm/overrides/logging.js +2 -0
- package/lib/esm/overrides/logging.js.map +1 -0
- package/lib/esm/request/DecafAuthHandler.js +1 -35
- package/lib/esm/request/DecafAuthHandler.js.map +1 -1
- package/lib/esm/request/DecafRequestContext.js +11 -18
- package/lib/esm/request/DecafRequestContext.js.map +1 -1
- package/lib/esm/request/DecafResponseInterceptor.js +4 -2
- package/lib/esm/request/DecafResponseInterceptor.js.map +1 -1
- package/lib/esm/webhooks/DecafWebhookModule.js +2 -1
- package/lib/esm/webhooks/DecafWebhookModule.js.map +1 -1
- package/lib/esm/webhooks/controllers.js +48 -53
- package/lib/esm/webhooks/controllers.js.map +1 -1
- package/lib/types/{interceptors → auth}/AuthInterceptor.d.cts +4 -1
- package/lib/types/{interceptors → auth}/AuthInterceptor.d.mts +4 -1
- package/lib/types/auth/DecafAuthHandler.d.cts +22 -0
- package/lib/types/auth/DecafAuthHandler.d.mts +22 -0
- package/lib/types/auth/DecafAuthModule.d.cts +9 -0
- package/lib/types/auth/DecafAuthModule.d.mts +9 -0
- package/lib/types/auth/constants.d.cts +5 -0
- package/lib/types/auth/constants.d.mts +5 -0
- package/lib/types/auth/decorators.d.cts +4 -0
- package/lib/types/auth/decorators.d.mts +4 -0
- package/lib/types/auth/index.d.cts +6 -0
- package/lib/types/auth/index.d.mts +6 -0
- package/lib/types/cli-module.d.cts +1 -4
- package/lib/types/cli-module.d.mts +1 -4
- package/lib/types/constants.d.cts +2 -3
- package/lib/types/constants.d.mts +2 -3
- package/lib/types/controllers.d.cts +7 -12
- package/lib/types/controllers.d.mts +7 -12
- package/lib/types/decaf-model/DecafModelModule.d.cts +1 -0
- package/lib/types/decaf-model/DecafModelModule.d.mts +1 -0
- package/lib/types/decaf-model/FromModelController.d.cts +32 -52
- package/lib/types/decaf-model/FromModelController.d.mts +32 -52
- package/lib/types/decaf-model/decorators/ApiOperationFromModel.d.cts +0 -2
- package/lib/types/decaf-model/decorators/ApiOperationFromModel.d.mts +0 -2
- package/lib/types/decaf-model/decorators/controller-config.d.cts +18 -0
- package/lib/types/decaf-model/decorators/controller-config.d.mts +18 -0
- package/lib/types/decaf-model/decorators/decorators.d.cts +1 -29
- package/lib/types/decaf-model/decorators/decorators.d.mts +1 -29
- package/lib/types/decaf-model/decorators/expose.d.cts +1 -0
- package/lib/types/decaf-model/decorators/expose.d.mts +1 -0
- package/lib/types/decaf-model/decorators/index.d.cts +1 -0
- package/lib/types/decaf-model/decorators/index.d.mts +1 -0
- package/lib/types/decaf-model/decorators/types.d.cts +8 -21
- package/lib/types/decaf-model/decorators/types.d.mts +8 -21
- package/lib/types/decaf-model/index.d.cts +11 -3
- package/lib/types/decaf-model/index.d.mts +11 -3
- package/lib/types/decaf-model/utils.d.cts +2 -1
- package/lib/types/decaf-model/utils.d.mts +2 -1
- package/lib/types/decorators.d.cts +35 -0
- package/lib/types/decorators.d.mts +35 -0
- package/lib/types/factory/NestBootstraper.d.cts +5 -0
- package/lib/types/factory/NestBootstraper.d.mts +5 -0
- package/lib/types/factory/exceptions/DecafErrorFilter.d.cts +33 -1
- package/lib/types/factory/exceptions/DecafErrorFilter.d.mts +33 -1
- package/lib/types/factory/openapi/DtoBuilder.d.cts +7 -3
- package/lib/types/factory/openapi/DtoBuilder.d.mts +7 -3
- package/lib/types/index.d.cts +5 -4
- package/lib/types/index.d.mts +5 -4
- package/lib/types/interceptors/DecafRequestHandlerInterceptor.d.cts +1 -2
- package/lib/types/interceptors/DecafRequestHandlerInterceptor.d.mts +1 -2
- package/lib/types/interceptors/index.d.cts +1 -2
- package/lib/types/interceptors/index.d.mts +1 -2
- package/lib/types/overrides/Adapter.d.cts +1 -1
- package/lib/types/overrides/Adapter.d.mts +1 -1
- package/lib/types/overrides/index.d.cts +1 -0
- package/lib/types/overrides/index.d.mts +1 -0
- package/lib/types/overrides/logging.d.cts +1 -0
- package/lib/types/overrides/logging.d.mts +1 -0
- package/lib/types/request/DecafAuthHandler.d.cts +1 -10
- package/lib/types/request/DecafAuthHandler.d.mts +1 -10
- package/lib/types/request/DecafRequestContext.d.cts +5 -5
- package/lib/types/request/DecafRequestContext.d.mts +5 -5
- package/lib/types/types.d.cts +12 -24
- package/lib/types/types.d.mts +12 -24
- package/package.json +12 -13
- package/lib/cjs/interceptors/AuthInterceptor.cjs +0 -52
- package/lib/cjs/interceptors/AuthInterceptor.cjs.map +0 -1
- package/lib/cjs/interceptors/context.cjs +0 -18
- package/lib/cjs/interceptors/context.cjs.map +0 -1
- package/lib/cjs/migrations/index.cjs +0 -35
- package/lib/cjs/migrations/index.cjs.map +0 -1
- package/lib/cjs/migrations/migration-module.cjs +0 -60
- package/lib/cjs/migrations/migration-module.cjs.map +0 -1
- package/lib/cjs/migrations/migration-options.cjs +0 -4
- package/lib/cjs/migrations/migration-options.cjs.map +0 -1
- package/lib/cjs/ram/RamRequestTransformer.cjs +0 -26
- package/lib/cjs/ram/RamRequestTransformer.cjs.map +0 -1
- package/lib/cjs/ram/index.cjs.map +0 -1
- package/lib/esm/interceptors/AuthInterceptor.js +0 -43
- package/lib/esm/interceptors/AuthInterceptor.js.map +0 -1
- package/lib/esm/interceptors/context.js +0 -12
- package/lib/esm/interceptors/context.js.map +0 -1
- package/lib/esm/migrations/index.js +0 -18
- package/lib/esm/migrations/index.js.map +0 -1
- package/lib/esm/migrations/migration-module.js +0 -52
- package/lib/esm/migrations/migration-module.js.map +0 -1
- package/lib/esm/migrations/migration-options.js +0 -7
- package/lib/esm/migrations/migration-options.js.map +0 -1
- package/lib/esm/ram/RamRequestTransformer.js +0 -21
- package/lib/esm/ram/RamRequestTransformer.js.map +0 -1
- package/lib/esm/ram/index.js +0 -2
- package/lib/esm/ram/index.js.map +0 -1
- package/lib/types/interceptors/context.d.cts +0 -6
- package/lib/types/interceptors/context.d.mts +0 -6
- package/lib/types/migrations/index.d.cts +0 -17
- package/lib/types/migrations/index.d.mts +0 -17
- package/lib/types/migrations/migration-module.d.cts +0 -15
- package/lib/types/migrations/migration-module.d.mts +0 -15
- package/lib/types/migrations/migration-options.d.cts +0 -11
- package/lib/types/migrations/migration-options.d.mts +0 -11
- package/lib/types/ram/RamRequestTransformer.d.cts +0 -5
- package/lib/types/ram/RamRequestTransformer.d.mts +0 -5
- package/lib/types/ram/index.d.cts +0 -1
- package/lib/types/ram/index.d.mts +0 -1
|
@@ -7,75 +7,33 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
};
|
|
13
|
-
import { Controller, Param, Query, Response } from "@nestjs/common";
|
|
14
|
-
import { ApiBadRequestResponse, ApiBody, ApiCreatedResponse, ApiExtraModels, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags, ApiUnprocessableEntityResponse, getSchemaPath, } from "@nestjs/swagger";
|
|
10
|
+
var DynamicModelController_1;
|
|
11
|
+
import { Controller, Param, Query, Response, SetMetadata, } from "@nestjs/common";
|
|
12
|
+
import { ApiBadRequestResponse, ApiBody, ApiCreatedResponse, ApiExtraModels, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags, ApiUnprocessableEntityResponse, getSchemaPath, } from "@nestjs/swagger";
|
|
15
13
|
import { ModelService, OrderDirection, PersistenceKeys, PreparedStatementKeys, Repository, Service, } from "@decaf-ts/core";
|
|
16
14
|
import { Model } from "@decaf-ts/decorator-validation";
|
|
17
15
|
import { Logging, toKebabCase } from "@decaf-ts/logging";
|
|
18
|
-
import {
|
|
16
|
+
import { DBKeys, BaseError, InternalError, OperationKeys, } from "@decaf-ts/db-decorators";
|
|
19
17
|
import { Metadata } from "@decaf-ts/decoration";
|
|
20
|
-
import { ApiOperationFromModel, ApiParamsFromModel, DecafBody,
|
|
18
|
+
import { ApiOperationFromModel, ApiParamsFromModel, DecafBody, DecafQuery, } from "./decorators/index.js";
|
|
19
|
+
import { HttpVerbToDecorator } from "./decorators/utils.js";
|
|
21
20
|
import { DecafRequestContext } from "./../request/index.js";
|
|
22
|
-
import { DECAF_ROUTE } from "./../constants.js";
|
|
23
|
-
import {
|
|
24
|
-
import { Auth } from "
|
|
21
|
+
import { DECAF_CONTROLLER_CONFIG, DECAF_ROUTE } from "./../constants.js";
|
|
22
|
+
import { SKIP_MODEL_ROLES_KEY } from "./../auth/constants.js";
|
|
23
|
+
import { Auth, Public, RequireRoles } from "./../auth/decorators.js";
|
|
25
24
|
import { DecafModelController } from "./../controllers.js";
|
|
26
25
|
import { DtoFor } from "./../factory/openapi/DtoBuilder.js";
|
|
27
26
|
import "./../overrides/index.js";
|
|
28
|
-
|
|
29
|
-
* @description
|
|
30
|
-
* Factory and utilities for generating dynamic NestJS controllers from Decaf {@link Model} definitions.
|
|
31
|
-
*
|
|
32
|
-
* @summary
|
|
33
|
-
* The `FromModelController` class provides the infrastructure necessary to automatically generate
|
|
34
|
-
* strongly-typed CRUD controllers based on a given {@link ModelConstructor}. It inspects metadata from
|
|
35
|
-
* the model, derives route paths, parameters, and generates a dynamic controller class at runtime with
|
|
36
|
-
* full support for querying, creation, update, and deletion of model entities through a {@link Repo}.
|
|
37
|
-
*
|
|
38
|
-
* @template T The {@link Model} type associated with the generated controller.
|
|
39
|
-
*
|
|
40
|
-
* @param ModelClazz The model class to generate the controller from.
|
|
41
|
-
*
|
|
42
|
-
* @class FromModelController
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```ts
|
|
46
|
-
* // Given a Decaf Model:
|
|
47
|
-
* class User extends Model<User> {
|
|
48
|
-
* id!: string;
|
|
49
|
-
* name!: string;
|
|
50
|
-
* }
|
|
51
|
-
*
|
|
52
|
-
* // Register controller:
|
|
53
|
-
* const UserController = FromModelController.create(User);
|
|
54
|
-
*
|
|
55
|
-
* // NestJS will expose:
|
|
56
|
-
* // POST /user
|
|
57
|
-
* // GET /user/:id
|
|
58
|
-
* // GET /user/query/:method
|
|
59
|
-
* // PUT /user/:id
|
|
60
|
-
* // DELETE /user/:id
|
|
61
|
-
* ```
|
|
62
|
-
*
|
|
63
|
-
* @mermaid
|
|
64
|
-
* sequenceDiagram
|
|
65
|
-
* participant Client
|
|
66
|
-
* participant Controller
|
|
67
|
-
* participant Repo
|
|
68
|
-
* participant DB
|
|
69
|
-
*
|
|
70
|
-
* Client->>Controller: HTTP Request
|
|
71
|
-
* Controller->>Repo: Resolve repository for Model
|
|
72
|
-
* Repo->>DB: Execute DB operation
|
|
73
|
-
* DB-->>Repo: DB Result
|
|
74
|
-
* Repo-->>Controller: Model Instance(s)
|
|
75
|
-
* Controller-->>Client: JSON Response
|
|
76
|
-
*/
|
|
27
|
+
import { ModelControllerFactory, } from "@decaf-ts/for-http/server";
|
|
77
28
|
export class FromModelController {
|
|
78
29
|
static { this.log = Logging.for(FromModelController.name); }
|
|
30
|
+
static toDecafError(error, fallbackMessage) {
|
|
31
|
+
if (error instanceof BaseError)
|
|
32
|
+
return error;
|
|
33
|
+
return new InternalError(error instanceof Error
|
|
34
|
+
? `${fallbackMessage}: ${error.message}`
|
|
35
|
+
: fallbackMessage);
|
|
36
|
+
}
|
|
79
37
|
static getPersistence(ModelClazz) {
|
|
80
38
|
try {
|
|
81
39
|
return Service.get(ModelClazz);
|
|
@@ -83,756 +41,537 @@ export class FromModelController {
|
|
|
83
41
|
catch (e) {
|
|
84
42
|
try {
|
|
85
43
|
return ModelService.getService(ModelClazz);
|
|
86
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
87
44
|
}
|
|
88
|
-
catch (
|
|
45
|
+
catch (e2) {
|
|
89
46
|
return Repository.forModel(ModelClazz);
|
|
90
47
|
}
|
|
91
48
|
}
|
|
92
49
|
}
|
|
93
50
|
static createQueryRoutesFromRepository(persistence, prefix = PersistenceKeys.QUERY) {
|
|
94
|
-
const log = FromModelController.log.for(FromModelController.createQueryRoutesFromRepository);
|
|
95
51
|
const repo = persistence instanceof ModelService ? persistence.repo : persistence;
|
|
96
|
-
const ModelConstr = repo.class;
|
|
97
52
|
const queryMethods = Metadata.get(repo.constructor, Metadata.key(PersistenceKeys.QUERY)) ?? {};
|
|
98
53
|
const routeMethods = Metadata.get(persistence.constructor, Metadata.key(DECAF_ROUTE)) ?? {};
|
|
99
|
-
// create base class
|
|
100
54
|
class QueryController extends DecafModelController {
|
|
101
55
|
get class() {
|
|
102
|
-
throw new
|
|
56
|
+
throw new InternalError("Method not implemented.");
|
|
103
57
|
}
|
|
104
58
|
constructor(clientContext, name) {
|
|
105
59
|
super(clientContext, name);
|
|
106
60
|
}
|
|
107
61
|
}
|
|
108
62
|
for (const [methodName, params] of Object.entries(routeMethods)) {
|
|
109
|
-
// regex to trim slashes from start and end
|
|
110
63
|
const routePath = [params.path.replace(/^\/+|\/+$/g, "")]
|
|
111
64
|
.filter((segment) => segment && segment.trim())
|
|
112
65
|
.join("/");
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (descriptor) {
|
|
122
|
-
const decorators = getApiDecorators(methodName, routePath, params.httpMethod);
|
|
123
|
-
applyApiDecorators(QueryController, methodName, descriptor, decorators);
|
|
124
|
-
}
|
|
66
|
+
const handler = FromModelController.createComplexQueryHandler(methodName);
|
|
67
|
+
FromModelController.defineMethod(QueryController, methodName, handler);
|
|
68
|
+
const httpDecorator = HttpVerbToDecorator(params.httpMethod)(routePath || undefined);
|
|
69
|
+
const decorators = FromModelController.getQueryDecorators(methodName, routePath, params.httpMethod);
|
|
70
|
+
FromModelController.applyDecorators(QueryController, methodName, [
|
|
71
|
+
httpDecorator,
|
|
72
|
+
...decorators,
|
|
73
|
+
]);
|
|
125
74
|
}
|
|
126
75
|
for (const [methodName, objValues] of Object.entries(queryMethods)) {
|
|
127
76
|
const fields = objValues.fields ?? [];
|
|
128
77
|
const routePath = [prefix, methodName, ...fields.map((f) => `:${f}`)]
|
|
129
78
|
.filter((segment) => segment && segment.trim())
|
|
130
79
|
.join("/");
|
|
131
|
-
const handler =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
80
|
+
const handler = FromModelController.createComplexQueryHandler(methodName);
|
|
81
|
+
FromModelController.defineMethod(QueryController, methodName, handler);
|
|
82
|
+
const httpDecorator = HttpVerbToDecorator("GET")(routePath || undefined);
|
|
83
|
+
const decorators = FromModelController.getQueryDecorators(methodName, routePath, "GET", true);
|
|
84
|
+
FromModelController.applyDecorators(QueryController, methodName, [
|
|
85
|
+
httpDecorator,
|
|
86
|
+
...decorators,
|
|
87
|
+
]);
|
|
137
88
|
}
|
|
138
89
|
return QueryController;
|
|
139
90
|
}
|
|
140
|
-
static create(ModelConstr) {
|
|
91
|
+
static create(ModelConstr, moduleConfigOverrides, globalDefaults) {
|
|
141
92
|
const log = FromModelController.log.for(FromModelController.create);
|
|
142
93
|
const tableName = Model.tableName(ModelConstr);
|
|
143
94
|
const routePath = toKebabCase(tableName);
|
|
144
95
|
const modelClazzName = ModelConstr.name;
|
|
145
96
|
const persistence = FromModelController.getPersistence(ModelConstr);
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
);
|
|
151
|
-
|
|
97
|
+
// When persistence is a ModelService, the @query/@route metadata lives on
|
|
98
|
+
// the underlying repository class (custom repo), not on ModelService itself.
|
|
99
|
+
// Pass the repo to the factory so addComplexQueries() can discover them.
|
|
100
|
+
const factoryPersistence = persistence instanceof ModelService ? persistence.repo : persistence;
|
|
101
|
+
const decoratorConfig = Metadata.get(ModelConstr, Metadata.key(DECAF_CONTROLLER_CONFIG));
|
|
102
|
+
const moduleOverride = moduleConfigOverrides?.[ModelConstr.name];
|
|
103
|
+
const mergedConfig = {
|
|
104
|
+
...(globalDefaults || {}),
|
|
105
|
+
...(decoratorConfig || {}),
|
|
106
|
+
...(moduleOverride || {}),
|
|
107
|
+
};
|
|
108
|
+
const FactoryController = ModelControllerFactory.create(ModelConstr, factoryPersistence, mergedConfig);
|
|
109
|
+
const factoryRoutes = FactoryController.__routes__;
|
|
110
|
+
const { getPK, apiProperties, path: pkPath, } = FromModelController.getRouteParametersFromModel(ModelConstr);
|
|
111
|
+
log.debug(`Creating controller for model: ${modelClazzName} with ${factoryRoutes?.length ?? 0} factory routes`);
|
|
112
|
+
const authConfig = mergedConfig.auth;
|
|
113
|
+
function applyClassAuth(target) {
|
|
114
|
+
if (authConfig?.public) {
|
|
115
|
+
Public()(target);
|
|
116
|
+
}
|
|
117
|
+
else if (authConfig?.roles?.length) {
|
|
118
|
+
RequireRoles(...authConfig.roles)(target);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
Auth(ModelConstr)(target);
|
|
122
|
+
}
|
|
123
|
+
if (authConfig?.skipModelRoles) {
|
|
124
|
+
SetMetadata(SKIP_MODEL_ROLES_KEY, true)(target);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
let DynamicModelController = DynamicModelController_1 = class DynamicModelController extends DecafModelController {
|
|
152
128
|
static get class() {
|
|
153
129
|
return ModelConstr;
|
|
154
130
|
}
|
|
155
131
|
get class() {
|
|
156
132
|
return ModelConstr;
|
|
157
|
-
// return DynamicModelController.class;
|
|
158
133
|
}
|
|
159
134
|
constructor(clientContext) {
|
|
160
|
-
super(clientContext);
|
|
135
|
+
super(clientContext, DynamicModelController_1.name);
|
|
161
136
|
this.pk = Model.pk(ModelConstr);
|
|
162
137
|
log.info(`Registering dynamic controller for model: ${this.class.name} route: /${routePath}`);
|
|
163
138
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
async page(value, details) {
|
|
197
|
-
const { ctx } = (await this.logCtx([], PreparedStatementKeys.PAGE, true)).for(this.page);
|
|
198
|
-
const { direction = OrderDirection.ASC, limit, offset, bookmark, } = details;
|
|
199
|
-
const ref = {
|
|
200
|
-
offset: offset ?? 1,
|
|
201
|
-
limit: limit ?? 10,
|
|
202
|
-
bookmark,
|
|
203
|
-
};
|
|
204
|
-
return resolvePersistenceMethod(this.persistence(ctx), this.page.name, value, direction, ref, ctx);
|
|
205
|
-
}
|
|
206
|
-
async findOneBy(key, value) {
|
|
207
|
-
const { ctx } = (await this.logCtx([], PreparedStatementKeys.FIND_ONE_BY, true)).for(this.findOneBy);
|
|
208
|
-
return this.persistence(ctx).findOneBy(key, value, ctx);
|
|
209
|
-
}
|
|
210
|
-
async findBy(key, value, details) {
|
|
211
|
-
const { ctx } = (await this.logCtx([], PreparedStatementKeys.FIND_BY, true)).for(this.findBy);
|
|
212
|
-
return this.persistence(ctx)
|
|
213
|
-
.for(ctx.toOverrides())
|
|
214
|
-
.findBy(key, value, ctx);
|
|
215
|
-
}
|
|
216
|
-
async statement(name, args, details) {
|
|
217
|
-
const { ctx } = (await this.logCtx([], PersistenceKeys.STATEMENT, true)).for(this.statement);
|
|
218
|
-
const { direction, offset, limit, bookmark } = details;
|
|
219
|
-
args = args.map((a) => (typeof a === "string" ? parseInt(a) : a) || a);
|
|
220
|
-
const pathDirection = args.length > 1 ? args[1] : undefined;
|
|
221
|
-
const resolvedDirection = (direction ?? pathDirection);
|
|
222
|
-
if (resolvedDirection && args.length > 1)
|
|
223
|
-
args[1] = resolvedDirection;
|
|
224
|
-
switch (name) {
|
|
225
|
-
case PreparedStatementKeys.FIND:
|
|
226
|
-
case PreparedStatementKeys.FIND_BY:
|
|
227
|
-
break;
|
|
228
|
-
case PreparedStatementKeys.LIST_BY:
|
|
229
|
-
args.push(direction);
|
|
230
|
-
break;
|
|
231
|
-
case PreparedStatementKeys.PAGE:
|
|
232
|
-
case PreparedStatementKeys.PAGE_BY:
|
|
233
|
-
args = [
|
|
234
|
-
args[0],
|
|
235
|
-
resolvedDirection,
|
|
236
|
-
{
|
|
237
|
-
limit: limit,
|
|
238
|
-
offset: offset,
|
|
239
|
-
bookmark: bookmark,
|
|
240
|
-
},
|
|
241
|
-
];
|
|
242
|
-
break;
|
|
243
|
-
case PreparedStatementKeys.FIND_ONE_BY:
|
|
244
|
-
break;
|
|
245
|
-
case PreparedStatementKeys.COUNT_OF:
|
|
246
|
-
case PreparedStatementKeys.MAX_OF:
|
|
247
|
-
case PreparedStatementKeys.MIN_OF:
|
|
248
|
-
case PreparedStatementKeys.AVG_OF:
|
|
249
|
-
case PreparedStatementKeys.SUM_OF:
|
|
250
|
-
case PreparedStatementKeys.DISTINCT_OF:
|
|
251
|
-
case PreparedStatementKeys.GROUP_OF:
|
|
252
|
-
// Aggregation methods - args[0] is the field name (if provided)
|
|
253
|
-
break;
|
|
139
|
+
};
|
|
140
|
+
DynamicModelController = DynamicModelController_1 = __decorate([
|
|
141
|
+
Controller(routePath),
|
|
142
|
+
ApiTags(modelClazzName),
|
|
143
|
+
ApiExtraModels(ModelConstr),
|
|
144
|
+
__metadata("design:paramtypes", [DecafRequestContext])
|
|
145
|
+
], DynamicModelController);
|
|
146
|
+
applyClassAuth(DynamicModelController);
|
|
147
|
+
if (factoryRoutes) {
|
|
148
|
+
const sortedRoutes = [...factoryRoutes].sort((a, b) => {
|
|
149
|
+
const aSegments = a.path.split("/").filter(Boolean);
|
|
150
|
+
const bSegments = b.path.split("/").filter(Boolean);
|
|
151
|
+
const aParamCount = aSegments.filter((s) => s.startsWith(":")).length;
|
|
152
|
+
const bParamCount = bSegments.filter((s) => s.startsWith(":")).length;
|
|
153
|
+
const aLiteralCount = aSegments.length - aParamCount;
|
|
154
|
+
const bLiteralCount = bSegments.length - bParamCount;
|
|
155
|
+
if (aLiteralCount !== bLiteralCount)
|
|
156
|
+
return bLiteralCount - aLiteralCount;
|
|
157
|
+
if (aParamCount !== bParamCount)
|
|
158
|
+
return aParamCount - bParamCount;
|
|
159
|
+
return 0;
|
|
160
|
+
});
|
|
161
|
+
for (const route of sortedRoutes) {
|
|
162
|
+
const methodName = route.implementation?.name || route.method.toLowerCase();
|
|
163
|
+
const registration = FromModelController.matchRoute(methodName, route, pkPath, apiProperties, getPK, ModelConstr, modelClazzName);
|
|
164
|
+
if (!registration)
|
|
165
|
+
continue;
|
|
166
|
+
const { methodName: registeredMethodName, handler, decorators, paramDecorators, } = registration;
|
|
167
|
+
const descriptor = FromModelController.defineMethod(DynamicModelController, registeredMethodName, handler);
|
|
168
|
+
if (descriptor) {
|
|
169
|
+
const httpDecorator = HttpVerbToDecorator(route.method)(route.path.replace(/^\/+|\/+$/g, "") || undefined);
|
|
170
|
+
FromModelController.applyDecorators(DynamicModelController, registeredMethodName, [httpDecorator, ...decorators], paramDecorators);
|
|
254
171
|
}
|
|
255
|
-
return this.persistence(ctx).statement(name, ...args, ctx);
|
|
256
172
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// summary: `Find maximum value of a field in ${modelClazzName}.`,
|
|
295
|
-
// })
|
|
296
|
-
// @ApiParam({ name: "field", description: "The field to find max of" })
|
|
297
|
-
// @ApiOkResponse({
|
|
298
|
-
// description: `Maximum value of the field in ${modelClazzName}.`,
|
|
299
|
-
// })
|
|
300
|
-
// async maxOf(@Param("field") field: string) {
|
|
301
|
-
// const { ctx } = (
|
|
302
|
-
// await this.logCtx([], PreparedStatementKeys.MAX_OF, true)
|
|
303
|
-
// ).for(this.maxOf);
|
|
304
|
-
// return this.persistence(ctx).statement(
|
|
305
|
-
// PreparedStatementKeys.MAX_OF,
|
|
306
|
-
// field,
|
|
307
|
-
// ctx
|
|
308
|
-
// );
|
|
309
|
-
// }
|
|
310
|
-
//
|
|
311
|
-
// @ApiOperationFromModel(ModelConstr, "GET", "minOf/:field")
|
|
312
|
-
// @ApiOperation({
|
|
313
|
-
// summary: `Find minimum value of a field in ${modelClazzName}.`,
|
|
314
|
-
// })
|
|
315
|
-
// @ApiParam({ name: "field", description: "The field to find min of" })
|
|
316
|
-
// @ApiOkResponse({
|
|
317
|
-
// description: `Minimum value of the field in ${modelClazzName}.`,
|
|
318
|
-
// })
|
|
319
|
-
// async minOf(@Param("field") field: string) {
|
|
320
|
-
// const { ctx } = (
|
|
321
|
-
// await this.logCtx([], PreparedStatementKeys.MIN_OF, true)
|
|
322
|
-
// ).for(this.minOf);
|
|
323
|
-
// return this.persistence(ctx).statement(
|
|
324
|
-
// PreparedStatementKeys.MIN_OF,
|
|
325
|
-
// field,
|
|
326
|
-
// ctx
|
|
327
|
-
// );
|
|
328
|
-
// }
|
|
329
|
-
//
|
|
330
|
-
// @ApiOperationFromModel(ModelConstr, "GET", "avgOf/:field")
|
|
331
|
-
// @ApiOperation({
|
|
332
|
-
// summary: `Calculate average of a field in ${modelClazzName}.`,
|
|
333
|
-
// })
|
|
334
|
-
// @ApiParam({
|
|
335
|
-
// name: "field",
|
|
336
|
-
// description: "The field to calculate average of",
|
|
337
|
-
// })
|
|
338
|
-
// @ApiOkResponse({
|
|
339
|
-
// description: `Average value of the field in ${modelClazzName}.`,
|
|
340
|
-
// type: Number,
|
|
341
|
-
// })
|
|
342
|
-
// async avgOf(@Param("field") field: string) {
|
|
343
|
-
// const { ctx } = (
|
|
344
|
-
// await this.logCtx([], PreparedStatementKeys.AVG_OF, true)
|
|
345
|
-
// ).for(this.avgOf);
|
|
346
|
-
// return this.persistence(ctx).statement(
|
|
347
|
-
// PreparedStatementKeys.AVG_OF,
|
|
348
|
-
// field,
|
|
349
|
-
// ctx
|
|
350
|
-
// );
|
|
351
|
-
// }
|
|
352
|
-
//
|
|
353
|
-
// @ApiOperationFromModel(ModelConstr, "GET", "sumOf/:field")
|
|
354
|
-
// @ApiOperation({
|
|
355
|
-
// summary: `Calculate sum of a field in ${modelClazzName}.`,
|
|
356
|
-
// })
|
|
357
|
-
// @ApiParam({ name: "field", description: "The field to calculate sum of" })
|
|
358
|
-
// @ApiOkResponse({
|
|
359
|
-
// description: `Sum of the field in ${modelClazzName}.`,
|
|
360
|
-
// type: Number,
|
|
361
|
-
// })
|
|
362
|
-
// async sumOf(@Param("field") field: string) {
|
|
363
|
-
// const { ctx } = (
|
|
364
|
-
// await this.logCtx([], PreparedStatementKeys.SUM_OF, true)
|
|
365
|
-
// ).for(this.sumOf);
|
|
366
|
-
// return this.persistence(ctx).statement(
|
|
367
|
-
// PreparedStatementKeys.SUM_OF,
|
|
368
|
-
// field,
|
|
369
|
-
// ctx
|
|
370
|
-
// );
|
|
371
|
-
// }
|
|
372
|
-
//
|
|
373
|
-
// @ApiOperationFromModel(ModelConstr, "GET", "distinctOf/:field")
|
|
374
|
-
// @ApiOperation({
|
|
375
|
-
// summary: `Find distinct values of a field in ${modelClazzName}.`,
|
|
376
|
-
// })
|
|
377
|
-
// @ApiParam({
|
|
378
|
-
// name: "field",
|
|
379
|
-
// description: "The field to find distinct values of",
|
|
380
|
-
// })
|
|
381
|
-
// @ApiOkResponse({
|
|
382
|
-
// description: `Distinct values of the field in ${modelClazzName}.`,
|
|
383
|
-
// type: [String],
|
|
384
|
-
// })
|
|
385
|
-
// async distinctOf(@Param("field") field: string) {
|
|
386
|
-
// const { ctx } = (
|
|
387
|
-
// await this.logCtx([], PreparedStatementKeys.DISTINCT_OF, true)
|
|
388
|
-
// ).for(this.distinctOf);
|
|
389
|
-
// return this.persistence(ctx).statement(
|
|
390
|
-
// PreparedStatementKeys.DISTINCT_OF,
|
|
391
|
-
// field,
|
|
392
|
-
// ctx
|
|
393
|
-
// );
|
|
394
|
-
// }
|
|
395
|
-
//
|
|
396
|
-
// @ApiOperationFromModel(ModelConstr, "GET", "groupOf/:field")
|
|
397
|
-
// @ApiOperation({
|
|
398
|
-
// summary: `Group ${modelClazzName} records by a field.`,
|
|
399
|
-
// })
|
|
400
|
-
// @ApiParam({ name: "field", description: "The field to group by" })
|
|
401
|
-
// @ApiOkResponse({
|
|
402
|
-
// description: `${modelClazzName} records grouped by the field.`,
|
|
403
|
-
// })
|
|
404
|
-
// async groupOf(@Param("field") field: string) {
|
|
405
|
-
// const { ctx } = (
|
|
406
|
-
// await this.logCtx([], PreparedStatementKeys.GROUP_OF, true)
|
|
407
|
-
// ).for(this.groupOf);
|
|
408
|
-
// return this.persistence(ctx).statement(
|
|
409
|
-
// PreparedStatementKeys.GROUP_OF,
|
|
410
|
-
// field,
|
|
411
|
-
// ctx
|
|
412
|
-
// );
|
|
413
|
-
// }
|
|
414
|
-
async createAll(data, resp) {
|
|
415
|
-
const { ctx, log } = (await this.logCtx([], BulkCrudOperationKeys.CREATE_ALL, true)).for(this.createAll);
|
|
416
|
-
log.verbose(`creating new ${modelClazzName}`);
|
|
417
|
-
let created;
|
|
418
|
-
try {
|
|
419
|
-
created = await this.persistence(ctx).createAll(data.map((d) => new ModelConstr(d)), ctx);
|
|
420
|
-
}
|
|
421
|
-
catch (e) {
|
|
422
|
-
log.error(`Failed to create new ${modelClazzName}`, e);
|
|
423
|
-
throw e;
|
|
424
|
-
}
|
|
425
|
-
log.info(`created new ${modelClazzName} with id ${created[this.pk]}`);
|
|
426
|
-
ctx.toResponse(resp);
|
|
427
|
-
return created;
|
|
173
|
+
}
|
|
174
|
+
return DynamicModelController;
|
|
175
|
+
}
|
|
176
|
+
static getRouteParametersFromModel(ModelClazz) {
|
|
177
|
+
const pk = Model.pk(ModelClazz);
|
|
178
|
+
const composed = Metadata.get(ModelClazz, Metadata.key(DBKeys.COMPOSED, pk));
|
|
179
|
+
const composedKeys = composed?.args ?? [];
|
|
180
|
+
const uniqueKeys = Array.isArray(composedKeys) && composedKeys.length > 0
|
|
181
|
+
? Array.from(new Set([...composedKeys]))
|
|
182
|
+
: Array.from(new Set([pk]));
|
|
183
|
+
const description = Metadata.description(ModelClazz) ?? "";
|
|
184
|
+
const path = `:${uniqueKeys.join("/:")}`;
|
|
185
|
+
const apiProperties = uniqueKeys.map((key) => {
|
|
186
|
+
return {
|
|
187
|
+
name: key,
|
|
188
|
+
description: Metadata.description(ModelClazz, key),
|
|
189
|
+
required: true,
|
|
190
|
+
type: String,
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
path,
|
|
195
|
+
description,
|
|
196
|
+
apiProperties,
|
|
197
|
+
getPK: (...params) => composed?.separator ? params.join(composed.separator) : params.join(""),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
static routeParamDecorators(path) {
|
|
201
|
+
let paramIndex = 0;
|
|
202
|
+
return path
|
|
203
|
+
.split("/")
|
|
204
|
+
.filter(Boolean)
|
|
205
|
+
.flatMap((segment) => {
|
|
206
|
+
if (segment.startsWith(":")) {
|
|
207
|
+
return [
|
|
208
|
+
{ decorator: Param(segment.slice(1)), index: paramIndex++ },
|
|
209
|
+
];
|
|
428
210
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
try {
|
|
434
|
-
const persistence = this.persistence(ctx);
|
|
435
|
-
created = await persistence.create(data, ctx);
|
|
436
|
-
}
|
|
437
|
-
catch (e) {
|
|
438
|
-
log.error(`Failed to create new ${modelClazzName}`, e);
|
|
439
|
-
throw e;
|
|
440
|
-
}
|
|
441
|
-
log.info(`created new ${modelClazzName} with id ${created[this.pk]}`);
|
|
442
|
-
ctx.toResponse(resp);
|
|
443
|
-
return created;
|
|
211
|
+
if (segment.startsWith("*")) {
|
|
212
|
+
return [
|
|
213
|
+
{ decorator: Param(segment.slice(1)), index: paramIndex++ },
|
|
214
|
+
];
|
|
444
215
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
216
|
+
return [];
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
static defineMethod(target, methodName, handler) {
|
|
220
|
+
Object.defineProperty(target.prototype || target, methodName, {
|
|
221
|
+
value: handler,
|
|
222
|
+
writable: false,
|
|
223
|
+
configurable: true,
|
|
224
|
+
enumerable: false,
|
|
225
|
+
});
|
|
226
|
+
return Object.getOwnPropertyDescriptor(target.prototype || target, methodName);
|
|
227
|
+
}
|
|
228
|
+
static applyDecorators(target, methodName, methodDecorators, paramDecorators = []) {
|
|
229
|
+
const proto = target?.prototype ?? target;
|
|
230
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
|
|
231
|
+
methodDecorators.forEach((d) => d(proto, methodName, descriptor));
|
|
232
|
+
paramDecorators.forEach(({ decorator, index }) => decorator(proto, methodName, index));
|
|
233
|
+
}
|
|
234
|
+
static matchRoute(methodName, route, pkPath, apiProperties, getPK, ModelConstr, modelClazzName) {
|
|
235
|
+
const { method, path } = route;
|
|
236
|
+
const normalizedPath = path.replace(/^\/+|\/+$/g, "");
|
|
237
|
+
const handler = route.implementation;
|
|
238
|
+
if (method === "POST" && normalizedPath === "") {
|
|
239
|
+
return FromModelController.createRegistration("create", handler, FromModelController.createCreateDecorators(ModelConstr, modelClazzName), [
|
|
240
|
+
{ decorator: DecafBody(), index: 0 },
|
|
241
|
+
{ decorator: Response({ passthrough: true }), index: 1 },
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
244
|
+
if (method === "POST" && normalizedPath === "bulk") {
|
|
245
|
+
return FromModelController.createRegistration("createAll", handler, FromModelController.bulkCreateDecorators(ModelConstr, modelClazzName), [
|
|
246
|
+
{ decorator: DecafBody(), index: 0 },
|
|
247
|
+
{ decorator: Response({ passthrough: true }), index: 1 },
|
|
248
|
+
]);
|
|
249
|
+
}
|
|
250
|
+
if (method === "GET" && normalizedPath === "bulk") {
|
|
251
|
+
return FromModelController.createRegistration("readAll", handler, FromModelController.bulkReadDecorators(ModelConstr, modelClazzName), [{ decorator: Query("ids"), index: 0 }]);
|
|
252
|
+
}
|
|
253
|
+
if (method === "PUT" && normalizedPath === "bulk") {
|
|
254
|
+
return FromModelController.createRegistration("updateAll", handler, FromModelController.bulkUpdateDecorators(ModelConstr, modelClazzName, apiProperties), [
|
|
255
|
+
{ decorator: DecafBody(), index: 0 },
|
|
256
|
+
{ decorator: Response({ passthrough: true }), index: 1 },
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
if (method === "DELETE" && normalizedPath === "bulk") {
|
|
260
|
+
return FromModelController.createRegistration("deleteAll", handler, FromModelController.bulkDeleteDecorators(ModelConstr, modelClazzName, apiProperties), [
|
|
261
|
+
{ decorator: Query("ids"), index: 0 },
|
|
262
|
+
{ decorator: Response({ passthrough: true }), index: 1 },
|
|
263
|
+
]);
|
|
264
|
+
}
|
|
265
|
+
if (method === "GET" && normalizedPath === pkPath) {
|
|
266
|
+
return FromModelController.createRegistration("read", handler, FromModelController.readDecorators(ModelConstr, modelClazzName, apiProperties, pkPath), FromModelController.routeParamDecorators(normalizedPath));
|
|
267
|
+
}
|
|
268
|
+
if (method === "PUT" && normalizedPath === pkPath) {
|
|
269
|
+
const routeParams = FromModelController.routeParamDecorators(normalizedPath);
|
|
270
|
+
return FromModelController.createRegistration("update", handler, FromModelController.updateDecorators(ModelConstr, modelClazzName, apiProperties, pkPath), [
|
|
271
|
+
{ decorator: DecafBody(), index: 0 },
|
|
272
|
+
...routeParams.map(({ decorator, index }) => ({
|
|
273
|
+
decorator,
|
|
274
|
+
index: index + 1,
|
|
275
|
+
})),
|
|
276
|
+
]);
|
|
277
|
+
}
|
|
278
|
+
if (method === "DELETE" && normalizedPath === pkPath) {
|
|
279
|
+
const routeParams = FromModelController.routeParamDecorators(normalizedPath);
|
|
280
|
+
return FromModelController.createRegistration("delete", handler, FromModelController.deleteDecorators(ModelConstr, modelClazzName, apiProperties, pkPath), routeParams);
|
|
281
|
+
}
|
|
282
|
+
// Composed PK fallback routes (filterEmpty) — path differs from pkPath
|
|
283
|
+
const fallbackSegments = normalizedPath.split("/").filter(Boolean);
|
|
284
|
+
const isAllParams = fallbackSegments.length > 0 &&
|
|
285
|
+
fallbackSegments.every((s) => s.startsWith(":"));
|
|
286
|
+
if (isAllParams && normalizedPath !== pkPath) {
|
|
287
|
+
const fallbackApiProps = fallbackSegments
|
|
288
|
+
.map((s) => s.slice(1))
|
|
289
|
+
.map((name) => ({
|
|
290
|
+
name,
|
|
291
|
+
description: `${name} parameter`,
|
|
292
|
+
required: true,
|
|
293
|
+
type: String,
|
|
294
|
+
}));
|
|
295
|
+
const suffix = fallbackSegments.map((s) => s.slice(1)).join("And");
|
|
296
|
+
const routeParams = FromModelController.routeParamDecorators(normalizedPath);
|
|
297
|
+
if (method === "GET") {
|
|
298
|
+
return FromModelController.createRegistration(`readBy${suffix}`, handler, FromModelController.readDecorators(ModelConstr, modelClazzName, fallbackApiProps, normalizedPath), routeParams);
|
|
460
299
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const persistence = this.persistence(ctx);
|
|
470
|
-
read = await persistence.read(id, ctx);
|
|
471
|
-
}
|
|
472
|
-
catch (e) {
|
|
473
|
-
log.error(`Failed to read ${modelClazzName} with id ${id}`, e);
|
|
474
|
-
throw e;
|
|
475
|
-
}
|
|
476
|
-
log.info(`read ${modelClazzName} with id ${read[this.pk]}`);
|
|
477
|
-
return read;
|
|
300
|
+
if (method === "PUT") {
|
|
301
|
+
return FromModelController.createRegistration(`updateBy${suffix}`, handler, FromModelController.updateDecorators(ModelConstr, modelClazzName, fallbackApiProps, normalizedPath), [
|
|
302
|
+
{ decorator: DecafBody(), index: 0 },
|
|
303
|
+
...routeParams.map(({ decorator, index }) => ({
|
|
304
|
+
decorator,
|
|
305
|
+
index: index + 1,
|
|
306
|
+
})),
|
|
307
|
+
]);
|
|
478
308
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
let updated;
|
|
482
|
-
try {
|
|
483
|
-
log.info(`updating ${body.length} ${modelClazzName}`);
|
|
484
|
-
updated = await this.persistence(ctx).updateAll(body, ctx);
|
|
485
|
-
}
|
|
486
|
-
catch (e) {
|
|
487
|
-
log.error(e);
|
|
488
|
-
throw e;
|
|
489
|
-
}
|
|
490
|
-
ctx.toResponse(resp);
|
|
491
|
-
return updated;
|
|
309
|
+
if (method === "DELETE") {
|
|
310
|
+
return FromModelController.createRegistration(`deleteBy${suffix}`, handler, FromModelController.deleteDecorators(ModelConstr, modelClazzName, fallbackApiProps, normalizedPath), routeParams);
|
|
492
311
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
312
|
+
}
|
|
313
|
+
if (method === "GET" && normalizedPath === "statement/:method/*args") {
|
|
314
|
+
return FromModelController.createRegistration("statement", handler, FromModelController.statementDecorators(ModelConstr, modelClazzName), [
|
|
315
|
+
{ decorator: Param("method"), index: 0 },
|
|
316
|
+
{ decorator: Param("args"), index: 1 },
|
|
317
|
+
{ decorator: DecafQuery(), index: 2 },
|
|
318
|
+
]);
|
|
319
|
+
}
|
|
320
|
+
if (method === "GET" && normalizedPath === "findOneBy/:key/:value") {
|
|
321
|
+
return FromModelController.createRegistration("findOneBy", handler, FromModelController.statementShortcutDecorators(ModelConstr, modelClazzName, normalizedPath, PreparedStatementKeys.FIND_ONE_BY), FromModelController.statementShortcutParams(normalizedPath));
|
|
322
|
+
}
|
|
323
|
+
const statementRoutes = {
|
|
324
|
+
"listBy/:key": PreparedStatementKeys.LIST_BY,
|
|
325
|
+
"paginateBy/:key/:page": PreparedStatementKeys.PAGE_BY,
|
|
326
|
+
"find/:value": PreparedStatementKeys.FIND,
|
|
327
|
+
"page/:value": PreparedStatementKeys.PAGE,
|
|
328
|
+
"findOneBy/:key/:value": PreparedStatementKeys.FIND_ONE_BY,
|
|
329
|
+
"findBy/:key/:value": PreparedStatementKeys.FIND_BY,
|
|
330
|
+
"countOf/:field": PreparedStatementKeys.COUNT_OF,
|
|
331
|
+
"maxOf/:field": PreparedStatementKeys.MAX_OF,
|
|
332
|
+
"minOf/:field": PreparedStatementKeys.MIN_OF,
|
|
333
|
+
"avgOf/:field": PreparedStatementKeys.AVG_OF,
|
|
334
|
+
"sumOf/:field": PreparedStatementKeys.SUM_OF,
|
|
335
|
+
"distinctOf/:field": PreparedStatementKeys.DISTINCT_OF,
|
|
336
|
+
"groupOf/:field": PreparedStatementKeys.GROUP_OF,
|
|
337
|
+
};
|
|
338
|
+
const statementKey = statementRoutes[normalizedPath];
|
|
339
|
+
if (statementKey && method === "GET") {
|
|
340
|
+
return FromModelController.createRegistration(FromModelController.statementMethodName(normalizedPath), handler, FromModelController.statementShortcutDecorators(ModelConstr, modelClazzName, normalizedPath, statementKey), FromModelController.statementShortcutParams(normalizedPath));
|
|
341
|
+
}
|
|
342
|
+
if (method === "GET" && normalizedPath.startsWith("query/")) {
|
|
343
|
+
const queryMethod = normalizedPath.replace(/^query\//, "").split("/")[0];
|
|
344
|
+
return FromModelController.createRegistration(queryMethod, handler, FromModelController.getQueryDecorators(queryMethod, normalizedPath, "GET", true), FromModelController.complexQueryParams(normalizedPath));
|
|
345
|
+
}
|
|
346
|
+
const pathSegments = normalizedPath.split("/").filter(Boolean);
|
|
347
|
+
const knownPrefixes = new Set([
|
|
348
|
+
"listBy",
|
|
349
|
+
"findBy",
|
|
350
|
+
"findByPaginate",
|
|
351
|
+
"findOneBy",
|
|
352
|
+
"paginateBy",
|
|
353
|
+
"find",
|
|
354
|
+
"page",
|
|
355
|
+
"countOf",
|
|
356
|
+
"maxOf",
|
|
357
|
+
"minOf",
|
|
358
|
+
"avgOf",
|
|
359
|
+
"sumOf",
|
|
360
|
+
"distinctOf",
|
|
361
|
+
"groupOf",
|
|
362
|
+
"statement",
|
|
363
|
+
"bulk",
|
|
364
|
+
"query",
|
|
365
|
+
]);
|
|
366
|
+
if (pathSegments.length > 0 &&
|
|
367
|
+
!normalizedPath.startsWith("query/") &&
|
|
368
|
+
!normalizedPath.startsWith("statement/") &&
|
|
369
|
+
!knownPrefixes.has(pathSegments[0])) {
|
|
370
|
+
const apiPathParams = pathSegments
|
|
371
|
+
.filter((s) => s.startsWith(":"))
|
|
372
|
+
.map((s) => s.slice(1))
|
|
373
|
+
.map((name) => ({
|
|
374
|
+
name,
|
|
375
|
+
description: `${name} parameter for the query`,
|
|
376
|
+
required: true,
|
|
377
|
+
type: String,
|
|
378
|
+
}));
|
|
379
|
+
return FromModelController.createRegistration(methodName, handler, [
|
|
380
|
+
...apiPathParams.map((p) => ApiParam(p)),
|
|
381
|
+
ApiOperation({ summary: `Retrieve records using "${methodName}".` }),
|
|
382
|
+
ApiOkResponse({ description: "Result successfully retrieved." }),
|
|
383
|
+
ApiNoContentResponse({
|
|
384
|
+
description: "No content returned by the method.",
|
|
385
|
+
}),
|
|
386
|
+
], FromModelController.routeParamDecorators(normalizedPath));
|
|
387
|
+
}
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
static createRegistration(methodName, handler, decorators, paramDecorators) {
|
|
391
|
+
return { methodName, handler, decorators, paramDecorators };
|
|
392
|
+
}
|
|
393
|
+
static statementMethodName(path) {
|
|
394
|
+
const firstSegment = path.split("/")[0];
|
|
395
|
+
return firstSegment;
|
|
396
|
+
}
|
|
397
|
+
static statementShortcutParams(path) {
|
|
398
|
+
const segments = path.split("/").filter((s) => s.startsWith(":"));
|
|
399
|
+
const params = [];
|
|
400
|
+
segments.forEach((seg, i) => {
|
|
401
|
+
const name = seg.replace(":", "");
|
|
402
|
+
params.push({ decorator: Param(name), index: i });
|
|
403
|
+
});
|
|
404
|
+
if (path.startsWith("listBy/") ||
|
|
405
|
+
path.startsWith("paginateBy/") ||
|
|
406
|
+
path.startsWith("find/") ||
|
|
407
|
+
path.startsWith("page/")) {
|
|
408
|
+
params.push({ decorator: DecafQuery(), index: segments.length });
|
|
409
|
+
}
|
|
410
|
+
return params;
|
|
411
|
+
}
|
|
412
|
+
static complexQueryParams(path) {
|
|
413
|
+
const segments = path.split("/").filter((s) => s.startsWith(":"));
|
|
414
|
+
const params = [];
|
|
415
|
+
segments.forEach((seg, i) => {
|
|
416
|
+
const name = seg.replace(":", "");
|
|
417
|
+
params.push({ decorator: Param(name), index: i });
|
|
418
|
+
});
|
|
419
|
+
if (path.startsWith("query/")) {
|
|
420
|
+
params.push({ decorator: DecafQuery(), index: segments.length });
|
|
421
|
+
}
|
|
422
|
+
return params;
|
|
423
|
+
}
|
|
424
|
+
static createComplexQueryHandler(methodName) {
|
|
425
|
+
return async function complexQuery(...args) {
|
|
426
|
+
const log = this.log?.for?.(complexQuery);
|
|
427
|
+
try {
|
|
428
|
+
if (log)
|
|
429
|
+
log.debug(`Invoking custom query "${methodName}"`);
|
|
430
|
+
const { ctx } = (await this.logCtx([], methodName, true)).for(complexQuery);
|
|
431
|
+
const persistence = this.persistence(ctx);
|
|
432
|
+
const spreadArgs = FromModelController.normalizeQueryArgs(args);
|
|
433
|
+
if (persistence?.repo &&
|
|
434
|
+
typeof persistence.repo[methodName] === "function") {
|
|
435
|
+
return persistence.repo[methodName](...spreadArgs);
|
|
507
436
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
throw e;
|
|
437
|
+
if (typeof persistence[methodName] === "function") {
|
|
438
|
+
return persistence[methodName](...spreadArgs);
|
|
511
439
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
async deleteAll(ids, resp) {
|
|
516
|
-
const { ctx, log } = (await this.logCtx([], BulkCrudOperationKeys.DELETE_ALL, true)).for(this.deleteAll);
|
|
517
|
-
const normalizedIds = Array.isArray(ids) ? ids : [ids];
|
|
518
|
-
let read;
|
|
519
|
-
try {
|
|
520
|
-
log.debug(`deleting ${normalizedIds.length} ${modelClazzName}: ${normalizedIds}`);
|
|
521
|
-
read = await this.persistence(ctx).deleteAll(normalizedIds, ctx);
|
|
440
|
+
if (persistence?.query && typeof persistence.query === "function") {
|
|
441
|
+
return persistence.query(methodName, ...spreadArgs);
|
|
522
442
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
throw e;
|
|
443
|
+
if (typeof persistence.statement === "function") {
|
|
444
|
+
return persistence.statement(methodName, ...spreadArgs, ctx);
|
|
526
445
|
}
|
|
527
|
-
|
|
528
|
-
ctx.toResponse(resp);
|
|
529
|
-
return read;
|
|
446
|
+
throw new InternalError(`Persistence method "${methodName}" not found on ${persistence?.constructor?.name}`);
|
|
530
447
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
throw new ValidationError(`No ${this.pk} provided`);
|
|
536
|
-
let del;
|
|
537
|
-
try {
|
|
538
|
-
log.debug(`deleting ${modelClazzName} with ${this.pk} ${id}`);
|
|
539
|
-
del = await this.persistence(ctx).delete(id, ctx);
|
|
540
|
-
}
|
|
541
|
-
catch (e) {
|
|
542
|
-
log.error(`Failed to delete ${modelClazzName} with id ${id}`, e);
|
|
543
|
-
throw e;
|
|
544
|
-
}
|
|
545
|
-
log.info(`deleted ${modelClazzName} with id ${id}`);
|
|
546
|
-
ctx.toResponse(resp);
|
|
547
|
-
return del;
|
|
448
|
+
catch (e) {
|
|
449
|
+
if (log)
|
|
450
|
+
log.error(`Custom query "${methodName}" failed`, e);
|
|
451
|
+
throw FromModelController.toDecafError(e, `Custom query "${methodName}" failed`);
|
|
548
452
|
}
|
|
549
453
|
};
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}),
|
|
607
|
-
__param(0, Param("value")),
|
|
608
|
-
__param(1, DecafQuery()),
|
|
609
|
-
__metadata("design:type", Function),
|
|
610
|
-
__metadata("design:paramtypes", [String, Object]),
|
|
611
|
-
__metadata("design:returntype", Promise)
|
|
612
|
-
], DynamicModelController.prototype, "find", null);
|
|
613
|
-
__decorate([
|
|
614
|
-
ApiOperationFromModel(ModelConstr, "GET", "page/:value"),
|
|
615
|
-
ApiOperation({
|
|
616
|
-
summary: `Page ${modelClazzName} records using the default query attributes.`,
|
|
617
|
-
}),
|
|
618
|
-
ApiParam({
|
|
619
|
-
name: "value",
|
|
620
|
-
description: "The string to match against the default query attributes",
|
|
621
|
-
}),
|
|
622
|
-
ApiQuery({
|
|
623
|
-
name: "direction",
|
|
624
|
-
required: false,
|
|
625
|
-
enum: OrderDirection,
|
|
626
|
-
description: "the sort order for the paged results",
|
|
627
|
-
}),
|
|
628
|
-
ApiQuery({
|
|
629
|
-
name: "limit",
|
|
630
|
-
required: false,
|
|
631
|
-
description: "page size",
|
|
632
|
-
}),
|
|
633
|
-
ApiQuery({
|
|
634
|
-
name: "offset",
|
|
635
|
-
required: false,
|
|
636
|
-
description: "page number",
|
|
637
|
-
}),
|
|
638
|
-
ApiQuery({
|
|
639
|
-
name: "bookmark",
|
|
640
|
-
required: false,
|
|
641
|
-
description: "bookmark for cursor pagination",
|
|
642
|
-
}),
|
|
643
|
-
ApiOkResponse({
|
|
644
|
-
description: `${modelClazzName} records paged by the provided prefix.`,
|
|
645
|
-
}),
|
|
646
|
-
__param(0, Param("value")),
|
|
647
|
-
__param(1, DecafQuery()),
|
|
648
|
-
__metadata("design:type", Function),
|
|
649
|
-
__metadata("design:paramtypes", [String, Object]),
|
|
650
|
-
__metadata("design:returntype", Promise)
|
|
651
|
-
], DynamicModelController.prototype, "page", null);
|
|
652
|
-
__decorate([
|
|
653
|
-
ApiOperationFromModel(ModelConstr, "GET", "findOneBy/:key"),
|
|
654
|
-
ApiOperation({ summary: `Retrieve ${modelClazzName} records by query.` }),
|
|
655
|
-
ApiParam({ name: "key", description: "the model key to sort by" }),
|
|
656
|
-
ApiOkResponse({
|
|
657
|
-
description: `${modelClazzName} listed found.`,
|
|
658
|
-
}),
|
|
659
|
-
ApiNotFoundResponse({
|
|
660
|
-
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
661
|
-
}),
|
|
662
|
-
__param(0, Param("key")),
|
|
663
|
-
__param(1, Param("value")),
|
|
664
|
-
__metadata("design:type", Function),
|
|
665
|
-
__metadata("design:paramtypes", [String, Object]),
|
|
666
|
-
__metadata("design:returntype", Promise)
|
|
667
|
-
], DynamicModelController.prototype, "findOneBy", null);
|
|
668
|
-
__decorate([
|
|
669
|
-
ApiOperationFromModel(ModelConstr, "GET", "findBy/:key/:value"),
|
|
670
|
-
ApiOperation({ summary: `Retrieve ${modelClazzName} records by query.` }),
|
|
671
|
-
ApiParam({ name: "key", description: "the model key to compare" }),
|
|
672
|
-
ApiParam({ name: "value", description: "the value to match" }),
|
|
673
|
-
ApiQuery({
|
|
674
|
-
name: "direction",
|
|
675
|
-
required: true,
|
|
676
|
-
enum: OrderDirection,
|
|
677
|
-
description: "the sort order when applicable",
|
|
678
|
-
}),
|
|
679
|
-
ApiOkResponse({
|
|
680
|
-
description: `${modelClazzName} listed found.`,
|
|
681
|
-
}),
|
|
682
|
-
ApiNotFoundResponse({
|
|
683
|
-
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
684
|
-
}),
|
|
685
|
-
__param(0, Param("key")),
|
|
686
|
-
__param(1, Param("value")),
|
|
687
|
-
__param(2, DecafQuery()),
|
|
688
|
-
__metadata("design:type", Function),
|
|
689
|
-
__metadata("design:paramtypes", [String, Object, Object]),
|
|
690
|
-
__metadata("design:returntype", Promise)
|
|
691
|
-
], DynamicModelController.prototype, "findBy", null);
|
|
692
|
-
__decorate([
|
|
693
|
-
ApiOperationFromModel(ModelConstr, "GET", "statement/:method/*args"),
|
|
694
|
-
ApiOperation({
|
|
695
|
-
summary: `Executes a prepared statement on ${modelClazzName}.`,
|
|
696
|
-
}),
|
|
697
|
-
ApiParam({
|
|
698
|
-
name: "method",
|
|
699
|
-
description: "the prepared statement to execute",
|
|
700
|
-
}),
|
|
701
|
-
ApiParam({
|
|
702
|
-
name: "args",
|
|
703
|
-
description: "concatenated list of arguments the prepared statement can accept",
|
|
704
|
-
}),
|
|
705
|
-
ApiQuery({
|
|
706
|
-
name: "direction",
|
|
707
|
-
required: true,
|
|
708
|
-
enum: OrderDirection,
|
|
709
|
-
description: "the sort order when applicable",
|
|
710
|
-
}),
|
|
711
|
-
ApiQuery({
|
|
712
|
-
name: "limit",
|
|
713
|
-
required: true,
|
|
714
|
-
description: "limit or page size when applicable",
|
|
715
|
-
}),
|
|
716
|
-
ApiQuery({
|
|
717
|
-
name: "offset",
|
|
718
|
-
required: true,
|
|
719
|
-
description: "offset or bookmark when applicable",
|
|
720
|
-
}),
|
|
721
|
-
ApiOkResponse({
|
|
722
|
-
description: `${modelClazzName} listed found.`,
|
|
723
|
-
}),
|
|
724
|
-
ApiNotFoundResponse({
|
|
725
|
-
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
726
|
-
}),
|
|
727
|
-
__param(0, Param("method")),
|
|
728
|
-
__param(1, Param("args")),
|
|
729
|
-
__param(2, DecafQuery()),
|
|
730
|
-
__metadata("design:type", Function),
|
|
731
|
-
__metadata("design:paramtypes", [String, Array, Object]),
|
|
732
|
-
__metadata("design:returntype", Promise)
|
|
733
|
-
], DynamicModelController.prototype, "statement", null);
|
|
734
|
-
__decorate([
|
|
735
|
-
ApiOperationFromModel(ModelConstr, "POST", "bulk"),
|
|
454
|
+
}
|
|
455
|
+
static extractQueryArgs(args) {
|
|
456
|
+
if (args.length === 0)
|
|
457
|
+
return args;
|
|
458
|
+
const last = args[args.length - 1];
|
|
459
|
+
if (last && typeof last === "object" && !Array.isArray(last)) {
|
|
460
|
+
const queryObj = args.pop();
|
|
461
|
+
const hasDirection = queryObj.direction !== undefined;
|
|
462
|
+
const hasLimit = queryObj.limit !== undefined;
|
|
463
|
+
const hasOffset = queryObj.offset !== undefined;
|
|
464
|
+
if (!hasDirection && !hasLimit && !hasOffset)
|
|
465
|
+
return args;
|
|
466
|
+
const extras = [];
|
|
467
|
+
if (hasDirection)
|
|
468
|
+
extras.push(queryObj.direction);
|
|
469
|
+
else if (hasLimit || hasOffset)
|
|
470
|
+
extras.push(undefined);
|
|
471
|
+
if (hasLimit)
|
|
472
|
+
extras.push(queryObj.limit);
|
|
473
|
+
if (hasOffset)
|
|
474
|
+
extras.push(queryObj.offset);
|
|
475
|
+
return [...args, ...extras];
|
|
476
|
+
}
|
|
477
|
+
return args;
|
|
478
|
+
}
|
|
479
|
+
static normalizeQueryArgs(args) {
|
|
480
|
+
const normalized = FromModelController.extractQueryArgs(args);
|
|
481
|
+
if (normalized.length === 0)
|
|
482
|
+
return normalized;
|
|
483
|
+
const last = normalized[normalized.length - 1];
|
|
484
|
+
if (!last || typeof last !== "object" || Array.isArray(last)) {
|
|
485
|
+
return normalized;
|
|
486
|
+
}
|
|
487
|
+
const queryObj = last;
|
|
488
|
+
const hasQueryFields = queryObj.direction !== undefined ||
|
|
489
|
+
queryObj.limit !== undefined ||
|
|
490
|
+
queryObj.offset !== undefined ||
|
|
491
|
+
queryObj.bookmark !== undefined;
|
|
492
|
+
if (!hasQueryFields) {
|
|
493
|
+
normalized.pop();
|
|
494
|
+
return normalized;
|
|
495
|
+
}
|
|
496
|
+
normalized.pop();
|
|
497
|
+
if (queryObj.direction !== undefined)
|
|
498
|
+
normalized.push(queryObj.direction);
|
|
499
|
+
if (queryObj.limit !== undefined)
|
|
500
|
+
normalized.push(queryObj.limit);
|
|
501
|
+
if (queryObj.offset !== undefined)
|
|
502
|
+
normalized.push(queryObj.offset);
|
|
503
|
+
if (queryObj.bookmark !== undefined)
|
|
504
|
+
normalized.push(queryObj.bookmark);
|
|
505
|
+
return normalized;
|
|
506
|
+
}
|
|
507
|
+
static createCreateDecorators(ModelConstr, modelClazzName) {
|
|
508
|
+
return [
|
|
509
|
+
ApiOperationFromModel(ModelConstr, "POST"),
|
|
736
510
|
ApiOperation({ summary: `Create a new ${modelClazzName}.` }),
|
|
737
511
|
ApiBody({
|
|
738
512
|
description: `Payload for ${modelClazzName}`,
|
|
739
|
-
|
|
740
|
-
type: "array",
|
|
741
|
-
items: {
|
|
742
|
-
$ref: getSchemaPath(ModelConstr),
|
|
743
|
-
// $ref: getSchemaPath(DtoFor(OperationKeys.CREATE, ModelConstr)),
|
|
744
|
-
},
|
|
745
|
-
},
|
|
513
|
+
type: DtoFor(OperationKeys.CREATE, ModelConstr),
|
|
746
514
|
}),
|
|
747
515
|
ApiCreatedResponse({
|
|
748
516
|
description: `${modelClazzName} created successfully.`,
|
|
749
|
-
schema: {
|
|
750
|
-
type: "array",
|
|
751
|
-
items: {
|
|
752
|
-
$ref: getSchemaPath(ModelConstr),
|
|
753
|
-
},
|
|
754
|
-
},
|
|
517
|
+
schema: { $ref: getSchemaPath(ModelConstr) },
|
|
755
518
|
}),
|
|
756
519
|
ApiBadRequestResponse({ description: "Payload validation failed." }),
|
|
757
520
|
ApiUnprocessableEntityResponse({
|
|
758
521
|
description: "Repository rejected the provided payload.",
|
|
759
522
|
}),
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
], DynamicModelController.prototype, "createAll", null);
|
|
766
|
-
__decorate([
|
|
767
|
-
ApiOperationFromModel(ModelConstr, "POST"),
|
|
523
|
+
];
|
|
524
|
+
}
|
|
525
|
+
static bulkCreateDecorators(ModelConstr, modelClazzName) {
|
|
526
|
+
return [
|
|
527
|
+
ApiOperationFromModel(ModelConstr, "POST", "bulk"),
|
|
768
528
|
ApiOperation({ summary: `Create a new ${modelClazzName}.` }),
|
|
769
529
|
ApiBody({
|
|
770
530
|
description: `Payload for ${modelClazzName}`,
|
|
771
|
-
|
|
531
|
+
schema: {
|
|
532
|
+
type: "array",
|
|
533
|
+
items: { $ref: getSchemaPath(ModelConstr) },
|
|
534
|
+
},
|
|
772
535
|
}),
|
|
773
536
|
ApiCreatedResponse({
|
|
774
537
|
description: `${modelClazzName} created successfully.`,
|
|
775
538
|
schema: {
|
|
776
|
-
|
|
539
|
+
type: "array",
|
|
540
|
+
items: { $ref: getSchemaPath(ModelConstr) },
|
|
777
541
|
},
|
|
778
542
|
}),
|
|
779
543
|
ApiBadRequestResponse({ description: "Payload validation failed." }),
|
|
780
544
|
ApiUnprocessableEntityResponse({
|
|
781
545
|
description: "Repository rejected the provided payload.",
|
|
782
546
|
}),
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
__metadata("design:returntype", Promise)
|
|
788
|
-
], DynamicModelController.prototype, "create", null);
|
|
789
|
-
__decorate([
|
|
547
|
+
];
|
|
548
|
+
}
|
|
549
|
+
static bulkReadDecorators(ModelConstr, modelClazzName) {
|
|
550
|
+
return [
|
|
790
551
|
ApiOperationFromModel(ModelConstr, "GET", "bulk"),
|
|
791
|
-
ApiOperation({ summary: `Retrieve
|
|
552
|
+
ApiOperation({ summary: `Retrieve ${modelClazzName} records by ids.` }),
|
|
792
553
|
ApiQuery({ name: "ids", required: true, type: "array" }),
|
|
793
554
|
ApiOkResponse({
|
|
794
555
|
description: `${modelClazzName} retrieved successfully.`,
|
|
795
556
|
schema: {
|
|
796
557
|
type: "array",
|
|
797
|
-
items: {
|
|
798
|
-
$ref: getSchemaPath(ModelConstr),
|
|
799
|
-
},
|
|
800
|
-
},
|
|
801
|
-
}),
|
|
802
|
-
ApiNotFoundResponse({
|
|
803
|
-
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
804
|
-
}),
|
|
805
|
-
__param(0, Query("ids")),
|
|
806
|
-
__metadata("design:type", Function),
|
|
807
|
-
__metadata("design:paramtypes", [Array]),
|
|
808
|
-
__metadata("design:returntype", Promise)
|
|
809
|
-
], DynamicModelController.prototype, "readAll", null);
|
|
810
|
-
__decorate([
|
|
811
|
-
ApiOperationFromModel(ModelConstr, "GET", path),
|
|
812
|
-
ApiParamsFromModel(apiProperties),
|
|
813
|
-
ApiOperation({ summary: `Retrieve a ${modelClazzName} record by id.` }),
|
|
814
|
-
ApiOkResponse({
|
|
815
|
-
description: `${modelClazzName} retrieved successfully.`,
|
|
816
|
-
schema: {
|
|
817
|
-
$ref: getSchemaPath(ModelConstr),
|
|
558
|
+
items: { $ref: getSchemaPath(ModelConstr) },
|
|
818
559
|
},
|
|
819
560
|
}),
|
|
820
561
|
ApiNotFoundResponse({
|
|
821
562
|
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
822
563
|
}),
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
__decorate([
|
|
829
|
-
ApiOperationFromModel(ModelConstr, "PUT", `bulk`),
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
static bulkUpdateDecorators(ModelConstr, modelClazzName, apiProperties) {
|
|
567
|
+
return [
|
|
568
|
+
ApiOperationFromModel(ModelConstr, "PUT", "bulk"),
|
|
830
569
|
ApiParamsFromModel(apiProperties),
|
|
831
570
|
ApiOperation({
|
|
832
|
-
summary: `Replace
|
|
571
|
+
summary: `Replace existing ${modelClazzName} records with new payloads.`,
|
|
833
572
|
}),
|
|
834
573
|
ApiBody({
|
|
835
|
-
description: `Payload for
|
|
574
|
+
description: `Payload for replacing existing records of ${modelClazzName}`,
|
|
836
575
|
schema: {
|
|
837
576
|
type: "array",
|
|
838
577
|
$ref: getSchemaPath(DtoFor(OperationKeys.UPDATE, ModelConstr)),
|
|
@@ -842,123 +581,210 @@ export class FromModelController {
|
|
|
842
581
|
description: `${modelClazzName} updated successfully.`,
|
|
843
582
|
schema: {
|
|
844
583
|
type: "array",
|
|
845
|
-
items: {
|
|
846
|
-
$ref: getSchemaPath(ModelConstr),
|
|
847
|
-
},
|
|
584
|
+
items: { $ref: getSchemaPath(ModelConstr) },
|
|
848
585
|
},
|
|
849
586
|
}),
|
|
850
587
|
ApiNotFoundResponse({
|
|
851
588
|
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
852
589
|
}),
|
|
853
590
|
ApiBadRequestResponse({ description: "Payload validation failed." }),
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
591
|
+
];
|
|
592
|
+
}
|
|
593
|
+
static bulkDeleteDecorators(ModelConstr, modelClazzName, apiProperties) {
|
|
594
|
+
return [
|
|
595
|
+
ApiOperationFromModel(ModelConstr, "DELETE", "bulk"),
|
|
596
|
+
ApiParamsFromModel(apiProperties),
|
|
597
|
+
ApiOperation({ summary: `Delete ${modelClazzName} records by ids.` }),
|
|
598
|
+
ApiQuery({ name: "ids", required: true, type: "array" }),
|
|
599
|
+
ApiOkResponse({
|
|
600
|
+
description: `${modelClazzName} deleted successfully.`,
|
|
601
|
+
schema: {
|
|
602
|
+
type: "array",
|
|
603
|
+
items: { $ref: getSchemaPath(ModelConstr) },
|
|
604
|
+
},
|
|
605
|
+
}),
|
|
606
|
+
ApiNotFoundResponse({
|
|
607
|
+
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
608
|
+
}),
|
|
609
|
+
];
|
|
610
|
+
}
|
|
611
|
+
static readDecorators(ModelConstr, modelClazzName, apiProperties, pkPath) {
|
|
612
|
+
return [
|
|
613
|
+
ApiOperationFromModel(ModelConstr, "GET", pkPath),
|
|
614
|
+
ApiParamsFromModel(apiProperties),
|
|
615
|
+
ApiOperation({ summary: `Retrieve a ${modelClazzName} record by id.` }),
|
|
616
|
+
ApiOkResponse({
|
|
617
|
+
description: `${modelClazzName} retrieved successfully.`,
|
|
618
|
+
schema: { $ref: getSchemaPath(ModelConstr) },
|
|
619
|
+
}),
|
|
620
|
+
ApiNotFoundResponse({
|
|
621
|
+
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
622
|
+
}),
|
|
623
|
+
];
|
|
624
|
+
}
|
|
625
|
+
static updateDecorators(ModelConstr, modelClazzName, apiProperties, pkPath) {
|
|
626
|
+
return [
|
|
627
|
+
ApiOperationFromModel(ModelConstr, "PUT", pkPath),
|
|
862
628
|
ApiParamsFromModel(apiProperties),
|
|
863
629
|
ApiOperation({
|
|
864
630
|
summary: `Replace an existing ${modelClazzName} record with a new payload.`,
|
|
865
631
|
}),
|
|
866
632
|
ApiBody({
|
|
867
|
-
description: `Payload for
|
|
633
|
+
description: `Payload for replacing an existing record of ${modelClazzName}`,
|
|
868
634
|
type: DtoFor(OperationKeys.UPDATE, ModelConstr),
|
|
869
635
|
}),
|
|
870
636
|
ApiOkResponse({
|
|
871
637
|
description: `${modelClazzName} updated successfully.`,
|
|
872
|
-
schema: {
|
|
873
|
-
$ref: getSchemaPath(ModelConstr),
|
|
874
|
-
},
|
|
638
|
+
schema: { $ref: getSchemaPath(ModelConstr) },
|
|
875
639
|
}),
|
|
876
640
|
ApiNotFoundResponse({
|
|
877
641
|
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
878
642
|
}),
|
|
879
643
|
ApiBadRequestResponse({ description: "Payload validation failed." }),
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
__metadata("design:returntype", Promise)
|
|
886
|
-
], DynamicModelController.prototype, "update", null);
|
|
887
|
-
__decorate([
|
|
888
|
-
ApiOperationFromModel(ModelConstr, "DELETE", "bulk"),
|
|
644
|
+
];
|
|
645
|
+
}
|
|
646
|
+
static deleteDecorators(ModelConstr, modelClazzName, apiProperties, pkPath) {
|
|
647
|
+
return [
|
|
648
|
+
ApiOperationFromModel(ModelConstr, "DELETE", pkPath),
|
|
889
649
|
ApiParamsFromModel(apiProperties),
|
|
890
|
-
ApiOperation({ summary: `
|
|
891
|
-
ApiQuery({ name: "ids", required: true, type: "array" }),
|
|
650
|
+
ApiOperation({ summary: `Delete a ${modelClazzName} record by id.` }),
|
|
892
651
|
ApiOkResponse({
|
|
893
652
|
description: `${modelClazzName} deleted successfully.`,
|
|
894
|
-
schema: {
|
|
895
|
-
type: "array",
|
|
896
|
-
items: {
|
|
897
|
-
$ref: getSchemaPath(ModelConstr),
|
|
898
|
-
},
|
|
899
|
-
},
|
|
653
|
+
schema: { $ref: getSchemaPath(ModelConstr) },
|
|
900
654
|
}),
|
|
901
655
|
ApiNotFoundResponse({
|
|
902
656
|
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
903
657
|
}),
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
658
|
+
];
|
|
659
|
+
}
|
|
660
|
+
static statementDecorators(ModelConstr, modelClazzName) {
|
|
661
|
+
return [
|
|
662
|
+
ApiOperationFromModel(ModelConstr, "GET", "statement/:method/*args"),
|
|
663
|
+
ApiOperation({
|
|
664
|
+
summary: `Executes a prepared statement on ${modelClazzName}.`,
|
|
665
|
+
}),
|
|
666
|
+
ApiParam({
|
|
667
|
+
name: "method",
|
|
668
|
+
description: "the prepared statement to execute",
|
|
669
|
+
}),
|
|
670
|
+
ApiParam({
|
|
671
|
+
name: "args",
|
|
672
|
+
description: "concatenated list of arguments the prepared statement can accept",
|
|
673
|
+
}),
|
|
674
|
+
ApiQuery({
|
|
675
|
+
name: "direction",
|
|
676
|
+
required: true,
|
|
677
|
+
enum: OrderDirection,
|
|
678
|
+
description: "the sort order when applicable",
|
|
919
679
|
}),
|
|
680
|
+
ApiQuery({
|
|
681
|
+
name: "limit",
|
|
682
|
+
required: true,
|
|
683
|
+
description: "limit or page size when applicable",
|
|
684
|
+
}),
|
|
685
|
+
ApiQuery({
|
|
686
|
+
name: "offset",
|
|
687
|
+
required: true,
|
|
688
|
+
description: "offset or bookmark when applicable",
|
|
689
|
+
}),
|
|
690
|
+
ApiOkResponse({ description: `${modelClazzName} listed found.` }),
|
|
920
691
|
ApiNotFoundResponse({
|
|
921
692
|
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
922
693
|
}),
|
|
923
|
-
|
|
924
|
-
__param(1, Response({ passthrough: true })),
|
|
925
|
-
__metadata("design:type", Function),
|
|
926
|
-
__metadata("design:paramtypes", [Object, Object]),
|
|
927
|
-
__metadata("design:returntype", Promise)
|
|
928
|
-
], DynamicModelController.prototype, "delete", null);
|
|
929
|
-
DynamicModelController = __decorate([
|
|
930
|
-
Controller(routePath),
|
|
931
|
-
ApiTags(modelClazzName),
|
|
932
|
-
ApiExtraModels(ModelConstr),
|
|
933
|
-
Auth(ModelConstr),
|
|
934
|
-
__metadata("design:paramtypes", [DecafRequestContext])
|
|
935
|
-
], DynamicModelController);
|
|
936
|
-
return DynamicModelController;
|
|
694
|
+
];
|
|
937
695
|
}
|
|
938
|
-
static
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
name: key,
|
|
951
|
-
description: Metadata.description(ModelClazz, key),
|
|
952
|
-
required: true,
|
|
953
|
-
type: String,
|
|
954
|
-
};
|
|
696
|
+
static statementShortcutDecorators(ModelConstr, modelClazzName, path, statementKey) {
|
|
697
|
+
const base = [
|
|
698
|
+
ApiOperationFromModel(ModelConstr, "GET", path),
|
|
699
|
+
ApiOperation({ summary: `Retrieve ${modelClazzName} records.` }),
|
|
700
|
+
ApiOkResponse({
|
|
701
|
+
description: `${modelClazzName} retrieved successfully.`,
|
|
702
|
+
}),
|
|
703
|
+
];
|
|
704
|
+
const segments = path.split("/").filter((s) => s.startsWith(":"));
|
|
705
|
+
segments.forEach((seg) => {
|
|
706
|
+
const name = seg.replace(":", "");
|
|
707
|
+
base.push(ApiParam({ name, description: `The ${name} parameter` }));
|
|
955
708
|
});
|
|
956
|
-
|
|
957
|
-
path
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
709
|
+
if (path.startsWith("listBy/") ||
|
|
710
|
+
path.startsWith("paginateBy/") ||
|
|
711
|
+
path.startsWith("find/") ||
|
|
712
|
+
path.startsWith("page/")) {
|
|
713
|
+
base.push(ApiQuery({
|
|
714
|
+
name: "direction",
|
|
715
|
+
required: true,
|
|
716
|
+
enum: OrderDirection,
|
|
717
|
+
description: "the sort order",
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
if (path.startsWith("paginateBy/") || path.startsWith("page/")) {
|
|
721
|
+
base.push(ApiQuery({ name: "limit", required: false, description: "page size" }), ApiQuery({
|
|
722
|
+
name: "offset",
|
|
723
|
+
required: false,
|
|
724
|
+
description: "page number",
|
|
725
|
+
}), ApiQuery({
|
|
726
|
+
name: "bookmark",
|
|
727
|
+
required: false,
|
|
728
|
+
description: "bookmark for cursor pagination",
|
|
729
|
+
}));
|
|
730
|
+
}
|
|
731
|
+
if (path.startsWith("findOneBy/") || path.startsWith("findBy/")) {
|
|
732
|
+
base.push(ApiNotFoundResponse({
|
|
733
|
+
description: `No ${modelClazzName} record matches the provided identifier.`,
|
|
734
|
+
}));
|
|
735
|
+
}
|
|
736
|
+
if (statementKey === PreparedStatementKeys.COUNT_OF ||
|
|
737
|
+
statementKey === PreparedStatementKeys.AVG_OF ||
|
|
738
|
+
statementKey === PreparedStatementKeys.SUM_OF) {
|
|
739
|
+
base.push(ApiOkResponse({
|
|
740
|
+
description: `Result for ${modelClazzName}.`,
|
|
741
|
+
type: Number,
|
|
742
|
+
}));
|
|
743
|
+
}
|
|
744
|
+
if (statementKey === PreparedStatementKeys.DISTINCT_OF) {
|
|
745
|
+
base.push(ApiOkResponse({
|
|
746
|
+
description: `Distinct values for ${modelClazzName}.`,
|
|
747
|
+
type: [String],
|
|
748
|
+
}));
|
|
749
|
+
}
|
|
750
|
+
return base;
|
|
751
|
+
}
|
|
752
|
+
static getQueryDecorators(methodName, routePath, httpVerb, includeQueryParams = false) {
|
|
753
|
+
const extractPathParams = (p) => p
|
|
754
|
+
.split("/")
|
|
755
|
+
.filter((s) => s.startsWith(":"))
|
|
756
|
+
.map((s) => s.slice(1));
|
|
757
|
+
const apiPathParams = extractPathParams(routePath).map((name) => ({
|
|
758
|
+
name,
|
|
759
|
+
description: `${name} parameter for the query`,
|
|
760
|
+
required: true,
|
|
761
|
+
type: String,
|
|
762
|
+
}));
|
|
763
|
+
const decorators = [
|
|
764
|
+
...apiPathParams.map((p) => ApiParam(p)),
|
|
765
|
+
ApiOperation({ summary: `Retrieve records using "${methodName}".` }),
|
|
766
|
+
ApiOkResponse({ description: "Result successfully retrieved." }),
|
|
767
|
+
ApiNoContentResponse({
|
|
768
|
+
description: "No content returned by the method.",
|
|
769
|
+
}),
|
|
770
|
+
];
|
|
771
|
+
if (httpVerb === "GET" && includeQueryParams) {
|
|
772
|
+
decorators.push(ApiQuery({
|
|
773
|
+
name: "direction",
|
|
774
|
+
required: false,
|
|
775
|
+
enum: OrderDirection,
|
|
776
|
+
description: "the sort order when applicable",
|
|
777
|
+
}), ApiQuery({
|
|
778
|
+
name: "limit",
|
|
779
|
+
required: false,
|
|
780
|
+
description: "limit or page size",
|
|
781
|
+
}), ApiQuery({
|
|
782
|
+
name: "offset",
|
|
783
|
+
required: false,
|
|
784
|
+
description: "offset or bookmark",
|
|
785
|
+
}));
|
|
786
|
+
}
|
|
787
|
+
return decorators;
|
|
962
788
|
}
|
|
963
789
|
}
|
|
964
790
|
//# sourceMappingURL=FromModelController.js.map
|