diff --git a/lightcalc/.app.js b/lightcalc/.app.js
new file mode 100644
index 0000000..456cb13
--- /dev/null
+++ b/lightcalc/.app.js
@@ -0,0 +1 @@
+require('sails').lift();
diff --git a/lightcalc/.bowerrc b/lightcalc/.bowerrc
new file mode 100644
index 0000000..b459ca8
--- /dev/null
+++ b/lightcalc/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "assets/bower_components"
+}
\ No newline at end of file
diff --git a/lightcalc/.editorconfig b/lightcalc/.editorconfig
new file mode 100644
index 0000000..0f09989
--- /dev/null
+++ b/lightcalc/.editorconfig
@@ -0,0 +1,10 @@
+# editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/lightcalc/.foreverignore b/lightcalc/.foreverignore
new file mode 100644
index 0000000..009d521
--- /dev/null
+++ b/lightcalc/.foreverignore
@@ -0,0 +1 @@
+**/.tmp/**
\ No newline at end of file
diff --git a/lightcalc/.gitignore b/lightcalc/.gitignore
new file mode 100644
index 0000000..a52eaec
--- /dev/null
+++ b/lightcalc/.gitignore
@@ -0,0 +1,118 @@
+################################################
+############### .gitignore ##################
+################################################
+#
+# This file is only relevant if you are using git.
+#
+# Files which match the splat patterns below will
+# be ignored by git. This keeps random crap and
+# sensitive credentials from being uploaded to
+# your repository. It allows you to configure your
+# app for your machine without accidentally
+# committing settings which will smash the local
+# settings of other developers on your team.
+#
+# Some reasonable defaults are included below,
+# but, of course, you should modify/extend/prune
+# to fit your needs!
+################################################
+
+
+
+
+################################################
+# Local Configuration
+#
+# Explicitly ignore files which contain:
+#
+# 1. Sensitive information you'd rather not push to
+# your git repository.
+# e.g., your personal API keys or passwords.
+#
+# 2. Environment-specific configuration
+# Basically, anything that would be annoying
+# to have to change every time you do a
+# `git pull`
+# e.g., your local development database, or
+# the S3 bucket you're using for file uploads
+# development.
+#
+################################################
+
+config/local.js
+
+
+
+
+
+################################################
+# Dependencies
+#
+# When releasing a production app, you may
+# consider including your node_modules and
+# bower_components directory in your git repo,
+# but during development, its best to exclude it,
+# since different developers may be working on
+# different kernels, where dependencies would
+# need to be recompiled anyway.
+#
+# More on that here about node_modules dir:
+# http://www.futurealoof.com/posts/nodemodules-in-git.html
+# (credit Mikeal Rogers, @mikeal)
+#
+# About bower_components dir, you can see this:
+# http://addyosmani.com/blog/checking-in-front-end-dependencies/
+# (credit Addy Osmani, @addyosmani)
+#
+################################################
+
+node_modules
+bower_components
+
+
+
+
+################################################
+# Sails.js / Waterline / Grunt
+#
+# Files generated by Sails and Grunt, or related
+# tasks and adapters.
+################################################
+.tmp
+dump.rdb
+
+
+
+
+
+################################################
+# Node.js / NPM
+#
+# Common files generated by Node, NPM, and the
+# related ecosystem.
+################################################
+lib-cov
+*.seed
+*.log
+*.out
+*.pid
+npm-debug.log
+
+
+
+
+
+################################################
+# Miscellaneous
+#
+# Common files generated by text editors,
+# operating systems, file systems, etc.
+################################################
+
+*~
+*#
+.DS_STORE
+.netbeans
+nbproject
+.idea
+.node_history
diff --git a/lightcalc/.sailsrc b/lightcalc/.sailsrc
new file mode 100644
index 0000000..fa89f5e
--- /dev/null
+++ b/lightcalc/.sailsrc
@@ -0,0 +1,5 @@
+{
+ "generators": {
+ "modules": {}
+ }
+}
\ No newline at end of file
diff --git a/lightcalc/Gruntfile.js b/lightcalc/Gruntfile.js
new file mode 100644
index 0000000..f4b2289
--- /dev/null
+++ b/lightcalc/Gruntfile.js
@@ -0,0 +1,81 @@
+/**
+ * Gruntfile
+ *
+ * This Node script is executed when you run `grunt` or `sails lift`.
+ * It's purpose is to load the Grunt tasks in your project's `tasks`
+ * folder, and allow you to add and remove tasks as you see fit.
+ * For more information on how this works, check out the `README.md`
+ * file that was generated in your `tasks` folder.
+ *
+ * WARNING:
+ * Unless you know what you're doing, you shouldn't change this file.
+ * Check out the `tasks` directory instead.
+ */
+
+module.exports = function(grunt) {
+
+
+ // Load the include-all library in order to require all of our grunt
+ // configurations and task registrations dynamically.
+ var includeAll;
+ try {
+ includeAll = require('include-all');
+ } catch (e0) {
+ try {
+ includeAll = require('sails/node_modules/include-all');
+ }
+ catch(e1) {
+ console.error('Could not find `include-all` module.');
+ console.error('Skipping grunt tasks...');
+ console.error('To fix this, please run:');
+ console.error('npm install include-all --save`');
+ console.error();
+
+ grunt.registerTask('default', []);
+ return;
+ }
+ }
+
+
+ /**
+ * Loads Grunt configuration modules from the specified
+ * relative path. These modules should export a function
+ * that, when run, should either load/configure or register
+ * a Grunt task.
+ */
+ function loadTasks(relPath) {
+ return includeAll({
+ dirname: require('path').resolve(__dirname, relPath),
+ filter: /(.+)\.js$/
+ }) || {};
+ }
+
+ /**
+ * Invokes the function from a Grunt configuration module with
+ * a single argument - the `grunt` object.
+ */
+ function invokeConfigFn(tasks) {
+ for (var taskName in tasks) {
+ if (tasks.hasOwnProperty(taskName)) {
+ tasks[taskName](grunt);
+ }
+ }
+ }
+
+
+
+
+ // Load task functions
+ var taskConfigurations = loadTasks('./tasks/config'),
+ registerDefinitions = loadTasks('./tasks/register');
+
+ // (ensure that a default task exists)
+ if (!registerDefinitions.default) {
+ registerDefinitions.default = function (grunt) { grunt.registerTask('default', []); };
+ }
+
+ // Run task functions to configure Grunt.
+ invokeConfigFn(taskConfigurations);
+ invokeConfigFn(registerDefinitions);
+
+};
diff --git a/lightcalc/Procfile b/lightcalc/Procfile
new file mode 100644
index 0000000..207d22f
--- /dev/null
+++ b/lightcalc/Procfile
@@ -0,0 +1 @@
+web: node app.js
\ No newline at end of file
diff --git a/lightcalc/README.md b/lightcalc/README.md
new file mode 100644
index 0000000..92ae7b1
--- /dev/null
+++ b/lightcalc/README.md
@@ -0,0 +1,37 @@
+# lightcalc
+
+a [Sails](http://sailsjs.org) application that uses angularjs to make beautiful and light calculators with isolates operations histories.
+
+## Running application
+
+Install nodejs
+
+In the application folder, run "npm install" with the required permissions.
+
+OBS.: in Ubuntu, you haave to run "sudo npm install", for example
+
+After npm install all the project dependencies, run "bower install" to download the frontend depencies.
+
+For last, run "sails lift", and then the application will be running at http://localhost:1337.
+
+
+## Running tests
+
+There are two sets of tests in the application: the backend tests, which test the REST api provided by sails, and the frontend tests, which test the angularjs application.
+
+### Backend tests
+
+To run the backend tests you have to install mocha globally running "npm install mocha -g" with the required permissions.
+
+After mocha is installed run the command "mocha test/bootstrap.test.js test/unit/**.*.test.js" in the project root folder.
+
+### Frontend tests
+
+To run the backend tests you have to install karma tests runner globally running "npm install karma -g" with the required permissions.
+
+After karma is installed run the command "karma start" in the project root folder.
+
+
+## Demo version
+
+[http://lightcalc.herokuapp.com](http://lightcalc.herokuapp.com)
\ No newline at end of file
diff --git a/lightcalc/api/controllers/.gitkeep b/lightcalc/api/controllers/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/lightcalc/api/controllers/CalculatorController.js b/lightcalc/api/controllers/CalculatorController.js
new file mode 100644
index 0000000..b595df1
--- /dev/null
+++ b/lightcalc/api/controllers/CalculatorController.js
@@ -0,0 +1,11 @@
+/**
+ * CalculatorController
+ *
+ * @description :: Server-side logic for managing calculators
+ * @help :: See http://sailsjs.org/#!/documentation/concepts/Controllers
+ */
+
+module.exports = {
+
+};
+
diff --git a/lightcalc/api/controllers/OperationController.js b/lightcalc/api/controllers/OperationController.js
new file mode 100644
index 0000000..cb556ae
--- /dev/null
+++ b/lightcalc/api/controllers/OperationController.js
@@ -0,0 +1,11 @@
+/**
+ * OperationController
+ *
+ * @description :: Server-side logic for managing operations
+ * @help :: See http://sailsjs.org/#!/documentation/concepts/Controllers
+ */
+
+module.exports = {
+
+};
+
diff --git a/lightcalc/api/models/.gitkeep b/lightcalc/api/models/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/lightcalc/api/models/Calculator.js b/lightcalc/api/models/Calculator.js
new file mode 100644
index 0000000..25c8899
--- /dev/null
+++ b/lightcalc/api/models/Calculator.js
@@ -0,0 +1,24 @@
+/**
+* Calculator.js
+*
+* @description :: TODO: You might write a short summary of how this model works and what it represents here.
+* @docs :: http://sailsjs.org/#!documentation/models
+*/
+
+module.exports = {
+ attributes: {
+ name: {
+ type: 'STRING',
+ required: true,
+ unique: true,
+ alphanumeric: true,
+ minLength: 8,
+ maxLength: 40
+ },
+ operations: {
+ collection: 'operation',
+ via: 'calculator'
+ }
+ }
+};
+
diff --git a/lightcalc/api/models/Operation.js b/lightcalc/api/models/Operation.js
new file mode 100644
index 0000000..d25cd61
--- /dev/null
+++ b/lightcalc/api/models/Operation.js
@@ -0,0 +1,22 @@
+/**
+* Operation.js
+*
+* @description :: TODO: You might write a short summary of how this model works and what it represents here.
+* @docs :: http://sailsjs.org/#!documentation/models
+*/
+
+module.exports = {
+
+ attributes: {
+ calculator: {
+ model: 'calculator',
+ required: true
+ },
+
+ text: {
+ type: 'STRING',
+ required: true,
+ }
+ }
+};
+
diff --git a/lightcalc/api/policies/sessionAuth.js b/lightcalc/api/policies/sessionAuth.js
new file mode 100644
index 0000000..8f9a264
--- /dev/null
+++ b/lightcalc/api/policies/sessionAuth.js
@@ -0,0 +1,21 @@
+/**
+ * sessionAuth
+ *
+ * @module :: Policy
+ * @description :: Simple policy to allow any authenticated user
+ * Assumes that your login action in one of your controllers sets `req.session.authenticated = true;`
+ * @docs :: http://sailsjs.org/#!/documentation/concepts/Policies
+ *
+ */
+module.exports = function(req, res, next) {
+
+ // User is allowed, proceed to the next policy,
+ // or if this is the last policy, the controller
+ if (req.session.authenticated) {
+ return next();
+ }
+
+ // User is not allowed
+ // (default res.forbidden() behavior can be overridden in `config/403.js`)
+ return res.forbidden('You are not permitted to perform this action.');
+};
diff --git a/lightcalc/api/responses/badRequest.js b/lightcalc/api/responses/badRequest.js
new file mode 100644
index 0000000..9f99b13
--- /dev/null
+++ b/lightcalc/api/responses/badRequest.js
@@ -0,0 +1,64 @@
+/**
+ * 400 (Bad Request) Handler
+ *
+ * Usage:
+ * return res.badRequest();
+ * return res.badRequest(data);
+ * return res.badRequest(data, 'some/specific/badRequest/view');
+ *
+ * e.g.:
+ * ```
+ * return res.badRequest(
+ * 'Please choose a valid `password` (6-12 characters)',
+ * 'trial/signup'
+ * );
+ * ```
+ */
+
+module.exports = function badRequest(data, options) {
+
+ // Get access to `req`, `res`, & `sails`
+ var req = this.req;
+ var res = this.res;
+ var sails = req._sails;
+
+ // Set status code
+ res.status(400);
+
+ // Log error to console
+ if (data !== undefined) {
+ sails.log.verbose('Sending 400 ("Bad Request") response: \n',data);
+ }
+ else sails.log.verbose('Sending 400 ("Bad Request") response');
+
+ // Only include errors in response if application environment
+ // is not set to 'production'. In production, we shouldn't
+ // send back any identifying information about errors.
+ if (sails.config.environment === 'production') {
+ data = undefined;
+ }
+
+ // If the user-agent wants JSON, always respond with JSON
+ if (req.wantsJSON) {
+ return res.jsonx(data);
+ }
+
+ // If second argument is a string, we take that to mean it refers to a view.
+ // If it was omitted, use an empty object (`{}`)
+ options = (typeof options === 'string') ? { view: options } : options || {};
+
+ // If a view was provided in options, serve it.
+ // Otherwise try to guess an appropriate view, or if that doesn't
+ // work, just send JSON.
+ if (options.view) {
+ return res.view(options.view, { data: data });
+ }
+
+ // If no second argument provided, try to serve the implied view,
+ // but fall back to sending JSON(P) if no view can be inferred.
+ else return res.guessView({ data: data }, function couldNotGuessView () {
+ return res.jsonx(data);
+ });
+
+};
+
diff --git a/lightcalc/api/responses/forbidden.js b/lightcalc/api/responses/forbidden.js
new file mode 100644
index 0000000..16e4d81
--- /dev/null
+++ b/lightcalc/api/responses/forbidden.js
@@ -0,0 +1,77 @@
+/**
+ * 403 (Forbidden) Handler
+ *
+ * Usage:
+ * return res.forbidden();
+ * return res.forbidden(err);
+ * return res.forbidden(err, 'some/specific/forbidden/view');
+ *
+ * e.g.:
+ * ```
+ * return res.forbidden('Access denied.');
+ * ```
+ */
+
+module.exports = function forbidden (data, options) {
+
+ // Get access to `req`, `res`, & `sails`
+ var req = this.req;
+ var res = this.res;
+ var sails = req._sails;
+
+ // Set status code
+ res.status(403);
+
+ // Log error to console
+ if (data !== undefined) {
+ sails.log.verbose('Sending 403 ("Forbidden") response: \n',data);
+ }
+ else sails.log.verbose('Sending 403 ("Forbidden") response');
+
+ // Only include errors in response if application environment
+ // is not set to 'production'. In production, we shouldn't
+ // send back any identifying information about errors.
+ if (sails.config.environment === 'production') {
+ data = undefined;
+ }
+
+ // If the user-agent wants JSON, always respond with JSON
+ if (req.wantsJSON) {
+ return res.jsonx(data);
+ }
+
+ // If second argument is a string, we take that to mean it refers to a view.
+ // If it was omitted, use an empty object (`{}`)
+ options = (typeof options === 'string') ? { view: options } : options || {};
+
+ // If a view was provided in options, serve it.
+ // Otherwise try to guess an appropriate view, or if that doesn't
+ // work, just send JSON.
+ if (options.view) {
+ return res.view(options.view, { data: data });
+ }
+
+ // If no second argument provided, try to serve the default view,
+ // but fall back to sending JSON(P) if any errors occur.
+ else return res.view('403', { data: data }, function (err, html) {
+
+ // If a view error occured, fall back to JSON(P).
+ if (err) {
+ //
+ // Additionally:
+ // • If the view was missing, ignore the error but provide a verbose log.
+ if (err.code === 'E_VIEW_FAILED') {
+ sails.log.verbose('res.forbidden() :: Could not locate view for error page (sending JSON instead). Details: ',err);
+ }
+ // Otherwise, if this was a more serious error, log to the console with the details.
+ else {
+ sails.log.warn('res.forbidden() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err);
+ }
+ return res.jsonx(data);
+ }
+
+ return res.send(html);
+ });
+
+};
+
diff --git a/lightcalc/api/responses/notFound.js b/lightcalc/api/responses/notFound.js
new file mode 100644
index 0000000..d14a200
--- /dev/null
+++ b/lightcalc/api/responses/notFound.js
@@ -0,0 +1,82 @@
+/**
+ * 404 (Not Found) Handler
+ *
+ * Usage:
+ * return res.notFound();
+ * return res.notFound(err);
+ * return res.notFound(err, 'some/specific/notfound/view');
+ *
+ * e.g.:
+ * ```
+ * return res.notFound();
+ * ```
+ *
+ * NOTE:
+ * If a request doesn't match any explicit routes (i.e. `config/routes.js`)
+ * or route blueprints (i.e. "shadow routes", Sails will call `res.notFound()`
+ * automatically.
+ */
+
+module.exports = function notFound (data, options) {
+
+ // Get access to `req`, `res`, & `sails`
+ var req = this.req;
+ var res = this.res;
+ var sails = req._sails;
+
+ // Set status code
+ res.status(404);
+
+ // Log error to console
+ if (data !== undefined) {
+ sails.log.verbose('Sending 404 ("Not Found") response: \n',data);
+ }
+ else sails.log.verbose('Sending 404 ("Not Found") response');
+
+ // Only include errors in response if application environment
+ // is not set to 'production'. In production, we shouldn't
+ // send back any identifying information about errors.
+ if (sails.config.environment === 'production') {
+ data = undefined;
+ }
+
+ // If the user-agent wants JSON, always respond with JSON
+ if (req.wantsJSON) {
+ return res.jsonx(data);
+ }
+
+ // If second argument is a string, we take that to mean it refers to a view.
+ // If it was omitted, use an empty object (`{}`)
+ options = (typeof options === 'string') ? { view: options } : options || {};
+
+ // If a view was provided in options, serve it.
+ // Otherwise try to guess an appropriate view, or if that doesn't
+ // work, just send JSON.
+ if (options.view) {
+ return res.view(options.view, { data: data });
+ }
+
+ // If no second argument provided, try to serve the default view,
+ // but fall back to sending JSON(P) if any errors occur.
+ else return res.view('404', { data: data }, function (err, html) {
+
+ // If a view error occured, fall back to JSON(P).
+ if (err) {
+ //
+ // Additionally:
+ // • If the view was missing, ignore the error but provide a verbose log.
+ if (err.code === 'E_VIEW_FAILED') {
+ sails.log.verbose('res.notFound() :: Could not locate view for error page (sending JSON instead). Details: ',err);
+ }
+ // Otherwise, if this was a more serious error, log to the console with the details.
+ else {
+ sails.log.warn('res.notFound() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err);
+ }
+ return res.jsonx(data);
+ }
+
+ return res.send(html);
+ });
+
+};
+
diff --git a/lightcalc/api/responses/ok.js b/lightcalc/api/responses/ok.js
new file mode 100644
index 0000000..4351d2e
--- /dev/null
+++ b/lightcalc/api/responses/ok.js
@@ -0,0 +1,48 @@
+/**
+ * 200 (OK) Response
+ *
+ * Usage:
+ * return res.ok();
+ * return res.ok(data);
+ * return res.ok(data, 'auth/login');
+ *
+ * @param {Object} data
+ * @param {String|Object} options
+ * - pass string to render specified view
+ */
+
+module.exports = function sendOK (data, options) {
+
+ // Get access to `req`, `res`, & `sails`
+ var req = this.req;
+ var res = this.res;
+ var sails = req._sails;
+
+ sails.log.silly('res.ok() :: Sending 200 ("OK") response');
+
+ // Set status code
+ res.status(200);
+
+ // If appropriate, serve data as JSON(P)
+ if (req.wantsJSON) {
+ return res.jsonx(data);
+ }
+
+ // If second argument is a string, we take that to mean it refers to a view.
+ // If it was omitted, use an empty object (`{}`)
+ options = (typeof options === 'string') ? { view: options } : options || {};
+
+ // If a view was provided in options, serve it.
+ // Otherwise try to guess an appropriate view, or if that doesn't
+ // work, just send JSON.
+ if (options.view) {
+ return res.view(options.view, { data: data });
+ }
+
+ // If no second argument provided, try to serve the implied view,
+ // but fall back to sending JSON(P) if no view can be inferred.
+ else return res.guessView({ data: data }, function couldNotGuessView () {
+ return res.jsonx(data);
+ });
+
+};
diff --git a/lightcalc/api/responses/serverError.js b/lightcalc/api/responses/serverError.js
new file mode 100644
index 0000000..e08b4ce
--- /dev/null
+++ b/lightcalc/api/responses/serverError.js
@@ -0,0 +1,77 @@
+/**
+ * 500 (Server Error) Response
+ *
+ * Usage:
+ * return res.serverError();
+ * return res.serverError(err);
+ * return res.serverError(err, 'some/specific/error/view');
+ *
+ * NOTE:
+ * If something throws in a policy or controller, or an internal
+ * error is encountered, Sails will call `res.serverError()`
+ * automatically.
+ */
+
+module.exports = function serverError (data, options) {
+
+ // Get access to `req`, `res`, & `sails`
+ var req = this.req;
+ var res = this.res;
+ var sails = req._sails;
+
+ // Set status code
+ res.status(500);
+
+ // Log error to console
+ if (data !== undefined) {
+ sails.log.error('Sending 500 ("Server Error") response: \n',data);
+ }
+ else sails.log.error('Sending empty 500 ("Server Error") response');
+
+ // Only include errors in response if application environment
+ // is not set to 'production'. In production, we shouldn't
+ // send back any identifying information about errors.
+ if (sails.config.environment === 'production') {
+ data = undefined;
+ }
+
+ // If the user-agent wants JSON, always respond with JSON
+ if (req.wantsJSON) {
+ return res.jsonx(data);
+ }
+
+ // If second argument is a string, we take that to mean it refers to a view.
+ // If it was omitted, use an empty object (`{}`)
+ options = (typeof options === 'string') ? { view: options } : options || {};
+
+ // If a view was provided in options, serve it.
+ // Otherwise try to guess an appropriate view, or if that doesn't
+ // work, just send JSON.
+ if (options.view) {
+ return res.view(options.view, { data: data });
+ }
+
+ // If no second argument provided, try to serve the default view,
+ // but fall back to sending JSON(P) if any errors occur.
+ else return res.view('500', { data: data }, function (err, html) {
+
+ // If a view error occured, fall back to JSON(P).
+ if (err) {
+ //
+ // Additionally:
+ // • If the view was missing, ignore the error but provide a verbose log.
+ if (err.code === 'E_VIEW_FAILED') {
+ sails.log.verbose('res.serverError() :: Could not locate view for error page (sending JSON instead). Details: ',err);
+ }
+ // Otherwise, if this was a more serious error, log to the console with the details.
+ else {
+ sails.log.warn('res.serverError() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err);
+ }
+ return res.jsonx(data);
+ }
+
+ return res.send(html);
+ });
+
+};
+
diff --git a/lightcalc/api/services/.gitkeep b/lightcalc/api/services/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/lightcalc/app.js b/lightcalc/app.js
new file mode 100644
index 0000000..85c0af7
--- /dev/null
+++ b/lightcalc/app.js
@@ -0,0 +1,59 @@
+/**
+ * app.js
+ *
+ * Use `app.js` to run your app without `sails lift`.
+ * To start the server, run: `node app.js`.
+ *
+ * This is handy in situations where the sails CLI is not relevant or useful.
+ *
+ * For example:
+ * => `node app.js`
+ * => `forever start app.js`
+ * => `node debug app.js`
+ * => `modulus deploy`
+ * => `heroku scale`
+ *
+ *
+ * The same command-line arguments are supported, e.g.:
+ * `node app.js --silent --port=80 --prod`
+ */
+
+// Ensure we're in the project directory, so relative paths work as expected
+// no matter where we actually lift from.
+process.chdir(__dirname);
+
+// Ensure a "sails" can be located:
+(function() {
+ var sails;
+ try {
+ sails = require('sails');
+ } catch (e) {
+ console.error('To run an app using `node app.js`, you usually need to have a version of `sails` installed in the same directory as your app.');
+ console.error('To do that, run `npm install sails`');
+ console.error('');
+ console.error('Alternatively, if you have sails installed globally (i.e. you did `npm install -g sails`), you can use `sails lift`.');
+ console.error('When you run `sails lift`, your app will still use a local `./node_modules/sails` dependency if it exists,');
+ console.error('but if it doesn\'t, the app will run with the global sails instead!');
+ return;
+ }
+
+ // Try to get `rc` dependency
+ var rc;
+ try {
+ rc = require('rc');
+ } catch (e0) {
+ try {
+ rc = require('sails/node_modules/rc');
+ } catch (e1) {
+ console.error('Could not find dependency: `rc`.');
+ console.error('Your `.sailsrc` file(s) will be ignored.');
+ console.error('To resolve this, run:');
+ console.error('npm install rc --save');
+ rc = function () { return {}; };
+ }
+ }
+
+
+ // Start server
+ sails.lift(rc('sails'));
+})();
diff --git a/lightcalc/assets/favicon.ico b/lightcalc/assets/favicon.ico
new file mode 100644
index 0000000..0092ec9
Binary files /dev/null and b/lightcalc/assets/favicon.ico differ
diff --git a/lightcalc/assets/images/.gitkeep b/lightcalc/assets/images/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/lightcalc/assets/images/ciro.png b/lightcalc/assets/images/ciro.png
new file mode 100644
index 0000000..7b9014b
Binary files /dev/null and b/lightcalc/assets/images/ciro.png differ
diff --git a/lightcalc/assets/index.html b/lightcalc/assets/index.html
new file mode 100644
index 0000000..654d691
--- /dev/null
+++ b/lightcalc/assets/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+ LightCalc
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lightcalc/assets/js/app.js b/lightcalc/assets/js/app.js
new file mode 100644
index 0000000..202bdee
--- /dev/null
+++ b/lightcalc/assets/js/app.js
@@ -0,0 +1 @@
+angular.module('LightCalc', ['ngRoute', 'ngResource', 'ngMessages', 'ui.bootstrap.showErrors'])
\ No newline at end of file
diff --git a/lightcalc/assets/js/controllers/calculatorCreateCtrl.js b/lightcalc/assets/js/controllers/calculatorCreateCtrl.js
new file mode 100644
index 0000000..d8755fa
--- /dev/null
+++ b/lightcalc/assets/js/controllers/calculatorCreateCtrl.js
@@ -0,0 +1,18 @@
+angular.module('LightCalc').controller('calculatorCreateCtrl', ['Calculators', '$scope', '$location', function(Calculators, $scope, $location){
+
+ $scope.calculatorForm = {name: ''};
+
+ $scope.submitForm = function(){
+ $scope.$broadcast('show-errors-check-validity');
+
+ if ($scope.form.$valid) {
+ var calculator = Calculators.save($scope.calculatorForm);
+ calculator.$promise.then(function(data){
+ $location.path('/'+data.name);
+ });
+
+ return calculator;
+ }
+ }
+
+}]);
diff --git a/lightcalc/assets/js/controllers/calculatorRetrieveCtrl.js b/lightcalc/assets/js/controllers/calculatorRetrieveCtrl.js
new file mode 100644
index 0000000..f3e4dea
--- /dev/null
+++ b/lightcalc/assets/js/controllers/calculatorRetrieveCtrl.js
@@ -0,0 +1,218 @@
+angular.module('LightCalc').controller('calculatorRetrieveCtrl', ['Calculators', 'Operations', '$scope', '$routeParams', '$location', function(Calculators, Operations, $scope, $routeParams, $location){
+/* Controller handles calculations and binding*/
+ var calculators = Calculators.get({name: $routeParams.name});
+ calculators.$promise.then(function(data){
+ if(calculators.length > 0){
+ $scope.calculator = calculators[0];
+ } else {
+ $location.path('/');
+ }
+ });
+ // Bound to the output display
+
+ $scope.output = "0";
+
+ var newNumber = true;
+
+ var partialNumber = "";
+ var tokens = [];
+
+ /*
+ * Runs every time a number button is clicked.
+ * Updates the output display and sets
+ * newNumber flag
+ */
+
+ var updateOutput = function(){
+ var i = 0;
+ $scope.output = "";
+ if(tokens.length > 0) {
+ if(tokens[0].type === 'operator'){
+ i = 1;
+ tokens[1].value = -Math.abs(tokens[1].value);
+ }
+
+ for (; i < tokens.length; i++){
+ $scope.output += tokens[i].value;
+ if(i < tokens.length - 1 || partialNumber.length > 0){
+ $scope.output += ' ';
+ }
+ }
+ }
+
+ if(partialNumber.length > 0){
+ $scope.output += partialNumber;
+ }
+ }
+
+ var doBinaryOperation = function(symbol){
+ newNumber = false;
+ if(partialNumber.length > 0){
+ tokens.push({type: 'number', value: parseFloat(partialNumber)});
+ partialNumber = '';
+ }
+ if(tokens.length === 0){
+ throw "Você tem que apertar algum dígito antes de realizar uma operação";
+ } else if(tokens.length > 0){
+ if(tokens[tokens.length - 1].type === 'number'){
+ tokens.push({type: 'operator', value: symbol});
+ } else {
+ tokens[tokens.length - 1].value = symbol;
+ }
+ updateOutput();
+ }
+ }
+
+ var calculate = function() {
+ var i, j, noDivMultTokens = [], value = 0;
+
+ if(tokens[0].type === 'operator'){
+ i = 1;
+ tokens[1].value = -Math.abs(tokens[1].value);
+ } else {
+ i = 0;
+ }
+
+ j = i;
+
+ for(;i < tokens.length; i++){
+ if(tokens[i].value === '÷'){
+ noDivMultTokens.pop();
+ if(tokens[i+1] !== 0){
+ noDivMultTokens.push({
+ type:'number',
+ value: (tokens[i-1].value / tokens[i+1].value)
+ });
+ i++;
+ } else {
+ throw "Divisão por zero";
+ }
+ } else if(tokens[i].value === 'x'){
+ noDivMultTokens.pop();
+ noDivMultTokens.push({
+ type:'number',
+ value: (tokens[i-1].value * tokens[i+1].value)
+ });
+ i++;
+ } else {
+ noDivMultTokens.push(tokens[i]);
+ }
+ }
+
+ value = noDivMultTokens[j].value;
+
+ for(; j < noDivMultTokens.length; j++){
+ if(noDivMultTokens[j].type === 'operator'){
+ if(noDivMultTokens[j].value === '+'){
+ value = value + noDivMultTokens[j+1].value;
+ } else {
+ value = value - noDivMultTokens[j+1].value;
+ }
+ }
+ }
+
+ return value;
+ };
+
+ $scope.putDigit = function(btn) {
+ if(newNumber)
+ tokens = [];
+ if((tokens.length === 0 && partialNumber.length === 0)|| newNumber) {
+ if (btn === '.')
+ partialNumber = '0.';
+ else
+ partialNumber = String(btn);
+ newNumber = false;
+ } else {
+ if(partialNumber.length === 0 && btn === '.')
+ partialNumber = '0.';
+ else
+ partialNumber += String(btn);
+ }
+
+ updateOutput();
+ };
+
+ $scope.add = function() {
+ doBinaryOperation('+');
+ };
+
+ $scope.subtract = function() {
+ if(tokens.length === 0 && partialNumber.length === 0){
+ $scope.putDigit('-');
+ } else {
+ doBinaryOperation('-');
+ }
+ };
+
+ $scope.divide = function() {
+ doBinaryOperation('÷');
+ };
+
+ $scope.multiply = function() {
+ doBinaryOperation('x');
+ };
+
+ $scope.equals = function(){
+ if(partialNumber.length > 0){
+ tokens.push({type: 'number', value: parseFloat(partialNumber)});
+ partialNumber = '';
+ }
+
+ var operation = Operations.save({
+ calculator: $scope.calculator.id,
+ text: $scope.output,
+ });
+
+ operation.$promise.then(function(data){
+ tokens = [{type: 'number', value: calculate()}];
+ updateOutput();
+ newNumber = true;
+ $scope.calculator.operations.push(data);
+ }).catch(function(){
+ tokens = [{type: 'number', value: calculate()}];
+ updateOutput();
+ newNumber = true;
+ throw "Não foi possível salvar a operação. Verifique sua conexão com a internet.";
+ });
+
+ return operation;
+ }
+
+ $scope.loadOperation = function(operation){
+ tokens = [];
+ partialNumber = '';
+ var operationTokens = operation.text.split(' ');
+
+ for(var i in operationTokens){
+ switch(operationTokens[i]){
+ case '+':
+ tokens.push({type: 'operator', value: operationTokens[i]});
+ break;
+ case '-':
+ tokens.push({type: 'operator', value: operationTokens[i]});
+ break;
+ case 'x':
+ tokens.push({type: 'operator', value: operationTokens[i]});
+ break;
+ case '÷':
+ tokens.push({type: 'operator', value: operationTokens[i]});
+ break;
+ default:
+ tokens.push({type: 'number', value: parseFloat(operationTokens[i])});
+ }
+ }
+
+ updateOutput();
+ }
+
+ /*
+ * Initializes the appropriate values
+ * when the clear button is clicked.
+ */
+ $scope.clear = function() {
+ tokens = [];
+ newNumber = true;
+ $scope.output = "0";
+ };
+}]);
\ No newline at end of file
diff --git a/lightcalc/assets/js/dependencies/sails.io.js b/lightcalc/assets/js/dependencies/sails.io.js
new file mode 100644
index 0000000..e05e36b
--- /dev/null
+++ b/lightcalc/assets/js/dependencies/sails.io.js
@@ -0,0 +1,1033 @@
+// socket.io-1.2.1
+// (from http://socket.io/download/)
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.io=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0&&!this.encoding){var pack=this.packetBuffer.shift();this.packet(pack)}};Manager.prototype.cleanup=function(){var sub;while(sub=this.subs.shift())sub.destroy();this.packetBuffer=[];this.encoding=false;this.decoder.destroy()};Manager.prototype.close=Manager.prototype.disconnect=function(){this.skipReconnect=true;this.readyState="closed";this.engine&&this.engine.close()};Manager.prototype.onclose=function(reason){debug("close");this.cleanup();this.readyState="closed";this.emit("close",reason);if(this._reconnection&&!this.skipReconnect){this.reconnect()}};Manager.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var self=this;this.attempts++;if(this.attempts>this._reconnectionAttempts){debug("reconnect failed");this.emitAll("reconnect_failed");this.reconnecting=false}else{var delay=this.attempts*this.reconnectionDelay();delay=Math.min(delay,this.reconnectionDelayMax());debug("will wait %dms before reconnect attempt",delay);this.reconnecting=true;var timer=setTimeout(function(){if(self.skipReconnect)return;debug("attempting reconnect");self.emitAll("reconnect_attempt",self.attempts);self.emitAll("reconnecting",self.attempts);if(self.skipReconnect)return;self.open(function(err){if(err){debug("reconnect attempt error");self.reconnecting=false;self.reconnect();self.emitAll("reconnect_error",err.data)}else{debug("reconnect success");self.onreconnect()}})},delay);this.subs.push({destroy:function(){clearTimeout(timer)}})}};Manager.prototype.onreconnect=function(){var attempt=this.attempts;this.attempts=0;this.reconnecting=false;this.emitAll("reconnect",attempt)}},{"./on":4,"./socket":5,"./url":6,"component-bind":7,"component-emitter":8,debug:9,"engine.io-client":10,indexof:39,"object-component":40,"socket.io-parser":43}],4:[function(_dereq_,module,exports){module.exports=on;function on(obj,ev,fn){obj.on(ev,fn);return{destroy:function(){obj.removeListener(ev,fn)}}}},{}],5:[function(_dereq_,module,exports){var parser=_dereq_("socket.io-parser");var Emitter=_dereq_("component-emitter");var toArray=_dereq_("to-array");var on=_dereq_("./on");var bind=_dereq_("component-bind");var debug=_dereq_("debug")("socket.io-client:socket");var hasBin=_dereq_("has-binary");module.exports=exports=Socket;var events={connect:1,connect_error:1,connect_timeout:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1};var emit=Emitter.prototype.emit;function Socket(io,nsp){this.io=io;this.nsp=nsp;this.json=this;this.ids=0;this.acks={};if(this.io.autoConnect)this.open();this.receiveBuffer=[];this.sendBuffer=[];this.connected=false;this.disconnected=true}Emitter(Socket.prototype);Socket.prototype.subEvents=function(){if(this.subs)return;var io=this.io;this.subs=[on(io,"open",bind(this,"onopen")),on(io,"packet",bind(this,"onpacket")),on(io,"close",bind(this,"onclose"))]};Socket.prototype.open=Socket.prototype.connect=function(){if(this.connected)return this;this.subEvents();this.io.open();if("open"==this.io.readyState)this.onopen();return this};Socket.prototype.send=function(){var args=toArray(arguments);args.unshift("message");this.emit.apply(this,args);return this};Socket.prototype.emit=function(ev){if(events.hasOwnProperty(ev)){emit.apply(this,arguments);return this}var args=toArray(arguments);var parserType=parser.EVENT;if(hasBin(args)){parserType=parser.BINARY_EVENT}var packet={type:parserType,data:args};if("function"==typeof args[args.length-1]){debug("emitting packet with ack id %d",this.ids);this.acks[this.ids]=args.pop();packet.id=this.ids++}if(this.connected){this.packet(packet)}else{this.sendBuffer.push(packet)}return this};Socket.prototype.packet=function(packet){packet.nsp=this.nsp;this.io.packet(packet)};Socket.prototype.onopen=function(){debug("transport is open - connecting");if("/"!=this.nsp){this.packet({type:parser.CONNECT})}};Socket.prototype.onclose=function(reason){debug("close (%s)",reason);this.connected=false;this.disconnected=true;this.emit("disconnect",reason)};Socket.prototype.onpacket=function(packet){if(packet.nsp!=this.nsp)return;switch(packet.type){case parser.CONNECT:this.onconnect();break;case parser.EVENT:this.onevent(packet);break;case parser.BINARY_EVENT:this.onevent(packet);break;case parser.ACK:this.onack(packet);break;case parser.BINARY_ACK:this.onack(packet);break;case parser.DISCONNECT:this.ondisconnect();break;case parser.ERROR:this.emit("error",packet.data);break}};Socket.prototype.onevent=function(packet){var args=packet.data||[];debug("emitting event %j",args);if(null!=packet.id){debug("attaching ack callback to event");args.push(this.ack(packet.id))}if(this.connected){emit.apply(this,args)}else{this.receiveBuffer.push(args)}};Socket.prototype.ack=function(id){var self=this;var sent=false;return function(){if(sent)return;sent=true;var args=toArray(arguments);debug("sending ack %j",args);var type=hasBin(args)?parser.BINARY_ACK:parser.ACK;self.packet({type:type,id:id,data:args})}};Socket.prototype.onack=function(packet){debug("calling ack %s with %j",packet.id,packet.data);var fn=this.acks[packet.id];fn.apply(this,packet.data);delete this.acks[packet.id]};Socket.prototype.onconnect=function(){this.connected=true;this.disconnected=false;this.emit("connect");this.emitBuffered()};Socket.prototype.emitBuffered=function(){var i;for(i=0;i=hour)return(ms/hour).toFixed(1)+"h";if(ms>=min)return(ms/min).toFixed(1)+"m";if(ms>=sec)return(ms/sec|0)+"s";return ms+"ms"};debug.enabled=function(name){for(var i=0,len=debug.skips.length;i';iframe=document.createElement(html)}catch(e){iframe=document.createElement("iframe");iframe.name=self.iframeId;iframe.src="javascript:0"}iframe.id=self.iframeId;self.form.appendChild(iframe);self.iframe=iframe}initIframe();data=data.replace(rEscapedNewline,"\\\n");this.area.value=data.replace(rNewline,"\\n");try{this.form.submit()}catch(e){}if(this.iframe.attachEvent){this.iframe.onreadystatechange=function(){if(self.iframe.readyState=="complete"){complete()}}}else{this.iframe.onload=complete}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./polling":17,"component-inherit":20}],16:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_("xmlhttprequest");var Polling=_dereq_("./polling");var Emitter=_dereq_("component-emitter");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling-xhr");module.exports=XHR;module.exports.Request=Request;function empty(){}function XHR(opts){Polling.call(this,opts);if(global.location){var isSSL="https:"==location.protocol;var port=location.port;if(!port){port=isSSL?443:80}this.xd=opts.hostname!=global.location.hostname||port!=opts.port;this.xs=opts.secure!=isSSL}}inherit(XHR,Polling);XHR.prototype.supportsBinary=true;XHR.prototype.request=function(opts){opts=opts||{};opts.uri=this.uri();opts.xd=this.xd;opts.xs=this.xs;opts.agent=this.agent||false;opts.supportsBinary=this.supportsBinary;opts.enablesXDR=this.enablesXDR;return new Request(opts)};XHR.prototype.doWrite=function(data,fn){var isBinary=typeof data!=="string"&&data!==undefined;var req=this.request({method:"POST",data:data,isBinary:isBinary});var self=this;req.on("success",fn);req.on("error",function(err){self.onError("xhr post error",err)});this.sendXhr=req};XHR.prototype.doPoll=function(){debug("xhr poll");var req=this.request();var self=this;req.on("data",function(data){self.onData(data)});req.on("error",function(err){self.onError("xhr poll error",err)});this.pollXhr=req};function Request(opts){this.method=opts.method||"GET";this.uri=opts.uri;this.xd=!!opts.xd;this.xs=!!opts.xs;this.async=false!==opts.async;this.data=undefined!=opts.data?opts.data:null;this.agent=opts.agent;this.isBinary=opts.isBinary;this.supportsBinary=opts.supportsBinary;this.enablesXDR=opts.enablesXDR;this.create()}Emitter(Request.prototype);Request.prototype.create=function(){var xhr=this.xhr=new XMLHttpRequest({agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR});var self=this;try{debug("xhr open %s: %s",this.method,this.uri);xhr.open(this.method,this.uri,this.async);if(this.supportsBinary){xhr.responseType="arraybuffer"}if("POST"==this.method){try{if(this.isBinary){xhr.setRequestHeader("Content-type","application/octet-stream")}else{xhr.setRequestHeader("Content-type","text/plain;charset=UTF-8")}}catch(e){}}if("withCredentials"in xhr){xhr.withCredentials=true}if(this.hasXDR()){xhr.onload=function(){self.onLoad()};xhr.onerror=function(){self.onError(xhr.responseText)}}else{xhr.onreadystatechange=function(){if(4!=xhr.readyState)return;if(200==xhr.status||1223==xhr.status){self.onLoad()}else{setTimeout(function(){self.onError(xhr.status)},0)}}}debug("xhr data %s",this.data);xhr.send(this.data)}catch(e){setTimeout(function(){self.onError(e)},0);return}if(global.document){this.index=Request.requestsCount++;Request.requests[this.index]=this}};Request.prototype.onSuccess=function(){this.emit("success");this.cleanup()};Request.prototype.onData=function(data){this.emit("data",data);this.onSuccess()};Request.prototype.onError=function(err){this.emit("error",err);this.cleanup()};Request.prototype.cleanup=function(){if("undefined"==typeof this.xhr||null===this.xhr){return}if(this.hasXDR()){this.xhr.onload=this.xhr.onerror=empty}else{this.xhr.onreadystatechange=empty}try{this.xhr.abort()}catch(e){}if(global.document){delete Request.requests[this.index]}this.xhr=null};Request.prototype.onLoad=function(){var data;try{var contentType;try{contentType=this.xhr.getResponseHeader("Content-Type").split(";")[0]}catch(e){}if(contentType==="application/octet-stream"){data=this.xhr.response}else{if(!this.supportsBinary){data=this.xhr.responseText}else{data="ok"}}}catch(e){this.onError(e)}if(null!=data){this.onData(data)}};Request.prototype.hasXDR=function(){return"undefined"!==typeof global.XDomainRequest&&!this.xs&&this.enablesXDR};Request.prototype.abort=function(){this.cleanup()};if(global.document){Request.requestsCount=0;Request.requests={};if(global.attachEvent){global.attachEvent("onunload",unloadHandler)}else if(global.addEventListener){global.addEventListener("beforeunload",unloadHandler,false)}}function unloadHandler(){for(var i in Request.requests){if(Request.requests.hasOwnProperty(i)){Request.requests[i].abort()}}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./polling":17,"component-emitter":8,"component-inherit":20,debug:21,xmlhttprequest:19}],17:[function(_dereq_,module,exports){var Transport=_dereq_("../transport");var parseqs=_dereq_("parseqs");var parser=_dereq_("engine.io-parser");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling");module.exports=Polling;var hasXHR2=function(){var XMLHttpRequest=_dereq_("xmlhttprequest");var xhr=new XMLHttpRequest({xdomain:false});return null!=xhr.responseType}();function Polling(opts){var forceBase64=opts&&opts.forceBase64;if(!hasXHR2||forceBase64){this.supportsBinary=false}Transport.call(this,opts)}inherit(Polling,Transport);Polling.prototype.name="polling";Polling.prototype.doOpen=function(){this.poll()};Polling.prototype.pause=function(onPause){var pending=0;var self=this;this.readyState="pausing";function pause(){debug("paused");self.readyState="paused";onPause()}if(this.polling||!this.writable){var total=0;if(this.polling){debug("we are currently polling - waiting to pause");total++;this.once("pollComplete",function(){debug("pre-pause polling complete");--total||pause()})}if(!this.writable){debug("we are currently writing - waiting to pause");total++;this.once("drain",function(){debug("pre-pause writing complete");--total||pause()})}}else{pause()}};Polling.prototype.poll=function(){debug("polling");this.polling=true;this.doPoll();this.emit("poll")};Polling.prototype.onData=function(data){var self=this;debug("polling got data %s",data);var callback=function(packet,index,total){if("opening"==self.readyState){self.onOpen()}if("close"==packet.type){self.onClose();return false}self.onPacket(packet)};parser.decodePayload(data,this.socket.binaryType,callback);if("closed"!=this.readyState){this.polling=false;this.emit("pollComplete");if("open"==this.readyState){this.poll()}else{debug('ignoring poll - transport state "%s"',this.readyState)}}};Polling.prototype.doClose=function(){var self=this;function close(){debug("writing close packet");self.write([{type:"close"}])}if("open"==this.readyState){debug("transport open - closing");close()}else{debug("transport not open - deferring close");this.once("open",close)}};Polling.prototype.write=function(packets){var self=this;this.writable=false;var callbackfn=function(){self.writable=true;self.emit("drain")};var self=this;parser.encodePayload(packets,this.supportsBinary,function(data){self.doWrite(data,callbackfn)})};Polling.prototype.uri=function(){var query=this.query||{};var schema=this.secure?"https":"http";var port="";if(false!==this.timestampRequests){query[this.timestampParam]=+new Date+"-"+Transport.timestamps++}if(!this.supportsBinary&&!query.sid){query.b64=1}query=parseqs.encode(query);if(this.port&&("https"==schema&&this.port!=443||"http"==schema&&this.port!=80)){port=":"+this.port}if(query.length){query="?"+query}return schema+"://"+this.hostname+port+this.path+query}},{"../transport":13,"component-inherit":20,debug:21,"engine.io-parser":24,parseqs:32,xmlhttprequest:19}],18:[function(_dereq_,module,exports){var Transport=_dereq_("../transport");var parser=_dereq_("engine.io-parser");var parseqs=_dereq_("parseqs");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:websocket");var WebSocket=_dereq_("ws");module.exports=WS;function WS(opts){var forceBase64=opts&&opts.forceBase64;if(forceBase64){this.supportsBinary=false}Transport.call(this,opts)}inherit(WS,Transport);WS.prototype.name="websocket";WS.prototype.supportsBinary=true;WS.prototype.doOpen=function(){if(!this.check()){return}var self=this;var uri=this.uri();var protocols=void 0;var opts={agent:this.agent};this.ws=new WebSocket(uri,protocols,opts);if(this.ws.binaryType===undefined){this.supportsBinary=false}this.ws.binaryType="arraybuffer";this.addEventListeners()};WS.prototype.addEventListeners=function(){var self=this;this.ws.onopen=function(){self.onOpen()};this.ws.onclose=function(){self.onClose()};this.ws.onmessage=function(ev){self.onData(ev.data)};this.ws.onerror=function(e){self.onError("websocket error",e)}};if("undefined"!=typeof navigator&&/iPad|iPhone|iPod/i.test(navigator.userAgent)){WS.prototype.onData=function(data){var self=this;setTimeout(function(){Transport.prototype.onData.call(self,data)},0)}}WS.prototype.write=function(packets){var self=this;this.writable=false;for(var i=0,l=packets.length;i=31}exports.formatters.j=function(v){return JSON.stringify(v)};function formatArgs(){var args=arguments;var useColors=this.useColors;args[0]=(useColors?"%c":"")+this.namespace+(useColors?" %c":" ")+args[0]+(useColors?"%c ":" ")+"+"+exports.humanize(this.diff);if(!useColors)return args;var c="color: "+this.color;args=[args[0],c,"color: inherit"].concat(Array.prototype.slice.call(args,1));var index=0;var lastC=0;args[0].replace(/%[a-z%]/g,function(match){if("%"===match)return;index++;if("%c"===match){lastC=index}});args.splice(lastC,0,c);return args}function log(){return"object"==typeof console&&"function"==typeof console.log&&Function.prototype.apply.call(console.log,console,arguments)}function save(namespaces){try{if(null==namespaces){localStorage.removeItem("debug")}else{localStorage.debug=namespaces}}catch(e){}}function load(){var r;try{r=localStorage.debug}catch(e){}return r}exports.enable(load())},{"./debug":22}],22:[function(_dereq_,module,exports){exports=module.exports=debug;exports.coerce=coerce;exports.disable=disable;exports.enable=enable;exports.enabled=enabled;exports.humanize=_dereq_("ms");exports.names=[];exports.skips=[];exports.formatters={};var prevColor=0;var prevTime;function selectColor(){return exports.colors[prevColor++%exports.colors.length]}function debug(namespace){function disabled(){}disabled.enabled=false;function enabled(){var self=enabled;var curr=+new Date;var ms=curr-(prevTime||curr);self.diff=ms;self.prev=prevTime;self.curr=curr;prevTime=curr;if(null==self.useColors)self.useColors=exports.useColors();if(null==self.color&&self.useColors)self.color=selectColor();var args=Array.prototype.slice.call(arguments);args[0]=exports.coerce(args[0]);if("string"!==typeof args[0]){args=["%o"].concat(args)}var index=0;args[0]=args[0].replace(/%([a-z%])/g,function(match,format){if(match==="%")return match;index++;var formatter=exports.formatters[format];if("function"===typeof formatter){var val=args[index];match=formatter.call(self,val);args.splice(index,1);index--}return match});if("function"===typeof exports.formatArgs){args=exports.formatArgs.apply(self,args)}var logFn=enabled.log||exports.log||console.log.bind(console);logFn.apply(self,args)}enabled.enabled=true;var fn=exports.enabled(namespace)?enabled:disabled;fn.namespace=namespace;return fn}function enable(namespaces){exports.save(namespaces);var split=(namespaces||"").split(/[\s,]+/);var len=split.length;for(var i=0;i=d)return Math.round(ms/d)+"d";if(ms>=h)return Math.round(ms/h)+"h";if(ms>=m)return Math.round(ms/m)+"m";if(ms>=s)return Math.round(ms/s)+"s";return ms+"ms"}function long(ms){return plural(ms,d,"day")||plural(ms,h,"hour")||plural(ms,m,"minute")||plural(ms,s,"second")||ms+" ms"}function plural(ms,n,name){if(ms1){return{type:packetslist[type],data:data.substring(1)}}else{return{type:packetslist[type]}}}var asArray=new Uint8Array(data);var type=asArray[0];var rest=sliceBuffer(data,1);if(Blob&&binaryType==="blob"){rest=new Blob([rest])}return{type:packetslist[type],data:rest}};exports.decodeBase64Packet=function(msg,binaryType){var type=packetslist[msg.charAt(0)];if(!global.ArrayBuffer){return{type:type,data:{base64:true,data:msg.substr(1)}}}var data=base64encoder.decode(msg.substr(1));if(binaryType==="blob"&&Blob){data=new Blob([data])}return{type:type,data:data}};exports.encodePayload=function(packets,supportsBinary,callback){if(typeof supportsBinary=="function"){callback=supportsBinary;supportsBinary=null}if(supportsBinary){if(Blob&&!isAndroid){return exports.encodePayloadAsBlob(packets,callback)}return exports.encodePayloadAsArrayBuffer(packets,callback)}if(!packets.length){return callback("0:")}function setLengthHeader(message){return message.length+":"+message}function encodeOne(packet,doneCallback){exports.encodePacket(packet,supportsBinary,true,function(message){doneCallback(null,setLengthHeader(message))})}map(packets,encodeOne,function(err,results){return callback(results.join(""))})};function map(ary,each,done){var result=new Array(ary.length);var next=after(ary.length,done);var eachWithIndex=function(i,el,cb){each(el,function(error,msg){result[i]=msg;cb(error,result)})};for(var i=0;i0){var tailArray=new Uint8Array(bufferTail);var isString=tailArray[0]===0;var msgLength="";for(var i=1;;i++){if(tailArray[i]==255)break;if(msgLength.length>310){numberTooLong=true;break}msgLength+=tailArray[i]}if(numberTooLong)return callback(err,0,1);bufferTail=sliceBuffer(bufferTail,2+msgLength.length);msgLength=parseInt(msgLength);var msg=sliceBuffer(bufferTail,0,msgLength);if(isString){try{msg=String.fromCharCode.apply(null,new Uint8Array(msg))}catch(e){var typed=new Uint8Array(msg);msg="";for(var i=0;ibytes){end=bytes}if(start>=bytes||start>=end||bytes===0){return new ArrayBuffer(0)}var abv=new Uint8Array(arraybuffer);var result=new Uint8Array(end-start);for(var i=start,ii=0;i>2];base64+=chars[(bytes[i]&3)<<4|bytes[i+1]>>4];base64+=chars[(bytes[i+1]&15)<<2|bytes[i+2]>>6];base64+=chars[bytes[i+2]&63]}if(len%3===2){base64=base64.substring(0,base64.length-1)+"="}else if(len%3===1){base64=base64.substring(0,base64.length-2)+"=="}return base64};exports.decode=function(base64){var bufferLength=base64.length*.75,len=base64.length,i,p=0,encoded1,encoded2,encoded3,encoded4;if(base64[base64.length-1]==="="){bufferLength--;if(base64[base64.length-2]==="="){bufferLength--}}var arraybuffer=new ArrayBuffer(bufferLength),bytes=new Uint8Array(arraybuffer);for(i=0;i>4;bytes[p++]=(encoded2&15)<<4|encoded3>>2;bytes[p++]=(encoded3&3)<<6|encoded4&63}return arraybuffer}})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")},{}],29:[function(_dereq_,module,exports){(function(global){var BlobBuilder=global.BlobBuilder||global.WebKitBlobBuilder||global.MSBlobBuilder||global.MozBlobBuilder;var blobSupported=function(){try{var b=new Blob(["hi"]);return b.size==2}catch(e){return false}}();var blobBuilderSupported=BlobBuilder&&BlobBuilder.prototype.append&&BlobBuilder.prototype.getBlob;function BlobBuilderConstructor(ary,options){options=options||{};var bb=new BlobBuilder;for(var i=0;i=55296&&value<=56319&&counter65535){value-=65536;output+=stringFromCharCode(value>>>10&1023|55296);value=56320|value&1023}output+=stringFromCharCode(value)}return output}function createByte(codePoint,shift){return stringFromCharCode(codePoint>>shift&63|128)}function encodeCodePoint(codePoint){if((codePoint&4294967168)==0){return stringFromCharCode(codePoint)}var symbol="";if((codePoint&4294965248)==0){symbol=stringFromCharCode(codePoint>>6&31|192)}else if((codePoint&4294901760)==0){symbol=stringFromCharCode(codePoint>>12&15|224);symbol+=createByte(codePoint,6)}else if((codePoint&4292870144)==0){symbol=stringFromCharCode(codePoint>>18&7|240);symbol+=createByte(codePoint,12);symbol+=createByte(codePoint,6)}symbol+=stringFromCharCode(codePoint&63|128);return symbol}function utf8encode(string){var codePoints=ucs2decode(string);var length=codePoints.length;var index=-1;var codePoint;var byteString="";while(++index=byteCount){throw Error("Invalid byte index")}var continuationByte=byteArray[byteIndex]&255;byteIndex++;if((continuationByte&192)==128){return continuationByte&63}throw Error("Invalid continuation byte")}function decodeSymbol(){var byte1;var byte2;var byte3;var byte4;var codePoint;if(byteIndex>byteCount){throw Error("Invalid byte index")}if(byteIndex==byteCount){return false}byte1=byteArray[byteIndex]&255;byteIndex++;if((byte1&128)==0){return byte1}if((byte1&224)==192){var byte2=readContinuationByte();codePoint=(byte1&31)<<6|byte2;if(codePoint>=128){return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&240)==224){byte2=readContinuationByte();byte3=readContinuationByte();codePoint=(byte1&15)<<12|byte2<<6|byte3;if(codePoint>=2048){return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&248)==240){byte2=readContinuationByte();byte3=readContinuationByte();byte4=readContinuationByte();codePoint=(byte1&15)<<18|byte2<<12|byte3<<6|byte4;if(codePoint>=65536&&codePoint<=1114111){return codePoint}}throw Error("Invalid UTF-8 detected")}var byteArray;var byteCount;var byteIndex;function utf8decode(byteString){byteArray=ucs2decode(byteString);byteCount=byteArray.length;byteIndex=0;var codePoints=[];var tmp;while((tmp=decodeSymbol())!==false){codePoints.push(tmp)}return ucs2encode(codePoints)}var utf8={version:"2.0.0",encode:utf8encode,decode:utf8decode};if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){define(function(){return utf8})}else if(freeExports&&!freeExports.nodeType){if(freeModule){freeModule.exports=utf8}else{var object={};var hasOwnProperty=object.hasOwnProperty;for(var key in utf8){hasOwnProperty.call(utf8,key)&&(freeExports[key]=utf8[key])}}}else{root.utf8=utf8}})(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],31:[function(_dereq_,module,exports){(function(global){var rvalidchars=/^[\],:{}\s]*$/;var rvalidescape=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rvalidtokens=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rvalidbraces=/(?:^|:|,)(?:\s*\[)+/g;var rtrimLeft=/^\s+/;var rtrimRight=/\s+$/;module.exports=function parsejson(data){if("string"!=typeof data||!data){return null}data=data.replace(rtrimLeft,"").replace(rtrimRight,"");if(global.JSON&&JSON.parse){return JSON.parse(data)}if(rvalidchars.test(data.replace(rvalidescape,"@").replace(rvalidtokens,"]").replace(rvalidbraces,""))){return new Function("return "+data)()}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],32:[function(_dereq_,module,exports){exports.encode=function(obj){var str="";for(var i in obj){if(obj.hasOwnProperty(i)){if(str.length)str+="&";str+=encodeURIComponent(i)+"="+encodeURIComponent(obj[i])}}return str};exports.decode=function(qs){var qry={};var pairs=qs.split("&");for(var i=0,l=pairs.length;i1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400)}}if(!(isProperty={}.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={toString:1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result}}else{constructor=members.constructor;isProperty=function(property){var parent=(this.constructor||constructor).prototype;return property in this&&!(property in parent&&this[property]===parent[property])}}members=null;return isProperty.call(this,property)}}var PrimitiveTypes={"boolean":1,number:1,string:1,undefined:1};var isHostType=function(object,property){var type=typeof object[property];return type=="object"?!!object[property]:!PrimitiveTypes[type]};forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0}).prototype.valueOf=0;members=new Properties;for(property in members){if(isProperty.call(members,property)){size++}}Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,length;var hasProperty=!isFunction&&typeof object.constructor!="function"&&isHostType(object,"hasOwnProperty")?object.hasOwnProperty:isProperty;for(property in object){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property)}}for(length=members.length;property=members[--length];hasProperty.call(object,property)&&callback(property));}}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property)}}}}else{forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,isConstructor;for(property in object){if(!(isFunction&&property=="prototype")&&isProperty.call(object,property)&&!(isConstructor=property==="constructor")){callback(property)}}if(isConstructor||isProperty.call(object,property="constructor")){callback(property)}}}return forEach(object,callback)};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width)};var unicodePrefix="\\u00";var quote=function(value){var result='"',index=0,length=value.length,isLarge=length>10&&charIndexBuggy,symbols;if(isLarge){symbols=value.split("")}for(;index-1/0&&value<1/0){if(getDay){date=floor(value/864e5);for(year=floor(date/365.2425)+1970-1;getDay(year+1,0)<=date;year++);for(month=floor((date-getDay(year,0))/30.42);getDay(year,month+1)<=date;month++);date=1+date-getDay(year,month);time=(value%864e5+864e5)%864e5;hours=floor(time/36e5)%24;minutes=floor(time/6e4)%60;seconds=floor(time/1e3)%60;milliseconds=time%1e3}else{year=value.getUTCFullYear();month=value.getUTCMonth();date=value.getUTCDate();hours=value.getUTCHours();minutes=value.getUTCMinutes();seconds=value.getUTCSeconds();milliseconds=value.getUTCMilliseconds()}value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z"}else{value=null}}else if(typeof value.toJSON=="function"&&(className!=numberClass&&className!=stringClass&&className!=arrayClass||isProperty.call(value,"toJSON"))){value=value.toJSON(property)}}if(callback){value=callback.call(object,property,value)}if(value===null){return"null"}className=getClass.call(value);if(className==booleanClass){return""+value}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null"}else if(className==stringClass){return quote(""+value)}if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError()}}stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index0){for(whitespace="",width>10&&(width=10);whitespace.length=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort()}}value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort()}}else{if(charCode==34){break}charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index)}value+=source.slice(begin,Index)}}if(source.charCodeAt(Index)==34){Index++;return value}abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index)}if(charCode>=48&&charCode<=57){if(charCode==48&&(charCode=source.charCodeAt(Index+1),charCode>=48&&charCode<=57)){abort()}isSigned=false;for(;Index=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++}for(position=Index;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}return+source.slice(begin,Index)}if(isSigned){abort()}if(source.slice(Index,Index+4)=="true"){Index+=4;return true}else if(source.slice(Index,Index+5)=="false"){Index+=5;return false}else if(source.slice(Index,Index+4)=="null"){Index+=4;return null}abort()}}return"$"};var get=function(value){var results,hasMembers;if(value=="$"){abort()}if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1)}if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break}if(hasMembers){if(value==","){value=lex();if(value=="]"){abort()}}else{abort()}}if(value==","){abort()}results.push(get(value))}return results}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break}if(hasMembers){if(value==","){value=lex();if(value=="}"){abort()}}else{abort()}}if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort()}results[value.slice(1)]=get(lex())}return results}abort()}return value};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property]}else{source[property]=element}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){if(getClass.call(value)==arrayClass){for(length=value.length;length--;){update(value,length,callback)}}else{forEach(value,function(property){update(value,property,callback)})}}return callback.call(source,property,value)};JSON3.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort()}Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result}}}if(isLoader){define(function(){return JSON3})}})(this)},{}],47:[function(_dereq_,module,exports){module.exports=toArray;function toArray(list,index){var array=[];index=index||0;for(var i=index||0;i :body
+ * => :statusCode
+ * => :headers
+ *
+ * @constructor
+ */
+
+ function JWR(responseCtx) {
+ this.body = responseCtx.body || {};
+ this.headers = responseCtx.headers || {};
+ this.statusCode = responseCtx.statusCode || 200;
+ if (this.statusCode < 200 || this.statusCode >= 400) {
+ this.error = this.body || this.statusCode;
+ }
+ }
+ JWR.prototype.toString = function() {
+ return '[ResponseFromSails]' + ' -- ' +
+ 'Status: ' + this.statusCode + ' -- ' +
+ 'Headers: ' + this.headers + ' -- ' +
+ 'Body: ' + this.body;
+ };
+ JWR.prototype.toPOJO = function() {
+ return {
+ body: this.body,
+ headers: this.headers,
+ statusCode: this.statusCode
+ };
+ };
+ JWR.prototype.pipe = function() {
+ // TODO: look at substack's stuff
+ return new Error('Client-side streaming support not implemented yet.');
+ };
+
+
+ /**
+ * @api private
+ * @param {SailsSocket} socket [description]
+ * @param {Object} requestCtx [description]
+ */
+
+ function _emitFrom(socket, requestCtx) {
+
+ if (!socket._raw) {
+ throw new Error('Failed to emit from socket- raw SIO socket is missing.');
+ }
+
+ // Since callback is embedded in requestCtx,
+ // retrieve it and delete the key before continuing.
+ var cb = requestCtx.cb;
+ delete requestCtx.cb;
+
+ // Name of the appropriate socket.io listener on the server
+ // ( === the request method or "verb", e.g. 'get', 'post', 'put', etc. )
+ var sailsEndpoint = requestCtx.method;
+
+ socket._raw.emit(sailsEndpoint, requestCtx, function serverResponded(responseCtx) {
+
+ // Send back (emulatedHTTPBody, jsonWebSocketResponse)
+ if (cb) {
+ cb(responseCtx.body, new JWR(responseCtx));
+ }
+ });
+ }
+
+ //////////////////////////////////////////////////////////////
+ ///// ////////////////////////
+ //////////////////////////////////////////////////////////////
+
+
+
+ // Version note:
+ //
+ // `io.SocketNamespace.prototype` doesn't exist in sio 1.0.
+ //
+ // Rather than adding methods to the prototype for the Socket instance that is returned
+ // when the browser connects with `io.connect()`, we create our own constructor, `SailsSocket`.
+ // This makes our solution more future-proof and helps us work better w/ the Socket.io team
+ // when changes are rolled out in the future. To get a `SailsSocket`, you can run:
+ // ```
+ // io.sails.connect();
+ // ```
+
+
+
+ /**
+ * SailsSocket
+ *
+ * A wrapper for an underlying Socket instance that communicates directly
+ * to the Socket.io server running inside of Sails.
+ *
+ * If no `socket` option is provied, SailsSocket will function as a mock. It will queue socket
+ * requests and event handler bindings, replaying them when the raw underlying socket actually
+ * connects. This is handy when we don't necessarily have the valid configuration to know
+ * WHICH SERVER to talk to yet, etc. It is also used by `io.socket` for your convenience.
+ *
+ * @constructor
+ */
+
+ function SailsSocket (opts){
+ var self = this;
+ opts = opts||{};
+
+ // Absorb opts
+ self.useCORSRouteToGetCookie = opts.useCORSRouteToGetCookie;
+ self.url = opts.url;
+ self.multiplex = opts.multiplex;
+ self.transports = opts.transports;
+
+ // Set up "eventQueue" to hold event handlers which have not been set on the actual raw socket yet.
+ self.eventQueue = {};
+
+ // Listen for special `parseError` event sent from sockets hook on the backend
+ // if an error occurs but a valid callback was not received from the client
+ // (i.e. so the server had no other way to send back the error information)
+ self.on('sails:parseError', function (err){
+ consolog('Sails encountered an error parsing a socket message sent from this client, and did not have access to a callback function to respond with.');
+ consolog('Error details:',err);
+ });
+
+ // TODO:
+ // Listen for a special private message on any connected that allows the server
+ // to set the environment (giving us 100% certainty that we guessed right)
+ // However, note that the `console.log`s called before and after connection
+ // are still forced to rely on our existing heuristics (to disable, tack #production
+ // onto the URL used to fetch this file.)
+ }
+
+
+ /**
+ * Start connecting this socket.
+ *
+ * @api private
+ */
+ SailsSocket.prototype._connect = function (){
+ var self = this;
+
+ // Apply `io.sails` config as defaults
+ // (now that at least one tick has elapsed)
+ self.useCORSRouteToGetCookie = self.useCORSRouteToGetCookie||io.sails.useCORSRouteToGetCookie;
+ self.url = self.url||io.sails.url;
+ self.transports = self.transports || io.sails.transports;
+
+ // Ensure URL has no trailing slash
+ self.url = self.url ? self.url.replace(/(\/)$/, '') : undefined;
+
+ // Mix the current SDK version into the query string in
+ // the connection request to the server:
+ if (typeof self.query !== 'string') self.query = SDK_INFO.versionString;
+ else self.query += '&' + SDK_INFO.versionString;
+
+ // Determine whether this is a cross-origin socket by examining the
+ // hostname and port on the `window.location` object.
+ var isXOrigin = (function (){
+
+ // If `window` doesn't exist (i.e. being used from node.js), then it's
+ // always "cross-domain".
+ if (typeof window === 'undefined' || typeof window.location === 'undefined') {
+ return false;
+ }
+
+ // If `self.url` (aka "target") is falsy, then we don't need to worry about it.
+ if (typeof self.url !== 'string') { return false; }
+
+ // Get information about the "target" (`self.url`)
+ var targetProtocol = (function (){
+ try {
+ targetProtocol = self.url.match(/^([a-z]+:\/\/)/i)[1].toLowerCase();
+ }
+ catch (e) {}
+ targetProtocol = targetProtocol || 'http://';
+ return targetProtocol;
+ })();
+ var isTargetSSL = !!self.url.match('^https');
+ var targetPort = (function (){
+ try {
+ return self.url.match(/^[a-z]+:\/\/[^:]*:([0-9]*)/i)[1];
+ }
+ catch (e){}
+ return isTargetSSL ? '443' : '80';
+ })();
+ var targetAfterProtocol = self.url.replace(/^([a-z]+:\/\/)/i, '');
+
+
+ // If target protocol is different than the actual protocol,
+ // then we'll consider this cross-origin.
+ if (targetProtocol.replace(/[:\/]/g, '') !== window.location.protocol.replace(/[:\/]/g,'')) {
+ return true;
+ }
+
+
+ // If target hostname is different than actual hostname, we'll consider this cross-origin.
+ var hasSameHostname = targetAfterProtocol.search(window.location.hostname) !== 0;
+ if (!hasSameHostname) {
+ return true;
+ }
+
+ // If no actual port is explicitly set on the `window.location` object,
+ // we'll assume either 80 or 443.
+ var isLocationSSL = window.location.protocol.match(/https/i);
+ var locationPort = (window.location.port+'') || (isLocationSSL ? '443' : '80');
+
+ // Finally, if ports don't match, we'll consider this cross-origin.
+ if (targetPort !== locationPort) {
+ return true;
+ }
+
+ // Otherwise, it's the same origin.
+ return false;
+
+ })();
+
+
+ // Prepare to start connecting the socket
+ (function selfInvoking (cb){
+
+ // If this is an attempt at a cross-origin or cross-port
+ // socket connection, send a JSONP request first to ensure
+ // that a valid cookie is available. This can be disabled
+ // by setting `io.sails.useCORSRouteToGetCookie` to false.
+ //
+ // Otherwise, skip the stuff below.
+ if (!(self.useCORSRouteToGetCookie && isXOrigin)) {
+ return cb();
+ }
+
+ // Figure out the x-origin CORS route
+ // (Sails provides a default)
+ var xOriginCookieURL = self.url;
+ if (typeof self.useCORSRouteToGetCookie === 'string') {
+ xOriginCookieURL += self.useCORSRouteToGetCookie;
+ }
+ else {
+ xOriginCookieURL += '/__getcookie';
+ }
+
+
+ // Make the AJAX request (CORS)
+ if (typeof window !== 'undefined') {
+ jsonp({
+ url: xOriginCookieURL,
+ method: 'GET'
+ }, cb);
+ return;
+ }
+
+ // If there's no `window` object, we must be running in Node.js
+ // so just require the request module and send the HTTP request that
+ // way.
+ var mikealsReq = require('request');
+ mikealsReq.get(xOriginCookieURL, function(err, httpResponse, body) {
+ if (err) {
+ consolog(
+ 'Failed to connect socket (failed to get cookie)',
+ 'Error:', err
+ );
+ return;
+ }
+ cb();
+ });
+
+ })(function goAheadAndActuallyConnect() {
+
+ // Now that we're ready to connect, create a raw underlying Socket
+ // using Socket.io and save it as `_raw` (this will start it connecting)
+ self._raw = io(self.url, self);
+
+ // Replay event bindings from the eager socket
+ self.replay();
+
+
+ /**
+ * 'connect' event is triggered when the socket establishes a connection
+ * successfully.
+ */
+ self.on('connect', function socketConnected() {
+
+ consolog.noPrefix(
+ '\n' +
+ '\n' +
+ // ' |> ' + '\n' +
+ // ' \\___/ '+️
+ // '\n'+
+ ' |> Now connected to Sails.' + '\n' +
+ '\\___/ For help, see: http://bit.ly/1DmTvgK' + '\n' +
+ ' (using '+io.sails.sdk.platform+' SDK @v'+io.sails.sdk.version+')'+ '\n' +
+ '\n'+
+ '\n'+
+ // '\n'+
+ ''
+ // ' ⚓︎ (development mode)'
+ // 'e.g. to send a GET request to Sails via WebSockets, run:'+ '\n' +
+ // '`io.socket.get("/foo", function serverRespondedWith (body, jwr) { console.log(body); })`'+ '\n' +
+ );
+ });
+
+ self.on('disconnect', function() {
+ self.connectionLostTimestamp = (new Date()).getTime();
+ consolog('====================================');
+ consolog('Socket was disconnected from Sails.');
+ consolog('Usually, this is due to one of the following reasons:' + '\n' +
+ ' -> the server ' + (self.url ? self.url + ' ' : '') + 'was taken down' + '\n' +
+ ' -> your browser lost internet connectivity');
+ consolog('====================================');
+ });
+
+ self.on('reconnecting', function(numAttempts) {
+ consolog(
+ '\n'+
+ ' Socket is trying to reconnect to Sails...\n'+
+ '_-|>_- (attempt #' + numAttempts + ')'+'\n'+
+ '\n'
+ );
+ });
+
+ self.on('reconnect', function(transport, numAttempts) {
+ var msSinceConnectionLost = ((new Date()).getTime() - self.connectionLostTimestamp);
+ var numSecsOffline = (msSinceConnectionLost / 1000);
+ consolog(
+ '\n'+
+ ' |> Socket reconnected successfully after'+'\n'+
+ '\\___/ being offline for ~' + numSecsOffline + ' seconds.'+'\n'+
+ '\n'
+ );
+ });
+
+ // 'error' event is triggered if connection can not be established.
+ // (usually because of a failed authorization, which is in turn
+ // usually due to a missing or invalid cookie)
+ self.on('error', function failedToConnect(err) {
+
+ // TODO:
+ // handle failed connections due to failed authorization
+ // in a smarter way (probably can listen for a different event)
+
+ // A bug in Socket.io 0.9.x causes `connect_failed`
+ // and `reconnect_failed` not to fire.
+ // Check out the discussion in github issues for details:
+ // https://github.com/LearnBoost/socket.io/issues/652
+ // io.socket.on('connect_failed', function () {
+ // consolog('io.socket emitted `connect_failed`');
+ // });
+ // io.socket.on('reconnect_failed', function () {
+ // consolog('io.socket emitted `reconnect_failed`');
+ // });
+
+ consolog(
+ 'Failed to connect socket (probably due to failed authorization on server)',
+ 'Error:', err
+ );
+ });
+ });
+
+ };
+
+
+ /**
+ * Disconnect the underlying socket.
+ *
+ * @api public
+ */
+ SailsSocket.prototype.disconnect = function (){
+ if (!this._raw) {
+ throw new Error('Cannot disconnect- socket is already disconnected');
+ }
+ return this._raw.disconnect();
+ };
+
+
+
+ /**
+ * isConnected
+ *
+ * @api private
+ * @return {Boolean} whether the socket is connected and able to
+ * communicate w/ the server.
+ */
+
+ SailsSocket.prototype.isConnected = function () {
+ if (!this._raw) {
+ return false;
+ }
+
+ return !!this._raw.connected;
+ };
+
+
+
+ /**
+ * [replay description]
+ * @return {[type]} [description]
+ */
+ SailsSocket.prototype.replay = function (){
+ var self = this;
+
+ // Pass events and a reference to the request queue
+ // off to the self._raw for consumption
+ for (var evName in self.eventQueue) {
+ for (var i in self.eventQueue[evName]) {
+ self._raw.on(evName, self.eventQueue[evName][i]);
+ }
+ }
+
+ // Bind a one-time function to run the request queue
+ // when the self._raw connects.
+ if ( !self.isConnected() ) {
+ var alreadyRanRequestQueue = false;
+ self._raw.on('connect', function whenRawSocketConnects() {
+ if (alreadyRanRequestQueue) return;
+ runRequestQueue(self);
+ alreadyRanRequestQueue = true;
+ });
+ }
+ // Or run it immediately if self._raw is already connected
+ else {
+ runRequestQueue(self);
+ }
+
+ return self;
+ };
+
+
+ /**
+ * Chainable method to bind an event to the socket.
+ *
+ * @param {String} evName [event name]
+ * @param {Function} fn [event handler function]
+ * @return {SailsSocket}
+ */
+ SailsSocket.prototype.on = function (evName, fn){
+
+ // Bind the event to the raw underlying socket if possible.
+ if (this._raw) {
+ this._raw.on(evName, fn);
+ return this;
+ }
+
+ // Otherwise queue the event binding.
+ if (!this.eventQueue[evName]) {
+ this.eventQueue[evName] = [fn];
+ }
+ else {
+ this.eventQueue[evName].push(fn);
+ }
+
+ return this;
+ };
+
+ /**
+ * Chainable method to unbind an event from the socket.
+ *
+ * @param {String} evName [event name]
+ * @param {Function} fn [event handler function]
+ * @return {SailsSocket}
+ */
+ SailsSocket.prototype.off = function (evName, fn){
+
+ // Bind the event to the raw underlying socket if possible.
+ if (this._raw) {
+ this._raw.off(evName, fn);
+ return this;
+ }
+
+ // Otherwise queue the event binding.
+ if (this.eventQueue[evName] && this.eventQueue[evName].indexOf(fn) > -1) {
+ this.eventQueue[evName].splice(this.eventQueue[evName].indexOf(fn), 1);
+ }
+
+ return this;
+ };
+
+
+ /**
+ * Chainable method to unbind all events from the socket.
+ *
+ * @return {SailsSocket}
+ */
+ SailsSocket.prototype.removeAllListeners = function (){
+
+ // Bind the event to the raw underlying socket if possible.
+ if (this._raw) {
+ this._raw.removeAllListeners();
+ return this;
+ }
+
+ // Otherwise queue the event binding.
+ this.eventQueue = {};
+
+ return this;
+ };
+
+ /**
+ * Simulate a GET request to sails
+ * e.g.
+ * `socket.get('/user/3', Stats.populate)`
+ *
+ * @api public
+ * @param {String} url :: destination URL
+ * @param {Object} params :: parameters to send with the request [optional]
+ * @param {Function} cb :: callback function to call when finished [optional]
+ */
+
+ SailsSocket.prototype.get = function(url, data, cb) {
+
+ // `data` is optional
+ if (typeof data === 'function') {
+ cb = data;
+ data = {};
+ }
+
+ return this.request({
+ method: 'get',
+ params: data,
+ url: url
+ }, cb);
+ };
+
+
+
+ /**
+ * Simulate a POST request to sails
+ * e.g.
+ * `socket.post('/event', newMeeting, $spinner.hide)`
+ *
+ * @api public
+ * @param {String} url :: destination URL
+ * @param {Object} params :: parameters to send with the request [optional]
+ * @param {Function} cb :: callback function to call when finished [optional]
+ */
+
+ SailsSocket.prototype.post = function(url, data, cb) {
+
+ // `data` is optional
+ if (typeof data === 'function') {
+ cb = data;
+ data = {};
+ }
+
+ return this.request({
+ method: 'post',
+ data: data,
+ url: url
+ }, cb);
+ };
+
+
+
+ /**
+ * Simulate a PUT request to sails
+ * e.g.
+ * `socket.post('/event/3', changedFields, $spinner.hide)`
+ *
+ * @api public
+ * @param {String} url :: destination URL
+ * @param {Object} params :: parameters to send with the request [optional]
+ * @param {Function} cb :: callback function to call when finished [optional]
+ */
+
+ SailsSocket.prototype.put = function(url, data, cb) {
+
+ // `data` is optional
+ if (typeof data === 'function') {
+ cb = data;
+ data = {};
+ }
+
+ return this.request({
+ method: 'put',
+ params: data,
+ url: url
+ }, cb);
+ };
+
+
+
+ /**
+ * Simulate a DELETE request to sails
+ * e.g.
+ * `socket.delete('/event', $spinner.hide)`
+ *
+ * @api public
+ * @param {String} url :: destination URL
+ * @param {Object} params :: parameters to send with the request [optional]
+ * @param {Function} cb :: callback function to call when finished [optional]
+ */
+
+ SailsSocket.prototype['delete'] = function(url, data, cb) {
+
+ // `data` is optional
+ if (typeof data === 'function') {
+ cb = data;
+ data = {};
+ }
+
+ return this.request({
+ method: 'delete',
+ params: data,
+ url: url
+ }, cb);
+ };
+
+
+
+ /**
+ * Simulate an HTTP request to sails
+ * e.g.
+ * ```
+ * socket.request({
+ * url:'/user',
+ * params: {},
+ * method: 'POST',
+ * headers: {}
+ * }, function (responseBody, JWR) {
+ * // ...
+ * });
+ * ```
+ *
+ * @api public
+ * @option {String} url :: destination URL
+ * @option {Object} params :: parameters to send with the request [optional]
+ * @option {Object} headers:: headers to send with the request [optional]
+ * @option {Function} cb :: callback function to call when finished [optional]
+ * @option {String} method :: HTTP request method [optional]
+ */
+
+ SailsSocket.prototype.request = function(options, cb) {
+
+ var usage =
+ 'Usage:\n'+
+ 'socket.request( options, [fnToCallWhenComplete] )\n\n'+
+ 'options.url :: e.g. "/foo/bar"'+'\n'+
+ 'options.method :: e.g. "get", "post", "put", or "delete", etc.'+'\n'+
+ 'options.params :: e.g. { emailAddress: "mike@sailsjs.org" }'+'\n'+
+ 'options.headers :: e.g. { "x-my-custom-header": "some string" }';
+ // Old usage:
+ // var usage = 'Usage:\n socket.'+(options.method||'request')+'('+
+ // ' destinationURL, [dataToSend], [fnToCallWhenComplete] )';
+
+
+ // Validate options and callback
+ if (typeof options !== 'object' || typeof options.url !== 'string') {
+ throw new Error('Invalid or missing URL!\n' + usage);
+ }
+ if (options.method && typeof options.method !== 'string') {
+ throw new Error('Invalid `method` provided (should be a string like "post" or "put")\n' + usage);
+ }
+ if (options.headers && typeof options.headers !== 'object') {
+ throw new Error('Invalid `headers` provided (should be an object with string values)\n' + usage);
+ }
+ if (options.params && typeof options.params !== 'object') {
+ throw new Error('Invalid `params` provided (should be an object with string values)\n' + usage);
+ }
+ if (cb && typeof cb !== 'function') {
+ throw new Error('Invalid callback function!\n' + usage);
+ }
+
+
+ // Build a simulated request object
+ // (and sanitize/marshal options along the way)
+ var requestCtx = {
+
+ method: options.method.toLowerCase() || 'get',
+
+ headers: options.headers || {},
+
+ data: options.params || options.data || {},
+
+ // Remove trailing slashes and spaces to make packets smaller.
+ url: options.url.replace(/^(.+)\/*\s*$/, '$1'),
+
+ cb: cb
+ };
+
+ // If this socket is not connected yet, queue up this request
+ // instead of sending it.
+ // (so it can be replayed when the socket comes online.)
+ if ( ! this.isConnected() ) {
+
+ // If no queue array exists for this socket yet, create it.
+ this.requestQueue = this.requestQueue || [];
+ this.requestQueue.push(requestCtx);
+ return;
+ }
+
+
+ // Otherwise, our socket is ok!
+ // Send the request.
+ _emitFrom(this, requestCtx);
+ };
+
+
+
+ /**
+ * Socket.prototype._request
+ *
+ * Simulate HTTP over Socket.io.
+ *
+ * @api private
+ * @param {[type]} options [description]
+ * @param {Function} cb [description]
+ */
+ SailsSocket.prototype._request = function(options, cb) {
+ throw new Error('`_request()` was a private API deprecated as of v0.11 of the sails.io.js client. Use `.request()` instead.');
+ };
+
+
+
+ // Set a `sails` object that may be used for configuration before the
+ // first socket connects (i.e. to prevent auto-connect)
+ io.sails = {
+
+ // Whether to automatically connect a socket and save it as `io.socket`.
+ autoConnect: true,
+
+ // The route (path) to hit to get a x-origin (CORS) cookie
+ // (or true to use the default: '/__getcookie')
+ useCORSRouteToGetCookie: true,
+
+ // The environment we're running in.
+ // (logs are not displayed when this is set to 'production')
+ //
+ // Defaults to development unless this script was fetched from a URL
+ // that ends in `*.min.js` or '#production' (may also be manually overridden.)
+ //
+ environment: urlThisScriptWasFetchedFrom.match(/(\#production|\.min\.js)/g) ? 'production' : 'development',
+
+ // The version of this sails.io.js client SDK
+ sdk: SDK_INFO,
+
+ // Transports to use when communicating with the server, in the order they will be tried
+ transports: ['polling', 'websocket']
+ };
+
+
+
+ /**
+ * Add `io.sails.connect` function as a wrapper for the built-in `io()` aka `io.connect()`
+ * method, returning a SailsSocket. This special function respects the configured io.sails
+ * connection URL, as well as sending other identifying information (most importantly, the
+ * current version of this SDK).
+ *
+ * @param {String} url [optional]
+ * @param {Object} opts [optional]
+ * @return {Socket}
+ */
+ io.sails.connect = function(url, opts) {
+ opts = opts || {};
+
+ // If explicit connection url is specified, save it to options
+ opts.url = url || opts.url || undefined;
+
+ // Instantiate and return a new SailsSocket- and try to connect immediately.
+ var socket = new SailsSocket(opts);
+ socket._connect();
+ return socket;
+ };
+
+
+
+ // io.socket
+ //
+ // The eager instance of Socket which will automatically try to connect
+ // using the host that this js file was served from.
+ //
+ // This can be disabled or configured by setting properties on `io.sails.*` within the
+ // first cycle of the event loop.
+ //
+
+
+ // Build `io.socket` so it exists
+ // (this does not start the connection process)
+ io.socket = new SailsSocket();
+
+ // In the mean time, this eager socket will be queue events bound by the user
+ // before the first cycle of the event loop (using `.on()`), which will later
+ // be rebound on the raw underlying socket.
+
+ // If configured to do so, start auto-connecting after the first cycle of the event loop
+ // has completed (to allow time for this behavior to be configured/disabled
+ // by specifying properties on `io.sails`)
+ setTimeout(function() {
+
+ // If autoConnect is disabled, delete the eager socket (io.socket) and bail out.
+ if (!io.sails.autoConnect) {
+ delete io.socket;
+ return;
+ }
+
+ // consolog('Eagerly auto-connecting socket to Sails... (requests will be queued in the mean-time)');
+ io.socket._connect();
+
+
+ }, 0); //
+
+
+ // Return the `io` object.
+ return io;
+ }
+
+
+ // Add CommonJS support to allow this client SDK to be used from Node.js.
+ if (typeof module === 'object' && typeof module.exports !== 'undefined') {
+ module.exports = SailsIOClient;
+ return SailsIOClient;
+ }
+
+ // Otherwise, try to instantiate the client:
+ // In case you're wrapping the socket.io client to prevent pollution of the
+ // global namespace, you can replace the global `io` with your own `io` here:
+ return SailsIOClient();
+
+})();
diff --git a/lightcalc/assets/js/directives/isNameFree.js b/lightcalc/assets/js/directives/isNameFree.js
new file mode 100644
index 0000000..cf364d0
--- /dev/null
+++ b/lightcalc/assets/js/directives/isNameFree.js
@@ -0,0 +1,30 @@
+angular.module('LightCalc').directive('isNameFree', ['Calculators', '$q', '$timeout',function(Calculators, $q, $timeout) {
+ return {
+ require: 'ngModel',
+ link: function(scope, elm, attrs, ctrl) {
+
+ ctrl.$asyncValidators.isNameFree = function(modelValue, viewValue) {
+
+ if (ctrl.$isEmpty(modelValue)) {
+ // consider empty model valid
+ return $q.when();
+ }
+
+ var def = $q.defer();
+
+ var calculators = Calculators.get({name: modelValue});
+ calculators.$promise.then(function(){
+ if (calculators.length === 0) {
+ // The username is available
+ def.resolve();
+ } else {
+ def.reject();
+ }
+ })
+
+
+ return def.promise;
+ };
+ }
+ };
+}]);
\ No newline at end of file
diff --git a/lightcalc/assets/js/routes.js b/lightcalc/assets/js/routes.js
new file mode 100644
index 0000000..1b330e8
--- /dev/null
+++ b/lightcalc/assets/js/routes.js
@@ -0,0 +1,12 @@
+angular.module('LightCalc').config(['$routeProvider', function($routeProvider){
+ $routeProvider
+ .when('/', {
+ templateUrl: 'templates/calculator/create.html',
+ controller: 'calculatorCreateCtrl'
+ })
+ .when('/:name/', {
+ templateUrl: '/templates/calculator/retrieve.html',
+ controller: 'calculatorRetrieveCtrl'
+ })
+ .otherwise({redirectTo: '/'});
+}]);
\ No newline at end of file
diff --git a/lightcalc/assets/js/services/calculators.js b/lightcalc/assets/js/services/calculators.js
new file mode 100644
index 0000000..7e1d8d4
--- /dev/null
+++ b/lightcalc/assets/js/services/calculators.js
@@ -0,0 +1,7 @@
+angular.module('LightCalc').factory('Calculators', ['$resource', function CalculatorsFactory($resource){
+ return $resource('/calculator/:slug/',{},{
+ get: {
+ isArray: true
+ }
+ });
+}]);
\ No newline at end of file
diff --git a/lightcalc/assets/js/services/operations.js b/lightcalc/assets/js/services/operations.js
new file mode 100644
index 0000000..d8f0034
--- /dev/null
+++ b/lightcalc/assets/js/services/operations.js
@@ -0,0 +1,3 @@
+angular.module('LightCalc').factory('Operations', ['$resource', function OperationsFactory($resource){
+ return $resource('/operation/:slug/');
+}]);
\ No newline at end of file
diff --git a/lightcalc/assets/js/tests/CalculatorsCreate.test.js b/lightcalc/assets/js/tests/CalculatorsCreate.test.js
new file mode 100644
index 0000000..60703d2
--- /dev/null
+++ b/lightcalc/assets/js/tests/CalculatorsCreate.test.js
@@ -0,0 +1,129 @@
+describe('calculatorCreateCtrl', function(){
+ var scope, location, httpBackend;
+
+ beforeEach(function(){
+ module('LightCalc');
+ module('MyTemplates');
+ });
+
+ beforeEach(inject(function($rootScope, $controller, $location, $httpBackend){
+ scope = $rootScope.$new();
+ location = $location;
+ location.path('/');
+
+ $controller('calculatorCreateCtrl', {
+ "$scope": scope
+ });
+ httpBackend = $httpBackend;
+ httpBackend.when('POST', '/calculator').respond(201, {
+ "operations": [],
+ "name": "teste123123",
+ "createdAt": "2015-06-12T05:19:54.810Z",
+ "updatedAt": "2015-06-12T05:19:54.810Z",
+ "id": 40
+ });
+
+ //httpBackend.when('GET', '/calculator').respond([]);
+ httpBackend.when('GET', '/calculator?name=').respond(200, []);
+ httpBackend.when('GET', '/calculator?name=teste123123').respond(200, []);
+ httpBackend.when('GET', '/calculator?name=teste').respond(200, []);
+ httpBackend.when('GET', '/calculator?name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa').respond(200, []);
+ httpBackend.when('GET', '/calculator?name=hugobessa').respond([{
+ "operations": [],
+ "name": "hugobessa",
+ "createdAt": "2015-06-12T05:19:54.810Z",
+ "updatedAt": "2015-06-12T05:19:54.810Z",
+ "id": 40
+ }]);
+ httpBackend.when('GET', '/calculator').respond([]);
+
+
+ }));
+
+ describe('submitForm', function(){
+
+ it('should redirect the user to the calculator if the name is valid', function(done) {
+ inject(function($templateCache, $compile){
+ templateHtml = $templateCache.get('assets/templates/calculator/create.html');
+ formElem = angular.element("
" + templateHtml + "
");
+
+ $compile(formElem)(scope);
+ scope.$apply();
+ scope.form.$setDirty();
+
+ scope.calculatorForm.name = 'teste123123';
+
+ resp = scope.submitForm();
+ scope.$apply();
+ httpBackend.flush();
+ resp.$promise.then(function(){
+ location.path().should.equals('/teste123123');
+ done();
+ })
+ scope.$root.$digest();
+ });
+ });
+
+ it('should fail if the name has less than 8 chars', inject(function($templateCache, $compile){
+ templateHtml = $templateCache.get('assets/templates/calculator/create.html');
+ formElem = angular.element("
" + templateHtml + "
");
+
+ scope.calculatorForm.name = 'teste';
+
+ $compile(formElem)(scope);
+ scope.$apply();
+
+ scope.submitForm();
+
+ (!!scope.form.$error.minlength).should.equal(true);
+ }));
+
+ it('should fail if the name has more than 40 chars', inject(function($templateCache, $compile){
+ templateHtml = $templateCache.get('assets/templates/calculator/create.html');
+ formElem = angular.element("
" + templateHtml + "
");
+
+ scope.calculatorForm.name = new Array( 40 + 2 ).join('a');
+
+ $compile(formElem)(scope);
+ scope.$apply();
+
+ scope.submitForm();
+
+ (!!scope.form.$error.maxlength).should.equal(true);
+ }));
+
+ it('should fail if the name is not alphanumeric', inject(function($templateCache, $compile){
+ templateHtml = $templateCache.get('assets/templates/calculator/create.html');
+ formElem = angular.element("
\ No newline at end of file
diff --git a/lightcalc/bower.json b/lightcalc/bower.json
new file mode 100644
index 0000000..26ad53e
--- /dev/null
+++ b/lightcalc/bower.json
@@ -0,0 +1,26 @@
+{
+ "name": "LightCalc",
+ "version": "0.0.0",
+ "homepage": "https://github.com/hugobessa/lightcalc",
+ "authors": [
+ "Hugo Bessa "
+ ],
+ "license": "MIT",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "test",
+ "tests"
+ ],
+ "dependencies": {
+ "angular-resource": "~1.4.0",
+ "angular-messages": "~1.4.0",
+ "bootstrap": "~3.3.4",
+ "angular": "~1.4.0",
+ "jquery": "~2.1.4",
+ "angular-route": "~1.4.0",
+ "angular-show-errors": "~2.4.3",
+ "angular-mocks": "~1.4.1"
+ }
+}
diff --git a/lightcalc/config/blueprints.js b/lightcalc/config/blueprints.js
new file mode 100644
index 0000000..8bd34f2
--- /dev/null
+++ b/lightcalc/config/blueprints.js
@@ -0,0 +1,162 @@
+/**
+ * Blueprint API Configuration
+ * (sails.config.blueprints)
+ *
+ * These settings are for the global configuration of blueprint routes and
+ * request options (which impact the behavior of blueprint actions).
+ *
+ * You may also override any of these settings on a per-controller basis
+ * by defining a '_config' key in your controller defintion, and assigning it
+ * a configuration object with overrides for the settings in this file.
+ * A lot of the configuration options below affect so-called "CRUD methods",
+ * or your controllers' `find`, `create`, `update`, and `destroy` actions.
+ *
+ * It's important to realize that, even if you haven't defined these yourself, as long as
+ * a model exists with the same name as the controller, Sails will respond with built-in CRUD
+ * logic in the form of a JSON API, including support for sort, pagination, and filtering.
+ *
+ * For more information on the blueprint API, check out:
+ * http://sailsjs.org/#!/documentation/reference/blueprint-api
+ *
+ * For more information on the settings in this file, see:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.blueprints.html
+ *
+ */
+
+module.exports.blueprints = {
+
+ /***************************************************************************
+ * *
+ * Action routes speed up the backend development workflow by *
+ * eliminating the need to manually bind routes. When enabled, GET, POST, *
+ * PUT, and DELETE routes will be generated for every one of a controller's *
+ * actions. *
+ * *
+ * If an `index` action exists, additional naked routes will be created for *
+ * it. Finally, all `actions` blueprints support an optional path *
+ * parameter, `id`, for convenience. *
+ * *
+ * `actions` are enabled by default, and can be OK for production-- *
+ * however, if you'd like to continue to use controller/action autorouting *
+ * in a production deployment, you must take great care not to *
+ * inadvertently expose unsafe/unintentional controller logic to GET *
+ * requests. *
+ * *
+ ***************************************************************************/
+
+ // actions: true,
+
+ /***************************************************************************
+ * *
+ * RESTful routes (`sails.config.blueprints.rest`) *
+ * *
+ * REST blueprints are the automatically generated routes Sails uses to *
+ * expose a conventional REST API on top of a controller's `find`, *
+ * `create`, `update`, and `destroy` actions. *
+ * *
+ * For example, a BoatController with `rest` enabled generates the *
+ * following routes: *
+ * ::::::::::::::::::::::::::::::::::::::::::::::::::::::: *
+ * GET /boat -> BoatController.find *
+ * GET /boat/:id -> BoatController.findOne *
+ * POST /boat -> BoatController.create *
+ * PUT /boat/:id -> BoatController.update *
+ * DELETE /boat/:id -> BoatController.destroy *
+ * *
+ * `rest` blueprint routes are enabled by default, and are suitable for use *
+ * in a production scenario, as long you take standard security precautions *
+ * (combine w/ policies, etc.) *
+ * *
+ ***************************************************************************/
+
+ // rest: true,
+
+ /***************************************************************************
+ * *
+ * Shortcut routes are simple helpers to provide access to a *
+ * controller's CRUD methods from your browser's URL bar. When enabled, *
+ * GET, POST, PUT, and DELETE routes will be generated for the *
+ * controller's`find`, `create`, `update`, and `destroy` actions. *
+ * *
+ * `shortcuts` are enabled by default, but should be disabled in *
+ * production. *
+ * *
+ ***************************************************************************/
+
+ // shortcuts: true,
+
+ /***************************************************************************
+ * *
+ * An optional mount path for all blueprint routes on a controller, *
+ * including `rest`, `actions`, and `shortcuts`. This allows you to take *
+ * advantage of blueprint routing, even if you need to namespace your API *
+ * methods. *
+ * *
+ * (NOTE: This only applies to blueprint autoroutes, not manual routes from *
+ * `sails.config.routes`) *
+ * *
+ ***************************************************************************/
+
+ // prefix: '',
+
+ /***************************************************************************
+ * *
+ * An optional mount path for all REST blueprint routes on a controller. *
+ * And it do not include `actions` and `shortcuts` routes. *
+ * This allows you to take advantage of REST blueprint routing, *
+ * even if you need to namespace your RESTful API methods *
+ * *
+ ***************************************************************************/
+
+ // restPrefix: '',
+
+ /***************************************************************************
+ * *
+ * Whether to pluralize controller names in blueprint routes. *
+ * *
+ * (NOTE: This only applies to blueprint autoroutes, not manual routes from *
+ * `sails.config.routes`) *
+ * *
+ * For example, REST blueprints for `FooController` with `pluralize` *
+ * enabled: *
+ * GET /foos/:id? *
+ * POST /foos *
+ * PUT /foos/:id? *
+ * DELETE /foos/:id? *
+ * *
+ ***************************************************************************/
+
+ // pluralize: false,
+
+ /***************************************************************************
+ * *
+ * Whether the blueprint controllers should populate model fetches with *
+ * data from other models which are linked by associations *
+ * *
+ * If you have a lot of data in one-to-many associations, leaving this on *
+ * may result in very heavy api calls *
+ * *
+ ***************************************************************************/
+
+ // populate: true,
+
+ /****************************************************************************
+ * *
+ * Whether to run Model.watch() in the find and findOne blueprint actions. *
+ * Can be overridden on a per-model basis. *
+ * *
+ ****************************************************************************/
+
+ // autoWatch: true,
+
+ /****************************************************************************
+ * *
+ * The default number of records to show in the response from a "find" *
+ * action. Doubles as the default size of populated arrays if populate is *
+ * true. *
+ * *
+ ****************************************************************************/
+
+ // defaultLimit: 30
+
+};
diff --git a/lightcalc/config/bootstrap.js b/lightcalc/config/bootstrap.js
new file mode 100644
index 0000000..96208a1
--- /dev/null
+++ b/lightcalc/config/bootstrap.js
@@ -0,0 +1,17 @@
+/**
+ * Bootstrap
+ * (sails.config.bootstrap)
+ *
+ * An asynchronous bootstrap function that runs before your Sails app gets lifted.
+ * This gives you an opportunity to set up your data model, run jobs, or perform some special logic.
+ *
+ * For more information on bootstrapping your app, check out:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.bootstrap.html
+ */
+
+module.exports.bootstrap = function(cb) {
+
+ // It's very important to trigger this callback method when you are finished
+ // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap)
+ cb();
+};
diff --git a/lightcalc/config/connections.js b/lightcalc/config/connections.js
new file mode 100644
index 0000000..bf9b49a
--- /dev/null
+++ b/lightcalc/config/connections.js
@@ -0,0 +1,94 @@
+/**
+ * Connections
+ * (sails.config.connections)
+ *
+ * `Connections` are like "saved settings" for your adapters. What's the difference between
+ * a connection and an adapter, you might ask? An adapter (e.g. `sails-mysql`) is generic--
+ * it needs some additional information to work (e.g. your database host, password, user, etc.)
+ * A `connection` is that additional information.
+ *
+ * Each model must have a `connection` property (a string) which is references the name of one
+ * of these connections. If it doesn't, the default `connection` configured in `config/models.js`
+ * will be applied. Of course, a connection can (and usually is) shared by multiple models.
+ * .
+ * Note: If you're using version control, you should put your passwords/api keys
+ * in `config/local.js`, environment variables, or use another strategy.
+ * (this is to prevent you inadvertently sensitive credentials up to your repository.)
+ *
+ * For more information on configuration, check out:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.connections.html
+ */
+
+module.exports.connections = {
+
+ /***************************************************************************
+ * *
+ * Local disk storage for DEVELOPMENT ONLY *
+ * *
+ * Installed by default. *
+ * *
+ ***************************************************************************/
+ localDiskDb: {
+ adapter: 'sails-disk'
+ },
+
+ /***************************************************************************
+ * *
+ * MySQL is the world's most popular relational database. *
+ * http://en.wikipedia.org/wiki/MySQL *
+ * *
+ * Run: npm install sails-mysql *
+ * *
+ ***************************************************************************/
+ someMysqlServer: {
+ adapter: 'sails-mysql',
+ host: 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS',
+ user: 'YOUR_MYSQL_USER',
+ password: 'YOUR_MYSQL_PASSWORD',
+ database: 'YOUR_MYSQL_DB'
+ },
+
+ /***************************************************************************
+ * *
+ * MongoDB is the leading NoSQL database. *
+ * http://en.wikipedia.org/wiki/MongoDB *
+ * *
+ * Run: npm install sails-mongo *
+ * *
+ ***************************************************************************/
+ someMongodbServer: {
+ adapter: 'sails-mongo',
+ // host: 'localhost',
+ // port: 27017,
+ // user: 'username',
+ // password: 'password',
+ // database: 'your_mongo_db_name_here'
+ url: process.env.MONGOLAB_URI || 'mongodb://heroku_lwjz5hqq:g0asvb54o1a54vm85r8sqh8lm6@ds043972.mongolab.com:43972/heroku_lwjz5hqq',
+ schema: 'true'
+ },
+
+ /***************************************************************************
+ * *
+ * PostgreSQL is another officially supported relational database. *
+ * http://en.wikipedia.org/wiki/PostgreSQL *
+ * *
+ * Run: npm install sails-postgresql *
+ * *
+ * *
+ ***************************************************************************/
+ somePostgresqlServer: {
+ adapter: 'sails-postgresql',
+ host: 'YOUR_POSTGRES_SERVER_HOSTNAME_OR_IP_ADDRESS',
+ user: 'YOUR_POSTGRES_USER',
+ password: 'YOUR_POSTGRES_PASSWORD',
+ database: 'YOUR_POSTGRES_DB'
+ }
+
+
+ /***************************************************************************
+ * *
+ * More adapters: https://github.com/balderdashy/sails *
+ * *
+ ***************************************************************************/
+
+};
diff --git a/lightcalc/config/cors.js b/lightcalc/config/cors.js
new file mode 100644
index 0000000..9939d58
--- /dev/null
+++ b/lightcalc/config/cors.js
@@ -0,0 +1,78 @@
+/**
+ * Cross-Origin Resource Sharing (CORS) Settings
+ * (sails.config.cors)
+ *
+ * CORS is like a more modern version of JSONP-- it allows your server/API
+ * to successfully respond to requests from client-side JavaScript code
+ * running on some other domain (e.g. google.com)
+ * Unlike JSONP, it works with POST, PUT, and DELETE requests
+ *
+ * For more information on CORS, check out:
+ * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+ *
+ * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis
+ * by adding a "cors" object to the route configuration:
+ *
+ * '/get foo': {
+ * controller: 'foo',
+ * action: 'bar',
+ * cors: {
+ * origin: 'http://foobar.com,https://owlhoot.com'
+ * }
+ * }
+ *
+ * For more information on this configuration file, see:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.cors.html
+ *
+ */
+
+module.exports.cors = {
+
+ /***************************************************************************
+ * *
+ * Allow CORS on all routes by default? If not, you must enable CORS on a *
+ * per-route basis by either adding a "cors" configuration object to the *
+ * route config, or setting "cors:true" in the route config to use the *
+ * default settings below. *
+ * *
+ ***************************************************************************/
+
+ // allRoutes: false,
+
+ /***************************************************************************
+ * *
+ * Which domains which are allowed CORS access? This can be a *
+ * comma-delimited list of hosts (beginning with http:// or https://) or *
+ * "*" to allow all domains CORS access. *
+ * *
+ ***************************************************************************/
+
+ // origin: '*',
+
+ /***************************************************************************
+ * *
+ * Allow cookies to be shared for CORS requests? *
+ * *
+ ***************************************************************************/
+
+ // credentials: true,
+
+ /***************************************************************************
+ * *
+ * Which methods should be allowed for CORS requests? This is only used in *
+ * response to preflight requests (see article linked above for more info) *
+ * *
+ ***************************************************************************/
+
+ // methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD',
+
+ /***************************************************************************
+ * *
+ * Which headers should be allowed for CORS requests? This is only used in *
+ * response to preflight requests. *
+ * *
+ ***************************************************************************/
+
+ // headers: 'content-type'
+
+};
diff --git a/lightcalc/config/csrf.js b/lightcalc/config/csrf.js
new file mode 100644
index 0000000..5667e0b
--- /dev/null
+++ b/lightcalc/config/csrf.js
@@ -0,0 +1,64 @@
+/**
+ * Cross-Site Request Forgery Protection Settings
+ * (sails.config.csrf)
+ *
+ * CSRF tokens are like a tracking chip. While a session tells the server that a user
+ * "is who they say they are", a csrf token tells the server "you are where you say you are".
+ *
+ * When enabled, all non-GET requests to the Sails server must be accompanied by
+ * a special token, identified as the '_csrf' parameter.
+ *
+ * This option protects your Sails app against cross-site request forgery (or CSRF) attacks.
+ * A would-be attacker needs not only a user's session cookie, but also this timestamped,
+ * secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain.
+ *
+ * This allows us to have certainty that our users' requests haven't been hijacked,
+ * and that the requests they're making are intentional and legitimate.
+ *
+ * This token has a short-lived expiration timeline, and must be acquired by either:
+ *
+ * (a) For traditional view-driven web apps:
+ * Fetching it from one of your views, where it may be accessed as
+ * a local variable, e.g.:
+ *
+ *
+ * or (b) For AJAX/Socket-heavy and/or single-page apps:
+ * Sending a GET request to the `/csrfToken` route, where it will be returned
+ * as JSON, e.g.:
+ * { _csrf: 'ajg4JD(JGdajhLJALHDa' }
+ *
+ *
+ * Enabling this option requires managing the token in your front-end app.
+ * For traditional web apps, it's as easy as passing the data from a view into a form action.
+ * In AJAX/Socket-heavy apps, just send a GET request to the /csrfToken route to get a valid token.
+ *
+ * For more information on CSRF, check out:
+ * http://en.wikipedia.org/wiki/Cross-site_request_forgery
+ *
+ * For more information on this configuration file, including info on CSRF + CORS, see:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.csrf.html
+ *
+ */
+
+/****************************************************************************
+* *
+* Enabled CSRF protection for your site? *
+* *
+****************************************************************************/
+
+// module.exports.csrf = false;
+
+/****************************************************************************
+* *
+* You may also specify more fine-grained settings for CSRF, including the *
+* domains which are allowed to request the CSRF token via AJAX. These *
+* settings override the general CORS settings in your config/cors.js file. *
+* *
+****************************************************************************/
+
+// module.exports.csrf = {
+// grantTokenViaAjax: true,
+// origin: ''
+// }
diff --git a/lightcalc/config/env/development.js b/lightcalc/config/env/development.js
new file mode 100644
index 0000000..ced04f4
--- /dev/null
+++ b/lightcalc/config/env/development.js
@@ -0,0 +1,25 @@
+/**
+ * Development environment settings
+ *
+ * This file can include shared settings for a development team,
+ * such as API keys or remote database passwords. If you're using
+ * a version control solution for your Sails app, this file will
+ * be committed to your repository unless you add it to your .gitignore
+ * file. If your repository will be publicly viewable, don't add
+ * any private information to this file!
+ *
+ */
+
+module.exports = {
+
+ /***************************************************************************
+ * Set the default database connection for models in the development *
+ * environment (see config/connections.js and config/models.js ) *
+ ***************************************************************************/
+
+ models: {
+ migrate: 'safe',
+ connection: 'localDiskDb'
+ }
+
+};
diff --git a/lightcalc/config/env/production.js b/lightcalc/config/env/production.js
new file mode 100644
index 0000000..97bbd35
--- /dev/null
+++ b/lightcalc/config/env/production.js
@@ -0,0 +1,39 @@
+/**
+ * Production environment settings
+ *
+ * This file can include shared settings for a production environment,
+ * such as API keys or remote database passwords. If you're using
+ * a version control solution for your Sails app, this file will
+ * be committed to your repository unless you add it to your .gitignore
+ * file. If your repository will be publicly viewable, don't add
+ * any private information to this file!
+ *
+ */
+
+module.exports = {
+
+ /***************************************************************************
+ * Set the default database connection for models in the production *
+ * environment (see config/connections.js and config/models.js ) *
+ ***************************************************************************/
+
+ models: {
+ migrate: 'safe',
+ connection: 'someMongodbServer'
+ },
+
+ /***************************************************************************
+ * Set the port in the production environment to 80 *
+ ***************************************************************************/
+
+ port: process.env.PORT || 5000,
+
+ /***************************************************************************
+ * Set the log level in production environment to "silent" *
+ ***************************************************************************/
+
+ log: {
+ level: "silent"
+ }
+
+};
diff --git a/lightcalc/config/globals.js b/lightcalc/config/globals.js
new file mode 100644
index 0000000..c1819c3
--- /dev/null
+++ b/lightcalc/config/globals.js
@@ -0,0 +1,63 @@
+/**
+ * Global Variable Configuration
+ * (sails.config.globals)
+ *
+ * Configure which global variables which will be exposed
+ * automatically by Sails.
+ *
+ * For more information on configuration, check out:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.globals.html
+ */
+module.exports.globals = {
+
+ /****************************************************************************
+ * *
+ * Expose the lodash installed in Sails core as a global variable. If this *
+ * is disabled, like any other node module you can always run npm install *
+ * lodash --save, then var _ = require('lodash') at the top of any file. *
+ * *
+ ****************************************************************************/
+
+ // _: true,
+
+ /****************************************************************************
+ * *
+ * Expose the async installed in Sails core as a global variable. If this is *
+ * disabled, like any other node module you can always run npm install async *
+ * --save, then var async = require('async') at the top of any file. *
+ * *
+ ****************************************************************************/
+
+ // async: true,
+
+ /****************************************************************************
+ * *
+ * Expose the sails instance representing your app. If this is disabled, you *
+ * can still get access via req._sails. *
+ * *
+ ****************************************************************************/
+
+ // sails: true,
+
+ /****************************************************************************
+ * *
+ * Expose each of your app's services as global variables (using their *
+ * "globalId"). E.g. a service defined in api/models/NaturalLanguage.js *
+ * would have a globalId of NaturalLanguage by default. If this is disabled, *
+ * you can still access your services via sails.services.* *
+ * *
+ ****************************************************************************/
+
+ // services: true,
+
+ /****************************************************************************
+ * *
+ * Expose each of your app's models as global variables (using their *
+ * "globalId"). E.g. a model defined in api/models/User.js would have a *
+ * globalId of User by default. If this is disabled, you can still access *
+ * your models via sails.models.*. *
+ * *
+ ****************************************************************************/
+
+ // models: true
+};
diff --git a/lightcalc/config/http.js b/lightcalc/config/http.js
new file mode 100644
index 0000000..6725416
--- /dev/null
+++ b/lightcalc/config/http.js
@@ -0,0 +1,87 @@
+/**
+ * HTTP Server Settings
+ * (sails.config.http)
+ *
+ * Configuration for the underlying HTTP server in Sails.
+ * Only applies to HTTP requests (not WebSockets)
+ *
+ * For more information on configuration, check out:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.http.html
+ */
+
+module.exports.http = {
+
+ /****************************************************************************
+ * *
+ * Express middleware to use for every Sails request. To add custom *
+ * middleware to the mix, add a function to the middleware config object and *
+ * add its key to the "order" array. The $custom key is reserved for *
+ * backwards-compatibility with Sails v0.9.x apps that use the *
+ * `customMiddleware` config option. *
+ * *
+ ****************************************************************************/
+
+ // middleware: {
+
+ /***************************************************************************
+ * *
+ * The order in which middleware should be run for HTTP request. (the Sails *
+ * router is invoked by the "router" middleware below.) *
+ * *
+ ***************************************************************************/
+
+ // order: [
+ // 'startRequestTimer',
+ // 'cookieParser',
+ // 'session',
+ // 'myRequestLogger',
+ // 'bodyParser',
+ // 'handleBodyParserError',
+ // 'compress',
+ // 'methodOverride',
+ // 'poweredBy',
+ // '$custom',
+ // 'router',
+ // 'www',
+ // 'favicon',
+ // '404',
+ // '500'
+ // ],
+
+ /****************************************************************************
+ * *
+ * Example custom middleware; logs each request to the console. *
+ * *
+ ****************************************************************************/
+
+ // myRequestLogger: function (req, res, next) {
+ // console.log("Requested :: ", req.method, req.url);
+ // return next();
+ // }
+
+
+ /***************************************************************************
+ * *
+ * The body parser that will handle incoming multipart HTTP requests. By *
+ * default as of v0.10, Sails uses *
+ * [skipper](http://github.com/balderdashy/skipper). See *
+ * http://www.senchalabs.org/connect/multipart.html for other options. *
+ * *
+ ***************************************************************************/
+
+ // bodyParser: require('skipper')
+
+ // },
+
+ /***************************************************************************
+ * *
+ * The number of seconds to cache flat files on disk being served by *
+ * Express static middleware (by default, these files are in `.tmp/public`) *
+ * *
+ * The HTTP static cache is only active in a 'production' environment, *
+ * since that's the only time Express will cache flat-files. *
+ * *
+ ***************************************************************************/
+
+ // cache: 31557600000
+};
diff --git a/lightcalc/config/i18n.js b/lightcalc/config/i18n.js
new file mode 100644
index 0000000..facf707
--- /dev/null
+++ b/lightcalc/config/i18n.js
@@ -0,0 +1,57 @@
+/**
+ * Internationalization / Localization Settings
+ * (sails.config.i18n)
+ *
+ * If your app will touch people from all over the world, i18n (or internationalization)
+ * may be an important part of your international strategy.
+ *
+ *
+ * For more informationom i18n in Sails, check out:
+ * http://sailsjs.org/#!/documentation/concepts/Internationalization
+ *
+ * For a complete list of i18n options, see:
+ * https://github.com/mashpie/i18n-node#list-of-configuration-options
+ *
+ *
+ */
+
+module.exports.i18n = {
+
+ /***************************************************************************
+ * *
+ * Which locales are supported? *
+ * *
+ ***************************************************************************/
+
+ // locales: ['en', 'es', 'fr', 'de'],
+
+ /****************************************************************************
+ * *
+ * What is the default locale for the site? Note that this setting will be *
+ * overridden for any request that sends an "Accept-Language" header (i.e. *
+ * most browsers), but it's still useful if you need to localize the *
+ * response for requests made by non-browser clients (e.g. cURL). *
+ * *
+ ****************************************************************************/
+
+ // defaultLocale: 'en',
+
+ /****************************************************************************
+ * *
+ * Automatically add new keys to locale (translation) files when they are *
+ * encountered during a request? *
+ * *
+ ****************************************************************************/
+
+ // updateFiles: false,
+
+ /****************************************************************************
+ * *
+ * Path (relative to app root) of directory to store locale (translation) *
+ * files in. *
+ * *
+ ****************************************************************************/
+
+ // localesDirectory: '/config/locales'
+
+};
diff --git a/lightcalc/config/locales/_README.md b/lightcalc/config/locales/_README.md
new file mode 100644
index 0000000..5f89b15
--- /dev/null
+++ b/lightcalc/config/locales/_README.md
@@ -0,0 +1,28 @@
+# Internationalization / Localization Settings
+
+> Also see the official docs on internationalization/localization:
+> http://links.sailsjs.org/docs/config/locales
+
+## Locales
+All locale files live under `config/locales`. Here is where you can add translations
+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 request headers.
+
+Here is an example locale stringfile for the Spanish language (`config/locales/es.json`):
+```json
+{
+ "Hello!": "Hola!",
+ "Hello %s, how are you today?": "¿Hola %s, como estas?",
+}
+```
+## Usage
+Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions.
+Remember that the keys are case sensitive and require exact key matches, e.g.
+
+```ejs
+
<%= __('Welcome to PencilPals!') %>
+
<%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>
+
<%= i18n('That\'s right-- you can use either i18n() or __()') %>
+```
+
+## Configuration
+Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales.
diff --git a/lightcalc/config/locales/de.json b/lightcalc/config/locales/de.json
new file mode 100644
index 0000000..12f23d9
--- /dev/null
+++ b/lightcalc/config/locales/de.json
@@ -0,0 +1,4 @@
+{
+ "Welcome": "Willkommen",
+ "A brand new app.": "Eine neue App."
+}
diff --git a/lightcalc/config/locales/en.json b/lightcalc/config/locales/en.json
new file mode 100644
index 0000000..6eeeb33
--- /dev/null
+++ b/lightcalc/config/locales/en.json
@@ -0,0 +1,4 @@
+{
+ "Welcome": "Welcome",
+ "A brand new app.": "A brand new app."
+}
diff --git a/lightcalc/config/locales/es.json b/lightcalc/config/locales/es.json
new file mode 100644
index 0000000..91f8248
--- /dev/null
+++ b/lightcalc/config/locales/es.json
@@ -0,0 +1,4 @@
+{
+ "Welcome": "Bienvenido",
+ "A brand new app.": "Una aplicación de la nueva marca."
+}
diff --git a/lightcalc/config/locales/fr.json b/lightcalc/config/locales/fr.json
new file mode 100644
index 0000000..972935c
--- /dev/null
+++ b/lightcalc/config/locales/fr.json
@@ -0,0 +1,4 @@
+{
+ "Welcome": "Bienvenue",
+ "A brand new app.": "Une toute nouvelle application."
+}
diff --git a/lightcalc/config/log.js b/lightcalc/config/log.js
new file mode 100644
index 0000000..e192529
--- /dev/null
+++ b/lightcalc/config/log.js
@@ -0,0 +1,29 @@
+/**
+ * Built-in Log Configuration
+ * (sails.config.log)
+ *
+ * Configure the log level for your app, as well as the transport
+ * (Underneath the covers, Sails uses Winston for logging, which
+ * allows for some pretty neat custom transports/adapters for log messages)
+ *
+ * For more information on the Sails logger, check out:
+ * http://sailsjs.org/#!/documentation/concepts/Logging
+ */
+
+module.exports.log = {
+
+ /***************************************************************************
+ * *
+ * Valid `level` configs: i.e. the minimum log level to capture with *
+ * sails.log.*() *
+ * *
+ * The order of precedence for log levels from lowest to highest is: *
+ * silly, verbose, info, debug, warn, error *
+ * *
+ * You may also set the level to "silent" to suppress all logs. *
+ * *
+ ***************************************************************************/
+
+ // level: 'info'
+
+};
diff --git a/lightcalc/config/models.js b/lightcalc/config/models.js
new file mode 100644
index 0000000..49ac9ee
--- /dev/null
+++ b/lightcalc/config/models.js
@@ -0,0 +1,32 @@
+/**
+ * Default model configuration
+ * (sails.config.models)
+ *
+ * Unless you override them, the following properties will be included
+ * in each of your models.
+ *
+ * For more info on Sails models, see:
+ * http://sailsjs.org/#!/documentation/concepts/ORM
+ */
+
+module.exports.models = {
+
+ /***************************************************************************
+ * *
+ * Your app's default connection. i.e. the name of one of your app's *
+ * connections (see `config/connections.js`) *
+ * *
+ ***************************************************************************/
+ // connection: 'localDiskDb',
+
+ /***************************************************************************
+ * *
+ * How and whether Sails will attempt to automatically rebuild the *
+ * tables/collections/etc. in your schema. *
+ * *
+ * See http://sailsjs.org/#!/documentation/concepts/ORM/model-settings.html *
+ * *
+ ***************************************************************************/
+ // migrate: 'alter'
+
+};
diff --git a/lightcalc/config/policies.js b/lightcalc/config/policies.js
new file mode 100644
index 0000000..1785d4e
--- /dev/null
+++ b/lightcalc/config/policies.js
@@ -0,0 +1,51 @@
+/**
+ * Policy Mappings
+ * (sails.config.policies)
+ *
+ * Policies are simple functions which run **before** your controllers.
+ * You can apply one or more policies to a given controller, or protect
+ * its actions individually.
+ *
+ * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed
+ * below by its filename, minus the extension, (e.g. "authenticated")
+ *
+ * For more information on how policies work, see:
+ * http://sailsjs.org/#!/documentation/concepts/Policies
+ *
+ * For more information on configuring policies, check out:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.policies.html
+ */
+
+
+module.exports.policies = {
+
+ /***************************************************************************
+ * *
+ * Default policy for all controllers and actions (`true` allows public *
+ * access) *
+ * *
+ ***************************************************************************/
+
+ // '*': true,
+
+ /***************************************************************************
+ * *
+ * Here's an example of mapping some policies to run before a controller *
+ * and its actions *
+ * *
+ ***************************************************************************/
+ // RabbitController: {
+
+ // Apply the `false` policy as the default for all of RabbitController's actions
+ // (`false` prevents all access, which ensures that nothing bad happens to our rabbits)
+ // '*': false,
+
+ // For the action `nurture`, apply the 'isRabbitMother' policy
+ // (this overrides `false` above)
+ // nurture : 'isRabbitMother',
+
+ // Apply the `isNiceToAnimals` AND `hasRabbitFood` policies
+ // before letting any users feed our rabbits
+ // feed : ['isNiceToAnimals', 'hasRabbitFood']
+ // }
+};
diff --git a/lightcalc/config/routes.js b/lightcalc/config/routes.js
new file mode 100644
index 0000000..a14c9cb
--- /dev/null
+++ b/lightcalc/config/routes.js
@@ -0,0 +1,49 @@
+/**
+ * Route Mappings
+ * (sails.config.routes)
+ *
+ * Your routes map URLs to views and controllers.
+ *
+ * If Sails receives a URL that doesn't match any of the routes below,
+ * it will check for matching files (images, scripts, stylesheets, etc.)
+ * in your assets directory. e.g. `http://localhost:1337/images/foo.jpg`
+ * might match an image file: `/assets/images/foo.jpg`
+ *
+ * Finally, if those don't match either, the default 404 handler is triggered.
+ * See `api/responses/notFound.js` to adjust your app's 404 logic.
+ *
+ * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies
+ * flat files from `assets` to `.tmp/public`. This allows you to do things like compile LESS or
+ * CoffeeScript for the front-end.
+ *
+ * For more information on configuring custom routes, check out:
+ * http://sailsjs.org/#!/documentation/concepts/Routes/RouteTargetSyntax.html
+ */
+
+module.exports.routes = {
+
+ /***************************************************************************
+ * *
+ * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, *
+ * etc. depending on your default view engine) your home page. *
+ * *
+ * (Alternatively, remove this and add an `index.html` file in your *
+ * `assets` directory) *
+ * *
+ ***************************************************************************/
+
+ '/' : {
+ controller : 'home'
+ }
+
+ /***************************************************************************
+ * *
+ * Custom routes here... *
+ * *
+ * If a request to a URL doesn't match any of the custom routes above, it *
+ * is matched against Sails route blueprints. See `config/blueprints.js` *
+ * for configuration options and examples. *
+ * *
+ ***************************************************************************/
+
+};
diff --git a/lightcalc/config/session.js b/lightcalc/config/session.js
new file mode 100644
index 0000000..4ea1e63
--- /dev/null
+++ b/lightcalc/config/session.js
@@ -0,0 +1,91 @@
+/**
+ * Session Configuration
+ * (sails.config.session)
+ *
+ * Sails session integration leans heavily on the great work already done by
+ * Express, but also unifies Socket.io with the Connect session store. It uses
+ * Connect's cookie parser to normalize configuration differences between Express
+ * and Socket.io and hooks into Sails' middleware interpreter to allow you to access
+ * and auto-save to `req.session` with Socket.io the same way you would with Express.
+ *
+ * For more information on configuring the session, check out:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.session.html
+ */
+
+module.exports.session = {
+
+ /***************************************************************************
+ * *
+ * Session secret is automatically generated when your new app is created *
+ * Replace at your own risk in production-- you will invalidate the cookies *
+ * of your users, forcing them to log in again. *
+ * *
+ ***************************************************************************/
+ secret: 'e3995e183a0e6b370e0ce948721abd4f',
+
+
+ /***************************************************************************
+ * *
+ * Set the session cookie expire time The maxAge is set by milliseconds, *
+ * the example below is for 24 hours *
+ * *
+ ***************************************************************************/
+
+ // cookie: {
+ // maxAge: 24 * 60 * 60 * 1000
+ // },
+
+ /***************************************************************************
+ * *
+ * In production, uncomment the following lines to set up a shared redis *
+ * session store that can be shared across multiple Sails.js servers *
+ ***************************************************************************/
+
+ // adapter: 'redis',
+
+ /***************************************************************************
+ * *
+ * The following values are optional, if no options are set a redis *
+ * instance running on localhost is expected. Read more about options at: *
+ * https://github.com/visionmedia/connect-redis *
+ * *
+ * *
+ ***************************************************************************/
+
+ // host: 'localhost',
+ // port: 6379,
+ // ttl: ,
+ // db: 0,
+ // pass: ,
+ // prefix: 'sess:',
+
+
+ /***************************************************************************
+ * *
+ * Uncomment the following lines to use your Mongo adapter as a session *
+ * store *
+ * *
+ ***************************************************************************/
+
+ // adapter: 'mongo',
+ // host: 'localhost',
+ // port: 27017,
+ // db: 'sails',
+ // collection: 'sessions',
+
+ /***************************************************************************
+ * *
+ * Optional Values: *
+ * *
+ * # Note: url will override other connection settings url: *
+ * 'mongodb://user:pass@host:port/database/collection', *
+ * *
+ ***************************************************************************/
+
+ // username: '',
+ // password: '',
+ // auto_reconnect: false,
+ // ssl: false,
+ // stringify: true
+
+};
diff --git a/lightcalc/config/sockets.js b/lightcalc/config/sockets.js
new file mode 100644
index 0000000..f23b925
--- /dev/null
+++ b/lightcalc/config/sockets.js
@@ -0,0 +1,141 @@
+/**
+ * WebSocket Server Settings
+ * (sails.config.sockets)
+ *
+ * These settings provide transparent access to the options for Sails'
+ * encapsulated WebSocket server, as well as some additional Sails-specific
+ * configuration layered on top.
+ *
+ * For more information on sockets configuration, including advanced config options, see:
+ * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.sockets.html
+ */
+
+module.exports.sockets = {
+
+
+ /***************************************************************************
+ * *
+ * Node.js (and consequently Sails.js) apps scale horizontally. It's a *
+ * powerful, efficient approach, but it involves a tiny bit of planning. At *
+ * scale, you'll want to be able to copy your app onto multiple Sails.js *
+ * servers and throw them behind a load balancer. *
+ * *
+ * One of the big challenges of scaling an application is that these sorts *
+ * of clustered deployments cannot share memory, since they are on *
+ * physically different machines. On top of that, there is no guarantee *
+ * that a user will "stick" with the same server between requests (whether *
+ * HTTP or sockets), since the load balancer will route each request to the *
+ * Sails server with the most available resources. However that means that *
+ * all room/pubsub/socket processing and shared memory has to be offloaded *
+ * to a shared, remote messaging queue (usually Redis) *
+ * *
+ * Luckily, Socket.io (and consequently Sails.js) apps support Redis for *
+ * sockets by default. To enable a remote redis pubsub server, uncomment *
+ * the config below. *
+ * *
+ * Worth mentioning is that, if `adapter` config is `redis`, but host/port *
+ * is left unset, Sails will try to connect to redis running on localhost *
+ * via port 6379 *
+ * *
+ ***************************************************************************/
+ // adapter: 'memory',
+
+ //
+ // -OR-
+ //
+
+ // adapter: 'redis',
+ // host: '127.0.0.1',
+ // port: 6379,
+ // db: 'sails',
+ // pass: '',
+
+
+
+ /***************************************************************************
+ * *
+ * Whether to expose a 'get /__getcookie' route with CORS support that sets *
+ * a cookie (this is used by the sails.io.js socket client to get access to *
+ * a 3rd party cookie and to enable sessions). *
+ * *
+ * Warning: Currently in this scenario, CORS settings apply to interpreted *
+ * requests sent via a socket.io connection that used this cookie to *
+ * connect, even for non-browser clients! (e.g. iOS apps, toasters, node.js *
+ * unit tests) *
+ * *
+ ***************************************************************************/
+
+ // grant3rdPartyCookie: true,
+
+
+
+ /***************************************************************************
+ * *
+ * `beforeConnect` *
+ * *
+ * This custom beforeConnect function will be run each time BEFORE a new *
+ * socket is allowed to connect, when the initial socket.io handshake is *
+ * performed with the server. *
+ * *
+ * By default, when a socket tries to connect, Sails allows it, every time. *
+ * (much in the same way any HTTP request is allowed to reach your routes. *
+ * If no valid cookie was sent, a temporary session will be created for the *
+ * connecting socket. *
+ * *
+ * If the cookie sent as part of the connection request doesn't match any *
+ * known user session, a new user session is created for it. *
+ * *
+ * In most cases, the user would already have a cookie since they loaded *
+ * the socket.io client and the initial HTML page you're building. *
+ * *
+ * However, in the case of cross-domain requests, it is possible to receive *
+ * a connection upgrade request WITHOUT A COOKIE (for certain transports) *
+ * In this case, there is no way to keep track of the requesting user *
+ * between requests, since there is no identifying information to link *
+ * him/her with a session. The sails.io.js client solves this by connecting *
+ * to a CORS/jsonp endpoint first to get a 3rd party cookie(fortunately this*
+ * works, even in Safari), then opening the connection. *
+ * *
+ * You can also pass along a ?cookie query parameter to the upgrade url, *
+ * which Sails will use in the absence of a proper cookie e.g. (when *
+ * connecting from the client): *
+ * io.sails.connect('http://localhost:1337?cookie=smokeybear') *
+ * *
+ * Finally note that the user's cookie is NOT (and will never be) accessible*
+ * from client-side javascript. Using HTTP-only cookies is crucial for your *
+ * app's security. *
+ * *
+ ***************************************************************************/
+ // beforeConnect: function(handshake, cb) {
+ // // `true` allows the connection
+ // return cb(null, true);
+ //
+ // // (`false` would reject the connection)
+ // },
+
+
+ /***************************************************************************
+ * *
+ * `afterDisconnect` *
+ * *
+ * This custom afterDisconnect function will be run each time a socket *
+ * disconnects *
+ * *
+ ***************************************************************************/
+ // afterDisconnect: function(session, socket, cb) {
+ // // By default: do nothing.
+ // return cb();
+ // },
+
+ /***************************************************************************
+ * *
+ * `transports` *
+ * *
+ * A array of allowed transport methods which the clients will try to use. *
+ * On server environments that don't support sticky sessions, the "polling" *
+ * transport should be disabled. *
+ * *
+ ***************************************************************************/
+ // transports: ["polling", "websocket"]
+
+};
diff --git a/lightcalc/config/views.js b/lightcalc/config/views.js
new file mode 100644
index 0000000..5b6496f
--- /dev/null
+++ b/lightcalc/config/views.js
@@ -0,0 +1,95 @@
+/**
+ * View Engine Configuration
+ * (sails.config.views)
+ *
+ * Server-sent views are a classic and effective way to get your app up
+ * and running. Views are normally served from controllers. Below, you can
+ * configure your templating language/framework of choice and configure
+ * Sails' layout support.
+ *
+ * For more information on views and layouts, check out:
+ * http://sailsjs.org/#!/documentation/concepts/Views
+ */
+
+module.exports.views = {
+
+ /****************************************************************************
+ * *
+ * View engine (aka template language) to use for your app's *server-side* *
+ * views *
+ * *
+ * Sails+Express supports all view engines which implement TJ Holowaychuk's *
+ * `consolidate.js`, including, but not limited to: *
+ * *
+ * ejs, jade, handlebars, mustache underscore, hogan, haml, haml-coffee, *
+ * dust atpl, eco, ect, jazz, jqtpl, JUST, liquor, QEJS, swig, templayed, *
+ * toffee, walrus, & whiskers *
+ * *
+ * For more options, check out the docs: *
+ * https://github.com/balderdashy/sails-wiki/blob/0.9/config.views.md#engine *
+ * *
+ ****************************************************************************/
+
+ engine: 'ejs',
+
+
+ /****************************************************************************
+ * *
+ * Layouts are simply top-level HTML templates you can use as wrappers for *
+ * your server-side views. If you're using ejs or jade, you can take *
+ * advantage of Sails' built-in `layout` support. *
+ * *
+ * When using a layout, when one of your views is served, it is injected *
+ * into the `body` partial defined in the layout. This lets you reuse header *
+ * and footer logic between views. *
+ * *
+ * NOTE: Layout support is only implemented for the `ejs` view engine! *
+ * For most other engines, it is not necessary, since they implement *
+ * partials/layouts themselves. In those cases, this config will be *
+ * silently ignored. *
+ * *
+ * The `layout` setting may be set to one of the following: *
+ * *
+ * If `false`, layouts will be disabled. Otherwise, if a string is *
+ * specified, it will be interpreted as the relative path to your layout *
+ * file from `views/` folder. (the file extension, ".ejs", should be *
+ * omitted) *
+ * *
+ ****************************************************************************/
+
+ /****************************************************************************
+ * *
+ * Using Multiple Layouts *
+ * *
+ * If you're using the default `ejs` or `handlebars` Sails supports the use *
+ * of multiple `layout` files. To take advantage of this, before rendering a *
+ * view, override the `layout` local in your controller by setting *
+ * `res.locals.layout`. (this is handy if you parts of your app's UI look *
+ * completely different from each other) *
+ * *
+ * e.g. your default might be *
+ * layout: 'layouts/public' *
+ * *
+ * But you might override that in some of your controllers with: *
+ * layout: 'layouts/internal' *
+ * *
+ ****************************************************************************/
+
+ layout: 'layout',
+
+ /****************************************************************************
+ * *
+ * Partials are simply top-level snippets you can leverage to reuse template *
+ * for your server-side views. If you're using handlebars, you can take *
+ * advantage of Sails' built-in `partials` support. *
+ * *
+ * If `false` or empty partials will be located in the same folder as views. *
+ * Otherwise, if a string is specified, it will be interpreted as the *
+ * relative path to your partial files from `views/` folder. *
+ * *
+ ****************************************************************************/
+
+ partials: false
+
+
+};
\ No newline at end of file
diff --git a/lightcalc/karma.conf.js b/lightcalc/karma.conf.js
new file mode 100644
index 0000000..caa76fb
--- /dev/null
+++ b/lightcalc/karma.conf.js
@@ -0,0 +1,95 @@
+// Karma configuration
+// Generated on Wed Jun 17 2015 00:51:08 GMT-0300 (BRT)
+
+module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['mocha', 'chai'],
+
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'assets/bower_components/jquery/dist/jquery.js',
+ 'assets/bower_components/angular/angular.js',
+ 'assets/bower_components/angular-mocks/angular-mocks.js',
+ 'assets/bower_components/angular-route/angular-route.js',
+ 'assets/bower_components/angular-resource/angular-resource.js',
+ 'assets/bower_components/angular-messages/angular-messages.js',
+ 'assets/bower_components/angular-show-errors/src/showErrors.js',
+ 'assets/js/app.js',
+ 'assets/js/routes.js',
+ 'assets/js/controllers/*.js',
+ 'assets/js/directives/*.js',
+ 'assets/js/services/*.js',
+ 'assets/js/filters/*.js',
+ 'assets/templates/**/*.html',
+ 'assets/js/tests/**/*.test.js'
+ ],
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ plugins : [
+ 'karma-chrome-launcher',
+ 'karma-firefox-launcher',
+ 'karma-mocha',
+ 'karma-chai',
+ 'karma-ng-html2js-preprocessor'
+ ],
+
+ preprocessors: {
+ "assets/templates/**/*.html": ["ng-html2js"]
+ },
+
+ ngHtml2JsPreprocessor: {
+ // the name of the Angular module to create
+ moduleName: "MyTemplates"
+ },
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['Chrome'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false
+ });
+};
diff --git a/lightcalc/package.json b/lightcalc/package.json
new file mode 100644
index 0000000..3a92f6e
--- /dev/null
+++ b/lightcalc/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "lightcalc",
+ "private": true,
+ "version": "0.0.0",
+ "description": "a Sails application",
+ "keywords": [],
+ "engines": {
+ "node": "0.12.4",
+ "npm": "2.11.1"
+ },
+ "dependencies": {
+ "ejs": "~0.8.4",
+ "forever": "^0.14.1",
+ "grunt": "0.4.2",
+ "grunt-contrib-clean": "~0.5.0",
+ "grunt-contrib-coffee": "~0.10.1",
+ "grunt-contrib-concat": "~0.3.0",
+ "grunt-contrib-copy": "~0.5.0",
+ "grunt-contrib-cssmin": "~0.9.0",
+ "grunt-contrib-jst": "~0.6.0",
+ "grunt-contrib-less": "0.11.1",
+ "grunt-contrib-uglify": "~0.4.0",
+ "grunt-contrib-watch": "~0.5.3",
+ "grunt-sails-linker": "~0.9.5",
+ "grunt-sync": "~0.0.4",
+ "include-all": "~0.1.3",
+ "rc": "~0.5.0",
+ "sails": "~0.11.0",
+ "sails-disk": "~0.10.0",
+ "sails-mongo": "^0.11.2"
+ },
+ "scripts": {
+ "debug": "node debug app.js",
+ "start": "node app.js",
+ "postinstall": "npm install bower -g && bower install"
+ },
+ "main": "app.js",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/hugo/lightcalc.git"
+ },
+ "author": "hugo",
+ "license": "",
+ "devDependencies": {
+ "chai": "^3.0.0",
+ "karma": "^0.12.36",
+ "karma-chai": "^0.1.0",
+ "karma-chrome-launcher": "^0.1.12",
+ "karma-mocha": "^0.1.10",
+ "karma-ng-html2js-preprocessor": "^0.1.2",
+ "mocha": "^2.2.5",
+ "supertest": "^1.0.1"
+ }
+}
diff --git a/lightcalc/tasks/README.md b/lightcalc/tasks/README.md
new file mode 100644
index 0000000..78d2f51
--- /dev/null
+++ b/lightcalc/tasks/README.md
@@ -0,0 +1,54 @@
+# About the `tasks` folder
+
+The `tasks` directory is a suite of Grunt tasks and their configurations, bundled for your convenience. The Grunt integration is mainly useful for bundling front-end assets, (like stylesheets, scripts, & markup templates) but it can also be used to run all kinds of development tasks, from browserify compilation to database migrations.
+
+If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, read on!
+
+
+### How does this work?
+
+The asset pipeline bundled in Sails is a set of Grunt tasks configured with conventional defaults designed to make your project more consistent and productive.
+
+The entire front-end asset workflow in Sails is completely customizable-- while it provides some suggestions out of the box, Sails makes no pretense that it can anticipate all of the needs you'll encounter building the browser-based/front-end portion of your application. Who's to say you're even building an app for a browser?
+
+
+
+### What tasks does Sails run automatically?
+
+Sails runs some of these tasks (the ones in the `tasks/register` folder) automatically when you run certain commands.
+
+###### `sails lift`
+
+Runs the `default` task (`tasks/register/default.js`).
+
+###### `sails lift --prod`
+
+Runs the `prod` task (`tasks/register/prod.js`).
+
+###### `sails www`
+
+Runs the `build` task (`tasks/register/build.js`).
+
+###### `sails www --prod` (production)
+
+Runs the `buildProd` task (`tasks/register/buildProd.js`).
+
+
+### Can I customize this for SASS, Angular, client-side Jade templates, etc?
+
+You can modify, omit, or replace any of these Grunt tasks to fit your requirements. You can also add your own Grunt tasks- just add a `someTask.js` file in the `grunt/config` directory to configure the new task, then register it with the appropriate parent task(s) (see files in `grunt/register/*.js`).
+
+
+### Do I have to use Grunt?
+
+Nope! To disable Grunt integration in Sails, just delete your Gruntfile or disable the Grunt hook.
+
+
+### What if I'm not building a web frontend?
+
+That's ok! A core tenant of Sails is client-agnosticism-- it's especially designed for building APIs used by all sorts of clients; native Android/iOS/Cordova, serverside SDKs, etc.
+
+You can completely disable Grunt by following the instructions above.
+
+If you still want to use Grunt for other purposes, but don't want any of the default web front-end stuff, just delete your project's `assets` folder and remove the front-end oriented tasks from the `grunt/register` and `grunt/config` folders. You can also run `sails new myCoolApi --no-frontend` to omit the `assets` folder and front-end-oriented Grunt tasks for future projects. You can also replace your `sails-generate-frontend` module with alternative community generators, or create your own. This allows `sails new` to create the boilerplate for native iOS apps, Android apps, Cordova apps, SteroidsJS apps, etc.
+
diff --git a/lightcalc/tasks/config/clean.js b/lightcalc/tasks/config/clean.js
new file mode 100644
index 0000000..7682afe
--- /dev/null
+++ b/lightcalc/tasks/config/clean.js
@@ -0,0 +1,21 @@
+/**
+ * Clean files and folders.
+ *
+ * ---------------------------------------------------------------
+ *
+ * This grunt task is configured to clean out the contents in the .tmp/public of your
+ * sails project.
+ *
+ * For usage docs see:
+ * https://github.com/gruntjs/grunt-contrib-clean
+ */
+module.exports = function(grunt) {
+
+ grunt.config.set('clean', {
+ dev: ['.tmp/public/**', '!.tmp/public/bower_components/**'],
+ build: ['www'],
+ bower: ['.tmp/public/bower_components/**']
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-clean');
+};
diff --git a/lightcalc/tasks/config/coffee.js b/lightcalc/tasks/config/coffee.js
new file mode 100644
index 0000000..7314a3f
--- /dev/null
+++ b/lightcalc/tasks/config/coffee.js
@@ -0,0 +1,38 @@
+/**
+ * Compile CoffeeScript files to JavaScript.
+ *
+ * ---------------------------------------------------------------
+ *
+ * Compiles coffeeScript files from `assest/js` into Javascript and places them into
+ * `.tmp/public/js` directory.
+ *
+ * For usage docs see:
+ * https://github.com/gruntjs/grunt-contrib-coffee
+ */
+module.exports = function(grunt) {
+
+ grunt.config.set('coffee', {
+ dev: {
+ options: {
+ bare: true,
+ sourceMap: true,
+ sourceRoot: './'
+ },
+ files: [{
+ expand: true,
+ cwd: 'assets/js/',
+ src: ['**/*.coffee'],
+ dest: '.tmp/public/js/',
+ ext: '.js'
+ }, {
+ expand: true,
+ cwd: 'assets/js/',
+ src: ['**/*.coffee'],
+ dest: '.tmp/public/js/',
+ ext: '.js'
+ }]
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-coffee');
+};
diff --git a/lightcalc/tasks/config/concat.js b/lightcalc/tasks/config/concat.js
new file mode 100644
index 0000000..4e18379
--- /dev/null
+++ b/lightcalc/tasks/config/concat.js
@@ -0,0 +1,27 @@
+/**
+ * Concatenate files.
+ *
+ * ---------------------------------------------------------------
+ *
+ * Concatenates files javascript and css from a defined array. Creates concatenated files in
+ * .tmp/public/contact directory
+ * [concat](https://github.com/gruntjs/grunt-contrib-concat)
+ *
+ * For usage docs see:
+ * https://github.com/gruntjs/grunt-contrib-concat
+ */
+module.exports = function(grunt) {
+
+ grunt.config.set('concat', {
+ js: {
+ src: require('../pipeline').jsFilesToInject,
+ dest: '.tmp/public/concat/production.js'
+ },
+ css: {
+ src: require('../pipeline').cssFilesToInject,
+ dest: '.tmp/public/concat/production.css'
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-concat');
+};
diff --git a/lightcalc/tasks/config/copy.js b/lightcalc/tasks/config/copy.js
new file mode 100644
index 0000000..76a2079
--- /dev/null
+++ b/lightcalc/tasks/config/copy.js
@@ -0,0 +1,38 @@
+/**
+ * Copy files and folders.
+ *
+ * ---------------------------------------------------------------
+ *
+ * # dev task config
+ * Copies all directories and files, exept coffescript and less fiels, from the sails
+ * assets folder into the .tmp/public directory.
+ *
+ * # build task config
+ * Copies all directories nd files from the .tmp/public directory into a www directory.
+ *
+ * For usage docs see:
+ * https://github.com/gruntjs/grunt-contrib-copy
+ */
+module.exports = function(grunt) {
+
+ grunt.config.set('copy', {
+ dev: {
+ files: [{
+ expand: true,
+ cwd: './assets',
+ src: ['**/*.!(coffee|less)'],
+ dest: '.tmp/public'
+ }]
+ },
+ build: {
+ files: [{
+ expand: true,
+ cwd: '.tmp/public',
+ src: ['**/*'],
+ dest: 'www'
+ }]
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-copy');
+};
diff --git a/lightcalc/tasks/config/cssmin.js b/lightcalc/tasks/config/cssmin.js
new file mode 100644
index 0000000..24bedd6
--- /dev/null
+++ b/lightcalc/tasks/config/cssmin.js
@@ -0,0 +1,21 @@
+/**
+ * Compress CSS files.
+ *
+ * ---------------------------------------------------------------
+ *
+ * Minifies css files and places them into .tmp/public/min directory.
+ *
+ * For usage docs see:
+ * https://github.com/gruntjs/grunt-contrib-cssmin
+ */
+module.exports = function(grunt) {
+
+ grunt.config.set('cssmin', {
+ dist: {
+ src: ['.tmp/public/concat/production.css'],
+ dest: '.tmp/public/min/production.min.css'
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-cssmin');
+};
diff --git a/lightcalc/tasks/config/jst.js b/lightcalc/tasks/config/jst.js
new file mode 100644
index 0000000..0740b79
--- /dev/null
+++ b/lightcalc/tasks/config/jst.js
@@ -0,0 +1,41 @@
+/**
+ * Precompiles Underscore templates to a `.jst` file.
+ *
+ * ---------------------------------------------------------------
+ *
+ * (i.e. basically it takes HTML files and turns them into tiny little
+ * javascript functions that you pass data to and return HTML. This can
+ * speed up template rendering on the client, and reduce bandwidth usage.)
+ *
+ * For usage docs see:
+ * https://github.com/gruntjs/grunt-contrib-jst
+ *
+ */
+
+module.exports = function(grunt) {
+
+ grunt.config.set('jst', {
+ dev: {
+
+ // To use other sorts of templates, specify a regexp like the example below:
+ // options: {
+ // templateSettings: {
+ // interpolate: /\{\{(.+?)\}\}/g
+ // }
+ // },
+
+ // Note that the interpolate setting above is simply an example of overwriting lodash's
+ // default interpolation. If you want to parse templates with the default _.template behavior
+ // (i.e. using ), there's no need to overwrite `templateSettings.interpolate`.
+
+
+ files: {
+ // e.g.
+ // 'relative/path/from/gruntfile/to/compiled/template/destination' : ['relative/path/to/sourcefiles/**/*.html']
+ '.tmp/public/jst.js': require('../pipeline').templateFilesToInject
+ }
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-jst');
+};
diff --git a/lightcalc/tasks/config/less.js b/lightcalc/tasks/config/less.js
new file mode 100644
index 0000000..476326e
--- /dev/null
+++ b/lightcalc/tasks/config/less.js
@@ -0,0 +1,28 @@
+/**
+ * Compiles LESS files into CSS.
+ *
+ * ---------------------------------------------------------------
+ *
+ * Only the `assets/styles/importer.less` is compiled.
+ * This allows you to control the ordering yourself, i.e. import your
+ * dependencies, mixins, variables, resets, etc. before other stylesheets)
+ *
+ * For usage docs see:
+ * https://github.com/gruntjs/grunt-contrib-less
+ */
+module.exports = function(grunt) {
+
+ grunt.config.set('less', {
+ dev: {
+ files: [{
+ expand: true,
+ cwd: 'assets/styles/',
+ src: ['*.less'],
+ dest: '.tmp/public/styles/',
+ ext: '.css'
+ }]
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-less');
+};
diff --git a/lightcalc/tasks/config/sails-linker.js b/lightcalc/tasks/config/sails-linker.js
new file mode 100644
index 0000000..a1a65d6
--- /dev/null
+++ b/lightcalc/tasks/config/sails-linker.js
@@ -0,0 +1,267 @@
+/**
+ * Autoinsert script tags (or other filebased tags) in an html file.
+ *
+ * ---------------------------------------------------------------
+ *
+ * Automatically inject ',
+ appRoot: '.tmp/public'
+ },
+ files: {
+ '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject,
+ 'views/**/*.html': require('../pipeline').jsFilesToInject,
+ 'views/**/*.ejs': require('../pipeline').jsFilesToInject
+ }
+ },
+
+ devJsRelative: {
+ options: {
+ startTag: '',
+ endTag: '',
+ fileTmpl: '',
+ appRoot: '.tmp/public',
+ relative: true
+ },
+ files: {
+ '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject,
+ 'views/**/*.html': require('../pipeline').jsFilesToInject,
+ 'views/**/*.ejs': require('../pipeline').jsFilesToInject
+ }
+ },
+
+ prodJs: {
+ options: {
+ startTag: '',
+ endTag: '',
+ fileTmpl: '',
+ appRoot: '.tmp/public'
+ },
+ files: {
+ '.tmp/public/**/*.html': ['.tmp/public/min/production.min.js'],
+ 'views/**/*.html': ['.tmp/public/min/production.min.js'],
+ 'views/**/*.ejs': ['.tmp/public/min/production.min.js']
+ }
+ },
+
+ prodJsRelative: {
+ options: {
+ startTag: '',
+ endTag: '',
+ fileTmpl: '',
+ appRoot: '.tmp/public',
+ relative: true
+ },
+ files: {
+ '.tmp/public/**/*.html': ['.tmp/public/min/production.min.js'],
+ 'views/**/*.html': ['.tmp/public/min/production.min.js'],
+ 'views/**/*.ejs': ['.tmp/public/min/production.min.js']
+ }
+ },
+
+ devStyles: {
+ options: {
+ startTag: '',
+ endTag: '',
+ fileTmpl: '',
+ appRoot: '.tmp/public'
+ },
+
+ files: {
+ '.tmp/public/**/*.html': require('../pipeline').cssFilesToInject,
+ 'views/**/*.html': require('../pipeline').cssFilesToInject,
+ 'views/**/*.ejs': require('../pipeline').cssFilesToInject
+ }
+ },
+
+ devStylesRelative: {
+ options: {
+ startTag: '',
+ endTag: '',
+ fileTmpl: '',
+ appRoot: '.tmp/public',
+ relative: true
+ },
+
+ files: {
+ '.tmp/public/**/*.html': require('../pipeline').cssFilesToInject,
+ 'views/**/*.html': require('../pipeline').cssFilesToInject,
+ 'views/**/*.ejs': require('../pipeline').cssFilesToInject
+ }
+ },
+
+ prodStyles: {
+ options: {
+ startTag: '',
+ endTag: '',
+ fileTmpl: '',
+ appRoot: '.tmp/public'
+ },
+ files: {
+ '.tmp/public/index.html': ['.tmp/public/min/production.min.css'],
+ 'views/**/*.html': ['.tmp/public/min/production.min.css'],
+ 'views/**/*.ejs': ['.tmp/public/min/production.min.css']
+ }
+ },
+
+ prodStylesRelative: {
+ options: {
+ startTag: '',
+ endTag: '',
+ fileTmpl: '',
+ appRoot: '.tmp/public',
+ relative: true
+ },
+ files: {
+ '.tmp/public/index.html': ['.tmp/public/min/production.min.css'],
+ 'views/**/*.html': ['.tmp/public/min/production.min.css'],
+ 'views/**/*.ejs': ['.tmp/public/min/production.min.css']
+ }
+ },
+
+ // Bring in JST template object
+ devTpl: {
+ options: {
+ startTag: '',
+ endTag: '',
+ fileTmpl: '',
+ appRoot: '.tmp/public'
+ },
+ files: {
+ '.tmp/public/index.html': ['.tmp/public/jst.js'],
+ 'views/**/*.html': ['.tmp/public/jst.js'],
+ 'views/**/*.ejs': ['.tmp/public/jst.js']
+ }
+ },
+
+ devJsJade: {
+ options: {
+ startTag: '// SCRIPTS',
+ endTag: '// SCRIPTS END',
+ fileTmpl: 'script(src="%s")',
+ appRoot: '.tmp/public'
+ },
+ files: {
+ 'views/**/*.jade': require('../pipeline').jsFilesToInject
+ }
+ },
+
+ devJsRelativeJade: {
+ options: {
+ startTag: '// SCRIPTS',
+ endTag: '// SCRIPTS END',
+ fileTmpl: 'script(src="%s")',
+ appRoot: '.tmp/public',
+ relative: true
+ },
+ files: {
+ 'views/**/*.jade': require('../pipeline').jsFilesToInject
+ }
+ },
+
+ prodJsJade: {
+ options: {
+ startTag: '// SCRIPTS',
+ endTag: '// SCRIPTS END',
+ fileTmpl: 'script(src="%s")',
+ appRoot: '.tmp/public'
+ },
+ files: {
+ 'views/**/*.jade': ['.tmp/public/min/production.min.js']
+ }
+ },
+
+ prodJsRelativeJade: {
+ options: {
+ startTag: '// SCRIPTS',
+ endTag: '// SCRIPTS END',
+ fileTmpl: 'script(src="%s")',
+ appRoot: '.tmp/public',
+ relative: true
+ },
+ files: {
+ 'views/**/*.jade': ['.tmp/public/min/production.min.js']
+ }
+ },
+
+ devStylesJade: {
+ options: {
+ startTag: '// STYLES',
+ endTag: '// STYLES END',
+ fileTmpl: 'link(rel="stylesheet", href="%s")',
+ appRoot: '.tmp/public'
+ },
+
+ files: {
+ 'views/**/*.jade': require('../pipeline').cssFilesToInject
+ }
+ },
+
+ devStylesRelativeJade: {
+ options: {
+ startTag: '// STYLES',
+ endTag: '// STYLES END',
+ fileTmpl: 'link(rel="stylesheet", href="%s")',
+ appRoot: '.tmp/public',
+ relative: true
+ },
+
+ files: {
+ 'views/**/*.jade': require('../pipeline').cssFilesToInject
+ }
+ },
+
+ prodStylesJade: {
+ options: {
+ startTag: '// STYLES',
+ endTag: '// STYLES END',
+ fileTmpl: 'link(rel="stylesheet", href="%s")',
+ appRoot: '.tmp/public'
+ },
+ files: {
+ 'views/**/*.jade': ['.tmp/public/min/production.min.css']
+ }
+ },
+
+ prodStylesRelativeJade: {
+ options: {
+ startTag: '// STYLES',
+ endTag: '// STYLES END',
+ fileTmpl: 'link(rel="stylesheet", href="%s")',
+ appRoot: '.tmp/public',
+ relative: true
+ },
+ files: {
+ 'views/**/*.jade': ['.tmp/public/min/production.min.css']
+ }
+ },
+
+ // Bring in JST template object
+ devTplJade: {
+ options: {
+ startTag: '// TEMPLATES',
+ endTag: '// TEMPLATES END',
+ fileTmpl: 'script(type="text/javascript", src="%s")',
+ appRoot: '.tmp/public'
+ },
+ files: {
+ 'views/**/*.jade': ['.tmp/public/jst.js']
+ }
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-sails-linker');
+};
diff --git a/lightcalc/tasks/config/sync.js b/lightcalc/tasks/config/sync.js
new file mode 100644
index 0000000..8acb31f
--- /dev/null
+++ b/lightcalc/tasks/config/sync.js
@@ -0,0 +1,27 @@
+/**
+ * A grunt task to keep directories in sync. It is very similar to grunt-contrib-copy
+ * but tries to copy only those files that has actually changed.
+ *
+ * ---------------------------------------------------------------
+ *
+ * Synchronize files from the `assets` folder to `.tmp/public`,
+ * smashing anything that's already there.
+ *
+ * For usage docs see:
+ * https://github.com/tomusdrw/grunt-sync
+ *
+ */
+module.exports = function(grunt) {
+
+ grunt.config.set('sync', {
+ dev: {
+ files: [{
+ cwd: './assets',
+ src: ['**/*.!(coffee)'],
+ dest: '.tmp/public'
+ }]
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-sync');
+};
diff --git a/lightcalc/tasks/config/uglify.js b/lightcalc/tasks/config/uglify.js
new file mode 100644
index 0000000..d06bd9a
--- /dev/null
+++ b/lightcalc/tasks/config/uglify.js
@@ -0,0 +1,22 @@
+/**
+ * Minify files with UglifyJS.
+ *
+ * ---------------------------------------------------------------
+ *
+ * Minifies client-side javascript `assets`.
+ *
+ * For usage docs see:
+ * https://github.com/gruntjs/grunt-contrib-uglify
+ *
+ */
+module.exports = function(grunt) {
+
+ grunt.config.set('uglify', {
+ dist: {
+ src: ['.tmp/public/concat/production.js'],
+ dest: '.tmp/public/min/production.min.js'
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-uglify');
+};
diff --git a/lightcalc/tasks/config/watch.js b/lightcalc/tasks/config/watch.js
new file mode 100644
index 0000000..2effc2c
--- /dev/null
+++ b/lightcalc/tasks/config/watch.js
@@ -0,0 +1,34 @@
+/**
+ * Run predefined tasks whenever watched file patterns are added, changed or deleted.
+ *
+ * ---------------------------------------------------------------
+ *
+ * Watch for changes on
+ * - files in the `assets` folder
+ * - the `tasks/pipeline.js` file
+ * and re-run the appropriate tasks.
+ *
+ * For usage docs see:
+ * https://github.com/gruntjs/grunt-contrib-watch
+ *
+ */
+module.exports = function(grunt) {
+
+ grunt.config.set('watch', {
+ api: {
+
+ // API files to watch:
+ files: ['api/**/*', '!**/node_modules/**']
+ },
+ assets: {
+
+ // Assets to watch:
+ files: ['assets/**/*', '!assets/bower_components/**', 'tasks/pipeline.js', '!**/node_modules/**'],
+
+ // When assets are changed:
+ tasks: ['syncAssets' , 'linkAssets']
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-watch');
+};
diff --git a/lightcalc/tasks/pipeline.js b/lightcalc/tasks/pipeline.js
new file mode 100644
index 0000000..5dd5045
--- /dev/null
+++ b/lightcalc/tasks/pipeline.js
@@ -0,0 +1,77 @@
+/**
+ * grunt/pipeline.js
+ *
+ * The order in which your css, javascript, and template files should be
+ * compiled and linked from your views and static HTML files.
+ *
+ * (Note that you can take advantage of Grunt-style wildcard/glob/splat expressions
+ * for matching multiple files.)
+ */
+
+
+
+// CSS files to inject in order
+//
+// (if you're using LESS with the built-in default config, you'll want
+// to change `assets/styles/importer.less` instead.)
+var cssFilesToInject = [
+ 'bower_components/bootstrap/dist/css/bootstrap.css',
+ 'styles/**/*.css'
+];
+
+
+// Client-side javascript files to inject in order
+// (uses Grunt-style wildcard/glob/splat expressions)
+var jsFilesToInject = [
+
+ // Load sails.io before everything else
+ 'js/dependencies/sails.io.js',
+
+ // Dependencies like jQuery, or Angular are brought in here
+ 'js/dependencies/**/*.js',
+
+ 'bower_components/jquery/dist/jquery.js',
+ 'bower_components/angular/angular.js',
+ 'bower_components/angular-route/angular-route.js',
+ 'bower_components/angular-resource/angular-resource.js',
+ 'bower_components/angular-messages/angular-messages.js',
+ 'bower_components/angular-show-errors/src/showErrors.js',
+
+ // All of the rest of your client-side js files
+ // will be injected here in no particular order.
+ 'js/app.js',
+ 'js/routes.js',
+ 'js/controllers/*.js',
+ 'js/directives/*.js',
+ 'js/services/*.js',
+ 'js/filters/*.js'
+];
+
+
+// Client-side HTML templates are injected using the sources below
+// The ordering of these templates shouldn't matter.
+// (uses Grunt-style wildcard/glob/splat expressions)
+//
+// By default, Sails uses JST templates and precompiles them into
+// functions for you. If you want to use jade, handlebars, dust, etc.,
+// with the linker, no problem-- you'll just want to make sure the precompiled
+// templates get spit out to the same file. Be sure and check out `tasks/README.md`
+// for information on customizing and installing new tasks.
+var templateFilesToInject = [
+ 'templates/**/*.html'
+];
+
+
+
+// Prefix relative paths to source files so they point to the proper locations
+// (i.e. where the other Grunt tasks spit them out, or in some cases, where
+// they reside in the first place)
+module.exports.cssFilesToInject = cssFilesToInject.map(function(path) {
+ return '.tmp/public/' + path;
+});
+module.exports.jsFilesToInject = jsFilesToInject.map(function(path) {
+ return '.tmp/public/' + path;
+});
+module.exports.templateFilesToInject = templateFilesToInject.map(function(path) {
+ return 'assets/' + path;
+});
diff --git a/lightcalc/tasks/register/build.js b/lightcalc/tasks/register/build.js
new file mode 100644
index 0000000..94a0e6d
--- /dev/null
+++ b/lightcalc/tasks/register/build.js
@@ -0,0 +1,8 @@
+module.exports = function (grunt) {
+ grunt.registerTask('build', [
+ 'compileAssets',
+ 'linkAssetsBuild',
+ 'clean:build',
+ 'copy:build'
+ ]);
+};
diff --git a/lightcalc/tasks/register/buildProd.js b/lightcalc/tasks/register/buildProd.js
new file mode 100644
index 0000000..eb2171f
--- /dev/null
+++ b/lightcalc/tasks/register/buildProd.js
@@ -0,0 +1,12 @@
+module.exports = function (grunt) {
+ grunt.registerTask('buildProd', [
+ 'compileAssets',
+ 'concat',
+ 'uglify',
+ 'cssmin',
+ 'linkAssetsBuildProd',
+ 'clean:bower',
+ 'clean:build',
+ 'copy:build'
+ ]);
+};
diff --git a/lightcalc/tasks/register/compileAssets.js b/lightcalc/tasks/register/compileAssets.js
new file mode 100644
index 0000000..b63e2b8
--- /dev/null
+++ b/lightcalc/tasks/register/compileAssets.js
@@ -0,0 +1,9 @@
+module.exports = function (grunt) {
+ grunt.registerTask('compileAssets', [
+ 'clean:dev',
+ 'jst:dev',
+ 'less:dev',
+ 'copy:dev',
+ 'coffee:dev'
+ ]);
+};
diff --git a/lightcalc/tasks/register/default.js b/lightcalc/tasks/register/default.js
new file mode 100644
index 0000000..c7de81e
--- /dev/null
+++ b/lightcalc/tasks/register/default.js
@@ -0,0 +1,3 @@
+module.exports = function (grunt) {
+ grunt.registerTask('default', ['compileAssets', 'linkAssets', 'watch']);
+};
diff --git a/lightcalc/tasks/register/linkAssets.js b/lightcalc/tasks/register/linkAssets.js
new file mode 100644
index 0000000..17225cf
--- /dev/null
+++ b/lightcalc/tasks/register/linkAssets.js
@@ -0,0 +1,10 @@
+module.exports = function (grunt) {
+ grunt.registerTask('linkAssets', [
+ 'sails-linker:devJs',
+ 'sails-linker:devStyles',
+ 'sails-linker:devTpl',
+ 'sails-linker:devJsJade',
+ 'sails-linker:devStylesJade',
+ 'sails-linker:devTplJade'
+ ]);
+};
diff --git a/lightcalc/tasks/register/linkAssetsBuild.js b/lightcalc/tasks/register/linkAssetsBuild.js
new file mode 100644
index 0000000..ad68e5d
--- /dev/null
+++ b/lightcalc/tasks/register/linkAssetsBuild.js
@@ -0,0 +1,10 @@
+module.exports = function (grunt) {
+ grunt.registerTask('linkAssetsBuild', [
+ 'sails-linker:devJsRelative',
+ 'sails-linker:devStylesRelative',
+ 'sails-linker:devTpl',
+ 'sails-linker:devJsRelativeJade',
+ 'sails-linker:devStylesRelativeJade',
+ 'sails-linker:devTplJade'
+ ]);
+};
diff --git a/lightcalc/tasks/register/linkAssetsBuildProd.js b/lightcalc/tasks/register/linkAssetsBuildProd.js
new file mode 100644
index 0000000..9682ac6
--- /dev/null
+++ b/lightcalc/tasks/register/linkAssetsBuildProd.js
@@ -0,0 +1,10 @@
+module.exports = function (grunt) {
+ grunt.registerTask('linkAssetsBuildProd', [
+ 'sails-linker:prodJsRelative',
+ 'sails-linker:prodStylesRelative',
+ 'sails-linker:devTpl',
+ 'sails-linker:prodJsRelativeJade',
+ 'sails-linker:prodStylesRelativeJade',
+ 'sails-linker:devTplJade'
+ ]);
+};
diff --git a/lightcalc/tasks/register/prod.js b/lightcalc/tasks/register/prod.js
new file mode 100644
index 0000000..db9daed
--- /dev/null
+++ b/lightcalc/tasks/register/prod.js
@@ -0,0 +1,14 @@
+module.exports = function (grunt) {
+ grunt.registerTask('prod', [
+ 'compileAssets',
+ 'concat',
+ 'uglify',
+ 'cssmin',
+ 'sails-linker:prodJs',
+ 'sails-linker:prodStyles',
+ 'sails-linker:devTpl',
+ 'sails-linker:prodJsJade',
+ 'sails-linker:prodStylesJade',
+ 'sails-linker:devTplJade'
+ ]);
+};
diff --git a/lightcalc/tasks/register/syncAssets.js b/lightcalc/tasks/register/syncAssets.js
new file mode 100644
index 0000000..99bb12f
--- /dev/null
+++ b/lightcalc/tasks/register/syncAssets.js
@@ -0,0 +1,8 @@
+module.exports = function (grunt) {
+ grunt.registerTask('syncAssets', [
+ 'jst:dev',
+ 'less:dev',
+ 'sync:dev',
+ 'coffee:dev'
+ ]);
+};
diff --git a/lightcalc/test/bootstrap.test.js b/lightcalc/test/bootstrap.test.js
new file mode 100644
index 0000000..0ff496a
--- /dev/null
+++ b/lightcalc/test/bootstrap.test.js
@@ -0,0 +1,46 @@
+var Sails = require('sails'),
+ sails;
+
+global.DOMAIN = 'http://localhost';
+global.PORT = 1420;
+global.HOST = DOMAIN + ':' + PORT;
+
+before(function(done) {
+ Sails.lift({
+ log: {
+ level: 'warn'
+ },
+ connections: {
+ testDiskDb: {
+ adapter: 'sails-disk',
+ filePath : '.tmp/sails.test.dat'
+ }
+ },
+ models: {
+ migrate: 'safe',
+ connection: 'testDiskDb'
+ },
+ port: process.env.PORT || 1338,
+ environment: 'test',
+ csrf: false,
+ hooks: {
+ grunt: false,
+ socket: false,
+ pubsub: false
+ },
+ session: {
+ adapter: 'memory'
+ },
+
+ }, function(err, server) {
+ sails = server;
+ if (err) return done(err);
+ // here you can load fixtures, etc.
+ done(err, sails);
+ });
+});
+
+after(function(done) {
+ // here you can clear fixtures, etc.
+ sails.lower(done);
+});
\ No newline at end of file
diff --git a/lightcalc/test/readme b/lightcalc/test/readme
new file mode 100644
index 0000000..a6db02c
--- /dev/null
+++ b/lightcalc/test/readme
@@ -0,0 +1,48 @@
+Project of software test
+
+
+
+By Hugo Bessa -- hugo@bessa.me
+By jef -- jfnf@cin.ufpe.br
+
+
+
+
+- * - * - * - * - * - * - * - * -
+Requeriments for run those tests of backend:
+
+#1 - The Node.js - comes with npm. It's an online repository to projetcs publication of open-sources codes to Node.js.
+
+Install: http://nodejs.org/dist/v0.12.4/node-v0.12.4.pkg
+
+
+#2 - Mocha - is a JavaScript test framework for node.js and the browser. It handles test suites and test cases.
+
+Install:
+
+$ npm install -g mocha
+
+$ npm install supertest
+
+$ npm install assert
+
+ $ npm install sails
+
+
+- * - * - * - * - * - * - * - * -
+Github's project:
+
+clonar esse respositório git clone https://github.com/hugobessa/lightcalc.git
+- * - * - * - * - * - * - * - * -
+Fire on the code
+
+go to the local folder of lightCalc and run the mocha framework, like -
+
+$ mocha test/bootstrap.test.js test/unit/**/*.test.js
+
+
+
+
+
+
+
diff --git a/lightcalc/test/unit/controllers/CalculatorsController.test.js b/lightcalc/test/unit/controllers/CalculatorsController.test.js
new file mode 100644
index 0000000..377a802
--- /dev/null
+++ b/lightcalc/test/unit/controllers/CalculatorsController.test.js
@@ -0,0 +1,93 @@
+var request = require('supertest');
+var assert = require('assert');
+
+describe('CalculatorsController', function() {
+
+ describe('#list()', function() {
+ beforeEach(function(done){
+ Calculator.destroy({}).exec(function(){
+ done();
+ });
+ });
+
+ it('should return an empty array', function (done) {
+ request(sails.hooks.http.app)
+ .get('/calculator')
+ .end(function(err, res){
+ assert.equal(res.status, 200);
+ assert.equal(res.body.length, 0);
+ done()
+ });
+ });
+ });
+
+ describe('#create()', function() {
+ beforeEach(function(done){
+ Calculator.create({name: 'FantasmaOpera'}).exec(function(){
+ done();
+ });
+ });
+
+ it('should create a calculator', function (done) {
+ var calculator = {name: 'hugobessa'};
+
+ request(sails.hooks.http.app)
+ .post('/calculator')
+ .send(calculator)
+ .end(function(err, res){
+ assert(res.status, 201);
+ Calculator.find({name: 'hugobessa'}).exec(function(err, found){
+ assert.equal(found[0].name, 'hugobessa', done);
+ done();
+ })
+ });
+ });
+
+ it('should return error trying to save a calculator with a short name', function (done) {
+ var calculator = {name: 'hugobes'};
+
+ request(sails.hooks.http.app)
+ .post('/calculator')
+ .send(calculator)
+ .end(function(err, res){
+ assert.equal(res.status, 400);
+ done();
+ });
+ });
+
+ it('should return error trying to save a calculator with a long name', function (done) {
+ var calculator = {name: 'hugobessahugobessahugobessahugobessahugobessahugobessa'};
+
+ request(sails.hooks.http.app)
+ .post('/calculator')
+ .send(calculator)
+ .end(function(err, res){
+ assert.equal(res.status, 400);
+ done();
+ });
+ });
+
+ it('should return error trying to save a calculator with a not alphanumeric name', function (done) {
+ var calculator = {name: 'hugãoteste'};
+
+ request(sails.hooks.http.app)
+ .post('/calculator')
+ .send(calculator)
+ .end(function(err, res){
+ assert.equal(res.status, 400);
+ done();
+ });
+ });
+
+ it('should return error trying to save duplicate calculator name', function (done) {
+ var calculator = {name: 'FantasmaOpera'};
+
+ request(sails.hooks.http.app)
+ .post('/calculator')
+ .send(calculator)
+ .expect(400, done);
+ });
+ });
+});
+
+
diff --git a/lightcalc/test/unit/controllers/OperationsController.test.js b/lightcalc/test/unit/controllers/OperationsController.test.js
new file mode 100644
index 0000000..064b4db
--- /dev/null
+++ b/lightcalc/test/unit/controllers/OperationsController.test.js
@@ -0,0 +1,92 @@
+var request = require('supertest');
+var assert = require('assert');
+
+describe('OperationsController', function() {
+
+ describe('#list()', function() {
+
+ describe('there\'s no operation registered', function(){
+ beforeEach(function(done){
+ Operation.destroy({}).exec(function(){
+ done();
+ });
+ })
+ it('should return an empty array', function (done) {
+ request(sails.hooks.http.app)
+ .get('/operation')
+ .send()
+ .end(function(err, res){
+ assert.equal(res.status, 200);
+ assert.equal(res.body.length, 0);
+ done()
+ });
+
+ });
+ });
+
+ describe('there\'s one operation registered', function(){
+ var calculator, operation;
+ beforeEach(function(done){
+ Calculator.destroy({}).exec(function(err){
+ Calculator.create({name: 'testecalc'}).exec(function(err, created){
+ calculator = created;
+ Operation.create({text: '2 + 2', calculator: calculator}).exec(function(err, created){
+ operation = created
+ done();
+ })
+ });
+ });
+ });
+
+ it('should return one operation', function (done) {
+ request(sails.hooks.http.app)
+ .get('/operation')
+ .send()
+ .end(function(err, res){
+ assert.equal(res.status, 200);
+ assert.equal(res.body[0].id, operation.id);
+ done()
+ });
+ });
+ })
+ });
+
+
+ describe('#create()', function() {
+ var calculator;
+ beforeEach(function(done){
+ Calculator.destroy({}).exec(function(err){
+ Calculator.create({name: 'testecalc'}).exec(function(err, created){
+ calculator = created;
+ done();
+ });
+ });
+ });
+ it('should create an operation in testecalc calculator', function (done) {
+ request(sails.hooks.http.app)
+ .post('/operation/')
+ .send({text:'2 + 3', calculator: calculator.id})
+ .end(function(err, res){
+ assert.equal(res.status, 201);
+
+ Operation.find({calculator: calculator.id}).exec(function(err, found){
+ assert.equal(found[0].text, '2 + 3');
+ done();
+ })
+
+ });
+ });
+
+ it('should fail creating an operation in an inexistent calculator', function (done) {
+ request(sails.hooks.http.app)
+ .post('/operation/')
+ .send({text:'2 + 3', calculator: 'asdasdsadas'})
+ .end(function(err, res){
+ assert.equal(res.status, 400);
+ done()
+ });
+ });
+
+ });
+
+});
\ No newline at end of file
diff --git a/lightcalc/views/403.ejs b/lightcalc/views/403.ejs
new file mode 100644
index 0000000..c32ba9e
--- /dev/null
+++ b/lightcalc/views/403.ejs
@@ -0,0 +1,76 @@
+
+
+
+
+ Forbidden
+
+
+
+
+
+
+
+
+
+
+
+
+ Forbidden
+
+
+ <% if (typeof error !== 'undefined') { %>
+ <%= error %>
+ <% } else { %>
+ You don't have permission to see the page you're trying to reach.
+ <% } %>
+
+ A team of highly trained sea bass is working on this as we speak.
+ 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.
+