From 0c0cc2765e7f9f880832e8da84cdc7b196a7f08b Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 15 Nov 2017 15:20:12 -0800 Subject: [PATCH] feat: Add REST mapping for repository interfaces --- packages/repository-rest/.gitignore | 3 + packages/repository-rest/.npmrc | 1 + packages/repository-rest/LICENSE | 25 ++++ packages/repository-rest/README.md | 15 +++ packages/repository-rest/docs.json | 11 ++ packages/repository-rest/index.d.ts | 6 + packages/repository-rest/index.js | 9 ++ packages/repository-rest/index.ts | 7 ++ packages/repository-rest/package.json | 48 ++++++++ .../src/controllers/crud-controller.ts | 85 ++++++++++++++ .../src/controllers/entity-crud-controller.ts | 110 ++++++++++++++++++ .../repository-rest/src/controllers/index.ts | 7 ++ packages/repository-rest/src/index.ts | 6 + .../test/acceptance/crud-controller.ts | 58 +++++++++ .../test/acceptance/entity-crud-controller.ts | 68 +++++++++++ packages/repository-rest/tsconfig.build.json | 8 ++ 16 files changed, 467 insertions(+) create mode 100644 packages/repository-rest/.gitignore create mode 100644 packages/repository-rest/.npmrc create mode 100644 packages/repository-rest/LICENSE create mode 100644 packages/repository-rest/README.md create mode 100644 packages/repository-rest/docs.json create mode 100644 packages/repository-rest/index.d.ts create mode 100644 packages/repository-rest/index.js create mode 100644 packages/repository-rest/index.ts create mode 100644 packages/repository-rest/package.json create mode 100644 packages/repository-rest/src/controllers/crud-controller.ts create mode 100644 packages/repository-rest/src/controllers/entity-crud-controller.ts create mode 100644 packages/repository-rest/src/controllers/index.ts create mode 100644 packages/repository-rest/src/index.ts create mode 100644 packages/repository-rest/test/acceptance/crud-controller.ts create mode 100644 packages/repository-rest/test/acceptance/entity-crud-controller.ts create mode 100644 packages/repository-rest/tsconfig.build.json diff --git a/packages/repository-rest/.gitignore b/packages/repository-rest/.gitignore new file mode 100644 index 000000000000..90a8d96cc3ff --- /dev/null +++ b/packages/repository-rest/.gitignore @@ -0,0 +1,3 @@ +*.tgz +dist* +package diff --git a/packages/repository-rest/.npmrc b/packages/repository-rest/.npmrc new file mode 100644 index 000000000000..43c97e719a5a --- /dev/null +++ b/packages/repository-rest/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/repository-rest/LICENSE b/packages/repository-rest/LICENSE new file mode 100644 index 000000000000..f78c63f15825 --- /dev/null +++ b/packages/repository-rest/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) IBM Corp. 2017. All Rights Reserved. +Node module: @loopback/repository +This project is licensed under the MIT License, full text below. + +-------- + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/repository-rest/README.md b/packages/repository-rest/README.md new file mode 100644 index 000000000000..771cabc7031d --- /dev/null +++ b/packages/repository-rest/README.md @@ -0,0 +1,15 @@ +# @loopback/repository-rest + +This module provides base controllers to map repository interfaces to REST APIs + +## Tests + +run 'npm test' from the root folder. + +## Contributors + +See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/packages/repository-rest/docs.json b/packages/repository-rest/docs.json new file mode 100644 index 000000000000..3eed349186bf --- /dev/null +++ b/packages/repository-rest/docs.json @@ -0,0 +1,11 @@ +{ + "content": [ + "./index.ts", + "./src/i**/*.ts" + ], + "codeSectionDepth": 4, + "assets": { + "/": "/docs", + "/docs": "/docs" + } +} diff --git a/packages/repository-rest/index.d.ts b/packages/repository-rest/index.d.ts new file mode 100644 index 000000000000..1439c16c90db --- /dev/null +++ b/packages/repository-rest/index.d.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './dist/src'; diff --git a/packages/repository-rest/index.js b/packages/repository-rest/index.js new file mode 100644 index 000000000000..2c0a0c33fcd2 --- /dev/null +++ b/packages/repository-rest/index.js @@ -0,0 +1,9 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +const nodeMajorVersion = +process.versions.node.split('.')[0]; +module.exports = nodeMajorVersion >= 7 ? + require('./dist/src') : + require('./dist6/src'); diff --git a/packages/repository-rest/index.ts b/packages/repository-rest/index.ts new file mode 100644 index 000000000000..313103704102 --- /dev/null +++ b/packages/repository-rest/index.ts @@ -0,0 +1,7 @@ +// Copyright IBM Corp. 2013,2017. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// NOTE(bajtos) This file is used by VSCode/TypeScriptServer at dev time only +export * from './src'; diff --git a/packages/repository-rest/package.json b/packages/repository-rest/package.json new file mode 100644 index 000000000000..5e576c6f8b4a --- /dev/null +++ b/packages/repository-rest/package.json @@ -0,0 +1,48 @@ +{ + "name": "@loopback/repository-rest", + "version": "4.0.0-alpha.1", + "description": "Repository REST APIs for LoopBack", + "engines": { + "node": ">=6" + }, + "main": "index", + "scripts": { + "acceptance": "lb-dist mocha --opts ../../test/mocha.opts 'DIST/test/acceptance/**/*.js'", + "build": "npm run build:dist && npm run build:dist6", + "build:current": "lb-tsc", + "build:dist": "lb-tsc es2017", + "build:dist6": "lb-tsc es2015", + "build:apidocs": "lb-apidocs", + "clean": "rm -rf loopback-context*.tgz dist* package", + "prepare": "npm run build && npm run build:apidocs", + "pretest": "npm run build:current", + "test": "lb-dist mocha --opts ../../test/mocha.opts 'DIST/test/unit/**/*.js' 'DIST/test/acceptance/**/*.js'", + "unit": "lb-dist mocha --opts ../../test/mocha.opts 'DIST/test/unit/**/*.js'", + "verify": "npm pack && tar xf loopback-juggler*.tgz && tree package && npm run clean" + }, + "author": "IBM", + "license": "MIT", + "devDependencies": { + "@loopback/build": "^4.0.0-alpha.5", + "@loopback/testlab": "^4.0.0-alpha.14" + }, + "dependencies": { + "@loopback/context": "^4.0.0-alpha.20", + "@loopback/core": "^4.0.0-alpha.22", + "@loopback/repository": "^4.0.0-alpha.16", + "@loopback/rest": "^4.0.0-alpha.9" + }, + "files": [ + "README.md", + "index.js", + "index.d.ts", + "dist/src", + "dist6/src", + "api-docs", + "src" + ], + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git" + } +} diff --git a/packages/repository-rest/src/controllers/crud-controller.ts b/packages/repository-rest/src/controllers/crud-controller.ts new file mode 100644 index 000000000000..ff89aa7d9702 --- /dev/null +++ b/packages/repository-rest/src/controllers/crud-controller.ts @@ -0,0 +1,85 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + CrudRepository, + ValueObject, + Entity, + DataObject, + Options, + Filter, + Where, +} from '@loopback/repository'; + +import {post, get, param} from '@loopback/rest'; + +/** + * Base controller class to expose CrudRepository operations to REST + */ +export abstract class CrudController { + protected repository: CrudRepository; + + @post(`/`) + create( + @param({name: '', in: 'body'}) + dataObject: DataObject, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.create(dataObject, options); + } + + @post(`/`) + createAll( + @param({name: '', in: 'body'}) + dataObjects: DataObject[], + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.createAll(dataObjects, options); + } + + @get(`/`) + find( + @param({name: 'filter', required: false, in: 'query'}) + filter?: Filter, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.find(filter, options); + } + + @post(`/updateAll`) + updateAll( + @param({name: '', in: 'body'}) + dataObject: DataObject, + @param({name: 'where', required: false, in: 'query'}) + where?: Where, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.updateAll(dataObject, where, options); + } + + @post(`/deleteAll`) + deleteAll( + @param({name: 'where', required: false, in: 'query'}) + where?: Where, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.deleteAll(where, options); + } + + @get(`/count`) + count( + @param({name: 'where', required: false, in: 'query'}) + where?: Where, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.count(where, options); + } +} diff --git a/packages/repository-rest/src/controllers/entity-crud-controller.ts b/packages/repository-rest/src/controllers/entity-crud-controller.ts new file mode 100644 index 000000000000..f8c8789d1c90 --- /dev/null +++ b/packages/repository-rest/src/controllers/entity-crud-controller.ts @@ -0,0 +1,110 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + Entity, + DataObject, + Options, + Filter, + EntityCrudRepository, +} from '@loopback/repository'; + +import {post, put, patch, get, del, param} from '@loopback/rest'; + +import {CrudController} from './crud-controller'; + +/** + * Base controller class to expose CrudRepository operations to REST + */ +export abstract class EntityCrudController< + T extends Entity, + ID +> extends CrudController { + protected repository: EntityCrudRepository; + + @put(`/save`) + save( + @param({name: '', in: 'body'}) + entity: DataObject, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.save(entity, options); + } + + @post(`/update`) + update( + @param({name: '', in: 'body'}) + entity: DataObject, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.update(entity, options); + } + + @post(`/delete`) + delete( + @param({name: '', in: 'body'}) + entity: DataObject, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.update(entity, options); + } + + @get(`/{id}`) + findById( + id: ID, + filter?: Filter, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.findById(id, filter, options); + } + + @patch(`/{id}`) + updateById( + @param({name: 'id', in: 'path'}) + id: ID, + @param({name: '', in: 'body'}) + data: DataObject, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.updateById(id, data, options); + } + + @put(`/{id}`) + replaceById( + @param({name: 'id', in: 'path'}) + id: ID, + @param({name: '', in: 'body'}) + data: DataObject, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.replaceById(id, data, options); + } + + @del(`{id}`) + deleteById( + @param({name: 'id', in: 'path'}) + id: ID, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.deleteById(id, options); + } + + @get(`/{id}/exists`) + exists( + @param({name: 'id', in: 'path'}) + id: ID, + @param({name: 'options', required: false, in: 'query'}) + options?: Options, + ): Promise { + return this.repository.exists(id, options); + } +} diff --git a/packages/repository-rest/src/controllers/index.ts b/packages/repository-rest/src/controllers/index.ts new file mode 100644 index 000000000000..a924f94de500 --- /dev/null +++ b/packages/repository-rest/src/controllers/index.ts @@ -0,0 +1,7 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './crud-controller'; +export * from './entity-crud-controller'; diff --git a/packages/repository-rest/src/index.ts b/packages/repository-rest/src/index.ts new file mode 100644 index 000000000000..eb783c2eb0ce --- /dev/null +++ b/packages/repository-rest/src/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2017. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './controllers'; diff --git a/packages/repository-rest/test/acceptance/crud-controller.ts b/packages/repository-rest/test/acceptance/crud-controller.ts new file mode 100644 index 000000000000..ba2e967b89fb --- /dev/null +++ b/packages/repository-rest/test/acceptance/crud-controller.ts @@ -0,0 +1,58 @@ +// Copyright IBM Corp. 2013,2017. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {repository, CrudRepository, ValueObject} from '@loopback/repository'; +import {api, getControllerSpec, ControllerSpec} from '@loopback/rest'; +import {expect} from '@loopback/testlab'; + +import {CrudController} from '../..'; + +describe('CrudController', () => { + class Address extends ValueObject {} + + @api({basePath: '/addresses', paths: {}}) + class AddressController extends CrudController
{ + constructor(@repository('addressRepo') repo: CrudRepository
) { + super(); + this.repository = repo; + } + } + + it('registers CRUD operations', () => { + const spec = getControllerSpec(AddressController); + const ops = getOperations(spec); + expect(ops).to.eql([ + 'get /: find', + 'post /: createAll', + 'post /updateAll: updateAll', + 'post /deleteAll: deleteAll', + 'get /count: count', + ]); + }); + + function getOperations(spec: ControllerSpec) { + const operations: string[] = []; + for (const p in spec.paths) { + const path = spec.paths[p]; + let verb, operationName; + for (const v of [ + 'delete', + 'get', + 'head', + 'options', + 'patch', + 'post', + 'put', + ]) { + if (v in path) { + verb = v; + operationName = path[v]['x-operation-name']; + operations.push(`${verb} ${p}: ${operationName}`); + } + } + } + return operations; + } +}); diff --git a/packages/repository-rest/test/acceptance/entity-crud-controller.ts b/packages/repository-rest/test/acceptance/entity-crud-controller.ts new file mode 100644 index 000000000000..f66f2b486574 --- /dev/null +++ b/packages/repository-rest/test/acceptance/entity-crud-controller.ts @@ -0,0 +1,68 @@ +// Copyright IBM Corp. 2013,2017. All Rights Reserved. +// Node module: @loopback/repository +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {EntityCrudController} from '../..'; +import {Entity, repository, EntityCrudRepository} from '@loopback/repository'; +import {api, getControllerSpec, ControllerSpec} from '@loopback/rest'; +import {expect} from '@loopback/testlab'; + +describe('EntityCrudController', () => { + class Customer extends Entity {} + + @api({basePath: '/customers', paths: {}}) + class CustomerController extends EntityCrudController { + constructor( + @repository('customerRepo') repo: EntityCrudRepository, + ) { + super(); + this.repository = repo; + } + } + + it('registers CRUD operations', () => { + const spec = getControllerSpec(CustomerController); + expect(spec.basePath).to.equal('/customers'); + const ops = getOperations(spec); + expect(ops).to.eql([ + 'get /: find', + 'post /: createAll', + 'post /updateAll: updateAll', + 'post /deleteAll: deleteAll', + 'get /count: count', + 'put /save: save', + 'post /update: update', + 'post /delete: delete', + 'get /{id}: findById', + 'patch /{id}: updateById', + 'put /{id}: replaceById', + 'delete {id}: deleteById', + 'get /{id}/exists: exists', + ]); + }); + + function getOperations(spec: ControllerSpec) { + const operations: string[] = []; + for (const p in spec.paths) { + const path = spec.paths[p]; + let verb, operationName; + for (const v of [ + 'delete', + 'get', + 'head', + 'options', + 'patch', + 'post', + 'put', + ]) { + if (v in path) { + verb = v; + operationName = path[v]['x-operation-name']; + operations.push(`${verb} ${p}: ${operationName}`); + } + } + } + return operations; + } +}); diff --git a/packages/repository-rest/tsconfig.build.json b/packages/repository-rest/tsconfig.build.json new file mode 100644 index 000000000000..855e02848b35 --- /dev/null +++ b/packages/repository-rest/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "../build/config/tsconfig.common.json", + "compilerOptions": { + "rootDir": "." + }, + "include": ["src", "test"] +}