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("
" + templateHtml + "
"); + + scope.calculatorForm.name = ' asdas'; + + $compile(formElem)(scope); + scope.$apply(); + + scope.submitForm(); + (!!scope.form.$error.pattern).should.equal(true); + })); + + it('should fail if the name already exists', function(done){ + //this.timeout(6000); + inject(function($templateCache, $compile, $timeout){ + templateHtml = $templateCache.get('assets/templates/calculator/create.html'); + formElem = angular.element("
" + templateHtml + "
"); + + $compile(formElem)(scope); + scope.calculatorForm.name = 'hugobessa'; + var promise = scope.form.name.$asyncValidators.isNameFree(scope.calculatorForm.name, scope.calculatorForm.name); + scope.$apply(); + httpBackend.flush(); + + promise.finally(function(data){ + scope.submitForm(); + (!!scope.form.$error.isNameFree).should.equal(true); + done(); + }); + scope.$apply(); + }); + }); + }); +}); \ No newline at end of file diff --git a/lightcalc/assets/js/tests/OperationsCreate.test.js b/lightcalc/assets/js/tests/OperationsCreate.test.js new file mode 100644 index 0000000..a8aef63 --- /dev/null +++ b/lightcalc/assets/js/tests/OperationsCreate.test.js @@ -0,0 +1,213 @@ +describe('calculatorRetrieveCtrl', function(){ + var scope, location, httpBackend; + + beforeEach(function(){ + module('LightCalc'); + module('MyTemplates'); + }); + + beforeEach(function(done){ + inject(function($rootScope, $controller, $location, $httpBackend){ + scope = $rootScope.$new(); + location = $location; + location.path('/hugobessa'); + + $controller('calculatorRetrieveCtrl', { + "$scope": scope, + "$location": location, + "$routeParams": {name: 'hugobessa'} + }); + + httpBackend = $httpBackend; + 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('POST', '/operation', { + 'text': '2 + 5 - 4 ÷ 2 x 7', + 'calculator': 40 + }).respond({ + "id": 400, + "text": '2 + 5 - 4 ÷ 2 x 7', + "calculator": { + "operations": [400], + "name": "hugobessa", + "createdAt": "2015-06-10T05:19:54.810Z", + "updatedAt": "2015-06-10T05:19:54.810Z", + "id": 40 + }, + "createdAt": "2015-06-12T05:19:54.810Z", + "updatedAt": "2015-06-12T05:19:54.810Z", + }); + + httpBackend.when('POST', '/operation', { + 'text': '2 + 23.4 - 4.4 ÷ 2.12 x 7.5', + 'calculator': 40 + }).respond({ + "id": 400, + "text": '2 + 23.4 - 4.4 ÷ 2.12 x 7.5', + "calculator": { + "operations": [400], + "name": "hugobessa", + "createdAt": "2015-06-10T05:19:54.810Z", + "updatedAt": "2015-06-10T05:19:54.810Z", + "id": 40 + }, + "createdAt": "2015-06-12T05:19:54.810Z", + "updatedAt": "2015-06-12T05:19:54.810Z", + }); + + scope.$apply(); + httpBackend.flush(); + + scope.$watch(function(){ + return scope.calculator; + }, function(nv, ov){ + if(nv !== undefined){ + done(); + } + }); + + scope.$apply() + + + }); + }); + + describe('Write on the keyboard', function(){ + it('should show operation 2 + 5 - 4/2 * 7 on the screen', function(){ + scope.putDigit(2); + scope.add(2); + scope.putDigit(5); + scope.subtract(); + scope.putDigit(4); + scope.divide(); + scope.putDigit(2); + scope.multiply(); + scope.putDigit(7); + + scope.output.should.equals('2 + 5 - 4 ÷ 2 x 7'); + }); + + it('should show operation with decimal values 2 + 23.4 - 4.4/2.12 * 7.5 on the screen', function(){ + scope.putDigit(2); + scope.add(); + scope.putDigit(2); + scope.putDigit(3); + scope.putDigit('.'); + scope.putDigit(4); + scope.subtract(); + scope.putDigit(4); + scope.putDigit('.'); + scope.putDigit(4); + scope.divide(); + scope.putDigit(2); + scope.putDigit('.'); + scope.putDigit(1); + scope.putDigit(2); + scope.multiply(); + scope.putDigit(7); + scope.putDigit('.'); + scope.putDigit(5); + + scope.output.should.equals('2 + 23.4 - 4.4 ÷ 2.12 x 7.5'); + }); + + it('should show operation with decimal values 2 + 23.4 - 4.4/2.12 * 7.5 on the screen and then, whene clear is called, show 0', function(){ + scope.putDigit(2); + scope.add(); + scope.putDigit(2); + scope.putDigit(3); + scope.putDigit('.'); + scope.putDigit(4); + scope.subtract(); + scope.putDigit(4); + scope.putDigit('.'); + scope.putDigit(4); + scope.divide(); + scope.putDigit(2); + scope.putDigit('.'); + scope.putDigit(1); + scope.putDigit(2); + scope.multiply(); + scope.putDigit(7); + scope.putDigit('.'); + scope.putDigit(5); + + scope.output.should.equals('2 + 23.4 - 4.4 ÷ 2.12 x 7.5'); + + scope.clear(); + + scope.output.should.equals('0'); + }); + }); + + describe('create operation', function(){ + + it('should calculate 2 + 5 - 4 / 2 * 7 and show the result, -7, on the screen', function(done){ + + scope.putDigit(2); + scope.add(); + scope.putDigit(5); + scope.subtract(); + scope.putDigit(4); + scope.divide(); + scope.putDigit(2); + scope.multiply(); + scope.putDigit(7); + + var resp = scope.equals(); + scope.$apply(); + httpBackend.flush(); + + resp.$promise.then(function(){ + scope.output.should.equals('-7'); + done(); + }); + + scope.$apply(); + + }); + + it('should calculate 2 + 23.4 - 4.4/2.12 * 7.5 and show the result, 9.499999999999998, on the screen', function(done){ + + scope.putDigit(2); + scope.add(); + scope.putDigit(2); + scope.putDigit(3); + scope.putDigit('.'); + scope.putDigit(4); + scope.subtract(); + scope.putDigit(4); + scope.putDigit('.'); + scope.putDigit(4); + scope.divide(); + scope.putDigit(2); + scope.putDigit('.'); + scope.putDigit(1); + scope.putDigit(2); + scope.multiply(); + scope.putDigit(7); + scope.putDigit('.'); + scope.putDigit(5); + + + var resp = scope.equals(); + scope.$apply(); + httpBackend.flush(); + + resp.$promise.then(function(){ + scope.output.should.equals('9.499999999999998'); + done(); + }); + + scope.$apply(); + + }); + }); +}) \ No newline at end of file diff --git a/lightcalc/assets/js/tests/OperationsList.test.js b/lightcalc/assets/js/tests/OperationsList.test.js new file mode 100644 index 0000000..1d5cda5 --- /dev/null +++ b/lightcalc/assets/js/tests/OperationsList.test.js @@ -0,0 +1,67 @@ +describe('calculatorRetrieveCtrl', function(){ + var scope, location, httpBackend; + + beforeEach(function(){ + module('LightCalc'); + module('MyTemplates'); + }); + + beforeEach(function(done){ + inject(function($rootScope, $controller, $location, $httpBackend){ + scope = $rootScope.$new(); + location = $location; + location.path('/hugobessa'); + + $controller('calculatorRetrieveCtrl', { + "$scope": scope, + "$location": location, + "$routeParams": {name: 'hugobessa'} + }); + + httpBackend = $httpBackend; + httpBackend.when('GET', '/calculator?name=hugobessa').respond([{ + "operations": [ + { + "id": 400, + "text": '2 + 23.4 - 4.4 ÷ 2.12 x 7.5', + "calculator": 40, + "createdAt": "2015-06-12T05:19:54.810Z", + "updatedAt": "2015-06-12T05:19:54.810Z", + }, + + { + "id": 401, + "text": '2 + 5 - 4 ÷ 2 x 7', + "calculator": 40, + "createdAt": "2015-06-12T05:19:54.810Z", + "updatedAt": "2015-06-12T05:19:54.810Z", + }, + ], + "name": "hugobessa", + "createdAt": "2015-06-12T05:19:54.810Z", + "updatedAt": "2015-06-12T05:19:54.810Z", + "id": 40 + }]); + + scope.$apply(); + httpBackend.flush(); + + scope.$watch(function(){ + return scope.calculator; + }, function(nv, ov){ + if(nv !== undefined){ + done(); + } + }); + + scope.$apply(); + }); + }); + + describe('Check list of operations', function(){ + it('should show operations 2 + 23.4 - 4.4 ÷ 2.12 x 7.5 and 2 + 5 - 4/2 * 7 on the operations list', function(){ + scope.calculator.operations[0].text.should.equals('2 + 23.4 - 4.4 ÷ 2.12 x 7.5'); + scope.calculator.operations[1].text.should.equals('2 + 5 - 4 ÷ 2 x 7'); + }); + }); +}); \ No newline at end of file diff --git a/lightcalc/assets/js/tests/OperationsLoad.test.js b/lightcalc/assets/js/tests/OperationsLoad.test.js new file mode 100644 index 0000000..b642327 --- /dev/null +++ b/lightcalc/assets/js/tests/OperationsLoad.test.js @@ -0,0 +1,102 @@ +describe('calculatorRetrieveCtrl', function(){ + var scope, location, httpBackend; + + beforeEach(function(){ + module('LightCalc'); + module('MyTemplates'); + }); + + beforeEach(function(done){ + inject(function($rootScope, $controller, $location, $httpBackend){ + scope = $rootScope.$new(); + location = $location; + location.path('/hugobessa'); + + $controller('calculatorRetrieveCtrl', { + "$scope": scope, + "$location": location, + "$routeParams": {name: 'hugobessa'} + }); + + httpBackend = $httpBackend; + httpBackend.when('GET', '/calculator?name=hugobessa').respond([{ + "operations": [ + { + "id": 400, + "text": '2 + 23.4 - 4.4 ÷ 2.12 x 7.5', + "calculator": 40, + "createdAt": "2015-06-12T05:19:54.810Z", + "updatedAt": "2015-06-12T05:19:54.810Z", + }, + + { + "id": 401, + "text": '2 + 5 - 4 ÷ 2 x 7', + "calculator": 40, + "createdAt": "2015-06-12T05:19:54.810Z", + "updatedAt": "2015-06-12T05:19:54.810Z", + }, + ], + "name": "hugobessa", + "createdAt": "2015-06-12T05:19:54.810Z", + "updatedAt": "2015-06-12T05:19:54.810Z", + "id": 40 + }]); + + httpBackend.when('POST', '/operation', { + 'text': '2 + 23.4 - 4.4 ÷ 2.12 x 7.5', + 'calculator': 40 + }).respond({ + "id": 400, + "text": '2 + 23.4 - 4.4 ÷ 2.12 x 7.5', + "calculator": { + "operations": [400], + "name": "hugobessa", + "createdAt": "2015-06-10T05:19:54.810Z", + "updatedAt": "2015-06-10T05:19:54.810Z", + "id": 40 + }, + "createdAt": "2015-06-12T05:19:54.810Z", + "updatedAt": "2015-06-12T05:19:54.810Z", + }); + + scope.$apply(); + httpBackend.flush(); + + scope.$watch(function(){ + return scope.calculator; + }, function(nv, ov){ + if(nv !== undefined){ + done(); + } + }); + + scope.$apply(); + }); + }); + + describe('Load operation', function(){ + it('should show operation 2 + 23.4 - 4.4 ÷ 2.12 x 7.5 on the screen', function(){ + scope.loadOperation(scope.calculator.operations[0]); + + scope.output.should.equals('2 + 23.4 - 4.4 ÷ 2.12 x 7.5'); + }); + + it('should show operation 2 + 23.4 - 4.4 ÷ 2.12 x 7.5 result, 9.499999999999998, on the screen', function(done){ + scope.loadOperation(scope.calculator.operations[0]); + + scope.output.should.equals('2 + 23.4 - 4.4 ÷ 2.12 x 7.5'); + + var resp = scope.equals(); + scope.$apply(); + httpBackend.flush(); + + resp.$promise.then(function(){ + scope.output.should.equals('9.499999999999998'); + done(); + }); + + scope.$apply(); + }); + }); +}); \ No newline at end of file diff --git a/lightcalc/assets/robots.txt b/lightcalc/assets/robots.txt new file mode 100644 index 0000000..33cd27d --- /dev/null +++ b/lightcalc/assets/robots.txt @@ -0,0 +1,8 @@ +# The robots.txt file is used to control how search engines index your live URLs. +# See http://www.robotstxt.org/wc/norobots.html for more information. + + + +# To prevent search engines from seeing the site altogether, uncomment the next two lines: +# User-Agent: * +# Disallow: / diff --git a/lightcalc/assets/styles/core.less b/lightcalc/assets/styles/core.less new file mode 100644 index 0000000..785b014 --- /dev/null +++ b/lightcalc/assets/styles/core.less @@ -0,0 +1,206 @@ +/* Basic reset */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + + /* Better text styling */ + font: bold 14px Arial, sans-serif; +} + +/* Finally adding some IE9 fallbacks for gradients to finish things up */ + +/* A nice BG gradient */ +html { + height: 100%; + background: white; + background: radial-gradient(circle, #fff 20%, #ccc); + background-size: cover; +} + +body { + background: transparent; +} + +/* Using box shadows to create 3D effects */ + +.form-group .help-block p.error { + display: none; +} + +.form-group.has-error .help-block p.error { + display: block; +} + +.container { + margin: 100px auto; + width: 575px; + height: auto; + padding: 0; +} + +.operations { + width: 250px; + height: 264px; + float: left; + overflow: auto; + padding: 20px 20px 9px; + background: #F1FF92; + color: #888; + box-shadow: 0px 4px #009de4, 0px 10px 15px rgba(0, 0, 0, 0.2); + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; +} + +.operations > ul { + list-style: none; +} + +.operations > ul > li { + width: 100%; + padding: 10px; + background: white; + box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.2); + margin-bottom: 10px; + cursor: pointer; +} + +.operations > h4 { + font-weight: 700; +} + +.calculator { + width: 325px; + height: auto; + float: right; + + padding: 20px 20px 9px; + + background: #9dd2ea; + background: linear-gradient(#9dd2ea, #8bceec); + border-bottom-right-radius: 6px; + border-top-right-radius: 6px; + box-shadow: 0px 4px #009de4, 0px 10px 15px rgba(0, 0, 0, 0.2); +} + +/* Top portion */ +.top span.clear { + float: left; +} + +/* Inset shadow on the screen to create indent */ +.top .screen { + position: relative; + height: 40px; + width: 212px; + + float: right; + + padding: 0 10px; + + background: rgba(0, 0, 0, 0.2); + border-radius: 3px; + box-shadow: inset 0px 4px rgba(0, 0, 0, 0.2); + + overflow: hidden; +} + +.top .screen .numbers { + position: absolute; + right: 10px; + white-space: nowrap; + letter-spacing: 1px; + line-height: 40px; + color: white; + font-size: 17px; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); +} + +/* Clear floats */ +.keys, .top {overflow: hidden;} + +/* Applying same to the keys */ +.keys span, .top span.clear { + float: left; + position: relative; + top: 0; + + cursor: pointer; + + width: 66px; + height: 36px; + + background: white; + border-radius: 3px; + box-shadow: 0px 4px rgba(0, 0, 0, 0.2); + + margin: 0 7px 11px 0; + + color: #888; + line-height: 36px; + text-align: center; + + /* prevent selection of text inside keys */ + user-select: none; + + /* Smoothing out hover and active states using css3 transitions */ + transition: all 0.2s ease; +} + +/* Remove right margins from operator keys */ +/* style different type of keys (operators/evaluate/clear) differently */ +.keys span.operator { + background: #FFF0F5; + margin-right: 0; +} + +.keys span.eval { + background: #f1ff92; + box-shadow: 0px 4px #9da853; + color: #888e5f; +} + +.top span.clear { + background: #ff9fa8; + box-shadow: 0px 4px #ff7c87; + color: white; +} + +/* Some hover effects */ +.keys span:hover { + background: #9c89f6; + box-shadow: 0px 4px #6b54d3; + color: white; +} + +.keys span.eval:hover { + background: #abb850; + box-shadow: 0px 4px #717a33; + color: #ffffff; +} + +.top span.clear:hover { + background: #f68991; + box-shadow: 0px 4px #d3545d; + color: white; +} + +/* Simulating "pressed" effect on active state of the keys by removing the box-shadow and moving the keys down a bit */ +.keys span:active { + box-shadow: 0px 0px #6b54d3; + top: 4px; +} + +.keys span.eval:active { + box-shadow: 0px 0px #717a33; + top: 4px; +} + +.top span.clear:active { + top: 4px; + box-shadow: 0px 0px #d3545d; +} + +.uppercase { + text-transform: uppercase; +} \ No newline at end of file diff --git a/lightcalc/assets/templates/.gitkeep b/lightcalc/assets/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lightcalc/assets/templates/calculator/create.html b/lightcalc/assets/templates/calculator/create.html new file mode 100644 index 0000000..84a8f9b --- /dev/null +++ b/lightcalc/assets/templates/calculator/create.html @@ -0,0 +1,27 @@ +
+

Crie uma calculadora para você! Basta dar um nome a ela!

+
+
+
+ + + +

+ O nome da calculadora é obrigatório + O nome da calculadora deve ter pelo menos 8 caracteres + O nome da calculadora deve ter no máximo 40 caracteres + O nome da calculadora pode conter letras maiúsculas e minúsculas + O nome da calculadora solicitado não está disponível +

+
+
+
+
+ +
+
+ +
+
+ +
\ No newline at end of file diff --git a/lightcalc/assets/templates/calculator/retrieve.html b/lightcalc/assets/templates/calculator/retrieve.html new file mode 100644 index 0000000..19283bf --- /dev/null +++ b/lightcalc/assets/templates/calculator/retrieve.html @@ -0,0 +1,38 @@ +
+

Calculadora {{calculator.name}}

+
+

OPERAÇÕES

+
    +
  • {{operation.text}}
  • +
+
+
+ + +
+ C +
{{output}}
+
+ +
+ + 7 + 8 + 9 + + + 4 + 5 + 6 + - + 1 + 2 + 3 + ÷ + 0 + . + = + x +
+ +
+
\ 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. + <% } %> +

