TwoToc code
authorPTKDev <ptkdev@gmail.com>
Fri, 20 Nov 2015 11:22:35 +0000 (12:22 +0100)
committerPTKDev <ptkdev@gmail.com>
Fri, 20 Nov 2015 11:22:35 +0000 (12:22 +0100)
149 files changed:
.bowerrc [new file with mode: 0755]
.buildignore [new file with mode: 0755]
.editorconfig [new file with mode: 0755]
.gitattributes [new file with mode: 0755]
.jscs.json [new file with mode: 0755]
.travis.yml [new file with mode: 0755]
.yo-rc.json [new file with mode: 0755]
Gruntfile.js [new file with mode: 0755]
README.md [new file with mode: 0755]
bower.json [new file with mode: 0755]
client/.DS_Store [new file with mode: 0755]
client/.htaccess [new file with mode: 0755]
client/.jshintrc [new file with mode: 0755]
client/app/account/account.js [new file with mode: 0755]
client/app/account/login/login.controller.js [new file with mode: 0755]
client/app/account/login/login.html [new file with mode: 0755]
client/app/account/login/login.scss [new file with mode: 0755]
client/app/account/settings/settings.controller.js [new file with mode: 0755]
client/app/account/settings/settings.html [new file with mode: 0755]
client/app/account/signup/signup.controller.js [new file with mode: 0755]
client/app/account/signup/signup.html [new file with mode: 0755]
client/app/admin/admin.controller.js [new file with mode: 0755]
client/app/admin/admin.html [new file with mode: 0755]
client/app/admin/admin.js [new file with mode: 0755]
client/app/admin/admin.scss [new file with mode: 0755]
client/app/app.js [new file with mode: 0755]
client/app/app.scss [new file with mode: 0755]
client/app/main/main.controller.js [new file with mode: 0755]
client/app/main/main.controller.spec.js [new file with mode: 0755]
client/app/main/main.html [new file with mode: 0755]
client/app/main/main.js [new file with mode: 0755]
client/app/main/main.scss [new file with mode: 0755]
client/app/partecipa/partecipa.controller.js [new file with mode: 0755]
client/app/partecipa/partecipa.html [new file with mode: 0755]
client/app/partecipa/partecipa.js [new file with mode: 0755]
client/app/partecipa/partecipa.scss [new file with mode: 0755]
client/app/partecipa/show/show.controller.js [new file with mode: 0755]
client/app/partecipa/show/show.html [new file with mode: 0755]
client/assets/.DS_Store [new file with mode: 0755]
client/assets/images/avatar.png [new file with mode: 0755]
client/assets/images/got.jpg [new file with mode: 0755]
client/assets/images/icon-tv-full.png [new file with mode: 0755]
client/assets/images/icon-tv.png [new file with mode: 0755]
client/assets/images/logo.png [new file with mode: 0755]
client/assets/images/soccer-ball.jpg [new file with mode: 0755]
client/assets/images/yeoman.png [new file with mode: 0755]
client/assets/langs/locale-it.json [new file with mode: 0755]
client/components/auth/auth.service.js [new file with mode: 0755]
client/components/auth/user.service.js [new file with mode: 0755]
client/components/footer/footer.directive.js [new file with mode: 0755]
client/components/footer/footer.html [new file with mode: 0755]
client/components/footer/footer.scss [new file with mode: 0755]
client/components/modal/modal.html [new file with mode: 0755]
client/components/modal/modal.scss [new file with mode: 0755]
client/components/modal/modal.service.js [new file with mode: 0755]
client/components/mongoose-error/mongoose-error.directive.js [new file with mode: 0755]
client/components/navbar/navbar.controller.js [new file with mode: 0755]
client/components/navbar/navbar.directive.js [new file with mode: 0755]
client/components/navbar/navbar.html [new file with mode: 0755]
client/components/ui-router/ui-router.mock.js [new file with mode: 0755]
client/favicon.ico [new file with mode: 0755]
client/index.html [new file with mode: 0755]
client/robots.txt [new file with mode: 0755]
client/uploads/97211.jpg [new file with mode: 0755]
client/uploads/Pixels.jpg [new file with mode: 0755]
client/uploads/calcio.jpg [new file with mode: 0755]
client/uploads/champions.jpg [new file with mode: 0755]
client/uploads/got.jpg [new file with mode: 0755]
client/uploads/jurassic.jpg [new file with mode: 0755]
e2e/account/login/login.po.js [new file with mode: 0755]
e2e/account/login/login.spec.js [new file with mode: 0755]
e2e/account/logout/logout.spec.js [new file with mode: 0755]
e2e/account/signup/signup.po.js [new file with mode: 0755]
e2e/account/signup/signup.spec.js [new file with mode: 0755]
e2e/components/navbar/navbar.po.js [new file with mode: 0755]
e2e/main/main.po.js [new file with mode: 0755]
e2e/main/main.spec.js [new file with mode: 0755]
karma.conf.js [new file with mode: 0755]
mocha.conf.js [new file with mode: 0755]
package.json [new file with mode: 0755]
protractor.conf.js [new file with mode: 0755]
server/.DS_Store [new file with mode: 0755]
server/.jshintrc [new file with mode: 0755]
server/.jshintrc-spec [new file with mode: 0755]
server/api/.DS_Store [new file with mode: 0755]
server/api/category/category.controller.js [new file with mode: 0755]
server/api/category/category.events.js [new file with mode: 0755]
server/api/category/category.integration.js [new file with mode: 0755]
server/api/category/category.model.js [new file with mode: 0755]
server/api/category/index.js [new file with mode: 0755]
server/api/category/index.spec.js [new file with mode: 0755]
server/api/comment/comment.controller.js [new file with mode: 0755]
server/api/comment/comment.events.js [new file with mode: 0755]
server/api/comment/comment.integration.js [new file with mode: 0755]
server/api/comment/comment.model.js [new file with mode: 0755]
server/api/comment/index.js [new file with mode: 0755]
server/api/comment/index.spec.js [new file with mode: 0755]
server/api/message/index.js [new file with mode: 0755]
server/api/message/index.spec.js [new file with mode: 0755]
server/api/message/message.controller.js [new file with mode: 0755]
server/api/message/message.events.js [new file with mode: 0755]
server/api/message/message.integration.js [new file with mode: 0755]
server/api/message/message.model.js [new file with mode: 0755]
server/api/show/index.js [new file with mode: 0755]
server/api/show/index.spec.js [new file with mode: 0755]
server/api/show/show.controller.js [new file with mode: 0755]
server/api/show/show.events.js [new file with mode: 0755]
server/api/show/show.integration.js [new file with mode: 0755]
server/api/show/show.model.js [new file with mode: 0755]
server/api/user/__user.model.spec.js [new file with mode: 0755]
server/api/user/index.js [new file with mode: 0755]
server/api/user/index.spec.js [new file with mode: 0755]
server/api/user/user.controller.js [new file with mode: 0755]
server/api/user/user.events.js [new file with mode: 0755]
server/api/user/user.integration.js [new file with mode: 0755]
server/api/user/user.model.js [new file with mode: 0755]
server/api/user/user.model.spec.js [new file with mode: 0755]
server/app.js [new file with mode: 0755]
server/auth/auth.service.js [new file with mode: 0755]
server/auth/facebook/index.js [new file with mode: 0755]
server/auth/facebook/passport.js [new file with mode: 0755]
server/auth/google/index.js [new file with mode: 0755]
server/auth/google/passport.js [new file with mode: 0755]
server/auth/index.js [new file with mode: 0755]
server/auth/local/index.js [new file with mode: 0755]
server/auth/local/passport.js [new file with mode: 0755]
server/auth/twitter/index.js [new file with mode: 0755]
server/auth/twitter/passport.js [new file with mode: 0755]
server/components/errors/index.js [new file with mode: 0755]
server/config/environment/development.js [new file with mode: 0755]
server/config/environment/index.js [new file with mode: 0755]
server/config/environment/production.js [new file with mode: 0755]
server/config/environment/test.js [new file with mode: 0755]
server/config/express.js [new file with mode: 0755]
server/config/local.env.sample.js [new file with mode: 0755]
server/config/seed.js [new file with mode: 0755]
server/data/db/local.0 [new file with mode: 0755]
server/data/db/local.ns [new file with mode: 0755]
server/data/db/mongod.lock [new file with mode: 0755]
server/data/db/storage.bson [new file with mode: 0755]
server/data/db/twotoc-dev.0 [new file with mode: 0755]
server/data/db/twotoc-dev.ns [new file with mode: 0755]
server/data/db/twotoc-test.0 [new file with mode: 0755]
server/data/db/twotoc-test.ns [new file with mode: 0755]
server/data/db/twotoc.0 [new file with mode: 0755]
server/data/db/twotoc.ns [new file with mode: 0755]
server/index.js [new file with mode: 0755]
server/routes.js [new file with mode: 0755]
server/views/404.html [new file with mode: 0755]

diff --git a/.bowerrc b/.bowerrc
new file mode 100755 (executable)
index 0000000..666f347
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+    "directory": "client/bower_components"
+}
diff --git a/.buildignore b/.buildignore
new file mode 100755 (executable)
index 0000000..3ae6d06
--- /dev/null
@@ -0,0 +1 @@
+*.coffee
diff --git a/.editorconfig b/.editorconfig
new file mode 100755 (executable)
index 0000000..c2cdfb8
--- /dev/null
@@ -0,0 +1,21 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+
+[*]
+
+# Change these settings to your own preference
+indent_style = space
+indent_size = 2
+
+# We recommend you to keep these unchanged
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitattributes b/.gitattributes
new file mode 100755 (executable)
index 0000000..fcadb2c
--- /dev/null
@@ -0,0 +1 @@
+* text eol=lf
diff --git a/.jscs.json b/.jscs.json
new file mode 100755 (executable)
index 0000000..99393d5
--- /dev/null
@@ -0,0 +1,44 @@
+{
+  "maximumLineLength": {
+    "value": 100,
+    "allowComments": true,
+    "allowRegex": true
+  },
+  "disallowMixedSpacesAndTabs": true,
+  "disallowMultipleLineStrings": true,
+  "disallowNewlineBeforeBlockStatements": true,
+  "disallowSpaceAfterObjectKeys": true,
+  "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
+  "disallowSpaceBeforeBinaryOperators": [","],
+  "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
+  "disallowSpacesInAnonymousFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInFunctionDeclaration": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInNamedFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInsideArrayBrackets": true,
+  "disallowSpacesInsideParentheses": true,
+  "disallowTrailingComma": true,
+  "disallowTrailingWhitespace": true,
+  "requireCommaBeforeLineBreak": true,
+  "requireLineFeedAtFileEnd": true,
+  "requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
+  "requireSpaceBeforeBlockStatements": true,
+  "requireSpacesInConditionalExpression": {
+    "afterTest": true,
+    "beforeConsequent": true,
+    "afterConsequent": true,
+    "beforeAlternate": true
+  },
+  "requireSpacesInFunction": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "validateLineBreaks": "LF",
+  "validateParameterSeparator": ", "
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100755 (executable)
index 0000000..3fa6d3f
--- /dev/null
@@ -0,0 +1,8 @@
+language: node_js
+node_js:
+  - '0.12'
+before_script:
+  - npm install -g bower grunt-cli
+  - gem install sass
+  - bower install
+services: mongodb
diff --git a/.yo-rc.json b/.yo-rc.json
new file mode 100755 (executable)
index 0000000..caaa7ba
--- /dev/null
@@ -0,0 +1,64 @@
+{
+  "generator-angular-fullstack": {
+    "endpointDirectory": "server/api/",
+    "insertRoutes": true,
+    "registerRoutesFile": "server/routes.js",
+    "routesNeedle": "// Insert routes below",
+    "routesBase": "/api/",
+    "pluralizeRoutes": true,
+    "insertSockets": true,
+    "registerSocketsFile": "server/config/socketio.js",
+    "socketsNeedle": "// Insert sockets below",
+    "insertModels": true,
+    "registerModelsFile": "server/sqldb/index.js",
+    "modelsNeedle": "// Insert models below",
+    "filters": {
+      "js": true,
+      "html": true,
+      "sass": true,
+      "uirouter": true,
+      "bootstrap": true,
+      "uibootstrap": true,
+      "auth": true,
+      "models": true,
+      "mongooseModels": true,
+      "mongoose": true,
+      "oauth": true,
+      "googleAuth": true,
+      "facebookAuth": true,
+      "twitterAuth": true,
+      "grunt": true,
+      "jasmine": true,
+      "mocha": false,
+      "should": false,
+      "expect": false
+    }
+  },
+  "generator-ng-component": {
+    "routeDirectory": "client/app/",
+    "directiveDirectory": "client/app/",
+    "filterDirectory": "client/app/",
+    "serviceDirectory": "client/app/",
+    "basePath": "client",
+    "moduleName": "",
+    "filters": [
+      "uirouter",
+      "jasmine",
+      "uirouter"
+    ],
+    "extensions": [
+      "js",
+      "html",
+      "scss"
+    ],
+    "directiveSimpleTemplates": "",
+    "directiveComplexTemplates": "",
+    "filterTemplates": "",
+    "serviceTemplates": "",
+    "factoryTemplates": "",
+    "controllerTemplates": "",
+    "decoratorTemplates": "",
+    "providerTemplates": "",
+    "routeTemplates": ""
+  }
+}
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100755 (executable)
index 0000000..37943f6
--- /dev/null
@@ -0,0 +1,750 @@
+// Generated on 2015-08-31 using generator-angular-fullstack 3.0.0-rc5
+'use strict';
+
+module.exports = function (grunt) {
+  var localConfig;
+  try {
+    localConfig = require('./server/config/local.env');
+  } catch(e) {
+    localConfig = {};
+  }
+
+  // Load grunt tasks automatically, when needed
+  require('jit-grunt')(grunt, {
+    express: 'grunt-express-server',
+    useminPrepare: 'grunt-usemin',
+    ngtemplates: 'grunt-angular-templates',
+    cdnify: 'grunt-google-cdn',
+    protractor: 'grunt-protractor-runner',
+    buildcontrol: 'grunt-build-control',
+    istanbul_check_coverage: 'grunt-mocha-istanbul'
+  });
+
+  // Time how long tasks take. Can help when optimizing build times
+  require('time-grunt')(grunt);
+
+  // Define the configuration for all the tasks
+  grunt.initConfig({
+
+    // Project settings
+    pkg: grunt.file.readJSON('package.json'),
+    yeoman: {
+      // configurable paths
+      client: require('./bower.json').appPath || 'client',
+      dist: 'dist'
+    },
+    express: {
+      options: {
+        port: process.env.PORT || 9000
+      },
+      dev: {
+        options: {
+          script: 'server',
+          debug: true
+        }
+      },
+      prod: {
+        options: {
+          script: 'dist/server'
+        }
+      }
+    },
+    open: {
+      server: {
+        url: 'http://localhost:<%= express.options.port %>'
+      }
+    },
+    watch: {
+      injectJS: {
+        files: [
+          '<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js',
+          '!<%= yeoman.client %>/app/app.js'
+        ],
+        tasks: ['injector:scripts']
+      },
+      injectCss: {
+        files: ['<%= yeoman.client %>/{app,components}/**/*.css'],
+        tasks: ['injector:css']
+      },
+      mochaTest: {
+        files: ['server/**/*.{spec,integration}.js'],
+        tasks: ['env:test', 'mochaTest']
+      },
+      jsTest: {
+        files: ['<%= yeoman.client %>/{app,components}/**/*.{spec,mock}.js'],
+        tasks: ['newer:jshint:all', 'wiredep:test', 'karma']
+      },
+      injectSass: {
+        files: ['<%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
+        tasks: ['injector:sass']
+      },
+      sass: {
+        files: ['<%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
+        tasks: ['sass', 'postcss']
+      },
+      gruntfile: {
+        files: ['Gruntfile.js']
+      },
+      livereload: {
+        files: [
+          '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.{css,html}',
+          '{.tmp,<%= yeoman.client %>}/{app,components}/**/!(*.spec|*.mock).js',
+          '<%= yeoman.client %>/assets/images/{,*//*}*.{png,jpg,jpeg,gif,webp,svg}'
+        ],
+        options: {
+          livereload: true
+        }
+      },
+      express: {
+        files: ['server/**/*.{js,json}'],
+        tasks: ['express:dev', 'wait'],
+        options: {
+          livereload: true,
+          spawn: false //Without this option specified express won't be reloaded
+        }
+      },
+      bower: {
+        files: ['bower.json'],
+        tasks: ['wiredep']
+      },
+    },
+
+    // Make sure code styles are up to par and there are no obvious mistakes
+    jshint: {
+      options: {
+        jshintrc: '<%= yeoman.client %>/.jshintrc',
+        reporter: require('jshint-stylish')
+      },
+      server: {
+        options: {
+          jshintrc: 'server/.jshintrc'
+        },
+        src: ['server/**/!(*.spec|*.integration).js']
+      },
+      serverTest: {
+        options: {
+          jshintrc: 'server/.jshintrc-spec'
+        },
+        src: ['server/**/*.{spec,integration}.js']
+      },
+      all: ['<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js'],
+      test: {
+        src: ['<%= yeoman.client %>/{app,components}/**/*.{spec,mock}.js']
+      }
+    },
+
+    jscs: {
+      options: {
+        config: ".jscs.json"
+      },
+      main: {
+        files: {
+          src: [
+            '<%= yeoman.client %>/app/**/*.js',
+            'server/**/*.js'
+          ]
+        }
+      }
+    },
+
+    // Empties folders to start fresh
+    clean: {
+      dist: {
+        files: [{
+          dot: true,
+          src: [
+            '.tmp',
+            '<%= yeoman.dist %>/!(.git*|.openshift|Procfile)**'
+          ]
+        }]
+      },
+      server: '.tmp'
+    },
+
+    // Add vendor prefixed styles
+    postcss: {
+      options: {
+        map: true,
+        processors: [
+          require('autoprefixer-core')({browsers: ['last 1 version']})
+        ]
+      },
+      dist: {
+        files: [{
+          expand: true,
+          cwd: '.tmp/',
+          src: '{,*/}*.css',
+          dest: '.tmp/'
+        }]
+      }
+    },
+
+    // Debugging with node inspector
+    'node-inspector': {
+      custom: {
+        options: {
+          'web-host': 'localhost'
+        }
+      }
+    },
+
+    // Use nodemon to run server in debug mode with an initial breakpoint
+    nodemon: {
+      debug: {
+        script: 'server',
+        options: {
+          nodeArgs: ['--debug-brk'],
+          env: {
+            PORT: process.env.PORT || 9000
+          },
+          callback: function (nodemon) {
+            nodemon.on('log', function (event) {
+              console.log(event.colour);
+            });
+
+            // opens browser on initial server start
+            nodemon.on('config:update', function () {
+              setTimeout(function () {
+                require('open')('http://localhost:8080/debug?port=5858');
+              }, 500);
+            });
+          }
+        }
+      }
+    },
+
+    // Automatically inject Bower components into the app and karma.conf.js
+    wiredep: {
+      options: {
+        exclude: [
+          /bootstrap.js/,
+          '/json3/',
+          '/es5-shim/',
+          /font-awesome\.css/,
+          /bootstrap\.css/,
+          /bootstrap-sass-official/
+        ]
+      },
+      client: {
+        src: '<%= yeoman.client %>/index.html',
+        ignorePath: '<%= yeoman.client %>/',
+      },
+      test: {
+        src: './karma.conf.js',
+        devDependencies: true
+      }
+    },
+
+    // Renames files for browser caching purposes
+    filerev: {
+      dist: {
+        src: [
+          '<%= yeoman.dist %>/client/!(bower_components){,*/}*.{js,css}',
+          // '<%= yeoman.dist %>/client/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
+          '<%= yeoman.dist %>/client/assets/fonts/*'
+        ]
+      }
+    },
+
+    // Reads HTML for usemin blocks to enable smart builds that automatically
+    // concat, minify and revision files. Creates configurations in memory so
+    // additional tasks can operate on them
+    useminPrepare: {
+      html: ['<%= yeoman.client %>/index.html'],
+      options: {
+        dest: '<%= yeoman.dist %>/client'
+      }
+    },
+
+    // Performs rewrites based on rev and the useminPrepare configuration
+    usemin: {
+      html: ['<%= yeoman.dist %>/client/!(bower_components){,*/}*.html'],
+      css: ['<%= yeoman.dist %>/client/!(bower_components){,*/}*.css'],
+      js: ['<%= yeoman.dist %>/client/!(bower_components){,*/}*.js'],
+      options: {
+        assetsDirs: [
+          '<%= yeoman.dist %>/client',
+          '<%= yeoman.dist %>/client/assets/images'
+        ],
+        // This is so we update image references in our ng-templates
+        patterns: {
+          js: [
+            [/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images']
+          ]
+        }
+      }
+    },
+
+    // The following *-min tasks produce minified files in the dist folder
+    imagemin: {
+      dist: {
+        files: [{
+          expand: true,
+          cwd: '<%= yeoman.client %>/assets/images',
+          src: '{,*/}*.{png,jpg,jpeg,gif,svg}',
+          dest: '<%= yeoman.dist %>/client/assets/images'
+        }]
+      }
+    },
+
+    // Allow the use of non-minsafe AngularJS files. Automatically makes it
+    // minsafe compatible so Uglify does not destroy the ng references
+    ngAnnotate: {
+      dist: {
+        files: [{
+          expand: true,
+          cwd: '.tmp/concat',
+          src: '**/*.js',
+          dest: '.tmp/concat'
+        }]
+      }
+    },
+
+    // Package all the html partials into a single javascript payload
+    ngtemplates: {
+      options: {
+        // This should be the name of your apps angular module
+        module: 'dashboardApp',
+        htmlmin: {
+          collapseBooleanAttributes: true,
+          collapseWhitespace: true,
+          removeAttributeQuotes: true,
+          removeEmptyAttributes: true,
+          removeRedundantAttributes: true,
+          removeScriptTypeAttributes: true,
+          removeStyleLinkTypeAttributes: true
+        },
+        usemin: 'app/app.js'
+      },
+      main: {
+        cwd: '<%= yeoman.client %>',
+        src: ['{app,components}/**/*.html'],
+        dest: '.tmp/templates.js'
+      },
+      tmp: {
+        cwd: '.tmp',
+        src: ['{app,components}/**/*.html'],
+        dest: '.tmp/tmp-templates.js'
+      }
+    },
+
+    // Replace Google CDN references
+    cdnify: {
+      dist: {
+        html: ['<%= yeoman.dist %>/client/*.html']
+      }
+    },
+
+    // Copies remaining files to places other tasks can use
+    copy: {
+      dist: {
+        files: [{
+          expand: true,
+          dot: true,
+          cwd: '<%= yeoman.client %>',
+          dest: '<%= yeoman.dist %>/client',
+          src: [
+            '*.{ico,png,txt}',
+            '.htaccess',
+            'bower_components/**/*',
+            'assets/images/{,*/}*.{webp}',
+            'assets/fonts/**/*',
+            'assets/langs/*',
+            'index.html'
+          ]
+        }, {
+          expand: true,
+          cwd: '.tmp/images',
+          dest: '<%= yeoman.dist %>/client/assets/images',
+          src: ['generated/*']
+        }, {
+          expand: true,
+          dest: '<%= yeoman.dist %>',
+          src: [
+            'package.json',
+            'server/**/*'
+          ]
+        }]
+      },
+      styles: {
+        expand: true,
+        cwd: '<%= yeoman.client %>',
+        dest: '.tmp/',
+        src: ['{app,components}/**/*.css']
+      }
+    },
+
+    buildcontrol: {
+      options: {
+        dir: 'dist',
+        commit: true,
+        push: true,
+        connectCommits: false,
+        message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
+      },
+      heroku: {
+        options: {
+          remote: 'heroku',
+          branch: 'master'
+        }
+      },
+      openshift: {
+        options: {
+          remote: 'openshift',
+          branch: 'master'
+        }
+      }
+    },
+
+    // Run some tasks in parallel to speed up the build process
+    concurrent: {
+      server: [
+        'sass',
+      ],
+      test: [
+        'sass',
+      ],
+      debug: {
+        tasks: [
+          'nodemon',
+          'node-inspector'
+        ],
+        options: {
+          logConcurrentOutput: true
+        }
+      },
+      dist: [
+        'sass',
+        'imagemin'
+      ]
+    },
+
+    // Test settings
+    karma: {
+      unit: {
+        configFile: 'karma.conf.js',
+        singleRun: true
+      }
+    },
+
+    mochaTest: {
+      options: {
+        reporter: 'spec',
+        require: 'mocha.conf.js',
+        timeout: 5000 // set default mocha spec timeout
+      },
+      unit: {
+        src: ['server/**/*.spec.js']
+      },
+      integration: {
+        src: ['server/**/*.integration.js']
+      }
+    },
+
+    mocha_istanbul: {
+      unit: {
+        options: {
+          excludes: ['**/*.{spec,mock,integration}.js'],
+          reporter: 'spec',
+          require: ['mocha.conf.js'],
+          mask: '**/*.spec.js',
+          coverageFolder: 'coverage/server/unit'
+        },
+        src: 'server'
+      },
+      integration: {
+        options: {
+          excludes: ['**/*.{spec,mock,integration}.js'],
+          reporter: 'spec',
+          require: ['mocha.conf.js'],
+          mask: '**/*.integration.js',
+          coverageFolder: 'coverage/server/integration'
+        },
+        src: 'server'
+      }
+    },
+
+    istanbul_check_coverage: {
+      default: {
+        options: {
+          coverageFolder: 'coverage/**',
+          check: {
+            lines: 80,
+            statements: 80,
+            branches: 80,
+            functions: 80
+          }
+        }
+      }
+    },
+
+    protractor: {
+      options: {
+        configFile: 'protractor.conf.js'
+      },
+      chrome: {
+        options: {
+          args: {
+            browser: 'chrome'
+          }
+        }
+      }
+    },
+
+    env: {
+      test: {
+        NODE_ENV: 'test'
+      },
+      prod: {
+        NODE_ENV: 'production'
+      },
+      all: localConfig
+    },
+
+    // Compiles Sass to CSS
+    sass: {
+      server: {
+        options: {
+          compass: false
+        },
+        files: {
+          '.tmp/app/app.css' : '<%= yeoman.client %>/app/app.scss'
+        }
+      }
+    },
+
+    injector: {
+      options: {
+
+      },
+      // Inject application script files into index.html (doesn't include bower)
+      scripts: {
+        options: {
+          transform: function(filePath) {
+            filePath = filePath.replace('/client/', '');
+            filePath = filePath.replace('/.tmp/', '');
+            return '<script src="' + filePath + '"></script>';
+          },
+          starttag: '<!-- injector:js -->',
+          endtag: '<!-- endinjector -->'
+        },
+        files: {
+          '<%= yeoman.client %>/index.html': [
+               [
+                 '{.tmp,<%= yeoman.client %>}/{app,components}/**/!(*.spec|*.mock).js',
+                 '!{.tmp,<%= yeoman.client %>}/app/app.js'
+               ]
+            ]
+        }
+      },
+
+      // Inject component scss into app.scss
+      sass: {
+        options: {
+          transform: function(filePath) {
+            filePath = filePath.replace('/client/app/', '');
+            filePath = filePath.replace('/client/components/', '../components/');
+            return '@import \'' + filePath + '\';';
+          },
+          starttag: '// injector',
+          endtag: '// endinjector'
+        },
+        files: {
+          '<%= yeoman.client %>/app/app.scss': [
+            '<%= yeoman.client %>/{app,components}/**/*.{scss,sass}',
+            '!<%= yeoman.client %>/app/app.{scss,sass}'
+          ]
+        }
+      },
+
+      // Inject component css into index.html
+      css: {
+        options: {
+          transform: function(filePath) {
+            filePath = filePath.replace('/client/', '');
+            filePath = filePath.replace('/.tmp/', '');
+            return '<link rel="stylesheet" href="' + filePath + '">';
+          },
+          starttag: '<!-- injector:css -->',
+          endtag: '<!-- endinjector -->'
+        },
+        files: {
+          '<%= yeoman.client %>/index.html': [
+            '<%= yeoman.client %>/{app,components}/**/*.css'
+          ]
+        }
+      }
+    },
+  });
+
+  // Used for delaying livereload until after server has restarted
+  grunt.registerTask('wait', function () {
+    grunt.log.ok('Waiting for server reload...');
+
+    var done = this.async();
+
+    setTimeout(function () {
+      grunt.log.writeln('Done waiting!');
+      done();
+    }, 1500);
+  });
+
+  grunt.registerTask('express-keepalive', 'Keep grunt running', function() {
+    this.async();
+  });
+
+  grunt.registerTask('serve', function (target) {
+    if (target === 'dist') {
+      return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'open', 'express-keepalive']);
+    }
+
+    if (target === 'debug') {
+      return grunt.task.run([
+        'clean:server',
+        'env:all',
+        'injector:sass',
+        'concurrent:server',
+        'injector',
+        'wiredep:client',
+        'postcss',
+        'concurrent:debug'
+      ]);
+    }
+
+    grunt.task.run([
+      'clean:server',
+      'env:all',
+      'injector:sass',
+      'concurrent:server',
+      'injector',
+      'wiredep:client',
+      'postcss',
+      'express:dev',
+      'wait',
+      'open',
+      'watch'
+    ]);
+  });
+
+  grunt.registerTask('server', function () {
+    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
+    grunt.task.run(['serve']);
+  });
+
+  grunt.registerTask('test', function(target, option) {
+    if (target === 'server') {
+      return grunt.task.run([
+        'env:all',
+        'env:test',
+        'mochaTest:unit',
+        'mochaTest:integration'
+      ]);
+    }
+
+    else if (target === 'client') {
+      return grunt.task.run([
+        'clean:server',
+        'env:all',
+        'injector:sass',
+        'concurrent:test',
+        'injector',
+        'postcss',
+        'wiredep:test',
+        'karma'
+      ]);
+    }
+
+    else if (target === 'e2e') {
+
+      if (option === 'prod') {
+        return grunt.task.run([
+          'build',
+          'env:all',
+          'env:prod',
+          'express:prod',
+          'protractor'
+        ]);
+      }
+
+      else {
+        return grunt.task.run([
+          'clean:server',
+          'env:all',
+          'env:test',
+          'injector:sass',
+          'concurrent:test',
+          'injector',
+          'wiredep:client',
+          'postcss',
+          'express:dev',
+          'protractor'
+        ]);
+      }
+    }
+
+    else if (target === 'coverage') {
+
+      if (option === 'unit') {
+        return grunt.task.run([
+          'env:all',
+          'env:test',
+          'mocha_istanbul:unit'
+        ]);
+      }
+
+      else if (option === 'integration') {
+        return grunt.task.run([
+          'env:all',
+          'env:test',
+          'mocha_istanbul:integration'
+        ]);
+      }
+
+      else if (option === 'check') {
+        return grunt.task.run([
+          'istanbul_check_coverage'
+        ]);
+      }
+
+      else {
+        return grunt.task.run([
+          'env:all',
+          'env:test',
+          'mocha_istanbul',
+          'istanbul_check_coverage'
+        ]);
+      }
+
+    }
+
+    else grunt.task.run([
+      'test:server',
+      'test:client'
+    ]);
+  });
+
+  grunt.registerTask('build', [
+    'clean:dist',
+    'injector:sass',
+    'concurrent:dist',
+    'injector',
+    'wiredep:client',
+    'useminPrepare',
+    'postcss',
+    'ngtemplates',
+    'concat',
+    'ngAnnotate',
+    'copy:dist',
+    'cdnify',
+    'cssmin',
+    'uglify',
+    'filerev',
+    'usemin'
+  ]);
+
+  grunt.registerTask('default', [
+    'newer:jshint',
+    'test',
+    'build'
+  ]);
+};
diff --git a/README.md b/README.md
new file mode 100755 (executable)
index 0000000..37188ca
--- /dev/null
+++ b/README.md
@@ -0,0 +1,33 @@
+# dashboard
+
+This project was generated with the [Angular Full-Stack Generator](https://github.com/DaftMonk/generator-angular-fullstack) version 3.0.0-rc5.
+
+## Getting Started
+
+### Prerequisites
+
+- [Git](https://git-scm.com/)
+- [Node.js and NPM](nodejs.org) >= v0.12.0
+- [Bower](bower.io) (`npm install --global bower`)
+- [Ruby](https://www.ruby-lang.org) and then `gem install sass`
+- [Grunt](http://gruntjs.com/) (`npm install --global grunt-cli`)
+- [MongoDB](https://www.mongodb.org/) - Keep a running daemon with `mongod`
+
+### Developing
+0. (PTKDev FIX) Run `sudo npm install -g grunt && sudo npm install --global grunt-cli && sudo npm install --global bower` `sudo apt-get install nodejs-legacy npm nodejs mongodb-server`
+
+1. Run `npm install` to install server dependencies.
+
+2. Run `bower install` to install front-end dependencies.
+
+3. Run `mongod` in a separate shell to keep an instance of the MongoDB Daemon running
+
+4. Run `grunt serve` to start the development server. It should automatically open the client in your browser when ready.
+
+## Build & development
+
+Run `grunt build` for building and `grunt serve` for preview.
+
+## Testing
+
+Running `npm test` will run the unit tests with karma.
diff --git a/bower.json b/bower.json
new file mode 100755 (executable)
index 0000000..ecff006
--- /dev/null
@@ -0,0 +1,27 @@
+{
+  "name": "dashboard",
+  "version": "0.0.0",
+  "dependencies": {
+    "angular": "~1.4.0",
+    "angular-bootstrap": "~0.13.0",
+    "angular-cookies": "~1.4.0",
+    "angular-resource": "~1.4.0",
+    "angular-sanitize": "~1.4.0",
+    "angular-translate": "~2.7.2",
+    "angular-translate-loader-static-files": "~2.8.1",
+    "angular-translate-storage-cookie": "~2.8.1",
+    "angular-ui-router": "~0.2.15",
+    "bootstrap": "~3.1.1",
+    "bootstrap-sass-official": "~3.1.1",
+    "es5-shim": "~3.0.1",
+    "font-awesome": ">=4.1.0",
+    "json3": "~3.3.1",
+    "lodash": "~2.4.1"
+  },
+  "devDependencies": {
+    "angular-mocks": "~1.4.0"
+  },
+  "resolutions": {
+    "angular-translate": "~2.8.1"
+  }
+}
diff --git a/client/.DS_Store b/client/.DS_Store
new file mode 100755 (executable)
index 0000000..5008ddf
Binary files /dev/null and b/client/.DS_Store differ
diff --git a/client/.htaccess b/client/.htaccess
new file mode 100755 (executable)
index 0000000..cb84cb9
--- /dev/null
@@ -0,0 +1,543 @@
+# Apache Configuration File
+
+# (!) Using `.htaccess` files slows down Apache, therefore, if you have access
+# to the main server config file (usually called `httpd.conf`), you should add
+# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
+
+# ##############################################################################
+# # CROSS-ORIGIN RESOURCE SHARING (CORS)                                       #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Cross-domain AJAX requests                                                 |
+# ------------------------------------------------------------------------------
+
+# Enable cross-origin AJAX requests.
+# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
+# http://enable-cors.org/
+
+# <IfModule mod_headers.c>
+#    Header set Access-Control-Allow-Origin "*"
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | CORS-enabled images                                                        |
+# ------------------------------------------------------------------------------
+
+# Send the CORS header for images when browsers request it.
+# https://developer.mozilla.org/en/CORS_Enabled_Image
+# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
+# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
+
+<IfModule mod_setenvif.c>
+    <IfModule mod_headers.c>
+        <FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
+            SetEnvIf Origin ":" IS_CORS
+            Header set Access-Control-Allow-Origin "*" env=IS_CORS
+        </FilesMatch>
+    </IfModule>
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Web fonts access                                                           |
+# ------------------------------------------------------------------------------
+
+# Allow access from all domains for web fonts
+
+<IfModule mod_headers.c>
+    <FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
+        Header set Access-Control-Allow-Origin "*"
+    </FilesMatch>
+</IfModule>
+
+
+# ##############################################################################
+# # ERRORS                                                                     #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | 404 error prevention for non-existing redirected folders                   |
+# ------------------------------------------------------------------------------
+
+# Prevent Apache from returning a 404 error for a rewrite if a directory
+# with the same name does not exist.
+# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
+# http://www.webmasterworld.com/apache/3808792.htm
+
+Options -MultiViews
+
+# ------------------------------------------------------------------------------
+# | Custom error messages / pages                                              |
+# ------------------------------------------------------------------------------
+
+# You can customize what Apache returns to the client in case of an error (see
+# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
+
+ErrorDocument 404 /404.html
+
+
+# ##############################################################################
+# # INTERNET EXPLORER                                                          #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Better website experience                                                  |
+# ------------------------------------------------------------------------------
+
+# Force IE to render pages in the highest available mode in the various
+# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
+
+<IfModule mod_headers.c>
+    Header set X-UA-Compatible "IE=edge"
+    # `mod_headers` can't match based on the content-type, however, we only
+    # want to send this header for HTML pages and not for the other resources
+    <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
+        Header unset X-UA-Compatible
+    </FilesMatch>
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Cookie setting from iframes                                                |
+# ------------------------------------------------------------------------------
+
+# Allow cookies to be set from iframes in IE.
+
+# <IfModule mod_headers.c>
+#   Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | Screen flicker                                                             |
+# ------------------------------------------------------------------------------
+
+# Stop screen flicker in IE on CSS rollovers (this only works in
+# combination with the `ExpiresByType` directives for images from below).
+
+# BrowserMatch "MSIE" brokenvary=1
+# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
+# BrowserMatch "Opera" !brokenvary
+# SetEnvIf brokenvary 1 force-no-vary
+
+
+# ##############################################################################
+# # MIME TYPES AND ENCODING                                                    #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Proper MIME types for all files                                            |
+# ------------------------------------------------------------------------------
+
+<IfModule mod_mime.c>
+
+  # Audio
+    AddType audio/mp4                                   m4a f4a f4b
+    AddType audio/ogg                                   oga ogg
+
+  # JavaScript
+    # Normalize to standard type (it's sniffed in IE anyways):
+    # http://tools.ietf.org/html/rfc4329#section-7.2
+    AddType application/javascript                      js jsonp
+    AddType application/json                            json
+
+  # Video
+    AddType video/mp4                                   mp4 m4v f4v f4p
+    AddType video/ogg                                   ogv
+    AddType video/webm                                  webm
+    AddType video/x-flv                                 flv
+
+  # Web fonts
+    AddType application/font-woff                       woff
+    AddType application/vnd.ms-fontobject               eot
+
+    # Browsers usually ignore the font MIME types and sniff the content,
+    # however, Chrome shows a warning if other MIME types are used for the
+    # following fonts.
+    AddType application/x-font-ttf                      ttc ttf
+    AddType font/opentype                               otf
+
+    # Make SVGZ fonts work on iPad:
+    # https://twitter.com/FontSquirrel/status/14855840545
+    AddType     image/svg+xml                           svg svgz
+    AddEncoding gzip                                    svgz
+
+  # Other
+    AddType application/octet-stream                    safariextz
+    AddType application/x-chrome-extension              crx
+    AddType application/x-opera-extension               oex
+    AddType application/x-shockwave-flash               swf
+    AddType application/x-web-app-manifest+json         webapp
+    AddType application/x-xpinstall                     xpi
+    AddType application/xml                             atom rdf rss xml
+    AddType image/webp                                  webp
+    AddType image/x-icon                                ico
+    AddType text/cache-manifest                         appcache manifest
+    AddType text/vtt                                    vtt
+    AddType text/x-component                            htc
+    AddType text/x-vcard                                vcf
+
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | UTF-8 encoding                                                             |
+# ------------------------------------------------------------------------------
+
+# Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
+AddDefaultCharset utf-8
+
+# Force UTF-8 for certain file formats.
+<IfModule mod_mime.c>
+    AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
+</IfModule>
+
+
+# ##############################################################################
+# # URL REWRITES                                                               #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Rewrite engine                                                             |
+# ------------------------------------------------------------------------------
+
+# Turning on the rewrite engine and enabling the `FollowSymLinks` option is
+# necessary for the following directives to work.
+
+# If your web host doesn't allow the `FollowSymlinks` option, you may need to
+# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
+# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
+
+# Also, some cloud hosting services require `RewriteBase` to be set:
+# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
+
+<IfModule mod_rewrite.c>
+    Options +FollowSymlinks
+  # Options +SymLinksIfOwnerMatch
+    RewriteEngine On
+  # RewriteBase /
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Suppressing / Forcing the "www." at the beginning of URLs                  |
+# ------------------------------------------------------------------------------
+
+# The same content should never be available under two different URLs especially
+# not with and without "www." at the beginning. This can cause SEO problems
+# (duplicate content), therefore, you should choose one of the alternatives and
+# redirect the other one.
+
+# By default option 1 (no "www.") is activated:
+# http://no-www.org/faq.php?q=class_b
+
+# If you'd prefer to use option 2, just comment out all the lines from option 1
+# and uncomment the ones from option 2.
+
+# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Option 1: rewrite www.example.com â†’ example.com
+
+<IfModule mod_rewrite.c>
+    RewriteCond %{HTTPS} !=on
+    RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
+    RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
+</IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Option 2: rewrite example.com â†’ www.example.com
+
+# Be aware that the following might not be a good idea if you use "real"
+# subdomains for certain parts of your website.
+
+# <IfModule mod_rewrite.c>
+#    RewriteCond %{HTTPS} !=on
+#    RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
+#    RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
+# </IfModule>
+
+
+# ##############################################################################
+# # SECURITY                                                                   #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Content Security Policy (CSP)                                              |
+# ------------------------------------------------------------------------------
+
+# You can mitigate the risk of cross-site scripting and other content-injection
+# attacks by setting a Content Security Policy which whitelists trusted sources
+# of content for your site.
+
+# The example header below allows ONLY scripts that are loaded from the current
+# site's origin (no inline scripts, no CDN, etc). This almost certainly won't
+# work as-is for your site!
+
+# To get all the details you'll need to craft a reasonable policy for your site,
+# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
+# see the specification: http://w3.org/TR/CSP).
+
+# <IfModule mod_headers.c>
+#    Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
+#    <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
+#        Header unset Content-Security-Policy
+#    </FilesMatch>
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | File access                                                                |
+# ------------------------------------------------------------------------------
+
+# Block access to directories without a default document.
+# Usually you should leave this uncommented because you shouldn't allow anyone
+# to surf through every directory on your server (which may includes rather
+# private places like the CMS's directories).
+
+<IfModule mod_autoindex.c>
+    Options -Indexes
+</IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Block access to hidden files and directories.
+# This includes directories used by version control systems such as Git and SVN.
+
+<IfModule mod_rewrite.c>
+    RewriteCond %{SCRIPT_FILENAME} -d [OR]
+    RewriteCond %{SCRIPT_FILENAME} -f
+    RewriteRule "(^|/)\." - [F]
+</IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Block access to backup and source files.
+# These files may be left by some text editors and can pose a great security
+# danger when anyone has access to them.
+
+<FilesMatch "(^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$">
+    Order allow,deny
+    Deny from all
+    Satisfy All
+</FilesMatch>
+
+# ------------------------------------------------------------------------------
+# | Secure Sockets Layer (SSL)                                                 |
+# ------------------------------------------------------------------------------
+
+# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
+# prevent `https://www.example.com` when your certificate only allows
+# `https://secure.example.com`.
+
+# <IfModule mod_rewrite.c>
+#    RewriteCond %{SERVER_PORT} !^443
+#    RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
+# </IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Force client-side SSL redirection.
+
+# If a user types "example.com" in his browser, the above rule will redirect him
+# to the secure version of the site. That still leaves a window of opportunity
+# (the initial HTTP connection) for an attacker to downgrade or redirect the
+# request. The following header ensures that browser will ONLY connect to your
+# server via HTTPS, regardless of what the users type in the address bar.
+# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
+
+# <IfModule mod_headers.c>
+#    Header set Strict-Transport-Security max-age=16070400;
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | Server software information                                                |
+# ------------------------------------------------------------------------------
+
+# Avoid displaying the exact Apache version number, the description of the
+# generic OS-type and the information about Apache's compiled-in modules.
+
+# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
+
+# ServerTokens Prod
+
+
+# ##############################################################################
+# # WEB PERFORMANCE                                                            #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Compression                                                                |
+# ------------------------------------------------------------------------------
+
+<IfModule mod_deflate.c>
+
+    # Force compression for mangled headers.
+    # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
+    <IfModule mod_setenvif.c>
+        <IfModule mod_headers.c>
+            SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
+            RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
+        </IfModule>
+    </IfModule>
+
+    # Compress all output labeled with one of the following MIME-types
+    # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
+    #  and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines
+    #  as `AddOutputFilterByType` is still in the core directives).
+    <IfModule mod_filter.c>
+        AddOutputFilterByType DEFLATE application/atom+xml \
+                                      application/javascript \
+                                      application/json \
+                                      application/rss+xml \
+                                      application/vnd.ms-fontobject \
+                                      application/x-font-ttf \
+                                      application/x-web-app-manifest+json \
+                                      application/xhtml+xml \
+                                      application/xml \
+                                      font/opentype \
+                                      image/svg+xml \
+                                      image/x-icon \
+                                      text/css \
+                                      text/html \
+                                      text/plain \
+                                      text/x-component \
+                                      text/xml
+    </IfModule>
+
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Content transformations                                                    |
+# ------------------------------------------------------------------------------
+
+# Prevent some of the mobile network providers from modifying the content of
+# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
+
+# <IfModule mod_headers.c>
+#    Header set Cache-Control "no-transform"
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | ETag removal                                                               |
+# ------------------------------------------------------------------------------
+
+# Since we're sending far-future expires headers (see below), ETags can
+# be removed: http://developer.yahoo.com/performance/rules.html#etags.
+
+# `FileETag None` is not enough for every server.
+<IfModule mod_headers.c>
+    Header unset ETag
+</IfModule>
+
+FileETag None
+
+# ------------------------------------------------------------------------------
+# | Expires headers (for better cache control)                                 |
+# ------------------------------------------------------------------------------
+
+# The following expires headers are set pretty far in the future. If you don't
+# control versioning with filename-based cache busting, consider lowering the
+# cache time for resources like CSS and JS to something like 1 week.
+
+<IfModule mod_expires.c>
+
+    ExpiresActive on
+    ExpiresDefault                                      "access plus 1 month"
+
+  # CSS
+    ExpiresByType text/css                              "access plus 1 year"
+
+  # Data interchange
+    ExpiresByType application/json                      "access plus 0 seconds"
+    ExpiresByType application/xml                       "access plus 0 seconds"
+    ExpiresByType text/xml                              "access plus 0 seconds"
+
+  # Favicon (cannot be renamed!)
+    ExpiresByType image/x-icon                          "access plus 1 week"
+
+  # HTML components (HTCs)
+    ExpiresByType text/x-component                      "access plus 1 month"
+
+  # HTML
+    ExpiresByType text/html                             "access plus 0 seconds"
+
+  # JavaScript
+    ExpiresByType application/javascript                "access plus 1 year"
+
+  # Manifest files
+    ExpiresByType application/x-web-app-manifest+json   "access plus 0 seconds"
+    ExpiresByType text/cache-manifest                   "access plus 0 seconds"
+
+  # Media
+    ExpiresByType audio/ogg                             "access plus 1 month"
+    ExpiresByType image/gif                             "access plus 1 month"
+    ExpiresByType image/jpeg                            "access plus 1 month"
+    ExpiresByType image/png                             "access plus 1 month"
+    ExpiresByType video/mp4                             "access plus 1 month"
+    ExpiresByType video/ogg                             "access plus 1 month"
+    ExpiresByType video/webm                            "access plus 1 month"
+
+  # Web feeds
+    ExpiresByType application/atom+xml                  "access plus 1 hour"
+    ExpiresByType application/rss+xml                   "access plus 1 hour"
+
+  # Web fonts
+    ExpiresByType application/font-woff                 "access plus 1 month"
+    ExpiresByType application/vnd.ms-fontobject         "access plus 1 month"
+    ExpiresByType application/x-font-ttf                "access plus 1 month"
+    ExpiresByType font/opentype                         "access plus 1 month"
+    ExpiresByType image/svg+xml                         "access plus 1 month"
+
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Filename-based cache busting                                               |
+# ------------------------------------------------------------------------------
+
+# If you're not using a build process to manage your filename version revving,
+# you might want to consider enabling the following directives to route all
+# requests such as `/css/style.12345.css` to `/css/style.css`.
+
+# To understand why this is important and a better idea than `*.css?v231`, read:
+# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
+
+# <IfModule mod_rewrite.c>
+#    RewriteCond %{REQUEST_FILENAME} !-f
+#    RewriteCond %{REQUEST_FILENAME} !-d
+#    RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | File concatenation                                                         |
+# ------------------------------------------------------------------------------
+
+# Allow concatenation from within specific CSS and JS files, e.g.:
+# Inside of `script.combined.js` you could have
+#   <!--#include file="libs/jquery.js" -->
+#   <!--#include file="plugins/jquery.idletimer.js" -->
+# and they would be included into this single file.
+
+# <IfModule mod_include.c>
+#    <FilesMatch "\.combined\.js$">
+#        Options +Includes
+#        AddOutputFilterByType INCLUDES application/javascript application/json
+#        SetOutputFilter INCLUDES
+#    </FilesMatch>
+#    <FilesMatch "\.combined\.css$">
+#        Options +Includes
+#        AddOutputFilterByType INCLUDES text/css
+#        SetOutputFilter INCLUDES
+#    </FilesMatch>
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | Persistent connections                                                     |
+# ------------------------------------------------------------------------------
+
+# Allow multiple requests to be sent over the same TCP connection:
+# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
+
+# Enable if you serve a lot of static content but, be aware of the
+# possible disadvantages!
+
+# <IfModule mod_headers.c>
+#    Header set Connection Keep-Alive
+# </IfModule>
diff --git a/client/.jshintrc b/client/.jshintrc
new file mode 100755 (executable)
index 0000000..8c5c42c
--- /dev/null
@@ -0,0 +1,36 @@
+{
+  "node": true,
+  "browser": true,
+  "esnext": true,
+  "bitwise": true,
+  "camelcase": true,
+  "curly": true,
+  "eqeqeq": true,
+  "immed": true,
+  "latedef": true,
+  "newcap": true,
+  "noarg": true,
+  "quotmark": "single",
+  "undef": true,
+  "unused": true,
+  "strict": true,
+  "trailing": true,
+  "smarttabs": true,
+  "globals": {
+    "jQuery": true,
+    "angular": true,
+    "console": true,
+    "$": true,
+    "_": true,
+    "moment": true,
+    "describe": true,
+    "beforeEach": true,
+    "module": true,
+    "inject": true,
+    "it": true,
+    "expect": true,
+    "browser": true,
+    "element": true,
+    "by": true
+  }
+}
diff --git a/client/app/account/account.js b/client/app/account/account.js
new file mode 100755 (executable)
index 0000000..d51d703
--- /dev/null
@@ -0,0 +1,41 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .config(function($stateProvider) {
+    $stateProvider
+      .state('login', {
+        url: '/login',
+        templateUrl: 'app/account/login/login.html',
+        controller: 'LoginCtrl'
+      })
+      .state('logout', {
+        url: '/logout?referrer',
+        referrer: 'main',
+        template: '',
+        controller: function($state, Auth) {
+          var referrer = $state.params.referrer ||
+                          $state.current.referrer ||
+                          'main';
+          Auth.logout();
+          $state.go(referrer);
+        }
+      })
+      .state('signup', {
+        url: '/signup',
+        templateUrl: 'app/account/signup/signup.html',
+        controller: 'SignupCtrl'
+      })
+      .state('settings', {
+        url: '/settings',
+        templateUrl: 'app/account/settings/settings.html',
+        controller: 'SettingsCtrl',
+        authenticate: true
+      });
+  })
+  .run(function($rootScope) {
+    $rootScope.$on('$stateChangeStart', function(event, next, nextParams, current) {
+      if (next.name === 'logout' && current && current.name && !current.authenticate) {
+        next.referrer = current.name;
+      }
+    });
+  });
diff --git a/client/app/account/login/login.controller.js b/client/app/account/login/login.controller.js
new file mode 100755 (executable)
index 0000000..a756b30
--- /dev/null
@@ -0,0 +1,29 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .controller('LoginCtrl', function($scope, Auth, $state, $window) {
+    $scope.user = {};
+    $scope.errors = {};
+
+    $scope.login = function(form) {
+      $scope.submitted = true;
+
+      if (form.$valid) {
+        Auth.login({
+          email: $scope.user.email,
+          password: $scope.user.password
+        })
+        .then(function() {
+          // Logged in, redirect to home
+          $state.go('main');
+        })
+        .catch(function(err) {
+          $scope.errors.other = err.message;
+        });
+      }
+    };
+
+    $scope.loginOauth = function(provider) {
+      $window.location.href = '/auth/' + provider;
+    };
+  });
diff --git a/client/app/account/login/login.html b/client/app/account/login/login.html
new file mode 100755 (executable)
index 0000000..f20d65a
--- /dev/null
@@ -0,0 +1,49 @@
+<navbar></navbar>
+
+<div class="container main-container">
+  <div class="row">
+    <div class="col-sm-12">
+      <h1>Login</h1>
+    </div>
+    <div class="col-sm-6">
+      <form class="form" name="form" ng-submit="login(form)" novalidate>
+
+        <div class="form-group">
+          <label>Email</label>
+
+          <input type="email" name="email" class="form-control" ng-model="user.email" required>
+        </div>
+
+        <div class="form-group">
+          <label>Password</label>
+
+          <input type="password" name="password" class="form-control" ng-model="user.password" required>
+        </div>
+
+        <div class="form-group has-error">
+          <p class="help-block" ng-show="form.email.$error.required && form.password.$error.required && submitted">
+             Please enter your email and password.
+          </p>
+          <p class="help-block" ng-show="form.email.$error.email && submitted">
+             Please enter a valid email.
+          </p>
+
+          <p class="help-block">{{ errors.other }}</p>
+        </div>
+
+        <div>
+          <button class="btn btn-inverse btn-lg btn-login" type="submit">
+            {{'LOGIN' | translate}}
+          </button>
+          <a class="btn btn-default btn-lg btn-register" ui-sref="signup">
+            {{'REGISTER' | translate}}
+          </a>
+          <a class="btn btn-lg btn-facebook pull-right" href="" ng-click="loginOauth('facebook')">
+            <i class="fa fa-facebook"></i> {{ 'ACCEDI_FACEBOOK' | translate }}
+          </a>
+        </div>
+      </form>
+    </div>
+  </div>
+  <hr>
+</div>
diff --git a/client/app/account/login/login.scss b/client/app/account/login/login.scss
new file mode 100755 (executable)
index 0000000..d3eafac
--- /dev/null
@@ -0,0 +1,30 @@
+// Colors
+// --------------------------------------------------
+
+$btnText:                           #fff;
+$btnTextAlt:                        #000;
+
+$btnFacebookBackground:             #3B5998;
+$btnFacebookBackgroundHighlight:    #133783;
+$btnTwitterBackground:              #2daddc;
+$btnTwitterBackgroundHighlight:     #0271bf;
+$btnGooglePlusBackground:           #dd4b39;
+$btnGooglePlusBackgroundHighlight:  #c53727;
+$btnGithubBackground:               #fafafa;
+$btnGithubBackgroundHighlight:      #ccc;
+
+// Social buttons
+// --------------------------------------------------
+
+.btn-facebook {
+  @include button-variant($btnText, $btnFacebookBackgroundHighlight, $btnFacebookBackgroundHighlight);
+}
+.btn-twitter {
+  @include button-variant($btnText, $btnTwitterBackground, $btnTwitterBackgroundHighlight);
+}
+.btn-google-plus {
+  @include button-variant($btnText, $btnGooglePlusBackground, $btnGooglePlusBackgroundHighlight);
+}
+.btn-github {
+  @include button-variant($btnTextAlt, $btnGithubBackground, $btnGithubBackgroundHighlight);
+}
diff --git a/client/app/account/settings/settings.controller.js b/client/app/account/settings/settings.controller.js
new file mode 100755 (executable)
index 0000000..b57829d
--- /dev/null
@@ -0,0 +1,21 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .controller('SettingsCtrl', function($scope, User, Auth) {
+    $scope.errors = {};
+
+    $scope.changePassword = function(form) {
+      $scope.submitted = true;
+      if (form.$valid) {
+        Auth.changePassword($scope.user.oldPassword, $scope.user.newPassword)
+          .then(function() {
+            $scope.message = 'Password successfully changed.';
+          })
+          .catch(function() {
+            form.password.$setValidity('mongoose', false);
+            $scope.errors.other = 'Incorrect password';
+            $scope.message = '';
+          });
+      }
+    };
+  });
diff --git a/client/app/account/settings/settings.html b/client/app/account/settings/settings.html
new file mode 100755 (executable)
index 0000000..ec4e2d8
--- /dev/null
@@ -0,0 +1,39 @@
+<navbar></navbar>
+
+<div class="container">
+  <div class="row">
+    <div class="col-sm-12">
+      <h1>Change Password</h1>
+    </div>
+    <div class="col-sm-12">
+      <form class="form" name="form" ng-submit="changePassword(form)" novalidate>
+
+        <div class="form-group">
+          <label>Current Password</label>
+
+          <input type="password" name="password" class="form-control" ng-model="user.oldPassword"
+                 mongoose-error/>
+          <p class="help-block" ng-show="form.password.$error.mongoose">
+              {{ errors.other }}
+          </p>
+        </div>
+
+        <div class="form-group">
+          <label>New Password</label>
+
+          <input type="password" name="newPassword" class="form-control" ng-model="user.newPassword"
+                 ng-minlength="3"
+                 required/>
+          <p class="help-block"
+             ng-show="(form.newPassword.$error.minlength || form.newPassword.$error.required) && (form.newPassword.$dirty || submitted)">
+            Password must be at least 3 characters.
+          </p>
+        </div>
+
+        <p class="help-block"> {{ message }} </p>
+
+        <button class="btn btn-lg btn-primary" type="submit">Save changes</button>
+      </form>
+    </div>
+  </div>
+</div>
diff --git a/client/app/account/signup/signup.controller.js b/client/app/account/signup/signup.controller.js
new file mode 100755 (executable)
index 0000000..6bf8864
--- /dev/null
@@ -0,0 +1,68 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .controller('SignupCtrl', function($scope, Auth, $state, $window, datepickerPopupConfig) {
+    $scope.user = {};
+    $scope.errors = {};
+
+    $scope.$watch('user', function() {
+      $scope.user.name = $scope.user.firstName + ' ' + $scope.user.lastName;
+    }, true);
+
+    $scope.datepickers = {
+      birthDate: false
+    };
+
+    $scope.open = function($event, which, whichnot) {
+      $event.preventDefault();
+      $event.stopPropagation();
+
+      $scope.datepickers[which]= true;
+      $scope.datepickers[whichnot]= false;
+    };
+
+    $scope.dateOptions = {
+      'year-format': 'yy',
+      'starting-day': 1,
+      'show-weeks': false
+    };
+
+    datepickerPopupConfig.showButtonBar = false;
+    datepickerPopupConfig.appendToBody = false;
+
+    $scope.minDate = new Date();
+    $scope.minDateFine = new Date();
+
+    $scope.register = function(form) {
+      $scope.submitted = true;
+
+      if (form.$valid) {
+        Auth.createUser({
+          name: $scope.user.name,
+          firstNname: $scope.user.firstName,
+          lastName: $scope.user.lastName,
+          birthDate: $scope.user.birthDate,
+          email: $scope.user.email,
+          password: $scope.user.password
+        })
+        .then(function() {
+          // Account created, redirect to home
+          $state.go('main');
+        })
+        .catch(function(err) {
+          err = err.data;
+          $scope.errors = {};
+
+          // Update validity of form fields that match the mongoose errors
+          angular.forEach(err.errors, function(error, field) {
+            form[field].$setValidity('mongoose', false);
+            $scope.errors[field] = error.message;
+          });
+        });
+      }
+    };
+
+    $scope.loginOauth = function(provider) {
+      $window.location.href = '/auth/' + provider;
+    };
+  });
diff --git a/client/app/account/signup/signup.html b/client/app/account/signup/signup.html
new file mode 100755 (executable)
index 0000000..a4b8a73
--- /dev/null
@@ -0,0 +1,115 @@
+<navbar></navbar>
+
+<div class="container main-container">
+  <div class="row bg-grey">
+    <div class="col-sm-12">
+      <h1>{{ 'SIGNUP' | translate }}</h1>
+    </div>
+    <form class="form" name="form" ng-submit="register(form)" novalidate>
+      <div class="col-sm-6">
+
+        <br>
+        <div class="form-group" ng-class="{ 'has-success': form.firstname.$valid && submitted,'has-error': form.firstname.$invalid && submitted }">
+          <label>{{ 'NOME' | translate }}</label>
+
+          <input type="text" name="firstname" class="form-control" ng-model="user.firstName" required/>
+          <p class="help-block" ng-show="form.firstname.$error.required && submitted">
+            {{ 'NOME_REQUIRED' | translate }}
+          </p>
+        </div>
+
+        <div class="form-group" ng-class="{ 'has-success': form.lastname.$valid && submitted, 'has-error': form.lastname.$invalid && submitted }">
+          <label>{{ 'COGNOME' | translate }}</label>
+
+          <input type="text" name="lastname" class="form-control" ng-model="user.lastName" required/>
+          <p class="help-block" ng-show="form.lastname.$error.required && submitted">
+            {{ 'COGNOME_REQUIRED' | translate }}
+          </p>
+        </div>
+
+        <div class="form-group" ng-class="{ 'has-success': form.birthDate.$valid && submitted, 'has-error': form.birthDate.$invalid && submitted }">
+          <label>{{ 'DATA_NASCITA' | translate }}</label>
+
+          <div class="input-group">
+            <div class="input-group datepicker-group">
+              <input type="text" class="form-control" style="background: #FFF;"
+              name="birthDate"
+              ng-model="user.birthDate"
+              datepicker-popup="dd-MM-yyyy"
+              is-open="datepickers.birthDate"
+              datepicker-options="dateOptions"
+              ng-readonly="true"
+              ng-required="true"
+              ng-click="open($event,'birthDate')">
+              <span class="input-group-btn">
+                <button class="btn btn-default" ng-click="open($event,'birthDate')"><i class="glyphicon glyphicon-calendar"></i></button>
+              </span>
+            </div>
+          </div>
+          <p class="help-block" ng-show="form.birthDate.$error.required && submitted">
+            {{ 'DATA_NASCITA_REQUIRED' | translate }}
+          </p>
+        </div>
+
+        
+      </div>
+
+      <div class="col-sm-6">
+
+        <br>
+
+        <div class="form-group" ng-class="{ 'has-success': form.email.$valid && submitted, 'has-error': form.email.$invalid && submitted }">
+          <label>{{ 'EMAIL' | translate }}</label>
+
+          <input type="email" name="email" class="form-control" ng-model="user.email"
+          required
+          mongoose-error/>
+          <p class="help-block" ng-show="form.email.$error.email && submitted">
+            {{ 'EMAIL_VALID' | translate }}
+          </p>
+          <p class="help-block" ng-show="form.email.$error.required && submitted">
+            {{ 'EMAIL_REQUIRED' | translate }}
+          </p>
+          <p class="help-block" ng-show="form.email.$error.mongoose">
+            {{ errors.email }}
+          </p>
+        </div>
+
+        <div class="form-group" ng-class="{ 'has-success': form.password.$valid && submitted, 'has-error': form.password.$invalid && submitted }">
+          <label>{{ 'PASSWORD' | translate }}</label>
+
+          <input type="password" name="password" class="form-control" ng-model="user.password" ng-minlength="3" required mongoose-error/>
+          <p class="help-block" ng-show="(form.password.$error.minlength || form.password.$error.required) && submitted">
+            Password must be at least 3 characters.
+          </p>
+          <p class="help-block" ng-show="form.password.$error.mongoose">
+            {{ errors.password }}
+          </p>
+        </div>
+
+        <div class="form-group" ng-class="{ 'has-success': form.password === form.passwordConf && submitted, 'has-error': form.password !== form.passwordConf }">
+          <label>{{ 'CONFERMA_PASSWORD' | translate }}</label>
+
+          <input type="password" name="passwordConf" class="form-control" ng-model="user.passwordConf" ng-minlength="3" required/>
+          <p class="help-block" ng-show="(user.password !== user.passwordConf || form.passwordConf.$error.minlength || form.passwordConf.$error.required) && submitted">
+            {{ 'PASSWORD_MATCH' | translate }}
+          </p>
+        </div>
+
+        <div>
+          <button class="btn btn-default btn-lg btn-register" type="submit">
+            {{ 'SIGNUP' | translate }}
+          </button>
+          <a class="btn btn-inverse btn-lg btn-login" ui-sref="login">
+            {{ 'LOGIN' | translate }}
+          </a>
+          <a class="btn btn-lg btn-facebook pull-right" href="" ng-click="loginOauth('facebook')">
+            <i class="fa fa-facebook"></i> {{ 'ACCEDI_FACEBOOK' | translate }}
+          </a>
+        </div>
+        <br>
+      </div>
+    </form>
+  </div>
+  <hr>
+</div>
diff --git a/client/app/admin/admin.controller.js b/client/app/admin/admin.controller.js
new file mode 100755 (executable)
index 0000000..25147e3
--- /dev/null
@@ -0,0 +1,20 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .controller('AdminCtrl', function($scope, $http, Auth, User) {
+
+    // Use the User $resource to fetch all users
+    $scope.users = User.query();
+
+    $scope.delete = function(user) {
+      User.remove({ id: user._id });
+      $scope.users.splice(this.$index, 1);
+    };
+
+    $scope.user = {};
+
+    $scope.create = function(user) {
+       User.save(user);
+       $scope.users.push(user);
+    };
+  });
diff --git a/client/app/admin/admin.html b/client/app/admin/admin.html
new file mode 100755 (executable)
index 0000000..425e176
--- /dev/null
@@ -0,0 +1,32 @@
+<navbar></navbar>
+
+<div class="container">
+  <p>The delete user and user index api routes are restricted to users with the 'admin' role.</p>
+  <ul class="list-group">
+    <li class="list-group-item" ng-repeat="user in users">
+        <strong>{{user.name}}</strong><br>
+        <span class="text-muted">{{user.email}}</span>
+        <a ng-click="delete(user)" class="trash"><span class="glyphicon glyphicon-trash pull-right"></span></a>
+    </li>
+  </ul>
+  <form class="form" name="form" ng-submit="create(user)" novalidate>
+
+    <div class="form-group">
+      <label>Email</label>
+
+      <input type="email" name="email" class="form-control" ng-model="user.email" required>
+    </div>
+
+    <div class="form-group">
+      <label>Password</label>
+
+      <input type="password" name="password" class="form-control" ng-model="user.password" required>
+    </div>
+
+    <div>
+      <button class="btn btn-inverse btn-lg btn-login" type="submit">
+        Save
+      </button>
+    </div>
+</form>
+</div>
diff --git a/client/app/admin/admin.js b/client/app/admin/admin.js
new file mode 100755 (executable)
index 0000000..35995b0
--- /dev/null
@@ -0,0 +1,11 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .config(function($stateProvider) {
+    $stateProvider
+      .state('admin', {
+        url: '/admin',
+        templateUrl: 'app/admin/admin.html',
+        controller: 'AdminCtrl'
+      });
+  });
diff --git a/client/app/admin/admin.scss b/client/app/admin/admin.scss
new file mode 100755 (executable)
index 0000000..a6f536d
--- /dev/null
@@ -0,0 +1 @@
+.trash { color:rgb(209, 91, 71); }
diff --git a/client/app/app.js b/client/app/app.js
new file mode 100755 (executable)
index 0000000..0e0e4b5
--- /dev/null
@@ -0,0 +1,133 @@
+'use strict';
+
+angular.module('dashboardApp', [
+  'ngCookies',
+  'ngResource',
+  'ngSanitize',
+  'ui.router',
+  'ui.bootstrap',
+  'pascalprecht.translate'
+])
+  .config(function($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider, $translateProvider) {
+    $urlRouterProvider
+      .otherwise('/');
+
+    $locationProvider.html5Mode(true);
+    $httpProvider.interceptors.push('authInterceptor');
+
+    $translateProvider.useLoaderCache(true).useStaticFilesLoader({
+      prefix: 'assets/langs/locale-',
+      suffix: '.json'
+    })
+    .useCookieStorage()
+    .storagePrefix('twotoc_')
+    .useSanitizeValueStrategy('sanitize')
+    .preferredLanguage('it');
+  })
+
+  .factory('authInterceptor', function($rootScope, $q, $cookies, $injector) {
+    var state;
+    return {
+      // Add authorization token to headers
+      request: function(config) {
+        config.headers = config.headers || {};
+        if ($cookies.get('token')) {
+          config.headers.Authorization = 'Bearer ' + $cookies.get('token');
+        }
+        return config;
+      },
+
+      // Intercept 401s and redirect you to login
+      responseError: function(response) {
+        if (response.status === 401) {
+          (state || (state = $injector.get('$state'))).go('login');
+          // remove any stale tokens
+          $cookies.remove('token');
+          return $q.reject(response);
+        }
+        else {
+          return $q.reject(response);
+        }
+      }
+    };
+  })
+
+  .directive('resizewin', ['$window', function($window) {
+    return function (scope) {
+      var w = angular.element($window);
+      scope.getWindowDimensions = function () {
+          return {
+              'h': w.height(),
+              'w': w.width()
+          };
+      };
+      scope.$watch(scope.getWindowDimensions, function (newValue) {
+          scope.windowHeight = newValue.h;
+          scope.windowWidth = newValue.w;
+
+          scope.style = function (offset) {
+              return {
+                  'height': (newValue.h - offset) + 'px'
+                      // 'width': (newValue.w - 100) + 'px'
+              };
+          };
+
+      }, true);
+
+      w.bind('resize', function () {
+          scope.$apply();
+      });
+    };
+  }])
+/*
+  .directive('mratio', ['$window',  function($window) {
+      return {
+        restrict: 'A',
+        link: function(scope, element, attrs) {
+
+          var w = angular.element($window);
+          scope.getWindowDimensions = function () {
+              return {
+                  'w': element.width()
+              };
+          };
+
+          scope.$watch(scope.getWindowDimensions, function (newValue, oldValue) {
+            mRatio(scope, element, attrs);
+          });
+
+          mRatio(scope, element, attrs);
+
+          function mRatio(scope, element, attrs) {
+            var image = new Image(); // or document.createElement('img')
+            var el_width, el_height, width, height, ratio;
+            el_width = element.width();
+            el_height = element.height();
+            image.onload = function() {
+              width = this.width;
+              height = this.height;
+              ratio = el_width / width;
+              var r_height = height * ratio;
+              if (el_height / height != ratio) {
+                attrs.$set('style', 'height: ' + r_height + 'px');
+              }
+            };
+            image.src = attrs.src;
+          }
+        }
+      }
+    }])
+*/
+  .run(function($rootScope, $state, Auth) {
+    // Redirect to login if route requires auth and the user is not logged in
+    $rootScope.$on('$stateChangeStart', function(event, next) {
+      if (next.authenticate) {
+        Auth.isLoggedIn(function(loggedIn) {
+          if (!loggedIn) {
+            event.preventDefault();
+            $state.go('login');
+          }
+        });
+      }
+    });
+  });
diff --git a/client/app/app.scss b/client/app/app.scss
new file mode 100755 (executable)
index 0000000..2a9222b
--- /dev/null
@@ -0,0 +1,46 @@
+$icon-font-path: "../bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/";
+$fa-font-path: "../bower_components/font-awesome/fonts";
+
+@import '../bower_components/bootstrap-sass-official/vendor/assets/stylesheets/bootstrap';
+@import '../bower_components/font-awesome/scss/font-awesome';
+
+@import url('http://fonts.googleapis.com/css?family=Oswald:300,400,700');
+
+@mixin center-center {
+    position: inherit;
+    top: 50%;
+    left: 50%;
+    -webkit-transform: translateX(-50%) translateY(-50%);
+    -ms-transform: translateX(-50%) translateY(-50%);
+    transform: translateX(-50%) translateY(-50%);
+}
+
+
+
+/**
+ * App-wide Styles
+ */
+
+.browserupgrade {
+  margin: 0.2em 0;
+  background: #ccc;
+  color: #000;
+  padding: 0.2em 0;
+}
+
+// Component styles are injected through grunt
+// injector
+@import 'account/login/login.scss';
+@import 'admin/admin.scss';
+@import 'main/main.scss';
+@import 'partecipa/partecipa.scss';
+@import '../components/footer/footer.scss';
+@import '../components/modal/modal.scss';
+// endinjector
+
+
+input, textarea {
+  border: 0 !important;
+  border-radius: 0 !important;
+  box-shadow: none !important;
+}
diff --git a/client/app/main/main.controller.js b/client/app/main/main.controller.js
new file mode 100755 (executable)
index 0000000..d8187e0
--- /dev/null
@@ -0,0 +1,37 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .controller('MainCtrl', function($scope, $http, $state) {
+
+    $scope.slides = [
+      {
+        image: 'soccer-ball.jpg',
+        text: 'PARTECIPA, ORGANIZZA, GUARDA, SOCIALIZZA'
+      },
+      {
+        image: 'got.jpg',
+        text: 'DAJE, SU, MEH, AHO'
+      }
+    ];
+
+    $scope.search = {};
+    $scope.getLocation = function(val) {
+      return $http.get('//maps.googleapis.com/maps/api/geocode/json', {
+        params: {
+          address: val,
+          sensor: false
+        }
+      }).then(function(response){
+        return response.data.results.map(function(item){
+          $scope.search.lat = (item.geometry.viewport.northeast.lat + item.geometry.viewport.southwest.lat) / 2;
+          $scope.search.lng = (item.geometry.viewport.northeast.lng + item.geometry.viewport.southwest.lng) / 2;
+          return item.formatted_address;
+        });
+      });
+    };
+
+    $scope.searchShow = function() {
+      console.log($scope.search);
+      $state.go('partecipa', {lat: $scope.search.lat, lng: $scope.search.lng});
+    };
+  });
diff --git a/client/app/main/main.controller.spec.js b/client/app/main/main.controller.spec.js
new file mode 100755 (executable)
index 0000000..38dccf2
--- /dev/null
@@ -0,0 +1,31 @@
+'use strict';
+
+describe('Controller: MainCtrl', function() {
+
+  // load the controller's module
+  beforeEach(module('dashboardApp'));
+  beforeEach(module('stateMock'));
+
+  var MainCtrl;
+  var scope;
+  var state;
+  var $httpBackend;
+
+  // Initialize the controller and a mock scope
+  beforeEach(inject(function(_$httpBackend_, $controller, $rootScope, $state) {
+    $httpBackend = _$httpBackend_;
+    $httpBackend.expectGET('/api/shows')
+      .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']);
+
+    scope = $rootScope.$new();
+    state = $state;
+    MainCtrl = $controller('MainCtrl', {
+      $scope: scope
+    });
+  }));
+
+  it('should attach a list of shows to the scope', function() {
+    $httpBackend.flush();
+    expect(scope.awesomeThings.length).toBe(4);
+  });
+});
diff --git a/client/app/main/main.html b/client/app/main/main.html
new file mode 100755 (executable)
index 0000000..fae909e
--- /dev/null
@@ -0,0 +1,32 @@
+<navbar></navbar>
+
+
+
+<div class="main-container">
+
+      <div id="homeSlider" resizewin ng-style="style(260)">
+        <carousel interval="5000">
+          <slide ng-repeat="slide in slides" active="slide.active">
+            <img ng-src="/assets/images/{{ slide.image }}">
+            <div class="carousel-caption">
+              <h4 ng-bind-html="slide.text"></h4>
+            </div>
+          </slide>
+        </carousel>
+
+        <div id="homesearch">
+          <form ng-submit="searchShow()">
+            <div class="input-group input-group-lg">
+              <input type="text" ng-model="search.place" typeahead="address for address in getLocation($viewValue)" typeahead-loading="loadingLocations" class="form-control" placeholder="In quale città vuoi bussare?">
+              <span class="input-group-btn">
+                <button class="btn btn-default" type="submit">CERCA</button>
+              </span>
+            </div>
+          </form>
+        </div>
+        
+      </div>
+
+</div>
+
+<footer class="homeFooter"></footer>
diff --git a/client/app/main/main.js b/client/app/main/main.js
new file mode 100755 (executable)
index 0000000..dd1cddd
--- /dev/null
@@ -0,0 +1,11 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .config(function($stateProvider) {
+    $stateProvider
+      .state('main', {
+        url: '/',
+        templateUrl: 'app/main/main.html',
+        controller: 'MainCtrl'
+      });
+  });
diff --git a/client/app/main/main.scss b/client/app/main/main.scss
new file mode 100755 (executable)
index 0000000..18d4a99
--- /dev/null
@@ -0,0 +1,356 @@
+body {
+    font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
+    font-size: 16px;
+    font-weight: 200;
+    color: #666;
+    background-color: #FFF;
+}
+
+.thing-form {
+  margin: 20px 0;
+}
+
+#banner {
+  border-bottom: none;
+  margin-top: -20px;
+}
+
+#banner h1 {
+  font-size: 60px;
+  line-height: 1;
+  letter-spacing: -1px;
+}
+
+.btn-lg, .btn-group-lg > .btn, .btn {
+  border-radius: 0;
+  border: 0;
+  font-family: "Oswald";
+  outline: none !important;
+}
+
+.btn-default {
+  background: #000;
+  border: 0;
+  color: #FFF;
+}
+
+.bg-grey {
+  background-color: #EDEFED;
+}
+
+
+
+#homeSlider {
+  width: 100%;
+  /*height: 300px;*/
+  overflow: hidden;
+  position: relative;
+  .carousel, .carousel-inner, .carousel-inner > .item {
+    height: inherit;
+    top: 0;
+  }
+  .carousel-inner > .item > img, .carousel-inner > .item > a > img {
+    width: 100%;
+    top: 0;
+    /*filter: blur(1px);*/
+    @include center-center();
+  }
+  .carousel-control.right, .carousel-control.left {
+    height: 100%;
+    width: 10%;
+    min-width: 40px;
+  }
+  .carousel-caption {
+    bottom: 50%;
+    h4 {
+      font-family: "Oswald", sans-serif;
+      font-size: 50px;
+      text-shadow: 0 0 20px #000;
+    }
+  }
+}
+
+#homesearch {
+  position: absolute;
+  bottom: 20%;
+  left: 50%;
+  margin-left: -250px;
+  width: 500px;
+  padding: 25px 70px;
+  background-color: rgba(0,0,0,0.5);
+
+  input, button {
+    border-radius: 0;
+  }
+  button {
+    background: #000;
+    color: #FFF;
+    font-family: "Oswald";
+    border: 0;
+  }
+}
+
+.datepicker-group ul li > div > table {
+  outline: none;
+}
+
+.hero-unit {
+  position: relative;
+  padding: 30px 15px;
+  color: #F5F5F5;
+  text-align: center;
+  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
+  background: #4393B9;
+}
+
+h1, h2 {
+  font-family: 'Oswald', sans-serif;
+  color: #333;
+  font-weight: 300;
+}
+
+h1 {
+  font-size: 26px;
+}
+h2 {
+  font-size: 22px;
+}
+h3 {
+  font-size: 18px;
+}
+h1.title {
+  margin: 30px 0 0;
+}
+h3.title {
+  margin: 10px 0 0;
+}
+
+hr {
+  border-top: 1px solid #666;
+  margin-top: 50px;
+}
+
+.box-default {
+  background-color: #EDEFED;
+  padding: 30px;
+}
+.box-user {
+  h1 {
+    margin: 0 0 10px;
+    text-transform: uppercase;
+  }
+  .glyphicon.userinfo {
+    font-size: 24px;
+    background: #FFF;
+    color: #EDEFED;
+    border-radius: 50%;
+    margin-right: 10px;
+    padding: 5px;
+    vertical-align: middle;
+  }
+  .rating.userinfo {
+    font-size: 28px;
+    color: #FFF;
+    margin-left: 4px;
+    vertical-align: middle;
+  }
+  .box-comment {
+    font-size: 12px;
+    background: #D6D6D6;
+    padding: 15px;
+    position: relative;
+    margin-bottom: 20px;
+
+    p {
+      margin: 0;
+    }
+
+    ul {
+      margin-bottom: 0;
+      b {
+        color: #000;
+      }
+      .rating {
+        color: #FFF;
+      }
+    }
+  }
+  .box-comment:after {
+    right: 100%;
+    top: 15px;
+    border: solid transparent;
+    content: " ";
+    height: 0;
+    width: 0;
+    position: absolute;
+    pointer-events: none;
+    border-color: rgba(214, 214, 214, 0);
+    border-right-color: #D6D6D6;
+    border-width: 10px;
+  }
+}
+
+.main-container {
+  margin-top: 130px;
+  padding-bottom: 130px;
+}
+.main-container.container {
+  padding-bottom: 40px;
+}
+
+.navbar-text {
+  margin-left: 15px;
+}
+
+.navbar-brand {
+  background: url('/assets/images/logo.png') no-repeat;
+  background-size: 70%;
+  color: transparent !important;
+  height: 130px;
+  margin: 0;
+  width: 160px;
+}
+
+.navbar .container {
+  position: relative;
+}
+
+.navbar .navbar-left {
+  width: 560px;
+  margin: 0 auto !important;
+  float: none !important;
+
+}
+
+.navbar-default {
+  border: 0;
+}
+
+.navbar-default .navbar-nav.navbar-right li a {
+    font-family: 'Oswald', sans-serif;
+    font-weight: 500;
+    height: auto;
+    padding: 2px 5px;
+    font-size: 14px;
+    line-height: 20px;
+    text-transform: uppercase;
+    border: 2px solid transparent;
+    margin: 10px 2px 0;
+    color: #000;
+    transition: border-color 0.2s ease-out;
+}
+.navbar-default .navbar-nav.navbar-right li a:hover {
+  border-color: #000;
+}
+
+.navbar > .container .navbar-brand {
+  margin-left: 0;
+}
+
+
+
+
+
+.navbar.open {
+    -webkit-transform: translate(0, 0);
+    transform: translate(0, 0);
+}
+
+.navbar, main {
+    -webkit-transition: -webkit-transform 0.3s ease-out;
+    transition: transform 0.3s ease-out;
+}
+
+.navbar h4,
+.navbar-default .navbar-nav.navbar-left li a {
+    font-family: 'Oswald', sans-serif;
+    font-weight: 300;
+    height: auto;
+    padding: 0 20px 10px;
+    font-size: 20px;
+    line-height: 20px;
+    padding-top: 80px;
+    cursor: pointer;
+}
+.navbar-default .navbar-nav.navbar-left li.active a {
+    background: #000;
+    color: #FFF;
+}
+
+.navbar h4 {
+    background-color: white;
+    color: #3367D6;
+}
+
+.navbar-default .navbar-nav.navbar-left {
+    padding: 0;
+    margin: 0;
+    list-style-type: none;
+}
+
+.navbar-default .navbar-nav.navbar-left li a {
+    display: block;
+    text-decoration: none;
+    color: black;
+    transition: background-color 0.2s ease-out;
+    white-space: nowrap;
+}
+
+
+.navbar-default .navbar-nav.navbar-left li::before {
+    content: none;
+}
+
+/*
+.navbar-default .navbar-nav.navbar-left li a:hover {
+    background-color: rgba(90, 90, 90, 0.2);
+}
+*/
+
+
+.navbar-default .navbar-nav.navbar-left li.active a {
+    background-color: rgba(0, 0, 0, 1);
+    color: #FFF;
+}
+
+@media all and (min-width: 320px) {
+  .navbar h4,
+  .navbar-default .navbar-nav.navbar-left li a {
+      padding: 0 10px 8px;
+      font-size: 18px;
+      line-height: 18px;
+      padding-top: 80px;
+      cursor: pointer;
+  }
+}
+
+@media all and (min-width: 768px) {
+  .navbar-default .navbar-nav.navbar-left li a {
+      padding: 0 20px 10px;
+      font-size: 20px;
+      line-height: 20px;
+      padding-top: 80px;
+      cursor: pointer;
+  }
+  .navbar .navbar-right {
+    position: absolute;
+    float: none;
+    margin-right: 0 !important;
+    top: 0;
+    right: 0;
+  }
+  .navbar-default  {
+      z-index: 1;
+      position: fixed;
+      top: 0;
+      bottom: 0;
+      width: 100%;
+      height: 130px;
+      background-color: #FFF;
+      color: #fefefe;
+      overflow-y: auto;
+      z-index: 9;
+  }
+}
+
+
diff --git a/client/app/partecipa/partecipa.controller.js b/client/app/partecipa/partecipa.controller.js
new file mode 100755 (executable)
index 0000000..ab4b740
--- /dev/null
@@ -0,0 +1,82 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .controller('PartecipaCtrl', function($scope, $http, datepickerPopupConfig, $stateParams) {
+
+  var baseLimit = 7;
+  $scope.limit = baseLimit;
+  var d = new Date();
+  d.setHours(0,0,0,0);
+  $scope.dataInizio = new Date(d);
+
+  $scope.posters = [];
+  var route;
+  $scope.loadCount = 0;
+  $scope.loadShows = function() {
+    route = '/api/shows?limit=' + $scope.limit;
+    if ($scope.dataInizio) {
+      route += '&date=' + $scope.dataInizio;
+    }
+    if ($scope.category) {
+      route += '&category=' + $scope.category;
+    }
+    if ($stateParams.lat && $stateParams.lng) {
+      route += '&lat=' + $stateParams.lat + '&lng=' + $stateParams.lng;
+    }
+    if ($scope.fulltext) {
+      route += '&fulltext=' + $scope.fulltext;
+    }
+       $http.get(route).then(function(response) {
+         $scope.posters = response.data;
+         $scope.limit += $scope.limit;
+      if ($scope.loadCount % 2 === 0) {
+        $scope.limit++;
+      }
+      $scope.loadCount++;
+       });
+  };
+  $scope.loadShows();
+
+
+  var posterCount = 0;
+  $scope.pCount = function(index) {
+    posterCount = index % 10 === 0 ? 0 : posterCount + 1;
+    return posterCount === 0 || posterCount === 6;
+  };
+
+  $scope.datepickers = {
+    dataInizio: false,
+    dataFine: false
+  };
+
+  $scope.open = function($event, which, whichnot) {
+    $event.preventDefault();
+    $event.stopPropagation();
+
+    $scope.datepickers[which]= true;
+    $scope.datepickers[whichnot]= false;
+  };
+
+  $scope.dateOptions = {
+    'year-format': 'yy',
+    'starting-day': 1,
+    'show-weeks': false
+  };
+
+  datepickerPopupConfig.showButtonBar = false;
+  datepickerPopupConfig.appendToBody = false;
+
+  $scope.minDate = new Date();
+  $scope.minDateFine = new Date();
+
+  $scope.searchShows = function() {
+    $scope.limit = baseLimit;
+    $scope.loadCount = 0;
+       $scope.loadShows();
+  };
+
+  // load categories
+  $http.get('/api/categories?active=true').then(function(response) {
+    $scope.categories = response.data;
+  });
+});
diff --git a/client/app/partecipa/partecipa.html b/client/app/partecipa/partecipa.html
new file mode 100755 (executable)
index 0000000..c65f569
--- /dev/null
@@ -0,0 +1,69 @@
+<navbar></navbar>
+
+<div class="main-container">
+    <div class="row">
+      <div class="col-sm-12 text-center bg-grey">
+        <form class="form-inline" ng-submit="searchShows()">
+          <div class="form-group">
+            <input type="text" class="form-control" placeholder="Cosa vuoi guardare?" ng-model="fulltext">
+          </div>
+          <div class="form-group">
+            <div class="input-group">
+              <div class="input-group datepicker-group">
+                <input type="text" class="form-control" placeholder="Quando?"
+                name="dataInizio"
+                ng-model="dataInizio"
+                min-date="minDate"
+                max-date="maxDateInizio"
+                datepicker-popup="dd-MM-yyyy"
+                is-open="datepickers.dataInizio"
+                datepicker-options="dateOptions"
+                ng-readonly="true"
+                ng-required="true"
+                ng-click="open($event,'dataInizio','dataFine')">
+                <span class="input-group-btn">
+                  <button class="btn btn-default" ng-click="open($event,'dataInizio','dataFine')"><i class="glyphicon glyphicon-calendar"></i></button>
+                </span>
+              </div>
+            </div>
+          </div>
+          <div class="form-group">
+            <select ng-model="category" ng-options="category._id as category.name for category in categories" class="form-control"></select>
+          </div>
+          <div class="form-group">
+            <button type="submit" class="btn btn-default">{{ 'CERCA' | translate }}</button>
+            <button type="button" ng-click="resetForm()" class="btn btn-default">{{ 'CANCELLA FILTRI' | translate }}</button>
+          </div>
+        </form>
+      </div>
+    </div>
+</div>
+<div class="container">
+       <div class="row">
+       <div class="col-sm-12 text-center">
+               <h1 class="title">PARTECIPA</h1>
+               <h3 class="title">Scopri gli eventi in programma questa settimana</h3>
+       </div>
+       <div class="poster-view">
+               <div class="poster" ng-repeat="poster in posters" ng-class="{'poster-wide': pCount($index)}">
+                       <img ng-src="/uploads/{{ poster.image }}">
+          <div class="poster_date">{{ poster.date | date: 'd' }}<br>{{ poster.date | date: 'MMM' }}</div>
+          <div class="poster_overlay">
+            <div class="poster_content">
+              <img src="/assets/images/avatar.png" class="avatar">
+              <rating ng-model="poster.userRating" max="5" readonly="true" class="rating"></rating>
+              <p class="user">{{ poster.user.name }} propone:</p>
+              <h3>{{ poster.title }}</h3>
+              <p class="descr">{{ poster.description }}</p>
+              <a class="btn btn-default" ui-sref="show({id: poster._id})">{{'PARTECIPA' | translate}}</a>
+            </div>
+          </div>
+               </div>
+       </div>
+      <div class="col-sm-12 text-center">
+          <button class="btn btn-default" ng-click="loadShows()">{{'CARICA ALTRO' | translate}}</button><br><br>
+      </div>
+       </div>
+</div>
+
+<footer></footer>
\ No newline at end of file
diff --git a/client/app/partecipa/partecipa.js b/client/app/partecipa/partecipa.js
new file mode 100755 (executable)
index 0000000..0dfc53b
--- /dev/null
@@ -0,0 +1,20 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .config(function($stateProvider) {
+    $stateProvider
+    .state('partecipa', {
+        url: '/partecipa',
+        templateUrl: 'app/partecipa/partecipa.html',
+        controller: 'PartecipaCtrl',
+        params: {
+          lng: null,
+          lat: null
+        }
+      })
+      .state('show', {
+        url: '/:id',
+        templateUrl: 'app/partecipa/show/show.html',
+        controller: 'ShowCtrl'
+      });
+  });
diff --git a/client/app/partecipa/partecipa.scss b/client/app/partecipa/partecipa.scss
new file mode 100755 (executable)
index 0000000..8b805b5
--- /dev/null
@@ -0,0 +1,100 @@
+.poster-view {
+       margin-top: 30px;
+}
+.poster {
+       overflow: hidden;
+       position: relative;
+       display: flex;
+       height: 350px;
+       width: 33.3%;
+       float: left;
+       border: 10px solid #FFF;
+       box-sizing: border-box;
+       align-items: center;
+       justify-content: center;
+}
+.poster_show {
+       width: 100% !important;
+       border: 0 !important;
+       margin-bottom: 30px;
+}
+.poster > img {
+       min-width: 100%;
+       min-height: 100%;
+       flex-shrink: 0;
+}
+.poster.poster-wide {
+       width: 66.6%;
+}
+
+@media all and (max-width: 768px) {
+       .poster {
+               width: 100% !important;
+       }
+}
+
+.poster_date {
+       position: absolute;
+       background: #FFF;
+       font-family: "Oswald", sans-serif;
+       font-size: 22px;
+       font-weight: 300;
+       color: #000;
+       top: 0;
+       left: 20px;
+       text-transform: uppercase;
+       text-align: center;
+       padding: 10px 15px;
+}
+
+.poster:hover {
+       .poster_overlay {
+               display: block;
+               opacity: 1;
+       }
+       .poster_date {
+               display: none;
+       }
+} 
+
+.poster_overlay {
+       background-color: rgba(255,255,255,0.8);
+       position: absolute;
+       top: 0;
+       left: 0;
+       width: 100%;
+       height: 100%;
+       display:none;
+       opacity: 0;
+
+       .poster_content {
+               text-align: center;
+               position: absolute;
+               top: 20px;
+               width: 100%;
+               color: #000;
+               padding: 20px;
+
+               .avatar {
+                       display: block;
+                       margin: 0 auto;
+               }
+
+               .user {
+                       font-size: 16px;
+                       margin: 0 0 5px;
+               }
+
+               h3 {
+                       margin: 0 0 10px;
+                       font-family: "Oswald", sans-serif;
+                       text-transform: uppercase;
+                       font-weight: 700;
+                       font-size: 20px;
+               }
+
+               .rating {
+                       font-size: 18px;
+               }
+       }
+}
\ No newline at end of file
diff --git a/client/app/partecipa/show/show.controller.js b/client/app/partecipa/show/show.controller.js
new file mode 100755 (executable)
index 0000000..9b3d64e
--- /dev/null
@@ -0,0 +1,25 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .controller('ShowCtrl', function($scope, $http, $stateParams) {
+    
+    $scope.posters = [];
+    var showId = $stateParams.id;
+
+    $http.get('/api/shows/' + showId).then(function(response) {
+       $scope.item = response.data;
+       $scope.diffDate = Math.abs(new Date() - Date.parse($scope.item.user.dateSubscribed));
+
+       $scope.loadComments();
+    });
+
+    var baseLimit = 5;
+    $scope.limit = baseLimit;
+
+    $scope.loadComments = function() {
+       $http.get('/api/comments?user=' + $scope.item.user._id + '&active=true&limit=' + $scope.limit).then(function(response) {
+               $scope.comments = response.data;
+               $scope.limit += $scope.limit;
+       });
+    };
+  });
diff --git a/client/app/partecipa/show/show.html b/client/app/partecipa/show/show.html
new file mode 100755 (executable)
index 0000000..a4e6439
--- /dev/null
@@ -0,0 +1,51 @@
+<navbar></navbar>
+
+
+<div class="main-container container">
+       <div class="row">
+      <div class="col-sm-8">
+        <div class="poster poster_show"><img ng-src="/uploads/{{ item.image }}"></div>
+        <h1>{{ item.title | uppercase }}</h1>
+        {{ item.description }}
+        <hr>
+        <h2>{{ 'INFORMAZIONI_AGGIUNTIVE' | translate }}</h2>
+        {{ item.info }}
+        <hr>
+        <h2>{{ 'SERVIZI' | translate }}</h2>
+        <ul class="list-inline clearfix">
+          <li class="col-sm-4" ng-repeat="service in item.services">{{ service | translate }}</li>
+        </ul>
+        <div class="box-default box-user">
+          <div class="row">
+            <div class="pull-left">
+              <img src="/assets/images/avatar.png" width="150" class="avatar">
+            </div>
+            <h1>{{ item.user.name }}</h1>
+            <p><span class="glyphicon glyphicon-map-marker userinfo"></span> {{ item.user.place }}</p>
+            <p><span class="glyphicon glyphicon-time userinfo"></span> su Two Toc da {{ diffDate | date: 'd' }} giorni</p>
+            <p><rating ng-model="item.user.rating" max="5" readonly="true" class="rating userinfo"></rating> {{ item.user.ratingCount }} voti</p>
+          </div>
+          <div class="row" ng-repeat="comment in comments">
+            <div class="col-xs-2">
+              <img src="/assets/images/avatar.png" class="avatar">
+            </div>
+            <div class="col-xs-10 box-comment">
+              <p><b>{{ comment.user.name }}</b></p>
+              <p>{{ comment.text }}</p>
+              <ul class="list-inline">
+                <li><b>{{ 'VOTO' | translate }}</b><rating ng-model="comment.rating" max="5" readonly="true" class="rating"></rating></li>
+                <li><b>{{ 'PULIZIA' | translate }}</b><rating ng-model="comment.ratingPulizia" max="5" readonly="true" class="rating"></rating></li>
+                <li><b>{{ 'ATMOSFERA' | translate }}</b><rating ng-model="comment.ratingAtmosfera" max="5" readonly="true" class="rating"></rating></li>
+              </ul>
+            </div>
+          </div>
+          <div class="row text-center">
+              <button class="btn btn-default" ng-click="loadComments()">{{'CARICA ALTRO' | translate}}</button><br><br>
+          </div>
+        </div>
+      </div>
+      <div class="col-sm-4"></div>
+       </div>
+</div>
+
+<footer></footer>
\ No newline at end of file
diff --git a/client/assets/.DS_Store b/client/assets/.DS_Store
new file mode 100755 (executable)
index 0000000..8af1da6
Binary files /dev/null and b/client/assets/.DS_Store differ
diff --git a/client/assets/images/avatar.png b/client/assets/images/avatar.png
new file mode 100755 (executable)
index 0000000..a6ad376
Binary files /dev/null and b/client/assets/images/avatar.png differ
diff --git a/client/assets/images/got.jpg b/client/assets/images/got.jpg
new file mode 100755 (executable)
index 0000000..a638c74
Binary files /dev/null and b/client/assets/images/got.jpg differ
diff --git a/client/assets/images/icon-tv-full.png b/client/assets/images/icon-tv-full.png
new file mode 100755 (executable)
index 0000000..6bdaa22
Binary files /dev/null and b/client/assets/images/icon-tv-full.png differ
diff --git a/client/assets/images/icon-tv.png b/client/assets/images/icon-tv.png
new file mode 100755 (executable)
index 0000000..b0bdfea
Binary files /dev/null and b/client/assets/images/icon-tv.png differ
diff --git a/client/assets/images/logo.png b/client/assets/images/logo.png
new file mode 100755 (executable)
index 0000000..ff81b2c
Binary files /dev/null and b/client/assets/images/logo.png differ
diff --git a/client/assets/images/soccer-ball.jpg b/client/assets/images/soccer-ball.jpg
new file mode 100755 (executable)
index 0000000..468b3a6
Binary files /dev/null and b/client/assets/images/soccer-ball.jpg differ
diff --git a/client/assets/images/yeoman.png b/client/assets/images/yeoman.png
new file mode 100755 (executable)
index 0000000..fe2b154
Binary files /dev/null and b/client/assets/images/yeoman.png differ
diff --git a/client/assets/langs/locale-it.json b/client/assets/langs/locale-it.json
new file mode 100755 (executable)
index 0000000..2ab7f1d
--- /dev/null
@@ -0,0 +1,19 @@
+{
+  "LANG_en": "English",
+  "LANG_it": "Italiano",
+  "LOGIN": "Accedi",
+  "SIGNUP": "Iscriviti",
+  "REGISTER": "Registrati",
+  "ACCEDI_FACEBOOK": "Accedi con Facebook",
+  "CONFERMA_PASSWORD": "Conferma Password",
+  "INFORMAZIONI_AGGIUNTIVE": "Informazioni aggiuntive",
+  "POLLICI_32": "TV 32 pollici",
+  "ANIMALI_AMMESSI": "Animali ammessi",
+  "WIFI": "WiFi",
+  "FUMATORI_AMMESSI": "Fumatori ammessi",
+  "ARIA_CONDIZIONATA": "Aria condizionata",
+  "SERVIZI": "Servizi",
+  "VOTO": "Voto",
+  "PULIZIA": "Pulizia",
+  "ATMOSFERA": "Atmosfera"
+}
diff --git a/client/components/auth/auth.service.js b/client/components/auth/auth.service.js
new file mode 100755 (executable)
index 0000000..d4b6b3f
--- /dev/null
@@ -0,0 +1,170 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .factory('Auth', function Auth($http, User, $cookies, $q) {
+    /**
+     * Return a callback or noop function
+     *
+     * @param  {Function|*} cb - a 'potential' function
+     * @return {Function}
+     */
+    var safeCb = function(cb) {
+      return (angular.isFunction(cb)) ? cb : angular.noop;
+    },
+
+    currentUser = {};
+
+    if ($cookies.get('token')) {
+      currentUser = User.get();
+    }
+
+    return {
+
+      /**
+       * Authenticate user and save token
+       *
+       * @param  {Object}   user     - login info
+       * @param  {Function} callback - optional, function(error, user)
+       * @return {Promise}
+       */
+      login: function(user, callback) {
+        return $http.post('/auth/local', {
+          email: user.email,
+          password: user.password
+        })
+        .then(function(res) {
+          $cookies.put('token', res.data.token);
+          currentUser = User.get();
+          return currentUser.$promise;
+        })
+        .then(function(user) {
+          safeCb(callback)(null, user);
+          return user;
+        })
+        .catch(function(err) {
+          this.logout();
+          safeCb(callback)(err.data);
+          return $q.reject(err.data);
+        }.bind(this));
+      },
+
+      /**
+       * Delete access token and user info
+       */
+      logout: function() {
+        $cookies.remove('token');
+        currentUser = {};
+      },
+
+      /**
+       * Create a new user
+       *
+       * @param  {Object}   user     - user info
+       * @param  {Function} callback - optional, function(error, user)
+       * @return {Promise}
+       */
+      createUser: function(user, callback) {
+        return User.save(user,
+          function(data) {
+            $cookies.put('token', data.token);
+            currentUser = User.get();
+            return safeCb(callback)(null, user);
+          },
+          function(err) {
+            this.logout();
+            return safeCb(callback)(err);
+          }.bind(this)).$promise;
+      },
+
+      /**
+       * Change password
+       *
+       * @param  {String}   oldPassword
+       * @param  {String}   newPassword
+       * @param  {Function} callback    - optional, function(error, user)
+       * @return {Promise}
+       */
+      changePassword: function(oldPassword, newPassword, callback) {
+        return User.changePassword({ id: currentUser._id }, {
+          oldPassword: oldPassword,
+          newPassword: newPassword
+        }, function() {
+          return safeCb(callback)(null);
+        }, function(err) {
+          return safeCb(callback)(err);
+        }).$promise;
+      },
+
+      /**
+       * Gets all available info on a user
+       *   (synchronous|asynchronous)
+       *
+       * @param  {Function|*} callback - optional, funciton(user)
+       * @return {Object|Promise}
+       */
+      getCurrentUser: function(callback) {
+        if (arguments.length === 0) {
+          return currentUser;
+        }
+
+        var value = (currentUser.hasOwnProperty('$promise')) ? currentUser.$promise : currentUser;
+        return $q.when(value)
+          .then(function(user) {
+            safeCb(callback)(user);
+            return user;
+          }, function() {
+            safeCb(callback)({});
+            return {};
+          });
+      },
+
+      /**
+       * Check if a user is logged in
+       *   (synchronous|asynchronous)
+       *
+       * @param  {Function|*} callback - optional, function(is)
+       * @return {Bool|Promise}
+       */
+      isLoggedIn: function(callback) {
+        if (arguments.length === 0) {
+          return currentUser.hasOwnProperty('role');
+        }
+
+        return this.getCurrentUser(null)
+          .then(function(user) {
+            var is = user.hasOwnProperty('role');
+            safeCb(callback)(is);
+            return is;
+          });
+      },
+
+       /**
+        * Check if a user is an admin
+        *   (synchronous|asynchronous)
+        *
+        * @param  {Function|*} callback - optional, function(is)
+        * @return {Bool|Promise}
+        */
+      isAdmin: function(callback) {
+        if (arguments.length === 0) {
+          return currentUser.role === 'admin';
+        }
+
+        return this.getCurrentUser(null)
+          .then(function(user) {
+            var is = user.role === 'admin';
+            safeCb(callback)(is);
+            return is;
+          });
+      },
+
+      /**
+       * Get auth token
+       *
+       * @return {String} - a token string used for authenticating
+       */
+      getToken: function() {
+        return $cookies.get('token');
+      }
+    };
+  });
diff --git a/client/components/auth/user.service.js b/client/components/auth/user.service.js
new file mode 100755 (executable)
index 0000000..60f52e5
--- /dev/null
@@ -0,0 +1,22 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .factory('User', function ($resource) {
+    return $resource('/api/users/:id/:controller', {
+      id: '@_id'
+    },
+    {
+      changePassword: {
+        method: 'PUT',
+        params: {
+          controller:'password'
+        }
+      },
+      get: {
+        method: 'GET',
+        params: {
+          id:'me'
+        }
+      }
+    });
+  });
diff --git a/client/components/footer/footer.directive.js b/client/components/footer/footer.directive.js
new file mode 100755 (executable)
index 0000000..4a20a16
--- /dev/null
@@ -0,0 +1,12 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .directive('footer', function () {
+    return {
+      templateUrl: 'components/footer/footer.html',
+      restrict: 'E',
+      link: function (scope, element) {
+        element.addClass('footer');
+      }
+    };
+  });
diff --git a/client/components/footer/footer.html b/client/components/footer/footer.html
new file mode 100755 (executable)
index 0000000..d58b3e3
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="container">
+  <ul class="navbar">
+       <li><a>Contatti</a></li>
+       <li><a>Iscriviti</a></li>
+       <li><a>Seguici</a><br>F T</li>
+       <li><a>FAQ</a></li>
+       <li><a>Termini e Condizioni</a></li>
+  </ul>
+</div>
diff --git a/client/components/footer/footer.scss b/client/components/footer/footer.scss
new file mode 100755 (executable)
index 0000000..44debe2
--- /dev/null
@@ -0,0 +1,29 @@
+footer.footer {
+    padding: 30px 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 130px;
+    background: #EDEFED;
+    margin-top: 0 !important;
+}
+footer.homeFooter {
+  position: fixed;
+}
+
+
+footer .navbar li {
+  list-style: none;
+  float: left;
+  position: relative;
+  display: block;
+  width: 33%;
+
+  a {
+    font-family: "Oswald", sans-serif;
+    font-size: 20px;
+    font-weight: 300;
+    color: #666;
+    text-decoration: none;
+  }
+}
\ No newline at end of file
diff --git a/client/components/modal/modal.html b/client/components/modal/modal.html
new file mode 100755 (executable)
index 0000000..f04d0db
--- /dev/null
@@ -0,0 +1,11 @@
+<div class="modal-header">
+  <button ng-if="modal.dismissable" type="button" ng-click="$dismiss()" class="close">&times;</button>
+  <h4 ng-if="modal.title" ng-bind="modal.title" class="modal-title"></h4>
+</div>
+<div class="modal-body">
+  <p ng-if="modal.text" ng-bind="modal.text"></p>
+  <div ng-if="modal.html" ng-bind-html="modal.html"></div>
+</div>
+<div class="modal-footer">
+  <button ng-repeat="button in modal.buttons" ng-class="button.classes" ng-click="button.click($event)" ng-bind="button.text" class="btn"></button>
+</div>
diff --git a/client/components/modal/modal.scss b/client/components/modal/modal.scss
new file mode 100755 (executable)
index 0000000..3b0b9d9
--- /dev/null
@@ -0,0 +1,25 @@
+.modal-primary,
+.modal-info,
+.modal-success,
+.modal-warning,
+.modal-danger {
+  .modal-header {
+    color: #fff;
+    border-radius: 5px 5px 0 0;
+  }
+}
+.modal-primary .modal-header {
+  background: $brand-primary;
+}
+.modal-info .modal-header {
+  background: $brand-info;
+}
+.modal-success .modal-header {
+  background: $brand-success;
+}
+.modal-warning .modal-header {
+  background: $brand-warning;
+}
+.modal-danger .modal-header {
+  background: $brand-danger;
+}
diff --git a/client/components/modal/modal.service.js b/client/components/modal/modal.service.js
new file mode 100755 (executable)
index 0000000..45d2816
--- /dev/null
@@ -0,0 +1,77 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .factory('Modal', function ($rootScope, $modal) {
+    /**
+     * Opens a modal
+     * @param  {Object} scope      - an object to be merged with modal's scope
+     * @param  {String} modalClass - (optional) class(es) to be applied to the modal
+     * @return {Object}            - the instance $modal.open() returns
+     */
+    function openModal(scope, modalClass) {
+      var modalScope = $rootScope.$new();
+      scope = scope || {};
+      modalClass = modalClass || 'modal-default';
+
+      angular.extend(modalScope, scope);
+
+      return $modal.open({
+        templateUrl: 'components/modal/modal.html',
+        windowClass: modalClass,
+        scope: modalScope
+      });
+    }
+
+    // Public API here
+    return {
+
+      /* Confirmation modals */
+      confirm: {
+
+        /**
+         * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)')
+         * @param  {Function} del - callback, ran when delete is confirmed
+         * @return {Function}     - the function to open the modal (ex. myModalFn)
+         */
+        delete: function(del) {
+          del = del || angular.noop;
+
+          /**
+           * Open a delete confirmation modal
+           * @param  {String} name   - name or info to show on modal
+           * @param  {All}           - any additional args are passed straight to del callback
+           */
+          return function() {
+            var args = Array.prototype.slice.call(arguments),
+                name = args.shift(),
+                deleteModal;
+
+            deleteModal = openModal({
+              modal: {
+                dismissable: true,
+                title: 'Confirm Delete',
+                html: '<p>Are you sure you want to delete <strong>' + name + '</strong> ?</p>',
+                buttons: [{
+                  classes: 'btn-danger',
+                  text: 'Delete',
+                  click: function(e) {
+                    deleteModal.close(e);
+                  }
+                }, {
+                  classes: 'btn-default',
+                  text: 'Cancel',
+                  click: function(e) {
+                    deleteModal.dismiss(e);
+                  }
+                }]
+              }
+            }, 'modal-danger');
+
+            deleteModal.result.then(function(event) {
+              del.apply(event, args);
+            });
+          };
+        }
+      }
+    };
+  });
diff --git a/client/components/mongoose-error/mongoose-error.directive.js b/client/components/mongoose-error/mongoose-error.directive.js
new file mode 100755 (executable)
index 0000000..e12a680
--- /dev/null
@@ -0,0 +1,17 @@
+'use strict';
+
+/**
+ * Removes server error when user updates input
+ */
+angular.module('dashboardApp')
+  .directive('mongooseError', function () {
+    return {
+      restrict: 'A',
+      require: 'ngModel',
+      link: function(scope, element, attrs, ngModel) {
+        element.on('keydown', function() {
+          return ngModel.$setValidity('mongoose', true);
+        });
+      }
+    };
+  });
diff --git a/client/components/navbar/navbar.controller.js b/client/components/navbar/navbar.controller.js
new file mode 100755 (executable)
index 0000000..e411c3b
--- /dev/null
@@ -0,0 +1,32 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .controller('NavbarCtrl', function ($scope, Auth) {
+    $scope.menu = [
+      {
+        'title': 'Home',
+        'state': 'main'
+      },
+      {
+        'title': 'Partecipa',
+        'state': 'partecipa'
+      },
+      {
+        'title': 'Organizza',
+        'state': 'organizza'
+      },
+      {
+        'title': 'Community',
+        'state': 'community'
+      },
+      {
+        'title': 'Come funziona',
+        'state': 'come-funziona'
+      }
+    ];
+
+    $scope.isCollapsed = true;
+    $scope.isLoggedIn = Auth.isLoggedIn;
+    $scope.isAdmin = Auth.isAdmin;
+    $scope.getCurrentUser = Auth.getCurrentUser;
+  });
diff --git a/client/components/navbar/navbar.directive.js b/client/components/navbar/navbar.directive.js
new file mode 100755 (executable)
index 0000000..f02bd10
--- /dev/null
@@ -0,0 +1,10 @@
+'use strict';
+
+angular.module('dashboardApp')
+  .directive('navbar', function () {
+    return {
+      templateUrl: 'components/navbar/navbar.html',
+      restrict: 'E',
+      controller: 'NavbarCtrl'
+    };
+  });
diff --git a/client/components/navbar/navbar.html b/client/components/navbar/navbar.html
new file mode 100755 (executable)
index 0000000..baf3932
--- /dev/null
@@ -0,0 +1,29 @@
+<div class="navbar navbar-default navbar-static-top" ng-controller="NavbarCtrl">
+  <div class="container">
+    <div class="navbar-header">
+      <button class="navbar-toggle" type="button" ng-click="isCollapsed = !isCollapsed">
+        <span class="sr-only">Toggle navigation</span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+      </button>
+      <a href="/" class="navbar-brand">dashboard</a>
+    </div>
+    <div collapse="isCollapsed" class="navbar-collapse collapse" id="navbar-main">
+      <ul class="nav navbar-nav navbar-left">
+        <li ng-repeat="item in menu" ui-sref-active="active">
+            <a ui-sref="{{item.state}}">{{item.title}}</a>
+        </li>
+        <li ng-show="isAdmin()" ui-sref-active="active"><a ui-sref="admin">Admin</a></li>
+      </ul>
+
+      <ul class="nav navbar-nav navbar-right">
+        <li ng-hide="isLoggedIn()" ui-sref-active="active"><a ui-sref="login">Accedi</a></li>
+        <li ng-hide="isLoggedIn()" ui-sref-active="active"><a ui-sref="signup">Pubblica</a></li>
+        <li ng-show="isLoggedIn()"><p class="navbar-text">Hello {{ getCurrentUser().name }}</p> </li>
+        <li ng-show="isLoggedIn()" ui-sref-active="active"><a ui-sref="settings"><span class="glyphicon glyphicon-cog"></span></a></li>
+        <li ng-show="isLoggedIn()"><a ui-sref="logout">Logout</a></li>
+      </ul>
+    </div>
+  </div>
+</div>
diff --git a/client/components/ui-router/ui-router.mock.js b/client/components/ui-router/ui-router.mock.js
new file mode 100755 (executable)
index 0000000..a5a1bf4
--- /dev/null
@@ -0,0 +1,34 @@
+'use strict';
+
+angular.module('stateMock', []);
+angular.module('stateMock').service('$state', function($q) {
+    this.expectedTransitions = [];
+
+    this.transitionTo = function(stateName) {
+        if (this.expectedTransitions.length > 0) {
+            var expectedState = this.expectedTransitions.shift();
+            if (expectedState !== stateName) {
+                throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName);
+            }
+        } else {
+            throw Error('No more transitions were expected! Tried to transition to ' + stateName);
+        }
+        console.log('Mock transition to: ' + stateName);
+        var deferred = $q.defer();
+        var promise = deferred.promise;
+        deferred.resolve();
+        return promise;
+    };
+
+    this.go = this.transitionTo;
+
+    this.expectTransitionTo = function(stateName) {
+        this.expectedTransitions.push(stateName);
+    };
+
+    this.ensureAllTransitionsHappened = function() {
+        if (this.expectedTransitions.length > 0) {
+            throw Error('Not all transitions happened!');
+        }
+    };
+});
diff --git a/client/favicon.ico b/client/favicon.ico
new file mode 100755 (executable)
index 0000000..8a163fb
Binary files /dev/null and b/client/favicon.ico differ
diff --git a/client/index.html b/client/index.html
new file mode 100755 (executable)
index 0000000..9e4b393
--- /dev/null
@@ -0,0 +1,86 @@
+<!doctype html>
+<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <base href="/">
+    <title></title>
+    <meta name="description" content="">
+    <meta name="viewport" content="width=device-width">
+    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
+    <!-- build:css(client) app/vendor.css -->
+      <!-- bower:css -->
+      <!-- endbower -->
+    <!-- endbuild -->
+    <!-- build:css({.tmp,client}) app/app.css -->
+      <link rel="stylesheet" href="app/app.css">
+      <!-- injector:css -->
+      <!-- endinjector -->
+    <!-- endbuild -->
+  </head>
+  <body ng-app="dashboardApp">
+    <!--[if lt IE 7]>
+      <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
+    <![endif]-->
+
+    <!-- Add your site or application content here -->
+    <div ui-view=""></div>
+
+    <!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
+    <script>
+      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+      })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+
+      ga('create', 'UA-XXXXX-X');
+      ga('send', 'pageview');
+    </script>
+
+    <!--[if lt IE 9]>
+    <script src="bower_components/es5-shim/es5-shim.js"></script>
+    <script src="bower_components/json3/lib/json3.min.js"></script>
+    <![endif]-->
+    <!-- build:js({client,node_modules}) app/vendor.js -->
+      <!-- bower:js -->
+      <script src="bower_components/jquery/dist/jquery.js"></script>
+      <script src="bower_components/angular/angular.js"></script>
+      <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
+      <script src="bower_components/angular-cookies/angular-cookies.js"></script>
+      <script src="bower_components/angular-resource/angular-resource.js"></script>
+      <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
+      <script src="bower_components/angular-translate/angular-translate.js"></script>
+      <script src="bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js"></script>
+      <script src="bower_components/angular-translate-storage-cookie/angular-translate-storage-cookie.js"></script>
+      <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
+      <script src="bower_components/lodash/dist/lodash.compat.js"></script>
+      <!-- endbower -->
+    <!-- endbuild -->
+    <!-- build:js({.tmp,client}) app/app.js -->
+      <script src="app/app.js"></script>
+      <!-- injector:js -->
+      <script src="app/account/account.js"></script>
+      <script src="app/account/login/login.controller.js"></script>
+      <script src="app/account/settings/settings.controller.js"></script>
+      <script src="app/account/signup/signup.controller.js"></script>
+      <script src="app/admin/admin.controller.js"></script>
+      <script src="app/admin/admin.js"></script>
+      <script src="app/main/main.controller.js"></script>
+      <script src="app/main/main.js"></script>
+      <script src="app/partecipa/partecipa.controller.js"></script>
+      <script src="app/partecipa/partecipa.js"></script>
+      <script src="app/partecipa/show/show.controller.js"></script>
+      <script src="components/auth/auth.service.js"></script>
+      <script src="components/auth/user.service.js"></script>
+      <script src="components/footer/footer.directive.js"></script>
+      <script src="components/modal/modal.service.js"></script>
+      <script src="components/mongoose-error/mongoose-error.directive.js"></script>
+      <script src="components/navbar/navbar.controller.js"></script>
+      <script src="components/navbar/navbar.directive.js"></script>
+      <!-- endinjector -->
+    <!-- endbuild -->
+  </body>
+</html>
diff --git a/client/robots.txt b/client/robots.txt
new file mode 100755 (executable)
index 0000000..9417495
--- /dev/null
@@ -0,0 +1,3 @@
+# robotstxt.org
+
+User-agent: *
diff --git a/client/uploads/97211.jpg b/client/uploads/97211.jpg
new file mode 100755 (executable)
index 0000000..c6716c6
Binary files /dev/null and b/client/uploads/97211.jpg differ
diff --git a/client/uploads/Pixels.jpg b/client/uploads/Pixels.jpg
new file mode 100755 (executable)
index 0000000..031ab57
Binary files /dev/null and b/client/uploads/Pixels.jpg differ
diff --git a/client/uploads/calcio.jpg b/client/uploads/calcio.jpg
new file mode 100755 (executable)
index 0000000..2cd12df
Binary files /dev/null and b/client/uploads/calcio.jpg differ
diff --git a/client/uploads/champions.jpg b/client/uploads/champions.jpg
new file mode 100755 (executable)
index 0000000..707e659
Binary files /dev/null and b/client/uploads/champions.jpg differ
diff --git a/client/uploads/got.jpg b/client/uploads/got.jpg
new file mode 100755 (executable)
index 0000000..9d5a486
Binary files /dev/null and b/client/uploads/got.jpg differ
diff --git a/client/uploads/jurassic.jpg b/client/uploads/jurassic.jpg
new file mode 100755 (executable)
index 0000000..eaf8ecf
Binary files /dev/null and b/client/uploads/jurassic.jpg differ
diff --git a/e2e/account/login/login.po.js b/e2e/account/login/login.po.js
new file mode 100755 (executable)
index 0000000..1186cdb
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var LoginPage = function() {
+  this.form = element(by.css('.form'));
+  this.form.email = this.form.element(by.model('user.email'));
+  this.form.password = this.form.element(by.model('user.password'));
+  this.form.submit = this.form.element(by.css('.btn-login'));
+
+  this.login = function(data) {
+    for (var prop in data) {
+      var formElem = this.form[prop];
+      if (data.hasOwnProperty(prop) && formElem && typeof formElem.sendKeys === 'function') {
+        formElem.sendKeys(data[prop]);
+      }
+    }
+
+    this.form.submit.click();
+  };
+};
+
+module.exports = new LoginPage();
+
diff --git a/e2e/account/login/login.spec.js b/e2e/account/login/login.spec.js
new file mode 100755 (executable)
index 0000000..42e8f29
--- /dev/null
@@ -0,0 +1,62 @@
+'use strict';
+
+var config = browser.params;
+var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');
+
+describe('Login View', function() {
+  var page;
+
+  var loadPage = function() {
+    browser.get(config.baseUrl + '/login');
+    page = require('./login.po');
+  };
+
+  var testUser = {
+    name: 'Test User',
+    email: 'test@example.com',
+    password: 'test'
+  };
+
+  beforeEach(function(done) {
+    UserModel.removeAsync()
+      .then(function() {
+        return UserModel.createAsync(testUser);
+      })
+      .then(loadPage)
+      .finally(done);
+  });
+
+  it('should include login form with correct inputs and submit button', function() {
+    expect(page.form.email.getAttribute('type')).toBe('email');
+    expect(page.form.email.getAttribute('name')).toBe('email');
+    expect(page.form.password.getAttribute('type')).toBe('password');
+    expect(page.form.password.getAttribute('name')).toBe('password');
+    expect(page.form.submit.getAttribute('type')).toBe('submit');
+    expect(page.form.submit.getText()).toBe('Login');
+  });
+
+  describe('with local auth', function() {
+
+    it('should login a user and redirecting to "/"', function() {
+      page.login(testUser);
+
+      var navbar = require('../../components/navbar/navbar.po');
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
+      expect(navbar.navbarAccountGreeting.getText()).toBe('Hello ' + testUser.name);
+    });
+
+    it('should indicate login failures', function() {
+      page.login({
+        email: testUser.email,
+        password: 'badPassword'
+      });
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/login');
+
+      var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding'));
+      expect(helpBlock.getText()).toBe('This password is not correct.');
+    });
+
+  });
+});
diff --git a/e2e/account/logout/logout.spec.js b/e2e/account/logout/logout.spec.js
new file mode 100755 (executable)
index 0000000..8a3e0b1
--- /dev/null
@@ -0,0 +1,46 @@
+'use strict';
+
+var config = browser.params;
+var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');
+
+describe('Logout View', function() {
+  var login = function(user) {
+    browser.get(config.baseUrl + '/login');
+    require('../login/login.po').login(user);
+  };
+
+  var testUser = {
+    name: 'Test User',
+    email: 'test@example.com',
+    password: 'test'
+  };
+
+  beforeEach(function(done) {
+    UserModel.removeAsync()
+      .then(function() {
+        return UserModel.createAsync(testUser);
+      })
+      .then(function() {
+        return login(testUser);
+      })
+      .finally(done);
+  });
+
+  describe('with local auth', function() {
+
+    it('should logout a user and redirecting to "/"', function() {
+      var navbar = require('../../components/navbar/navbar.po');
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
+      expect(navbar.navbarAccountGreeting.getText()).toBe('Hello ' + testUser.name);
+
+      browser.get(config.baseUrl + '/logout');
+
+      navbar = require('../../components/navbar/navbar.po');
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
+      expect(navbar.navbarAccountGreeting.isDisplayed()).toBe(false);
+    });
+
+  });
+});
diff --git a/e2e/account/signup/signup.po.js b/e2e/account/signup/signup.po.js
new file mode 100755 (executable)
index 0000000..3a496c6
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var SignupPage = function() {
+  this.form = element(by.css('.form'));
+  this.form.name = this.form.element(by.model('user.name'));
+  this.form.email = this.form.element(by.model('user.email'));
+  this.form.password = this.form.element(by.model('user.password'));
+  this.form.submit = this.form.element(by.css('.btn-register'));
+
+  this.signup = function(data) {
+    for (var prop in data) {
+      var formElem = this.form[prop];
+      if (data.hasOwnProperty(prop) && formElem && typeof formElem.sendKeys === 'function') {
+        formElem.sendKeys(data[prop]);
+      }
+    }
+
+    this.form.submit.click();
+  };
+};
+
+module.exports = new SignupPage();
+
diff --git a/e2e/account/signup/signup.spec.js b/e2e/account/signup/signup.spec.js
new file mode 100755 (executable)
index 0000000..54beb0f
--- /dev/null
@@ -0,0 +1,62 @@
+'use strict';
+
+var config = browser.params;
+var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');
+
+describe('Signup View', function() {
+  var page;
+
+  var loadPage = function() {
+    browser.manage().deleteAllCookies();
+    browser.get(config.baseUrl + '/signup');
+    page = require('./signup.po');
+  };
+
+  var testUser = {
+    name: 'Test',
+    email: 'test@example.com',
+    password: 'test'
+  };
+
+  beforeEach(function() {
+    loadPage();
+  });
+
+  it('should include signup form with correct inputs and submit button', function() {
+    expect(page.form.name.getAttribute('type')).toBe('text');
+    expect(page.form.name.getAttribute('name')).toBe('name');
+    expect(page.form.email.getAttribute('type')).toBe('email');
+    expect(page.form.email.getAttribute('name')).toBe('email');
+    expect(page.form.password.getAttribute('type')).toBe('password');
+    expect(page.form.password.getAttribute('name')).toBe('password');
+    expect(page.form.submit.getAttribute('type')).toBe('submit');
+    expect(page.form.submit.getText()).toBe('Sign up');
+  });
+
+  describe('with local auth', function() {
+
+    beforeAll(function(done) {
+      UserModel.removeAsync().then(done);
+    });
+
+    it('should signup a new user, log them in, and redirecting to "/"', function() {
+      page.signup(testUser);
+
+      var navbar = require('../../components/navbar/navbar.po');
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
+      expect(navbar.navbarAccountGreeting.getText()).toBe('Hello ' + testUser.name);
+    });
+
+    it('should indicate signup failures', function() {
+      page.signup(testUser);
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/signup');
+      expect(page.form.email.getAttribute('class')).toContain('ng-invalid-mongoose');
+
+      var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding'));
+      expect(helpBlock.getText()).toBe('The specified email address is already in use.');
+    });
+
+  });
+});
diff --git a/e2e/components/navbar/navbar.po.js b/e2e/components/navbar/navbar.po.js
new file mode 100755 (executable)
index 0000000..b904342
--- /dev/null
@@ -0,0 +1,16 @@
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var NavbarComponent = function() {
+  this.navbar = element(by.css('.navbar'));
+  this.navbarHeader = this.navbar.element(by.css('.navbar-header'));
+  this.navbarNav = this.navbar.element(by.css('#navbar-main .nav.navbar-nav:not(.navbar-right)'));
+  this.navbarAccount = this.navbar.element(by.css('#navbar-main .nav.navbar-nav.navbar-right'));
+  this.navbarAccountGreeting = this.navbarAccount.element(by.binding('getCurrentUser().name'));
+};
+
+module.exports = new NavbarComponent();
diff --git a/e2e/main/main.po.js b/e2e/main/main.po.js
new file mode 100755 (executable)
index 0000000..6718608
--- /dev/null
@@ -0,0 +1,15 @@
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var MainPage = function() {
+  this.heroEl = element(by.css('.hero-unit'));
+  this.h1El = this.heroEl.element(by.css('h1'));
+  this.imgEl = this.heroEl.element(by.css('img'));
+};
+
+module.exports = new MainPage();
+
diff --git a/e2e/main/main.spec.js b/e2e/main/main.spec.js
new file mode 100755 (executable)
index 0000000..5728449
--- /dev/null
@@ -0,0 +1,18 @@
+'use strict';
+
+var config = browser.params;
+
+describe('Main View', function() {
+  var page;
+
+  beforeEach(function() {
+    browser.get(config.baseUrl + '/');
+    page = require('./main.po');
+  });
+
+  it('should include jumbotron with correct data', function() {
+    expect(page.h1El.getText()).toBe('\'Allo, \'Allo!');
+    expect(page.imgEl.getAttribute('src')).toMatch(/yeoman.png$/);
+    expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman');
+  });
+});
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100755 (executable)
index 0000000..effb1b3
--- /dev/null
@@ -0,0 +1,94 @@
+// Karma configuration
+// http://karma-runner.github.io/0.10/config/configuration-file.html
+
+module.exports = function(config) {
+  config.set({
+    // base path, that will be used to resolve files and exclude
+    basePath: '',
+
+    // testing framework to use (jasmine/mocha/qunit/...)
+    frameworks: ['jasmine'],
+
+    // list of files / patterns to load in the browser
+    files: [
+      // bower:js
+      'client/bower_components/jquery/dist/jquery.js',
+      'client/bower_components/angular/angular.js',
+      'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
+      'client/bower_components/angular-cookies/angular-cookies.js',
+      'client/bower_components/angular-resource/angular-resource.js',
+      'client/bower_components/angular-sanitize/angular-sanitize.js',
+      'client/bower_components/angular-translate/angular-translate.js',
+      'client/bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js',
+      'client/bower_components/angular-translate-storage-cookie/angular-translate-storage-cookie.js',
+      'client/bower_components/angular-ui-router/release/angular-ui-router.js',
+      'client/bower_components/lodash/dist/lodash.compat.js',
+      'client/bower_components/angular-mocks/angular-mocks.js',
+      // endbower
+      'client/app/app.js',
+      'client/app/app.coffee',
+      'client/app/**/*.js',
+      'client/app/**/*.coffee',
+      'client/components/**/*.js',
+      'client/components/**/*.coffee',
+      'client/app/**/*.jade',
+      'client/components/**/*.jade',
+      'client/app/**/*.html',
+      'client/components/**/*.html'
+    ],
+
+    preprocessors: {
+      '**/*.jade': 'ng-jade2js',
+      '**/*.html': 'html2js',
+      '**/*.coffee': 'coffee',
+    },
+
+    ngHtml2JsPreprocessor: {
+      stripPrefix: 'client/'
+    },
+
+    ngJade2JsPreprocessor: {
+      stripPrefix: 'client/'
+    },
+
+    
+
+    // list of files / patterns to exclude
+    exclude: [],
+
+    // web server port
+    port: 8080,
+
+    // level of logging
+    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+    // reporter types:
+    // - dots
+    // - progress (default)
+    // - spec (karma-spec-reporter)
+    // - junit
+    // - growl
+    // - coverage
+    reporters: ['spec'],
+
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: false,
+
+
+    // Start these browsers, currently available:
+    // - Chrome
+    // - ChromeCanary
+    // - Firefox
+    // - Opera
+    // - Safari (only Mac)
+    // - PhantomJS
+    // - IE (only Windows)
+    browsers: ['PhantomJS'],
+
+
+    // Continuous Integration mode
+    // if true, it capture browsers, run tests and exit
+    singleRun: false
+  });
+};
diff --git a/mocha.conf.js b/mocha.conf.js
new file mode 100755 (executable)
index 0000000..76f5662
--- /dev/null
@@ -0,0 +1,19 @@
+'use strict';
+
+// Register the Babel require hook
+require('babel-core/register');
+
+var chai = require('chai');
+
+// Load Chai assertions
+global.expect = chai.expect;
+global.assert = chai.assert;
+chai.should();
+
+// Load Sinon
+global.sinon = require('sinon');
+
+// Initialize Chai plugins
+chai.use(require('sinon-chai'));
+chai.use(require('chai-as-promised'));
+chai.use(require('chai-things'))
diff --git a/package.json b/package.json
new file mode 100755 (executable)
index 0000000..6352384
--- /dev/null
@@ -0,0 +1,104 @@
+{
+  "name": "dashboard",
+  "version": "0.0.0",
+  "main": "server/app.js",
+  "dependencies": {
+    "angular-translate": "^2.8.1",
+    "babel-core": "^5.6.4",
+    "bluebird": "^2.9.34",
+    "body-parser": "~1.5.0",
+    "composable-middleware": "^0.3.0",
+    "compression": "~1.0.1",
+    "connect-mongo": "^0.8.1",
+    "cookie-parser": "~1.0.1",
+    "ejs": "~0.8.4",
+    "errorhandler": "~1.0.0",
+    "express": "~4.9.0",
+    "express-jwt": "^3.0.0",
+    "express-session": "~1.0.2",
+    "jsonwebtoken": "^5.0.0",
+    "lodash": "~2.4.1",
+    "method-override": "~1.0.0",
+    "mongoose": "^4.1.2",
+    "morgan": "~1.0.0",
+    "passport": "~0.2.0",
+    "passport-facebook": "latest",
+    "passport-google-oauth": "latest",
+    "passport-local": "~0.1.6",
+    "passport-twitter": "latest",
+    "serve-favicon": "~2.0.1"
+  },
+  "devDependencies": {
+    "autoprefixer-core": "^5.2.1",
+    "grunt": "~0.4.5",
+    "grunt-wiredep": "^2.0.0",
+    "grunt-concurrent": "^2.0.1",
+    "grunt-contrib-clean": "^0.6.0",
+    "grunt-contrib-concat": "^0.5.1",
+    "grunt-contrib-copy": "^0.8.0",
+    "grunt-contrib-cssmin": "^0.13.0",
+    "grunt-contrib-imagemin": "^0.9.4",
+    "grunt-contrib-jshint": "~0.11.2",
+    "grunt-contrib-uglify": "^0.9.1",
+    "grunt-contrib-watch": "~0.6.1",
+    "grunt-google-cdn": "~0.4.0",
+    "grunt-jscs": "^2.0.0",
+    "grunt-newer": "^1.1.1",
+    "grunt-ng-annotate": "^1.0.1",
+    "grunt-filerev": "^2.3.1",
+    "grunt-usemin": "^3.0.0",
+    "grunt-env": "~0.4.1",
+    "grunt-node-inspector": "^0.2.0",
+    "grunt-nodemon": "^0.4.0",
+    "grunt-angular-templates": "^0.5.4",
+    "grunt-dom-munger": "^3.4.0",
+    "grunt-protractor-runner": "^2.0.0",
+    "grunt-injector": "^0.6.0",
+    "grunt-karma": "~0.12.0",
+    "grunt-build-control": "^0.5.0",
+    "grunt-contrib-sass": "^0.9.0",
+    "jit-grunt": "^0.9.1",
+    "time-grunt": "^1.2.1",
+    "grunt-express-server": "^0.5.1",
+    "grunt-postcss": "^0.5.5",
+    "grunt-open": "~0.2.3",
+    "open": "~0.0.4",
+    "jshint-stylish": "~2.0.1",
+    "connect-livereload": "^0.5.3",
+    "mocha": "^2.2.5",
+    "grunt-mocha-test": "~0.12.7",
+    "grunt-mocha-istanbul": "^3.0.1",
+    "istanbul": "^0.3.17",
+    "chai-as-promised": "^5.1.0",
+    "chai-things": "^0.2.0",
+    "sinon-chai": "^2.8.0",
+    "jasmine-core": "^2.3.4",
+    "karma-jasmine": "~0.3.0",
+    "jasmine-spec-reporter": "^2.4.0",
+    "karma-ng-scenario": "~0.1.0",
+    "karma-firefox-launcher": "~0.1.6",
+    "karma-script-launcher": "~0.1.0",
+    "karma-html2js-preprocessor": "~0.1.0",
+    "karma-ng-jade2js-preprocessor": "^0.2.0",
+    "karma-chrome-launcher": "~0.2.0",
+    "requirejs": "~2.1.11",
+    "karma-requirejs": "~0.2.2",
+    "karma-coffee-preprocessor": "~0.3.0",
+    "karma-jade-preprocessor": "0.0.11",
+    "karma-phantomjs-launcher": "~0.2.0",
+    "karma": "~0.13.3",
+    "karma-ng-html2js-preprocessor": "~0.1.2",
+    "karma-spec-reporter": "~0.0.20",
+    "proxyquire": "^1.0.1",
+    "supertest": "~0.11.0"
+  },
+  "engines": {
+    "node": ">=0.12.0"
+  },
+  "scripts": {
+    "start": "node server",
+    "test": "grunt test",
+    "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update"
+  },
+  "private": true
+}
diff --git a/protractor.conf.js b/protractor.conf.js
new file mode 100755 (executable)
index 0000000..7e13ef5
--- /dev/null
@@ -0,0 +1,75 @@
+// Protractor configuration
+// https://github.com/angular/protractor/blob/master/referenceConf.js
+
+'use strict';
+
+var config = {
+  // The timeout for each script run on the browser. This should be longer
+  // than the maximum time your application needs to stabilize between tasks.
+  allScriptsTimeout: 110000,
+
+  // A base URL for your application under test. Calls to protractor.get()
+  // with relative paths will be prepended with this.
+  baseUrl: 'http://localhost:' + (process.env.PORT || '9000'),
+
+  // Credientials for Saucelabs
+  sauceUser: process.env.SAUCE_USERNAME,
+
+  sauceKey: process.env.SAUCE_ACCESS_KEY,
+
+  // list of files / patterns to load in the browser
+  specs: [
+    'e2e/**/*.spec.js'
+  ],
+
+  // Patterns to exclude.
+  exclude: [],
+
+  // ----- Capabilities to be passed to the webdriver instance ----
+  //
+  // For a full list of available capabilities, see
+  // https://code.google.com/p/selenium/wiki/DesiredCapabilities
+  // and
+  // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
+  capabilities: {
+    'browserName': 'chrome',
+    'name': 'Fullstack E2E',
+    'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
+    'build': process.env.TRAVIS_BUILD_NUMBER
+  },
+
+  // ----- The test framework -----
+  //
+  // Jasmine and Cucumber are fully supported as a test and assertion framework.
+  // Mocha has limited beta support. You will need to include your own
+  // assertion framework if working with mocha.
+  framework: 'jasmine2',
+
+  // ----- Options to be passed to minijasminenode -----
+  //
+  // See the full list at https://github.com/jasmine/jasmine-npm
+  jasmineNodeOpts: {
+    defaultTimeoutInterval: 30000,
+    print: function() {}  // for jasmine-spec-reporter
+  },
+
+  // Prepare environment for tests
+  params: {
+    serverConfig: require('./server/config/environment')
+  },
+
+  onPrepare: function() {
+    var SpecReporter = require('jasmine-spec-reporter');
+    // add jasmine spec reporter
+    jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: true}));
+
+    var serverConfig = config.params.serverConfig;
+
+    // Setup mongo for tests
+    var mongoose = require('mongoose');
+    mongoose.connect(serverConfig.mongo.uri, serverConfig.mongo.options); // Connect to database
+  }
+};
+
+config.params.baseUrl = config.baseUrl;
+exports.config = config;
diff --git a/server/.DS_Store b/server/.DS_Store
new file mode 100755 (executable)
index 0000000..941feef
Binary files /dev/null and b/server/.DS_Store differ
diff --git a/server/.jshintrc b/server/.jshintrc
new file mode 100755 (executable)
index 0000000..69f3b00
--- /dev/null
@@ -0,0 +1,15 @@
+{
+  "expr": true,
+  "node": true,
+  "esnext": true,
+  "bitwise": true,
+  "eqeqeq": true,
+  "immed": true,
+  "latedef": "nofunc",
+  "newcap": true,
+  "noarg": true,
+  "undef": true,
+  "smarttabs": true,
+  "asi": true,
+  "debug": true
+}
diff --git a/server/.jshintrc-spec b/server/.jshintrc-spec
new file mode 100755 (executable)
index 0000000..b9390c3
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "extends": ".jshintrc",
+  "globals": {
+    "describe": true,
+    "it": true,
+    "before": true,
+    "beforeEach": true,
+    "after": true,
+    "afterEach": true,
+    "expect": true,
+    "assert": true,
+    "sinon": true
+  }
+}
diff --git a/server/api/.DS_Store b/server/api/.DS_Store
new file mode 100755 (executable)
index 0000000..5008ddf
Binary files /dev/null and b/server/api/.DS_Store differ
diff --git a/server/api/category/category.controller.js b/server/api/category/category.controller.js
new file mode 100755 (executable)
index 0000000..97d31da
--- /dev/null
@@ -0,0 +1,107 @@
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/categories              ->  index
+ * POST    /api/categories              ->  create
+ * GET     /api/categories/:id          ->  show
+ * PUT     /api/categories/:id          ->  update
+ * DELETE  /api/categories/:id          ->  destroy
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var Category = require('./category.model');
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    var updated = _.merge(entity, updates);
+    return updated.saveAsync()
+      .spread(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.removeAsync()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of Categorys
+exports.index = function(req, res) {
+  var active = req.query.active;
+  var sq = {};
+  if (active != undefined) {
+    sq.active = active;
+  }
+  Category.findAsync(sq)
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Gets a single Category from the DB
+exports.show = function(req, res) {
+  Category.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Creates a new Category in the DB
+exports.create = function(req, res) {
+  Category.createAsync(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+};
+
+// Updates an existing Category in the DB
+exports.update = function(req, res) {
+  if (req.body._id) {
+    delete req.body._id;
+  }
+  Category.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Deletes a Category from the DB
+exports.destroy = function(req, res) {
+  Category.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+};
diff --git a/server/api/category/category.events.js b/server/api/category/category.events.js
new file mode 100755 (executable)
index 0000000..521b387
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Category model events
+ */
+
+'use strict';
+
+var EventEmitter = require('events').EventEmitter;
+var Category = require('./category.model');
+var CategoryEvents = new EventEmitter();
+
+// Set max event listeners (0 == unlimited)
+CategoryEvents.setMaxListeners(0);
+
+// Model events
+var events = {
+  'save': 'save',
+  'remove': 'remove'
+};
+
+// Register the event emitter to the model events
+for (var e in events) {
+  var event = events[e];
+  Category.schema.post(e, emitEvent(event));
+}
+
+function emitEvent(event) {
+  return function(doc) {
+    CategoryEvents.emit(event + ':' + doc._id, doc);
+    CategoryEvents.emit(event, doc);
+  }
+}
+
+module.exports = CategoryEvents;
diff --git a/server/api/category/category.integration.js b/server/api/category/category.integration.js
new file mode 100755 (executable)
index 0000000..b9a3595
--- /dev/null
@@ -0,0 +1,147 @@
+'use strict';
+
+var app = require('../..');
+var request = require('supertest');
+
+var newCategory;
+
+describe('Category API:', function() {
+
+  describe('GET /api/categories', function() {
+    var categorys;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('/api/categories')
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          categorys = res.body;
+          done();
+        });
+    });
+
+    it('should respond with JSON array', function() {
+      categorys.should.be.instanceOf(Array);
+    });
+
+  });
+
+  describe('POST /api/categories', function() {
+    beforeEach(function(done) {
+      request(app)
+        .post('/api/categories')
+        .send({
+          name: 'New Category',
+          info: 'This is the brand new category!!!'
+        })
+        .expect(201)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          newCategory = res.body;
+          done();
+        });
+    });
+
+    it('should respond with the newly created category', function() {
+      newCategory.name.should.equal('New Category');
+      newCategory.info.should.equal('This is the brand new category!!!');
+    });
+
+  });
+
+  describe('GET /api/categories/:id', function() {
+    var category;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('/api/categories/' + newCategory._id)
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          category = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      category = {};
+    });
+
+    it('should respond with the requested category', function() {
+      category.name.should.equal('New Category');
+      category.info.should.equal('This is the brand new category!!!');
+    });
+
+  });
+
+  describe('PUT /api/categories/:id', function() {
+    var updatedCategory
+
+    beforeEach(function(done) {
+      request(app)
+        .put('/api/categories/' + newCategory._id)
+        .send({
+          name: 'Updated Category',
+          info: 'This is the updated category!!!'
+        })
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          updatedCategory = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      updatedCategory = {};
+    });
+
+    it('should respond with the updated category', function() {
+      updatedCategory.name.should.equal('Updated Category');
+      updatedCategory.info.should.equal('This is the updated category!!!');
+    });
+
+  });
+
+  describe('DELETE /api/categories/:id', function() {
+
+    it('should respond with 204 on successful removal', function(done) {
+      request(app)
+        .delete('/api/categories/' + newCategory._id)
+        .expect(204)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+    it('should respond with 404 when category does not exist', function(done) {
+      request(app)
+        .delete('/api/categories/' + newCategory._id)
+        .expect(404)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+  });
+
+});
diff --git a/server/api/category/category.model.js b/server/api/category/category.model.js
new file mode 100755 (executable)
index 0000000..fcb8fe6
--- /dev/null
@@ -0,0 +1,13 @@
+'use strict';
+
+var mongoose = require('bluebird').promisifyAll(require('mongoose'));
+var Schema = mongoose.Schema;
+
+var CategorySchema = new Schema({
+  name: String,
+  parent_id: String,
+  order: Number,
+  active: Boolean
+});
+
+module.exports = mongoose.model('Category', CategorySchema);
diff --git a/server/api/category/index.js b/server/api/category/index.js
new file mode 100755 (executable)
index 0000000..d132ebd
--- /dev/null
@@ -0,0 +1,15 @@
+'use strict';
+
+var express = require('express');
+var controller = require('./category.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/category/index.spec.js b/server/api/category/index.spec.js
new file mode 100755 (executable)
index 0000000..a3f61ca
--- /dev/null
@@ -0,0 +1,97 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var categoryCtrlStub = {
+  index: 'categoryCtrl.index',
+  show: 'categoryCtrl.show',
+  create: 'categoryCtrl.create',
+  update: 'categoryCtrl.update',
+  destroy: 'categoryCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var categoryIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './category.controller': categoryCtrlStub
+});
+
+describe('Category API Router:', function() {
+
+  it('should return an express router instance', function() {
+    categoryIndex.should.equal(routerStub);
+  });
+
+  describe('GET /api/categories', function() {
+
+    it('should route to category.controller.index', function() {
+      routerStub.get
+        .withArgs('/', 'categoryCtrl.index')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/categories/:id', function() {
+
+    it('should route to category.controller.show', function() {
+      routerStub.get
+        .withArgs('/:id', 'categoryCtrl.show')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/categories', function() {
+
+    it('should route to category.controller.create', function() {
+      routerStub.post
+        .withArgs('/', 'categoryCtrl.create')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/categories/:id', function() {
+
+    it('should route to category.controller.update', function() {
+      routerStub.put
+        .withArgs('/:id', 'categoryCtrl.update')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/categories/:id', function() {
+
+    it('should route to category.controller.update', function() {
+      routerStub.patch
+        .withArgs('/:id', 'categoryCtrl.update')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/categories/:id', function() {
+
+    it('should route to category.controller.destroy', function() {
+      routerStub.delete
+        .withArgs('/:id', 'categoryCtrl.destroy')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/comment/comment.controller.js b/server/api/comment/comment.controller.js
new file mode 100755 (executable)
index 0000000..91e0601
--- /dev/null
@@ -0,0 +1,132 @@
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/comments              ->  index
+ * POST    /api/comments              ->  create
+ * GET     /api/comments/:id          ->  show
+ * PUT     /api/comments/:id          ->  update
+ * DELETE  /api/comments/:id          ->  destroy
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var Comment = require('./comment.model');
+var User = require('../user/user.model');
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, dig, statusCode) {
+  statusCode = statusCode || 200;
+  var promise = [];
+  return function(entity) {
+    if (entity) {
+      if (dig) {
+        entity.forEach(function(obj, index) {
+          promise.push(new Promise(function(resolve, reject) {
+            User.findById(obj.setterId, function(err, userObj) {
+              obj.user = userObj;
+              resolve(obj);
+            });
+          }));
+        });
+        Promise.all(promise).then(function(data) {
+          console.log(data);
+          res.status(statusCode).json(data);
+        });
+      } else {
+        User.findById(entity.userId, function(err, userObj) {
+          entity.user = userObj;
+          res.status(statusCode).json(entity);
+        });
+      }
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    var updated = _.merge(entity, updates);
+    return updated.saveAsync()
+      .spread(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.removeAsync()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of Comments
+exports.index = function(req, res) {
+  var limit = req.query.limit;
+  var userId = req.query.user;
+  var active = req.query.active;
+  var sq = {};
+  if (userId) {
+    sq.getterId = userId
+  }
+  if (active) {
+    sq.active = active
+  }
+  Comment.find(sq).sort( { dateAdded: -1 } ).lean().limit(limit)
+    .then(responseWithResult(res, true))
+    .catch(handleError(res));
+};
+
+// Gets a single Comment from the DB
+exports.show = function(req, res) {
+  Comment.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Creates a new Comment in the DB
+exports.create = function(req, res) {
+  Comment.createAsync(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+};
+
+// Updates an existing Comment in the DB
+exports.update = function(req, res) {
+  if (req.body._id) {
+    delete req.body._id;
+  }
+  Comment.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Deletes a Comment from the DB
+exports.destroy = function(req, res) {
+  Comment.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+};
diff --git a/server/api/comment/comment.events.js b/server/api/comment/comment.events.js
new file mode 100755 (executable)
index 0000000..11b79df
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Comment model events
+ */
+
+'use strict';
+
+var EventEmitter = require('events').EventEmitter;
+var Comment = require('./comment.model');
+var CommentEvents = new EventEmitter();
+
+// Set max event listeners (0 == unlimited)
+CommentEvents.setMaxListeners(0);
+
+// Model events
+var events = {
+  'save': 'save',
+  'remove': 'remove'
+};
+
+// Register the event emitter to the model events
+for (var e in events) {
+  var event = events[e];
+  Comment.schema.post(e, emitEvent(event));
+}
+
+function emitEvent(event) {
+  return function(doc) {
+    CommentEvents.emit(event + ':' + doc._id, doc);
+    CommentEvents.emit(event, doc);
+  }
+}
+
+module.exports = CommentEvents;
diff --git a/server/api/comment/comment.integration.js b/server/api/comment/comment.integration.js
new file mode 100755 (executable)
index 0000000..27de1b2
--- /dev/null
@@ -0,0 +1,147 @@
+'use strict';
+
+var app = require('../..');
+var request = require('supertest');
+
+var newComment;
+
+describe('Comment API:', function() {
+
+  describe('GET /api/comments', function() {
+    var comments;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('/api/comments')
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          comments = res.body;
+          done();
+        });
+    });
+
+    it('should respond with JSON array', function() {
+      comments.should.be.instanceOf(Array);
+    });
+
+  });
+
+  describe('POST /api/comments', function() {
+    beforeEach(function(done) {
+      request(app)
+        .post('/api/comments')
+        .send({
+          name: 'New Comment',
+          info: 'This is the brand new comment!!!'
+        })
+        .expect(201)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          newComment = res.body;
+          done();
+        });
+    });
+
+    it('should respond with the newly created comment', function() {
+      newComment.name.should.equal('New Comment');
+      newComment.info.should.equal('This is the brand new comment!!!');
+    });
+
+  });
+
+  describe('GET /api/comments/:id', function() {
+    var comment;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('/api/comments/' + newComment._id)
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          comment = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      comment = {};
+    });
+
+    it('should respond with the requested comment', function() {
+      comment.name.should.equal('New Comment');
+      comment.info.should.equal('This is the brand new comment!!!');
+    });
+
+  });
+
+  describe('PUT /api/comments/:id', function() {
+    var updatedComment
+
+    beforeEach(function(done) {
+      request(app)
+        .put('/api/comments/' + newComment._id)
+        .send({
+          name: 'Updated Comment',
+          info: 'This is the updated comment!!!'
+        })
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          updatedComment = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      updatedComment = {};
+    });
+
+    it('should respond with the updated comment', function() {
+      updatedComment.name.should.equal('Updated Comment');
+      updatedComment.info.should.equal('This is the updated comment!!!');
+    });
+
+  });
+
+  describe('DELETE /api/comments/:id', function() {
+
+    it('should respond with 204 on successful removal', function(done) {
+      request(app)
+        .delete('/api/comments/' + newComment._id)
+        .expect(204)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+    it('should respond with 404 when comment does not exist', function(done) {
+      request(app)
+        .delete('/api/comments/' + newComment._id)
+        .expect(404)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+  });
+
+});
diff --git a/server/api/comment/comment.model.js b/server/api/comment/comment.model.js
new file mode 100755 (executable)
index 0000000..69294e8
--- /dev/null
@@ -0,0 +1,20 @@
+'use strict';
+
+var mongoose = require('bluebird').promisifyAll(require('mongoose'));
+var Schema = mongoose.Schema;
+
+var CommentSchema = new Schema({
+  setterId: String,
+  getterId: String,
+  text: String,
+  rating: Number,
+  ratingPulizia: Number,
+  ratingAtmosfera: Number,
+  dateAdded: {
+       type: Date,
+       default: Date.now()
+  },
+  active: Boolean
+});
+
+module.exports = mongoose.model('Comment', CommentSchema);
diff --git a/server/api/comment/index.js b/server/api/comment/index.js
new file mode 100755 (executable)
index 0000000..dd4d78f
--- /dev/null
@@ -0,0 +1,15 @@
+'use strict';
+
+var express = require('express');
+var controller = require('./comment.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/comment/index.spec.js b/server/api/comment/index.spec.js
new file mode 100755 (executable)
index 0000000..7095c48
--- /dev/null
@@ -0,0 +1,97 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var commentCtrlStub = {
+  index: 'commentCtrl.index',
+  show: 'commentCtrl.show',
+  create: 'commentCtrl.create',
+  update: 'commentCtrl.update',
+  destroy: 'commentCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var commentIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './comment.controller': commentCtrlStub
+});
+
+describe('Comment API Router:', function() {
+
+  it('should return an express router instance', function() {
+    commentIndex.should.equal(routerStub);
+  });
+
+  describe('GET /api/comments', function() {
+
+    it('should route to comment.controller.index', function() {
+      routerStub.get
+        .withArgs('/', 'commentCtrl.index')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/comments/:id', function() {
+
+    it('should route to comment.controller.show', function() {
+      routerStub.get
+        .withArgs('/:id', 'commentCtrl.show')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/comments', function() {
+
+    it('should route to comment.controller.create', function() {
+      routerStub.post
+        .withArgs('/', 'commentCtrl.create')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/comments/:id', function() {
+
+    it('should route to comment.controller.update', function() {
+      routerStub.put
+        .withArgs('/:id', 'commentCtrl.update')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/comments/:id', function() {
+
+    it('should route to comment.controller.update', function() {
+      routerStub.patch
+        .withArgs('/:id', 'commentCtrl.update')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/comments/:id', function() {
+
+    it('should route to comment.controller.destroy', function() {
+      routerStub.delete
+        .withArgs('/:id', 'commentCtrl.destroy')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/message/index.js b/server/api/message/index.js
new file mode 100755 (executable)
index 0000000..40ec25b
--- /dev/null
@@ -0,0 +1,15 @@
+'use strict';
+
+var express = require('express');
+var controller = require('./message.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/message/index.spec.js b/server/api/message/index.spec.js
new file mode 100755 (executable)
index 0000000..61bfb40
--- /dev/null
@@ -0,0 +1,97 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var messageCtrlStub = {
+  index: 'messageCtrl.index',
+  show: 'messageCtrl.show',
+  create: 'messageCtrl.create',
+  update: 'messageCtrl.update',
+  destroy: 'messageCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var messageIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './message.controller': messageCtrlStub
+});
+
+describe('Message API Router:', function() {
+
+  it('should return an express router instance', function() {
+    messageIndex.should.equal(routerStub);
+  });
+
+  describe('GET /api/test', function() {
+
+    it('should route to message.controller.index', function() {
+      routerStub.get
+        .withArgs('/', 'messageCtrl.index')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/test/:id', function() {
+
+    it('should route to message.controller.show', function() {
+      routerStub.get
+        .withArgs('/:id', 'messageCtrl.show')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/test', function() {
+
+    it('should route to message.controller.create', function() {
+      routerStub.post
+        .withArgs('/', 'messageCtrl.create')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/test/:id', function() {
+
+    it('should route to message.controller.update', function() {
+      routerStub.put
+        .withArgs('/:id', 'messageCtrl.update')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/test/:id', function() {
+
+    it('should route to message.controller.update', function() {
+      routerStub.patch
+        .withArgs('/:id', 'messageCtrl.update')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/test/:id', function() {
+
+    it('should route to message.controller.destroy', function() {
+      routerStub.delete
+        .withArgs('/:id', 'messageCtrl.destroy')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/message/message.controller.js b/server/api/message/message.controller.js
new file mode 100755 (executable)
index 0000000..962845c
--- /dev/null
@@ -0,0 +1,102 @@
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/test              ->  index
+ * POST    /api/test              ->  create
+ * GET     /api/test/:id          ->  show
+ * PUT     /api/test/:id          ->  update
+ * DELETE  /api/test/:id          ->  destroy
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var Message = require('./message.model');
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    var updated = _.merge(entity, updates);
+    return updated.saveAsync()
+      .spread(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.removeAsync()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of Messages
+exports.index = function(req, res) {
+  Message.findAsync()
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Gets a single Message from the DB
+exports.show = function(req, res) {
+  Message.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Creates a new Message in the DB
+exports.create = function(req, res) {
+  Message.createAsync(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+};
+
+// Updates an existing Message in the DB
+exports.update = function(req, res) {
+  if (req.body._id) {
+    delete req.body._id;
+  }
+  Message.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Deletes a Message from the DB
+exports.destroy = function(req, res) {
+  Message.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+};
diff --git a/server/api/message/message.events.js b/server/api/message/message.events.js
new file mode 100755 (executable)
index 0000000..276c787
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Message model events
+ */
+
+'use strict';
+
+var EventEmitter = require('events').EventEmitter;
+var Message = require('./message.model');
+var MessageEvents = new EventEmitter();
+
+// Set max event listeners (0 == unlimited)
+MessageEvents.setMaxListeners(0);
+
+// Model events
+var events = {
+  'save': 'save',
+  'remove': 'remove'
+};
+
+// Register the event emitter to the model events
+for (var e in events) {
+  var event = events[e];
+  Message.schema.post(e, emitEvent(event));
+}
+
+function emitEvent(event) {
+  return function(doc) {
+    MessageEvents.emit(event + ':' + doc._id, doc);
+    MessageEvents.emit(event, doc);
+  }
+}
+
+module.exports = MessageEvents;
diff --git a/server/api/message/message.integration.js b/server/api/message/message.integration.js
new file mode 100755 (executable)
index 0000000..2a38250
--- /dev/null
@@ -0,0 +1,147 @@
+'use strict';
+
+var app = require('../..');
+var request = require('supertest');
+
+var newMessage;
+
+describe('Message API:', function() {
+
+  describe('GET /api/test', function() {
+    var messages;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('/api/test')
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          messages = res.body;
+          done();
+        });
+    });
+
+    it('should respond with JSON array', function() {
+      messages.should.be.instanceOf(Array);
+    });
+
+  });
+
+  describe('POST /api/test', function() {
+    beforeEach(function(done) {
+      request(app)
+        .post('/api/test')
+        .send({
+          name: 'New Message',
+          info: 'This is the brand new message!!!'
+        })
+        .expect(201)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          newMessage = res.body;
+          done();
+        });
+    });
+
+    it('should respond with the newly created message', function() {
+      newMessage.name.should.equal('New Message');
+      newMessage.info.should.equal('This is the brand new message!!!');
+    });
+
+  });
+
+  describe('GET /api/test/:id', function() {
+    var message;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('/api/test/' + newMessage._id)
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          message = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      message = {};
+    });
+
+    it('should respond with the requested message', function() {
+      message.name.should.equal('New Message');
+      message.info.should.equal('This is the brand new message!!!');
+    });
+
+  });
+
+  describe('PUT /api/test/:id', function() {
+    var updatedMessage
+
+    beforeEach(function(done) {
+      request(app)
+        .put('/api/test/' + newMessage._id)
+        .send({
+          name: 'Updated Message',
+          info: 'This is the updated message!!!'
+        })
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          updatedMessage = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      updatedMessage = {};
+    });
+
+    it('should respond with the updated message', function() {
+      updatedMessage.name.should.equal('Updated Message');
+      updatedMessage.info.should.equal('This is the updated message!!!');
+    });
+
+  });
+
+  describe('DELETE /api/test/:id', function() {
+
+    it('should respond with 204 on successful removal', function(done) {
+      request(app)
+        .delete('/api/test/' + newMessage._id)
+        .expect(204)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+    it('should respond with 404 when message does not exist', function(done) {
+      request(app)
+        .delete('/api/test/' + newMessage._id)
+        .expect(404)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+  });
+
+});
diff --git a/server/api/message/message.model.js b/server/api/message/message.model.js
new file mode 100755 (executable)
index 0000000..a503c08
--- /dev/null
@@ -0,0 +1,12 @@
+'use strict';
+
+var mongoose = require('bluebird').promisifyAll(require('mongoose'));
+var Schema = mongoose.Schema;
+
+var MessageSchema = new Schema({
+  name: String,
+  info: String,
+  active: Boolean
+});
+
+module.exports = mongoose.model('Message', MessageSchema);
diff --git a/server/api/show/index.js b/server/api/show/index.js
new file mode 100755 (executable)
index 0000000..ec0e10c
--- /dev/null
@@ -0,0 +1,15 @@
+'use strict';
+
+var express = require('express');
+var controller = require('./show.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/show/index.spec.js b/server/api/show/index.spec.js
new file mode 100755 (executable)
index 0000000..9ba1896
--- /dev/null
@@ -0,0 +1,97 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var showCtrlStub = {
+  index: 'showCtrl.index',
+  show: 'showCtrl.show',
+  create: 'showCtrl.create',
+  update: 'showCtrl.update',
+  destroy: 'showCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var showIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './show.controller': showCtrlStub
+});
+
+describe('Show API Router:', function() {
+
+  it('should return an express router instance', function() {
+    showIndex.should.equal(routerStub);
+  });
+
+  describe('GET /api/shows', function() {
+
+    it('should route to show.controller.index', function() {
+      routerStub.get
+        .withArgs('/', 'showCtrl.index')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/shows/:id', function() {
+
+    it('should route to show.controller.show', function() {
+      routerStub.get
+        .withArgs('/:id', 'showCtrl.show')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/shows', function() {
+
+    it('should route to show.controller.create', function() {
+      routerStub.post
+        .withArgs('/', 'showCtrl.create')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/shows/:id', function() {
+
+    it('should route to show.controller.update', function() {
+      routerStub.put
+        .withArgs('/:id', 'showCtrl.update')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/shows/:id', function() {
+
+    it('should route to show.controller.update', function() {
+      routerStub.patch
+        .withArgs('/:id', 'showCtrl.update')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/shows/:id', function() {
+
+    it('should route to show.controller.destroy', function() {
+      routerStub.delete
+        .withArgs('/:id', 'showCtrl.destroy')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/show/show.controller.js b/server/api/show/show.controller.js
new file mode 100755 (executable)
index 0000000..2a5c2d8
--- /dev/null
@@ -0,0 +1,154 @@
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/shows              ->  index
+ * POST    /api/shows              ->  create
+ * GET     /api/shows/:id          ->  show
+ * PUT     /api/shows/:id          ->  update
+ * DELETE  /api/shows/:id          ->  destroy
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var Show = require('./show.model');
+var User = require('../user/user.model');
+// var Promise_ = require('promise');
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, dig, statusCode) {
+  statusCode = statusCode || 200;
+  var promise = [];
+  return function(entity) {
+    if (entity) {
+      if (dig) {
+        entity.forEach(function(obj, index) {
+          promise.push(new Promise(function(resolve, reject) {
+            User.findById(obj.userId, function(err, userObj) {
+              obj.user = userObj;
+              resolve(obj);
+            });
+          }));
+        });
+        Promise.all(promise).then(function(data) {
+          // console.log(data);
+          res.status(statusCode).json(data);
+        });
+      } else {
+        User.findById(entity.userId, function(err, userObj) {
+          entity.user = userObj;
+          res.status(statusCode).json(entity);
+        });
+      }
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    var updated = _.merge(entity, updates);
+    return updated.saveAsync()
+      .spread(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.removeAsync()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of Shows
+exports.index = function(req, res) {
+  var limit = req.query.limit;
+  var sq = {};
+  var date = req.query.date;
+  var category = req.query.category;
+  var fulltext = req.query.fulltext;
+  var lat = req.query.lat;
+  var lng = req.query.lng;
+  var maxDistance = 20; 
+  if (date) {
+    sq.date = {
+      $gte: date
+    }
+  }
+  if (category) {
+    sq.categoryId = category
+  }
+  if (lat && lng) {
+    sq.location = {
+      $near: [lng, lat],
+      $maxDistance: maxDistance
+    };
+  }
+  if (fulltext) {
+    var words = fulltext.replace(/[.,-\/#!$%\^&\*;:{}=\-_`~()]/g,"").split(/\s+/);
+    var pattern = '';
+    for (let word of words) {
+      pattern += "(?=.*\\b" + word + "\\b)";
+    }
+    sq.title = new RegExp("^" + pattern + ".*$", 'i');
+  }
+  Show.find(sq).lean().limit(limit)
+    .then(responseWithResult(res, true))
+    .catch(handleError(res));
+};
+
+
+// Gets a single Show from the DB
+exports.show = function(req, res) {
+  Show.findById(req.params.id).lean()
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Creates a new Show in the DB
+exports.create = function(req, res) {
+  Show.createAsync(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+};
+
+// Updates an existing Show in the DB
+exports.update = function(req, res) {
+  if (req.body._id) {
+    delete req.body._id;
+  }
+  Show.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Deletes a Show from the DB
+exports.destroy = function(req, res) {
+  Show.findByIdAsync(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+};
diff --git a/server/api/show/show.events.js b/server/api/show/show.events.js
new file mode 100755 (executable)
index 0000000..1efd360
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Show model events
+ */
+
+'use strict';
+
+var EventEmitter = require('events').EventEmitter;
+var Show = require('./show.model');
+var ShowEvents = new EventEmitter();
+
+// Set max event listeners (0 == unlimited)
+ShowEvents.setMaxListeners(0);
+
+// Model events
+var events = {
+  'save': 'save',
+  'remove': 'remove'
+};
+
+// Register the event emitter to the model events
+for (var e in events) {
+  var event = events[e];
+  Show.schema.post(e, emitEvent(event));
+}
+
+function emitEvent(event) {
+  return function(doc) {
+    ShowEvents.emit(event + ':' + doc._id, doc);
+    ShowEvents.emit(event, doc);
+  }
+}
+
+module.exports = ShowEvents;
diff --git a/server/api/show/show.integration.js b/server/api/show/show.integration.js
new file mode 100755 (executable)
index 0000000..d29ff54
--- /dev/null
@@ -0,0 +1,147 @@
+'use strict';
+
+var app = require('../..');
+var request = require('supertest');
+
+var newShow;
+
+describe('Show API:', function() {
+
+  describe('GET /api/shows', function() {
+    var shows;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('/api/shows')
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          shows = res.body;
+          done();
+        });
+    });
+
+    it('should respond with JSON array', function() {
+      shows.should.be.instanceOf(Array);
+    });
+
+  });
+
+  describe('POST /api/shows', function() {
+    beforeEach(function(done) {
+      request(app)
+        .post('/api/shows')
+        .send({
+          name: 'New Show',
+          info: 'This is the brand new show!!!'
+        })
+        .expect(201)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          newShow = res.body;
+          done();
+        });
+    });
+
+    it('should respond with the newly created show', function() {
+      newShow.name.should.equal('New Show');
+      newShow.info.should.equal('This is the brand new show!!!');
+    });
+
+  });
+
+  describe('GET /api/shows/:id', function() {
+    var show;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('/api/shows/' + newShow._id)
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          show = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      show = {};
+    });
+
+    it('should respond with the requested show', function() {
+      show.name.should.equal('New Show');
+      show.info.should.equal('This is the brand new show!!!');
+    });
+
+  });
+
+  describe('PUT /api/shows/:id', function() {
+    var updatedShow
+
+    beforeEach(function(done) {
+      request(app)
+        .put('/api/shows/' + newShow._id)
+        .send({
+          name: 'Updated Show',
+          info: 'This is the updated show!!!'
+        })
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          updatedShow = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      updatedShow = {};
+    });
+
+    it('should respond with the updated show', function() {
+      updatedShow.name.should.equal('Updated Show');
+      updatedShow.info.should.equal('This is the updated show!!!');
+    });
+
+  });
+
+  describe('DELETE /api/shows/:id', function() {
+
+    it('should respond with 204 on successful removal', function(done) {
+      request(app)
+        .delete('/api/shows/' + newShow._id)
+        .expect(204)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+    it('should respond with 404 when show does not exist', function(done) {
+      request(app)
+        .delete('/api/shows/' + newShow._id)
+        .expect(404)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+  });
+
+});
diff --git a/server/api/show/show.model.js b/server/api/show/show.model.js
new file mode 100755 (executable)
index 0000000..2bb15d6
--- /dev/null
@@ -0,0 +1,26 @@
+'use strict';
+
+var mongoose = require('bluebird').promisifyAll(require('mongoose'));
+var Schema = mongoose.Schema;
+
+var ShowSchema = new Schema({
+  title: String,
+  image: String,
+  description: String,
+  date: {type:Date, default: Date.now()},
+  userId: String,
+  services: Array,
+  info: String,
+  location: {
+    type: [Number],
+    index: '2d'
+  },
+  place: String,
+  categoryId: String,
+  active: Boolean
+});
+
+
+// ShowSchema.set('toObject', { virtuals: true });
+
+module.exports = mongoose.model('Show', ShowSchema);
diff --git a/server/api/user/__user.model.spec.js b/server/api/user/__user.model.spec.js
new file mode 100755 (executable)
index 0000000..cbebda7
--- /dev/null
@@ -0,0 +1,72 @@
+'use strict';
+
+var app = require('../..');
+var User = require('./user.model');
+var user;
+var genUser = function() {
+  user = new User({
+    provider: 'local',
+    name: 'Fake User',
+    email: 'test@example.com',
+    password: 'password'
+  });
+  return user;
+};
+
+describe('User Model', function() {
+  before(function() {
+    // Clear users before testing
+    return User.removeAsync();
+  });
+
+  beforeEach(function() {
+    genUser();
+  });
+
+  afterEach(function() {
+    return User.removeAsync();
+  });
+
+  it('should begin with no users', function() {
+    return User.findAsync({}).should
+      .eventually.have.length(0);
+  });
+
+  it('should fail when saving a duplicate user', function() {
+    return user.saveAsync()
+      .then(function() {
+        var userDup = genUser();
+        return userDup.saveAsync();
+      }).should.be.rejected;
+  });
+
+  describe('#email', function() {
+    it('should fail when saving without an email', function() {
+      user.email = '';
+      return user.saveAsync().should.be.rejected;
+    });
+  });
+
+  describe('#password', function() {
+    beforeEach(function() {
+      return user.saveAsync();
+    });
+
+    it('should authenticate user if valid', function() {
+      user.authenticate('password').should.be.true;
+    });
+
+    it('should not authenticate user if invalid', function() {
+      user.authenticate('blah').should.not.be.true;
+    });
+
+    it('should remain the same hash unless the password is updated', function() {
+      user.name = 'Test User';
+      return user.saveAsync()
+        .spread(function(u) {
+          return u.authenticate('password');
+        }).should.eventually.be.true;
+    });
+  });
+
+});
diff --git a/server/api/user/index.js b/server/api/user/index.js
new file mode 100755 (executable)
index 0000000..be6fd3a
--- /dev/null
@@ -0,0 +1,16 @@
+'use strict';
+
+var express = require('express');
+var controller = require('./user.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.hasRole('admin'), controller.index);
+router.delete('/:id', auth.hasRole('admin'), controller.destroy);
+router.get('/me', auth.isAuthenticated(), controller.me);
+router.put('/:id/password', auth.isAuthenticated(), controller.changePassword);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', controller.create);
+
+module.exports = router;
diff --git a/server/api/user/index.spec.js b/server/api/user/index.spec.js
new file mode 100755 (executable)
index 0000000..da42ce1
--- /dev/null
@@ -0,0 +1,107 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var userCtrlStub = {
+  index: 'userCtrl.index',
+  destroy: 'userCtrl.destroy',
+  me: 'userCtrl.me',
+  changePassword: 'userCtrl.changePassword',
+  show: 'userCtrl.show',
+  create: 'userCtrl.create'
+};
+
+var authServiceStub = {
+  isAuthenticated: function() {
+    return 'authService.isAuthenticated';
+  },
+  hasRole: function(role) {
+    return 'authService.hasRole.' + role;
+  }
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var userIndex = proxyquire('./index', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './user.controller': userCtrlStub,
+  '../../auth/auth.service': authServiceStub
+});
+
+describe('User API Router:', function() {
+
+  it('should return an express router instance', function() {
+    userIndex.should.equal(routerStub);
+  });
+
+  describe('GET /api/users', function() {
+
+    it('should verify admin role and route to user.controller.index', function() {
+      routerStub.get
+        .withArgs('/', 'authService.hasRole.admin', 'userCtrl.index')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/users/:id', function() {
+
+    it('should verify admin role and route to user.controller.destroy', function() {
+      routerStub.delete
+        .withArgs('/:id', 'authService.hasRole.admin', 'userCtrl.destroy')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/users/me', function() {
+
+    it('should be authenticated and route to user.controller.me', function() {
+      routerStub.get
+        .withArgs('/me', 'authService.isAuthenticated', 'userCtrl.me')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/users/:id/password', function() {
+
+    it('should be authenticated and route to user.controller.changePassword', function() {
+      routerStub.put
+        .withArgs('/:id/password', 'authService.isAuthenticated', 'userCtrl.changePassword')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/users/:id', function() {
+
+    it('should be authenticated and route to user.controller.show', function() {
+      routerStub.get
+        .withArgs('/:id', 'authService.isAuthenticated', 'userCtrl.show')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/users', function() {
+
+    it('should route to user.controller.create', function() {
+      routerStub.post
+        .withArgs('/', 'userCtrl.create')
+        .should.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/user/user.controller.js b/server/api/user/user.controller.js
new file mode 100755 (executable)
index 0000000..aa7d829
--- /dev/null
@@ -0,0 +1,135 @@
+'use strict';
+
+var User = require('./user.model');
+var passport = require('passport');
+var config = require('../../config/environment');
+var jwt = require('jsonwebtoken');
+
+function validationError(res, statusCode) {
+  statusCode = statusCode || 422;
+  return function(err) {
+    res.status(statusCode).json(err);
+  }
+}
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function respondWith(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function() {
+    res.status(statusCode).end();
+  };
+}
+
+/**
+ * Get list of users
+ * restriction: 'admin'
+ */
+exports.index = function(req, res) {
+  User.findAsync({}, '-salt -hashedPassword')
+    .then(function(users) {
+      res.status(200).json(users);
+    })
+    .catch(handleError(res));
+};
+
+/**
+ * Creates a new user
+ */
+exports.create = function(req, res, next) {
+  var newUser = new User(req.body);
+  newUser.provider = 'local';
+  newUser.role = 'user';
+  newUser.saveAsync()
+    .spread(function(user) {
+      var token = jwt.sign({ _id: user._id }, config.secrets.session, {
+        expiresInMinutes: 60 * 5
+      });
+      user.token = token;
+      res.json({ token: token });
+    })
+    .catch(validationError(res));
+};
+
+/**
+ * Get a single user
+ */
+exports.show = function(req, res, next) {
+  var userId = req.params.id;
+
+  User.findByIdAsync(userId)
+    .then(function(user) {
+      if (!user) {
+        return res.status(404).end();
+      }
+      res.json(user.profile);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+/**
+ * Deletes a user
+ * restriction: 'admin'
+ */
+exports.destroy = function(req, res) {
+  User.findByIdAndRemoveAsync(req.params.id)
+    .then(function() {
+      res.status(204).end();
+    })
+    .catch(handleError(res));
+};
+
+/**
+ * Change a users password
+ */
+exports.changePassword = function(req, res, next) {
+  var userId = req.user._id;
+  var oldPass = String(req.body.oldPassword);
+  var newPass = String(req.body.newPassword);
+
+  User.findByIdAsync(userId)
+    .then(function(user) {
+      if (user.authenticate(oldPass)) {
+        user.password = newPass;
+        return user.saveAsync()
+          .then(function() {
+            res.status(204).end();
+          })
+          .catch(validationError(res));
+      } else {
+        return res.status(403).end();
+      }
+    });
+};
+
+/**
+ * Get my info
+ */
+exports.me = function(req, res, next) {
+  var userId = req.user._id;
+
+  User.findOneAsync({ _id: userId }, '-salt -hashedPassword')
+    .then(function(user) { // don't ever give out the password or salt
+      if (!user) {
+        return res.status(401).end();
+      }
+      res.json(user);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+/**
+ * Authentication callback
+ */
+exports.authCallback = function(req, res, next) {
+  res.redirect('/');
+};
diff --git a/server/api/user/user.events.js b/server/api/user/user.events.js
new file mode 100755 (executable)
index 0000000..f8af012
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * User model events
+ */
+
+'use strict';
+
+var EventEmitter = require('events').EventEmitter;
+var User = require('./user.model');
+var UserEvents = new EventEmitter();
+
+// Set max event listeners (0 == unlimited)
+UserEvents.setMaxListeners(0);
+
+// Model events
+var events = {
+  'save': 'save',
+  'remove': 'remove'
+};
+
+// Register the event emitter to the model events
+for (var e in events) {
+  var event = events[e];
+  User.schema.post(e, emitEvent(event));
+}
+
+function emitEvent(event) {
+  return function(doc) {
+    UserEvents.emit(event + ':' + doc._id, doc);
+    UserEvents.emit(event, doc);
+  }
+}
+
+module.exports = UserEvents;
diff --git a/server/api/user/user.integration.js b/server/api/user/user.integration.js
new file mode 100755 (executable)
index 0000000..b4f9db0
--- /dev/null
@@ -0,0 +1,65 @@
+'use strict';
+
+var app = require('../..');
+var User = require('./user.model');
+var request = require('supertest');
+
+describe('User API:', function() {
+  var user;
+
+  // Clear users before testing
+  before(function() {
+    return User.removeAsync().then(function() {
+      user = new User({
+        name: 'Fake User',
+        email: 'test@example.com',
+        password: 'password'
+      });
+
+      return user.saveAsync();
+    });
+  });
+
+  // Clear users after testing
+  after(function() {
+    return User.removeAsync();
+  });
+
+  describe('GET /api/users/me', function() {
+    var token;
+
+    before(function(done) {
+      request(app)
+        .post('/auth/local')
+        .send({
+          email: 'test@example.com',
+          password: 'password'
+        })
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          token = res.body.token;
+          done();
+        });
+    });
+
+    it('should respond with a user profile when authenticated', function(done) {
+      request(app)
+        .get('/api/users/me')
+        .set('authorization', 'Bearer ' + token)
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          res.body._id.toString().should.equal(user._id.toString());
+          done();
+        });
+    });
+
+    it('should respond with a 401 when not authenticated', function(done) {
+      request(app)
+        .get('/api/users/me')
+        .expect(401)
+        .end(done);
+    });
+  });
+});
diff --git a/server/api/user/user.model.js b/server/api/user/user.model.js
new file mode 100755 (executable)
index 0000000..217fbd0
--- /dev/null
@@ -0,0 +1,273 @@
+'use strict';
+
+var mongoose = require('bluebird').promisifyAll(require('mongoose'));
+var Schema = mongoose.Schema;
+var crypto = require('crypto');
+var authTypes = ['github', 'twitter', 'facebook', 'google'];
+var nodemailer = require('nodemailer');
+
+var UserSchema = new Schema({
+  name: String,
+  firstName: String,
+  lastName: String,
+  email: {
+    type: String,
+    lowercase: true
+  },
+  role: {
+    type: String,
+    default: 'user'
+  },
+  password: String,
+  provider: String,
+  salt: String,
+  birthDate: Date,
+  facebook: {},
+  twitter: {},
+  google: {},
+  github: {},
+  rating: Number,
+  ratingCount: Number,
+  place: String,
+  location: {
+    type: [Number],
+    index: '2d'
+  },
+  dateSubscribed: {
+    type: Date,
+    default: Date.now()
+  }
+});
+
+/**
+ * Virtuals
+ */
+
+// Public profile information
+UserSchema
+  .virtual('profile')
+  .get(function() {
+    return {
+      'name': this.name,
+      'role': this.role
+    };
+  });
+
+// Non-sensitive info we'll be putting in the token
+UserSchema
+  .virtual('token')
+  .get(function() {
+    return {
+      '_id': this._id,
+      'role': this.role
+    };
+  });
+
+/**
+ * Validations
+ */
+
+// Validate empty email
+UserSchema
+  .path('email')
+  .validate(function(email) {
+    if (authTypes.indexOf(this.provider) !== -1) {
+      return true;
+    }
+    return email.length;
+  }, 'Email cannot be blank');
+
+// Validate empty password
+UserSchema
+  .path('password')
+  .validate(function(password) {
+    if (authTypes.indexOf(this.provider) !== -1) {
+      return true;
+    }
+    return password.length;
+  }, 'Password cannot be blank');
+
+// Validate email is not taken
+UserSchema
+  .path('email')
+  .validate(function(value, respond) {
+    var self = this;
+    return this.constructor.findOneAsync({ email: value })
+      .then(function(user) {
+        if (user) {
+          if (self.id === user.id) {
+            return respond(true);
+          }
+          return respond(false);
+        }
+        return respond(true);
+      })
+      .catch(function(err) {
+        throw err;
+      });
+  }, 'The specified email address is already in use.');
+
+var validatePresenceOf = function(value) {
+  return value && value.length;
+};
+
+/**
+ * Pre-save hook
+ */
+UserSchema
+  .pre('save', function(next) {
+    // Handle new/update passwords
+    if (this.isModified('password')) {
+      if (!validatePresenceOf(this.password) && authTypes.indexOf(this.provider) === -1) {
+        next(new Error('Invalid password'));
+      }
+
+      // Make salt with a callback
+      var _this = this;
+      this.makeSalt(function(saltErr, salt) {
+        if (saltErr) {
+          next(saltErr);
+        }
+        _this.salt = salt;
+        _this.encryptPassword(_this.password, function(encryptErr, hashedPassword) {
+          if (encryptErr) {
+            next(encryptErr);
+          }
+          _this.password = hashedPassword;
+          next();
+        });
+      });
+    } else {
+      next();
+    }
+  });
+
+  /**
+   * Post-save hook
+   */
+  UserSchema
+    .post('save', function(user) {
+      console.log('token', user.token);
+      this.sendMail(user.token);
+    });
+
+/**
+ * Methods
+ */
+UserSchema.methods = {
+
+  /* send mail */
+  sendMail: function(tokenConfirmation) {
+    var transporter = nodemailer.createTransport({
+        service: "Gmail",
+        auth: {
+            user: "twotoc@gmail.com",
+            pass: "2toc2015"
+        }
+    });
+    transporter.sendMail({
+      from: 'twotoc@gmail.com',
+      to: this.email,
+      subject: 'CONFERMA REGISTRAZIONE',
+      html: '<b>PER POTER COMPLETARE LA REGISTRAZIONE CLICCA QUESTO LINK: <a href="http://www.twotoc.it/api/users/'+this.email+'/confirmation/'+tokenConfirmation+'">token</a>.</br>SE NON FUNZIONA IL LINK INCOLLALO (http://www.twotoc.it/api/users/'+this.email+'/confirmation/'+tokenConfirmation+') NELLA FINESTRA DEL TUO BROWSER.</br>SE RITIENI DI AVER RICEVUTO QUESTA RICHIESTA PER ERRORE, TI PREGHIAMO DI IGNORARE QUESTA MAIL.</b>'
+    }, function(err, response){
+      console.log(err, response);
+    });
+  },
+
+
+  /**
+   * Authenticate - check if the passwords are the same
+   *
+   * @param {String} password
+   * @param {Function} callback
+   * @return {Boolean}
+   * @api public
+   */
+  authenticate: function(password, callback) {
+    if (!callback) {
+      return this.password === this.encryptPassword(password);
+    }
+
+    var _this = this;
+    this.encryptPassword(password, function(err, pwdGen) {
+      if (err) {
+        callback(err);
+      }
+
+      if (_this.password === pwdGen) {
+        callback(null, true);
+      }
+      else {
+        callback(null, false);
+      }
+    });
+  },
+
+  /**
+   * Make salt
+   *
+   * @param {Number} byteSize Optional salt byte size, default to 16
+   * @param {Function} callback
+   * @return {String}
+   * @api public
+   */
+  makeSalt: function(byteSize, callback) {
+    var defaultByteSize = 16;
+
+    if (typeof arguments[0] === 'function') {
+      callback = arguments[0];
+      byteSize = defaultByteSize;
+    }
+    else if (typeof arguments[1] === 'function') {
+      callback = arguments[1];
+    }
+
+    if (!byteSize) {
+      byteSize = defaultByteSize;
+    }
+
+    if (!callback) {
+      return crypto.randomBytes(byteSize).toString('base64');
+    }
+
+    return crypto.randomBytes(byteSize, function(err, salt) {
+      if (err) {
+        callback(err);
+      }
+      return callback(null, salt.toString('base64'));
+    });
+  },
+
+  /**
+   * Encrypt password
+   *
+   * @param {String} password
+   * @param {Function} callback
+   * @return {String}
+   * @api public
+   */
+  encryptPassword: function(password, callback) {
+    if (!password || !this.salt) {
+      return null;
+    }
+
+    var defaultIterations = 10000;
+    var defaultKeyLength = 64;
+    var salt = new Buffer(this.salt, 'base64');
+
+    if (!callback) {
+      return crypto.pbkdf2Sync(password, salt, defaultIterations, defaultKeyLength)
+                   .toString('base64');
+    }
+
+    return crypto.pbkdf2(password, salt, defaultIterations, defaultKeyLength, function(err, key) {
+      if (err) {
+        callback(err);
+      }
+      return callback(null, key.toString('base64'));
+    });
+  }
+};
+
+module.exports = mongoose.model('User', UserSchema);
diff --git a/server/api/user/user.model.spec.js b/server/api/user/user.model.spec.js
new file mode 100755 (executable)
index 0000000..cbebda7
--- /dev/null
@@ -0,0 +1,72 @@
+'use strict';
+
+var app = require('../..');
+var User = require('./user.model');
+var user;
+var genUser = function() {
+  user = new User({
+    provider: 'local',
+    name: 'Fake User',
+    email: 'test@example.com',
+    password: 'password'
+  });
+  return user;
+};
+
+describe('User Model', function() {
+  before(function() {
+    // Clear users before testing
+    return User.removeAsync();
+  });
+
+  beforeEach(function() {
+    genUser();
+  });
+
+  afterEach(function() {
+    return User.removeAsync();
+  });
+
+  it('should begin with no users', function() {
+    return User.findAsync({}).should
+      .eventually.have.length(0);
+  });
+
+  it('should fail when saving a duplicate user', function() {
+    return user.saveAsync()
+      .then(function() {
+        var userDup = genUser();
+        return userDup.saveAsync();
+      }).should.be.rejected;
+  });
+
+  describe('#email', function() {
+    it('should fail when saving without an email', function() {
+      user.email = '';
+      return user.saveAsync().should.be.rejected;
+    });
+  });
+
+  describe('#password', function() {
+    beforeEach(function() {
+      return user.saveAsync();
+    });
+
+    it('should authenticate user if valid', function() {
+      user.authenticate('password').should.be.true;
+    });
+
+    it('should not authenticate user if invalid', function() {
+      user.authenticate('blah').should.not.be.true;
+    });
+
+    it('should remain the same hash unless the password is updated', function() {
+      user.name = 'Test User';
+      return user.saveAsync()
+        .spread(function(u) {
+          return u.authenticate('password');
+        }).should.eventually.be.true;
+    });
+  });
+
+});
diff --git a/server/app.js b/server/app.js
new file mode 100755 (executable)
index 0000000..34ece29
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * Main application file
+ */
+
+'use strict';
+
+// Set default node environment to development
+process.env.NODE_ENV = process.env.NODE_ENV || 'development';
+
+var express = require('express');
+var mongoose = require('mongoose');
+var config = require('./config/environment');
+
+// Connect to MongoDB
+mongoose.connect(config.mongo.uri, config.mongo.options);
+mongoose.connection.on('error', function(err) {
+  console.error('MongoDB connection error: ' + err);
+  process.exit(-1);
+});
+
+// Populate databases with sample data
+if (config.seedDB) { require('./config/seed'); }
+
+// Setup server
+var app = express();
+var server = require('http').createServer(app);
+require('./config/express')(app);
+require('./routes')(app);
+
+// Start server
+function startServer() {
+  server.listen(config.port, config.ip, function() {
+    console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
+  });
+}
+
+setImmediate(startServer);
+
+// Expose app
+exports = module.exports = app;
diff --git a/server/auth/auth.service.js b/server/auth/auth.service.js
new file mode 100755 (executable)
index 0000000..5952757
--- /dev/null
@@ -0,0 +1,88 @@
+'use strict';
+
+var passport = require('passport');
+var config = require('../config/environment');
+var jwt = require('jsonwebtoken');
+var expressJwt = require('express-jwt');
+var compose = require('composable-middleware');
+var User = require('../api/user/user.model');
+var validateJwt = expressJwt({
+  secret: config.secrets.session
+});
+
+/**
+ * Attaches the user object to the request if authenticated
+ * Otherwise returns 403
+ */
+function isAuthenticated() {
+  return compose()
+    // Validate jwt
+    .use(function(req, res, next) {
+      // allow access_token to be passed through query parameter as well
+      if (req.query && req.query.hasOwnProperty('access_token')) {
+        req.headers.authorization = 'Bearer ' + req.query.access_token;
+      }
+      validateJwt(req, res, next);
+    })
+    // Attach user to request
+    .use(function(req, res, next) {
+      User.findByIdAsync(req.user._id)
+        .then(function(user) {
+          if (!user) {
+            return res.status(401).end();
+          }
+          req.user = user;
+          next();
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    });
+}
+
+/**
+ * Checks if the user role meets the minimum requirements of the route
+ */
+function hasRole(roleRequired) {
+  if (!roleRequired) {
+    throw new Error('Required role needs to be set');
+  }
+
+  return compose()
+    .use(isAuthenticated())
+    .use(function meetsRequirements(req, res, next) {
+      if (config.userRoles.indexOf(req.user.role) >=
+          config.userRoles.indexOf(roleRequired)) {
+        next();
+      }
+      else {
+        res.status(403).send('Forbidden');
+      }
+    });
+}
+
+/**
+ * Returns a jwt token signed by the app secret
+ */
+function signToken(id, role) {
+  return jwt.sign({ _id: id, role: role }, config.secrets.session, {
+    expiresInMinutes: 60 * 5
+  });
+}
+
+/**
+ * Set token cookie directly for oAuth strategies
+ */
+function setTokenCookie(req, res) {
+  if (!req.user) {
+    return res.status(404).send('Something went wrong, please try again.');
+  }
+  var token = signToken(req.user._id, req.user.role);
+  res.cookie('token', JSON.stringify(token));
+  res.redirect('/');
+}
+
+exports.isAuthenticated = isAuthenticated;
+exports.hasRole = hasRole;
+exports.signToken = signToken;
+exports.setTokenCookie = setTokenCookie;
diff --git a/server/auth/facebook/index.js b/server/auth/facebook/index.js
new file mode 100755 (executable)
index 0000000..f13d463
--- /dev/null
@@ -0,0 +1,21 @@
+'use strict';
+
+var express = require('express');
+var passport = require('passport');
+var auth = require('../auth.service');
+
+var router = express.Router();
+
+router
+  .get('/', passport.authenticate('facebook', {
+    scope: ['email', 'user_about_me'],
+    failureRedirect: '/signup',
+    session: false
+  }))
+
+  .get('/callback', passport.authenticate('facebook', {
+    failureRedirect: '/signup',
+    session: false
+  }), auth.setTokenCookie);
+
+module.exports = router;
diff --git a/server/auth/facebook/passport.js b/server/auth/facebook/passport.js
new file mode 100755 (executable)
index 0000000..5f6e8e4
--- /dev/null
@@ -0,0 +1,42 @@
+var passport = require('passport');
+var FacebookStrategy = require('passport-facebook').Strategy;
+
+exports.setup = function(User, config) {
+  passport.use(new FacebookStrategy({
+    clientID: config.facebook.clientID,
+    clientSecret: config.facebook.clientSecret,
+    callbackURL: config.facebook.callbackURL,
+    profileFields: [
+      'displayName',
+      'emails'
+    ]
+  },
+  function(accessToken, refreshToken, profile, done) {
+    User.findOneAsync({
+      'facebook.id': profile.id
+    })
+      .then(function(user) {
+        if (!user) {
+          user = new User({
+            name: profile.displayName,
+            email: profile.emails[0].value,
+            role: 'user',
+            provider: 'facebook',
+            facebook: profile._json
+          });
+          user.saveAsync()
+            .then(function(user) {
+              return done(null, user);
+            })
+            .catch(function(err) {
+              return done(err);
+            });
+        } else {
+          return done(null, user);
+        }
+      })
+      .catch(function(err) {
+        return done(err);
+      });
+  }));
+};
diff --git a/server/auth/google/index.js b/server/auth/google/index.js
new file mode 100755 (executable)
index 0000000..7789def
--- /dev/null
@@ -0,0 +1,24 @@
+'use strict';
+
+var express = require('express');
+var passport = require('passport');
+var auth = require('../auth.service');
+
+var router = express.Router();
+
+router
+  .get('/', passport.authenticate('google', {
+    failureRedirect: '/signup',
+    scope: [
+      'profile',
+      'email'
+    ],
+    session: false
+  }))
+
+  .get('/callback', passport.authenticate('google', {
+    failureRedirect: '/signup',
+    session: false
+  }), auth.setTokenCookie);
+
+module.exports = router;
diff --git a/server/auth/google/passport.js b/server/auth/google/passport.js
new file mode 100755 (executable)
index 0000000..72edb09
--- /dev/null
@@ -0,0 +1,39 @@
+var passport = require('passport');
+var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
+
+exports.setup = function(User, config) {
+  passport.use(new GoogleStrategy({
+    clientID: config.google.clientID,
+    clientSecret: config.google.clientSecret,
+    callbackURL: config.google.callbackURL
+  },
+  function(accessToken, refreshToken, profile, done) {
+    User.findOneAsync({
+      'google.id': profile.id
+    })
+      .then(function(user) {
+        if (!user) {
+          user = new User({
+            name: profile.displayName,
+            email: profile.emails[0].value,
+            role: 'user',
+            username: profile.emails[0].value.split('@')[0],
+            provider: 'google',
+            google: profile._json
+          });
+          user.saveAsync()
+            .then(function(user) {
+              return done(null, user);
+            })
+            .catch(function(err) {
+              return done(err);
+            });
+        } else {
+          return done(null, user);
+        }
+      })
+      .catch(function(err) {
+        return done(err);
+      });
+  }));
+};
diff --git a/server/auth/index.js b/server/auth/index.js
new file mode 100755 (executable)
index 0000000..55c01c6
--- /dev/null
@@ -0,0 +1,21 @@
+'use strict';
+
+var express = require('express');
+var passport = require('passport');
+var config = require('../config/environment');
+var User = require('../api/user/user.model');
+
+// Passport Configuration
+require('./local/passport').setup(User, config);
+require('./facebook/passport').setup(User, config);
+require('./google/passport').setup(User, config);
+require('./twitter/passport').setup(User, config);
+
+var router = express.Router();
+
+router.use('/local', require('./local'));
+router.use('/facebook', require('./facebook'));
+router.use('/twitter', require('./twitter'));
+router.use('/google', require('./google'));
+
+module.exports = router;
diff --git a/server/auth/local/index.js b/server/auth/local/index.js
new file mode 100755 (executable)
index 0000000..e94d0da
--- /dev/null
@@ -0,0 +1,24 @@
+'use strict';
+
+var express = require('express');
+var passport = require('passport');
+var auth = require('../auth.service');
+
+var router = express.Router();
+
+router.post('/', function(req, res, next) {
+  passport.authenticate('local', function(err, user, info) {
+    var error = err || info;
+    if (error) {
+      return res.status(401).json(error);
+    }
+    if (!user) {
+      return res.status(404).json({message: 'Something went wrong, please try again.'});
+    }
+
+    var token = auth.signToken(user._id, user.role);
+    res.json({ token: token });
+  })(req, res, next)
+});
+
+module.exports = router;
diff --git a/server/auth/local/passport.js b/server/auth/local/passport.js
new file mode 100755 (executable)
index 0000000..8b298c9
--- /dev/null
@@ -0,0 +1,39 @@
+var passport = require('passport');
+var LocalStrategy = require('passport-local').Strategy;
+
+function localAuthenticate(User, email, password, done) {
+  User.findOneAsync({
+    email: email.toLowerCase()
+  })
+    .then(function(user) {
+      if (!user) {
+        return done(null, false, {
+          message: 'This email is not registered.'
+        });
+      }
+      user.authenticate(password, function(authError, authenticated) {
+        if (authError) {
+          return done(authError);
+        }
+        if (!authenticated) {
+          return done(null, false, {
+            message: 'This password is not correct.'
+          });
+        } else {
+          return done(null, user);
+        }
+      });
+    })
+    .catch(function(err) {
+      return done(err);
+    });
+}
+
+exports.setup = function(User, config) {
+  passport.use(new LocalStrategy({
+    usernameField: 'email',
+    passwordField: 'password' // this is the virtual field on the model
+  }, function(email, password, done) {
+    return localAuthenticate(User, email, password, done);
+  }));
+};
diff --git a/server/auth/twitter/index.js b/server/auth/twitter/index.js
new file mode 100755 (executable)
index 0000000..8e6f32b
--- /dev/null
@@ -0,0 +1,20 @@
+'use strict';
+
+var express = require('express');
+var passport = require('passport');
+var auth = require('../auth.service');
+
+var router = express.Router();
+
+router
+  .get('/', passport.authenticate('twitter', {
+    failureRedirect: '/signup',
+    session: false
+  }))
+
+  .get('/callback', passport.authenticate('twitter', {
+    failureRedirect: '/signup',
+    session: false
+  }), auth.setTokenCookie);
+
+module.exports = router;
diff --git a/server/auth/twitter/passport.js b/server/auth/twitter/passport.js
new file mode 100755 (executable)
index 0000000..53ff035
--- /dev/null
@@ -0,0 +1,38 @@
+exports.setup = function(User, config) {
+  var passport = require('passport');
+  var TwitterStrategy = require('passport-twitter').Strategy;
+
+  passport.use(new TwitterStrategy({
+    consumerKey: config.twitter.clientID,
+    consumerSecret: config.twitter.clientSecret,
+    callbackURL: config.twitter.callbackURL
+  },
+  function(token, tokenSecret, profile, done) {
+    User.findOneAsync({
+      'twitter.id_str': profile.id
+    })
+      .then(function(user) {
+        if (!user) {
+          user = new User({
+            name: profile.displayName,
+            username: profile.username,
+            role: 'user',
+            provider: 'twitter',
+            twitter: profile._json
+          });
+          user.saveAsync()
+            .then(function(user) {
+              return done(null, user);
+            })
+            .catch(function(err) {
+              return done(err);
+            });
+        } else {
+          return done(null, user);
+        }
+      })
+      .catch(function(err) {
+        return done(err);
+      });
+  }));
+};
diff --git a/server/components/errors/index.js b/server/components/errors/index.js
new file mode 100755 (executable)
index 0000000..ba71c73
--- /dev/null
@@ -0,0 +1,22 @@
+/**
+ * Error responses
+ */
+
+'use strict';
+
+module.exports[404] = function pageNotFound(req, res) {
+  var viewFilePath = '404';
+  var statusCode = 404;
+  var result = {
+    status: statusCode
+  };
+
+  res.status(result.status);
+  res.render(viewFilePath, {}, function(err, html) {
+    if (err) {
+      return res.json(result, result.status);
+    }
+
+    res.send(html);
+  });
+};
diff --git a/server/config/environment/development.js b/server/config/environment/development.js
new file mode 100755 (executable)
index 0000000..8ae9f93
--- /dev/null
@@ -0,0 +1,22 @@
+'use strict';
+
+// Development specific configuration
+// ==================================
+module.exports = {
+  // MongoDB connection options
+  mongo: {
+    uri: 'mongodb://localhost/twotoc-dev'
+  },
+  sequelize: {
+    uri: 'sqlite://',
+    options: {
+      logging: false,
+      storage: 'dev.sqlite',
+      define: {
+        timestamps: false
+      }
+    }
+  },
+
+  seedDB: true
+};
diff --git a/server/config/environment/index.js b/server/config/environment/index.js
new file mode 100755 (executable)
index 0000000..504e586
--- /dev/null
@@ -0,0 +1,70 @@
+'use strict';
+
+var path = require('path');
+var _ = require('lodash');
+
+function requiredProcessEnv(name) {
+  if (!process.env[name]) {
+    throw new Error('You must set the ' + name + ' environment variable');
+  }
+  return process.env[name];
+}
+
+// All configurations will extend these options
+// ============================================
+var all = {
+  env: process.env.NODE_ENV,
+
+  // Root path of server
+  root: path.normalize(__dirname + '/../../..'),
+
+  // Server port
+  port: process.env.PORT || 9000,
+
+  // Server IP
+  ip: process.env.IP || '0.0.0.0',
+
+  // Should we populate the DB with sample data?
+  seedDB: false,
+
+  // Secret for session, you will want to change this and make it an environment variable
+  secrets: {
+    session: 'dashboard-secret'
+  },
+
+  // List of user roles
+  userRoles: ['guest', 'user', 'admin'],
+
+  // MongoDB connection options
+  mongo: {
+    options: {
+      db: {
+        safe: true
+      }
+    }
+  },
+
+  facebook: {
+    clientID:     process.env.FACEBOOK_ID || 'id',
+    clientSecret: process.env.FACEBOOK_SECRET || 'secret',
+    callbackURL:  (process.env.DOMAIN || '') + '/auth/facebook/callback'
+  },
+
+  twitter: {
+    clientID:     process.env.TWITTER_ID || 'id',
+    clientSecret: process.env.TWITTER_SECRET || 'secret',
+    callbackURL:  (process.env.DOMAIN || '') + '/auth/twitter/callback'
+  },
+
+  google: {
+    clientID:     process.env.GOOGLE_ID || 'id',
+    clientSecret: process.env.GOOGLE_SECRET || 'secret',
+    callbackURL:  (process.env.DOMAIN || '') + '/auth/google/callback'
+  }
+};
+
+// Export the config object based on the NODE_ENV
+// ==============================================
+module.exports = _.merge(
+  all,
+  require('./' + process.env.NODE_ENV + '.js') || {});
diff --git a/server/config/environment/production.js b/server/config/environment/production.js
new file mode 100755 (executable)
index 0000000..037dfe2
--- /dev/null
@@ -0,0 +1,24 @@
+'use strict';
+
+// Production specific configuration
+// =================================
+module.exports = {
+  // Server IP
+  ip:       process.env.OPENSHIFT_NODEJS_IP ||
+            process.env.IP ||
+            undefined,
+
+  // Server port
+  port:     process.env.OPENSHIFT_NODEJS_PORT ||
+            process.env.PORT ||
+            8080,
+
+  // MongoDB connection options
+  mongo: {
+    uri:    process.env.MONGOLAB_URI ||
+            process.env.MONGOHQ_URL ||
+            process.env.OPENSHIFT_MONGODB_DB_URL +
+            process.env.OPENSHIFT_APP_NAME ||
+            'mongodb://localhost/twotoc'
+  }
+};
diff --git a/server/config/environment/test.js b/server/config/environment/test.js
new file mode 100755 (executable)
index 0000000..129eb2c
--- /dev/null
@@ -0,0 +1,20 @@
+'use strict';
+
+// Test specific configuration
+// ===========================
+module.exports = {
+  // MongoDB connection options
+  mongo: {
+    uri: 'mongodb://localhost/twotoc-test'
+  },
+  sequelize: {
+    uri: 'sqlite://',
+    options: {
+      logging: false,
+      storage: 'test.sqlite',
+      define: {
+        timestamps: false
+      }
+    }
+  }
+};
diff --git a/server/config/express.js b/server/config/express.js
new file mode 100755 (executable)
index 0000000..aa389fc
--- /dev/null
@@ -0,0 +1,65 @@
+/**
+ * Express configuration
+ */
+
+'use strict';
+
+var express = require('express');
+var favicon = require('serve-favicon');
+var morgan = require('morgan');
+var compression = require('compression');
+var bodyParser = require('body-parser');
+var methodOverride = require('method-override');
+var cookieParser = require('cookie-parser');
+var errorHandler = require('errorhandler');
+var path = require('path');
+var config = require('./environment');
+var passport = require('passport');
+var session = require('express-session');
+var mongoStore = require('connect-mongo')(session);
+var mongoose = require('mongoose');
+
+module.exports = function(app) {
+  var env = app.get('env');
+
+  app.set('views', config.root + '/server/views');
+  app.engine('html', require('ejs').renderFile);
+  app.set('view engine', 'html');
+  app.use(compression());
+  app.use(bodyParser.urlencoded({ extended: false }));
+  app.use(bodyParser.json());
+  app.use(methodOverride());
+  app.use(cookieParser());
+  app.use(passport.initialize());
+
+  // Persist sessions with mongoStore / sequelizeStore
+  // We need to enable sessions for passport twitter because its an oauth 1.0 strategy
+  app.use(session({
+    secret: config.secrets.session,
+    resave: true,
+    saveUninitialized: true,
+    store: new mongoStore({
+      mongooseConnection: mongoose.connection,
+      db: 'dashboard'
+    })
+  }));
+
+  app.set('appPath', path.join(config.root, 'client'));
+
+  if ('production' === env) {
+    app.use(favicon(path.join(config.root, 'client', 'favicon.ico')));
+    app.use(express.static(app.get('appPath')));
+    app.use(morgan('dev'));
+  }
+
+  if ('development' === env) {
+    app.use(require('connect-livereload')());
+  }
+
+  if ('development' === env || 'test' === env) {
+    app.use(express.static(path.join(config.root, '.tmp')));
+    app.use(express.static(app.get('appPath')));
+    app.use(morgan('dev'));
+    app.use(errorHandler()); // Error handler - has to be last
+  }
+};
diff --git a/server/config/local.env.sample.js b/server/config/local.env.sample.js
new file mode 100755 (executable)
index 0000000..8c49d08
--- /dev/null
@@ -0,0 +1,23 @@
+'use strict';
+
+// Use local.env.js for environment variables that grunt will set when the server starts locally.
+// Use for your api keys, secrets, etc. This file should not be tracked by git.
+//
+// You will need to set these on the server you deploy to.
+
+module.exports = {
+  DOMAIN:           'http://localhost:9000',
+  SESSION_SECRET:   'dashboard-secret',
+
+  FACEBOOK_ID:      'app-id',
+  FACEBOOK_SECRET:  'secret',
+
+  TWITTER_ID:       'app-id',
+  TWITTER_SECRET:   'secret',
+
+  GOOGLE_ID:        'app-id',
+  GOOGLE_SECRET:    'secret',
+
+  // Control debug level for modules using visionmedia/debug
+  DEBUG: ''
+};
diff --git a/server/config/seed.js b/server/config/seed.js
new file mode 100755 (executable)
index 0000000..55d7756
--- /dev/null
@@ -0,0 +1,412 @@
+/**
+ * Populate DB with sample data on server start
+ * to disable, edit config/environment/index.js, and set `seedDB: false`
+ */
+
+'use strict';
+
+var User = require('../api/user/user.model');
+var Show = require('../api/show/show.model');
+var Comment = require('../api/comment/comment.model');
+var Category = require('../api/category/category.model');
+
+
+Comment.find({}).removeAsync()
+  .then(function() {
+    Comment.create(
+    {
+      setterId: '55f6befd4fa15d2104323a85',
+      getterId: '55f6befd4fa15d2104323a85',
+      text: 'Lorem ipsum dolor sit amet consectetur adipisici elit dfgfdg',
+      rating: 4,
+      active: true
+    },
+    {
+      setterId: '55f6befd4fa15d2104323a85',
+      getterId: '55f6befd4fa15d2104323a85',
+      text: 'Lorem ipsum dolor sit amet consectetur adipisici elit sdgdfgf',
+      rating: 3,
+      active: true
+    },
+    {
+      setterId: '55f6befd4fa15d2104323a85',
+      getterId: '55f6befd4fa15d2104323a85',
+      text: 'Lorem ipsum dolor sit amet consectetur adipisici elit fgdfgf',
+      rating: 5,
+      active: true
+    },
+    {
+      setterId: '55f6befd4fa15d2104323a85',
+      getterId: '55f6befd4fa15d2104323a85',
+      text: 'Lorem ipsum dolor sit amet consectetur adipisici elit fxgdfg',
+      rating: 4,
+      active: true
+    },
+    {
+      setterId: '55f6befd4fa15d2104323a85',
+      getterId: '55f6befd4fa15d2104323a85',
+      text: 'Lorem ipsum dolor sit amet consectetur adipisici elit sddf',
+      rating: 4,
+      active: true
+    },
+    {
+      setterId: '55f6befd4fa15d2104323a85',
+      getterId: '55f6befd4fa15d2104323a85',
+      text: 'Lorem ipsum dolor sit amet consectetur adipisici elit fffff',
+      rating: 3,
+      active: true
+    },
+    {
+      setterId: '55f6befd4fa15d2104323a85',
+      getterId: '55f6befd4fa15d2104323a85',
+      text: 'Lorem ipsum dolor sit amet consectetur adipisici elit dddd ',
+      rating: 5,
+      active: true
+    },
+    {
+      setterId: '55f6befd4fa15d2104323a85',
+      getterId: '55f6befd4fa15d2104323a85',
+      text: 'Lorem ipsum dolor sit amet consectetur adipisici elit aaa',
+      rating: 4,
+      active: true
+    }
+      );
+  });
+
+/*
+Category.find({}).removeAsync()
+  .then(function() {
+    Category.create(
+    {
+      name: 'Sport',
+      parent_id: 0,
+      order: 0,
+      active: true
+    },
+    {
+      name: 'Cinema',
+      parent_id: 0,
+      order: 0,
+      active: true
+    },
+    {
+      name: 'Serie TV',
+      parent_id: 0,
+      order: 0,
+      active: true
+    },
+    {
+      name: 'Reality',
+      parent_id: 0,
+      order: 0,
+      active: true
+    },
+    {
+      name: 'Videogames',
+      parent_id: 0,
+      order: 0,
+      active: true
+    },
+    {
+      name: 'Altro',
+      parent_id: 0,
+      order: 0,
+      active: true
+    }
+    );
+});
+*/
+
+
+Show.find({}).removeAsync()
+  .then(function() {
+    Show.createAsync(
+      {
+        image: '97211.jpg',
+        title: 'Breaking Bad',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'calcio.jpg',
+        title: 'Juventus - Manchester C.',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'jurassic.jpg',
+        title: 'Jurassic World',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'pixels.jpg',
+        title: 'Pixels',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'champions.jpg',
+        title: 'Juventus - Manchester C.',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'got.jpg',
+        title: 'Game of Thrones',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'pixels.jpg',
+        title: 'Pixels',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: '97211.jpg',
+        title: 'Breaking Bad',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'calcio.jpg',
+        title: 'Juventus - Manchester C.',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        date: '2015-09-15',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'jurassic.jpg',
+        title: 'Jurassic World',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'pixels.jpg',
+        title: 'Pixels',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'champions.jpg',
+        title: 'Juventus - Manchester C.',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'got.jpg',
+        title: 'Game of Thrones',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'pixels.jpg',
+        title: 'Pixels',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: '97211.jpg',
+        title: 'Breaking Bad',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'calcio.jpg',
+        title: 'Juventus - Manchester C.',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'jurassic.jpg',
+        title: 'Jurassic World',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'pixels.jpg',
+        title: 'Pixels',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'champions.jpg',
+        title: 'Juventus - Manchester C.',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'got.jpg',
+        title: 'Game of Thrones',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'pixels.jpg',
+        title: 'Pixels',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: '97211.jpg',
+        title: 'Breaking Bad',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'calcio.jpg',
+        title: 'Juventus - Manchester C.',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        date: '2015-09-15',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'jurassic.jpg',
+        title: 'Jurassic World',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'pixels.jpg',
+        title: 'Pixels',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'champions.jpg',
+        title: 'Juventus - Manchester C.',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'got.jpg',
+        title: 'Game of Thrones',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      },
+      {
+        image: 'pixels.jpg',
+        title: 'Pixels',
+        description: 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.',
+        userId: '55f6befd4fa15d2104323a85',
+        categoryId: '5613fa45b20b68d304a7f746',
+        services: ['POLLICI_32', 'ANIMALI_AMMESSI', 'WIFI', 'FUMATORI_AMMESSI', 'ARIA_CONDIZIONATA'],
+        location : [12.5, 41.9],
+        place: 'Roma, Italia'
+      }
+      )
+    .then(function() {
+      console.log('finished populating shows');
+    });
+  });
+
+  
\ No newline at end of file
diff --git a/server/data/db/local.0 b/server/data/db/local.0
new file mode 100755 (executable)
index 0000000..68c15aa
Binary files /dev/null and b/server/data/db/local.0 differ
diff --git a/server/data/db/local.ns b/server/data/db/local.ns
new file mode 100755 (executable)
index 0000000..8363199
Binary files /dev/null and b/server/data/db/local.ns differ
diff --git a/server/data/db/mongod.lock b/server/data/db/mongod.lock
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/server/data/db/storage.bson b/server/data/db/storage.bson
new file mode 100755 (executable)
index 0000000..7da5234
Binary files /dev/null and b/server/data/db/storage.bson differ
diff --git a/server/data/db/twotoc-dev.0 b/server/data/db/twotoc-dev.0
new file mode 100755 (executable)
index 0000000..b5aa01f
Binary files /dev/null and b/server/data/db/twotoc-dev.0 differ
diff --git a/server/data/db/twotoc-dev.ns b/server/data/db/twotoc-dev.ns
new file mode 100755 (executable)
index 0000000..482d8f5
Binary files /dev/null and b/server/data/db/twotoc-dev.ns differ
diff --git a/server/data/db/twotoc-test.0 b/server/data/db/twotoc-test.0
new file mode 100755 (executable)
index 0000000..23d5814
Binary files /dev/null and b/server/data/db/twotoc-test.0 differ
diff --git a/server/data/db/twotoc-test.ns b/server/data/db/twotoc-test.ns
new file mode 100755 (executable)
index 0000000..64011be
Binary files /dev/null and b/server/data/db/twotoc-test.ns differ
diff --git a/server/data/db/twotoc.0 b/server/data/db/twotoc.0
new file mode 100755 (executable)
index 0000000..358e694
Binary files /dev/null and b/server/data/db/twotoc.0 differ
diff --git a/server/data/db/twotoc.ns b/server/data/db/twotoc.ns
new file mode 100755 (executable)
index 0000000..c3c1846
Binary files /dev/null and b/server/data/db/twotoc.ns differ
diff --git a/server/index.js b/server/index.js
new file mode 100755 (executable)
index 0000000..fc65cd5
--- /dev/null
@@ -0,0 +1,7 @@
+'use strict';
+
+// Register the Babel require hook
+require('babel-core/register');
+
+// Export the application
+exports = module.exports = require('./app');
diff --git a/server/routes.js b/server/routes.js
new file mode 100755 (executable)
index 0000000..fb92942
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Main application routes
+ */
+
+'use strict';
+
+var errors = require('./components/errors');
+var path = require('path');
+
+module.exports = function(app) {
+
+  // Insert routes below
+  app.use('/api/comments', require('./api/comment'));
+  app.use('/api/categories', require('./api/category'));
+  app.use('/api/shows', require('./api/show'));
+  app.use('/api/test', require('./api/message'));
+  app.use('/api/users', require('./api/user'));
+
+  app.use('/auth', require('./auth'));
+
+  // All undefined asset or api routes should return a 404
+  app.route('/:url(api|auth|components|app|bower_components|assets)/*')
+   .get(errors[404]);
+
+  // All other routes should redirect to the index.html
+  app.route('/*')
+    .get(function(req, res) {
+      res.sendFile(path.resolve(app.get('appPath') + '/index.html'));
+    });
+};
diff --git a/server/views/404.html b/server/views/404.html
new file mode 100755 (executable)
index 0000000..ec98e3c
--- /dev/null
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Page Not Found :(</title>
+    <style>
+      ::-moz-selection {
+        background: #b3d4fc;
+        text-shadow: none;
+      }
+
+      ::selection {
+        background: #b3d4fc;
+        text-shadow: none;
+      }
+
+      html {
+        padding: 30px 10px;
+        font-size: 20px;
+        line-height: 1.4;
+        color: #737373;
+        background: #f0f0f0;
+        -webkit-text-size-adjust: 100%;
+        -ms-text-size-adjust: 100%;
+      }
+
+      html,
+      input {
+        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+      }
+
+      body {
+        max-width: 500px;
+        _width: 500px;
+        padding: 30px 20px 50px;
+        border: 1px solid #b3b3b3;
+        border-radius: 4px;
+        margin: 0 auto;
+        box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;
+        background: #fcfcfc;
+      }
+
+      h1 {
+        margin: 0 10px;
+        font-size: 50px;
+        text-align: center;
+      }
+
+      h1 span {
+        color: #bbb;
+      }
+
+      h3 {
+        margin: 1.5em 0 0.5em;
+      }
+
+      p {
+        margin: 1em 0;
+      }
+
+      ul {
+        padding: 0 0 0 40px;
+        margin: 1em 0;
+      }
+
+      .container {
+        max-width: 380px;
+        _width: 380px;
+        margin: 0 auto;
+      }
+
+      /* google search */
+
+      #goog-fixurl ul {
+        list-style: none;
+        padding: 0;
+        margin: 0;
+      }
+
+      #goog-fixurl form {
+        margin: 0;
+      }
+
+      #goog-wm-qt,
+      #goog-wm-sb {
+        border: 1px solid #bbb;
+        font-size: 16px;
+        line-height: normal;
+        vertical-align: top;
+        color: #444;
+        border-radius: 2px;
+      }
+
+      #goog-wm-qt {
+        width: 220px;
+        height: 20px;
+        padding: 5px;
+        margin: 5px 10px 0 0;
+        box-shadow: inset 0 1px 1px #ccc;
+      }
+
+      #goog-wm-sb {
+        display: inline-block;
+        height: 32px;
+        padding: 0 10px;
+        margin: 5px 0 0;
+        white-space: nowrap;
+        cursor: pointer;
+        background-color: #f5f5f5;
+        background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+        background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+        background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+        background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1);
+        -webkit-appearance: none;
+        -moz-appearance: none;
+        appearance: none;
+        *overflow: visible;
+        *display: inline;
+        *zoom: 1;
+      }
+
+      #goog-wm-sb:hover,
+      #goog-wm-sb:focus {
+        border-color: #aaa;
+        box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+        background-color: #f8f8f8;
+      }
+
+      #goog-wm-qt:hover,
+      #goog-wm-qt:focus {
+        border-color: #105cb6;
+        outline: 0;
+        color: #222;
+      }
+
+      input::-moz-focus-inner {
+        padding: 0;
+        border: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="container">
+      <h1>Not found <span>:(</span></h1>
+      <p>Sorry, but the page you were trying to view does not exist.</p>
+      <p>It looks like this was the result of either:</p>
+      <ul>
+        <li>a mistyped address</li>
+        <li>an out-of-date link</li>
+      </ul>
+      <script>
+        var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host;
+      </script>
+      <script src="//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
+    </div>
+  </body>
+</html>