├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── api ├── adapters │ └── .gitkeep ├── controllers │ ├── .gitkeep │ ├── AssetsController.js │ ├── EventsController.js │ ├── ReadersController.js │ └── TagsController.js ├── models │ ├── .gitkeep │ ├── Assets.js │ ├── Events.js │ ├── Readers.js │ └── Tags.js ├── policies │ └── isAuthenticated.js └── services │ └── .gitkeep ├── app.js ├── architecture.png ├── assets ├── favicon.ico ├── images │ └── .gitkeep ├── js │ ├── .gitkeep │ ├── app.js │ ├── sails.io.js │ └── socket.io.js ├── robots.txt └── styles │ └── .gitkeep ├── config ├── 400.js ├── 403.js ├── 404.js ├── 500.js ├── adapters.js ├── bootstrap.js ├── controllers.js ├── cors.js ├── csrf.js ├── i18n.js ├── locales │ ├── _README.md │ ├── de.json │ ├── en.json │ ├── es.json │ └── fr.json ├── log.js ├── policies.js ├── routes.js ├── session.js ├── sockets.js └── views.js ├── package.json └── views ├── 403.ejs ├── 404.ejs ├── 500.ejs ├── home └── index.ejs └── layout.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | ######################## 2 | # sails 3 | ######################## 4 | .sails 5 | .waterline 6 | .rigging 7 | .tmp 8 | 9 | 10 | ######################## 11 | # node.js / npm 12 | ######################## 13 | lib-cov 14 | *.seed 15 | *.log 16 | *.csv 17 | *.dat 18 | *.out 19 | *.pid 20 | *.gz 21 | 22 | pids 23 | logs 24 | results 25 | 26 | node_modules 27 | 28 | npm-debug.log 29 | 30 | 31 | ######################## 32 | # misc / editors 33 | ######################## 34 | *~ 35 | *# 36 | .DS_STORE 37 | .netbeans 38 | nbproject 39 | .idea 40 | 41 | 42 | ######################## 43 | # local config 44 | ######################## 45 | config/local.js 46 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gruntfile 3 | * 4 | * If you created your Sails app with `sails new foo --linker`, 5 | * the following files will be automatically injected (in order) 6 | * into the EJS and HTML files in your `views` and `assets` folders. 7 | * 8 | * At the top part of this file, you'll find a few of the most commonly 9 | * configured options, but Sails' integration with Grunt is also fully 10 | * customizable. If you'd like to work with your assets differently 11 | * you can change this file to do anything you like! 12 | * 13 | * More information on using Grunt to work with static assets: 14 | * http://gruntjs.com/configuring-tasks 15 | */ 16 | 17 | module.exports = function (grunt) { 18 | 19 | 20 | 21 | /** 22 | * CSS files to inject in order 23 | * (uses Grunt-style wildcard/glob/splat expressions) 24 | * 25 | * By default, Sails also supports LESS in development and production. 26 | * To use SASS/SCSS, Stylus, etc., edit the `sails-linker:devStyles` task 27 | * below for more options. For this to work, you may need to install new 28 | * dependencies, e.g. `npm install grunt-contrib-sass` 29 | */ 30 | 31 | var cssFilesToInject = [ 32 | 'linker/**/*.css' 33 | ]; 34 | 35 | 36 | /** 37 | * Javascript files to inject in order 38 | * (uses Grunt-style wildcard/glob/splat expressions) 39 | * 40 | * To use client-side CoffeeScript, TypeScript, etc., edit the 41 | * `sails-linker:devJs` task below for more options. 42 | */ 43 | 44 | var jsFilesToInject = [ 45 | 46 | // Below, as a demonstration, you'll see the built-in dependencies 47 | // linked in the proper order order 48 | 49 | // Bring in the socket.io client 50 | 'linker/js/socket.io.js', 51 | 52 | // then beef it up with some convenience logic for talking to Sails.js 53 | 'linker/js/sails.io.js', 54 | 55 | // A simpler boilerplate library for getting you up and running w/ an 56 | // automatic listener for incoming messages from Socket.io. 57 | 'linker/js/app.js', 58 | 59 | // *-> put other dependencies here <-* 60 | 61 | // All of the rest of your app scripts imported here 62 | 'linker/**/*.js' 63 | ]; 64 | 65 | 66 | /** 67 | * Client-side HTML templates are injected using the sources below 68 | * The ordering of these templates shouldn't matter. 69 | * (uses Grunt-style wildcard/glob/splat expressions) 70 | * 71 | * By default, Sails uses JST templates and precompiles them into 72 | * functions for you. If you want to use jade, handlebars, dust, etc., 73 | * edit the relevant sections below. 74 | */ 75 | 76 | var templateFilesToInject = [ 77 | 'linker/**/*.html' 78 | ]; 79 | 80 | 81 | 82 | ///////////////////////////////////////////////////////////////// 83 | ///////////////////////////////////////////////////////////////// 84 | ///////////////////////////////////////////////////////////////// 85 | ///////////////////////////////////////////////////////////////// 86 | ///////////////////////////////////////////////////////////////// 87 | ///////////////////////////////////////////////////////////////// 88 | ///////////////////////////////////////////////////////////////// 89 | ///////////////////////////////////////////////////////////////// 90 | ///////////////////////////////////////////////////////////////// 91 | ///////////////////////////////////////////////////////////////// 92 | // 93 | // DANGER: 94 | // 95 | // With great power comes great responsibility. 96 | // 97 | ///////////////////////////////////////////////////////////////// 98 | ///////////////////////////////////////////////////////////////// 99 | ///////////////////////////////////////////////////////////////// 100 | ///////////////////////////////////////////////////////////////// 101 | ///////////////////////////////////////////////////////////////// 102 | ///////////////////////////////////////////////////////////////// 103 | ///////////////////////////////////////////////////////////////// 104 | ///////////////////////////////////////////////////////////////// 105 | ///////////////////////////////////////////////////////////////// 106 | ///////////////////////////////////////////////////////////////// 107 | 108 | // Modify css file injection paths to use 109 | cssFilesToInject = cssFilesToInject.map(function (path) { 110 | return '.tmp/public/' + path; 111 | }); 112 | 113 | // Modify js file injection paths to use 114 | jsFilesToInject = jsFilesToInject.map(function (path) { 115 | return '.tmp/public/' + path; 116 | }); 117 | 118 | 119 | templateFilesToInject = templateFilesToInject.map(function (path) { 120 | return 'assets/' + path; 121 | }); 122 | 123 | 124 | // Get path to core grunt dependencies from Sails 125 | var depsPath = grunt.option('gdsrc') || 'node_modules/sails/node_modules'; 126 | grunt.loadTasks(depsPath + '/grunt-contrib-clean/tasks'); 127 | grunt.loadTasks(depsPath + '/grunt-contrib-copy/tasks'); 128 | grunt.loadTasks(depsPath + '/grunt-contrib-concat/tasks'); 129 | grunt.loadTasks(depsPath + '/grunt-sails-linker/tasks'); 130 | grunt.loadTasks(depsPath + '/grunt-contrib-jst/tasks'); 131 | grunt.loadTasks(depsPath + '/grunt-contrib-watch/tasks'); 132 | grunt.loadTasks(depsPath + '/grunt-contrib-uglify/tasks'); 133 | grunt.loadTasks(depsPath + '/grunt-contrib-cssmin/tasks'); 134 | grunt.loadTasks(depsPath + '/grunt-contrib-less/tasks'); 135 | grunt.loadTasks(depsPath + '/grunt-contrib-coffee/tasks'); 136 | 137 | // Project configuration. 138 | grunt.initConfig({ 139 | pkg: grunt.file.readJSON('package.json'), 140 | 141 | copy: { 142 | dev: { 143 | files: [ 144 | { 145 | expand: true, 146 | cwd: './assets', 147 | src: ['**/*.!(coffee)'], 148 | dest: '.tmp/public' 149 | } 150 | ] 151 | }, 152 | build: { 153 | files: [ 154 | { 155 | expand: true, 156 | cwd: '.tmp/public', 157 | src: ['**/*'], 158 | dest: 'www' 159 | } 160 | ] 161 | } 162 | }, 163 | 164 | clean: { 165 | dev: ['.tmp/public/**'], 166 | build: ['www'] 167 | }, 168 | 169 | jst: { 170 | dev: { 171 | 172 | // To use other sorts of templates, specify the regexp below: 173 | // options: { 174 | // templateSettings: { 175 | // interpolate: /\{\{(.+?)\}\}/g 176 | // } 177 | // }, 178 | 179 | files: { 180 | '.tmp/public/jst.js': templateFilesToInject 181 | } 182 | } 183 | }, 184 | 185 | less: { 186 | dev: { 187 | files: [ 188 | { 189 | expand: true, 190 | cwd: 'assets/styles/', 191 | src: ['*.less'], 192 | dest: '.tmp/public/styles/', 193 | ext: '.css' 194 | }, { 195 | expand: true, 196 | cwd: 'assets/linker/styles/', 197 | src: ['*.less'], 198 | dest: '.tmp/public/linker/styles/', 199 | ext: '.css' 200 | } 201 | ] 202 | } 203 | }, 204 | 205 | coffee: { 206 | dev: { 207 | options:{ 208 | bare:true 209 | }, 210 | files: [ 211 | { 212 | expand: true, 213 | cwd: 'assets/js/', 214 | src: ['**/*.coffee'], 215 | dest: '.tmp/public/js/', 216 | ext: '.js' 217 | }, { 218 | expand: true, 219 | cwd: 'assets/linker/js/', 220 | src: ['**/*.coffee'], 221 | dest: '.tmp/public/linker/js/', 222 | ext: '.js' 223 | } 224 | ] 225 | } 226 | }, 227 | 228 | concat: { 229 | js: { 230 | src: jsFilesToInject, 231 | dest: '.tmp/public/concat/production.js' 232 | }, 233 | css: { 234 | src: cssFilesToInject, 235 | dest: '.tmp/public/concat/production.css' 236 | } 237 | }, 238 | 239 | uglify: { 240 | dist: { 241 | src: ['.tmp/public/concat/production.js'], 242 | dest: '.tmp/public/min/production.js' 243 | } 244 | }, 245 | 246 | cssmin: { 247 | dist: { 248 | src: ['.tmp/public/concat/production.css'], 249 | dest: '.tmp/public/min/production.css' 250 | } 251 | }, 252 | 253 | 'sails-linker': { 254 | 255 | devJs: { 256 | options: { 257 | startTag: '', 258 | endTag: '', 259 | fileTmpl: '', 260 | appRoot: '.tmp/public' 261 | }, 262 | files: { 263 | '.tmp/public/**/*.html': jsFilesToInject, 264 | 'views/**/*.html': jsFilesToInject, 265 | 'views/**/*.ejs': jsFilesToInject 266 | } 267 | }, 268 | 269 | prodJs: { 270 | options: { 271 | startTag: '', 272 | endTag: '', 273 | fileTmpl: '', 274 | appRoot: '.tmp/public' 275 | }, 276 | files: { 277 | '.tmp/public/**/*.html': ['.tmp/public/min/production.js'], 278 | 'views/**/*.html': ['.tmp/public/min/production.js'], 279 | 'views/**/*.ejs': ['.tmp/public/min/production.js'] 280 | } 281 | }, 282 | 283 | devStyles: { 284 | options: { 285 | startTag: '', 286 | endTag: '', 287 | fileTmpl: '', 288 | appRoot: '.tmp/public' 289 | }, 290 | 291 | // cssFilesToInject defined up top 292 | files: { 293 | '.tmp/public/**/*.html': cssFilesToInject, 294 | 'views/**/*.html': cssFilesToInject, 295 | 'views/**/*.ejs': cssFilesToInject 296 | } 297 | }, 298 | 299 | prodStyles: { 300 | options: { 301 | startTag: '', 302 | endTag: '', 303 | fileTmpl: '', 304 | appRoot: '.tmp/public' 305 | }, 306 | files: { 307 | '.tmp/public/index.html': ['.tmp/public/min/production.css'], 308 | 'views/**/*.html': ['.tmp/public/min/production.css'], 309 | 'views/**/*.ejs': ['.tmp/public/min/production.css'] 310 | } 311 | }, 312 | 313 | // Bring in JST template object 314 | devTpl: { 315 | options: { 316 | startTag: '', 317 | endTag: '', 318 | fileTmpl: '', 319 | appRoot: '.tmp/public' 320 | }, 321 | files: { 322 | '.tmp/public/index.html': ['.tmp/public/jst.js'], 323 | 'views/**/*.html': ['.tmp/public/jst.js'], 324 | 'views/**/*.ejs': ['.tmp/public/jst.js'] 325 | } 326 | }, 327 | 328 | 329 | /******************************************* 330 | * Jade linkers (TODO: clean this up) 331 | *******************************************/ 332 | 333 | devJsJADE: { 334 | options: { 335 | startTag: '// SCRIPTS', 336 | endTag: '// SCRIPTS END', 337 | fileTmpl: 'script(type="text/javascript", src="%s")', 338 | appRoot: '.tmp/public' 339 | }, 340 | files: { 341 | 'views/**/*.jade': jsFilesToInject 342 | } 343 | }, 344 | 345 | prodJsJADE: { 346 | options: { 347 | startTag: '// SCRIPTS', 348 | endTag: '// SCRIPTS END', 349 | fileTmpl: 'script(type="text/javascript", src="%s")', 350 | appRoot: '.tmp/public' 351 | }, 352 | files: { 353 | 'views/**/*.jade': ['.tmp/public/min/production.js'] 354 | } 355 | }, 356 | 357 | devStylesJADE: { 358 | options: { 359 | startTag: '// STYLES', 360 | endTag: '// STYLES END', 361 | fileTmpl: 'link(rel="stylesheet", href="%s")', 362 | appRoot: '.tmp/public' 363 | }, 364 | files: { 365 | 'views/**/*.jade': cssFilesToInject 366 | } 367 | }, 368 | 369 | prodStylesJADE: { 370 | options: { 371 | startTag: '// STYLES', 372 | endTag: '// STYLES END', 373 | fileTmpl: 'link(rel="stylesheet", href="%s")', 374 | appRoot: '.tmp/public' 375 | }, 376 | files: { 377 | 'views/**/*.jade': ['.tmp/public/min/production.css'] 378 | } 379 | }, 380 | 381 | // Bring in JST template object 382 | devTplJADE: { 383 | options: { 384 | startTag: '// TEMPLATES', 385 | endTag: '// TEMPLATES END', 386 | fileTmpl: 'script(type="text/javascript", src="%s")', 387 | appRoot: '.tmp/public' 388 | }, 389 | files: { 390 | 'views/**/*.jade': ['.tmp/public/jst.js'] 391 | } 392 | } 393 | /************************************ 394 | * Jade linker end 395 | ************************************/ 396 | }, 397 | 398 | watch: { 399 | api: { 400 | 401 | // API files to watch: 402 | files: ['api/**/*'] 403 | }, 404 | assets: { 405 | 406 | // Assets to watch: 407 | files: ['assets/**/*'], 408 | 409 | // When assets are changed: 410 | tasks: ['compileAssets', 'linkAssets'] 411 | } 412 | } 413 | }); 414 | 415 | // When Sails is lifted: 416 | grunt.registerTask('default', [ 417 | 'compileAssets', 418 | 'linkAssets', 419 | 'watch' 420 | ]); 421 | 422 | grunt.registerTask('compileAssets', [ 423 | 'clean:dev', 424 | 'jst:dev', 425 | 'less:dev', 426 | 'copy:dev', 427 | 'coffee:dev' 428 | ]); 429 | 430 | grunt.registerTask('linkAssets', [ 431 | 432 | // Update link/script/template references in `assets` index.html 433 | 'sails-linker:devJs', 434 | 'sails-linker:devStyles', 435 | 'sails-linker:devTpl', 436 | 'sails-linker:devJsJADE', 437 | 'sails-linker:devStylesJADE', 438 | 'sails-linker:devTplJADE' 439 | ]); 440 | 441 | 442 | // Build the assets into a web accessible folder. 443 | // (handy for phone gap apps, chrome extensions, etc.) 444 | grunt.registerTask('build', [ 445 | 'compileAssets', 446 | 'linkAssets', 447 | 'clean:build', 448 | 'copy:build' 449 | ]); 450 | 451 | // When sails is lifted in production 452 | grunt.registerTask('prod', [ 453 | 'clean:dev', 454 | 'jst:dev', 455 | 'less:dev', 456 | 'copy:dev', 457 | 'coffee:dev', 458 | 'concat', 459 | 'uglify', 460 | 'cssmin', 461 | 'sails-linker:prodJs', 462 | 'sails-linker:prodStyles', 463 | 'sails-linker:devTpl', 464 | 'sails-linker:prodJsJADE', 465 | 'sails-linker:prodStylesJADE', 466 | 'sails-linker:devTplJADE' 467 | ]); 468 | 469 | // When API files are changed: 470 | // grunt.event.on('watch', function(action, filepath) { 471 | // grunt.log.writeln(filepath + ' has ' + action); 472 | 473 | // // Send a request to a development-only endpoint on the server 474 | // // which will reuptake the file that was changed. 475 | // var baseurl = grunt.option('baseurl'); 476 | // var gruntSignalRoute = grunt.option('signalpath'); 477 | // var url = baseurl + gruntSignalRoute + '?action=' + action + '&filepath=' + filepath; 478 | 479 | // require('http').get(url) 480 | // .on('error', function(e) { 481 | // console.error(filepath + ' has ' + action + ', but could not signal the Sails.js server: ' + e.message); 482 | // }); 483 | // }); 484 | }; 485 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Innobec Technologies Inc. (http://www.innobec.com/) 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RFID-based Asset Tracking with Node.js and MongoDB 2 | 3 | As the title states, we are going to discuss how to build an 4 | [RFID](http://en.wikipedia.org/wiki/Radio-frequency_identification)-based asset 5 | tracking system using popular open source tools like 6 | [Node.js](http://nodejs.org/) and [MongoDB](http://www.mongodb.org/). For the 7 | sake of simplicity, let's start with a very basic model of a system addressing 8 | only a general concern of getting some information from RFID hardware and 9 | putting it in the database. Of course, in real life we would have to think 10 | about many details and additional components to take into account security, 11 | bandwidth consumption, monitoring, etc., but let us forget about it all for now 12 | and get back to these aspects in our future posts. 13 | 14 | ## Overall Architecture 15 | 16 | Let's take a look at some basic components our RFID asset tracking system needs. 17 | 18 | ![Overall Architecture](architecture.png) 19 | 20 | ### RFID Tags 21 | 22 | RFID tags are compact devices which in our context are physically attached to 23 | the asset being tracked. These tags can be active (containing a power source 24 | and periodically emitting their ID as a signal), passive (no power source) or 25 | semi-passive (have a power source to power internal circuits but use the power 26 | from the reader to transmit). Within the scope of this post, the type we are 27 | using doesn't really matter since any tag type can provide us with some kind of 28 | a tag ID. 29 | 30 | ### RFID Readers 31 | 32 | RFID readers are the devices that receive information regarding the positions 33 | of RFID tags and transmit it upstream. The type of readers and the way they 34 | receive data strongly depends on the type of the tags being used. Once again, 35 | let's not focus on the particular hardware so far as we will get back to it 36 | later. 37 | 38 | ### Gateways 39 | 40 | A gateway is a hub connecting RFID readers with remote modules. It can be a 41 | physical device located on the same premises the readers are located on, as 42 | well as a remote and / or virtual container. The main purpose of a gateway is 43 | gathering messages coming from the readers (or sensors, etc.) and transmitting 44 | them to the system backend. Depending on the environment and the complexity of 45 | the data, some sort of buffering, caching and pre-processing can also be 46 | required to be performed on the gateway. 47 | 48 | If you are asking yourself whether or not you need some special equipment for 49 | an RFID gateway, the answer really depends on the scale and the environment of 50 | your project, but in most relatively simple cases a 51 | [Raspberry Pi](http://en.wikipedia.org/wiki/Raspberry_Pi) or a 52 | [Beaglebone Black](http://en.wikipedia.org/wiki/BeagleBone_Black#BeagleBone_Black) 53 | can easily do the job for you. 54 | 55 | ### API Server 56 | 57 | In our case, 58 | [API](http://en.wikipedia.org/wiki/Application_programming_interface) 59 | server is a bridge between the gateways and the database. This server 60 | translates the messages from end devices to the database objects. To a certain 61 | extent, you could say that it defines what part of your database you want to 62 | expose and how you would prefer to do it. 63 | 64 | There is a lot of ways to design such a server, but if we are talking about 65 | some standards, then 66 | [REST](http://en.wikipedia.org/wiki/Representational_state_transfer) is, 67 | obviously, one of the most advanced and popular. Among other things, it defines 68 | a set of actions (e.g. CREATE, READ, UPDATE or DELETE) that can be performed 69 | over resources (corresponding to the database collections which are the NoSQL 70 | equivalents of tables in 71 | [RDBMS](http://en.wikipedia.org/wiki/Relational_database_management_system) 72 | and match those actions with the HTTP methods (e.g. POST, GET, PUT or DELETE). 73 | 74 | Of course, using HTTP and a RESTful API server is only one of many possible 75 | approaches, but it can easily give you the idea on how such a system could work 76 | in general. 77 | 78 | ### Database 79 | 80 | The database, basically, is the place where your RFID data should ultimately 81 | land up. At this point it should already be transformed to match the object 82 | model of your system, and once the data is received you can easily track the 83 | changes through your frontend (e.g. Web, iOS or Android application). 84 | 85 | ## Software Choices 86 | 87 | As we agreed earlier, here we are describing a very basic use case of an RFID 88 | system without getting in the details. So, you could be curious then, why we 89 | are so definitive about Node.js and MongoDB? Let's see some reasoning. 90 | 91 | ### Node.js and Related Frameworks 92 | 93 | Obviously, there is a lot of programming environments which could be used in 94 | order to implement the solution in question. Node.js and its various frameworks 95 | have some important features that could provide considerable help during the 96 | implementation, including: 97 | 98 | - speed: Node.js' V8 engine is much faster than most of the popular languages 99 | used for Web development (see 100 | https://www.paypal-engineering.com/2013/11/22/node-js-at-paypal/, 101 | http://www.appdynamics.com/blog/nodejs/an-example-of-how-node-js-is-faster-than-php/ 102 | and http://inkstonehq.com/2013/07/10/node-js-vs-rails-which-is-better/ for some 103 | benchmarks); 104 | - scalability: a single Node.js process is able to handle thousands of 105 | concurrent connections with minimal overhead; 106 | - language: JavaScript is extremely popular programming language, which 107 | simplifies the task of finding developers for the project; moreover, using 108 | JavaScript on both the server and the browser improves communication between 109 | system components and offers some opportunities to reuse the server code on 110 | the client and vice versa, especially, in case of 111 | [isomorphic frameworks](http://venturebeat.com/2013/11/08/the-future-of-web-apps-is-ready-isomorphic-javascript/); 112 | - community: Node.js has one of the most strong and active communities 113 | nowadays, which makes the processes of code maintenance, debugging and 114 | refactoring many times easier and faster; 115 | - industry support: [a lot of companies](http://nodejs.org/industry/) from 116 | startups to big corporations successfully use Node.js in production; 117 | - libraries: more then 70000 different packages are currently accessible via 118 | Node Packaged Modules, [npm](https://npmjs.org/), and the total number of 119 | packages grows on a faster pace then, for example, even the number of 120 | [Ruby gems](http://rubygems.org/stats), that have been around much longer. 121 | 122 | However, only in very rare cases it makes sense to use pure language 123 | implementation for a new Web project, since most of the time some popular 124 | features are already implemented with some frameworks. In our case, the 125 | following frameworks constitute considerable interest: 126 | 127 | - [Express.js](http://expressjs.com/) is a server-side web-application 128 | framework for Node.js, but a very minimalistic one, which means it provides 129 | only some basic functionality, no hidden magic or anything like that. However, 130 | Express.js has quickly became a standard in Node.js based Web-development and 131 | a base not only for so called 132 | [MEAN-stack](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and) 133 | (MongoDB-Express.js-Angular.js-Node.js), but also for a lot of higher level 134 | frameworks; 135 | - [Sails.js](http://sailsjs.org/) is one of most popular frameworks (obviously, 136 | Ruby on Rails-inspired) using Express.js as a foundation. Some of its key 137 | features that can help us to implement our solution include: 138 | * automatic RESTful API generation; 139 | * built-in database-agnostic 140 | [ORM](http://en.wikipedia.org/wiki/Object-relational_mapping); 141 | * real-time features support with built-in [Socket.io](http://socket.io/) 142 | engine. 143 | 144 | ### MongoDB 145 | 146 | Currently, [MongoDB](http://www.mongodb.org/) is probably most popular of the 147 | [NoSQL](http://en.wikipedia.org/wiki/NoSQL) database engines. And this is for 148 | a reason: it has so many great qualities enhancing previously traditional 149 | relational database experience. Namely, for our project the following features 150 | are of special interest: 151 | 152 | - flexibility: storing data in JSON ([BSON](http://en.wikipedia.org/wiki/BSON)) 153 | documents, MongoDB is not bounded by such a serious relational database 154 | limitation as fixed schema and doesn't need the data to be normalized; for us, 155 | it means that we can easily store different class of assets with different 156 | specifications and uniquely structured embedded data for every document; 157 | - functionality: MongoDB provides most of the traditional RDBMS features 158 | including indexes, dynamic queries, sorting, updates, upserts (update if 159 | document exists, insert if it doesn’t), etc., adding on top of it all the 160 | capacities offered by non-relational model; 161 | - speed: keeping related data embedded in documents, we can query results much 162 | faster than in case of relational databases where we usually need to join 163 | multiple tables in order to achieve similar results; 164 | - scalability: scaling the database becomes really easy with MongoDB's 165 | [autosharding](http://docs.mongodb.org/manual/sharding/), and that can be 166 | really helpful in case of our task, when the number of assets being tracked 167 | can grow significantly; 168 | - simplicity: easy to install, configure, support, and use, MongoDB works right 169 | out of the box, without any excessive configuration or fine-tuning. 170 | 171 | These features make MongoDB a perfect candidate for the role of the system's 172 | main database engine. 173 | 174 | ## Implementation 175 | 176 | The basic idea is simple: your gateway is receiving some data from the 177 | peripherals (RFID readers in our case) and makes appropriate HTTP-calls to the 178 | API server. The latter accepts the information, processes it and updates the 179 | database accordingly. Now, let's see how to do it in practice. 180 | 181 | ### Prerequisites 182 | 183 | Obviously, in order to run the following examples, you have to install some 184 | software first. 185 | 186 | #### Node.js 187 | 188 | Download the latest stable Node.js package for your operating system from 189 | http://nodejs.org/ or install it via a package manager: 190 | https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager. 191 | Alternatively, you can use your own build of the desired version, see 192 | https://github.com/joyent/node/wiki/Installation for details. 193 | 194 | #### MongoDB 195 | 196 | You can download MongoDB binaries from http://www.mongodb.org/downloads, then 197 | follow one of the installation guides corresponding to your operating system: 198 | http://docs.mongodb.org/manual/installation/. Note, that unless you are using a 199 | packaged version, the server doesn't start automatically, so you would need to 200 | start it manually as described here: 201 | http://docs.mongodb.org/manual/tutorial/manage-mongodb-processes/. 202 | 203 | #### npm 204 | 205 | Good news is that npm comes with Node nowadays, so, in general, you don't have 206 | to install anything else. Typing npm in the command line should give you a list 207 | of available commands (if it doesn't, you can find a bunch of other ways to 208 | install npm here: https://www.npmjs.org/doc/README.html#Fancy-Install-Unix or 209 | https://www.npmjs.org/doc/README.html#Fancy-Windows-Install). 210 | 211 | #### Sails.js 212 | 213 | As simple as `npm install sails@0.9.13 -g` (you may need to prefix it with 214 | `sudo` though, depending on your operating system and user privileges). 215 | 216 | ### Data Structure 217 | 218 | Keeping it simple, let's define 4 collections that will represent our system's 219 | entities: 220 | 221 | - tags, RFID tags containing: 222 | * tag — tag unique identifier (independent from the database, not the 223 | database object ID), which can come as an EPC or in any other form allowing 224 | to differ a tag unambiguously, 225 | - readers, RFID reader objects: 226 | * reader — reader unique identifier, in any form the gateway is sending it, 227 | - assets, objects corresponding to the assets we are tracking: 228 | * name — asset name / title, 229 | * tagID — the database object ID (reference) of the tag assigned to this 230 | asset, 231 | * currentReaderID — the database object ID (reference) of the reader where 232 | this asset was seen last time, 233 | * note, that here we can also have some custom fields that differ from one 234 | asset to another (given the fact that we are using a NoSQL solution, it comes 235 | at no cost), since the assets themselves can have completely different nature, 236 | - events, containing messages being received from gateways and meaning, in 237 | general, that "this tag was seen near this reader": 238 | * tag — tag identifier, 239 | * reader — reader identifier, 240 | * rssi — 241 | [RSSI](http://en.wikipedia.org/wiki/Received_signal_strength_indication), an 242 | optional field which may or may not be presented depending on your RFID 243 | hardware. 244 | 245 | Let's see how we can create an API server for these models and get it running 246 | in 5 minutes. Literally. 247 | 248 | ### RESTful API with Sails.js 249 | 250 | A lot of arguments are being conducted on whether it makes sense to extend 251 | standard Express' functionality to sort of match higher level Web frameworks 252 | or Express itself with its middleware ecosystem already contains everything 253 | needed to build a Web application from scratch. The answer, most probably, 254 | depends on the project you are developing, but one of the things about 255 | Sails.js that looks really impressive is the RESTful API it automatically 256 | generates. Indeed, you may not even have to write a single line of code. 257 | 258 | Let's start by creating a new Sails.js application (Note: the following 259 | examples use v0.9.13 of Sails.js, which was the last stable version of the 260 | framework as of this writing): 261 | 262 | sails new rfid-based-asset-tracking-with-nodejs-and-mongodb 263 | 264 | will create a subfolder named 265 | `rfid-based-asset-tracking-with-nodejs-and-mongodb` (or you could use any other 266 | name) within your current location and generate base boilerplate code 267 | necessary to run the simplest Sails application. Now, in order to create a 268 | complete API for the previously described data structure, let's switch to this 269 | new folder and type the following: 270 | 271 | sails g tags 272 | sails g readers 273 | sails g assets 274 | sails g events 275 | 276 | These commands will generate models and controllers for corresponding entities, 277 | so `ls ./api/controllers/` will give you 278 | 279 | AssetsController.js EventsController.js ReadersController.js TagsController.js 280 | 281 | and `ls ./api/models/` will display 282 | 283 | Assets.js Events.js Readers.js Tags.js 284 | 285 | At this point we don't even care about the code in these files (there's not a 286 | lot of it anyway). All we need to see our API server in action is to start the 287 | Sails.js application, but first let's make sure it will connect to our MongoDB 288 | database. 289 | 290 | ### Connecting to MongoDB 291 | 292 | By default, Sails.js comes with a proprietary database adapter called 293 | `sails-disk`, which is a simple disk-based storage engine that can be used for 294 | development purposes, but not suitable for production (mainly, performance and 295 | scalability wise). Let's switch it to Mongo. 296 | 297 | Open `./config/local.js` and add the following code right after 298 | `module.exports = {`: 299 | 300 | adapters: { 301 | default: 'mongo', 302 | 303 | // MongoDB 304 | mongo: { 305 | module : 'sails-mongo', 306 | host : 'localhost', 307 | port : 27017, 308 | // user : 'username', // Authentication is supposed to be disabled by default, 309 | // password : 'password', // if it's not, put the username / password here 310 | database : 'rfid' // Or any other name you want 311 | }, 312 | }, 313 | 314 | Since Sails requires a separate module to connect to Mongo, we have to install 315 | it: 316 | 317 | npm install --save sails-mongo 318 | 319 | Once again, before you proceed, make sure your MongoDB server is running. 320 | 321 | ### Processing the Data 322 | 323 | Let's start our server: 324 | 325 | sails lift 326 | 327 | By default, Sails application will be listening on port 1337, so if you open 328 | `http://localhost:1337/` in your browser, you should get a nice default page 329 | (which is useless for us though). Note, that your server must be running during 330 | the further experiments. 331 | 332 | If you open `http://localhost:1337/tags/` now, you will see [], which is a JSON 333 | form of an empty array. This simply means we don't have any objects in tags 334 | collection of our database. Let's create some. 335 | 336 | By default, with Sails.js you not only can use a standard RESTful mapping of 337 | HTTP methods to CRUD action (e.g. POST to CREATE an object), but you also have 338 | so called 339 | [developer shortcuts](https://github.com/balderdashy/sails-docs/blob/master/config.controllers.md#shortcuts-boolean) 340 | that let you use simple HTTP GET requests for all the actions. So, creating new 341 | tags is as simple as opening the following links in your browser: 342 | 343 | http://localhost:1337/tags/create/?tag=tag1 344 | http://localhost:1337/tags/create/?tag=tag2 345 | http://localhost:1337/tags/create/?tag=tag3 346 | 347 | Now, if you go back to `http://localhost:1337/tags/`, you should see something 348 | like this: 349 | 350 | [ 351 | { 352 | "tag": "tag1", 353 | "createdAt": "2014-03-25T15:40:54.995Z", 354 | "updatedAt": "2014-03-25T15:40:54.995Z", 355 | "id": "5331a38683d14dd59d527277" 356 | }, 357 | { 358 | "tag": "tag2", 359 | "createdAt": "2014-03-25T15:41:14.645Z", 360 | "updatedAt": "2014-03-25T15:41:14.645Z", 361 | "id": "5331a39a83d14dd59d527278" 362 | }, 363 | { 364 | "tag": "tag3", 365 | "createdAt": "2014-03-25T15:41:18.731Z", 366 | "updatedAt": "2014-03-25T15:41:18.731Z", 367 | "id": "5331a39e83d14dd59d527279" 368 | } 369 | ] 370 | 371 | Thus, we created three objects that correspond to our RFID tags. Note, that we 372 | only needed to provide an identifier to each tag and Sails.js automatically 373 | made sure the objects receive additional fields, such as `id` corresponding to 374 | the MongoDB `_id` and `createdAt` / `updatedAt` timestamps, similar to the ones 375 | Rails provides with its 376 | [Active Record](http://guides.rubyonrails.org/active_record_querying.html). 377 | Let's create other objects we need in the same manner: 378 | 379 | http://localhost:1337/readers/create/?reader=reader1 380 | http://localhost:1337/readers/create/?reader=reader2 381 | http://localhost:1337/readers/create?reader=reader3 382 | http://localhost:1337/assets/create?name=asset1 383 | http://localhost:1337/assets/create?name=asset2 384 | http://localhost:1337/assets/create?name=asset3 385 | 386 | After that, visiting `http://localhost:1337/readers/` and 387 | `http://localhost:1337/assets/` will give you something like 388 | 389 | [ 390 | { 391 | "reader": "reader1", 392 | "createdAt": "2014-03-25T16:35:13.776Z", 393 | "updatedAt": "2014-03-25T16:35:13.776Z", 394 | "id": "5331b04183d14dd59d52727a" 395 | }, 396 | { 397 | "reader": "reader2", 398 | "createdAt": "2014-03-25T16:35:24.761Z", 399 | "updatedAt": "2014-03-25T16:35:24.761Z", 400 | "id": "5331b04c83d14dd59d52727b" 401 | }, 402 | { 403 | "reader": "reader3", 404 | "createdAt": "2014-03-25T16:35:38.987Z", 405 | "updatedAt": "2014-03-25T16:35:38.987Z", 406 | "id": "5331b05a83d14dd59d52727c" 407 | } 408 | ] 409 | 410 | and 411 | 412 | [ 413 | { 414 | "name": "asset1", 415 | "createdAt": "2014-03-25T16:36:44.136Z", 416 | "updatedAt": "2014-03-25T16:36:44.136Z", 417 | "id": "5331b09c83d14dd59d52727d" 418 | }, 419 | { 420 | "name": "asset2", 421 | "createdAt": "2014-03-25T16:36:48.303Z", 422 | "updatedAt": "2014-03-25T16:36:48.303Z", 423 | "id": "5331b0a083d14dd59d52727e" 424 | }, 425 | { 426 | "name": "asset3", 427 | "createdAt": "2014-03-25T16:36:59.127Z", 428 | "updatedAt": "2014-03-25T16:36:59.127Z", 429 | "id": "5331b0ab83d14dd59d52727f" 430 | } 431 | ] 432 | 433 | respectively. 434 | 435 | Now we have to link our assets and our tags (which corresponds to attaching 436 | tags to the physical objects). We will use the tagID property of asset 437 | objects in order to establish such a link. This operation, once again, can be 438 | performed directly in browser, but since MongoDB generates unique object IDs 439 | [in a way randomly](http://docs.mongodb.org/manual/reference/object-id/#ObjectIDs-BSONObjectIDSpecification), 440 | we can only provide here a template, and you will have to construct your own 441 | URLs: 442 | 443 | http://localhost:1337/assets/update//?tagID= 444 | 445 | For example, in our case we got the following URLs: 446 | 447 | http://localhost:1337/assets/update/5331b09c83d14dd59d52727d/?tagID=5331a38683d14dd59d527277 448 | http://localhost:1337/assets/update/5331b0a083d14dd59d52727e/?tagID=5331a39a83d14dd59d527278 449 | http://localhost:1337/assets/update/5331b0ab83d14dd59d52727f/?tagID=5331a39e83d14dd59d527279 450 | 451 | Technically, after that we can already make some API calls to notify our 452 | server on the current position (the closest reader) of our assets. The URL 453 | template would be 454 | 455 | http://localhost:1337/assets/update//?currentReaderID= 456 | 457 | But there is a couple of problems with this approach. First, the ID of the tag 458 | object in this URL is supposed to be the database object ID, which the gateway 459 | shouldn't be aware of as we would prefer to keep our peripherals relatively 460 | independent from the central server and the database. Second, going this way we 461 | will not have any historical information about our assets' locations. That's 462 | where we going to need the last class of objects we described in our data 463 | structure, events. 464 | 465 | The idea is pretty simple: instead of updating the location of the asset 466 | directly, we are going to create an event each time a new location comes from a 467 | gateway. For example: 468 | 469 | http://localhost:1337/events/create/?tag=tag1&reader=reader1&rssi=128 470 | 471 | This will let our server (and the database) know that the tag with the 472 | identifier "tag1" was seen near the RFID reader identified as "reader1" and the 473 | signal strength was 128 (on a scale from 0 to 255). 474 | 475 | Perfect. However, it would be much more convenient for us to minimize the 476 | number of associations needed whenever we are querying the database trying to 477 | get the current location of an asset. Namely, using the `currentReaderID` 478 | property of an asset object we could organize our assets collection in an 479 | easily queryable way. Here is, probably, the only place (apart from the config 480 | file) where you will have to write some code. 481 | 482 | In order to achieve our goal, we are going to use a feature of Sails.js' 483 | [ORM](http://en.wikipedia.org/wiki/Object-relational_mapping) 484 | [Waterline](https://github.com/balderdashy/waterline) called lifecycle 485 | callbacks (similar to 486 | [Rails Active Record callbacks](http://edgeguides.rubyonrails.org/active_record_callbacks.html)), 487 | specifically, beforeCreate event. The code we will write goes to 488 | `./api/models/Events.js`: 489 | 490 | module.exports = { 491 | attributes: { 492 | /* e.g. 493 | nickname: 'string' 494 | */ 495 | }, 496 | 497 | // Lifecycle Callbacks 498 | beforeCreate: function(values, next) { 499 | Tags.findOne({'tag': values['tag'].toString()}, function(err, tag) { 500 | if (err) 501 | return next(err); 502 | if (!tag) 503 | return next(new Error("No tags with the following parameters were found: {'tag': " + values['tag'] + "}")); 504 | Assets.findOne({'tagID': tag.id}, function(err, asset) { 505 | if (err) 506 | return next(err); 507 | if (!asset) 508 | return next(new Error("No assets assigned to the following tag were found: {'tag': " + values['tag'] + "}")); 509 | Readers.findOne({'reader': values['reader'].toString()}, function(err, reader) { 510 | if (err) 511 | return next(err); 512 | if (!reader) 513 | return next(new Error("No readers with the following parameters were found: {'reader': " + values['reader'] + "}")); 514 | if (asset.currentReaderId === reader.id) 515 | next(); 516 | else { 517 | asset.currentReaderId = reader.id; 518 | asset.save(function(err) { 519 | if (err) 520 | return next(err); 521 | next(); 522 | }); 523 | } 524 | }); 525 | }); 526 | }); 527 | } 528 | }; 529 | 530 | Here is what this code essentially does: 531 | 532 | - beforeCreate event fires every time a new event object is being created 533 | (obviously, before creation); 534 | - the handler function receives the values of the event properties; 535 | - it finds the tag object corresponding to the identifier sent by the gateway; 536 | - it finds the asset to which the found tag is attached; 537 | - it finds the reader object corresponding to the identifier sent by the 538 | gateway; 539 | - it checks whether the asset changed its location, and if it did, the handler 540 | set the currentReaderID property of the asset to the new value (object ID of 541 | the reader it just found); 542 | - finally the handler function calls next() callback to get back to the event 543 | loop. 544 | 545 | Restart your Sails.js application (stop with Ctrl+C and start with 546 | `sails lift`). 547 | 548 | Now, whenever a gateway is sending new data with 549 | 550 | http://localhost:1337/events/create/?tag=&reader=&rssi=, 551 | 552 | not only corresponding event object (a history log entry) will be saved to the 553 | database, but also the currentReaderID property of the tagged asset will be 554 | changed accordingly. You can easily do some tests and check the changes via 555 | API: `http://localhost:1337/assets` and `http://localhost:1337/readers`. 556 | 557 | ### Getting Even More 558 | 559 | So, with only about 50 lines of code we got ourselves an API server that logs 560 | movements of our assets and can provide us with up-to-date information on the 561 | current location of any of these assets. What could be better? Maybe, the fact 562 | that we are actually getting even more with Node.js / Sails.js and MongoDB. 563 | These "freebees" include: 564 | 565 | - though it's very simple, our architecture can easily be scaled horizontally, 566 | since the components are relatively independent from each other and we can 567 | have, for example, multiple API servers behind a load balancer or use 568 | replication and sharding functionalities MongoDB provides out of the box; 569 | - one of the main features of Sails.js is Socket.io support built in, so you 570 | can have a real-time frontend to your RFID system with minimum effort; 571 | - since MongoDB is a schemaless database (or, as they officially say nowadays, 572 | "database with dynamic / flexible schema"), we can store with our assets and 573 | have ready to be displayed any related information without worrying too much 574 | about relational structures, normalization, etc., for example, we can fetch 575 | some parameters from our ERP system, store within our Mongo document and 576 | display within our asset monitoring frontend; 577 | - and so on. 578 | 579 | ## What's next? 580 | 581 | Thus, we designed a way our RFID gateways can report the events to the central 582 | database. Of course, it is a very simplistic model leaving aside a lot of 583 | important aspects (the biggest of which is, probably, security), but it would 584 | be somewhat unreasonable to try to cover too many topics within one article, 585 | so we will be moving gradually. 586 | 587 | And the next thing we will look at within our series is the architecture of a 588 | simple RFID gateway, its software (and how to write it in Node.js, of course) 589 | and connecting RFID peripherals. 590 | 591 | ## License 592 | 593 | [The MIT License](http://opensource.org/licenses/MIT) 594 | 595 | Copyright (c) 2014 [Innobec Technologies Inc.](http://www.innobec.com/) 596 | -------------------------------------------------------------------------------- /api/adapters/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bredikhin/rfid-based-asset-tracking-with-nodejs-and-mongodb/a4fa5dcbf10dccaa74026d1d3a5ceb9eb7ca4970/api/adapters/.gitkeep -------------------------------------------------------------------------------- /api/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bredikhin/rfid-based-asset-tracking-with-nodejs-and-mongodb/a4fa5dcbf10dccaa74026d1d3a5ceb9eb7ca4970/api/controllers/.gitkeep -------------------------------------------------------------------------------- /api/controllers/AssetsController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AssetsController 3 | * 4 | * @module :: Controller 5 | * @description :: A set of functions called `actions`. 6 | * 7 | * Actions contain code telling Sails how to respond to a certain type of request. 8 | * (i.e. do stuff, then send some JSON, show an HTML page, or redirect to another URL) 9 | * 10 | * You can configure the blueprint URLs which trigger these actions (`config/controllers.js`) 11 | * and/or override them with custom routes (`config/routes.js`) 12 | * 13 | * NOTE: The code you write here supports both HTTP and Socket.io automatically. 14 | * 15 | * @docs :: http://sailsjs.org/#!documentation/controllers 16 | */ 17 | 18 | module.exports = { 19 | 20 | 21 | 22 | 23 | /** 24 | * Overrides for the settings in `config/controllers.js` 25 | * (specific to AssetsController) 26 | */ 27 | _config: {} 28 | 29 | 30 | }; 31 | -------------------------------------------------------------------------------- /api/controllers/EventsController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EventsController 3 | * 4 | * @module :: Controller 5 | * @description :: A set of functions called `actions`. 6 | * 7 | * Actions contain code telling Sails how to respond to a certain type of request. 8 | * (i.e. do stuff, then send some JSON, show an HTML page, or redirect to another URL) 9 | * 10 | * You can configure the blueprint URLs which trigger these actions (`config/controllers.js`) 11 | * and/or override them with custom routes (`config/routes.js`) 12 | * 13 | * NOTE: The code you write here supports both HTTP and Socket.io automatically. 14 | * 15 | * @docs :: http://sailsjs.org/#!documentation/controllers 16 | */ 17 | 18 | module.exports = { 19 | 20 | 21 | 22 | 23 | /** 24 | * Overrides for the settings in `config/controllers.js` 25 | * (specific to EventsController) 26 | */ 27 | _config: {} 28 | 29 | 30 | }; 31 | -------------------------------------------------------------------------------- /api/controllers/ReadersController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ReadersController 3 | * 4 | * @module :: Controller 5 | * @description :: A set of functions called `actions`. 6 | * 7 | * Actions contain code telling Sails how to respond to a certain type of request. 8 | * (i.e. do stuff, then send some JSON, show an HTML page, or redirect to another URL) 9 | * 10 | * You can configure the blueprint URLs which trigger these actions (`config/controllers.js`) 11 | * and/or override them with custom routes (`config/routes.js`) 12 | * 13 | * NOTE: The code you write here supports both HTTP and Socket.io automatically. 14 | * 15 | * @docs :: http://sailsjs.org/#!documentation/controllers 16 | */ 17 | 18 | module.exports = { 19 | 20 | 21 | 22 | 23 | /** 24 | * Overrides for the settings in `config/controllers.js` 25 | * (specific to ReadersController) 26 | */ 27 | _config: {} 28 | 29 | 30 | }; 31 | -------------------------------------------------------------------------------- /api/controllers/TagsController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TagsController 3 | * 4 | * @module :: Controller 5 | * @description :: A set of functions called `actions`. 6 | * 7 | * Actions contain code telling Sails how to respond to a certain type of request. 8 | * (i.e. do stuff, then send some JSON, show an HTML page, or redirect to another URL) 9 | * 10 | * You can configure the blueprint URLs which trigger these actions (`config/controllers.js`) 11 | * and/or override them with custom routes (`config/routes.js`) 12 | * 13 | * NOTE: The code you write here supports both HTTP and Socket.io automatically. 14 | * 15 | * @docs :: http://sailsjs.org/#!documentation/controllers 16 | */ 17 | 18 | module.exports = { 19 | 20 | 21 | 22 | 23 | /** 24 | * Overrides for the settings in `config/controllers.js` 25 | * (specific to TagsController) 26 | */ 27 | _config: {} 28 | 29 | 30 | }; 31 | -------------------------------------------------------------------------------- /api/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bredikhin/rfid-based-asset-tracking-with-nodejs-and-mongodb/a4fa5dcbf10dccaa74026d1d3a5ceb9eb7ca4970/api/models/.gitkeep -------------------------------------------------------------------------------- /api/models/Assets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Assets 3 | * 4 | * @module :: Model 5 | * @description :: A short summary of how this model works and what it represents. 6 | * @docs :: http://sailsjs.org/#!documentation/models 7 | */ 8 | 9 | module.exports = { 10 | 11 | attributes: { 12 | 13 | /* e.g. 14 | nickname: 'string' 15 | */ 16 | 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /api/models/Events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Events 3 | * 4 | * @module :: Model 5 | * @description :: A short summary of how this model works and what it represents. 6 | * @docs :: http://sailsjs.org/#!documentation/models 7 | */ 8 | 9 | module.exports = { 10 | attributes: { 11 | /* e.g. 12 | nickname: 'string' 13 | */ 14 | }, 15 | 16 | // Lifecycle Callbacks 17 | beforeCreate: function(values, next) { 18 | Tags.findOne({'tag': values['tag'].toString()}, function(err, tag) { 19 | if (err) 20 | return next(err); 21 | if (!tag) 22 | return next(new Error("No tags with the following parameters were found: {'tag': " + values['tag'] + "}")); 23 | Assets.findOne({'tagID': tag.id}, function(err, asset) { 24 | if (err) 25 | return next(err); 26 | if (!asset) 27 | return next(new Error("No assets assigned to the following tag were found: {'tag': " + values['tag'] + "}")); 28 | Readers.findOne({'reader': values['reader'].toString()}, function(err, reader) { 29 | if (err) 30 | return next(err); 31 | if (!reader) 32 | return next(new Error("No readers with the following parameters were found: {'reader': " + values['reader'] + "}")); 33 | if (asset.currentReaderId === reader.id) 34 | next(); 35 | else { 36 | asset.currentReaderId = reader.id; 37 | asset.save(function(err) { 38 | if (err) 39 | return next(err); 40 | next(); 41 | }); 42 | } 43 | }); 44 | }); 45 | }); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /api/models/Readers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Readers 3 | * 4 | * @module :: Model 5 | * @description :: A short summary of how this model works and what it represents. 6 | * @docs :: http://sailsjs.org/#!documentation/models 7 | */ 8 | 9 | module.exports = { 10 | 11 | attributes: { 12 | 13 | /* e.g. 14 | nickname: 'string' 15 | */ 16 | 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /api/models/Tags.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tags 3 | * 4 | * @module :: Model 5 | * @description :: A short summary of how this model works and what it represents. 6 | * @docs :: http://sailsjs.org/#!documentation/models 7 | */ 8 | 9 | module.exports = { 10 | 11 | attributes: { 12 | 13 | /* e.g. 14 | nickname: 'string' 15 | */ 16 | 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /api/policies/isAuthenticated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * isAuthenticated 3 | * 4 | * @module :: Policy 5 | * @description :: Simple policy to allow any authenticated user 6 | * Assumes that your login action in one of your controllers sets `req.session.authenticated = true;` 7 | * @docs :: http://sailsjs.org/#!documentation/policies 8 | * 9 | */ 10 | module.exports = function(req, res, next) { 11 | 12 | // User is allowed, proceed to the next policy, 13 | // or if this is the last policy, the controller 14 | if (req.session.authenticated) { 15 | return next(); 16 | } 17 | 18 | // User is not allowed 19 | // (default res.forbidden() behavior can be overridden in `config/403.js`) 20 | return res.forbidden('You are not permitted to perform this action.'); 21 | }; 22 | -------------------------------------------------------------------------------- /api/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bredikhin/rfid-based-asset-tracking-with-nodejs-and-mongodb/a4fa5dcbf10dccaa74026d1d3a5ceb9eb7ca4970/api/services/.gitkeep -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // Start sails and pass it command line arguments 2 | require('sails').lift(require('optimist').argv); 3 | -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bredikhin/rfid-based-asset-tracking-with-nodejs-and-mongodb/a4fa5dcbf10dccaa74026d1d3a5ceb9eb7ca4970/architecture.png -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bredikhin/rfid-based-asset-tracking-with-nodejs-and-mongodb/a4fa5dcbf10dccaa74026d1d3a5ceb9eb7ca4970/assets/favicon.ico -------------------------------------------------------------------------------- /assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bredikhin/rfid-based-asset-tracking-with-nodejs-and-mongodb/a4fa5dcbf10dccaa74026d1d3a5ceb9eb7ca4970/assets/images/.gitkeep -------------------------------------------------------------------------------- /assets/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bredikhin/rfid-based-asset-tracking-with-nodejs-and-mongodb/a4fa5dcbf10dccaa74026d1d3a5ceb9eb7ca4970/assets/js/.gitkeep -------------------------------------------------------------------------------- /assets/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | * 4 | * This file contains some conventional defaults for working with Socket.io + Sails. 5 | * It is designed to get you up and running fast, but is by no means anything special. 6 | * 7 | * Feel free to change none, some, or ALL of this file to fit your needs! 8 | */ 9 | 10 | 11 | (function (io) { 12 | 13 | // as soon as this file is loaded, connect automatically, 14 | var socket = io.connect(); 15 | if (typeof console !== 'undefined') { 16 | log('Connecting to Sails.js...'); 17 | } 18 | 19 | socket.on('connect', function socketConnected() { 20 | 21 | // Listen for Comet messages from Sails 22 | socket.on('message', function messageReceived(message) { 23 | 24 | /////////////////////////////////////////////////////////// 25 | // Replace the following with your own custom logic 26 | // to run when a new message arrives from the Sails.js 27 | // server. 28 | /////////////////////////////////////////////////////////// 29 | log('New comet message received :: ', message); 30 | ////////////////////////////////////////////////////// 31 | 32 | }); 33 | 34 | 35 | /////////////////////////////////////////////////////////// 36 | // Here's where you'll want to add any custom logic for 37 | // when the browser establishes its socket connection to 38 | // the Sails.js server. 39 | /////////////////////////////////////////////////////////// 40 | log( 41 | 'Socket is now connected and globally accessible as `socket`.\n' + 42 | 'e.g. to send a GET request to Sails, try \n' + 43 | '`socket.get("/", function (response) ' + 44 | '{ console.log(response); })`' 45 | ); 46 | /////////////////////////////////////////////////////////// 47 | 48 | 49 | }); 50 | 51 | 52 | // Expose connected `socket` instance globally so that it's easy 53 | // to experiment with from the browser console while prototyping. 54 | window.socket = socket; 55 | 56 | 57 | // Simple log function to keep the example simple 58 | function log () { 59 | if (typeof console !== 'undefined') { 60 | console.log.apply(console, arguments); 61 | } 62 | } 63 | 64 | 65 | })( 66 | 67 | // In case you're wrapping socket.io to prevent pollution of the global namespace, 68 | // you can replace `window.io` with your own `io` here: 69 | window.io 70 | 71 | ); 72 | -------------------------------------------------------------------------------- /assets/js/sails.io.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sails.io.js 3 | * 4 | * This file allows you to send and receive socket.io messages to & from Sails 5 | * by simulating a REST client interface on top of socket.io. 6 | * 7 | * It models its API after the $.ajax pattern from jQuery you might be familiar with. 8 | * 9 | * So to switch from using AJAX to Socket.io, instead of: 10 | * `$.post( url, [data], [cb] )` 11 | * 12 | * You would use: 13 | * `socket.post( url, [data], [cb] )` 14 | * 15 | * For more information, visit: 16 | * http://sailsjs.org/#documentation 17 | */ 18 | 19 | (function (io) { 20 | 21 | 22 | // We'll be adding methods to `io.SocketNamespace.prototype`, the prototype for the 23 | // Socket instance returned when the browser connects with `io.connect()` 24 | var Socket = io.SocketNamespace; 25 | 26 | 27 | 28 | /** 29 | * Simulate a GET request to sails 30 | * e.g. 31 | * `socket.get('/user/3', Stats.populate)` 32 | * 33 | * @param {String} url :: destination URL 34 | * @param {Object} params :: parameters to send with the request [optional] 35 | * @param {Function} cb :: callback function to call when finished [optional] 36 | */ 37 | 38 | Socket.prototype.get = function (url, data, cb) { 39 | return this.request(url, data, cb, 'get'); 40 | }; 41 | 42 | 43 | 44 | /** 45 | * Simulate a POST request to sails 46 | * e.g. 47 | * `socket.post('/event', newMeeting, $spinner.hide)` 48 | * 49 | * @param {String} url :: destination URL 50 | * @param {Object} params :: parameters to send with the request [optional] 51 | * @param {Function} cb :: callback function to call when finished [optional] 52 | */ 53 | 54 | Socket.prototype.post = function (url, data, cb) { 55 | return this.request(url, data, cb, 'post'); 56 | }; 57 | 58 | 59 | 60 | /** 61 | * Simulate a PUT request to sails 62 | * e.g. 63 | * `socket.post('/event/3', changedFields, $spinner.hide)` 64 | * 65 | * @param {String} url :: destination URL 66 | * @param {Object} params :: parameters to send with the request [optional] 67 | * @param {Function} cb :: callback function to call when finished [optional] 68 | */ 69 | 70 | Socket.prototype.put = function (url, data, cb) { 71 | return this.request(url, data, cb, 'put'); 72 | }; 73 | 74 | 75 | 76 | /** 77 | * Simulate a DELETE request to sails 78 | * e.g. 79 | * `socket.delete('/event', $spinner.hide)` 80 | * 81 | * @param {String} url :: destination URL 82 | * @param {Object} params :: parameters to send with the request [optional] 83 | * @param {Function} cb :: callback function to call when finished [optional] 84 | */ 85 | 86 | Socket.prototype['delete'] = function (url, data, cb) { 87 | return this.request(url, data, cb, 'delete'); 88 | }; 89 | 90 | 91 | 92 | 93 | /** 94 | * Simulate HTTP over Socket.io 95 | * @api private :: but exposed for backwards compatibility w/ <= sails@~0.8 96 | */ 97 | 98 | Socket.prototype.request = request; 99 | function request (url, data, cb, method) { 100 | 101 | var socket = this; 102 | 103 | var usage = 'Usage:\n socket.' + 104 | (method || 'request') + 105 | '( destinationURL, dataToSend, fnToCallWhenComplete )'; 106 | 107 | // Remove trailing slashes and spaces 108 | url = url.replace(/^(.+)\/*\s*$/, '$1'); 109 | 110 | // If method is undefined, use 'get' 111 | method = method || 'get'; 112 | 113 | 114 | if ( typeof url !== 'string' ) { 115 | throw new Error('Invalid or missing URL!\n' + usage); 116 | } 117 | 118 | // Allow data arg to be optional 119 | if ( typeof data === 'function' ) { 120 | cb = data; 121 | data = {}; 122 | } 123 | 124 | // Build to request 125 | var json = io.JSON.stringify({ 126 | url: url, 127 | data: data 128 | }); 129 | 130 | 131 | // Send the message over the socket 132 | socket.emit(method, json, function afterEmitted (result) { 133 | 134 | var parsedResult = result; 135 | 136 | if (result && typeof result === 'string') { 137 | try { 138 | parsedResult = io.JSON.parse(result); 139 | } catch (e) { 140 | if (typeof console !== 'undefined') { 141 | console.warn("Could not parse:", result, e); 142 | } 143 | throw new Error("Server response could not be parsed!\n" + result); 144 | } 145 | } 146 | 147 | // TODO: Handle errors more effectively 148 | if (parsedResult === 404) throw new Error("404: Not found"); 149 | if (parsedResult === 403) throw new Error("403: Forbidden"); 150 | if (parsedResult === 500) throw new Error("500: Server error"); 151 | 152 | cb && cb(parsedResult); 153 | 154 | }); 155 | } 156 | 157 | 158 | 159 | 160 | }) ( 161 | 162 | // In case you're wrapping socket.io to prevent pollution of the global namespace, 163 | // you can replace `window.io` with your own `io` here: 164 | window.io 165 | 166 | ); 167 | -------------------------------------------------------------------------------- /assets/robots.txt: -------------------------------------------------------------------------------- 1 | # The robots.txt file is used to control how search engines index your live URLs. 2 | # See http://www.robotstxt.org/wc/norobots.html for more information. 3 | # 4 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 5 | # User-Agent: * 6 | # Disallow: / 7 | -------------------------------------------------------------------------------- /assets/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bredikhin/rfid-based-asset-tracking-with-nodejs-and-mongodb/a4fa5dcbf10dccaa74026d1d3a5ceb9eb7ca4970/assets/styles/.gitkeep -------------------------------------------------------------------------------- /config/400.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default 400 (Bad Request) handler 3 | * 4 | * Sails will automatically respond using this middleware when a blueprint is requested 5 | * with missing or invalid parameters 6 | * (e.g. `POST /user` was used to create a user, but required parameters were missing) 7 | * 8 | * This middleware can also be invoked manually from a controller or policy: 9 | * res.badRequest( [validationErrors], [redirectTo] ) 10 | * 11 | * 12 | * @param {Array|Object|String} validationErrors 13 | * optional errors 14 | * usually an array of validation errors from the ORM 15 | * 16 | * @param {String} redirectTo 17 | * optional URL 18 | * (absolute or relative, e.g. google.com/foo or /bar/baz) 19 | * of the page to redirect to. Usually only relevant for traditional HTTP requests, 20 | * since if this was triggered from an AJAX or socket request, JSON should be sent instead. 21 | */ 22 | 23 | module.exports[400] = function badRequest(validationErrors, redirectTo, req, res) { 24 | 25 | /* 26 | * NOTE: This function is Sails middleware-- that means that not only do `req` and `res` 27 | * work just like their Express equivalents to handle HTTP requests, they also simulate 28 | * the same interface for receiving socket messages. 29 | */ 30 | 31 | var statusCode = 400; 32 | 33 | var result = { 34 | status: statusCode 35 | }; 36 | 37 | // Optional validationErrors object 38 | if (validationErrors) { 39 | result.validationErrors = validationErrors; 40 | } 41 | 42 | // For requesters expecting JSON, everything works like you would expect-- a simple JSON response 43 | // indicating the 400: Bad Request status with relevant information will be returned. 44 | if (req.wantsJSON) { 45 | return res.json(result, result.status); 46 | } 47 | 48 | // For traditional (not-AJAX) web forms, this middleware follows best-practices 49 | // for when a user submits invalid form data: 50 | // i. First, a one-time-use flash variable is populated, probably a string message or an array 51 | // of semantic validation error objects. 52 | // ii. Then the user is redirected back to `redirectTo`, i.e. the URL where the bad request originated. 53 | // iii. There, the controller and/or view might use the flash `errors` to either display a message or highlight 54 | // the invalid HTML form fields. 55 | if (redirectTo) { 56 | 57 | // Set flash message called `errors` (one-time-use in session) 58 | req.flash('errors', validationErrors); 59 | 60 | // then redirect back to the `redirectTo` URL 61 | return res.redirect(redirectTo); 62 | } 63 | 64 | 65 | // Depending on your app's needs, you may choose to look at the Referer header here 66 | // and redirect back. Please do so at your own risk! 67 | // For security reasons, Sails does not provide this affordance by default. 68 | // It's safest to provide a 'redirectTo' URL and redirect there directly. 69 | 70 | // If `redirectTo` was not specified, just respond w/ JSON 71 | return res.json(result, result.status); 72 | 73 | }; -------------------------------------------------------------------------------- /config/403.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default 403 (Forbidden) middleware 3 | * 4 | * This middleware can be invoked from a controller or policy: 5 | * res.forbidden( [message] ) 6 | * 7 | * 8 | * @param {String|Object|Array} message 9 | * optional message to inject into view locals or JSON response 10 | * 11 | */ 12 | 13 | module.exports[403] = function badRequest(message, req, res) { 14 | 15 | /* 16 | * NOTE: This function is Sails middleware-- that means that not only do `req` and `res` 17 | * work just like their Express equivalents to handle HTTP requests, they also simulate 18 | * the same interface for receiving socket messages. 19 | */ 20 | 21 | var viewFilePath = '403'; 22 | var statusCode = 403; 23 | 24 | var result = { 25 | status: statusCode 26 | }; 27 | 28 | // Optional message 29 | if (message) { 30 | result.message = message; 31 | } 32 | 33 | // If the user-agent wants a JSON response, send json 34 | if (req.wantsJSON) { 35 | return res.json(result, result.status); 36 | } 37 | 38 | // Set status code and view locals 39 | res.status(result.status); 40 | for (var key in result) { 41 | res.locals[key] = result[key]; 42 | } 43 | // And render view 44 | res.render(viewFilePath, result, function (err) { 45 | // If the view doesn't exist, or an error occured, send json 46 | if (err) { return res.json(result, result.status); } 47 | 48 | // Otherwise, serve the `views/403.*` page 49 | res.render(viewFilePath); 50 | }); 51 | 52 | }; -------------------------------------------------------------------------------- /config/404.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default 404 (Not Found) handler 3 | * 4 | * If no route matches are found for a request, Sails will respond using this handler. 5 | * 6 | * This middleware can also be invoked manually from a controller or policy: 7 | * Usage: res.notFound() 8 | */ 9 | 10 | module.exports[404] = function pageNotFound(req, res) { 11 | 12 | /* 13 | * NOTE: This function is Sails middleware-- that means that not only do `req` and `res` 14 | * work just like their Express equivalents to handle HTTP requests, they also simulate 15 | * the same interface for receiving socket messages. 16 | */ 17 | 18 | var viewFilePath = '404'; 19 | var statusCode = 404; 20 | var result = { 21 | status: statusCode 22 | }; 23 | 24 | // If the user-agent wants a JSON response, send json 25 | if (req.wantsJSON) { 26 | return res.json(result, result.status); 27 | } 28 | 29 | res.status(result.status); 30 | res.render(viewFilePath, function (err) { 31 | // If the view doesn't exist, or an error occured, send json 32 | if (err) { return res.json(result, result.status); } 33 | 34 | // Otherwise, serve the `views/404.*` page 35 | res.render(viewFilePath); 36 | }); 37 | 38 | }; -------------------------------------------------------------------------------- /config/500.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default 500 (Server Error) middleware 3 | * 4 | * If an error is thrown in a policy or controller, 5 | * Sails will respond using this default error handler 6 | * 7 | * This middleware can also be invoked manually from a controller or policy: 8 | * res.serverError( [errors] ) 9 | * 10 | * 11 | * @param {Array|Object|String} errors 12 | * optional errors 13 | */ 14 | 15 | module.exports[500] = function serverErrorOccurred(errors, req, res) { 16 | 17 | /* 18 | * NOTE: This function is Sails middleware-- that means that not only do `req` and `res` 19 | * work just like their Express equivalents to handle HTTP requests, they also simulate 20 | * the same interface for receiving socket messages. 21 | */ 22 | 23 | var viewFilePath = '500', 24 | statusCode = 500, 25 | i, errorToLog, errorToJSON; 26 | 27 | var result = { 28 | status: statusCode 29 | }; 30 | 31 | // Normalize a {String|Object|Error} or array of {String|Object|Error} 32 | // into an array of proper, readable {Error} 33 | var errorsToDisplay = sails.util.normalizeErrors(errors); 34 | for (i in errorsToDisplay) { 35 | 36 | // Log error(s) as clean `stack` 37 | // (avoids ending up with \n, etc.) 38 | if ( errorsToDisplay[i].original ) { 39 | errorToLog = sails.util.inspect(errorsToDisplay[i].original); 40 | } 41 | else { 42 | errorToLog = errorsToDisplay[i].stack; 43 | } 44 | sails.log.error('Server Error (500)'); 45 | sails.log.error(errorToLog); 46 | 47 | // Use original error if it exists 48 | errorToJSON = errorsToDisplay[i].original || errorsToDisplay[i].message; 49 | errorsToDisplay[i] = errorToJSON; 50 | } 51 | 52 | // Only include errors if application environment is set to 'development' 53 | // In production, don't display any identifying information about the error(s) 54 | if (sails.config.environment === 'development') { 55 | result.errors = errorsToDisplay; 56 | } 57 | 58 | // If the user-agent wants JSON, respond with JSON 59 | if (req.wantsJSON) { 60 | return res.json(result, result.status); 61 | } 62 | 63 | // Set status code and view locals 64 | res.status(result.status); 65 | for (var key in result) { 66 | res.locals[key] = result[key]; 67 | } 68 | // And render view 69 | res.render(viewFilePath, result, function (err) { 70 | // If the view doesn't exist, or an error occured, just send JSON 71 | if (err) { return res.json(result, result.status); } 72 | 73 | // Otherwise, if it can be rendered, the `views/500.*` page is rendered 74 | res.render(viewFilePath, result); 75 | }); 76 | 77 | }; 78 | -------------------------------------------------------------------------------- /config/adapters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global adapter config 3 | * 4 | * The `adapters` configuration object lets you create different global "saved settings" 5 | * that you can mix and match in your models. The `default` option indicates which 6 | * "saved setting" should be used if a model doesn't have an adapter specified. 7 | * 8 | * Keep in mind that options you define directly in your model definitions 9 | * will override these settings. 10 | * 11 | * For more information on adapter configuration, check out: 12 | * http://sailsjs.org/#documentation 13 | */ 14 | 15 | module.exports.adapters = { 16 | 17 | // If you leave the adapter config unspecified 18 | // in a model definition, 'default' will be used. 19 | 'default': 'disk', 20 | 21 | // Persistent adapter for DEVELOPMENT ONLY 22 | // (data is preserved when the server shuts down) 23 | disk: { 24 | module: 'sails-disk' 25 | }, 26 | 27 | // MySQL is the world's most popular relational database. 28 | // Learn more: http://en.wikipedia.org/wiki/MySQL 29 | myLocalMySQLDatabase: { 30 | 31 | module: 'sails-mysql', 32 | host: 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS', 33 | user: 'YOUR_MYSQL_USER', 34 | // Psst.. You can put your password in config/local.js instead 35 | // so you don't inadvertently push it up if you're using version control 36 | password: 'YOUR_MYSQL_PASSWORD', 37 | database: 'YOUR_MYSQL_DB' 38 | } 39 | }; -------------------------------------------------------------------------------- /config/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap 3 | * 4 | * An asynchronous boostrap function that runs before your Sails app gets lifted. 5 | * This gives you an opportunity to set up your data model, run jobs, or perform some special logic. 6 | * 7 | * For more information on bootstrapping your app, check out: 8 | * http://sailsjs.org/#documentation 9 | */ 10 | 11 | module.exports.bootstrap = function (cb) { 12 | 13 | // It's very important to trigger this callack method when you are finished 14 | // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap) 15 | cb(); 16 | }; -------------------------------------------------------------------------------- /config/controllers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Controllers 3 | * 4 | * By default, Sails inspects your controllers, models, and configuration and binds 5 | * certain routes automatically. These dynamically generated routes are called blueprints. 6 | * 7 | * These settings are for the global configuration of controllers & blueprint routes. 8 | * You may also override these settings on a per-controller basis by defining a '_config' 9 | * key in any of your controller files, and assigning it an object, e.g.: 10 | * { 11 | * // ... 12 | * _config: { blueprints: { rest: false } } 13 | * // ... 14 | * } 15 | * 16 | * For more information on configuring controllers and blueprints, check out: 17 | * http://sailsjs.org/#documentation 18 | */ 19 | 20 | module.exports.controllers = { 21 | 22 | 23 | /** 24 | * NOTE: 25 | * A lot of the configuration options below affect so-called "CRUD methods", 26 | * or your controllers' `find`, `create`, `update`, and `destroy` actions. 27 | * 28 | * It's important to realize that, even if you haven't defined these yourself, as long as 29 | * a model exists with the same name as the controller, Sails will respond with built-in CRUD 30 | * logic in the form of a JSON API, including support for sort, pagination, and filtering. 31 | */ 32 | blueprints: { 33 | 34 | /** 35 | * `actions` 36 | * 37 | * Action blueprints speed up backend development and shorten the development workflow by 38 | * eliminating the need to manually bind routes. 39 | * When enabled, GET, POST, PUT, and DELETE routes will be generated for every one of a controller's actions. 40 | * 41 | * If an `index` action exists, additional naked routes will be created for it. 42 | * Finally, all `actions` blueprints support an optional path parameter, `id`, for convenience. 43 | * 44 | * For example, assume we have an EmailController with actions `send` and `index`. 45 | * With `actions` enabled, the following blueprint routes would be bound at runtime: 46 | * 47 | * `EmailController.index` 48 | * ::::::::::::::::::::::::::::::::::::::::::::::::::::::: 49 | * `GET /email/:id?` `GET /email/index/:id?` 50 | * `POST /email/:id?` `POST /email/index/:id?` 51 | * `PUT /email/:id?` `PUT /email/index/:id?` 52 | * `DELETE /email/:id?` `DELETE /email/index/:id?` 53 | * 54 | * `EmailController.send` 55 | * ::::::::::::::::::::::::::::::::::::::::::::::::::::::: 56 | * `GET /email/send/:id?` 57 | * `POST /email/send/:id?` 58 | * `PUT /email/send/:id?` 59 | * `DELETE /email/send/:id?` 60 | * 61 | * 62 | * `actions` are enabled by default, and are OK for production-- however, 63 | * you must take great care not to inadvertently expose unsafe controller logic to GET requests. 64 | */ 65 | actions: true, 66 | 67 | 68 | 69 | /** 70 | * `rest` 71 | * 72 | * REST blueprints are the automatically generated routes Sails uses to expose 73 | * a conventional REST API on top of a controller's `find`, `create`, `update`, and `destroy` 74 | * actions. 75 | * 76 | * For example, a BoatController with `rest` enabled generates the following routes: 77 | * ::::::::::::::::::::::::::::::::::::::::::::::::::::::: 78 | * GET /boat/:id? -> BoatController.find 79 | * POST /boat -> BoatController.create 80 | * PUT /boat/:id -> BoatController.update 81 | * DELETE /boat/:id -> BoatController.destroy 82 | * 83 | * `rest` blueprints are enabled by default, and suitable for a production scenario. 84 | */ 85 | rest: true, 86 | 87 | 88 | /** 89 | * `shortcuts` 90 | * 91 | * Shortcut blueprints are simple helpers to provide access to a controller's CRUD methods 92 | * from your browser's URL bar. When enabled, GET, POST, PUT, and DELETE routes will be generated 93 | * for the controller's`find`, `create`, `update`, and `destroy` actions. 94 | * 95 | * `shortcuts` are enabled by default, but SHOULD BE DISABLED IN PRODUCTION!!!!! 96 | */ 97 | shortcuts: true, 98 | 99 | 100 | 101 | /** 102 | * `prefix` 103 | * 104 | * An optional mount path for all blueprint routes on a controller, including `rest`, 105 | * `actions`, and `shortcuts`. This allows you to continue to use blueprints, even if you 106 | * need to namespace your API methods. 107 | * 108 | * For example, `prefix: '/api/v2'` would make the following REST blueprint routes 109 | * for a FooController: 110 | * 111 | * `GET /api/v2/foo/:id?` 112 | * `POST /api/v2/foo` 113 | * `PUT /api/v2/foo/:id` 114 | * `DELETE /api/v2/foo/:id` 115 | * 116 | * By default, no prefix is used. 117 | */ 118 | prefix: '', 119 | 120 | 121 | 122 | 123 | 124 | /** 125 | * `pluralize` 126 | * 127 | * Whether to pluralize controller names in generated routes 128 | * 129 | * For example, REST blueprints for `FooController` with `pluralize` enabled: 130 | * GET /foos/:id? 131 | * POST /foos 132 | * PUT /foos/:id? 133 | * DELETE /foos/:id? 134 | */ 135 | pluralize: false 136 | 137 | }, 138 | 139 | 140 | 141 | /** 142 | * `jsonp` 143 | * 144 | * If enabled, allows built-in CRUD methods to support JSONP for cross-domain requests. 145 | * 146 | * Example usage (REST blueprint + UserController): 147 | * `GET /user?name=ciaran&limit=10&callback=receiveJSONPResponse` 148 | * 149 | * Defaults to false. 150 | */ 151 | jsonp: false, 152 | 153 | 154 | 155 | /** 156 | * `expectIntegerId` 157 | * 158 | * If enabled, built-in CRUD methods will only accept valid integers as an :id parameter. 159 | * 160 | * i.e. trigger built-in API if requests look like: 161 | * `GET /user/8` 162 | * but not like: 163 | * `GET /user/a8j4g9jsd9ga4ghjasdha` 164 | * 165 | * Defaults to false. 166 | */ 167 | expectIntegerId: false 168 | 169 | }; 170 | -------------------------------------------------------------------------------- /config/cors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Origin Resource Sharing (CORS) 3 | * 4 | * CORS is like a more modern version of JSONP-- it allows your server/API 5 | * to successfully respond to requests from client-side JavaScript code 6 | * running on some other domain (e.g. google.com) 7 | * Unlike JSONP, it works with POST, PUT, and DELETE requests 8 | * 9 | * For more information on CORS, check out: 10 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 11 | * 12 | * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis 13 | * by adding a "cors" object to the route configuration: 14 | * 15 | * '/get foo': { 16 | * controller: 'foo', 17 | * action: 'bar', 18 | * cors: { 19 | * origin: 'http://foobar.com,https://owlhoot.com' 20 | * } 21 | * } 22 | * 23 | */ 24 | 25 | module.exports.cors = { 26 | 27 | // Allow CORS on all routes by default? If not, you must enable CORS on a 28 | // per-route basis by either adding a "cors" configuration object 29 | // to the route config, or setting "cors:true" in the route config to 30 | // use the default settings below. 31 | allRoutes: false, 32 | 33 | // Which domains which are allowed CORS access? 34 | // This can be a comma-delimited list of hosts (beginning with http:// or https://) 35 | // or "*" to allow all domains CORS access. 36 | origin: '*', 37 | 38 | // Allow cookies to be shared for CORS requests? 39 | credentials: true, 40 | 41 | // Which methods should be allowed for CORS requests? This is only used 42 | // in response to preflight requests (see article linked above for more info) 43 | methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD', 44 | 45 | // Which headers should be allowed for CORS requests? This is only used 46 | // in response to preflight requests. 47 | headers: 'content-type' 48 | 49 | }; -------------------------------------------------------------------------------- /config/csrf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Site Request Forgery Protection 3 | * 4 | * CSRF tokens are like a tracking chip. While a session tells the server that a user 5 | * "is who they say they are", a csrf token tells the server "you are where you say you are". 6 | * 7 | * When enabled, all non-GET requests to the Sails server must be accompanied by 8 | * a special token, identified as the '_csrf' parameter. 9 | * 10 | * This option protects your Sails app against cross-site request forgery (or CSRF) attacks. 11 | * A would-be attacker needs not only a user's session cookie, but also this timestamped, 12 | * secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain. 13 | * 14 | * This allows us to have certainty that our users' requests haven't been hijacked, 15 | * and that the requests they're making are intentional and legitimate. 16 | * 17 | * This token has a short-lived expiration timeline, and must be acquired by either: 18 | * 19 | * (a) For traditional view-driven web apps: 20 | * Fetching it from one of your views, where it may be accessed as 21 | * a local variable, e.g.: 22 | *
23 | * 24 | *
25 | * 26 | * or (b) For AJAX/Socket-heavy and/or single-page apps: 27 | * Sending a GET request to the `/csrfToken` route, where it will be returned 28 | * as JSON, e.g.: 29 | * { _csrf: 'ajg4JD(JGdajhLJALHDa' } 30 | * 31 | * 32 | * Enabling this option requires managing the token in your front-end app. 33 | * For traditional web apps, it's as easy as passing the data from a view into a form action. 34 | * In AJAX/Socket-heavy apps, just send a GET request to the /csrfToken route to get a valid token. 35 | * 36 | * For more information on CSRF, check out: 37 | * http://en.wikipedia.org/wiki/Cross-site_request_forgery 38 | */ 39 | 40 | module.exports.csrf = false; -------------------------------------------------------------------------------- /config/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internationalization / Localization Settings 3 | * 4 | * If your app will touch people from all over the world, i18n (or internationalization) 5 | * may be an important part of your international strategy. 6 | * 7 | * 8 | * For more information, check out: 9 | * http://sailsjs.org/#documentation 10 | */ 11 | 12 | module.exports.i18n = { 13 | 14 | // Which locales are supported? 15 | locales: ['en', 'es', 'fr', 'de'] 16 | 17 | }; 18 | -------------------------------------------------------------------------------- /config/locales/_README.md: -------------------------------------------------------------------------------- 1 | # Internationalization / Localization Settings 2 | 3 | ## Locale 4 | All locale files live under `config/locales`. Here is where you can add locale data as JSON key-value pairs. The name of the file should match the language that you are supporting, which allows for automatic language detection based on the user request. 5 | 6 | Here is an example locale stringfile for the Spanish language (`config/locales/es.json`): 7 | ```json 8 | { 9 | "Hello!": "Hola!", 10 | "Hello %s, how are you today?": "¿Hola %s, como estas?", 11 | } 12 | ``` 13 | ## Usage 14 | Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions. 15 | Remember that the keys are case sensitive and require exact key matches, e.g. 16 | 17 | ```ejs 18 |

