From 564e7a5123befe14a4635d7792940c8f689eec81 Mon Sep 17 00:00:00 2001 From: James Lyon Date: Tue, 3 Feb 2015 11:50:20 -0700 Subject: [PATCH] Initial project generation --- .bowerrc | 3 + .buildignore | 1 + .editorconfig | 21 + .gitattributes | 1 + .gitignore | 7 + .travis.yml | 8 + .yo-rc.json | 47 ++ Gruntfile.js | 667 ++++++++++++++++++ bower.json | 23 + client/.htaccess | 543 ++++++++++++++ client/.jshintrc | 38 + client/app/app.js | 16 + client/app/app.styl | 44 ++ client/app/main/main.controller.js | 27 + client/app/main/main.controller.spec.js | 29 + client/app/main/main.html | 38 + client/app/main/main.js | 11 + client/app/main/main.styl | 25 + client/assets/images/yeoman.png | Bin 0 -> 12331 bytes client/components/modal/modal.html | 11 + client/components/modal/modal.service.js | 77 ++ client/components/modal/modal.styl | 23 + client/components/navbar/navbar.controller.js | 15 + client/components/navbar/navbar.html | 20 + client/components/socket/socket.mock.js | 16 + client/components/socket/socket.service.js | 74 ++ client/favicon.ico | Bin 0 -> 6774 bytes client/index.html | 73 ++ client/robots.txt | 3 + e2e/main/main.po.js | 15 + e2e/main/main.spec.js | 16 + karma.conf.js | 81 +++ package.json | 88 +++ protractor.conf.js | 50 ++ server/.jshintrc | 15 + server/.jshintrc-spec | 11 + server/api/thing/index.js | 15 + server/api/thing/thing.controller.js | 68 ++ server/api/thing/thing.model.js | 12 + server/api/thing/thing.socket.js | 24 + server/api/thing/thing.spec.js | 20 + server/app.js | 37 + server/components/errors/index.js | 20 + server/config/environment/development.js | 12 + server/config/environment/index.js | 50 ++ server/config/environment/production.js | 23 + server/config/environment/test.js | 10 + server/config/express.js | 45 ++ server/config/local.env.sample.js | 14 + server/config/seed.js | 31 + server/config/socketio.js | 57 ++ server/routes.js | 23 + server/views/404.html | 157 +++++ 53 files changed, 2755 insertions(+) create mode 100644 .bowerrc create mode 100644 .buildignore create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 .yo-rc.json create mode 100644 Gruntfile.js create mode 100644 bower.json create mode 100644 client/.htaccess create mode 100644 client/.jshintrc create mode 100644 client/app/app.js create mode 100644 client/app/app.styl create mode 100644 client/app/main/main.controller.js create mode 100644 client/app/main/main.controller.spec.js create mode 100644 client/app/main/main.html create mode 100644 client/app/main/main.js create mode 100644 client/app/main/main.styl create mode 100644 client/assets/images/yeoman.png create mode 100644 client/components/modal/modal.html create mode 100644 client/components/modal/modal.service.js create mode 100644 client/components/modal/modal.styl create mode 100644 client/components/navbar/navbar.controller.js create mode 100644 client/components/navbar/navbar.html create mode 100644 client/components/socket/socket.mock.js create mode 100644 client/components/socket/socket.service.js create mode 100644 client/favicon.ico create mode 100644 client/index.html create mode 100644 client/robots.txt create mode 100644 e2e/main/main.po.js create mode 100644 e2e/main/main.spec.js create mode 100644 karma.conf.js create mode 100644 package.json create mode 100644 protractor.conf.js create mode 100644 server/.jshintrc create mode 100644 server/.jshintrc-spec create mode 100644 server/api/thing/index.js create mode 100644 server/api/thing/thing.controller.js create mode 100644 server/api/thing/thing.model.js create mode 100644 server/api/thing/thing.socket.js create mode 100644 server/api/thing/thing.spec.js create mode 100644 server/app.js create mode 100644 server/components/errors/index.js create mode 100644 server/config/environment/development.js create mode 100644 server/config/environment/index.js create mode 100644 server/config/environment/production.js create mode 100644 server/config/environment/test.js create mode 100644 server/config/express.js create mode 100644 server/config/local.env.sample.js create mode 100644 server/config/seed.js create mode 100644 server/config/socketio.js create mode 100644 server/routes.js create mode 100644 server/views/404.html diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..666f347 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "client/bower_components" +} diff --git a/.buildignore b/.buildignore new file mode 100644 index 0000000..fc98b8e --- /dev/null +++ b/.buildignore @@ -0,0 +1 @@ +*.coffee \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c2cdfb8 --- /dev/null +++ b/.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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c029da6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +public +.tmp +.idea +client/bower_components +dist +/server/config/local.env.js \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7989278 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: node_js +node_js: + - '0.10' + - '0.11' +before_script: + - npm install -g bower grunt-cli + - bower install +services: mongodb \ No newline at end of file diff --git a/.yo-rc.json b/.yo-rc.json new file mode 100644 index 0000000..f4c155d --- /dev/null +++ b/.yo-rc.json @@ -0,0 +1,47 @@ +{ + "generator-angular-fullstack": { + "insertRoutes": true, + "registerRoutesFile": "server/routes.js", + "routesNeedle": "// Insert routes below", + "routesBase": "/api/", + "pluralizeRoutes": true, + "insertSockets": true, + "registerSocketsFile": "server/config/socketio.js", + "socketsNeedle": "// Insert sockets below", + "filters": { + "js": true, + "html": true, + "stylus": true, + "uirouter": true, + "bootstrap": true, + "uibootstrap": true, + "socketio": true, + "mongoose": true + } + }, + "generator-ng-component": { + "routeDirectory": "client/app/", + "directiveDirectory": "client/app/", + "filterDirectory": "client/app/", + "serviceDirectory": "client/app/", + "basePath": "client", + "moduleName": "", + "filters": [ + "uirouter" + ], + "extensions": [ + "js", + "html", + "styl" + ], + "directiveSimpleTemplates": "", + "directiveComplexTemplates": "", + "filterTemplates": "", + "serviceTemplates": "", + "factoryTemplates": "", + "controllerTemplates": "", + "decoratorTemplates": "", + "providerTemplates": "", + "routeTemplates": "" + } +} \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..059bb3a --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,667 @@ +// Generated on 2015-02-03 using generator-angular-fullstack 2.0.13 +'use strict'; + +module.exports = function (grunt) { + var localConfig; + try { + localConfig = require('./server/config/local.env'); + } catch(e) { + localConfig = {}; + } + + // Load grunt tasks automatically, when needed + require('jit-grunt')(grunt, { + express: 'grunt-express-server', + useminPrepare: 'grunt-usemin', + ngtemplates: 'grunt-angular-templates', + cdnify: 'grunt-google-cdn', + protractor: 'grunt-protractor-runner', + injector: 'grunt-asset-injector', + buildcontrol: 'grunt-build-control' + }); + + // Time how long tasks take. Can help when optimizing build times + require('time-grunt')(grunt); + + // Define the configuration for all the tasks + grunt.initConfig({ + + // Project settings + pkg: grunt.file.readJSON('package.json'), + yeoman: { + // configurable paths + client: require('./bower.json').appPath || 'client', + dist: 'dist' + }, + express: { + options: { + port: process.env.PORT || 9000 + }, + dev: { + options: { + script: 'server/app.js', + debug: true + } + }, + prod: { + options: { + script: 'dist/server/app.js' + } + } + }, + open: { + server: { + url: 'http://localhost:<%= express.options.port %>' + } + }, + watch: { + injectJS: { + files: [ + '<%= yeoman.client %>/{app,components}/**/*.js', + '!<%= yeoman.client %>/{app,components}/**/*.spec.js', + '!<%= yeoman.client %>/{app,components}/**/*.mock.js', + '!<%= yeoman.client %>/app/app.js'], + tasks: ['injector:scripts'] + }, + injectCss: { + files: [ + '<%= yeoman.client %>/{app,components}/**/*.css' + ], + tasks: ['injector:css'] + }, + mochaTest: { + files: ['server/**/*.spec.js'], + tasks: ['env:test', 'mochaTest'] + }, + jsTest: { + files: [ + '<%= yeoman.client %>/{app,components}/**/*.spec.js', + '<%= yeoman.client %>/{app,components}/**/*.mock.js' + ], + tasks: ['newer:jshint:all', 'karma'] + }, + injectStylus: { + files: [ + '<%= yeoman.client %>/{app,components}/**/*.styl'], + tasks: ['injector:stylus'] + }, + stylus: { + files: [ + '<%= yeoman.client %>/{app,components}/**/*.styl'], + tasks: ['stylus', 'autoprefixer'] + }, + gruntfile: { + files: ['Gruntfile.js'] + }, + livereload: { + files: [ + '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.css', + '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.html', + '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js', + '!{.tmp,<%= yeoman.client %>}{app,components}/**/*.spec.js', + '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js', + '<%= yeoman.client %>/assets/images/{,*//*}*.{png,jpg,jpeg,gif,webp,svg}' + ], + options: { + livereload: true + } + }, + express: { + files: [ + 'server/**/*.{js,json}' + ], + tasks: ['express:dev', 'wait'], + options: { + livereload: true, + nospawn: true //Without this option specified express won't be reloaded + } + } + }, + + // Make sure code styles are up to par and there are no obvious mistakes + jshint: { + options: { + jshintrc: '<%= yeoman.client %>/.jshintrc', + reporter: require('jshint-stylish') + }, + server: { + options: { + jshintrc: 'server/.jshintrc' + }, + src: [ + 'server/**/*.js', + '!server/**/*.spec.js' + ] + }, + serverTest: { + options: { + jshintrc: 'server/.jshintrc-spec' + }, + src: ['server/**/*.spec.js'] + }, + all: [ + '<%= yeoman.client %>/{app,components}/**/*.js', + '!<%= yeoman.client %>/{app,components}/**/*.spec.js', + '!<%= yeoman.client %>/{app,components}/**/*.mock.js' + ], + test: { + src: [ + '<%= yeoman.client %>/{app,components}/**/*.spec.js', + '<%= yeoman.client %>/{app,components}/**/*.mock.js' + ] + } + }, + + // Empties folders to start fresh + clean: { + dist: { + files: [{ + dot: true, + src: [ + '.tmp', + '<%= yeoman.dist %>/*', + '!<%= yeoman.dist %>/.git*', + '!<%= yeoman.dist %>/.openshift', + '!<%= yeoman.dist %>/Procfile' + ] + }] + }, + server: '.tmp' + }, + + // Add vendor prefixed styles + autoprefixer: { + options: { + browsers: ['last 1 version'] + }, + dist: { + files: [{ + expand: true, + cwd: '.tmp/', + src: '{,*/}*.css', + dest: '.tmp/' + }] + } + }, + + // Debugging with node inspector + 'node-inspector': { + custom: { + options: { + 'web-host': 'localhost' + } + } + }, + + // Use nodemon to run server in debug mode with an initial breakpoint + nodemon: { + debug: { + script: 'server/app.js', + options: { + nodeArgs: ['--debug-brk'], + env: { + PORT: process.env.PORT || 9000 + }, + callback: function (nodemon) { + nodemon.on('log', function (event) { + console.log(event.colour); + }); + + // opens browser on initial server start + nodemon.on('config:update', function () { + setTimeout(function () { + require('open')('http://localhost:8080/debug?port=5858'); + }, 500); + }); + } + } + } + }, + + // Automatically inject Bower components into the app + wiredep: { + target: { + src: '<%= yeoman.client %>/index.html', + ignorePath: '<%= yeoman.client %>/', + exclude: [/bootstrap-sass-official/, /bootstrap.js/, '/json3/', '/es5-shim/', /bootstrap.css/, /font-awesome.css/ ] + } + }, + + // Renames files for browser caching purposes + rev: { + dist: { + files: { + src: [ + '<%= yeoman.dist %>/public/{,*/}*.js', + '<%= yeoman.dist %>/public/{,*/}*.css', + '<%= yeoman.dist %>/public/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', + '<%= yeoman.dist %>/public/assets/fonts/*' + ] + } + } + }, + + // Reads HTML for usemin blocks to enable smart builds that automatically + // concat, minify and revision files. Creates configurations in memory so + // additional tasks can operate on them + useminPrepare: { + html: ['<%= yeoman.client %>/index.html'], + options: { + dest: '<%= yeoman.dist %>/public' + } + }, + + // Performs rewrites based on rev and the useminPrepare configuration + usemin: { + html: ['<%= yeoman.dist %>/public/{,*/}*.html'], + css: ['<%= yeoman.dist %>/public/{,*/}*.css'], + js: ['<%= yeoman.dist %>/public/{,*/}*.js'], + options: { + assetsDirs: [ + '<%= yeoman.dist %>/public', + '<%= yeoman.dist %>/public/assets/images' + ], + // This is so we update image references in our ng-templates + patterns: { + js: [ + [/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images'] + ] + } + } + }, + + // The following *-min tasks produce minified files in the dist folder + imagemin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.client %>/assets/images', + src: '{,*/}*.{png,jpg,jpeg,gif}', + dest: '<%= yeoman.dist %>/public/assets/images' + }] + } + }, + + svgmin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.client %>/assets/images', + src: '{,*/}*.svg', + dest: '<%= yeoman.dist %>/public/assets/images' + }] + } + }, + + // Allow the use of non-minsafe AngularJS files. Automatically makes it + // minsafe compatible so Uglify does not destroy the ng references + ngAnnotate: { + dist: { + files: [{ + expand: true, + cwd: '.tmp/concat', + src: '*/**.js', + dest: '.tmp/concat' + }] + } + }, + + // Package all the html partials into a single javascript payload + ngtemplates: { + options: { + // This should be the name of your apps angular module + module: 'journalinkApp', + htmlmin: { + collapseBooleanAttributes: true, + collapseWhitespace: true, + removeAttributeQuotes: true, + removeEmptyAttributes: true, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true + }, + usemin: 'app/app.js' + }, + main: { + cwd: '<%= yeoman.client %>', + src: ['{app,components}/**/*.html'], + dest: '.tmp/templates.js' + }, + tmp: { + cwd: '.tmp', + src: ['{app,components}/**/*.html'], + dest: '.tmp/tmp-templates.js' + } + }, + + // Replace Google CDN references + cdnify: { + dist: { + html: ['<%= yeoman.dist %>/public/*.html'] + } + }, + + // Copies remaining files to places other tasks can use + copy: { + dist: { + files: [{ + expand: true, + dot: true, + cwd: '<%= yeoman.client %>', + dest: '<%= yeoman.dist %>/public', + src: [ + '*.{ico,png,txt}', + '.htaccess', + 'bower_components/**/*', + 'assets/images/{,*/}*.{webp}', + 'assets/fonts/**/*', + 'index.html' + ] + }, { + expand: true, + cwd: '.tmp/images', + dest: '<%= yeoman.dist %>/public/assets/images', + src: ['generated/*'] + }, { + expand: true, + dest: '<%= yeoman.dist %>', + src: [ + 'package.json', + 'server/**/*' + ] + }] + }, + styles: { + expand: true, + cwd: '<%= yeoman.client %>', + dest: '.tmp/', + src: ['{app,components}/**/*.css'] + } + }, + + buildcontrol: { + options: { + dir: 'dist', + commit: true, + push: true, + connectCommits: false, + message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%' + }, + heroku: { + options: { + remote: 'heroku', + branch: 'master' + } + }, + openshift: { + options: { + remote: 'openshift', + branch: 'master' + } + } + }, + + // Run some tasks in parallel to speed up the build process + concurrent: { + server: [ + 'stylus', + ], + test: [ + 'stylus', + ], + debug: { + tasks: [ + 'nodemon', + 'node-inspector' + ], + options: { + logConcurrentOutput: true + } + }, + dist: [ + 'stylus', + 'imagemin', + 'svgmin' + ] + }, + + // Test settings + karma: { + unit: { + configFile: 'karma.conf.js', + singleRun: true + } + }, + + mochaTest: { + options: { + reporter: 'spec' + }, + src: ['server/**/*.spec.js'] + }, + + protractor: { + options: { + configFile: 'protractor.conf.js' + }, + chrome: { + options: { + args: { + browser: 'chrome' + } + } + } + }, + + env: { + test: { + NODE_ENV: 'test' + }, + prod: { + NODE_ENV: 'production' + }, + all: localConfig + }, + + // Compiles Stylus to CSS + stylus: { + server: { + options: { + paths: [ + '<%= yeoman.client %>/bower_components', + '<%= yeoman.client %>/app', + '<%= yeoman.client %>/components' + ], + "include css": true + }, + files: { + '.tmp/app/app.css' : '<%= yeoman.client %>/app/app.styl' + } + } + }, + + injector: { + options: { + + }, + // Inject application script files into index.html (doesn't include bower) + scripts: { + options: { + transform: function(filePath) { + filePath = filePath.replace('/client/', ''); + filePath = filePath.replace('/.tmp/', ''); + return ''; + }, + starttag: '', + endtag: '' + }, + files: { + '<%= yeoman.client %>/index.html': [ + ['{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js', + '!{.tmp,<%= yeoman.client %>}/app/app.js', + '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.spec.js', + '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js'] + ] + } + }, + + // Inject component styl into app.styl + stylus: { + options: { + transform: function(filePath) { + filePath = filePath.replace('/client/app/', ''); + filePath = filePath.replace('/client/components/', ''); + return '@import \'' + filePath + '\';'; + }, + starttag: '// injector', + endtag: '// endinjector' + }, + files: { + '<%= yeoman.client %>/app/app.styl': [ + '<%= yeoman.client %>/{app,components}/**/*.styl', + '!<%= yeoman.client %>/app/app.styl' + ] + } + }, + + // Inject component css into index.html + css: { + options: { + transform: function(filePath) { + filePath = filePath.replace('/client/', ''); + filePath = filePath.replace('/.tmp/', ''); + return ''; + }, + starttag: '', + endtag: '' + }, + files: { + '<%= yeoman.client %>/index.html': [ + '<%= yeoman.client %>/{app,components}/**/*.css' + ] + } + } + }, + }); + + // Used for delaying livereload until after server has restarted + grunt.registerTask('wait', function () { + grunt.log.ok('Waiting for server reload...'); + + var done = this.async(); + + setTimeout(function () { + grunt.log.writeln('Done waiting!'); + done(); + }, 1500); + }); + + grunt.registerTask('express-keepalive', 'Keep grunt running', function() { + this.async(); + }); + + grunt.registerTask('serve', function (target) { + if (target === 'dist') { + return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'open', 'express-keepalive']); + } + + if (target === 'debug') { + return grunt.task.run([ + 'clean:server', + 'env:all', + 'injector:stylus', + 'concurrent:server', + 'injector', + 'wiredep', + 'autoprefixer', + 'concurrent:debug' + ]); + } + + grunt.task.run([ + 'clean:server', + 'env:all', + 'injector:stylus', + 'concurrent:server', + 'injector', + 'wiredep', + 'autoprefixer', + 'express:dev', + 'wait', + 'open', + 'watch' + ]); + }); + + grunt.registerTask('server', function () { + grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); + grunt.task.run(['serve']); + }); + + grunt.registerTask('test', function(target) { + if (target === 'server') { + return grunt.task.run([ + 'env:all', + 'env:test', + 'mochaTest' + ]); + } + + else if (target === 'client') { + return grunt.task.run([ + 'clean:server', + 'env:all', + 'injector:stylus', + 'concurrent:test', + 'injector', + 'autoprefixer', + 'karma' + ]); + } + + else if (target === 'e2e') { + return grunt.task.run([ + 'clean:server', + 'env:all', + 'env:test', + 'injector:stylus', + 'concurrent:test', + 'injector', + 'wiredep', + 'autoprefixer', + 'express:dev', + 'protractor' + ]); + } + + else grunt.task.run([ + 'test:server', + 'test:client' + ]); + }); + + grunt.registerTask('build', [ + 'clean:dist', + 'injector:stylus', + 'concurrent:dist', + 'injector', + 'wiredep', + 'useminPrepare', + 'autoprefixer', + 'ngtemplates', + 'concat', + 'ngAnnotate', + 'copy:dist', + 'cdnify', + 'cssmin', + 'uglify', + 'rev', + 'usemin' + ]); + + grunt.registerTask('default', [ + 'newer:jshint', + 'test', + 'build' + ]); +}; diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..11c0a22 --- /dev/null +++ b/bower.json @@ -0,0 +1,23 @@ +{ + "name": "journalink", + "version": "0.0.0", + "dependencies": { + "angular": ">=1.2.*", + "json3": "~3.3.1", + "es5-shim": "~3.0.1", + "jquery": "~1.11.0", + "bootstrap": "~3.1.1", + "angular-resource": ">=1.2.*", + "angular-cookies": ">=1.2.*", + "angular-sanitize": ">=1.2.*", + "angular-bootstrap": "~0.11.0", + "font-awesome": "~4.3.0", + "lodash": "~2.4.1", + "angular-socket-io": "~0.6.0", + "angular-ui-router": "~0.2.10" + }, + "devDependencies": { + "angular-mocks": ">=1.2.*", + "angular-scenario": ">=1.2.*" + } +} diff --git a/client/.htaccess b/client/.htaccess new file mode 100644 index 0000000..cb84cb9 --- /dev/null +++ b/client/.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/ + +# +# Header set Access-Control-Allow-Origin "*" +# + +# ------------------------------------------------------------------------------ +# | 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/ + + + + + SetEnvIf Origin ":" IS_CORS + Header set Access-Control-Allow-Origin "*" env=IS_CORS + + + + +# ------------------------------------------------------------------------------ +# | Web fonts access | +# ------------------------------------------------------------------------------ + +# Allow access from all domains for web fonts + + + + Header set Access-Control-Allow-Origin "*" + + + + +# ############################################################################## +# # 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. + + + 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 + + Header unset X-UA-Compatible + + + +# ------------------------------------------------------------------------------ +# | Cookie setting from iframes | +# ------------------------------------------------------------------------------ + +# Allow cookies to be set from iframes in IE. + +# +# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" +# + +# ------------------------------------------------------------------------------ +# | 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 | +# ------------------------------------------------------------------------------ + + + + # 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 + + + +# ------------------------------------------------------------------------------ +# | 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. + + AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml + + + +# ############################################################################## +# # 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 + + + Options +FollowSymlinks + # Options +SymLinksIfOwnerMatch + RewriteEngine On + # RewriteBase / + + +# ------------------------------------------------------------------------------ +# | 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 + + + RewriteCond %{HTTPS} !=on + RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] + RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# 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. + +# +# RewriteCond %{HTTPS} !=on +# RewriteCond %{HTTP_HOST} !^www\..+$ [NC] +# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] +# + + +# ############################################################################## +# # 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). + +# +# Header set Content-Security-Policy "script-src 'self'; object-src 'self'" +# +# Header unset Content-Security-Policy +# +# + +# ------------------------------------------------------------------------------ +# | 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). + + + Options -Indexes + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# Block access to hidden files and directories. +# This includes directories used by version control systems such as Git and SVN. + + + RewriteCond %{SCRIPT_FILENAME} -d [OR] + RewriteCond %{SCRIPT_FILENAME} -f + RewriteRule "(^|/)\." - [F] + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# 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. + + + Order allow,deny + Deny from all + Satisfy All + + +# ------------------------------------------------------------------------------ +# | 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`. + +# +# RewriteCond %{SERVER_PORT} !^443 +# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] +# + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# 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/ + +# +# Header set Strict-Transport-Security max-age=16070400; +# + +# ------------------------------------------------------------------------------ +# | 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 | +# ------------------------------------------------------------------------------ + + + + # Force compression for mangled headers. + # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping + + + 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 + + + + # 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 `` and `` lines + # as `AddOutputFilterByType` is still in the core directives). + + 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 + + + + +# ------------------------------------------------------------------------------ +# | 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. + +# +# Header set Cache-Control "no-transform" +# + +# ------------------------------------------------------------------------------ +# | 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. + + Header unset ETag + + +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. + + + + 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" + + + +# ------------------------------------------------------------------------------ +# | 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 + +# +# RewriteCond %{REQUEST_FILENAME} !-f +# RewriteCond %{REQUEST_FILENAME} !-d +# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] +# + +# ------------------------------------------------------------------------------ +# | File concatenation | +# ------------------------------------------------------------------------------ + +# Allow concatenation from within specific CSS and JS files, e.g.: +# Inside of `script.combined.js` you could have +# +# +# and they would be included into this single file. + +# +# +# Options +Includes +# AddOutputFilterByType INCLUDES application/javascript application/json +# SetOutputFilter INCLUDES +# +# +# Options +Includes +# AddOutputFilterByType INCLUDES text/css +# SetOutputFilter INCLUDES +# +# + +# ------------------------------------------------------------------------------ +# | 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! + +# +# Header set Connection Keep-Alive +# diff --git a/client/.jshintrc b/client/.jshintrc new file mode 100644 index 0000000..52c6a6d --- /dev/null +++ b/client/.jshintrc @@ -0,0 +1,38 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 2, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "globals": { + "jQuery": true, + "angular": true, + "console": true, + "$": true, + "_": true, + "moment": true, + "describe": true, + "beforeEach": true, + "module": true, + "inject": true, + "it": true, + "expect": true, + "browser": true, + "element": true, + "by": true + } +} diff --git a/client/app/app.js b/client/app/app.js new file mode 100644 index 0000000..8e1fe76 --- /dev/null +++ b/client/app/app.js @@ -0,0 +1,16 @@ +'use strict'; + +angular.module('journalinkApp', [ + 'ngCookies', + 'ngResource', + 'ngSanitize', + 'btford.socket-io', + 'ui.router', + 'ui.bootstrap' +]) + .config(function ($stateProvider, $urlRouterProvider, $locationProvider) { + $urlRouterProvider + .otherwise('/'); + + $locationProvider.html5Mode(true); + }); \ No newline at end of file diff --git a/client/app/app.styl b/client/app/app.styl new file mode 100644 index 0000000..45ff63f --- /dev/null +++ b/client/app/app.styl @@ -0,0 +1,44 @@ +@import "font-awesome/css/font-awesome.css" +@import "bootstrap/dist/css/bootstrap.css" + +// +// Bootstrap Fonts +// + +@font-face + font-family: 'Glyphicons Halflings' + src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot') + src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), + url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), + url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), + url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); + +// +// Font Awesome Fonts +// + +@font-face + font-family: 'FontAwesome' + src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0') + src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), + url('../bower_components/font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), + url('../bower_components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), + url('../bower_components/font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg'); + font-weight: normal + font-style: normal + +// +// App-wide Styles +// + +.browsehappy + background #ccc + color #000 + margin 0.2em 0 + padding 0.2em 0 + +// Component styles are injected through grunt +// injector +@import 'main/main.styl'; +@import 'modal/modal.styl'; +// endinjector \ No newline at end of file diff --git a/client/app/main/main.controller.js b/client/app/main/main.controller.js new file mode 100644 index 0000000..4c835d3 --- /dev/null +++ b/client/app/main/main.controller.js @@ -0,0 +1,27 @@ +'use strict'; + +angular.module('journalinkApp') + .controller('MainCtrl', function ($scope, $http, socket) { + $scope.awesomeThings = []; + + $http.get('/api/things').success(function(awesomeThings) { + $scope.awesomeThings = awesomeThings; + socket.syncUpdates('thing', $scope.awesomeThings); + }); + + $scope.addThing = function() { + if($scope.newThing === '') { + return; + } + $http.post('/api/things', { name: $scope.newThing }); + $scope.newThing = ''; + }; + + $scope.deleteThing = function(thing) { + $http.delete('/api/things/' + thing._id); + }; + + $scope.$on('$destroy', function () { + socket.unsyncUpdates('thing'); + }); + }); diff --git a/client/app/main/main.controller.spec.js b/client/app/main/main.controller.spec.js new file mode 100644 index 0000000..08beb47 --- /dev/null +++ b/client/app/main/main.controller.spec.js @@ -0,0 +1,29 @@ +'use strict'; + +describe('Controller: MainCtrl', function () { + + // load the controller's module + beforeEach(module('journalinkApp')); + beforeEach(module('socketMock')); + + var MainCtrl, + scope, + $httpBackend; + + // Initialize the controller and a mock scope + beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) { + $httpBackend = _$httpBackend_; + $httpBackend.expectGET('/api/things') + .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']); + + scope = $rootScope.$new(); + MainCtrl = $controller('MainCtrl', { + $scope: scope + }); + })); + + it('should attach a list of things to the scope', function () { + $httpBackend.flush(); + expect(scope.awesomeThings.length).toBe(4); + }); +}); diff --git a/client/app/main/main.html b/client/app/main/main.html new file mode 100644 index 0000000..8ef33e8 --- /dev/null +++ b/client/app/main/main.html @@ -0,0 +1,38 @@ +
+ + + +
+
+
+

