diff --git a/.gitignore b/.gitignore index 4796eb8..76f576a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ node_modules # Optional REPL history .node_repl_history + +local-server/node_modules +external-server/node_modules diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f978251..eec57f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,9 @@ ### Contributing ### -Thank you for your interest in `loopback`, an open source project +Thank you for your interest in `loopback-example-access-control`, an open source project administered by StrongLoop. -Contributing to `loopback` is easy. In a few simple steps: +Contributing to `loopback-example-access-control` is easy. In a few simple steps: * Ensure that your effort is aligned with the project's roadmap by talking to the maintainers, especially if you are going to spend a @@ -14,7 +14,7 @@ Contributing to `loopback` is easy. In a few simple steps: * Adhere to code style outlined in the [Google C++ Style Guide][] and [Google Javascript Style Guide][]. - * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback) + * Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-example-access-control) * Submit a pull request through Github. diff --git a/FAKE REST SERVER/.editorconfig b/FAKE REST SERVER/.editorconfig new file mode 100644 index 0000000..3ee22e5 --- /dev/null +++ b/FAKE REST SERVER/.editorconfig @@ -0,0 +1,13 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/FAKE REST SERVER/.jshintignore b/FAKE REST SERVER/.jshintignore new file mode 100644 index 0000000..ee8c771 --- /dev/null +++ b/FAKE REST SERVER/.jshintignore @@ -0,0 +1,2 @@ +/client/ +/node_modules/ diff --git a/FAKE REST SERVER/.jshintrc b/FAKE REST SERVER/.jshintrc new file mode 100644 index 0000000..feb0928 --- /dev/null +++ b/FAKE REST SERVER/.jshintrc @@ -0,0 +1,21 @@ +{ + "node": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "eqeqeq": true, + "eqnull": true, + "immed": true, + "indent": 2, + "latedef": "nofunc", + "newcap": true, + "nonew": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": false, + "trailing": true, + "sub": true, + "maxlen": 80 +} diff --git a/FAKE REST SERVER/.npmignore b/FAKE REST SERVER/.npmignore new file mode 100644 index 0000000..7ec7473 --- /dev/null +++ b/FAKE REST SERVER/.npmignore @@ -0,0 +1,16 @@ +.idea +.project +*.sublime-* +.DS_Store +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.swp +*.swo +node_modules +coverage +*.tgz +*.xml diff --git a/FAKE REST SERVER/client/README.md b/FAKE REST SERVER/client/README.md new file mode 100644 index 0000000..dd00c9e --- /dev/null +++ b/FAKE REST SERVER/client/README.md @@ -0,0 +1,3 @@ +## Client + +This is the place for your application front-end files. diff --git a/FAKE REST SERVER/common/models/coffee-shop.js b/FAKE REST SERVER/common/models/coffee-shop.js new file mode 100644 index 0000000..5812a03 --- /dev/null +++ b/FAKE REST SERVER/common/models/coffee-shop.js @@ -0,0 +1,3 @@ +module.exports = function(CoffeeShop) { + +}; diff --git a/FAKE REST SERVER/common/models/coffee-shop.json b/FAKE REST SERVER/common/models/coffee-shop.json new file mode 100644 index 0000000..fd7565b --- /dev/null +++ b/FAKE REST SERVER/common/models/coffee-shop.json @@ -0,0 +1,18 @@ +{ + "name": "CoffeeShop", + "base": "PersistedModel", + "properties": { + "name": { + "type": "string", + "required": true + }, + "city": { + "type": "string", + "required": true + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": [] +} diff --git a/FAKE REST SERVER/package.json b/FAKE REST SERVER/package.json new file mode 100644 index 0000000..2ca8e7a --- /dev/null +++ b/FAKE REST SERVER/package.json @@ -0,0 +1,20 @@ +{ + "name": "external-server", + "version": "1.0.0", + "main": "server/server.js", + "scripts": { + "pretest": "jshint ." + }, + "dependencies": { + "compression": "^1.0.3", + "cors": "^2.5.2", + "loopback": "^2.22.0", + "loopback-boot": "^2.6.5", + "loopback-component-explorer": "^2.1.0", + "loopback-datasource-juggler": "^2.39.0", + "serve-favicon": "^2.0.1" + }, + "devDependencies": { + "jshint": "^2.5.6" + } +} diff --git a/FAKE REST SERVER/server/boot/authentication.js b/FAKE REST SERVER/server/boot/authentication.js new file mode 100644 index 0000000..a87cd08 --- /dev/null +++ b/FAKE REST SERVER/server/boot/authentication.js @@ -0,0 +1,4 @@ +module.exports = function enableAuthentication(server) { + // enable authentication + server.enableAuth(); +}; diff --git a/FAKE REST SERVER/server/boot/create-sample-models.js b/FAKE REST SERVER/server/boot/create-sample-models.js new file mode 100644 index 0000000..cd0858c --- /dev/null +++ b/FAKE REST SERVER/server/boot/create-sample-models.js @@ -0,0 +1,10 @@ +module.exports = function(app) { + app.models.CoffeeShop.create([ + {name: 'Bel Cafe', city: 'Vancouver'}, + {name: 'Three Bees Coffee House', city: 'San Mateo'}, + {name: 'Caffe Artigiano', city: 'Vancouver'} + ], function(err) { + if (err) throw err; + console.log('> coffee shop models created'); + }); +}; diff --git a/FAKE REST SERVER/server/boot/root.js b/FAKE REST SERVER/server/boot/root.js new file mode 100644 index 0000000..e106142 --- /dev/null +++ b/FAKE REST SERVER/server/boot/root.js @@ -0,0 +1,6 @@ +module.exports = function(server) { + // Install a `/` route that returns server status + var router = server.loopback.Router(); + router.get('/', server.loopback.status()); + server.use(router); +}; diff --git a/FAKE REST SERVER/server/component-config.json b/FAKE REST SERVER/server/component-config.json new file mode 100644 index 0000000..bbe9320 --- /dev/null +++ b/FAKE REST SERVER/server/component-config.json @@ -0,0 +1,5 @@ +{ + "loopback-component-explorer": { + "mountPath": "/explorer" + } +} \ No newline at end of file diff --git a/FAKE REST SERVER/server/config.json b/FAKE REST SERVER/server/config.json new file mode 100644 index 0000000..65efaea --- /dev/null +++ b/FAKE REST SERVER/server/config.json @@ -0,0 +1,27 @@ +{ + "restApiRoot": "/api", + "host": "0.0.0.0", + "port": 3001, + "remoting": { + "context": { + "enableHttpContext": false + }, + "rest": { + "normalizeHttpPath": false, + "xml": false + }, + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": false, + "errorHandler": { + "disableStackTrace": false + } + }, + "legacyExplorer": false +} \ No newline at end of file diff --git a/FAKE REST SERVER/server/datasources.json b/FAKE REST SERVER/server/datasources.json new file mode 100644 index 0000000..d6caf56 --- /dev/null +++ b/FAKE REST SERVER/server/datasources.json @@ -0,0 +1,6 @@ +{ + "db": { + "name": "db", + "connector": "memory" + } +} diff --git a/FAKE REST SERVER/server/middleware.json b/FAKE REST SERVER/server/middleware.json new file mode 100644 index 0000000..ea6540d --- /dev/null +++ b/FAKE REST SERVER/server/middleware.json @@ -0,0 +1,34 @@ +{ + "initial:before": { + "loopback#favicon": {} + }, + "initial": { + "compression": {}, + "cors": { + "params": { + "origin": true, + "credentials": true, + "maxAge": 86400 + } + } + }, + "session": { + }, + "auth": { + }, + "parse": { + }, + "routes": { + "loopback#rest": { + "paths": ["${restApiRoot}"] + } + }, + "files": { + }, + "final": { + "loopback#urlNotFound": {} + }, + "final:after": { + "loopback#errorHandler": {} + } +} \ No newline at end of file diff --git a/FAKE REST SERVER/server/model-config.json b/FAKE REST SERVER/server/model-config.json new file mode 100644 index 0000000..1de89d1 --- /dev/null +++ b/FAKE REST SERVER/server/model-config.json @@ -0,0 +1,39 @@ +{ + "_meta": { + "sources": [ + "loopback/common/models", + "loopback/server/models", + "../common/models", + "./models" + ], + "mixins": [ + "loopback/common/mixins", + "loopback/server/mixins", + "../common/mixins", + "./mixins" + ] + }, + "User": { + "dataSource": "db" + }, + "AccessToken": { + "dataSource": "db", + "public": false + }, + "ACL": { + "dataSource": "db", + "public": false + }, + "RoleMapping": { + "dataSource": "db", + "public": false + }, + "Role": { + "dataSource": "db", + "public": false + }, + "CoffeeShop": { + "dataSource": "db", + "public": true + } +} diff --git a/FAKE REST SERVER/server/server.js b/FAKE REST SERVER/server/server.js new file mode 100644 index 0000000..620ce22 --- /dev/null +++ b/FAKE REST SERVER/server/server.js @@ -0,0 +1,27 @@ +var loopback = require('loopback'); +var boot = require('loopback-boot'); + +var app = module.exports = loopback(); + +app.start = function() { + // start the web server + return app.listen(function() { + app.emit('started'); + var baseUrl = app.get('url').replace(/\/$/, ''); + console.log('Web server listening at: %s', baseUrl); + if (app.get('loopback-component-explorer')) { + var explorerPath = app.get('loopback-component-explorer').mountPath; + console.log('Browse your REST API at %s%s', baseUrl, explorerPath); + } + }); +}; + +// Bootstrap the application, configure models, datasources and middleware. +// Sub-apps like REST API are mounted via boot scripts. +boot(app, __dirname, function(err) { + if (err) throw err; + + // start the server if `$ node server.js` + if (require.main === module) + app.start(); +}); \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index 29d7815..12c1973 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,9 +1,21 @@ -Copyright (c) 2013-2015 StrongLoop, Inc and other contributors. +Copyright (c) 2013-2015 StrongLoop, Inc. -loopback uses a dual license model. +MIT license -You may use this library under the terms of the [MIT License][], -or under the terms of the [StrongLoop Subscription Agreement][]. +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: -[MIT License]: http://opensource.org/licenses/MIT -[StrongLoop Subscription Agreement]: http://strongloop.com/license +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/LoopBack Server/.editorconfig b/LoopBack Server/.editorconfig new file mode 100644 index 0000000..3ee22e5 --- /dev/null +++ b/LoopBack Server/.editorconfig @@ -0,0 +1,13 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/LoopBack Server/.eslintrc b/LoopBack Server/.eslintrc new file mode 100644 index 0000000..9f7dcc6 --- /dev/null +++ b/LoopBack Server/.eslintrc @@ -0,0 +1,7 @@ +{ + "env": { + "node": true, + "mocha": true + }, + "extends": "eslint:recommended" +} diff --git a/LoopBack Server/.jshintignore b/LoopBack Server/.jshintignore new file mode 100644 index 0000000..ee8c771 --- /dev/null +++ b/LoopBack Server/.jshintignore @@ -0,0 +1,2 @@ +/client/ +/node_modules/ diff --git a/LoopBack Server/.jshintrc b/LoopBack Server/.jshintrc new file mode 100644 index 0000000..feb0928 --- /dev/null +++ b/LoopBack Server/.jshintrc @@ -0,0 +1,21 @@ +{ + "node": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "eqeqeq": true, + "eqnull": true, + "immed": true, + "indent": 2, + "latedef": "nofunc", + "newcap": true, + "nonew": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": false, + "trailing": true, + "sub": true, + "maxlen": 80 +} diff --git a/LoopBack Server/.npmignore b/LoopBack Server/.npmignore new file mode 100644 index 0000000..7ec7473 --- /dev/null +++ b/LoopBack Server/.npmignore @@ -0,0 +1,16 @@ +.idea +.project +*.sublime-* +.DS_Store +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.swp +*.swo +node_modules +coverage +*.tgz +*.xml diff --git a/LoopBack Server/client/README.md b/LoopBack Server/client/README.md new file mode 100644 index 0000000..dd00c9e --- /dev/null +++ b/LoopBack Server/client/README.md @@ -0,0 +1,3 @@ +## Client + +This is the place for your application front-end files. diff --git a/LoopBack Server/common/models/magazine.js b/LoopBack Server/common/models/magazine.js new file mode 100644 index 0000000..45a8005 --- /dev/null +++ b/LoopBack Server/common/models/magazine.js @@ -0,0 +1,41 @@ +module.exports = function(Magazine) { + + // Magazine.observe('before save', function(ctx, next) { + + // console.log('> Magazine before save triggered'); + + // var model = ctx.instance; + // var coffeeShopService = Magazine.app.dataSources.CoffeeShopService; + + // coffeeShopService.find(function(err, response, context) { + // if (err) throw err; //error making request + // if (response.error) { + // next('> response error: ' + response.error.stack); + // } + // model.coffeeShops = response; + // console.log('> coffee shops fetched successfully from remote server'); + // //verify via `curl localhost:3000/api/Magazines` + // next(); + // }); + // }); + + Magazine.getCoffeeShops = function(name, cb) { + CoffeeShopService = Magazine.app.dataSources.CoffeeShopService; + CoffeeShopService.find(function(err, res) { + if (err) cb(err); + var coffeeShop = 'No Coffee Shops by the name of ' + name; + res.forEach(function(element) { + if (element.name === name) coffeeShop = element; + }); + if (typeof coffeeShop === 'string') cb(coffeeShop); + cb(null, coffeeShop); + }); + } + + Magazine.remoteMethod('getCoffeeShops', { + accepts: [{arg: 'name', type: 'string', http: {source: 'query'}, required: true}], + returns: {arg: 'response', type: 'object', root: true}, + http: {path: '/', verb: 'get'} + }); + +}; diff --git a/LoopBack Server/common/models/magazine.json b/LoopBack Server/common/models/magazine.json new file mode 100644 index 0000000..a3e4944 --- /dev/null +++ b/LoopBack Server/common/models/magazine.json @@ -0,0 +1,18 @@ +{ + "name": "Magazine", + "base": "Model", + "properties": { + "name": { + "type": "string" + }, + "coffeeShops": { + "type": [ + "object" + ] + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": [] +} diff --git a/LoopBack Server/package.json b/LoopBack Server/package.json new file mode 100644 index 0000000..d8b68c0 --- /dev/null +++ b/LoopBack Server/package.json @@ -0,0 +1,23 @@ +{ + "name": "local-server", + "version": "1.0.0", + "main": "server/server.js", + "scripts": { + "lint": "eslint **/*.js", + "test": "mocha .", + "posttest": "npm run lint" + }, + "dependencies": { + "compression": "^1.0.3", + "cors": "^2.5.2", + "loopback": "^2.22.0", + "loopback-boot": "^2.6.5", + "loopback-component-explorer": "^2.1.0", + "loopback-datasource-juggler": "^2.39.0", + "serve-favicon": "^2.0.1", + "loopback-connector-rest": "^1.5.0" + }, + "devDependencies": { + "jshint": "^2.5.6" + } +} diff --git a/LoopBack Server/server/boot/authentication.js b/LoopBack Server/server/boot/authentication.js new file mode 100644 index 0000000..a87cd08 --- /dev/null +++ b/LoopBack Server/server/boot/authentication.js @@ -0,0 +1,4 @@ +module.exports = function enableAuthentication(server) { + // enable authentication + server.enableAuth(); +}; diff --git a/LoopBack Server/server/boot/create-sample-model.js b/LoopBack Server/server/boot/create-sample-model.js new file mode 100644 index 0000000..f43d239 --- /dev/null +++ b/LoopBack Server/server/boot/create-sample-model.js @@ -0,0 +1,8 @@ +module.exports = function(app) { + // app.models.Magazine.create([ + // {name: 'Bean around the world'} + // ], function(err) { + // if (err) throw err; + // console.log('> magazine model created'); + // }); +}; diff --git a/LoopBack Server/server/boot/root.js b/LoopBack Server/server/boot/root.js new file mode 100644 index 0000000..e106142 --- /dev/null +++ b/LoopBack Server/server/boot/root.js @@ -0,0 +1,6 @@ +module.exports = function(server) { + // Install a `/` route that returns server status + var router = server.loopback.Router(); + router.get('/', server.loopback.status()); + server.use(router); +}; diff --git a/LoopBack Server/server/component-config.json b/LoopBack Server/server/component-config.json new file mode 100644 index 0000000..bbe9320 --- /dev/null +++ b/LoopBack Server/server/component-config.json @@ -0,0 +1,5 @@ +{ + "loopback-component-explorer": { + "mountPath": "/explorer" + } +} \ No newline at end of file diff --git a/LoopBack Server/server/config.json b/LoopBack Server/server/config.json new file mode 100644 index 0000000..fad1436 --- /dev/null +++ b/LoopBack Server/server/config.json @@ -0,0 +1,27 @@ +{ + "restApiRoot": "/api", + "host": "0.0.0.0", + "port": 3000, + "remoting": { + "context": { + "enableHttpContext": false + }, + "rest": { + "normalizeHttpPath": false, + "xml": false + }, + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": false, + "errorHandler": { + "disableStackTrace": false + } + }, + "legacyExplorer": false +} \ No newline at end of file diff --git a/LoopBack Server/server/datasources.json b/LoopBack Server/server/datasources.json new file mode 100644 index 0000000..803e01d --- /dev/null +++ b/LoopBack Server/server/datasources.json @@ -0,0 +1,21 @@ +{ + "db": { + "name": "db", + "connector": "memory" + }, + "CoffeeShopService": { + "name": "CoffeeShopService", + "connector": "rest", + "operations": [ + { + "template": { + "method": "GET", + "url": "http://localhost:3001/api/CoffeeShops" + }, + "functions": { + "find": [] + } + } + ] + } +} diff --git a/LoopBack Server/server/middleware.json b/LoopBack Server/server/middleware.json new file mode 100644 index 0000000..ea6540d --- /dev/null +++ b/LoopBack Server/server/middleware.json @@ -0,0 +1,34 @@ +{ + "initial:before": { + "loopback#favicon": {} + }, + "initial": { + "compression": {}, + "cors": { + "params": { + "origin": true, + "credentials": true, + "maxAge": 86400 + } + } + }, + "session": { + }, + "auth": { + }, + "parse": { + }, + "routes": { + "loopback#rest": { + "paths": ["${restApiRoot}"] + } + }, + "files": { + }, + "final": { + "loopback#urlNotFound": {} + }, + "final:after": { + "loopback#errorHandler": {} + } +} \ No newline at end of file diff --git a/LoopBack Server/server/model-config.json b/LoopBack Server/server/model-config.json new file mode 100644 index 0000000..26abf40 --- /dev/null +++ b/LoopBack Server/server/model-config.json @@ -0,0 +1,34 @@ +{ + "_meta": { + "sources": [ + "loopback/common/models", + "loopback/server/models", + "../common/models", + "./models" + ] + }, + "User": { + "dataSource": "db", + "public": false + }, + "AccessToken": { + "dataSource": "db", + "public": false + }, + "ACL": { + "dataSource": "db", + "public": false + }, + "RoleMapping": { + "dataSource": "db", + "public": false + }, + "Role": { + "dataSource": "db", + "public": false + }, + "Magazine": { + "dataSource": false, + "public": true + } +} diff --git a/LoopBack Server/server/server.js b/LoopBack Server/server/server.js new file mode 100644 index 0000000..6c12246 --- /dev/null +++ b/LoopBack Server/server/server.js @@ -0,0 +1,25 @@ +var loopback = require('loopback'); +var boot = require('loopback-boot'); + +var app = module.exports = loopback(); + +// boot scripts mount components like REST API +boot(app, __dirname); + +app.start = function() { + // start the web server + return app.listen(function() { + app.emit('started'); + var baseUrl = app.get('url').replace(/\/$/, ''); + console.log('Web server listening at: %s', baseUrl); //eslint-disable-line no-console + if (app.get('loopback-component-explorer')) { + var explorerPath = app.get('loopback-component-explorer').mountPath; + console.log('Browse your REST API at %s%s', baseUrl, explorerPath); //eslint-disable-line no-console + } + }); +}; + +// start the server if `$ node server.js` +if (require.main === module) { + app.start(); +} diff --git a/README.md b/README.md index 9249b41..7282c74 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,61 @@ -# WORK IN PROGRESS +[strongloop-website]: http://strongloop.com/ -This repo is still in the works. The contents may or not be up to date at this -point in time. +#loopback-example-rest-connector ---- +- [Overview](#Overview) +- [Running the app](#running-the-app) +- [FAQs](#faqs) + +## Overview +This example demonstrates basic usage of [loopback-connector-rest](https://github.com/strongloop/loopback-connector-rest). + +The project has two servers: `local-server` and `external-server`. The "external" server +serves a simple REST API, while the local server fetches data using +this REST API. + +## Running the app + +``` +$ git clone https://github.com/strongloop/loopback-example-rest-connector.git +$ cd loopback-example-rest-connector/external-server +$ npm install +# node . +``` +In another shell: +``` +$ cd local-server +$ npm install +$ node . +``` + +You should see console messages on the local server. Verify the data has been +retrieved from the remote server by doing a GET request on Magazines in the +explorer or by running `curl localhost:3000/api/Magazines`. -# loopback-example-connector +## FAQs +The following are common questions related to using the REST connector. -LoopBack connector examples. +##How do you perform a GET request to a remote server? +In this example, we have a REST API exposed in [model-config.json](https://github.com/strongloop/loopback-example-rest-connector/blob/master/external-server/server/model-config.json#L31) +. -## Getting started +To make a request to the remote server, declare new datasource that uses the +REST connector in the local [datasources.json](https://github.com/strongloop/loopback-example-rest-connector/blob/master/local-server/server/datasources.json#L6-L20). -Switch to a branch to view the corresponding connector example in the table -below: +A few things to note in this file is the `connector` property's value is `rest` and +there is an `operations` property that takes an array of objects. + +This object has two properties: `template` and `function`. + +The `template` property contains `method` which is the HTTP method type to +perform the request with and `url` which is the URL to the remote resource. + +The `function` property is the name of the property you will use to trigger the +request. For example, we name our property `find` because we will trigger the +request using `Magazine.find()...`. + +The idea is to use [`find`](https://github.com/strongloop/loopback-example-rest-connector/blob/master/local-server/server/datasources.json#L16) to make a [`GET`](https://github.com/strongloop/loopback-example-rest-connector/blob/master/local-server/server/datasources.json#L12) request to a [`url`](https://github.com/strongloop/loopback-example-rest-connector/blob/master/local-server/server/datasources.json#L13) we specify. + +--- -Connector|Branch -:--|:-- -Remote|https://github.com/strongloop/loopback-example-connector/tree/remote -SOAP|https://github.com/strongloop/loopback-example-connector/tree/soap +[More LoopBack examples](https://github.com/strongloop/loopback-example) diff --git a/example/comparison/geocode-qlio.js b/example/comparison/geocode-qlio.js new file mode 100644 index 0000000..a97a9ad --- /dev/null +++ b/example/comparison/geocode-qlio.js @@ -0,0 +1,25 @@ +var Engine = require('ql.io-engine'); + +// Create an instance of ql.io engine +var engine = new Engine({}); + +// Define the script with two statements +// First create a virtual table +var script = "create table google.geocode on " + + " select get from \"http://maps.googleapis.com/maps/api/geocode/{format}?sensor=true&latlng={^latlng}\"" + + " using defaults format = 'json'" + + " resultset 'results'\n"; + +// Define a select statement +script += "select formatted_address from google.geocode where latlng='40.714224,-73.961452'"; + +// Run the query +engine.execute(script, function (req) { + req.on('end', function (err, results) { + console.log(results); + }) +}) + + + + diff --git a/example/comparison/geocode-request.js b/example/comparison/geocode-request.js new file mode 100644 index 0000000..789e863 --- /dev/null +++ b/example/comparison/geocode-request.js @@ -0,0 +1,37 @@ +var request = require('request'); + +var processResponse = function (error, response, body) { + if (!error) { + if (typeof body === 'string') { + body = JSON.parse(body); + } + + if (body.status === 'OK') { + console.log(body.results[0].formatted_address); + } else { + console.log('Error: ', body.status); + } + + } else { + console.log('Error: ' + response.statusCode) + console.log('Body: ', body) + } +}; + +request('http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true', processResponse); + +request.get('http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true', processResponse); + +request( + { + method: 'GET', + uri: 'http://maps.googleapis.com/maps/api/geocode/json', + qs: { + latlng: '40.714224,-73.961452', + sensor: true + } + }, + processResponse +) + + diff --git a/example/comparison/geocode-restler.js b/example/comparison/geocode-restler.js new file mode 100644 index 0000000..bda9fb5 --- /dev/null +++ b/example/comparison/geocode-restler.js @@ -0,0 +1,43 @@ +var restler = require('restler'); + +var processResponse = function (prefix, body, response) { + // console.log(body); + if (typeof body === 'string') { + body = JSON.parse(body); + } + + if (body.status === 'OK') { + console.log(prefix, 'Address: ', body.results[0].formatted_address); + } else { + console.log(prefix, 'Error: ', body.status); + } +}; + +var req1 = restler.get('http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true'); +req1.on('complete', processResponse.bind(null, '1')); + +var req2 = restler.request('http://maps.googleapis.com/maps/api/geocode/json', + { + method: 'GET', + query: { + latlng: '40.714224,-73.961452', + sensor: true + } + } +); + +req2.on('complete', processResponse.bind(null, '2')); + +var GeoService = restler.service(function (sensor) { + // this.defaults.query = {sensor: sensor ? true : false}; +}, { + baseURL: 'http://maps.googleapis.com/maps/api/geocode/' +}, { + getAddress: function (latlng, cb) { + return this.get('json', {query: {sensor: true, latlng: latlng}}).on('complete', cb); + } +}); + +var geoService = new GeoService(false); +// geoService.getAddress('40.714224,-73.961452'); +geoService.getAddress('40.714224,-73.961452', processResponse.bind(null, '3')); diff --git a/example/comparison/geocode-superagent.js b/example/comparison/geocode-superagent.js new file mode 100644 index 0000000..dff1b57 --- /dev/null +++ b/example/comparison/geocode-superagent.js @@ -0,0 +1,21 @@ +var agent = require('superagent'); + +var processResponse = function (prefix, err, response) { + var body = response.body; + if (typeof body === 'string') { + body = JSON.parse(body); + } + + if (body.status === 'OK') { + console.log(prefix, 'Address: ', body.results[0].formatted_address); + } else { + console.log(prefix, 'Error: ', body.status); + } +}; + +agent.get('http://maps.googleapis.com/maps/api/geocode/json') + .query({latlng: '40.714224,-73.961452', sensor: true}) + .end(processResponse.bind(null, '1')); + +agent.get('http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true', processResponse.bind(null, '2')); + diff --git a/example/comparison/geocode-swagger.js b/example/comparison/geocode-swagger.js new file mode 100644 index 0000000..85f9068 --- /dev/null +++ b/example/comparison/geocode-swagger.js @@ -0,0 +1,75 @@ +var apiList = +{ + apiVersion: "1.0", + swaggerVersion: "1.0", + basePath: "http://maps.googleapis.com/maps/api/", + + apis: [ + { + path: "/geocode/{format}", + description: "Google Maps Geocode API" + } + ] +}; + +var geocodeApiSpec = { + resourcePath: "/geocode", + basePath: "http://maps.googleapis.com/maps/api/", + apis: [ + { + swaggerVersion: "1.0", + apiVersion: "1.0", + path: "geocode/{format}", + description: "", + operations: [ + { + parameters: [ + { + name: "latlng", + description: "Latitude & Longitude (lat,lng)", + required: true, + dataType: "string", + paramType: "query" + }, + { + name: "sensor", + description: "Sensor", + required: true, + dataType: "boolean", + paramType: "query" + } + ], + summary: "Geocode reverse lookup", + httpMethod: "GET", + errorResponses: [ + { + reason: "Internal Error", + code: 500 + } + ], + nickname: "geocode", + responseClass: "AddressComponents" + } + + ], + models: { + AddressComponents: { + uniqueItems: false, + properties: { + formatted_address: { + uniqueItems: false, + type: "string", + required: false + }, + status: { + uniqueItems: false, + type: "string", + required: false + } + } + } + } + } + ] +} + diff --git a/example/comparison/geocode.ql b/example/comparison/geocode.ql new file mode 100644 index 0000000..1ad1ef3 --- /dev/null +++ b/example/comparison/geocode.ql @@ -0,0 +1,7 @@ +create table google.geocode on +select get from "http://maps.googleapis.com/maps/api/geocode/{format}?sensor=true&latlng={^latlng}" +using defaults format = 'json' +resultset 'results'; + +return select formatted_address from google.geocode where latlng='40.714224,-73.961452'; + diff --git a/example/comparison/qlio-connector/loopback.js b/example/comparison/qlio-connector/loopback.js new file mode 100644 index 0000000..8972c10 --- /dev/null +++ b/example/comparison/qlio-connector/loopback.js @@ -0,0 +1,18 @@ +console.log('Loading LoopbackConnector'); + +var LoopbackConnector = module.exports = + + function (table, statement, type, bag, path) { + console.log('Exporting LoopbackConnector: ', table, statement, type, bag, path); + + this.exec = function (args) { + console.log('Executing LoopbackConnector: ', args); + return args.callback(null, {headers: {}, body: {results: [ + {formatted_address: '123 Street'} + ]}}); + } + }; + +LoopbackConnector.connectorName = 'loopback'; + + diff --git a/example/comparison/qlio-loopback-connector.js b/example/comparison/qlio-loopback-connector.js new file mode 100644 index 0000000..76f9b68 --- /dev/null +++ b/example/comparison/qlio-loopback-connector.js @@ -0,0 +1,47 @@ +var Engine = require('ql.io-engine'); +var path = require('path'); + +console.log(__dirname); + +var engine = new Engine({ + connectors: path.join(__dirname, './qlio-connector') +}); +var script = 'create table loopback.inventory via loopback on ' + + ' select do find at "inventory where product={^product}"' + + ' using defaults format = \'json\'' + + ' resultset \'results\''; + +script += '\nselect formatted_address from loopback.inventory where product=\'p001\''; + +var inFlight, success, error, request, response; +engine.execute(script, function (req) { + /* + req.on(Engine.Events.STATEMENT_IN_FLIGHT, function() { + console.log ("Statement In_flight event"); + inFlight = true; + }); + req.on(Engine.Events.STATEMENT_SUCCESS, function() { + console.log ("Statement Success event"); + success = true; + }); + req.on(Engine.Events.STATEMENT_REQUEST, function() { + console.log ("Statement Request event"); + request = true; + }); + req.on(Engine.Events.STATEMENT_RESPONSE, function() { + console.log ("Statement Response event"); + response = true; + }); + req.on(Engine.Events.STATEMENT_ERROR, function() { + console.log ("Statement Error event"); + error = true; + }); + */ + req.on('end', function (err, results) { + console.log('Results: ', err, results); + }) +}) + + + + diff --git a/example/geocode.json b/example/geocode.json new file mode 100644 index 0000000..a01752e --- /dev/null +++ b/example/geocode.json @@ -0,0 +1,11 @@ +{ + "method": "GET", + "url": "http://maps.googleapis.com/maps/api/geocode/{format=json}", + "headers": { + "accept": "application/json" + }, + "query": { + "latlng": "{latitude},{longitude}", + "sensor": "{sensor=true}" + } +} diff --git a/example/rest-builder-app.js b/example/rest-builder-app.js new file mode 100644 index 0000000..d3d9fa7 --- /dev/null +++ b/example/rest-builder-app.js @@ -0,0 +1,69 @@ +var restConnector = require('../lib/rest-builder'); + +// Define a callback to handle the response +var processResponse = function (error, result, response) { + if (!error) { + var body = response.body; + if (typeof body === 'string') { + body = JSON.parse(body); + } + + if (body.status === 'OK') { + if (Array.isArray(result)) { + console.log('Result:', result); + } + console.log('formatted_address', body.results[0].formatted_address); + console.log('geometry.location', body.results[0].geometry.location); + } else { + console.log('Error: ', body.status); + } + + } else { + console.log('Error: ' + error) + console.log('Body: ', body) + } +}; + +// Build a REST API request using templates +var req = restConnector.get('http://maps.googleapis.com/maps/api/geocode/{format=json}') + .query({latlng: '{!latitude:number},{!longitude:number}', sensor: '{sensor=true}'}).responsePath('$.results[0].formatted_address') +// .body({x: 1, y: 'y', z: [1, 2, '{z:3}']}); + +// var schema = req.parse(); +// console.log(schema); + +// Now we can invoke the REST API using an object that provide values to the templatized variables +req.invoke({latitude: 40.714224, longitude: -73.961452, sensor: true}, processResponse); + +// The 2nd flavor is to construct a function from the request by +// specifying an array of parameter names corresponding to the templatized variables +var fn = req.operation(['latitude', 'longitude']); + +// Now we invoke the REST API as a method +fn(40.714224, -73.961452, processResponse); + +// We can also directly use request apis +restConnector.request('http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true', processResponse); + +restConnector.request({ + method: 'GET', + uri: 'http://maps.googleapis.com/maps/api/geocode/json', + qs: { + latlng: '40.714224,-73.961452', + sensor: true + } +}, processResponse); + +// Load the spec from json doc +var spec = require('./geocode.json'); +req = restConnector.fromJSON(spec); + +// Now we can invoke the REST API using an object that provide values to the templatized variables +req.invoke({latitude: 40.714224, longitude: -73.961452, sensor: true}, processResponse); + +// Lookup by address +var req2 = restConnector.get('http://maps.googleapis.com/maps/api/geocode/{format=json}') + .query({address: '{address}', sensor: '{sensor=false}', components: 'country:US'}); + +req2.invoke({address: '107 S B St, San Mateo, CA'}, processResponse); + diff --git a/example/rest-crud-app.js b/example/rest-crud-app.js new file mode 100644 index 0000000..e2c6ad5 --- /dev/null +++ b/example/rest-crud-app.js @@ -0,0 +1,113 @@ +var loopback = require('loopback'); + +// simplier way to describe model +var User = loopback.createModel('User', { + name: String, + bio: String, + approved: Boolean, + joinedAt: Date, + age: Number +}); + +console.log(User.modelName); + +var app = loopback(); + +app.set('port', process.env.PORT || 3000); +app.use(loopback.bodyParser.json({extended: false})); + +var count = 2; +var users = [new User({id: 1, name: 'Ray'}), new User({id: 2, name: 'Joe'})] + +app.get('/Users', function (req, res, next) { + res.setHeader('Content-Type', 'application/json'); + res.json(users); + res.end(); +}); + +app.post('/Users', function (req, res, next) { + res.setHeader('Content-Type', 'application/json'); + var body = req.body; + if (!body.id) { + body.id = (++count); + } + res.setHeader('Location', req.protocol + '://' + req.headers['host'] + '/' + body.id); + users.push(body); + res.status(201).json(body); + res.end(); +}); + +app.put('/Users/:id', function (req, res, next) { + for (var i = 0; i < users.length; i++) { + var user = users[i]; + if (user.id == req.id) { + res.setHeader('Content-Type', 'application/json'); + users[i] = body; + res.end(null, 200); + return; + } + } + res.end(null, 404); +}); + +app.delete('/Users/:id', function (req, res, next) { + for (var i = 0; i < users.length; i++) { + var user = users[i]; + if (user.id == req.params.id) { + res.setHeader('Content-Type', 'application/json'); + users.splice(i, 1); + res.end(null, 200); + return; + } + } + res.end(404); +}); + +app.get('/Users/:id', function (req, res, next) { + for (var i = 0; i < users.length; i++) { + var user = users[i]; + if (user.id == req.params.id) { + res.setHeader('Content-Type', 'application/json'); + res.json(user); + res.end(); + return; + } + } +}); + +app.listen(app.get('port'), function (err, data) { + console.log('Server listening on 3000.'); + var RestResource = require('../lib/rest-model'); + + var rest = new RestResource('Users', 'http://localhost:3000'); + + rest.query(function (err, body, response) { + console.log(body); + }); + + rest.find(1, function (err, body, response) { + console.log(err, response && response.statusCode); + console.log(body); + }); + + rest.update(1, new User({name: 'Raymond'}), function (err, body, response) { + console.log(err, response && response.statusCode); + }); + + rest.delete(1, function (err, body, response) { + console.log(err, response && response.statusCode); + }); + + rest.create(new User({name: 'Mary'}), function (err, body, response) { + console.log(response && response.statusCode); + console.log(response && response.headers['location']); + console.log(body); + }); + + rest.query(function (err, body, response) { + console.log(body); + }); + + console.log('Press Ctrl+C to exit.'); + +}); \ No newline at end of file diff --git a/example/rest-loopback-crud.js b/example/rest-loopback-crud.js new file mode 100644 index 0000000..3ae001b --- /dev/null +++ b/example/rest-loopback-crud.js @@ -0,0 +1,123 @@ +var loopback = require("loopback"); +var app = loopback(); + +app.set('port', process.env.PORT || 3000); +app.use(loopback.bodyParser.json({extended: false})); + +var ds = loopback.createDataSource({ + connector: require("../index"), + debug: false, + baseURL: 'http://localhost:3000' +}); + +var User = ds.createModel('User', { + name: String, + bio: String, + approved: Boolean, + joinedAt: Date, + age: Number +}); + +var count = 2; +var users = [new User({id: 1, name: 'Ray'}), new User({id: 2, name: 'Joe'})] + +app.get('/Users', function (req, res, next) { + res.setHeader('Content-Type', 'application/json'); + res.json(users); + res.end(); +}); + +app.post('/Users', function (req, res, next) { + res.setHeader('Content-Type', 'application/json'); + var body = req.body; + if (!body.id) { + body.id = (++count); + } + res.setHeader('Location', req.protocol + '://' + req.headers['host'] + '/' + body.id); + users.push(body); + res.status(201).json(body); + res.end(); +}); + +app.put('/Users/:id', function (req, res, next) { + for (var i = 0; i < users.length; i++) { + var user = users[i]; + if (user.id == req.id) { + res.setHeader('Content-Type', 'application/json'); + users[i] = body; + res.end(null, 200); + return; + } + } + res.end(null, 404); +}); + +app.delete('/Users/:id', function (req, res, next) { + for (var i = 0; i < users.length; i++) { + var user = users[i]; + if (user.id == req.params.id) { + res.setHeader('Content-Type', 'application/json'); + users.splice(i, 1); + res.end(null, 200); + return; + } + } + res.end(404); +}); + +app.get('/Users/:id', function (req, res, next) { + for (var i = 0; i < users.length; i++) { + var user = users[i]; + if (user.id == req.params.id) { + res.setHeader('Content-Type', 'application/json'); + res.json(user); + res.end(); + return; + } + } +}); + +app.listen(app.get('port'), function (err, data) { + console.log('Server listening on 3000.'); + + User.find(function (err, user) { + console.log(user); + }); + + User.findById(1, function (err, user) { + console.log(err, user); + }); + + User.upsert(new User({id: 1, name: 'Raymond'}), function (err, user) { + console.log(err, user); + }); + + User.findById(1, function (err, user) { + console.log(err, user); + if (!err) { + user.delete(function (err, user) { + console.log(err, user); + }); + } + + }); + + /* + User.delete(1, function (err, user) { + console.log(err, user); + }); + */ + + User.create(new User({name: 'Mary'}), function (err, user) { + console.log(user); + }); + + User.find(function (err, user) { + console.log(user); + }); + + console.log('Press Ctrl+C to exit.'); + +}); + + diff --git a/example/rest-loopback-geocode.js b/example/rest-loopback-geocode.js new file mode 100644 index 0000000..696c65f --- /dev/null +++ b/example/rest-loopback-geocode.js @@ -0,0 +1,46 @@ +var loopback = require("loopback"); + +var ds = loopback.createDataSource({ + connector: require("../index"), + strictSSL: false, + debug: false, + defaults: { + "headers": { + "accept": "application/json", + "content-type": "application/json" + } + }, + operations: [ + { + template: { + "method": "GET", + "url": "http://maps.googleapis.com/maps/api/geocode/{format=json}", + "query": { + "address": "{street},{city},{zipcode}", + "sensor": "{sensor=false}" + }, + "options": { + "strictSSL": true, + "useQuerystring": true + }, + "responsePath": "$.results[0].geometry.location" + }, + functions: { + "geocode": ["street", "city", "zipcode"] + } + } + ]}); + +var model = ds.createModel('dummy'); + +var loc = { + street: '107 S B St', + city: 'San Mateo', + zipcode: '94401' +}; + +model.geocode(loc.street, loc.city, loc.zipcode, function (err, result) { + if (result && result[0]) { + console.log(result[0]); + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..6b5a8d0 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "loopback-example-rest-connector", + "version": "1.0.0", + "description": "LoopBack REST connector example", + "repository": { + "type": "git", + "url": "git+https://github.com/strongloop/loopback-example-rest-connector.git" + }, + "keywords": [ + "LoopBack", + "REST", + "connector" + ], + "author": "Simon Ho", + "license": "MIT", + "bugs": { + "url": "https://github.com/strongloop/loopback-example-rest-connector/issues" + }, + "homepage": "https://github.com/strongloop/loopback-example-rest-connector#readme", + "main": "index.js", + "scripts": { + "test": "tape tests/**/*.js" + }, + "devDependencies": { + "tape": "^4.2.0" + } +} diff --git a/tests/smoke.js b/tests/smoke.js new file mode 100644 index 0000000..b044d26 --- /dev/null +++ b/tests/smoke.js @@ -0,0 +1,6 @@ +var test = require('tape'); + +test('smoke test', function(t) { + t.plan(1); + t.equal(1, 1); +});