<%= __('Welcome to PencilPals!') %>

19 |

<%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>

20 |

<%= i18n('That\'s right-- you can use either i18n() or __()') %>

21 | ``` 22 | 23 | ## Configuration 24 | Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales. -------------------------------------------------------------------------------- /config/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Wilkommen" 3 | } -------------------------------------------------------------------------------- /config/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Welcome" 3 | } 4 | -------------------------------------------------------------------------------- /config/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenido" 3 | } 4 | -------------------------------------------------------------------------------- /config/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenue" 3 | } 4 | -------------------------------------------------------------------------------- /config/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Logger configuration 3 | * 4 | * Configure the log level for your app, as well as the transport 5 | * (Underneath the covers, Sails uses Winston for logging, which 6 | * allows for some pretty neat custom transports/adapters for log messages) 7 | * 8 | * For more information on the Sails logger, check out: 9 | * http://sailsjs.org/#documentation 10 | */ 11 | 12 | module.exports = { 13 | 14 | // Valid `level` configs: 15 | // i.e. the minimum log level to capture with sails.log.*() 16 | // 17 | // 'error' : Display calls to `.error()` 18 | // 'warn' : Display calls from `.error()` to `.warn()` 19 | // 'debug' : Display calls from `.error()`, `.warn()` to `.debug()` 20 | // 'info' : Display calls from `.error()`, `.warn()`, `.debug()` to `.info()` 21 | // 'verbose': Display calls from `.error()`, `.warn()`, `.debug()`, `.info()` to `.verbose()` 22 | // 23 | log: { 24 | level: 'info' 25 | } 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /config/policies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Policy mappings (ACL) 3 | * 4 | * Policies are simply Express middleware functions which run **before** your controllers. 5 | * You can apply one or more policies to a given controller, or protect just one of its actions. 6 | * 7 | * Any policy file (e.g. `authenticated.js`) can be dropped into the `/policies` folder, 8 | * at which point it can be accessed below by its filename, minus the extension, (e.g. `authenticated`) 9 | * 10 | * For more information on policies, check out: 11 | * http://sailsjs.org/#documentation 12 | */ 13 | 14 | 15 | module.exports.policies = { 16 | 17 | // Default policy for all controllers and actions 18 | // (`true` allows public access) 19 | '*': true 20 | 21 | /* 22 | // Here's an example of adding some policies to a controller 23 | RabbitController: { 24 | 25 | // Apply the `false` policy as the default for all of RabbitController's actions 26 | // (`false` prevents all access, which ensures that nothing bad happens to our rabbits) 27 | '*': false, 28 | 29 | // For the action `nurture`, apply the 'isRabbitMother' policy 30 | // (this overrides `false` above) 31 | nurture : 'isRabbitMother', 32 | 33 | // Apply the `isNiceToAnimals` AND `hasRabbitFood` policies 34 | // before letting any users feed our rabbits 35 | feed : ['isNiceToAnimals', 'hasRabbitFood'] 36 | } 37 | */ 38 | }; 39 | 40 | 41 | /** 42 | * Here's what the `isNiceToAnimals` policy from above might look like: 43 | * (this file would be located at `policies/isNiceToAnimals.js`) 44 | * 45 | * We'll make some educated guesses about whether our system will 46 | * consider this user someone who is nice to animals. 47 | * 48 | * Besides protecting rabbits (while a noble cause, no doubt), 49 | * here are a few other example use cases for policies: 50 | * 51 | * + cookie-based authentication 52 | * + role-based access control 53 | * + limiting file uploads based on MB quotas 54 | * + OAuth 55 | * + BasicAuth 56 | * + or any other kind of authentication scheme you can imagine 57 | * 58 | */ 59 | 60 | /* 61 | module.exports = function isNiceToAnimals (req, res, next) { 62 | 63 | // `req.session` contains a set of data specific to the user making this request. 64 | // It's kind of like our app's "memory" of the current user. 65 | 66 | // If our user has a history of animal cruelty, not only will we 67 | // prevent her from going even one step further (`return`), 68 | // we'll go ahead and redirect her to PETA (`res.redirect`). 69 | if ( req.session.user.hasHistoryOfAnimalCruelty ) { 70 | return res.redirect('http://PETA.org'); 71 | } 72 | 73 | // If the user has been seen frowning at puppies, we have to assume that 74 | // they might end up being mean to them, so we'll 75 | if ( req.session.user.frownsAtPuppies ) { 76 | return res.redirect('http://www.dailypuppy.com/'); 77 | } 78 | 79 | // Finally, if the user has a clean record, we'll call the `next()` function 80 | // to let them through to the next policy or our controller 81 | next(); 82 | }; 83 | */ 84 | -------------------------------------------------------------------------------- /config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Routes 3 | * 4 | * Sails uses a number of different strategies to route requests. 5 | * Here they are top-to-bottom, in order of precedence. 6 | * 7 | * For more information on routes, check out: 8 | * http://sailsjs.org/#documentation 9 | */ 10 | 11 | 12 | 13 | /** 14 | * (1) Core middleware 15 | * 16 | * Middleware included with `app.use` is run first, before the router 17 | */ 18 | 19 | 20 | /** 21 | * (2) Static routes 22 | * 23 | * This object routes static URLs to handler functions-- 24 | * In most cases, these functions are actions inside of your controllers. 25 | * For convenience, you can also connect routes directly to views or external URLs. 26 | * 27 | */ 28 | 29 | module.exports.routes = { 30 | 31 | // By default, your root route (aka home page) points to a view 32 | // located at `views/home/index.ejs` 33 | // 34 | // (This would also work if you had a file at: `/views/home.ejs`) 35 | '/': { 36 | view: 'home/index' 37 | } 38 | 39 | /* 40 | // But what if you want your home page to display 41 | // a signup form located at `views/user/signup.ejs`? 42 | '/': { 43 | view: 'user/signup' 44 | } 45 | 46 | 47 | // Let's say you're building an email client, like Gmail 48 | // You might want your home route to serve an interface using custom logic. 49 | // In this scenario, you have a custom controller `MessageController` 50 | // with an `inbox` action. 51 | '/': 'MessageController.inbox' 52 | 53 | 54 | // Alternatively, you can use the more verbose syntax: 55 | '/': { 56 | controller: 'MessageController', 57 | action: 'inbox' 58 | } 59 | 60 | 61 | // If you decided to call your action `index` instead of `inbox`, 62 | // since the `index` action is the default, you can shortcut even further to: 63 | '/': 'MessageController' 64 | 65 | 66 | // Up until now, we haven't specified a specific HTTP method/verb 67 | // The routes above will apply to ALL verbs! 68 | // If you want to set up a route only for one in particular 69 | // (GET, POST, PUT, DELETE, etc.), just specify the verb before the path. 70 | // For example, if you have a `UserController` with a `signup` action, 71 | // and somewhere else, you're serving a signup form looks like: 72 | // 73 | //
74 | // 75 | // 76 | // 77 | //
78 | 79 | // You would want to define the following route to handle your form: 80 | 'post /signup': 'UserController.signup' 81 | 82 | 83 | // What about the ever-popular "vanity URLs" aka URL slugs? 84 | // (you might remember doing this with `mod_rewrite` in Apache) 85 | // 86 | // This is where you want to set up root-relative dynamic routes like: 87 | // http://yourwebsite.com/twinkletoez 88 | // 89 | // NOTE: 90 | // You'll still want to allow requests through to the static assets, 91 | // so we need to set up this route to ignore URLs that have a trailing ".": 92 | // (e.g. your javascript, CSS, and image files) 93 | 'get /*(^.*)': 'UserController.profile' 94 | 95 | */ 96 | }; 97 | 98 | 99 | 100 | /** 101 | * (3) Action blueprints 102 | * These routes can be disabled by setting (in `config/controllers.js`): 103 | * `module.exports.controllers.blueprints.actions = false` 104 | * 105 | * All of your controllers ' actions are automatically bound to a route. For example: 106 | * + If you have a controller, `FooController`: 107 | * + its action `bar` is accessible at `/foo/bar` 108 | * + its action `index` is accessible at `/foo/index`, and also `/foo` 109 | */ 110 | 111 | 112 | /** 113 | * (4) Shortcut CRUD blueprints 114 | * 115 | * These routes can be disabled by setting (in config/controllers.js) 116 | * `module.exports.controllers.blueprints.shortcuts = false` 117 | * 118 | * If you have a model, `Foo`, and a controller, `FooController`, 119 | * you can access CRUD operations for that model at: 120 | * /foo/find/:id? -> search lampshades using specified criteria or with id=:id 121 | * 122 | * /foo/create -> create a lampshade using specified values 123 | * 124 | * /foo/update/:id -> update the lampshade with id=:id 125 | * 126 | * /foo/destroy/:id -> delete lampshade with id=:id 127 | * 128 | */ 129 | 130 | /** 131 | * (5) REST blueprints 132 | * 133 | * These routes can be disabled by setting (in config/controllers.js) 134 | * `module.exports.controllers.blueprints.rest = false` 135 | * 136 | * If you have a model, `Foo`, and a controller, `FooController`, 137 | * you can access CRUD operations for that model at: 138 | * 139 | * get /foo/:id? -> search lampshades using specified criteria or with id=:id 140 | * 141 | * post /foo -> create a lampshade using specified values 142 | * 143 | * put /foo/:id -> update the lampshade with id=:id 144 | * 145 | * delete /foo/:id -> delete lampshade with id=:id 146 | * 147 | */ 148 | 149 | /** 150 | * (6) Static assets 151 | * 152 | * Flat files in your `assets` directory- (these are sometimes referred to as 'public') 153 | * If you have an image file at `/assets/images/foo.jpg`, it will be made available 154 | * automatically via the route: `/images/foo.jpg` 155 | * 156 | */ 157 | 158 | 159 | 160 | /** 161 | * (7) 404 (not found) handler 162 | * 163 | * Finally, if nothing else matched, the default 404 handler is triggered. 164 | * See `config/404.js` to adjust your app's 404 logic. 165 | */ 166 | 167 | -------------------------------------------------------------------------------- /config/session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Session 3 | * 4 | * Sails session integration leans heavily on the great work already done by Express, but also unifies 5 | * Socket.io with the Connect session store. It uses Connect's cookie parser to normalize configuration 6 | * differences between Express and Socket.io and hooks into Sails' middleware interpreter to allow you 7 | * to access and auto-save to `req.session` with Socket.io the same way you would with Express. 8 | * 9 | * For more information on configuring the session, check out: 10 | * http://sailsjs.org/#documentation 11 | */ 12 | 13 | module.exports.session = { 14 | 15 | // Session secret is automatically generated when your new app is created 16 | // Replace at your own risk in production-- you will invalidate the cookies of your users, 17 | // forcing them to log in again. 18 | secret: '7178de3bec90d9c2d2e0bd9c2e1eb242' 19 | 20 | 21 | // In production, uncomment the following lines to set up a shared redis session store 22 | // that can be shared across multiple Sails.js servers 23 | // adapter: 'redis', 24 | // 25 | // The following values are optional, if no options are set a redis instance running 26 | // on localhost is expected. 27 | // Read more about options at: https://github.com/visionmedia/connect-redis 28 | // 29 | // host: 'localhost', 30 | // port: 6379, 31 | // ttl: , 32 | // db: 0, 33 | // pass: 34 | // prefix: 'sess:' 35 | 36 | 37 | // Uncomment the following lines to use your Mongo adapter as a session store 38 | // adapter: 'mongo', 39 | // 40 | // host: 'localhost', 41 | // port: 27017, 42 | // db: 'sails', 43 | // collection: 'sessions', 44 | // 45 | // Optional Values: 46 | // 47 | // # Note: url will override other connection settings 48 | // url: 'mongodb://user:pass@host:port/database/collection', 49 | // 50 | // username: '', 51 | // password: '', 52 | // auto_reconnect: false, 53 | // ssl: false, 54 | // stringify: true 55 | 56 | }; 57 | -------------------------------------------------------------------------------- /config/sockets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Socket Configuration 3 | * 4 | * These configuration options provide transparent access to Sails' encapsulated 5 | * pubsub/socket server for complete customizability. 6 | * 7 | * For more information on using Sails with Sockets, check out: 8 | * http://sailsjs.org/#documentation 9 | */ 10 | 11 | module.exports.sockets = { 12 | 13 | // This custom onConnect function will be run each time AFTER a new socket connects 14 | // (To control whether a socket is allowed to connect, check out `authorization` config.) 15 | // Keep in mind that Sails' RESTful simulation for sockets 16 | // mixes in socket.io events for your routes and blueprints automatically. 17 | onConnect: function(session, socket) { 18 | 19 | // By default: do nothing 20 | // This is a good place to subscribe a new socket to a room, inform other users that 21 | // someone new has come online, or any other custom socket.io logic 22 | }, 23 | 24 | // This custom onDisconnect function will be run each time a socket disconnects 25 | onDisconnect: function(session, socket) { 26 | 27 | // By default: do nothing 28 | // This is a good place to broadcast a disconnect message, or any other custom socket.io logic 29 | }, 30 | 31 | 32 | 33 | // `transports` 34 | // 35 | // A array of allowed transport methods which the clients will try to use. 36 | // The flashsocket transport is disabled by default 37 | // You can enable flashsockets by adding 'flashsocket' to this list: 38 | transports: [ 39 | 'websocket', 40 | 'htmlfile', 41 | 'xhr-polling', 42 | 'jsonp-polling' 43 | ], 44 | 45 | 46 | 47 | 48 | // Use this option to set the datastore socket.io will use to manage rooms/sockets/subscriptions: 49 | // default: memory 50 | adapter: 'memory', 51 | 52 | 53 | // Node.js (and consequently Sails.js) apps scale horizontally. 54 | // It's a powerful, efficient approach, but it involves a tiny bit of planning. 55 | // At scale, you'll want to be able to copy your app onto multiple Sails.js servers 56 | // and throw them behind a load balancer. 57 | // 58 | // One of the big challenges of scaling an application is that these sorts of clustered 59 | // deployments cannot share memory, since they are on physically different machines. 60 | // On top of that, there is no guarantee that a user will "stick" with the same server between 61 | // requests (whether HTTP or sockets), since the load balancer will route each request to the 62 | // Sails server with the most available resources. However that means that all room/pubsub/socket 63 | // processing and shared memory has to be offloaded to a shared, remote messaging queue (usually Redis) 64 | // 65 | // Luckily, Socket.io (and consequently Sails.js) apps support Redis for sockets by default. 66 | // To enable a remote redis pubsub server: 67 | // adapter: 'redis', 68 | // host: '127.0.0.1', 69 | // port: 6379, 70 | // db: 'sails', 71 | // pass: '' 72 | // Worth mentioning is that, if `adapter` config is `redis`, 73 | // but host/port is left unset, Sails will try to connect to redis 74 | // running on localhost via port 6379 75 | 76 | 77 | 78 | // `authorization` 79 | // 80 | // Global authorization for Socket.IO access, 81 | // this is called when the initial handshake is performed with the server. 82 | // 83 | // By default (`authorization: true`), when a socket tries to connect, Sails verifies 84 | // that a valid cookie was sent with the upgrade request. If the cookie doesn't match 85 | // any known user session, a new user session is created for it. 86 | // 87 | // However, in the case of cross-domain requests, it is possible to receive a connection 88 | // upgrade request WITHOUT A COOKIE (for certain transports) 89 | // In this case, there is no way to keep track of the requesting user between requests, 90 | // since there is no identifying information to link him/her with a session. 91 | // 92 | // If you don't care about keeping track of your socket users between requests, 93 | // you can bypass this cookie check by setting `authorization: false` 94 | // which will disable the session for socket requests (req.session is still accessible 95 | // in each request, but it will be empty, and any changes to it will not be persisted) 96 | // 97 | // On the other hand, if you DO need to keep track of user sessions, 98 | // you can pass along a ?cookie query parameter to the upgrade url, 99 | // which Sails will use in the absense of a proper cookie 100 | // e.g. (when connection from the client): 101 | // io.connect('http://localhost:1337?cookie=smokeybear') 102 | // 103 | // (Un)fortunately, the user's cookie is (should!) not accessible in client-side js. 104 | // Using HTTP-only cookies is crucial for your app's security. 105 | // Primarily because of this situation, as well as a handful of other advanced 106 | // use cases, Sails allows you to override the authorization behavior 107 | // with your own custom logic by specifying a function, e.g: 108 | /* 109 | authorization: function authorizeAttemptedSocketConnection(reqObj, cb) { 110 | 111 | // Any data saved in `handshake` is available in subsequent requests 112 | // from this as `req.socket.handshake.*` 113 | 114 | // 115 | // to allow the connection, call `cb(null, true)` 116 | // to prevent the connection, call `cb(null, false)` 117 | // to report an error, call `cb(err)` 118 | } 119 | */ 120 | authorization: true, 121 | 122 | // Match string representing the origins that are allowed to connect to the Socket.IO server 123 | origins: '*:*', 124 | 125 | // Should we use heartbeats to check the health of Socket.IO connections? 126 | heartbeats: true, 127 | 128 | // When client closes connection, the # of seconds to wait before attempting a reconnect. 129 | // This value is sent to the client after a successful handshake. 130 | 'close timeout': 60, 131 | 132 | // The # of seconds between heartbeats sent from the client to the server 133 | // This value is sent to the client after a successful handshake. 134 | 'heartbeat timeout': 60, 135 | 136 | // The max # of seconds to wait for an expcted heartbeat before declaring the pipe broken 137 | // This number should be less than the `heartbeat timeout` 138 | 'heartbeat interval': 25, 139 | 140 | // The maximum duration of one HTTP poll- 141 | // if it exceeds this limit it will be closed. 142 | 'polling duration': 20, 143 | 144 | // Enable the flash policy server if the flashsocket transport is enabled 145 | // 'flash policy server': true, 146 | 147 | // By default the Socket.IO client will check port 10843 on your server 148 | // to see if flashsocket connections are allowed. 149 | // The Adobe Flash Player normally uses 843 as default port, 150 | // but Socket.io defaults to a non root port (10843) by default 151 | // 152 | // If you are using a hosting provider that doesn't allow you to start servers 153 | // other than on port 80 or the provided port, and you still want to support flashsockets 154 | // you can set the `flash policy port` to -1 155 | 'flash policy port': 10843, 156 | 157 | // Used by the HTTP transports. The Socket.IO server buffers HTTP request bodies up to this limit. 158 | // This limit is not applied to websocket or flashsockets. 159 | 'destroy buffer size': '10E7', 160 | 161 | // Do we need to destroy non-socket.io upgrade requests? 162 | 'destroy upgrade': true, 163 | 164 | // Should Sails/Socket.io serve the `socket.io.js` client? 165 | // (as well as WebSocketMain.swf for Flash sockets, etc.) 166 | 'browser client': true, 167 | 168 | // Cache the Socket.IO file generation in the memory of the process 169 | // to speed up the serving of the static files. 170 | 'browser client cache': true, 171 | 172 | // Does Socket.IO need to send a minified build of the static client script? 173 | 'browser client minification': false, 174 | 175 | // Does Socket.IO need to send an ETag header for the static requests? 176 | 'browser client etag': false, 177 | 178 | // Adds a Cache-Control: private, x-gzip-ok="", max-age=31536000 header to static requests, 179 | // but only if the file is requested with a version number like /socket.io/socket.io.v0.9.9.js. 180 | 'browser client expires': 315360000, 181 | 182 | // Does Socket.IO need to GZIP the static files? 183 | // This process is only done once and the computed output is stored in memory. 184 | // So we don't have to spawn a gzip process for each request. 185 | 'browser client gzip': false, 186 | 187 | // Optional override function to serve all static files, 188 | // including socket.io.js et al. 189 | // Of the form :: function (req, res) { /* serve files */ } 190 | 'browser client handler': false, 191 | 192 | // Meant to be used when running socket.io behind a proxy. 193 | // Should be set to true when you want the location handshake to match the protocol of the origin. 194 | // This fixes issues with terminating the SSL in front of Node 195 | // and forcing location to think it's wss instead of ws. 196 | 'match origin protocol': false, 197 | 198 | // Direct access to the socket.io MQ store config 199 | // The 'adapter' property is the preferred method 200 | // (`undefined` indicates that Sails should defer to the 'adapter' config) 201 | store: undefined, 202 | 203 | // A logger instance that is used to output log information. 204 | // (`undefined` indicates deferment to the main Sails log config) 205 | logger: undefined, 206 | 207 | // The amount of detail that the server should output to the logger. 208 | // (`undefined` indicates deferment to the main Sails log config) 209 | 'log level': undefined, 210 | 211 | // Whether to color the log type when output to the logger. 212 | // (`undefined` indicates deferment to the main Sails log config) 213 | 'log colors': undefined, 214 | 215 | // A Static instance that is used to serve the socket.io client and its dependencies. 216 | // (`undefined` indicates use default) 217 | 'static': undefined, 218 | 219 | // The entry point where Socket.IO starts looking for incoming connections. 220 | // This should be the same between the client and the server. 221 | resource: '/socket.io' 222 | 223 | }; -------------------------------------------------------------------------------- /config/views.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Views 3 | * 4 | * Server-sent views are a classic and effective way to get your app up and running. 5 | * Views are normally served from controllers. Below, you can configure your 6 | * templating language/framework of choice and configure Sails' layout support. 7 | * 8 | * For more information on views and layouts, check out: 9 | * http://sailsjs.org/#documentation 10 | */ 11 | 12 | module.exports.views = { 13 | 14 | // View engine (aka template language) 15 | // to use for your app's *server-side* views 16 | // 17 | // Sails+Express supports all view engines which implement 18 | // TJ Holowaychuk's `consolidate.js`, including, but not limited to: 19 | // 20 | // ejs, jade, handlebars, mustache 21 | // underscore, hogan, haml, haml-coffee, dust 22 | // atpl, eco, ect, jazz, jqtpl, JUST, liquor, QEJS, 23 | // swig, templayed, toffee, walrus, & whiskers 24 | 25 | // For more options, check out the docs: 26 | // https://github.com/balderdashy/sails-wiki/blob/0.9/config.views.md#engine 27 | 28 | engine: 'ejs', 29 | 30 | 31 | 32 | // Layouts are simply top-level HTML templates you can use as wrappers 33 | // for your server-side views. If you're using ejs or jade, you can take advantage of 34 | // Sails' built-in `layout` support. 35 | // 36 | // When using a layout, when one of your views is served, it is injected into 37 | // the `body` partial defined in the layout. This lets you reuse header 38 | // and footer logic between views. 39 | // 40 | // NOTE: Layout support is only implemented for the `ejs` view engine! 41 | // For most other engines, it is not necessary, since they implement 42 | // partials/layouts themselves. In those cases, this config will be silently 43 | // ignored. 44 | // 45 | // The `layout` setting may be set to one of: 46 | // 47 | // If `true`, Sails will look for the default, located at `views/layout.ejs` 48 | // If `false`, layouts will be disabled. 49 | // Otherwise, if a string is specified, it will be interpreted as the relative path 50 | // to your layout from `views/` folder. 51 | // (the file extension, e.g. ".ejs", should be omitted) 52 | // 53 | 54 | layout: 'layout' 55 | 56 | 57 | 58 | // Using Multiple Layouts with EJS 59 | // 60 | // If you're using the default engine, `ejs`, Sails supports the use of multiple 61 | // `layout` files. To take advantage of this, before rendering a view, override 62 | // the `layout` local in your controller by setting `res.locals.layout`. 63 | // (this is handy if you parts of your app's UI look completely different from each other) 64 | // 65 | // e.g. your default might be 66 | // layout: 'layouts/public' 67 | // 68 | // But you might override that in some of your controllers with: 69 | // layout: 'layouts/internal' 70 | 71 | 72 | }; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfid-based-asset-tracking-with-nodejs-and-mongodb", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "a Sails application", 6 | "dependencies": { 7 | "sails": "0.9.13", 8 | "grunt": "0.4.1", 9 | "sails-disk": "~0.9.0", 10 | "ejs": "0.8.4", 11 | "optimist": "0.3.4" 12 | }, 13 | "scripts": { 14 | "start": "node app.js", 15 | "debug": "node debug app.js" 16 | }, 17 | "main": "app.js", 18 | "repository": "", 19 | "author": "", 20 | "license": "" 21 | } -------------------------------------------------------------------------------- /views/403.ejs: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 38 | Forbidden 39 | 40 | 44 | 45 | 46 | 47 |
48 |
49 | 50 |
51 | 52 |
53 |

54 | Forbidden 55 |

56 |

57 | <% if (typeof message !== 'undefined') { %> 58 | <%= message %> 59 | <% } else { %> 60 | You don't have permission to see the page you're trying to reach. 61 | <% } %> 62 |

63 |

64 | Why might this be happening? 65 |

66 |
67 | 68 | 73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /views/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 38 | Page Not Found 39 | 40 | 44 | 45 | 46 | 47 |
48 |
49 | 50 |
51 | 52 |
53 |

54 | Something's fishy here. 55 |

56 |

57 | The page you were trying to reach doesn't exist. 58 |

59 |

60 | Why might this be happening? 61 |

62 |
63 | 64 | 69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /views/500.ejs: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 38 | Server Error 39 | 40 | 47 | 48 | 49 |
50 |
51 |
52 | 53 | 54 |
55 |
56 |
57 |

58 | Internal Server Error 59 |

60 |

61 | Something isn't right here. 62 |

63 | <% if (typeof errors !== 'undefined') { %> 64 |

65 |         <%
66 |           _.each(errors, function (error) { %>
67 |           <%= error %>
68 |           <%
69 |           }); %>
70 |         
71 | <% } else { %> 72 | 73 |

74 | A team of highly trained sea bass is working on this as we speak.
75 | If the problem persists, please contact the system administrator and inform them of the time that the error occured, and anything you might have done that may have caused the error. 76 |

77 | <% } %> 78 | 79 |
80 | 81 | 84 |
85 | 86 | -------------------------------------------------------------------------------- /views/home/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
94 | -------------------------------------------------------------------------------- /views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | <%- title %> 10 | 11 | 12 | 13 | 14 | 23 | 24 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | <%- body %> 42 | 43 | 44 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 77 | 78 | 79 | 80 | 94 | 95 | 96 | 97 | --------------------------------------------------------------------------------