--- /dev/null
+{
+ "directory": "client/bower_components"
+}
--- /dev/null
+# 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
--- /dev/null
+* text eol=lf
--- /dev/null
+node_modules
+public
+.tmp
+.sass-cache
+.idea
+client/bower_components
+dist
+/server/config/local.env.js
+npm-debug.log
--- /dev/null
+{
+ "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": ", "
+}
--- /dev/null
+language: node_js
+node_js:
+ - '0.12'
+before_script:
+ - npm install -g bower grunt-cli
+ - gem install sass
+ - bower install
+services: mongodb
--- /dev/null
+{
+ "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
--- /dev/null
+// 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'
+ ]);
+};
--- /dev/null
+# 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
+
+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.
--- /dev/null
+{
+ "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"
+ }
+}
--- /dev/null
+// 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
+ });
+};
--- /dev/null
+'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'))
--- /dev/null
+{
+ "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
+}
--- /dev/null
+// 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;
--- /dev/null
+{
+ "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
+}
--- /dev/null
+{
+ "extends": ".jshintrc",
+ "globals": {
+ "describe": true,
+ "it": true,
+ "before": true,
+ "beforeEach": true,
+ "after": true,
+ "afterEach": true,
+ "expect": true,
+ "assert": true,
+ "sinon": true
+ }
+}
--- /dev/null
+/**
+ * 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));
+};
--- /dev/null
+/**
+ * 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;
--- /dev/null
+'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();
+ });
+ });
+
+ });
+
+});
--- /dev/null
+'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);
--- /dev/null
+'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;
--- /dev/null
+'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;
+ });
+
+ });
+
+});
--- /dev/null
+/**
+ * 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));
+};
--- /dev/null
+/**
+ * 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;
--- /dev/null
+'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();
+ });
+ });
+
+ });
+
+});
--- /dev/null
+'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);
--- /dev/null
+'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;
--- /dev/null
+'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;
+ });
+
+ });
+
+});
--- /dev/null
+'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;
--- /dev/null
+'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;
+ });
+
+ });
+
+});
--- /dev/null
+/**
+ * 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));
+};
--- /dev/null
+/**
+ * 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;
--- /dev/null
+'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();
+ });
+ });
+
+ });
+
+});
--- /dev/null
+'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);
--- /dev/null
+'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;
--- /dev/null
+'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;
+ });
+
+ });
+
+});
--- /dev/null
+/**
+ * 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));
+};
--- /dev/null
+/**
+ * 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;
--- /dev/null
+'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();
+ });
+ });
+
+ });
+
+});
--- /dev/null
+'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);
--- /dev/null
+'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;
+ });
+ });
+
+});
--- /dev/null
+'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;
--- /dev/null
+'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;
+ });
+
+ });
+
+});
--- /dev/null
+'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('/');
+};
--- /dev/null
+/**
+ * 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;
--- /dev/null
+'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);
+ });
+ });
+});
--- /dev/null
+'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);
--- /dev/null
+'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;
+ });
+ });
+
+});
--- /dev/null
+/**
+ * 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;
--- /dev/null
+'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;
--- /dev/null
+'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;
--- /dev/null
+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);
+ });
+ }));
+};
--- /dev/null
+'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;
--- /dev/null
+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);
+ });
+ }));
+};
--- /dev/null
+'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;
--- /dev/null
+'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;
--- /dev/null
+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);
+ }));
+};
--- /dev/null
+'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;
--- /dev/null
+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);
+ });
+ }));
+};
--- /dev/null
+/**
+ * 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);
+ });
+};
--- /dev/null
+'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
+};
--- /dev/null
+'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') || {});
--- /dev/null
+'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'
+ }
+};
--- /dev/null
+'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
+ }
+ }
+ }
+};
--- /dev/null
+/**
+ * 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
+ }
+};
--- /dev/null
+'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: ''
+};
--- /dev/null
+/**
+ * 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
--- /dev/null
+'use strict';
+
+// Register the Babel require hook
+require('babel-core/register');
+
+// Export the application
+exports = module.exports = require('./app');
--- /dev/null
+/**
+ * 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'));
+ });
+};
--- /dev/null
+<!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>
<navbar></navbar>
<div class="container main-container">
- <div class="row">
+ <div class="row bg-grey">
<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="col-sm-6">
+ <form class="form" name="form" ng-submit="login(form)" novalidate>
<div class="form-group">
<label>Email</label>
<i class="fa fa-facebook"></i> {{ 'ACCEDI_FACEBOOK' | translate }}
</a>
</div>
+ <br>
</form>
</div>
</div>
<navbar></navbar>
<div class="container">
- <div class="row">
+ <div class="row bg-grey">
<div class="col-sm-12">
<h1>Change Password</h1>
</div>
<div class="form-group">
<label>Current Password</label>
- <input type="password" name="password" class="form-control" ng-model="user.oldPassword"
- mongoose-error/>
+ <input type="password" name="password" class="form-control" ng-model="user.oldPassword" mongoose-error/>
<p class="help-block" ng-show="form.password.$error.mongoose">
+ <br>
{{ 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/>
+ <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 class="help-block"> {{ message }} </p>
<button class="btn btn-lg btn-primary" type="submit">Save changes</button>
- </form>
+ <br>
+ <br>
+ </form>
+ </div>
</div>
- </div>
</div>
// injector
@import 'account/login/login.scss';
@import 'admin/admin.scss';
+@import 'come_funziona/come_funziona.scss';
+@import 'community/community.scss';
@import 'main/main.scss';
+@import 'organizza/organizza.scss';
@import 'partecipa/partecipa.scss';
+@import 'search/search.scss';
@import '../components/footer/footer.scss';
@import '../components/modal/modal.scss';
// endinjector
--- /dev/null
+'use strict';
+
+angular.module('dashboardApp')
+ .controller('Come_FunzionaCtrl', 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;
+ });
+});
--- /dev/null
+<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">Organizza</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>
--- /dev/null
+'use strict';
+
+angular.module('dashboardApp')
+ .config(function($stateProvider) {
+ $stateProvider
+ .state('come_funziona', {
+ url: '/come_funziona',
+ templateUrl: 'app/come_funziona/come_funziona.html',
+ controller: 'Come_FunzionaCtrl',
+ params: {
+ lng: null,
+ lat: null
+ }
+ })
+ /*.state('showOrga', {
+ url: '/:id',
+ templateUrl: 'app/organizza/showOrga/showOrg.html',
+ controller: 'ShowOrgaCtrl'
+ });*/
+ });
--- /dev/null
+.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
--- /dev/null
+'use strict';
+
+angular.module('dashboardApp')
+ .controller('CommunityCtrl', 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;
+ });
+});
--- /dev/null
+<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">Organizza</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>
--- /dev/null
+'use strict';
+
+angular.module('dashboardApp')
+ .config(function($stateProvider) {
+ $stateProvider
+ .state('community', {
+ url: '/community',
+ templateUrl: 'app/community/community.html',
+ controller: 'CommunityCtrl',
+ params: {
+ lng: null,
+ lat: null
+ }
+ })
+ /*.state('showOrga', {
+ url: '/:id',
+ templateUrl: 'app/organizza/showOrga/showOrg.html',
+ controller: 'ShowOrgaCtrl'
+ });*/
+ });
--- /dev/null
+.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
$scope.searchShow = function() {
console.log($scope.search);
- $state.go('partecipa', {lat: $scope.search.lat, lng: $scope.search.lng});
+ /*$state.go('partecipa', {lat: $scope.search.lat, lng: $scope.search.lng});*/
+ $state.go('search', {lat: $scope.search.lat, lng: $scope.search.lng});
};
});
--- /dev/null
+'use strict';
+
+angular.module('dashboardApp')
+ .controller('OrganizzaCtrl', 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;
+ });
+});
--- /dev/null
+<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">Organizza</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>
--- /dev/null
+'use strict';
+
+angular.module('dashboardApp')
+ .config(function($stateProvider) {
+ $stateProvider
+ .state('organizza', {
+ url: '/organizza',
+ templateUrl: 'app/organizza/organizza.html',
+ controller: 'OrganizzaCtrl',
+ params: {
+ lng: null,
+ lat: null
+ }
+ })
+ /*.state('showOrga', {
+ url: '/:id',
+ templateUrl: 'app/organizza/showOrga/showOrg.html',
+ controller: 'ShowOrgaCtrl'
+ });*/
+ });
--- /dev/null
+.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
datepicker-popup="dd-MM-yyyy"
is-open="datepickers.dataInizio"
datepicker-options="dateOptions"
- ng-readonly="true"
+ ng-readonly="false"
ng-required="true"
ng-click="open($event,'dataInizio','dataFine')">
<span class="input-group-btn">
</div>
</div>
-<footer></footer>
\ No newline at end of file
+<footer></footer>
--- /dev/null
+'use strict';
+
+angular.module('dashboardApp')
+ .controller('SearchCtrl', 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();
+ };
+ $scope.lat = $stateParams.lat;
+ $scope.lng = $stateParams.lng;
+
+ // load categories
+ $http.get('/api/categories?active=true').then(function(response) {
+ $scope.categories = response.data;
+ });
+});
--- /dev/null
+<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">Search</h1>
+ <h3 class="title">Scopri gli eventi in programma questa settimana</h3>
+ </div>
+ <div ng-app="dashboardApp" ng-controller="SearchCtrl">
+ <h3>{{ lat }}</h3>
+ <h3>{{ lng }}</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>
--- /dev/null
+'use strict';
+
+angular.module('dashboardApp')
+ .config(function($stateProvider) {
+ $stateProvider
+ .state('search', {
+ url: '/search',
+ templateUrl: 'app/search/search.html',
+ controller: 'SearchCtrl',
+ params: {
+ lng: null,
+ lat: null
+ }
+ })
+ /*.state('showOrga', {
+ url: '/:id',
+ templateUrl: 'app/organizza/showOrga/showOrg.html',
+ controller: 'ShowOrgaCtrl'
+ });*/
+ });
--- /dev/null
+.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
},
{
'title': 'Come funziona',
- 'state': 'come-funziona'
+ 'state': 'come_funziona'
}
];
<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/come_funziona/come_funziona.controller.js"></script>
+ <script src="app/come_funziona/come_funziona.js"></script>
+ <script src="app/community/community.controller.js"></script>
+ <script src="app/community/community.js"></script>
<script src="app/main/main.controller.js"></script>
<script src="app/main/main.js"></script>
+ <script src="app/organizza/organizza.controller.js"></script>
+ <script src="app/organizza/organizza.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="app/search/search.controller.js"></script>
+ <script src="app/search/search.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>
--- /dev/null
+'use strict';
+
+angular.module('dashboardApp')
+ .controller('ShowOrgaCtrl', 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;
+ });
+ };
+ });
--- /dev/null
+<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