├── .bluemix └── pipeline.yml ├── .gitignore ├── .jshintignore ├── .jshintrc ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE ├── Procfile ├── README.md ├── bin └── create-load.js ├── client ├── css │ ├── bootstrap.min.css │ ├── code.css │ └── main.css ├── fonts │ ├── 0ihfXUL2emPh0ROJezvraLO3LdcAZYWl9Si6vvxL-qU.woff │ ├── OsJ2DjdpjqFRVUSto6IffLO3LdcAZYWl9Si6vvxL-qU.woff │ └── _aijTyevf54tkVDLy-dlnLO3LdcAZYWl9Si6vvxL-qU.woff ├── index.html └── js │ ├── bootstrap.min.js │ ├── jquery-scrollTo.js │ ├── jquery.min.js │ └── main.js ├── common └── models │ ├── car.json │ ├── customer.json │ ├── inventory.json │ ├── location.js │ ├── location.json │ └── note.json ├── package.json ├── server ├── boot │ ├── authentication.js │ ├── create-sample-data.js │ ├── explorer.js │ └── rest-api.js ├── config.json ├── datasources.json ├── datasources.local.js ├── model-config.json ├── sample-data │ ├── cars.json │ ├── customers.json │ ├── generate-inventory.js │ ├── import.js │ ├── inventory.json │ ├── locations.json │ ├── oracle-tables.sql │ └── updatedb.js ├── server.js └── test │ └── rest-api.test.js └── strong-resources ├── app.js ├── node_modules └── http-proxy │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── lib │ ├── http-proxy.js │ └── http-proxy │ │ ├── common.js │ │ ├── index.js │ │ └── passes │ │ ├── web-incoming.js │ │ ├── web-outgoing.js │ │ └── ws-incoming.js │ ├── node_modules │ ├── eventemitter3 │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.js │ │ └── package.json │ └── requires-port │ │ ├── .npmignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── index.js │ │ ├── package.json │ │ └── test.js │ └── package.json ├── package.json └── startup /.bluemix/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | stages: 3 | - name: Deploy App 4 | inputs: 5 | - type: git 6 | branch: master 7 | triggers: 8 | - type: commit 9 | jobs: 10 | - name: Push to Bluemix 11 | type: deployer 12 | target: 13 | url: ${CF_TARGET_URL} 14 | organization: ${CF_ORGANIZATION} 15 | space: ${CF_SPACE} 16 | application: ${CF_APP} 17 | script: | 18 | cf push -m 640M "${CF_APP}" 19 | cf map-route "${CF_APP}" mybluemix.net -n "${CF_APP}"-pm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .project 3 | *.sublime-* 4 | .DS_Store 5 | *.seed 6 | *.log 7 | *.csv 8 | *.dat 9 | *.out 10 | *.pid 11 | *.swp 12 | *.swo 13 | coverage 14 | *.tgz 15 | *.xml 16 | /node_modules/ 17 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | /client/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "eqeqeq": true, 7 | "eqnull": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": "nofunc", 11 | "newcap": true, 12 | "nonew": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "trailing": true, 19 | "sub": true, 20 | "maxlen": 80 21 | } 22 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2015-08-04, Version 3.1.0 2 | ========================= 3 | 4 | * Move to a new instance of oracle demo DB (Raymond Feng) 5 | 6 | * Use Operation hooks instead of legacy model hooks (Hage Yaapa) 7 | 8 | * Fix a typo (Raymond Feng) 9 | 10 | * Update README.md (Rand McKinney) 11 | 12 | * Clarifications for newcomers (Rand McKinney) 13 | 14 | * deps: add jshint as dev dependency (Ryan Graham) 15 | 16 | * Changed slc run to node . (DeniseLee) 17 | 18 | * lat and lng are reversed (Fabian Jakobs) 19 | 20 | * Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham) 21 | 22 | * Remove unused reservation model relation (Simon Ho) 23 | 24 | * fixed command (Shannon Code) 25 | 26 | * Add contribution guidelines (Ryan Graham) 27 | 28 | * Fix the scripts (Raymond Feng) 29 | 30 | * Change yo loopback to slc loopback everywhere (Rand McKinney) 31 | 32 | * add geo to sample locations (Miroslav Bajtoš) 33 | 34 | * Fix description for geo location (Raymond Feng) 35 | 36 | * Update to loopback:example 2.0.0 (Miroslav Bajtoš) 37 | 38 | * Update license link (Sam Roberts) 39 | 40 | * bin: fix jshint warnings (Miroslav Bajtoš) 41 | 42 | * README: describe `yo loopback:example` + cleanup (Miroslav Bajtoš) 43 | 44 | * Upgrade to the new project layout (Miroslav Bajtoš) 45 | 46 | * Drop support for Cloud9. (Miroslav Bajtoš) 47 | 48 | * Rename the module (Raymond Feng) 49 | 50 | 51 | 2014-07-02, Version 2.0.5 52 | ========================= 53 | 54 | * Bump version (Raymond Feng) 55 | 56 | * Update config (Raymond Feng) 57 | 58 | 59 | 2014-07-01, Version 2.0.4 60 | ========================= 61 | 62 | * Update versions (Raymond Feng) 63 | 64 | 65 | 2014-05-20, Version 2.0.3 66 | ========================= 67 | 68 | * Bump version (Raymond Feng) 69 | 70 | * Update deps (Raymond Feng) 71 | 72 | 73 | 2014-05-05, Version 2.0.2 74 | ========================= 75 | 76 | * move async to dependencies from optional deps (Sam Roberts) 77 | 78 | * move async to a run-time dependency (Sam Roberts) 79 | 80 | * Updated C9 readme (cgole) 81 | 82 | 83 | 2014-04-04, Version 2.0.1 84 | ========================= 85 | 86 | * Minor tweak of the message paragraph (Raymond Feng) 87 | 88 | * Change StrongLoop Suite to LoopBack (Raymond Feng) 89 | 90 | * Allow tests to run against demo oracle/mongodb (Miroslav Bajtoš) 91 | 92 | * Add the utility to recreate tables (Raymond Feng) 93 | 94 | * Enable access control. (Miroslav Bajtoš) 95 | 96 | * create-load: error on failure to connect (Sam Roberts) 97 | 98 | * Update deps (Raymond Feng) 99 | 100 | 101 | 2014-02-05, Version 2.0.0 102 | ========================= 103 | 104 | * Bump the version to 2.0.0 (Raymond Feng) 105 | 106 | * Upgrade versions (Raymond Feng) 107 | 108 | * Remove console.log in test (Raymond Feng) 109 | 110 | * Minor wording corrections. (Rand McKinney) 111 | 112 | * Minor wording cleanup. (Rand McKinney) 113 | 114 | * Remove console.log (Raymond Feng) 115 | 116 | * Update the test data and import utility (Raymond Feng) 117 | 118 | * Replace weapon/ammo with car/note models (Raymond Feng) 119 | 120 | * Remove dependency on agent and cluster-control (Sam Roberts) 121 | 122 | 123 | 2014-01-07, Version 1.1.1 124 | ========================= 125 | 126 | * Fix the base url to use /api (Raymond Feng) 127 | 128 | 129 | 2013-12-17, Version 1.1.0 130 | ========================= 131 | 132 | * Update mongodb connector version (Raymond Feng) 133 | 134 | * Customize the url output for Cloud9 (Raymond Feng) 135 | 136 | * Clean up the dead code/comment (Raymond Feng) 137 | 138 | * Fix test cases (Raymond Feng) 139 | 140 | * Update to the latest loopback 1.3.x (Raymond Feng) 141 | 142 | * Check the env var to create the base URL (Raymond Feng) 143 | 144 | * Print to stderr so we don't interfer with test reports (Ryan Graham) 145 | 146 | * Ignore test related files and node_modules (Ryan Graham) 147 | 148 | * Update to allow latest version of cluster-control (Sam Roberts) 149 | 150 | * Adding .jshintignore file (Ryan Graham) 151 | 152 | * remove unused blanket dependencies (Sam Roberts) 153 | 154 | 155 | 2013-10-29, Version strongloopsuite-1.1.0-4 156 | =========================================== 157 | 158 | 159 | 160 | 2013-10-29, Version strongloopsuite-1.1.0-6 161 | =========================================== 162 | 163 | 164 | 165 | 2013-10-29, Version strongloopsuite-1.1.0-5 166 | =========================================== 167 | 168 | 169 | 170 | 2013-10-29, Version strongloopsuite-1.1.0-7 171 | =========================================== 172 | 173 | 174 | 175 | 2013-10-29, Version strongloopsuite-1.1.0-3 176 | =========================================== 177 | 178 | 179 | 180 | 2013-10-29, Version strongloopsuite-1.1.0-2 181 | =========================================== 182 | 183 | 184 | 185 | 2013-10-29, Version strongloopsuite-1.1.0-1 186 | =========================================== 187 | 188 | 189 | 190 | 2013-10-29, Version strongloopsuite-1.1.0-0 191 | =========================================== 192 | 193 | * Updated to rest connector 1.1.0 (cgole) 194 | 195 | * rest connector 1.1.0 is not published yet (cgole) 196 | 197 | * Release sls-1.1 (cgole) 198 | 199 | * Update version and deps for sls-1.1 (Raymond Feng) 200 | 201 | * Add CORS support. (Michael Schoonmaker) 202 | 203 | * Add npm start script. (Michael Schoonmaker) 204 | 205 | * Enable mysql connector (Raymond Feng) 206 | 207 | * Fix the package.json due to merging mistake (Raymond Feng) 208 | 209 | * Update the deps (Raymond Feng) 210 | 211 | * Remove unused collapsible navbar. (Michael Schoonmaker) 212 | 213 | * Hide TOC on mobile. (Michael Schoonmaker) 214 | 215 | 216 | 2013-09-11, Version strongloopsuite-1.0.0-5 217 | =========================================== 218 | 219 | 220 | 221 | 2013-09-11, Version strongloopsuite-1.0.0-4 222 | =========================================== 223 | 224 | * Remove unused collapsible navbar. (Michael Schoonmaker) 225 | 226 | * Hide TOC on mobile. (Michael Schoonmaker) 227 | 228 | * added more info to readme (altsang) 229 | 230 | * Upgrade to strong-agent 0.2.15 (Raymond Feng) 231 | 232 | * changed first bullet to refer to getting started guide instead of quick start (Al Tsang) 233 | 234 | 235 | 2013-09-10, Version strongloopsuite-1.0.0-2 236 | =========================================== 237 | 238 | 239 | 240 | 2013-09-10, Version strongloopsuite-1.0.0-3 241 | =========================================== 242 | 243 | * Add oracle/mongodb config for the demo server (Raymond Feng) 244 | 245 | * Upgrade to with strong-agent 0.2.11 (Raymond Feng) 246 | 247 | * Fix the merge error (Raymond Feng) 248 | 249 | * Fix the id (Raymond Feng) 250 | 251 | * Fix the id substitution Change the id generations so that load-generator can predict the id (Raymond Feng) 252 | 253 | * Add safeMode flag for GET-only requests. (Michael Schoonmaker) 254 | 255 | * Add a load generator bin script. (Michael Schoonmaker) 256 | 257 | * Finalize package.json for sls-1.0.0 (Raymond Feng) 258 | 259 | * Changed tag to strongloopsuite-1.0.0-2 (cgole) 260 | 261 | 262 | 2013-09-09, Version strongloopsuite-1.0.0-1 263 | =========================================== 264 | 265 | * Fix the test case (Raymond Feng) 266 | 267 | * Tidy up the name ref to sls-sample-app (Raymond Feng) 268 | 269 | * Change the env var to PARTNER (Raymond Feng) 270 | 271 | * Set default README.md based on PAAS_PROVIDER env var (Raymond Feng) 272 | 273 | * Allows custom listener ip (Raymond Feng) 274 | 275 | * Update README (Raymond Feng) 276 | 277 | * Update C9_README.md (Ritchie Martori) 278 | 279 | * Add config to support oracle demo (Raymond Feng) 280 | 281 | 282 | 2013-09-05, Version strongloopsuite-1.0.0-0 283 | =========================================== 284 | 285 | * Updated to use tagged version strongloopsuite-1.0.0-0 of dependencies (cgole) 286 | 287 | * Add sql for oracle tests (Raymond Feng) 288 | 289 | * Add newline to end of package.json (Sam Roberts) 290 | 291 | * jshint configuration (Sam Roberts) 292 | 293 | * Tidy up package.json for LoopBack 1.0.0 (Raymond Feng) 294 | 295 | * Update license file (Raymond Feng) 296 | 297 | * Add a console message to report the url (SLA-486) (Raymond Feng) 298 | 299 | * Fix js style using closure_linter (Sam Roberts) 300 | 301 | * Use minimal call to strong-agent's .profile(). (Sam Roberts) 302 | 303 | * Add new homepage copy and sample requests. (Michael Schoonmaker) 304 | 305 | * Use cluster control if configured for clustering (Sam Roberts) 306 | 307 | * Move blanket and mocha to devDependencies (Sam Roberts) 308 | 309 | * Use consistent capitalization in comments (Sam Roberts) 310 | 311 | * Remove old API references (Ritchie Martori) 312 | 313 | * Remove the old sample REST calls (Ritchie Martori) 314 | 315 | * Add Cloud9 readme (Ritchie Martori) 316 | 317 | * Update the model so that it has one id property to support findById (Raymond Feng) 318 | 319 | * Make config more flexible. (Michael Schoonmaker) 320 | 321 | * Use rc to load configurations (Raymond Feng) 322 | 323 | * Add schema-less Ammo model. (Michael Schoonmaker) 324 | 325 | * Repurpose as StrongLoop Sample App. (Michael Schoonmaker) 326 | 327 | * Add description to the nearby method (Raymond Feng) 328 | 329 | * Fix a typo (Raymond Feng) 330 | 331 | * Fix html to make resource url relative, remove broken link, allows delete (Raymond Feng) 332 | 333 | * Upgrade to the SL swagger UI (Raymond Feng) 334 | 335 | * Remove the hard-coded DB env so that we can test against MongoDB. (Raymond Feng) 336 | 337 | * Add LICENSE (Raymond Feng) 338 | 339 | * Added code coverage tool (cgole) 340 | 341 | * Clean up the dependencies (Raymond Feng) 342 | 343 | * Allows 'delete' for dev (Raymond Feng) 344 | 345 | * Update to the latest version of swagger UI (Raymond Feng) 346 | 347 | * Update the callback (Raymond Feng) 348 | 349 | * Make it working directory independent (Raymond Feng) 350 | 351 | * Remove required id (Ritchie Martori) 352 | 353 | * Update static index and add API Explorer. (Michael Schoonmaker) 354 | 355 | * Remove old app html (Ritchie) 356 | 357 | * Swagger integration (Ritchie) 358 | 359 | * Add inventory data and location/inventory relationship (Ritchie) 360 | 361 | * Fix the typo (Raymond Feng) 362 | 363 | * Add a home page to show case the apis (Raymond Feng) 364 | 365 | * Remove public (Ritchie) 366 | 367 | * Add root true to nearby remote method (Ritchie) 368 | 369 | * Add fields filtering to customer (Ritchie Martori) 370 | 371 | * rename asteroid to loopback (Raymond Feng) 372 | 373 | * Fix tests, remove password from remoting (Ritchie Martori) 374 | 375 | * Remove DB from help log (Ritchie Martori) 376 | 377 | * Add more geo tests (Ritchie Martori) 378 | 379 | * Add connector selectioin (Ritchie) 380 | 381 | * Cleanup connector selection. Add initial user model meta data. (Ritchie Martori) 382 | 383 | * Better nearby test (Ritchie Martori) 384 | 385 | * Rename long to lng (Ritchie Martori) 386 | 387 | * Add geo filtering example (Ritchie Martori) 388 | 389 | * Use the new find api instead of all. (Ritchie Martori) 390 | 391 | * Fix PUT test (Ritchie Martori) 392 | 393 | * Add update test (Ritchie Martori) 394 | 395 | * Rename oracle connector (Raymond Feng) 396 | 397 | * Removed timeout from tests (Ritchie) 398 | 399 | * Update discover apis in discover script (Ritchie) 400 | 401 | * Fix export bug, added tests (Ritchie Martori) 402 | 403 | * Update the rest connector config (Raymond Feng) 404 | 405 | * Abstract oracle, use db instead (Ritchie Martori) 406 | 407 | * gitignore (Ritchie Martori) 408 | 409 | * Use REST datasource for geocode (Raymond Feng) 410 | 411 | * initial tests (Ritchie Martori) 412 | 413 | * Rename product to weapon (Ritchie Martori) 414 | 415 | * Remove asteroid.json config (Ritchie Martori) 416 | 417 | * Api updates, discover script (Ritchie) 418 | 419 | * remove node modules (Ritchie) 420 | 421 | * Fix incompatible data (Ritchie Martori) 422 | 423 | * Update import.js (Raymond Feng) 424 | 425 | * Add test data and script (Ritchie Martori) 426 | 427 | * Init refactor / reorg (Ritchie Martori) 428 | 429 | * Revert "Add discoverSchema examples, initial geo lookup" (Ritchie) 430 | 431 | * Add discoverSchema examples, initial geo lookup (Ritchie) 432 | 433 | * added .gitignore (Al Tsang) 434 | 435 | * Initial app api version of sample app (Ritchie Martori) 436 | 437 | * Update docs (Ritchie Martori) 438 | 439 | * Updated sample app with additional features and JavaScript sdk examples. (Ritchie Martori) 440 | 441 | * Made sample app compatible with editor (Dallon Feldner) 442 | 443 | * Update docs for inventory view (Ritchie Martori) 444 | 445 | * Bring example app up to date with current asteroid (Ritchie Martori) 446 | 447 | * Update readme with additional docs (Ritchie Martori) 448 | 449 | * Move service and other module inter-dependencies into a "dependencies" field (Ritchie Martori) 450 | 451 | * Change config.json to use "module" instead of typename (Ritchie Martori) 452 | 453 | * switch precedence for init port (Ritchie Martori) 454 | 455 | * updated init script to remove middleware (Ritchie Martori) 456 | 457 | * added servers, middleware, updated services (Ritchie Martori) 458 | 459 | 460 | 2013-03-29, Version INITIAL 461 | =========================== 462 | 463 | * First release! 464 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing ### 2 | 3 | Thank you for your interest in `loopback-example-app`, an open source project 4 | administered by StrongLoop. 5 | 6 | Contributing to `loopback-example-app` is easy. In a few simple steps: 7 | 8 | * Ensure that your effort is aligned with the project's roadmap by 9 | talking to the maintainers, especially if you are going to spend a 10 | lot of time on it. 11 | 12 | * Make something better or fix a bug. 13 | 14 | * Adhere to code style outlined in the [Google C++ Style Guide][] and 15 | [Google Javascript Style Guide][]. 16 | 17 | * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback-example-app) 18 | 19 | * Submit a pull request through Github. 20 | 21 | 22 | ### Contributor License Agreement ### 23 | 24 | ``` 25 | Individual Contributor License Agreement 26 | 27 | By signing this Individual Contributor License Agreement 28 | ("Agreement"), and making a Contribution (as defined below) to 29 | StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and 30 | agree to the following terms and conditions for Your present and 31 | future Contributions submitted to StrongLoop. Except for the license 32 | granted in this Agreement to StrongLoop and recipients of software 33 | distributed by StrongLoop, You reserve all right, title, and interest 34 | in and to Your Contributions. 35 | 36 | 1. Definitions 37 | 38 | "You" or "Your" shall mean the copyright owner or the individual 39 | authorized by the copyright owner that is entering into this 40 | Agreement with StrongLoop. 41 | 42 | "Contribution" shall mean any original work of authorship, 43 | including any modifications or additions to an existing work, that 44 | is intentionally submitted by You to StrongLoop for inclusion in, 45 | or documentation of, any of the products owned or managed by 46 | StrongLoop ("Work"). For purposes of this definition, "submitted" 47 | means any form of electronic, verbal, or written communication 48 | sent to StrongLoop or its representatives, including but not 49 | limited to communication or electronic mailing lists, source code 50 | control systems, and issue tracking systems that are managed by, 51 | or on behalf of, StrongLoop for the purpose of discussing and 52 | improving the Work, but excluding communication that is 53 | conspicuously marked or otherwise designated in writing by You as 54 | "Not a Contribution." 55 | 56 | 2. You Grant a Copyright License to StrongLoop 57 | 58 | Subject to the terms and conditions of this Agreement, You hereby 59 | grant to StrongLoop and recipients of software distributed by 60 | StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, 61 | royalty-free, irrevocable copyright license to reproduce, prepare 62 | derivative works of, publicly display, publicly perform, 63 | sublicense, and distribute Your Contributions and such derivative 64 | works under any license and without any restrictions. 65 | 66 | 3. You Grant a Patent License to StrongLoop 67 | 68 | Subject to the terms and conditions of this Agreement, You hereby 69 | grant to StrongLoop and to recipients of software distributed by 70 | StrongLoop a perpetual, worldwide, non-exclusive, no-charge, 71 | royalty-free, irrevocable (except as stated in this Section) 72 | patent license to make, have made, use, offer to sell, sell, 73 | import, and otherwise transfer the Work under any license and 74 | without any restrictions. The patent license You grant to 75 | StrongLoop under this Section applies only to those patent claims 76 | licensable by You that are necessarily infringed by Your 77 | Contributions(s) alone or by combination of Your Contributions(s) 78 | with the Work to which such Contribution(s) was submitted. If any 79 | entity institutes a patent litigation against You or any other 80 | entity (including a cross-claim or counterclaim in a lawsuit) 81 | alleging that Your Contribution, or the Work to which You have 82 | contributed, constitutes direct or contributory patent 83 | infringement, any patent licenses granted to that entity under 84 | this Agreement for that Contribution or Work shall terminate as 85 | of the date such litigation is filed. 86 | 87 | 4. You Have the Right to Grant Licenses to StrongLoop 88 | 89 | You represent that You are legally entitled to grant the licenses 90 | in this Agreement. 91 | 92 | If Your employer(s) has rights to intellectual property that You 93 | create, You represent that You have received permission to make 94 | the Contributions on behalf of that employer, that Your employer 95 | has waived such rights for Your Contributions, or that Your 96 | employer has executed a separate Corporate Contributor License 97 | Agreement with StrongLoop. 98 | 99 | 5. The Contributions Are Your Original Work 100 | 101 | You represent that each of Your Contributions are Your original 102 | works of authorship (see Section 8 (Submissions on Behalf of 103 | Others) for submission on behalf of others). You represent that to 104 | Your knowledge, no other person claims, or has the right to claim, 105 | any right in any intellectual property right related to Your 106 | Contributions. 107 | 108 | You also represent that You are not legally obligated, whether by 109 | entering into an agreement or otherwise, in any way that conflicts 110 | with the terms of this Agreement. 111 | 112 | You represent that Your Contribution submissions include complete 113 | details of any third-party license or other restriction (including, 114 | but not limited to, related patents and trademarks) of which You 115 | are personally aware and which are associated with any part of 116 | Your Contributions. 117 | 118 | 6. You Don't Have an Obligation to Provide Support for Your Contributions 119 | 120 | You are not expected to provide support for Your Contributions, 121 | except to the extent You desire to provide support. You may provide 122 | support for free, for a fee, or not at all. 123 | 124 | 6. No Warranties or Conditions 125 | 126 | StrongLoop acknowledges that unless required by applicable law or 127 | agreed to in writing, You provide Your Contributions on an "AS IS" 128 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 129 | EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES 130 | OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR 131 | FITNESS FOR A PARTICULAR PURPOSE. 132 | 133 | 7. Submission on Behalf of Others 134 | 135 | If You wish to submit work that is not Your original creation, You 136 | may submit it to StrongLoop separately from any Contribution, 137 | identifying the complete details of its source and of any license 138 | or other restriction (including, but not limited to, related 139 | patents, trademarks, and license agreements) of which You are 140 | personally aware, and conspicuously marking the work as 141 | "Submitted on Behalf of a Third-Party: [named here]". 142 | 143 | 8. Agree to Notify of Change of Circumstances 144 | 145 | You agree to notify StrongLoop of any facts or circumstances of 146 | which You become aware that would make these representations 147 | inaccurate in any respect. Email us at callback@strongloop.com. 148 | ``` 149 | 150 | [Google C++ Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml 151 | [Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 StrongLoop, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: ./strong-resources/startup -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Bluemix LoopBack Sample 2 | This is a sample LoopBack Node.js application. It starts strong-pm (StrongLoop process manager) in Bluemix to start and supervise your application. You can then use StrongLoop Arc to remotely manage your Bluemix application. 3 | 4 | ### Deploy LoopBack Sample to Bluemix 5 | 6 | [![Deploy to Bluemix](https://bluemix.net/deploy/button.png)](https://bluemix.net/deploy?repository=https://github.com/strongloop-bluemix/loopback-example.git) 7 | 8 | When you click this button, Bluemix will clone this repository into an IBM DevOps Services project, set up the deployment pipeline, and push your application to Bluemix. Your application will get deployed with two routes. 9 | ``` 10 | .mybluemix.net # To access your sample application 11 | -pm.mybluemix.net # To access Strong PM 12 | ``` 13 | 14 | ### Deploy StrongLoop Arc 15 | 16 | [![Deploy to Bluemix](https://bluemix.net/deploy/button.png)](https://bluemix.net/deploy?repository=https://github.com/strongloop-bluemix/arc-app.git) 17 | 18 | When you click this button, Bluemix will create and deploy an instance of StrongLoop Arc for your convenience. 19 | 20 | ### Connecting to the LoopBack application from Arc 21 | 22 | Once the LoopBack application is deployed, launch Arc to manage the application. You can do this by deploying Arc with the `Deploy to Bluemix` button above, or install and launch Arc locally. 23 | 24 | To install and launch Arc locally: 25 | > In a CLI, globally install the strongloop client and then launch Arc. Node (and npm) is a prerequisite. 26 | > ``` 27 | > npm install -g strongloop 28 | > slc arc 29 | > ``` 30 | 31 | Then, add your PM URL with port 80 into the Process Manager. You can then use Arc to manage your remotely running application. 32 | -------------------------------------------------------------------------------- /bin/create-load.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * This script creates weighted random load on the sample server. 5 | */ 6 | 7 | var request = require('request'); 8 | var shuffle = require('shuffle').shuffle; 9 | var table = require('text-table'); 10 | var weighted = require('weighted'); 11 | 12 | // If false, non-GET requests are enabled. Not recommended for shared (e.g. C9) 13 | // servers. 14 | var safeMode = true; 15 | 16 | /** 17 | * Returns `body` parsed as JSON if it's not already been parsed, `body 18 | * otherwise. 19 | */ 20 | function toJSON(body) { 21 | if (typeof body !== 'string') { 22 | return body; 23 | } 24 | 25 | return JSON.parse(body); 26 | } 27 | 28 | /** 29 | * Returns a random Array with half the elements from `arr`. 30 | */ 31 | function randomHalf(arr) { 32 | var size = Math.ceil(arr.length / 2); 33 | 34 | return shuffle({ deck: arr }).draw(size); 35 | } 36 | 37 | /** 38 | * Returns a tabular string of `choices`' contents, with all weights converted 39 | * to relative percentages. 40 | */ 41 | function toWeightTable(choices) { 42 | return table([ 43 | ['Route', 'Weight'], 44 | ['-----', '-----'] 45 | ].concat(Object.keys(choices).map(function (key) { 46 | return [key, Math.round(choices[key] * 10000) / 100 + '%']; 47 | }))); 48 | } 49 | 50 | function getBaseURL() { 51 | var ip = process.env.IP || process.env.HOST || '127.0.0.1'; 52 | var port = process.env.PORT || 3000; 53 | var baseURL = 'http://' + ip + ':' + port + '/api'; 54 | return baseURL; 55 | } 56 | /** 57 | * This kicks off the application 58 | * @type {[type]} 59 | */ 60 | function start() { 61 | request.get(getBaseURL() + '/routes', function (err, response, body) { 62 | if(err) 63 | throw err; 64 | body = toJSON(body); 65 | 66 | var routes = distillRoutes(body); 67 | routes = randomHalf(routes); 68 | 69 | var choices = weighChoices(routes); 70 | 71 | // We print out the choice table so the user can compare the weighted load 72 | // to analytics and monitoring data. 73 | console.log('Hitting routes with the following weights:'); 74 | console.log(toWeightTable(choices)); 75 | 76 | // Go! 77 | hit(choices); 78 | }); 79 | } 80 | 81 | /** 82 | * Returns an Array of choices distilled from the complete route table, 83 | * `routes`. 84 | */ 85 | function distillRoutes(routes) { 86 | return routes.filter(function (route) { 87 | if (safeMode && route.verb.toUpperCase() !== 'GET') { 88 | return false; 89 | } 90 | 91 | return true; 92 | }).map(function (route) { 93 | // TODO(schoon) - Handle the `accepts` in a meaningful way. 94 | return route.verb.toUpperCase() + ' ' + route.path; 95 | }); 96 | } 97 | 98 | /** 99 | * Returns a weighted choice table from an Array of choices. 100 | */ 101 | function weighChoices(routes) { 102 | var total = 0; 103 | var choices = routes.reduce(function (obj, route) { 104 | obj[route] = Math.random(); 105 | total += obj[route]; 106 | 107 | return obj; 108 | }, {}); 109 | 110 | // For simplicity, we normalize the weights to add up to 1. 111 | Object.keys(choices).forEach(function (key) { 112 | choices[key] /= total; 113 | }); 114 | 115 | return choices; 116 | } 117 | 118 | /** 119 | * Hits a randomly-selected route from the available `choices`. 120 | */ 121 | function hit(choices) { 122 | var route = weighted(choices); 123 | var parts = route.split(' '); 124 | var verb = parts[0].toLowerCase(); 125 | var path = parts[1]; 126 | 127 | // We replace any :id path fragments with 1, which we hope exists. 128 | path = path.replace(/\:id/g, 1); 129 | 130 | if (verb === 'all') { 131 | verb = 'post'; 132 | } 133 | 134 | // Make the request. 135 | request[verb](getBaseURL() + path, { 136 | json: {} 137 | }, function (err/*, response, body*/) { 138 | if (err) { 139 | console.error('Request error with %s: %s', route, err); 140 | return; 141 | } 142 | 143 | // Ignore the result. 144 | }); 145 | 146 | // TODO(schoon) - Make the request rate configurable. 147 | setTimeout(function () { 148 | hit(choices); 149 | }, 100); 150 | } 151 | 152 | // Now that all the pieces have been defined, it's time to `start` the engine! 153 | start(); 154 | -------------------------------------------------------------------------------- /client/css/code.css: -------------------------------------------------------------------------------- 1 | pre code { 2 | display: block; 3 | background: #1d1f21; 4 | color: #c5c8c6; 5 | font-family: Menlo, Monaco, Consolas, monospace; 6 | line-height: 1.5; 7 | border: 1px solid #ccc; 8 | padding: 10px; 9 | } 10 | -------------------------------------------------------------------------------- /client/css/main.css: -------------------------------------------------------------------------------- 1 | /* Since we generate (both via Marked and JSDoc) empty anchors as jump link 2 | targets, we can target them here to offset said jump links. */ 3 | a[name] { 4 | display: block; position: relative; top: -70px; visibility: hidden; 5 | } 6 | 7 | .column { 8 | padding-top: 20px; 9 | padding-bottom: 20px; 10 | } 11 | 12 | .scroll-spy-target { 13 | border-right: 1px solid #ccc; 14 | } 15 | 16 | .readability { 17 | padding-left: 40px; 18 | max-width: 700px; 19 | } 20 | 21 | .hide-mobile { 22 | display: none; 23 | } 24 | 25 | @media (min-width: 992px) { 26 | .hide-mobile { 27 | display: inherit; 28 | } 29 | 30 | .column { 31 | position: absolute; 32 | top: 52px; 33 | bottom: 0; 34 | overflow-y: auto; 35 | } 36 | } 37 | 38 | .nav-pills a { 39 | color: #08592b; 40 | } 41 | 42 | .nav .depth-1 > a { font-size: 170%; } 43 | .nav .depth-2 > a { font-size: 140%; } 44 | .nav .depth-3 > a { font-size: 120%; } 45 | .nav .depth-4 > a { font-size: 100%; font-weight: 500; } 46 | .nav .depth-5 > a { font-size: 90%; font-weight: 500; } 47 | .nav .depth-6 > a { font-size: 80%; font-weight: 500; } 48 | 49 | .nav-pills li > a { 50 | padding: 3px 6px; 51 | } 52 | .nav .depth-1 { margin: 0; padding: 0 0 0 0; } 53 | .nav .depth-2 { margin: 0; padding: 20px 0 0 0; } 54 | .nav .depth-3 { margin: 0; padding: 10px 0 0 10px; } 55 | .nav .depth-4 { margin: 0; padding: 0 0 0 20px; } 56 | .nav .depth-5 { margin: 0; padding: 0 0 0 40px; } 57 | .nav .depth-6 { margin: 0; padding: 0 0 0 60px; } 58 | 59 | /* Annotations */ 60 | .code-arguments-hdr { 61 | text-transform: capitalize; 62 | font-size: 120%; 63 | margin-top: 20px; 64 | display: block; 65 | } 66 | .code-arg { 67 | margin-top: 10px; 68 | margin-left: 20px; 69 | } 70 | .code-arg div { 71 | display: inline; 72 | } 73 | .code-arg-types { 74 | font-weight: bold; 75 | } 76 | .code-arg-types:before { content: "{"; } 77 | .code-arg-types:after { content: "}"; } 78 | 79 | .readability { 80 | position: relative; 81 | height: 100%; 82 | } 83 | 84 | .intentionally-left-blank { 85 | display: block; 86 | height: 100%; 87 | } 88 | -------------------------------------------------------------------------------- /client/fonts/0ihfXUL2emPh0ROJezvraLO3LdcAZYWl9Si6vvxL-qU.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-bluemix/loopback-example-app/29bd7ab7102b5a69def418dfe4263afd6dd4055c/client/fonts/0ihfXUL2emPh0ROJezvraLO3LdcAZYWl9Si6vvxL-qU.woff -------------------------------------------------------------------------------- /client/fonts/OsJ2DjdpjqFRVUSto6IffLO3LdcAZYWl9Si6vvxL-qU.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-bluemix/loopback-example-app/29bd7ab7102b5a69def418dfe4263afd6dd4055c/client/fonts/OsJ2DjdpjqFRVUSto6IffLO3LdcAZYWl9Si6vvxL-qU.woff -------------------------------------------------------------------------------- /client/fonts/_aijTyevf54tkVDLy-dlnLO3LdcAZYWl9Si6vvxL-qU.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strongloop-bluemix/loopback-example-app/29bd7ab7102b5a69def418dfe4263afd6dd4055c/client/fonts/_aijTyevf54tkVDLy-dlnLO3LdcAZYWl9Si6vvxL-qU.woff -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | LoopBack Sample Application 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 |
22 | 37 |
38 |
39 |
40 |

