├── .bowerrc ├── .csslintrc ├── .editorconfig ├── .floo ├── .flooignore ├── .gitignore ├── .idea └── dbnavigator.xml ├── .jshintignore ├── .jshintrc ├── .slugignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── Procfile ├── README.md ├── STYLE-GUIDE.md ├── api.md ├── bower.json ├── config ├── assets.json ├── env │ ├── all.js │ ├── development.js │ ├── production.js │ └── test.js └── express.js ├── karma.conf.js ├── mean.json ├── package.json ├── packages ├── articles │ ├── README.md │ ├── app.js │ ├── mean.json │ ├── package.json │ ├── public │ │ ├── assets │ │ │ ├── css │ │ │ │ └── articles.css │ │ │ └── img │ │ │ │ └── logo1.png │ │ ├── controllers │ │ │ ├── AddTreeController.js │ │ │ ├── MapViewController.js │ │ │ ├── MessagesController.js │ │ │ ├── TreesController.js │ │ │ └── paginationController.js │ │ ├── routes │ │ │ └── articles.js │ │ ├── services │ │ │ ├── articles.js │ │ │ └── search.js │ │ ├── tests │ │ │ └── articles.spec.js │ │ └── views │ │ │ ├── list.html │ │ │ ├── new.html │ │ │ └── profile.html │ └── server │ │ ├── controllers │ │ └── articles.js │ │ ├── models │ │ └── article.js │ │ ├── routes │ │ └── articles.js │ │ └── tests │ │ └── articles.js ├── system │ ├── README.md │ ├── app.js │ ├── mean.json │ ├── package.json │ ├── public │ │ ├── assets │ │ │ ├── css │ │ │ │ └── common.css │ │ │ ├── img │ │ │ │ ├── about │ │ │ │ │ ├── black-github-256.png │ │ │ │ │ ├── blog.gif │ │ │ │ │ ├── github-10-512.png │ │ │ │ │ ├── li.png │ │ │ │ │ ├── tree-10-512.png │ │ │ │ │ ├── tree-26-512.png │ │ │ │ │ ├── tree-45-64.png │ │ │ │ │ ├── tree-51-64.png │ │ │ │ │ ├── tree-7-512.png │ │ │ │ │ ├── tree-72-512.png │ │ │ │ │ ├── tree-74-64.png │ │ │ │ │ ├── tree-76-512.png │ │ │ │ │ ├── tree-85-64.png │ │ │ │ │ └── twitter.png │ │ │ │ ├── apple │ │ │ │ │ ├── apple-touch-icon-114x114-precomposed.png │ │ │ │ │ ├── apple-touch-icon-144x144-precomposed.png │ │ │ │ │ ├── apple-touch-icon-57x57-precomposed.png │ │ │ │ │ ├── apple-touch-icon-72x72-precomposed.png │ │ │ │ │ ├── apple-touch-icon-precomposed.png │ │ │ │ │ ├── apple-touch-icon.png │ │ │ │ │ ├── splash.png │ │ │ │ │ └── splash2x.png │ │ │ │ ├── favicon.ico │ │ │ │ ├── loaders │ │ │ │ │ └── loader.gif │ │ │ │ ├── placeholder.png │ │ │ │ └── sprites │ │ │ │ │ ├── glyphicons-halflings-white.png │ │ │ │ │ └── glyphicons-halflings.png │ │ │ ├── js │ │ │ │ └── chat.js │ │ │ └── static │ │ │ │ ├── humans.txt │ │ │ │ └── robots.txt │ │ ├── controllers │ │ │ ├── header.js │ │ │ └── index.js │ │ ├── routes │ │ │ └── system.js │ │ ├── services │ │ │ ├── global.js │ │ │ ├── interceptor.js │ │ │ └── menus.js │ │ ├── system.js │ │ ├── tests │ │ │ ├── headers.spec.js │ │ │ └── index.spec.js │ │ └── views │ │ │ ├── about.html │ │ │ ├── header.html │ │ │ └── index.html │ └── server │ │ ├── controllers │ │ └── index.js │ │ ├── routes │ │ ├── index.js │ │ └── menus.js │ │ └── views │ │ ├── 404.html │ │ ├── 500.html │ │ ├── includes │ │ ├── foot.html │ │ └── head.html │ │ ├── index.html │ │ └── layouts │ │ └── default.html ├── theme │ ├── README.md │ ├── app.js │ ├── mean.json │ ├── package.json │ ├── public │ │ ├── assets │ │ │ ├── css │ │ │ │ ├── loginForms.css │ │ │ │ └── theme.css │ │ │ ├── fonts │ │ │ │ ├── mark_simonson_-_proxima_nova_regular.eot │ │ │ │ ├── mark_simonson_-_proxima_nova_regular.svg │ │ │ │ ├── mark_simonson_-_proxima_nova_regular.ttf │ │ │ │ ├── mark_simonson_-_proxima_nova_regular.woff │ │ │ │ ├── opensanshebrew-light.eot │ │ │ │ ├── opensanshebrew-light.svg │ │ │ │ ├── opensanshebrew-light.ttf │ │ │ │ ├── opensanshebrew-light.woff │ │ │ │ ├── opensanshebrew-regular.eot │ │ │ │ ├── opensanshebrew-regular.svg │ │ │ │ ├── opensanshebrew-regular.ttf │ │ │ │ ├── opensanshebrew-regular.woff │ │ │ │ ├── proximanova-light.eot │ │ │ │ ├── proximanova-light.svg │ │ │ │ ├── proximanova-light.ttf │ │ │ │ ├── proximanova-light.woff │ │ │ │ ├── ufonts.com_futura-book.eot │ │ │ │ ├── ufonts.com_futura-book.svg │ │ │ │ ├── ufonts.com_futura-book.ttf │ │ │ │ └── ufonts.com_futura-book.woff │ │ │ └── img │ │ │ │ ├── bg.png │ │ │ │ ├── button_login.png │ │ │ │ ├── button_login_hover.png │ │ │ │ ├── cactus.png │ │ │ │ ├── christmas128.png │ │ │ │ ├── cypress1.jpg │ │ │ │ ├── devide_line.png │ │ │ │ ├── devide_line_long.png │ │ │ │ ├── eye_password.png │ │ │ │ ├── eye_password_disabled.png │ │ │ │ ├── forest7.png │ │ │ │ ├── gmap.png │ │ │ │ ├── icons │ │ │ │ ├── facebook.png │ │ │ │ ├── favicon.ico │ │ │ │ ├── github.png │ │ │ │ ├── google.png │ │ │ │ ├── linkedin.png │ │ │ │ ├── twitter.png │ │ │ │ └── user-icon.png │ │ │ │ ├── logo.png │ │ │ │ ├── mail_login.png │ │ │ │ ├── makeapoint.png │ │ │ │ ├── sequoia-logo copy 2.png │ │ │ │ ├── sequoia-logo-white-small.png │ │ │ │ ├── sequoia-logo.png │ │ │ │ ├── single14.png │ │ │ │ ├── social_fb.png │ │ │ │ ├── social_g+.png │ │ │ │ ├── social_github.png │ │ │ │ ├── social_in.png │ │ │ │ ├── social_tw.png │ │ │ │ ├── top-gradient.jpg │ │ │ │ ├── tree101.png │ │ │ │ ├── tree111.png │ │ │ │ ├── tree144.png │ │ │ │ ├── tree65.png │ │ │ │ ├── tree79.png │ │ │ │ ├── trees10.png │ │ │ │ ├── trees7.png │ │ │ │ ├── welcome_social.png │ │ │ │ ├── welcome_sprite.png │ │ │ │ ├── woman_ninja.png │ │ │ │ └── woman_ninja_forg_pass.png │ │ ├── controllers │ │ │ └── theme.js │ │ ├── routes │ │ │ └── theme.js │ │ ├── services │ │ │ └── theme.js │ │ └── views │ │ │ └── index.html │ └── server │ │ ├── routes │ │ └── theme.js │ │ └── views │ │ └── index.html └── users │ ├── README.md │ ├── app.js │ ├── authorization.js │ ├── mean.json │ ├── package.json │ ├── passport.js │ ├── public │ ├── assets │ │ └── css │ │ │ └── meanUser.css │ ├── controllers │ │ ├── UserController.js │ │ └── meanUser.js │ ├── routes │ │ └── auth.js │ ├── services │ │ └── meanUser.js │ ├── tests │ │ └── auth.spec.js │ └── views │ │ ├── forgot-password.html │ │ ├── index.html │ │ ├── login.html │ │ ├── register.html │ │ ├── reset-password.html │ │ └── userProfile.html │ └── server │ ├── controllers │ └── users.js │ ├── models │ └── user.js │ ├── routes │ └── users.js │ ├── template.js │ └── tests │ └── users.js ├── server.js ├── toDo └── tools ├── grunt ├── Gruntfile.js └── grunt.json ├── gulp ├── gulp.json └── gulpfile.js └── test └── mocha-req.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "storage": { 4 | "packages": ".bower-cache", 5 | "registry": ".bower-registry" 6 | }, 7 | "tmp": ".bower-tmp" 8 | } 9 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-model": false, 4 | "box-sizing": false, 5 | "compatible-vendor-prefixes": false, 6 | "floats": false, 7 | "font-sizes": false, 8 | "gradients": false, 9 | "important": false, 10 | "known-properties": false, 11 | "outline-none": false, 12 | "overqualified-elements": false, 13 | "qualified-headings": false, 14 | "regex-selectors": false, 15 | "shorthand": false, 16 | "text-indent": false, 17 | "unique-headings": false, 18 | "universal-selector": false, 19 | "unqualified-attributes": false 20 | } 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.floo: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://floobits.com/dolan.charles08/TreeBook" 3 | } -------------------------------------------------------------------------------- /.flooignore: -------------------------------------------------------------------------------- 1 | extern 2 | node_modules 3 | tmp 4 | vendor 5 | .idea/workspace.xml 6 | .idea/misc.xml 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .profile 2 | # -------------------- 3 | # OSX Files 4 | # -------------------- 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | Icon 9 | ._* 10 | .Spotlight-V100 11 | .Trashes 12 | 13 | # -------------------- 14 | # Eclipse or Nodeclipse "Enide Studio" Files 15 | # -------------------- 16 | # recommended to add to Git SCM 17 | # or generate with `nodeclipse -p` command 18 | .project 19 | .settings/ 20 | .*.md.html 21 | 22 | 23 | # -------------------- 24 | # IntelliJ Files 25 | # -------------------- 26 | *.iml 27 | *.ipr 28 | *.iws 29 | .idea/ 30 | 31 | # -------------------- 32 | # Netbeans Files 33 | # -------------------- 34 | nbproject/private/ 35 | build/ 36 | nbbuild/ 37 | dist/ 38 | nbdist/ 39 | nbactions.xml 40 | nb-configuration.xml 41 | 42 | # -------------------- 43 | # Node Files 44 | # -------------------- 45 | .nodemonignore 46 | npm-debug.log 47 | node_modules/ 48 | 49 | # -------------------- 50 | # SASS Files 51 | # -------------------- 52 | .sass-cache/ 53 | 54 | # -------------------- 55 | # Bower Files 56 | # -------------------- 57 | .bower-*/ 58 | bower_components 59 | 60 | # -------------------- 61 | # App Files 62 | # -------------------- 63 | test/coverage/ 64 | modules/public/ 65 | modules/views/ 66 | /public/build/ 67 | /packages/custom/ 68 | /packages/contrib/ 69 | 70 | # -------------------- 71 | # vim Files 72 | # -------------------- 73 | *.swp 74 | .flooignore 75 | .floo 76 | 77 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 78 | 79 | *.iml 80 | 81 | ## Directory-based project format: 82 | .idea/ 83 | # if you remove the above rule, at least ignore the following: 84 | 85 | # User-specific stuff: 86 | # .idea/workspace.xml 87 | # .idea/tasks.xml 88 | # .idea/dictionaries 89 | 90 | # Sensitive or high-churn files: 91 | # .idea/dataSources.ids 92 | # .idea/dataSources.xml 93 | # .idea/sqlDataSources.xml 94 | # .idea/dynamic.xml 95 | # .idea/uiDesigner.xml 96 | 97 | # Gradle: 98 | # .idea/gradle.xml 99 | # .idea/libraries 100 | 101 | # Mongo Explorer plugin: 102 | # .idea/mongoSettings.xml 103 | 104 | ## File-based project format: 105 | *.ipr 106 | *.iws 107 | 108 | 109 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | test/coverage/** -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr" : 50, // {int} Maximum error before stopping 3 | 4 | // Enforcing 5 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 6 | "camelcase" : false, // true: Identifiers must be in camelCase 7 | "curly" : false, // true: Require {} for every new block or scope 8 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 9 | "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() 10 | "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 11 | "indent" : 2, // {int} Number of spaces to use for indentation 12 | "latedef" : true, // true: Require variables/functions to be defined before being used 13 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` 14 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 15 | "noempty" : true, // true: Prohibit use of empty blocks 16 | "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment) 17 | "plusplus" : true, // true: Prohibit use of `++` & `--` 18 | "quotmark" : "single", // Quotation mark consistency: 19 | // false : do nothing (default) 20 | // true : ensure whatever is used is consistent 21 | // "single" : require single quotes 22 | // "double" : require double quotes 23 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 24 | "unused" : "vars", // true: Require all defined variables be used 25 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 26 | "maxparams" : false, // {int} Max number of formal params allowed per function 27 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 28 | "maxstatements" : false, // {int} Max number statements per function 29 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 30 | "maxlen" : false, // {int} Max number of characters per line 31 | 32 | // Relaxing 33 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 34 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 35 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 36 | "eqnull" : false, // true: Tolerate use of `== null` 37 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 38 | "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) 39 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 40 | // (ex: `for each`, multiple try/catch, function expression…) 41 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 42 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 43 | "funcscope" : false, // true: Tolerate defining variables inside control statements 44 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 45 | "iterator" : false, // true: Tolerate using the `__iterator__` property 46 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 47 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 48 | "laxcomma" : false, // true: Tolerate comma-first style coding 49 | "loopfunc" : false, // true: Tolerate functions being defined in loops 50 | "multistr" : false, // true: Tolerate multi-line strings 51 | "proto" : false, // true: Tolerate using the `__proto__` property 52 | "scripturl" : false, // true: Tolerate script-targeted URLs 53 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 54 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 55 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 56 | "validthis" : false, // true: Tolerate using this in a non-constructor function 57 | 58 | // Environments 59 | "browser" : true, // Web Browser (window, document, etc) 60 | "couch" : false, // CouchDB 61 | "devel" : true, // Development/debugging (alert, confirm, etc) 62 | "dojo" : false, // Dojo Toolkit 63 | "jquery" : false, // jQuery 64 | "mootools" : false, // MooTools 65 | "node" : true, // Node.js 66 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 67 | "prototypejs" : false, // Prototype and Scriptaculous 68 | "rhino" : false, // Rhino 69 | "worker" : false, // Web Workers 70 | "wsh" : false, // Windows Scripting Host 71 | "yui" : false, // Yahoo User Interface 72 | 73 | // Custom Globals 74 | "globals": { 75 | "angular" : true, 76 | "define" : false, 77 | "jasmine" : false, 78 | "require" : false, 79 | "exports" : false, 80 | "module" : false, 81 | "describe" : false, 82 | "before" : false, 83 | "beforeEach" : false, 84 | "after" : false, 85 | "afterEach" : false, 86 | "it" : false, 87 | "inject" : false, 88 | "expect" : false, 89 | "spyOn" : false 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.slugignore: -------------------------------------------------------------------------------- 1 | /test -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | env: 5 | - NODE_ENV=development 6 | services: 7 | - mongodb 8 | 9 | 10 | notifications: 11 | webhooks: 12 | urls: 13 | - https://webhooks.gitter.im/e/08c84711c36e875930d0 14 | on_success: change # options: [always|never|change] default: always 15 | on_failure: always # options: [always|never|change] default: always 16 | on_start: false # default: false 17 | 18 | 19 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var paths = { 4 | js: ['*.js', 'test/**/*.js', '!test/coverage/**', '!bower_components/**', 'packages/**/*.js', '!packages/**/node_modules/**', '!packages/contrib/**/*.js', '!packages/contrib/**/node_modules/**'], 5 | html: ['packages/**/public/**/views/**', 'packages/**/server/views/**'], 6 | css: ['!bower_components/**', 'packages/**/public/**/css/*.css', '!packages/contrib/**/public/**/css/*.css'] 7 | }; 8 | 9 | module.exports = function(grunt) { 10 | 11 | if (process.env.NODE_ENV !== 'production') { 12 | require('time-grunt')(grunt); 13 | } 14 | 15 | // Project Configuration 16 | grunt.initConfig({ 17 | pkg: grunt.file.readJSON('package.json'), 18 | assets: grunt.file.readJSON('config/assets.json'), 19 | clean: ['bower_components/build'], 20 | watch: { 21 | js: { 22 | files: paths.js, 23 | tasks: ['jshint'], 24 | options: { 25 | livereload: true 26 | } 27 | }, 28 | html: { 29 | files: paths.html, 30 | options: { 31 | livereload: true, 32 | interval: 500 33 | } 34 | }, 35 | css: { 36 | files: paths.css, 37 | tasks: ['csslint'], 38 | options: { 39 | livereload: true 40 | } 41 | } 42 | }, 43 | jshint: { 44 | all: { 45 | src: paths.js, 46 | options: { 47 | jshintrc: true 48 | } 49 | } 50 | }, 51 | uglify: { 52 | core: { 53 | options: { 54 | mangle: false 55 | }, 56 | files: '<%= assets.core.js %>' 57 | } 58 | }, 59 | csslint: { 60 | options: { 61 | csslintrc: '.csslintrc' 62 | }, 63 | src: paths.css 64 | }, 65 | cssmin: { 66 | core: { 67 | files: '<%= assets.core.css %>' 68 | } 69 | }, 70 | nodemon: { 71 | dev: { 72 | script: 'server.js', 73 | options: { 74 | args: [], 75 | ignore: ['node_modules/**'], 76 | ext: 'js,html', 77 | nodeArgs: ['--debug'], 78 | delayTime: 1, 79 | cwd: __dirname 80 | } 81 | } 82 | }, 83 | concurrent: { 84 | tasks: ['nodemon', 'watch'], 85 | options: { 86 | logConcurrentOutput: true 87 | } 88 | }, 89 | mochaTest: { 90 | options: { 91 | reporter: 'spec', 92 | require: [ 93 | 'server.js', 94 | function() { 95 | require('meanio/lib/core_modules/module/util').preload(__dirname + '/packages/**/server', 'model'); 96 | } 97 | ] 98 | }, 99 | src: ['packages/**/server/tests/**/*.js'] 100 | }, 101 | env: { 102 | test: { 103 | NODE_ENV: 'test' 104 | } 105 | }, 106 | karma: { 107 | unit: { 108 | configFile: 'karma.conf.js' 109 | } 110 | } 111 | }); 112 | 113 | //Load NPM tasks 114 | require('load-grunt-tasks')(grunt); 115 | 116 | /** 117 | * Default Task 118 | */ 119 | grunt.hook.push('clean', -9999); 120 | grunt.hook.push('concurrent', 9999); 121 | if (process.env.NODE_ENV === 'production') { 122 | grunt.hook.push('cssmin', 100); 123 | grunt.hook.push('uglify', 200); 124 | } else { 125 | grunt.hook.push('jshint', -200); 126 | grunt.hook.push('csslint', 100); 127 | } 128 | 129 | //Default task. 130 | grunt.registerTask('default', ['hook']); 131 | 132 | //Test task. 133 | grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']); 134 | 135 | // For Heroku users only. 136 | // Docs: https://github.com/linnovate/mean/wiki/Deploying-on-Heroku 137 | grunt.registerTask('heroku:production', ['cssmin', 'uglify']); 138 | }; 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ======================================== 2 | MEAN.IO is licensed under the MIT License 3 | ======================================== 4 | 5 | Copyright (C) 2012-2014 Linnovate Technologies 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: ./node_modules/.bin/forever -m 5 server.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![TreeBook Logo](https://s3-us-west-1.amazonaws.com/treebookicons/tree-64.png) TreeBook 2 | 3 | TreeBook is the definitive social networking site for Tree Huggers, Lovers and Enthusiasts. 4 | 5 | ##Contributors: 6 | *Greenfield:* Paulo Dniz, Eddie Dolan, Jonah Nisenson, and Eric Liu 7 | 8 | *Legacy:* John Zhang, Seunghoon Ko, and Eli Xian 9 | 10 | ## Folder structure 11 | 12 | The folder structure used was provided by [mean.io](mean.io). They have extensive documentation on the folder structure and how best to program in it. We strongly recommend taking time to go through the folder structure and understand how the logic flows. 13 | 14 | ``` 15 | 16 | ├── config/ 17 | │ ├── env/ // Configuration files for different environments. 18 | │ ├── packages/ // Application logic 19 | │ │ ├── articles/ // Logic for tree functionality 20 | │ │ ├── system/ // Logic for all non tree and non user pages (index, about, etc) 21 | │ │ ├── theme/ // Logic for styling and components (header / footer) 22 | │ │ ├── users/ // Logic for users 23 | 24 | ``` 25 | Each of articles, system, then and users loosely has the following structure 26 | ``` 27 | 28 | ├── public/ // Front end files 29 | │ ├── assets/ // Css and images 30 | │ ├── controllers/ // Angular controllers 31 | │ ├── routes/ // Angular routes for front end logic 32 | │ ├── services/ // Angular services 33 | │ ├── tests/ // Mocha tests 34 | │ ├── views/ // HTML 35 | ├── server/ // Holds all backend files 36 | │ ├── controllers/ // Backend controllers 37 | │ ├── routes/ // Express routes 38 | 39 | 40 | ``` 41 | 42 | ### Code Flow and API 43 | In order to help you understand the folder structure we will walk through how data flows through the application logic for a specific use. 44 | For detailed API information please refer to the [api readme](api.md). 45 |
46 | When a user posts a message on the tree profile page -
47 | 'SubmitMessage' is invoked in packages/articles/public/controllers/MessagesController.js
48 | 'SubmitMessage' uses the the 'Messages' factory in packages/articles/public/services/articles.js
49 | A post request is submitted to /usermessages
50 | Express routes the request via packages/articles/server/routes/MessagesController.js to 'postMessagesFromUser'
51 | In packages/articles/server/controllers/articles.js 'postMessagesFromUser' inserts the message into the database
52 | 
53 | 54 | 55 | 56 | ## Environmental Vars 57 | Create a .profile bash script that exports the following variables 58 | ``` 59 | #!/bin/sh 60 | export TBSECRET="" #Secret Key for cookies 61 | export APPLICATIONID="" #ChatBot API 62 | export POSTGRES="" #SQL DB with tree data 63 | export AZURE_STORAGE_ACCOUNT="" #Storage for image uploads 64 | export AZURE_STORAGE_ACCESS_KEY="" #Storage for image uploads 65 | export TBPASS="" #Email for reset password 66 | export TBEMAIL="" #Email for reset password 67 | export NODE_ENV="DEVELOPMENT" #For local development only 68 | ``` 69 | 70 | 71 | ## License 72 | We believe that mean should be free and easy to integrate within your existing projects so we chose [The MIT License](http://opensource.org/licenses/MIT) 73 | 74 | ## ToDo 75 | - Refactor much of the code from 'articles' to 'trees' 76 | - Build admin functionality. Mean.io has the capabilities to set admin users already. 77 | - Implement tests. Although testing is currently implemented only very basic tests have been implemented. 78 | - Move certain logic out of 'articles' folder. This folder holds a lot of excess logic that ideally would be placed elsewhere. I.e. messages functionality moved to a new 'messages' folder. 79 | - Styling improvements. 80 | - Optimizations. Currently there are a significant number of angular watchers on each profile page that could potentially be removed. -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- 1 | #TreeBook API 2 | 3 | ##TREE DATA 4 |
 5 | /articles               GET  (PG) get some Tree Data for 250 trees
 6 | /articles/:treeId       GET  (PG) gets a tree data by ID
 7 | /treeimage/:treeId      GET  (PG) gets tree imgurl by ID
 8 | 
 9 | /articles/new           POST (PG) adds a new tree to the database
