Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit e4d026a9101e43f5c3b10e34011900ca5d03866f 0 parents
Adam Campbell authored
Showing with 2,381 additions and 0 deletions.
  1. +3 −0  .bowerrc
  2. +21 −0 .editorconfig
  3. +1 −0  .gitattributes
  4. +8 −0 .gitignore
  5. +24 −0 .jshintrc
  6. +7 −0 .travis.yml
  7. +545 −0 Gruntfile.js
  8. +1 −0  app/.buildignore
  9. +543 −0 app/.htaccess
  10. BIN  app/favicon.ico
  11. BIN  app/images/yeoman.png
  12. +3 −0  app/robots.txt
  13. +20 −0 app/scripts/app.js
  14. +55 −0 app/scripts/controllers/main.js
  15. +13 −0 app/scripts/controllers/navbar.js
  16. +22 −0 app/scripts/directives/forms.js
  17. +99 −0 app/styles/main.scss
  18. +157 −0 app/views/404.html
  19. +70 −0 app/views/index.html
  20. +96 −0 app/views/partials/main.html
  21. +8 −0 app/views/partials/navbar.html
  22. +21 −0 bower.json
  23. +54 −0 karma-e2e.conf.js
  24. +56 −0 karma.conf.js
  25. +13 −0 lib/.jshintrc
  26. +10 −0 lib/config/config.js
  27. +10 −0 lib/config/env/all.js
  28. +5 −0 lib/config/env/development.js
  29. +5 −0 lib/config/env/production.js
  30. +5 −0 lib/config/env/test.js
  31. +50 −0 lib/config/express.js
  32. +213 −0 lib/controllers/api.js
  33. +27 −0 lib/controllers/index.js
  34. +23 −0 lib/routes.js
  35. +70 −0 package.json
  36. +29 −0 server.js
  37. +36 −0 test/client/.jshintrc
  38. +10 −0 test/client/runner.html
  39. +28 −0 test/client/spec/controllers/main.js
  40. +20 −0 test/server/thing/api.js