LoopBack

41 |

Welcome to the LoopBack sample application!

42 |

43 | LoopBack is an open source Node.js framework for connecting enterprise data to devices and browsers by allowing to rapidly build APIs and next generation web applications. 44 |

45 |

46 | This sample application simulates an (imaginary) car rental dealer with locations in major cities around the world. 47 | They need to replace their existing desktop reservation system with a new mobile app. The app exposes a set of REST APIs for inventory data. 48 |

49 |

50 | Click around and explore the APIs! 51 |

52 |
53 |

Sample Requests

54 |

Click on the friendly GET buttons below to try out a few example requests!

55 |
56 |

GET /api/cars

57 |
58 |
59 |
60 |

GET /api/cars/82

61 |
62 |
63 |
64 |

GET /api/cars?filter

65 |

Actual request: GET /api/cars?filter[order]=make&filter[where][carClass]=fullsize&filter[limit]=3 (The three full size cars ordered by make)

66 |
67 |
68 |
69 |

GET /api/locations

70 |
71 |
72 |
73 |

GET /api/locations/nearby

74 |

Actual request: GET /api/locations/nearby?here[lat]=37.587409&here[lng]=-122.338225

75 |
76 |
77 |
78 |

GET /api/locations/:id/inventory

79 |

Actual request: GET /api/locations/88/inventory

80 |
81 |
82 |

Next Steps

83 |
84 |

API Explorer

85 |

The API Explorer is a great resource to check out the rest of the methods available to you through the LoopBack's default REST API.

86 |

Choose Your Own Adventure

87 |

88 |
    89 |
  • To consume the REST API from your mobile client, check out the LoopBack Getting Started Guide.
  • 90 |
  • Install strongloop (npm install -g strongloop) and run slc arc to check out the rest of StrongLoop.
  • 91 |