10 | 
11 | 12 | ##MESSAGES (Trees/Users) 13 |
14 | /usermessages           POST (PG) inserts a message made by a user
15 | /usermessages/:userId   GET  (PG) gets all messages made by a user
16 | 
17 | /treemessages           POST (PG) inserts a message made by a tree (bot)
18 | /treemessages/:treeId   GET  (PG) gets all messages made by a tree
19 | 
20 | 21 | ##USER DATA 22 |
23 | 
24 | /user/:username         GET  (mongo) fetch a user's document based on username
25 | /user/:username/status  POST (mongo) insert a status update in the user's document
26 | 
27 | /userimage              POST (mongo) upsert user imageurl in db
28 | /userimage/*            GET  (mongo) fetch user's profile imgurl
29 | 
30 | Note: Actual profile picture is stored in AzureCDN
31 | 
32 | 33 | ##LIKES 34 |
35 | /treelike               POST (PG) inserts into likes table a userid/treeid
36 | /treelikes              POST (PG) queries DB for params given, 
37 |                                     returns list of users which like a tree
38 | /userlikes              POST (PG) queries DB for params given,
39 |                                     returns list of trees a user likes
40 | 
41 | 42 | ##Search 43 |
44 | /searchbyloc            GET (PG) gets a list of trees near a given coordinate
45 | /searchbyname/:search   GET (PG) gets a list of 250 trees that match the query
46 | 
47 | 48 | 49 | ##Example Request Control Flow 50 | User gets tree page by id 51 | 52 | 1. articles/controllers 53 | * TreesController.js > findOne 54 | 55 | 2. articles/services 56 | * articles.js['TreeData'] > ['Trees'] > getTree 57 | * automatically passes id of tree to method 58 | * get request for article/:treeID 59 | 60 | 3. server/routes 61 | * articles.js > hits articles/:treeID resource 62 | 63 | 4. server/controllers 64 | * articles.js > hits getTreeData 65 | * connects to pg. and selects name, id, species.. etc 66 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mean", 3 | "dependencies": { 4 | "jquery": "1.x", 5 | "angular": "1.3.2", 6 | "angular-resource": "1.3.2", 7 | "angular-cookies": "1.3.2", 8 | "angular-mocks": "1.3.2", 9 | "angular-route": "1.3.2", 10 | "bootstrap": "3.1.1", 11 | "angular-bootstrap": "0.11.0", 12 | "angular-ui-router": "#master", 13 | "web-bootstrap": "./node_modules/meanio/resources/web-bootstrap.js", 14 | "underscore": "~1.8.2", 15 | "angular-google-maps": "~2.0.16", 16 | "ng-file-upload": "~3.2.4", 17 | "ng-file-upload-shim": "~3.2.4", 18 | "firebase": "~2.2.3", 19 | "angularfire": "~1.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /config/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": { 3 | "css": { 4 | "bower_components/build/css/dist.min.css": [] 5 | }, 6 | "js": { 7 | "bower_components/build/js/dist.min.js": [ 8 | "bower_components/jquery/dist/jquery.min.js", 9 | "bower_components/angular/angular.min.js", 10 | "bower_components/angular-mocks/angular-mocks.js", 11 | "bower_components/angular-cookies/angular-cookies.min.js", 12 | "bower_components/angular-resource/angular-resource.min.js", 13 | "bower_components/angular-ui-router/release/angular-ui-router.min.js", 14 | "bower_components/angular-bootstrap/ui-bootstrap-tpls.js", 15 | "bower_components/web-bootstrap/index.js", 16 | 17 | "bower_components/underscore/underscore.js", 18 | "bower_components/lodash/lodash.underscore.js", 19 | 20 | "bower_components/ng-file-upload-shim/angular-file-upload-shim.min.js", 21 | "bower_components/ng-file-upload-shim/angular-file-upload.min.js", 22 | 23 | "maps.googleapis.com/maps/api/js?sensor=false", 24 | "bower_components/angular-google-maps/dist/angular-google-maps.js", 25 | "bower_components/firebase/firebase.js", 26 | "bower_components/angularfire/dist/angularfire.min.js" 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /config/env/all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | rootPath = path.normalize(__dirname + '/../..'); 5 | 6 | module.exports = { 7 | root: rootPath, 8 | http: { 9 | port: process.env.PORT || 3000 10 | }, 11 | https: { 12 | port: false, 13 | 14 | // Paths to key and cert as string 15 | ssl: { 16 | key: '', 17 | cert: '' 18 | } 19 | }, 20 | hostname: process.env.HOST || process.env.HOSTNAME, 21 | //db: 'mongodb://treebookuser:7777jjjj@ds045107.mongolab.com:45107/treebook', 22 | db: 'mongodb://' + (process.env.MONGODB || 'localhost'), 23 | templateEngine: 'swig', 24 | 25 | // The secret should be set to a non-guessable string that 26 | // is used to compute a session hash 27 | sessionSecret: process.env.TBSECRET, 28 | 29 | // The name of the MongoDB collection to store sessions in 30 | sessionCollection: 'sessions', 31 | 32 | // The session cookie settings 33 | sessionCookie: { 34 | path: '/', 35 | httpOnly: true, 36 | // If secure is set to true then it will cause the cookie to be set 37 | // only when SSL-enabled (HTTPS) is used, and otherwise it won't 38 | // set a cookie. 'true' is recommended yet it requires the above 39 | // mentioned pre-requisite. 40 | secure: false, 41 | // Only set the maxAge to null if the cookie shouldn't be expired 42 | // at all. The cookie will expunge when the browser is closed. 43 | maxAge: null 44 | }, 45 | 46 | // The session cookie name 47 | sessionName: 'connect.sid' 48 | }; 49 | -------------------------------------------------------------------------------- /config/env/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | db: 'mongodb://' + (process.env.MONGODB || 'localhost') + '/mean-dev', 5 | //db: 'mongodb://treebookuser:7777jjjj@ds045107.mongolab.com:45107/treebook', 6 | debug: true, 7 | // aggregate: 'whatever that is not false, because boolean false value turns aggregation off', //false 8 | aggregate: false, 9 | mongoose: { 10 | debug: false 11 | }, 12 | app: { 13 | name: 'TreeBook' 14 | }, 15 | facebook: { 16 | clientID: 'DEFAULT_APP_ID', 17 | clientSecret: 'APP_SECRET', 18 | callbackURL: 'http://localhost:3000/auth/facebook/callback' 19 | }, 20 | twitter: { 21 | clientID: 'DEFAULT_CONSUMER_KEY', 22 | clientSecret: 'CONSUMER_SECRET', 23 | callbackURL: 'http://localhost:3000/auth/twitter/callback' 24 | }, 25 | github: { 26 | clientID: 'DEFAULT_APP_ID', 27 | clientSecret: 'APP_SECRET', 28 | callbackURL: 'http://localhost:3000/auth/github/callback' 29 | }, 30 | google: { 31 | clientID: 'DEFAULT_APP_ID', 32 | clientSecret: 'APP_SECRET', 33 | callbackURL: 'http://localhost:3000/auth/google/callback' 34 | }, 35 | linkedin: { 36 | clientID: 'DEFAULT_API_KEY', 37 | clientSecret: 'SECRET_KEY', 38 | callbackURL: 'http://localhost:3000/auth/linkedin/callback' 39 | }, 40 | emailFrom: process.env.TBEMAIL, // sender address like ABC 41 | mailer: { 42 | service: 'gmail', // Gmail, SMTP 43 | auth: { 44 | user: process.env.TBEMAIL, 45 | pass: process.env.TBPASS 46 | } 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | db: 'mongodb://' + (process.env.MONGODB || 'localhost') + '/mean-prod', 5 | //db: "mongodb://treebookuser:7777jjjj@ds045107.mongolab.com:45107/treebook", 6 | /** 7 | * Database options that will be passed directly to mongoose.connect 8 | * Below are some examples. 9 | * See http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html#mongoclient-connect-options 10 | * and http://mongoosejs.com/docs/connections.html for more information 11 | */ 12 | dbOptions: { 13 | /* 14 | server: { 15 | socketOptions: { 16 | keepAlive: 1 17 | }, 18 | poolSize: 5 19 | }, 20 | replset: { 21 | rs_name: 'myReplicaSet', 22 | poolSize: 5 23 | }, 24 | db: { 25 | w: 1, 26 | numberOfRetries: 2 27 | } 28 | */ 29 | }, 30 | app: { 31 | name: 'MEAN - A Modern Stack - Production' 32 | }, 33 | facebook: { 34 | clientID: 'APP_ID', 35 | clientSecret: 'APP_SECRET', 36 | callbackURL: 'http://localhost:3000/auth/facebook/callback' 37 | }, 38 | twitter: { 39 | clientID: 'CONSUMER_KEY', 40 | clientSecret: 'CONSUMER_SECRET', 41 | callbackURL: 'http://localhost:3000/auth/twitter/callback' 42 | }, 43 | github: { 44 | clientID: 'APP_ID', 45 | clientSecret: 'APP_SECRET', 46 | callbackURL: 'http://localhost:3000/auth/github/callback' 47 | }, 48 | google: { 49 | clientID: 'APP_ID', 50 | clientSecret: 'APP_SECRET', 51 | callbackURL: 'http://localhost:3000/auth/google/callback' 52 | }, 53 | linkedin: { 54 | clientID: 'API_KEY', 55 | clientSecret: 'SECRET_KEY', 56 | callbackURL: 'http://localhost:3000/auth/linkedin/callback' 57 | }, 58 | emailFrom: 'SENDER EMAIL ADDRESS', // sender address like ABC 59 | mailer: { 60 | service: 'SERVICE_PROVIDER', 61 | auth: { 62 | user: 'EMAIL_ID', 63 | pass: 'PASSWORD' 64 | } 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /config/env/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | //db: 'mongodb://' + (process.env.MONGODB || 'localhost') + '/mean-test', 5 | db: 'mongodb://treebookuser:7777jjjj@ds045107.mongolab.com:45107/treebook', 6 | http: { 7 | port: 3001 8 | }, 9 | app: { 10 | name: 'MEAN - A Modern Stack - Test' 11 | }, 12 | facebook: { 13 | clientID: 'APP_ID', 14 | clientSecret: 'APP_SECRET', 15 | callbackURL: 'http://localhost:3000/auth/facebook/callback' 16 | }, 17 | twitter: { 18 | clientID: 'CONSUMER_KEY', 19 | clientSecret: 'CONSUMER_SECRET', 20 | callbackURL: 'http://localhost:3000/auth/twitter/callback' 21 | }, 22 | github: { 23 | clientID: 'APP_ID', 24 | clientSecret: 'APP_SECRET', 25 | callbackURL: 'http://localhost:3000/auth/github/callback' 26 | }, 27 | google: { 28 | clientID: 'APP_ID', 29 | clientSecret: 'APP_SECRET', 30 | callbackURL: 'http://localhost:3000/auth/google/callback' 31 | }, 32 | linkedin: { 33 | clientID: 'API_KEY', 34 | clientSecret: 'SECRET_KEY', 35 | callbackURL: 'http://localhost:3000/auth/linkedin/callback' 36 | }, 37 | emailFrom: 'SENDER EMAIL ADDRESS', // sender address like ABC 38 | mailer: { 39 | service: 'SERVICE_PROVIDER', 40 | auth: { 41 | user: 'EMAIL_ID', 42 | pass: 'PASSWORD' 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /config/express.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mean = require('meanio'), 7 | compression = require('compression'), 8 | morgan = require('morgan'), 9 | consolidate = require('consolidate'), 10 | express = require('express'), 11 | helpers = require('view-helpers'), 12 | flash = require('connect-flash'), 13 | config = mean.loadConfig(); 14 | 15 | module.exports = function(app, db) { 16 | 17 | app.set('showStackError', true); 18 | 19 | // Prettify HTML 20 | app.locals.pretty = true; 21 | 22 | // cache=memory or swig dies in NODE_ENV=production 23 | app.locals.cache = 'memory'; 24 | 25 | // Should be placed before express.static 26 | // To ensure that all assets and data are compressed (utilize bandwidth) 27 | app.use(compression({ 28 | // Levels are specified in a range of 0 to 9, where-as 0 is 29 | // no compression and 9 is best compression, but slowest 30 | level: 9 31 | })); 32 | 33 | // Enable compression on bower_components 34 | app.use('/bower_components', express.static(config.root + '/bower_components')); 35 | 36 | // Only use logger for development environment 37 | if (process.env.NODE_ENV === 'development') { 38 | app.use(morgan('dev')); 39 | } 40 | 41 | // assign the template engine to .html files 42 | app.engine('html', consolidate[config.templateEngine]); 43 | 44 | // set .html as the default extension 45 | app.set('view engine', 'html'); 46 | 47 | 48 | // Dynamic helpers 49 | app.use(helpers(config.app.name)); 50 | 51 | // Connect flash for flash messages 52 | app.use(flash()); 53 | }; 54 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Karma configuration 4 | module.exports = function(config) { 5 | var _ = require('lodash'), 6 | basePath = '.', 7 | assets = require(basePath + '/config/assets.json'); 8 | 9 | config.set({ 10 | 11 | // base path, that will be used to resolve files and exclude 12 | basePath: basePath, 13 | 14 | // frameworks to use 15 | frameworks: ['jasmine'], 16 | 17 | // list of files / patterns to load in the browser 18 | files: _.flatten(_.values(assets.core.js)).concat([ 19 | 'packages/*/public/*.js', 20 | 'packages/*/public/*/*.js' 21 | ]), 22 | 23 | // list of files to exclude 24 | exclude: [], 25 | 26 | // test results reporter to use 27 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 28 | reporters: ['progress', 'coverage'], 29 | 30 | // coverage 31 | preprocessors: { 32 | // source files that you want to generate coverage for 33 | // do not include tests or libraries 34 | // (these files will be instrumented by Istanbul) 35 | 'packages/*/public/controllers/*.js': ['coverage'], 36 | 'packages/*/public/services/*.js': ['coverage'] 37 | }, 38 | 39 | coverageReporter: { 40 | type: 'html', 41 | dir: 'test/coverage/' 42 | }, 43 | 44 | // web server port 45 | port: 9876, 46 | // Look for server on port 3001 (invoked by mocha) - via @brownman 47 | proxies: { 48 | '/': 'http://localhost:3001/' 49 | }, 50 | 51 | // enable / disable colors in the output (reporters and logs) 52 | colors: true, 53 | 54 | // level of logging 55 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 56 | logLevel: config.LOG_DEBUG, 57 | 58 | // enable / disable watching file and executing tests whenever any file changes 59 | autoWatch: true, 60 | 61 | // Start these browsers, currently available: 62 | // - Chrome 63 | // - ChromeCanary 64 | // - Firefox 65 | // - Opera 66 | // - Safari (only Mac) 67 | // - PhantomJS 68 | // - IE (only Windows) 69 | browsers: ['PhantomJS'], 70 | 71 | // If browser does not capture in given timeout [ms], kill it 72 | captureTimeout: 60000, 73 | 74 | // Continuous Integration mode 75 | // if true, it capture browsers, run tests and exit 76 | singleRun: true 77 | }); 78 | }; 79 | -------------------------------------------------------------------------------- /mean.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": {} 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TreeBook", 3 | "description": "MEAN.io: A fullstack JavaScript framework powered by MongoDB, ExpressJS, AngularJS, NodeJS.", 4 | "version": "0.4.3", 5 | "private": false, 6 | "author": "Linnovate ", 7 | "contributors": "https://github.com/linnovate/mean/graphs/contributors", 8 | "mean": "0.4.3", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/linnovate/mean.git" 12 | }, 13 | "engines": { 14 | "node": "0.10.x", 15 | "npm": "1.3.x" 16 | }, 17 | "scripts": { 18 | "start": "node server", 19 | "mocha": "node node_modules/.bin/mocha packages/**/server/tests/**/*.js -R spec -r tools/test/mocha-req.js", 20 | "karma": "node node_modules/karma/bin/karma start", 21 | "test": "grunt test", 22 | "postinstall": "node node_modules/meanio/node_modules/mean-cli/bin/mean-postinstall" 23 | }, 24 | "dependencies": { 25 | "angularfire": "^1.0.0", 26 | "assetmanager": "1.1.2", 27 | "async": "^0.9.0", 28 | "azure-storage": "^0.4.3", 29 | "body-parser": "1.10.0", 30 | "compression": "1.2.1", 31 | "connect-flash": "0.1.1", 32 | "consolidate": "0.10.0", 33 | "cookie-parser": "1.3.3", 34 | "errorhandler": "1.3.0", 35 | "express": "4.11.1", 36 | "firebase": "^2.2.3", 37 | "forever": "0.11.1", 38 | "gridfs-stream": "0.5.3", 39 | "grunt-cli": "^0.1.13", 40 | "grunt-concurrent": "^1.0.0", 41 | "grunt-contrib-clean": "^0.6.0", 42 | "grunt-contrib-csslint": "^0.3.1", 43 | "grunt-contrib-cssmin": "^0.10.0", 44 | "grunt-contrib-jshint": "^0.10.0", 45 | "grunt-contrib-uglify": "^0.5.1", 46 | "grunt-contrib-watch": "^0.6.1", 47 | "grunt-env": "^0.4.1", 48 | "grunt-hook": "~0.3.0", 49 | "grunt-nodemon": "^0.3.0", 50 | "load-grunt-tasks": "^0.6.0", 51 | "lodash": "2.4.1", 52 | "meanio": "linnovate/meanio", 53 | "mongodb": "1.4.28", 54 | "mongoose": "3.8.21", 55 | "morgan": "1.5.0", 56 | "ms": "0.6.2", 57 | "multer": "^0.1.8", 58 | "ng-file-upload": "^3.2.5", 59 | "nodemailer": "1.2.2", 60 | "passport-facebook": "1.0.3", 61 | "passport-github": "0.1.5", 62 | "passport-google-oauth": "0.1.5", 63 | "passport-linkedin": "0.1.3", 64 | "passport-local": "1.0.0", 65 | "passport-twitter": "1.0.2", 66 | "pg": "^4.3.0", 67 | "request": "^2.53.0", 68 | "serve-favicon": "2.2.0", 69 | "socket.io": "^1.3.5", 70 | "swig": "1.4.2", 71 | "time-grunt": "^1.0.0", 72 | "view-helpers": "0.1.5", 73 | "xml2js": "^0.4.6" 74 | }, 75 | "devDependencies": { 76 | "expect.js": "0.3.1", 77 | "grunt-karma": "^0.8.2", 78 | "grunt-mocha-test": "^0.10.2", 79 | "karma": "0.12.28", 80 | "karma-chrome-launcher": "0.1.7", 81 | "karma-coffee-preprocessor": "0.2.1", 82 | "karma-coverage": "0.2.7", 83 | "karma-firefox-launcher": "0.1.3", 84 | "karma-html2js-preprocessor": "0.1.0", 85 | "karma-jasmine": "0.2.3", 86 | "karma-ng-html2js-preprocessor": "0.1.2", 87 | "karma-ng-scenario": "0.1.0", 88 | "karma-phantomjs-launcher": "0.1.4", 89 | "karma-requirejs": "0.2.2", 90 | "karma-script-launcher": "0.1.0", 91 | "mocha": "2.1.0", 92 | "requirejs": "2.1.15", 93 | "supertest": "0.11.0", 94 | "del": "^0.1.3", 95 | "gulp": "^3.8.8", 96 | "gulp-concat": "^2.4.1", 97 | "gulp-csslint": "^0.1.5", 98 | "gulp-cssmin": "^0.1.6", 99 | "gulp-jshint": "^1.8.5", 100 | "gulp-less": "^1.3.6", 101 | "gulp-livereload": "^2.1.1", 102 | "gulp-load-plugins": "^0.7.0", 103 | "gulp-mocha": "^1.1.0", 104 | "gulp-nodemon": "^1.0.4", 105 | "gulp-rimraf": "^0.1.1", 106 | "gulp-uglify": "^1.0.1", 107 | "gulp-util": "^3.0.1", 108 | "jshint-stylish": "^1.0.0", 109 | "through": "^2.3.6" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /packages/articles/README.md: -------------------------------------------------------------------------------- 1 | README: articles 2 | -------------------------------------------------------------------------------- /packages/articles/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * Defining the Package 5 | */ 6 | var Module = require('meanio').Module; 7 | 8 | var Articles = new Module('articles'); 9 | 10 | /* 11 | * All MEAN packages require registration 12 | * Dependency injection is used to define required modules 13 | */ 14 | Articles.register(function(app, auth, database) { 15 | 16 | //We enable routing. By default the Package Object is passed to the routes 17 | Articles.routes(app, auth, database); 18 | 19 | //We are adding a link to the main menu for all authenticated users 20 | Articles.menus.add({ 21 | 'roles': ['authenticated'], 22 | 'title': 'Browse Trees', 23 | 'link': 'all trees' 24 | }); 25 | 26 | 27 | //Articles.aggregateAsset('js','/packages/system/public/services/menus.js', {group:'footer', absolute:true, weight:-9999}); 28 | //Articles.aggregateAsset('js', 'test.js', {group: 'footer', weight: -1}); 29 | 30 | /* 31 | //Uncomment to use. Requires meanio@0.3.7 or above 32 | // Save settings with callback 33 | // Use this for saving data from administration pages 34 | Articles.settings({'someSetting':'some value'},function (err, settings) { 35 | //you now have the settings object 36 | }); 37 | 38 | // Another save settings example this time with no callback 39 | // This writes over the last settings. 40 | Articles.settings({'anotherSettings':'some value'}); 41 | 42 | // Get settings. Retrieves latest saved settings 43 | Articles.settings(function (err, settings) { 44 | //you now have the settings object 45 | }); 46 | */ 47 | Articles.aggregateAsset('css', 'articles.css'); 48 | 49 | return Articles; 50 | }); 51 | -------------------------------------------------------------------------------- /packages/articles/mean.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "users": "latest" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/articles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "articles", 3 | "version": "0.0.1", 4 | "description": "Articles example package", 5 | "author": { 6 | "name": "Linnovate" 7 | }, 8 | "mean": "0.4.x", 9 | "engines": { 10 | "node": "0.10.x", 11 | "npm": "1.4.x" 12 | }, 13 | "dependencies": {}, 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /packages/articles/public/assets/css/articles.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-align: center 3 | } 4 | ul.articles li:not(:last-child) { 5 | border-bottom: 1px solid #ccc 6 | } 7 | 8 | .listitem{ 9 | border: 2px black solid; 10 | border-radius: 15px; 11 | } 12 | 13 | .angular-google-map-container { 14 | position: absolute; 15 | top: 0; 16 | bottom: 0; 17 | right: 0; 18 | left: 0; 19 | } 20 | 21 | .tree-in-treeview { 22 | width:115px; 23 | height: 115px; 24 | overflow:hidden; 25 | float: right; 26 | } 27 | 28 | .tree-thumb-listview { 29 | width: 100%; 30 | } 31 | 32 | .tree-row-listview { 33 | margin: 0 0 10px 0; 34 | } 35 | 36 | .species { font-size: 1.2em; } 37 | 38 | .profile-image-div { 39 | width: 300px; 40 | height: 225px; 41 | overflow: hidden; 42 | } 43 | 44 | .profile-tree-image { 45 | width: 100%; 46 | height: auto; 47 | } 48 | 49 | .profile-user-image { 50 | width: 100%; 51 | height: auto; 52 | } 53 | 54 | .navbar-inverse { 55 | /* fallback */ 56 | background-color: #9EBD2E; 57 | background: url(images/linear_bg_2.png); 58 | background-repeat: repeat-x; 59 | 60 | /* Safari 4-5, Chrome 1-9 */ 61 | background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#9EBD2E), to(#83857A)); 62 | 63 | /* Safari 5.1, Chrome 10+ */ 64 | background: -webkit-linear-gradient(top, #9EBD2E, #83857A); 65 | 66 | /* Firefox 3.6+ */ 67 | background: -moz-linear-gradient(top, #9EBD2E, #83857A); 68 | 69 | /* IE 10 */ 70 | background: -ms-linear-gradient(top, #9EBD2E, #83857A); 71 | 72 | /* Opera 11.10+ */ 73 | background: -o-linear-gradient(top, #9EBD2E, #83857A); 74 | } 75 | 76 | .navbar-inverse .navbar-brand, .navbar-inverse .navbar-nav > li > a { 77 | color: #fff; 78 | text-shadow: 1px 1px 2px rgba(150, 150, 150, 0.8); 79 | } 80 | 81 | .message-button { 82 | float:right; 83 | } 84 | 85 | .textarea-holder { 86 | width: 805px; 87 | } 88 | 89 | .thumb-container { height: 100%; } 90 | 91 | .well-user { 92 | min-height: 20px; 93 | padding: 0 19px 19px 19px; 94 | margin-bottom: 20px; 95 | margin-top: 10px; 96 | background-color: #f5f5f5; 97 | border: 1px solid #e3e3e3; 98 | border-radius: 4px; 99 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); 100 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); 101 | } 102 | 103 | .profile-link { 104 | color: #fff; 105 | text-shadow: 1px 1px 2px rgba(150, 150, 150, 0.8); 106 | padding: 15px; 107 | height: 100%; 108 | } 109 | 110 | .profile-link:hover, .profile-link:active, .profile-link:visited, .profile-link:focus { 111 | color: #fff; 112 | background-color: transparent; 113 | text-decoration: none; 114 | } 115 | 116 | .no-top-margin { 117 | margin-top: 0; 118 | } 119 | 120 | .tree-name { 121 | display: inline-block; 122 | margin-right: 25px; 123 | } 124 | 125 | .tree-info { 126 | margin-bottom: 5px; 127 | } 128 | -------------------------------------------------------------------------------- /packages/articles/public/assets/img/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/articles/public/assets/img/logo1.png -------------------------------------------------------------------------------- /packages/articles/public/controllers/AddTreeController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.articles') 4 | 5 | .controller('AddTreeController', ['$scope', 6 | /* 7 | * @param #scope 8 | * 9 | * 10 | * 11 | */ 12 | function ($scope) { 13 | $scope.tree = {}; 14 | $scope.addTree = function (tree) { 15 | console.log(tree); 16 | }; 17 | } 18 | 19 | ]); -------------------------------------------------------------------------------- /packages/articles/public/controllers/MapViewController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Controller that handle the map display, get the lat and lng data and 5 | * display the map on the profile.html 6 | * currently need maker to be added 7 | */ 8 | angular.module('mean.articles', ['uiGmapgoogle-maps', 'angularFileUpload']) 9 | 10 | /** 11 | * Configure tha google map api 12 | */ 13 | .config(['uiGmapGoogleMapApiProvider', function(uiGmapGoogleMapApi){ 14 | uiGmapGoogleMapApi.configure({ 15 | //provide api key if available 16 | v: '3.18', 17 | libraries: 'geometry, visualization, places' 18 | }); 19 | }]) 20 | 21 | /** 22 | * MapView Controller to handle the MapView on ProfilePage 23 | */ 24 | .controller('MapViewController', ['$scope', '$q', 'uiGmapGoogleMapApi', 'TreeData', 'Search', 25 | /** 26 | * 27 | * @param $scope 28 | * @param $q 29 | * @param uiGmapGoogleMapApi 30 | * @param TreeData 31 | * @param Search 32 | */ 33 | function($scope, $q, uiGmapGoogleMapApi, TreeData, Search) { 34 | $scope.resolved = false; 35 | 36 | //Promise that retrive the near by tree data based on the location 37 | var searchNearTrees = function(center){ 38 | $scope.nearTrees = []; 39 | return $q.when(center).then(function(center){ 40 | Search.getNearTrees().get(center, function(results){ 41 | for(var i = 0, size = results.length; i < size; i = i + 1){ 42 | var tmp = {}; 43 | tmp.id = i; 44 | tmp.coords = {latitude: results[i].latitude, longitude: results[i].longitude}; 45 | tmp.options = { draggable: false }; 46 | if(tmp.coords.latitude === center.latitude && tmp.coords.longitude === center.longitude){ 47 | tmp.options = { animation: 1, draggable: false }; 48 | } 49 | $scope.nearTrees.push(tmp); 50 | } 51 | }); 52 | }); 53 | }; 54 | /** 55 | * Promise assign the latitude and longitude to the $scope, $scope.resolved is used for the ng-if. 56 | * @param data 57 | * @returns {*} 58 | */ 59 | var onLoad = function(data){ 60 | return $q.when(data).then(function(data){ 61 | var mapCenter = { 62 | latitude: data.latitude, 63 | longitude: data.longitude 64 | }; 65 | $scope.map = {center: mapCenter, zoom: 20 }; 66 | searchNearTrees(mapCenter).then(function(){ 67 | // console.log('Get the near trees and markers'); 68 | 69 | //Changed to resolve once all data are loaded 70 | $scope.resolved = true; 71 | }); 72 | }); 73 | }; 74 | 75 | //Load the tree 76 | TreeData.getTree().$promise.then(function(tree){ 77 | onLoad(tree); 78 | }); 79 | } 80 | ]); 81 | -------------------------------------------------------------------------------- /packages/articles/public/controllers/MessagesController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.articles') 4 | 5 | 6 | /** 7 | * Handles sumbit message and get all messages on tree profile page 8 | */ 9 | .controller('MessagesController', ['$scope', 'Messages', 'Global', 'GetMessages', 'TreeData', '$stateParams', 'UserImage', '$timeout', 10 | /** 11 | * 12 | * @param $scope 13 | * @param Messages 14 | * @param Global 15 | * @param GetMessages 16 | * @param TreeData 17 | * @param $stateParams 18 | * @param UserImage 19 | * @param $timeout 20 | */ 21 | function($scope, Messages, Global, GetMessages, TreeData, $stateParams, UserImage, $timeout) { 22 | 23 | // $scope.global is necissary to get user information 24 | $scope.global = Global; 25 | // the TreeData factory handles the basic tree data. It is in the services folder. 26 | var treeMessages = []; 27 | 28 | /** 29 | * 30 | * @param url 31 | * @param name 32 | * @param treeId 33 | */ 34 | var setParams = function(url, name, treeId) { 35 | treeMessages.forEach(function(message) { 36 | message.imageUrl = url; 37 | message.username = name; 38 | message.redirect = '#!/trees/' + treeId; 39 | }); 40 | }; 41 | 42 | $scope.tree = TreeData.getTree(function(t) { 43 | console.log(t.name); 44 | setParams(t.imageurl, t.name, t.treeid); 45 | }); 46 | 47 | 48 | /** 49 | * Post message to database from single tree profile view 50 | */ 51 | $scope.submitMessage = function() { 52 | var message = $scope.inputMessage; 53 | var username = $scope.global.user.username; 54 | var treeid = $scope.tree.treeid; 55 | var body = { 56 | message: message, 57 | username: username, 58 | treeid: treeid 59 | }; 60 | 61 | // Messages is a factory that in services/articles.js 62 | Messages.save(body, function(data) { 63 | console.log(data[0]); 64 | var newMessage = data[0]; 65 | console.log(newMessage.createdat); 66 | //change date format for each message to readable format 67 | var date = new Date(newMessage.createdat); 68 | var options = { 69 | weekday: 'long', year: 'numeric', month: 'short', 70 | day: 'numeric', hour: '2-digit', minute: '2-digit' 71 | }; 72 | newMessage.createdat = date.toLocaleDateString('en-us', options); 73 | //async load new message to DOM. Loads to end of message list 74 | $scope.messages.push(newMessage); 75 | 76 | // UserImage is a factory in services/articles.js 77 | UserImage.loadUserImage(newMessage.username, function(url) { 78 | console.log(url, 'here'); 79 | newMessage.imageUrl = url; 80 | }); 81 | 82 | $scope.inputMessage = ''; 83 | }); 84 | }; 85 | 86 | /** 87 | * get All messages for a Tree and display on tree profile page 88 | */ 89 | $scope.getMessages = function() { 90 | // GetMessage is a factory in services/articles.js 91 | GetMessages.get({treeid: $stateParams.treeId}, function(messages) { 92 | $scope.messages = messages; 93 | $scope.messages.forEach(function(message) { 94 | message.redirect = '#!/user/' + message.username; 95 | // UserImage is a factory in services/articles.js 96 | // It is called for each message to get the url of the users picture 97 | UserImage.loadUserImage(message.username, function(url) { 98 | if (url.length === 0) { 99 | // This is a treemesage 100 | message.isTree = true; 101 | treeMessages.push(message); 102 | } else { 103 | message.isTree = false; // unused 104 | } 105 | message.imageUrl = url; 106 | }); 107 | }); 108 | }); 109 | }; 110 | 111 | }]); 112 | -------------------------------------------------------------------------------- /packages/articles/public/controllers/TreesController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.articles') 4 | 5 | 6 | /** 7 | * Controller to handle getting tree data 8 | */ 9 | .controller('TreesController', ['$scope', '$resource', '$stateParams', 'Trees', 'TreeData', 'Search', 'Global', 'Likes', 10 | /** 11 | * 12 | * @param $scope 13 | * @param $stateParams 14 | * @param $resource 15 | * @param Trees 16 | * @param TreeData 17 | * @param Search 18 | * @param Global 19 | * @param Likes 20 | */ 21 | function($scope, $stateParams, $resource, Trees, TreeData, Search, Global, Likes) { 22 | $scope.likes = []; 23 | // anyLikes is a boolean used in ng-if to show the like box 24 | $scope.anyLikes = false; 25 | /** 26 | * Helper method to save a like when a user likes a tree 27 | */ 28 | $scope.like = function() { 29 | $scope.global = Global; 30 | var context = $scope.getLikes; 31 | // Likes is a factory found in services/articles.js 32 | Likes.saveLike($scope.global.user.username, $scope.tree.treeid, function() { 33 | // callback to call get likes after adding a like 34 | context(); 35 | }); 36 | }; 37 | 38 | /** 39 | * Helper methods to call TreeData service with to get a specific trees data. 40 | */ 41 | $scope.findOne = function() { 42 | // TreeData is a factory in services/articles.js 43 | // It determines the tree by looking at the $stateparams 44 | TreeData.getTree().$promise.then(function(tree) { 45 | $scope.tree = tree; 46 | $scope.getLikes(); 47 | }); 48 | }; 49 | 50 | /** 51 | * Helper method to get all likes 52 | */ 53 | $scope.getLikes = function() { 54 | // Likes is a factory in services/articles.js 55 | Likes.getLikes($scope.tree.treeid, function(likes) { 56 | $scope.likes = likes; 57 | if (likes.length !== 0) { 58 | console.log(likes); 59 | $scope.anyLikes = true; 60 | } 61 | }); 62 | }; 63 | } 64 | ]); 65 | -------------------------------------------------------------------------------- /packages/articles/public/controllers/paginationController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.articles') 4 | 5 | /** 6 | * Handles Pagination on list page 7 | */ 8 | .controller('PaginationDemoCtrl', ['$scope', '$state', 'Trees', 'Search', '$http', 9 | /** 10 | * 11 | * @param $scope 12 | * @param $state 13 | * @param Trees 14 | * @param Search 15 | */ 16 | function($scope, $state, Trees, Search, $http) { 17 | $scope.totalItems = 8; 18 | var itemsPerPage = 25; 19 | $scope.currentPage = 1; 20 | // $scope.trees is an array of arrays. Each subarray is one page which contains tree objects 21 | $scope.treees = []; 22 | $scope.newTree = {}; 23 | //'/articles/new' 24 | //944 Market St #8, San Francisco, CA 94102 25 | $scope.addTree = function(tree){ 26 | Search.getLocation(tree.address) 27 | .then(function (result) { 28 | var location = {}; 29 | location.latitude = result.lat(); 30 | location.longitude = result.lng(); 31 | tree.treeid = Math.floor(500000 + (Math.random() * 100000)); 32 | tree.location = location; 33 | tree.qspecies = 'Tree(s) ::'; 34 | console.log(tree); 35 | return tree; 36 | }) 37 | .then(function (tree){ 38 | return $http({ 39 | method: 'POST', 40 | url: 'articles/new', 41 | data: tree 42 | }) 43 | .then(function (result) { 44 | console.log('added new tree', result); 45 | }); 46 | }) 47 | .catch(function (err) { 48 | console.log(err); 49 | }); 50 | }; 51 | 52 | //Factor out the pagination function to be reused for all the methods 53 | /** 54 | * 55 | * @param trees 56 | */ 57 | var paginateTree = function(trees) { 58 | 59 | $scope.treees = [[]]; 60 | 61 | if (trees.length > 8) { 62 | $scope.totalItems = Math.ceil(trees.length / itemsPerPage * 8) ; 63 | for (var i = 0; i < $scope.totalItems; i = i + 1) { 64 | $scope.treees.push(trees.slice(i * itemsPerPage, (i + 1) * itemsPerPage)); 65 | } 66 | } else { 67 | $scope.totalItems = trees.length; 68 | var t = []; 69 | for (var j = 0; j < trees.length; j = j + 1) { 70 | t.push(trees[j]); 71 | } 72 | $scope.treees.push(t); 73 | } 74 | $scope.searchString = ''; 75 | }; 76 | 77 | // Search by name based on the search String, async promise 78 | /** 79 | * 80 | * @param searchString 81 | * @returns {*} 82 | */ 83 | var searchByName = function(searchString) { 84 | console.log('Search by name called'); 85 | searchString = searchString.toLowerCase(); 86 | searchString = searchString[0].toUpperCase() + searchString.slice(1); 87 | console.log(searchString); 88 | console.log($state.current.name); 89 | var body = {search: searchString}; 90 | return Search.getByName().get(body, function(results) { 91 | //add the results to the page 92 | return results; 93 | }); 94 | }; 95 | 96 | // Search by location based on the string, async promise 97 | /** 98 | * 99 | * @param lat 100 | * @param lng 101 | * @returns {*} 102 | */ 103 | var searchByLocation = function(lat, lng) { 104 | //Search by location 105 | var body = {longitude: lng, latitude: lat}; 106 | console.log('Search place called'); 107 | console.log($state.current.name); 108 | return Search.getNearTrees().get(body, function(results) { 109 | return results; 110 | }); 111 | }; 112 | 113 | // Helper method to call Trees factory to get all trees 114 | 115 | $scope.find = function() { 116 | console.log($state.current.name); 117 | Trees.query(function(trees) { 118 | $scope.trees = trees; 119 | paginateTree(trees); 120 | }); 121 | }; 122 | 123 | /** 124 | * function to handle the page setting 125 | */ 126 | $scope.setPage = function(pageNo) { 127 | $scope.currentPage = pageNo; 128 | }; 129 | 130 | // Search for the tree location based on the address typed in 131 | $scope.searchTrees = function() { 132 | var searchString = $scope.searchString; 133 | //location or the other 134 | Search.getLocation(searchString).then(function(location) { 135 | //Weird place, is a function to be called location.lat() 136 | var lat = location.lat(); 137 | var lng = location.lng(); 138 | if (lng <= -122.368107024455 && lng >= -122.511257155794 && lat <= 37.8103949467147 && lat >= 37.5090039879895) { 139 | searchByLocation(lat, lng).$promise.then(function(results) { 140 | paginateTree(results); 141 | }); 142 | } else { 143 | //search by name 144 | searchByName(searchString).$promise.then(function(results) { 145 | console.log(results); 146 | paginateTree(results); 147 | }); 148 | } 149 | }, function(status) { 150 | console.log(status + 'address failed'); 151 | //return a promise? 152 | searchByName(searchString).$promise.then(function(results) { 153 | paginateTree(results); 154 | }); 155 | } 156 | ); 157 | }; 158 | }]); 159 | -------------------------------------------------------------------------------- /packages/articles/public/routes/articles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('mean.articles').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Check if the user is connected 7 | var checkLoggedin = function($q, $timeout, $http, $location) { 8 | // Initialize a new promise 9 | var deferred = $q.defer(); 10 | 11 | // Make an AJAX call to check if the user is logged in 12 | $http.get('/loggedin').success(function(user) { 13 | // Authenticated 14 | if (user !== '0') $timeout(deferred.resolve); 15 | 16 | // Not Authenticated 17 | else { 18 | $timeout(deferred.reject); 19 | $location.url('/login'); 20 | } 21 | }); 22 | 23 | return deferred.promise; 24 | }; 25 | 26 | // states for my app 27 | $stateProvider 28 | .state('all trees', { 29 | url: '/trees', 30 | templateUrl: 'articles/views/list.html', 31 | controller: 'PaginationDemoCtrl', 32 | resolve: { 33 | loggedin: checkLoggedin, 34 | } 35 | }) 36 | .state('search', { 37 | url: '/trees/search', 38 | templateUrl: 'articles/views/list.html', 39 | controller: 'PaginationDemoCtrl', 40 | resolve: { 41 | loggedin: checkLoggedin, 42 | } 43 | }) 44 | .state('tree display', { 45 | url: '/about', 46 | templateUrl: 'articles/views/create.html', 47 | resolve: { 48 | loggedin: checkLoggedin 49 | } 50 | }) 51 | .state('tree profile page', { 52 | url: '/trees/profile', 53 | templateUrl: 'articles/views/profile.html', 54 | resolve: { 55 | loggedin: checkLoggedin 56 | } 57 | }) 58 | .state('add tree page', { 59 | url: '/trees/new', 60 | templateUrl: 'articles/views/new.html', 61 | resolve: { 62 | loggedin: checkLoggedin 63 | } 64 | }) 65 | .state('single tree display', { 66 | url: '/trees/:treeId', 67 | templateUrl: 'articles/views/profile.html', 68 | resolve: { 69 | loggedin: checkLoggedin 70 | } 71 | }) 72 | .state('user profile page', { 73 | url: '/user/:userId', 74 | templateUrl: 'users/views/userProfile.html', 75 | resolve: { 76 | loggedin: checkLoggedin 77 | } 78 | }); 79 | } 80 | ]); 81 | -------------------------------------------------------------------------------- /packages/articles/public/services/articles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * In this file there are numerous factories that are used to connect the controllers in public to the server. 5 | * These factories are used by the controllers and usually get picked up by the server in server/routes/articles.js 6 | * TODO: consolidate factories. For example we have 2 facotiries for messages (get and post), when ideally that would 7 | * be 1. 8 | */ 9 | 10 | 11 | angular.module('mean.articles') 12 | 13 | /** 14 | * Trees factory to handle the routing to the server for basic tree requests 15 | */ 16 | .factory('Trees', ['$resource', 17 | /** 18 | * 19 | * @param $resource 20 | * @returns {*} 21 | */ 22 | function($resource) { 23 | return $resource('articles/:treeId', { 24 | treeId: '@_id' 25 | }, { 26 | update: { 27 | method: 'GET' 28 | } 29 | }); 30 | } 31 | ]) 32 | 33 | /** 34 | * TreeData factory to expose the getTree method which gets tree data using Trees factory 35 | * There is a redundancy in this method as it is called by MapViewController and TreesController, both of which init a request 36 | */ 37 | .factory('TreeData', ['Trees', '$stateParams', 38 | /** 39 | * 40 | * @param Trees 41 | * @param $stateParams 42 | * @returns {{getTree: Function}} 43 | */ 44 | function(Trees, $stateParams) { 45 | var getTree = function(cb) { 46 | return Trees 47 | .get({treeId: $stateParams.treeId}, function(t) { 48 | // optional callback to set a paramter in messages only after load 49 | if (cb) { 50 | cb(t); 51 | } 52 | return t; 53 | }); 54 | }; 55 | 56 | return {getTree: getTree}; 57 | } 58 | ]) 59 | 60 | /** 61 | * GetMessages factory to handle the routing to the server for basic message requests 62 | */ 63 | .factory('GetMessages', ['$resource', '$stateParams', 'Global', 64 | /** 65 | * 66 | * @param $resource 67 | * @param $stateParams 68 | * @param Global 69 | * @returns {*} 70 | */ 71 | function($resource, $stateParams, Global) { 72 | return $resource('treemessages/:treeid', { 73 | treeid: '@_treeid' 74 | }, { 75 | get: { 76 | method: 'GET', 77 | isArray: true 78 | } 79 | }); 80 | } 81 | ]) 82 | 83 | /** 84 | * Messages factory to handle the posting of messages to the server 85 | */ 86 | .factory('Messages', 87 | /** 88 | * 89 | * @param $resource 90 | * @param $stateParams 91 | * @returns {*} 92 | */ 93 | function($resource, $stateParams) { 94 | return $resource('usermessages', {}, { 95 | save: { 96 | method: 'POST', 97 | isArray: true 98 | } 99 | }); 100 | } 101 | ) 102 | 103 | /** 104 | * User Image factory to get images for usernames. Uses data storage to avoid redundant server calls 105 | */ 106 | .factory('UserImage', ['$http', 107 | /** 108 | * 109 | * @param $http 110 | * @param $stateParams 111 | * @returns {{loadUserImage: Function, saveUserImage: Function}} 112 | */ 113 | function($http, $stateParams) { 114 | var imageStore = {}; 115 | 116 | var loadUserImage = function(username, cb) { 117 | if (imageStore[username]) { 118 | cb(imageStore[username]); 119 | } else { 120 | $http.get('/userimage/' + username) 121 | .success(function(url) { 122 | imageStore[username] = url; 123 | cb(url); 124 | }) 125 | .error(function() { 126 | console.log('Error getting user image URL'); 127 | }); 128 | } 129 | }; 130 | /** 131 | * 132 | * @param user 133 | * @param url 134 | * @param cb 135 | */ 136 | var saveUserImage = function(user, url, cb) { 137 | $http.post('/userimage', {username: user, imageUrl: url}). 138 | success(function(data) { 139 | imageStore[data.username] = data.url; 140 | cb(data); 141 | }). 142 | error(function() { 143 | console.log('there was an error'); 144 | }); 145 | }; 146 | return { 147 | loadUserImage: loadUserImage, 148 | saveUserImage: saveUserImage 149 | }; 150 | } 151 | ]) 152 | 153 | /** 154 | * Likes factory to handle the 155 | */ 156 | .factory('Likes', ['$http', 157 | /** 158 | * 159 | * @param $http 160 | * @returns {{getLikes: Function, saveLike: Function}} 161 | */ 162 | function($http) { 163 | 164 | /** 165 | * getLikes function to get user likes uses storage to avoid multiple likes 166 | * Ideally that redundancy would be handled in SQL. 167 | */ 168 | var getLikes = function(treeId, cb) { 169 | // The callback here is to asynchronously save this data to the necissary $scope 170 | var userLikes = []; 171 | $http.post('/treelikes', {treeId: treeId}) 172 | .success(function(data) { 173 | // iterating through to find redundant user likes. 1 user can like a tree multiple times, 174 | // but it should only show once on the page. 175 | data.forEach(function(userLike) { 176 | if (userLikes.indexOf(userLike.username) === -1) { 177 | userLikes.push(userLike.username); 178 | } 179 | }); 180 | cb(userLikes); 181 | }) 182 | .error(function(error) { 183 | console.log('error while saving like'); 184 | }); 185 | }; 186 | /** 187 | * saveLike function to handle the posting of likes to the server 188 | * @param username 189 | * @param treeId 190 | * @param cb 191 | */ 192 | var saveLike = function(username, treeId, cb) { 193 | // the callback is to asynchronously call getLikes in the controller 194 | $http.post('/treelike', {username: username, treeId: treeId}) 195 | .success(function(data) { 196 | console.log('success saving like'); 197 | cb(); 198 | }) 199 | .error(function(error) { 200 | console.log('error while saving like'); 201 | }); 202 | }; 203 | 204 | return { 205 | // exposing the functions to the controllers. 206 | getLikes: getLikes, 207 | saveLike: saveLike 208 | }; 209 | } 210 | ]); 211 | 212 | -------------------------------------------------------------------------------- /packages/articles/public/services/search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* 3 | Factory that handle the address search in the list.html 4 | */ 5 | angular.module('mean.articles') 6 | /* 7 | Factory that handle the search in the list.html 8 | */ 9 | .factory('Search', ['$resource', '$q', 'uiGmapGoogleMapApi', 10 | /** 11 | * 12 | * @param $resource 13 | * @param $q 14 | * @param uiGmapGoogleMapApi 15 | * @returns {{getLocation: Function, getNearTrees: Function, getByName: Function}} 16 | */ 17 | function($resource, $q, uiGmapGoogleMapApi) { 18 | /** 19 | * Take the address as params and return the location(lat, lng) 20 | * should remember check the validation of the input somewhere 21 | * @param target 22 | * @returns {*} 23 | */ 24 | var getLocation = function(target) { 25 | console.log('get Location async'); 26 | //this function need to be an async, so &q is used 27 | return $q(function(resolve, reject) { 28 | uiGmapGoogleMapApi.then(function(maps) { 29 | var geocoder = new maps.Geocoder(); 30 | var request = {address: target}; 31 | geocoder.geocode(request, function(results, status) { 32 | // location is found 33 | console.log(results); 34 | if (status === maps.GeocoderStatus.OK) { 35 | var location = results[0].geometry.location; 36 | resolve(location); 37 | } else { 38 | console.log('No Valid Address Found: ' + status); 39 | reject(status); 40 | } 41 | }); 42 | }); 43 | }); 44 | }; 45 | 46 | /** 47 | * Get the nearby trees using the service 48 | * @param queryObj 49 | * @returns {*} 50 | */ 51 | var getNearTrees = function(queryObj) { 52 | return $resource('/searchbyloc', {lat: '@latitude', lng: '@longitude'}, 53 | { 54 | get: { 55 | method: 'GET', 56 | isArray: true 57 | } 58 | }); 59 | }; 60 | 61 | /** 62 | * Get the trees using the service through the name, id or species 63 | * search should support both clear and vague string 64 | * @param queryObj 65 | * @returns {*} 66 | */ 67 | var getByName = function(queryObj) { 68 | return $resource('/searchbyname/:search', {search: '@search'}, 69 | { 70 | get: { 71 | method: 'GET', 72 | isArray: true 73 | } 74 | }); 75 | }; 76 | 77 | return { 78 | getLocation: getLocation, 79 | getNearTrees: getNearTrees, 80 | getByName: getByName 81 | }; 82 | } 83 | ]); 84 | -------------------------------------------------------------------------------- /packages/articles/public/tests/articles.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | -------------------------------------------------------------------------------- /packages/articles/public/views/list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Browse Trees 6 |