Features:

+ +
+
+ +
+ +

+ + + + +

+
+
+ + diff --git a/client/app/main/main.js b/client/app/main/main.js new file mode 100644 index 0000000..403eecb --- /dev/null +++ b/client/app/main/main.js @@ -0,0 +1,11 @@ +'use strict'; + +angular.module('journalinkApp') + .config(function ($stateProvider) { + $stateProvider + .state('main', { + url: '/', + templateUrl: 'app/main/main.html', + controller: 'MainCtrl' + }); + }); \ No newline at end of file diff --git a/client/app/main/main.styl b/client/app/main/main.styl new file mode 100644 index 0000000..5cd9e47 --- /dev/null +++ b/client/app/main/main.styl @@ -0,0 +1,25 @@ +.thing-form + margin 20px 0 + +#banner + border-bottom none + margin-top -20px + +#banner h1 + font-size 60px + letter-spacing -1px + line-height 1 + +.hero-unit + background #4393B9 + color #F5F5F5 + padding 30px 15px + position relative + text-align center + text-shadow 0 1px 0 rgba(0, 0, 0, 0.1) + +.footer + border-top 1px solid #E5E5E5 + margin-top 70px + padding 30px 0 + text-align center \ No newline at end of file diff --git a/client/assets/images/yeoman.png b/client/assets/images/yeoman.png new file mode 100644 index 0000000000000000000000000000000000000000..7d0a1ac7120ab1e1b06598a02f2670f52aa00829 GIT binary patch literal 12331 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 RTQC3s002ovPDHLkV1gt^4@v+4 literal 0 HcmV?d00001 diff --git a/client/components/modal/modal.html b/client/components/modal/modal.html new file mode 100644 index 0000000..4580254 --- /dev/null +++ b/client/components/modal/modal.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/client/components/modal/modal.service.js b/client/components/modal/modal.service.js new file mode 100644 index 0000000..c53bbd9 --- /dev/null +++ b/client/components/modal/modal.service.js @@ -0,0 +1,77 @@ +'use strict'; + +angular.module('journalinkApp') + .factory('Modal', function ($rootScope, $modal) { + /** + * Opens a modal + * @param {Object} scope - an object to be merged with modal's scope + * @param {String} modalClass - (optional) class(es) to be applied to the modal + * @return {Object} - the instance $modal.open() returns + */ + function openModal(scope, modalClass) { + var modalScope = $rootScope.$new(); + scope = scope || {}; + modalClass = modalClass || 'modal-default'; + + angular.extend(modalScope, scope); + + return $modal.open({ + templateUrl: 'components/modal/modal.html', + windowClass: modalClass, + scope: modalScope + }); + } + + // Public API here + return { + + /* Confirmation modals */ + confirm: { + + /** + * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)') + * @param {Function} del - callback, ran when delete is confirmed + * @return {Function} - the function to open the modal (ex. myModalFn) + */ + delete: function(del) { + del = del || angular.noop; + + /** + * Open a delete confirmation modal + * @param {String} name - name or info to show on modal + * @param {All} - any additional args are passed staight to del callback + */ + return function() { + var args = Array.prototype.slice.call(arguments), + name = args.shift(), + deleteModal; + + deleteModal = openModal({ + modal: { + dismissable: true, + title: 'Confirm Delete', + html: '