+

+ Why might this be happening? +

+
+ + +
+ + + diff --git a/lightcalc/views/404.ejs b/lightcalc/views/404.ejs new file mode 100644 index 0000000..1329819 --- /dev/null +++ b/lightcalc/views/404.ejs @@ -0,0 +1,76 @@ + + + + + Page Not Found + + + + + +
+
+ +
+ +
+

+ Something's fishy here. +

+

+ <% if (typeof error!== 'undefined') { %> + <%= error %> + <% } else { %> + The page you were trying to reach doesn't exist. + <% } %> +

+

+ Why might this be happening? +

+
+ + +
+ + + diff --git a/lightcalc/views/500.ejs b/lightcalc/views/500.ejs new file mode 100644 index 0000000..34e8af9 --- /dev/null +++ b/lightcalc/views/500.ejs @@ -0,0 +1,81 @@ + + + + + Server Error + + + + +
+
+
+ + +
+
+
+

+ Internal Server Error +

+

+ Something isn't right here. +

+ <% if (typeof error !== 'undefined') { %> +

+        	<%- error %>
+        
+ <% } else { %> +

+ 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. +

+ <% } %> + +
+ + +
+ + diff --git a/lightcalc/views/home/index.ejs b/lightcalc/views/home/index.ejs new file mode 100644 index 0000000..c785652 --- /dev/null +++ b/lightcalc/views/home/index.ejs @@ -0,0 +1,3 @@ +
+ +
diff --git a/lightcalc/views/layout.ejs b/lightcalc/views/layout.ejs new file mode 100644 index 0000000..71277e7 --- /dev/null +++ b/lightcalc/views/layout.ejs @@ -0,0 +1,109 @@ + + + + Light Calc + + + + + + + + + + + + + + + <%- body %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +