@loopback/rest-crud 0.11.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/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) IBM Corp. 2019.
2
+ Node module: @loopback/rest-crud
3
+ This project is licensed under the MIT License, full text below.
4
+
5
+ --------
6
+
7
+ MIT license
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in
17
+ all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # @loopback/rest-crud
2
+
3
+ REST API controller implementing default CRUD semantics.
4
+
5
+ ## Overview
6
+
7
+ This module allows applications to quickly expose models via REST API without
8
+ having to implement custom controller or repository classes.
9
+
10
+ ## Installation
11
+
12
+ ```sh
13
+ npm install --save @loopback/rest-crud
14
+ ```
15
+
16
+ ## Basic use
17
+
18
+ `@loopback/rest-crud` can be used along with the built-in `ModelApiBooter` to
19
+ easily create a repository class and a controller class for your model. The
20
+ following use is a simple approach for this creation, however, you can look at
21
+ the "Advanced use" section instead for a more flexible approach.
22
+
23
+ For the examples in the following sections, we are assuming a model named
24
+ `Product` and a datasource named `db` have already been created.
25
+
26
+ In your `src/application.ts` file:
27
+
28
+ ```ts
29
+ // add the following import
30
+ import {CrudRestComponent} from '@loopback/rest-crud';
31
+
32
+ export class TryApplication extends BootMixin(
33
+ ServiceMixin(RepositoryMixin(RestApplication)),
34
+ ) {
35
+ constructor(options: ApplicationConfig = {}) {
36
+ // other code
37
+
38
+ // add the following line
39
+ this.component(CrudRestComponent);
40
+ }
41
+ }
42
+ ```
43
+
44
+ Create a new file for the configuration, e.g.
45
+ `src/model-endpoints/product.rest-config.ts` that defines the `model`,
46
+ `pattern`, `dataSource`, and `basePath` properties:
47
+
48
+ ```ts
49
+ import {ModelCrudRestApiConfig} from '@loopback/rest-crud';
50
+ import {Product} from '../models';
51
+
52
+ module.exports = <ModelCrudRestApiConfig>{
53
+ model: Product,
54
+ pattern: 'CrudRest', // make sure to use this pattern
55
+ dataSource: 'db',
56
+ basePath: '/products',
57
+ };
58
+ ```
59
+
60
+ Now your `Product` model will have a default repository and default controller
61
+ class defined without the need for a repository or controller class file.
62
+
63
+ ## Advanced use
64
+
65
+ If you would like more flexibility, e.g. if you would only like to define a
66
+ default `CrudRest` controller or repository, you can use the two helper methods
67
+ (`defineCrudRestController` from `@loopback/rest-crud` and
68
+ `defineCrudRepositoryClass` from `@loopback/repository`). These functions will
69
+ help you create controllers and repositories using code.
70
+
71
+ For the examples in the following sections, we are also assuming a model named
72
+ `Product`, and a datasource named `db` have already been created.
73
+
74
+ ### Creating a CRUD Controller
75
+
76
+ Here is how you would use `defineCrudRestController` for exposing the CRUD
77
+ endpoints of an existing model with a respository.
78
+
79
+ 1. Create a REST CRUD controller class for your model.
80
+
81
+ ```ts
82
+ const ProductController = defineCrudRestController<
83
+ Product,
84
+ typeof Product.prototype.id,
85
+ 'id'
86
+ >(Product, {basePath: '/products'});
87
+ ```
88
+
89
+ 2. Set up dependency injection for the `ProductController`.
90
+
91
+ ```ts
92
+ inject('repositories.ProductRepository')(ProductController, undefined, 0);
93
+ ```
94
+
95
+ 3. Register the controller with your application.
96
+
97
+ ```ts
98
+ app.controller(ProductController);
99
+ ```
100
+
101
+ ### Creating a CRUD repository
102
+
103
+ Use the `defineCrudRepositoryClass` method to create named repositories (based
104
+ on the Model) for your app.
105
+
106
+ Usage example:
107
+
108
+ ```ts
109
+ import {defineCrudRepositoryClass} from '@loopback/repository';
110
+
111
+ const ProductRepository = defineCrudRepositoryClass(Product);
112
+ this.repository(ProductRepository);
113
+ inject('datasources.db')(ProductRepository, undefined, 0);
114
+ ```
115
+
116
+ ### Integrated example
117
+
118
+ Here is an example of an app which uses `defineCrudRepositoryClass` and
119
+ `defineCrudRestController` to fulfill its repository and controller
120
+ requirements.
121
+
122
+ ```ts
123
+ import {defineCrudRepositoryClass} from '@loopback/repository';
124
+
125
+ export class TryApplication extends BootMixin(
126
+ ServiceMixin(RepositoryMixin(RestApplication)),
127
+ ) {
128
+ constructor(options: ApplicationConfig = {}) {
129
+ // ...
130
+ }
131
+
132
+ async boot(): Promise<void> {
133
+ await super.boot();
134
+
135
+ const ProductRepository = defineCrudRepositoryClass(Product);
136
+ const repoBinding = this.repository(ProductRepository);
137
+
138
+ inject('datasources.db')(ProductRepository, undefined, 0);
139
+
140
+ const ProductController = defineCrudRestController<
141
+ Product,
142
+ typeof Product.prototype.id,
143
+ 'id'
144
+ >(Product, {basePath: '/products'});
145
+
146
+ inject(repoBinding.key)(ProductController, undefined, 0);
147
+ this.controller(ProductController);
148
+ }
149
+ }
150
+ ```
151
+
152
+ ## Contributions
153
+
154
+ - [Guidelines](https://github.com/loopbackio/loopback-next/blob/master/docs/CONTRIBUTING.md)
155
+ - [Join the team](https://github.com/loopbackio/loopback-next/issues/110)
156
+
157
+ ## Tests
158
+
159
+ Run `npm test` from the root folder.
160
+
161
+ ## Contributors
162
+
163
+ See
164
+ [all contributors](https://github.com/loopbackio/loopback-next/graphs/contributors).
165
+
166
+ ## License
167
+
168
+ MIT
@@ -0,0 +1,12 @@
1
+ import { ModelApiBuilder, ModelApiConfig } from '@loopback/model-api-builder';
2
+ import { ApplicationWithRepositories } from '@loopback/repository';
3
+ import { Model } from '@loopback/rest';
4
+ export interface ModelCrudRestApiConfig extends ModelApiConfig {
5
+ basePath: string;
6
+ }
7
+ export declare class CrudRestApiBuilder implements ModelApiBuilder {
8
+ readonly pattern: string;
9
+ build(application: ApplicationWithRepositories, modelClass: typeof Model & {
10
+ prototype: Model;
11
+ }, cfg: ModelApiConfig): Promise<void>;
12
+ }
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2020. All Rights Reserved.
3
+ // Node module: @loopback/rest-crud
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CrudRestApiBuilder = void 0;
8
+ const tslib_1 = require("tslib");
9
+ const core_1 = require("@loopback/core");
10
+ const model_api_builder_1 = require("@loopback/model-api-builder");
11
+ const repository_1 = require("@loopback/repository");
12
+ const debug_1 = tslib_1.__importDefault(require("debug"));
13
+ const _1 = require(".");
14
+ const debug = debug_1.default('loopback:boot:crud-rest');
15
+ let CrudRestApiBuilder = class CrudRestApiBuilder {
16
+ constructor() {
17
+ this.pattern = 'CrudRest';
18
+ }
19
+ build(application, modelClass, cfg) {
20
+ const modelName = modelClass.name;
21
+ const config = cfg;
22
+ if (!config.basePath) {
23
+ throw new Error(`Missing required field "basePath" in configuration for model ${modelName}.`);
24
+ }
25
+ if (!(modelClass.prototype instanceof repository_1.Entity)) {
26
+ throw new Error(`CrudRestController requires a model that extends 'Entity'. (Model name ${modelName} does not extend 'Entity')`);
27
+ }
28
+ const entityClass = modelClass;
29
+ let repoBindingName = `repositories.${entityClass.name}Repository`;
30
+ if (application.isBound(repoBindingName)) {
31
+ debug('Using the existing Repository binding %j', repoBindingName);
32
+ }
33
+ else {
34
+ // repository class does not exist
35
+ const repositoryClass = setupCrudRepository(entityClass, config);
36
+ application.repository(repositoryClass);
37
+ repoBindingName = repositoryClass.name;
38
+ debug('Registered repository class', repoBindingName);
39
+ }
40
+ const controllerClass = setupCrudRestController(entityClass, config);
41
+ application.controller(controllerClass);
42
+ debug('Registered controller class', controllerClass.name);
43
+ return Promise.resolve();
44
+ }
45
+ };
46
+ CrudRestApiBuilder = tslib_1.__decorate([
47
+ core_1.injectable(model_api_builder_1.asModelApiBuilder)
48
+ ], CrudRestApiBuilder);
49
+ exports.CrudRestApiBuilder = CrudRestApiBuilder;
50
+ /**
51
+ * Set up a CRUD Repository class for the given Entity class.
52
+ *
53
+ * @param entityClass - the Entity class the repository is built for
54
+ * @param config - configuration of the Entity class
55
+ */
56
+ function setupCrudRepository(entityClass, config) {
57
+ const repositoryClass = repository_1.defineCrudRepositoryClass(entityClass);
58
+ injectFirstConstructorArg(repositoryClass, `datasources.${config.dataSource}`);
59
+ return repositoryClass;
60
+ }
61
+ /**
62
+ * Set up a CRUD Controller class for the given Entity class.
63
+ *
64
+ * @param entityClass - the Entity class the controller is built for
65
+ * @param config - configuration of the Entity class
66
+ */
67
+ function setupCrudRestController(entityClass, config) {
68
+ const controllerClass = _1.defineCrudRestController(entityClass,
69
+ // important - forward the entire config object to allow controller
70
+ // factories to accept additional (custom) config options
71
+ config);
72
+ injectFirstConstructorArg(controllerClass, `repositories.${entityClass.name}Repository`);
73
+ return controllerClass;
74
+ }
75
+ /**
76
+ * Inject given key into a given class constructor
77
+ *
78
+ * @param ctor - constructor for a class (e.g. a controller class)
79
+ * @param key - binding to use in order to resolve the value of the decorated
80
+ * constructor parameter or property
81
+ */
82
+ function injectFirstConstructorArg(ctor, key) {
83
+ core_1.inject(key)(ctor, undefined, // constructor member
84
+ 0);
85
+ }
86
+ //# sourceMappingURL=crud-rest.api-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud-rest.api-builder.js","sourceRoot":"","sources":["../src/crud-rest.api-builder.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,mCAAmC;AACnC,+CAA+C;AAC/C,gEAAgE;;;;AAEhE,yCAMwB;AACxB,mEAIqC;AACrC,qDAM8B;AAE9B,0DAAiC;AACjC,wBAA2C;AAE3C,MAAM,KAAK,GAAG,eAAY,CAAC,yBAAyB,CAAC,CAAC;AAQtD,IAAa,kBAAkB,GAA/B,MAAa,kBAAkB;IAA/B;QACW,YAAO,GAAW,UAAU,CAAC;IAwCxC,CAAC;IAtCC,KAAK,CACH,WAAwC,EACxC,UAA6C,EAC7C,GAAmB;QAEnB,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC;QAClC,MAAM,MAAM,GAAG,GAA6B,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;YACpB,MAAM,IAAI,KAAK,CACb,gEAAgE,SAAS,GAAG,CAC7E,CAAC;SACH;QAED,IAAI,CAAC,CAAC,UAAU,CAAC,SAAS,YAAY,mBAAM,CAAC,EAAE;YAC7C,MAAM,IAAI,KAAK,CACb,0EAA0E,SAAS,4BAA4B,CAChH,CAAC;SACH;QACD,MAAM,WAAW,GAAG,UAAiD,CAAC;QAEtE,IAAI,eAAe,GAAG,gBAAgB,WAAW,CAAC,IAAI,YAAY,CAAC;QAEnE,IAAI,WAAW,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE;YACxC,KAAK,CAAC,0CAA0C,EAAE,eAAe,CAAC,CAAC;SACpE;aAAM;YACL,kCAAkC;YAClC,MAAM,eAAe,GAAG,mBAAmB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YACjE,WAAW,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;YACxC,eAAe,GAAG,eAAe,CAAC,IAAI,CAAC;YACvC,KAAK,CAAC,6BAA6B,EAAE,eAAe,CAAC,CAAC;SACvD;QAED,MAAM,eAAe,GAAG,uBAAuB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACrE,WAAW,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;QACxC,KAAK,CAAC,6BAA6B,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;QAE3D,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;CACF,CAAA;AAzCY,kBAAkB;IAD9B,iBAAU,CAAC,qCAAiB,CAAC;GACjB,kBAAkB,CAyC9B;AAzCY,gDAAkB;AA2C/B;;;;;GAKG;AACH,SAAS,mBAAmB,CAC1B,WAAgD,EAChD,MAA8B;IAE9B,MAAM,eAAe,GAAG,sCAAyB,CAAC,WAAW,CAAC,CAAC;IAE/D,yBAAyB,CACvB,eAAe,EACf,eAAe,MAAM,CAAC,UAAU,EAAE,CACnC,CAAC;IAEF,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;;;GAKG;AACH,SAAS,uBAAuB,CAC9B,WAAgD,EAChD,MAA8B;IAE9B,MAAM,eAAe,GAAG,2BAAwB,CAC9C,WAAW;IACX,mEAAmE;IACnE,yDAAyD;IACzD,MAAM,CACP,CAAC;IAEF,yBAAyB,CACvB,eAAe,EACf,gBAAgB,WAAW,CAAC,IAAI,YAAY,CAC7C,CAAC;IAEF,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,yBAAyB,CAChC,IAAoB,EACpB,GAAoB;IAEpB,aAAM,CAAC,GAAG,CAAC,CACT,IAAI,EACJ,SAAS,EAAE,qBAAqB;IAChC,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Binding, Component } from '@loopback/core';
2
+ export declare class CrudRestComponent implements Component {
3
+ bindings: Binding[];
4
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
3
+ // Node module: @loopback/rest-crud
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CrudRestComponent = void 0;
8
+ const core_1 = require("@loopback/core");
9
+ const crud_rest_api_builder_1 = require("./crud-rest.api-builder");
10
+ class CrudRestComponent {
11
+ constructor() {
12
+ this.bindings = [core_1.createBindingFromClass(crud_rest_api_builder_1.CrudRestApiBuilder)];
13
+ }
14
+ }
15
+ exports.CrudRestComponent = CrudRestComponent;
16
+ //# sourceMappingURL=crud-rest.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud-rest.component.js","sourceRoot":"","sources":["../src/crud-rest.component.ts"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,mCAAmC;AACnC,+CAA+C;AAC/C,gEAAgE;;;AAEhE,yCAA0E;AAC1E,mEAA2D;AAE3D,MAAa,iBAAiB;IAA9B;QACE,aAAQ,GAAc,CAAC,6BAAsB,CAAC,0CAAkB,CAAC,CAAC,CAAC;IACrE,CAAC;CAAA;AAFD,8CAEC"}
@@ -0,0 +1,60 @@
1
+ import { Entity, EntityCrudRepository } from '@loopback/repository';
2
+ /**
3
+ * This interface describes prototype members of the controller class
4
+ * returned by `defineCrudRestController`.
5
+ */
6
+ export interface CrudRestController<T extends Entity, IdType, IdName extends keyof T, Relations extends object = {}> {
7
+ /**
8
+ * The backing repository used to access & modify model data.
9
+ */
10
+ readonly repository: EntityCrudRepository<T, IdType>;
11
+ /**
12
+ * Implementation of the endpoint `POST /`.
13
+ * @param data Model data
14
+ */
15
+ create(data: Omit<T, IdName>): Promise<T>;
16
+ }
17
+ /**
18
+ * Constructor of the controller class returned by `defineCrudRestController`.
19
+ */
20
+ export interface CrudRestControllerCtor<T extends Entity, IdType, IdName extends keyof T, Relations extends object = {}> {
21
+ new (repository: EntityCrudRepository<T, IdType, Relations>): CrudRestController<T, IdType, IdName, Relations>;
22
+ }
23
+ /**
24
+ * Options to configure different aspects of a CRUD REST Controller.
25
+ */
26
+ export interface CrudRestControllerOptions {
27
+ /**
28
+ * The base path where to "mount" the controller.
29
+ */
30
+ basePath: string;
31
+ }
32
+ /**
33
+ * Create (define) a CRUD Controller class for the given model.
34
+ *
35
+ * @example
36
+ *
37
+ * ```ts
38
+ * const ProductController = defineCrudRestController<
39
+ * Product,
40
+ * typeof Product.prototype.id,
41
+ * 'id'
42
+ * >(Product, {basePath: '/products'});
43
+ *
44
+ * inject('repositories.ProductRepository')(
45
+ * ProductController,
46
+ * undefined,
47
+ * 0,
48
+ * );
49
+ *
50
+ * app.controller(ProductController);
51
+ * ```
52
+ *
53
+ * @param modelCtor A model class, e.g. `Product`.
54
+ * @param options Configuration options, e.g. `{basePath: '/products'}`.
55
+ */
56
+ export declare function defineCrudRestController<T extends Entity, IdType, IdName extends keyof T, Relations extends object = {}>(modelCtor: typeof Entity & {
57
+ prototype: T & {
58
+ [key in IdName]: IdType;
59
+ };
60
+ }, options: CrudRestControllerOptions): CrudRestControllerCtor<T, IdType, IdName, Relations>;
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
3
+ // Node module: @loopback/rest-crud
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.defineCrudRestController = void 0;
8
+ const tslib_1 = require("tslib");
9
+ const repository_1 = require("@loopback/repository");
10
+ const rest_1 = require("@loopback/rest");
11
+ const assert = require("assert");
12
+ /**
13
+ * Create (define) a CRUD Controller class for the given model.
14
+ *
15
+ * @example
16
+ *
17
+ * ```ts
18
+ * const ProductController = defineCrudRestController<
19
+ * Product,
20
+ * typeof Product.prototype.id,
21
+ * 'id'
22
+ * >(Product, {basePath: '/products'});
23
+ *
24
+ * inject('repositories.ProductRepository')(
25
+ * ProductController,
26
+ * undefined,
27
+ * 0,
28
+ * );
29
+ *
30
+ * app.controller(ProductController);
31
+ * ```
32
+ *
33
+ * @param modelCtor A model class, e.g. `Product`.
34
+ * @param options Configuration options, e.g. `{basePath: '/products'}`.
35
+ */
36
+ function defineCrudRestController(modelCtor, options) {
37
+ const modelName = modelCtor.name;
38
+ const idPathParam = {
39
+ name: 'id',
40
+ in: 'path',
41
+ schema: getIdSchema(modelCtor),
42
+ };
43
+ let CrudRestControllerImpl = class CrudRestControllerImpl {
44
+ constructor(repository) {
45
+ this.repository = repository;
46
+ }
47
+ async create(data) {
48
+ return this.repository.create(
49
+ // FIXME(bajtos) Improve repository API to support this use case
50
+ // with no explicit type-casts required
51
+ data);
52
+ }
53
+ async find(filter) {
54
+ return this.repository.find(filter);
55
+ }
56
+ async findById(id, filter) {
57
+ return this.repository.findById(id, filter);
58
+ }
59
+ async count(where) {
60
+ return this.repository.count(where);
61
+ }
62
+ async updateAll(data, where) {
63
+ return this.repository.updateAll(
64
+ // FIXME(bajtos) Improve repository API to support this use case
65
+ // with no explicit type-casts required
66
+ data, where);
67
+ }
68
+ async updateById(id, data) {
69
+ await this.repository.updateById(id,
70
+ // FIXME(bajtos) Improve repository API to support this use case
71
+ // with no explicit type-casts required
72
+ data);
73
+ }
74
+ async replaceById(id, data) {
75
+ await this.repository.replaceById(id, data);
76
+ }
77
+ async deleteById(id) {
78
+ await this.repository.deleteById(id);
79
+ }
80
+ };
81
+ tslib_1.__decorate([
82
+ rest_1.post('/', {
83
+ ...response.model(200, `${modelName} instance created`, modelCtor),
84
+ }),
85
+ tslib_1.__param(0, body(modelCtor, {
86
+ title: `New${modelName}`,
87
+ exclude: modelCtor.getIdProperties(),
88
+ })),
89
+ tslib_1.__metadata("design:type", Function),
90
+ tslib_1.__metadata("design:paramtypes", [Object]),
91
+ tslib_1.__metadata("design:returntype", Promise)
92
+ ], CrudRestControllerImpl.prototype, "create", null);
93
+ tslib_1.__decorate([
94
+ rest_1.get('/', {
95
+ ...response.array(200, `Array of ${modelName} instances`, modelCtor, {
96
+ includeRelations: true,
97
+ }),
98
+ }),
99
+ tslib_1.__param(0, rest_1.param.filter(modelCtor)),
100
+ tslib_1.__metadata("design:type", Function),
101
+ tslib_1.__metadata("design:paramtypes", [Object]),
102
+ tslib_1.__metadata("design:returntype", Promise)
103
+ ], CrudRestControllerImpl.prototype, "find", null);
104
+ tslib_1.__decorate([
105
+ rest_1.get('/{id}', {
106
+ ...response.model(200, `${modelName} instance`, modelCtor, {
107
+ includeRelations: true,
108
+ }),
109
+ }),
110
+ tslib_1.__param(0, rest_1.param(idPathParam)),
111
+ tslib_1.__param(1, rest_1.param.query.object('filter', rest_1.getFilterSchemaFor(modelCtor, { exclude: 'where' }))),
112
+ tslib_1.__metadata("design:type", Function),
113
+ tslib_1.__metadata("design:paramtypes", [Object, Object]),
114
+ tslib_1.__metadata("design:returntype", Promise)
115
+ ], CrudRestControllerImpl.prototype, "findById", null);
116
+ tslib_1.__decorate([
117
+ rest_1.get('/count', {
118
+ ...response(200, `${modelName} count`, { schema: repository_1.CountSchema }),
119
+ }),
120
+ tslib_1.__param(0, rest_1.param.where(modelCtor)),
121
+ tslib_1.__metadata("design:type", Function),
122
+ tslib_1.__metadata("design:paramtypes", [Object]),
123
+ tslib_1.__metadata("design:returntype", Promise)
124
+ ], CrudRestControllerImpl.prototype, "count", null);
125
+ tslib_1.__decorate([
126
+ rest_1.patch('/', {
127
+ ...response(200, `Count of ${modelName} models updated`, {
128
+ schema: repository_1.CountSchema,
129
+ }),
130
+ }),
131
+ tslib_1.__param(0, body(modelCtor, { partial: true })),
132
+ tslib_1.__param(1, rest_1.param.where(modelCtor)),
133
+ tslib_1.__metadata("design:type", Function),
134
+ tslib_1.__metadata("design:paramtypes", [Object, Object]),
135
+ tslib_1.__metadata("design:returntype", Promise)
136
+ ], CrudRestControllerImpl.prototype, "updateAll", null);
137
+ tslib_1.__decorate([
138
+ rest_1.patch('/{id}', {
139
+ responses: {
140
+ '204': { description: `${modelName} was updated` },
141
+ },
142
+ }),
143
+ tslib_1.__param(0, rest_1.param(idPathParam)),
144
+ tslib_1.__param(1, body(modelCtor, { partial: true })),
145
+ tslib_1.__metadata("design:type", Function),
146
+ tslib_1.__metadata("design:paramtypes", [Object, Object]),
147
+ tslib_1.__metadata("design:returntype", Promise)
148
+ ], CrudRestControllerImpl.prototype, "updateById", null);
149
+ tslib_1.__decorate([
150
+ rest_1.put('/{id}', {
151
+ responses: {
152
+ '204': { description: `${modelName} was updated` },
153
+ },
154
+ }),
155
+ tslib_1.__param(0, rest_1.param(idPathParam)),
156
+ tslib_1.__param(1, body(modelCtor)),
157
+ tslib_1.__metadata("design:type", Function),
158
+ tslib_1.__metadata("design:paramtypes", [Object, Object]),
159
+ tslib_1.__metadata("design:returntype", Promise)
160
+ ], CrudRestControllerImpl.prototype, "replaceById", null);
161
+ tslib_1.__decorate([
162
+ rest_1.del('/{id}', {
163
+ responses: {
164
+ '204': { description: `${modelName} was deleted` },
165
+ },
166
+ }),
167
+ tslib_1.__param(0, rest_1.param(idPathParam)),
168
+ tslib_1.__metadata("design:type", Function),
169
+ tslib_1.__metadata("design:paramtypes", [Object]),
170
+ tslib_1.__metadata("design:returntype", Promise)
171
+ ], CrudRestControllerImpl.prototype, "deleteById", null);
172
+ CrudRestControllerImpl = tslib_1.__decorate([
173
+ rest_1.api({ basePath: options.basePath, paths: {} }),
174
+ tslib_1.__metadata("design:paramtypes", [Object])
175
+ ], CrudRestControllerImpl);
176
+ const controllerName = modelName + 'Controller';
177
+ const defineNamedController = new Function('controllerClass', `return class ${controllerName} extends controllerClass {}`);
178
+ const controller = defineNamedController(CrudRestControllerImpl);
179
+ assert.equal(controller.name, controllerName);
180
+ return controller;
181
+ }
182
+ exports.defineCrudRestController = defineCrudRestController;
183
+ function getIdSchema(modelCtor) {
184
+ var _a;
185
+ const idProp = modelCtor.getIdProperties()[0];
186
+ const modelSchema = rest_1.jsonToSchemaObject(rest_1.getJsonSchema(modelCtor));
187
+ return ((_a = modelSchema.properties) !== null && _a !== void 0 ? _a : {})[idProp];
188
+ }
189
+ // Temporary implementation of a short-hand version of `@requestBody`
190
+ // See https://github.com/loopbackio/loopback-next/issues/3493
191
+ function body(modelCtor, options) {
192
+ return rest_1.requestBody({
193
+ content: {
194
+ 'application/json': {
195
+ schema: rest_1.getModelSchemaRef(modelCtor, options),
196
+ },
197
+ },
198
+ });
199
+ }
200
+ // Temporary workaround for a missing `@response` decorator
201
+ // See https://github.com/loopbackio/loopback-next/issues/1672
202
+ // Please note this is just a workaround, the real helper should be implemented
203
+ // as a decorator that contributes OpenAPI metadata in a way that allows
204
+ // `@post` to merge the responses with the metadata provided at operation level
205
+ function response(statusCode, description, payload) {
206
+ return {
207
+ responses: {
208
+ [`${statusCode}`]: {
209
+ description,
210
+ content: {
211
+ 'application/json': payload,
212
+ },
213
+ },
214
+ },
215
+ };
216
+ }
217
+ (function (response) {
218
+ function model(statusCode, description, modelCtor, options) {
219
+ return response(statusCode, description, {
220
+ schema: rest_1.getModelSchemaRef(modelCtor, options),
221
+ });
222
+ }
223
+ response.model = model;
224
+ function array(statusCode, description, modelCtor, options) {
225
+ return response(statusCode, description, {
226
+ schema: {
227
+ type: 'array',
228
+ items: rest_1.getModelSchemaRef(modelCtor, options),
229
+ },
230
+ });
231
+ }
232
+ response.array = array;
233
+ })(response || (response = {}));
234
+ //# sourceMappingURL=crud-rest.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud-rest.controller.js","sourceRoot":"","sources":["../src/crud-rest.controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * REST API controller implementing default CRUD semantics.
3
+ *
4
+ * @remarks
5
+ * Allows LoopBack 4 applications to quickly expose models via REST API without
6
+ * having to implement custom controller or repository classes.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ export { defineCrudRepositoryClass } from '@loopback/repository';
11
+ export * from './crud-rest.api-builder';
12
+ export * from './crud-rest.component';
13
+ export * from './crud-rest.controller';
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
3
+ // Node module: @loopback/rest-crud
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.defineCrudRepositoryClass = void 0;
8
+ const tslib_1 = require("tslib");
9
+ /**
10
+ * REST API controller implementing default CRUD semantics.
11
+ *
12
+ * @remarks
13
+ * Allows LoopBack 4 applications to quickly expose models via REST API without
14
+ * having to implement custom controller or repository classes.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ // Re-export `defineCrudRepositoryClass` for backward-compatibility
19
+ var repository_1 = require("@loopback/repository");
20
+ Object.defineProperty(exports, "defineCrudRepositoryClass", { enumerable: true, get: function () { return repository_1.defineCrudRepositoryClass; } });
21
+ tslib_1.__exportStar(require("./crud-rest.api-builder"), exports);
22
+ tslib_1.__exportStar(require("./crud-rest.component"), exports);
23
+ tslib_1.__exportStar(require("./crud-rest.controller"), exports);
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,mCAAmC;AACnC,+CAA+C;AAC/C,gEAAgE;;;;AAEhE;;;;;;;;GAQG;AAEH,mEAAmE;AACnE,mDAA+D;AAAvD,uHAAA,yBAAyB,OAAA;AACjC,kEAAwC;AACxC,gEAAsC;AACtC,iEAAuC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@loopback/rest-crud",
3
+ "description": "REST API controller implementing default CRUD semantics",
4
+ "version": "0.11.1",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "author": "IBM Corp.",
9
+ "copyright.owner": "IBM Corp.",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/loopbackio/loopback-next.git",
13
+ "directory": "packages/rest-crud"
14
+ },
15
+ "engines": {
16
+ "node": "^10.16 || 12 || 14 || 16"
17
+ },
18
+ "scripts": {
19
+ "build": "lb-tsc",
20
+ "clean": "lb-clean loopback-rest-crud*.tgz dist *.tsbuildinfo package",
21
+ "pretest": "npm run build",
22
+ "test": "lb-mocha \"dist/__tests__/**/*.js\"",
23
+ "verify": "npm pack && tar xf loopback-rest-crud*.tgz && tree package && npm run clean"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "files": [
29
+ "README.md",
30
+ "dist",
31
+ "src",
32
+ "!*/__tests__"
33
+ ],
34
+ "peerDependencies": {
35
+ "@loopback/core": "^2.17.0",
36
+ "@loopback/repository": "^3.7.2",
37
+ "@loopback/rest": "^10.0.1"
38
+ },
39
+ "dependencies": {
40
+ "@loopback/model-api-builder": "^2.3.3",
41
+ "debug": "^4.3.2",
42
+ "tslib": "^2.3.1"
43
+ },
44
+ "devDependencies": {
45
+ "@loopback/build": "^7.0.1",
46
+ "@loopback/core": "^2.17.0",
47
+ "@loopback/repository": "^3.7.2",
48
+ "@loopback/rest": "^10.0.1",
49
+ "@loopback/testlab": "^3.4.3",
50
+ "@types/debug": "^4.1.7",
51
+ "@types/node": "^10.17.60"
52
+ },
53
+ "gitHead": "1df36bb1ee2e513d9e197bd6010c4cfb296d50b8"
54
+ }
@@ -0,0 +1,141 @@
1
+ // Copyright IBM Corp. 2020. All Rights Reserved.
2
+ // Node module: @loopback/rest-crud
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import {
7
+ BindingSelector,
8
+ Constructor,
9
+ ControllerClass,
10
+ inject,
11
+ injectable,
12
+ } from '@loopback/core';
13
+ import {
14
+ asModelApiBuilder,
15
+ ModelApiBuilder,
16
+ ModelApiConfig,
17
+ } from '@loopback/model-api-builder';
18
+ import {
19
+ ApplicationWithRepositories,
20
+ Class,
21
+ defineCrudRepositoryClass,
22
+ Entity,
23
+ EntityCrudRepository,
24
+ } from '@loopback/repository';
25
+ import {Model} from '@loopback/rest';
26
+ import debugFactory from 'debug';
27
+ import {defineCrudRestController} from '.';
28
+
29
+ const debug = debugFactory('loopback:boot:crud-rest');
30
+
31
+ export interface ModelCrudRestApiConfig extends ModelApiConfig {
32
+ // E.g. '/products'
33
+ basePath: string;
34
+ }
35
+
36
+ @injectable(asModelApiBuilder)
37
+ export class CrudRestApiBuilder implements ModelApiBuilder {
38
+ readonly pattern: string = 'CrudRest';
39
+
40
+ build(
41
+ application: ApplicationWithRepositories,
42
+ modelClass: typeof Model & {prototype: Model},
43
+ cfg: ModelApiConfig,
44
+ ): Promise<void> {
45
+ const modelName = modelClass.name;
46
+ const config = cfg as ModelCrudRestApiConfig;
47
+ if (!config.basePath) {
48
+ throw new Error(
49
+ `Missing required field "basePath" in configuration for model ${modelName}.`,
50
+ );
51
+ }
52
+
53
+ if (!(modelClass.prototype instanceof Entity)) {
54
+ throw new Error(
55
+ `CrudRestController requires a model that extends 'Entity'. (Model name ${modelName} does not extend 'Entity')`,
56
+ );
57
+ }
58
+ const entityClass = modelClass as typeof Entity & {prototype: Entity};
59
+
60
+ let repoBindingName = `repositories.${entityClass.name}Repository`;
61
+
62
+ if (application.isBound(repoBindingName)) {
63
+ debug('Using the existing Repository binding %j', repoBindingName);
64
+ } else {
65
+ // repository class does not exist
66
+ const repositoryClass = setupCrudRepository(entityClass, config);
67
+ application.repository(repositoryClass);
68
+ repoBindingName = repositoryClass.name;
69
+ debug('Registered repository class', repoBindingName);
70
+ }
71
+
72
+ const controllerClass = setupCrudRestController(entityClass, config);
73
+ application.controller(controllerClass);
74
+ debug('Registered controller class', controllerClass.name);
75
+
76
+ return Promise.resolve();
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Set up a CRUD Repository class for the given Entity class.
82
+ *
83
+ * @param entityClass - the Entity class the repository is built for
84
+ * @param config - configuration of the Entity class
85
+ */
86
+ function setupCrudRepository(
87
+ entityClass: typeof Entity & {prototype: Entity},
88
+ config: ModelCrudRestApiConfig,
89
+ ): Class<EntityCrudRepository<Entity, unknown>> {
90
+ const repositoryClass = defineCrudRepositoryClass(entityClass);
91
+
92
+ injectFirstConstructorArg(
93
+ repositoryClass,
94
+ `datasources.${config.dataSource}`,
95
+ );
96
+
97
+ return repositoryClass;
98
+ }
99
+
100
+ /**
101
+ * Set up a CRUD Controller class for the given Entity class.
102
+ *
103
+ * @param entityClass - the Entity class the controller is built for
104
+ * @param config - configuration of the Entity class
105
+ */
106
+ function setupCrudRestController(
107
+ entityClass: typeof Entity & {prototype: Entity},
108
+ config: ModelCrudRestApiConfig,
109
+ ): ControllerClass {
110
+ const controllerClass = defineCrudRestController(
111
+ entityClass,
112
+ // important - forward the entire config object to allow controller
113
+ // factories to accept additional (custom) config options
114
+ config,
115
+ );
116
+
117
+ injectFirstConstructorArg(
118
+ controllerClass,
119
+ `repositories.${entityClass.name}Repository`,
120
+ );
121
+
122
+ return controllerClass;
123
+ }
124
+
125
+ /**
126
+ * Inject given key into a given class constructor
127
+ *
128
+ * @param ctor - constructor for a class (e.g. a controller class)
129
+ * @param key - binding to use in order to resolve the value of the decorated
130
+ * constructor parameter or property
131
+ */
132
+ function injectFirstConstructorArg<T>(
133
+ ctor: Constructor<T>,
134
+ key: BindingSelector,
135
+ ) {
136
+ inject(key)(
137
+ ctor,
138
+ undefined, // constructor member
139
+ 0, // the first argument
140
+ );
141
+ }
@@ -0,0 +1,11 @@
1
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
2
+ // Node module: @loopback/rest-crud
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import {Binding, Component, createBindingFromClass} from '@loopback/core';
7
+ import {CrudRestApiBuilder} from './crud-rest.api-builder';
8
+
9
+ export class CrudRestComponent implements Component {
10
+ bindings: Binding[] = [createBindingFromClass(CrudRestApiBuilder)];
11
+ }
@@ -0,0 +1,348 @@
1
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
2
+ // Node module: @loopback/rest-crud
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import {
7
+ Count,
8
+ CountSchema,
9
+ DataObject,
10
+ Entity,
11
+ EntityCrudRepository,
12
+ Filter,
13
+ FilterExcludingWhere,
14
+ Where,
15
+ } from '@loopback/repository';
16
+ import {
17
+ api,
18
+ del,
19
+ get,
20
+ getFilterSchemaFor,
21
+ getJsonSchema,
22
+ getModelSchemaRef,
23
+ JsonSchemaOptions,
24
+ jsonToSchemaObject,
25
+ MediaTypeObject,
26
+ param,
27
+ ParameterObject,
28
+ patch,
29
+ post,
30
+ put,
31
+ requestBody,
32
+ ResponsesObject,
33
+ SchemaObject,
34
+ } from '@loopback/rest';
35
+ import assert = require('assert');
36
+
37
+ // Ideally, this file should simply `export class CrudRestController<...>{}`
38
+ // Unfortunately, that's not possible for several reasons.
39
+ //
40
+ // First of all, to correctly decorate methods and define schemas for request
41
+ // and response bodies, we need to know the target model which will be used by
42
+ // the controller. As a result, this file has to export a function that will
43
+ // create a constructor class specific to the given model.
44
+ //
45
+ // Secondly, TypeScript does not allow decorators to be used in class
46
+ // expressions - see https://github.com/microsoft/TypeScript/issues/7342.
47
+ // As a result, we cannot write implement the factory as `return class ...`,
48
+ // but have to define the class as an internal type and return the controller
49
+ // constructor in a new statement.
50
+ // Because the controller class is an internal type scoped to the body of the
51
+ // factory function, we cannot use it to describe the return type. We must
52
+ // explicitly provide the return type.
53
+ //
54
+ // To work around those issues, we use the following design:
55
+ // - The interface `CrudRestController` describes controller methods (members)
56
+ // - The type `CrudRestControllerCtor` describes the class constructor.
57
+ // - `defineCrudRestController` returns `CrudRestControllerCtor` type.
58
+
59
+ /**
60
+ * This interface describes prototype members of the controller class
61
+ * returned by `defineCrudRestController`.
62
+ */
63
+ export interface CrudRestController<
64
+ T extends Entity,
65
+ IdType,
66
+ IdName extends keyof T,
67
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
68
+ Relations extends object = {},
69
+ > {
70
+ /**
71
+ * The backing repository used to access & modify model data.
72
+ */
73
+ readonly repository: EntityCrudRepository<T, IdType>;
74
+
75
+ /**
76
+ * Implementation of the endpoint `POST /`.
77
+ * @param data Model data
78
+ */
79
+ create(data: Omit<T, IdName>): Promise<T>;
80
+ }
81
+
82
+ /**
83
+ * Constructor of the controller class returned by `defineCrudRestController`.
84
+ */
85
+ export interface CrudRestControllerCtor<
86
+ T extends Entity,
87
+ IdType,
88
+ IdName extends keyof T,
89
+ Relations extends object = {},
90
+ > {
91
+ new (
92
+ repository: EntityCrudRepository<T, IdType, Relations>,
93
+ ): CrudRestController<T, IdType, IdName, Relations>;
94
+ }
95
+
96
+ /**
97
+ * Options to configure different aspects of a CRUD REST Controller.
98
+ */
99
+ export interface CrudRestControllerOptions {
100
+ /**
101
+ * The base path where to "mount" the controller.
102
+ */
103
+ basePath: string;
104
+ }
105
+
106
+ /**
107
+ * Create (define) a CRUD Controller class for the given model.
108
+ *
109
+ * @example
110
+ *
111
+ * ```ts
112
+ * const ProductController = defineCrudRestController<
113
+ * Product,
114
+ * typeof Product.prototype.id,
115
+ * 'id'
116
+ * >(Product, {basePath: '/products'});
117
+ *
118
+ * inject('repositories.ProductRepository')(
119
+ * ProductController,
120
+ * undefined,
121
+ * 0,
122
+ * );
123
+ *
124
+ * app.controller(ProductController);
125
+ * ```
126
+ *
127
+ * @param modelCtor A model class, e.g. `Product`.
128
+ * @param options Configuration options, e.g. `{basePath: '/products'}`.
129
+ */
130
+ export function defineCrudRestController<
131
+ T extends Entity,
132
+ IdType,
133
+ IdName extends keyof T,
134
+ Relations extends object = {},
135
+ >(
136
+ modelCtor: typeof Entity & {prototype: T & {[key in IdName]: IdType}},
137
+ options: CrudRestControllerOptions,
138
+ ): CrudRestControllerCtor<T, IdType, IdName, Relations> {
139
+ const modelName = modelCtor.name;
140
+ const idPathParam: ParameterObject = {
141
+ name: 'id',
142
+ in: 'path',
143
+ schema: getIdSchema(modelCtor),
144
+ };
145
+
146
+ @api({basePath: options.basePath, paths: {}})
147
+ class CrudRestControllerImpl
148
+ implements CrudRestController<T, IdType, IdName>
149
+ {
150
+ constructor(
151
+ public readonly repository: EntityCrudRepository<T, IdType, Relations>,
152
+ ) {}
153
+
154
+ @post('/', {
155
+ ...response.model(200, `${modelName} instance created`, modelCtor),
156
+ })
157
+ async create(
158
+ @body(modelCtor, {
159
+ title: `New${modelName}`,
160
+ exclude: modelCtor.getIdProperties() as (keyof T)[],
161
+ })
162
+ data: Omit<T, IdName>,
163
+ ): Promise<T> {
164
+ return this.repository.create(
165
+ // FIXME(bajtos) Improve repository API to support this use case
166
+ // with no explicit type-casts required
167
+ data as DataObject<T>,
168
+ );
169
+ }
170
+
171
+ @get('/', {
172
+ ...response.array(200, `Array of ${modelName} instances`, modelCtor, {
173
+ includeRelations: true,
174
+ }),
175
+ })
176
+ async find(
177
+ @param.filter(modelCtor)
178
+ filter?: Filter<T>,
179
+ ): Promise<(T & Relations)[]> {
180
+ return this.repository.find(filter);
181
+ }
182
+
183
+ @get('/{id}', {
184
+ ...response.model(200, `${modelName} instance`, modelCtor, {
185
+ includeRelations: true,
186
+ }),
187
+ })
188
+ async findById(
189
+ @param(idPathParam) id: IdType,
190
+ @param.query.object(
191
+ 'filter',
192
+ getFilterSchemaFor(modelCtor, {exclude: 'where'}),
193
+ )
194
+ filter?: FilterExcludingWhere<T>,
195
+ ): Promise<T & Relations> {
196
+ return this.repository.findById(id, filter);
197
+ }
198
+
199
+ @get('/count', {
200
+ ...response(200, `${modelName} count`, {schema: CountSchema}),
201
+ })
202
+ async count(
203
+ @param.where(modelCtor)
204
+ where?: Where<T>,
205
+ ): Promise<Count> {
206
+ return this.repository.count(where);
207
+ }
208
+
209
+ @patch('/', {
210
+ ...response(200, `Count of ${modelName} models updated`, {
211
+ schema: CountSchema,
212
+ }),
213
+ })
214
+ async updateAll(
215
+ @body(modelCtor, {partial: true}) data: Partial<T>,
216
+ @param.where(modelCtor)
217
+ where?: Where<T>,
218
+ ): Promise<Count> {
219
+ return this.repository.updateAll(
220
+ // FIXME(bajtos) Improve repository API to support this use case
221
+ // with no explicit type-casts required
222
+ data as DataObject<T>,
223
+ where,
224
+ );
225
+ }
226
+
227
+ @patch('/{id}', {
228
+ responses: {
229
+ '204': {description: `${modelName} was updated`},
230
+ },
231
+ })
232
+ async updateById(
233
+ @param(idPathParam) id: IdType,
234
+ @body(modelCtor, {partial: true}) data: Partial<T>,
235
+ ): Promise<void> {
236
+ await this.repository.updateById(
237
+ id,
238
+ // FIXME(bajtos) Improve repository API to support this use case
239
+ // with no explicit type-casts required
240
+ data as DataObject<T>,
241
+ );
242
+ }
243
+
244
+ @put('/{id}', {
245
+ responses: {
246
+ '204': {description: `${modelName} was updated`},
247
+ },
248
+ })
249
+ async replaceById(
250
+ @param(idPathParam) id: IdType,
251
+ @body(modelCtor) data: T,
252
+ ): Promise<void> {
253
+ await this.repository.replaceById(id, data);
254
+ }
255
+
256
+ @del('/{id}', {
257
+ responses: {
258
+ '204': {description: `${modelName} was deleted`},
259
+ },
260
+ })
261
+ async deleteById(@param(idPathParam) id: IdType): Promise<void> {
262
+ await this.repository.deleteById(id);
263
+ }
264
+ }
265
+
266
+ const controllerName = modelName + 'Controller';
267
+ const defineNamedController = new Function(
268
+ 'controllerClass',
269
+ `return class ${controllerName} extends controllerClass {}`,
270
+ );
271
+ const controller = defineNamedController(CrudRestControllerImpl);
272
+ assert.equal(controller.name, controllerName);
273
+ return controller;
274
+ }
275
+
276
+ function getIdSchema<T extends Entity>(
277
+ modelCtor: typeof Entity & {prototype: T},
278
+ ): SchemaObject {
279
+ const idProp = modelCtor.getIdProperties()[0];
280
+ const modelSchema = jsonToSchemaObject(
281
+ getJsonSchema(modelCtor),
282
+ ) as SchemaObject;
283
+ return (modelSchema.properties ?? {})[idProp] as SchemaObject;
284
+ }
285
+
286
+ // Temporary implementation of a short-hand version of `@requestBody`
287
+ // See https://github.com/loopbackio/loopback-next/issues/3493
288
+ function body<T extends Entity>(
289
+ modelCtor: Function & {prototype: T},
290
+ options?: JsonSchemaOptions<T>,
291
+ ) {
292
+ return requestBody({
293
+ content: {
294
+ 'application/json': {
295
+ schema: getModelSchemaRef(modelCtor, options),
296
+ },
297
+ },
298
+ });
299
+ }
300
+
301
+ // Temporary workaround for a missing `@response` decorator
302
+ // See https://github.com/loopbackio/loopback-next/issues/1672
303
+ // Please note this is just a workaround, the real helper should be implemented
304
+ // as a decorator that contributes OpenAPI metadata in a way that allows
305
+ // `@post` to merge the responses with the metadata provided at operation level
306
+ function response(
307
+ statusCode: number,
308
+ description: string,
309
+ payload: MediaTypeObject,
310
+ ): {responses: ResponsesObject} {
311
+ return {
312
+ responses: {
313
+ [`${statusCode}`]: {
314
+ description,
315
+ content: {
316
+ 'application/json': payload,
317
+ },
318
+ },
319
+ },
320
+ };
321
+ }
322
+
323
+ namespace response {
324
+ export function model<T extends Entity>(
325
+ statusCode: number,
326
+ description: string,
327
+ modelCtor: Function & {prototype: T},
328
+ options?: JsonSchemaOptions<T>,
329
+ ) {
330
+ return response(statusCode, description, {
331
+ schema: getModelSchemaRef(modelCtor, options),
332
+ });
333
+ }
334
+
335
+ export function array<T extends Entity>(
336
+ statusCode: number,
337
+ description: string,
338
+ modelCtor: Function & {prototype: T},
339
+ options?: JsonSchemaOptions<T>,
340
+ ) {
341
+ return response(statusCode, description, {
342
+ schema: {
343
+ type: 'array',
344
+ items: getModelSchemaRef(modelCtor, options),
345
+ },
346
+ });
347
+ }
348
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
2
+ // Node module: @loopback/rest-crud
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ /**
7
+ * REST API controller implementing default CRUD semantics.
8
+ *
9
+ * @remarks
10
+ * Allows LoopBack 4 applications to quickly expose models via REST API without
11
+ * having to implement custom controller or repository classes.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+
16
+ // Re-export `defineCrudRepositoryClass` for backward-compatibility
17
+ export {defineCrudRepositoryClass} from '@loopback/repository';
18
+ export * from './crud-rest.api-builder';
19
+ export * from './crud-rest.component';
20
+ export * from './crud-rest.controller';