Are you sure you want to delete ' + name + ' ?

', + buttons: [{ + classes: 'btn-danger', + text: 'Delete', + click: function(e) { + deleteModal.close(e); + } + }, { + classes: 'btn-default', + text: 'Cancel', + click: function(e) { + deleteModal.dismiss(e); + } + }] + } + }, 'modal-danger'); + + deleteModal.result.then(function(event) { + del.apply(event, args); + }); + }; + } + } + }; + }); diff --git a/client/components/modal/modal.styl b/client/components/modal/modal.styl new file mode 100644 index 0000000..d394ee0 --- /dev/null +++ b/client/components/modal/modal.styl @@ -0,0 +1,23 @@ +.modal-primary +.modal-info +.modal-success +.modal-warning +.modal-danger + .modal-header + color #fff + border-radius 5px 5px 0 0 + +.modal-primary .modal-header + background #428bca + +.modal-info .modal-header + background #5bc0de + +.modal-success .modal-header + background #5cb85c + +.modal-warning .modal-header + background #f0ad4e + +.modal-danger .modal-header + background #d9534f diff --git a/client/components/navbar/navbar.controller.js b/client/components/navbar/navbar.controller.js new file mode 100644 index 0000000..2835cc7 --- /dev/null +++ b/client/components/navbar/navbar.controller.js @@ -0,0 +1,15 @@ +'use strict'; + +angular.module('journalinkApp') + .controller('NavbarCtrl', function ($scope, $location) { + $scope.menu = [{ + 'title': 'Home', + 'link': '/' + }]; + + $scope.isCollapsed = true; + + $scope.isActive = function(route) { + return route === $location.path(); + }; + }); \ No newline at end of file diff --git a/client/components/navbar/navbar.html b/client/components/navbar/navbar.html new file mode 100644 index 0000000..9e65ff0 --- /dev/null +++ b/client/components/navbar/navbar.html @@ -0,0 +1,20 @@ + diff --git a/client/components/socket/socket.mock.js b/client/components/socket/socket.mock.js new file mode 100644 index 0000000..84a2e0c --- /dev/null +++ b/client/components/socket/socket.mock.js @@ -0,0 +1,16 @@ +'use strict'; + +angular.module('socketMock', []) + .factory('socket', function() { + return { + socket: { + connect: function() {}, + on: function() {}, + emit: function() {}, + receive: function() {} + }, + + syncUpdates: function() {}, + unsyncUpdates: function() {} + }; + }); \ No newline at end of file diff --git a/client/components/socket/socket.service.js b/client/components/socket/socket.service.js new file mode 100644 index 0000000..a101bda --- /dev/null +++ b/client/components/socket/socket.service.js @@ -0,0 +1,74 @@ +/* global io */ +'use strict'; + +angular.module('journalinkApp') + .factory('socket', function(socketFactory) { + + // socket.io now auto-configures its connection when we ommit a connection url + var ioSocket = io('', { + // Send auth token on connection, you will need to DI the Auth service above + // 'query': 'token=' + Auth.getToken() + path: '/socket.io-client' + }); + + var socket = socketFactory({ + ioSocket: ioSocket + }); + + return { + socket: socket, + + /** + * Register listeners to sync an array with updates on a model + * + * Takes the array we want to sync, the model name that socket updates are sent from, + * and an optional callback function after new items are updated. + * + * @param {String} modelName + * @param {Array} array + * @param {Function} cb + */ + syncUpdates: function (modelName, array, cb) { + cb = cb || angular.noop; + + /** + * Syncs item creation/updates on 'model:save' + */ + socket.on(modelName + ':save', function (item) { + var oldItem = _.find(array, {_id: item._id}); + var index = array.indexOf(oldItem); + var event = 'created'; + + // replace oldItem if it exists + // otherwise just add item to the collection + if (oldItem) { + array.splice(index, 1, item); + event = 'updated'; + } else { + array.push(item); + } + + cb(event, item, array); + }); + + /** + * Syncs removed items on 'model:remove' + */ + socket.on(modelName + ':remove', function (item) { + var event = 'deleted'; + _.remove(array, {_id: item._id}); + cb(event, item, array); + }); + }, + + /** + * Removes listeners for a models updates on the socket + * + * @param modelName + */ + unsyncUpdates: function (modelName) { + socket.removeAllListeners(modelName + ':save'); + socket.removeAllListeners(modelName + ':remove'); + } + }; + }); diff --git a/client/favicon.ico b/client/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8a163fbc628488b0f58bc23dc517294ce9aff557 GIT binary patch literal 6774 zcmeHL&1w`u5bl`l2C`_PXh1Qz8d+i>B4Yd@7`=E<2!h~2FuDjHyvX{4guUeITjWV3 zZ{cm9!B?<)YO24cx_hT*W|2LZ%v4uZ*Uwiqz1_2G%mV%Q`^Hl9=k(W_G3&;dExJi* zp3-e+ia(mW(BqpGA?>d2++Du>a&+VIh=RTNPQ)8OusDYl$TViI#MWeM3oB*~%@66O z%5rG$=3B2TIqmr2jKDe76h^Ayj5*kx%~03sW+-1>zQ6aGyuLZ4`yV=5 zqvwo3091@J)@Po9&w;}a4!htTyOlP;RJRwB2bqBe98kyWyTxEfgJWhk>@i7~$Sh5# z>9b09d%?Q-i6h;%D}pW=MK5;Zr$=O(j)-Ta23?cXte-X3f_Uz@G#B4Hwia{ftid@I zF%O(jHr~l00}FWhR>%O(pU>f?PE-yK{n4Gf`sMde>9QVkvZice70X(|T(!6Opl{XQ zsi?kQY7N(m?TGHuLw4N~FBzY^koYWv!?OhX@?a-F*r6h+7M`#Ihgq>l1Xv*xvcnU0 z;7|=#4mjz7?v0iAxDP{EAGppSdyJEPWc?L8oGZ6IEysLgx|F}flYB8>=zt!a)EKM3 z)9DcWIj3Wde9rMwN4uRBqCRf>W)viOU=jH1sU zCnqWVT2J+y=Evp!$NTNCCLQt08T<04wbklD^_rlq;{!h(W<`jl~~8kqR-qb zIJ5k9P-3O$I`h1ZV`_X_PMsD1zJl;<)mZ=cxBXLz_0-qd&tv;6?IZY@rty@i;Xc+9 zW93euQS+}REwg6s139~@#^=ip>uh>EuKN8^a#MN6tQK@Jpv#4ZDcdj~TKbxl&9x$0 SNI7y2W#1=7oMTJv>*HS$DU+}O literal 0 HcmV?d00001 diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..32aa63f --- /dev/null +++ b/client/index.html @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/robots.txt b/client/robots.txt new file mode 100644 index 0000000..9417495 --- /dev/null +++ b/client/robots.txt @@ -0,0 +1,3 @@ +# robotstxt.org + +User-agent: * diff --git a/e2e/main/main.po.js b/e2e/main/main.po.js new file mode 100644 index 0000000..6718608 --- /dev/null +++ b/e2e/main/main.po.js @@ -0,0 +1,15 @@ +/** + * This file uses the Page Object pattern to define the main page for tests + * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ + */ + +'use strict'; + +var MainPage = function() { + this.heroEl = element(by.css('.hero-unit')); + this.h1El = this.heroEl.element(by.css('h1')); + this.imgEl = this.heroEl.element(by.css('img')); +}; + +module.exports = new MainPage(); + diff --git a/e2e/main/main.spec.js b/e2e/main/main.spec.js new file mode 100644 index 0000000..61745a8 --- /dev/null +++ b/e2e/main/main.spec.js @@ -0,0 +1,16 @@ +'use strict'; + +describe('Main View', function() { + var page; + + beforeEach(function() { + browser.get('/'); + page = require('./main.po'); + }); + + it('should include jumbotron with correct data', function() { + expect(page.h1El.getText()).toBe('\'Allo, \'Allo!'); + expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/); + expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman'); + }); +}); diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..1bed91e --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,81 @@ +// Karma configuration +// http://karma-runner.github.io/0.10/config/configuration-file.html + +module.exports = function(config) { + config.set({ + // base path, that will be used to resolve files and exclude + basePath: '', + + // testing framework to use (jasmine/mocha/qunit/...) + frameworks: ['jasmine'], + + // list of files / patterns to load in the browser + files: [ + 'client/bower_components/jquery/dist/jquery.js', + 'client/bower_components/angular/angular.js', + 'client/bower_components/angular-mocks/angular-mocks.js', + 'client/bower_components/angular-resource/angular-resource.js', + 'client/bower_components/angular-cookies/angular-cookies.js', + 'client/bower_components/angular-sanitize/angular-sanitize.js', + 'client/bower_components/angular-route/angular-route.js', + 'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js', + 'client/bower_components/lodash/dist/lodash.compat.js', + 'client/bower_components/angular-socket-io/socket.js', + 'client/bower_components/angular-ui-router/release/angular-ui-router.js', + 'client/app/app.js', + 'client/app/app.coffee', + 'client/app/**/*.js', + 'client/app/**/*.coffee', + 'client/components/**/*.js', + 'client/components/**/*.coffee', + 'client/app/**/*.jade', + 'client/components/**/*.jade', + 'client/app/**/*.html', + 'client/components/**/*.html' + ], + + preprocessors: { + '**/*.jade': 'ng-jade2js', + '**/*.html': 'html2js', + '**/*.coffee': 'coffee', + }, + + ngHtml2JsPreprocessor: { + stripPrefix: 'client/' + }, + + ngJade2JsPreprocessor: { + stripPrefix: 'client/' + }, + + // list of files / patterns to exclude + exclude: [], + + // web server port + port: 8080, + + // level of logging + // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: ['PhantomJS'], + + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..e8eb172 --- /dev/null +++ b/package.json @@ -0,0 +1,88 @@ +{ + "name": "journalink", + "version": "0.0.0", + "main": "server/app.js", + "dependencies": { + "express": "~4.0.0", + "morgan": "~1.0.0", + "body-parser": "~1.5.0", + "method-override": "~1.0.0", + "serve-favicon": "~2.0.1", + "cookie-parser": "~1.0.1", + "express-session": "~1.0.2", + "errorhandler": "~1.0.0", + "compression": "~1.0.1", + "lodash": "~2.4.1", + "ejs": "~0.8.4", + "mongoose": "~3.8.8", + "composable-middleware": "^0.3.0", + "connect-mongo": "^0.4.1", + "socket.io": "^1.0.6", + "socket.io-client": "^1.0.6", + "socketio-jwt": "^2.0.2" + }, + "devDependencies": { + "grunt": "~0.4.4", + "grunt-autoprefixer": "~0.7.2", + "grunt-wiredep": "~1.8.0", + "grunt-concurrent": "~0.5.0", + "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-concat": "~0.4.0", + "grunt-contrib-copy": "~0.5.0", + "grunt-contrib-cssmin": "~0.9.0", + "grunt-contrib-htmlmin": "~0.2.0", + "grunt-contrib-imagemin": "~0.7.1", + "grunt-contrib-jshint": "~0.10.0", + "grunt-contrib-uglify": "~0.4.0", + "grunt-contrib-watch": "~0.6.1", + "grunt-google-cdn": "~0.4.0", + "grunt-newer": "~0.7.0", + "grunt-ng-annotate": "^0.2.3", + "grunt-rev": "~0.1.0", + "grunt-svgmin": "~0.4.0", + "grunt-usemin": "~2.1.1", + "grunt-env": "~0.4.1", + "grunt-node-inspector": "~0.1.5", + "grunt-nodemon": "~0.2.0", + "grunt-angular-templates": "^0.5.4", + "grunt-dom-munger": "^3.4.0", + "grunt-protractor-runner": "^1.1.0", + "grunt-asset-injector": "^0.1.0", + "grunt-karma": "~0.8.2", + "grunt-build-control": "DaftMonk/grunt-build-control", + "grunt-mocha-test": "~0.10.2", + "grunt-contrib-stylus": "latest", + "jit-grunt": "^0.5.0", + "time-grunt": "~0.3.1", + "grunt-express-server": "~0.4.17", + "grunt-open": "~0.2.3", + "open": "~0.0.4", + "jshint-stylish": "~0.1.5", + "connect-livereload": "~0.4.0", + "karma-ng-scenario": "~0.1.0", + "karma-firefox-launcher": "~0.1.3", + "karma-script-launcher": "~0.1.0", + "karma-html2js-preprocessor": "~0.1.0", + "karma-ng-jade2js-preprocessor": "^0.1.2", + "karma-jasmine": "~0.1.5", + "karma-chrome-launcher": "~0.1.3", + "requirejs": "~2.1.11", + "karma-requirejs": "~0.2.1", + "karma-coffee-preprocessor": "~0.2.1", + "karma-jade-preprocessor": "0.0.11", + "karma-phantomjs-launcher": "~0.1.4", + "karma": "~0.12.9", + "karma-ng-html2js-preprocessor": "~0.1.0", + "supertest": "~0.11.0", + "should": "~3.3.1" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "start": "node server/app.js", + "test": "grunt test", + "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update" + }, + "private": true +} diff --git a/protractor.conf.js b/protractor.conf.js new file mode 100644 index 0000000..cb66c67 --- /dev/null +++ b/protractor.conf.js @@ -0,0 +1,50 @@ +// Protractor configuration +// https://github.com/angular/protractor/blob/master/referenceConf.js + +'use strict'; + +exports.config = { + // The timeout for each script run on the browser. This should be longer + // than the maximum time your application needs to stabilize between tasks. + allScriptsTimeout: 110000, + + // A base URL for your application under test. Calls to protractor.get() + // with relative paths will be prepended with this. + baseUrl: 'http://localhost:' + (process.env.PORT || '9000'), + + // If true, only chromedriver will be started, not a standalone selenium. + // Tests for browsers other than chrome will not run. + chromeOnly: true, + + // list of files / patterns to load in the browser + specs: [ + 'e2e/**/*.spec.js' + ], + + // Patterns to exclude. + exclude: [], + + // ----- Capabilities to be passed to the webdriver instance ---- + // + // For a full list of available capabilities, see + // https://code.google.com/p/selenium/wiki/DesiredCapabilities + // and + // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js + capabilities: { + 'browserName': 'chrome' + }, + + // ----- The test framework ----- + // + // Jasmine and Cucumber are fully supported as a test and assertion framework. + // Mocha has limited beta support. You will need to include your own + // assertion framework if working with mocha. + framework: 'jasmine', + + // ----- Options to be passed to minijasminenode ----- + // + // See the full list at https://github.com/juliemr/minijasminenode + jasmineNodeOpts: { + defaultTimeoutInterval: 30000 + } +}; diff --git a/server/.jshintrc b/server/.jshintrc new file mode 100644 index 0000000..d7b958e --- /dev/null +++ b/server/.jshintrc @@ -0,0 +1,15 @@ +{ + "node": true, + "esnext": true, + "bitwise": true, + "eqeqeq": true, + "immed": true, + "latedef": "nofunc", + "newcap": true, + "noarg": true, + "regexp": true, + "undef": true, + "smarttabs": true, + "asi": true, + "debug": true +} diff --git a/server/.jshintrc-spec b/server/.jshintrc-spec new file mode 100644 index 0000000..b6b55cb --- /dev/null +++ b/server/.jshintrc-spec @@ -0,0 +1,11 @@ +{ + "extends": ".jshintrc", + "globals": { + "describe": true, + "it": true, + "before": true, + "beforeEach": true, + "after": true, + "afterEach": true + } +} diff --git a/server/api/thing/index.js b/server/api/thing/index.js new file mode 100644 index 0000000..845c9f0 --- /dev/null +++ b/server/api/thing/index.js @@ -0,0 +1,15 @@ +'use strict'; + +var express = require('express'); +var controller = require('./thing.controller'); + +var router = express.Router(); + +router.get('/', controller.index); +router.get('/:id', controller.show); +router.post('/', controller.create); +router.put('/:id', controller.update); +router.patch('/:id', controller.update); +router.delete('/:id', controller.destroy); + +module.exports = router; \ No newline at end of file diff --git a/server/api/thing/thing.controller.js b/server/api/thing/thing.controller.js new file mode 100644 index 0000000..be6541e --- /dev/null +++ b/server/api/thing/thing.controller.js @@ -0,0 +1,68 @@ +/** + * Using Rails-like standard naming convention for endpoints. + * GET /things -> index + * POST /things -> create + * GET /things/:id -> show + * PUT /things/:id -> update + * DELETE /things/:id -> destroy + */ + +'use strict'; + +var _ = require('lodash'); +var Thing = require('./thing.model'); + +// Get list of things +exports.index = function(req, res) { + Thing.find(function (err, things) { + if(err) { return handleError(res, err); } + return res.json(200, things); + }); +}; + +// Get a single thing +exports.show = function(req, res) { + Thing.findById(req.params.id, function (err, thing) { + if(err) { return handleError(res, err); } + if(!thing) { return res.send(404); } + return res.json(thing); + }); +}; + +// Creates a new thing in the DB. +exports.create = function(req, res) { + Thing.create(req.body, function(err, thing) { + if(err) { return handleError(res, err); } + return res.json(201, thing); + }); +}; + +// Updates an existing thing in the DB. +exports.update = function(req, res) { + if(req.body._id) { delete req.body._id; } + Thing.findById(req.params.id, function (err, thing) { + if (err) { return handleError(res, err); } + if(!thing) { return res.send(404); } + var updated = _.merge(thing, req.body); + updated.save(function (err) { + if (err) { return handleError(res, err); } + return res.json(200, thing); + }); + }); +}; + +// Deletes a thing from the DB. +exports.destroy = function(req, res) { + Thing.findById(req.params.id, function (err, thing) { + if(err) { return handleError(res, err); } + if(!thing) { return res.send(404); } + thing.remove(function(err) { + if(err) { return handleError(res, err); } + return res.send(204); + }); + }); +}; + +function handleError(res, err) { + return res.send(500, err); +} \ No newline at end of file diff --git a/server/api/thing/thing.model.js b/server/api/thing/thing.model.js new file mode 100644 index 0000000..ed857cd --- /dev/null +++ b/server/api/thing/thing.model.js @@ -0,0 +1,12 @@ +'use strict'; + +var mongoose = require('mongoose'), + Schema = mongoose.Schema; + +var ThingSchema = new Schema({ + name: String, + info: String, + active: Boolean +}); + +module.exports = mongoose.model('Thing', ThingSchema); \ No newline at end of file diff --git a/server/api/thing/thing.socket.js b/server/api/thing/thing.socket.js new file mode 100644 index 0000000..79d3276 --- /dev/null +++ b/server/api/thing/thing.socket.js @@ -0,0 +1,24 @@ +/** + * Broadcast updates to client when the model changes + */ + +'use strict'; + +var thing = require('./thing.model'); + +exports.register = function(socket) { + thing.schema.post('save', function (doc) { + onSave(socket, doc); + }); + thing.schema.post('remove', function (doc) { + onRemove(socket, doc); + }); +} + +function onSave(socket, doc, cb) { + socket.emit('thing:save', doc); +} + +function onRemove(socket, doc, cb) { + socket.emit('thing:remove', doc); +} \ No newline at end of file diff --git a/server/api/thing/thing.spec.js b/server/api/thing/thing.spec.js new file mode 100644 index 0000000..17c8c6c --- /dev/null +++ b/server/api/thing/thing.spec.js @@ -0,0 +1,20 @@ +'use strict'; + +var should = require('should'); +var app = require('../../app'); +var request = require('supertest'); + +describe('GET /api/things', function() { + + it('should respond with JSON array', function(done) { + request(app) + .get('/api/things') + .expect(200) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) return done(err); + res.body.should.be.instanceof(Array); + done(); + }); + }); +}); diff --git a/server/app.js b/server/app.js new file mode 100644 index 0000000..c122143 --- /dev/null +++ b/server/app.js @@ -0,0 +1,37 @@ +/** + * Main application file + */ + +'use strict'; + +// Set default node environment to development +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var express = require('express'); +var mongoose = require('mongoose'); +var config = require('./config/environment'); + +// Connect to database +mongoose.connect(config.mongo.uri, config.mongo.options); + +// Populate DB with sample data +if(config.seedDB) { require('./config/seed'); } + +// Setup server +var app = express(); +var server = require('http').createServer(app); +var socketio = require('socket.io')(server, { + serveClient: (config.env === 'production') ? false : true, + path: '/socket.io-client' +}); +require('./config/socketio')(socketio); +require('./config/express')(app); +require('./routes')(app); + +// Start server +server.listen(config.port, config.ip, function () { + console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); +}); + +// Expose app +exports = module.exports = app; \ No newline at end of file diff --git a/server/components/errors/index.js b/server/components/errors/index.js new file mode 100644 index 0000000..4c5a57c --- /dev/null +++ b/server/components/errors/index.js @@ -0,0 +1,20 @@ +/** + * Error responses + */ + +'use strict'; + +module.exports[404] = function pageNotFound(req, res) { + var viewFilePath = '404'; + var statusCode = 404; + var result = { + status: statusCode + }; + + res.status(result.status); + res.render(viewFilePath, function (err) { + if (err) { return res.json(result, result.status); } + + res.render(viewFilePath); + }); +}; diff --git a/server/config/environment/development.js b/server/config/environment/development.js new file mode 100644 index 0000000..78aa0e4 --- /dev/null +++ b/server/config/environment/development.js @@ -0,0 +1,12 @@ +'use strict'; + +// Development specific configuration +// ================================== +module.exports = { + // MongoDB connection options + mongo: { + uri: 'mongodb://localhost/journalink-dev' + }, + + seedDB: true +}; diff --git a/server/config/environment/index.js b/server/config/environment/index.js new file mode 100644 index 0000000..1409cef --- /dev/null +++ b/server/config/environment/index.js @@ -0,0 +1,50 @@ +'use strict'; + +var path = require('path'); +var _ = require('lodash'); + +function requiredProcessEnv(name) { + if(!process.env[name]) { + throw new Error('You must set the ' + name + ' environment variable'); + } + return process.env[name]; +} + +// All configurations will extend these options +// ============================================ +var all = { + env: process.env.NODE_ENV, + + // Root path of server + root: path.normalize(__dirname + '/../../..'), + + // Server port + port: process.env.PORT || 9000, + + // Should we populate the DB with sample data? + seedDB: false, + + // Secret for session, you will want to change this and make it an environment variable + secrets: { + session: 'journalink-secret' + }, + + // List of user roles + userRoles: ['guest', 'user', 'admin'], + + // MongoDB connection options + mongo: { + options: { + db: { + safe: true + } + } + }, + +}; + +// Export the config object based on the NODE_ENV +// ============================================== +module.exports = _.merge( + all, + require('./' + process.env.NODE_ENV + '.js') || {}); \ No newline at end of file diff --git a/server/config/environment/production.js b/server/config/environment/production.js new file mode 100644 index 0000000..0c44946 --- /dev/null +++ b/server/config/environment/production.js @@ -0,0 +1,23 @@ +'use strict'; + +// Production specific configuration +// ================================= +module.exports = { + // Server IP + ip: process.env.OPENSHIFT_NODEJS_IP || + process.env.IP || + undefined, + + // Server port + port: process.env.OPENSHIFT_NODEJS_PORT || + process.env.PORT || + 8080, + + // MongoDB connection options + mongo: { + uri: process.env.MONGOLAB_URI || + process.env.MONGOHQ_URL || + process.env.OPENSHIFT_MONGODB_DB_URL+process.env.OPENSHIFT_APP_NAME || + 'mongodb://localhost/journalink' + } +}; \ No newline at end of file diff --git a/server/config/environment/test.js b/server/config/environment/test.js new file mode 100644 index 0000000..3416f20 --- /dev/null +++ b/server/config/environment/test.js @@ -0,0 +1,10 @@ +'use strict'; + +// Test specific configuration +// =========================== +module.exports = { + // MongoDB connection options + mongo: { + uri: 'mongodb://localhost/journalink-test' + } +}; \ No newline at end of file diff --git a/server/config/express.js b/server/config/express.js new file mode 100644 index 0000000..80adef6 --- /dev/null +++ b/server/config/express.js @@ -0,0 +1,45 @@ +/** + * Express configuration + */ + +'use strict'; + +var express = require('express'); +var favicon = require('serve-favicon'); +var morgan = require('morgan'); +var compression = require('compression'); +var bodyParser = require('body-parser'); +var methodOverride = require('method-override'); +var cookieParser = require('cookie-parser'); +var errorHandler = require('errorhandler'); +var path = require('path'); +var config = require('./environment'); + +module.exports = function(app) { + var env = app.get('env'); + + app.set('views', config.root + '/server/views'); + app.engine('html', require('ejs').renderFile); + app.set('view engine', 'html'); + app.use(compression()); + app.use(bodyParser.urlencoded({ extended: false })); + app.use(bodyParser.json()); + app.use(methodOverride()); + app.use(cookieParser()); + + if ('production' === env) { + app.use(favicon(path.join(config.root, 'public', 'favicon.ico'))); + app.use(express.static(path.join(config.root, 'public'))); + app.set('appPath', config.root + '/public'); + app.use(morgan('dev')); + } + + if ('development' === env || 'test' === env) { + app.use(require('connect-livereload')()); + app.use(express.static(path.join(config.root, '.tmp'))); + app.use(express.static(path.join(config.root, 'client'))); + app.set('appPath', 'client'); + app.use(morgan('dev')); + app.use(errorHandler()); // Error handler - has to be last + } +}; \ No newline at end of file diff --git a/server/config/local.env.sample.js b/server/config/local.env.sample.js new file mode 100644 index 0000000..7c21dca --- /dev/null +++ b/server/config/local.env.sample.js @@ -0,0 +1,14 @@ +'use strict'; + +// Use local.env.js for environment variables that grunt will set when the server starts locally. +// Use for your api keys, secrets, etc. This file should not be tracked by git. +// +// You will need to set these on the server you deploy to. + +module.exports = { + DOMAIN: 'http://localhost:9000', + SESSION_SECRET: 'journalink-secret', + + // Control debug level for modules using visionmedia/debug + DEBUG: '' +}; diff --git a/server/config/seed.js b/server/config/seed.js new file mode 100644 index 0000000..2360e52 --- /dev/null +++ b/server/config/seed.js @@ -0,0 +1,31 @@ +/** + * Populate DB with sample data on server start + * to disable, edit config/environment/index.js, and set `seedDB: false` + */ + +'use strict'; + +var Thing = require('../api/thing/thing.model'); + + +Thing.find({}).remove(function() { + Thing.create({ + name : 'Development Tools', + info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.' + }, { + name : 'Server and Client integration', + info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.' + }, { + name : 'Smart Build System', + info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html' + }, { + name : 'Modular Structure', + info : 'Best practice client and server structures allow for more code reusability and maximum scalability' + }, { + name : 'Optimized Build', + info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.' + },{ + name : 'Deployment Ready', + info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators' + }); +}); \ No newline at end of file diff --git a/server/config/socketio.js b/server/config/socketio.js new file mode 100644 index 0000000..2fbbc07 --- /dev/null +++ b/server/config/socketio.js @@ -0,0 +1,57 @@ +/** + * Socket.io configuration + */ + +'use strict'; + +var config = require('./environment'); + +// When the user disconnects.. perform this +function onDisconnect(socket) { +} + +// When the user connects.. perform this +function onConnect(socket) { + // When the client emits 'info', this listens and executes + socket.on('info', function (data) { + console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2)); + }); + + // Insert sockets below + require('../api/thing/thing.socket').register(socket); +} + +module.exports = function (socketio) { + // socket.io (v1.x.x) is powered by debug. + // In order to see all the debug output, set DEBUG (in server/config/local.env.js) to including the desired scope. + // + // ex: DEBUG: "http*,socket.io:socket" + + // We can authenticate socket.io users and access their token through socket.handshake.decoded_token + // + // 1. You will need to send the token in `client/components/socket/socket.service.js` + // + // 2. Require authentication here: + // socketio.use(require('socketio-jwt').authorize({ + // secret: config.secrets.session, + // handshake: true + // })); + + socketio.on('connection', function (socket) { + socket.address = socket.handshake.address !== null ? + socket.handshake.address.address + ':' + socket.handshake.address.port : + process.env.DOMAIN; + + socket.connectedAt = new Date(); + + // Call onDisconnect. + socket.on('disconnect', function () { + onDisconnect(socket); + console.info('[%s] DISCONNECTED', socket.address); + }); + + // Call onConnect. + onConnect(socket); + console.info('[%s] CONNECTED', socket.address); + }); +}; \ No newline at end of file diff --git a/server/routes.js b/server/routes.js new file mode 100644 index 0000000..a67203b --- /dev/null +++ b/server/routes.js @@ -0,0 +1,23 @@ +/** + * Main application routes + */ + +'use strict'; + +var errors = require('./components/errors'); + +module.exports = function(app) { + + // Insert routes below + app.use('/api/things', require('./api/thing')); + + // All undefined asset or api routes should return a 404 + app.route('/:url(api|auth|components|app|bower_components|assets)/*') + .get(errors[404]); + + // All other routes should redirect to the index.html + app.route('/*') + .get(function(req, res) { + res.sendfile(app.get('appPath') + '/index.html'); + }); +}; diff --git a/server/views/404.html b/server/views/404.html new file mode 100644 index 0000000..ec98e3c --- /dev/null +++ b/server/views/404.html @@ -0,0 +1,157 @@ + + + + + Page Not Found :( + + + +
+

Not found :(

+

Sorry, but the page you were trying to view does not exist.

+

It looks like this was the result of either:

+
    +
  • a mistyped address
  • +
  • an out-of-date link
  • +
+ + +
+ +