├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── client ├── errors │ ├── 404.html │ ├── 500.html │ └── 502.html ├── favicon.ico ├── images │ └── yeoman.png ├── index.html ├── lb-services.js ├── lbclient │ ├── .gitignore │ ├── boot │ │ └── replication.js │ ├── build.js │ ├── code.js │ ├── datasources.json │ ├── datasources.local.js │ ├── lbclient.js │ ├── model-config.json │ ├── models │ │ └── local-user.json │ └── package.json ├── robots.txt ├── scripts │ ├── app.js │ ├── controllers │ │ ├── home.js │ │ └── signin.js │ └── services │ │ └── services.js ├── styles │ ├── main.scss │ ├── mixins │ │ └── _breakpoints.scss │ ├── modules │ │ ├── _buttons.scss │ │ ├── _defaults.scss │ │ ├── _figures.scss │ │ ├── _forms.scss │ │ ├── _lists.scss │ │ ├── _misc.scss │ │ ├── _normalize.scss │ │ ├── _print.scss │ │ ├── _quotes.scss │ │ ├── _tables.scss │ │ ├── _typography.scss │ │ ├── _utils.scss │ │ └── _vars.scss │ └── partials │ │ ├── _apps.scss │ │ ├── _log-forms.scss │ │ └── _main.scss └── views │ ├── about.html │ ├── home.html │ └── signin.html ├── common └── models │ ├── user.json │ ├── userCredential.json │ └── userIdentity.json ├── global-config.js ├── newrelic.js ├── nginx.conf ├── nginx_ssl.conf ├── package.json └── server ├── boot ├── authentication.js ├── explorer.js ├── passport-init.js ├── rest-api.js ├── sockets.js └── static.js ├── config.json ├── datasources.json ├── lib └── utils.js ├── model-config.json ├── providers.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by http://gitignore.io 2 | 3 | ### Node ### 4 | lib-cov 5 | *.seed 6 | *.log 7 | *.csv 8 | *.dat 9 | *.out 10 | *.pid 11 | *.gz 12 | 13 | pids 14 | logs 15 | results 16 | 17 | npm-debug.log 18 | node_modules 19 | dist 20 | .tmp 21 | 22 | ### OSX ### 23 | .DS_Store 24 | .AppleDouble 25 | .LSOverride 26 | Icon 27 | 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear on external disk 33 | .Spotlight-V100 34 | .Trashes 35 | 36 | ### SublimeText ### 37 | # SublimeText project files 38 | *.sublime-workspace 39 | 40 | ### Linux ### 41 | .* 42 | !.gitignore 43 | !.git* 44 | !.travis.yml 45 | *~ 46 | 47 | 48 | ### Compass ### 49 | .sass-cache 50 | 51 | ### SASS ### 52 | ### SASS Ignores - "Sassy CSS" http://sass-lang.com/ 53 | *.sass-cache 54 | 55 | newrelic_agent.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_install: 5 | - export DISPLAY=:99.0 6 | - sh -e /etc/init.d/xvfb start 7 | - gem install sass 8 | - gem install compass 9 | before_script: 10 | - npm install -g bower grunt-cli karma 11 | - bower install 12 | script: grunt 13 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2014-07-04 using generator-angular 0.9.2 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | var buildClientBundle = require('./client/lbclient/build'); 10 | 11 | module.exports = function (grunt) { 12 | 13 | // Load grunt tasks automatically 14 | require('load-grunt-tasks')(grunt); 15 | 16 | // Time how long tasks take. Can help when optimizing build times 17 | require('time-grunt')(grunt); 18 | 19 | // Define the configuration for all the tasks 20 | grunt.initConfig({ 21 | 22 | // Watches files for changes and runs tasks based on the changed files 23 | watch: { 24 | bower: { 25 | files: ['bower.json'], 26 | tasks: ['wiredep'] 27 | }, 28 | jsWebsite: { 29 | files: ['client/scripts/{,*/}*.js'], 30 | tasks: ['newer:jshint:website'], 31 | options: { 32 | livereload: { 33 | port: 35729, 34 | // key: grunt.file.read('server.key'), 35 | // cert: grunt.file.read('server.pem'), 36 | // ca: grunt.file.read('gd_bundle.crt') 37 | } 38 | } 39 | }, 40 | jsTestWebsite: { 41 | files: ['client/test/spec/{,*/}*.js'], 42 | tasks: ['newer:jshint:website', 'karma:website'] 43 | }, 44 | compassWebsite: { 45 | files: ['client/styles/{,*/}*.{scss,sass}'], 46 | tasks: ['compass:website', 'autoprefixer:website'] 47 | }, 48 | lbclient: { 49 | files: [ 50 | 'client/lbclient/models/*', 51 | 'client/lbclient/app*', 52 | 'client/lbclient/datasources*', 53 | 'client/lbclient/models*', 54 | 'client/lbclient/build.js' 55 | ], 56 | tasks: ['build-lbclient'], 57 | options: { 58 | livereload: { 59 | port: 35729, 60 | // key: grunt.file.read('server.key'), 61 | // cert: grunt.file.read('server.pem'), 62 | // ca: grunt.file.read('gd_bundle.crt') 63 | } 64 | } 65 | }, 66 | gruntfile: { 67 | files: ['Gruntfile.js'] 68 | }, 69 | server: { 70 | files: ['server/{,*/}*.{js,json}', 'common/{,*/}*.{js,json}'], 71 | tasks: [ 72 | 'loopback_sdk_angular', 73 | //'docular' 74 | ] 75 | }, 76 | livereload: { 77 | options: { 78 | livereload: { 79 | port: 35729, 80 | // key: grunt.file.read('server.key'), 81 | // cert: grunt.file.read('server.pem'), 82 | // ca: grunt.file.read('gd_bundle.crt') 83 | } 84 | }, 85 | files: [ 86 | 'client/{,*/}*.html', 87 | 'client/.tmp/styles/{,*/}*.css', 88 | 'client/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 89 | 'client/lbclient/browser.bundle.js' 90 | ] 91 | } 92 | }, 93 | 94 | // Make sure code styles are up to par and there are no obvious mistakes 95 | jshint: { 96 | options: { 97 | jshintrc: '.jshintrc', 98 | reporter: require('jshint-stylish') 99 | }, 100 | website: { 101 | src: [ 102 | 'Gruntfile.js', 103 | 'client/scripts/{,*/}*.js' 104 | ] 105 | }, 106 | test: { 107 | options: { 108 | jshintrc: 'test/.jshintrc' 109 | }, 110 | src: ['test/spec/{,*/}*.js'] 111 | } 112 | }, 113 | 114 | // Empties folders to start fresh 115 | clean: { 116 | dist: { 117 | files: [{ 118 | dot: true, 119 | src: [ 120 | '.tmp', 121 | 'client/.tmp', 122 | 'dist', 123 | 'client/lbclient/browser.bundle.js' 124 | ] 125 | }] 126 | }, 127 | server: ['client/.tmp'] 128 | }, 129 | 130 | // Add vendor prefixed styles 131 | autoprefixer: { 132 | options: { 133 | browsers: ['last 1 version'] 134 | }, 135 | website: { 136 | files: [{ 137 | expand: true, 138 | cwd: 'client/.tmp/styles/', 139 | src: '{,*/}*.css', 140 | dest: 'client/.tmp/styles/' 141 | }] 142 | } 143 | }, 144 | 145 | // Automatically inject Bower components into the app 146 | wiredep: { 147 | options: { 148 | cwd: '.' 149 | }, 150 | website: { 151 | src: ['client/index.html'], 152 | ignorePath: /\.\.\//, 153 | fileTypes: { 154 | html: { 155 | replace: { 156 | js: '', 157 | css: '' 158 | } 159 | } 160 | }, 161 | exclude: [ 162 | 'bower_components/es5-shim/es5-shim.js', 163 | 'bower_components/json3/lib/json3.js', 164 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/affix.js', 165 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/alert.js', 166 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/button.js', 167 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/carousel.js', 168 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/collapse.js', 169 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/tab.js', 170 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/transition.js', 171 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/scrollspy.js', 172 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/modal.js', 173 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/tooltip.js', 174 | 'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/popover.js', 175 | 'bower_components/angular-route/angular-route.js', 176 | 'bower_components/angulartics/src/angulartics-adobe.js', 177 | 'bower_components/angulartics/src/angulartics-chartbeat.js', 178 | 'bower_components/angulartics/src/angulartics-flurry.js', 179 | 'bower_components/angulartics/src/angulartics-ga-cordova.js', 180 | 'bower_components/angulartics/src/angulartics-gtm.js', 181 | 'bower_components/angulartics/src/angulartics-kissmetrics.js', 182 | 'bower_components/angulartics/src/angulartics-mixpanel.js', 183 | 'bower_components/angulartics/src/angulartics-piwik.js', 184 | 'bower_components/angulartics/src/angulartics-scroll.js', 185 | 'bower_components/angulartics/src/angulartics-segmentio.js', 186 | 'bower_components/angulartics/src/angulartics-splunk.js', 187 | 'bower_components/angulartics/src/angulartics-woopra.js', 188 | 'bower_components/angulartics/src/angulartics-cnzz.js', 189 | 'bower_components/angulartics/src/angulartics-marketo.js', 190 | 'bower_components/angulartics/src/angulartics-intercom.js', 191 | 'bower_components/jquery-waypoints/waypoints.js', 192 | 'bower_components/blueimp', 193 | 'bower_components/jquery-file-upload/', 194 | 'bower_components/moment/', 195 | 'bower_components/cryptojslib/', 196 | 'bower_components/imagesloaded/', 197 | 'bower_components/eventEmitter/', 198 | 'bower_components/eventie/', 199 | 'bower_components/angular-carousel/', 200 | 'bower_components/angular-modal-service/' 201 | ], 202 | }, 203 | sassWebsite: { 204 | src: ['client/styles/{,*/}*.{scss,sass}'], 205 | ignorePath: /(\.\.\/){1,2}bower_components\// 206 | } 207 | }, 208 | 209 | // Compiles Sass to CSS and generates necessary files if requested 210 | compass: { 211 | website: { 212 | options: { 213 | sassDir: 'client/styles', 214 | cssDir: 'client/.tmp/styles', 215 | generatedImagesDir: 'client/.tmp/images/generated', 216 | imagesDir: 'client/images', 217 | javascriptsDir: 'client/scripts', 218 | fontsDir: 'client/fonts', 219 | importPath: 'bower_components', 220 | httpImagesPath: 'images', 221 | httpGeneratedImagesPath: 'client/images/generated', 222 | httpFontsPath: 'fonts', 223 | relativeAssets: false, 224 | assetCacheBuster: false, 225 | raw: 'Sass::Script::Number.precision = 10\n' 226 | } 227 | }, 228 | dist: { 229 | options: { 230 | generatedImagesDir: 'client/website/images/generated' 231 | } 232 | }, 233 | server: { 234 | options: { 235 | debugInfo: true 236 | } 237 | } 238 | }, 239 | 240 | loopback_sdk_angular: { 241 | services: { 242 | options: { 243 | input: 'server/server.js', 244 | output: 'client/lb-services.js', 245 | apiUrl: '/api' 246 | } 247 | } 248 | }, 249 | docular: { 250 | groups: [ 251 | { 252 | groupTitle: 'LoopBack', 253 | groupId: 'loopback', 254 | sections: [ 255 | { 256 | id: 'lbServices', 257 | title: 'LoopBack Services', 258 | scripts: [ 'client/lb-services.js' ] 259 | } 260 | ] 261 | } 262 | ] 263 | }, 264 | 265 | // Renames files for browser caching purposes 266 | filerev: { 267 | website: { 268 | src: [ 269 | 'dist/scripts/{,*/}*.js', 270 | 'dist/styles/{,*/}*.css', 271 | 'dist/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 272 | 'dist/styles/fonts/*' 273 | ] 274 | } 275 | }, 276 | 277 | // Reads HTML for usemin blocks to enable smart builds that automatically 278 | // concat, minify and revision files. Creates configurations in memory so 279 | // additional tasks can operate on them 280 | useminPrepare: { 281 | website: { 282 | options: { 283 | dest: 'dist' 284 | }, 285 | src: ['client/index.html'], 286 | } 287 | }, 288 | 289 | // Performs rewrites based on filerev and the useminPrepare configuration 290 | usemin: { 291 | 'website-html': { 292 | options: { 293 | assetsDirs: ['dist','dist/images'], 294 | type:'html' 295 | }, 296 | files: { src: ['dist/*.html'] } 297 | }, 298 | 'website-css': { 299 | options: { 300 | assetsDirs: ['dist','dist/images'], 301 | type:'css' 302 | }, 303 | files: { src: ['dist/styles/{,*/}*.css'] } 304 | } 305 | }, 306 | 307 | imagemin: { 308 | dist: { 309 | files: [{ 310 | expand: true, 311 | cwd: 'client/images', 312 | src: '{,*/}*.{png,jpg,jpeg,gif}', 313 | dest: 'dist/images' 314 | }] 315 | } 316 | }, 317 | 318 | svgmin: { 319 | dist: { 320 | files: [{ 321 | expand: true, 322 | cwd: 'client/images', 323 | src: '{,*/}*.svg', 324 | dest: 'dist/images' 325 | }] 326 | } 327 | }, 328 | 329 | htmlmin: { 330 | dist: { 331 | options: { 332 | collapseWhitespace: true, 333 | conservativeCollapse: true, 334 | collapseBooleanAttributes: true, 335 | removeCommentsFromCDATA: true, 336 | removeOptionalTags: true 337 | }, 338 | files: [{ 339 | expand: true, 340 | cwd: 'dist', 341 | src: ['*.html', 'views/{,*/}*.html'], 342 | dest: 'dist' 343 | }] 344 | } 345 | }, 346 | 347 | ngAnnotate: { 348 | options: { 349 | singleQuotes: true, 350 | }, 351 | dist: { 352 | files: [{ 353 | expand: true, 354 | cwd: '.tmp/concat/scripts', 355 | src: '*.js', 356 | dest: '.tmp/concat/scripts' 357 | }] 358 | } 359 | }, 360 | 361 | i18nextract: { 362 | website: { 363 | src: [ 'client/scripts/**/*.js', 'client/*.html', 'client/views/{,*/}*.html' ], 364 | lang: ['en'], 365 | dest: 'client/locales' 366 | } 367 | }, 368 | 369 | // Replace Google CDN references 370 | cdnify: { 371 | dist: { 372 | html: ['dist/*.html'] 373 | } 374 | }, 375 | 376 | // Copies remaining files to places other tasks can use 377 | copy: { 378 | dist: { 379 | files: [{ 380 | expand: true, 381 | dot: true, 382 | cwd: 'client', 383 | dest: 'dist', 384 | src: [ 385 | '*.{ico,png,txt}', 386 | '.htaccess', 387 | '*.html', 388 | 'views/{,*/}*.html', 389 | 'images/{,*/}*.{webp}', 390 | 'fonts/*' 391 | ] 392 | }, { 393 | expand: true, 394 | cwd: 'client/.tmp/images', 395 | dest: 'dist/images', 396 | src: ['generated/*'] 397 | }, { 398 | expand: true, 399 | cwd: 'client/locales/', 400 | src: '**', 401 | dest: 'dist/locales/' 402 | }] 403 | }, 404 | styles: { 405 | expand: true, 406 | cwd: 'client/styles', 407 | dest: 'client/.tmp/styles/', 408 | src: '{,*/}*.css' 409 | } 410 | }, 411 | 412 | // Run some tasks in parallel to speed up the build process 413 | concurrent: { 414 | nodemon_dev: { 415 | options: { 416 | logConcurrentOutput: true, 417 | }, 418 | tasks: [ 419 | //'compass', 420 | 'nodemon:dev', 421 | 'watch' 422 | ] 423 | }, 424 | nodemon_prod: { 425 | options: { 426 | logConcurrentOutput: true, 427 | }, 428 | tasks: [ 429 | 'nodemon:prod', 430 | 'watch' 431 | ] 432 | }, 433 | server: [ 434 | 'compass:server' 435 | ], 436 | dist: [ 437 | 'compass', 438 | 'imagemin', 439 | 'svgmin' 440 | ], 441 | test: [ 442 | 'compass' 443 | ] 444 | }, 445 | 446 | nodemon: { 447 | dev: { 448 | script: 'server/server.js', 449 | options: { 450 | args: ['development'], 451 | watch: ['server', 'common'], 452 | ignore: ['node_modules/**'], 453 | debug: false, 454 | delayTime: 1, 455 | env: { 456 | NODE_ENV: 'development', 457 | PORT: 50447, 458 | PORT_SSL: 50337 459 | }, 460 | cwd: __dirname 461 | } 462 | }, 463 | prod: { 464 | script: 'server/server.js', 465 | options: { 466 | args: ['production'], 467 | watch: ['server', 'common'], 468 | ignore: ['node_modules/**'], 469 | debug: false, 470 | delayTime: 1, 471 | env: { 472 | NODE_ENV: 'production', 473 | PORT: 50447, 474 | PORT_SSL: 50337 475 | }, 476 | cwd: __dirname 477 | } 478 | } 479 | }, 480 | 481 | // Test settings 482 | karma: { 483 | website: { 484 | configFile: 'website/test/karma.conf.js', 485 | singleRun: true 486 | } 487 | } 488 | }); 489 | 490 | grunt.registerTask('build-lbclient', 'Build lbclient browser bundle', function() { 491 | var done = this.async(); 492 | buildClientBundle(process.env.NODE_ENV || 'development', done); 493 | }); 494 | 495 | grunt.registerTask('buildClients', [ 496 | 'i18nextract', 497 | 'clean:dist', 498 | 'wiredep', 499 | 'useminPrepare', 500 | 'concurrent:dist', 501 | 'autoprefixer', 502 | 'concat', 503 | 'ngAnnotate', 504 | 'copy:dist', 505 | 'cdnify', 506 | 'cssmin', 507 | 'uglify', 508 | 'filerev', 509 | 'usemin', 510 | 'htmlmin' 511 | ]); 512 | 513 | grunt.registerTask('dev', [ 514 | //'loopback_sdk_angular', 515 | //'build-lbclient', 516 | 'wiredep', 517 | 'autoprefixer', 518 | 'concurrent:nodemon_dev' 519 | ]); 520 | 521 | grunt.registerTask('prod', [ 522 | //'build-lbclient', 523 | //'docular', 524 | 'concurrent:nodemon_prod' 525 | ]); 526 | 527 | grunt.registerTask('test', [ 528 | 'testWebsite' 529 | ]); 530 | 531 | grunt.registerTask('build', [ 532 | //'build-lbclient', 533 | 'loopback_sdk_angular', 534 | 'buildClients' 535 | ]); 536 | }; 537 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 João Ribeiro 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bootstrap-loopback-fullstack 2 | ============================ 3 | 4 | Project bootstrap for loopback backend project 5 | 6 | 7 | Installation 8 | ============ 9 | - Download and extract 10 | 11 | - npm install 12 | 13 | - bower install 14 | 15 | 16 | Config 17 | ====== 18 | 19 | - Set PROJECT-NAME in ./package.json and ./bower.js 20 | 21 | - Set PROJECT-NAME and LICENSE-KEY in ./newrelic.js 22 | 23 | - Set MONGODB-SERVER in ./server/datasources.json 24 | 25 | - Set API_URL in ./Gruntfile.js 26 | 27 | - Set api keys in ./server/providers.json 28 | 29 | 30 | Extras 31 | ====== 32 | 33 | You can use ./nginx.conf as a starting point for nginx configuration. 34 | Edit all APP_ROOT_FOLDER, SEO4AJAX_API_KEY (some might be repeated, make sure you edit all of them) 35 | Copy ./nginx.conf to /etc/nginx/sites-available/ 36 | 37 | Alternatively, if you want ssl configuration do the above steps with nging_ssl.conf and place your certificate files in: 38 | 39 | - /opt/nginx/ssl_certs/server.pem 40 | - /opt/nginx/ssl_certs/server.key 41 | - /opt/nginx/ssl_certs/dhparam.pem 42 | - /opt/nginx/ssl_certs/godaddy_ssl_trusted_certificate.pem 43 | 44 | (this configuration will grant you A+ grade security) -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PROJECT-NAME", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "latest", 6 | "json3": "latest", 7 | "es5-shim": "latest", 8 | "jquery": "latest", 9 | "bootstrap-sass-official": "latest", 10 | "angular-resource": "latest", 11 | "angular-cookies": "latest", 12 | "angular-sanitize": "latest", 13 | "angular-animate": "latest", 14 | "angular-touch": "latest", 15 | "angular-translate": "latest", 16 | "angular-translate-loader-static-files": "latest", 17 | "angular-translate-storage-local": "latest", 18 | "angular-ui-router": "latest", 19 | "angulartics": "latest", 20 | "fontawesome": "latest", 21 | "socket.io-client": "latest", 22 | "visibilityjs": "latest" 23 | }, 24 | "devDependencies": { 25 | "angular-mocks": "latest", 26 | "angular-scenario": "latest" 27 | }, 28 | "appPath": "app" 29 | } 30 | -------------------------------------------------------------------------------- /client/errors/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /client/errors/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /client/errors/502.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonnyBGod/bootstrap-loopback-fullstack/f1a8bf3ecececf6361e8e90295ea845ab7ffc1b0/client/favicon.ico -------------------------------------------------------------------------------- /client/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonnyBGod/bootstrap-loopback-fullstack/f1a8bf3ecececf6361e8e90295ea845ab7ffc1b0/client/images/yeoman.png -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 |
26 | 27 | 28 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /client/lb-services.js: -------------------------------------------------------------------------------- 1 | (function(window, angular, undefined) {'use strict'; 2 | 3 | var urlBase = "API_URL"; 4 | var authHeader = 'authorization'; 5 | 6 | /** 7 | * @ngdoc overview 8 | * @name lbServices 9 | * @module 10 | * @description 11 | * 12 | * The `lbServices` module provides services for interacting with 13 | * the models exposed by the LoopBack server via the REST API. 14 | * 15 | */ 16 | var module = angular.module("lbServices", ['ngResource']); 17 | 18 | /** 19 | * @ngdoc object 20 | * @name lbServices.User 21 | * @header lbServices.User 22 | * @object 23 | * 24 | * @description 25 | * 26 | * A $resource object for interacting with the `User` model. 27 | * 28 | * ## Example 29 | * 30 | * See 31 | * {@link http://docs.angularjs.org/api/ngResource.$resource#example $resource} 32 | * for an example of using this object. 33 | * 34 | */ 35 | module.factory( 36 | "User", 37 | ['LoopBackResource', 'LoopBackAuth', '$injector', function(Resource, LoopBackAuth, $injector) { 38 | var R = Resource( 39 | urlBase + "/users/:id", 40 | { 'id': '@id' }, 41 | { 42 | 43 | /** 44 | * @ngdoc method 45 | * @name lbServices.User#login 46 | * @methodOf lbServices.User 47 | * 48 | * @description 49 | * 50 | * Login a user with username/email and password 51 | * 52 | * @param {Object=} parameters Request parameters. 53 | * 54 | * - `include` – `{string=}` - Related objects to include in the response. See the description of return value for more details. 55 | * Default value: `user`. 56 | * 57 | * - `rememberMe` - `boolean` - Whether the authentication credentials 58 | * should be remembered in localStorage across app/browser restarts. 59 | * Default: `true`. 60 | * 61 | * @param {Object} postData Request data. 62 | * 63 | * This method expects a subset of model properties as request parameters. 64 | * 65 | * @param {function(Object,Object)=} successCb 66 | * Success callback with two arguments: `value`, `responseHeaders`. 67 | * 68 | * @param {function(Object)=} errorCb Error callback with one argument: 69 | * `httpResponse`. 70 | * 71 | * @returns {Object} An empty reference that will be 72 | * populated with the actual data once the response is returned 73 | * from the server. 74 | * 75 | * The response body contains properties of the AccessToken created on login. 76 | * Depending on the value of `include` parameter, the body may contain additional properties: 77 | * 78 | * - `user` - `{User}` - Data of the currently logged in user. (`include=user`) 79 | * 80 | * 81 | */ 82 | "login": { 83 | params: { 84 | include: "user" 85 | }, 86 | interceptor: { 87 | response: function(response) { 88 | var accessToken = response.data; 89 | LoopBackAuth.setUser(accessToken.id, accessToken.userId, accessToken.user); 90 | LoopBackAuth.rememberMe = response.config.params.rememberMe !== false; 91 | LoopBackAuth.save(); 92 | return response.resource; 93 | } 94 | }, 95 | url: urlBase + "/users/login", 96 | method: "POST" 97 | }, 98 | 99 | /** 100 | * @ngdoc method 101 | * @name lbServices.User#logout 102 | * @methodOf lbServices.User 103 | * 104 | * @description 105 | * 106 | * Logout a user with access token 107 | * 108 | * @param {Object=} parameters Request parameters. 109 | * 110 | * This method does not accept any parameters. 111 | * Supply an empty object or omit this argument altogether. 112 | * 113 | * @param {Object} postData Request data. 114 | * 115 | * - `access_token` – `{string}` - Do not supply this argument, it is automatically extracted from request headers. 116 | * 117 | * @param {function(Object,Object)=} successCb 118 | * Success callback with two arguments: `value`, `responseHeaders`. 119 | * 120 | * @param {function(Object)=} errorCb Error callback with one argument: 121 | * `httpResponse`. 122 | * 123 | * @returns {Object} An empty reference that will be 124 | * populated with the actual data once the response is returned 125 | * from the server. 126 | * 127 | * This method returns no data. 128 | */ 129 | "logout": { 130 | interceptor: { 131 | response: function(response) { 132 | LoopBackAuth.clearUser(); 133 | LoopBackAuth.clearStorage(); 134 | return response.resource; 135 | } 136 | }, 137 | url: urlBase + "/users/logout", 138 | method: "POST" 139 | }, 140 | 141 | /** 142 | * @ngdoc method 143 | * @name lbServices.User#confirm 144 | * @methodOf lbServices.User 145 | * 146 | * @description 147 | * 148 | * Confirm a user registration with email verification token 149 | * 150 | * @param {Object=} parameters Request parameters. 151 | * 152 | * - `uid` – `{string}` - 153 | * 154 | * - `token` – `{string}` - 155 | * 156 | * - `redirect` – `{string}` - 157 | * 158 | * @param {function(Object,Object)=} successCb 159 | * Success callback with two arguments: `value`, `responseHeaders`. 160 | * 161 | * @param {function(Object)=} errorCb Error callback with one argument: 162 | * `httpResponse`. 163 | * 164 | * @returns {Object} An empty reference that will be 165 | * populated with the actual data once the response is returned 166 | * from the server. 167 | * 168 | * This method returns no data. 169 | */ 170 | "confirm": { 171 | url: urlBase + "/users/confirm", 172 | method: "GET" 173 | }, 174 | 175 | /** 176 | * @ngdoc method 177 | * @name lbServices.User#resetPassword 178 | * @methodOf lbServices.User 179 | * 180 | * @description 181 | * 182 | * Reset password for a user with email 183 | * 184 | * @param {Object=} parameters Request parameters. 185 | * 186 | * This method does not accept any parameters. 187 | * Supply an empty object or omit this argument altogether. 188 | * 189 | * @param {Object} postData Request data. 190 | * 191 | * This method expects a subset of model properties as request parameters. 192 | * 193 | * @param {function(Object,Object)=} successCb 194 | * Success callback with two arguments: `value`, `responseHeaders`. 195 | * 196 | * @param {function(Object)=} errorCb Error callback with one argument: 197 | * `httpResponse`. 198 | * 199 | * @returns {Object} An empty reference that will be 200 | * populated with the actual data once the response is returned 201 | * from the server. 202 | * 203 | * This method returns no data. 204 | */ 205 | "resetPassword": { 206 | url: urlBase + "/users/reset", 207 | method: "POST" 208 | }, 209 | 210 | /** 211 | * @ngdoc method 212 | * @name lbServices.User#prototype$__findById__identities 213 | * @methodOf lbServices.User 214 | * 215 | * @description 216 | * 217 | * Find a related item by id for identities 218 | * 219 | * @param {Object=} parameters Request parameters. 220 | * 221 | * - `id` – `{*}` - User id 222 | * 223 | * - `fk` – `{*}` - Foreign key for identities 224 | * 225 | * @param {function(Object,Object)=} successCb 226 | * Success callback with two arguments: `value`, `responseHeaders`. 227 | * 228 | * @param {function(Object)=} errorCb Error callback with one argument: 229 | * `httpResponse`. 230 | * 231 | * @returns {Object} An empty reference that will be 232 | * populated with the actual data once the response is returned 233 | * from the server. 234 | * 235 | * 236 | * (The remote method definition does not provide any description. 237 | * This usually means the response is a `User` object.) 238 | * 239 | */ 240 | "prototype$__findById__identities": { 241 | url: urlBase + "/users/:id/identities/:fk", 242 | method: "GET" 243 | }, 244 | 245 | /** 246 | * @ngdoc method 247 | * @name lbServices.User#prototype$__destroyById__identities 248 | * @methodOf lbServices.User 249 | * 250 | * @description 251 | * 252 | * Delete a related item by id for identities 253 | * 254 | * @param {Object=} parameters Request parameters. 255 | * 256 | * - `id` – `{*}` - User id 257 | * 258 | * - `fk` – `{*}` - Foreign key for identities 259 | * 260 | * @param {function(Object,Object)=} successCb 261 | * Success callback with two arguments: `value`, `responseHeaders`. 262 | * 263 | * @param {function(Object)=} errorCb Error callback with one argument: 264 | * `httpResponse`. 265 | * 266 | * @returns {Object} An empty reference that will be 267 | * populated with the actual data once the response is returned 268 | * from the server. 269 | * 270 | * This method returns no data. 271 | */ 272 | "prototype$__destroyById__identities": { 273 | url: urlBase + "/users/:id/identities/:fk", 274 | method: "DELETE" 275 | }, 276 | 277 | /** 278 | * @ngdoc method 279 | * @name lbServices.User#prototype$__updateById__identities 280 | * @methodOf lbServices.User 281 | * 282 | * @description 283 | * 284 | * Update a related item by id for identities 285 | * 286 | * @param {Object=} parameters Request parameters. 287 | * 288 | * - `id` – `{*}` - User id 289 | * 290 | * - `fk` – `{*}` - Foreign key for identities 291 | * 292 | * @param {Object} postData Request data. 293 | * 294 | * This method expects a subset of model properties as request parameters. 295 | * 296 | * @param {function(Object,Object)=} successCb 297 | * Success callback with two arguments: `value`, `responseHeaders`. 298 | * 299 | * @param {function(Object)=} errorCb Error callback with one argument: 300 | * `httpResponse`. 301 | * 302 | * @returns {Object} An empty reference that will be 303 | * populated with the actual data once the response is returned 304 | * from the server. 305 | * 306 | * 307 | * (The remote method definition does not provide any description. 308 | * This usually means the response is a `User` object.) 309 | * 310 | */ 311 | "prototype$__updateById__identities": { 312 | url: urlBase + "/users/:id/identities/:fk", 313 | method: "PUT" 314 | }, 315 | 316 | /** 317 | * @ngdoc method 318 | * @name lbServices.User#prototype$__findById__credentials 319 | * @methodOf lbServices.User 320 | * 321 | * @description 322 | * 323 | * Find a related item by id for credentials 324 | * 325 | * @param {Object=} parameters Request parameters. 326 | * 327 | * - `id` – `{*}` - User id 328 | * 329 | * - `fk` – `{*}` - Foreign key for credentials 330 | * 331 | * @param {function(Object,Object)=} successCb 332 | * Success callback with two arguments: `value`, `responseHeaders`. 333 | * 334 | * @param {function(Object)=} errorCb Error callback with one argument: 335 | * `httpResponse`. 336 | * 337 | * @returns {Object} An empty reference that will be 338 | * populated with the actual data once the response is returned 339 | * from the server. 340 | * 341 | * 342 | * (The remote method definition does not provide any description. 343 | * This usually means the response is a `User` object.) 344 | * 345 | */ 346 | "prototype$__findById__credentials": { 347 | url: urlBase + "/users/:id/credentials/:fk", 348 | method: "GET" 349 | }, 350 | 351 | /** 352 | * @ngdoc method 353 | * @name lbServices.User#prototype$__destroyById__credentials 354 | * @methodOf lbServices.User 355 | * 356 | * @description 357 | * 358 | * Delete a related item by id for credentials 359 | * 360 | * @param {Object=} parameters Request parameters. 361 | * 362 | * - `id` – `{*}` - User id 363 | * 364 | * - `fk` – `{*}` - Foreign key for credentials 365 | * 366 | * @param {function(Object,Object)=} successCb 367 | * Success callback with two arguments: `value`, `responseHeaders`. 368 | * 369 | * @param {function(Object)=} errorCb Error callback with one argument: 370 | * `httpResponse`. 371 | * 372 | * @returns {Object} An empty reference that will be 373 | * populated with the actual data once the response is returned 374 | * from the server. 375 | * 376 | * This method returns no data. 377 | */ 378 | "prototype$__destroyById__credentials": { 379 | url: urlBase + "/users/:id/credentials/:fk", 380 | method: "DELETE" 381 | }, 382 | 383 | /** 384 | * @ngdoc method 385 | * @name lbServices.User#prototype$__updateById__credentials 386 | * @methodOf lbServices.User 387 | * 388 | * @description 389 | * 390 | * Update a related item by id for credentials 391 | * 392 | * @param {Object=} parameters Request parameters. 393 | * 394 | * - `id` – `{*}` - User id 395 | * 396 | * - `fk` – `{*}` - Foreign key for credentials 397 | * 398 | * @param {Object} postData Request data. 399 | * 400 | * This method expects a subset of model properties as request parameters. 401 | * 402 | * @param {function(Object,Object)=} successCb 403 | * Success callback with two arguments: `value`, `responseHeaders`. 404 | * 405 | * @param {function(Object)=} errorCb Error callback with one argument: 406 | * `httpResponse`. 407 | * 408 | * @returns {Object} An empty reference that will be 409 | * populated with the actual data once the response is returned 410 | * from the server. 411 | * 412 | * 413 | * (The remote method definition does not provide any description. 414 | * This usually means the response is a `User` object.) 415 | * 416 | */ 417 | "prototype$__updateById__credentials": { 418 | url: urlBase + "/users/:id/credentials/:fk", 419 | method: "PUT" 420 | }, 421 | 422 | /** 423 | * @ngdoc method 424 | * @name lbServices.User#prototype$__findById__accessTokens 425 | * @methodOf lbServices.User 426 | * 427 | * @description 428 | * 429 | * Find a related item by id for accessTokens 430 | * 431 | * @param {Object=} parameters Request parameters. 432 | * 433 | * - `id` – `{*}` - User id 434 | * 435 | * - `fk` – `{*}` - Foreign key for accessTokens 436 | * 437 | * @param {function(Object,Object)=} successCb 438 | * Success callback with two arguments: `value`, `responseHeaders`. 439 | * 440 | * @param {function(Object)=} errorCb Error callback with one argument: 441 | * `httpResponse`. 442 | * 443 | * @returns {Object} An empty reference that will be 444 | * populated with the actual data once the response is returned 445 | * from the server. 446 | * 447 | * 448 | * (The remote method definition does not provide any description. 449 | * This usually means the response is a `User` object.) 450 | * 451 | */ 452 | "prototype$__findById__accessTokens": { 453 | url: urlBase + "/users/:id/accessTokens/:fk", 454 | method: "GET" 455 | }, 456 | 457 | /** 458 | * @ngdoc method 459 | * @name lbServices.User#prototype$__destroyById__accessTokens 460 | * @methodOf lbServices.User 461 | * 462 | * @description 463 | * 464 | * Delete a related item by id for accessTokens 465 | * 466 | * @param {Object=} parameters Request parameters. 467 | * 468 | * - `id` – `{*}` - User id 469 | * 470 | * - `fk` – `{*}` - Foreign key for accessTokens 471 | * 472 | * @param {function(Object,Object)=} successCb 473 | * Success callback with two arguments: `value`, `responseHeaders`. 474 | * 475 | * @param {function(Object)=} errorCb Error callback with one argument: 476 | * `httpResponse`. 477 | * 478 | * @returns {Object} An empty reference that will be 479 | * populated with the actual data once the response is returned 480 | * from the server. 481 | * 482 | * This method returns no data. 483 | */ 484 | "prototype$__destroyById__accessTokens": { 485 | url: urlBase + "/users/:id/accessTokens/:fk", 486 | method: "DELETE" 487 | }, 488 | 489 | /** 490 | * @ngdoc method 491 | * @name lbServices.User#prototype$__updateById__accessTokens 492 | * @methodOf lbServices.User 493 | * 494 | * @description 495 | * 496 | * Update a related item by id for accessTokens 497 | * 498 | * @param {Object=} parameters Request parameters. 499 | * 500 | * - `id` – `{*}` - User id 501 | * 502 | * - `fk` – `{*}` - Foreign key for accessTokens 503 | * 504 | * @param {Object} postData Request data. 505 | * 506 | * This method expects a subset of model properties as request parameters. 507 | * 508 | * @param {function(Object,Object)=} successCb 509 | * Success callback with two arguments: `value`, `responseHeaders`. 510 | * 511 | * @param {function(Object)=} errorCb Error callback with one argument: 512 | * `httpResponse`. 513 | * 514 | * @returns {Object} An empty reference that will be 515 | * populated with the actual data once the response is returned 516 | * from the server. 517 | * 518 | * 519 | * (The remote method definition does not provide any description. 520 | * This usually means the response is a `User` object.) 521 | * 522 | */ 523 | "prototype$__updateById__accessTokens": { 524 | url: urlBase + "/users/:id/accessTokens/:fk", 525 | method: "PUT" 526 | }, 527 | 528 | /** 529 | * @ngdoc method 530 | * @name lbServices.User#prototype$__get__identities 531 | * @methodOf lbServices.User 532 | * 533 | * @description 534 | * 535 | * Queries identities of user. 536 | * 537 | * @param {Object=} parameters Request parameters. 538 | * 539 | * - `id` – `{*}` - User id 540 | * 541 | * - `filter` – `{object=}` - 542 | * 543 | * @param {function(Array.,Object)=} successCb 544 | * Success callback with two arguments: `value`, `responseHeaders`. 545 | * 546 | * @param {function(Object)=} errorCb Error callback with one argument: 547 | * `httpResponse`. 548 | * 549 | * @returns {Array.} An empty reference that will be 550 | * populated with the actual data once the response is returned 551 | * from the server. 552 | * 553 | * 554 | * (The remote method definition does not provide any description. 555 | * This usually means the response is a `User` object.) 556 | * 557 | */ 558 | "prototype$__get__identities": { 559 | isArray: true, 560 | url: urlBase + "/users/:id/identities", 561 | method: "GET" 562 | }, 563 | 564 | /** 565 | * @ngdoc method 566 | * @name lbServices.User#prototype$__create__identities 567 | * @methodOf lbServices.User 568 | * 569 | * @description 570 | * 571 | * Creates a new instance in identities of this model. 572 | * 573 | * @param {Object=} parameters Request parameters. 574 | * 575 | * - `id` – `{*}` - User id 576 | * 577 | * @param {Object} postData Request data. 578 | * 579 | * This method expects a subset of model properties as request parameters. 580 | * 581 | * @param {function(Object,Object)=} successCb 582 | * Success callback with two arguments: `value`, `responseHeaders`. 583 | * 584 | * @param {function(Object)=} errorCb Error callback with one argument: 585 | * `httpResponse`. 586 | * 587 | * @returns {Object} An empty reference that will be 588 | * populated with the actual data once the response is returned 589 | * from the server. 590 | * 591 | * 592 | * (The remote method definition does not provide any description. 593 | * This usually means the response is a `User` object.) 594 | * 595 | */ 596 | "prototype$__create__identities": { 597 | url: urlBase + "/users/:id/identities", 598 | method: "POST" 599 | }, 600 | 601 | /** 602 | * @ngdoc method 603 | * @name lbServices.User#prototype$__delete__identities 604 | * @methodOf lbServices.User 605 | * 606 | * @description 607 | * 608 | * Deletes all identities of this model. 609 | * 610 | * @param {Object=} parameters Request parameters. 611 | * 612 | * - `id` – `{*}` - User id 613 | * 614 | * @param {function(Object,Object)=} successCb 615 | * Success callback with two arguments: `value`, `responseHeaders`. 616 | * 617 | * @param {function(Object)=} errorCb Error callback with one argument: 618 | * `httpResponse`. 619 | * 620 | * @returns {Object} An empty reference that will be 621 | * populated with the actual data once the response is returned 622 | * from the server. 623 | * 624 | * This method returns no data. 625 | */ 626 | "prototype$__delete__identities": { 627 | url: urlBase + "/users/:id/identities", 628 | method: "DELETE" 629 | }, 630 | 631 | /** 632 | * @ngdoc method 633 | * @name lbServices.User#prototype$__count__identities 634 | * @methodOf lbServices.User 635 | * 636 | * @description 637 | * 638 | * Counts identities of user. 639 | * 640 | * @param {Object=} parameters Request parameters. 641 | * 642 | * - `id` – `{*}` - User id 643 | * 644 | * - `where` – `{object=}` - Criteria to match model instances 645 | * 646 | * @param {function(Object,Object)=} successCb 647 | * Success callback with two arguments: `value`, `responseHeaders`. 648 | * 649 | * @param {function(Object)=} errorCb Error callback with one argument: 650 | * `httpResponse`. 651 | * 652 | * @returns {Object} An empty reference that will be 653 | * populated with the actual data once the response is returned 654 | * from the server. 655 | * 656 | * Data properties: 657 | * 658 | * - `count` – `{number=}` - 659 | */ 660 | "prototype$__count__identities": { 661 | url: urlBase + "/users/:id/identities/count", 662 | method: "GET" 663 | }, 664 | 665 | /** 666 | * @ngdoc method 667 | * @name lbServices.User#prototype$__get__credentials 668 | * @methodOf lbServices.User 669 | * 670 | * @description 671 | * 672 | * Queries credentials of user. 673 | * 674 | * @param {Object=} parameters Request parameters. 675 | * 676 | * - `id` – `{*}` - User id 677 | * 678 | * - `filter` – `{object=}` - 679 | * 680 | * @param {function(Array.,Object)=} successCb 681 | * Success callback with two arguments: `value`, `responseHeaders`. 682 | * 683 | * @param {function(Object)=} errorCb Error callback with one argument: 684 | * `httpResponse`. 685 | * 686 | * @returns {Array.} An empty reference that will be 687 | * populated with the actual data once the response is returned 688 | * from the server. 689 | * 690 | * 691 | * (The remote method definition does not provide any description. 692 | * This usually means the response is a `User` object.) 693 | * 694 | */ 695 | "prototype$__get__credentials": { 696 | isArray: true, 697 | url: urlBase + "/users/:id/credentials", 698 | method: "GET" 699 | }, 700 | 701 | /** 702 | * @ngdoc method 703 | * @name lbServices.User#prototype$__create__credentials 704 | * @methodOf lbServices.User 705 | * 706 | * @description 707 | * 708 | * Creates a new instance in credentials of this model. 709 | * 710 | * @param {Object=} parameters Request parameters. 711 | * 712 | * - `id` – `{*}` - User id 713 | * 714 | * @param {Object} postData Request data. 715 | * 716 | * This method expects a subset of model properties as request parameters. 717 | * 718 | * @param {function(Object,Object)=} successCb 719 | * Success callback with two arguments: `value`, `responseHeaders`. 720 | * 721 | * @param {function(Object)=} errorCb Error callback with one argument: 722 | * `httpResponse`. 723 | * 724 | * @returns {Object} An empty reference that will be 725 | * populated with the actual data once the response is returned 726 | * from the server. 727 | * 728 | * 729 | * (The remote method definition does not provide any description. 730 | * This usually means the response is a `User` object.) 731 | * 732 | */ 733 | "prototype$__create__credentials": { 734 | url: urlBase + "/users/:id/credentials", 735 | method: "POST" 736 | }, 737 | 738 | /** 739 | * @ngdoc method 740 | * @name lbServices.User#prototype$__delete__credentials 741 | * @methodOf lbServices.User 742 | * 743 | * @description 744 | * 745 | * Deletes all credentials of this model. 746 | * 747 | * @param {Object=} parameters Request parameters. 748 | * 749 | * - `id` – `{*}` - User id 750 | * 751 | * @param {function(Object,Object)=} successCb 752 | * Success callback with two arguments: `value`, `responseHeaders`. 753 | * 754 | * @param {function(Object)=} errorCb Error callback with one argument: 755 | * `httpResponse`. 756 | * 757 | * @returns {Object} An empty reference that will be 758 | * populated with the actual data once the response is returned 759 | * from the server. 760 | * 761 | * This method returns no data. 762 | */ 763 | "prototype$__delete__credentials": { 764 | url: urlBase + "/users/:id/credentials", 765 | method: "DELETE" 766 | }, 767 | 768 | /** 769 | * @ngdoc method 770 | * @name lbServices.User#prototype$__count__credentials 771 | * @methodOf lbServices.User 772 | * 773 | * @description 774 | * 775 | * Counts credentials of user. 776 | * 777 | * @param {Object=} parameters Request parameters. 778 | * 779 | * - `id` – `{*}` - User id 780 | * 781 | * - `where` – `{object=}` - Criteria to match model instances 782 | * 783 | * @param {function(Object,Object)=} successCb 784 | * Success callback with two arguments: `value`, `responseHeaders`. 785 | * 786 | * @param {function(Object)=} errorCb Error callback with one argument: 787 | * `httpResponse`. 788 | * 789 | * @returns {Object} An empty reference that will be 790 | * populated with the actual data once the response is returned 791 | * from the server. 792 | * 793 | * Data properties: 794 | * 795 | * - `count` – `{number=}` - 796 | */ 797 | "prototype$__count__credentials": { 798 | url: urlBase + "/users/:id/credentials/count", 799 | method: "GET" 800 | }, 801 | 802 | /** 803 | * @ngdoc method 804 | * @name lbServices.User#prototype$__get__accessTokens 805 | * @methodOf lbServices.User 806 | * 807 | * @description 808 | * 809 | * Queries accessTokens of user. 810 | * 811 | * @param {Object=} parameters Request parameters. 812 | * 813 | * - `id` – `{*}` - User id 814 | * 815 | * - `filter` – `{object=}` - 816 | * 817 | * @param {function(Array.,Object)=} successCb 818 | * Success callback with two arguments: `value`, `responseHeaders`. 819 | * 820 | * @param {function(Object)=} errorCb Error callback with one argument: 821 | * `httpResponse`. 822 | * 823 | * @returns {Array.} An empty reference that will be 824 | * populated with the actual data once the response is returned 825 | * from the server. 826 | * 827 | * 828 | * (The remote method definition does not provide any description. 829 | * This usually means the response is a `User` object.) 830 | * 831 | */ 832 | "prototype$__get__accessTokens": { 833 | isArray: true, 834 | url: urlBase + "/users/:id/accessTokens", 835 | method: "GET" 836 | }, 837 | 838 | /** 839 | * @ngdoc method 840 | * @name lbServices.User#prototype$__create__accessTokens 841 | * @methodOf lbServices.User 842 | * 843 | * @description 844 | * 845 | * Creates a new instance in accessTokens of this model. 846 | * 847 | * @param {Object=} parameters Request parameters. 848 | * 849 | * - `id` – `{*}` - User id 850 | * 851 | * @param {Object} postData Request data. 852 | * 853 | * This method expects a subset of model properties as request parameters. 854 | * 855 | * @param {function(Object,Object)=} successCb 856 | * Success callback with two arguments: `value`, `responseHeaders`. 857 | * 858 | * @param {function(Object)=} errorCb Error callback with one argument: 859 | * `httpResponse`. 860 | * 861 | * @returns {Object} An empty reference that will be 862 | * populated with the actual data once the response is returned 863 | * from the server. 864 | * 865 | * 866 | * (The remote method definition does not provide any description. 867 | * This usually means the response is a `User` object.) 868 | * 869 | */ 870 | "prototype$__create__accessTokens": { 871 | url: urlBase + "/users/:id/accessTokens", 872 | method: "POST" 873 | }, 874 | 875 | /** 876 | * @ngdoc method 877 | * @name lbServices.User#prototype$__delete__accessTokens 878 | * @methodOf lbServices.User 879 | * 880 | * @description 881 | * 882 | * Deletes all accessTokens of this model. 883 | * 884 | * @param {Object=} parameters Request parameters. 885 | * 886 | * - `id` – `{*}` - User id 887 | * 888 | * @param {function(Object,Object)=} successCb 889 | * Success callback with two arguments: `value`, `responseHeaders`. 890 | * 891 | * @param {function(Object)=} errorCb Error callback with one argument: 892 | * `httpResponse`. 893 | * 894 | * @returns {Object} An empty reference that will be 895 | * populated with the actual data once the response is returned 896 | * from the server. 897 | * 898 | * This method returns no data. 899 | */ 900 | "prototype$__delete__accessTokens": { 901 | url: urlBase + "/users/:id/accessTokens", 902 | method: "DELETE" 903 | }, 904 | 905 | /** 906 | * @ngdoc method 907 | * @name lbServices.User#prototype$__count__accessTokens 908 | * @methodOf lbServices.User 909 | * 910 | * @description 911 | * 912 | * Counts accessTokens of user. 913 | * 914 | * @param {Object=} parameters Request parameters. 915 | * 916 | * - `id` – `{*}` - User id 917 | * 918 | * - `where` – `{object=}` - Criteria to match model instances 919 | * 920 | * @param {function(Object,Object)=} successCb 921 | * Success callback with two arguments: `value`, `responseHeaders`. 922 | * 923 | * @param {function(Object)=} errorCb Error callback with one argument: 924 | * `httpResponse`. 925 | * 926 | * @returns {Object} An empty reference that will be 927 | * populated with the actual data once the response is returned 928 | * from the server. 929 | * 930 | * Data properties: 931 | * 932 | * - `count` – `{number=}` - 933 | */ 934 | "prototype$__count__accessTokens": { 935 | url: urlBase + "/users/:id/accessTokens/count", 936 | method: "GET" 937 | }, 938 | 939 | /** 940 | * @ngdoc method 941 | * @name lbServices.User#create 942 | * @methodOf lbServices.User 943 | * 944 | * @description 945 | * 946 | * Create a new instance of the model and persist it into the data source 947 | * 948 | * @param {Object=} parameters Request parameters. 949 | * 950 | * This method does not accept any parameters. 951 | * Supply an empty object or omit this argument altogether. 952 | * 953 | * @param {Object} postData Request data. 954 | * 955 | * This method expects a subset of model properties as request parameters. 956 | * 957 | * @param {function(Object,Object)=} successCb 958 | * Success callback with two arguments: `value`, `responseHeaders`. 959 | * 960 | * @param {function(Object)=} errorCb Error callback with one argument: 961 | * `httpResponse`. 962 | * 963 | * @returns {Object} An empty reference that will be 964 | * populated with the actual data once the response is returned 965 | * from the server. 966 | * 967 | * 968 | * (The remote method definition does not provide any description. 969 | * This usually means the response is a `User` object.) 970 | * 971 | */ 972 | "create": { 973 | url: urlBase + "/users", 974 | method: "POST" 975 | }, 976 | 977 | /** 978 | * @ngdoc method 979 | * @name lbServices.User#upsert 980 | * @methodOf lbServices.User 981 | * 982 | * @description 983 | * 984 | * Update an existing model instance or insert a new one into the data source 985 | * 986 | * @param {Object=} parameters Request parameters. 987 | * 988 | * This method does not accept any parameters. 989 | * Supply an empty object or omit this argument altogether. 990 | * 991 | * @param {Object} postData Request data. 992 | * 993 | * This method expects a subset of model properties as request parameters. 994 | * 995 | * @param {function(Object,Object)=} successCb 996 | * Success callback with two arguments: `value`, `responseHeaders`. 997 | * 998 | * @param {function(Object)=} errorCb Error callback with one argument: 999 | * `httpResponse`. 1000 | * 1001 | * @returns {Object} An empty reference that will be 1002 | * populated with the actual data once the response is returned 1003 | * from the server. 1004 | * 1005 | * 1006 | * (The remote method definition does not provide any description. 1007 | * This usually means the response is a `User` object.) 1008 | * 1009 | */ 1010 | "upsert": { 1011 | url: urlBase + "/users", 1012 | method: "PUT" 1013 | }, 1014 | 1015 | /** 1016 | * @ngdoc method 1017 | * @name lbServices.User#exists 1018 | * @methodOf lbServices.User 1019 | * 1020 | * @description 1021 | * 1022 | * Check whether a model instance exists in the data source 1023 | * 1024 | * @param {Object=} parameters Request parameters. 1025 | * 1026 | * - `id` – `{*}` - Model id 1027 | * 1028 | * @param {function(Object,Object)=} successCb 1029 | * Success callback with two arguments: `value`, `responseHeaders`. 1030 | * 1031 | * @param {function(Object)=} errorCb Error callback with one argument: 1032 | * `httpResponse`. 1033 | * 1034 | * @returns {Object} An empty reference that will be 1035 | * populated with the actual data once the response is returned 1036 | * from the server. 1037 | * 1038 | * Data properties: 1039 | * 1040 | * - `exists` – `{boolean=}` - 1041 | */ 1042 | "exists": { 1043 | url: urlBase + "/users/:id/exists", 1044 | method: "GET" 1045 | }, 1046 | 1047 | /** 1048 | * @ngdoc method 1049 | * @name lbServices.User#findById 1050 | * @methodOf lbServices.User 1051 | * 1052 | * @description 1053 | * 1054 | * Find a model instance by id from the data source 1055 | * 1056 | * @param {Object=} parameters Request parameters. 1057 | * 1058 | * - `id` – `{*}` - Model id 1059 | * 1060 | * @param {function(Object,Object)=} successCb 1061 | * Success callback with two arguments: `value`, `responseHeaders`. 1062 | * 1063 | * @param {function(Object)=} errorCb Error callback with one argument: 1064 | * `httpResponse`. 1065 | * 1066 | * @returns {Object} An empty reference that will be 1067 | * populated with the actual data once the response is returned 1068 | * from the server. 1069 | * 1070 | * 1071 | * (The remote method definition does not provide any description. 1072 | * This usually means the response is a `User` object.) 1073 | * 1074 | */ 1075 | "findById": { 1076 | url: urlBase + "/users/:id", 1077 | method: "GET" 1078 | }, 1079 | 1080 | /** 1081 | * @ngdoc method 1082 | * @name lbServices.User#find 1083 | * @methodOf lbServices.User 1084 | * 1085 | * @description 1086 | * 1087 | * Find all instances of the model matched by filter from the data source 1088 | * 1089 | * @param {Object=} parameters Request parameters. 1090 | * 1091 | * - `filter` – `{object=}` - Filter defining fields, where, orderBy, offset, and limit 1092 | * 1093 | * @param {function(Array.,Object)=} successCb 1094 | * Success callback with two arguments: `value`, `responseHeaders`. 1095 | * 1096 | * @param {function(Object)=} errorCb Error callback with one argument: 1097 | * `httpResponse`. 1098 | * 1099 | * @returns {Array.} An empty reference that will be 1100 | * populated with the actual data once the response is returned 1101 | * from the server. 1102 | * 1103 | * 1104 | * (The remote method definition does not provide any description. 1105 | * This usually means the response is a `User` object.) 1106 | * 1107 | */ 1108 | "find": { 1109 | isArray: true, 1110 | url: urlBase + "/users", 1111 | method: "GET" 1112 | }, 1113 | 1114 | /** 1115 | * @ngdoc method 1116 | * @name lbServices.User#findOne 1117 | * @methodOf lbServices.User 1118 | * 1119 | * @description 1120 | * 1121 | * Find first instance of the model matched by filter from the data source 1122 | * 1123 | * @param {Object=} parameters Request parameters. 1124 | * 1125 | * - `filter` – `{object=}` - Filter defining fields, where, orderBy, offset, and limit 1126 | * 1127 | * @param {function(Object,Object)=} successCb 1128 | * Success callback with two arguments: `value`, `responseHeaders`. 1129 | * 1130 | * @param {function(Object)=} errorCb Error callback with one argument: 1131 | * `httpResponse`. 1132 | * 1133 | * @returns {Object} An empty reference that will be 1134 | * populated with the actual data once the response is returned 1135 | * from the server. 1136 | * 1137 | * 1138 | * (The remote method definition does not provide any description. 1139 | * This usually means the response is a `User` object.) 1140 | * 1141 | */ 1142 | "findOne": { 1143 | url: urlBase + "/users/findOne", 1144 | method: "GET" 1145 | }, 1146 | 1147 | /** 1148 | * @ngdoc method 1149 | * @name lbServices.User#updateAll 1150 | * @methodOf lbServices.User 1151 | * 1152 | * @description 1153 | * 1154 | * Update instances of the model matched by where from the data source 1155 | * 1156 | * @param {Object=} parameters Request parameters. 1157 | * 1158 | * - `where` – `{object=}` - Criteria to match model instances 1159 | * 1160 | * @param {Object} postData Request data. 1161 | * 1162 | * This method expects a subset of model properties as request parameters. 1163 | * 1164 | * @param {function(Object,Object)=} successCb 1165 | * Success callback with two arguments: `value`, `responseHeaders`. 1166 | * 1167 | * @param {function(Object)=} errorCb Error callback with one argument: 1168 | * `httpResponse`. 1169 | * 1170 | * @returns {Object} An empty reference that will be 1171 | * populated with the actual data once the response is returned 1172 | * from the server. 1173 | * 1174 | * This method returns no data. 1175 | */ 1176 | "updateAll": { 1177 | url: urlBase + "/users/update", 1178 | method: "POST" 1179 | }, 1180 | 1181 | /** 1182 | * @ngdoc method 1183 | * @name lbServices.User#deleteById 1184 | * @methodOf lbServices.User 1185 | * 1186 | * @description 1187 | * 1188 | * Delete a model instance by id from the data source 1189 | * 1190 | * @param {Object=} parameters Request parameters. 1191 | * 1192 | * - `id` – `{*}` - Model id 1193 | * 1194 | * @param {function(Object,Object)=} successCb 1195 | * Success callback with two arguments: `value`, `responseHeaders`. 1196 | * 1197 | * @param {function(Object)=} errorCb Error callback with one argument: 1198 | * `httpResponse`. 1199 | * 1200 | * @returns {Object} An empty reference that will be 1201 | * populated with the actual data once the response is returned 1202 | * from the server. 1203 | * 1204 | * This method returns no data. 1205 | */ 1206 | "deleteById": { 1207 | url: urlBase + "/users/:id", 1208 | method: "DELETE" 1209 | }, 1210 | 1211 | /** 1212 | * @ngdoc method 1213 | * @name lbServices.User#count 1214 | * @methodOf lbServices.User 1215 | * 1216 | * @description 1217 | * 1218 | * Count instances of the model matched by where from the data source 1219 | * 1220 | * @param {Object=} parameters Request parameters. 1221 | * 1222 | * - `where` – `{object=}` - Criteria to match model instances 1223 | * 1224 | * @param {function(Object,Object)=} successCb 1225 | * Success callback with two arguments: `value`, `responseHeaders`. 1226 | * 1227 | * @param {function(Object)=} errorCb Error callback with one argument: 1228 | * `httpResponse`. 1229 | * 1230 | * @returns {Object} An empty reference that will be 1231 | * populated with the actual data once the response is returned 1232 | * from the server. 1233 | * 1234 | * Data properties: 1235 | * 1236 | * - `count` – `{number=}` - 1237 | */ 1238 | "count": { 1239 | url: urlBase + "/users/count", 1240 | method: "GET" 1241 | }, 1242 | 1243 | /** 1244 | * @ngdoc method 1245 | * @name lbServices.User#prototype$updateAttributes 1246 | * @methodOf lbServices.User 1247 | * 1248 | * @description 1249 | * 1250 | * Update attributes for a model instance and persist it into the data source 1251 | * 1252 | * @param {Object=} parameters Request parameters. 1253 | * 1254 | * - `id` – `{*}` - User id 1255 | * 1256 | * @param {Object} postData Request data. 1257 | * 1258 | * This method expects a subset of model properties as request parameters. 1259 | * 1260 | * @param {function(Object,Object)=} successCb 1261 | * Success callback with two arguments: `value`, `responseHeaders`. 1262 | * 1263 | * @param {function(Object)=} errorCb Error callback with one argument: 1264 | * `httpResponse`. 1265 | * 1266 | * @returns {Object} An empty reference that will be 1267 | * populated with the actual data once the response is returned 1268 | * from the server. 1269 | * 1270 | * 1271 | * (The remote method definition does not provide any description. 1272 | * This usually means the response is a `User` object.) 1273 | * 1274 | */ 1275 | "prototype$updateAttributes": { 1276 | url: urlBase + "/users/:id", 1277 | method: "PUT" 1278 | }, 1279 | 1280 | /** 1281 | * @ngdoc method 1282 | * @name lbServices.User#getCurrent 1283 | * @methodOf lbServices.User 1284 | * 1285 | * @description 1286 | * 1287 | * Get data of the currently logged user. Fail with HTTP result 401 1288 | * when there is no user logged in. 1289 | * 1290 | * @param {function(Object,Object)=} successCb 1291 | * Success callback with two arguments: `value`, `responseHeaders`. 1292 | * 1293 | * @param {function(Object)=} errorCb Error callback with one argument: 1294 | * `httpResponse`. 1295 | * 1296 | * @returns {Object} An empty reference that will be 1297 | * populated with the actual data once the response is returned 1298 | * from the server. 1299 | */ 1300 | "getCurrent": { 1301 | url: urlBase + "/users" + "/:id", 1302 | method: "GET", 1303 | params: { 1304 | id: function() { 1305 | var id = LoopBackAuth.currentUserId; 1306 | if (id == null) id = '__anonymous__'; 1307 | return id; 1308 | }, 1309 | }, 1310 | interceptor: { 1311 | response: function(response) { 1312 | LoopBackAuth.currentUserData = response.data; 1313 | return response.resource; 1314 | } 1315 | }, 1316 | __isGetCurrentUser__ : true 1317 | } 1318 | } 1319 | ); 1320 | 1321 | 1322 | 1323 | /** 1324 | * @ngdoc method 1325 | * @name lbServices.User#updateOrCreate 1326 | * @methodOf lbServices.User 1327 | * 1328 | * @description 1329 | * 1330 | * Update an existing model instance or insert a new one into the data source 1331 | * 1332 | * @param {Object=} parameters Request parameters. 1333 | * 1334 | * This method does not accept any parameters. 1335 | * Supply an empty object or omit this argument altogether. 1336 | * 1337 | * @param {Object} postData Request data. 1338 | * 1339 | * This method expects a subset of model properties as request parameters. 1340 | * 1341 | * @param {function(Object,Object)=} successCb 1342 | * Success callback with two arguments: `value`, `responseHeaders`. 1343 | * 1344 | * @param {function(Object)=} errorCb Error callback with one argument: 1345 | * `httpResponse`. 1346 | * 1347 | * @returns {Object} An empty reference that will be 1348 | * populated with the actual data once the response is returned 1349 | * from the server. 1350 | * 1351 | * 1352 | * (The remote method definition does not provide any description. 1353 | * This usually means the response is a `User` object.) 1354 | * 1355 | */ 1356 | R["updateOrCreate"] = R["upsert"]; 1357 | 1358 | /** 1359 | * @ngdoc method 1360 | * @name lbServices.User#update 1361 | * @methodOf lbServices.User 1362 | * 1363 | * @description 1364 | * 1365 | * Update instances of the model matched by where from the data source 1366 | * 1367 | * @param {Object=} parameters Request parameters. 1368 | * 1369 | * - `where` – `{object=}` - Criteria to match model instances 1370 | * 1371 | * @param {Object} postData Request data. 1372 | * 1373 | * This method expects a subset of model properties as request parameters. 1374 | * 1375 | * @param {function(Object,Object)=} successCb 1376 | * Success callback with two arguments: `value`, `responseHeaders`. 1377 | * 1378 | * @param {function(Object)=} errorCb Error callback with one argument: 1379 | * `httpResponse`. 1380 | * 1381 | * @returns {Object} An empty reference that will be 1382 | * populated with the actual data once the response is returned 1383 | * from the server. 1384 | * 1385 | * This method returns no data. 1386 | */ 1387 | R["update"] = R["updateAll"]; 1388 | 1389 | /** 1390 | * @ngdoc method 1391 | * @name lbServices.User#destroyById 1392 | * @methodOf lbServices.User 1393 | * 1394 | * @description 1395 | * 1396 | * Delete a model instance by id from the data source 1397 | * 1398 | * @param {Object=} parameters Request parameters. 1399 | * 1400 | * - `id` – `{*}` - Model id 1401 | * 1402 | * @param {function(Object,Object)=} successCb 1403 | * Success callback with two arguments: `value`, `responseHeaders`. 1404 | * 1405 | * @param {function(Object)=} errorCb Error callback with one argument: 1406 | * `httpResponse`. 1407 | * 1408 | * @returns {Object} An empty reference that will be 1409 | * populated with the actual data once the response is returned 1410 | * from the server. 1411 | * 1412 | * This method returns no data. 1413 | */ 1414 | R["destroyById"] = R["deleteById"]; 1415 | 1416 | /** 1417 | * @ngdoc method 1418 | * @name lbServices.User#removeById 1419 | * @methodOf lbServices.User 1420 | * 1421 | * @description 1422 | * 1423 | * Delete a model instance by id from the data source 1424 | * 1425 | * @param {Object=} parameters Request parameters. 1426 | * 1427 | * - `id` – `{*}` - Model id 1428 | * 1429 | * @param {function(Object,Object)=} successCb 1430 | * Success callback with two arguments: `value`, `responseHeaders`. 1431 | * 1432 | * @param {function(Object)=} errorCb Error callback with one argument: 1433 | * `httpResponse`. 1434 | * 1435 | * @returns {Object} An empty reference that will be 1436 | * populated with the actual data once the response is returned 1437 | * from the server. 1438 | * 1439 | * This method returns no data. 1440 | */ 1441 | R["removeById"] = R["deleteById"]; 1442 | 1443 | /** 1444 | * @ngdoc method 1445 | * @name lbServices.User#getCachedCurrent 1446 | * @methodOf lbServices.User 1447 | * 1448 | * @description 1449 | * 1450 | * Get data of the currently logged user that was returned by the last 1451 | * call to {@link lbServices.User#login} or 1452 | * {@link lbServices.User#getCurrent}. Return null when there 1453 | * is no user logged in or the data of the current user were not fetched 1454 | * yet. 1455 | * 1456 | * @returns {Object} A User instance. 1457 | */ 1458 | R.getCachedCurrent = function() { 1459 | var data = LoopBackAuth.currentUserData; 1460 | return data ? new R(data) : null; 1461 | }; 1462 | 1463 | /** 1464 | * @ngdoc method 1465 | * @name lbServices.User#isAuthenticated 1466 | * @methodOf lbServices.User 1467 | * 1468 | * @returns {boolean} True if the current user is authenticated (logged in). 1469 | */ 1470 | R.isAuthenticated = function() { 1471 | return this.getCurrentId() != null; 1472 | }; 1473 | 1474 | /** 1475 | * @ngdoc method 1476 | * @name lbServices.User#getCurrentId 1477 | * @methodOf lbServices.User 1478 | * 1479 | * @returns {Object} Id of the currently logged-in user or null. 1480 | */ 1481 | R.getCurrentId = function() { 1482 | return LoopBackAuth.currentUserId; 1483 | }; 1484 | 1485 | /** 1486 | * @ngdoc property 1487 | * @name lbServices.User#modelName 1488 | * @propertyOf lbServices.User 1489 | * @description 1490 | * The name of the model represented by this $resource, 1491 | * i.e. `User`. 1492 | */ 1493 | R.modelName = "User"; 1494 | 1495 | 1496 | return R; 1497 | }]); 1498 | 1499 | 1500 | module 1501 | .factory('LoopBackAuth', function() { 1502 | var props = ['accessTokenId', 'currentUserId']; 1503 | var propsPrefix = '$LoopBack$'; 1504 | 1505 | function LoopBackAuth() { 1506 | var self = this; 1507 | props.forEach(function(name) { 1508 | self[name] = load(name); 1509 | }); 1510 | this.rememberMe = undefined; 1511 | this.currentUserData = null; 1512 | } 1513 | 1514 | LoopBackAuth.prototype.save = function() { 1515 | var self = this; 1516 | var storage = this.rememberMe ? localStorage : sessionStorage; 1517 | props.forEach(function(name) { 1518 | save(storage, name, self[name]); 1519 | }); 1520 | }; 1521 | 1522 | LoopBackAuth.prototype.setUser = function(accessTokenId, userId, userData) { 1523 | this.accessTokenId = accessTokenId; 1524 | this.currentUserId = userId; 1525 | this.currentUserData = userData; 1526 | } 1527 | 1528 | LoopBackAuth.prototype.clearUser = function() { 1529 | this.accessTokenId = null; 1530 | this.currentUserId = null; 1531 | this.currentUserData = null; 1532 | } 1533 | 1534 | LoopBackAuth.prototype.clearStorage = function() { 1535 | props.forEach(function(name) { 1536 | save(sessionStorage, name, null); 1537 | save(localStorage, name, null); 1538 | }); 1539 | }; 1540 | 1541 | return new LoopBackAuth(); 1542 | 1543 | // Note: LocalStorage converts the value to string 1544 | // We are using empty string as a marker for null/undefined values. 1545 | function save(storage, name, value) { 1546 | var key = propsPrefix + name; 1547 | if (value == null) value = ''; 1548 | storage[key] = value; 1549 | } 1550 | 1551 | function load(name) { 1552 | var key = propsPrefix + name; 1553 | return localStorage[key] || sessionStorage[key] || null; 1554 | } 1555 | }) 1556 | .config(['$httpProvider', function($httpProvider) { 1557 | $httpProvider.interceptors.push('LoopBackAuthRequestInterceptor'); 1558 | }]) 1559 | .factory('LoopBackAuthRequestInterceptor', [ '$q', 'LoopBackAuth', 1560 | function($q, LoopBackAuth) { 1561 | return { 1562 | 'request': function(config) { 1563 | 1564 | // filter out non urlBase requests 1565 | if (config.url.substr(0, urlBase.length) !== urlBase) { 1566 | return config; 1567 | } 1568 | 1569 | if (LoopBackAuth.accessTokenId) { 1570 | config.headers[authHeader] = LoopBackAuth.accessTokenId; 1571 | } else if (config.__isGetCurrentUser__) { 1572 | // Return a stub 401 error for User.getCurrent() when 1573 | // there is no user logged in 1574 | var res = { 1575 | body: { error: { status: 401 } }, 1576 | status: 401, 1577 | config: config, 1578 | headers: function() { return undefined; } 1579 | }; 1580 | return $q.reject(res); 1581 | } 1582 | return config || $q.when(config); 1583 | } 1584 | } 1585 | }]) 1586 | 1587 | /** 1588 | * @ngdoc object 1589 | * @name lbServices.LoopBackResourceProvider 1590 | * @header lbServices.LoopBackResourceProvider 1591 | * @description 1592 | * Use `LoopBackResourceProvider` to change the global configuration 1593 | * settings used by all models. Note that the provider is available 1594 | * to Configuration Blocks only, see 1595 | * {@link https://docs.angularjs.org/guide/module#module-loading-dependencies Module Loading & Dependencies} 1596 | * for more details. 1597 | * 1598 | * ## Example 1599 | * 1600 | * ```js 1601 | * angular.module('app') 1602 | * .config(function(LoopBackResourceProvider) { 1603 | * LoopBackResourceProvider.setAuthHeader('X-Access-Token'); 1604 | * }); 1605 | * ``` 1606 | */ 1607 | .provider('LoopBackResource', function LoopBackResourceProvider() { 1608 | /** 1609 | * @ngdoc method 1610 | * @name lbServices.LoopBackResourceProvider#setAuthHeader 1611 | * @methodOf lbServices.LoopBackResourceProvider 1612 | * @param {string} header The header name to use, e.g. `X-Access-Token` 1613 | * @description 1614 | * Configure the REST transport to use a different header for sending 1615 | * the authentication token. It is sent in the `Authorization` header 1616 | * by default. 1617 | */ 1618 | this.setAuthHeader = function(header) { 1619 | authHeader = header; 1620 | }; 1621 | 1622 | /** 1623 | * @ngdoc method 1624 | * @name lbServices.LoopBackResourceProvider#setUrlBase 1625 | * @methodOf lbServices.LoopBackResourceProvider 1626 | * @param {string} url The URL to use, e.g. `/api` or `//example.com/api`. 1627 | * @description 1628 | * Change the URL of the REST API server. By default, the URL provided 1629 | * to the code generator (`lb-ng` or `grunt-loopback-sdk-angular`) is used. 1630 | */ 1631 | this.setUrlBase = function(url) { 1632 | urlBase = url; 1633 | }; 1634 | 1635 | this.$get = ['$resource', function($resource) { 1636 | return function(url, params, actions) { 1637 | var resource = $resource(url, params, actions); 1638 | 1639 | // Angular always calls POST on $save() 1640 | // This hack is based on 1641 | // http://kirkbushell.me/angular-js-using-ng-resource-in-a-more-restful-manner/ 1642 | resource.prototype.$save = function(success, error) { 1643 | // Fortunately, LoopBack provides a convenient `upsert` method 1644 | // that exactly fits our needs. 1645 | var result = resource.upsert.call(this, {}, this, success, error); 1646 | return result.$promise || result; 1647 | }; 1648 | return resource; 1649 | }; 1650 | }]; 1651 | }); 1652 | 1653 | })(window, window.angular); 1654 | -------------------------------------------------------------------------------- /client/lbclient/.gitignore: -------------------------------------------------------------------------------- 1 | browser.bundle.js 2 | -------------------------------------------------------------------------------- /client/lbclient/boot/replication.js: -------------------------------------------------------------------------------- 1 | // User(bajtos) Move the bi-di replication to loopback core, 2 | // add model settings to enable the replication. 3 | // Example: 4 | // LocalUser: { options: { 5 | // base: 'User', 6 | // replicate: { 7 | // target: 'Todo', 8 | // mode: 'push' | 'pull' | 'bidi' 9 | // }}} 10 | module.exports = function(client) { 11 | var LocalUser = client.models.LocalUser; 12 | var RemoteUser = client.models.user; 13 | 14 | var LocalChatRoom = client.models.LocalChatRoom; 15 | var RemoteChatRoom = client.models.chatRoom; 16 | 17 | client.network = { 18 | _isConnected: true, 19 | get isConnected() { 20 | console.log('isConnected?', this._isConnected); 21 | return this._isConnected; 22 | }, 23 | set isConnected(value) { 24 | this._isConnected = value; 25 | } 26 | }; 27 | 28 | // setup model replication 29 | function syncUser(cb) { 30 | if (client.network.isConnected && window.currentUserId) { 31 | RemoteUser.replicate(-1, LocalUser, { filter: { where: { id: window.currentUserId, include: 'events' }}}, function() { 32 | LocalUser.replicate(-1, RemoteUser, { filter: { where: { id: window.currentUserId, include: 'events' }}}, cb); 33 | }); 34 | } 35 | } 36 | 37 | function syncChatRoom(cb) { 38 | if (client.network.isConnected && window.currentUserId) { 39 | RemoteChatRoom.replicate(-1, LocalChatRoom, { filter: { where: { users: window.currentUserId }}}, function() { 40 | LocalChatRoom.replicate(-1, RemoteChatRoom, { filter: { where: { users: window.currentUserId }}}, cb); 41 | }); 42 | } 43 | } 44 | 45 | // sync local changes if connected 46 | LocalUser.on('changed', syncUser); 47 | LocalUser.on('deleted', syncUser); 48 | LocalChatRoom.on('changed', syncChatRoom); 49 | LocalChatRoom.on('deleted', syncChatRoom); 50 | 51 | client.syncUser = syncUser; 52 | client.syncChatRoom = syncChatRoom; 53 | }; 54 | -------------------------------------------------------------------------------- /client/lbclient/build.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var pkg = require('./package.json'); 3 | var fs = require('fs'); 4 | var browserify = require('browserify'); 5 | var boot = require('loopback-boot'); 6 | 7 | module.exports = function buildBrowserBundle(env, callback) { 8 | var b = browserify({ basedir: __dirname, debug: isDevEnv }); 9 | b.require('./' + pkg.main, { expose: 'lbclient' }); 10 | 11 | try { 12 | boot.compileToBrowserify({ 13 | appRootDir: __dirname, 14 | env: env 15 | }, b); 16 | } catch(err) { 17 | return callback(err); 18 | } 19 | 20 | var bundlePath = path.resolve(__dirname, 'browser.bundle.js'); 21 | var out = fs.createWriteStream(bundlePath); 22 | var isDevEnv = ~['debug', 'development', 'test'].indexOf(env); 23 | 24 | b.bundle() 25 | .on('error', callback) 26 | .pipe(out); 27 | 28 | out.on('error', callback); 29 | out.on('close', callback); 30 | }; 31 | -------------------------------------------------------------------------------- /client/lbclient/code.js: -------------------------------------------------------------------------------- 1 | if (window.accessToken) { 2 | if (!req.headers) { 3 | req.headers = {}; 4 | } 5 | req.headers.authorization = window.accessToken; 6 | } -------------------------------------------------------------------------------- /client/lbclient/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "remote": { 3 | "connector": "remote" 4 | }, 5 | "local": { 6 | "connector": "memory", 7 | "localStorage": "chat-db" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /client/lbclient/datasources.local.js: -------------------------------------------------------------------------------- 1 | var GLOBAL_CONFIG = require('../../global-config'); 2 | 3 | module.exports = { 4 | remote: { 5 | url: GLOBAL_CONFIG.restApiUrl 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /client/lbclient/lbclient.js: -------------------------------------------------------------------------------- 1 | var loopback = require('loopback'); 2 | var boot = require('loopback-boot'); 3 | 4 | var client = module.exports = loopback(); 5 | boot(client); 6 | -------------------------------------------------------------------------------- /client/lbclient/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": ["../../common/models", "./models"] 4 | }, 5 | "LocalUser": { 6 | "dataSource": "local" 7 | }, 8 | "user": { 9 | "dataSource": "remote" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/lbclient/models/local-user.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LocalUser", 3 | "base": "user" 4 | } 5 | -------------------------------------------------------------------------------- /client/lbclient/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "main": "lbclient.js" 4 | } 5 | -------------------------------------------------------------------------------- /client/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /client/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc overview 5 | * @name caaSwebsiteApp 6 | * @description 7 | * # caaSwebsiteApp 8 | * 9 | * Main module of the application. 10 | */ 11 | angular 12 | .module('App', [ 13 | 'ngAnimate', 14 | 'ngCookies', 15 | 'ngResource', 16 | 'ngSanitize', 17 | 'ngTouch', 18 | 'lbServices', 19 | 'services', 20 | 'ui.router', 21 | 'pascalprecht.translate', 'angulartics', 'angulartics.google.analytics' 22 | ]) 23 | 24 | .config(function($locationProvider) { 25 | $locationProvider. 26 | html5Mode(true).hashPrefix('!'); 27 | }) 28 | 29 | .config(function ($translateProvider) { 30 | // add translation tables 31 | $translateProvider.useStaticFilesLoader({ 32 | prefix: 'locales/en.', 33 | suffix: '.json' 34 | }); 35 | $translateProvider.preferredLanguage('en-GB'); 36 | // remember language 37 | $translateProvider.useLocalStorage(); 38 | 39 | //$translateProvider.useMissingTranslationHandler('sendMissingTranslation'); 40 | }) 41 | 42 | .config(function ($analyticsProvider) { 43 | // turn off automatic tracking 44 | $analyticsProvider.settings.trackRelativePath = true; 45 | }) 46 | 47 | .config(function ($httpProvider) { 48 | $httpProvider.interceptors.push(function($q, $location, $injector) { 49 | return { 50 | responseError: function(rejection) { 51 | if (rejection.status === 401) { 52 | var state = $injector.get('$state'); 53 | if (!state.get($location.path().split('/')[1]).public && $location.path().split('/')[1] !== '') { 54 | $location.nextAfterLogin = $location.path(); 55 | $location.path('/signin'); 56 | } 57 | } 58 | return $q.reject(rejection); 59 | } 60 | }; 61 | }); 62 | }) 63 | 64 | .config(function ($stateProvider, $urlRouterProvider) { 65 | $urlRouterProvider.rule(function($injector, $location) { 66 | var path = $location.path(), 67 | // Note: misnomer. This returns a query object, not a search string 68 | search = $location.search(), 69 | params 70 | ; 71 | 72 | // check to see if the path already ends in '/' 73 | if (path[path.length - 1] === '/') { 74 | return; 75 | } 76 | 77 | // If there was no search string / query params, return with a `/` 78 | if (Object.keys(search).length === 0) { 79 | return path + '/'; 80 | } 81 | 82 | // Otherwise build the search string and return a `/?` prefix 83 | params = []; 84 | angular.forEach(search, function(v, k){ 85 | params.push(k + '=' + v); 86 | }); 87 | return path + '/?' + params.join('&'); 88 | }); 89 | 90 | $urlRouterProvider 91 | .when('/verify-email', '/signin') 92 | .when('/verify-email/', '/signin') 93 | .otherwise('/'); 94 | 95 | $stateProvider 96 | .state('home', { 97 | url: '/', 98 | views: { 99 | 'mainview': { 100 | templateUrl: '/views/home.html', 101 | controller: 'homeCtrl' 102 | } 103 | }, 104 | public: true 105 | }) 106 | .state('theapp', { 107 | url: '/theapp/', 108 | views: { 109 | 'mainview@': { 110 | templateUrl: '/views/theapp.html', 111 | controller: 'theappCtrl' 112 | } 113 | }, 114 | public: true 115 | }) 116 | .state('signin', { 117 | url: '/signin/', 118 | views: { 119 | 'mainview@': { 120 | templateUrl: '/views/signin.html', 121 | controller: 'signinCtrl' 122 | } 123 | }, 124 | public: true 125 | }) 126 | .state('signup', { 127 | url: '/signup/', 128 | views: { 129 | 'mainview@': { 130 | templateUrl: '/views/signin.html', 131 | controller: 'signinCtrl' 132 | } 133 | }, 134 | public: true 135 | }) 136 | .state('verify-email', { 137 | url: '/verify-email/', 138 | views: { 139 | 'mainview@': { 140 | templateUrl: '/views/signin.html', 141 | controller: 'signinCtrl' 142 | } 143 | }, 144 | public: true 145 | }) 146 | .state('reset-password', { 147 | url: '/reset-password/{resetPasswordToken}/', 148 | views: { 149 | 'mainview@': { 150 | templateUrl: '/views/signin.html', 151 | controller: 'signinCtrl' 152 | } 153 | }, 154 | public: true 155 | }) 156 | .state('legal', { 157 | url: '/legal/', 158 | views: { 159 | 'mainview@': { 160 | templateUrl: function () { 161 | //return '/views/legal/' + (locale || 'en') + '.html'; 162 | }, 163 | controller: 'legalCtrl' 164 | } 165 | }, 166 | public: true 167 | }) 168 | .state('account', { 169 | url: '/account/', 170 | views: { 171 | 'mainview@': { 172 | templateUrl: '/views/account.html', 173 | controller: 'accountCtrl' 174 | } 175 | } 176 | }) 177 | .state('recover-account', { 178 | url: '/recover-account/', 179 | views: { 180 | 'mainview@': { 181 | templateUrl: '/views/signin.html', 182 | controller: 'signinCtrl' 183 | } 184 | }, 185 | public: true 186 | }) 187 | .state('deactivated', { 188 | url: '/deactivated/', 189 | views: { 190 | 'mainview@': { 191 | templateUrl: '/views/deactivated.html' 192 | } 193 | } 194 | }) 195 | .state('notfound', { 196 | url: '/notfound/', 197 | views: { 198 | 'mainview@': { 199 | template: '', 200 | controller: ['$location', 'toaster', '$translate', function ($location, toaster, $translate) { 201 | toaster.pop('warning', $translate('Ad'), $translate('The Ad you requested does not exist.')); 202 | return $location.path('/'); 203 | }] 204 | } 205 | }, 206 | public: true 207 | }); 208 | }) 209 | 210 | .run(function($rootScope, $cookies, $location, AppAuth, $http, User, LoopBackAuth) { 211 | if ($cookies.userId && $cookies.access_token) { 212 | LoopBackAuth.currentUserId = $cookies.userId || null; 213 | LoopBackAuth.accessTokenId = $cookies.access_token || ''; 214 | LoopBackAuth.rememberMe = true; 215 | LoopBackAuth.save(); 216 | } 217 | 218 | delete $cookies.userId; 219 | delete $cookies.access_token; 220 | AppAuth.ensureHasCurrentUser(User); 221 | 222 | Visibility.change(function (e, state) { 223 | if (state === 'visible') { 224 | LoopBackAuth.currentUserId = localStorage['$LoopBack$currentUserId'] || sessionStorage['$LoopBack$currentUserId'] || null; 225 | LoopBackAuth.accessTokenId = localStorage['$LoopBack$accessTokenId'] || sessionStorage['$LoopBack$accessTokenId'] || null; 226 | AppAuth.ensureHasCurrentUser(User); 227 | } 228 | }); 229 | }); 230 | -------------------------------------------------------------------------------- /client/scripts/controllers/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name caaSwebsiteApp.controller:MainCtrl 6 | * @description 7 | * # MainCtrl 8 | * Controller of the caaSwebsiteApp 9 | */ 10 | angular.module('App') 11 | .controller('headerCtrl', function ($scope, User, $location, AppAuth) { 12 | $scope.currentUser = AppAuth.currentUser; 13 | 14 | $scope.logout = function () { 15 | AppAuth.logout(User); 16 | }; 17 | 18 | $scope.$on('login', function () { 19 | $scope.currentUser = AppAuth.currentUser; 20 | }); 21 | }) 22 | .controller('homeCtrl', function ($scope) { 23 | $scope.awesomeThings = [ 24 | 'HTML5 Boilerplate', 25 | 'AngularJS', 26 | 'Karma' 27 | ]; 28 | }); 29 | -------------------------------------------------------------------------------- /client/scripts/controllers/signin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name caaSwebsiteApp.controller:AboutCtrl 6 | * @description 7 | * # AboutCtrl 8 | * Controller of the caaSwebsiteApp 9 | */ 10 | angular.module('App') 11 | .controller('signinCtrl', function ($scope, $state, $stateParams, $location, User, AppAuth, LoopBackAuth) { 12 | $scope.err = false; 13 | $scope.success = false; 14 | $scope.loading = false; 15 | 16 | $scope.formData = {}; 17 | $scope.currentUser = AppAuth.currentUser; 18 | $scope.showForm = $state.current.name; 19 | 20 | if ($scope.currentUser.username && $state.current.name === 'signup') { 21 | $scope.formData.email = $scope.currentUser.email; 22 | $scope.formData.name = $scope.currentUser.name; 23 | } 24 | 25 | if ($stateParams.resetPasswordToken) { 26 | //$scope.formData.password = $stateParams.resetPasswordToken; 27 | LoopBackAuth.accessTokenId = $stateParams.resetPasswordToken; 28 | LoopBackAuth.save(); 29 | } 30 | 31 | $scope.login = function () { 32 | $scope.loading = true; 33 | 34 | if ($scope.currentUser.username) { 35 | $scope.loginResult = User.link({rememberMe: false}, $scope.formData, 36 | function() { 37 | $scope.loading = false; 38 | var next = $location.nextAfterLogin || '/'; 39 | $location.nextAfterLogin = null; 40 | 41 | AppAuth.ensureHasCurrentUser(User); 42 | $location.path(next); 43 | }, 44 | function(res) { 45 | $scope.loading = false; 46 | $scope.loginError = res.data.error; 47 | } 48 | ); 49 | } else { 50 | $scope.loginResult = User.login({rememberMe: false}, $scope.formData, 51 | function() { 52 | $scope.loading = false; 53 | var next = $location.nextAfterLogin || '/'; 54 | $location.nextAfterLogin = null; 55 | 56 | AppAuth.ensureHasCurrentUser(User); 57 | $location.path(next); 58 | }, 59 | function(res) { 60 | $scope.loading = false; 61 | $scope.loginError = res.data.error; 62 | } 63 | ); 64 | } 65 | }; 66 | 67 | $scope.register = function() { 68 | $scope.loading = true; 69 | 70 | if ($scope.currentUser.username) { 71 | $scope.user = User.createAndLink($scope.formData, 72 | function () { 73 | $scope.loading = false; 74 | $scope.login(); 75 | }, 76 | function (res) { 77 | $scope.loading = false; 78 | $scope.registerError = res.data.error; 79 | } 80 | ); 81 | } else { 82 | $scope.user = User.save($scope.formData, 83 | function () { 84 | $scope.loading = false; 85 | $scope.login(); 86 | }, 87 | function (res) { 88 | $scope.loading = false; 89 | $scope.registerError = res.data.error; 90 | } 91 | ); 92 | } 93 | }; 94 | 95 | $scope.resetPassword = function () { 96 | $scope.loading = true; 97 | 98 | if ($scope.currentUser.name) { 99 | 100 | } else { 101 | User.upsert({password: $scope.formData.password}, 102 | function(res) { 103 | $scope.loading = false; 104 | console.log(res); 105 | }, 106 | function(res) { 107 | $scope.loading = false; 108 | console.log(res); 109 | $scope.loginError = res.data.error; 110 | } 111 | ); 112 | } 113 | }; 114 | 115 | $scope.recover = function () { 116 | $scope.loading = true; 117 | 118 | User.resetPassword($scope.formData, 119 | function(res) { 120 | $scope.loading = false; 121 | console.log(res); 122 | }, 123 | function(res) { 124 | $scope.loading = false; 125 | console.log(res); 126 | $scope.loginError = res.data.error; 127 | } 128 | ); 129 | 130 | }; 131 | 132 | }); 133 | -------------------------------------------------------------------------------- /client/scripts/services/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('services', ['lbServices']) 4 | .factory('AppAuth', function($rootScope, $location, LoopBackAuth, $window) { 5 | return { 6 | currentUser: {}, 7 | 8 | // Note: we can't make the User a dependency of AppAuth 9 | // because that would create a circular dependency 10 | // AppAuth <- $http <- $resource <- LoopBackResource <- User <- AppAuth 11 | ensureHasCurrentUser: function(User) { 12 | if (this.currentUser.id) { 13 | if (!LoopBackAuth.currentUserId || !LoopBackAuth.accessTokenId) { 14 | return this.logout(User); 15 | } 16 | } else if (LoopBackAuth.currentUserId && LoopBackAuth.accessTokenId) { 17 | var self = this; 18 | self.currentUser = User.getCurrent(function(response) { 19 | // success 20 | }, function(response) { 21 | console.log(response); 22 | }); 23 | } 24 | }, 25 | logout: function (User) { 26 | var self = this; 27 | User.logout(function() { 28 | self.currentUser = null; 29 | 30 | LoopBackAuth.currentUserId = ''; 31 | LoopBackAuth.accessTokenId = ''; 32 | LoopBackAuth.rememberMe = true; 33 | LoopBackAuth.save(); 34 | 35 | $location.path('/'); 36 | $window.location.reload(); 37 | }, function (err) { 38 | if (err && err.data.error.message === 'could not find accessToken') { 39 | self.currentUser = null; 40 | 41 | LoopBackAuth.currentUserId = ''; 42 | LoopBackAuth.accessTokenId = ''; 43 | LoopBackAuth.rememberMe = true; 44 | LoopBackAuth.save(); 45 | 46 | $location.path('/'); 47 | $window.location.reload(); 48 | } 49 | }); 50 | } 51 | }; 52 | }) 53 | .factory('socket', function($rootScope, LoopBackAuth, AppAuth, $notification) { 54 | return { 55 | socket: false, 56 | connect: function (token) { 57 | var self = this; 58 | if (!self.socket) { 59 | self.socket = io('/', { 60 | 'query': 'access_token=' + LoopBackAuth.accessTokenId 61 | }); 62 | 63 | self.on('connection', function(data) { 64 | console.log('connect'); 65 | }); 66 | 67 | self.on('connection_error', function(data) { 68 | console.log('connect_error'); 69 | }); 70 | 71 | self.on('message', function(data) { 72 | console.log('message'); 73 | }); 74 | } 75 | }, 76 | on: function(eventName, callback) { 77 | var self = this; 78 | self.socket.on(eventName, function() { 79 | var args = arguments; 80 | $rootScope.$apply(function() { 81 | callback.apply(self.socket, args); 82 | }); 83 | }); 84 | }, 85 | emit: function(eventName, data, callback) { 86 | var self = this; 87 | self.socket.emit(eventName, data, function() { 88 | var args = arguments; 89 | $rootScope.$apply(function() { 90 | if(callback) { 91 | callback.apply(self.socket, args); 92 | } 93 | }); 94 | }); 95 | }, 96 | json: { 97 | send: function(data, callback) { 98 | var self = this; 99 | self.socket.json.send(data, function() { 100 | var args = arguments; 101 | $rootScope.$apply(function() { 102 | if(callback) { 103 | callback.apply(self.socket, args); 104 | } 105 | }); 106 | }); 107 | } 108 | } 109 | }; 110 | }); -------------------------------------------------------------------------------- /client/styles/main.scss: -------------------------------------------------------------------------------- 1 | // bower:scss 2 | @import "bootstrap-sass-official/assets/stylesheets/_bootstrap.scss"; 3 | // endbower 4 | // 5 | //----------------------------------*\ 6 | // SCSS MIXINS 7 | //----------------------------------*/ 8 | 9 | @import "mixins/_breakpoints.scss"; 10 | 11 | //----------------------------------*\ 12 | // CONFIG MODULES 13 | //----------------------------------*/ 14 | 15 | // Config modules 16 | @import "modules/_vars.scss"; 17 | @import "modules/_normalize.scss"; 18 | @import "modules/_defaults.scss"; 19 | 20 | //----------------------------------*\ 21 | // PARTIALS 22 | //----------------------------------*/ 23 | 24 | @import "partials/_main.scss"; 25 | @import "partials/_log-forms.scss"; 26 | @import "partials/_apps.scss"; 27 | //@import "partials/_post-single.scss"; 28 | //@import "partials/_footer.scss"; 29 | 30 | // Enhancement modules 31 | @import "modules/_print.scss"; 32 | 33 | // Need to be after every module 34 | @import "modules/_utils.scss"; -------------------------------------------------------------------------------- /client/styles/mixins/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | @mixin breakpoint($point) { 2 | @if $point == retina { 3 | @media print, 4 | (-o-min-device-pixel-ratio: 5/4), 5 | (-webkit-min-device-pixel-ratio: 1.25), 6 | (min-resolution: 120dpi) { @content; } 7 | } @else if $point == xx-small { 8 | @media only screen and (min-width: $breakpoint-xx-small) { @content; } 9 | } @else if $point == x-small { 10 | @media only screen and (min-width: $breakpoint-x-small) { @content; } 11 | } @else if $point == small { 12 | @media only screen and (min-width: $breakpoint-small) { @content; } 13 | } @else if $point == medium { 14 | @media only screen and (min-width: $breakpoint-medium) { @content; } 15 | } @else if $point == large { 16 | @media only screen and (min-width: $breakpoint-large) { @content; } 17 | } @else if $point == x-large { 18 | @media only screen and (min-width: $breakpoint-x-large) { @content; } 19 | } @else if $point == xx-large { 20 | @media only screen and (min-width: $breakpoint-xx-large) { @content; } 21 | } @else { 22 | @media only screen and ($point) { @content; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/styles/modules/_buttons.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | display: inline-block; 3 | position: relative; 4 | height: 40px; 5 | padding: 0 20px; 6 | border: 0; 7 | color: $text-color; 8 | background-color: $line-breaks-color; 9 | font-size: 14px; 10 | line-height: 39px; 11 | text-align: center; 12 | text-decoration: none; 13 | font-style: normal; 14 | cursor: pointer; 15 | border-bottom: 1px solid rgba(0,0,0,0.1); 16 | vertical-align: bottom; 17 | touch-callout: none; 18 | user-select: none; 19 | font-family: $title-font-family; 20 | font-weight: 400; 21 | border-radius: 3px; 22 | transition: all .1s ease; 23 | 24 | &:hover, 25 | &:focus { 26 | color: $text-color; 27 | background-image: linear-gradient(to bottom,rgba(0,0,0,0.05),rgba(0,0,0,0.05)); 28 | background-repeat: repeat-x; 29 | border-bottom: 1px solid rgba(0,0,0,0.2); 30 | } 31 | 32 | &:active { 33 | background-image: linear-gradient(to bottom,rgba(0,0,0,0.1),rgba(0,0,0,0.1)); 34 | background-repeat: repeat-x; 35 | border-top: 1px solid rgba(0,0,0,0.2); 36 | border-bottom-width: 0; 37 | } 38 | 39 | &.btn-small { 40 | height: 34px; 41 | line-height: 35px; 42 | } 43 | 44 | &.btn-big { 45 | height: 45px; 46 | line-height: 44px; 47 | } 48 | 49 | &.btn-primary { 50 | background-color: $primary-color; 51 | color: $background-color; 52 | 53 | &:hover, 54 | &:focus { 55 | color: $background-color; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /client/styles/modules/_defaults.scss: -------------------------------------------------------------------------------- 1 | * { 2 | font-smoothing: antialiased; 3 | text-rendering: optimizeLegibility; 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-size: 100%; 9 | } 10 | 11 | body { 12 | font: 400 20px/1.6 $text-font-family; 13 | background: $background-color; 14 | color: $text-color; 15 | 16 | @include breakpoint(small) { 17 | font-size: 18px; 18 | line-height: 1.4; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/styles/modules/_figures.scss: -------------------------------------------------------------------------------- 1 | @keyframes load-spinner { 2 | 0% { transform: rotate(0deg); transform: rotate(0deg); } 3 | 100% { transform: rotate(360deg); transform: rotate(360deg); } 4 | } 5 | 6 | [data-load-image] { 7 | position: relative; 8 | background-position: center; 9 | background-size: cover; 10 | background-color: $text-color; 11 | background-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); 12 | transition: background-image .5s ease-in-out; 13 | 14 | &:before { 15 | content: ""; 16 | display: block; 17 | position: absolute; 18 | top: 50%; 19 | left: 50%; 20 | width: 14px; 21 | height: 14px; 22 | margin-top: -7px; 23 | margin-left: -7px; 24 | border: solid 2px transparent; 25 | border-top-color: $background-color; 26 | border-left-color: $background-color; 27 | border-radius: 10px; 28 | animation: load-spinner 400ms linear infinite; 29 | transition: opacity .2s; 30 | } 31 | 32 | &.image-loaded:before { 33 | opacity: 0; 34 | } 35 | } 36 | 37 | img { 38 | max-width: 100%; 39 | margin: 0 auto; 40 | } 41 | 42 | figure { 43 | position: relative; 44 | margin-bottom: 30px; 45 | clear: both; 46 | outline: 0; 47 | width: 100%; 48 | 49 | img { 50 | @extend .center-block; 51 | } 52 | 53 | figcaption { 54 | position: absolute; 55 | left: -170px; 56 | width: 150px; 57 | top: 0; 58 | color: $muted-text-color; 59 | font-style: italic; 60 | font-size: 12px; 61 | line-height: 1.3; 62 | color: #666665; 63 | outline: 0; 64 | text-align: right; 65 | 66 | &:before { 67 | width: 25%; 68 | margin-left: 75%; 69 | border: 1px solid $line-breaks-color; 70 | display: block; 71 | content: ""; 72 | margin-bottom: 10px; 73 | 74 | @include breakpoint(medium) { 75 | display: none; 76 | } 77 | } 78 | 79 | @include breakpoint(medium) { 80 | position: relative; 81 | width: 100%; 82 | text-align: center; 83 | left: 0; 84 | margin-top: 10px; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /client/styles/modules/_forms.scss: -------------------------------------------------------------------------------- 1 | label { 2 | font-family: $input-font-family; 3 | font-size: 1rem; 4 | } 5 | 6 | fieldset { 7 | border: 0; 8 | margin-bottom: 30px; 9 | } 10 | 11 | input[type="text"], 12 | input[type="password"], 13 | input[type="email"], 14 | input[type="url"], 15 | input[type="date"], 16 | input[type="month"], 17 | input[type="time"], 18 | input[type="datetime"], 19 | input[type="datetime-local"], 20 | input[type="week"], 21 | input[type="number"], 22 | input[type="search"], 23 | input[type="tel"], 24 | input[type="color"], 25 | select, 26 | textarea { 27 | @extend label; 28 | 29 | display: inline-block; 30 | background-color: $input-background-color; 31 | color: $input-text-color; 32 | padding: .2rem .3rem; 33 | margin-bottom: 5px; 34 | line-height: normal; 35 | border: 0; 36 | transition: all .3s linear; 37 | 38 | &:focus { 39 | outline: 0; 40 | background-color: darken($input-background-color, 5%); 41 | } 42 | 43 | &::-webkit-input-placeholder, 44 | &:-moz-placeholder, 45 | &::-moz-placeholder, 46 | &:-ms-input-placeholder { 47 | color: $input-placeholder-color; 48 | font-style: italic; 49 | } 50 | 51 | &[disabled], 52 | &[readonly] { 53 | font-style: italic; 54 | color: darken($input-background-color, 15%); 55 | } 56 | 57 | &[disabled] { 58 | cursor: not-allowed; 59 | &:focus { 60 | background-color: $input-background-color; 61 | } 62 | } 63 | } 64 | 65 | button[type="submit"], 66 | button[type="reset"], 67 | input[type="submit"], 68 | input[type="reset"] { 69 | @extend .btn; 70 | } 71 | 72 | button[type="submit"], 73 | input[type="submit"]{ 74 | @extend .btn-primary; 75 | } 76 | -------------------------------------------------------------------------------- /client/styles/modules/_lists.scss: -------------------------------------------------------------------------------- 1 | ul, 2 | ol { 3 | padding: 0; 4 | margin: 0 0 30px 25px; 5 | } 6 | 7 | ul ul, 8 | ul ol, 9 | ol ol, 10 | ol ul { 11 | margin-bottom: 0; 12 | } 13 | -------------------------------------------------------------------------------- /client/styles/modules/_misc.scss: -------------------------------------------------------------------------------- 1 | ::selection{ 2 | background: $primary-color; 3 | color: #fff; 4 | text-shadow: none; 5 | } 6 | -------------------------------------------------------------------------------- /client/styles/modules/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /** 47 | * Address `[hidden]` styling not present in IE 8/9. 48 | * Hide the `template` element in IE, Safari, and Firefox < 22. 49 | */ 50 | 51 | [hidden], 52 | template { 53 | display: none; 54 | } 55 | 56 | /* ========================================================================== 57 | Base 58 | ========================================================================== */ 59 | 60 | /** 61 | * 1. Set default font family to sans-serif. 62 | * 2. Prevent iOS text size adjust after orientation change, without disabling 63 | * user zoom. 64 | */ 65 | 66 | html { 67 | font-family: sans-serif; /* 1 */ 68 | -ms-text-size-adjust: 100%; /* 2 */ 69 | -webkit-text-size-adjust: 100%; /* 2 */ 70 | } 71 | 72 | /** 73 | * Remove default margin. 74 | */ 75 | 76 | body { 77 | margin: 0; 78 | } 79 | 80 | /* ========================================================================== 81 | Links 82 | ========================================================================== */ 83 | 84 | /** 85 | * Remove the gray background color from active links in IE 10. 86 | */ 87 | 88 | a { 89 | background: transparent; 90 | } 91 | 92 | /** 93 | * Address `outline` inconsistency between Chrome and other browsers. 94 | */ 95 | 96 | a:focus { 97 | outline: thin dotted; 98 | } 99 | 100 | /** 101 | * Improve readability when focused and also mouse hovered in all browsers. 102 | */ 103 | 104 | a:active, 105 | a:hover { 106 | outline: 0; 107 | } 108 | 109 | /* ========================================================================== 110 | Typography 111 | ========================================================================== */ 112 | 113 | /** 114 | * Address variable `h1` font-size and margin within `section` and `article` 115 | * contexts in Firefox 4+, Safari 5, and Chrome. 116 | */ 117 | 118 | h1 { 119 | font-size: 2em; 120 | margin: 0.67em 0; 121 | } 122 | 123 | /** 124 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 125 | */ 126 | 127 | abbr[title] { 128 | border-bottom: 1px dotted; 129 | } 130 | 131 | /** 132 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 133 | */ 134 | 135 | b, 136 | strong { 137 | font-weight: bold; 138 | } 139 | 140 | /** 141 | * Address styling not present in Safari 5 and Chrome. 142 | */ 143 | 144 | dfn { 145 | font-style: italic; 146 | } 147 | 148 | /** 149 | * Address differences between Firefox and other browsers. 150 | */ 151 | 152 | hr { 153 | -moz-box-sizing: content-box; 154 | box-sizing: content-box; 155 | height: 0; 156 | } 157 | 158 | /** 159 | * Address styling not present in IE 8/9. 160 | */ 161 | 162 | mark { 163 | background: #ff0; 164 | color: #000; 165 | } 166 | 167 | /** 168 | * Correct font family set oddly in Safari 5 and Chrome. 169 | */ 170 | 171 | code, 172 | kbd, 173 | pre, 174 | samp { 175 | font-family: monospace, serif; 176 | font-size: 1em; 177 | } 178 | 179 | /** 180 | * Improve readability of pre-formatted text in all browsers. 181 | */ 182 | 183 | pre { 184 | white-space: pre-wrap; 185 | } 186 | 187 | /** 188 | * Set consistent quote types. 189 | */ 190 | 191 | q { 192 | quotes: "\201C" "\201D" "\2018" "\2019"; 193 | } 194 | 195 | /** 196 | * Address inconsistent and variable font size in all browsers. 197 | */ 198 | 199 | small { 200 | font-size: 80%; 201 | } 202 | 203 | /** 204 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 205 | */ 206 | 207 | sub, 208 | sup { 209 | font-size: 75%; 210 | line-height: 0; 211 | position: relative; 212 | vertical-align: baseline; 213 | } 214 | 215 | sup { 216 | top: -0.5em; 217 | } 218 | 219 | sub { 220 | bottom: -0.25em; 221 | } 222 | 223 | /* ========================================================================== 224 | Embedded content 225 | ========================================================================== */ 226 | 227 | /** 228 | * Remove border when inside `a` element in IE 8/9. 229 | */ 230 | 231 | img { 232 | border: 0; 233 | } 234 | 235 | /** 236 | * Correct overflow displayed oddly in IE 9. 237 | */ 238 | 239 | svg:not(:root) { 240 | overflow: hidden; 241 | } 242 | 243 | /* ========================================================================== 244 | Figures 245 | ========================================================================== */ 246 | 247 | /** 248 | * Address margin not present in IE 8/9 and Safari 5. 249 | */ 250 | 251 | figure { 252 | margin: 0; 253 | } 254 | 255 | /* ========================================================================== 256 | Forms 257 | ========================================================================== */ 258 | 259 | /** 260 | * Define consistent border, margin, and padding. 261 | */ 262 | 263 | fieldset { 264 | border: 1px solid #c0c0c0; 265 | margin: 0 2px; 266 | padding: 0.35em 0.625em 0.75em; 267 | } 268 | 269 | /** 270 | * 1. Correct `color` not being inherited in IE 8/9. 271 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 272 | */ 273 | 274 | legend { 275 | border: 0; /* 1 */ 276 | padding: 0; /* 2 */ 277 | } 278 | 279 | /** 280 | * 1. Correct font family not being inherited in all browsers. 281 | * 2. Correct font size not being inherited in all browsers. 282 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 283 | */ 284 | 285 | button, 286 | input, 287 | select, 288 | textarea { 289 | font-family: inherit; /* 1 */ 290 | font-size: 100%; /* 2 */ 291 | margin: 0; /* 3 */ 292 | } 293 | 294 | /** 295 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 296 | * the UA stylesheet. 297 | */ 298 | 299 | button, 300 | input { 301 | line-height: normal; 302 | } 303 | 304 | /** 305 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 306 | * All other form control elements do not inherit `text-transform` values. 307 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 308 | * Correct `select` style inheritance in Firefox 4+ and Opera. 309 | */ 310 | 311 | button, 312 | select { 313 | text-transform: none; 314 | } 315 | 316 | /** 317 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 318 | * and `video` controls. 319 | * 2. Correct inability to style clickable `input` types in iOS. 320 | * 3. Improve usability and consistency of cursor style between image-type 321 | * `input` and others. 322 | */ 323 | 324 | button, 325 | html input[type="button"], /* 1 */ 326 | input[type="reset"], 327 | input[type="submit"] { 328 | -webkit-appearance: button; /* 2 */ 329 | cursor: pointer; /* 3 */ 330 | } 331 | 332 | /** 333 | * Re-set default cursor for disabled elements. 334 | */ 335 | 336 | button[disabled], 337 | html input[disabled] { 338 | cursor: default; 339 | } 340 | 341 | /** 342 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 343 | * 2. Remove excess padding in IE 8/9/10. 344 | */ 345 | 346 | input[type="checkbox"], 347 | input[type="radio"] { 348 | box-sizing: border-box; /* 1 */ 349 | padding: 0; /* 2 */ 350 | } 351 | 352 | /** 353 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 354 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 355 | * (include `-moz` to future-proof). 356 | */ 357 | 358 | input[type="search"] { 359 | -webkit-appearance: textfield; /* 1 */ 360 | -moz-box-sizing: content-box; 361 | -webkit-box-sizing: content-box; /* 2 */ 362 | box-sizing: content-box; 363 | } 364 | 365 | /** 366 | * Remove inner padding and search cancel button in Safari 5 and Chrome 367 | * on OS X. 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Remove inner padding and border in Firefox 4+. 377 | */ 378 | 379 | button::-moz-focus-inner, 380 | input::-moz-focus-inner { 381 | border: 0; 382 | padding: 0; 383 | } 384 | 385 | /** 386 | * 1. Remove default vertical scrollbar in IE 8/9. 387 | * 2. Improve readability and alignment in all browsers. 388 | */ 389 | 390 | textarea { 391 | overflow: auto; /* 1 */ 392 | vertical-align: top; /* 2 */ 393 | } 394 | 395 | /* ========================================================================== 396 | Tables 397 | ========================================================================== */ 398 | 399 | /** 400 | * Remove most spacing between table cells. 401 | */ 402 | 403 | table { 404 | border-collapse: collapse; 405 | border-spacing: 0; 406 | } 407 | -------------------------------------------------------------------------------- /client/styles/modules/_print.scss: -------------------------------------------------------------------------------- 1 | @media print { 2 | * { 3 | background: transparent !important; 4 | color: #000 !important; 5 | box-shadow: none !important; 6 | text-shadow: none !important; 7 | } 8 | a, 9 | a:visited { 10 | text-decoration: underline; 11 | } 12 | a[href]:after { 13 | content: " (" attr(href) ")"; 14 | } 15 | abbr[title]:after { 16 | content: " (" attr(title) ")"; 17 | } 18 | .ir a:after, 19 | a[href^="javascript:"]:after, 20 | a[href^="#"]:after { 21 | content: ""; 22 | } 23 | pre, 24 | blockquote { 25 | border: 1px solid #999; 26 | page-break-inside: avoid; 27 | } 28 | thead { 29 | display: table-header-group; 30 | } 31 | tr, 32 | img { 33 | page-break-inside: avoid; 34 | } 35 | img { 36 | max-width: 100% !important; 37 | } 38 | @page { 39 | margin: 0.5cm; 40 | } 41 | p, 42 | h2, 43 | h3 { 44 | orphans: 3; 45 | widows: 3; 46 | } 47 | h2, 48 | h3 { 49 | page-break-after: avoid; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /client/styles/modules/_quotes.scss: -------------------------------------------------------------------------------- 1 | blockquote { 2 | font-style: italic; 3 | border-left: 5px solid $primary-color; 4 | padding-left: 20px; 5 | padding-bottom: 3px; 6 | margin: 0 0 30px -25px; 7 | 8 | p { 9 | margin: 0; 10 | } 11 | 12 | &.pullquote { 13 | padding: 0; 14 | line-height: 1.4; 15 | text-align: center; 16 | font-size: 28px; 17 | margin: 48px -90px; 18 | border-left: none; 19 | 20 | @include breakpoint(small) { 21 | margin: 30px 0; 22 | font-size: 22px; 23 | } 24 | } 25 | 26 | @include breakpoint(small) { 27 | border-left: none; 28 | margin: 30px 0; 29 | font-size: 22px; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/styles/modules/_tables.scss: -------------------------------------------------------------------------------- 1 | table { 2 | min-width: 100%; 3 | text-align: left; 4 | margin-bottom: 30px; 5 | } 6 | 7 | td, th { 8 | padding: .3rem 1rem; 9 | } 10 | 11 | tbody > tr:nth-child(odd) { 12 | background-color: $line-breaks-color; 13 | border-top: 1px solid $muted-text-color; 14 | border-bottom: 1px solid $muted-text-color; 15 | } 16 | -------------------------------------------------------------------------------- /client/styles/modules/_typography.scss: -------------------------------------------------------------------------------- 1 | a { 2 | color: $text-color; 3 | transition: color .3s; 4 | 5 | &:focus, 6 | &:hover, 7 | &:active { 8 | outline: 0; 9 | } 10 | 11 | &:focus, 12 | &:hover { 13 | color: $primary-color; 14 | } 15 | } 16 | 17 | p, 18 | pre { 19 | margin: 0 0 30px 0; 20 | } 21 | 22 | hr { 23 | width: 20%; 24 | border: 1px solid $line-breaks-color; 25 | margin-top: 0; 26 | margin-bottom: 30px; 27 | } 28 | 29 | dl dt { 30 | font-weight: 700; 31 | } 32 | 33 | h1, .h1, 34 | h2, .h2, 35 | h3, .h3, 36 | h4, .h4, 37 | h5, .h5, 38 | h6, .h6 { 39 | margin: 0; 40 | } 41 | 42 | h1, .h1, 43 | h2, .h2, 44 | h3, .h3, 45 | h4, .h4 { 46 | font-family: $title-font-family; 47 | font-weight: 700; 48 | word-wrap: break-word; 49 | } 50 | 51 | h1, .h1 { 52 | font-size: 48.5px; 53 | text-indent: -2px; 54 | letter-spacing: -1px; 55 | line-height: 1.2; 56 | 57 | @include breakpoint(small) { 58 | font-size: 30.5px; 59 | } 60 | } 61 | 62 | h2, .h2 { 63 | font-size: 31.5px; 64 | line-height: 1.2; 65 | 66 | @include breakpoint(small) { 67 | font-size: 24px; 68 | } 69 | } 70 | 71 | h3, .h3 { 72 | font-size: 26.5px; 73 | line-height: 1.2; 74 | 75 | @include breakpoint(small) { 76 | font-size: 20px; 77 | } 78 | } 79 | 80 | h4, .h4 { 81 | font-size: 22.5px; 82 | 83 | @include breakpoint(small) { 84 | font-size: 17.5px; 85 | } 86 | } 87 | 88 | h5, .h5 { 89 | font-size: 17.5px; 90 | } 91 | 92 | h6, .h6 { 93 | font-size: 14.5px; 94 | } 95 | -------------------------------------------------------------------------------- /client/styles/modules/_utils.scss: -------------------------------------------------------------------------------- 1 | %clearfix { 2 | content: ' '; 3 | display: table; 4 | } 5 | 6 | .clearfix { 7 | *zoom: 1; 8 | 9 | &:before { 10 | @extend %clearfix; 11 | } 12 | 13 | &:after { 14 | @extend %clearfix; 15 | clearfix: both; 16 | } 17 | } 18 | 19 | .center-block { 20 | display: block; 21 | margin-right: auto; 22 | margin-left: auto; 23 | } 24 | 25 | .pull-right { 26 | float: right !important; 27 | } 28 | 29 | .pull-left { 30 | float: left !important; 31 | } 32 | 33 | .hide { 34 | display: none !important; 35 | } 36 | 37 | .show { 38 | display: block !important; 39 | } 40 | 41 | .invisible { 42 | visibility: hidden; 43 | } 44 | 45 | .text-hide { 46 | font: 0/0 a; 47 | color: transparent; 48 | text-shadow: none; 49 | background-color: transparent; 50 | border: 0; 51 | } 52 | 53 | .hidden { 54 | display: none !important; 55 | visibility: hidden !important; 56 | } 57 | 58 | .text-left { 59 | text-align: left; 60 | } 61 | 62 | .text-right { 63 | text-align: right; 64 | } 65 | 66 | .text-center { 67 | text-align: center; 68 | } 69 | 70 | .text-justify { 71 | text-align: justify; 72 | } 73 | -------------------------------------------------------------------------------- /client/styles/modules/_vars.scss: -------------------------------------------------------------------------------- 1 | //----------------------------------*\ 2 | // BREAKPOINTS 3 | //----------------------------------*/ 4 | 5 | $breakpoint-xx-small: 320px - 1px; 6 | $breakpoint-x-small: 480px - 1px; 7 | $breakpoint-small: 768px - 1px; 8 | $breakpoint-medium: 1024px - 1px; 9 | $breakpoint-large: 1140px - 1px; 10 | $breakpoint-x-large: 1280px - 1px; 11 | $breakpoint-xx-large: 1444px - 1px; 12 | 13 | 14 | 15 | //----------------------------------*\ 16 | // PALLETE 17 | //----------------------------------*/ 18 | 19 | $background-color: #FFF; 20 | $text-color: #333; 21 | $muted-text-color: #B3B3B3; 22 | $line-breaks-color: #F2F2F2; 23 | 24 | $primary-color: #57A3E8; 25 | 26 | //----------------------------------*\ 27 | // TYPO 28 | //----------------------------------*/ 29 | 30 | $title-font-family: "ProximaNova", "Helvetica Neue", Helvetica, Arial, sans-serif; 31 | 32 | $text-font-family: "ProximaNova", "Helvetica Neue", Helvetica, Arial, sans-serif; 33 | 34 | //----------------------------------*\ 35 | // DRAWER 36 | //----------------------------------*/ 37 | 38 | $drawer-background-color: #FFF; 39 | $drawer-text-color: #909090; 40 | $drawer-hover-color: #DDD; 41 | 42 | //----------------------------------*\ 43 | // INPUTS 44 | //----------------------------------*/ 45 | 46 | $input-font-family: $title-font-family; 47 | $input-background-color: $line-breaks-color; 48 | $input-text-color: $text-color; 49 | $input-placeholder-color: $muted-text-color; 50 | -------------------------------------------------------------------------------- /client/styles/partials/_apps.scss: -------------------------------------------------------------------------------- 1 | .apps { 2 | 3 | h1 { 4 | text-align: center; 5 | color: #FFF; 6 | margin: 0 auto; 7 | padding: 4.5% 0; 8 | } 9 | 10 | .break-line { 11 | border-top: 2px solid #333; 12 | margin-bottom: 30px; 13 | } 14 | 15 | > div > h4 { 16 | text-align: center; 17 | } 18 | 19 | .background { 20 | position: absolute; 21 | width: 100%; 22 | height: 250px; 23 | background: #64c235; 24 | z-index: -1; 25 | } 26 | 27 | .form-block { 28 | background: #FFF; 29 | width: 100%; 30 | max-width: 428px; 31 | margin: 0 auto; 32 | padding: 30px 40px; 33 | text-align: center; 34 | } 35 | 36 | .container { 37 | width: 100%; 38 | 39 | @include breakpoint(small) { 40 | width: 95%; 41 | } 42 | 43 | @include breakpoint(medium) { 44 | width: 970px; 45 | } 46 | } 47 | 48 | .headerContainer { 49 | color: #FFF; 50 | /* Make the masthead heading the same height as the navigation */ 51 | h2 { 52 | margin-top: 30px; 53 | margin-bottom: 0; 54 | line-height: 70px; 55 | padding-bottom: 0; 56 | } 57 | 58 | li { 59 | margin-top: 25px; 60 | line-height: 70px; 61 | } 62 | 63 | a { 64 | background: none !important; 65 | color: #FFF; 66 | 67 | &:hover { 68 | text-decoration: none; 69 | } 70 | } 71 | 72 | ul > li { 73 | a:hover { 74 | text-decoration: underline; 75 | } 76 | 77 | &.dropdown { 78 | a:hover { 79 | text-decoration: none; 80 | } 81 | } 82 | 83 | ul { 84 | margin-top: -25px; 85 | 86 | li { 87 | margin-top: 0; 88 | 89 | a { 90 | line-height: 32px; 91 | padding-bottom: 8px; 92 | background: none !important; 93 | color: #333333; 94 | 95 | &:hover { 96 | text-decoration: underline !important; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | .mainContainer { 105 | background: #FFF; 106 | border: 1px solid #000; 107 | border-radius: 4px; 108 | 109 | .header { 110 | border-bottom: 1px solid #000; 111 | margin-left: -15px; 112 | margin-right: -15px; 113 | 114 | .dropdown-toggle { 115 | border-right: 1px solid #000; 116 | border-radius: 0; 117 | border-top-left-radius: 4px; 118 | } 119 | 120 | h4 { 121 | padding: 4px 0 0 15px; 122 | } 123 | 124 | a { 125 | background: none !important; 126 | color: #777; 127 | } 128 | 129 | a:hover { 130 | text-decoration: underline; 131 | } 132 | 133 | .active { 134 | font-weight: bold; 135 | } 136 | } 137 | 138 | .nestednestedview { 139 | ol { 140 | clear: both; 141 | margin: 3.2em 0 0 4.2em; 142 | padding-left: 0; 143 | counter-reset: li; 144 | list-style: decimal; 145 | 146 | li { 147 | font-size: 16px; 148 | line-height: 1.5em; 149 | position: relative; 150 | z-index: 200; 151 | list-style: none; 152 | margin-bottom: 4em; 153 | 154 | > textarea { 155 | display: block; 156 | width: 100%; 157 | height: 220px; 158 | margin-top: 20px; 159 | background-color: #f5f5f5; 160 | border: 1px solid #ccc; 161 | cursor: not-allowed; 162 | color: #666; 163 | font-size: 13px; 164 | line-height: 18px; 165 | padding: 4px; 166 | border-radius: 3px; 167 | overflow: auto; 168 | vertical-align: top; 169 | } 170 | } 171 | 172 | li:before { 173 | -webkit-box-sizing: border-box; 174 | -moz-box-sizing: border-box; 175 | -ms-box-sizing: border-box; 176 | box-sizing: border-box; 177 | content: counter(li); 178 | counter-increment: li; 179 | position: absolute; 180 | top: -0.45em; 181 | left: -3em; 182 | width: 2em; 183 | height: 2em; 184 | margin-right: 8px; 185 | padding: .42em 0; 186 | color: #fff; 187 | background: #ffcd4d; 188 | font-size: 21px; 189 | font-weight: bold; 190 | text-align: center; 191 | -webkit-border-radius: 1em; 192 | -moz-border-radius: 1em; 193 | border-radius: 1em; 194 | } 195 | } 196 | 197 | ol:before, ol:after { 198 | display: table; 199 | content: ""; 200 | } 201 | 202 | ol:after { 203 | clear: both; 204 | } 205 | } 206 | } 207 | 208 | .containerFooter { 209 | padding: 0; 210 | 211 | .nav-pills > li { 212 | 213 | span { 214 | height: 45px; 215 | line-height: 45px; 216 | } 217 | 218 | &.pull-right { 219 | padding-right: 15px; 220 | } 221 | 222 | a { 223 | background: none !important; 224 | color: #777; 225 | } 226 | 227 | a:hover { 228 | text-decoration: underline; 229 | } 230 | } 231 | } 232 | } -------------------------------------------------------------------------------- /client/styles/partials/_log-forms.scss: -------------------------------------------------------------------------------- 1 | .log-form { 2 | 3 | h1 { 4 | text-align: center; 5 | color: #FFF; 6 | margin: 0 auto; 7 | padding: 4.5% 0; 8 | } 9 | 10 | > div > h4 { 11 | text-align: center; 12 | } 13 | 14 | .background { 15 | position: absolute; 16 | width: 100%; 17 | height: 250px; 18 | background: #AAA; 19 | z-index: -1; 20 | } 21 | 22 | .form-block { 23 | background: #FFF; 24 | width: 100%; 25 | max-width: 428px; 26 | margin: 0 auto; 27 | padding: 30px 40px; 28 | text-align: center; 29 | } 30 | 31 | .social a { 32 | text-decoration: none; 33 | 34 | i:last-child { 35 | color: #FFF; 36 | } 37 | } 38 | 39 | .social a.fb_connect > span > i:first-child { 40 | color: #3B5998; 41 | } 42 | .social a.go_connect > span > i:first-child { 43 | color: #D34836; 44 | } 45 | .social a.tw_connect > span > i:first-child { 46 | color: #4099FF; 47 | } 48 | 49 | .form_row_password { 50 | position: relative; 51 | 52 | a { 53 | position: absolute; 54 | right: 10px; 55 | top: 5px; 56 | text-decoration: none; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /client/styles/partials/_main.scss: -------------------------------------------------------------------------------- 1 | .browsehappy { 2 | margin: 0.2em 0; 3 | background: #ccc; 4 | color: #000; 5 | padding: 0.2em 0; 6 | } 7 | 8 | /* Space out content a bit */ 9 | body { 10 | background: #f0f2f4; 11 | } 12 | 13 | /* Everything but the jumbotron gets side spacing for mobile first views */ 14 | .header, 15 | .marketing, 16 | .footer { 17 | padding-left: 15px; 18 | padding-right: 15px; 19 | } 20 | 21 | /* Custom page header */ 22 | .header { 23 | 24 | /* Make the masthead heading the same height as the navigation */ 25 | h3 { 26 | margin-top: 0; 27 | margin-bottom: 0; 28 | line-height: 40px; 29 | padding-bottom: 19px; 30 | } 31 | 32 | .nav > li > a > img { 33 | max-height: 34px; 34 | } 35 | } 36 | a:hover { 37 | cursor: pointer; 38 | } 39 | 40 | /* Custom page footer */ 41 | .footer { 42 | padding-top: 19px; 43 | color: #777; 44 | border-top: 1px solid #e5e5e5; 45 | } 46 | 47 | .container-narrow > hr { 48 | margin: 30px 0; 49 | } 50 | 51 | /* Main marketing message and sign up button */ 52 | .jumbotron { 53 | text-align: center; 54 | border-bottom: 1px solid #e5e5e5; 55 | 56 | .btn { 57 | font-size: 21px; 58 | padding: 14px 24px; 59 | } 60 | } 61 | 62 | /* Supporting marketing content */ 63 | .marketing { 64 | margin: 40px 0; 65 | 66 | p + h4 { 67 | margin-top: 28px; 68 | } 69 | } 70 | 71 | /* Responsive: Portrait tablets and up */ 72 | @media screen and (min-width: 768px) { 73 | 74 | /* Remove the padding we set earlier */ 75 | .header, 76 | .marketing, 77 | .footer { 78 | padding-left: 0; 79 | padding-right: 0; 80 | } 81 | /* Space out the masthead */ 82 | .header { 83 | margin-bottom: 30px; 84 | } 85 | /* Remove the bottom border on the jumbotron for visual effect */ 86 | .jumbotron { 87 | border-bottom: 0; 88 | } 89 | } -------------------------------------------------------------------------------- /client/views/about.html: -------------------------------------------------------------------------------- 1 |

This is the about view.

2 | -------------------------------------------------------------------------------- /client/views/home.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

App  

4 | 5 | 21 |
22 |
23 | 24 |
25 |

'Allo, 'Allo!

26 |

27 | I'm Yeoman
28 | Always a pleasure scaffolding your apps. 29 |

30 |

Splendid!

31 |
32 | 33 |
34 |

HTML5 Boilerplate

35 |

36 | HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites. 37 |

38 | 39 |

Angular

40 |

41 | AngularJS is a toolset for building the framework most suited to your application development. 42 |

43 | 44 |

Karma

45 |

Spectacular Test Runner for JavaScript.

46 |
47 | -------------------------------------------------------------------------------- /client/views/signin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

App

5 | 6 |
7 |
8 |
9 |

Sign in to continue

10 |

Link to existing account

11 |
12 | 13 | 14 |
15 | 16 |
17 | 18 | 19 | ? 20 | 21 |
22 |
23 | 24 | 25 | Create an Account 26 | 27 | 28 | Create a New Account 29 | 30 |
31 |
32 | 33 |

Alternatively, you can log in using:

34 |
35 | 36 | 56 |
57 | 58 | 59 | 60 |
61 | 62 |
63 |
64 |
65 | 66 |

Create an account

67 |
68 | 69 | 70 |
71 | 72 |
73 | 74 |
75 | 76 | 77 | 78 | 79 | 80 |
81 |
82 |
83 | 84 | 85 |
86 | 87 | 88 |
89 | 90 |
91 |
92 | 93 |

...or create an account with:

94 |
95 | 96 | 116 |
117 |
118 | 119 | 121 |

Already have an account? Log in

122 | 123 |
124 | 125 |
126 | 127 |

Reset your password

128 |
129 | 130 |

If you have forgotten your password, we will send you an email to reset your password.

131 | 132 |
133 | 134 | 135 |
136 | 137 | 138 | 139 |
140 | 141 |
142 | 143 |
144 |
145 |
146 |

Sign in to continue

147 |

Update your password

148 |
149 | 150 | 151 |
152 | 153 | 154 |
155 | 156 | 157 | 158 |
159 |
160 |
-------------------------------------------------------------------------------- /common/models/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user", 3 | "base": "User", 4 | "validations": [], 5 | "properties": { 6 | "photo": { 7 | "type": "string" 8 | } 9 | }, 10 | "relations": { 11 | "identities": { 12 | "type": "hasMany", 13 | "model": "userIdentity", 14 | "foreignKey": "userId" 15 | }, 16 | "credentials": { 17 | "type": "hasMany", 18 | "model": "userCredential", 19 | "foreignKey": "userId" 20 | } 21 | }, 22 | "acls": [ 23 | { 24 | "accessType": "*", 25 | "permission": "ALLOW", 26 | "principalType": "ROLE", 27 | "principalId": "$owner" 28 | }, 29 | { 30 | "property": "changes", 31 | "permission": "ALLOW", 32 | "principalType": "ROLE", 33 | "principalId": "$everyone" 34 | }, 35 | { 36 | "property": "diff", 37 | "permission": "ALLOW", 38 | "principalType": "ROLE", 39 | "principalId": "$everyone" 40 | }, 41 | { 42 | "property": "createUpdates", 43 | "permission": "ALLOW", 44 | "principalType": "ROLE", 45 | "principalId": "$everyone" 46 | }, 47 | { 48 | "property": "checkpoint", 49 | "permission": "ALLOW", 50 | "principalType": "ROLE", 51 | "principalId": "$everyone" 52 | }, 53 | { 54 | "property": "currentCheckpoint", 55 | "permission": "ALLOW", 56 | "principalType": "ROLE", 57 | "principalId": "$everyone" 58 | }, 59 | { 60 | "property": "bulkUpdate", 61 | "permission": "ALLOW", 62 | "principalType": "ROLE", 63 | "principalId": "$everyone" 64 | }, 65 | { 66 | "property": "rectifyAllChanges", 67 | "permission": "ALLOW", 68 | "principalType": "ROLE", 69 | "principalId": "$everyone" 70 | }, 71 | { 72 | "property": "rectifyChange", 73 | "permission": "ALLOW", 74 | "principalType": "ROLE", 75 | "principalId": "$everyone" 76 | }, 77 | { 78 | "property": "find", 79 | "permission": "ALLOW", 80 | "principalType": "ROLE", 81 | "principalId": "$everyone" 82 | }, 83 | { 84 | "property": "findOne", 85 | "permission": "ALLOW", 86 | "principalType": "ROLE", 87 | "principalId": "$everyone" 88 | }, 89 | { 90 | "property": "findById", 91 | "permission": "ALLOW", 92 | "principalType": "ROLE", 93 | "principalId": "$everyone" 94 | }, 95 | { 96 | "property": "create", 97 | "permission": "ALLOW", 98 | "principalType": "ROLE", 99 | "principalId": "$everyone" 100 | }, 101 | { 102 | "property": "login", 103 | "permission": "ALLOW", 104 | "principalType": "ROLE", 105 | "principalId": "$everyone" 106 | }, 107 | { 108 | "property": "logout", 109 | "permission": "ALLOW", 110 | "principalType": "ROLE", 111 | "principalId": "$everyone" 112 | }, 113 | { 114 | "property": "confirm", 115 | "permission": "ALLOW", 116 | "principalType": "ROLE", 117 | "principalId": "$everyone" 118 | }, 119 | { 120 | "property": "resetPassword", 121 | "accessType": "*", 122 | "permission": "ALLOW", 123 | "principalType": "ROLE", 124 | "principalId": "$everyone" 125 | }, 126 | { 127 | "property": "createAndLink", 128 | "accessType": "*", 129 | "permission": "ALLOW", 130 | "principalType": "ROLE", 131 | "principalId": "$everyone" 132 | }, 133 | { 134 | "property": "link", 135 | "accessType": "*", 136 | "permission": "ALLOW", 137 | "principalType": "ROLE", 138 | "principalId": "$everyone" 139 | } 140 | ], 141 | "methods": [], 142 | "properties": {} 143 | } -------------------------------------------------------------------------------- /common/models/userCredential.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userCredential", 3 | "base": "UserCredential", 4 | "validations": [], 5 | "relations": { 6 | "user": { 7 | "type": "belongsTo", 8 | "model": "user", 9 | "foreignKey": "userId" 10 | } 11 | }, 12 | "acls": [], 13 | "methods": [], 14 | "properties": {} 15 | } -------------------------------------------------------------------------------- /common/models/userIdentity.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userIdentity", 3 | "base": "UserIdentity", 4 | "validations": [], 5 | "relations": { 6 | "user": { 7 | "type": "belongsTo", 8 | "model": "user", 9 | "foreignKey": "userId" 10 | } 11 | }, 12 | "acls": [], 13 | "methods": [], 14 | "properties": {} 15 | } -------------------------------------------------------------------------------- /global-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Global configuration shared by components. 3 | */ 4 | 5 | var pkg = require('./package.json'); 6 | 7 | // The path where to mount the REST API app 8 | exports.restApiRoot = '/api'; 9 | 10 | // The URL where the browser client can access the REST API is available 11 | // Replace with a full url (including hostname) if your client is being 12 | // served from a different server than your REST API. 13 | exports.restApiUrl = exports.restApiRoot; 14 | -------------------------------------------------------------------------------- /newrelic.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | app_name : ['PROJECT-NAME'], 3 | license_key : 'LICENSE-KEY', 4 | logging : { 5 | level : 'info' 6 | }, 7 | rules : { 8 | ignore : [ 9 | '^/socket.io/.*/xhr-polling', 10 | '^/heartbeat.html' 11 | ] 12 | } 13 | }; -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=one:8m max_size=3000m inactive=600m; 2 | 3 | gzip_comp_level 6; 4 | gzip_vary on; 5 | gzip_min_length 1000; 6 | gzip_proxied any; 7 | gzip_types text/plain text/html text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 8 | gzip_buffers 16 8k; 9 | 10 | upstream my_app_upstream { 11 | server localhost:3000; 12 | keepalive 64; 13 | } 14 | 15 | server { 16 | listen 80 default_server; 17 | listen [::]:80 default_server ipv6only=on; 18 | 19 | server_name localhost; 20 | root /home/jonnybgod/ori; 21 | passenger_app_root /home/jonnybgod/ori; 22 | passenger_enabled on; 23 | 24 | passenger_app_type node; 25 | #passenger_app_env development; 26 | passenger_min_instances 1; 27 | passenger_startup_file server.js; 28 | 29 | passenger_user app; 30 | passenger_sticky_sessions on; 31 | passenger_set_cgi_param _PASSENGER_NODE_CONTROL_SERVER 1; 32 | 33 | passenger_restart_dir /home/jonnybgod/ori; 34 | 35 | access_log /dev/null; 36 | error_log /dev/null; 37 | 38 | error_page 500 /client/errors/500.html; 39 | error_page 502 /client/errors/502.html; 40 | error_page 404 /client/errors/404.html; 41 | 42 | ### If a bot requests an escaped URL then rewrite the request to SEO4Ajax location 43 | if ($args ~* _escaped_fragment_=) { 44 | rewrite ^(.*)$ /seo4ajax/$1 last; 45 | } 46 | 47 | ### SEO4Ajax location to handle proxyfication to SEO4Ajax API 48 | location /seo4ajax/ { 49 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 50 | proxy_set_header Host api.seo4ajax.com; 51 | proxy_pass http://api.seo4ajax.com/SEO4AJAX_API_KEY/; 52 | } 53 | 54 | location /api { 55 | proxy_redirect off; 56 | proxy_set_header X-Real-IP $remote_addr; 57 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 58 | proxy_set_header X-Forwarded-Proto $scheme; 59 | proxy_set_header host $http_host; 60 | proxy_set_header X-NginX-Proxy true; 61 | proxy_set_header Connection ""; 62 | proxy_http_version 1.1; 63 | proxy_cache one; 64 | proxy_cache_key sfs$request_uri$scheme; 65 | proxy_pass http://my_app_upstream; 66 | } 67 | 68 | location /explorer { 69 | proxy_redirect off; 70 | proxy_set_header X-Real-IP $remote_addr; 71 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 72 | proxy_set_header X-Forwarded-Proto $scheme; 73 | proxy_set_header host $http_host; 74 | proxy_set_header X-NginX-Proxy true; 75 | proxy_set_header Connection ""; 76 | proxy_http_version 1.1; 77 | proxy_cache one; 78 | proxy_cache_key sfs$request_uri$scheme; 79 | proxy_pass http://my_app_upstream; 80 | } 81 | 82 | location /websocket { 83 | proxy_http_version 1.1; 84 | proxy_set_header X-Real-IP $remote_addr; 85 | proxy_set_header Upgrade $http_upgrade; 86 | proxy_set_header Connection 'upgrade'; 87 | proxy_set_header Host $host; 88 | proxy_cache_bypass $http_upgrade; 89 | proxy_pass http://my_app_upstream; 90 | } 91 | 92 | location /bower_components { 93 | try_files $uri =404; 94 | } 95 | 96 | location / { 97 | try_files /website/dist/$uri /website/app/$uri /client/$uri /client/index.html =404; 98 | } 99 | } 100 | 101 | passenger_pre_start http://localhost/; -------------------------------------------------------------------------------- /nginx_ssl.conf: -------------------------------------------------------------------------------- 1 | proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=one:8m max_size=3000m inactive=600m; 2 | 3 | gzip_comp_level 6; 4 | gzip_vary on; 5 | gzip_min_length 1000; 6 | gzip_proxied any; 7 | gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 8 | gzip_buffers 16 8k; 9 | 10 | upstream my_app_upstream { 11 | server 127.0.0.1:3000; 12 | keepalive 64; 13 | } 14 | 15 | server { 16 | listen 80; 17 | server_name localhost; 18 | return 301 https://$server_name$request_uri; 19 | 20 | location ~ \.(php|html)$ { 21 | deny all; 22 | } 23 | 24 | access_log /dev/null; 25 | error_log /dev/null; 26 | } 27 | 28 | server { 29 | listen 443 ssl; 30 | 31 | # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate 32 | ssl_certificate /opt/nginx/ssl_certs/server.pem; 33 | ssl_certificate_key /opt/nginx/ssl_certs/server.key; 34 | ssl_session_timeout 5m; 35 | ssl_session_cache shared:SSL:50m; 36 | 37 | # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits 38 | ssl_dhparam /opt/nginx/ssl_certs/dhparam.pem; 39 | 40 | # intermediate configuration. tweak to your needs. 41 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 42 | ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; 43 | ssl_prefer_server_ciphers on; 44 | 45 | # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) 46 | add_header Strict-Transport-Security max-age=15768000; 47 | 48 | # OCSP Stapling --- 49 | # fetch OCSP records from URL in ssl_certificate and cache them 50 | ssl_stapling on; 51 | ssl_stapling_verify on; 52 | 53 | server_name localhost; 54 | 55 | root /home/jonnybgod/ori; 56 | 57 | index index.html; 58 | 59 | access_log /dev/null; 60 | error_log /dev/null; 61 | 62 | error_page 500 /client/errors/500.html; 63 | error_page 502 /client/errors/502.html; 64 | error_page 404 /client/errors/404.html; 65 | 66 | location /api { 67 | proxy_redirect off; 68 | proxy_set_header X-Real-IP $remote_addr; 69 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 70 | proxy_set_header X-Forwarded-Proto $scheme; 71 | proxy_set_header host $http_host; 72 | proxy_set_header X-NginX-Proxy true; 73 | proxy_set_header Connection ""; 74 | proxy_http_version 1.1; 75 | proxy_cache one; 76 | proxy_cache_key sfs$request_uri$scheme; 77 | proxy_pass http://my_app_upstream; 78 | } 79 | 80 | location /explorer { 81 | proxy_redirect off; 82 | proxy_set_header X-Real-IP $remote_addr; 83 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 84 | proxy_set_header X-Forwarded-Proto $scheme; 85 | proxy_set_header host $http_host; 86 | proxy_set_header X-NginX-Proxy true; 87 | proxy_set_header Connection ""; 88 | proxy_http_version 1.1; 89 | proxy_cache one; 90 | proxy_cache_key sfs$request_uri$scheme; 91 | proxy_pass http://my_app_upstream; 92 | } 93 | 94 | location /websocket { 95 | proxy_http_version 1.1; 96 | proxy_set_header X-Real-IP $remote_addr; 97 | proxy_set_header Upgrade $http_upgrade; 98 | proxy_set_header Connection 'upgrade'; 99 | proxy_set_header Host $host; 100 | proxy_cache_bypass $http_upgrade; 101 | proxy_pass http://my_app_upstream; 102 | } 103 | 104 | location /bower_components { 105 | try_files $uri =404; 106 | } 107 | 108 | ### If a bot requests an escaped URL then rewrite the request to SEO4Ajax location 109 | if ($args ~* _escaped_fragment_=) { 110 | rewrite ^(.*)$ /seo4ajax/$1 last; 111 | } 112 | 113 | ### SEO4Ajax location to handle proxyfication to SEO4Ajax API 114 | location /seo4ajax/ { 115 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 116 | proxy_set_header Host api.seo4ajax.com; 117 | proxy_pass http://api.seo4ajax.com/SEO4AJAX_API_KEY/; 118 | } 119 | 120 | location / { 121 | try_files /website/dist/$uri /website/app/$uri /client/$uri /client/index.html =404; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PROJECT-NAME", 3 | "version": "0.0.0", 4 | "main": "server/server.js", 5 | "dependencies": { 6 | "async": "latest", 7 | "compression": "latest", 8 | "errorhandler": "latest", 9 | "loopback": "latest", 10 | "loopback-boot": "latest", 11 | "loopback-component-passport": "latest", 12 | "loopback-connector-mongodb": "latest", 13 | "loopback-datasource-juggler": "latest", 14 | "newrelic": "latest", 15 | "passport-facebook": "latest", 16 | "passport-google-oauth": "latest", 17 | "passport-twitter": "latest", 18 | "redis": "latest", 19 | "serve-favicon": "latest", 20 | "socket.io": "latest" 21 | }, 22 | "optionalDependencies": { 23 | "loopback-explorer": "latest" 24 | }, 25 | "devDependencies": { 26 | "browserify": "latest", 27 | "connect-livereload": "latest", 28 | "grunt": "latest", 29 | "grunt-angular-translate": "latest", 30 | "grunt-autoprefixer": "latest", 31 | "grunt-aws-sdk": "latest", 32 | "grunt-concurrent": "latest", 33 | "grunt-contrib-clean": "latest", 34 | "grunt-contrib-compass": "latest", 35 | "grunt-contrib-concat": "latest", 36 | "grunt-contrib-copy": "latest", 37 | "grunt-contrib-cssmin": "latest", 38 | "grunt-contrib-htmlmin": "latest", 39 | "grunt-contrib-imagemin": "latest", 40 | "grunt-contrib-jshint": "latest", 41 | "grunt-contrib-uglify": "latest", 42 | "grunt-contrib-watch": "latest", 43 | "grunt-docular": "latest", 44 | "grunt-filerev": "latest", 45 | "grunt-google-cdn": "latest", 46 | "grunt-loopback-sdk-angular": "latest", 47 | "grunt-newer": "latest", 48 | "grunt-ng-annotate": "latest", 49 | "grunt-nodemon": "latest", 50 | "grunt-svgmin": "latest", 51 | "grunt-usemin": "latest", 52 | "grunt-wiredep": "latest", 53 | "jshint-stylish": "latest", 54 | "load-grunt-tasks": "latest", 55 | "time-grunt": "latest" 56 | }, 57 | "engines": { 58 | "node": ">=0.10.0" 59 | }, 60 | "scripts": { 61 | "postinstall": "node_modules/.bin/bower install", 62 | "test": "grunt test" 63 | } 64 | } -------------------------------------------------------------------------------- /server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | module.exports = function enableAuthentication(server) { 2 | // enable authentication 3 | server.enableAuth(); 4 | }; 5 | -------------------------------------------------------------------------------- /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/passport-init.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var utils = require('./../lib/utils'); 3 | 4 | module.exports = function(server) { 5 | // Load the provider configurations 6 | var config = {}; 7 | //try { 8 | config = require(path.resolve(__dirname, '../', 'providers.json')); 9 | //} catch(err) { 10 | // console.error('Please configure your passport strategy in `providers.json`.'); 11 | // console.error('Copy `providers.json.template` to `providers.json` and replace the clientID/clientSecret values with your own.'); 12 | // process.exit(1); 13 | //} 14 | 15 | config["facebook-login"].profileToUser = function(provider, profile) { 16 | // Let's create a user for that 17 | var email = profile.emails && profile.emails[0] && profile.emails[0].value; 18 | if (!email) { 19 | // Fake an e-mail 20 | email = (profile.username || profile.id) + '@loopback.' 21 | + (profile.provider || provider) + '.com'; 22 | } 23 | var username = provider + '.' + (profile.username || profile.id); 24 | var password = utils.generateKey('password'); 25 | 26 | var photo = false; 27 | if (provider.indexOf('facebook') != -1) { 28 | photo = '//graph.facebook.com/'+ profile.id +'/picture?type=normal'; 29 | } 30 | var userObj = { 31 | username: username, 32 | password: password, 33 | email: email, 34 | photo: photo 35 | }; 36 | return userObj; 37 | } 38 | // Initialize passport 39 | var passport = server.passportConfigurator.init(true); 40 | // Set up related models 41 | server.passportConfigurator.setupModels({ 42 | userModel: server.models.user, 43 | userIdentityModel: server.models.userIdentity, 44 | userCredentialModel: server.models.userCredential 45 | }); 46 | // Configure passport strategies for third party auth providers 47 | for(var s in config) { 48 | var c = config[s]; 49 | c.session = c.session !== false; 50 | server.passportConfigurator.configureProvider(s, c); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /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/boot/sockets.js: -------------------------------------------------------------------------------- 1 | module.exports = function startSocket(server) { 2 | 3 | function findForRequest (req, options, cb) { 4 | var id = tokenIdForRequest(req, options); 5 | 6 | if(id) { 7 | server.models.AccessToken.findById(id, function(err, token) { 8 | if(err) { 9 | cb(err); 10 | } else if(token) { 11 | token.validate(function(err, isValid) { 12 | if(err) { 13 | cb(err); 14 | } else if(isValid) { 15 | cb(null, token); 16 | } else { 17 | var e = new Error('Invalid Access Token'); 18 | e.status = e.statusCode = 401; 19 | cb(e); 20 | } 21 | }); 22 | } else { 23 | var e = new Error('Missing Access Token'); 24 | e.status = e.statusCode = 401; 25 | cb(e); 26 | } 27 | }); 28 | } else { 29 | process.nextTick(function() { 30 | var e = new Error('Missing Access Token'); 31 | e.status = e.statusCode = 401; 32 | cb(e); 33 | }); 34 | } 35 | } 36 | 37 | function tokenIdForRequest(req, options) { 38 | var query = options.query || []; 39 | var headers = options.headers || []; 40 | var i = 0; 41 | var length; 42 | var id; 43 | 44 | query = query.concat(['access_token']); 45 | headers = headers.concat(['X-Access-Token', 'authorization']); 46 | 47 | for(length = query.length; i < length; i++) { 48 | id = req.query[query[i]]; 49 | 50 | if(typeof id === 'string') { 51 | return id; 52 | } 53 | } 54 | 55 | for(i = 0, length = headers.length; i < length; i++) { 56 | id = req.headers[headers[i]]; 57 | 58 | if(typeof id === 'string') { 59 | // Add support for oAuth 2.0 bearer token 60 | // http://tools.ietf.org/html/rfc6750 61 | if (id.indexOf('Bearer ') === 0) { 62 | id = id.substring(7); 63 | // Decode from base64 64 | var buf = new Buffer(id, 'base64'); 65 | id = buf.toString('utf8'); 66 | } 67 | return id; 68 | } 69 | } 70 | return null; 71 | } 72 | 73 | server.io.use(function (socket, next) { 74 | if (socket.handshake.accessToken !== undefined) return next(); 75 | findForRequest(socket.handshake, {params: []}, function(err, token) { 76 | socket.handshake.accessToken = token || null; 77 | next(err); 78 | }); 79 | }); 80 | 81 | var chatRoom = server.models.chatRoom; 82 | var User = server.models.user; 83 | var Event = server.models.Event; 84 | var ObjectID = Event.dataSource.ObjectID; 85 | 86 | server.io.on('connection', function (socket) { 87 | socket.userId = socket.handshake.accessToken.userId; 88 | //if(!socket.client.request.user) return; 89 | 90 | /* 91 | When the user sends a chat message, publish it to everyone (including myself) using 92 | Redis' 'pub' client we created earlier. 93 | Notice that we are getting user's name from session. 94 | */ 95 | socket.on('chat', function (data) { 96 | }); 97 | 98 | /* 99 | When a user joins the channel, publish it to everyone (including myself) using 100 | Redis' 'pub' client we created earlier. 101 | Notice that we are getting user's name from session. 102 | */ 103 | socket.on('join', function () { 104 | }); 105 | 106 | socket.on('disconnect', function () { 107 | }); 108 | }); 109 | 110 | /* 111 | Use Redis' 'sub' (subscriber) client to listen to any message from Redis to server. 112 | When a message arrives, send it back to browser using socket.io 113 | */ 114 | server.sub.on('message', function (channel, message) { 115 | 116 | }); 117 | }; -------------------------------------------------------------------------------- /server/boot/static.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = function(server) { 4 | if (process.env.NODE_ENV !== 'development') return; 5 | 6 | var serveDir = server.loopback.static; 7 | 8 | server.use('/bower_components', serveDir(projectPath('bower_components'))); 9 | server.use('/lbclient', serveDir(projectPath('client/lbclient'))); 10 | 11 | server.use('/styles', serveDir(projectPath('client/.tmp/styles'))); 12 | server.use('/', serveDir(projectPath('client'))); 13 | server.use('/', function(req, res) { 14 | res.sendFile(projectPath('client/index.html')); 15 | }); 16 | }; 17 | 18 | function projectPath(relative) { 19 | return path.resolve(__dirname, '../..', relative); 20 | } -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "restApiRoot": "/api", 3 | "host": "0.0.0.0", 4 | "port": 50447, 5 | "url": "http://localhost:50447/" 6 | } -------------------------------------------------------------------------------- /server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "url": "mongodb://USER:PASSWORD@SERVER:PORT/DATABASE", 4 | "name": "db", 5 | "connector": "loopback-connector-mongodb", 6 | "defaultForType": "db" 7 | }, 8 | "mail": { 9 | "name": "mail", 10 | "connector": "mail", 11 | "defaultForType": "mail" 12 | } 13 | } -------------------------------------------------------------------------------- /server/lib/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Internal utilities for models 3 | */ 4 | var crypto = require('crypto'); 5 | var assert = require('assert'); 6 | 7 | /** 8 | * Get the model class 9 | * @param {Function} cls The sub class 10 | * @param {Function} base The base class 11 | * @returns {Function} The resolved class 12 | */ 13 | function getModel(cls, base) { 14 | if (!cls) { 15 | return base; 16 | } 17 | return (cls.prototype instanceof base)? cls: base; 18 | } 19 | 20 | /** 21 | * Generate a key 22 | * @param {String} hmacKey The hmac key, default to 'loopback' 23 | * @param {String} algorithm The algorithm, default to 'sha1' 24 | * @param {String} encoding The string encoding, default to 'hex' 25 | * @returns {String} The generated key 26 | */ 27 | function generateKey(hmacKey, algorithm, encoding) { 28 | assert(hmacKey, 'HMAC key is required'); 29 | algorithm = algorithm || 'sha1'; 30 | encoding = encoding || 'hex'; 31 | var hmac = crypto.createHmac(algorithm, hmacKey); 32 | var buf = crypto.randomBytes(32); 33 | hmac.update(buf); 34 | var key = hmac.digest(encoding); 35 | return key; 36 | } 37 | 38 | var querystring = require('querystring'); 39 | 40 | 41 | function gravatar (email, parameters) { 42 | var baseUrl = '//gravatar.com/avatar/'; 43 | var result = ""; 44 | var convertedQueryString = querystring.stringify(parameters); 45 | 46 | if (convertedQueryString !== ""){ 47 | result = "?" + convertedQueryString; 48 | } 49 | 50 | return baseUrl + crypto.createHash('md5').update(email.toLowerCase().trim()).digest('hex') + result; 51 | } 52 | 53 | exports.gravatar = gravatar; 54 | exports.getModel = getModel; 55 | exports.generateKey = generateKey; -------------------------------------------------------------------------------- /server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "../common/models", 5 | "./models" 6 | ] 7 | }, 8 | "user": { 9 | "dataSource": "db" 10 | }, 11 | "userIdentity": { 12 | "dataSource": "db", 13 | "public": false 14 | }, 15 | "userCredential": { 16 | "dataSource": "db", 17 | "public": false 18 | }, 19 | "AccessToken": { 20 | "dataSource": "db", 21 | "public": false 22 | }, 23 | "ACL": { 24 | "dataSource": "db", 25 | "public": false 26 | }, 27 | "RoleMapping": { 28 | "dataSource": "db", 29 | "public": false 30 | }, 31 | "Role": { 32 | "dataSource": "db", 33 | "public": false 34 | } 35 | } -------------------------------------------------------------------------------- /server/providers.json: -------------------------------------------------------------------------------- 1 | { 2 | "facebook-login": { 3 | "provider": "facebook", 4 | "module": "passport-facebook", 5 | "clientID": "{facebook-client-id}", 6 | "clientSecret": "{facebook-client-secret}", 7 | "callbackURL": "/auth/facebook/callback", 8 | "authPath": "/auth/facebook", 9 | "callbackPath": "/auth/facebook/callback", 10 | "successRedirect": "/", 11 | "failureRedirect": "/", 12 | "scope": ["email", "user_photos"], 13 | "autoLogin": true, 14 | "session": false 15 | }, 16 | "google-login": { 17 | "provider": "google", 18 | "module": "passport-google-oauth", 19 | "strategy": "OAuth2Strategy", 20 | "clientID": "{google-client-id}", 21 | "clientSecret": "{google-client-secret}", 22 | "callbackURL": "/auth/google/callback", 23 | "authPath": "/auth/google", 24 | "callbackPath": "/auth/google/callback", 25 | "successRedirect": "/", 26 | "failureRedirect": "/", 27 | "scope": ["email", "profile"], 28 | "autoLogin": true, 29 | "session": false 30 | }, 31 | "twitter-login": { 32 | "provider": "twitter", 33 | "authScheme": "oauth", 34 | "module": "passport-twitter", 35 | "callbackURL": "/auth/twitter/callback", 36 | "authPath": "/auth/twitter", 37 | "callbackPath": "/auth/twitter/callback", 38 | "successRedirect": "/", 39 | "failureRedirect": "/", 40 | "consumerKey": "{twitter-consumer-key}", 41 | "consumerSecret": "{twitter-consumer-secret}", 42 | "autoLogin": true, 43 | "session": false 44 | }, 45 | "facebook-link": { 46 | "provider": "facebook", 47 | "module": "passport-facebook", 48 | "clientID": "{facebook-client-id}", 49 | "clientSecret": "{facebook-client-secret}", 50 | "callbackURL": "/link/facebook/callback", 51 | "authPath": "/link/facebook", 52 | "callbackPath": "/link/facebook/callback", 53 | "successRedirect": "/", 54 | "failureRedirect": "/", 55 | "scope": ["email", "user_photos"], 56 | "link": true, 57 | "session": false 58 | }, 59 | "google-link": { 60 | "provider": "google", 61 | "module": "passport-google-oauth", 62 | "strategy": "OAuth2Strategy", 63 | "clientID": "{google-client-id}", 64 | "clientSecret": "{google-client-secret}", 65 | "callbackURL": "/link/google/callback", 66 | "authPath": "/link/google", 67 | "callbackPath": "/link/google/callback", 68 | "successRedirect": "/", 69 | "failureRedirect": "/", 70 | "scope": ["email", "profile"], 71 | "link": true, 72 | "session": false 73 | }, 74 | "twitter-link": { 75 | "provider": "twitter", 76 | "authScheme": "oauth", 77 | "module": "passport-twitter", 78 | "callbackURL": "/link/twitter/callback", 79 | "authPath": "/link/twitter", 80 | "callbackPath": "/link/twitter/callback", 81 | "successRedirect": "/", 82 | "failureRedirect": "/", 83 | "consumerKey": "{twitter-consumer-key}", 84 | "consumerSecret": "{twitter-consumer-secret}", 85 | "link": true, 86 | "session": false 87 | } 88 | } -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV !== 'development') { 2 | require('newrelic'); 3 | } 4 | 5 | var loopback = require('loopback'); 6 | var boot = require('loopback-boot'); 7 | 8 | var redis = require('redis'); 9 | 10 | var path = require('path'), 11 | fs = require("fs"); 12 | 13 | var app = module.exports = loopback(); 14 | app.io = require('socket.io')(); 15 | var env = process.env.NODE_ENV || 'production'; 16 | 17 | // Create an instance of PassportConfigurator with the app instance 18 | var PassportConfigurator = require('loopback-component-passport').PassportConfigurator; 19 | app.passportConfigurator = new PassportConfigurator(app); 20 | 21 | /* 22 | Create two redis connections. A 'pub' for publishing and a 'sub' for subscribing. 23 | Subscribe 'sub' connection to 'chat' channel. 24 | */ 25 | 26 | app.sub = redis.createClient(6379, 'localhost', {return_buffers: true}); 27 | app.pub = redis.createClient(6379, 'localhost', {return_buffers: true}); 28 | app.sub.setMaxListeners(0); 29 | app.sub.subscribe('chat'); 30 | 31 | // request pre-processing middleware 32 | app.use(loopback.token({model: app.models.AccessToken})); 33 | app.use(loopback.json()); 34 | app.use(loopback.urlencoded({ extended: false })); 35 | 36 | if (env === 'development') { 37 | app.use(require('connect-livereload')({ 38 | port: 35729 39 | })); 40 | 41 | app.get('*', function(req, res, next){ 42 | if (!req.secure && !req.isSpdy) { 43 | res.redirect(301, "https://" + req.headers.host + req.url); 44 | } else { 45 | next(); 46 | } 47 | }); 48 | } 49 | 50 | // boot scripts mount components like REST API 51 | boot(app, __dirname); 52 | 53 | if (env === 'development') { 54 | var spdyOptions = { 55 | key : fs.readFileSync(__dirname + '/../server.key'), 56 | cert: fs.readFileSync(__dirname + '/../server.pem'), 57 | ca: fs.readFileSync(__dirname + '/../gd_bundle.crt'), 58 | ssl: true 59 | }; 60 | var http = require('http').createServer(app), 61 | server = require('https').createServer(spdyOptions, app); 62 | 63 | http.listen(process.env.NODE_ENV == 'development' ? 50447 : process.env.PORT || 80, function(){ 64 | console.log("http server started"); 65 | }); 66 | server.listen(process.env.NODE_ENV == 'development' ? 50337 : process.env.PORT_SSL || 443, function(){ 67 | console.log("Server started"); 68 | }); 69 | } else { 70 | var server = app.listen(function() { 71 | app.emit('started', app.get('url')); 72 | console.log('Web server listening at: %s', app.get('url')); 73 | }); 74 | } 75 | 76 | app.io.attach(server); 77 | --------------------------------------------------------------------------------