7 | 8 | 14 | 15 |
16 | Search by name, species or address. 17 |
18 | 24 |
25 |
26 |

Add New Tree

27 | 28 |
29 | 31 | 32 |
Please enter a correct info #
33 |
34 | 35 |
36 | 38 | 39 |
Please enter a correct info #
40 |
41 | 42 |
43 | 45 | 46 |
Please enter a correct info #
47 |
48 |
49 | 50 | 52 |
53 |
54 |
55 |
56 | 57 |
58 | 59 | 60 | 79 | 80 | 81 |
82 | 83 |
84 | 85 |
86 | -------------------------------------------------------------------------------- /packages/articles/public/views/new.html: -------------------------------------------------------------------------------- 1 | ADD A NEW TREE 2 | 3 |
4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 |
-------------------------------------------------------------------------------- /packages/articles/public/views/profile.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 | 7 |
8 | 9 |

{{tree.name}}

10 | 11 | 12 | 13 |

Species: {{tree.qspecies}}

14 |

Plotsize: {{tree.plotsize}}

15 |

Date planted: {{tree.plantdate | date:'yyyy-MM-dd'}}

16 |

Caretaker: {{tree.qcaretaker}}

17 | 18 | 30 | 31 | 32 |
33 |
34 |
35 |
36 |
37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 |
49 | 50 |
51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 66 | 67 |
68 |
69 |
70 |
71 | 72 |
73 |
74 | 75 |
76 | 77 | 78 | 79 | {{ message.username }} 80 | 81 | 82 | Tree Response 83 | 84 | 85 | 86 |
87 | {{ message.createdat | date: 'medium' }} 88 |