3  .bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "app/bower_components"
+}
21 .editorconfig
@@ -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
@@ -0,0 +1 @@
+* text=auto
8 .gitignore
@@ -0,0 +1,8 @@
+node_modules
+public
+.tmp
+.sass-cache
+app/bower_components
+heroku
+/views
+dist
24 .jshintrc
@@ -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
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+ - '0.8'
+ - '0.10'
+before_script:
+ - 'npm install -g bower grunt-cli'
+ - 'bower install'
545 Gruntfile.js
@@ -0,0 +1,545 @@
+// Generated on 2014-03-08 using generator-angular-fullstack 1.3.0
+'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/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/.jshintrc'
+ },
+ src: ['test/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
+ }
+ },
+
+ 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
@@ -0,0 +1 @@
+*.coffee
543 app/.htaccess
@@ -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
Binary file not shown
BIN  app/images/yeoman.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3  app/robots.txt
@@ -0,0 +1,3 @@
+# robotstxt.org
+
+User-agent: *
20 app/scripts/app.js
@@ -0,0 +1,20 @@
+'use strict';
+
+angular.module('sitesApp', [
+ 'ngCookies',
+ 'ngResource',
+ 'ngSanitize',
+ 'ngRoute'
+])
+ .config(function ($routeProvider, $locationProvider) {
+ $routeProvider
+ .when('/', {
+ templateUrl: 'partials/main',
+ controller: 'MainCtrl'
+ })
+ .otherwise({
+ redirectTo: '/'
+ });
+
+ $locationProvider.html5Mode(true);
+ });
55 app/scripts/controllers/main.js
@@ -0,0 +1,55 @@
+'use strict';
+
+angular.module('sitesApp')
+ .controller('MainCtrl', function($scope, $http) {
+
+ // we will store our form data in this object
+ $scope.submitted = false;
+ $scope.formData = {};
+
+ // Test values.
+ // $scope.formData.name = 'Adam Campbell';
+ // $scope.formData.board = 432713;
+
+ // Table sorting.
+ $scope.predicate = 'place';
+ $scope.reverse = false;
+
+
+ function requestSuccess(data) {
+ $scope.submitted = false;
+ $scope.loading = false;
+
+ $scope.scores = data.scores;
+ $scope.averages = data.averages;
+ }
+
+ function requestError(data) {
+ $scope.message = data;
+ $scope.loading = false;
+ }
+
+ $scope.setId = function(id) {
+ $scope.formData.board = id;
+ };
+
+ $scope.submit = function(isValid) {
+
+ $scope.submitted = true;
+
+ if (isValid) {
+
+ $scope.loading = true;
+
+ $http.post('/api/leaderboard', {
+ id: $scope.formData.board,
+ name: $scope.formData.name
+ })
+ .success(requestSuccess)
+ .error(requestError);
+
+ }
+
+ };
+
+ });
13 app/scripts/controllers/navbar.js
@@ -0,0 +1,13 @@
+'use strict';
+
+angular.module('sitesApp')
+ .controller('NavbarCtrl', function ($scope, $location) {
+ $scope.menu = [{
+ 'title': 'Home',
+ 'link': '/'
+ }];
+
+ $scope.isActive = function(route) {
+ return route === $location.path();
+ };
+ });
22 app/scripts/directives/forms.js
@@ -0,0 +1,22 @@
+'use strict';
+
+
+angular.module('sitesApp')
+
+// http://kurtfunai.com/2014/02/angular-directives-intro.html
+.directive('submitButton', function($compile) {
+ return {
+ link: function(scope, elm, attrs) {
+ var btnContents = $compile(elm.contents())(scope);
+ scope.$watch(attrs.ngModel, function(value) {
+ if (value) {
+ elm.html(scope.$eval(attrs.submitButton));
+ elm.attr('disabled', true);
+ } else {
+ elm.html('').append(btnContents);
+ elm.attr('disabled', false);
+ }
+ });
+ }
+ };
+});
99 app/styles/main.scss
@@ -0,0 +1,99 @@
+$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;
+ }
+}
+
+.average-area {
+ background-color: $gray-lighter;
+ padding: 1em;
+}
+
+
+
157 app/views/404.html
@@ -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>
70 app/views/index.html
@@ -0,0 +1,70 @@
+<!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="sitesApp">
+ <!--[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">
+ <div class="header">
+ <h3 class="text-muted">CFHSV Score Calculator</h3>
+ </div>
+ <main ng-view=""></main>
+ </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>
+ <script src="bower_components/underscore/underscore.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/directives/forms.js"></script>
+ <!-- endbuild -->
+</body>
+</html>
96 app/views/partials/main.html
@@ -0,0 +1,96 @@
+<div class="row">
+
+ <div id="messages" ng-show="message">{{ message }}</div>
+
+ <form class="form-horizontal" name="athleteForm" ng-submit="submit(athleteForm.$valid)" novalidate>
+
+ <div class="form-group" ng-class="{ 'has-error' : athleteForm.board.$invalid && submitted }">
+ <label class="col-sm-3 control-label" for="ath-board">Leaderboard ID</label>
+ <div class="col-sm-6">
+ <input class="form-control" size="12" type="text" class="form-control" id="ath-board" ng-model="formData.board" name="board" placeholder="432713" required />
+ <small class="text-muted">
+ <strong>Quick picks:</strong> <a ng-click="setId(399516)" href="#">All CFHSV</a>, <a ng-click="setId(432713)" href="#">CFHSV - Men</a>, <a ng-click="setId(439095)" href="#">CFHSV - Women</a>
+ </small>
+ </div>
+
+ <p ng-show="athleteForm.board.$invalid && submitted" class="help-block">A leaderboard ID is required.</p>
+
+ </div>
+
+ <div class="form-group">
+ <label class="col-sm-3 control-label" for="ath-name">Your full name</label>
+ <div class="col-sm-6">
+ <input class="form-control" size="30" type="text" class="form-control" id="ath-name" name="name" ng-model="formData.name" placeholder="Adam Campbell" />
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <!-- <button class="btn btn-primary" type="submit" submit-button="Loading...">Submit</button> -->
+ <button class="btn btn-primary" type="submit" submit-button="'Loading...'" ng-model="loading">Submit</button>
+ </div>
+ </div>
+ </form>
+
+<!-- <strong>athleteForm.$pristine =</strong> {{athleteForm.$pristine}}<br />
+ <strong>athleteForm.$valid =</strong> {{athleteForm.$valid}}<br />
+ <strong>athleteForm.$invalid =</strong> {{athleteForm.$invalid}}<br />
+ <strong>submitted =</strong> {{submitted}}<br /> -->
+
+</div>
+
+<div class="row scores-area" ng-show="scores">
+
+ <div class="row average-area">
+ <div class="col-xs-12">
+ <h3>Average Scores</h3>
+ </div>
+
+ <div class="col-md-2" ng-repeat="average in averages">
+ <h4>Round {{average.round}}</h4>
+ <h2>{{average.score}}</h2>
+ </div>
+ </div>
+
+
+ <div class="col-md-12">
+ <h3>All Scores</h3>
+
+ <table class="table table-hover">
+ <thead>
+ <tr>
+ <th><a href="#" ng-click="predicate = 'place'; reverse=!reverse">Place</a></th>
+ <th><a href="#" ng-click="predicate = 'name'; reverse=!reverse">Name</a></th>
+ <th><a href="#" ng-click="predicate = 'rounds[0].score'; reverse=!reverse">Round 1</a></th>
+ <th><a href="#" ng-click="predicate = 'rounds[1].score'; reverse=!reverse">Round 2</a></th>
+ <th><a href="#" ng-click="predicate = 'rounds[2].score'; reverse=!reverse">Round 3</a></th>
+ <th><a href="#" ng-click="predicate = 'rounds[3].score'; reverse=!reverse">Round 4</a></th>
+ <th><a href="#" ng-click="predicate = 'rounds[4].score'; reverse=!reverse">Round 5</a></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="scorecard in scores | orderBy:predicate:reverse" ng-class="{ 'info' : scorecard.target }">
+ <td>{{scorecard.place}}</td>
+ <th>{{scorecard.name}}</th>
+
+ <td ng-repeat="round in scorecard.rounds">
+ {{round.score}} <span ng-show="round.deviation" class="label" ng-class="{ 'label-danger' : round.deviation < 0, 'label-success' : round.deviation > 0 }">{{round.deviation}}%</span>
+ </td>
+
+ <!--
+ <td>{{scorecard.rounds[0].score}} <span class="label" ng-class="{ 'label-danger' : scorecard.rounds[0].deviation < 0, 'label-success' : scorecard.rounds[0].deviation > 0 }">{{scorecard.rounds[0].deviation}}</td>
+ <td>{{scorecard.rounds[1].score}}</td>
+ <td>{{scorecard.rounds[2].score}}</td>
+ <td>{{scorecard.rounds[3].score}}</td>
+ <td>{{scorecard.rounds[4].score}}</td>
+ -->
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+</div>
+
+<!-- <div class="footer">
+ <p>♥ Adam</p>
+</div> -->
8 app/views/partials/navbar.html
@@ -0,0 +1,8 @@
+<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>
+ </ul>
+ <h3 class="text-muted">sites</h3>
+</div>
21 bower.json
@@ -0,0 +1,21 @@
+{
+ "name": "sites",
+ "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",
+ "underscore": "~1.6.0"
+ },
+ "devDependencies": {
+ "angular-mocks": "1.2.11",
+ "angular-scenario": "1.2.11"
+ },
+ "testPath": "test/client/spec"
+}
54 karma-e2e.conf.js
@@ -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
@@ -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
@@ -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
@@ -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') || {});
10 lib/config/env/all.js
@@ -0,0 +1,10 @@
+'use strict';
+
+var path = require('path');
+
+var rootPath = path.normalize(__dirname + '/../../..');
+
+module.exports = {
+ root: rootPath,
+ port: process.env.PORT || 3000
+};
5 lib/config/env/development.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ env: 'development'
+};
5 lib/config/env/production.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ env: 'production'
+};
5 lib/config/env/test.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ env: 'test'
+};
50 lib/config/express.js
@@ -0,0 +1,50 @@
+'use strict';
+
+var express = require('express'),
+ path = require('path'),
+ config = require('./config');
+
+/**
+ * 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());
+ // Router (only error handlers should come after this)
+ app.use(app.router);
+ });
+
+ // Error handler
+ app.configure('development', function(){
+ app.use(express.errorHandler());
+ });
+};
213 lib/controllers/api.js
@@ -0,0 +1,213 @@
+'use strict';
+
+var _ = require('underscore');
+var async = require('async');
+var cheerio = require('cheerio');
+var querystring = require('querystring');
+var request = require('request');
+var util = require('util');
+
+if (!String.prototype.trim) {
+ String.prototype.trim = function() {
+ return this.replace(/^\s*(\S*(?:\s+\S+)*)\s*$/, "$1");
+ };
+}
+
+
+var cfUrl = 'http://games.crossfit.com/lboards/displayLeaderBoard?'; // ?id=432713&numberperpage=60&page=1';
+
+var Scorecard = function(name) {
+ this.name = name;
+ this.place = 0;
+ this.target = false;
+ this.rounds = [];
+};
+
+Scorecard.prototype.addRound = function(round, position, score) {
+
+ score = score || 0;
+
+ this.rounds.push({
+ round: round,
+ position: position,
+ score: +score
+ });
+};
+
+
+function getAverages(data) {
+
+ var avgs = [];
+
+ var chain = _.chain(data)
+ .groupBy(function(obj) {
+ return obj.round;
+ })
+ .each(function(obj, index) {
+
+ var avg = _.reduce(obj, function(memo, value) {
+ return memo + value.score;
+ });
+
+ avgs.push({
+ round: index += 1,
+ average: avg
+ });
+
+ })
+ .value();
+
+ console.log(avgs);
+}
+
+/**
+ * Get the leaderboard and process it.
+ */
+exports.leaderboard = function(req, res) {
+
+ var roundScores = [];
+ var averages = [];
+
+ var boardId = req.body.id;
+ var targetName = req.body.name || '';
+ targetName = targetName.trim();
+
+ if (!boardId) res.send(500, 'You have not submitted a leaderboard ID.');
+
+ // Build query.
+ var scores = [];
+
+ var startPage = 1;
+
+ var queryparams = {
+ id: boardId,
+ numberperpage: 60
+ };
+ var qs = '';
+
+ var hasResults = true;
+
+ async.whilst(
+
+ function test() {
+ return hasResults;
+ },
+
+ function asyncRequest(callback) {
+
+ queryparams.page = startPage;
+ qs = querystring.stringify(queryparams);
+
+ request.get(cfUrl + qs, function getScores(error, response, body) {
+
+ if(error) {
+ hasResults = false;
+ callback();
+ }
+
+ var $ = cheerio.load(body);
+
+ // All rows but header.
+ var rows = $('#lbtable tbody tr').slice(1);
+
+ if (rows.first().children().length <= 2) {
+ hasResults = false;
+ callback();
+ }
+
+ // Otherwise, has results.
+ hasResults = true;
+ startPage++;
+
+ // Parse rows.
+ rows.each(function() {
+
+ var place = $(this).children('.number').text().split('(')[0];
+ var name = $(this).children('.name').text().trim();
+
+ var $scoreCells = $(this).children('.score-cell');
+
+ // Set scorecard.
+ var scorecard = new Scorecard(name);
+
+ scorecard.place = +(place.trim());
+
+ // Add rounds.
+ $scoreCells.each(function(i) {
+
+ var scoreData = $(this).first().text().split('(');
+ var position = scoreData[0].trim();
+ var score = !! scoreData[1] ? Math.abs(scoreData[1].split(')')[0].trim()) : 0;
+
+ var roundNum = i += 1;
+
+ scorecard.addRound(roundNum, +position, score);
+
+ roundScores.push({
+ round: roundNum,
+ score: score
+ });
+
+ });
+
+ if (name.toLowerCase() === targetName.toLowerCase()) {
+ scorecard.target = true;
+ }
+
+ scores.push(scorecard);
+ });
+
+ setTimeout(callback, 200);
+
+ });
+
+ },
+
+ function asyncCallback(err) {
+
+ var totalScores = scores.length;
+
+ // Calculate round averages.
+ _.chain(roundScores)
+ .groupBy(function(obj) {
+ return obj.round;
+ })
+ .each(function(el, index) {
+
+ var sum = _.reduce(el, function(a, b) {
+ // console.log(a, b);
+ return a + b.score;
+ }, 0);
+
+ var avg = sum / totalScores;
+
+ averages.push({
+ round: index,
+ score: sum > 0 ? Math.round(avg * 10) / 10 : '--'
+ });
+
+ });
+
+ // Calculate deviation from average for each score.
+ _.each(scores, function(sc, i) {
+
+ _.each(sc.rounds, function(round, j) {
+
+ if (averages[j].score > 0) {
+ var avgScore = averages[j].score;
+ var deviation = -(avgScore - round.score) / avgScore;
+ round.deviation = Math.round((deviation * 100) * 10) / 10;
+ }
+
+ });
+
+ });
+
+ res.send(200, {
+ scores: scores,
+ averages: averages
+ });
+ }
+ );
+
+};
27 lib/controllers/index.js
@@ -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');
+};
23 lib/routes.js
@@ -0,0 +1,23 @@
+'use strict';
+
+var api = require('./controllers/api'),
+ index = require('./controllers');
+
+/**
+ * Application routes
+ */
+module.exports = function(app) {
+
+ // Server API Routes
+ app.post('/api/leaderboard', api.leaderboard);
+
+
+ // 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('/*', index.index);
+};
70 package.json
@@ -0,0 +1,70 @@
+{
+ "name": "sites",
+ "version": "0.0.0",
+ "dependencies": {
+ "express": "~3.4.3",
+ "lodash": "~2.4.1",
+ "ejs": "~0.8.4",
+ "underscore": "~1.6.0",
+ "request": "~2.34.0",
+ "cheerio": "~0.13.1",
+ "querystring": "~0.2.0",
+ "async": "~0.2.10"
+ },
+ "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",
+ "jpegtran-bin": "0.2.0",
+ "grunt-contrib-imagemin": "~0.3.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"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "scripts": {
+ "test": "grunt test"
+ }
+}
29 server.js
@@ -0,0 +1,29 @@
+'use strict';
+
+var express = require('express');
+
+/**
+ * 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');
+
+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
@@ -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
@@ -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
@@ -0,0 +1,28 @@
+'use strict';
+
+describe('Controller: MainCtrl', function () {
+
+ // load the controller's module
+ beforeEach(module('sitesApp'));
+
+ 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);
+ });
+});
20 test/server/thing/api.js
@@ -0,0 +1,20 @@
+'use strict';
+
+var should = require('should'),
+ app = require('../../../server'),
+ request = require('supertest');
+
+describe('GET /api/awesomeThings', function() {
+
+ it('should respond with JSON array', function(done) {
+ request(app)
+ .get('/api/awesomeThings')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ res.body.should.be.instanceof(Array);
+ done();
+ });
+ });
+});
Please sign in to comment.
Something went wrong with that request. Please try again.