92 |
93 |
94 |
95 |
96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /client/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap.js v3.0.0 by @fat and @mdo 3 | * Copyright 2013 Twitter Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover"},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .accordion-group > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),""===a.find(".popover-title").html()&&a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery); -------------------------------------------------------------------------------- /client/js/jquery-scrollTo.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery.ScrollTo 3 | * Copyright (c) 2007-2012 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 4 | * Dual licensed under MIT and GPL. 5 | * Date: 4/09/2012 6 | * 7 | * @projectDescription Easy element scrolling using jQuery. 8 | * http://flesler.blogspot.com/2007/10/jqueryscrollto.html 9 | * @author Ariel Flesler 10 | * @version 1.4.3.1 11 | * 12 | * @id jQuery.scrollTo 13 | * @id jQuery.fn.scrollTo 14 | * @param {String, Number, DOMElement, jQuery, Object} target Where to scroll the matched elements. 15 | * The different options for target are: 16 | * - A number position (will be applied to all axes). 17 | * - A string position ('44', '100px', '+=90', etc ) will be applied to all axes 18 | * - A jQuery/DOM element ( logically, child of the element to scroll ) 19 | * - A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc ) 20 | * - A hash { top:x, left:y }, x and y can be any kind of number/string like above. 21 | * - A percentage of the container's dimension/s, for example: 50% to go to the middle. 22 | * - The string 'max' for go-to-end. 23 | * @param {Number, Function} duration The OVERALL length of the animation, this argument can be the settings object instead. 24 | * @param {Object,Function} settings Optional set of settings or the onAfter callback. 25 | * @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'. 26 | * @option {Number, Function} duration The OVERALL length of the animation. 27 | * @option {String} easing The easing method for the animation. 28 | * @option {Boolean} margin If true, the margin of the target element will be deducted from the final position. 29 | * @option {Object, Number} offset Add/deduct from the end position. One number for both axes or { top:x, left:y }. 30 | * @option {Object, Number} over Add/deduct the height/width multiplied by 'over', can be { top:x, left:y } when using both axes. 31 | * @option {Boolean} queue If true, and both axis are given, the 2nd axis will only be animated after the first one ends. 32 | * @option {Function} onAfter Function to be called after the scrolling ends. 33 | * @option {Function} onAfterFirst If queuing is activated, this function will be called after the first scrolling ends. 34 | * @return {jQuery} Returns the same jQuery object, for chaining. 35 | * 36 | * @desc Scroll to a fixed position 37 | * @example $('div').scrollTo( 340 ); 38 | * 39 | * @desc Scroll relatively to the actual position 40 | * @example $('div').scrollTo( '+=340px', { axis:'y' } ); 41 | * 42 | * @desc Scroll using a selector (relative to the scrolled element) 43 | * @example $('div').scrollTo( 'p.paragraph:eq(2)', 500, { easing:'swing', queue:true, axis:'xy' } ); 44 | * 45 | * @desc Scroll to a DOM element (same for jQuery object) 46 | * @example var second_child = document.getElementById('container').firstChild.nextSibling; 47 | * $('#container').scrollTo( second_child, { duration:500, axis:'x', onAfter:function(){ 48 | * alert('scrolled!!'); 49 | * }}); 50 | * 51 | * @desc Scroll on both axes, to different values 52 | * @example $('div').scrollTo( { top: 300, left:'+=200' }, { axis:'xy', offset:-20 } ); 53 | */ 54 | 55 | ;(function( $ ){ 56 | 57 | var $scrollTo = $.scrollTo = function( target, duration, settings ){ 58 | $(window).scrollTo( target, duration, settings ); 59 | }; 60 | 61 | $scrollTo.defaults = { 62 | axis:'xy', 63 | duration: parseFloat($.fn.jquery) >= 1.3 ? 0 : 1, 64 | limit:true 65 | }; 66 | 67 | // Returns the element that needs to be animated to scroll the window. 68 | // Kept for backwards compatibility (specially for localScroll & serialScroll) 69 | $scrollTo.window = function( scope ){ 70 | return $(window)._scrollable(); 71 | }; 72 | 73 | // Hack, hack, hack :) 74 | // Returns the real elements to scroll (supports window/iframes, documents and regular nodes) 75 | $.fn._scrollable = function(){ 76 | return this.map(function(){ 77 | var elem = this, 78 | isWin = !elem.nodeName || $.inArray( elem.nodeName.toLowerCase(), ['iframe','#document','html','body'] ) != -1; 79 | 80 | if( !isWin ) 81 | return elem; 82 | 83 | var doc = (elem.contentWindow || elem).document || elem.ownerDocument || elem; 84 | 85 | return /webkit/i.test(navigator.userAgent) || doc.compatMode == 'BackCompat' ? 86 | doc.body : 87 | doc.documentElement; 88 | }); 89 | }; 90 | 91 | $.fn.scrollTo = function( target, duration, settings ){ 92 | if( typeof duration == 'object' ){ 93 | settings = duration; 94 | duration = 0; 95 | } 96 | if( typeof settings == 'function' ) 97 | settings = { onAfter:settings }; 98 | 99 | if( target == 'max' ) 100 | target = 9e9; 101 | 102 | settings = $.extend( {}, $scrollTo.defaults, settings ); 103 | // Speed is still recognized for backwards compatibility 104 | duration = duration || settings.duration; 105 | // Make sure the settings are given right 106 | settings.queue = settings.queue && settings.axis.length > 1; 107 | 108 | if( settings.queue ) 109 | // Let's keep the overall duration 110 | duration /= 2; 111 | settings.offset = both( settings.offset ); 112 | settings.over = both( settings.over ); 113 | 114 | return this._scrollable().each(function(){ 115 | // Null target yields nothing, just like jQuery does 116 | if (target == null) return; 117 | 118 | var elem = this, 119 | $elem = $(elem), 120 | targ = target, toff, attr = {}, 121 | win = $elem.is('html,body'); 122 | 123 | switch( typeof targ ){ 124 | // A number will pass the regex 125 | case 'number': 126 | case 'string': 127 | if( /^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(targ) ){ 128 | targ = both( targ ); 129 | // We are done 130 | break; 131 | } 132 | // Relative selector, no break! 133 | targ = $(targ,this); 134 | if (!targ.length) return; 135 | case 'object': 136 | // DOMElement / jQuery 137 | if( targ.is || targ.style ) 138 | // Get the real position of the target 139 | toff = (targ = $(targ)).offset(); 140 | } 141 | $.each( settings.axis.split(''), function( i, axis ){ 142 | var Pos = axis == 'x' ? 'Left' : 'Top', 143 | pos = Pos.toLowerCase(), 144 | key = 'scroll' + Pos, 145 | old = elem[key], 146 | max = $scrollTo.max(elem, axis); 147 | 148 | if( toff ){// jQuery / DOMElement 149 | attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] ); 150 | 151 | // If it's a dom element, reduce the margin 152 | if( settings.margin ){ 153 | attr[key] -= parseInt(targ.css('margin'+Pos)) || 0; 154 | attr[key] -= parseInt(targ.css('border'+Pos+'Width')) || 0; 155 | } 156 | 157 | attr[key] += settings.offset[pos] || 0; 158 | 159 | if( settings.over[pos] ) 160 | // Scroll to a fraction of its width/height 161 | attr[key] += targ[axis=='x'?'width':'height']() * settings.over[pos]; 162 | }else{ 163 | var val = targ[pos]; 164 | // Handle percentage values 165 | attr[key] = val.slice && val.slice(-1) == '%' ? 166 | parseFloat(val) / 100 * max 167 | : val; 168 | } 169 | 170 | // Number or 'number' 171 | if( settings.limit && /^\d+$/.test(attr[key]) ) 172 | // Check the limits 173 | attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max ); 174 | 175 | // Queueing axes 176 | if( !i && settings.queue ){ 177 | // Don't waste time animating, if there's no need. 178 | if( old != attr[key] ) 179 | // Intermediate animation 180 | animate( settings.onAfterFirst ); 181 | // Don't animate this axis again in the next iteration. 182 | delete attr[key]; 183 | } 184 | }); 185 | 186 | animate( settings.onAfter ); 187 | 188 | function animate( callback ){ 189 | $elem.animate( attr, duration, settings.easing, callback && function(){ 190 | callback.call(this, target, settings); 191 | }); 192 | }; 193 | 194 | }).end(); 195 | }; 196 | 197 | // Max scrolling position, works on quirks mode 198 | // It only fails (not too badly) on IE, quirks mode. 199 | $scrollTo.max = function( elem, axis ){ 200 | var Dim = axis == 'x' ? 'Width' : 'Height', 201 | scroll = 'scroll'+Dim; 202 | 203 | if( !$(elem).is('html,body') ) 204 | return elem[scroll] - $(elem)[Dim.toLowerCase()](); 205 | 206 | var size = 'client' + Dim, 207 | html = elem.ownerDocument.documentElement, 208 | body = elem.ownerDocument.body; 209 | 210 | return Math.max( html[scroll], body[scroll] ) 211 | - Math.min( html[size] , body[size] ); 212 | }; 213 | 214 | function both( val ){ 215 | return typeof val == 'object' ? val : { top:val, left:val }; 216 | }; 217 | 218 | })( jQuery ); -------------------------------------------------------------------------------- /client/js/main.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | // Switch from empty anchors to id-ed headings 3 | $('a[name]').get().forEach(function (i) { 4 | var $i = $(i); 5 | 6 | $i.next().attr('id', $i.attr('name')); 7 | $i.detach(); 8 | }); 9 | 10 | $('.scroll-spy-target').on('activate.bs.scrollspy', function (event) { 11 | var $this = $(this); 12 | var $target = $(event.target); 13 | 14 | $this.scrollTo($target, 0, { 15 | offset: -($this.innerHeight() / 2) 16 | }); 17 | }); 18 | 19 | function truncate(json, length) { 20 | if (length == null) { 21 | length = 20; 22 | } 23 | 24 | var split = json.split('\n'); 25 | 26 | if (split.length <= length) { 27 | return json; 28 | } 29 | 30 | return split.slice(0, length).join('\n') + '\n...'; 31 | } 32 | 33 | $('[data-route]').each(function () { 34 | // For now, GET only. 35 | var $this = $(this); 36 | var $target = $($this.attr('data-target')); 37 | var route = $this.attr('data-route'); 38 | var length = $this.attr('data-truncate') || Infinity; 39 | 40 | $this.click(function () { 41 | $target.text('Loading ' + route + ' ...'); 42 | 43 | $.ajax(route, { 44 | success: function (data) { 45 | var json = truncate(JSON.stringify(data, null, 2), length); 46 | $target.text(json); 47 | } 48 | }); 49 | 50 | console.log($this.attr('data-route')); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /common/models/car.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Car", 3 | "base": "PersistedModel", 4 | "options": { 5 | "mysql": { 6 | "table": "car" 7 | }, 8 | "mongodb": { 9 | "collection": "car" 10 | }, 11 | "oracle": { 12 | "schema": "DEMO", 13 | "table": "PRODUCT" 14 | } 15 | }, 16 | "properties": { 17 | "id": { 18 | "type": "string", 19 | "id": true 20 | }, 21 | "vin": { 22 | "type": "string" 23 | }, 24 | "year": { 25 | "type": "number" 26 | }, 27 | "make": { 28 | "type": "string" 29 | }, 30 | "model": { 31 | "type": "string" 32 | }, 33 | "image": { 34 | "type": "string" 35 | }, 36 | "carClass": { 37 | "type": "string" 38 | }, 39 | "color": { 40 | "type": "string" 41 | } 42 | }, 43 | "validations": [], 44 | "relations": {}, 45 | "acls": [], 46 | "methods": [] 47 | } 48 | -------------------------------------------------------------------------------- /common/models/customer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Customer", 3 | "base": "User", 4 | "idInjection": false, 5 | "options": { 6 | "mysql": { 7 | "table": "customer" 8 | }, 9 | "mongodb": { 10 | "collection": "customer" 11 | }, 12 | "oracle": { 13 | "schema": "DEMO", 14 | "table": "CUSTOMER" 15 | } 16 | }, 17 | "properties": { 18 | "id": { 19 | "type": "String", 20 | "id": true, 21 | "length": 20, 22 | "oracle": { 23 | "columnName": "ID", 24 | "dataType": "VARCHAR2", 25 | "dataLength": 20, 26 | "nullable": "N" 27 | } 28 | }, 29 | "name": { 30 | "type": "String", 31 | "length": 40, 32 | "oracle": { 33 | "columnName": "NAME", 34 | "dataType": "VARCHAR2", 35 | "dataLength": 40, 36 | "nullable": "Y" 37 | } 38 | } 39 | }, 40 | "validations": [], 41 | "relations": {}, 42 | "acls": [], 43 | "methods": [] 44 | } 45 | -------------------------------------------------------------------------------- /common/models/inventory.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Inventory", 3 | "plural": "inventory", 4 | "base": "PersistedModel", 5 | "idInjection": false, 6 | "options": { 7 | "mysql": { 8 | "table": "inventory" 9 | }, 10 | "mongodb": { 11 | "collection": "inventory" 12 | }, 13 | "oracle": { 14 | "schema": "DEMO", 15 | "table": "INVENTORY" 16 | } 17 | }, 18 | "properties": { 19 | "id": { 20 | "type": "String", 21 | "id": true, 22 | "required": true, 23 | "length": 20, 24 | "oracle": { 25 | "columnName": "ID", 26 | "dataType": "VARCHAR2", 27 | "dataLength": 20, 28 | "nullable": "N" 29 | } 30 | }, 31 | "productId": { 32 | "type": "String", 33 | "required": true, 34 | "length": 20, 35 | "oracle": { 36 | "columnName": "PRODUCT_ID", 37 | "dataType": "VARCHAR2", 38 | "dataLength": 20, 39 | "nullable": "N" 40 | } 41 | }, 42 | "locationId": { 43 | "type": "String", 44 | "required": true, 45 | "length": 20, 46 | "oracle": { 47 | "columnName": "LOCATION_ID", 48 | "dataType": "VARCHAR2", 49 | "dataLength": 20, 50 | "nullable": "N" 51 | } 52 | }, 53 | "available": { 54 | "type": "Number", 55 | "length": 22, 56 | "oracle": { 57 | "columnName": "AVAILABLE", 58 | "dataType": "NUMBER", 59 | "dataLength": 22, 60 | "nullable": "Y" 61 | } 62 | }, 63 | "total": { 64 | "type": "Number", 65 | "length": 22, 66 | "oracle": { 67 | "columnName": "TOTAL", 68 | "dataType": "NUMBER", 69 | "dataLength": 22, 70 | "nullable": "Y" 71 | } 72 | } 73 | }, 74 | "validations": [], 75 | "relations": {}, 76 | "acls": [], 77 | "methods": [] 78 | } 79 | -------------------------------------------------------------------------------- /common/models/location.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RentalLocation) { 2 | RentalLocation.nearby = function(here, page, max, fn) { 3 | if (typeof page === 'function') { 4 | fn = page; 5 | page = 0; 6 | max = 0; 7 | } 8 | 9 | if (typeof max === 'function') { 10 | fn = max; 11 | max = 0; 12 | } 13 | 14 | var limit = 10; 15 | page = page || 0; 16 | max = Number(max || 100000); 17 | 18 | RentalLocation.find({ 19 | // find locations near the provided GeoPoint 20 | where: {geo: {near: here, maxDistance: max}}, 21 | // paging 22 | skip: limit * page, 23 | limit: limit 24 | }, fn); 25 | }; 26 | 27 | // Google Maps API has a rate limit of 10 requests per second 28 | // Seems we need to enforce a lower rate to prevent errors 29 | var lookupGeo = require('function-rate-limit')(5, 1000, function() { 30 | var geoService = RentalLocation.app.dataSources.geo; 31 | geoService.geocode.apply(geoService, arguments); 32 | }); 33 | 34 | RentalLocation.observe('before save', function(ctx, next) { 35 | 36 | var loc = ctx.instance || ctx.data; 37 | 38 | if (loc.geo) return next(); 39 | 40 | // geo code the address 41 | lookupGeo(loc.street, loc.city, loc.state, 42 | function(err, result) { 43 | if (result && result[0]) { 44 | loc.geo = result[0]; 45 | next(); 46 | } else { 47 | next(new Error('could not find location')); 48 | } 49 | }); 50 | }); 51 | 52 | RentalLocation.setup = function() { 53 | RentalLocation.base.setup.apply(this, arguments); 54 | 55 | this.remoteMethod('nearby', { 56 | description: 'Find nearby locations around the geo point', 57 | accepts: [ 58 | {arg: 'here', type: 'GeoPoint', required: true, 59 | description: 'geo location (lng & lat)'}, 60 | {arg: 'page', type: 'Number', 61 | description: 'number of pages (page size=10)'}, 62 | {arg: 'max', type: 'Number', 63 | description: 'max distance in miles'} 64 | ], 65 | returns: {arg: 'locations', root: true}, 66 | http: { verb: 'GET' } 67 | }); 68 | }; 69 | 70 | RentalLocation.setup(); 71 | }; 72 | -------------------------------------------------------------------------------- /common/models/location.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Location", 3 | "base": "PersistedModel", 4 | "idInjection": false, 5 | "options": { 6 | "mysql": { 7 | "table": "location" 8 | }, 9 | "mongodb": { 10 | "collection": "location" 11 | }, 12 | "oracle": { 13 | "schema": "DEMO", 14 | "table": "LOCATION" 15 | } 16 | }, 17 | "properties": { 18 | "id": { 19 | "type": "String", 20 | "id": true, 21 | "length": 20, 22 | "oracle": { 23 | "columnName": "ID", 24 | "dataType": "VARCHAR2", 25 | "dataLength": 20, 26 | "nullable": "N" 27 | } 28 | }, 29 | "street": { 30 | "type": "String", 31 | "length": 64, 32 | "oracle": { 33 | "columnName": "STREET", 34 | "dataType": "VARCHAR2", 35 | "dataLength": 64, 36 | "nullable": "Y" 37 | } 38 | }, 39 | "city": { 40 | "type": "String", 41 | "length": 64, 42 | "oracle": { 43 | "columnName": "CITY", 44 | "dataType": "VARCHAR2", 45 | "dataLength": 64, 46 | "nullable": "Y" 47 | } 48 | }, 49 | "zipcode": { 50 | "type": "Number", 51 | "length": 20, 52 | "oracle": { 53 | "columnName": "ZIPCODE", 54 | "dataType": "VARCHAR2", 55 | "dataLength": 20, 56 | "nullable": "Y" 57 | } 58 | }, 59 | "name": { 60 | "type": "String", 61 | "length": 32, 62 | "oracle": { 63 | "columnName": "NAME", 64 | "dataType": "VARCHAR2", 65 | "dataLength": 32, 66 | "nullable": "Y" 67 | } 68 | }, 69 | "geo": { 70 | "type": "GeoPoint" 71 | } 72 | }, 73 | "validations": [], 74 | "relations": { 75 | "inventory": { 76 | "type": "hasMany", 77 | "model": "Inventory" 78 | } 79 | }, 80 | "acls": [], 81 | "methods": [] 82 | } 83 | -------------------------------------------------------------------------------- /common/models/note.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Note", 3 | "plural": "notes", 4 | "base": "PersistedModel", 5 | "options": { 6 | "mysql": { 7 | "table": "note" 8 | }, 9 | "mongodb": { 10 | "collection": "note" 11 | } 12 | }, 13 | "properties": {}, 14 | "validations": [], 15 | "relations": {}, 16 | "acls": [], 17 | "methods": [] 18 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopback-example-app", 3 | "version": "3.1.0", 4 | "description": "LoopBack Example Application", 5 | "main": "server/server.js", 6 | "dependencies": { 7 | "compression": "^1.0.3", 8 | "errorhandler": "^1.1.1", 9 | "strong-start": "*", 10 | "loopback": "^2.0.0", 11 | "loopback-boot": "^2.0.0", 12 | "loopback-datasource-juggler": "^2.0.0", 13 | "serve-favicon": "^2.0.1", 14 | "function-rate-limit": "~0.0.1", 15 | "request": "^2.37.0", 16 | "shuffle": "~0.2.1", 17 | "text-table": "~0.2.0", 18 | "weighted": "~0.2.2", 19 | "async": "~0.9.0", 20 | "loopback-connector-rest": "^1.1.4" 21 | }, 22 | "optionalDependencies": { 23 | "loopback-explorer": "^1.1.0", 24 | "loopback-connector-mysql": "^1.2.1", 25 | "loopback-connector-mongodb": "^1.2.5", 26 | "loopback-connector-oracle": "^1.2.1" 27 | }, 28 | "devDependencies": { 29 | "jshint": "^2.7.0", 30 | "mocha": "^1.20.1", 31 | "supertest": "^0.13.0" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/strongloop/loopback-example-app.git" 36 | }, 37 | "author": "Miroslav Bajtos ", 38 | "license": { 39 | "name": "MIT", 40 | "url": "https://github.com/strongloop/loopback-example-app/blob/master/LICENSE" 41 | }, 42 | "scripts": { 43 | "test": "mocha -R spec server/test", 44 | "pretest": "jshint .", 45 | "start": "node ." 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | module.exports = function enableAuthentication(server) { 2 | // enable authentication 3 | server.enableAuth(); 4 | }; 5 | -------------------------------------------------------------------------------- /server/boot/create-sample-data.js: -------------------------------------------------------------------------------- 1 | var importer = require('../sample-data/import'); 2 | 3 | module.exports = function(app) { 4 | if (app.dataSources.db.name !== 'Memory') return; 5 | 6 | console.error('Started the import of sample data.'); 7 | app.importing = true; 8 | 9 | importer(app, function(err) { 10 | delete app.importing; 11 | if (err) { 12 | console.error('Cannot import sample data - ', err); 13 | } else { 14 | console.error('Sample data was imported.'); 15 | } 16 | app.emit('import done', err); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /server/boot/explorer.js: -------------------------------------------------------------------------------- 1 | module.exports = function mountLoopBackExplorer(server) { 2 | var explorer; 3 | try { 4 | explorer = require('loopback-explorer'); 5 | } catch(err) { 6 | console.log( 7 | 'Run `npm install loopback-explorer` to enable the LoopBack explorer' 8 | ); 9 | return; 10 | } 11 | 12 | var restApiRoot = server.get('restApiRoot'); 13 | 14 | var explorerApp = explorer(server, { basePath: restApiRoot }); 15 | server.use('/explorer', explorerApp); 16 | server.once('started', function() { 17 | var baseUrl = server.get('url').replace(/\/$/, ''); 18 | // express 4.x (loopback 2.x) uses `mountpath` 19 | // express 3.x (loopback 1.x) uses `route` 20 | var explorerPath = explorerApp.mountpath || explorerApp.route; 21 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /server/boot/rest-api.js: -------------------------------------------------------------------------------- 1 | module.exports = function mountRestApi(server) { 2 | var restApiRoot = server.get('restApiRoot'); 3 | server.use(restApiRoot, server.loopback.rest()); 4 | }; 5 | -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "legacyExplorer": true, 3 | "restApiRoot": "/api", 4 | "host": "0.0.0.0", 5 | "port": 3000, 6 | "url": "http://localhost:3000/" 7 | } 8 | -------------------------------------------------------------------------------- /server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "name": "db", 4 | "connector": "memory" 5 | }, 6 | "geo": { 7 | "name": "geo", 8 | "connector": "rest", 9 | "operations": [ 10 | { 11 | "template": { 12 | "method": "GET", 13 | "url": "http://maps.googleapis.com/maps/api/geocode/{format=json}", 14 | "headers": { 15 | "accepts": "application/json", 16 | "content-type": "application/json" 17 | }, 18 | "query": { 19 | "address": "{street},{city},{zipcode}", 20 | "sensor": "{sensor=false}" 21 | }, 22 | "responsePath": "$.results[0].geometry.location" 23 | }, 24 | "functions": { 25 | "geocode": [ 26 | "street", 27 | "city", 28 | "zipcode" 29 | ] 30 | } 31 | } 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /server/datasources.local.js: -------------------------------------------------------------------------------- 1 | var extend = require('util')._extend; 2 | 3 | // Use the memory connector by default. 4 | var DB = process.env.DB || 'memory'; 5 | 6 | var DATASTORES = { 7 | memory: { 8 | }, 9 | oracle: { 10 | host: 'oracle-demo.strongloop.com', 11 | port: 1521, 12 | database: 'XE', 13 | username: 'demo', 14 | maxConn: 40, 15 | password: 'L00pBack' 16 | }, 17 | mongodb: { 18 | host: 'demo.strongloop.com', 19 | database: 'demo', 20 | username: 'demo', 21 | password: 'L00pBack', 22 | port: 27017 23 | }, 24 | mysql: { 25 | host: 'demo.strongloop.com', 26 | port: 3306, 27 | database: 'demo', 28 | username: 'demo', 29 | password: 'L00pBack' 30 | } 31 | }; 32 | 33 | if (!(DB in DATASTORES)) { 34 | console.error('Invalid DB "%s"', DB); 35 | console.error('Supported values', Object.keys(DATASTORES).join(' ')); 36 | process.exit(1); 37 | } 38 | 39 | console.error('Using the %s connector.', DB); 40 | console.error('To specify another connector:'); 41 | console.error(' `DB=oracle node .` or `DB=oracle slc run .`'); 42 | console.error(' `DB=mongodb node .` or `DB=mongodb slc run .`'); 43 | console.error(' `DB=mysql node .` or `DB=mysql slc run .`'); 44 | 45 | var connector = DB === 'memory' ? DB : 'loopback-connector-' + DB; 46 | var config = extend({ connector: connector }, DATASTORES[DB]); 47 | 48 | module.exports = { 49 | db: config 50 | }; 51 | -------------------------------------------------------------------------------- /server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "../common/models", 5 | "./models" 6 | ] 7 | }, 8 | "AccessToken": { 9 | "dataSource": "db", 10 | "public": false 11 | }, 12 | "ACL": { 13 | "dataSource": "db", 14 | "public": false 15 | }, 16 | "RoleMapping": { 17 | "dataSource": "db", 18 | "public": false 19 | }, 20 | "Role": { 21 | "dataSource": "db", 22 | "public": false 23 | }, 24 | "Car": { 25 | "dataSource": "db" 26 | }, 27 | "Customer": { 28 | "dataSource": "db" 29 | }, 30 | "Inventory": { 31 | "dataSource": "db" 32 | }, 33 | "Location": { 34 | "dataSource": "db" 35 | }, 36 | "Note": { 37 | "dataSource": "db" 38 | } 39 | } -------------------------------------------------------------------------------- /server/sample-data/customers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "email": "foo@bar.com", 4 | "username": "foo", 5 | "password": "123456" 6 | }, 7 | { 8 | "email": "bar@bar.com", 9 | "username": "bar", 10 | "password": "123456" 11 | }, 12 | { 13 | "email": "bat@bar.com", 14 | "username": "bat", 15 | "password": "123456" 16 | }, 17 | { 18 | "email": "baz@bar.com", 19 | "username": "baz", 20 | "password": "123456" 21 | } 22 | ] -------------------------------------------------------------------------------- /server/sample-data/generate-inventory.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This short script is only for generating data in a JSON file. 3 | * 4 | * Usage: 5 | * node app.js # in the app dir 6 | * node generate-inventory.js # in this dir 7 | */ 8 | 9 | var fs = require('fs'); 10 | var path = require('path'); 11 | var inventory = []; 12 | var request = require('request'); 13 | 14 | request('http://localhost:3000/api/cars', {json: true}, 15 | function(err, res, cars) { 16 | if (err) { 17 | console.error('Cannot get Cars', err); 18 | return; 19 | } 20 | 21 | request('http://localhost:3000/api/locations', {json: true}, 22 | function(err, res, locations) { 23 | if (err) { 24 | console.error('Cannot get Locations', err); 25 | return; 26 | } 27 | 28 | locations.forEach(function(loc) { 29 | cars.forEach(function(car) { 30 | var availableAtLocation = rand(0, 100); 31 | 32 | inventory.push({ 33 | productId: car.id, 34 | locationId: loc.id, 35 | available: rand(0, availableAtLocation), 36 | total: availableAtLocation 37 | }); 38 | 39 | }); 40 | }); 41 | 42 | fs.writeFileSync( 43 | path.resolve(__dirname, 'inventory.json'), 44 | JSON.stringify(inventory, null, 2)); 45 | }); 46 | }); 47 | 48 | function rand(min, max) { 49 | return Math.floor(Math.random() * (max - min + 1)) + min; 50 | } 51 | -------------------------------------------------------------------------------- /server/sample-data/import.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run `node import.js` to import the sample data into the db. 3 | */ 4 | 5 | var async = require('async'); 6 | 7 | // sample data 8 | var cars = require('./cars.json'); 9 | var customers = require('./customers.json'); 10 | var inventory = require('./inventory.json'); 11 | var locations = require('./locations.json'); 12 | 13 | module.exports = function(app, cb) { 14 | var Inventory = app.models.Inventory; 15 | var Location = app.models.Location; 16 | var Customer = app.models.Customer; 17 | var Car = app.models.Car; 18 | var db = app.dataSources.db; 19 | 20 | var ids = { 21 | }; 22 | 23 | function importData(Model, data, cb) { 24 | // console.log('Importing data for ' + Model.modelName); 25 | Model.destroyAll(function(err) { 26 | if (err) { 27 | cb(err); 28 | return; 29 | } 30 | async.eachLimit(data, 32, function(d, callback) { 31 | if (ids[Model.modelName] === undefined) { 32 | // The Oracle data has Location with ids over 80 33 | // and the index.html depends on location 88 being present 34 | ids[Model.modelName] = 80; 35 | } 36 | d.id = ids[Model.modelName]++; 37 | Model.create(d, callback); 38 | }, cb); 39 | }); 40 | } 41 | 42 | async.series([ 43 | function(cb) { 44 | db.autoupdate(cb); 45 | }, 46 | 47 | importData.bind(null, Location, locations), 48 | importData.bind(null, Car, cars), 49 | importData.bind(null, Inventory, inventory), 50 | importData.bind(null, Customer, customers) 51 | ], function(err/*, results*/) { 52 | cb(err); 53 | }); 54 | }; 55 | 56 | if (require.main === module) { 57 | // Run the import 58 | module.exports(require('../server'), function(err) { 59 | if (err) { 60 | console.error('Cannot import sample data - ', err); 61 | } else { 62 | console.log('Sample data was imported.'); 63 | } 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /server/sample-data/locations.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "City Rent-a-Car", 4 | "street": "1433 Bush St", 5 | "city": "San Francisco", 6 | "state": "CA", 7 | "country": "US", 8 | "geo": { 9 | "lat": 37.7884036, 10 | "lng": -122.4208504 11 | }, 12 | "phone": "(415) 359-1331" 13 | }, 14 | { 15 | "name": "Thrifty Car Rental", 16 | "street": "350 O'Farrell St", 17 | "city": "San Francisco", 18 | "state": "CA", 19 | "country": "US", 20 | "geo": { 21 | "lat": 37.7863186, 22 | "lng": -122.4105792 23 | }, 24 | "phone": "(415) 788-8111" 25 | }, 26 | { 27 | "name": "GoCar Tours", 28 | "street": "431 Beach St", 29 | "city": "San Francisco", 30 | "state": "CA", 31 | "country": "US", 32 | "geo": { 33 | "lat": 37.80722290000001, 34 | "lng": -122.4161326 35 | }, 36 | "phone": "(415) 441-5695" 37 | }, 38 | { 39 | "name": "Enterprise Rent-A-Car", 40 | "street": "727 Folsom St", 41 | "city": "San Francisco", 42 | "state": "CA", 43 | "country": "US", 44 | "geo": { 45 | "lat": 37.7831801, 46 | "lng": -122.3994078 47 | }, 48 | "phone": "(415) 546-6777" 49 | }, 50 | { 51 | "name": "Budget Rent A Car", 52 | "street": "5 Embarcadero Center", 53 | "city": "San Francisco", 54 | "state": "CA", 55 | "country": "US", 56 | "geo": { 57 | "lat": 37.7944232, 58 | "lng": -122.3955429 59 | }, 60 | "phone": "(415) 433-3717" 61 | }, 62 | { 63 | "name": "National Car Rental", 64 | "street": "750 Bush St", 65 | "city": "San Francisco", 66 | "state": "CA", 67 | "country": "US", 68 | "geo": { 69 | "lat": 37.7902067, 70 | "lng": -122.409726 71 | }, 72 | "phone": "(650) 238-5302" 73 | }, 74 | { 75 | "name": "Enterprise Rent-A-Car", 76 | "street": "1600 Mission St", 77 | "city": "San Francisco", 78 | "state": "CA", 79 | "country": "US", 80 | "geo": { 81 | "lat": 37.7724061, 82 | "lng": -122.4193033 83 | }, 84 | "phone": "(415) 522-5900" 85 | }, 86 | { 87 | "name": "Alamo Rent A Car", 88 | "street": "750 Bush St", 89 | "city": "San Francisco", 90 | "state": "CA", 91 | "country": "US", 92 | "geo": { 93 | "lat": 37.7902067, 94 | "lng": -122.409726 95 | }, 96 | "phone": "(888) 826-6893" 97 | }, 98 | { 99 | "name": "DOLLAR RENT A CAR", 100 | "street": "364 O'Farrell St", 101 | "city": "San Francisco", 102 | "state": "CA", 103 | "country": "US", 104 | "geo": { 105 | "lat": 37.7863325, 106 | "lng": -122.4106713 107 | }, 108 | "phone": "(866) 434-2226" 109 | }, 110 | { 111 | "name": "RelayRides", 112 | "street": "116 Natoma St", 113 | "city": "San Francisco", 114 | "state": "CA", 115 | "country": "US", 116 | "geo": { 117 | "lat": 37.787076, 118 | "lng": -122.399412 119 | }, 120 | "phone": "(866) 735-2901" 121 | }, 122 | { 123 | "name": "Pacific Car Rentals", 124 | "street": "501 W Georgia St", 125 | "city": "Vancouver", 126 | "state": "BC", 127 | "country": "Canada", 128 | "geo": { 129 | "lat": 49.2812405, 130 | "lng": -123.1161732 131 | }, 132 | "phone": "+1 604-689-4506" 133 | }, 134 | { 135 | "name": "Enterprise Rent-A-Car", 136 | "street": "1250 Granville St #2", 137 | "city": "Vancouver", 138 | "state": "BC", 139 | "country": "Canada", 140 | "geo": { 141 | "lat": 49.2764472, 142 | "lng": -123.1267565 143 | }, 144 | "phone": "+1 604-688-5500" 145 | }, 146 | { 147 | "name": "Riz Rent a Car & Truck", 148 | "street": "1361 Robson St", 149 | "city": "Vancouver", 150 | "state": "BC", 151 | "country": "Canada", 152 | "geo": { 153 | "lat": 49.2876165, 154 | "lng": -123.1294248 155 | }, 156 | "phone": "+1 604-689-1231" 157 | }, 158 | { 159 | "name": "Vancouver West Side", 160 | "street": "906 W Broadway #101", 161 | "city": "Vancouver", 162 | "state": "BC", 163 | "country": "Canada ‎", 164 | "geo": { 165 | "lat": 49.2630901, 166 | "lng": -123.1244681 167 | }, 168 | "phone": "+1 604-606-2821" 169 | }, 170 | { 171 | "name": "Enterprise Rent-A-Car ", 172 | "street": "3510 Fraser St", 173 | "city": "Vancouver", 174 | "state": "BC", 175 | "country": "Canada", 176 | "geo": { 177 | "lat": 49.2535244, 178 | "lng": -123.0896339 179 | }, 180 | "phone": "+1 604-872-7368" 181 | }, 182 | { 183 | "name": "Hertz Rent a Car", 184 | "street": "1270 Granville St", 185 | "city": "Vancouver", 186 | "state": "BC", 187 | "country": "Canada", 188 | "geo": { 189 | "lat": 49.2764246, 190 | "lng": -123.1272679 191 | }, 192 | "phone": "+1 604-606-4711" 193 | }, 194 | { 195 | "name": "Mr Rent-A-Car (Downtown) Ltd", 196 | "street": "968 Kingsway", 197 | "city": "Vancouver", 198 | "state": "BC", 199 | "country": "Canada", 200 | "geo": { 201 | "lat": 49.2540889, 202 | "lng": -123.084271 203 | }, 204 | "phone": "+1 604-876-7777" 205 | }, 206 | { 207 | "name": "Thrifty Car Rental", 208 | "street": "413 Seymour St", 209 | "city": "Vancouver", 210 | "state": "BC", 211 | "country": "Canada", 212 | "geo": { 213 | "lat": 49.2847411, 214 | "lng": -123.1134275 215 | }, 216 | "phone": "+1 604-606-1666" 217 | }, 218 | { 219 | "name": "Budget Car & Truck Rental", 220 | "street": "855 Kingsway", 221 | "city": "Vancouver", 222 | "state": "BC", 223 | "country": "Canada", 224 | "geo": { 225 | "lat": 49.2557069, 226 | "lng": -123.0876104 227 | }, 228 | "phone": "+1 604-668-7000" 229 | }, 230 | { 231 | "name": "Pacific Car Rentals", 232 | "street": "1132 W Hastings St", 233 | "city": "Vancouver", 234 | "state": "BC", 235 | "country": "Canada", 236 | "geo": { 237 | "lat": 49.2881493, 238 | "lng": -123.1205626 239 | }, 240 | "phone": "+1 604-689-3994" 241 | }, 242 | { 243 | "name": "Dollar Rent A Car", 244 | "street": "1659 Airport Blvd", 245 | "city": "San Jose", 246 | "state": "CA", 247 | "country": "US", 248 | "geo": { 249 | "lat": 37.365759, 250 | "lng": -121.9233569 251 | }, 252 | "phone": "(866) 434-2226" 253 | }, 254 | { 255 | "name": "Avis Rent A Car", 256 | "street": "1659 Airport Blvd #3", 257 | "city": "San Jose", 258 | "state": "CA", 259 | "country": "US", 260 | "geo": { 261 | "lat": 37.365759, 262 | "lng": -121.9233569 263 | }, 264 | "phone": "(408) 993-2224" 265 | }, 266 | { 267 | "name": "National Car Rental", 268 | "street": "1659 Airport Blvd #8", 269 | "city": "San Jose", 270 | "state": "CA", 271 | "country": "US", 272 | "geo": { 273 | "lat": 37.365759, 274 | "lng": -121.9233569 275 | }, 276 | "phone": "(408) 288-4662" 277 | }, 278 | { 279 | "name": "Alamo Rent A Car", 280 | "street": "2300 Airport Blvd #120", 281 | "city": "San Jose", 282 | "state": "CA", 283 | "country": "US", 284 | "geo": { 285 | "lat": 37.3682221, 286 | "lng": -121.9265461 287 | }, 288 | "phone": "(888) 826-6893" 289 | }, 290 | { 291 | "name": "Enterprise Rent-A-Car", 292 | "street": "598 S 1st St", 293 | "city": "San Jose", 294 | "state": "CA", 295 | "country": "US", 296 | "geo": { 297 | "lat": 37.3276518, 298 | "lng": -121.8837121 299 | }, 300 | "phone": "(408) 286-4444" 301 | }, 302 | { 303 | "name": "Club Sportiva", 304 | "street": "521 Charcot Ave #237", 305 | "city": "San Jose", 306 | "state": "CA", 307 | "country": "US", 308 | "geo": { 309 | "lat": 37.3835609, 310 | "lng": -121.9145028 311 | }, 312 | "phone": "(866) 719-1600" 313 | }, 314 | { 315 | "name": "Service Rent-A-Car Inc", 316 | "street": "698 S 1st St", 317 | "city": "San Jose", 318 | "state": "CA", 319 | "country": "US", 320 | "geo": { 321 | "lat": 37.325977, 322 | "lng": -121.8823558 323 | }, 324 | "phone": "(408) 554-6351" 325 | }, 326 | { 327 | "name": "Budget Rent A Car", 328 | "street": "1659 Airport Blvd #3", 329 | "city": "San Jose", 330 | "state": "CA", 331 | "country": "US", 332 | "geo": { 333 | "lat": 37.365759, 334 | "lng": -121.9233569 335 | }, 336 | "phone": "(408) 286-7850" 337 | }, 338 | { 339 | "name": "Hertz Rent A Car", 340 | "street": "1659 Airport Blvd", 341 | "city": "San Jose", 342 | "state": "CA", 343 | "country": "US", 344 | "geo": { 345 | "lat": 37.365759, 346 | "lng": -121.9233569 347 | }, 348 | "phone": "(408) 450-6000" 349 | }, 350 | { 351 | "name": "Enterprise Rent-A-Car", 352 | "street": "1485 Kerley Dr", 353 | "city": "San Jose", 354 | "state": "CA", 355 | "country": "US", 356 | "geo": { 357 | "lat": 37.363965, 358 | "lng": -121.9094736 359 | }, 360 | "phone": "(408) 437-1001" 361 | }, 362 | { 363 | "name": "Enterprise Rent-A-Car", 364 | "street": "445 SW Pine St", 365 | "city": "Portland", 366 | "state": "OR", 367 | "country": "US", 368 | "geo": { 369 | "lat": 45.52209510000001, 370 | "lng": -122.6749466 371 | }, 372 | "phone": "(503) 275-5359" 373 | }, 374 | { 375 | "name": "Enterprise Rent-A-Car", 376 | "street": "1623 W Burnside St", 377 | "city": "Portland", 378 | "state": "OR", 379 | "country": "US", 380 | "geo": { 381 | "lat": 45.52318, 382 | "lng": -122.6878289 383 | }, 384 | "phone": "(503) 220-8200" 385 | }, 386 | { 387 | "name": "Enterprise Rent-A-Car", 388 | "street": "8360 SW Barbur Blvd", 389 | "city": "Portland", 390 | "state": "OR", 391 | "country": "US", 392 | "geo": { 393 | "lat": 45.4640728, 394 | "lng": -122.6998026 395 | }, 396 | "phone": "(503) 977-7700" 397 | }, 398 | { 399 | "name": "Enterprise Rent-A-Car", 400 | "street": "611 E Burnside St", 401 | "city": "Portland", 402 | "state": "OR", 403 | "country": "US", 404 | "geo": { 405 | "lat": 45.5232538, 406 | "lng": -122.6593052 407 | }, 408 | "phone": "(503) 230-1212" 409 | }, 410 | { 411 | "name": "Enterprise Rent-A-Car", 412 | "street": "2740 NE Sandy Blvd", 413 | "city": "Portland", 414 | "state": "OR", 415 | "country": "US", 416 | "geo": { 417 | "lat": 45.5286503, 418 | "lng": -122.6375496 419 | }, 420 | "phone": "(503) 963-1795" 421 | }, 422 | { 423 | "name": "Budget Rent-A-Car", 424 | "street": "3400 NE Columbia Blvd", 425 | "city": "Portland", 426 | "state": "OR", 427 | "country": "US", 428 | "geo": { 429 | "lat": 45.5728696, 430 | "lng": -122.6292638 431 | }, 432 | "phone": "(503) 288-2985" 433 | }, 434 | { 435 | "name": "Hertz Rent A Car", 436 | "street": "1441 NE 2nd Ave", 437 | "city": "Portland", 438 | "state": "OR", 439 | "country": "US", 440 | "geo": { 441 | "lat": 45.5337763, 442 | "lng": -122.664146 443 | }, 444 | "phone": "(503) 282-2644" 445 | }, 446 | { 447 | "name": "Avis Rent A Car", 448 | "street": "330 SW Washington St", 449 | "city": "Portland", 450 | "state": "OR", 451 | "country": "US", 452 | "geo": { 453 | "lat": 45.5195197, 454 | "lng": -122.675584 455 | }, 456 | "phone": "(503) 227-0220" 457 | }, 458 | { 459 | "name": "Dollar Rent a Car", 460 | "street": "132 NW Broadway", 461 | "city": "Portland", 462 | "state": "OR", 463 | "country": "US", 464 | "geo": { 465 | "lat": 45.5243455, 466 | "lng": -122.6772406 467 | }, 468 | "phone": "(503) 228-3540" 469 | }, 470 | { 471 | "name": "Crown Auto Rental", 472 | "street": "4826 NE 105 Ave", 473 | "city": "Portland", 474 | "state": "OR", 475 | "country": "US", 476 | "geo": { 477 | "lat": 45.55833620000001, 478 | "lng": -122.5545602 479 | }, 480 | "phone": "(503) 230-1103" 481 | } 482 | ] -------------------------------------------------------------------------------- /server/sample-data/updatedb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run `node import.js` to import the test data into the db. 3 | */ 4 | 5 | var app = require('../server'); 6 | var db = app.dataSources.db; 7 | 8 | var models = ['AccessToken', 'Role', 'ACL', 'RoleMapping']; 9 | db.autoupdate(models, function(err) { 10 | if(err) { 11 | console.error(err); 12 | } else { 13 | console.log('Tables are created for ', models); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var loopback = require('loopback'); 2 | var boot = require('loopback-boot'); 3 | 4 | var app = module.exports = loopback(); 5 | 6 | // Set up the /favicon.ico 7 | app.use(loopback.favicon()); 8 | 9 | // request pre-processing middleware 10 | app.use(loopback.compress()); 11 | 12 | // -- Add your pre-processing middleware here -- 13 | 14 | // boot scripts mount components like REST API 15 | boot(app, __dirname); 16 | 17 | // -- Mount static files here-- 18 | // All static middleware should be registered at the end, as all requests 19 | // passing the static middleware are hitting the file system 20 | // Example: 21 | // app.use(loopback.static(path.resolve(__dirname', '../client'))); 22 | var websitePath = require('path').resolve(__dirname, '../client'); 23 | app.use(loopback.static(websitePath)); 24 | 25 | // Requests that get this far won't be handled 26 | // by any middleware. Convert them into a 404 error 27 | // that will be handled later down the chain. 28 | app.use(loopback.urlNotFound()); 29 | 30 | // The ultimate error handler. 31 | app.use(loopback.errorHandler()); 32 | 33 | app.start = function() { 34 | // start the web server 35 | return app.listen(process.env.PORT || 3000, function() { 36 | app.emit('started'); 37 | console.log('Web server listening at: %s', app.get('url')); 38 | }); 39 | }; 40 | 41 | // start the server if `$ node server.js` 42 | if (require.main === module) { 43 | app.start(); 44 | } 45 | -------------------------------------------------------------------------------- /server/test/rest-api.test.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, before */ 2 | /** 3 | * REST API Tests 4 | */ 5 | var request = require('supertest'); 6 | var app = require('../server'); 7 | var assert = require('assert'); 8 | 9 | before(function importSampleData(done) { 10 | this.timeout(50000); 11 | if (app.importing) { 12 | app.on('import done', done); 13 | } else { 14 | done(); 15 | } 16 | }); 17 | 18 | function json(verb, url) { 19 | return request(app)[verb](url) 20 | .set('Content-Type', 'application/json') 21 | .set('Accept', 'application/json') 22 | .expect('Content-Type', /json/); 23 | } 24 | 25 | describe('REST', function() { 26 | this.timeout(30000); 27 | 28 | /** 29 | * Expected Input Tests 30 | */ 31 | 32 | describe('Expected Usage', function() { 33 | 34 | describe('GET /api/cars', function() { 35 | it('should return a list of all cars', function(done) { 36 | json('get', '/api/cars') 37 | .expect(200) 38 | .end(function(err, res) { 39 | assert(Array.isArray(res.body)); 40 | assert(res.body.length); 41 | 42 | done(); 43 | }); 44 | }); 45 | }); 46 | 47 | var carId; 48 | 49 | describe('POST /api/cars', function() { 50 | 51 | it('should create a new car', function(done) { 52 | json('post', '/api/cars') 53 | .send({ 54 | vin: 'ebaddaa5-35bb-4b33-a388-87203acb6478', 55 | year: '2013', 56 | make: 'Dodge', 57 | model: 'Taurus', 58 | image: '/images/car/car_0.jpg', 59 | carClass: 'suv', 60 | color: 'white' 61 | }) 62 | .expect(200) 63 | .end(function(err, res) { 64 | assert(typeof res.body === 'object'); 65 | assert(res.body.id, 'must have an id'); 66 | carId = res.body.id; 67 | done(); 68 | }); 69 | }); 70 | }); 71 | 72 | describe('PUT /api/cars/:id', function() { 73 | it('should update a car with the given id', function(done) { 74 | json('put', '/api/cars/' + carId) 75 | .send({ 76 | year: 2000, 77 | color: 'red' 78 | }) 79 | .expect(200, function(err, res) { 80 | var updatedCar = res.body; 81 | assert(updatedCar); 82 | assert(updatedCar.id); 83 | assert.equal(updatedCar.id, carId); 84 | assert.equal(updatedCar.year, 2000); 85 | json('get', '/api/cars/' + carId) 86 | .expect(200, function(err, res) { 87 | var foundCar = res.body; 88 | assert.equal(foundCar.id, carId); 89 | assert.equal(foundCar.year, 2000); 90 | assert.equal(foundCar.color, 'red'); 91 | done(); 92 | }); 93 | }); 94 | }); 95 | }); 96 | 97 | describe('GET /api/locations', function() { 98 | it('should return a list of locations', function(done) { 99 | json('get', '/api/locations') 100 | .expect(200, function(err, res) { 101 | var locations = res.body; 102 | assert(Array.isArray(locations)); 103 | assert(locations.length); 104 | done(); 105 | }); 106 | }); 107 | }); 108 | 109 | describe('GET /api/locations/nearby', function() { 110 | it('should return a list of locations near given point', function(done) { 111 | var url = '/api/locations/nearby?' + 112 | 'here[lat]=37.7883415&here[lng]=-122.4209035'; 113 | json('get', url) 114 | .expect(200, function(err, res) { 115 | var locations = res.body; 116 | assert(Array.isArray(locations)); 117 | assert.equal(locations[0].name, 'City Rent-a-Car'); 118 | assert.equal(locations[0].city, 'San Francisco'); 119 | assert.equal(locations.length, 10); 120 | locations.forEach(function(l) { 121 | assert(l.geo); 122 | assert.equal(typeof l.geo.lat, 'number'); 123 | assert.equal(typeof l.geo.lng, 'number'); 124 | }); 125 | done(); 126 | }); 127 | }); 128 | }); 129 | 130 | describe('GET /api/locations/:id/inventory', function() { 131 | it('should return a list of inventory for the given location id', 132 | function(done) { 133 | json('get', '/api/locations/88/inventory') 134 | .expect(200, function(err, res) { 135 | var inventory = res.body; 136 | inventory.forEach(function(inv) { 137 | assert.equal(typeof inv.total, 'number'); 138 | assert.equal(typeof inv.available, 'number'); 139 | }); 140 | done(); 141 | }); 142 | } 143 | ); 144 | }); 145 | 146 | describe('/api/customers', function() { 147 | // hard-coded in sample data 148 | var credentials = { email: 'foo@bar.com', password: '123456' }; 149 | var token; 150 | var customerId; 151 | 152 | it('should login existing customer on POST /api/customers/login', 153 | function(done) { 154 | json('post', '/api/customers/login?include=user') 155 | .send(credentials) 156 | .expect(200, function(err, res) { 157 | if (err) return done(err); 158 | token = res.body; 159 | assert(token.userId !== undefined); 160 | customerId = token.userId; 161 | done(); 162 | }); 163 | } 164 | ); 165 | 166 | it('should allow GET /api/customers/{my-id}', function(done) { 167 | json('get', '/api/customers/' + customerId) 168 | .set('Authorization', token.id) 169 | .expect(200, function(err, res) { 170 | if (err) return done(err); 171 | assert.equal(res.body.email, token.user.email); 172 | done(); 173 | }); 174 | }); 175 | 176 | it('should not allow GET /api/customers/{another-id}', function(done) { 177 | json('get', '/api/customers/' + (customerId + 1000)) 178 | .set('Authorization', token.id) 179 | .expect(401, function(err) { 180 | done(err); 181 | }); 182 | }); 183 | 184 | it('should logout existing customer on POST /api/customers/logout', 185 | function(done) { 186 | json('post', '/api/customers/logout') 187 | .set('Authorization', token.id) 188 | .send({}) 189 | .expect(204, done); 190 | } 191 | ); 192 | }); 193 | }); 194 | 195 | describe('Unexpected Usage', function() { 196 | describe('POST /api/cars/:id', function() { 197 | it('should not crash the server when posting a bad id', function(done) { 198 | json('post', '/api/cars/foobar').send({}).expect(404, done); 199 | }); 200 | }); 201 | }); 202 | 203 | }); 204 | -------------------------------------------------------------------------------- /strong-resources/app.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | httpProxy = require('http-proxy'); 3 | 4 | // Create a proxy server with custom application logic 5 | var proxy = httpProxy.createProxyServer({}); 6 | proxy.on('error', function (err, req, res) { 7 | res.writeHead(500, { 8 | 'Content-Type': 'text/plain' 9 | }); 10 | res.end(err); 11 | }); 12 | 13 | // If "-pm" is at the end of the app/domain name, or PM_URL matches the route, 14 | // proxy to the Process Manager. Otherwise, proxy to the application itself 15 | var server = http.createServer(function(req, res) { 16 | 17 | req.headers['Access-Control-Allow-Origin'] = "*" 18 | if (req.headers.host.indexOf('-pm.') != -1){ 19 | proxy.web(req, res, { target: 'http://127.0.0.1:8701' }); 20 | } 21 | // specify something like myapp-pm.mybluemix.net 22 | else if (req.headers.host.indexOf(process.env.PM_URL) != -1){ 23 | proxy.web(req, res, { target: 'http://127.0.0.1:8701' }); 24 | } 25 | else 26 | { 27 | proxy.web(req, res, { target: 'http://127.0.0.1:3001' }); 28 | } 29 | }); 30 | 31 | 32 | console.log("listening on port " + process.env.PORT) 33 | server.listen(process.env.PORT); 34 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | examples 3 | doc 4 | benchmark 5 | .travis.yml 6 | CHANGELOG.md 7 | UPGRADING.md -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | node-http-proxy 3 | 4 | Copyright (c) Nodejitsu 2013 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | node-http-proxy 6 | ======= 7 | 8 | `node-http-proxy` is an HTTP programmable proxying library that supports 9 | websockets. It is suitable for implementing components such as 10 | proxies and load balancers. 11 | 12 | ### Build Status 13 | 14 |

15 | 16 |    17 | 18 | 19 |

20 | 21 | ### Looking to Upgrade from 0.8.x ? Click [here](UPGRADING.md) 22 | 23 | ### Core Concept 24 | 25 | A new proxy is created by calling `createProxyServer` and passing 26 | an `options` object as argument ([valid properties are available here](lib/http-proxy.js#L33-L50)) 27 | 28 | ```javascript 29 | var httpProxy = require('http-proxy'); 30 | 31 | var proxy = httpProxy.createProxyServer(options); 32 | ``` 33 | 34 | An object will be returned with four values: 35 | 36 | * web `req, res, [options]` (used for proxying regular HTTP(S) requests) 37 | * ws `req, socket, head, [options]` (used for proxying WS(S) requests) 38 | * listen `port` (a function that wraps the object in a webserver, for your convenience) 39 | * close `[callback]` (a function that closes the inner webserver and stops listening on given port) 40 | 41 | It is then possible to proxy requests by calling these functions 42 | 43 | ```javascript 44 | http.createServer(function(req, res) { 45 | proxy.web(req, res, { target: 'http://mytarget.com:8080' }); 46 | }); 47 | ``` 48 | 49 | Errors can be listened on either using the Event Emitter API 50 | 51 | ```javascript 52 | proxy.on('error', function(e) { 53 | ... 54 | }); 55 | ``` 56 | 57 | or using the callback API 58 | 59 | ```javascript 60 | proxy.web(req, res, { target: 'http://mytarget.com:8080' }, function(e) { ... }); 61 | ``` 62 | 63 | When a request is proxied it follows two different pipelines ([available here](lib/http-proxy/passes)) 64 | which apply transformations to both the `req` and `res` object. 65 | The first pipeline (ingoing) is responsible for the creation and manipulation of the stream that connects your client to the target. 66 | The second pipeline (outgoing) is responsible for the creation and manipulation of the stream that, from your target, returns data 67 | to the client. 68 | 69 | 70 | #### Setup a basic stand-alone proxy server 71 | 72 | ```js 73 | var http = require('http'), 74 | httpProxy = require('http-proxy'); 75 | // 76 | // Create your proxy server and set the target in the options. 77 | // 78 | httpProxy.createProxyServer({target:'http://localhost:9000'}).listen(8000); 79 | 80 | // 81 | // Create your target server 82 | // 83 | http.createServer(function (req, res) { 84 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 85 | res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2)); 86 | res.end(); 87 | }).listen(9000); 88 | ``` 89 | 90 | #### Setup a stand-alone proxy server with custom server logic 91 | This example show how you can proxy a request using your own HTTP server 92 | and also you can put your own logic to handle the request. 93 | 94 | ```js 95 | var http = require('http'), 96 | httpProxy = require('http-proxy'); 97 | 98 | // 99 | // Create a proxy server with custom application logic 100 | // 101 | var proxy = httpProxy.createProxyServer({}); 102 | 103 | // 104 | // Create your custom server and just call `proxy.web()` to proxy 105 | // a web request to the target passed in the options 106 | // also you can use `proxy.ws()` to proxy a websockets request 107 | // 108 | var server = http.createServer(function(req, res) { 109 | // You can define here your custom logic to handle the request 110 | // and then proxy the request. 111 | proxy.web(req, res, { target: 'http://127.0.0.1:5060' }); 112 | }); 113 | 114 | console.log("listening on port 5050") 115 | server.listen(5050); 116 | ``` 117 | #### Modify a response from a proxied server 118 | Sometimes when you have received a HTML/XML document from the server of origin you would like to modify it before forwarding it on. 119 | 120 | [Harmon](https://github.com/No9/harmon) allows you to do this in a streaming style so as to keep the pressure on the proxy to a minimum. 121 | 122 | 123 | #### Setup a stand-alone proxy server with proxy request header re-writing 124 | This example shows how you can proxy a request using your own HTTP server that 125 | modifies the outgoing proxy request by adding a special header. 126 | 127 | ```js 128 | var http = require('http'), 129 | httpProxy = require('http-proxy'); 130 | 131 | // 132 | // Create a proxy server with custom application logic 133 | // 134 | var proxy = httpProxy.createProxyServer({}); 135 | 136 | // To modify the proxy connection before data is sent, you can listen 137 | // for the 'proxyReq' event. When the event is fired, you will receive 138 | // the following arguments: 139 | // (http.ClientRequest proxyReq, http.IncomingMessage req, 140 | // http.ServerResponse res, Object options). This mechanism is useful when 141 | // you need to modify the proxy request before the proxy connection 142 | // is made to the target. 143 | // 144 | proxy.on('proxyReq', function(proxyReq, req, res, options) { 145 | proxyReq.setHeader('X-Special-Proxy-Header', 'foobar'); 146 | }); 147 | 148 | var server = http.createServer(function(req, res) { 149 | // You can define here your custom logic to handle the request 150 | // and then proxy the request. 151 | proxy.web(req, res, { 152 | target: 'http://127.0.0.1:5060' 153 | }); 154 | }); 155 | 156 | console.log("listening on port 5050") 157 | server.listen(5050); 158 | ``` 159 | 160 | #### Setup a stand-alone proxy server with latency 161 | 162 | ```js 163 | var http = require('http'), 164 | httpProxy = require('http-proxy'); 165 | 166 | // 167 | // Create a proxy server with latency 168 | // 169 | var proxy = httpProxy.createProxyServer(); 170 | 171 | // 172 | // Create your server that makes an operation that waits a while 173 | // and then proxies the request 174 | // 175 | http.createServer(function (req, res) { 176 | // This simulates an operation that takes 500ms to execute 177 | setTimeout(function () { 178 | proxy.web(req, res, { 179 | target: 'http://localhost:9008' 180 | }); 181 | }, 500); 182 | }).listen(8008); 183 | 184 | // 185 | // Create your target server 186 | // 187 | http.createServer(function (req, res) { 188 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 189 | res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 190 | res.end(); 191 | }).listen(9008); 192 | ``` 193 | 194 | #### Listening for proxy events 195 | 196 | * `error`: The error event is emitted if the request to the target fail. 197 | * `proxyRes`: This event is emitted if the request to the target got a response. 198 | * `open`: This event is emitted once the proxy websocket was created and piped into the target websocket. 199 | * `close`: This event is emitted once the proxy websocket was closed. 200 | * (DEPRECATED) `proxySocket`: Deprecated in favor of `open`. 201 | 202 | ```js 203 | var httpProxy = require('http-proxy'); 204 | // Error example 205 | // 206 | // Http Proxy Server with bad target 207 | // 208 | var proxy = httpProxy.createServer({ 209 | target:'http://localhost:9005' 210 | }); 211 | 212 | proxy.listen(8005); 213 | 214 | // 215 | // Listen for the `error` event on `proxy`. 216 | proxy.on('error', function (err, req, res) { 217 | res.writeHead(500, { 218 | 'Content-Type': 'text/plain' 219 | }); 220 | 221 | res.end('Something went wrong. And we are reporting a custom error message.'); 222 | }); 223 | 224 | // 225 | // Listen for the `proxyRes` event on `proxy`. 226 | // 227 | proxy.on('proxyRes', function (proxyRes, req, res) { 228 | console.log('RAW Response from the target', JSON.stringify(proxyRes.headers, true, 2)); 229 | }); 230 | 231 | // 232 | // Listen for the `open` event on `proxy`. 233 | // 234 | proxy.on('open', function (proxySocket) { 235 | // listen for messages coming FROM the target here 236 | proxySocket.on('data', hybiParseAndLogMessage); 237 | }); 238 | 239 | // 240 | // Listen for the `close` event on `proxy`. 241 | // 242 | proxy.on('close', function (req, socket, head) { 243 | // view disconnected websocket connections 244 | console.log('Client disconnected'); 245 | }); 246 | ``` 247 | 248 | #### Using HTTPS 249 | You can activate the validation of a secure SSL certificate to the target connection (avoid self signed certs), just set `secure: true` in the options. 250 | 251 | ##### HTTPS -> HTTP 252 | 253 | ```js 254 | // 255 | // Create the HTTPS proxy server in front of a HTTP server 256 | // 257 | httpProxy.createServer({ 258 | target: { 259 | host: 'localhost', 260 | port: 9009 261 | }, 262 | ssl: { 263 | key: fs.readFileSync('valid-ssl-key.pem', 'utf8'), 264 | cert: fs.readFileSync('valid-ssl-cert.pem', 'utf8') 265 | } 266 | }).listen(8009); 267 | ``` 268 | 269 | ##### HTTPS -> HTTPS 270 | 271 | ```js 272 | // 273 | // Create the proxy server listening on port 443 274 | // 275 | httpProxy.createServer({ 276 | ssl: { 277 | key: fs.readFileSync('valid-ssl-key.pem', 'utf8'), 278 | cert: fs.readFileSync('valid-ssl-cert.pem', 'utf8') 279 | }, 280 | target: 'https://localhost:9010', 281 | secure: true // Depends on your needs, could be false. 282 | }).listen(443); 283 | ``` 284 | 285 | #### Proxying WebSockets 286 | You can activate the websocket support for the proxy using `ws:true` in the options. 287 | 288 | ```js 289 | // 290 | // Create a proxy server for websockets 291 | // 292 | httpProxy.createServer({ 293 | target: 'ws://localhost:9014', 294 | ws: true 295 | }).listen(8014); 296 | ``` 297 | 298 | Also you can proxy the websocket requests just calling the `ws(req, socket, head)` method. 299 | 300 | ```js 301 | // 302 | // Setup our server to proxy standard HTTP requests 303 | // 304 | var proxy = new httpProxy.createProxyServer({ 305 | target: { 306 | host: 'localhost', 307 | port: 9015 308 | } 309 | }); 310 | var proxyServer = http.createServer(function (req, res) { 311 | proxy.web(req, res); 312 | }); 313 | 314 | // 315 | // Listen to the `upgrade` event and proxy the 316 | // WebSocket requests as well. 317 | // 318 | proxyServer.on('upgrade', function (req, socket, head) { 319 | proxy.ws(req, socket, head); 320 | }); 321 | 322 | proxyServer.listen(8015); 323 | ``` 324 | 325 | ### Contributing and Issues 326 | 327 | * Search on Google/Github 328 | * If you can't find anything, open an issue 329 | * If you feel comfortable about fixing the issue, fork the repo 330 | * Commit to your local branch (which must be different from `master`) 331 | * Submit your Pull Request (be sure to include tests and update documentation) 332 | 333 | ### Options 334 | 335 | `httpProxy.createProxyServer` supports the following options: 336 | 337 | * **target**: url string to be parsed with the url module 338 | * **forward**: url string to be parsed with the url module 339 | * **agent**: object to be passed to http(s).request (see Node's [https agent](http://nodejs.org/api/https.html#https_class_https_agent) and [http agent](http://nodejs.org/api/http.html#http_class_http_agent) objects) 340 | * **secure**: true/false, if you want to verify the SSL Certs 341 | * **xfwd**: true/false, adds x-forward headers 342 | * **toProxy**: passes the absolute URL as the `path` (useful for proxying to proxies) 343 | * **hostRewrite**: rewrites the location hostname on (301/302/307/308) redirects. 344 | 345 | If you are using the `proxyServer.listen` method, the following options are also applicable: 346 | 347 | * **ssl**: object to be passed to https.createServer() 348 | * **ws**: true/false, if you want to proxy websockets 349 | 350 | ### Shutdown 351 | 352 | * When testing or running server within another program it may be necessary to close the proxy. 353 | * This will stop the proxy from accepting new connections. 354 | 355 | ```js 356 | var proxy = new httpProxy.createProxyServer({ 357 | target: { 358 | host: 'localhost', 359 | port: 1337 360 | } 361 | }); 362 | 363 | proxy.close(); 364 | ``` 365 | 366 | ### Test 367 | 368 | ``` 369 | $ npm test 370 | ``` 371 | 372 | ### Logo 373 | 374 | Logo created by [Diego Pasquali](http://dribbble.com/diegopq) 375 | 376 | ### License 377 | 378 | >The MIT License (MIT) 379 | > 380 | >Copyright (c) 2010 - 2013 Nodejitsu Inc. 381 | > 382 | >Permission is hereby granted, free of charge, to any person obtaining a copy 383 | >of this software and associated documentation files (the "Software"), to deal 384 | >in the Software without restriction, including without limitation the rights 385 | >to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 386 | >copies of the Software, and to permit persons to whom the Software is 387 | >furnished to do so, subject to the following conditions: 388 | > 389 | >The above copyright notice and this permission notice shall be included in 390 | >all copies or substantial portions of the Software. 391 | > 392 | >THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 393 | >IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 394 | >FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 395 | >AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 396 | >LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 397 | >OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 398 | >THE SOFTWARE. 399 | 400 | 401 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Caron dimonio, con occhi di bragia 3 | * loro accennando, tutte le raccoglie; 4 | * batte col remo qualunque s’adagia 5 | * 6 | * Charon the demon, with the eyes of glede, 7 | * Beckoning to them, collects them all together, 8 | * Beats with his oar whoever lags behind 9 | * 10 | * Dante - The Divine Comedy (Canto III) 11 | */ 12 | 13 | module.exports = require('./lib/http-proxy'); -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/lib/http-proxy.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | https = require('https'), 3 | url = require('url'), 4 | httpProxy = require('./http-proxy/'); 5 | 6 | /** 7 | * Export the proxy "Server" as the main export. 8 | */ 9 | module.exports = httpProxy.Server; 10 | 11 | /** 12 | * Creates the proxy server. 13 | * 14 | * Examples: 15 | * 16 | * httpProxy.createProxyServer({ .. }, 8000) 17 | * // => '{ web: [Function], ws: [Function] ... }' 18 | * 19 | * @param {Object} Options Config object passed to the proxy 20 | * 21 | * @return {Object} Proxy Proxy object with handlers for `ws` and `web` requests 22 | * 23 | * @api public 24 | */ 25 | 26 | module.exports.createProxyServer = 27 | module.exports.createServer = 28 | module.exports.createProxy = function createProxyServer(options) { 29 | /* 30 | * `options` is needed and it must have the following layout: 31 | * 32 | * { 33 | * target : 34 | * forward: 35 | * agent : 36 | * ssl : 37 | * ws : 38 | * xfwd : 39 | * secure : 40 | * toProxy: 41 | * prependPath: 42 | * ignorePath: 43 | * localAddress : 44 | * changeOrigin: 45 | * auth : Basic authentication i.e. 'user:password' to compute an Authorization header. 46 | * hostRewrite: rewrites the location hostname on (301/302/307/308) redirects, Default: null. 47 | * autoRewrite: rewrites the location host/port on (301/302/307/308) redirects based on requested host/port. Default: false. 48 | * protocolRewrite: rewrites the location protocol on (301/302/307/308) redirects to 'http' or 'https'. Default: null. 49 | * } 50 | * 51 | * NOTE: `options.ws` and `options.ssl` are optional. 52 | * `options.target and `options.forward` cannot be 53 | * both missing 54 | * } 55 | */ 56 | 57 | return new httpProxy.Server(options); 58 | }; 59 | 60 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/lib/http-proxy/common.js: -------------------------------------------------------------------------------- 1 | var common = exports, 2 | url = require('url'), 3 | extend = require('util')._extend, 4 | required = require('requires-port'); 5 | 6 | var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i, 7 | isSSL = /^https|wss/; 8 | 9 | /** 10 | * Simple Regex for testing if protocol is https 11 | */ 12 | common.isSSL = isSSL; 13 | /** 14 | * Copies the right headers from `options` and `req` to 15 | * `outgoing` which is then used to fire the proxied 16 | * request. 17 | * 18 | * Examples: 19 | * 20 | * common.setupOutgoing(outgoing, options, req) 21 | * // => { host: ..., hostname: ...} 22 | * 23 | * @param {Object} Outgoing Base object to be filled with required properties 24 | * @param {Object} Options Config object passed to the proxy 25 | * @param {ClientRequest} Req Request Object 26 | * @param {String} Forward String to select forward or target 27 | *  28 | * @return {Object} Outgoing Object with all required properties set 29 | * 30 | * @api private 31 | */ 32 | 33 | common.setupOutgoing = function(outgoing, options, req, forward) { 34 | outgoing.port = options[forward || 'target'].port || 35 | (isSSL.test(options[forward || 'target'].protocol) ? 443 : 80); 36 | 37 | ['host', 'hostname', 'socketPath', 'pfx', 'key', 38 | 'passphrase', 'cert', 'ca', 'ciphers', 'secureProtocol'].forEach( 39 | function(e) { outgoing[e] = options[forward || 'target'][e]; } 40 | ); 41 | 42 | outgoing.method = req.method; 43 | outgoing.headers = extend({}, req.headers); 44 | 45 | if (options.headers){ 46 | extend(outgoing.headers, options.headers); 47 | } 48 | 49 | if (options.auth) { 50 | outgoing.auth = options.auth; 51 | } 52 | 53 | if (isSSL.test(options[forward || 'target'].protocol)) { 54 | outgoing.rejectUnauthorized = (typeof options.secure === "undefined") ? true : options.secure; 55 | } 56 | 57 | 58 | outgoing.agent = options.agent || false; 59 | outgoing.localAddress = options.localAddress; 60 | 61 | // 62 | // Remark: If we are false and not upgrading, set the connection: close. This is the right thing to do 63 | // as node core doesn't handle this COMPLETELY properly yet. 64 | // 65 | if (!outgoing.agent) { 66 | outgoing.headers = outgoing.headers || {}; 67 | if (typeof outgoing.headers.connection !== 'string' 68 | || !upgradeHeader.test(outgoing.headers.connection) 69 | ) { outgoing.headers.connection = 'close'; } 70 | } 71 | 72 | 73 | // the final path is target path + relative path requested by user: 74 | var target = options[forward || 'target']; 75 | var targetPath = target && options.prependPath !== false 76 | ? (target.path || '') 77 | : ''; 78 | 79 | // 80 | // Remark: Can we somehow not use url.parse as a perf optimization? 81 | // 82 | var outgoingPath = !options.toProxy 83 | ? (url.parse(req.url).path || '/') 84 | : req.url; 85 | 86 | // 87 | // Remark: ignorePath will just straight up ignore whatever the request's 88 | // path is. This can be labeled as FOOT-GUN material if you do not know what 89 | // you are doing and are using conflicting options. 90 | // 91 | outgoingPath = !options.ignorePath ? outgoingPath : '/'; 92 | 93 | outgoing.path = common.urlJoin(targetPath, outgoingPath); 94 | 95 | if (options.changeOrigin) { 96 | outgoing.headers.host = 97 | required(outgoing.port, options[forward || 'target'].protocol) && !hasPort(outgoing.host) 98 | ? outgoing.host + ':' + outgoing.port 99 | : outgoing.host; 100 | } 101 | return outgoing; 102 | }; 103 | 104 | /** 105 | * Set the proper configuration for sockets, 106 | * set no delay and set keep alive, also set 107 | * the timeout to 0. 108 | * 109 | * Examples: 110 | * 111 | * common.setupSocket(socket) 112 | * // => Socket 113 | * 114 | * @param {Socket} Socket instance to setup 115 | *  116 | * @return {Socket} Return the configured socket. 117 | * 118 | * @api private 119 | */ 120 | 121 | common.setupSocket = function(socket) { 122 | socket.setTimeout(0); 123 | socket.setNoDelay(true); 124 | 125 | socket.setKeepAlive(true, 0); 126 | 127 | return socket; 128 | }; 129 | 130 | /** 131 | * Get the port number from the host. Or guess it based on the connection type. 132 | * 133 | * @param {Request} req Incoming HTTP request. 134 | * 135 | * @return {String} The port number. 136 | * 137 | * @api private 138 | */ 139 | common.getPort = function(req) { 140 | var res = req.headers.host ? req.headers.host.match(/:(\d+)/) : ''; 141 | 142 | return res ? 143 | res[1] : 144 | common.hasEncryptedConnection(req) ? '443' : '80'; 145 | }; 146 | 147 | /** 148 | * Check if the request has an encrypted connection. 149 | * 150 | * @param {Request} req Incoming HTTP request. 151 | * 152 | * @return {Boolean} Whether the connection is encrypted or not. 153 | * 154 | * @api private 155 | */ 156 | common.hasEncryptedConnection = function(req) { 157 | return Boolean(req.connection.encrypted || req.connection.pair); 158 | }; 159 | 160 | /** 161 | * OS-agnostic join (doesn't break on URLs like path.join does on Windows)> 162 | * 163 | * @return {String} The generated path. 164 | * 165 | * @api private 166 | */ 167 | 168 | common.urlJoin = function() { 169 | // 170 | // We do not want to mess with the query string. All we want to touch is the path. 171 | // 172 | var args = Array.prototype.slice.call(arguments), 173 | lastIndex = args.length - 1, 174 | last = args[lastIndex], 175 | lastSegs = last.split('?'), 176 | retSegs; 177 | 178 | args[lastIndex] = lastSegs.shift(); 179 | 180 | // 181 | // Join all strings, but remove empty strings so we don't get extra slashes from 182 | // joining e.g. ['', 'am'] 183 | // 184 | retSegs = [ 185 | args.filter(Boolean).join('/').replace(/\/+/g, '/').replace(/:\//g, '://') 186 | ]; 187 | 188 | // Only join the query string if it exists so we don't have trailing a '?' 189 | // on every request 190 | 191 | // Handle case where there could be multiple ? in the URL. 192 | retSegs.push.apply(retSegs, lastSegs); 193 | 194 | return retSegs.join('?') 195 | }; 196 | 197 | /** 198 | * Check the host and see if it potentially has a port in it (keep it simple) 199 | * 200 | * @returns {Boolean} Whether we have one or not 201 | * 202 | * @api private 203 | */ 204 | function hasPort(host) { 205 | return !!~host.indexOf(':'); 206 | }; 207 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/lib/http-proxy/index.js: -------------------------------------------------------------------------------- 1 | var httpProxy = exports, 2 | extend = require('util')._extend, 3 | parse_url = require('url').parse, 4 | EE3 = require('eventemitter3'), 5 | http = require('http'), 6 | https = require('https'), 7 | web = require('./passes/web-incoming'), 8 | ws = require('./passes/ws-incoming'); 9 | 10 | httpProxy.Server = ProxyServer; 11 | 12 | /** 13 | * Returns a function that creates the loader for 14 | * either `ws` or `web`'s passes. 15 | * 16 | * Examples: 17 | * 18 | * httpProxy.createRightProxy('ws') 19 | * // => [Function] 20 | * 21 | * @param {String} Type Either 'ws' or 'web' 22 | *  23 | * @return {Function} Loader Function that when called returns an iterator for the right passes 24 | * 25 | * @api private 26 | */ 27 | 28 | function createRightProxy(type) { 29 | 30 | return function(options) { 31 | return function(req, res /*, [head], [opts] */) { 32 | var passes = (type === 'ws') ? this.wsPasses : this.webPasses, 33 | args = [].slice.call(arguments), 34 | cntr = args.length - 1, 35 | head, cbl; 36 | 37 | /* optional args parse begin */ 38 | if(typeof args[cntr] === 'function') { 39 | cbl = args[cntr]; 40 | 41 | cntr--; 42 | } 43 | 44 | if( 45 | !(args[cntr] instanceof Buffer) && 46 | args[cntr] !== res 47 | ) { 48 | //Copy global options 49 | options = extend({}, options); 50 | //Overwrite with request options 51 | extend(options, args[cntr]); 52 | 53 | cntr--; 54 | } 55 | 56 | if(args[cntr] instanceof Buffer) { 57 | head = args[cntr]; 58 | } 59 | 60 | /* optional args parse end */ 61 | 62 | ['target', 'forward'].forEach(function(e) { 63 | if (typeof options[e] === 'string') 64 | options[e] = parse_url(options[e]); 65 | }); 66 | 67 | if (!options.target && !options.forward) { 68 | return this.emit('error', new Error('Must provide a proper URL as target')); 69 | } 70 | 71 | for(var i=0; i < passes.length; i++) { 72 | /** 73 | * Call of passes functions 74 | * pass(req, res, options, head) 75 | * 76 | * In WebSockets case the `res` variable 77 | * refer to the connection socket 78 | * pass(req, socket, options, head) 79 | */ 80 | if(passes[i](req, res, options, head, this, cbl)) { // passes can return a truthy value to halt the loop 81 | break; 82 | } 83 | } 84 | }; 85 | }; 86 | } 87 | httpProxy.createRightProxy = createRightProxy; 88 | 89 | function ProxyServer(options) { 90 | EE3.call(this); 91 | 92 | options = options || {}; 93 | options.prependPath = options.prependPath === false ? false : true; 94 | 95 | this.web = this.proxyRequest = createRightProxy('web')(options); 96 | this.ws = this.proxyWebsocketRequest = createRightProxy('ws')(options); 97 | this.options = options; 98 | 99 | this.webPasses = Object.keys(web).map(function(pass) { 100 | return web[pass]; 101 | }); 102 | 103 | this.wsPasses = Object.keys(ws).map(function(pass) { 104 | return ws[pass]; 105 | }); 106 | 107 | this.on('error', this.onError, this); 108 | 109 | } 110 | 111 | require('util').inherits(ProxyServer, EE3); 112 | 113 | ProxyServer.prototype.onError = function (err) { 114 | // 115 | // Remark: Replicate node core behavior using EE3 116 | // so we force people to handle their own errors 117 | // 118 | if(this.listeners('error').length === 1) { 119 | throw err; 120 | } 121 | }; 122 | 123 | ProxyServer.prototype.listen = function(port, hostname) { 124 | var self = this, 125 | closure = function(req, res) { self.web(req, res); }; 126 | 127 | this._server = this.options.ssl ? 128 | https.createServer(this.options.ssl, closure) : 129 | http.createServer(closure); 130 | 131 | if(this.options.ws) { 132 | this._server.on('upgrade', function(req, socket, head) { self.ws(req, socket, head); }); 133 | } 134 | 135 | this._server.listen(port, hostname); 136 | 137 | return this; 138 | }; 139 | 140 | ProxyServer.prototype.close = function(callback) { 141 | var self = this; 142 | if (this._server) { 143 | this._server.close(done); 144 | } 145 | 146 | // Wrap callback to nullify server after all open connections are closed. 147 | function done() { 148 | self._server = null; 149 | if (callback) { 150 | callback.apply(null, arguments); 151 | } 152 | }; 153 | }; 154 | 155 | ProxyServer.prototype.before = function(type, passName, callback) { 156 | if (type !== 'ws' && type !== 'web') { 157 | throw new Error('type must be `web` or `ws`'); 158 | } 159 | var passes = (type === 'ws') ? this.wsPasses : this.webPasses, 160 | i = false; 161 | 162 | passes.forEach(function(v, idx) { 163 | if(v.name === passName) i = idx; 164 | }) 165 | 166 | if(i === false) throw new Error('No such pass'); 167 | 168 | passes.splice(i, 0, callback); 169 | }; 170 | ProxyServer.prototype.after = function(type, passName, callback) { 171 | if (type !== 'ws' && type !== 'web') { 172 | throw new Error('type must be `web` or `ws`'); 173 | } 174 | var passes = (type === 'ws') ? this.wsPasses : this.webPasses, 175 | i = false; 176 | 177 | passes.forEach(function(v, idx) { 178 | if(v.name === passName) i = idx; 179 | }) 180 | 181 | if(i === false) throw new Error('No such pass'); 182 | 183 | passes.splice(i++, 0, callback); 184 | }; 185 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/lib/http-proxy/passes/web-incoming.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | https = require('https'), 3 | web_o = require('./web-outgoing'), 4 | common = require('../common'), 5 | passes = exports; 6 | 7 | web_o = Object.keys(web_o).map(function(pass) { 8 | return web_o[pass]; 9 | }); 10 | 11 | /*! 12 | * Array of passes. 13 | * 14 | * A `pass` is just a function that is executed on `req, res, options` 15 | * so that you can easily add new checks while still keeping the base 16 | * flexible. 17 | */ 18 | 19 | [ // <-- 20 | 21 | /** 22 | * Sets `content-length` to '0' if request is of DELETE type. 23 | * 24 | * @param {ClientRequest} Req Request object 25 | * @param {IncomingMessage} Res Response object 26 | * @param {Object} Options Config object passed to the proxy 27 | * 28 | * @api private 29 | */ 30 | 31 | function deleteLength(req, res, options) { 32 | if((req.method === 'DELETE' || req.method === 'OPTIONS') 33 | && !req.headers['content-length']) { 34 | req.headers['content-length'] = '0'; 35 | } 36 | }, 37 | 38 | /** 39 | * Sets timeout in request socket if it was specified in options. 40 | * 41 | * @param {ClientRequest} Req Request object 42 | * @param {IncomingMessage} Res Response object 43 | * @param {Object} Options Config object passed to the proxy 44 | * 45 | * @api private 46 | */ 47 | 48 | function timeout(req, res, options) { 49 | if(options.timeout) { 50 | req.socket.setTimeout(options.timeout); 51 | } 52 | }, 53 | 54 | /** 55 | * Sets `x-forwarded-*` headers if specified in config. 56 | * 57 | * @param {ClientRequest} Req Request object 58 | * @param {IncomingMessage} Res Response object 59 | * @param {Object} Options Config object passed to the proxy 60 | * 61 | * @api private 62 | */ 63 | 64 | function XHeaders(req, res, options) { 65 | if(!options.xfwd) return; 66 | 67 | var encrypted = req.isSpdy || common.hasEncryptedConnection(req); 68 | var values = { 69 | for : req.connection.remoteAddress || req.socket.remoteAddress, 70 | port : common.getPort(req), 71 | proto: encrypted ? 'https' : 'http' 72 | }; 73 | 74 | ['for', 'port', 'proto'].forEach(function(header) { 75 | req.headers['x-forwarded-' + header] = 76 | (req.headers['x-forwarded-' + header] || '') + 77 | (req.headers['x-forwarded-' + header] ? ',' : '') + 78 | values[header]; 79 | }); 80 | }, 81 | 82 | /** 83 | * Does the actual proxying. If `forward` is enabled fires up 84 | * a ForwardStream, same happens for ProxyStream. The request 85 | * just dies otherwise. 86 | * 87 | * @param {ClientRequest} Req Request object 88 | * @param {IncomingMessage} Res Response object 89 | * @param {Object} Options Config object passed to the proxy 90 | * 91 | * @api private 92 | */ 93 | 94 | function stream(req, res, options, _, server, clb) { 95 | 96 | // And we begin! 97 | server.emit('start', req, res, options.target) 98 | if(options.forward) { 99 | // If forward enable, so just pipe the request 100 | var forwardReq = (options.forward.protocol === 'https:' ? https : http).request( 101 | common.setupOutgoing(options.ssl || {}, options, req, 'forward') 102 | ); 103 | (options.buffer || req).pipe(forwardReq); 104 | if(!options.target) { return res.end(); } 105 | } 106 | 107 | // Request initalization 108 | var proxyReq = (options.target.protocol === 'https:' ? https : http).request( 109 | common.setupOutgoing(options.ssl || {}, options, req) 110 | ); 111 | 112 | // Enable developers to modify the proxyReq before headers are sent 113 | proxyReq.on('socket', function(socket) { 114 | if(server) { server.emit('proxyReq', proxyReq, req, res, options); } 115 | }); 116 | 117 | // allow outgoing socket to timeout so that we could 118 | // show an error page at the initial request 119 | if(options.proxyTimeout) { 120 | proxyReq.setTimeout(options.proxyTimeout, function() { 121 | proxyReq.abort(); 122 | }); 123 | } 124 | 125 | // Ensure we abort proxy if request is aborted 126 | req.on('aborted', function () { 127 | proxyReq.abort(); 128 | }); 129 | 130 | // Handle errors on incoming request as well as it makes sense to 131 | req.on('error', proxyError); 132 | 133 | // Error Handler 134 | proxyReq.on('error', proxyError); 135 | 136 | function proxyError (err){ 137 | if (clb) { 138 | clb(err, req, res, options.target); 139 | } else { 140 | server.emit('error', err, req, res, options.target); 141 | } 142 | } 143 | 144 | (options.buffer || req).pipe(proxyReq); 145 | 146 | proxyReq.on('response', function(proxyRes) { 147 | if(server) { server.emit('proxyRes', proxyRes, req, res); } 148 | for(var i=0; i < web_o.length; i++) { 149 | if(web_o[i](req, res, proxyRes, options)) { break; } 150 | } 151 | 152 | // Allow us to listen when the proxy has completed 153 | proxyRes.on('end', function () { 154 | server.emit('end', req, res, proxyRes); 155 | }); 156 | 157 | proxyRes.pipe(res); 158 | }); 159 | 160 | //proxyReq.end(); 161 | } 162 | 163 | ] // <-- 164 | .forEach(function(func) { 165 | passes[func.name] = func; 166 | }); 167 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/lib/http-proxy/passes/web-outgoing.js: -------------------------------------------------------------------------------- 1 | var url = require('url'), 2 | passes = exports; 3 | 4 | var redirectRegex = /^30(1|2|7|8)$/; 5 | 6 | /*! 7 | * Array of passes. 8 | * 9 | * A `pass` is just a function that is executed on `req, res, options` 10 | * so that you can easily add new checks while still keeping the base 11 | * flexible. 12 | */ 13 | 14 | [ // <-- 15 | 16 | /** 17 | * If is a HTTP 1.0 request, remove chunk headers 18 | * 19 | * @param {ClientRequest} Req Request object 20 | * @param {IncomingMessage} Res Response object 21 | * @param {proxyResponse} Res Response object from the proxy request 22 | * 23 | * @api private 24 | */ 25 | function removeChunked(req, res, proxyRes) { 26 | if (req.httpVersion === '1.0') { 27 | delete proxyRes.headers['transfer-encoding']; 28 | } 29 | }, 30 | 31 | /** 32 | * If is a HTTP 1.0 request, set the correct connection header 33 | * or if connection header not present, then use `keep-alive` 34 | * 35 | * @param {ClientRequest} Req Request object 36 | * @param {IncomingMessage} Res Response object 37 | * @param {proxyResponse} Res Response object from the proxy request 38 | * 39 | * @api private 40 | */ 41 | function setConnection(req, res, proxyRes) { 42 | if (req.httpVersion === '1.0') { 43 | proxyRes.headers.connection = req.headers.connection || 'close'; 44 | } else if (!proxyRes.headers.connection) { 45 | proxyRes.headers.connection = req.headers.connection || 'keep-alive'; 46 | } 47 | }, 48 | 49 | function setRedirectHostRewrite(req, res, proxyRes, options) { 50 | if ((options.hostRewrite || options.autoRewrite || options.protocolRewrite) 51 | && proxyRes.headers['location'] 52 | && redirectRegex.test(proxyRes.statusCode)) { 53 | var target = url.parse(options.target); 54 | var u = url.parse(proxyRes.headers['location']); 55 | 56 | // make sure the redirected host matches the target host before rewriting 57 | if (target.host != u.host) { 58 | return; 59 | } 60 | 61 | if (options.hostRewrite) { 62 | u.host = options.hostRewrite; 63 | } else if (options.autoRewrite) { 64 | u.host = req.headers['host']; 65 | } 66 | if (options.protocolRewrite) { 67 | u.protocol = options.protocolRewrite; 68 | } 69 | 70 | proxyRes.headers['location'] = u.format(); 71 | } 72 | }, 73 | /** 74 | * Copy headers from proxyResponse to response 75 | * set each header in response object. 76 | * 77 | * @param {ClientRequest} Req Request object 78 | * @param {IncomingMessage} Res Response object 79 | * @param {proxyResponse} Res Response object from the proxy request 80 | * 81 | * @api private 82 | */ 83 | function writeHeaders(req, res, proxyRes) { 84 | Object.keys(proxyRes.headers).forEach(function(key) { 85 | res.setHeader(key, proxyRes.headers[key]); 86 | }); 87 | }, 88 | 89 | /** 90 | * Set the statusCode from the proxyResponse 91 | * 92 | * @param {ClientRequest} Req Request object 93 | * @param {IncomingMessage} Res Response object 94 | * @param {proxyResponse} Res Response object from the proxy request 95 | * 96 | * @api private 97 | */ 98 | function writeStatusCode(req, res, proxyRes) { 99 | res.writeHead(proxyRes.statusCode); 100 | } 101 | 102 | ] // <-- 103 | .forEach(function(func) { 104 | passes[func.name] = func; 105 | }); 106 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/lib/http-proxy/passes/ws-incoming.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | https = require('https'), 3 | common = require('../common'), 4 | passes = exports; 5 | 6 | /*! 7 | * Array of passes. 8 | * 9 | * A `pass` is just a function that is executed on `req, socket, options` 10 | * so that you can easily add new checks while still keeping the base 11 | * flexible. 12 | */ 13 | 14 | /* 15 | * Websockets Passes 16 | * 17 | */ 18 | 19 | var passes = exports; 20 | 21 | [ 22 | /** 23 | * WebSocket requests must have the `GET` method and 24 | * the `upgrade:websocket` header 25 | * 26 | * @param {ClientRequest} Req Request object 27 | * @param {Socket} Websocket 28 | * 29 | * @api private 30 | */ 31 | 32 | function checkMethodAndHeader (req, socket) { 33 | if (req.method !== 'GET' || !req.headers.upgrade) { 34 | socket.destroy(); 35 | return true; 36 | } 37 | 38 | if (req.headers.upgrade.toLowerCase() !== 'websocket') { 39 | socket.destroy(); 40 | return true; 41 | } 42 | }, 43 | 44 | /** 45 | * Sets `x-forwarded-*` headers if specified in config. 46 | * 47 | * @param {ClientRequest} Req Request object 48 | * @param {Socket} Websocket 49 | * @param {Object} Options Config object passed to the proxy 50 | * 51 | * @api private 52 | */ 53 | 54 | function XHeaders(req, socket, options) { 55 | if(!options.xfwd) return; 56 | 57 | var values = { 58 | for : req.connection.remoteAddress || req.socket.remoteAddress, 59 | port : common.getPort(req), 60 | proto: common.hasEncryptedConnection(req) ? 'wss' : 'ws' 61 | }; 62 | 63 | ['for', 'port', 'proto'].forEach(function(header) { 64 | req.headers['x-forwarded-' + header] = 65 | (req.headers['x-forwarded-' + header] || '') + 66 | (req.headers['x-forwarded-' + header] ? ',' : '') + 67 | values[header]; 68 | }); 69 | }, 70 | 71 | /** 72 | * Does the actual proxying. Make the request and upgrade it 73 | * send the Switching Protocols request and pipe the sockets. 74 | * 75 | * @param {ClientRequest} Req Request object 76 | * @param {Socket} Websocket 77 | * @param {Object} Options Config object passed to the proxy 78 | * 79 | * @api private 80 | */ 81 | function stream(req, socket, options, head, server, clb) { 82 | common.setupSocket(socket); 83 | 84 | if (head && head.length) socket.unshift(head); 85 | 86 | 87 | var proxyReq = (common.isSSL.test(options.target.protocol) ? https : http).request( 88 | common.setupOutgoing(options.ssl || {}, options, req) 89 | ); 90 | // Error Handler 91 | proxyReq.on('error', onOutgoingError); 92 | proxyReq.on('response', function (res) { 93 | // if upgrade event isn't going to happen, close the socket 94 | if (!res.upgrade) socket.end(); 95 | }); 96 | 97 | proxyReq.on('upgrade', function(proxyRes, proxySocket, proxyHead) { 98 | proxySocket.on('error', onOutgoingError); 99 | 100 | // Allow us to listen when the websocket has completed 101 | proxySocket.on('end', function () { 102 | server.emit('close', proxyRes, proxySocket, proxyHead); 103 | }); 104 | 105 | // The pipe below will end proxySocket if socket closes cleanly, but not 106 | // if it errors (eg, vanishes from the net and starts returning 107 | // EHOSTUNREACH). We need to do that explicitly. 108 | socket.on('error', function () { 109 | proxySocket.end(); 110 | }); 111 | 112 | common.setupSocket(proxySocket); 113 | 114 | if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead); 115 | 116 | socket.write('HTTP/1.1 101 Switching Protocols\r\n'); 117 | socket.write(Object.keys(proxyRes.headers).map(function(i) { 118 | return i + ": " + proxyRes.headers[i]; 119 | }).join('\r\n') + '\r\n\r\n'); 120 | proxySocket.pipe(socket).pipe(proxySocket); 121 | 122 | server.emit('open', proxySocket); 123 | server.emit('proxySocket', proxySocket); //DEPRECATED. 124 | }); 125 | 126 | return proxyReq.end(); // XXX: CHECK IF THIS IS THIS CORRECT 127 | 128 | function onOutgoingError(err) { 129 | if (clb) { 130 | clb(err, req, socket); 131 | } else { 132 | server.emit('error', err, req, socket); 133 | } 134 | socket.end(); 135 | } 136 | } 137 | 138 | ] // <-- 139 | .forEach(function(func) { 140 | passes[func.name] = func; 141 | }); 142 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/eventemitter3/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Arnout Kazemier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/eventemitter3/README.md: -------------------------------------------------------------------------------- 1 | # EventEmitter3 2 | 3 | [![Version npm](https://img.shields.io/npm/v/eventemitter3.svg?style=flat-square)](http://browsenpm.org/package/eventemitter3)[![Build Status](https://img.shields.io/travis/primus/eventemitter3/master.svg?style=flat-square)](https://travis-ci.org/primus/eventemitter3)[![Dependencies](https://img.shields.io/david/primus/eventemitter3.svg?style=flat-square)](https://david-dm.org/primus/eventemitter3)[![Coverage Status](https://img.shields.io/coveralls/primus/eventemitter3/master.svg?style=flat-square)](https://coveralls.io/r/primus/eventemitter3?branch=master)[![IRC channel](https://img.shields.io/badge/IRC-irc.freenode.net%23primus-00a8ff.svg?style=flat-square)](https://webchat.freenode.net/?channels=primus) 4 | 5 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/eventemitter3.svg)](https://saucelabs.com/u/eventemitter3) 6 | 7 | EventEmitter3 is a high performance EventEmitter. It has been micro-optimized 8 | for various of code paths making this, one of, if not the fastest EventEmitter 9 | available for Node.js and browsers. The module is API compatible with the 10 | EventEmitter that ships by default with Node.js but there are some slight 11 | differences: 12 | 13 | - Domain support has been removed. 14 | - We do not `throw` an error when you emit an `error` event and nobody is 15 | listening. 16 | - The `newListener` event is removed as the use-cases for this functionality are 17 | really just edge cases. 18 | - No `setMaxListeners` and it's pointless memory leak warnings. If you want to 19 | add `end` listeners you should be able to do that without modules complaining. 20 | - No `listenerCount` function. Use `EE.listeners(event).length` instead. 21 | - Support for custom context for events so there is no need to use `fn.bind`. 22 | - `listeners` method can do existence checking instead of returning only arrays. 23 | 24 | It's a drop in replacement for existing EventEmitters, but just faster. Free 25 | performance, who wouldn't want that? The EventEmitter is written in EcmaScript 3 26 | so it will work in the oldest browsers and node versions that you need to 27 | support. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ npm install --save eventemitter3 # npm 33 | $ component install primus/eventemitter3 # Component 34 | $ bower install eventemitter3 # Bower 35 | ``` 36 | 37 | ## Usage 38 | 39 | After installation the only thing you need to do is require the module: 40 | 41 | ```js 42 | var EventEmitter = require('eventemitter3'); 43 | ``` 44 | 45 | And you're ready to create your own EventEmitter instances. For the API 46 | documentation, please follow the official Node.js documentation: 47 | 48 | http://nodejs.org/api/events.html 49 | 50 | ### Contextual emits 51 | 52 | We've upgraded the API of the `EventEmitter.on`, `EventEmitter.once` and 53 | `EventEmitter.removeListener` to accept an extra argument which is the `context` 54 | or `this` value that should be set for the emitted events. This means you no 55 | longer have the overhead of an event that required `fn.bind` in order to get a 56 | custom `this` value. 57 | 58 | ```js 59 | var EE = new EventEmitter() 60 | , context = { foo: 'bar' }; 61 | 62 | function emitted() { 63 | console.log(this === context); // true 64 | } 65 | 66 | EE.once('event-name', emitted, context); 67 | EE.on('another-event', emitted, context); 68 | EE.removeListener('another-event', emitted, context); 69 | ``` 70 | 71 | ### Existence 72 | 73 | To check if there is already a listener for a given event you can supply the 74 | `listeners` method with an extra boolean argument. This will transform the 75 | output from an array, to a boolean value which indicates if there are listeners 76 | in place for the given event: 77 | 78 | ```js 79 | var EE = new EventEmitter(); 80 | EE.once('event-name', function () {}); 81 | EE.on('another-event', function () {}); 82 | 83 | EE.listeners('event-name', true); // returns true 84 | EE.listeners('unknown-name', true); // returns false 85 | ``` 86 | 87 | ## License 88 | 89 | [MIT](LICENSE) 90 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/eventemitter3/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 4 | // We store our EE objects in a plain object whose properties are event names. 5 | // If `Object.create(null)` is not supported we prefix the event names with a 6 | // `~` to make sure that the built-in object properties are not overridden or 7 | // used as an attack vector. 8 | // We also assume that `Object.create(null)` is available when the event name 9 | // is an ES6 Symbol. 10 | // 11 | var prefix = typeof Object.create !== 'function' ? '~' : false; 12 | 13 | /** 14 | * Representation of a single EventEmitter function. 15 | * 16 | * @param {Function} fn Event handler to be called. 17 | * @param {Mixed} context Context for function execution. 18 | * @param {Boolean} once Only emit once 19 | * @api private 20 | */ 21 | function EE(fn, context, once) { 22 | this.fn = fn; 23 | this.context = context; 24 | this.once = once || false; 25 | } 26 | 27 | /** 28 | * Minimal EventEmitter interface that is molded against the Node.js 29 | * EventEmitter interface. 30 | * 31 | * @constructor 32 | * @api public 33 | */ 34 | function EventEmitter() { /* Nothing to set */ } 35 | 36 | /** 37 | * Holds the assigned EventEmitters by name. 38 | * 39 | * @type {Object} 40 | * @private 41 | */ 42 | EventEmitter.prototype._events = undefined; 43 | 44 | /** 45 | * Return a list of assigned event listeners. 46 | * 47 | * @param {String} event The events that should be listed. 48 | * @param {Boolean} exists We only need to know if there are listeners. 49 | * @returns {Array|Boolean} 50 | * @api public 51 | */ 52 | EventEmitter.prototype.listeners = function listeners(event, exists) { 53 | var evt = prefix ? prefix + event : event 54 | , available = this._events && this._events[evt]; 55 | 56 | if (exists) return !!available; 57 | if (!available) return []; 58 | if (available.fn) return [available.fn]; 59 | 60 | for (var i = 0, l = available.length, ee = new Array(l); i < l; i++) { 61 | ee[i] = available[i].fn; 62 | } 63 | 64 | return ee; 65 | }; 66 | 67 | /** 68 | * Emit an event to all registered event listeners. 69 | * 70 | * @param {String} event The name of the event. 71 | * @returns {Boolean} Indication if we've emitted an event. 72 | * @api public 73 | */ 74 | EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { 75 | var evt = prefix ? prefix + event : event; 76 | 77 | if (!this._events || !this._events[evt]) return false; 78 | 79 | var listeners = this._events[evt] 80 | , len = arguments.length 81 | , args 82 | , i; 83 | 84 | if ('function' === typeof listeners.fn) { 85 | if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); 86 | 87 | switch (len) { 88 | case 1: return listeners.fn.call(listeners.context), true; 89 | case 2: return listeners.fn.call(listeners.context, a1), true; 90 | case 3: return listeners.fn.call(listeners.context, a1, a2), true; 91 | case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; 92 | case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; 93 | case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; 94 | } 95 | 96 | for (i = 1, args = new Array(len -1); i < len; i++) { 97 | args[i - 1] = arguments[i]; 98 | } 99 | 100 | listeners.fn.apply(listeners.context, args); 101 | } else { 102 | var length = listeners.length 103 | , j; 104 | 105 | for (i = 0; i < length; i++) { 106 | if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); 107 | 108 | switch (len) { 109 | case 1: listeners[i].fn.call(listeners[i].context); break; 110 | case 2: listeners[i].fn.call(listeners[i].context, a1); break; 111 | case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; 112 | default: 113 | if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { 114 | args[j - 1] = arguments[j]; 115 | } 116 | 117 | listeners[i].fn.apply(listeners[i].context, args); 118 | } 119 | } 120 | } 121 | 122 | return true; 123 | }; 124 | 125 | /** 126 | * Register a new EventListener for the given event. 127 | * 128 | * @param {String} event Name of the event. 129 | * @param {Functon} fn Callback function. 130 | * @param {Mixed} context The context of the function. 131 | * @api public 132 | */ 133 | EventEmitter.prototype.on = function on(event, fn, context) { 134 | var listener = new EE(fn, context || this) 135 | , evt = prefix ? prefix + event : event; 136 | 137 | if (!this._events) this._events = prefix ? {} : Object.create(null); 138 | if (!this._events[evt]) this._events[evt] = listener; 139 | else { 140 | if (!this._events[evt].fn) this._events[evt].push(listener); 141 | else this._events[evt] = [ 142 | this._events[evt], listener 143 | ]; 144 | } 145 | 146 | return this; 147 | }; 148 | 149 | /** 150 | * Add an EventListener that's only called once. 151 | * 152 | * @param {String} event Name of the event. 153 | * @param {Function} fn Callback function. 154 | * @param {Mixed} context The context of the function. 155 | * @api public 156 | */ 157 | EventEmitter.prototype.once = function once(event, fn, context) { 158 | var listener = new EE(fn, context || this, true) 159 | , evt = prefix ? prefix + event : event; 160 | 161 | if (!this._events) this._events = prefix ? {} : Object.create(null); 162 | if (!this._events[evt]) this._events[evt] = listener; 163 | else { 164 | if (!this._events[evt].fn) this._events[evt].push(listener); 165 | else this._events[evt] = [ 166 | this._events[evt], listener 167 | ]; 168 | } 169 | 170 | return this; 171 | }; 172 | 173 | /** 174 | * Remove event listeners. 175 | * 176 | * @param {String} event The event we want to remove. 177 | * @param {Function} fn The listener that we need to find. 178 | * @param {Mixed} context Only remove listeners matching this context. 179 | * @param {Boolean} once Only remove once listeners. 180 | * @api public 181 | */ 182 | EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { 183 | var evt = prefix ? prefix + event : event; 184 | 185 | if (!this._events || !this._events[evt]) return this; 186 | 187 | var listeners = this._events[evt] 188 | , events = []; 189 | 190 | if (fn) { 191 | if (listeners.fn) { 192 | if ( 193 | listeners.fn !== fn 194 | || (once && !listeners.once) 195 | || (context && listeners.context !== context) 196 | ) { 197 | events.push(listeners); 198 | } 199 | } else { 200 | for (var i = 0, length = listeners.length; i < length; i++) { 201 | if ( 202 | listeners[i].fn !== fn 203 | || (once && !listeners[i].once) 204 | || (context && listeners[i].context !== context) 205 | ) { 206 | events.push(listeners[i]); 207 | } 208 | } 209 | } 210 | } 211 | 212 | // 213 | // Reset the array, or remove it completely if we have no more listeners. 214 | // 215 | if (events.length) { 216 | this._events[evt] = events.length === 1 ? events[0] : events; 217 | } else { 218 | delete this._events[evt]; 219 | } 220 | 221 | return this; 222 | }; 223 | 224 | /** 225 | * Remove all listeners or only the listeners for the specified event. 226 | * 227 | * @param {String} event The event want to remove all listeners for. 228 | * @api public 229 | */ 230 | EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { 231 | if (!this._events) return this; 232 | 233 | if (event) delete this._events[prefix ? prefix + event : event]; 234 | else this._events = prefix ? {} : Object.create(null); 235 | 236 | return this; 237 | }; 238 | 239 | // 240 | // Alias methods names because people roll like that. 241 | // 242 | EventEmitter.prototype.off = EventEmitter.prototype.removeListener; 243 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 244 | 245 | // 246 | // This function doesn't apply anymore. 247 | // 248 | EventEmitter.prototype.setMaxListeners = function setMaxListeners() { 249 | return this; 250 | }; 251 | 252 | // 253 | // Expose the prefix. 254 | // 255 | EventEmitter.prefixed = prefix; 256 | 257 | // 258 | // Expose the module. 259 | // 260 | if ('undefined' !== typeof module) { 261 | module.exports = EventEmitter; 262 | } 263 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/eventemitter3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eventemitter3", 3 | "version": "1.1.1", 4 | "description": "EventEmitter3 focuses on performance while maintaining a Node.js AND browser compatible interface.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test-browser": "zuul --browser-name ${BROWSER_NAME} --browser-version ${BROWSER_VERSION} -- test.js", 8 | "test-node": "istanbul cover node_modules/.bin/_mocha --report lcovonly -- test.js", 9 | "coverage": "istanbul cover ./node_modules/.bin/_mocha -- test.js", 10 | "sync": "node versions.js", 11 | "test": "mocha test.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/primus/eventemitter3.git" 16 | }, 17 | "keywords": [ 18 | "EventEmitter", 19 | "EventEmitter2", 20 | "EventEmitter3", 21 | "Events", 22 | "addEventListener", 23 | "addListener", 24 | "emit", 25 | "emits", 26 | "emitter", 27 | "event", 28 | "once", 29 | "pub/sub", 30 | "publish", 31 | "reactor", 32 | "subscribe" 33 | ], 34 | "author": { 35 | "name": "Arnout Kazemier" 36 | }, 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/primus/eventemitter3/issues" 40 | }, 41 | "pre-commit": "sync, test", 42 | "devDependencies": { 43 | "assume": "1.2.x", 44 | "istanbul": "0.3.x", 45 | "mocha": "2.2.x", 46 | "pre-commit": "1.0.x", 47 | "zuul": "3.0.x" 48 | }, 49 | "gitHead": "91f571f40c918d71220747791c05ec33f3402a56", 50 | "homepage": "https://github.com/primus/eventemitter3#readme", 51 | "_id": "eventemitter3@1.1.1", 52 | "_shasum": "47786bdaa087caf7b1b75e73abc5c7d540158cd0", 53 | "_from": "eventemitter3@>=1.0.0 <2.0.0", 54 | "_npmVersion": "2.9.1", 55 | "_nodeVersion": "0.12.3", 56 | "_npmUser": { 57 | "name": "3rdeden", 58 | "email": "npm@3rd-Eden.com" 59 | }, 60 | "maintainers": [ 61 | { 62 | "name": "v1", 63 | "email": "npm@3rd-Eden.com" 64 | }, 65 | { 66 | "name": "3rdeden", 67 | "email": "npm@3rd-Eden.com" 68 | }, 69 | { 70 | "name": "lpinca", 71 | "email": "luigipinca@gmail.com" 72 | } 73 | ], 74 | "dist": { 75 | "shasum": "47786bdaa087caf7b1b75e73abc5c7d540158cd0", 76 | "tarball": "http://registry.npmjs.org/eventemitter3/-/eventemitter3-1.1.1.tgz" 77 | }, 78 | "directories": {}, 79 | "_resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.1.1.tgz", 80 | "readme": "ERROR: No README data found!" 81 | } 82 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/requires-port/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/requires-port/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "0.11" 5 | - "0.10" 6 | - "0.9" 7 | - "0.8" 8 | - "iojs-v1.1" 9 | - "iojs-v1.0" 10 | before_install: 11 | - "npm install -g npm@1.4.x" 12 | script: 13 | - "npm run test-travis" 14 | after_script: 15 | - "npm install coveralls@2.11.x && cat coverage/lcov.info | coveralls" 16 | matrix: 17 | fast_finish: true 18 | allow_failures: 19 | - node_js: "0.11" 20 | - node_js: "0.9" 21 | - node_js: "iojs-v1.1" 22 | - node_js: "iojs-v1.0" 23 | notifications: 24 | irc: 25 | channels: 26 | - "irc.freenode.org#unshift" 27 | on_success: change 28 | on_failure: change 29 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/requires-port/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Unshift.io, Arnout Kazemier, the Contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/requires-port/README.md: -------------------------------------------------------------------------------- 1 | # requires-port 2 | 3 | [![Made by unshift](https://img.shields.io/badge/made%20by-unshift-00ffcc.svg?style=flat-square)](http://unshift.io)[![Version npm](http://img.shields.io/npm/v/requires-port.svg?style=flat-square)](http://browsenpm.org/package/requires-port)[![Build Status](http://img.shields.io/travis/unshiftio/requires-port/master.svg?style=flat-square)](https://travis-ci.org/unshiftio/requires-port)[![Dependencies](https://img.shields.io/david/unshiftio/requires-port.svg?style=flat-square)](https://david-dm.org/unshiftio/requires-port)[![Coverage Status](http://img.shields.io/coveralls/unshiftio/requires-port/master.svg?style=flat-square)](https://coveralls.io/r/unshiftio/requires-port?branch=master)[![IRC channel](http://img.shields.io/badge/IRC-irc.freenode.net%23unshift-00a8ff.svg?style=flat-square)](http://webchat.freenode.net/?channels=unshift) 4 | 5 | The module name says it all, check if a protocol requires a given port. 6 | 7 | ## Installation 8 | 9 | This module is intended to be used with browserify or Node.js and is distributed 10 | in the public npm registry. To install it simply run the following command from 11 | your CLI: 12 | 13 | ```j 14 | npm install --save requires-port 15 | ``` 16 | 17 | ## Usage 18 | 19 | The module exports it self as function and requires 2 arguments: 20 | 21 | 1. The port number, can be a string or number. 22 | 2. Protocol, can be `http`, `http:` or even `https://yomoma.com`. We just split 23 | it at `:` and use the first result. We currently accept the following 24 | protocols: 25 | - `http` 26 | - `https` 27 | - `ws` 28 | - `wss` 29 | - `ftp` 30 | - `gopher` 31 | - `file` 32 | 33 | It returns a boolean that indicates if protocol requires this port to be added 34 | to your URL. 35 | 36 | ```js 37 | 'use strict'; 38 | 39 | var required = require('requires-port'); 40 | 41 | console.log(required('8080', 'http')) // true 42 | console.log(required('80', 'http')) // false 43 | ``` 44 | 45 | # License 46 | 47 | MIT 48 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/requires-port/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Check if we're required to add a port number. 5 | * 6 | * @see https://url.spec.whatwg.org/#default-port 7 | * @param {Number|String} port Port number we need to check 8 | * @param {String} protocol Protocol we need to check against. 9 | * @returns {Boolean} Is it a default port for the given protocol 10 | * @api private 11 | */ 12 | module.exports = function required(port, protocol) { 13 | protocol = protocol.split(':')[0]; 14 | port = +port; 15 | 16 | if (!port) return false; 17 | 18 | switch (protocol) { 19 | case 'http': 20 | case 'ws': 21 | return port !== 80; 22 | 23 | case 'https': 24 | case 'wss': 25 | return port !== 443; 26 | 27 | case 'ftp': 28 | return port !== 22; 29 | 30 | case 'gopher': 31 | return port !== 70; 32 | 33 | case 'file': 34 | return false; 35 | } 36 | 37 | return port !== 0; 38 | }; 39 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/requires-port/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "requires-port", 3 | "version": "0.0.1", 4 | "description": "Check if a protocol requires a certain port number to be added to an URL.", 5 | "main": "index.js", 6 | "scripts": { 7 | "100%": "istanbul check-coverage --statements 100 --functions 100 --lines 100 --branches 100", 8 | "test": "mocha test.js", 9 | "watch": "mocha --watch test.js", 10 | "coverage": "istanbul cover ./node_modules/.bin/_mocha -- test.js", 11 | "test-travis": "istanbul cover node_modules/.bin/_mocha --report lcovonly -- test.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/unshiftio/requires-port.git" 16 | }, 17 | "keywords": [ 18 | "port", 19 | "require", 20 | "http", 21 | "https", 22 | "ws", 23 | "wss", 24 | "gopher", 25 | "file", 26 | "ftp", 27 | "requires", 28 | "requried", 29 | "portnumber", 30 | "url", 31 | "parsing", 32 | "validation", 33 | "cows" 34 | ], 35 | "author": { 36 | "name": "Arnout Kazemier" 37 | }, 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/unshiftio/requires-port/issues" 41 | }, 42 | "homepage": "https://github.com/unshiftio/requires-port", 43 | "devDependencies": { 44 | "assume": "1.1.x", 45 | "istanbul": "0.3.x", 46 | "mocha": "2.1.x", 47 | "pre-commit": "1.0.x" 48 | }, 49 | "gitHead": "d6235df7aa7e8d08e9ac72c842e1e2c6c366376f", 50 | "_id": "requires-port@0.0.1", 51 | "_shasum": "4b4414411d9df7c855995dd899a8c78a2951c16d", 52 | "_from": "requires-port@>=0.0.0 <1.0.0", 53 | "_npmVersion": "2.9.1", 54 | "_nodeVersion": "0.12.3", 55 | "_npmUser": { 56 | "name": "3rdeden", 57 | "email": "npm@3rd-Eden.com" 58 | }, 59 | "maintainers": [ 60 | { 61 | "name": "v1", 62 | "email": "info@3rd-Eden.com" 63 | }, 64 | { 65 | "name": "3rdeden", 66 | "email": "npm@3rd-Eden.com" 67 | } 68 | ], 69 | "dist": { 70 | "shasum": "4b4414411d9df7c855995dd899a8c78a2951c16d", 71 | "tarball": "http://registry.npmjs.org/requires-port/-/requires-port-0.0.1.tgz" 72 | }, 73 | "directories": {}, 74 | "_resolved": "https://registry.npmjs.org/requires-port/-/requires-port-0.0.1.tgz", 75 | "readme": "ERROR: No README data found!" 76 | } 77 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/node_modules/requires-port/test.js: -------------------------------------------------------------------------------- 1 | describe('requires-port', function () { 2 | 'use strict'; 3 | 4 | var assume = require('assume') 5 | , required = require('./'); 6 | 7 | it('is exported as a function', function () { 8 | assume(required).is.a('function'); 9 | }); 10 | 11 | it('does not require empty ports', function () { 12 | assume(required('', 'http')).false(); 13 | assume(required('', 'wss')).false(); 14 | assume(required('', 'ws')).false(); 15 | assume(required('', 'cowsack')).false(); 16 | }); 17 | 18 | it('assumes true for unknown protocols',function () { 19 | assume(required('808', 'foo')).true(); 20 | assume(required('80', 'bar')).true(); 21 | }); 22 | 23 | it('never requires port numbers for file', function () { 24 | assume(required(8080, 'file')).false(); 25 | }); 26 | 27 | it('does not require port 80 for http', function () { 28 | assume(required('80', 'http')).false(); 29 | assume(required(80, 'http')).false(); 30 | assume(required(80, 'http://')).false(); 31 | assume(required(80, 'http://www.google.com')).false(); 32 | 33 | assume(required('8080', 'http')).true(); 34 | assume(required(8080, 'http')).true(); 35 | assume(required(8080, 'http://')).true(); 36 | assume(required(8080, 'http://www.google.com')).true(); 37 | }); 38 | 39 | it('does not require port 80 for ws', function () { 40 | assume(required('80', 'ws')).false(); 41 | assume(required(80, 'ws')).false(); 42 | assume(required(80, 'ws://')).false(); 43 | assume(required(80, 'ws://www.google.com')).false(); 44 | 45 | assume(required('8080', 'ws')).true(); 46 | assume(required(8080, 'ws')).true(); 47 | assume(required(8080, 'ws://')).true(); 48 | assume(required(8080, 'ws://www.google.com')).true(); 49 | }); 50 | 51 | it('does not require port 443 for https', function () { 52 | assume(required('443', 'https')).false(); 53 | assume(required(443, 'https')).false(); 54 | assume(required(443, 'https://')).false(); 55 | assume(required(443, 'https://www.google.com')).false(); 56 | 57 | assume(required('8080', 'https')).true(); 58 | assume(required(8080, 'https')).true(); 59 | assume(required(8080, 'https://')).true(); 60 | assume(required(8080, 'https://www.google.com')).true(); 61 | }); 62 | 63 | it('does not require port 443 for wss', function () { 64 | assume(required('443', 'wss')).false(); 65 | assume(required(443, 'wss')).false(); 66 | assume(required(443, 'wss://')).false(); 67 | assume(required(443, 'wss://www.google.com')).false(); 68 | 69 | assume(required('8080', 'wss')).true(); 70 | assume(required(8080, 'wss')).true(); 71 | assume(required(8080, 'wss://')).true(); 72 | assume(required(8080, 'wss://www.google.com')).true(); 73 | }); 74 | 75 | it('does not require port 22 for ftp', function () { 76 | assume(required('22', 'ftp')).false(); 77 | assume(required(22, 'ftp')).false(); 78 | assume(required(22, 'ftp://')).false(); 79 | assume(required(22, 'ftp://www.google.com')).false(); 80 | 81 | assume(required('8080', 'ftp')).true(); 82 | assume(required(8080, 'ftp')).true(); 83 | assume(required(8080, 'ftp://')).true(); 84 | assume(required(8080, 'ftp://www.google.com')).true(); 85 | }); 86 | 87 | it('does not require port 70 for gopher', function () { 88 | assume(required('70', 'gopher')).false(); 89 | assume(required(70, 'gopher')).false(); 90 | assume(required(70, 'gopher://')).false(); 91 | assume(required(70, 'gopher://www.google.com')).false(); 92 | 93 | assume(required('8080', 'gopher')).true(); 94 | assume(required(8080, 'gopher')).true(); 95 | assume(required(8080, 'gopher://')).true(); 96 | assume(required(8080, 'gopher://www.google.com')).true(); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /strong-resources/node_modules/http-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-proxy", 3 | "version": "1.11.1", 4 | "repository": { 5 | "type": "git", 6 | "url": "git+https://github.com/nodejitsu/node-http-proxy.git" 7 | }, 8 | "description": "HTTP proxying for the masses", 9 | "author": { 10 | "name": "Nodejitsu Inc.", 11 | "email": "info@nodejitsu.com" 12 | }, 13 | "maintainers": [ 14 | { 15 | "name": "indexzero", 16 | "email": "charlie.robbins@gmail.com" 17 | }, 18 | { 19 | "name": "cronopio", 20 | "email": "aristizabal.daniel@gmail.com" 21 | }, 22 | { 23 | "name": "yawnt", 24 | "email": "yawn.localhost@gmail.com" 25 | }, 26 | { 27 | "name": "jcrugzz", 28 | "email": "jcrugzz@gmail.com" 29 | } 30 | ], 31 | "main": "index.js", 32 | "dependencies": { 33 | "eventemitter3": "1.x.x", 34 | "requires-port": "0.x.x" 35 | }, 36 | "devDependencies": { 37 | "async": "*", 38 | "blanket": "*", 39 | "coveralls": "*", 40 | "dox": "*", 41 | "expect.js": "*", 42 | "mocha": "*", 43 | "mocha-lcov-reporter": "*", 44 | "semver": "^4.3.3", 45 | "socket.io": "*", 46 | "socket.io-client": "*", 47 | "ws": "~0.5.0" 48 | }, 49 | "scripts": { 50 | "coveralls": "mocha --require blanket --reporter mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js", 51 | "test": "mocha -R landing test/*-test.js", 52 | "test-cov": "mocha --require blanket -R html-cov > cov/coverage.html" 53 | }, 54 | "engines": { 55 | "node": ">=0.10.0" 56 | }, 57 | "license": "MIT", 58 | "gitHead": "7e6c66a7e485a6c0ec3a1c567bbe800fdc56c9fd", 59 | "bugs": { 60 | "url": "https://github.com/nodejitsu/node-http-proxy/issues" 61 | }, 62 | "homepage": "https://github.com/nodejitsu/node-http-proxy", 63 | "_id": "http-proxy@1.11.1", 64 | "_shasum": "71df55757e802d58ea810df2244019dda05ae85d", 65 | "_from": "http-proxy@*", 66 | "_npmVersion": "2.5.1", 67 | "_nodeVersion": "0.10.37", 68 | "_npmUser": { 69 | "name": "jcrugzz", 70 | "email": "jcrugzz@gmail.com" 71 | }, 72 | "dist": { 73 | "shasum": "71df55757e802d58ea810df2244019dda05ae85d", 74 | "tarball": "http://registry.npmjs.org/http-proxy/-/http-proxy-1.11.1.tgz" 75 | }, 76 | "directories": {}, 77 | "_resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.11.1.tgz", 78 | "readme": "ERROR: No README data found!" 79 | } 80 | -------------------------------------------------------------------------------- /strong-resources/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strong-proxy", 3 | "version": "1.0.0", 4 | "description": "Proxy to serve both PM and application on a single port", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node ." 9 | }, 10 | "author": "Sai Vennam", 11 | "license": "ISC", 12 | "dependencies": { 13 | "http-proxy": "^1.11.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /strong-resources/startup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 2 4 | node node_modules/strong-start/bin/sl-start.js ./ 5 | sleep 2 6 | node strong-resources/app.js --------------------------------------------------------------------------------