{{ message.message }}

89 |
90 |
91 |
92 |
-------------------------------------------------------------------------------- /packages/articles/server/models/article.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | -------------------------------------------------------------------------------- /packages/articles/server/routes/articles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs'); 3 | var articles = require('../controllers/articles'); 4 | 5 | module.exports = function(Articles, app, auth) { 6 | var multer = require('multer'); 7 | 8 | //use multer middleware for image uploads, max size 500kb 9 | //middleware active for any route 10 | //image uploads are coming in on '/user/image' 11 | app.use(function(req, res, next) { 12 | var fileTooLarge = false; 13 | var handler = multer({ 14 | dest: 'packages/theme/public/assets/img/uploads/', 15 | limits: { 16 | fileSize: 500000 17 | }, 18 | rename: function (fieldname, filename, req, res) { 19 | var username = req.user.username; 20 | return username + '001'; 21 | }, 22 | onFileSizeLimit: function (file) { 23 | fileTooLarge = true; 24 | res.json({ 25 | uploadError: 'Upload failed. File must be less than 500 KB' 26 | }); 27 | }, 28 | onFileUploadStart: function (file) { 29 | console.log(file.originalname + ' is starting ...'); 30 | }, 31 | onFileUploadComplete: function (file, req, res) { 32 | console.log(file.fieldname + ' uploaded to ' + file.path); 33 | var newFileName = req.files.file[0].name; 34 | if(!fileTooLarge) { 35 | articles.uploadUserImage(req, res, newFileName, function() { 36 | file.path = 'http://dinizcdnstorage.blob.core.windows.net/userpictures/' + newFileName; 37 | //file param is actually an object with the path as a property 38 | res.send(file); 39 | //delete file from local uploads folder 40 | fs.unlink('packages/theme/public/assets/img/uploads/' + newFileName); 41 | }); 42 | } else { 43 | fs.unlink('packages/theme/public/assets/img/uploads/' + newFileName); 44 | } 45 | } 46 | }); 47 | handler(req, res, next); 48 | }); 49 | 50 | 51 | 52 | app.route('/articles').get(articles.getAll); 53 | app.route('/articles/:treeId').get(articles.getTreeData); 54 | //endpoint to add new tree 55 | app.route('/articles/new').post(articles.addTree); 56 | 57 | app.route('/usermessages/:userid').get(articles.getMessagesForUsers); 58 | app.route('/usermessages').post(articles.postMessageFromUser); 59 | app.route('/treemessages/:treeid').get(articles.getMessagesForTree); 60 | app.route('/treemessages').post(articles.insertMessagesFromTrees); 61 | //the app.use middleware route above uses multer to handle file uploads 62 | app.route('/user/image').post(function(req, res) {}); 63 | app.route('/treelike').post(articles.insertLikes); 64 | app.route('/treelikes').post(articles.getUserLikes); 65 | app.route('/userlikes').post(articles.getTreeLikes); 66 | app.route('/treeimage/:treeId').get(articles.getTreeImage); 67 | 68 | 69 | app.route('/searchbyloc').get(articles.findTreesByLocation); 70 | app.route('/searchbyname/:search').get(articles.searchTrees); 71 | //the app.use middleware route above with multer handles file uploads 72 | //we don't need to set up a route for /user/images/ b/c multer handles it 73 | 74 | }; 75 | -------------------------------------------------------------------------------- /packages/articles/server/tests/articles.js: -------------------------------------------------------------------------------- 1 | /* jshint -W079 */ 2 | /* Related to https://github.com/linnovate/mean/issues/898 */ 3 | 'use strict'; -------------------------------------------------------------------------------- /packages/system/README.md: -------------------------------------------------------------------------------- 1 | README: system -------------------------------------------------------------------------------- /packages/system/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * Defining the Package 5 | */ 6 | var Module = require('meanio').Module, 7 | favicon = require('serve-favicon'); 8 | 9 | var SystemPackage = new Module('system'); 10 | 11 | /* 12 | * All MEAN packages require registration 13 | * Dependency injection is used to define required modules 14 | */ 15 | SystemPackage.register(function(app, auth, database) { 16 | 17 | //We enable routing. By default the Package Object is passed to the routes 18 | SystemPackage.routes(app, auth, database); 19 | 20 | SystemPackage.aggregateAsset('css', 'common.css'); 21 | SystemPackage.aggregateAsset('js', 'chat.js'); 22 | SystemPackage.angularDependencies(['ui.router', 'mean-factory-interceptor']); 23 | 24 | // The middleware in config/express will run before this code 25 | 26 | // Set views path, template engine and default layout 27 | app.set('views', __dirname + '/server/views'); 28 | 29 | // Setting the favicon and static folder 30 | app.use(favicon(__dirname + '/public/assets/img/favicon.ico')); 31 | 32 | // Adding robots and humans txt 33 | app.useStatic(__dirname + '/public/assets/static'); 34 | 35 | SystemPackage.menus.add({ 36 | title: 'Log Out', 37 | link: 'Log Out', 38 | roles: ['authenticated'], 39 | menu: 'account' 40 | }); 41 | 42 | 43 | return SystemPackage; 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /packages/system/mean.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /packages/system/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "system", 3 | "version": "0.0.1", 4 | "description": "Base system package", 5 | "author": { 6 | "name": "Linnovate" 7 | }, 8 | "mean": "0.4.x", 9 | "engines": { 10 | "node": "0.10.x", 11 | "npm": "1.4.x" 12 | }, 13 | "dependencies": {}, 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /packages/system/public/assets/css/common.css: -------------------------------------------------------------------------------- 1 | .page-header { 2 | padding-bottom: 0 !important; 3 | margin: 0 !important; 4 | border-bottom: 0 !important; 5 | } 6 | .page-header .navbar-nav { 7 | float: right; 8 | } 9 | .page-header > div > div, .page-header > div > ul { 10 | display: inline-block; 11 | } 12 | .text-edit { 13 | padding: 13px 0 0 0; 14 | } 15 | .account { 16 | text-align:right; 17 | } 18 | .account > div, .account > ul { 19 | text-align: left; 20 | } 21 | .navbar .nav > li > a.brand { 22 | padding-left: 20px; 23 | margin-left: 0 24 | } 25 | .content { 26 | margin-top: 70px; 27 | width: 100% 28 | } 29 | footer { 30 | position: fixed; 31 | left: 0; 32 | bottom: 0; 33 | height: 30px; 34 | width: 100%; 35 | background: #ddd; 36 | -webkit-box-shadow: 0 8px 6px 6px black; 37 | -moz-box-shadow: 0 8px 6px 6px black; 38 | box-shadow: 0 8px 6px 6px black 39 | } 40 | footer p { 41 | padding: 5px 0 12px 10px 42 | } 43 | 44 | .featuredpic img{ 45 | width:510px; 46 | } 47 | /*.ng-invalid.ng-dirty{*/ 48 | /*border-color:#FA787E;*/ 49 | /*}*/ 50 | /*.ng-valid.ng-dirty{*/ 51 | /*border-color:#78FA89;*/ 52 | /*}*/ 53 | 54 | 55 | /*********************************************************** 56 | Chat Style 57 | ***********************************************************/ 58 | 59 | .chat { 60 | position: absolute; 61 | right: 0; 62 | top: 155px; 63 | width: 300px; 64 | height: 600px; 65 | border: 1px solid black; 66 | overflow-y: scroll; 67 | box-shadow: 0 0 30px black; 68 | background: #868C70; 69 | } 70 | 71 | .chat form input { 72 | width: 230px; 73 | display: block; 74 | margin: 10px auto; 75 | } 76 | .chat ul { 77 | list-style-type: none; 78 | padding:0; 79 | padding-left: 5px; 80 | background: #97AE43; 81 | } 82 | 83 | .message-container { 84 | margin-top: 10px; 85 | } 86 | 87 | .chat-button { 88 | position: fixed; 89 | right: 10px; bottom: 10px; 90 | background: #97AE43; 91 | cursor: n-resize; 92 | } -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/black-github-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/black-github-256.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/blog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/blog.gif -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/github-10-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/github-10-512.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/li.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/li.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/tree-10-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/tree-10-512.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/tree-26-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/tree-26-512.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/tree-45-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/tree-45-64.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/tree-51-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/tree-51-64.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/tree-7-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/tree-7-512.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/tree-72-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/tree-72-512.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/tree-74-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/tree-74-64.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/tree-76-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/tree-76-512.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/tree-85-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/tree-85-64.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/about/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/about/twitter.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/apple/apple-touch-icon-114x114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/apple/apple-touch-icon-114x114-precomposed.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/apple/apple-touch-icon-144x144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/apple/apple-touch-icon-144x144-precomposed.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/apple/apple-touch-icon-57x57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/apple/apple-touch-icon-57x57-precomposed.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/apple/apple-touch-icon-72x72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/apple/apple-touch-icon-72x72-precomposed.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/apple/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/apple/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/apple/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/apple/apple-touch-icon.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/apple/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/apple/splash.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/apple/splash2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/apple/splash2x.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/favicon.ico -------------------------------------------------------------------------------- /packages/system/public/assets/img/loaders/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/loaders/loader.gif -------------------------------------------------------------------------------- /packages/system/public/assets/img/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/placeholder.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/sprites/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/sprites/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /packages/system/public/assets/img/sprites/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/img/sprites/glyphicons-halflings.png -------------------------------------------------------------------------------- /packages/system/public/assets/js/chat.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/system/public/assets/js/chat.js -------------------------------------------------------------------------------- /packages/system/public/assets/static/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | jQuery, Modernizr 16 | -------------------------------------------------------------------------------- /packages/system/public/assets/static/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /packages/system/public/controllers/header.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.system').controller('HeaderController', ['$scope', '$rootScope', 'Global', 'Menus', 4 | function($scope, $rootScope, Global, Menus) { 5 | $scope.global = Global; 6 | $scope.menus = {}; 7 | 8 | // Default hard coded menu items for main menu 9 | var defaultMainMenu = []; 10 | 11 | // Query menus added by modules. Only returns menus that user is allowed to see. 12 | function queryMenu(name, defaultMenu) { 13 | 14 | Menus.query({ 15 | name: name, 16 | defaultMenu: defaultMenu 17 | }, function(menu) { 18 | $scope.menus[name] = menu; 19 | }); 20 | } 21 | 22 | // Query server for menus and check permissions 23 | queryMenu('main', defaultMainMenu); 24 | queryMenu('account', []); 25 | 26 | 27 | $scope.isCollapsed = false; 28 | 29 | $rootScope.$on('loggedin', function() { 30 | 31 | queryMenu('main', defaultMainMenu); 32 | 33 | $scope.global = { 34 | authenticated: !! $rootScope.user, 35 | user: $rootScope.user 36 | }; 37 | }); 38 | 39 | $scope.chatToggle = false; 40 | $scope.chatPanelStatus = 'Show Chat'; 41 | 42 | $scope.toggleChat = function (){ 43 | if($scope.chatToggle){ 44 | $scope.chatToggle = false; 45 | $scope.chatPanelStatus = 'Show Chat'; 46 | 47 | } else { 48 | $scope.chatToggle = true; 49 | $scope.chatPanelStatus = 'Hide Chat'; 50 | } 51 | }; 52 | 53 | } 54 | ]).controller('ChatController', ['$scope', '$firebaseArray', 'Global', function($scope, $firebaseArray, Global){ 55 | 56 | var ref = new Firebase('https://flickering-torch-2529.firebaseio.com/treeChat'); //jshint ignore:line 57 | 58 | $scope.messages = $firebaseArray(ref); 59 | 60 | var placeHolder = 'system/assets/img/placeholder.png'; 61 | $scope.sendMessage = function(msg){ 62 | Global.user.imageUrl = Global.user.imageUrl || placeHolder; 63 | $scope.messages.$add({ 64 | from: Global.user, 65 | content: msg 66 | }); 67 | 68 | $scope.msg = ''; 69 | }; 70 | 71 | }]).filter('reverse', function(){ 72 | return function(items){ 73 | return items.slice().reverse(); 74 | }; 75 | }); 76 | -------------------------------------------------------------------------------- /packages/system/public/controllers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.system').controller('IndexController', ['$scope', 'Global', 4 | function($scope, Global) { 5 | $scope.global = Global; 6 | $scope.sites = { 7 | 'makeapoint':{ 8 | 'name':'makeapoint', 9 | 'text':'Makeapoint is a platform to craft and fine-tune ideas and messages providing a graphical experience which brough an offline methodlogy online', 10 | 'author':'Linnovate', 11 | 'link':'http://www.linnovate.net', 12 | 'image':'/theme/assets/img/makeapoint.png' 13 | }, 14 | 'cactus':{ 15 | 'name':'Cactus Intranet', 16 | 'text':'Cactus Intranet is an enterprise social network with features like real-time newsfeed, notifications, groups, events, polls, referral system etc. The system has role based permission system, allowing different stakeholders access and controls relevant to them.', 17 | 'author':'QED42', 18 | 'link':'http://www.qed42.com', 19 | 'image':'/theme/assets/img/cactus.png' 20 | } 21 | }; 22 | $scope.packages = { 23 | 'gmap':{ 24 | 'name':'gmap', 25 | 'text':'gmap lets you add geographical information to your applications objects', 26 | 'author':'linnovate', 27 | 'link':'http://www.qed42.com', 28 | 'image':'/theme/assets/img/gmap.png' 29 | }, 30 | 'upload':{ 31 | 'name':'Upload', 32 | 'text':'hello text', 33 | 'author':'Linnovate', 34 | 'link':'http://www.linnovate.net', 35 | 'image':'http://cdn.designbyhumans.com/pictures/blog/09-2013/pop-culture-cats/Pop_Culture_Cats_Hamilton_Hipster.jpg' 36 | }, 37 | 'socket':{ 38 | 'name':'Socket', 39 | 'text':'Socket.io support', 40 | 'author':'Linnovate', 41 | 'link':'http://www.linnovate.net', 42 | 'image':'http://cdn.designbyhumans.com/pictures/blog/09-2013/pop-culture-cats/Pop_Culture_Cats_Hamilton_Hipster.jpg' 43 | } 44 | }; 45 | 46 | $scope.$watch(function () { 47 | for (var i = 0; i < $scope.sites.length; i+=1) { 48 | if ($scope.sites[i].active) { 49 | return $scope.sites[i]; 50 | } 51 | } 52 | }, function (currentSlide, previousSlide) { 53 | if (currentSlide !== previousSlide) { 54 | console.log('currentSlide:', currentSlide); 55 | } 56 | }); 57 | } 58 | ]); 59 | -------------------------------------------------------------------------------- /packages/system/public/routes/system.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // $viewPathProvider, to allow overriding system default views 4 | angular.module('mean.system').provider('$viewPath', function() { 5 | function ViewPathProvider() { 6 | var overrides = {}; 7 | 8 | this.path = function(path) { 9 | return function() { 10 | return overrides[path] || path; 11 | }; 12 | }; 13 | 14 | this.override = function(defaultPath, newPath) { 15 | if (overrides[defaultPath]) { 16 | throw new Error('View already has an override: ' + defaultPath); 17 | } 18 | overrides[defaultPath] = newPath; 19 | return this; 20 | }; 21 | 22 | this.$get = function() { 23 | return this; 24 | }; 25 | } 26 | 27 | return new ViewPathProvider(); 28 | }); 29 | 30 | // $meanStateProvider, provider to wire up $viewPathProvider to $stateProvider 31 | angular.module('mean.system').provider('$meanState', ['$stateProvider', '$viewPathProvider', function($stateProvider, $viewPathProvider) { 32 | function MeanStateProvider() { 33 | this.state = function(stateName, data) { 34 | if (data.templateUrl) { 35 | data.templateUrl = $viewPathProvider.path(data.templateUrl); 36 | } 37 | $stateProvider.state(stateName, data); 38 | return this; 39 | }; 40 | 41 | this.$get = function() { 42 | return this; 43 | }; 44 | } 45 | 46 | return new MeanStateProvider(); 47 | }]); 48 | 49 | //Setting up route 50 | angular.module('mean.system').config(['$meanStateProvider', '$urlRouterProvider', 51 | function($meanStateProvider, $urlRouterProvider) { 52 | // For unmatched routes: 53 | $urlRouterProvider.otherwise('/'); 54 | 55 | // states for my app 56 | $meanStateProvider 57 | .state('home', { 58 | url: '/', 59 | templateUrl: 'system/views/index.html' 60 | }) 61 | .state('about', { 62 | url: '/about', 63 | templateUrl: 'system/views/about.html' 64 | }); 65 | 66 | $meanStateProvider 67 | .state('Log Out', { 68 | controller: function () { 69 | window.location = '/logout'; 70 | } 71 | }); 72 | } 73 | ]).config(['$locationProvider', 74 | function($locationProvider) { 75 | $locationProvider.hashPrefix('!'); 76 | } 77 | ]); 78 | -------------------------------------------------------------------------------- /packages/system/public/services/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Global service for global variables 4 | angular.module('mean.system').factory('Global', [ 5 | 6 | function() { 7 | var _this = this; 8 | _this._data = { 9 | user: window.user, 10 | authenticated: false, 11 | isAdmin: false 12 | }; 13 | if (window.user && window.user.roles) { 14 | _this._data.authenticated = window.user.roles.length; 15 | _this._data.isAdmin = window.user.roles.indexOf('admin') !== -1; 16 | } 17 | return _this._data; 18 | } 19 | ]); 20 | -------------------------------------------------------------------------------- /packages/system/public/services/interceptor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean-factory-interceptor', []) 4 | .factory('httpInterceptor', ['$q', '$location', 5 | function($q, $location) { 6 | return { 7 | 'response': function(response) { 8 | if (response.status === 401) { 9 | $location.path('/auth/login'); 10 | return $q.reject(response); 11 | } 12 | return response || $q.when(response); 13 | }, 14 | 15 | 'responseError': function(rejection) { 16 | 17 | if (rejection.status === 401) { 18 | $location.url('/auth/login'); 19 | return $q.reject(rejection); 20 | } 21 | return $q.reject(rejection); 22 | } 23 | 24 | }; 25 | } 26 | ]) 27 | //Http Interceptor to check auth failures for XHR requests 28 | .config(['$httpProvider', 29 | function($httpProvider) { 30 | $httpProvider.interceptors.push('httpInterceptor'); 31 | } 32 | ]); 33 | -------------------------------------------------------------------------------- /packages/system/public/services/menus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.system').factory('Menus', ['$resource', 4 | function($resource) { 5 | return $resource('admin/menu/:name', { 6 | name: '@name', 7 | defaultMenu: '@defaultMenu' 8 | }); 9 | } 10 | ]); 11 | -------------------------------------------------------------------------------- /packages/system/public/system.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.system', ['ui.router', 'mean-factory-interceptor', 'firebase']) 4 | .run(['$rootScope', function($rootScope) { 5 | $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){ 6 | var toPath = toState.url; 7 | toPath = toPath.replace(new RegExp('/', 'g'), ''); 8 | toPath = toPath.replace(new RegExp(':', 'g'),'-'); 9 | $rootScope.state = toPath; 10 | if($rootScope.state === '' ) { 11 | $rootScope.state = 'firstPage'; 12 | } 13 | }); 14 | }]) 15 | ; 16 | -------------------------------------------------------------------------------- /packages/system/public/tests/headers.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('MEAN controllers', function() { 5 | describe('HeaderController', function() { 6 | beforeEach(function() { 7 | module('mean'); 8 | module('mean.system'); 9 | }); 10 | 11 | var scope, HeaderController; 12 | 13 | beforeEach(inject(function($controller, $rootScope) { 14 | scope = $rootScope.$new(); 15 | 16 | HeaderController = $controller('HeaderController', { 17 | $scope: scope 18 | }); 19 | })); 20 | 21 | it('should expose some global scope', function() { 22 | expect(scope.global).toBeTruthy(); 23 | }); 24 | }); 25 | }); 26 | })(); 27 | -------------------------------------------------------------------------------- /packages/system/public/tests/index.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('MEAN controllers', function() { 5 | describe('IndexController', function() { 6 | beforeEach(function() { 7 | module('mean'); 8 | module('mean.system'); 9 | }); 10 | 11 | var scope, IndexController; 12 | 13 | beforeEach(inject(function($controller, $rootScope) { 14 | scope = $rootScope.$new(); 15 | 16 | IndexController = $controller('IndexController', { 17 | $scope: scope 18 | }); 19 | })); 20 | 21 | it('should expose some global scope', function() { 22 | expect(scope.global).toBeTruthy(); 23 | }); 24 | }); 25 | }); 26 | })(); 27 | -------------------------------------------------------------------------------- /packages/system/public/views/header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/system/public/views/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 | 7 |
8 | 9 |
10 | 11 | 12 |
13 |

