--- /dev/null
+{
+ "directory": "client/bower_components"
+}
--- /dev/null
+*.coffee
\ No newline at end of file
--- /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=auto
\ No newline at end of file
--- /dev/null
+node_modules
+public
+.tmp
+.sass-cache
+client/bower_components
+dist
--- /dev/null
+language: node_js
+node_js:
+ - '0.8'
+ - '0.10'
+before_script:
+ - 'npm install -g bower grunt-cli'
+ - 'bower install'
+services: mongodb
\ No newline at end of file
--- /dev/null
+{
+ "generator-angular-fullstack": {
+ "insertRoutes": true,
+ "registerRoutesFile": "server/routes.js",
+ "routesNeedle": "// Insert routes below",
+ "insertSockets": true,
+ "registerSocketsFile": "server/config/socketio.js",
+ "socketsNeedle": "// Insert sockets below",
+ "filters": {
+ "js": true,
+ "html": true,
+ "css": true,
+ "uirouter": true,
+ "socketio": true,
+ "mongoose": true,
+ "auth": true
+ }
+ },
+ "generator-ng-component": {
+ "routeDirectory": "client/app/",
+ "directiveDirectory": "client/app/",
+ "filterDirectory": "client/app/",
+ "serviceDirectory": "client/app/",
+ "basePath": "client",
+ "filters": [
+ "uirouter"
+ ],
+ "extensions": [
+ "js",
+ "html",
+ "css"
+ ]
+ }
+}
\ No newline at end of file
--- /dev/null
+'use strict';
+
+module.exports = function (grunt) {
+
+ // 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',
+ injector: 'grunt-asset-injector'
+ });
+
+ // 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
+ yeoman: {
+ // configurable paths
+ client: require('./bower.json').appPath || 'client',
+ dist: 'dist'
+ },
+ express: {
+ options: {
+ port: process.env.PORT || 9000
+ },
+ dev: {
+ options: {
+ script: 'server/app.js',
+ debug: true
+ }
+ },
+ prod: {
+ options: {
+ script: 'dist/server/app.js'
+ }
+ }
+ },
+ open: {
+ server: {
+ url: 'http://localhost:<%= express.options.port %>'
+ }
+ },
+ watch: {
+ injectJS: {
+ files: [
+ '<%= yeoman.client %>/{app,components}/**/*.js',
+ '!<%= yeoman.client %>/{app,components}/**/*.spec.js',
+ '!<%= yeoman.client %>/{app,components}/**/*.mock.js',
+ '!<%= yeoman.client %>/app/app.js'],
+ tasks: ['injector:scripts']
+ },
+ injectCss: {
+ files: [
+ '<%= yeoman.client %>/{app,components}/**/*.css'
+ ],
+ tasks: ['injector:css']
+ },
+ mochaTest: {
+ files: ['server/**/*.spec.js'],
+ tasks: ['env:test', 'mochaTest']
+ },
+ jsTest: {
+ files: [
+ '<%= yeoman.client %>/{app,components}/**/*.spec.js',
+ '<%= yeoman.client %>/{app,components}/**/*.mock.js'
+ ],
+ tasks: ['newer:jshint:all', 'karma']
+ },
+ gruntfile: {
+ files: ['Gruntfile.js']
+ },
+ livereload: {
+ files: [
+ '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.css',
+ '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.html',
+ '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js',
+ '!{.tmp,<%= yeoman.client %>}{app,components}/**/*.spec.js',
+ '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.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,
+ nospawn: true //Without this option specified express won't be reloaded
+ }
+ }
+ },
+
+ // 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/{,*/}*.js']
+ },
+ all: [
+ '<%= yeoman.client %>/{app,components}/**/*.js',
+ '!<%= yeoman.client %>/{app,components}/**/*.spec.js',
+ '!<%= yeoman.client %>/{app,components}/**/*.mock.js'
+ ],
+ test: {
+ src: [
+ '<%= yeoman.client %>/{app,components}/**/*.spec.js',
+ '<%= yeoman.client %>/{app,components}/**/*.mock.js'
+ ]
+ }
+ },
+
+ // Empties folders to start fresh
+ clean: {
+ dist: {
+ files: [{
+ dot: true,
+ src: [
+ '.tmp',
+ '<%= yeoman.dist %>/*',
+ '!<%= yeoman.dist %>/.git*',
+ '!<%= yeoman.dist %>/.openshift',
+ '!<%= yeoman.dist %>/Procfile'
+ ]
+ }]
+ },
+ server: '.tmp'
+ },
+
+ // Add vendor prefixed styles
+ autoprefixer: {
+ options: {
+ 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/app.js',
+ 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
+ bowerInstall: {
+ target: {
+ src: '<%= yeoman.client %>/index.html',
+ ignorePath: '<%= yeoman.client %>/',
+ exclude: [/bootstrap-sass-official/, /bootstrap.js/]
+ }
+ },
+
+ // Renames files for browser caching purposes
+ rev: {
+ dist: {
+ files: {
+ src: [
+ '<%= yeoman.dist %>/public/{,*/}*.js',
+ '<%= yeoman.dist %>/public/{,*/}*.css',
+ '<%= yeoman.dist %>/public/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
+ '<%= yeoman.dist %>/public/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 %>/public'
+ }
+ },
+
+ // Performs rewrites based on rev and the useminPrepare configuration
+ usemin: {
+ html: ['<%= yeoman.dist %>/public/{,*/}*.html'],
+ css: ['<%= yeoman.dist %>/public/{,*/}*.css'],
+ js: ['<%= yeoman.dist %>/public/{,*/}*.js'],
+ options: {
+ assetsDirs: [
+ '<%= yeoman.dist %>/public',
+ '<%= yeoman.dist %>/public/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}',
+ dest: '<%= yeoman.dist %>/public/assets/images'
+ }]
+ }
+ },
+
+ svgmin: {
+ dist: {
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.client %>/assets/images',
+ src: '{,*/}*.svg',
+ dest: '<%= yeoman.dist %>/public/assets/images'
+ }]
+ }
+ },
+
+ // Allow the use of non-minsafe AngularJS files. Automatically makes it
+ // minsafe compatible so Uglify does not destroy the ng references
+ ngmin: {
+ 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: 'ocreApp',
+ 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 %>/*.html']
+ }
+ },
+
+ // Copies remaining files to places other tasks can use
+ copy: {
+ dist: {
+ files: [{
+ expand: true,
+ dot: true,
+ cwd: '<%= yeoman.client %>',
+ dest: '<%= yeoman.dist %>/public',
+ src: [
+ '*.{ico,png,txt}',
+ '.htaccess',
+ 'bower_components/**/*',
+ 'assets/images/{,*/}*.{webp}',
+ 'assets/fonts/**/*',
+ 'index.html'
+ ]
+ }, {
+ expand: true,
+ cwd: '.tmp/images',
+ dest: '<%= yeoman.dist %>/public/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']
+ }
+ },
+
+ // Run some tasks in parallel to speed up the build process
+ concurrent: {
+ server: [
+ ],
+ test: [
+ ],
+ debug: {
+ tasks: [
+ 'nodemon',
+ 'node-inspector'
+ ],
+ options: {
+ logConcurrentOutput: true
+ }
+ },
+ dist: [
+ 'imagemin',
+ 'svgmin'
+ ]
+ },
+
+ // Test settings
+ karma: {
+ unit: {
+ configFile: 'karma.conf.js',
+ singleRun: true
+ }
+ },
+
+ mochaTest: {
+ options: {
+ reporter: 'spec'
+ },
+ src: ['server/**/*.spec.js']
+ },
+
+ protractor: {
+ options: {
+ configFile: 'protractor.conf.js'
+ },
+ chrome: {
+ options: {
+ args: {
+ browser: 'chrome'
+ }
+ }
+ }
+ },
+
+ env: {
+ test: {
+ NODE_ENV: 'test'
+ },
+ prod: {
+ NODE_ENV: 'production'
+ },
+ all: require('./server/config/local.env')
+ },
+
+ 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}/**/*.js',
+ '!{.tmp,<%= yeoman.client %>}/app/app.js',
+ '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.spec.js',
+ '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js']
+ ]
+ }
+ },
+
+ // 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();
+ }, 500);
+ });
+
+ 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', 'open', 'express-keepalive']);
+ }
+
+ if (target === 'debug') {
+ return grunt.task.run([
+ 'clean:server',
+ 'env:all',
+ 'concurrent:server',
+ 'injector',
+ 'bowerInstall',
+ 'autoprefixer',
+ 'concurrent:debug'
+ ]);
+ }
+
+ grunt.task.run([
+ 'clean:server',
+ 'env:all',
+ 'concurrent:server',
+ 'injector',
+ 'bowerInstall',
+ 'autoprefixer',
+ '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) {
+ if (target === 'server') {
+ return grunt.task.run([
+ 'env:all',
+ 'env:test',
+ 'mochaTest'
+ ]);
+ }
+
+ else if (target === 'client') {
+ return grunt.task.run([
+ 'clean:server',
+ 'env:all',
+ 'concurrent:test',
+ 'injector',
+ 'autoprefixer',
+ 'karma'
+ ]);
+ }
+
+ else if (target === 'e2e') {
+ return grunt.task.run([
+ 'clean:server',
+ 'env:all',
+ 'env:test',
+ 'concurrent:test',
+ 'injector',
+ 'bowerInstall',
+ 'autoprefixer',
+ 'express:dev',
+ 'protractor'
+ ]);
+ }
+
+ else grunt.task.run([
+ 'test:server',
+ 'test:client'
+ ]);
+ });
+
+ grunt.registerTask('build', [
+ 'clean:dist',
+ 'concurrent:dist',
+ 'injector',
+ 'bowerInstall',
+ 'useminPrepare',
+ 'autoprefixer',
+ 'ngtemplates',
+ 'concat',
+ 'ngmin',
+ 'copy:dist',
+ 'cdnify',
+ 'cssmin',
+ 'uglify',
+ 'rev',
+ 'usemin'
+ ]);
+
+ grunt.registerTask('default', [
+ 'newer:jshint',
+ 'test',
+ 'build'
+ ]);
+};
--- /dev/null
+# README #
+
+This README would normally document whatever steps are necessary to get your application up and running.
+
+### What is this repository for? ###
+
+* Quick summary
+* Version
+* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
+
+### Install ###
+
+* Checkout
+* Run : "bower install & npm install"
+* Run : "mongod" to start moongoose with MongoDb
+* Run : "grunt serve" to launch app in browser
+
+### Contribution guidelines ###
+
+* Writing tests
+* Code review
+* Other guidelines
+
+### Who do I talk to? ###
+
+* Repo owner or admin
+* Other community or team contact
\ No newline at end of file
--- /dev/null
+{
+ "name": "ocre",
+ "version": "0.0.0",
+ "dependencies": {
+ "angular": ">=1.2.*",
+ "json3": "~3.3.1",
+ "es5-shim": "~3.0.1",
+ "jquery": "~1.11.0",
+ "bootstrap-sass-official": "~3.1.1",
+ "bootstrap": "~3.1.1",
+ "angular-resource": ">=1.2.*",
+ "angular-cookies": ">=1.2.*",
+ "angular-sanitize": ">=1.2.*",
+ "angular-bootstrap": "~0.11.0",
+ "font-awesome": ">=4.1.0",
+ "lodash": "~2.4.1",
+ "angular-socket-io": "~0.6.0",
+ "angular-ui-router": "~0.2.10"
+ },
+ "devDependencies": {
+ "angular-mocks": ">=1.2.*",
+ "angular-scenario": ">=1.2.*"
+ }
+}
--- /dev/null
+# Apache Configuration File
+
+# (!) Using `.htaccess` files slows down Apache, therefore, if you have access
+# to the main server config file (usually called `httpd.conf`), you should add
+# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
+
+# ##############################################################################
+# # CROSS-ORIGIN RESOURCE SHARING (CORS) #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Cross-domain AJAX requests |
+# ------------------------------------------------------------------------------
+
+# Enable cross-origin AJAX requests.
+# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
+# http://enable-cors.org/
+
+# <IfModule mod_headers.c>
+# Header set Access-Control-Allow-Origin "*"
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | CORS-enabled images |
+# ------------------------------------------------------------------------------
+
+# Send the CORS header for images when browsers request it.
+# https://developer.mozilla.org/en/CORS_Enabled_Image
+# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
+# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
+
+<IfModule mod_setenvif.c>
+ <IfModule mod_headers.c>
+ <FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
+ SetEnvIf Origin ":" IS_CORS
+ Header set Access-Control-Allow-Origin "*" env=IS_CORS
+ </FilesMatch>
+ </IfModule>
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Web fonts access |
+# ------------------------------------------------------------------------------
+
+# Allow access from all domains for web fonts
+
+<IfModule mod_headers.c>
+ <FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
+ Header set Access-Control-Allow-Origin "*"
+ </FilesMatch>
+</IfModule>
+
+
+# ##############################################################################
+# # ERRORS #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | 404 error prevention for non-existing redirected folders |
+# ------------------------------------------------------------------------------
+
+# Prevent Apache from returning a 404 error for a rewrite if a directory
+# with the same name does not exist.
+# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
+# http://www.webmasterworld.com/apache/3808792.htm
+
+Options -MultiViews
+
+# ------------------------------------------------------------------------------
+# | Custom error messages / pages |
+# ------------------------------------------------------------------------------
+
+# You can customize what Apache returns to the client in case of an error (see
+# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
+
+ErrorDocument 404 /404.html
+
+
+# ##############################################################################
+# # INTERNET EXPLORER #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Better website experience |
+# ------------------------------------------------------------------------------
+
+# Force IE to render pages in the highest available mode in the various
+# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
+
+<IfModule mod_headers.c>
+ Header set X-UA-Compatible "IE=edge"
+ # `mod_headers` can't match based on the content-type, however, we only
+ # want to send this header for HTML pages and not for the other resources
+ <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
+ Header unset X-UA-Compatible
+ </FilesMatch>
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Cookie setting from iframes |
+# ------------------------------------------------------------------------------
+
+# Allow cookies to be set from iframes in IE.
+
+# <IfModule mod_headers.c>
+# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | Screen flicker |
+# ------------------------------------------------------------------------------
+
+# Stop screen flicker in IE on CSS rollovers (this only works in
+# combination with the `ExpiresByType` directives for images from below).
+
+# BrowserMatch "MSIE" brokenvary=1
+# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
+# BrowserMatch "Opera" !brokenvary
+# SetEnvIf brokenvary 1 force-no-vary
+
+
+# ##############################################################################
+# # MIME TYPES AND ENCODING #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Proper MIME types for all files |
+# ------------------------------------------------------------------------------
+
+<IfModule mod_mime.c>
+
+ # Audio
+ AddType audio/mp4 m4a f4a f4b
+ AddType audio/ogg oga ogg
+
+ # JavaScript
+ # Normalize to standard type (it's sniffed in IE anyways):
+ # http://tools.ietf.org/html/rfc4329#section-7.2
+ AddType application/javascript js jsonp
+ AddType application/json json
+
+ # Video
+ AddType video/mp4 mp4 m4v f4v f4p
+ AddType video/ogg ogv
+ AddType video/webm webm
+ AddType video/x-flv flv
+
+ # Web fonts
+ AddType application/font-woff woff
+ AddType application/vnd.ms-fontobject eot
+
+ # Browsers usually ignore the font MIME types and sniff the content,
+ # however, Chrome shows a warning if other MIME types are used for the
+ # following fonts.
+ AddType application/x-font-ttf ttc ttf
+ AddType font/opentype otf
+
+ # Make SVGZ fonts work on iPad:
+ # https://twitter.com/FontSquirrel/status/14855840545
+ AddType image/svg+xml svg svgz
+ AddEncoding gzip svgz
+
+ # Other
+ AddType application/octet-stream safariextz
+ AddType application/x-chrome-extension crx
+ AddType application/x-opera-extension oex
+ AddType application/x-shockwave-flash swf
+ AddType application/x-web-app-manifest+json webapp
+ AddType application/x-xpinstall xpi
+ AddType application/xml atom rdf rss xml
+ AddType image/webp webp
+ AddType image/x-icon ico
+ AddType text/cache-manifest appcache manifest
+ AddType text/vtt vtt
+ AddType text/x-component htc
+ AddType text/x-vcard vcf
+
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | UTF-8 encoding |
+# ------------------------------------------------------------------------------
+
+# Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
+AddDefaultCharset utf-8
+
+# Force UTF-8 for certain file formats.
+<IfModule mod_mime.c>
+ AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
+</IfModule>
+
+
+# ##############################################################################
+# # URL REWRITES #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Rewrite engine |
+# ------------------------------------------------------------------------------
+
+# Turning on the rewrite engine and enabling the `FollowSymLinks` option is
+# necessary for the following directives to work.
+
+# If your web host doesn't allow the `FollowSymlinks` option, you may need to
+# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
+# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
+
+# Also, some cloud hosting services require `RewriteBase` to be set:
+# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
+
+<IfModule mod_rewrite.c>
+ Options +FollowSymlinks
+ # Options +SymLinksIfOwnerMatch
+ RewriteEngine On
+ # RewriteBase /
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Suppressing / Forcing the "www." at the beginning of URLs |
+# ------------------------------------------------------------------------------
+
+# The same content should never be available under two different URLs especially
+# not with and without "www." at the beginning. This can cause SEO problems
+# (duplicate content), therefore, you should choose one of the alternatives and
+# redirect the other one.
+
+# By default option 1 (no "www.") is activated:
+# http://no-www.org/faq.php?q=class_b
+
+# If you'd prefer to use option 2, just comment out all the lines from option 1
+# and uncomment the ones from option 2.
+
+# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Option 1: rewrite www.example.com → example.com
+
+<IfModule mod_rewrite.c>
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
+ RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
+</IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Option 2: rewrite example.com → www.example.com
+
+# Be aware that the following might not be a good idea if you use "real"
+# subdomains for certain parts of your website.
+
+# <IfModule mod_rewrite.c>
+# RewriteCond %{HTTPS} !=on
+# RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
+# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
+# </IfModule>
+
+
+# ##############################################################################
+# # SECURITY #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Content Security Policy (CSP) |
+# ------------------------------------------------------------------------------
+
+# You can mitigate the risk of cross-site scripting and other content-injection
+# attacks by setting a Content Security Policy which whitelists trusted sources
+# of content for your site.
+
+# The example header below allows ONLY scripts that are loaded from the current
+# site's origin (no inline scripts, no CDN, etc). This almost certainly won't
+# work as-is for your site!
+
+# To get all the details you'll need to craft a reasonable policy for your site,
+# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
+# see the specification: http://w3.org/TR/CSP).
+
+# <IfModule mod_headers.c>
+# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
+# <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
+# Header unset Content-Security-Policy
+# </FilesMatch>
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | File access |
+# ------------------------------------------------------------------------------
+
+# Block access to directories without a default document.
+# Usually you should leave this uncommented because you shouldn't allow anyone
+# to surf through every directory on your server (which may includes rather
+# private places like the CMS's directories).
+
+<IfModule mod_autoindex.c>
+ Options -Indexes
+</IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Block access to hidden files and directories.
+# This includes directories used by version control systems such as Git and SVN.
+
+<IfModule mod_rewrite.c>
+ RewriteCond %{SCRIPT_FILENAME} -d [OR]
+ RewriteCond %{SCRIPT_FILENAME} -f
+ RewriteRule "(^|/)\." - [F]
+</IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Block access to backup and source files.
+# These files may be left by some text editors and can pose a great security
+# danger when anyone has access to them.
+
+<FilesMatch "(^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$">
+ Order allow,deny
+ Deny from all
+ Satisfy All
+</FilesMatch>
+
+# ------------------------------------------------------------------------------
+# | Secure Sockets Layer (SSL) |
+# ------------------------------------------------------------------------------
+
+# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
+# prevent `https://www.example.com` when your certificate only allows
+# `https://secure.example.com`.
+
+# <IfModule mod_rewrite.c>
+# RewriteCond %{SERVER_PORT} !^443
+# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
+# </IfModule>
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Force client-side SSL redirection.
+
+# If a user types "example.com" in his browser, the above rule will redirect him
+# to the secure version of the site. That still leaves a window of opportunity
+# (the initial HTTP connection) for an attacker to downgrade or redirect the
+# request. The following header ensures that browser will ONLY connect to your
+# server via HTTPS, regardless of what the users type in the address bar.
+# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
+
+# <IfModule mod_headers.c>
+# Header set Strict-Transport-Security max-age=16070400;
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | Server software information |
+# ------------------------------------------------------------------------------
+
+# Avoid displaying the exact Apache version number, the description of the
+# generic OS-type and the information about Apache's compiled-in modules.
+
+# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
+
+# ServerTokens Prod
+
+
+# ##############################################################################
+# # WEB PERFORMANCE #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Compression |
+# ------------------------------------------------------------------------------
+
+<IfModule mod_deflate.c>
+
+ # Force compression for mangled headers.
+ # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
+ <IfModule mod_setenvif.c>
+ <IfModule mod_headers.c>
+ SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
+ RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
+ </IfModule>
+ </IfModule>
+
+ # Compress all output labeled with one of the following MIME-types
+ # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
+ # and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines
+ # as `AddOutputFilterByType` is still in the core directives).
+ <IfModule mod_filter.c>
+ AddOutputFilterByType DEFLATE application/atom+xml \
+ application/javascript \
+ application/json \
+ application/rss+xml \
+ application/vnd.ms-fontobject \
+ application/x-font-ttf \
+ application/x-web-app-manifest+json \
+ application/xhtml+xml \
+ application/xml \
+ font/opentype \
+ image/svg+xml \
+ image/x-icon \
+ text/css \
+ text/html \
+ text/plain \
+ text/x-component \
+ text/xml
+ </IfModule>
+
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Content transformations |
+# ------------------------------------------------------------------------------
+
+# Prevent some of the mobile network providers from modifying the content of
+# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
+
+# <IfModule mod_headers.c>
+# Header set Cache-Control "no-transform"
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | ETag removal |
+# ------------------------------------------------------------------------------
+
+# Since we're sending far-future expires headers (see below), ETags can
+# be removed: http://developer.yahoo.com/performance/rules.html#etags.
+
+# `FileETag None` is not enough for every server.
+<IfModule mod_headers.c>
+ Header unset ETag
+</IfModule>
+
+FileETag None
+
+# ------------------------------------------------------------------------------
+# | Expires headers (for better cache control) |
+# ------------------------------------------------------------------------------
+
+# The following expires headers are set pretty far in the future. If you don't
+# control versioning with filename-based cache busting, consider lowering the
+# cache time for resources like CSS and JS to something like 1 week.
+
+<IfModule mod_expires.c>
+
+ ExpiresActive on
+ ExpiresDefault "access plus 1 month"
+
+ # CSS
+ ExpiresByType text/css "access plus 1 year"
+
+ # Data interchange
+ ExpiresByType application/json "access plus 0 seconds"
+ ExpiresByType application/xml "access plus 0 seconds"
+ ExpiresByType text/xml "access plus 0 seconds"
+
+ # Favicon (cannot be renamed!)
+ ExpiresByType image/x-icon "access plus 1 week"
+
+ # HTML components (HTCs)
+ ExpiresByType text/x-component "access plus 1 month"
+
+ # HTML
+ ExpiresByType text/html "access plus 0 seconds"
+
+ # JavaScript
+ ExpiresByType application/javascript "access plus 1 year"
+
+ # Manifest files
+ ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
+ ExpiresByType text/cache-manifest "access plus 0 seconds"
+
+ # Media
+ ExpiresByType audio/ogg "access plus 1 month"
+ ExpiresByType image/gif "access plus 1 month"
+ ExpiresByType image/jpeg "access plus 1 month"
+ ExpiresByType image/png "access plus 1 month"
+ ExpiresByType video/mp4 "access plus 1 month"
+ ExpiresByType video/ogg "access plus 1 month"
+ ExpiresByType video/webm "access plus 1 month"
+
+ # Web feeds
+ ExpiresByType application/atom+xml "access plus 1 hour"
+ ExpiresByType application/rss+xml "access plus 1 hour"
+
+ # Web fonts
+ ExpiresByType application/font-woff "access plus 1 month"
+ ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
+ ExpiresByType application/x-font-ttf "access plus 1 month"
+ ExpiresByType font/opentype "access plus 1 month"
+ ExpiresByType image/svg+xml "access plus 1 month"
+
+</IfModule>
+
+# ------------------------------------------------------------------------------
+# | Filename-based cache busting |
+# ------------------------------------------------------------------------------
+
+# If you're not using a build process to manage your filename version revving,
+# you might want to consider enabling the following directives to route all
+# requests such as `/css/style.12345.css` to `/css/style.css`.
+
+# To understand why this is important and a better idea than `*.css?v231`, read:
+# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
+
+# <IfModule mod_rewrite.c>
+# RewriteCond %{REQUEST_FILENAME} !-f
+# RewriteCond %{REQUEST_FILENAME} !-d
+# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | File concatenation |
+# ------------------------------------------------------------------------------
+
+# Allow concatenation from within specific CSS and JS files, e.g.:
+# Inside of `script.combined.js` you could have
+# <!--#include file="libs/jquery.js" -->
+# <!--#include file="plugins/jquery.idletimer.js" -->
+# and they would be included into this single file.
+
+# <IfModule mod_include.c>
+# <FilesMatch "\.combined\.js$">
+# Options +Includes
+# AddOutputFilterByType INCLUDES application/javascript application/json
+# SetOutputFilter INCLUDES
+# </FilesMatch>
+# <FilesMatch "\.combined\.css$">
+# Options +Includes
+# AddOutputFilterByType INCLUDES text/css
+# SetOutputFilter INCLUDES
+# </FilesMatch>
+# </IfModule>
+
+# ------------------------------------------------------------------------------
+# | Persistent connections |
+# ------------------------------------------------------------------------------
+
+# Allow multiple requests to be sent over the same TCP connection:
+# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
+
+# Enable if you serve a lot of static content but, be aware of the
+# possible disadvantages!
+
+# <IfModule mod_headers.c>
+# Header set Connection Keep-Alive
+# </IfModule>
--- /dev/null
+{
+ "node": true,
+ "browser": true,
+ "esnext": true,
+ "bitwise": true,
+ "camelcase": true,
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "indent": 2,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "quotmark": "single",
+ "regexp": true,
+ "undef": true,
+ "unused": true,
+ "strict": true,
+ "trailing": true,
+ "smarttabs": true,
+ "globals": {
+ "jQuery": true,
+ "angular": true,
+ "console": true,
+ "$": true,
+ "_": true,
+ "moment": true,
+ "describe": true,
+ "beforeEach": true,
+ "module": true,
+ "inject": true,
+ "it": true,
+ "expect": true,
+ "browser": true,
+ "element": true,
+ "by": true
+ }
+}
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .config(function ($stateProvider) {
+ $stateProvider
+ .state('login', {
+ url: '/login',
+ templateUrl: 'app/account/login/login.html',
+ controller: 'LoginCtrl'
+ })
+ .state('signup', {
+ url: '/signup',
+ templateUrl: 'app/account/signup/signup.html',
+ controller: 'SignupCtrl'
+ })
+ .state('settings', {
+ url: '/settings',
+ templateUrl: 'app/account/settings/settings.html',
+ controller: 'SettingsCtrl'
+ });
+ });
\ No newline at end of file
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .controller('LoginCtrl', function ($scope, Auth, $location, $window) {
+ $scope.user = {};
+ $scope.errors = {};
+
+ $scope.login = function(form) {
+ $scope.submitted = true;
+
+ if(form.$valid) {
+ Auth.login({
+ email: $scope.user.email,
+ password: $scope.user.password
+ })
+ .then( function() {
+ // Logged in, redirect to home
+ $location.path('/');
+ })
+ .catch( function(err) {
+ $scope.errors.other = err.message;
+ });
+ }
+ };
+
+ $scope.loginOauth = function(provider) {
+ $window.location.href = '/auth/' + provider;
+ };
+ });
--- /dev/null
+.btn-facebook {
+ color: #fff;
+ background-color: #3B5998;
+ border-color: #133783;
+}
+
+.btn-twitter {
+ color: #fff;
+ background-color: #2daddc;
+ border-color: #0271bf;
+}
+
+.btn-github {
+ color: #fff;
+ background-color: #fafafa;
+ border-color: #ccc;
+}
+
+.btn-google-plus {
+ color: #fff;
+ background-color: #dd4b39;
+ border-color: #c53727;
+}
\ No newline at end of file
--- /dev/null
+<div ng-include="'components/navbar/navbar.html'"></div>
+
+<div class="container" style="margin-top:50px;">
+ <div class="row">
+ <div class="col-sm-12">
+ <h1>Login</h1>
+ <p>Accounts are reset on server restart from <code>server/config/seed.js</code>. Default account is <code>test@test.com</code> / <code>test</code></p>
+ <p>Admin account is <code>admin@admin.com</code> / <code>admin</code></p>
+ </div>
+ <div class="col-sm-12">
+ <form class="form" name="form" ng-submit="login(form)" novalidate>
+
+ <div class="form-group">
+ <label>Email</label>
+
+ <input type="text" name="email" class="form-control" ng-model="user.email">
+ </div>
+
+ <div class="form-group">
+ <label>Password</label>
+
+ <input type="password" name="password" class="form-control" ng-model="user.password">
+ </div>
+
+ <div class="form-group has-error">
+ <p class="help-block" ng-show="form.email.$error.required && form.password.$error.required && submitted">
+ Please enter your email and password.
+ </p>
+ <p class="help-block">{{ errors.other }}</p>
+ </div>
+
+ <div>
+ <button class="btn btn-ochre btn-lg btn-login" type="submit">
+ Login
+ </button>
+ <a class="btn btn-default btn-lg btn-register" href="/signup">
+ Register
+ </a>
+ </div>
+
+
+ </form>
+ </div>
+ </div>
+ <hr>
+</div>
\ No newline at end of file
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .controller('SettingsCtrl', function ($scope, User, Auth) {
+ $scope.errors = {};
+
+ $scope.changePassword = function(form) {
+ $scope.submitted = true;
+ if(form.$valid) {
+ Auth.changePassword( $scope.user.oldPassword, $scope.user.newPassword )
+ .then( function() {
+ $scope.message = 'Password successfully changed.';
+ })
+ .catch( function() {
+ form.password.$setValidity('mongoose', false);
+ $scope.errors.other = 'Incorrect password';
+ $scope.message = '';
+ });
+ }
+ };
+ });
--- /dev/null
+<div ng-include="'components/navbar/navbar.html'"></div>
+
+<div class="container">
+ <div class="row">
+ <div class="col-sm-12">
+ <h1>Change Password</h1>
+ </div>
+ <div class="col-sm-12">
+ <form class="form" name="form" ng-submit="changePassword(form)" novalidate>
+
+ <div class="form-group">
+ <label>Current Password</label>
+
+ <input type="password" name="password" class="form-control" ng-model="user.oldPassword"
+ mongoose-error/>
+ <p class="help-block" ng-show="form.password.$error.mongoose">
+ {{ errors.other }}
+ </p>
+ </div>
+
+ <div class="form-group">
+ <label>New Password</label>
+
+ <input type="password" name="newPassword" class="form-control" ng-model="user.newPassword"
+ ng-minlength="3"
+ required/>
+ <p class="help-block"
+ ng-show="(form.newPassword.$error.minlength || form.newPassword.$error.required) && (form.newPassword.$dirty || submitted)">
+ Password must be at least 3 characters.
+ </p>
+ </div>
+
+ <p class="help-block"> {{ message }} </p>
+
+ <button class="btn btn-lg btn-primary" type="submit">Save changes</button>
+ </form>
+ </div>
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .controller('SignupCtrl', function ($scope, Auth, $location) {
+ $scope.user = {};
+ $scope.errors = {};
+
+ $scope.register = function(form) {
+ $scope.submitted = true;
+
+ if(form.$valid) {
+ Auth.createUser({
+ name: $scope.user.name,
+ email: $scope.user.email,
+ password: $scope.user.password
+ })
+ .then( function() {
+ // Account created, redirect to home
+ $location.path('/');
+ })
+ .catch( function(err) {
+ err = err.data;
+ $scope.errors = {};
+
+ // Update validity of form fields that match the mongoose errors
+ angular.forEach(err.errors, function(error, field) {
+ form[field].$setValidity('mongoose', false);
+ $scope.errors[field] = error.message;
+ });
+ });
+ }
+ };
+ });
\ No newline at end of file
--- /dev/null
+<div ng-include="'components/navbar/navbar.html'"></div>
+
+<div class="container" style="margin-top:50px;">
+ <div class="row">
+ <div class="col-sm-12">
+ <h1>Sign up</h1>
+ </div>
+ <div class="col-sm-12">
+ <form class="form" name="form" ng-submit="register(form)" novalidate>
+
+ <div class="form-group" ng-class="{ 'has-success': form.name.$valid && submitted,
+ 'has-error': form.name.$invalid && submitted }">
+ <label>Name</label>
+
+ <input type="text" name="name" class="form-control" ng-model="user.name"
+ required/>
+ <p class="help-block" ng-show="form.name.$error.required && submitted">
+ A name is required
+ </p>
+ </div>
+
+ <div class="form-group" ng-class="{ 'has-success': form.email.$valid && submitted,
+ 'has-error': form.email.$invalid && submitted }">
+ <label>Email</label>
+
+ <input type="email" name="email" class="form-control" ng-model="user.email"
+ required
+ mongoose-error/>
+ <p class="help-block" ng-show="form.email.$error.email && submitted">
+ Doesn't look like a valid email.
+ </p>
+ <p class="help-block" ng-show="form.email.$error.required && submitted">
+ What's your email address?
+ </p>
+ <p class="help-block" ng-show="form.email.$error.mongoose">
+ {{ errors.email }}
+ </p>
+ </div>
+
+ <div class="form-group" ng-class="{ 'has-success': form.password.$valid && submitted,
+ 'has-error': form.password.$invalid && submitted }">
+ <label>Password</label>
+
+ <input type="password" name="password" class="form-control" ng-model="user.password"
+ ng-minlength="3"
+ required
+ mongoose-error/>
+ <p class="help-block"
+ ng-show="(form.password.$error.minlength || form.password.$error.required) && submitted">
+ Password must be at least 3 characters.
+ </p>
+ <p class="help-block" ng-show="form.password.$error.mongoose">
+ {{ errors.password }}
+ </p>
+ </div>
+
+ <div>
+ <button class="btn btn-ochre btn-lg btn-login" type="submit">
+ Sign up
+ </button>
+ <a class="btn btn-default btn-lg btn-register" href="/login">
+ Login
+ </a>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .controller('AdminCtrl', function ($scope, $http, Auth, User) {
+
+ $http.get('/api/users').success(function(users) {
+ $scope.users = users;
+ });
+
+ $scope.delete = function(user) {
+ User.remove({ id: user._id });
+ angular.forEach($scope.users, function(u, i) {
+ if (u === user) {
+ $scope.users.splice(i, 1);
+ }
+ });
+ };
+ });
--- /dev/null
+.trash { color:rgb(209, 91, 71); }
--- /dev/null
+<div ng-include="'components/navbar/navbar.html'"></div>
+
+<div class="container">
+ <p>The delete user and user index api routes are restricted to users with the 'admin' role.</p>
+ <ul class="list-group">
+ <li class="list-group-item" ng-repeat="user in users">
+ <strong>{{user.name}}</strong><br>
+ <span class="text-muted">{{user.email}}</span>
+ <a ng-click="delete(user)" class="trash"><span class="glyphicon glyphicon-trash pull-right"></span></a>
+ </li>
+ </ul>
+</div>
\ No newline at end of file
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .config(function ($stateProvider) {
+ $stateProvider
+ .state('admin', {
+ url: '/admin',
+ templateUrl: 'app/admin/admin.html',
+ controller: 'AdminCtrl'
+ });
+ });
\ No newline at end of file
--- /dev/null
+/**
+* App-wide Styles
+*/
+
+.browsehappy {
+ margin: 0.2em 0;
+ background: #ccc;
+ color: #000;
+ padding: 0.2em 0;
+}
+
+.btn-ochre {
+ color: #fff;
+ background-color: #cc7722;
+ border-color: #cc7722;
+}
+
+.navbar {
+ background-color: white;
+ border-color: #cc7722;
+}
+
+.carousel {
+ background-color: #cc7722;
+}
+
+.carousel img {
+ min-width: 100%;
+ min-height: 100%;
+ max-width: none;
+ max-height: none;
+
+}
+
+/* Responsive: Portrait tablets and up */
+@media screen and (min-width: 768px) {
+ .container {
+ max-width: 730px;
+ }
+}
\ No newline at end of file
--- /dev/null
+'use strict';
+
+angular.module('ocreApp', [
+ 'ngCookies',
+ 'ngResource',
+ 'ngSanitize',
+ 'ui.bootstrap',
+ 'btford.socket-io',
+ 'ui.router'
+])
+ .config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) {
+ $urlRouterProvider
+ .otherwise('/');
+
+ $locationProvider.html5Mode(true);
+ $httpProvider.interceptors.push('authInterceptor');
+ })
+
+ .factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
+ return {
+ // Add authorization token to headers
+ request: function (config) {
+ config.headers = config.headers || {};
+ if ($cookieStore.get('token')) {
+ config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
+ }
+ return config;
+ },
+
+ // Intercept 401s and redirect you to login
+ responseError: function(response) {
+ if(response.status === 401) {
+ $location.path('/login');
+ // remove any stale tokens
+ $cookieStore.remove('token');
+ return $q.reject(response);
+ }
+ else {
+ return $q.reject(response);
+ }
+ }
+ };
+ })
+
+ .run(function ($rootScope, $location, Auth) {
+ // Redirect to login if route requires auth and you're not logged in
+ $rootScope.$on('$stateChangeStart', function (event, next) {
+ if (next.authenticate && !Auth.isLoggedIn()) {
+ $location.path('/login');
+ }
+ });
+ });
\ No newline at end of file
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+.controller('MainCtrl', function ($scope, $http, $window, $location, $anchorScroll, socket) {
+
+ /**
+ *
+ * @function setStyles
+ * @description Setting styles in scope
+ *
+ **/
+ var setStyles = function () {
+
+ // Main section style
+ $scope.sectionStyle = {
+ marginTop : 50,
+ height : $window.innerHeight
+ };
+
+ // Carousel style
+ $scope.carouselStyle = {
+ width : $scope.sectionStyle.width,
+ height : $scope.sectionStyle.height * 0.85 - 50
+ };
+
+ // Carousel > Image style
+ $scope.imgStyle = {
+ width : $scope.sectionStyle.width,
+ height : $scope.sectionStyle.height * 0.85 - 50
+ };
+
+ // Carousel banner style
+ $scope.bannerStyle = {
+ backgroundColor : '#cc7722',
+ height : $scope.sectionStyle.height * 0.15,
+ textAlign : 'center'
+ };
+ };
+
+ // Initializing styles
+ setStyles();
+
+ // Listening to window resize events
+ angular.element($window).bind('resize', function() {
+ // Resetting styles on resize
+ setStyles();
+ // Applying to scope - refresh the scope
+ $scope.$apply();
+ });
+
+ // Initializing slides used by carousel
+ var slides = $scope.slides = [];
+
+ // Setting carousel interval between slides
+ $scope.myInterval = 5000;
+
+ /**
+ *
+ * @function addSlide
+ * @description Adding slide in slides array
+ *
+ **/
+ $scope.addSlide = function() {
+ slides.push({
+ image: 'http://placekitten.com/1600/1200',
+ text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' ' +
+ ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
+ });
+ };
+
+ // Adding 4 slides in slides array
+ for (var i=0; i<4; i++) {
+ $scope.addSlide();
+ }
+
+ /**
+ *
+ * @function scrollTo
+ * @parameters String route
+ * @description Scrolling to anchor position defined by route
+ *
+ **/
+ $scope.scrollTo= function(route) {
+ // Getting location by id
+ $location.hash(route);
+ // Scrolling to anchor
+ $anchorScroll();
+ };
+
+ $scope.projects = [];
+
+ $scope.projects.push({
+ title : 'Projet 1',
+ author : 'Romain Monin',
+ category : 'Informatique',
+ informations : 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam quis bibendum dui, sit amet vehicula odio. Ut dictum lobortis elementum. Donec cursus, dui in fermentum euismod, nibh nunc aliquet dui, quis elementum neque mauris sed felis. Suspendisse facilisis adipiscing felis, sed iaculis tellus molestie at. Cras euismod, nibh vitae congue ultricies, ligula justo imperdiet ante, vitae dictum ante dolor eget tellus. In cursus vehicula lorem, at tristique metus. Vestibulum vel dui accumsan, hendrerit augue eget, dignissim elit. Nullam hendrerit a elit vitae sodales. Fusce id vestibulum nulla. Nulla odio lacus, lacinia eget eleifend eget, porta sit amet justo. Sed aliquet adipiscing nulla, in luctus velit porta vel. Curabitur egestas porta nulla, eget faucibus neque consectetur quis. Mauris venenatis consectetur interdum. Fusce fermentum et erat vitae auctor. Sed quis scelerisque nisi. Vestibulum cursus libero eros, ornare dignissim mi facilisis et.',
+ image : 'http://placekitten.com/200/100',
+ beginningDate : 14092014,
+ endingDate : 30092014,
+ url : '/projet1',
+ likes : 32,
+ comments : 2,
+ askedDonations : 20000,
+ currentDonations : 11249
+ });
+
+/* $scope.awesomeThings = [];
+
+
+ $http.get('/api/things').success(function(awesomeThings) {
+ $scope.awesomeThings = awesomeThings;
+ socket.syncUpdates('thing', $scope.awesomeThings);
+ });
+
+ $scope.addThing = function() {
+ if($scope.newThing === '') {
+ return;
+ }
+ $http.post('/api/things', { name: $scope.newThing });
+ $scope.newThing = '';
+ };
+
+ $scope.deleteThing = function(thing) {
+ $http.delete('/api/things/' + thing._id);
+ };
+
+ $scope.$on('$destroy', function () {
+ socket.unsyncUpdates('thing');
+ });*/
+});
\ No newline at end of file
--- /dev/null
+'use strict';
+
+describe('Controller: MainCtrl', function () {
+
+ // load the controller's module
+ beforeEach(module('ocreApp'));
+ beforeEach(module('socketMock'));
+
+ var MainCtrl,
+ scope,
+ $httpBackend;
+
+ // Initialize the controller and a mock scope
+ beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) {
+ $httpBackend = _$httpBackend_;
+ $httpBackend.expectGET('/api/things')
+ .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']);
+
+ scope = $rootScope.$new();
+ MainCtrl = $controller('MainCtrl', {
+ $scope: scope
+ });
+ }));
+
+ it('should attach a list of things to the scope', function () {
+ $httpBackend.flush();
+ expect(scope.awesomeThings.length).toBe(4);
+ });
+});
--- /dev/null
+.thing-form {
+ margin: 20px 0;
+}
+
+#banner {
+ border-bottom: none;
+ margin-top: -20px;
+}
+
+#banner h1 {
+ font-size: 60px;
+ line-height: 1;
+ letter-spacing: -1px;
+}
+
+.hero-unit {
+ position: relative;
+ padding: 30px 15px;
+ color: #F5F5F5;
+ text-align: center;
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
+ background: #4393B9;
+}
+
+.footer {
+ text-align: center;
+ padding: 15px 0;
+ margin-top: 70px;
+ border-top: 1px solid #E5E5E5;
+}
\ No newline at end of file
--- /dev/null
+<div ng-include="'components/navbar/navbar.html'"></div>
+
+<!--<div class="row">-->
+<div ng-style="sectionStyle">
+ <carousel interval="myInterval" ng-style="carouselStyle">
+ <slide ng-repeat="slide in slides" active="slide.active">
+ <img ng-src="{{slide.image}}" ng-style="imgStyle">
+ <div class="carousel-caption">
+
+ </div>
+ </slide>
+ </carousel>
+ <div class="col-md-12" ng-style="bannerStyle">
+ <div class="btn btn-lg btn-warning col-md-6 col-md-offset-3" style="top:30%; height:40%;" ng-click="scrollTo('projects')">Plus de projets...</div>
+ </div>
+</div>
+<!--</div>-->
+
+<div id="projects" class="container">
+ <div class="row">
+ <div id="projects" class="col-md-12">
+ <h1 class="page-header">Projets:</h1>
+ <div class="col-md-3" style="height:200px;" ng-repeat="project in projects">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">{{project.title}}</h3>
+ <p style="font-size:10px">Par {{project.author}}</p>
+ </div>
+ <div class="panel-body">
+ <a href={{project.url}} class="thumbnail">
+ <img src={{project.image}} alt="...">
+ </a>
+ </div>
+ <div class="panel-footer">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+<!-- <form class="thing-form">
+ <label>Syncs in realtime across clients</label>
+ <p class="input-group">
+ <input type="text" class="form-control" placeholder="Add a new thing here." ng-model="newThing">
+ <span class="input-group-btn">
+ <button type="submit" class="btn btn-ochre" ng-click="addThing()">Add New</button>
+ </span>
+ </p>
+ </form>-->
+</div>
+
+<footer class="footer">
+ <div class="container">
+ <p>Projet OCRE |
+ <a href="https://bitbucket.org/genonin/ocre" target="_blank">Participer</a></p>
+ </div>
+</footer>
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .config(function ($stateProvider) {
+ $stateProvider
+ .state('main', {
+ url: '/',
+ templateUrl: 'app/main/main.html',
+ controller: 'MainCtrl'
+ });
+ });
\ No newline at end of file
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .factory('Auth', function Auth($location, $rootScope, $http, User, $cookieStore, $q) {
+ var currentUser = {};
+ if($cookieStore.get('token')) {
+ currentUser = User.get();
+ }
+
+ return {
+
+ /**
+ * Authenticate user and save token
+ *
+ * @param {Object} user - login info
+ * @param {Function} callback - optional
+ * @return {Promise}
+ */
+ login: function(user, callback) {
+ var cb = callback || angular.noop;
+ var deferred = $q.defer();
+
+ $http.post('/auth/local', {
+ email: user.email,
+ password: user.password
+ }).
+ success(function(data) {
+ $cookieStore.put('token', data.token);
+ currentUser = User.get();
+ deferred.resolve(data);
+ return cb();
+ }).
+ error(function(err) {
+ this.logout();
+ deferred.reject(err);
+ return cb(err);
+ }.bind(this));
+
+ return deferred.promise;
+ },
+
+ /**
+ * Delete access token and user info
+ *
+ * @param {Function}
+ */
+ logout: function() {
+ $cookieStore.remove('token');
+ currentUser = {};
+ },
+
+ /**
+ * Create a new user
+ *
+ * @param {Object} user - user info
+ * @param {Function} callback - optional
+ * @return {Promise}
+ */
+ createUser: function(user, callback) {
+ var cb = callback || angular.noop;
+
+ return User.save(user,
+ function(data) {
+ $cookieStore.put('token', data.token);
+ currentUser = User.get();
+ return cb(user);
+ },
+ function(err) {
+ this.logout();
+ return cb(err);
+ }.bind(this)).$promise;
+ },
+
+ /**
+ * Change password
+ *
+ * @param {String} oldPassword
+ * @param {String} newPassword
+ * @param {Function} callback - optional
+ * @return {Promise}
+ */
+ changePassword: function(oldPassword, newPassword, callback) {
+ var cb = callback || angular.noop;
+
+ return User.changePassword({ id: currentUser._id }, {
+ oldPassword: oldPassword,
+ newPassword: newPassword
+ }, function(user) {
+ return cb(user);
+ }, function(err) {
+ return cb(err);
+ }).$promise;
+ },
+
+ /**
+ * Gets all available info on authenticated user
+ *
+ * @return {Object} user
+ */
+ getCurrentUser: function() {
+ return currentUser;
+ },
+
+ /**
+ * Check if a user is logged in
+ *
+ * @return {Boolean}
+ */
+ isLoggedIn: function() {
+ return currentUser.hasOwnProperty('role');
+ },
+
+ /**
+ * Check if a user is an admin
+ *
+ * @return {Boolean}
+ */
+ isAdmin: function() {
+ return currentUser.role === 'admin';
+ },
+
+ /**
+ * Get auth token
+ */
+ getToken: function() {
+ return $cookieStore.get('token');
+ }
+ };
+ });
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .factory('User', function ($resource) {
+ return $resource('/api/users/:id/:controller', {
+ id: '@_id'
+ },
+ {
+ changePassword: {
+ method: 'PUT',
+ params: {
+ controller:'password'
+ }
+ },
+ get: {
+ method: 'GET',
+ params: {
+ id:'me'
+ }
+ }
+ });
+ });
--- /dev/null
+'use strict';
+
+/**
+ * Removes server error when user updates input
+ */
+angular.module('ocreApp')
+ .directive('mongooseError', function () {
+ return {
+ restrict: 'A',
+ require: 'ngModel',
+ link: function(scope, element, attrs, ngModel) {
+ element.on('keydown', function() {
+ return ngModel.$setValidity('mongoose', true);
+ });
+ }
+ };
+ });
\ No newline at end of file
--- /dev/null
+'use strict';
+
+angular.module('ocreApp')
+ .controller('NavbarCtrl', function ($scope, $location, Auth) {
+ $scope.menu = [{
+ 'title': 'Accueil',
+ 'link': '/'
+ }];
+
+ $scope.isCollapsed = true;
+ $scope.isLoggedIn = Auth.isLoggedIn;
+ $scope.isAdmin = Auth.isAdmin;
+ $scope.getCurrentUser = Auth.getCurrentUser;
+
+ $scope.logout = function() {
+ Auth.logout();
+ $location.path('/login');
+ };
+
+ $scope.isActive = function(route) {
+ return route === $location.path();
+ };
+ });
\ No newline at end of file
--- /dev/null
+<div id="topNav" class="navbar navbar-default navbar-fixed-top" ng-controller="NavbarCtrl">
+<!-- <div class="container">-->
+ <div class="navbar-header">
+ <button class="navbar-toggle" type="button" ng-click="isCollapsed = !isCollapsed">
+ <span class="sr-only">Afficher la barre de navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a href="/" class="navbar-brand">OCRE</a>
+ </div>
+ <div collapse="isCollapsed" class="navbar-collapse collapse" id="navbar-main">
+ <ul class="nav navbar-nav">
+ <li ng-repeat="item in menu" ng-class="{active: isActive(item.link)}">
+ <a ng-href="{{item.link}}">{{item.title}}</a>
+ </li>
+ <li ng-show="isAdmin()" ng-class="{active: isActive('/admin')}"><a href="/admin">Administrateur</a></li>
+ </ul>
+
+ <ul class="nav navbar-nav pull-right">
+ <li ng-hide="isLoggedIn()" ng-class="{active: isActive('/signup')}"><a href="/signup">S'inscrire</a></li>
+ <li ng-hide="isLoggedIn()" ng-class="{active: isActive('/login')}"><a href="/login">Se connecter</a></li>
+ <li ng-show="isLoggedIn()"><p class="navbar-text">Salut {{ getCurrentUser().name }}</p> </li>
+ <li ng-show="isLoggedIn()" ng-class="{active: isActive('/settings')}"><a href="/settings"><span class="glyphicon glyphicon-cog"></span></a></li>
+ <li ng-show="isLoggedIn()" ng-class="{active: isActive('/logout')}"><a href="" ng-click="logout()">Se déconnecter</a></li>
+ </ul>
+ </div>
+<!-- </div>-->
+</div>
--- /dev/null
+'use strict';
+
+angular.module('socketMock', [])
+ .factory('socket', function() {
+ return {
+ socket: {
+ connect: function() {},
+ on: function() {},
+ emit: function() {},
+ receive: function() {}
+ },
+
+ syncUpdates: function() {},
+ unsyncUpdates: function() {}
+ };
+ });
\ No newline at end of file
--- /dev/null
+/* global io */
+'use strict';
+
+angular.module('ocreApp')
+ .factory('socket', function(socketFactory) {
+ var retryInterval = 5000;
+ var retryTimer;
+
+ clearInterval(retryTimer);
+
+ var ioSocket = io.connect('', {
+ 'force new connection': true,
+
+ 'max reconnection attempts': Infinity,
+
+ 'reconnection limit': 10 * 1000,
+
+ // Send auth token on connection
+ // 'query': 'token=' + Auth.getToken()
+ });
+
+ retryTimer = setInterval(function () {
+ if (!ioSocket.socket.connected &&
+ !ioSocket.socket.connecting &&
+ !ioSocket.socket.reconnecting) {
+ ioSocket.connect();
+ }
+ }, retryInterval);
+
+ var socket = socketFactory({
+ ioSocket: ioSocket
+ });
+
+ return {
+ socket: socket,
+
+ /**
+ * Register listeners to sync an array with updates on a model
+ *
+ * Takes the array we want to sync, the model name that socket updates are sent from,
+ * and an optional callback function after new items are updated.
+ *
+ * @param {String} modelName
+ * @param {Array} array
+ * @param {Function} cb
+ */
+ syncUpdates: function (modelName, array, cb) {
+ cb = cb || angular.noop;
+
+ /**
+ * Syncs item creation/updates on 'model:save'
+ */
+ socket.on(modelName + ':save', function (item) {
+ var oldItem = _.find(array, {_id: item._id});
+ var index = array.indexOf(oldItem);
+ var event = 'created';
+
+ // replace oldItem if it exists
+ // otherwise just add item to the collection
+ if (oldItem) {
+ array.splice(index, 1, item);
+ event = 'updated';
+ } else {
+ array.push(item);
+ }
+
+ cb(event, item, array);
+ });
+
+ /**
+ * Syncs removed items on 'model:remove'
+ */
+ socket.on(modelName + ':remove', function (item) {
+ var event = 'deleted';
+ _.remove(array, {_id: item._id});
+ cb(event, item, array);
+ });
+ },
+
+ /**
+ * Removes listeners for a models updates on the socket
+ *
+ * @param modelName
+ */
+ unsyncUpdates: function (modelName) {
+ socket.removeAllListeners(modelName + ':save');
+ socket.removeAllListeners(modelName + ':remove');
+ }
+ };
+ });
\ No newline at end of file
--- /dev/null
+<!doctype html>
+<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <base href="/">
+ <title></title>
+ <meta name="description" content="">
+ <meta name="viewport" content="width=device-width">
+ <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
+ <!-- build:css(client) app/vendor.css -->
+ <!-- bower:css -->
+ <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
+ <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css" />
+ <!-- endbower -->
+ <!-- endbuild -->
+ <!-- build:css({.tmp,client}) app/app.css -->
+ <link rel="stylesheet" href="app/app.css">
+ <!-- injector:css -->
+ <link rel="stylesheet" href="app/account/login/login.css">
+ <link rel="stylesheet" href="app/admin/admin.css">
+ <link rel="stylesheet" href="app/app.css">
+ <link rel="stylesheet" href="app/main/main.css">
+ <!-- endinjector -->
+ <!-- endbuild -->
+ </head>
+ <body ng-app="ocreApp">
+ <!--[if lt IE 7]>
+ <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
+ <![endif]-->
+
+ <!-- Add your site or application content here -->
+ <div ui-view=""></div>
+
+ <!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
+ <script>
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+ ga('create', 'UA-XXXXX-X');
+ ga('send', 'pageview');
+ </script>
+
+ <!--[if lt IE 9]>
+ <script src="bower_components/es5-shim/es5-shim.js"></script>
+ <script src="bower_components/json3/lib/json3.min.js"></script>
+ <![endif]-->
+ <script src="socket.io/socket.io.js"></script>
+ <!-- build:js(client) app/vendor.js -->
+ <!-- bower:js -->
+ <script src="bower_components/jquery/dist/jquery.js"></script>
+ <script src="bower_components/angular/angular.js"></script>
+ <script src="bower_components/json3/lib/json3.js"></script>
+ <script src="bower_components/angular-resource/angular-resource.js"></script>
+ <script src="bower_components/angular-cookies/angular-cookies.js"></script>
+ <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
+ <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
+ <script src="bower_components/lodash/dist/lodash.compat.js"></script>
+ <script src="bower_components/angular-socket-io/socket.js"></script>
+ <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
+ <!-- endbower -->
+ <!-- endbuild -->
+
+ <!-- build:js({.tmp,client}) app/app.js -->
+ <script src="app/app.js"></script>
+ <!-- injector:js -->
+ <script src="app/account/account.js"></script>
+ <script src="app/account/login/login.controller.js"></script>
+ <script src="app/account/settings/settings.controller.js"></script>
+ <script src="app/account/signup/signup.controller.js"></script>
+ <script src="app/admin/admin.controller.js"></script>
+ <script src="app/admin/admin.js"></script>
+ <script src="app/main/main.controller.js"></script>
+ <script src="app/main/main.js"></script>
+ <script src="components/auth/auth.service.js"></script>
+ <script src="components/auth/user.service.js"></script>
+ <script src="components/mongoose-error/mongoose-error.directive.js"></script>
+ <script src="components/navbar/navbar.controller.js"></script>
+ <script src="components/socket/socket.service.js"></script>
+ <!-- endinjector -->
+ <!-- endbuild -->
+</body>
+</html>
--- /dev/null
+# robotstxt.org
+
+User-agent: *
--- /dev/null
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var MainPage = function() {
+ this.heroEl = element(by.css('.hero-unit'));
+ this.h1El = this.heroEl.element(by.css('h1'));
+ this.imgEl = this.heroEl.element(by.css('img'));
+ this.anchorEl = this.heroEl.element(by.css('a'));
+
+ this.repeater = by.repeater('thing in awesomeThings');
+ this.firstAwesomeThingNameEl = element(this.repeater.row(0).column('{{thing.name}}'));
+ this.awesomeThingsCount = element.all(this.repeater).count();
+};
+
+module.exports = new MainPage();
+
--- /dev/null
+'use strict';
+
+describe('Main View', function() {
+ var page;
+
+ beforeEach(function() {
+ browser.get('/');
+ page = require('./main.po');
+ });
+
+ it('should include jumbotron with correct data', function() {
+ expect(page.h1El.getText()).toBe('\'Allo, \'Allo!');
+ expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/);
+ expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman');
+ });
+
+ it('should render awesomeThings', function() {
+ expect(page.firstAwesomeThingNameEl.getText()).toContain('Development Tools');
+ page.awesomeThingsCount.then(function(count) {
+ expect(count).toBe(6);
+ });
+ });
+});
--- /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: [
+ 'client/bower_components/jquery/dist/jquery.js',
+ 'client/bower_components/angular/angular.js',
+ 'client/bower_components/angular-mocks/angular-mocks.js',
+ 'client/bower_components/angular-resource/angular-resource.js',
+ 'client/bower_components/angular-cookies/angular-cookies.js',
+ 'client/bower_components/angular-sanitize/angular-sanitize.js',
+ 'client/bower_components/angular-route/angular-route.js',
+ 'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
+ 'client/bower_components/lodash/dist/lodash.compat.js',
+ 'client/bower_components/angular-socket-io/socket.js',
+ 'client/bower_components/angular-ui-router/release/angular-ui-router.js',
+ '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,
+
+
+ // 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
+{
+ "name": "ocre",
+ "version": "0.0.0",
+ "main": "server/app.js",
+ "dependencies": {
+ "express": "~4.0.0",
+ "morgan": "~1.0.0",
+ "body-parser": "~1.0.0",
+ "method-override": "~1.0.0",
+ "static-favicon": "~1.0.1",
+ "cookie-parser": "~1.0.1",
+ "express-session": "~1.0.2",
+ "errorhandler": "~1.0.0",
+ "compression": "~1.0.1",
+ "lodash": "~2.4.1",
+ "ejs": "~0.8.4",
+ "mongoose": "~3.8.8",
+ "jsonwebtoken": "^0.3.0",
+ "express-jwt": "^0.1.3",
+ "passport": "~0.2.0",
+ "passport-local": "~0.1.6",
+ "composable-middleware": "^0.3.0",
+ "connect-mongo": "^0.4.1",
+ "socket.io": "~0.9.16",
+ "socketio-jwt": "^2.0.2"
+ },
+ "devDependencies": {
+ "grunt": "~0.4.4",
+ "grunt-autoprefixer": "~0.7.2",
+ "grunt-bower-install": "~1.4.0",
+ "grunt-concurrent": "~0.5.0",
+ "grunt-contrib-clean": "~0.5.0",
+ "grunt-contrib-compass": "~0.7.2",
+ "grunt-contrib-concat": "~0.4.0",
+ "grunt-contrib-copy": "~0.5.0",
+ "grunt-contrib-cssmin": "~0.9.0",
+ "grunt-contrib-htmlmin": "~0.2.0",
+ "grunt-contrib-imagemin": "~0.7.1",
+ "grunt-contrib-jshint": "~0.10.0",
+ "grunt-contrib-uglify": "~0.4.0",
+ "grunt-contrib-watch": "~0.6.1",
+ "grunt-contrib-coffee": "^0.10.1",
+ "grunt-contrib-jade": "^0.11.0",
+ "grunt-contrib-less": "^0.11.0",
+ "grunt-google-cdn": "~0.4.0",
+ "grunt-newer": "~0.7.0",
+ "grunt-ngmin": "~0.0.3",
+ "grunt-rev": "~0.1.0",
+ "grunt-svgmin": "~0.4.0",
+ "grunt-usemin": "~2.1.1",
+ "grunt-env": "~0.4.1",
+ "grunt-node-inspector": "~0.1.5",
+ "grunt-nodemon": "~0.2.0",
+ "grunt-angular-templates": "^0.5.4",
+ "grunt-dom-munger": "^3.4.0",
+ "grunt-protractor-runner": "^0.2.4",
+ "grunt-asset-injector": "^0.1.0",
+ "grunt-karma": "~0.8.2",
+ "grunt-mocha-test": "~0.10.2",
+ "grunt-contrib-sass": "^0.7.3",
+ "jit-grunt": "^0.5.0",
+ "time-grunt": "~0.3.1",
+ "grunt-express-server": "~0.4.17",
+ "grunt-open": "~0.2.3",
+ "open": "~0.0.4",
+ "jshint-stylish": "~0.1.5",
+ "connect-livereload": "~0.4.0",
+ "karma-ng-scenario": "~0.1.0",
+ "karma-firefox-launcher": "~0.1.3",
+ "karma-script-launcher": "~0.1.0",
+ "karma-html2js-preprocessor": "~0.1.0",
+ "karma-ng-jade2js-preprocessor": "^0.1.2",
+ "karma-jasmine": "~0.1.5",
+ "karma-chrome-launcher": "~0.1.3",
+ "requirejs": "~2.1.11",
+ "karma-requirejs": "~0.2.1",
+ "karma-coffee-preprocessor": "~0.2.1",
+ "karma-jade-preprocessor": "0.0.11",
+ "karma-phantomjs-launcher": "~0.1.4",
+ "karma": "~0.12.9",
+ "karma-ng-html2js-preprocessor": "~0.1.0",
+ "supertest": "~0.11.0",
+ "should": "~3.3.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "scripts": {
+ "start": "node server/app.js",
+ "test": "grunt test",
+ "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update"
+ }
+}
--- /dev/null
+// Protractor configuration
+// https://github.com/angular/protractor/blob/master/referenceConf.js
+
+'use strict';
+
+exports.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'),
+
+ // If true, only chromedriver will be started, not a standalone selenium.
+ // Tests for browsers other than chrome will not run.
+ chromeOnly: true,
+
+ // 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'
+ },
+
+ // ----- 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: 'jasmine',
+
+ // ----- Options to be passed to minijasminenode -----
+ //
+ // See the full list at https://github.com/juliemr/minijasminenode
+ jasmineNodeOpts: {
+ defaultTimeoutInterval: 30000
+ }
+};
--- /dev/null
+{
+ "node": true,
+ "esnext": true,
+ "bitwise": true,
+ "eqeqeq": true,
+ "immed": true,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "regexp": true,
+ "undef": true,
+ "smarttabs": true,
+ "asi": true,
+ "debug": true
+}
\ No newline at end of file
--- /dev/null
+'use strict';
+
+var express = require('express');
+var controller = require('./thing.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;
\ No newline at end of file
--- /dev/null
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET /things -> index
+ * POST /things -> create
+ * GET /things/:id -> show
+ * PUT /things/:id -> update
+ * DELETE /things/:id -> destroy
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var Thing = require('./thing.model');
+
+// Get list of things
+exports.index = function(req, res) {
+ Thing.find(function (err, things) {
+ if(err) { return handleError(res, err); }
+ return res.json(200, things);
+ });
+};
+
+// Get a single thing
+exports.show = function(req, res) {
+ Thing.findById(req.params.id, function (err, thing) {
+ if(err) { return handleError(res, err); }
+ if(!thing) { return res.send(404); }
+ return res.json(thing);
+ });
+};
+
+// Creates a new thing in the DB.
+exports.create = function(req, res) {
+ Thing.create(req.body, function(err, thing) {
+ if(err) { return handleError(res, err); }
+ return res.json(201, thing);
+ });
+};
+
+// Updates an existing thing in the DB.
+exports.update = function(req, res) {
+ if(req.body._id) { delete req.body._id; }
+ Thing.findById(req.params.id, function (err, thing) {
+ if (err) { return handleError(err); }
+ if(!thing) { return res.send(404); }
+ var updated = _.merge(thing, req.body);
+ updated.save(function (err) {
+ if (err) { return handleError(err); }
+ return res.json(200, thing);
+ });
+ });
+};
+
+// Deletes a thing from the DB.
+exports.destroy = function(req, res) {
+ Thing.findById(req.params.id, function (err, thing) {
+ if(err) { return handleError(res, err); }
+ if(!thing) { return res.send(404); }
+ thing.remove(function(err) {
+ if(err) { return handleError(res, err); }
+ return res.send(204);
+ });
+ });
+};
+
+function handleError(res, err) {
+ return res.send(500, err);
+}
\ No newline at end of file
--- /dev/null
+'use strict';
+
+var mongoose = require('mongoose'),
+ Schema = mongoose.Schema;
+
+var ThingSchema = new Schema({
+ name: String,
+ info: String,
+ active: Boolean
+});
+
+module.exports = mongoose.model('Thing', ThingSchema);
\ No newline at end of file
--- /dev/null
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var thing = require('./thing.model');
+
+exports.register = function(socket) {
+ thing.schema.post('save', function (doc) {
+ onSave(socket, doc);
+ });
+ thing.schema.post('remove', function (doc) {
+ onRemove(socket, doc);
+ });
+}
+
+function onSave(socket, doc, cb) {
+ socket.emit('thing:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+ socket.emit('thing:remove', doc);
+}
\ No newline at end of file
--- /dev/null
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/things', function() {
+
+ it('should respond with JSON array', function(done) {
+ request(app)
+ .get('/api/things')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ res.body.should.be.instanceof(Array);
+ done();
+ });
+ });
+});
\ No newline at end of file
--- /dev/null
+'use strict';
+
+var express = require('express');
+var controller = require('./user.controller');
+var config = require('../../config/environment');
+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 User = require('./user.model');
+var passport = require('passport');
+var config = require('../../config/environment');
+var jwt = require('jsonwebtoken');
+
+var validationError = function(res, err) {
+ return res.json(422, err);
+};
+
+/**
+ * Get list of users
+ * restriction: 'admin'
+ */
+exports.index = function(req, res) {
+ User.find({}, '-salt -hashedPassword', function (err, users) {
+ if(err) return res.send(500, err);
+ res.json(200, users);
+ });
+};
+
+/**
+ * Creates a new user
+ */
+exports.create = function (req, res, next) {
+ var newUser = new User(req.body);
+ newUser.provider = 'local';
+ newUser.role = 'user';
+ newUser.save(function(err, user) {
+ if (err) return validationError(res, err);
+ var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60*5 });
+ res.json({ token: token });
+ });
+};
+
+/**
+ * Get a single user
+ */
+exports.show = function (req, res, next) {
+ var userId = req.params.id;
+
+ User.findById(userId, function (err, user) {
+ if (err) return next(err);
+ if (!user) return res.send(401);
+ res.json(user.profile);
+ });
+};
+
+/**
+ * Deletes a user
+ * restriction: 'admin'
+ */
+exports.destroy = function(req, res) {
+ User.findByIdAndRemove(req.params.id, function(err, user) {
+ if(err) return res.send(500, err);
+ return res.send(204);
+ });
+};
+
+/**
+ * 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.findById(userId, function (err, user) {
+ if(user.authenticate(oldPass)) {
+ user.password = newPass;
+ user.save(function(err) {
+ if (err) return validationError(res, err);
+ res.send(200);
+ });
+ } else {
+ res.send(403);
+ }
+ });
+};
+
+/**
+ * Get my info
+ */
+exports.me = function(req, res, next) {
+ var userId = req.user._id;
+ User.findOne({
+ _id: userId
+ }, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt
+ if (err) return next(err);
+ if (!user) return res.json(401);
+ res.json(user);
+ });
+};
+
+/**
+ * Authentication callback
+ */
+exports.authCallback = function(req, res, next) {
+ res.redirect('/');
+};
--- /dev/null
+'use strict';
+
+var mongoose = require('mongoose');
+var Schema = mongoose.Schema;
+var crypto = require('crypto');
+
+var authTypes = ['github', 'twitter', 'facebook', 'google'];
+
+var UserSchema = new Schema({
+ name: String,
+ email: { type: String, lowercase: true },
+ role: {
+ type: String,
+ default: 'user'
+ },
+ hashedPassword: String,
+ provider: String,
+ salt: String,
+ facebook: {},
+ twitter: {},
+ github: {},
+ google: {}
+});
+
+/**
+ * Virtuals
+ */
+UserSchema
+ .virtual('password')
+ .set(function(password) {
+ this._password = password;
+ this.salt = this.makeSalt();
+ this.hashedPassword = this.encryptPassword(password);
+ })
+ .get(function() {
+ return this._password;
+ });
+
+// 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 you are authenticating by any of the oauth strategies, don't validate
+ if (authTypes.indexOf(this.provider) !== -1) return true;
+ return email.length;
+ }, 'Email cannot be blank');
+
+// Validate empty password
+UserSchema
+ .path('hashedPassword')
+ .validate(function(hashedPassword) {
+ // if you are authenticating by any of the oauth strategies, don't validate
+ if (authTypes.indexOf(this.provider) !== -1) return true;
+ return hashedPassword.length;
+ }, 'Password cannot be blank');
+
+// Validate email is not taken
+UserSchema
+ .path('email')
+ .validate(function(value, respond) {
+ var self = this;
+ this.constructor.findOne({email: value}, function(err, user) {
+ if(err) throw err;
+ if(user) {
+ if(self.id === user.id) return respond(true);
+ return respond(false);
+ }
+ respond(true);
+ });
+}, 'The specified email address is already in use.');
+
+var validatePresenceOf = function(value) {
+ return value && value.length;
+};
+
+/**
+ * Pre-save hook
+ */
+UserSchema
+ .pre('save', function(next) {
+ if (!this.isNew) return next();
+
+ if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1)
+ next(new Error('Invalid password'));
+ else
+ next();
+ });
+
+/**
+ * Methods
+ */
+UserSchema.methods = {
+ /**
+ * Authenticate - check if the passwords are the same
+ *
+ * @param {String} plainText
+ * @return {Boolean}
+ * @api public
+ */
+ authenticate: function(plainText) {
+ return this.encryptPassword(plainText) === this.hashedPassword;
+ },
+
+ /**
+ * Make salt
+ *
+ * @return {String}
+ * @api public
+ */
+ makeSalt: function() {
+ return crypto.randomBytes(16).toString('base64');
+ },
+
+ /**
+ * Encrypt password
+ *
+ * @param {String} password
+ * @return {String}
+ * @api public
+ */
+ encryptPassword: function(password) {
+ if (!password || !this.salt) return '';
+ var salt = new Buffer(this.salt, 'base64');
+ return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64');
+ }
+};
+
+module.exports = mongoose.model('User', UserSchema);
--- /dev/null
+'use strict';
+
+var should = require('should');
+var User = require('./user.model');
+
+var user;
+
+describe('User Model', function() {
+ before(function(done) {
+ user = new User({
+ provider: 'local',
+ name: 'Fake User',
+ email: 'test@test.com',
+ password: 'password'
+ });
+
+ // Clear users before testing
+ User.remove().exec().then(function() {
+ done();
+ });
+ });
+
+ afterEach(function(done) {
+ User.remove().exec().then(function() {
+ done();
+ });
+ });
+
+ it('should begin with no users', function(done) {
+ User.find({}, function(err, users) {
+ users.should.have.length(0);
+ done();
+ });
+ });
+
+ it('should fail when saving a duplicate user', function(done) {
+ user.save(function() {
+ var userDup = new User(user);
+ userDup.save(function(err) {
+ should.exist(err);
+ done();
+ });
+ });
+ });
+
+ it('should fail when saving without an email', function(done) {
+ user.email = '';
+ user.save(function(err) {
+ should.exist(err);
+ done();
+ });
+ });
+
+ it("should authenticate user if password is valid", function() {
+ user.authenticate('password').should.be.true;
+ });
+
+ it("should not authenticate user if password is invalid", function() {
+ user.authenticate('blah').should.not.be.true;
+ });
+});
\ No newline at end of file
--- /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 database
+mongoose.connect(config.mongo.uri, config.mongo.options);
+
+// Populate DB with sample data
+if(config.seedDB) { require('./config/seed'); }
+
+// Setup server
+var app = express();
+var server = require('http').createServer(app);
+var socketio = require('socket.io').listen(server);
+require('./config/socketio')(socketio);
+require('./config/express')(app);
+require('./routes')(app);
+
+// Start server
+server.listen(config.port, config.ip, function () {
+ console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
+});
+
+// Expose app
+exports = module.exports = app;
\ No newline at end of file
--- /dev/null
+'use strict';
+
+var mongoose = require('mongoose');
+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.findById(req.user._id, function (err, user) {
+ if (err) return next(err);
+ if (!user) return res.send(401);
+
+ req.user = user;
+ next();
+ });
+ });
+}
+
+/**
+ * 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.send(403);
+ }
+ });
+}
+
+/**
+ * Returns a jwt token signed by the app secret
+ */
+function signToken(id) {
+ return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*5 });
+}
+
+/**
+ * Set token cookie directly for oAuth strategies
+ */
+function setTokenCookie(req, res) {
+ if (!req.user) return res.json(404, { message: '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;
\ No newline at end of file
--- /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);
+
+var router = express.Router();
+
+router.use('/local', require('./local'));
+
+module.exports = router;
\ No newline at end of file
--- /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.json(401, error);
+ if (!user) return res.json(404, {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;
\ No newline at end of file
--- /dev/null
+var passport = require('passport');
+var LocalStrategy = require('passport-local').Strategy;
+
+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) {
+ User.findOne({
+ email: email.toLowerCase()
+ }, function(err, user) {
+ if (err) return done(err);
+
+ if (!user) {
+ return done(null, false, { message: 'This email is not registered.' });
+ }
+ if (!user.authenticate(password)) {
+ return done(null, false, { message: 'This password is not correct.' });
+ }
+ return done(null, user);
+ });
+ }
+ ));
+};
\ No newline at end of file
--- /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) {
+ if (err) { return res.json(result, result.status); }
+
+ res.render(viewFilePath);
+ });
+};
--- /dev/null
+'use strict';
+
+// Development specific configuration
+// ==================================
+module.exports = {
+ // MongoDB connection options
+ mongo: {
+ uri: 'mongodb://localhost/ocre-dev'
+ }
+};
--- /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,
+
+ // Should we populate the DB with sample data?
+ seedDB: true,
+
+ // Secret for session, you will want to change this and make it an environment variable
+ secrets: {
+ session: 'ocre-secret'
+ },
+
+ // List of user roles
+ userRoles: ['guest', 'user', 'admin'],
+
+ // MongoDB connection options
+ mongo: {
+ options: {
+ db: {
+ safe: true
+ }
+ }
+ },
+
+};
+
+// Export the config object based on the NODE_ENV
+// ==============================================
+module.exports = _.merge(
+ all,
+ require('./' + process.env.NODE_ENV + '.js') || {});
\ No newline at end of file
--- /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/ocre'
+ }
+};
\ No newline at end of file
--- /dev/null
+'use strict';
+
+// Test specific configuration
+// ===========================
+module.exports = {
+ // MongoDB connection options
+ mongo: {
+ uri: 'mongodb://localhost/ocre-test'
+ }
+};
\ No newline at end of file
--- /dev/null
+/**
+ * Express configuration
+ */
+
+'use strict';
+
+var express = require('express');
+var favicon = require('static-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');
+
+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());
+ app.use(methodOverride());
+ app.use(cookieParser());
+ app.use(passport.initialize());
+
+ if ('production' === env) {
+ app.use(favicon(path.join(config.root, 'public', 'favicon.ico')));
+ app.use(express.static(path.join(config.root, 'public')));
+ app.set('appPath', config.root + '/public');
+ app.use(morgan('dev'));
+ }
+
+ if ('development' === env || 'test' === env) {
+ app.use(require('connect-livereload')());
+ app.use(express.static(path.join(config.root, '.tmp')));
+ app.use(express.static(path.join(config.root, 'client')));
+ app.set('appPath', 'client');
+ app.use(morgan('dev'));
+ app.use(errorHandler()); // Error handler - has to be last
+ }
+};
\ No newline at end of file
--- /dev/null
+'use strict';
+
+// Environment variables that grunt will set when the server starts locally. Use for your api keys, secrets, etc.
+// You will need to set these on the server you deploy to.
+//
+// This file should not be tracked by git.
+
+module.exports = {
+ SESSION_SECRET: "ocre-secret",
+ FACEBOOK_ID: "app-id",
+ FACEBOOK_SECRET: "secret",
+ TWITTER_ID: "app-id",
+ TWITTER_SECRET: "secret",
+ GOOGLE_ID: "app-id",
+ GOOGLE_SECRET: "secret"
+};
\ No newline at end of file
--- /dev/null
+/**
+ * Populate DB with sample data on server start
+ * to disable, edit config/environment/index.js, and set `seedDB: false`
+ */
+
+'use strict';
+
+var Thing = require('../api/thing/thing.model');
+var User = require('../api/user/user.model');
+
+Thing.find({}).remove(function() {
+ Thing.create({
+ name : 'Development Tools',
+ info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Sass, CoffeeScript, and Less.'
+ }, {
+ name : 'Server and Client integration',
+ info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.'
+ }, {
+ name : 'Smart Build System',
+ info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html'
+ }, {
+ name : 'Modular Structure',
+ info : 'Best practice client and server structures allow for more code reusability and maximum scalability'
+ }, {
+ name : 'Optimized Build',
+ info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.'
+ },{
+ name : 'Deployment Ready',
+ info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators'
+ });
+});
+
+User.find({}).remove(function() {
+ User.create({
+ provider: 'local',
+ name: 'Test User',
+ email: 'test@test.com',
+ password: 'test'
+ }, {
+ provider: 'local',
+ role: 'admin',
+ name: 'Admin',
+ email: 'admin@admin.com',
+ password: 'admin'
+ }, function() {
+ console.log('finished populating users');
+ }
+ );
+});
\ No newline at end of file
--- /dev/null
+/**
+ * Socket.io configuration
+ */
+
+'use strict';
+
+var config = require('./environment');
+
+// When the user disconnects.. perform this
+function onDisconnect(socket) {
+}
+
+// When the user connects.. perform this
+function onConnect(socket) {
+ // When the client emits 'info', this listens and executes
+ socket.on('info', function (data) {
+ console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2));
+ });
+
+ // Insert sockets below
+ require('../api/thing/thing.socket').register(socket);
+}
+
+module.exports = function (socketio) {
+ // The amount of detail that the server should output to the logger.
+ // 0 - error
+ // 1 - warn
+ // 2 - info
+ // 3 - debug
+ socketio.set('log level', 2);
+
+ // We can authenticate socket.io users and access their token through socket.handshake.decoded_token
+ //
+ // 1. You will need to send the token in `client/components/socket/socket.service.js`
+ //
+ // 2. Require authentication here:
+ // socketio.set('authorization', require('socketio-jwt').authorize({
+ // secret: config.secrets.session,
+ // handshake: true
+ // }));
+
+ socketio.sockets.on('connection', function (socket) {
+ socket.address = socket.handshake.address.address + ':' +
+ socket.handshake.address.port;
+ socket.connectedAt = new Date();
+
+ // Call onDisconnect.
+ socket.on('disconnect', function () {
+ onDisconnect(socket);
+ console.info('[%s] DISCONNECTED', socket.address);
+ });
+
+ // Call onConnect.
+ onConnect(socket);
+ console.info('[%s] CONNECTED', socket.address);
+ });
+};
\ No newline at end of file
--- /dev/null
+/**
+ * Main application routes
+ */
+
+'use strict';
+
+var errors = require('./components/errors');
+
+module.exports = function(app) {
+
+ // Insert routes below
+ app.use('/api/things', require('./api/thing'));
+ 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(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>