@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 +25 -0
- package/README.md +168 -0
- package/dist/crud-rest.api-builder.d.ts +12 -0
- package/dist/crud-rest.api-builder.js +86 -0
- package/dist/crud-rest.api-builder.js.map +1 -0
- package/dist/crud-rest.component.d.ts +4 -0
- package/dist/crud-rest.component.js +16 -0
- package/dist/crud-rest.component.js.map +1 -0
- package/dist/crud-rest.controller.d.ts +60 -0
- package/dist/crud-rest.controller.js +234 -0
- package/dist/crud-rest.controller.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/src/crud-rest.api-builder.ts +141 -0
- package/src/crud-rest.component.ts +11 -0
- package/src/crud-rest.controller.ts +348 -0
- package/src/index.ts +20 -0
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,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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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';
|