14 | Treebook 15 |

16 | TreeBook is a social network for trees. We have a profile page for each registered tree in San Francisco so come make some new sprouting friends. Grow your social branches by connecting with some perrenial plants. The code that runs this site can be found on github. 17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 |

Explore the Forest

25 |

26 | Each tree has a profile page like the beautiful Alicia Higgins. Each profile has basic information and map showing its precise location. The profile pictures are of each tree species, but if you want to see the exact tree you can use the street view functionality of the embedded map. 27 |

28 | Browse all trees 29 |
30 |
31 |
32 |
33 |
34 |

About the Project

35 |

36 | The project was created in a week long sprint by 4 students at Hack Reactor. We use the MEAN stack and also used SQL to store the tree data. 37 |
38 |
39 |
40 | 41 |

42 | Read more 43 |
44 |
45 | 46 | 47 |
48 |
49 |
50 | 51 | Built at Hack Reactor 52 | 53 |
54 | -------------------------------------------------------------------------------- /packages/system/server/controllers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mean = require('meanio'); 4 | 5 | exports.render = function(req, res) { 6 | console.log('IN RENDER'); 7 | var modules = []; 8 | // Preparing angular modules list with dependencies 9 | for (var name in mean.modules) { 10 | modules.push({ 11 | name: name, 12 | module: 'mean.' + name, 13 | angularDependencies: mean.modules[name].angularDependencies 14 | }); 15 | } 16 | 17 | function isAdmin() { 18 | return req.user && req.user.roles.indexOf('admin') !== -1; 19 | } 20 | 21 | // Send some basic starting info to the view 22 | res.render('index', { 23 | user: req.user ? { 24 | name: req.user.name, 25 | _id: req.user._id, 26 | username: req.user.username, 27 | profile: req.user.profile, 28 | roles: req.user.roles, 29 | imageUrl: req.user.imageUrl 30 | } : {}, 31 | modules: modules, 32 | isAdmin: isAdmin, 33 | adminEnabled: isAdmin() && mean.moduleEnabled('mean-admin') 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /packages/system/server/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mean = require('meanio'); 4 | 5 | module.exports = function(System, app, auth, database) { 6 | 7 | // Home route 8 | var index = require('../controllers/index'); 9 | app.route('/') 10 | .get(index.render); 11 | 12 | app.get('/*',function(req,res,next){ 13 | res.header('workerID' , JSON.stringify(mean.options.workerid) ); 14 | next(); // http://expressjs.com/guide.html#passing-route control 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/system/server/routes/menus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mean = require('meanio'); 4 | 5 | module.exports = function(System, app, auth, database) { 6 | 7 | app.route('/admin/menu/:name') 8 | .get(function(req, res) { 9 | var roles = req.user ? req.user.roles : ['anonymous']; 10 | var menu = req.params.name || 'main'; 11 | var defaultMenu = req.query.defaultMenu || []; 12 | 13 | if (!Array.isArray(defaultMenu)) defaultMenu = [defaultMenu]; 14 | 15 | var items = mean.menus.get({ 16 | roles: roles, 17 | menu: menu, 18 | defaultMenu: defaultMenu.map(function(item) { 19 | return JSON.parse(item); 20 | }) 21 | }); 22 | 23 | res.json(items); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /packages/system/server/views/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/default.html' %} 2 | 3 | {% block main %} 4 |

Oops something went wrong

5 |
6 | 404 7 | {% endblock %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |       {{error}}
14 |     
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /packages/system/server/views/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/default.html' %} 2 | 3 | {% block main %} 4 |

Oops something went wrong

5 |
6 | 500 7 | {% endblock %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |         {{error}}
14 |       
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /packages/system/server/views/includes/foot.html: -------------------------------------------------------------------------------- 1 | {% for file in aggregatedassets.footer.js %} 2 | 3 | {% endfor %} 4 | 5 | {% if (process.env.NODE_ENV == 'development') %} 6 | 7 | 11 | {% endif %} 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/system/server/views/includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{appName}} - {{title}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for file in aggregatedassets.header.css %} 23 | 24 | {% endfor %} 25 | 26 | {% for file in aggregatedassets.footer.css %} 27 | 28 | {% endfor %} 29 | 30 | {% for file in aggregatedassets.header.js %} 31 | 32 | {% endfor %} 33 | 34 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /packages/system/server/views/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/default.html' %} 2 | {% block content %} 3 |
4 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /packages/system/server/views/layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% include '../includes/head.html' %} 4 | 5 | 6 | 7 | {% if adminEnabled %} 8 |
9 |
10 |
11 | {% endif %} 12 |
13 |
14 | {% block content %}{% endblock %} 15 |
16 |
17 | {% include '../includes/foot.html' %} 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/theme/README.md: -------------------------------------------------------------------------------- 1 | README: theme -------------------------------------------------------------------------------- /packages/theme/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * Defining the Package 5 | */ 6 | var Module = require('meanio').Module; 7 | 8 | var Theme = new Module('theme'); 9 | 10 | /* 11 | * All MEAN packages require registration 12 | * Dependency injection is used to define required modules 13 | */ 14 | Theme.register(function(app, auth, database) { 15 | 16 | //We enable routing. By default the Package Object is passed to the routes 17 | Theme.routes(app, auth, database); 18 | 19 | //We are adding a link to the main menu for all authenticated users 20 | // Theme.menus.add({ 21 | // title: 'theme example page', 22 | // link: 'theme example page', 23 | // roles: ['authenticated'], 24 | // menu: 'main' 25 | // }); 26 | 27 | Theme.aggregateAsset('css', 'loginForms.css'); 28 | Theme.aggregateAsset('css', 'theme.css'); 29 | Theme.angularDependencies(['mean.system']); 30 | 31 | /** 32 | //Uncomment to use. Requires meanio@0.3.7 or above 33 | // Save settings with callback 34 | // Use this for saving data from administration pages 35 | Theme.settings({ 36 | 'someSetting': 'some value' 37 | }, function(err, settings) { 38 | //you now have the settings object 39 | }); 40 | 41 | // Another save settings example this time with no callback 42 | // This writes over the last settings. 43 | Theme.settings({ 44 | 'anotherSettings': 'some value' 45 | }); 46 | 47 | // Get settings. Retrieves latest saved settings 48 | Theme.settings(function(err, settings) { 49 | //you now have the settings object 50 | }); 51 | */ 52 | 53 | return Theme; 54 | }); 55 | -------------------------------------------------------------------------------- /packages/theme/mean.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "users": "latest" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/theme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "theme", 3 | "version": "0.0.1", 4 | "description": "Some description of theme", 5 | "author": { 6 | "name": "mean scaffold" 7 | }, 8 | "mean": "0.4.0", 9 | "engines": { 10 | "node": "0.10.x", 11 | "npm": "1.4.x" 12 | }, 13 | "dependencies": {}, 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /packages/theme/public/assets/css/theme.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'futura_bkbook'; 3 | src: url("/theme/assets/fonts/ufonts.com_futura-book.eot"); 4 | src: url("/theme/assets/fonts/ufonts.com_futura-book.eot?#iefix") format("embedded-opentype"), url("/theme/assets/fonts/ufonts.com_futura-book.woff") format("woff"), url("/theme/assets/fonts/ufonts.com_futura-book.ttf") format("truetype"), url("/theme/assets/fonts/ufonts.com_futura-book.svg#futura_bkbook") format("svg"); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: 'open_sans_hebrewregular'; 11 | src: url("/theme/assets/fonts/opensanshebrew-regular.eot"); 12 | src: url("/theme/assets/fonts/opensanshebrew-regular.eot?#iefix") format("embedded-opentype"), url("/theme/assets/fonts/opensanshebrew-regular.woff") format("woff"), url("/theme/assets/fonts/opensanshebrew-regular.ttf") format("truetype"), url("/theme/assets/fonts/opensanshebrew-regular.svg#open_sans_hebrewregular") format("svg"); 13 | font-weight: bold; 14 | font-style: normal; 15 | } 16 | 17 | .welcome.container-fluid { 18 | margin:18px; 19 | padding-top:10px; 20 | background-color:#fff; 21 | color:#525252; 22 | } 23 | 24 | .welcome .info, .welcome .featured { 25 | float:left; 26 | } 27 | .welcome .row { 28 | margin-top:10px; 29 | background-color:#f3f3f3; 30 | } 31 | .welcome .first{ 32 | padding:0; 33 | margin-bottom:30px; 34 | font-size:22px; 35 | background-color:#fff; 36 | } 37 | 38 | .welcome { 39 | padding-bottom: 4px; 40 | /*font-family: "futura_bkbook";*/ 41 | font-family:"open_sans_hebrewregular"; 42 | font-size:16px; 43 | margin: 0; 44 | margin:10px; 45 | } 46 | 47 | .welcome .info { 48 | margin-top:10px; 49 | margin-bottom:10px; 50 | background-color: #fff; 51 | padding-bottom: 10px; 52 | } 53 | 54 | .welcome h1 { 55 | font-size:30px; 56 | text-transform: uppercase; 57 | color:#0e0e0e; 58 | } 59 | 60 | .welcome h2 { 61 | margin-top: 27px; 62 | font-size:30px; 63 | font-weight:300; 64 | } 65 | 66 | .featured-packages h2{ 67 | margin-top:0; 68 | } 69 | 70 | .welcome h3 { 71 | font-size:17px; 72 | /* font-weight:300; */ 73 | text-transform: uppercase; 74 | color: #0e0e0e; 75 | } 76 | 77 | .docs h3 { 78 | padding:10px; 79 | } 80 | 81 | 82 | .welcome .packages, .welcome .community, .welcome .featured-packages { 83 | clear:both; 84 | margin-top:80px; 85 | height:100%; 86 | border-top:3px solid #f3f3f3; 87 | } 88 | 89 | .welcome .packages h3 { 90 | margin:20px 0 20px 0; 91 | } 92 | 93 | /* Docs */ 94 | .welcome .info ul { 95 | padding-left:20px; 96 | width:220px; 97 | list-style:none; 98 | } 99 | 100 | .welcome .info ul li { 101 | width:100px; 102 | font-size:14px; 103 | } 104 | 105 | .welcome .info ul li:before { 106 | content: '\003E'; 107 | margin: 0 0.6em 0 -1em; 108 | } 109 | 110 | .welcome p { 111 | font-size: 12px; 112 | margin-top: 10px; 113 | margin-left:53px; 114 | } 115 | .welcome .info .docs { 116 | height:100%; 117 | } 118 | .welcome .info .docs p{ 119 | font-size:14px; 120 | } 121 | 122 | .docs-desc { 123 | float:left; 124 | } 125 | 126 | .docs-link { 127 | margin: 16px 0 0 0; 128 | float:left; 129 | width:120px; 130 | } 131 | 132 | .docs-link.first { 133 | margin: 16px 0 0 53px; 134 | } 135 | 136 | .morelink { 137 | margin: 16px 0 6px 0; 138 | clear:both; 139 | float:right; 140 | } 141 | 142 | .docs .morelink { 143 | margin-top:30px; 144 | } 145 | 146 | .morelink :before { 147 | text-transform: uppercase; 148 | content: '\003E'; 149 | margin: 0 0.5em 0 -1em; 150 | } 151 | 152 | /* Packages */ 153 | 154 | .packages .package{ 155 | float:left; 156 | width:200px; 157 | margin-right:40px; 158 | } 159 | 160 | /* Packages */ 161 | .packages .package h4, .community .package h4{ 162 | text-transform: uppercase; 163 | margin-left:53px; 164 | color:#176583; 165 | font-size:16px; 166 | } 167 | 168 | .packages .package p { 169 | margin-left:53px; 170 | } 171 | 172 | /* Community */ 173 | .welcome-sprite{ 174 | float:left; 175 | width:40px; 176 | height:53px; 177 | } 178 | .welcome-sprite{ 179 | background-image: url(/theme/assets/img/tree101.png); 180 | background-repeat: no-repeat; 181 | display: block; 182 | } 183 | /*.docs .welcome-sprite { 184 | 185 | }*/ 186 | 187 | .packages .welcome-sprite { 188 | background-position: -60px 0; 189 | } 190 | 191 | .community .welcome-sprite { 192 | background-position: -120px 0; 193 | } 194 | 195 | .community .package { 196 | float:left; 197 | margin-bottom:10px; 198 | } 199 | .community .package p{ 200 | width:190px; 201 | margin: 0 0 0 10px; 202 | } 203 | .community .package h4{ 204 | margin: 0 0 0 10px; 205 | } 206 | 207 | 208 | .socialsprite { 209 | background-image: url(/theme/assets/img/welcome_social.png); 210 | background-repeat: no-repeat; 211 | display: block; 212 | float:left; 213 | } 214 | 215 | .sprite-facebook { 216 | width: 39px; 217 | height: 42px; 218 | background-position: -5px -5px; 219 | } 220 | 221 | .sprite-github { 222 | width: 40px; 223 | height: 44px; 224 | background-position: -54px -5px; 225 | } 226 | 227 | .sprite-irc { 228 | width: 38px; 229 | height: 42px; 230 | background-position: -104px -5px; 231 | } 232 | 233 | .sprite-stackoverflow { 234 | width: 40px; 235 | height: 44px; 236 | background-position: -152px -5px; 237 | } 238 | 239 | /* Featured Pic */ 240 | .featuredpic img{ 241 | width:510px; 242 | } 243 | 244 | .welcome .community h3{ 245 | margin-bottom:40px; 246 | } 247 | 248 | /* Featured Sites */ 249 | 250 | 251 | .featured-sites h4{ 252 | font-size:24px; 253 | } 254 | 255 | .featured-sites h4 span{ 256 | font-size:14px; 257 | } 258 | 259 | .featured-sites p, .featured-packages p { 260 | font-size:14px; 261 | margin-left:0; 262 | } 263 | 264 | .white-logo{ 265 | background-image: url(/theme/assets/img/sequoia-logo.png); 266 | background-repeat: no-repeat; 267 | display: block; 268 | } 269 | 270 | .indexBlurb { 271 | display: table-cell; 272 | vertical-align:middle; 273 | text-align: center; 274 | } -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/mark_simonson_-_proxima_nova_regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/mark_simonson_-_proxima_nova_regular.eot -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/mark_simonson_-_proxima_nova_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/mark_simonson_-_proxima_nova_regular.ttf -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/mark_simonson_-_proxima_nova_regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/mark_simonson_-_proxima_nova_regular.woff -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/opensanshebrew-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/opensanshebrew-light.eot -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/opensanshebrew-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/opensanshebrew-light.ttf -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/opensanshebrew-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/opensanshebrew-light.woff -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/opensanshebrew-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/opensanshebrew-regular.eot -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/opensanshebrew-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/opensanshebrew-regular.ttf -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/opensanshebrew-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/opensanshebrew-regular.woff -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/proximanova-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/proximanova-light.eot -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/proximanova-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/proximanova-light.ttf -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/proximanova-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/proximanova-light.woff -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/ufonts.com_futura-book.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/ufonts.com_futura-book.eot -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/ufonts.com_futura-book.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/ufonts.com_futura-book.ttf -------------------------------------------------------------------------------- /packages/theme/public/assets/fonts/ufonts.com_futura-book.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/fonts/ufonts.com_futura-book.woff -------------------------------------------------------------------------------- /packages/theme/public/assets/img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/bg.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/button_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/button_login.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/button_login_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/button_login_hover.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/cactus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/cactus.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/christmas128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/christmas128.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/cypress1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/cypress1.jpg -------------------------------------------------------------------------------- /packages/theme/public/assets/img/devide_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/devide_line.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/devide_line_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/devide_line_long.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/eye_password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/eye_password.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/eye_password_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/eye_password_disabled.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/forest7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/forest7.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/gmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/gmap.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/icons/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/icons/facebook.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/icons/favicon.ico -------------------------------------------------------------------------------- /packages/theme/public/assets/img/icons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/icons/github.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/icons/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/icons/google.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/icons/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/icons/linkedin.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/icons/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/icons/twitter.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/icons/user-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/icons/user-icon.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/logo.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/mail_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/mail_login.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/makeapoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/makeapoint.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/sequoia-logo copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/sequoia-logo copy 2.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/sequoia-logo-white-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/sequoia-logo-white-small.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/sequoia-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/sequoia-logo.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/single14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/single14.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/social_fb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/social_fb.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/social_g+.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/social_g+.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/social_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/social_github.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/social_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/social_in.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/social_tw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/social_tw.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/top-gradient.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/top-gradient.jpg -------------------------------------------------------------------------------- /packages/theme/public/assets/img/tree101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/tree101.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/tree111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/tree111.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/tree144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/tree144.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/tree65.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/tree65.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/tree79.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/tree79.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/trees10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/trees10.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/trees7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/trees7.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/welcome_social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/welcome_social.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/welcome_sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/welcome_sprite.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/woman_ninja.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/woman_ninja.png -------------------------------------------------------------------------------- /packages/theme/public/assets/img/woman_ninja_forg_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitInsights/TreeBook/d1d27bbe385b564dd45def0d70c4c1efb21bc58f/packages/theme/public/assets/img/woman_ninja_forg_pass.png -------------------------------------------------------------------------------- /packages/theme/public/controllers/theme.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.theme') 4 | .controller('ThemeController', ['$scope', 'Global', 5 | function($scope, Global) { 6 | // Original scaffolded code. 7 | $scope.global = Global; 8 | $scope.package = { 9 | name: 'theme' 10 | }; 11 | } 12 | ]); 13 | -------------------------------------------------------------------------------- /packages/theme/public/routes/theme.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.theme').config(['$meanStateProvider', 4 | function($meanStateProvider) { 5 | $meanStateProvider.state('theme example page', { 6 | url: '/theme/example', 7 | templateUrl: 'theme/views/index.html' 8 | }); 9 | } 10 | ]); 11 | -------------------------------------------------------------------------------- /packages/theme/public/services/theme.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.theme').factory('Theme', [ 4 | function() { 5 | return { 6 | name: 'theme' 7 | }; 8 | } 9 | ]); 10 | -------------------------------------------------------------------------------- /packages/theme/public/views/index.html: -------------------------------------------------------------------------------- 1 |
2 |

Example view from your new awesome package

3 |

Package: {{package.name}}

4 |
    5 |
  1. 6 | Server route that anyone can access 7 |
  2. 8 |
  3. 9 | Server route that requires authentication 10 |
  4. 11 |
  5. 12 | Server route that requires admin user 13 |
  6. 14 |
  7. 15 | Raw Html rendering example from using swig 16 |
  8. 17 |
18 |

19 |
20 |

You can find your package in /packages/theme

21 |
22 |

MEAN versions prior to 0.3.2 will be in /node_modules/theme

23 |
24 |

Documentation

25 |
26 |
27 | 28 | -------------------------------------------------------------------------------- /packages/theme/server/routes/theme.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // The Package is passed automatically as first parameter 4 | module.exports = function(Theme, app, auth, database) { 5 | 6 | app.get('/theme/example/anyone', function(req, res, next) { 7 | res.send('Anyone can access this'); 8 | }); 9 | 10 | app.get('/theme/example/auth', auth.requiresLogin, function(req, res, next) { 11 | res.send('Only authenticated users can access this'); 12 | }); 13 | 14 | app.get('/theme/example/admin', auth.requiresAdmin, function(req, res, next) { 15 | res.send('Only users with Admin role can access this'); 16 | }); 17 | 18 | app.get('/theme/example/render', function(req, res, next) { 19 | Theme.render('index', { 20 | package: 'theme' 21 | }, function(err, html) { 22 | //Rendering a view from the Package server/views 23 | res.send(html); 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/theme/server/views/index.html: -------------------------------------------------------------------------------- 1 |

This is rendered from the package itself.

2 |

The package uses swig by default

3 | 4 | -------------------------------------------------------------------------------- /packages/users/README.md: -------------------------------------------------------------------------------- 1 | README: meanUser 2 | -------------------------------------------------------------------------------- /packages/users/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * Defining the Package 5 | */ 6 | var mean = require('meanio'), 7 | Module = mean.Module; 8 | function MeanUserKlass () { 9 | Module.call(this, 'users'); 10 | this.auth = null; 11 | } 12 | MeanUserKlass.prototype = Object.create(Module.prototype,{constructor:{ 13 | value:MeanUserKlass, 14 | configurable: false, 15 | enumerable: false, 16 | writable: false 17 | }}); 18 | 19 | var MeanUser = new MeanUserKlass(); 20 | 21 | /* 22 | * All MEAN packages require registration 23 | * Dependency injection is used to define required modules 24 | */ 25 | MeanUser.register(function(app, database, passport) { 26 | // This is for backwards compatibility 27 | MeanUser.auth =require('./authorization'); 28 | require('./passport')(passport); 29 | 30 | mean.register('auth', MeanUser.auth); 31 | 32 | //We enable routing. By default the Package Object is passed to the routes 33 | MeanUser.routes(app, MeanUser.auth, database, passport); 34 | 35 | //We are adding a link to the main menu for all authenticated users 36 | // MeanUser.menus.add({ 37 | // title: 'meanUser example page', 38 | // link: 'meanUser example page', 39 | // roles: ['authenticated'], 40 | // menu: 'main' 41 | // }); 42 | 43 | MeanUser.angularDependencies(['mean.system']); 44 | 45 | /** 46 | //Uncomment to use. Requires meanio@0.3.7 or above 47 | // Save settings with callback 48 | // Use this for saving data from administration pages 49 | MeanUser.settings({ 50 | 'someSetting': 'some value' 51 | }, function(err, settings) { 52 | //you now have the settings object 53 | }); 54 | 55 | // Another save settings example this time with no callback 56 | // This writes over the last settings. 57 | MeanUser.settings({ 58 | 'anotherSettings': 'some value' 59 | }); 60 | 61 | // Get settings. Retrieves latest saved settings 62 | MeanUser.settings(function(err, settings) { 63 | //you now have the settings object 64 | }); 65 | */ 66 | 67 | return MeanUser; 68 | }); 69 | -------------------------------------------------------------------------------- /packages/users/authorization.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var mongoose = require('mongoose'), 3 | _ = require('lodash'); 4 | 5 | /** 6 | * Generic require login routing middleware 7 | */ 8 | exports.requiresLogin = function(req, res, next) { 9 | if (!req.isAuthenticated()) { 10 | return res.status(401).send('User is not authorized'); 11 | } 12 | next(); 13 | }; 14 | 15 | /** 16 | * Generic require Admin routing middleware 17 | * Basic Role checking - future release with full permission system 18 | */ 19 | exports.requiresAdmin = function(req, res, next) { 20 | if (!req.isAuthenticated() || !req.user.hasRole('admin')) { 21 | return res.status(401).send('User is not authorized'); 22 | } 23 | next(); 24 | }; 25 | 26 | /** 27 | * Generic validates if the first parameter is a mongo ObjectId 28 | */ 29 | exports.isMongoId = function(req, res, next) { 30 | if ((_.size(req.params) === 1) && (!mongoose.Types.ObjectId.isValid(_.values(req.params)[0]))) { 31 | return res.status(500).send('Parameter passed is not a valid Mongo ObjectId'); 32 | } 33 | next(); 34 | }; 35 | -------------------------------------------------------------------------------- /packages/users/mean.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "system": "latest" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/users/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "users", 3 | "version": "0.0.1", 4 | "description": "Users package", 5 | "author": { 6 | "name": "Linnovate" 7 | }, 8 | "mean": "0.4.x", 9 | "engines": { 10 | "node": "0.10.x", 11 | "npm": "1.4.x" 12 | }, 13 | "dependencies": {}, 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /packages/users/public/assets/css/meanUser.css: -------------------------------------------------------------------------------- 1 | .meanUser-example h1 { 2 | background-color: purple 3 | } -------------------------------------------------------------------------------- /packages/users/public/controllers/UserController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.articles') 4 | 5 | //UserController for userProfile page 6 | .controller('UserController', ['$scope', '$stateParams', '$upload', 'UserImage', 'GetUserMessages', 'Global', 'TreeImage', 'UserLikes', 'UserInfo', 7 | /** 8 | * 9 | * @param $scope 10 | * @param $stateParams 11 | * @param $upload 12 | * @param UserImage 13 | * @param GetUserMessages 14 | * @param Global 15 | * @param TreeImage 16 | * @param UserLikes 17 | * @param UserInfo 18 | */ 19 | function($scope, $stateParams, $upload, UserImage, GetUserMessages, Global, TreeImage, UserLikes, UserInfo) { 20 | $scope.global = Global; 21 | $scope.likes = []; 22 | $scope.user = {}; 23 | $scope.user.updates = []; 24 | $scope.user.status = ''; 25 | $scope.anyLikes = false; 26 | $scope.imagesLoaded = false; 27 | var contextUsername = $stateParams.userId; 28 | $scope.isProfileOwner = (contextUsername === $scope.global.user.username); 29 | 30 | //watch for image file upload 31 | $scope.$watch('files', function() { // user controller 32 | $scope.upload($scope.files); 33 | }); 34 | 35 | 36 | 37 | $scope.getLikes = function() { 38 | console.log($stateParams); 39 | UserLikes.getLikes(contextUsername, function(likes) { 40 | $scope.likes = likes; 41 | if (likes.length !== 0) { 42 | $scope.anyLikes = true; 43 | } 44 | }); 45 | 46 | }; 47 | 48 | /** 49 | * upload image file 50 | * @param files 51 | */ 52 | $scope.upload = function(files) { // user controller 53 | console.log('stateParams', $stateParams); 54 | var thisUser = contextUsername; 55 | if (files && files.length) { 56 | var file = files[0]; 57 | $upload.upload({ 58 | url: 'user/image', 59 | fields: { 60 | 'username': thisUser 61 | }, 62 | file: file 63 | }).progress(function(evt) { 64 | var progressPercentage = parseInt(100.0 * evt.loaded / evt.total); 65 | console.log('progress: ' + progressPercentage + '% ' + 66 | evt.config.file.name); 67 | }).success(function(data, status, headers, config) { 68 | $scope.image = data; 69 | if ($scope.image.uploadError) { 70 | $scope.user.uploadError = $scope.image.uploadError; 71 | console.log('error on hand'); 72 | } else { 73 | $scope.user.uploadError = ''; 74 | UserImage.saveUserImage(thisUser, $scope.image.path, function(data) { 75 | $scope.loadUserImage(data.username); 76 | }); 77 | } 78 | }); 79 | } 80 | 81 | }; 82 | 83 | /** 84 | * load user image in conjunction with factory UserImage 85 | * @param username 86 | */ 87 | $scope.loadUserImage = function(username) { 88 | UserImage.loadUserImage(username, function(imageUrl) { 89 | var random = (new Date()).toString(); 90 | //append a random string as a param to force ng-src to reload the image 91 | 92 | $scope.user.imageUrl = imageUrl + '?cb=' + random; 93 | 94 | $scope.user.imageUrl = $scope.user.imageUrl ? 95 | $scope.user.imageUrl : 96 | 'https://s-media-cache-ak0.pinimg.com/136x136/d0/d0/4c/d0d04c2afbc78f100bbc1f0387829943.jpg'; 97 | }); 98 | }; 99 | 100 | /** 101 | * get All messages from a User and display on user profile page 102 | * @param $stateParams 103 | */ 104 | $scope.getUserMessages = function($stateParams) { 105 | GetUserMessages.get({username: contextUsername}, function(messages) { 106 | $scope.user.messages = messages; 107 | $scope.user.messages.forEach(function(message) { 108 | TreeImage.loadTreeImage(message.treeid, function(url) { 109 | message.imageUrl = url; 110 | }); 111 | }); 112 | $scope.imagesLoaded = true; 113 | 114 | $scope.user.name = contextUsername; 115 | $scope.loadUserImage(contextUsername); 116 | $scope.getLikes(); 117 | }); 118 | }; 119 | 120 | 121 | /** 122 | * get UserInfo 123 | * @user object with name property 124 | */ 125 | $scope.getUserInfo = function(user) { 126 | UserInfo.get(user.name) 127 | .then(function (res) { 128 | console.log(res.data); 129 | $scope.user.updates = res.data.updates; 130 | }); 131 | }; 132 | 133 | /** 134 | * Initialize data for status and messages 135 | * 136 | */ 137 | $scope.init = function () { 138 | $scope.getUserMessages(); 139 | $scope.getUserInfo({name: contextUsername}); 140 | }; 141 | 142 | /** 143 | * Inserts a status update in db and adds return value to user.updates 144 | * @user object with name property 145 | * @message string 146 | */ 147 | $scope.updateStatus = function (user, message) { 148 | console.log(message); 149 | UserInfo.post(user.name, message) 150 | .then(function (res) { 151 | $scope.user.status = ''; 152 | $scope.user.updates.push(res.data); 153 | }); 154 | }; 155 | }]); 156 | -------------------------------------------------------------------------------- /packages/users/public/controllers/meanUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.users') 4 | .controller('AuthCtrl', ['$scope', '$rootScope', '$http', '$location', 'Global', 5 | function($scope, $rootScope, $http, $location, Global) { 6 | // This object will contain list of available social buttons to authorize 7 | $scope.socialButtonsCounter = 0; 8 | $scope.global = Global; 9 | 10 | $http.get('/get-config') 11 | .success(function(config) { 12 | $scope.socialButtons = config; 13 | }); 14 | } 15 | ]) 16 | .controller('LoginCtrl', ['$scope', '$rootScope', '$http', '$location', 'Global', 17 | function($scope, $rootScope, $http, $location, Global) { 18 | // This object will be filled by the form 19 | $scope.user = {}; 20 | $scope.global = Global; 21 | $scope.global.registerForm = false; 22 | $scope.input = { 23 | type: 'password', 24 | placeholder: 'Password', 25 | confirmPlaceholder: 'Repeat Password', 26 | iconClass: '', 27 | tooltipText: 'Show password' 28 | }; 29 | 30 | $scope.togglePasswordVisible = function() { 31 | $scope.input.type = $scope.input.type === 'text' ? 'password' : 'text'; 32 | $scope.input.placeholder = $scope.input.placeholder === 'Password' ? 'Visible Password' : 'Password'; 33 | $scope.input.iconClass = $scope.input.iconClass === 'icon_hide_password' ? '' : 'icon_hide_password'; 34 | $scope.input.tooltipText = $scope.input.tooltipText === 'Show password' ? 'Hide password' : 'Show password'; 35 | }; 36 | 37 | // Register the login() function 38 | $scope.login = function() { 39 | $http.post('/login', { 40 | email: $scope.user.email, 41 | password: $scope.user.password 42 | }) 43 | .success(function(response) { 44 | // authentication OK 45 | $scope.loginError = 0; 46 | $rootScope.user = response.user; 47 | $rootScope.$emit('loggedin'); 48 | if (response.redirect) { 49 | if (window.location.href === response.redirect) { 50 | //This is so an admin user will get full admin page 51 | window.location.reload(); 52 | } else { 53 | window.location = response.redirect; 54 | } 55 | } else { 56 | $location.url('/'); 57 | } 58 | }) 59 | .error(function() { 60 | $scope.loginerror = 'Authentication failed.'; 61 | }); 62 | }; 63 | } 64 | ]) 65 | .controller('RegisterCtrl', ['$scope', '$rootScope', '$http', '$location', 'Global', 66 | function($scope, $rootScope, $http, $location, Global) { 67 | $scope.user = {}; 68 | $scope.global = Global; 69 | $scope.global.registerForm = true; 70 | $scope.input = { 71 | type: 'password', 72 | placeholder: 'Password', 73 | placeholderConfirmPass: 'Repeat Password', 74 | iconClassConfirmPass: '', 75 | tooltipText: 'Show password', 76 | tooltipTextConfirmPass: 'Show password' 77 | }; 78 | 79 | $scope.togglePasswordVisible = function() { 80 | $scope.input.type = $scope.input.type === 'text' ? 'password' : 'text'; 81 | $scope.input.placeholder = $scope.input.placeholder === 'Password' ? 'Visible Password' : 'Password'; 82 | $scope.input.iconClass = $scope.input.iconClass === 'icon_hide_password' ? '' : 'icon_hide_password'; 83 | $scope.input.tooltipText = $scope.input.tooltipText === 'Show password' ? 'Hide password' : 'Show password'; 84 | }; 85 | $scope.togglePasswordConfirmVisible = function() { 86 | $scope.input.type = $scope.input.type === 'text' ? 'password' : 'text'; 87 | $scope.input.placeholderConfirmPass = $scope.input.placeholderConfirmPass === 'Repeat Password' ? 'Visible Password' : 'Repeat Password'; 88 | $scope.input.iconClassConfirmPass = $scope.input.iconClassConfirmPass === 'icon_hide_password' ? '' : 'icon_hide_password'; 89 | $scope.input.tooltipTextConfirmPass = $scope.input.tooltipTextConfirmPass === 'Show password' ? 'Hide password' : 'Show password'; 90 | }; 91 | 92 | $scope.register = function() { 93 | $scope.usernameError = null; 94 | $scope.registerError = null; 95 | $http.post('/register', { 96 | email: $scope.user.email, 97 | password: $scope.user.password, 98 | confirmPassword: $scope.user.confirmPassword, 99 | username: $scope.user.username, 100 | name: $scope.user.name 101 | }) 102 | .success(function() { 103 | // authentication OK 104 | $scope.registerError = 0; 105 | $rootScope.user = $scope.user; 106 | Global.user = $rootScope.user; 107 | Global.authenticated = !! $rootScope.user; 108 | $rootScope.$emit('loggedin'); 109 | $location.url('/'); 110 | }) 111 | .error(function(error) { 112 | // Error: authentication failed 113 | if (error === 'Username already taken') { 114 | $scope.usernameError = error; 115 | } else if (error === 'Email already taken') { 116 | $scope.emailError = error; 117 | } else $scope.registerError = error; 118 | }); 119 | }; 120 | } 121 | ]) 122 | .controller('ForgotPasswordCtrl', ['$scope', '$rootScope', '$http', '$location', 'Global', 123 | function($scope, $rootScope, $http, $location, Global) { 124 | $scope.user = {}; 125 | $scope.global = Global; 126 | $scope.global.registerForm = false; 127 | $scope.forgotpassword = function() { 128 | $http.post('/forgot-password', { 129 | text: $scope.user.email 130 | }) 131 | .success(function(response) { 132 | $scope.response = response; 133 | }) 134 | .error(function(error) { 135 | $scope.response = error; 136 | }); 137 | }; 138 | } 139 | ]) 140 | .controller('ResetPasswordCtrl', ['$scope', '$rootScope', '$http', '$location', '$stateParams', 'Global', 141 | function($scope, $rootScope, $http, $location, $stateParams, Global) { 142 | $scope.user = {}; 143 | $scope.global = Global; 144 | $scope.global.registerForm = false; 145 | $scope.resetpassword = function() { 146 | $http.post('/reset/' + $stateParams.tokenId, { 147 | password: $scope.user.password, 148 | confirmPassword: $scope.user.confirmPassword 149 | }) 150 | .success(function(response) { 151 | $rootScope.user = response.user; 152 | $rootScope.$emit('loggedin'); 153 | if (response.redirect) { 154 | if (window.location.href === response.redirect) { 155 | //This is so an admin user will get full admin page 156 | window.location.reload(); 157 | } else { 158 | window.location = response.redirect; 159 | } 160 | } else { 161 | $location.url('/'); 162 | } 163 | }) 164 | .error(function(error) { 165 | if (error.msg === 'Token invalid or expired') 166 | $scope.resetpassworderror = 'Could not update password as token is invalid or may have expired'; 167 | else 168 | $scope.validationError = error; 169 | }); 170 | }; 171 | } 172 | ]); -------------------------------------------------------------------------------- /packages/users/public/routes/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('mean.users').config(['$meanStateProvider', 5 | function($meanStateProvider) { 6 | // Check if the user is not connected 7 | var checkLoggedOut = function($q, $timeout, $http, $location) { 8 | // Initialize a new promise 9 | var deferred = $q.defer(); 10 | 11 | // Make an AJAX call to check if the user is logged in 12 | $http.get('/loggedin').success(function(user) { 13 | // Authenticated 14 | if (user !== '0') { 15 | $timeout(deferred.reject); 16 | $location.url('/login'); 17 | } 18 | 19 | // Not Authenticated 20 | else $timeout(deferred.resolve); 21 | }); 22 | 23 | return deferred.promise; 24 | }; 25 | 26 | 27 | // states for my app 28 | $meanStateProvider 29 | .state('auth', { 30 | url: '/auth', 31 | templateUrl: 'users/views/index.html' 32 | }) 33 | .state('auth.login', { 34 | url: '/login', 35 | templateUrl: 'users/views/login.html', 36 | resolve: { 37 | loggedin: checkLoggedOut 38 | } 39 | }) 40 | .state('auth.register', { 41 | url: '/register', 42 | templateUrl: 'users/views/register.html', 43 | resolve: { 44 | loggedin: checkLoggedOut 45 | } 46 | }) 47 | .state('forgot-password', { 48 | url: '/forgot-password', 49 | templateUrl: 'users/views/forgot-password.html', 50 | resolve: { 51 | loggedin: checkLoggedOut 52 | } 53 | }) 54 | .state('reset-password', { 55 | url: '/reset/:tokenId', 56 | templateUrl: 'users/views/reset-password.html', 57 | resolve: { 58 | loggedin: checkLoggedOut 59 | } 60 | }); 61 | } 62 | ]); 63 | -------------------------------------------------------------------------------- /packages/users/public/services/meanUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mean.users') 4 | 5 | .factory('MeanUser', [ 6 | 7 | function() { 8 | return { 9 | name: 'users' 10 | }; 11 | } 12 | ]) 13 | 14 | // Factory to retrieve tree images 15 | .factory('TreeImage', ['$http', 16 | function($http, $stateParams) { 17 | var imageStore = {}; 18 | 19 | var loadTreeImage = function(treeId, cb) { 20 | if (imageStore[treeId]){ 21 | cb(imageStore[treeId]); 22 | } else { 23 | $http.get('/treeimage/'+treeId) 24 | .success(function(url){ 25 | imageStore[treeId] = url; 26 | cb(url); 27 | }) 28 | .error(function(){ 29 | console.log('Error getting tree image URL'); 30 | }); 31 | } 32 | }; 33 | 34 | 35 | return { 36 | loadTreeImage: loadTreeImage, 37 | }; 38 | } 39 | ]) 40 | 41 | .factory('UserLikes', ['$http', 42 | function($http) { 43 | 44 | var getLikes = function(username, cb){ 45 | var treeLikes = []; 46 | $http.post('/userlikes', {username: username}) 47 | .success(function(data){ 48 | data.forEach(function(treeLike){ 49 | console.log(treeLike); 50 | if (treeLikes.indexOf(treeLike.name) === -1){ 51 | treeLikes.push({ 52 | name: treeLike.name, 53 | id: treeLike.treeid, 54 | imgUrl: treeLike.url 55 | }); 56 | } 57 | }); 58 | cb(treeLikes); 59 | }) 60 | .error(function(error){ 61 | console.log('error getting userlikes'); 62 | }); 63 | }; 64 | 65 | 66 | return { 67 | getLikes: getLikes, 68 | }; 69 | } 70 | ]) 71 | 72 | /** 73 | * GetUserMessages factory to handle the routing to the server for getting user messages 74 | */ 75 | .factory('GetUserMessages', ['$resource', '$stateParams', 76 | function($resource, $stateParams) { 77 | return $resource('usermessages/:username', { 78 | treeid: '@_username' 79 | }, { 80 | get: { 81 | method: 'GET', 82 | isArray: true 83 | } 84 | }); 85 | } 86 | ]) 87 | 88 | .factory('UserInfo', ['$http', 89 | function($http) { 90 | var get = function (username) { 91 | return $http({ 92 | url: '/users/' + username, 93 | method: 'GET' 94 | }); 95 | }; 96 | 97 | var post = function (username, status) { 98 | return $http({ 99 | url: '/users/' + username + '/status/', 100 | method:'POST', 101 | data: {status: status} 102 | }); 103 | }; 104 | 105 | return { 106 | get: get, 107 | post: post 108 | }; 109 | } 110 | ]); 111 | 112 | -------------------------------------------------------------------------------- /packages/users/public/tests/auth.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | // Login Controller Spec 5 | describe('MEAN controllers', function() { 6 | describe('LoginCtrl', function() { 7 | beforeEach(function() { 8 | jasmine.addMatchers({ 9 | toEqualData: function() { 10 | return { 11 | compare: function(actual, expected) { 12 | return { 13 | pass: angular.equals(actual, expected) 14 | }; 15 | } 16 | }; 17 | } 18 | }); 19 | }); 20 | 21 | beforeEach(function() { 22 | module('mean'); 23 | module('mean.system'); 24 | module('mean.users'); 25 | }); 26 | 27 | var LoginCtrl, 28 | scope, 29 | $rootScope, 30 | $httpBackend, 31 | $location; 32 | 33 | beforeEach(inject(function($controller, _$rootScope_, _$location_, _$httpBackend_) { 34 | 35 | scope = _$rootScope_.$new(); 36 | $rootScope = _$rootScope_; 37 | 38 | LoginCtrl = $controller('LoginCtrl', { 39 | $scope: scope, 40 | $rootScope: _$rootScope_ 41 | }); 42 | 43 | $httpBackend = _$httpBackend_; 44 | 45 | $location = _$location_; 46 | 47 | })); 48 | 49 | afterEach(function() { 50 | $httpBackend.verifyNoOutstandingExpectation(); 51 | $httpBackend.verifyNoOutstandingRequest(); 52 | }); 53 | 54 | it('should login with a correct user and password', function() { 55 | 56 | spyOn($rootScope, '$emit'); 57 | // test expected GET request 58 | $httpBackend.when('POST', '/login').respond(200, { 59 | user: 'Fred' 60 | }); 61 | scope.login(); 62 | $httpBackend.flush(); 63 | // test scope value 64 | expect($rootScope.user).toEqual('Fred'); 65 | expect($rootScope.$emit).toHaveBeenCalledWith('loggedin'); 66 | expect($location.url()).toEqual('/'); 67 | }); 68 | 69 | 70 | 71 | it('should fail to log in ', function() { 72 | $httpBackend.expectPOST('/login').respond(400, 'Authentication failed'); 73 | scope.login(); 74 | $httpBackend.flush(); 75 | // test scope value 76 | expect(scope.loginerror).toEqual('Authentication failed.'); 77 | 78 | }); 79 | }); 80 | 81 | describe('RegisterCtrl', function() { 82 | beforeEach(function() { 83 | jasmine.addMatchers({ 84 | toEqualData: function() { 85 | return { 86 | compare: function(actual, expected) { 87 | return { 88 | pass: angular.equals(actual, expected) 89 | }; 90 | } 91 | }; 92 | } 93 | }); 94 | }); 95 | 96 | beforeEach(function() { 97 | module('mean'); 98 | module('mean.system'); 99 | module('mean.users'); 100 | }); 101 | 102 | var RegisterCtrl, 103 | scope, 104 | $rootScope, 105 | $httpBackend, 106 | $location; 107 | 108 | beforeEach(inject(function($controller, _$rootScope_, _$location_, _$httpBackend_) { 109 | 110 | scope = _$rootScope_.$new(); 111 | $rootScope = _$rootScope_; 112 | 113 | RegisterCtrl = $controller('RegisterCtrl', { 114 | $scope: scope, 115 | $rootScope: _$rootScope_ 116 | }); 117 | 118 | $httpBackend = _$httpBackend_; 119 | 120 | $location = _$location_; 121 | 122 | })); 123 | 124 | afterEach(function() { 125 | $httpBackend.verifyNoOutstandingExpectation(); 126 | $httpBackend.verifyNoOutstandingRequest(); 127 | }); 128 | 129 | it('should register with correct data', function() { 130 | 131 | spyOn($rootScope, '$emit'); 132 | // test expected GET request 133 | scope.user.name = 'Fred'; 134 | $httpBackend.when('POST', '/register').respond(200, 'Fred'); 135 | scope.register(); 136 | $httpBackend.flush(); 137 | // test scope value 138 | expect($rootScope.user.name).toBe('Fred'); 139 | expect(scope.registerError).toEqual(0); 140 | expect($rootScope.$emit).toHaveBeenCalledWith('loggedin'); 141 | expect($location.url()).toBe('/'); 142 | }); 143 | 144 | 145 | 146 | it('should fail to register with duplicate Username', function() { 147 | $httpBackend.when('POST', '/register').respond(400, 'Username already taken'); 148 | scope.register(); 149 | $httpBackend.flush(); 150 | // test scope value 151 | expect(scope.usernameError).toBe('Username already taken'); 152 | expect(scope.registerError).toBe(null); 153 | }); 154 | 155 | it('should fail to register with non-matching passwords', function() { 156 | $httpBackend.when('POST', '/register').respond(400, 'Password mismatch'); 157 | scope.register(); 158 | $httpBackend.flush(); 159 | // test scope value 160 | expect(scope.usernameError).toBe(null); 161 | expect(scope.registerError).toBe('Password mismatch'); 162 | }); 163 | }); 164 | 165 | describe('ForgotPasswordCtrl', function() { 166 | beforeEach(function() { 167 | jasmine.addMatchers({ 168 | toEqualData: function() { 169 | return { 170 | compare: function(actual, expected) { 171 | return { 172 | pass: angular.equals(actual, expected) 173 | }; 174 | } 175 | }; 176 | } 177 | }); 178 | }); 179 | 180 | beforeEach(function() { 181 | module('mean'); 182 | module('mean.system'); 183 | module('mean.users'); 184 | }); 185 | 186 | var ForgotPasswordCtrl, 187 | scope, 188 | $rootScope, 189 | $httpBackend ; 190 | 191 | beforeEach(inject(function($controller, _$rootScope_, _$httpBackend_) { 192 | 193 | scope = _$rootScope_.$new(); 194 | $rootScope = _$rootScope_; 195 | 196 | ForgotPasswordCtrl = $controller('ForgotPasswordCtrl', { 197 | $scope: scope, 198 | $rootScope: _$rootScope_ 199 | }); 200 | 201 | $httpBackend = _$httpBackend_; 202 | 203 | })); 204 | 205 | afterEach(function() { 206 | $httpBackend.verifyNoOutstandingExpectation(); 207 | $httpBackend.verifyNoOutstandingRequest(); 208 | }); 209 | 210 | it('should display success response on success', function() { 211 | scope.user.email = 'test@test.com'; 212 | $httpBackend.when('POST', '/forgot-password').respond(200,'Mail successfully sent'); 213 | scope.forgotpassword(); 214 | $httpBackend.flush(); 215 | 216 | expect(scope.response).toEqual('Mail successfully sent'); 217 | 218 | }); 219 | it('should display error response on failure', function() { 220 | scope.user.email = 'test@test.com'; 221 | $httpBackend.when('POST', '/forgot-password').respond(400,'User does not exist'); 222 | scope.forgotpassword(); 223 | $httpBackend.flush(); 224 | 225 | expect(scope.response).toEqual('User does not exist'); 226 | 227 | }); 228 | 229 | }); 230 | }); 231 | 232 | 233 | }()); 234 | -------------------------------------------------------------------------------- /packages/users/public/views/forgot-password.html: -------------------------------------------------------------------------------- 1 |
2 |
{{response.message}}
3 |

Forgot Password

4 |

Please enter your email address to reset your password.

5 |
6 |
7 | 8 | 10 |
Please enter a correct info #
11 | 12 |
13 |
14 | 15 |

Don't have an account?

16 | Sign up 17 |
18 | 19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /packages/users/public/views/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 37 | 38 |
39 | -------------------------------------------------------------------------------- /packages/users/public/views/login.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
{{loginerror}}
4 | 5 |

Login

6 | 7 |
8 |
9 | 10 |
Please enter a correct info #
11 | 12 |
13 |
14 | 15 |
Please enter a correct info #
16 | 17 |
18 | {{input.tooltipText}} 19 |
20 |
21 |
22 | 25 | 30 |
31 |
32 | -------------------------------------------------------------------------------- /packages/users/public/views/register.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{error.msg}}
4 |
5 |
{{usernameError}}
6 |
{{emailError}}
7 |
8 |

Sign Up

9 | 10 |
11 | 13 | 14 |
Please enter a correct info #
15 | 16 |
17 | 18 |
19 | 21 | 22 |
Please enter a correct info #
23 | 24 |
25 | 26 |
27 | 29 | 30 |
Please enter a correct info #
31 | 32 |
33 | 34 |
35 | 37 | 38 |
Please enter a correct info #
39 | 40 |
41 | {{input.tooltipText}} 42 |
43 |
44 |
45 |
46 | 48 | 49 |
Please enter a correct info #
50 | 51 |
52 | {{input.tooltipTextConfirmPass}} 53 |
54 |
55 |
56 | 57 |
58 | 59 |

Already have an account?

60 | Log in 61 |
62 |
63 |
64 | -------------------------------------------------------------------------------- /packages/users/public/views/reset-password.html: -------------------------------------------------------------------------------- 1 |
2 |
{{resetpassworderror}}
3 |
4 |
{{error.msg}}
5 |
6 |

Enter your new password

7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /packages/users/public/views/userProfile.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
6 | 7 |
8 | 9 | 10 |
11 | 12 | 13 |
14 |
15 |

16 | ... 17 |
18 |

{{user.name}}

19 |
20 | 23 |
{{user.uploadError}}
24 |
25 |
26 |
27 | 28 |

My Forest

29 |
30 |
31 | {{like.name}} 32 |
33 |
34 | 35 | 36 |
37 | 38 |
39 | 40 | 47 | 48 | 49 |
50 |
51 | 52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 | 60 | 61 |
62 |
    63 |
  • {{update.status}}
  • 64 |
65 |
66 | 67 |
68 |
69 |
70 | 71 |
72 |
73 |
74 | 75 |
76 |
77 | 78 | 79 | 80 |
81 |

{{like.name}}

82 | Like 83 |
84 |
85 |
86 | 88 | 89 |
90 |
91 |
92 | 93 |
94 |
95 | 96 |
97 |

My Comments

98 |
99 |
100 | 101 |
102 | {{ message.name }}
103 | {{message.createdat | date: 'medium'}} 104 |

{{message.message}}

105 |
106 |
107 |
108 | 109 |
110 |
111 | 112 |
113 |
114 |
115 | 116 |
117 | -------------------------------------------------------------------------------- /packages/users/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema, 8 | crypto = require('crypto'), 9 | _ = require('lodash'); 10 | 11 | /** 12 | * Validations 13 | */ 14 | var validatePresenceOf = function(value) { 15 | // If you are authenticating by any of the oauth strategies, don't validate. 16 | return (this.provider && this.provider !== 'local') || (value && value.length); 17 | }; 18 | 19 | var validateUniqueEmail = function(value, callback) { 20 | var User = mongoose.model('User'); 21 | User.find({ 22 | $and: [{ 23 | email: value 24 | }, { 25 | _id: { 26 | $ne: this._id 27 | } 28 | }] 29 | }, function(err, user) { 30 | callback(err || user.length === 0); 31 | }); 32 | }; 33 | 34 | /** 35 | * Getter 36 | */ 37 | var escapeProperty = function(value) { 38 | return _.escape(value); 39 | }; 40 | 41 | /** 42 | * Update Schema 43 | */ 44 | var UpdateSchema = new Schema({ 45 | status: { 46 | type: String, 47 | required: true, 48 | get: escapeProperty 49 | }, 50 | updated: { 51 | type: Date, 52 | default: Date.now 53 | } 54 | }); 55 | 56 | 57 | /** 58 | * User Schema 59 | */ 60 | 61 | var UserSchema = new Schema({ 62 | name: { 63 | type: String, 64 | required: true, 65 | get: escapeProperty 66 | }, 67 | email: { 68 | type: String, 69 | required: true, 70 | unique: true, 71 | // Regexp to validate emails with more strict rules as added in tests/users.js which also conforms mostly with RFC2822 guide lines 72 | match: [/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 'Please enter a valid email'], 73 | validate: [validateUniqueEmail, 'E-mail address is already in-use'] 74 | }, 75 | username: { 76 | type: String, 77 | unique: true, 78 | required: true, 79 | get: escapeProperty 80 | }, 81 | roles: { 82 | type: Array, 83 | default: ['authenticated'] 84 | }, 85 | hashed_password: { 86 | type: String, 87 | validate: [validatePresenceOf, 'Password cannot be blank'] 88 | }, 89 | provider: { 90 | type: String, 91 | default: 'local' 92 | }, 93 | imageUrl: { 94 | type: String, 95 | default: '' 96 | }, 97 | updates: [UpdateSchema], 98 | salt: String, 99 | resetPasswordToken: String, 100 | resetPasswordExpires: Date, 101 | profile: {}, 102 | facebook: {}, 103 | twitter: {}, 104 | github: {}, 105 | google: {}, 106 | linkedin: {} 107 | }); 108 | 109 | /** 110 | * Virtuals 111 | */ 112 | UserSchema.virtual('password').set(function(password) { 113 | this._password = password; 114 | this.salt = this.makeSalt(); 115 | this.hashed_password = this.hashPassword(password); 116 | }).get(function() { 117 | return this._password; 118 | }); 119 | 120 | /** 121 | * Pre-save hook 122 | */ 123 | UserSchema.pre('save', function(next) { 124 | if (this.isNew && this.provider === 'local' && this.password && !this.password.length) 125 | return next(new Error('Invalid password')); 126 | next(); 127 | }); 128 | 129 | /** 130 | * Methods 131 | */ 132 | UserSchema.methods = { 133 | 134 | /** 135 | * HasRole - check if the user has required role 136 | * 137 | * @param {String} plainText 138 | * @return {Boolean} 139 | * @api public 140 | */ 141 | hasRole: function(role) { 142 | var roles = this.roles; 143 | return roles.indexOf('admin') !== -1 || roles.indexOf(role) !== -1; 144 | }, 145 | 146 | /** 147 | * IsAdmin - check if the user is an administrator 148 | * 149 | * @return {Boolean} 150 | * @api public 151 | */ 152 | isAdmin: function() { 153 | return this.roles.indexOf('admin') !== -1; 154 | }, 155 | 156 | /** 157 | * Authenticate - check if the passwords are the same 158 | * 159 | * @param {String} plainText 160 | * @return {Boolean} 161 | * @api public 162 | */ 163 | authenticate: function(plainText) { 164 | return this.hashPassword(plainText) === this.hashed_password; 165 | }, 166 | 167 | /** 168 | * Make salt 169 | * 170 | * @return {String} 171 | * @api public 172 | */ 173 | makeSalt: function() { 174 | return crypto.randomBytes(16).toString('base64'); 175 | }, 176 | 177 | /** 178 | * Hash password 179 | * 180 | * @param {String} password 181 | * @return {String} 182 | * @api public 183 | */ 184 | hashPassword: function(password) { 185 | if (!password || !this.salt) return ''; 186 | var salt = new Buffer(this.salt, 'base64'); 187 | return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); 188 | }, 189 | 190 | /** 191 | * Hide security sensitive fields 192 | * 193 | * @returns {*|Array|Binary|Object} 194 | */ 195 | toJSON: function() { 196 | var obj = this.toObject(); 197 | delete obj.hashed_password; 198 | delete obj.salt; 199 | return obj; 200 | } 201 | }; 202 | 203 | mongoose.model('Update', UpdateSchema); 204 | mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /packages/users/server/routes/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // User routes use users controller 4 | var users = require('../controllers/users'), 5 | config = require('meanio').loadConfig(); 6 | 7 | module.exports = function(MeanUser, app, auth, database, passport) { 8 | 9 | app.route('/logout') 10 | .get(users.signout); 11 | app.route('/users/me') 12 | .get(users.me); 13 | 14 | // Setting up the users api 15 | app.route('/register') 16 | .post(users.create); 17 | 18 | app.route('/forgot-password') 19 | .post(users.forgotpassword); 20 | 21 | app.route('/reset/:token') 22 | .post(users.resetpassword); 23 | 24 | // Setting up the userId param 25 | app.param('userId', users.user); 26 | 27 | // AngularJS route to check for authentication 28 | app.route('/loggedin') 29 | .get(function(req, res) { 30 | res.send(req.isAuthenticated() ? req.user : '0'); 31 | }); 32 | 33 | // Setting the local strategy route 34 | app.route('/login') 35 | .post(passport.authenticate('local', { 36 | failureFlash: true 37 | }), function(req, res) { 38 | res.send({ 39 | user: req.user, 40 | redirect: '/' 41 | }); 42 | }); 43 | 44 | app.route('/users/:user') 45 | .get(users.getUserInfo); 46 | 47 | app.route('/users/:user/status/') 48 | .post(users.updateStatus); 49 | 50 | // Save user profile url in mongo db 51 | app.route('/userimage') 52 | .post(users.updateImageUrl); 53 | 54 | // Get user profile url from mongo db 55 | app.route('/userimage/*') 56 | .get(users.getImageUrl); 57 | 58 | // AngularJS route to get config of social buttons 59 | app.route('/get-config') 60 | .get(function (req, res) { 61 | // To avoid displaying unneccesary social logins 62 | var clientIdProperty = 'clientID'; 63 | var defaultPrefix = 'DEFAULT_'; 64 | var socialNetworks = ['facebook','linkedin','twitter','github','google']; //ugly hardcoding :( 65 | var configuredApps = {}; 66 | for (var network in socialNetworks){ 67 | var netObject = config[socialNetworks[network]]; 68 | if ( netObject.hasOwnProperty(clientIdProperty) ) { 69 | if (netObject[clientIdProperty].indexOf(defaultPrefix) === -1 ){ 70 | configuredApps[socialNetworks[network]] = true ; 71 | } 72 | } 73 | } 74 | res.send(configuredApps); 75 | }); 76 | 77 | // Setting the facebook oauth routes 78 | app.route('/auth/facebook') 79 | .get(passport.authenticate('facebook', { 80 | scope: ['email', 'user_about_me'], 81 | failureRedirect: '#!/login' 82 | }), users.signin); 83 | 84 | app.route('/auth/facebook/callback') 85 | .get(passport.authenticate('facebook', { 86 | failureRedirect: '#!/login' 87 | }), users.authCallback); 88 | 89 | // Setting the github oauth routes 90 | app.route('/auth/github') 91 | .get(passport.authenticate('github', { 92 | failureRedirect: '#!/login' 93 | }), users.signin); 94 | 95 | app.route('/auth/github/callback') 96 | .get(passport.authenticate('github', { 97 | failureRedirect: '#!/login' 98 | }), users.authCallback); 99 | 100 | // Setting the twitter oauth routes 101 | app.route('/auth/twitter') 102 | .get(passport.authenticate('twitter', { 103 | failureRedirect: '#!/login' 104 | }), users.signin); 105 | 106 | app.route('/auth/twitter/callback') 107 | .get(passport.authenticate('twitter', { 108 | failureRedirect: '#!/login' 109 | }), users.authCallback); 110 | 111 | // Setting the google oauth routes 112 | app.route('/auth/google') 113 | .get(passport.authenticate('google', { 114 | failureRedirect: '#!/login', 115 | scope: [ 116 | 'https://www.googleapis.com/auth/userinfo.profile', 117 | 'https://www.googleapis.com/auth/userinfo.email' 118 | ] 119 | }), users.signin); 120 | 121 | app.route('/auth/google/callback') 122 | .get(passport.authenticate('google', { 123 | failureRedirect: '#!/login' 124 | }), users.authCallback); 125 | 126 | // Setting the linkedin oauth routes 127 | app.route('/auth/linkedin') 128 | .get(passport.authenticate('linkedin', { 129 | failureRedirect: '#!/login', 130 | scope: ['r_emailaddress'] 131 | }), users.signin); 132 | 133 | app.route('/auth/linkedin/callback') 134 | .get(passport.authenticate('linkedin', { 135 | failureRedirect: '#!/login' 136 | }), users.authCallback); 137 | 138 | }; 139 | -------------------------------------------------------------------------------- /packages/users/server/template.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | forgot_password_email: function(user, req, token, mailOptions) { 5 | mailOptions.html = [ 6 | 'Hi ' + user.name + ',', 7 | 'We have received a request to reset the password for your account.', 8 | 'If you made this request, please click on the link below or paste this into your browser to complete the process:', 9 | 'http://' + req.headers.host + '/#!/reset/' + token, 10 | 'This link will work for 1 hour or until your password is reset.', 11 | 'If you did not ask to change your password, please ignore this email and your account will remain unchanged.' 12 | ].join('\n\n'); 13 | mailOptions.subject = 'Resetting the password'; 14 | return mailOptions; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | var cl = console.log; 5 | console.log = function(){ 6 | console.trace(); 7 | cl.apply(console,arguments); 8 | }; 9 | */ 10 | 11 | // Requires meanio . 12 | var mean = require('meanio'); 13 | var cluster = require('cluster'); 14 | 15 | 16 | // Code to run if we're in the master process or if we are not in debug mode/ running tests 17 | 18 | if ((cluster.isMaster) && (process.execArgv.indexOf('--debug') < 0) && (process.env.NODE_ENV!=='test') && (process.execArgv.indexOf('--singleProcess')<0)) { 19 | //if (cluster.isMaster) { 20 | 21 | console.log('for real!'); 22 | // Count the machine's CPUs 23 | var cpuCount = require('os').cpus().length; 24 | 25 | // Create a worker for each CPU 26 | for (var i = 0; i < cpuCount; i += 1) { 27 | console.log ('forking ',i); 28 | cluster.fork(); 29 | } 30 | 31 | // Listen for dying workers 32 | cluster.on('exit', function (worker) { 33 | // Replace the dead worker, we're not sentimental 34 | console.log('Worker ' + worker.id + ' died :('); 35 | cluster.fork(); 36 | 37 | }); 38 | 39 | // Code to run if we're in a worker process 40 | } else { 41 | 42 | var workerId = 0; 43 | if (!cluster.isMaster) 44 | { 45 | workerId = cluster.worker.id; 46 | } 47 | // Creates and serves mean application 48 | mean.serve({ workerid: workerId /* more options placeholder*/ }, function (app) { 49 | var config = app.config.clean; 50 | var port = config.https && config.https.port ? config.https.port : config.http.port; 51 | console.log('Mean app started on port ' + port + ' (' + process.env.NODE_ENV + ') cluster.worker.id:', workerId); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /toDo: -------------------------------------------------------------------------------- 1 | 2 | 3 | Refactor "articles" to "trees" - entire folder uncluding outside references - should be done on its own branch 4 | 5 | Fix the frontpage so the is a shaded gap between the "explore the forest" and the "about the project line" 6 | 7 | Make an about page 8 | 9 | Ability for users to like trees. 10 | -------------------------------------------------------------------------------- /tools/grunt/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var paths = { 4 | js: ['*.js', 'test/**/*.js', '!test/coverage/**', '!bower_components/**', 'packages/**/*.js', '!packages/**/node_modules/**', '!packages/contrib/**/*.js', '!packages/contrib/**/node_modules/**'], 5 | html: ['packages/**/public/**/views/**', 'packages/**/server/views/**'], 6 | css: ['!bower_components/**', 'packages/**/public/**/css/*.css', '!packages/contrib/**/public/**/css/*.css'] 7 | }; 8 | 9 | module.exports = function(grunt) { 10 | 11 | if (process.env.NODE_ENV !== 'production') { 12 | require('time-grunt')(grunt); 13 | } 14 | 15 | // Project Configuration 16 | grunt.initConfig({ 17 | pkg: grunt.file.readJSON('package.json'), 18 | assets: grunt.file.readJSON('config/assets.json'), 19 | clean: ['bower_components/build'], 20 | watch: { 21 | js: { 22 | files: paths.js, 23 | tasks: ['jshint'], 24 | options: { 25 | livereload: true 26 | } 27 | }, 28 | html: { 29 | files: paths.html, 30 | options: { 31 | livereload: true, 32 | interval: 500 33 | } 34 | }, 35 | css: { 36 | files: paths.css, 37 | tasks: ['csslint'], 38 | options: { 39 | livereload: true 40 | } 41 | } 42 | }, 43 | jshint: { 44 | all: { 45 | src: paths.js, 46 | options: { 47 | jshintrc: true 48 | } 49 | } 50 | }, 51 | uglify: { 52 | core: { 53 | options: { 54 | mangle: false 55 | }, 56 | files: '<%= assets.core.js %>' 57 | } 58 | }, 59 | csslint: { 60 | options: { 61 | csslintrc: '.csslintrc' 62 | }, 63 | src: paths.css 64 | }, 65 | cssmin: { 66 | core: { 67 | files: '<%= assets.core.css %>' 68 | } 69 | }, 70 | nodemon: { 71 | dev: { 72 | script: 'server.js', 73 | options: { 74 | args: [], 75 | ignore: ['node_modules/**'], 76 | ext: 'js,html', 77 | nodeArgs: ['--debug'], 78 | delayTime: 1, 79 | cwd: __dirname 80 | } 81 | } 82 | }, 83 | concurrent: { 84 | tasks: ['nodemon', 'watch'], 85 | options: { 86 | logConcurrentOutput: true 87 | } 88 | }, 89 | mochaTest: { 90 | options: { 91 | reporter: 'spec', 92 | require: [ 93 | 'server.js', 94 | function() { 95 | require('meanio/lib/util').preload(__dirname + '/packages/**/server', 'model'); 96 | } 97 | ] 98 | }, 99 | src: ['packages/**/server/tests/**/*.js'] 100 | }, 101 | env: { 102 | test: { 103 | NODE_ENV: 'test' 104 | } 105 | }, 106 | karma: { 107 | unit: { 108 | configFile: 'karma.conf.js' 109 | } 110 | } 111 | }); 112 | 113 | //Load NPM tasks 114 | require('load-grunt-tasks')(grunt); 115 | 116 | /** 117 | * Default Task 118 | */ 119 | grunt.hook.push('clean', -9999); 120 | grunt.hook.push('concurrent', 9999); 121 | if (process.env.NODE_ENV === 'production') { 122 | grunt.hook.push('cssmin', 100); 123 | grunt.hook.push('uglify', 200); 124 | } else { 125 | grunt.hook.push('jshint', -200); 126 | grunt.hook.push('csslint', 100); 127 | } 128 | 129 | //Default task. 130 | grunt.registerTask('default', ['hook']); 131 | 132 | //Test task. 133 | grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']); 134 | 135 | // For Heroku users only. 136 | // Docs: https://github.com/linnovate/mean/wiki/Deploying-on-Heroku 137 | grunt.registerTask('heroku:production', ['cssmin', 'uglify']); 138 | }; -------------------------------------------------------------------------------- /tools/grunt/grunt.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "grunt-cli": "^0.1.13", 4 | "grunt-hook": "~0.3.0", 5 | "grunt-concurrent": "^1.0.0", 6 | "grunt-contrib-clean": "^0.6.0", 7 | "grunt-contrib-csslint": "^0.3.1", 8 | "grunt-contrib-cssmin": "^0.10.0", 9 | "grunt-contrib-jshint": "^0.10.0", 10 | "grunt-contrib-uglify": "^0.5.1", 11 | "grunt-contrib-watch": "^0.6.1", 12 | "grunt-env": "^0.4.1", 13 | "grunt-nodemon": "^0.3.0", 14 | "load-grunt-tasks": "^0.6.0", 15 | "time-grunt": "^1.0.0" 16 | }, 17 | "devDependencies": { 18 | "grunt-karma": "^0.8.2", 19 | "grunt-mocha-test": "^0.10.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tools/gulp/gulp.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "del": "^0.1.3", 4 | "gulp": "^3.8.8", 5 | "gulp-concat": "^2.4.1", 6 | "gulp-csslint": "^0.1.5", 7 | "gulp-cssmin": "^0.1.6", 8 | "gulp-jshint": "^1.8.5", 9 | "gulp-less": "^1.3.6", 10 | "gulp-livereload": "^2.1.1", 11 | "gulp-load-plugins": "^0.7.0", 12 | "gulp-mocha": "^1.1.0", 13 | "gulp-nodemon": "^1.0.4", 14 | "gulp-rimraf": "^0.1.1", 15 | "gulp-uglify": "^1.0.1", 16 | "gulp-util": "^3.0.1", 17 | "jshint-stylish": "^1.0.0", 18 | "through": "^2.3.6" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tools/gulp/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var del = require('del'), 4 | path = require('path'), 5 | gulp = require('gulp'), 6 | gutil = require('gulp-util'), 7 | through = require('through'), 8 | gulpLoadPlugins = require('gulp-load-plugins'), 9 | karma = require('karma').server, 10 | plugins = gulpLoadPlugins(), 11 | paths = { 12 | js: ['*.js', 'test/**/*.js', '!test/coverage/**', '!bower_components/**', 'packages/**/*.js', '!packages/**/node_modules/**', '!packages/contrib/**/*.js', '!packages/contrib/**/node_modules/**'], 13 | html: ['packages/**/public/**/views/**', 'packages/**/server/views/**'], 14 | css: ['!bower_components/**', 'packages/**/public/**/css/*.css', '!packages/contrib/**/public/**/css/*.css'], 15 | less: ['**/public/**/css/*.less'] 16 | }, 17 | assets = require('./config/assets.json'), 18 | _ = require('lodash'); 19 | 20 | function count(taskName, message) { 21 | var fileCount = 0; 22 | 23 | function countFiles(file) { 24 | fileCount++; // jshint ignore:line 25 | } 26 | 27 | function endStream() { 28 | gutil.log(gutil.colors.cyan(taskName + ': ') + fileCount + ' ' + message || 'files processed.'); 29 | this.emit('end'); // jshint ignore:line 30 | } 31 | 32 | return through(countFiles, endStream); 33 | } 34 | 35 | function tokenizeConfig(config) { 36 | var destTokens = _.keys(config)[0].split('/'); 37 | 38 | return { 39 | srcGlob: _.flatten(_.values(config)), 40 | destDir: destTokens[destTokens.length - 2], 41 | destFile: destTokens[destTokens.length - 1] 42 | }; 43 | } 44 | 45 | gulp.task('csslint', function () { 46 | return gulp.src(paths.css) 47 | .pipe(plugins.csslint('.csslintrc')) 48 | .pipe(plugins.csslint.reporter()) 49 | .pipe(count('csslint', 'files lint free')); 50 | }); 51 | 52 | gulp.task('cssmin', function () { 53 | var config = tokenizeConfig(assets.core.css); 54 | 55 | if (config.srcGlob.length) { 56 | return gulp.src(config.srcGlob) 57 | .pipe(plugins.cssmin({keepBreaks: true})) 58 | .pipe(plugins.concat(config.destFile)) 59 | .pipe(gulp.dest(path.join('bower_components/build', config.destDir))); 60 | } 61 | }); 62 | 63 | gulp.task('less', function() { 64 | return gulp.src(paths.less) 65 | .pipe(plugins.less()) 66 | .pipe(gulp.dest(function (vinylFile) { 67 | return vinylFile.cwd; 68 | })); 69 | }); 70 | 71 | gulp.task('jshint', function () { 72 | return gulp.src(paths.js) 73 | .pipe(plugins.jshint()) 74 | .pipe(plugins.jshint.reporter('jshint-stylish')) 75 | .pipe(plugins.jshint.reporter('fail')) 76 | .pipe(count('jshint', 'files lint free')); 77 | }); 78 | 79 | gulp.task('uglify', function () { 80 | var config = tokenizeConfig(assets.core.js); 81 | 82 | if (config.srcGlob.length) { 83 | return gulp.src(config.srcGlob) 84 | .pipe(plugins.concat(config.destFile)) 85 | .pipe(plugins.uglify({mangle: false})) 86 | .pipe(gulp.dest(path.join('bower_components/build', config.destDir))); 87 | } 88 | }); 89 | 90 | gulp.task('env:test', function () { 91 | process.env.NODE_ENV = 'test'; 92 | }); 93 | 94 | gulp.task('env:develop', function () { 95 | process.env.NODE_ENV = 'development'; 96 | }); 97 | 98 | gulp.task('env:production', function () { 99 | process.env.NODE_ENV = 'production'; 100 | }); 101 | 102 | gulp.task('karma:unit', function (done) { 103 | karma.start({ 104 | configFile: __dirname + '/karma.conf.js', 105 | singleRun: true 106 | }, done); 107 | }); 108 | 109 | gulp.task('loadTestSchema', function () { 110 | require('server.js'); 111 | require('meanio/lib/util').preload(__dirname + '/packages/**/server', 'model'); 112 | }); 113 | 114 | gulp.task('mochaTest', ['loadTestSchema'], function () { 115 | return gulp.src('packages/**/server/tests/**/*.js', {read: false}) 116 | .pipe(plugins.mocha({ 117 | reporter: 'spec' 118 | })); 119 | }); 120 | 121 | gulp.task('watch', function () { 122 | gulp.watch(paths.js, ['jshint']).on('change', plugins.livereload.changed); 123 | gulp.watch(paths.html).on('change', plugins.livereload.changed); 124 | gulp.watch(paths.css, ['csslint']).on('change', plugins.livereload.changed); 125 | gulp.watch(paths.less, ['less']).on('change', plugins.livereload.changed); 126 | 127 | plugins.livereload.listen({interval: 500}); 128 | }); 129 | 130 | // https://github.com/gulpjs/gulp/blob/master/docs/recipes/delete-files-folder.md 131 | gulp.task('clean', function (cb) { 132 | return del(['bower_components/build'], cb); 133 | }); 134 | 135 | gulp.task('develop', ['env:develop'], function () { 136 | plugins.nodemon({ 137 | script: 'server.js', 138 | ext: 'html js', 139 | env: { 'NODE_ENV': 'development' } , 140 | ignore: ['./node_modules/**'], 141 | nodeArgs: ['--debug'] 142 | }); 143 | }); 144 | 145 | gulp.task('test', ['env:test', 'karma:unit', 'mochaTest']); 146 | 147 | var defaultTasks = ['clean', 'jshint', 'less', 'csslint', 'develop', 'watch']; 148 | 149 | if (process.env.NODE_ENV === 'production') { 150 | defaultTasks = ['clean', 'cssmin', 'uglify']; 151 | } 152 | 153 | gulp.task('default', defaultTasks); 154 | 155 | // See also: https://github.com/timdp/heroku-buildpack-nodejs-gulp 156 | // For Heroku users only. 157 | // Docs: https://github.com/linnovate/mean/wiki/Deploying-on-Heroku 158 | gulp.task('heroku:production', ['env:production', 'cssmin', 'uglify']); 159 | -------------------------------------------------------------------------------- /tools/test/mocha-req.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.NODE_ENV = 'test'; 4 | var appRoot = __dirname + '/../../'; 5 | require(appRoot + 'server.js'); 6 | require('meanio/lib/core_modules/module/util').preload(appRoot + '/packages/**/server', 'model'); 7 | --------------------------------------------------------------------------------