mirror of
https://github.com/fergalmoran/roboto-promoto.git
synced 2025-12-22 09:37:37 +00:00
Initial Commit
This commit is contained in:
21
.editorconfig
Normal file
21
.editorconfig
Normal file
@@ -0,0 +1,21 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto
|
||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
node_modules
|
||||
public
|
||||
.tmp
|
||||
.sass-cache
|
||||
app/bower_components
|
||||
heroku
|
||||
/views
|
||||
dist
|
||||
tags
|
||||
24
.jshintrc
Normal file
24
.jshintrc
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"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": {
|
||||
"angular": false
|
||||
}
|
||||
}
|
||||
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '0.8'
|
||||
- '0.10'
|
||||
before_script:
|
||||
- 'npm install -g bower grunt-cli'
|
||||
- 'bower install'
|
||||
550
Gruntfile.js
Normal file
550
Gruntfile.js
Normal file
@@ -0,0 +1,550 @@
|
||||
// Generated on 2014-03-18 using generator-angular-fullstack 1.3.2
|
||||
'use strict';
|
||||
|
||||
// # Globbing
|
||||
// for performance reasons we're only matching one level down:
|
||||
// 'test/spec/{,*/}*.js'
|
||||
// use this if you want to recursively match all subfolders:
|
||||
// 'test/spec/**/*.js'
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
// Load grunt tasks automatically
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
// 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
|
||||
app: require('./bower.json').appPath || 'app',
|
||||
dist: 'dist'
|
||||
},
|
||||
express: {
|
||||
options: {
|
||||
port: process.env.PORT || 9000
|
||||
},
|
||||
dev: {
|
||||
options: {
|
||||
script: 'server.js',
|
||||
debug: true
|
||||
}
|
||||
},
|
||||
prod: {
|
||||
options: {
|
||||
script: 'dist/server.js',
|
||||
node_env: 'production'
|
||||
}
|
||||
}
|
||||
},
|
||||
open: {
|
||||
server: {
|
||||
url: 'http://localhost:<%= express.options.port %>'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
js: {
|
||||
files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
|
||||
tasks: ['newer:jshint:all'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
mochaTest: {
|
||||
files: ['test/server/{,*/}*.js'],
|
||||
tasks: ['mochaTest']
|
||||
},
|
||||
jsTest: {
|
||||
files: ['test/client/spec/{,*/}*.js'],
|
||||
tasks: ['newer:jshint:test', 'karma']
|
||||
},
|
||||
compass: {
|
||||
files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
|
||||
tasks: ['compass:server', 'autoprefixer']
|
||||
},
|
||||
gruntfile: {
|
||||
files: ['Gruntfile.js']
|
||||
},
|
||||
livereload: {
|
||||
files: [
|
||||
'<%= yeoman.app %>/views/{,*//*}*.{html,jade}',
|
||||
'{.tmp,<%= yeoman.app %>}/styles/{,*//*}*.css',
|
||||
'{.tmp,<%= yeoman.app %>}/scripts/{,*//*}*.js',
|
||||
'<%= yeoman.app %>/images/{,*//*}*.{png,jpg,jpeg,gif,webp,svg}',
|
||||
],
|
||||
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
express: {
|
||||
files: [
|
||||
'server.js',
|
||||
'lib/**/*.{js,json}'
|
||||
],
|
||||
tasks: ['newer:jshint:server', '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: '.jshintrc',
|
||||
reporter: require('jshint-stylish')
|
||||
},
|
||||
server: {
|
||||
options: {
|
||||
jshintrc: 'lib/.jshintrc'
|
||||
},
|
||||
src: [ 'lib/{,*/}*.js']
|
||||
},
|
||||
all: [
|
||||
'<%= yeoman.app %>/scripts/{,*/}*.js'
|
||||
],
|
||||
test: {
|
||||
options: {
|
||||
jshintrc: 'test/client/.jshintrc'
|
||||
},
|
||||
src: ['test/client/spec/{,*/}*.js']
|
||||
}
|
||||
},
|
||||
|
||||
// Empties folders to start fresh
|
||||
clean: {
|
||||
dist: {
|
||||
files: [{
|
||||
dot: true,
|
||||
src: [
|
||||
'.tmp',
|
||||
'<%= yeoman.dist %>/*',
|
||||
'!<%= yeoman.dist %>/.git*',
|
||||
'!<%= yeoman.dist %>/Procfile'
|
||||
]
|
||||
}]
|
||||
},
|
||||
heroku: {
|
||||
files: [{
|
||||
dot: true,
|
||||
src: [
|
||||
'heroku/*',
|
||||
'!heroku/.git*',
|
||||
'!heroku/Procfile'
|
||||
]
|
||||
}]
|
||||
},
|
||||
server: '.tmp'
|
||||
},
|
||||
|
||||
// Add vendor prefixed styles
|
||||
autoprefixer: {
|
||||
options: {
|
||||
browsers: ['last 1 version']
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '.tmp/styles/',
|
||||
src: '{,*/}*.css',
|
||||
dest: '.tmp/styles/'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// 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.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
|
||||
'bower-install': {
|
||||
app: {
|
||||
html: '<%= yeoman.app %>/views/index.html',
|
||||
ignorePath: '<%= yeoman.app %>/',
|
||||
exclude: ['bootstrap-sass']
|
||||
}
|
||||
},
|
||||
|
||||
// Compiles Sass to CSS and generates necessary files if requested
|
||||
compass: {
|
||||
options: {
|
||||
sassDir: '<%= yeoman.app %>/styles',
|
||||
cssDir: '.tmp/styles',
|
||||
generatedImagesDir: '.tmp/images/generated',
|
||||
imagesDir: '<%= yeoman.app %>/images',
|
||||
javascriptsDir: '<%= yeoman.app %>/scripts',
|
||||
fontsDir: '<%= yeoman.app %>/styles/fonts',
|
||||
importPath: '<%= yeoman.app %>/bower_components',
|
||||
httpImagesPath: '/images',
|
||||
httpGeneratedImagesPath: '/images/generated',
|
||||
httpFontsPath: '/styles/fonts',
|
||||
relativeAssets: false,
|
||||
assetCacheBuster: false,
|
||||
raw: 'Sass::Script::Number.precision = 10\n'
|
||||
},
|
||||
dist: {
|
||||
options: {
|
||||
generatedImagesDir: '<%= yeoman.dist %>/public/images/generated'
|
||||
}
|
||||
},
|
||||
server: {
|
||||
options: {
|
||||
debugInfo: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Renames files for browser caching purposes
|
||||
rev: {
|
||||
dist: {
|
||||
files: {
|
||||
src: [
|
||||
'<%= yeoman.dist %>/public/scripts/{,*/}*.js',
|
||||
'<%= yeoman.dist %>/public/styles/{,*/}*.css',
|
||||
'<%= yeoman.dist %>/public/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
|
||||
'<%= yeoman.dist %>/public/styles/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.app %>/views/index.html',
|
||||
'<%= yeoman.app %>/views/index.jade'],
|
||||
options: {
|
||||
dest: '<%= yeoman.dist %>/public'
|
||||
}
|
||||
},
|
||||
|
||||
// Performs rewrites based on rev and the useminPrepare configuration
|
||||
usemin: {
|
||||
html: ['<%= yeoman.dist %>/views/{,*/}*.html',
|
||||
'<%= yeoman.dist %>/views/{,*/}*.jade'],
|
||||
css: ['<%= yeoman.dist %>/public/styles/{,*/}*.css'],
|
||||
options: {
|
||||
assetsDirs: ['<%= yeoman.dist %>/public']
|
||||
}
|
||||
},
|
||||
|
||||
// The following *-min tasks produce minified files in the dist folder
|
||||
imagemin: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/images',
|
||||
src: '{,*/}*.{png,jpg,jpeg,gif}',
|
||||
dest: '<%= yeoman.dist %>/public/images'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
svgmin: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/images',
|
||||
src: '{,*/}*.svg',
|
||||
dest: '<%= yeoman.dist %>/public/images'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
htmlmin: {
|
||||
dist: {
|
||||
options: {
|
||||
//collapseWhitespace: true,
|
||||
//collapseBooleanAttributes: true,
|
||||
//removeCommentsFromCDATA: true,
|
||||
//removeOptionalTags: true
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/views',
|
||||
src: ['*.html', 'partials/**/*.html'],
|
||||
dest: '<%= yeoman.dist %>/views'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// 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/scripts',
|
||||
src: '*.js',
|
||||
dest: '.tmp/concat/scripts'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Replace Google CDN references
|
||||
cdnify: {
|
||||
dist: {
|
||||
html: ['<%= yeoman.dist %>/views/*.html']
|
||||
}
|
||||
},
|
||||
|
||||
// Copies remaining files to places other tasks can use
|
||||
copy: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
dot: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
dest: '<%= yeoman.dist %>/public',
|
||||
src: [
|
||||
'*.{ico,png,txt}',
|
||||
'.htaccess',
|
||||
'bower_components/**/*',
|
||||
'images/{,*/}*.{webp}',
|
||||
'fonts/**/*'
|
||||
]
|
||||
}, {
|
||||
expand: true,
|
||||
dot: true,
|
||||
cwd: '<%= yeoman.app %>/views',
|
||||
dest: '<%= yeoman.dist %>/views',
|
||||
src: '**/*.jade'
|
||||
}, {
|
||||
expand: true,
|
||||
cwd: '.tmp/images',
|
||||
dest: '<%= yeoman.dist %>/public/images',
|
||||
src: ['generated/*']
|
||||
}, {
|
||||
expand: true,
|
||||
dest: '<%= yeoman.dist %>',
|
||||
src: [
|
||||
'package.json',
|
||||
'server.js',
|
||||
'lib/**/*'
|
||||
]
|
||||
}]
|
||||
},
|
||||
styles: {
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/styles',
|
||||
dest: '.tmp/styles/',
|
||||
src: '{,*/}*.css'
|
||||
}
|
||||
},
|
||||
|
||||
// Run some tasks in parallel to speed up the build process
|
||||
concurrent: {
|
||||
server: [
|
||||
'compass:server'
|
||||
],
|
||||
test: [
|
||||
'compass'
|
||||
],
|
||||
debug: {
|
||||
tasks: [
|
||||
'nodemon',
|
||||
'node-inspector'
|
||||
],
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
}
|
||||
},
|
||||
dist: [
|
||||
'compass:dist',
|
||||
'imagemin',
|
||||
'svgmin',
|
||||
'htmlmin'
|
||||
]
|
||||
},
|
||||
|
||||
// By default, your `index.html`'s <!-- Usemin block --> will take care of
|
||||
// minification. These next options are pre-configured if you do not wish
|
||||
// to use the Usemin blocks.
|
||||
// cssmin: {
|
||||
// dist: {
|
||||
// files: {
|
||||
// '<%= yeoman.dist %>/styles/main.css': [
|
||||
// '.tmp/styles/{,*/}*.css',
|
||||
// '<%= yeoman.app %>/styles/{,*/}*.css'
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// uglify: {
|
||||
// dist: {
|
||||
// files: {
|
||||
// '<%= yeoman.dist %>/scripts/scripts.js': [
|
||||
// '<%= yeoman.dist %>/scripts/scripts.js'
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// concat: {
|
||||
// dist: {}
|
||||
// },
|
||||
|
||||
// Test settings
|
||||
karma: {
|
||||
unit: {
|
||||
configFile: 'karma.conf.js',
|
||||
singleRun: true
|
||||
},
|
||||
server: {
|
||||
configFile: 'karma.conf.js',
|
||||
singleRun: false,
|
||||
autoWatch: true
|
||||
}
|
||||
},
|
||||
|
||||
mochaTest: {
|
||||
options: {
|
||||
reporter: 'spec'
|
||||
},
|
||||
src: ['test/server/**/*.js']
|
||||
},
|
||||
|
||||
env: {
|
||||
test: {
|
||||
NODE_ENV: 'test'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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', 'express:prod', 'open', 'express-keepalive']);
|
||||
}
|
||||
|
||||
if (target === 'debug') {
|
||||
return grunt.task.run([
|
||||
'clean:server',
|
||||
'bower-install',
|
||||
'concurrent:server',
|
||||
'autoprefixer',
|
||||
'concurrent:debug'
|
||||
]);
|
||||
}
|
||||
|
||||
grunt.task.run([
|
||||
'clean:server',
|
||||
'bower-install',
|
||||
'concurrent:server',
|
||||
'autoprefixer',
|
||||
'express:dev',
|
||||
'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:test',
|
||||
'mochaTest'
|
||||
]);
|
||||
}
|
||||
|
||||
if (target === 'client') {
|
||||
return grunt.task.run([
|
||||
'clean:server',
|
||||
'concurrent:test',
|
||||
'autoprefixer',
|
||||
'karma'
|
||||
]);
|
||||
}
|
||||
|
||||
grunt.task.run([
|
||||
'env:test',
|
||||
'mochaTest',
|
||||
'clean:server',
|
||||
'concurrent:test',
|
||||
'autoprefixer',
|
||||
'karma'
|
||||
]);
|
||||
});
|
||||
|
||||
grunt.registerTask('build', [
|
||||
'clean:dist',
|
||||
'bower-install',
|
||||
'useminPrepare',
|
||||
'concurrent:dist',
|
||||
'autoprefixer',
|
||||
'concat',
|
||||
'ngmin',
|
||||
'copy:dist',
|
||||
'cdnify',
|
||||
'cssmin',
|
||||
'uglify',
|
||||
'rev',
|
||||
'usemin'
|
||||
]);
|
||||
|
||||
grunt.registerTask('heroku', function () {
|
||||
grunt.log.warn('The `heroku` task has been deprecated. Use `grunt build` to build for deployment.');
|
||||
grunt.task.run(['build']);
|
||||
});
|
||||
|
||||
grunt.registerTask('default', [
|
||||
'newer:jshint',
|
||||
'test',
|
||||
'build'
|
||||
]);
|
||||
};
|
||||
1
app/.buildignore
Normal file
1
app/.buildignore
Normal file
@@ -0,0 +1 @@
|
||||
*.coffee
|
||||
543
app/.htaccess
Normal file
543
app/.htaccess
Normal file
@@ -0,0 +1,543 @@
|
||||
# Apache Configuration File
|
||||
|
||||
# (!) Using `.htaccess` files slows down Apache, therefore, if you have access
|
||||
# to the main server config file (usually called `httpd.conf`), you should add
|
||||
# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
|
||||
|
||||
# ##############################################################################
|
||||
# # CROSS-ORIGIN RESOURCE SHARING (CORS) #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Cross-domain AJAX requests |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Enable cross-origin AJAX requests.
|
||||
# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
|
||||
# http://enable-cors.org/
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Access-Control-Allow-Origin "*"
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | CORS-enabled images |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Send the CORS header for images when browsers request it.
|
||||
# https://developer.mozilla.org/en/CORS_Enabled_Image
|
||||
# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
|
||||
# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
|
||||
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
|
||||
SetEnvIf Origin ":" IS_CORS
|
||||
Header set Access-Control-Allow-Origin "*" env=IS_CORS
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Web fonts access |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Allow access from all domains for web fonts
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # ERRORS #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | 404 error prevention for non-existing redirected folders |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Prevent Apache from returning a 404 error for a rewrite if a directory
|
||||
# with the same name does not exist.
|
||||
# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
|
||||
# http://www.webmasterworld.com/apache/3808792.htm
|
||||
|
||||
Options -MultiViews
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Custom error messages / pages |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# You can customize what Apache returns to the client in case of an error (see
|
||||
# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
|
||||
|
||||
ErrorDocument 404 /404.html
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # INTERNET EXPLORER #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Better website experience |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Force IE to render pages in the highest available mode in the various
|
||||
# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-UA-Compatible "IE=edge"
|
||||
# `mod_headers` can't match based on the content-type, however, we only
|
||||
# want to send this header for HTML pages and not for the other resources
|
||||
<FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
|
||||
Header unset X-UA-Compatible
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Cookie setting from iframes |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Allow cookies to be set from iframes in IE.
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Screen flicker |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Stop screen flicker in IE on CSS rollovers (this only works in
|
||||
# combination with the `ExpiresByType` directives for images from below).
|
||||
|
||||
# BrowserMatch "MSIE" brokenvary=1
|
||||
# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
|
||||
# BrowserMatch "Opera" !brokenvary
|
||||
# SetEnvIf brokenvary 1 force-no-vary
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # MIME TYPES AND ENCODING #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Proper MIME types for all files |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
|
||||
# Audio
|
||||
AddType audio/mp4 m4a f4a f4b
|
||||
AddType audio/ogg oga ogg
|
||||
|
||||
# JavaScript
|
||||
# Normalize to standard type (it's sniffed in IE anyways):
|
||||
# http://tools.ietf.org/html/rfc4329#section-7.2
|
||||
AddType application/javascript js jsonp
|
||||
AddType application/json json
|
||||
|
||||
# Video
|
||||
AddType video/mp4 mp4 m4v f4v f4p
|
||||
AddType video/ogg ogv
|
||||
AddType video/webm webm
|
||||
AddType video/x-flv flv
|
||||
|
||||
# Web fonts
|
||||
AddType application/font-woff woff
|
||||
AddType application/vnd.ms-fontobject eot
|
||||
|
||||
# Browsers usually ignore the font MIME types and sniff the content,
|
||||
# however, Chrome shows a warning if other MIME types are used for the
|
||||
# following fonts.
|
||||
AddType application/x-font-ttf ttc ttf
|
||||
AddType font/opentype otf
|
||||
|
||||
# Make SVGZ fonts work on iPad:
|
||||
# https://twitter.com/FontSquirrel/status/14855840545
|
||||
AddType image/svg+xml svg svgz
|
||||
AddEncoding gzip svgz
|
||||
|
||||
# Other
|
||||
AddType application/octet-stream safariextz
|
||||
AddType application/x-chrome-extension crx
|
||||
AddType application/x-opera-extension oex
|
||||
AddType application/x-shockwave-flash swf
|
||||
AddType application/x-web-app-manifest+json webapp
|
||||
AddType application/x-xpinstall xpi
|
||||
AddType application/xml atom rdf rss xml
|
||||
AddType image/webp webp
|
||||
AddType image/x-icon ico
|
||||
AddType text/cache-manifest appcache manifest
|
||||
AddType text/vtt vtt
|
||||
AddType text/x-component htc
|
||||
AddType text/x-vcard vcf
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | UTF-8 encoding |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
|
||||
AddDefaultCharset utf-8
|
||||
|
||||
# Force UTF-8 for certain file formats.
|
||||
<IfModule mod_mime.c>
|
||||
AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # URL REWRITES #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Rewrite engine |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Turning on the rewrite engine and enabling the `FollowSymLinks` option is
|
||||
# necessary for the following directives to work.
|
||||
|
||||
# If your web host doesn't allow the `FollowSymlinks` option, you may need to
|
||||
# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
|
||||
# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
|
||||
|
||||
# Also, some cloud hosting services require `RewriteBase` to be set:
|
||||
# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
Options +FollowSymlinks
|
||||
# Options +SymLinksIfOwnerMatch
|
||||
RewriteEngine On
|
||||
# RewriteBase /
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Suppressing / Forcing the "www." at the beginning of URLs |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# The same content should never be available under two different URLs especially
|
||||
# not with and without "www." at the beginning. This can cause SEO problems
|
||||
# (duplicate content), therefore, you should choose one of the alternatives and
|
||||
# redirect the other one.
|
||||
|
||||
# By default option 1 (no "www.") is activated:
|
||||
# http://no-www.org/faq.php?q=class_b
|
||||
|
||||
# If you'd prefer to use option 2, just comment out all the lines from option 1
|
||||
# and uncomment the ones from option 2.
|
||||
|
||||
# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Option 1: rewrite www.example.com → example.com
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{HTTPS} !=on
|
||||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Option 2: rewrite example.com → www.example.com
|
||||
|
||||
# Be aware that the following might not be a good idea if you use "real"
|
||||
# subdomains for certain parts of your website.
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteCond %{HTTPS} !=on
|
||||
# RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
|
||||
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # SECURITY #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Content Security Policy (CSP) |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# You can mitigate the risk of cross-site scripting and other content-injection
|
||||
# attacks by setting a Content Security Policy which whitelists trusted sources
|
||||
# of content for your site.
|
||||
|
||||
# The example header below allows ONLY scripts that are loaded from the current
|
||||
# site's origin (no inline scripts, no CDN, etc). This almost certainly won't
|
||||
# work as-is for your site!
|
||||
|
||||
# To get all the details you'll need to craft a reasonable policy for your site,
|
||||
# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
|
||||
# see the specification: http://w3.org/TR/CSP).
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
|
||||
# <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
|
||||
# Header unset Content-Security-Policy
|
||||
# </FilesMatch>
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | File access |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Block access to directories without a default document.
|
||||
# Usually you should leave this uncommented because you shouldn't allow anyone
|
||||
# to surf through every directory on your server (which may includes rather
|
||||
# private places like the CMS's directories).
|
||||
|
||||
<IfModule mod_autoindex.c>
|
||||
Options -Indexes
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Block access to hidden files and directories.
|
||||
# This includes directories used by version control systems such as Git and SVN.
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{SCRIPT_FILENAME} -d [OR]
|
||||
RewriteCond %{SCRIPT_FILENAME} -f
|
||||
RewriteRule "(^|/)\." - [F]
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Block access to backup and source files.
|
||||
# These files may be left by some text editors and can pose a great security
|
||||
# danger when anyone has access to them.
|
||||
|
||||
<FilesMatch "(^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
Satisfy All
|
||||
</FilesMatch>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Secure Sockets Layer (SSL) |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
|
||||
# prevent `https://www.example.com` when your certificate only allows
|
||||
# `https://secure.example.com`.
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteCond %{SERVER_PORT} !^443
|
||||
# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
|
||||
# </IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Force client-side SSL redirection.
|
||||
|
||||
# If a user types "example.com" in his browser, the above rule will redirect him
|
||||
# to the secure version of the site. That still leaves a window of opportunity
|
||||
# (the initial HTTP connection) for an attacker to downgrade or redirect the
|
||||
# request. The following header ensures that browser will ONLY connect to your
|
||||
# server via HTTPS, regardless of what the users type in the address bar.
|
||||
# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Strict-Transport-Security max-age=16070400;
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Server software information |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Avoid displaying the exact Apache version number, the description of the
|
||||
# generic OS-type and the information about Apache's compiled-in modules.
|
||||
|
||||
# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
|
||||
|
||||
# ServerTokens Prod
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # WEB PERFORMANCE #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Compression |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
|
||||
# Force compression for mangled headers.
|
||||
# http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
|
||||
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# Compress all output labeled with one of the following MIME-types
|
||||
# (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
|
||||
# and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines
|
||||
# as `AddOutputFilterByType` is still in the core directives).
|
||||
<IfModule mod_filter.c>
|
||||
AddOutputFilterByType DEFLATE application/atom+xml \
|
||||
application/javascript \
|
||||
application/json \
|
||||
application/rss+xml \
|
||||
application/vnd.ms-fontobject \
|
||||
application/x-font-ttf \
|
||||
application/x-web-app-manifest+json \
|
||||
application/xhtml+xml \
|
||||
application/xml \
|
||||
font/opentype \
|
||||
image/svg+xml \
|
||||
image/x-icon \
|
||||
text/css \
|
||||
text/html \
|
||||
text/plain \
|
||||
text/x-component \
|
||||
text/xml
|
||||
</IfModule>
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Content transformations |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Prevent some of the mobile network providers from modifying the content of
|
||||
# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Cache-Control "no-transform"
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | ETag removal |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Since we're sending far-future expires headers (see below), ETags can
|
||||
# be removed: http://developer.yahoo.com/performance/rules.html#etags.
|
||||
|
||||
# `FileETag None` is not enough for every server.
|
||||
<IfModule mod_headers.c>
|
||||
Header unset ETag
|
||||
</IfModule>
|
||||
|
||||
FileETag None
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Expires headers (for better cache control) |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# The following expires headers are set pretty far in the future. If you don't
|
||||
# control versioning with filename-based cache busting, consider lowering the
|
||||
# cache time for resources like CSS and JS to something like 1 week.
|
||||
|
||||
<IfModule mod_expires.c>
|
||||
|
||||
ExpiresActive on
|
||||
ExpiresDefault "access plus 1 month"
|
||||
|
||||
# CSS
|
||||
ExpiresByType text/css "access plus 1 year"
|
||||
|
||||
# Data interchange
|
||||
ExpiresByType application/json "access plus 0 seconds"
|
||||
ExpiresByType application/xml "access plus 0 seconds"
|
||||
ExpiresByType text/xml "access plus 0 seconds"
|
||||
|
||||
# Favicon (cannot be renamed!)
|
||||
ExpiresByType image/x-icon "access plus 1 week"
|
||||
|
||||
# HTML components (HTCs)
|
||||
ExpiresByType text/x-component "access plus 1 month"
|
||||
|
||||
# HTML
|
||||
ExpiresByType text/html "access plus 0 seconds"
|
||||
|
||||
# JavaScript
|
||||
ExpiresByType application/javascript "access plus 1 year"
|
||||
|
||||
# Manifest files
|
||||
ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
|
||||
ExpiresByType text/cache-manifest "access plus 0 seconds"
|
||||
|
||||
# Media
|
||||
ExpiresByType audio/ogg "access plus 1 month"
|
||||
ExpiresByType image/gif "access plus 1 month"
|
||||
ExpiresByType image/jpeg "access plus 1 month"
|
||||
ExpiresByType image/png "access plus 1 month"
|
||||
ExpiresByType video/mp4 "access plus 1 month"
|
||||
ExpiresByType video/ogg "access plus 1 month"
|
||||
ExpiresByType video/webm "access plus 1 month"
|
||||
|
||||
# Web feeds
|
||||
ExpiresByType application/atom+xml "access plus 1 hour"
|
||||
ExpiresByType application/rss+xml "access plus 1 hour"
|
||||
|
||||
# Web fonts
|
||||
ExpiresByType application/font-woff "access plus 1 month"
|
||||
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
|
||||
ExpiresByType application/x-font-ttf "access plus 1 month"
|
||||
ExpiresByType font/opentype "access plus 1 month"
|
||||
ExpiresByType image/svg+xml "access plus 1 month"
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Filename-based cache busting |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# If you're not using a build process to manage your filename version revving,
|
||||
# you might want to consider enabling the following directives to route all
|
||||
# requests such as `/css/style.12345.css` to `/css/style.css`.
|
||||
|
||||
# To understand why this is important and a better idea than `*.css?v231`, read:
|
||||
# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | File concatenation |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Allow concatenation from within specific CSS and JS files, e.g.:
|
||||
# Inside of `script.combined.js` you could have
|
||||
# <!--#include file="libs/jquery.js" -->
|
||||
# <!--#include file="plugins/jquery.idletimer.js" -->
|
||||
# and they would be included into this single file.
|
||||
|
||||
# <IfModule mod_include.c>
|
||||
# <FilesMatch "\.combined\.js$">
|
||||
# Options +Includes
|
||||
# AddOutputFilterByType INCLUDES application/javascript application/json
|
||||
# SetOutputFilter INCLUDES
|
||||
# </FilesMatch>
|
||||
# <FilesMatch "\.combined\.css$">
|
||||
# Options +Includes
|
||||
# AddOutputFilterByType INCLUDES text/css
|
||||
# SetOutputFilter INCLUDES
|
||||
# </FilesMatch>
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Persistent connections |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Allow multiple requests to be sent over the same TCP connection:
|
||||
# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
|
||||
|
||||
# Enable if you serve a lot of static content but, be aware of the
|
||||
# possible disadvantages!
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Connection Keep-Alive
|
||||
# </IfModule>
|
||||
BIN
app/favicon.ico
Normal file
BIN
app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
app/images/roboto-small.png
Normal file
BIN
app/images/roboto-small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
app/images/yeoman.png
Normal file
BIN
app/images/yeoman.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
3
app/robots.txt
Normal file
3
app/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# robotstxt.org
|
||||
|
||||
User-agent: *
|
||||
58
app/scripts/app.js
Normal file
58
app/scripts/app.js
Normal file
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp', [
|
||||
'ngCookies',
|
||||
'ngResource',
|
||||
'ngSanitize',
|
||||
'ngRoute'
|
||||
])
|
||||
.config(function ($routeProvider, $locationProvider, $httpProvider) {
|
||||
$routeProvider
|
||||
.when('/', {
|
||||
templateUrl: 'partials/main',
|
||||
controller: 'MainCtrl'
|
||||
})
|
||||
.when('/login', {
|
||||
templateUrl: 'partials/login',
|
||||
controller: 'LoginCtrl'
|
||||
})
|
||||
.when('/signup', {
|
||||
templateUrl: 'partials/signup',
|
||||
controller: 'SignupCtrl'
|
||||
})
|
||||
.when('/settings', {
|
||||
templateUrl: 'partials/settings',
|
||||
controller: 'SettingsCtrl',
|
||||
authenticate: true
|
||||
})
|
||||
.otherwise({
|
||||
redirectTo: '/'
|
||||
});
|
||||
|
||||
$locationProvider.html5Mode(true);
|
||||
|
||||
// Intercept 401s and redirect you to login
|
||||
$httpProvider.interceptors.push(['$q', '$location', function($q, $location) {
|
||||
return {
|
||||
'responseError': function(response) {
|
||||
if(response.status === 401) {
|
||||
$location.path('/login');
|
||||
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('$routeChangeStart', function (event, next) {
|
||||
|
||||
if (next.authenticate && !Auth.isLoggedIn()) {
|
||||
$location.path('/login');
|
||||
}
|
||||
});
|
||||
});
|
||||
26
app/scripts/controllers/login.js
Normal file
26
app/scripts/controllers/login.js
Normal file
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp')
|
||||
.controller('LoginCtrl', function ($scope, Auth, $location) {
|
||||
$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) {
|
||||
err = err.data;
|
||||
$scope.errors.other = err.message;
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
8
app/scripts/controllers/main.js
Normal file
8
app/scripts/controllers/main.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp')
|
||||
.controller('MainCtrl', function ($scope, $http) {
|
||||
$http.get('/api/awesomeThings').success(function(awesomeThings) {
|
||||
$scope.awesomeThings = awesomeThings;
|
||||
});
|
||||
});
|
||||
23
app/scripts/controllers/navbar.js
Normal file
23
app/scripts/controllers/navbar.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp')
|
||||
.controller('NavbarCtrl', function ($scope, $location, Auth) {
|
||||
$scope.menu = [{
|
||||
'title': 'Home',
|
||||
'link': '/'
|
||||
}, {
|
||||
'title': 'Settings',
|
||||
'link': '/settings'
|
||||
}];
|
||||
|
||||
$scope.logout = function() {
|
||||
Auth.logout()
|
||||
.then(function() {
|
||||
$location.path('/login');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isActive = function(route) {
|
||||
return route === $location.path();
|
||||
};
|
||||
});
|
||||
21
app/scripts/controllers/settings.js
Normal file
21
app/scripts/controllers/settings.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp')
|
||||
.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';
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
33
app/scripts/controllers/signup.js
Normal file
33
app/scripts/controllers/signup.js
Normal file
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp')
|
||||
.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;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
18
app/scripts/directives/mongooseError.js
Normal file
18
app/scripts/directives/mongooseError.js
Normal file
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp')
|
||||
|
||||
/**
|
||||
* Removes server error when user updates input
|
||||
*/
|
||||
.directive('mongooseError', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
element.on('keydown', function() {
|
||||
return ngModel.$setValidity('mongoose', true);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
111
app/scripts/services/auth.js
Normal file
111
app/scripts/services/auth.js
Normal file
@@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp')
|
||||
.factory('Auth', function Auth($location, $rootScope, Session, User, $cookieStore) {
|
||||
|
||||
// Get currentUser from cookie
|
||||
$rootScope.currentUser = $cookieStore.get('user') || null;
|
||||
$cookieStore.remove('user');
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Authenticate user
|
||||
*
|
||||
* @param {Object} user - login info
|
||||
* @param {Function} callback - optional
|
||||
* @return {Promise}
|
||||
*/
|
||||
login: function(user, callback) {
|
||||
var cb = callback || angular.noop;
|
||||
|
||||
return Session.save({
|
||||
email: user.email,
|
||||
password: user.password
|
||||
}, function(user) {
|
||||
$rootScope.currentUser = user;
|
||||
return cb();
|
||||
}, function(err) {
|
||||
return cb(err);
|
||||
}).$promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unauthenticate user
|
||||
*
|
||||
* @param {Function} callback - optional
|
||||
* @return {Promise}
|
||||
*/
|
||||
logout: function(callback) {
|
||||
var cb = callback || angular.noop;
|
||||
|
||||
return Session.delete(function() {
|
||||
$rootScope.currentUser = null;
|
||||
return cb();
|
||||
},
|
||||
function(err) {
|
||||
return cb(err);
|
||||
}).$promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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(user) {
|
||||
$rootScope.currentUser = user;
|
||||
return cb(user);
|
||||
},
|
||||
function(err) {
|
||||
return cb(err);
|
||||
}).$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.update({
|
||||
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
|
||||
*/
|
||||
currentUser: function() {
|
||||
return User.get();
|
||||
},
|
||||
|
||||
/**
|
||||
* Simple check to see if a user is logged in
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isLoggedIn: function() {
|
||||
var user = $rootScope.currentUser;
|
||||
return !!user;
|
||||
},
|
||||
};
|
||||
});
|
||||
6
app/scripts/services/session.js
Normal file
6
app/scripts/services/session.js
Normal file
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp')
|
||||
.factory('Session', function ($resource) {
|
||||
return $resource('/api/session/');
|
||||
});
|
||||
19
app/scripts/services/user.js
Normal file
19
app/scripts/services/user.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('robotoApp')
|
||||
.factory('User', function ($resource) {
|
||||
return $resource('/api/users/:id', {
|
||||
id: '@id'
|
||||
}, { //parameters default
|
||||
update: {
|
||||
method: 'PUT',
|
||||
params: {}
|
||||
},
|
||||
get: {
|
||||
method: 'GET',
|
||||
params: {
|
||||
id:'me'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
91
app/styles/main.scss
Normal file
91
app/styles/main.scss
Normal file
@@ -0,0 +1,91 @@
|
||||
$icon-font-path: "/bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/";
|
||||
|
||||
@import 'bootstrap-sass-official/vendor/assets/stylesheets/bootstrap';
|
||||
|
||||
.browsehappy {
|
||||
margin: 0.2em 0;
|
||||
background: #ccc;
|
||||
color: #000;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
|
||||
/* Space out content a bit */
|
||||
body {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Everything but the jumbotron gets side spacing for mobile first views */
|
||||
.header,
|
||||
.marketing,
|
||||
.footer {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
/* Custom page header */
|
||||
.header {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
/* Make the masthead heading the same height as the navigation */
|
||||
.header h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
line-height: 40px;
|
||||
padding-bottom: 19px;
|
||||
}
|
||||
|
||||
/* Custom page footer */
|
||||
.footer {
|
||||
padding-top: 19px;
|
||||
color: #777;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.container-narrow > hr {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
/* Main marketing message and sign up button */
|
||||
.jumbotron {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.jumbotron .btn {
|
||||
font-size: 21px;
|
||||
padding: 14px 24px;
|
||||
}
|
||||
|
||||
/* Supporting marketing content */
|
||||
.marketing {
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
.marketing p + h4 {
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
/* Responsive: Portrait tablets and up */
|
||||
@media screen and (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 730px;
|
||||
}
|
||||
|
||||
/* Remove the padding we set earlier */
|
||||
.header,
|
||||
.marketing,
|
||||
.footer {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
/* Space out the masthead */
|
||||
.header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
/* Remove the bottom border on the jumbotron for visual effect */
|
||||
.jumbotron {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
157
app/views/404.html
Normal file
157
app/views/404.html
Normal file
@@ -0,0 +1,157 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Page Not Found :(</title>
|
||||
<style>
|
||||
::-moz-selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
html {
|
||||
padding: 30px 10px;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
color: #737373;
|
||||
background: #f0f0f0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
input {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 500px;
|
||||
_width: 500px;
|
||||
padding: 30px 20px 50px;
|
||||
border: 1px solid #b3b3b3;
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 10px;
|
||||
font-size: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 span {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 1.5em 0 0.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0 0 0 40px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 380px;
|
||||
_width: 380px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* google search */
|
||||
|
||||
#goog-fixurl ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#goog-fixurl form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#goog-wm-qt,
|
||||
#goog-wm-sb {
|
||||
border: 1px solid #bbb;
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
vertical-align: top;
|
||||
color: #444;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#goog-wm-qt {
|
||||
width: 220px;
|
||||
height: 20px;
|
||||
padding: 5px;
|
||||
margin: 5px 10px 0 0;
|
||||
box-shadow: inset 0 1px 1px #ccc;
|
||||
}
|
||||
|
||||
#goog-wm-sb {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
margin: 5px 0 0;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
background-color: #f5f5f5;
|
||||
background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
*overflow: visible;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
#goog-wm-sb:hover,
|
||||
#goog-wm-sb:focus {
|
||||
border-color: #aaa;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
#goog-wm-qt:hover,
|
||||
#goog-wm-qt:focus {
|
||||
border-color: #105cb6;
|
||||
outline: 0;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
input::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Not found <span>:(</span></h1>
|
||||
<p>Sorry, but the page you were trying to view does not exist.</p>
|
||||
<p>It looks like this was the result of either:</p>
|
||||
<ul>
|
||||
<li>a mistyped address</li>
|
||||
<li>an out-of-date link</li>
|
||||
</ul>
|
||||
<script>
|
||||
var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host;
|
||||
</script>
|
||||
<script src="//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
69
app/views/index.html
Normal file
69
app/views/index.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!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(app) styles/vendor.css -->
|
||||
<!-- bower:css -->
|
||||
<!-- endbower -->
|
||||
<!-- endbuild -->
|
||||
<!-- build:css({.tmp,app}) styles/main.css -->
|
||||
<link rel="stylesheet" href="styles/main.css">
|
||||
<!-- endbuild -->
|
||||
</head>
|
||||
<body ng-app="robotoApp">
|
||||
<!--[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 class="container" ng-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]-->
|
||||
|
||||
<!-- build:js(app) scripts/vendor.js -->
|
||||
<!-- bower:js -->
|
||||
<script src="bower_components/jquery/dist/jquery.js"></script>
|
||||
<script src="bower_components/angular/angular.js"></script>
|
||||
<script src="bower_components/angular-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-route/angular-route.js"></script>
|
||||
<!-- endbower -->
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- build:js({.tmp,app}) scripts/scripts.js -->
|
||||
<script src="scripts/app.js"></script>
|
||||
<script src="scripts/controllers/main.js"></script>
|
||||
<script src="scripts/controllers/navbar.js"></script>
|
||||
<script src="scripts/controllers/login.js"></script>
|
||||
<script src="scripts/controllers/signup.js"></script>
|
||||
<script src="scripts/controllers/settings.js"></script>
|
||||
<script src="scripts/services/auth.js"></script>
|
||||
<script src="scripts/services/session.js"></script>
|
||||
<script src="scripts/services/user.js"></script>
|
||||
<script src="scripts/directives/mongooseError.js"></script>
|
||||
<!-- endbuild -->
|
||||
</body>
|
||||
</html>
|
||||
40
app/views/partials/login.html
Normal file
40
app/views/partials/login.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<div ng-include="'partials/navbar.html'"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1>Login</h1>
|
||||
<p>Accounts are reset on server restart from <code>dummydata.js</code>. Default account is <code>test@test.com</code> / <code>test</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>
|
||||
|
||||
<button type="submit" class="btn btn-lg btn-primary"> Sign in </button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<hr>
|
||||
Not registered? <a href="/signup" class="text-center new-account">Create an account.</a>
|
||||
</div>
|
||||
</div>
|
||||
12
app/views/partials/main.html
Normal file
12
app/views/partials/main.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<div ng-include="'partials/navbar'"></div>
|
||||
|
||||
<div class="jumbotron">
|
||||
<p class="lead">
|
||||
<img src="images/roboto-small.png" alt="I'm Roboto"><br>
|
||||
Intelligent Promotion Distribution for Humans
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>♥ from the PodNoms team</p>
|
||||
</div>
|
||||
11
app/views/partials/navbar.html
Normal file
11
app/views/partials/navbar.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="header" ng-controller="NavbarCtrl">
|
||||
<ul class="nav nav-pills pull-right">
|
||||
<li ng-repeat="item in menu" ng-class="{active: isActive(item.link)}">
|
||||
<a ng-href="{{item.link}}">{{item.title}}</a>
|
||||
</li>
|
||||
<li ng-hide="currentUser" ng-class="{active: isActive('/login')}"><a href="/login">Login</a></li>
|
||||
<li ng-hide="currentUser" ng-class="{active: isActive('/signup')}"><a href="/signup">Sign up</a></li>
|
||||
<li ng-show="currentUser" ng-class="{active: isActive('/logout')}"><a href="" ng-click="logout()">Logout</a></li>
|
||||
</ul>
|
||||
<h3 class="text-muted">Roboto Promoto</h3>
|
||||
</div>
|
||||
37
app/views/partials/settings.html
Normal file
37
app/views/partials/settings.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<div ng-include="'partials/navbar.html'"></div>
|
||||
|
||||
<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>
|
||||
66
app/views/partials/signup.html
Normal file
66
app/views/partials/signup.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<div ng-include="'partials/navbar.html'"></div>
|
||||
|
||||
<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>
|
||||
|
||||
<button class="btn btn-lg btn-primary" type="submit">Sign up</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<hr>
|
||||
Have an account? <a href="/login" class="text-center new-account">Log in.</a>
|
||||
</div>
|
||||
</div>
|
||||
26
azure.err
Normal file
26
azure.err
Normal file
@@ -0,0 +1,26 @@
|
||||
Thu Mar 20 2014 11:27:44 GMT+0000 (GMT):
|
||||
{ [Error: Git credentials needs to be setup on the portal]
|
||||
stack: [Getter/Setter],
|
||||
__frame:
|
||||
{ name: 'getPublishingUser__14',
|
||||
line: 639,
|
||||
file: '/usr/lib/node_modules/azure-cli/lib/commands/websites/websitesclient._js',
|
||||
prev:
|
||||
{ name: 'LinkedRevisionControlClient_prototype__addRemotesToLocalGitRepo__5',
|
||||
line: 160,
|
||||
file: '/usr/lib/node_modules/azure-cli/lib/util/git/linkedrevisioncontrol._js',
|
||||
prev: [Object],
|
||||
active: false,
|
||||
offset: 17,
|
||||
col: 29 },
|
||||
active: false,
|
||||
offset: 39,
|
||||
col: 8 },
|
||||
rawStack: [Getter] }
|
||||
Error: Git credentials needs to be setup on the portal
|
||||
at fallbackToPortal (/usr/lib/node_modules/azure-cli/lib/commands/websites/websitesclient._js:645:20)
|
||||
at getPublishingUser__14 (/usr/lib/node_modules/azure-cli/lib/commands/websites/websitesclient._js:678:8)
|
||||
at LinkedRevisionControlClient_prototype__addRemotesToLocalGitRepo__5 (/usr/lib/node_modules/azure-cli/lib/util/git/linkedrevisioncontrol._js:177:29)
|
||||
at GitClient_prototype_deploy__7 (/usr/lib/node_modules/azure-cli/lib/util/git/linkedrevisioncontrol._js:214:2)
|
||||
at __4 (/usr/lib/node_modules/azure-cli/lib/commands/site._js:266:12)
|
||||
|
||||
20
bower.json
Normal file
20
bower.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "roboto-promoto",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"angular": "1.2.11",
|
||||
"json3": "~3.2.6",
|
||||
"es5-shim": "~2.1.0",
|
||||
"jquery": "~1.11.0",
|
||||
"bootstrap-sass-official": "~3.1.1",
|
||||
"angular-resource": "1.2.11",
|
||||
"angular-cookies": "1.2.11",
|
||||
"angular-sanitize": "1.2.11",
|
||||
"angular-route": "1.2.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.2.11",
|
||||
"angular-scenario": "1.2.11"
|
||||
},
|
||||
"testPath": "test/client/spec"
|
||||
}
|
||||
27
iisnode.yml
Normal file
27
iisnode.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
# For documentation see https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/iisnode.yml
|
||||
|
||||
# loggingEnabled: false
|
||||
# debuggingEnabled: false
|
||||
# devErrorsEnabled: false
|
||||
# node_env: production
|
||||
# nodeProcessCountPerApplication: 1
|
||||
# maxConcurrentRequestsPerProcess: 1024
|
||||
# maxNamedPipeConnectionRetry: 24
|
||||
# namedPipeConnectionRetryDelay: 250
|
||||
# maxNamedPipeConnectionPoolSize: 512
|
||||
# maxNamedPipePooledConnectionAge: 30000
|
||||
# asyncCompletionThreadCount: 0
|
||||
# initialRequestBufferSize: 4096
|
||||
# maxRequestBufferSize: 65536
|
||||
# watchedFiles: *.js;iisnode.yml;node_modules\*
|
||||
# uncFileChangesPollingInterval: 5000
|
||||
# gracefulShutdownTimeout: 60000
|
||||
# logDirectoryNameSuffix: logs
|
||||
# debuggerPortRange: 5058-6058
|
||||
# debuggerPathSegment: debug
|
||||
# maxLogFileSizeInKB: 128
|
||||
# appendToExistingLog: false
|
||||
# logFileFlushInterval: 5000
|
||||
# flushResponse: false
|
||||
# enableXFF: false
|
||||
# promoteServerVars:
|
||||
54
karma-e2e.conf.js
Normal file
54
karma-e2e.conf.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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: ['ng-scenario'],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'test/e2e/**/*.js'
|
||||
],
|
||||
|
||||
// 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: ['Chrome'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
singleRun: false
|
||||
|
||||
// Uncomment the following lines if you are using grunt's server to run the tests
|
||||
// proxies: {
|
||||
// '/': 'http://localhost:9000/'
|
||||
// },
|
||||
// URL root prevent conflicts with the site root
|
||||
// urlRoot: '_karma_'
|
||||
});
|
||||
};
|
||||
56
karma.conf.js
Normal file
56
karma.conf.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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: [
|
||||
'app/bower_components/angular/angular.js',
|
||||
'app/bower_components/angular-mocks/angular-mocks.js',
|
||||
'app/bower_components/angular-resource/angular-resource.js',
|
||||
'app/bower_components/angular-cookies/angular-cookies.js',
|
||||
'app/bower_components/angular-sanitize/angular-sanitize.js',
|
||||
'app/bower_components/angular-route/angular-route.js',
|
||||
'app/scripts/*.js',
|
||||
'app/scripts/**/*.js',
|
||||
'test/mock/**/*.js',
|
||||
'test/spec/**/*.js'
|
||||
],
|
||||
|
||||
// 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: ['Chrome'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
13
lib/.jshintrc
Normal file
13
lib/.jshintrc
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"node": true,
|
||||
"esnext": true,
|
||||
"bitwise": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"regexp": true,
|
||||
"undef": true,
|
||||
"smarttabs": true
|
||||
}
|
||||
10
lib/config/config.js
Normal file
10
lib/config/config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Load environment configuration
|
||||
*/
|
||||
module.exports = _.merge(
|
||||
require('./env/all.js'),
|
||||
require('./env/' + process.env.NODE_ENV + '.js') || {});
|
||||
37
lib/config/dummydata.js
Normal file
37
lib/config/dummydata.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
Promotion = mongoose.model('Promotion'),
|
||||
Thing = mongoose.model('Thing');
|
||||
var user;
|
||||
|
||||
console.log("Creating users");
|
||||
// Clear old users, then add a default user
|
||||
User.find({}).remove(function() {
|
||||
user = new User({
|
||||
provider: 'local',
|
||||
name: 'Test User',
|
||||
email: 'test@test.com',
|
||||
password: 'test' }, function() {
|
||||
console.log('finished populating users');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
console.log("Creating promotions");
|
||||
Promotion.find({}).remove(function(){
|
||||
Promotion.create({
|
||||
title: 'Dev prom 1',
|
||||
user: user
|
||||
}, {
|
||||
title: 'Dev prom 2',
|
||||
user: user
|
||||
}, {
|
||||
title: 'Dev prom 3',
|
||||
user: user
|
||||
}, function(){
|
||||
console.log('finished populating promotions');
|
||||
});
|
||||
});
|
||||
|
||||
17
lib/config/env/all.js
vendored
Normal file
17
lib/config/env/all.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var rootPath = path.normalize(__dirname + '/../../..');
|
||||
|
||||
module.exports = {
|
||||
root: rootPath,
|
||||
port: process.env.PORT || 3000,
|
||||
mongo: {
|
||||
options: {
|
||||
db: {
|
||||
safe: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
8
lib/config/env/development.js
vendored
Normal file
8
lib/config/env/development.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: 'development',
|
||||
mongo: {
|
||||
uri: 'mongodb://localhost/roboto-dev'
|
||||
}
|
||||
};
|
||||
10
lib/config/env/production.js
vendored
Normal file
10
lib/config/env/production.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: 'production',
|
||||
mongo: {
|
||||
uri: process.env.MONGOLAB_URI ||
|
||||
process.env.MONGOHQ_URL ||
|
||||
'mongodb://localhost/roboto'
|
||||
}
|
||||
};
|
||||
8
lib/config/env/test.js
vendored
Normal file
8
lib/config/env/test.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: 'test',
|
||||
mongo: {
|
||||
uri: 'mongodb://localhost/roboto-test'
|
||||
}
|
||||
};
|
||||
69
lib/config/express.js
Normal file
69
lib/config/express.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
var express = require('express'),
|
||||
path = require('path'),
|
||||
config = require('./config'),
|
||||
passport = require('passport'),
|
||||
mongoStore = require('connect-mongo')(express);
|
||||
|
||||
/**
|
||||
* Express configuration
|
||||
*/
|
||||
module.exports = function(app) {
|
||||
app.configure('development', function(){
|
||||
app.use(require('connect-livereload')());
|
||||
|
||||
// Disable caching of scripts for easier testing
|
||||
app.use(function noCache(req, res, next) {
|
||||
if (req.url.indexOf('/scripts/') === 0) {
|
||||
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
res.header('Pragma', 'no-cache');
|
||||
res.header('Expires', 0);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(express.static(path.join(config.root, '.tmp')));
|
||||
app.use(express.static(path.join(config.root, 'app')));
|
||||
app.set('views', config.root + '/app/views');
|
||||
});
|
||||
|
||||
app.configure('production', function(){
|
||||
app.use(express.favicon(path.join(config.root, 'public', 'favicon.ico')));
|
||||
app.use(express.static(path.join(config.root, 'public')));
|
||||
app.set('views', config.root + '/views');
|
||||
});
|
||||
|
||||
app.configure(function(){
|
||||
app.engine('html', require('ejs').renderFile);
|
||||
app.set('view engine', 'html');
|
||||
app.use(express.logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.cookieParser());
|
||||
|
||||
// Persist sessions with mongoStore
|
||||
app.use(express.session({
|
||||
secret: 'angular-fullstack secret',
|
||||
store: new mongoStore({
|
||||
url: config.mongo.uri,
|
||||
collection: 'sessions'
|
||||
}, function () {
|
||||
console.log("db connection open");
|
||||
})
|
||||
}));
|
||||
|
||||
//use passport session
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// Router (only error handlers should come after this)
|
||||
app.use(app.router);
|
||||
});
|
||||
|
||||
// Error handler
|
||||
app.configure('development', function(){
|
||||
app.use(express.errorHandler());
|
||||
});
|
||||
};
|
||||
48
lib/config/passport.js
Normal file
48
lib/config/passport.js
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
passport = require('passport'),
|
||||
LocalStrategy = require('passport-local').Strategy;
|
||||
|
||||
/**
|
||||
* Passport configuration
|
||||
*/
|
||||
passport.serializeUser(function(user, done) {
|
||||
done(null, user.id);
|
||||
});
|
||||
passport.deserializeUser(function(id, done) {
|
||||
User.findOne({
|
||||
_id: id
|
||||
}, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt
|
||||
done(err, user);
|
||||
});
|
||||
});
|
||||
|
||||
// add other strategies for more authentication flexibility
|
||||
passport.use(new LocalStrategy({
|
||||
usernameField: 'email',
|
||||
passwordField: 'password' // this is the virtual field on the model
|
||||
},
|
||||
function(email, password, done) {
|
||||
User.findOne({
|
||||
email: email
|
||||
}, 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);
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
module.exports = passport;
|
||||
18
lib/controllers/api.js
Normal file
18
lib/controllers/api.js
Normal file
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Promotion = mongoose.model('Promotion'),
|
||||
Thing = mongoose.model('Thing');
|
||||
|
||||
/**
|
||||
* Get awesome things
|
||||
*/
|
||||
exports.awesomeThings = function(req, res) {
|
||||
return Thing.find(function (err, things) {
|
||||
if (!err) {
|
||||
return res.json(things);
|
||||
} else {
|
||||
return res.send(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
27
lib/controllers/index.js
Normal file
27
lib/controllers/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
/**
|
||||
* Send partial, or 404 if it doesn't exist
|
||||
*/
|
||||
exports.partials = function(req, res) {
|
||||
var stripped = req.url.split('.')[0];
|
||||
var requestedView = path.join('./', stripped);
|
||||
res.render(requestedView, function(err, html) {
|
||||
if(err) {
|
||||
console.log("Error rendering partial '" + requestedView + "'\n", err);
|
||||
res.status(404);
|
||||
res.send(404);
|
||||
} else {
|
||||
res.send(html);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send our single page app
|
||||
*/
|
||||
exports.index = function(req, res) {
|
||||
res.render('index');
|
||||
};
|
||||
59
lib/controllers/promotions.js
Normal file
59
lib/controllers/promotions.js
Normal file
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Promotion = mongoose.model('Promotion'),
|
||||
passport = require('passport');
|
||||
|
||||
|
||||
exports.list = function(req, res){
|
||||
return Promotion.find(function(err, promotions){
|
||||
if (!err){
|
||||
return res.json(promotions);
|
||||
}else{
|
||||
return res.send(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
exports.create = function (req, res, next) {
|
||||
var newPromotion = new Promotion(req.body);
|
||||
newPromotion.provider = 'local';
|
||||
newPromotion.save(function(err) {
|
||||
if (err) return res.json(400, err);
|
||||
|
||||
req.logIn(newPromotion, function(err) {
|
||||
if (err) return next(err);
|
||||
|
||||
return res.json(req.promotion.promotionInfo);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.show = function (req, res, next) {
|
||||
var promotionId = req.params.id;
|
||||
|
||||
Promotion.findById(promotionId, function (err, promotion) {
|
||||
if (err) return next(err);
|
||||
if (!promotion) return res.send(404);
|
||||
|
||||
res.send({ profile: promotion.profile });
|
||||
});
|
||||
};
|
||||
|
||||
exports.changePassword = function(req, res, next) {
|
||||
var promotionId = req.promotion._id;
|
||||
var oldPass = String(req.body.oldPassword);
|
||||
var newPass = String(req.body.newPassword);
|
||||
|
||||
Promotion.findById(promotionId, function (err, promotion) {
|
||||
if(promotion.authenticate(oldPass)) {
|
||||
promotion.password = newPass;
|
||||
promotion.save(function(err) {
|
||||
if (err) return res.send(400);
|
||||
|
||||
res.send(200);
|
||||
});
|
||||
} else {
|
||||
res.send(403);
|
||||
}
|
||||
});
|
||||
};
|
||||
28
lib/controllers/session.js
Normal file
28
lib/controllers/session.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
passport = require('passport');
|
||||
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
exports.logout = function (req, res) {
|
||||
req.logout();
|
||||
res.send(200);
|
||||
};
|
||||
|
||||
/**
|
||||
* Login
|
||||
*/
|
||||
exports.login = function (req, res, next) {
|
||||
passport.authenticate('local', function(err, user, info) {
|
||||
var error = err || info;
|
||||
if (error) return res.json(401, error);
|
||||
|
||||
req.logIn(user, function(err) {
|
||||
|
||||
if (err) return res.send(err);
|
||||
res.json(req.user.userInfo);
|
||||
});
|
||||
})(req, res, next);
|
||||
};
|
||||
65
lib/controllers/users.js
Normal file
65
lib/controllers/users.js
Normal file
@@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
passport = require('passport');
|
||||
|
||||
/**
|
||||
* Create user
|
||||
*/
|
||||
exports.create = function (req, res, next) {
|
||||
var newUser = new User(req.body);
|
||||
newUser.provider = 'local';
|
||||
newUser.save(function(err) {
|
||||
if (err) return res.json(400, err);
|
||||
|
||||
req.logIn(newUser, function(err) {
|
||||
if (err) return next(err);
|
||||
|
||||
return res.json(req.user.userInfo);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get profile of specified 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(404);
|
||||
|
||||
res.send({ profile: user.profile });
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Change 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 res.send(400);
|
||||
|
||||
res.send(200);
|
||||
});
|
||||
} else {
|
||||
res.send(403);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current user
|
||||
*/
|
||||
exports.me = function(req, res) {
|
||||
res.json(req.user || null);
|
||||
};
|
||||
25
lib/middleware.js
Normal file
25
lib/middleware.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Custom middleware used by the application
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Protect routes on your api from unauthenticated access
|
||||
*/
|
||||
auth: function auth(req, res, next) {
|
||||
if (req.isAuthenticated()) return next();
|
||||
res.send(401);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a cookie for angular so it knows we have an http session
|
||||
*/
|
||||
setUserCookie: function(req, res, next) {
|
||||
if(req.user) {
|
||||
res.cookie('user', JSON.stringify(req.user.userInfo));
|
||||
}
|
||||
next();
|
||||
}
|
||||
};
|
||||
22
lib/models/promotion.js
Normal file
22
lib/models/promotion.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema;
|
||||
|
||||
/** Promotion Schema */
|
||||
var PromotionSchema = new Schema({
|
||||
created: { type: Date, default: Date.now },
|
||||
title: { type: String, default: '', trim: true, required: true },
|
||||
user: { type: Schema.ObjectId, ref: 'User', required: true }
|
||||
});
|
||||
|
||||
/* Indexes */
|
||||
PromotionSchema.index({title: 1, user: 1}, {unique: true});
|
||||
var _model = mongoose.model('Promotion', PromotionSchema);
|
||||
|
||||
/* Operations */
|
||||
PromotionSchema.statics.findByTitle = function(title, cb){
|
||||
console.log("findByTitle: " + title);
|
||||
_model.findOne({title: title}, cb);
|
||||
};
|
||||
|
||||
22
lib/models/thing.js
Normal file
22
lib/models/thing.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema;
|
||||
|
||||
/**
|
||||
* Thing Schema
|
||||
*/
|
||||
var ThingSchema = new Schema({
|
||||
name: String,
|
||||
info: String,
|
||||
awesomeness: Number
|
||||
});
|
||||
|
||||
/**
|
||||
* Validations
|
||||
*/
|
||||
ThingSchema.path('awesomeness').validate(function (num) {
|
||||
return num >= 1 && num <= 10;
|
||||
}, 'Awesomeness must be between 1 and 10');
|
||||
|
||||
mongoose.model('Thing', ThingSchema);
|
||||
145
lib/models/user.js
Normal file
145
lib/models/user.js
Normal file
@@ -0,0 +1,145 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema,
|
||||
crypto = require('crypto');
|
||||
|
||||
var authTypes = ['github', 'twitter', 'facebook', 'google'];
|
||||
|
||||
/**
|
||||
* User Schema
|
||||
*/
|
||||
var UserSchema = new Schema({
|
||||
name: String,
|
||||
email: String,
|
||||
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;
|
||||
});
|
||||
|
||||
// Basic info to identify the current authenticated user in the app
|
||||
UserSchema.virtual('userInfo').get(function() {
|
||||
return {
|
||||
'name': this.name,
|
||||
'role': this.role,
|
||||
'provider': this.provider
|
||||
};
|
||||
});
|
||||
|
||||
// Public profile information
|
||||
UserSchema.virtual('profile').get(function() {
|
||||
return {
|
||||
'name': this.name,
|
||||
'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);
|
||||
36
lib/routes.js
Normal file
36
lib/routes.js
Normal file
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
var api = require('./controllers/api'),
|
||||
index = require('./controllers'),
|
||||
users = require('./controllers/users'),
|
||||
promotions = require('./controllers/promotions'),
|
||||
session = require('./controllers/session');
|
||||
|
||||
var middleware = require('./middleware');
|
||||
|
||||
/**
|
||||
* Application routes
|
||||
*/
|
||||
module.exports = function(app) {
|
||||
|
||||
// Server API Routes
|
||||
app.get('/api/awesomeThings', api.awesomeThings);
|
||||
app.get('/api/promotions', promotions.list);
|
||||
|
||||
app.post('/api/users', users.create);
|
||||
app.put('/api/users', users.changePassword);
|
||||
app.get('/api/users/me', users.me);
|
||||
app.get('/api/users/:id', users.show);
|
||||
|
||||
app.post('/api/session', session.login);
|
||||
app.del('/api/session', session.logout);
|
||||
|
||||
// All undefined api routes should return a 404
|
||||
app.get('/api/*', function(req, res) {
|
||||
res.send(404);
|
||||
});
|
||||
|
||||
// All other routes to use Angular routing in app/scripts/app.js
|
||||
app.get('/partials/*', index.partials);
|
||||
app.get('/*', middleware.setUserCookie, index.index);
|
||||
};
|
||||
71
package.json
Normal file
71
package.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "roboto-promoto",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"express": "~3.4.3",
|
||||
"lodash": "~2.4.1",
|
||||
"mongoose": "~3.8.8",
|
||||
"connect-mongo": "~0.4.0",
|
||||
"passport": "~0.2.0",
|
||||
"passport-local": "~0.1.6",
|
||||
"ejs": "~0.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-autoprefixer": "~0.4.0",
|
||||
"grunt-bower-install": "~0.7.0",
|
||||
"grunt-concurrent": "~0.4.1",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-coffee": "~0.7.0",
|
||||
"grunt-contrib-compass": "~0.6.0",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-contrib-cssmin": "~0.7.0",
|
||||
"grunt-contrib-htmlmin": "~0.1.3",
|
||||
"grunt-contrib-imagemin": "~0.5.0",
|
||||
"grunt-contrib-jshint": "~0.7.1",
|
||||
"grunt-contrib-uglify": "~0.2.0",
|
||||
"grunt-contrib-watch": "~0.5.2",
|
||||
"grunt-google-cdn": "~0.2.0",
|
||||
"grunt-newer": "~0.5.4",
|
||||
"grunt-ngmin": "~0.0.2",
|
||||
"grunt-rev": "~0.1.0",
|
||||
"grunt-svgmin": "~0.2.0",
|
||||
"grunt-usemin": "~2.0.0",
|
||||
"jshint-stylish": "~0.1.3",
|
||||
"load-grunt-tasks": "~0.2.0",
|
||||
"time-grunt": "~0.2.1",
|
||||
"grunt-express-server": "~0.4.5",
|
||||
"grunt-open": "~0.2.0",
|
||||
"connect-livereload": "~0.3.0",
|
||||
"karma-ng-scenario": "~0.1.0",
|
||||
"grunt-karma": "~0.6.2",
|
||||
"karma-firefox-launcher": "~0.1.3",
|
||||
"karma-script-launcher": "~0.1.0",
|
||||
"karma-html2js-preprocessor": "~0.1.0",
|
||||
"karma-jasmine": "~0.1.5",
|
||||
"karma-chrome-launcher": "~0.1.2",
|
||||
"requirejs": "~2.1.10",
|
||||
"karma-requirejs": "~0.2.1",
|
||||
"karma-coffee-preprocessor": "~0.1.2",
|
||||
"karma-phantomjs-launcher": "~0.1.1",
|
||||
"karma": "~0.10.9",
|
||||
"karma-ng-html2js-preprocessor": "~0.1.0",
|
||||
"grunt-mocha-test": "~0.8.1",
|
||||
"supertest": "~0.8.2",
|
||||
"should": "~2.1.0",
|
||||
"grunt-env": "~0.4.1",
|
||||
"grunt-node-inspector": "~0.1.3",
|
||||
"grunt-nodemon": "~0.2.0",
|
||||
"open": "~0.0.4",
|
||||
"mocha": "~1.18.2",
|
||||
"mongodb": "~1.3.23",
|
||||
"mocha-mongoose": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
}
|
||||
}
|
||||
49
server.js
Normal file
49
server.js
Normal file
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
var express = require('express'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
mongoose = require('mongoose');
|
||||
|
||||
/**
|
||||
* Main application file
|
||||
*/
|
||||
|
||||
// Set default node environment to development
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
// Application Config
|
||||
var config = require('./lib/config/config');
|
||||
|
||||
// Connect to database
|
||||
var db = mongoose.connect(config.mongo.uri, config.mongo.options);
|
||||
|
||||
// Bootstrap models
|
||||
var modelsPath = path.join(__dirname, 'lib/models');
|
||||
fs.readdirSync(modelsPath).forEach(function (file) {
|
||||
if (/(.*)\.(js$|coffee$)/.test(file)) {
|
||||
require(modelsPath + '/' + file);
|
||||
}
|
||||
});
|
||||
|
||||
// Populate empty DB with sample data
|
||||
require('./lib/config/dummydata');
|
||||
|
||||
// Passport Configuration
|
||||
var passport = require('./lib/config/passport');
|
||||
|
||||
var app = express();
|
||||
|
||||
// Express settings
|
||||
require('./lib/config/express')(app);
|
||||
|
||||
// Routing
|
||||
require('./lib/routes')(app);
|
||||
|
||||
// Start server
|
||||
app.listen(config.port, function () {
|
||||
console.log('Express server listening on port %d in %s mode', config.port, app.get('env'));
|
||||
});
|
||||
|
||||
// Expose app
|
||||
exports = module.exports = app;
|
||||
36
test/client/.jshintrc
Normal file
36
test/client/.jshintrc
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"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": {
|
||||
"after": false,
|
||||
"afterEach": false,
|
||||
"angular": false,
|
||||
"before": false,
|
||||
"beforeEach": false,
|
||||
"browser": false,
|
||||
"describe": false,
|
||||
"expect": false,
|
||||
"inject": false,
|
||||
"it": false,
|
||||
"jasmine": false,
|
||||
"spyOn": false
|
||||
}
|
||||
}
|
||||
|
||||
10
test/client/runner.html
Normal file
10
test/client/runner.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>End2end Test Runner</title>
|
||||
<script src="vendor/angular-scenario.js" ng-autotest></script>
|
||||
<script src="scenarios.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
28
test/client/spec/controllers/main.js
Normal file
28
test/client/spec/controllers/main.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
describe('Controller: MainCtrl', function () {
|
||||
|
||||
// load the controller's module
|
||||
beforeEach(module('robotoApp'));
|
||||
|
||||
var MainCtrl,
|
||||
scope,
|
||||
$httpBackend;
|
||||
|
||||
// Initialize the controller and a mock scope
|
||||
beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpBackend.expectGET('/api/awesomeThings')
|
||||
.respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']);
|
||||
scope = $rootScope.$new();
|
||||
MainCtrl = $controller('MainCtrl', {
|
||||
$scope: scope
|
||||
});
|
||||
}));
|
||||
|
||||
it('should attach a list of awesomeThings to the scope', function () {
|
||||
expect(scope.awesomeThings).toBeUndefined();
|
||||
$httpBackend.flush();
|
||||
expect(scope.awesomeThings.length).toBe(4);
|
||||
});
|
||||
});
|
||||
19
test/server/promotion/api.js
Normal file
19
test/server/promotion/api.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('should'),
|
||||
app = require('../../../server'),
|
||||
request = require('supertest');
|
||||
|
||||
describe('GET /api/promotions', function() {
|
||||
it('should respond with JSON array', function(done) {
|
||||
request(app)
|
||||
.get('/api/promotions')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.end(function(err, res) {
|
||||
if (err) return done(err);
|
||||
res.body.should.be.instanceof(Array);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
85
test/server/promotion/model.js
Normal file
85
test/server/promotion/model.js
Normal file
@@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('should'),
|
||||
mongoose = require('mongoose'),
|
||||
Promotion = mongoose.model('Promotion'),
|
||||
User = mongoose.model('User');
|
||||
|
||||
var promotion, promotion2;
|
||||
var user;
|
||||
|
||||
describe('Promotion Model', function() {
|
||||
before(function(done) {
|
||||
user = new User({
|
||||
provider: 'local',
|
||||
name: 'Fake User',
|
||||
email: 'test@test.com',
|
||||
password: 'password'
|
||||
});
|
||||
promotion = new Promotion({
|
||||
title: 'Test Prom 1',
|
||||
user: user
|
||||
});
|
||||
promotion2 = new Promotion({
|
||||
title: 'Test Prom 1',
|
||||
user: user
|
||||
});
|
||||
// Clear promotions before testing
|
||||
Promotion.remove().exec();
|
||||
User.remove().exec();
|
||||
done();
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
//going to leave the data intact post test
|
||||
//as it is removed in before()
|
||||
//Promotion.remove().exec();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should begin with no promotions', function(done) {
|
||||
Promotion.find({}, function(err, promotions) {
|
||||
promotions.should.have.length(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
describe('Save methods', function(){
|
||||
it('should be able to save', function(done){
|
||||
promotion.save(function(err){
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should fail when saving without a user', function(done) {
|
||||
promotion.user = '';
|
||||
promotion.save(function(err) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should fail when saving without a title', function(done) {
|
||||
promotion.title = '';
|
||||
promotion.save(function(err) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should fail when saving a duplicate promotion', function(done){
|
||||
promotion.save(function(err){
|
||||
promotion2.save(function(err){
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Search methods', function(){
|
||||
it('should find promotion by title', function(done){
|
||||
Promotion.findByTitle('Test Prom 1', function(result){
|
||||
should.exist(result);
|
||||
result.title.should.equal('Test Prom 1');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
60
test/server/user/model.js
Normal file
60
test/server/user/model.js
Normal file
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('should'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User');
|
||||
|
||||
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();
|
||||
done();
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
User.remove().exec();
|
||||
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();
|
||||
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;
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user