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 |
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 |
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 |