From 126097a8bc7345fbea1f5c1e07b828fb6578e199 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 6 Oct 2014 17:45:23 +0300 Subject: [PATCH 01/12] Remove articles example --- app/controllers/articles.server.controller.js | 116 ------------ app/models/article.server.model.js | 34 ---- app/routes/articles.server.routes.js | 22 --- app/tests/article.server.model.test.js | 64 ------- .../articles/articles.client.module.js | 4 - .../articles/config/articles.client.routes.js | 25 --- .../controllers/articles.client.controller.js | 62 ------- public/modules/articles/less/articles.less | 0 .../services/articles.client.service.js | 14 -- .../tests/articles.client.controller.test.js | 170 ------------------ .../views/create-article.client.view.html | 29 --- .../views/edit-article.client.view.html | 35 ---- .../views/list-articles.client.view.html | 20 --- .../views/view-article.client.view.html | 22 --- 14 files changed, 617 deletions(-) delete mode 100644 app/controllers/articles.server.controller.js delete mode 100644 app/models/article.server.model.js delete mode 100644 app/routes/articles.server.routes.js delete mode 100644 app/tests/article.server.model.test.js delete mode 100755 public/modules/articles/articles.client.module.js delete mode 100755 public/modules/articles/config/articles.client.routes.js delete mode 100644 public/modules/articles/controllers/articles.client.controller.js delete mode 100644 public/modules/articles/less/articles.less delete mode 100644 public/modules/articles/services/articles.client.service.js delete mode 100644 public/modules/articles/tests/articles.client.controller.test.js delete mode 100644 public/modules/articles/views/create-article.client.view.html delete mode 100644 public/modules/articles/views/edit-article.client.view.html delete mode 100644 public/modules/articles/views/list-articles.client.view.html delete mode 100644 public/modules/articles/views/view-article.client.view.html diff --git a/app/controllers/articles.server.controller.js b/app/controllers/articles.server.controller.js deleted file mode 100644 index 311f1875fb..0000000000 --- a/app/controllers/articles.server.controller.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var mongoose = require('mongoose'), - errorHandler = require('./errors'), - Article = mongoose.model('Article'), - _ = require('lodash'); - -/** - * Create a article - */ -exports.create = function(req, res) { - var article = new Article(req.body); - article.user = req.user; - - article.save(function(err) { - if (err) { - return res.status(400).send({ - message: errorHandler.getErrorMessage(err) - }); - } else { - - // Article is saved, notify everyone who's listening... - - var socketio = req.app.get('socketio'); // tacke out socket instance from the app container - socketio.sockets.emit('article.created', article); // emit an event for all connected clients - - // Return it - res.jsonp(article); - } - }); -}; - -/** - * Show the current article - */ -exports.read = function(req, res) { - res.jsonp(req.article); -}; - -/** - * Update a article - */ -exports.update = function(req, res) { - var article = req.article; - - article = _.extend(article, req.body); - - article.save(function(err) { - if (err) { - return res.status(400).send({ - message: errorHandler.getErrorMessage(err) - }); - } else { - res.jsonp(article); - } - }); -}; - -/** - * Delete an article - */ -exports.delete = function(req, res) { - var article = req.article; - - article.remove(function(err) { - if (err) { - return res.status(400).send({ - message: errorHandler.getErrorMessage(err) - }); - } else { - res.jsonp(article); - } - }); -}; - -/** - * List of Articles - */ -exports.list = function(req, res) { - Article.find().sort('-created').populate('user', 'displayName').exec(function(err, articles) { - if (err) { - return res.status(400).send({ - message: errorHandler.getErrorMessage(err) - }); - } else { - res.jsonp(articles); - } - }); -}; - -/** - * Article middleware - */ -exports.articleByID = function(req, res, next, id) { - Article.findById(id).populate('user', 'displayName').exec(function(err, article) { - if (err) return next(err); - if (!article) return next(new Error('Failed to load article ' + id)); - req.article = article; - next(); - }); -}; - -/** - * Article authorization middleware - */ -exports.hasAuthorization = function(req, res, next) { - if (req.article.user.id !== req.user.id) { - return res.status(403).send({ - message: 'User is not authorized' - }); - } - next(); -}; \ No newline at end of file diff --git a/app/models/article.server.model.js b/app/models/article.server.model.js deleted file mode 100644 index 3f6fd0df1f..0000000000 --- a/app/models/article.server.model.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var mongoose = require('mongoose'), - Schema = mongoose.Schema; - -/** - * Article Schema - */ -var ArticleSchema = new Schema({ - created: { - type: Date, - default: Date.now - }, - title: { - type: String, - default: '', - trim: true, - required: 'Title cannot be blank' - }, - content: { - type: String, - default: '', - trim: true - }, - user: { - type: Schema.ObjectId, - ref: 'User' - } -}); - -mongoose.model('Article', ArticleSchema); \ No newline at end of file diff --git a/app/routes/articles.server.routes.js b/app/routes/articles.server.routes.js deleted file mode 100644 index 9dfcb03887..0000000000 --- a/app/routes/articles.server.routes.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var users = require('../../app/controllers/users'), - articles = require('../../app/controllers/articles'); - -module.exports = function(app) { - // Article Routes - app.route('/articles') - .get(articles.list) - .post(users.requiresLogin, articles.create); - - app.route('/articles/:articleId') - .get(articles.read) - .put(users.requiresLogin, articles.hasAuthorization, articles.update) - .delete(users.requiresLogin, articles.hasAuthorization, articles.delete); - - // Finish by binding the article middleware - app.param('articleId', articles.articleByID); -}; \ No newline at end of file diff --git a/app/tests/article.server.model.test.js b/app/tests/article.server.model.test.js deleted file mode 100644 index e3dd60c889..0000000000 --- a/app/tests/article.server.model.test.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var should = require('should'), - mongoose = require('mongoose'), - User = mongoose.model('User'), - Article = mongoose.model('Article'); - -/** - * Globals - */ -var user, article; - -/** - * Unit tests - */ -describe('Article Model Unit Tests:', function() { - beforeEach(function(done) { - user = new User({ - firstName: 'Full', - lastName: 'Name', - displayName: 'Full Name', - email: 'test@test.com', - username: 'username', - password: 'password' - }); - - user.save(function() { - article = new Article({ - title: 'Article Title', - content: 'Article Content', - user: user - }); - - done(); - }); - }); - - describe('Method Save', function() { - it('should be able to save without problems', function(done) { - return article.save(function(err) { - should.not.exist(err); - done(); - }); - }); - - it('should be able to show an error when try to save without title', function(done) { - article.title = ''; - - return article.save(function(err) { - should.exist(err); - done(); - }); - }); - }); - - afterEach(function(done) { - Article.remove().exec(); - User.remove().exec(); - done(); - }); -}); \ No newline at end of file diff --git a/public/modules/articles/articles.client.module.js b/public/modules/articles/articles.client.module.js deleted file mode 100755 index 7435fc78a8..0000000000 --- a/public/modules/articles/articles.client.module.js +++ /dev/null @@ -1,4 +0,0 @@ -'use strict'; - -// Use Applicaion configuration module to register a new module -ApplicationConfiguration.registerModule('articles'); \ No newline at end of file diff --git a/public/modules/articles/config/articles.client.routes.js b/public/modules/articles/config/articles.client.routes.js deleted file mode 100755 index 1531a9a57c..0000000000 --- a/public/modules/articles/config/articles.client.routes.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -// Setting up route -angular.module('articles').config(['$stateProvider', - function($stateProvider) { - // Articles state routing - $stateProvider. - state('listArticles', { - url: '/articles', - templateUrl: 'modules/articles/views/list-articles.client.view.html' - }). - state('createArticle', { - url: '/articles/create', - templateUrl: 'modules/articles/views/create-article.client.view.html' - }). - state('viewArticle', { - url: '/articles/:articleId', - templateUrl: 'modules/articles/views/view-article.client.view.html' - }). - state('editArticle', { - url: '/articles/:articleId/edit', - templateUrl: 'modules/articles/views/edit-article.client.view.html' - }); - } -]); \ No newline at end of file diff --git a/public/modules/articles/controllers/articles.client.controller.js b/public/modules/articles/controllers/articles.client.controller.js deleted file mode 100644 index dfa2120eca..0000000000 --- a/public/modules/articles/controllers/articles.client.controller.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -angular.module('articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', 'Socket', 'Authentication', 'Articles', - function($scope, $stateParams, $location, Socket, Authentication, Articles) { - $scope.authentication = Authentication; - - Socket.on('article.created', function(article) { - console.log(article); - }); - - $scope.create = function() { - var article = new Articles({ - title: this.title, - content: this.content - }); - article.$save(function(response) { - $location.path('articles/' + response._id); - - $scope.title = ''; - $scope.content = ''; - }, function(errorResponse) { - $scope.error = errorResponse.data.message; - }); - }; - - $scope.remove = function(article) { - if (article) { - article.$remove(); - - for (var i in $scope.articles) { - if ($scope.articles[i] === article) { - $scope.articles.splice(i, 1); - } - } - } else { - $scope.article.$remove(function() { - $location.path('articles'); - }); - } - }; - - $scope.update = function() { - var article = $scope.article; - - article.$update(function() { - $location.path('articles/' + article._id); - }, function(errorResponse) { - $scope.error = errorResponse.data.message; - }); - }; - - $scope.find = function() { - $scope.articles = Articles.query(); - }; - - $scope.findOne = function() { - $scope.article = Articles.get({ - articleId: $stateParams.articleId - }); - }; - } -]); \ No newline at end of file diff --git a/public/modules/articles/less/articles.less b/public/modules/articles/less/articles.less deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/public/modules/articles/services/articles.client.service.js b/public/modules/articles/services/articles.client.service.js deleted file mode 100644 index deeb7da58c..0000000000 --- a/public/modules/articles/services/articles.client.service.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -//Articles service used for communicating with the articles REST endpoints -angular.module('articles').factory('Articles', ['$resource', - function($resource) { - return $resource('articles/:articleId', { - articleId: '@_id' - }, { - update: { - method: 'PUT' - } - }); - } -]); \ No newline at end of file diff --git a/public/modules/articles/tests/articles.client.controller.test.js b/public/modules/articles/tests/articles.client.controller.test.js deleted file mode 100644 index 7e25c699bb..0000000000 --- a/public/modules/articles/tests/articles.client.controller.test.js +++ /dev/null @@ -1,170 +0,0 @@ -'use strict'; - -(function() { - // Articles Controller Spec - describe('ArticlesController', function() { - // Initialize global variables - var ArticlesController, - scope, - $httpBackend, - $stateParams, - $location; - - // The $resource service augments the response object with methods for updating and deleting the resource. - // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match - // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. - // When the toEqualData matcher compares two objects, it takes only object properties into - // account and ignores methods. - beforeEach(function() { - jasmine.addMatchers({ - toEqualData: function(util, customEqualityTesters) { - return { - compare: function(actual, expected) { - return { - pass: angular.equals(actual, expected) - }; - } - }; - } - }); - }); - - // Then we can start by loading the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - - // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). - // This allows us to inject a service but then attach it to a variable - // with the same name as the service. - beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { - // Set a new global scope - scope = $rootScope.$new(); - - // Point global variables to injected services - $stateParams = _$stateParams_; - $httpBackend = _$httpBackend_; - $location = _$location_; - - // Initialize the Articles controller. - ArticlesController = $controller('ArticlesController', { - $scope: scope - }); - })); - - it('$scope.find() should create an array with at least one article object fetched from XHR', inject(function(Articles) { - // Create sample article using the Articles service - var sampleArticle = new Articles({ - title: 'An Article about MEAN', - content: 'MEAN rocks!' - }); - - // Create a sample articles array that includes the new article - var sampleArticles = [sampleArticle]; - - // Set GET response - $httpBackend.expectGET('articles').respond(sampleArticles); - - // Run controller functionality - scope.find(); - $httpBackend.flush(); - - // Test scope value - expect(scope.articles).toEqualData(sampleArticles); - })); - - it('$scope.findOne() should create an array with one article object fetched from XHR using a articleId URL parameter', inject(function(Articles) { - // Define a sample article object - var sampleArticle = new Articles({ - title: 'An Article about MEAN', - content: 'MEAN rocks!' - }); - - // Set the URL parameter - $stateParams.articleId = '525a8422f6d0f87f0e407a33'; - - // Set GET response - $httpBackend.expectGET(/articles\/([0-9a-fA-F]{24})$/).respond(sampleArticle); - - // Run controller functionality - scope.findOne(); - $httpBackend.flush(); - - // Test scope value - expect(scope.article).toEqualData(sampleArticle); - })); - - it('$scope.create() with valid form data should send a POST request with the form input values and then locate to new object URL', inject(function(Articles) { - // Create a sample article object - var sampleArticlePostData = new Articles({ - title: 'An Article about MEAN', - content: 'MEAN rocks!' - }); - - // Create a sample article response - var sampleArticleResponse = new Articles({ - _id: '525cf20451979dea2c000001', - title: 'An Article about MEAN', - content: 'MEAN rocks!' - }); - - // Fixture mock form input values - scope.title = 'An Article about MEAN'; - scope.content = 'MEAN rocks!'; - - // Set POST response - $httpBackend.expectPOST('articles', sampleArticlePostData).respond(sampleArticleResponse); - - // Run controller functionality - scope.create(); - $httpBackend.flush(); - - // Test form inputs are reset - expect(scope.title).toEqual(''); - expect(scope.content).toEqual(''); - - // Test URL redirection after the article was created - expect($location.path()).toBe('/articles/' + sampleArticleResponse._id); - })); - - it('$scope.update() should update a valid article', inject(function(Articles) { - // Define a sample article put data - var sampleArticlePutData = new Articles({ - _id: '525cf20451979dea2c000001', - title: 'An Article about MEAN', - content: 'MEAN Rocks!' - }); - - // Mock article in scope - scope.article = sampleArticlePutData; - - // Set PUT response - $httpBackend.expectPUT(/articles\/([0-9a-fA-F]{24})$/).respond(); - - // Run controller functionality - scope.update(); - $httpBackend.flush(); - - // Test URL location to new object - expect($location.path()).toBe('/articles/' + sampleArticlePutData._id); - })); - - it('$scope.remove() should send a DELETE request with a valid articleId and remove the article from the scope', inject(function(Articles) { - // Create new article object - var sampleArticle = new Articles({ - _id: '525a8422f6d0f87f0e407a33' - }); - - // Create new articles array and include the article - scope.articles = [sampleArticle]; - - // Set expected DELETE response - $httpBackend.expectDELETE(/articles\/([0-9a-fA-F]{24})$/).respond(204); - - // Run controller functionality - scope.remove(sampleArticle); - $httpBackend.flush(); - - // Test array after successful delete - expect(scope.articles.length).toBe(0); - })); - }); -}()); \ No newline at end of file diff --git a/public/modules/articles/views/create-article.client.view.html b/public/modules/articles/views/create-article.client.view.html deleted file mode 100644 index ab8db8ef61..0000000000 --- a/public/modules/articles/views/create-article.client.view.html +++ /dev/null @@ -1,29 +0,0 @@ -
- -
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
-
- -
-
-
-
-
\ No newline at end of file diff --git a/public/modules/articles/views/edit-article.client.view.html b/public/modules/articles/views/edit-article.client.view.html deleted file mode 100644 index 353cb8e666..0000000000 --- a/public/modules/articles/views/edit-article.client.view.html +++ /dev/null @@ -1,35 +0,0 @@ -
- -
-
-
-
- -
- -
-
-

Title is required

-
-
-
- -
- -
-
-

Content is required

-
-
-
- -
-
- -
-
-
-
-
\ No newline at end of file diff --git a/public/modules/articles/views/list-articles.client.view.html b/public/modules/articles/views/list-articles.client.view.html deleted file mode 100644 index 861ae5b6ba..0000000000 --- a/public/modules/articles/views/list-articles.client.view.html +++ /dev/null @@ -1,20 +0,0 @@ -
- - -
- No articles yet, why don't you create one? -
-
\ No newline at end of file diff --git a/public/modules/articles/views/view-article.client.view.html b/public/modules/articles/views/view-article.client.view.html deleted file mode 100644 index 312d25c84e..0000000000 --- a/public/modules/articles/views/view-article.client.view.html +++ /dev/null @@ -1,22 +0,0 @@ -
- - - - - Posted on - - by - - - -

-
\ No newline at end of file From b835de490ccde6381ffb0592ec82f0fd40c6520f Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 6 Oct 2014 19:16:09 +0300 Subject: [PATCH 02/12] #11 Basic location selector directive Uses genomes.org for now --- .../tr-location.client.directive.js | 46 +++++++++++++++++++ .../profile/edit-profile.client.view.html | 6 ++- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 public/modules/core/directives/tr-location.client.directive.js diff --git a/public/modules/core/directives/tr-location.client.directive.js b/public/modules/core/directives/tr-location.client.directive.js new file mode 100644 index 0000000000..aee1139827 --- /dev/null +++ b/public/modules/core/directives/tr-location.client.directive.js @@ -0,0 +1,46 @@ +'use strict'; + +angular.module('core').directive('trLocation', [ + '$http', + function($http) { + return { + template: '' + + '', + restrict: 'EA', + controller: function($scope, $http) { + + $scope.getLocation = function(val) { + + // http://www.geonames.org/export/geonames-search.html + return $http.get('http://api.geonames.org/searchJSON', { + params: { + q: val, + maxRows: 10, + lang: 'en', + featureClass: 'P', // P for city, A for country - http://www.geonames.org/export/codes.html + style: 'full', + username: 'trustroots' + } + }).then(function(response){ + return response.data.geonames.map(function(place){ + + var title = ''; + + // Prefer toponym name like 'Jyväskylä' instead of 'Jyvaskyla' + if(place.toponymName) title += place.toponymName; + else if(place.name) title += place.name; + + if(place.countryName) title += ', ' + place.countryName; + + place.trTitle = title; + + return place; + }); + }); + }; + + + } + }; + } +]); diff --git a/public/modules/users/views/profile/edit-profile.client.view.html b/public/modules/users/views/profile/edit-profile.client.view.html index aaf6f77366..830488daa6 100644 --- a/public/modules/users/views/profile/edit-profile.client.view.html +++ b/public/modules/users/views/profile/edit-profile.client.view.html @@ -29,12 +29,14 @@
- +
+
- + +
From 67c3edac2c14352e748731ee94b377d9698b309a Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 6 Oct 2014 19:21:12 +0300 Subject: [PATCH 03/12] #15 Basic account settings page - password reset (from mean.js) - account removal (no backend yet) --- .../users/config/users.client.routes.js | 4 ++ .../controllers/password.client.controller.js | 4 +- .../controllers/settings.client.controller.js | 33 +++++++++--- .../profile/edit-settings.client.view.html | 51 +++++++++++++++++++ 4 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 public/modules/users/views/profile/edit-settings.client.view.html diff --git a/public/modules/users/config/users.client.routes.js b/public/modules/users/config/users.client.routes.js index 04632a9c9e..c2fe15f2c6 100755 --- a/public/modules/users/config/users.client.routes.js +++ b/public/modules/users/config/users.client.routes.js @@ -9,6 +9,10 @@ angular.module('users').config(['$stateProvider', url: '/welcome', templateUrl: 'modules/users/views/authentication/welcome.client.view.html' }). + state('profile-settings', { + url: '/profile-settings', + templateUrl: 'modules/users/views/profile/edit-settings.client.view.html' + }). state('profile', { url: '/profile/:username', templateUrl: 'modules/users/views/profile/view-profile.client.view.html' diff --git a/public/modules/users/controllers/password.client.controller.js b/public/modules/users/controllers/password.client.controller.js index dbc9e92977..6614a0ba82 100644 --- a/public/modules/users/controllers/password.client.controller.js +++ b/public/modules/users/controllers/password.client.controller.js @@ -5,7 +5,7 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam $scope.authentication = Authentication; //If user is signed in then redirect back home - if ($scope.authentication.user) $location.path('/'); + //if ($scope.authentication.user) $location.path('/'); // Submit forgotten password account id $scope.askForPasswordReset = function() { @@ -41,4 +41,4 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam }); }; } -]); \ No newline at end of file +]); diff --git a/public/modules/users/controllers/settings.client.controller.js b/public/modules/users/controllers/settings.client.controller.js index 5d2b5fefd7..94d8c1da58 100644 --- a/public/modules/users/controllers/settings.client.controller.js +++ b/public/modules/users/controllers/settings.client.controller.js @@ -71,14 +71,31 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$s }; $scope.findProfile = function() { - if(!$stateParams.username) { - $scope.profile = $scope.user; - } - else { - $scope.profile = Users.get({ - username: $stateParams.username - }); - } + if(!$stateParams.username) { + $scope.profile = $scope.user; + } + else { + $scope.profile = Users.get({ + username: $stateParams.username + }); + } + }; + + + // Remove user permanently + $scope.removeUser = function() { + $scope.success = $scope.error = null; + + var duhhAreYouSureYouWantToRemoveYourself = confirm('Are you sure you want to remove your account? This cannot be undone.'); + + if(duhhAreYouSureYouWantToRemoveYourself) { + $http.post('/users/remove').success(function(response) { + // Do something! + }).error(function(response) { + $scope.error = response.message; + }); + }//yup, user is sure + }; } diff --git a/public/modules/users/views/profile/edit-settings.client.view.html b/public/modules/users/views/profile/edit-settings.client.view.html new file mode 100644 index 0000000000..a0f5eff742 --- /dev/null +++ b/public/modules/users/views/profile/edit-settings.client.view.html @@ -0,0 +1,51 @@ +
+ +
+
+ +
+
+ + +
+ +

Change your password

+
+ +
+ +
+ + + +
+

Remove your account

+
+

+
+
+ + +
From e84fc03e340fb7b3b3c30df5d7ee9a7cf293b81a Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 6 Oct 2014 19:22:59 +0300 Subject: [PATCH 04/12] #12 #5 Profile references, profile tab/header menu --- .gitignore | 1 + app/controllers/messages.server.controller.js | 20 +-- .../references.server.controller.js | 65 +++++++- .../users/users.profile.server.controller.js | 68 +++++++- app/models/reference.server.model.js | 16 +- app/models/user.server.model.js | 3 + app/routes/references.server.routes.js | 9 +- app/routes/users.server.routes.js | 4 +- app/tests/reference.server.model.test.js | 48 +++--- app/tests/user.server.model.test.js | 2 +- public/less/application.less | 7 +- .../articles/config/articles.client.config.js | 11 -- .../controllers/header.client.controller.js | 5 +- .../core/services/menus.client.service.js | 39 ++++- .../core/views/header.client.view.html | 62 ++++--- .../messages/config/messages.client.config.js | 4 +- .../views/thread-messages.client.view.html | 4 +- .../config/references.client.routes.js | 25 --- .../references.client.controller.js | 56 +++++-- .../modules/references/less/references.less | 152 +++++++++++++++++ .../services/references.client.service.js | 16 +- .../views/create-reference.client.modal.html | 38 +++++ .../views/create-reference.client.view.html | 23 --- .../views/list-references.client.view.html | 93 ++++++++--- .../search/config/search.client.config.js | 9 ++ .../users/config/users.client.config.js | 19 ++- .../users/config/users.client.routes.js | 10 +- .../controllers/profile.client.controller.js | 38 +++-- public/modules/users/less/editor.less | 4 + public/modules/users/less/references.less | 153 ------------------ public/modules/users/less/users.less | 3 + .../tab-profile-overview.client.view.html | 6 +- .../profile/view-profile.client.view.html | 20 ++- 33 files changed, 680 insertions(+), 353 deletions(-) delete mode 100644 public/modules/articles/config/articles.client.config.js delete mode 100644 public/modules/references/config/references.client.routes.js create mode 100644 public/modules/references/views/create-reference.client.modal.html delete mode 100644 public/modules/references/views/create-reference.client.view.html create mode 100644 public/modules/search/config/search.client.config.js delete mode 100644 public/modules/users/less/references.less diff --git a/.gitignore b/.gitignore index 5cee720957..ab414ed3c8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ public/dist app/tests/coverage/ .bower-*/ .idea/ +scripts/deploy.sh diff --git a/app/controllers/messages.server.controller.js b/app/controllers/messages.server.controller.js index b0f4f112d6..eb3e1b3e16 100755 --- a/app/controllers/messages.server.controller.js +++ b/app/controllers/messages.server.controller.js @@ -56,13 +56,13 @@ exports.inbox = function(req, res) { var threadsCleaned = []; threads.forEach(function(thread) { - // Threads need just excerpt - thread.message.excerpt = sanitizeHtml(thread.message.content, {allowedTags: []}); // Clean message content from html - thread.message.excerpt = thread.message.excerpt.replace(/\s/g, ' '); // Remove white space. Matches a single white space character, including space, tab, form feed, line feed. - thread.message.excerpt = thread.message.excerpt.substring(0,100) + ' ...'; // Shorten + // Threads need just excerpt + thread.message.excerpt = sanitizeHtml(thread.message.content, {allowedTags: []}); // Clean message content from html + thread.message.excerpt = thread.message.excerpt.replace(/\s/g, ' '); // Remove white space. Matches a single white space character, including space, tab, form feed, line feed. + thread.message.excerpt = thread.message.excerpt.substring(0,100) + ' ...'; // Shorten - delete thread.message.content; - threadsCleaned.push(thread); + delete thread.message.content; + threadsCleaned.push(thread); }); res.jsonp(threadsCleaned); @@ -80,12 +80,6 @@ exports.send = function(req, res) { // take out socket instance from the app container, we'll need it later var socketio = req.app.get('socketio'); - var userByID = function(id) { - User.findById(id).exec(function(err, user) { - return (err || !user) ? false : user; - }); - }; - var message = new Message(req.body); message.userFrom = req.user; @@ -277,5 +271,3 @@ exports.delete = function(req, res) { }); }; */ - - diff --git a/app/controllers/references.server.controller.js b/app/controllers/references.server.controller.js index 7290ea598e..a219986816 100644 --- a/app/controllers/references.server.controller.js +++ b/app/controllers/references.server.controller.js @@ -13,7 +13,8 @@ var mongoose = require('mongoose'), */ exports.create = function(req, res) { var reference = new Reference(req.body); - reference.user = req.user; + reference.userFrom = req.user; + reference.updated = null; reference.save(function(err) { if (err) { @@ -37,17 +38,38 @@ exports.read = function(req, res) { * Update a Reference */ exports.update = function(req, res) { - var reference = req.reference ; + var reference = req.reference; + + // Make sure we won't touch creation date, but do change update timestamp + if(req.body.created) delete req.body.created; reference = _.extend(reference , req.body); + reference.updated = new Date(); + reference.save(function(err) { if (err) { return res.status(400).send({ message: errorHandler.getErrorMessage(err) }); } else { - res.jsonp(reference); + + // We'll need some info about related users, populate some fields + reference + .populate({ + path: 'userTo', + select: 'displayName username' + }, function(err, reference) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + // Response + res.jsonp(reference); + } + }); + } }); }; @@ -72,7 +94,17 @@ exports.delete = function(req, res) { /** * List of References */ -exports.list = function(req, res) { Reference.find().sort('-created').populate('user', 'displayName').exec(function(err, references) { +exports.list = function(req, res) { + console.log('->list'); + res.jsonp(req.references); + /* + Reference.find({ + userTo: userId + }) + .sort('-created') + .populate('userTo', 'username displayName') + .populate('userFrom', 'username displayName') + .exec(function(err, references) { if (err) { return res.status(400).send({ message: errorHandler.getErrorMessage(err) @@ -81,6 +113,7 @@ exports.list = function(req, res) { Reference.find().sort('-created').populate(' res.jsonp(references); } }); + */ }; /** @@ -94,12 +127,32 @@ exports.referenceByID = function(req, res, next, id) { Reference.findById(id).po }); }; +exports.referencesByUser = function(req, res, next, userId) { + console.log('->referencesByUser: ' + userId); + Reference + .find({ + $or: [ + { userFrom: userId }, + { userTo: userId } + ] + }) + .populate('userFrom', 'displayName username') + .populate('userTo', 'displayName username') + .exec(function(err, references) { + if (err) return next(err); + if (! references) return next(new Error('Failed to load References for user ' + userId)); + req.references = references; + next(); + }); +}; + + /** * Reference authorization middleware */ exports.hasAuthorization = function(req, res, next) { - if (req.reference.user.id !== req.user.id) { + if (req.reference.userFrom.id !== req.user.id) { return res.status(403).send('User is not authorized'); } next(); -}; \ No newline at end of file +}; diff --git a/app/controllers/users/users.profile.server.controller.js b/app/controllers/users/users.profile.server.controller.js index ceff5ebd27..ad2cf74cf1 100644 --- a/app/controllers/users/users.profile.server.controller.js +++ b/app/controllers/users/users.profile.server.controller.js @@ -8,7 +8,9 @@ var _ = require('lodash'), mongoose = require('mongoose'), passport = require('passport'), sanitizeHtml = require('sanitize-html'), - User = mongoose.model('User'); + User = mongoose.model('User'), +//Contact = mongoose.model('Contact'), + Reference = mongoose.model('Reference'); /** @@ -82,6 +84,7 @@ exports.me = function(req, res) { * Show the profile of the user */ exports.getUser = function(req, res) { + console.log(req.user); res.jsonp(req.user || null); }; @@ -118,8 +121,33 @@ exports.userByID = function(req, res, next, id) { if (err) return next(err); if (!user) return next(new Error('Failed to load user ' + id)); + // Make sure we're not sending unsequre content (eg. passwords) + // Pick here fields to send + user = _.pick(user, 'id', + 'displayName', + 'username', + 'gender', + 'tagline', + 'description', + 'locationFrom', + 'locationLiving', + 'birthdate', + 'seen', + 'created', + 'updated' + ); + // Sanitize output - user.description = sanitizeHtml(user.description, userSanitizeOptions); + if(user.description) user.description = sanitizeHtml(user.description, userSanitizeOptions); + + // Check if logged in user has left reference for this profile + console.log('->userByID, check if user ' + req.user._id + ' has written reference for ' + user._id); + Reference.findOne({ + userTo: user._id, + userFrom: req.user._id + }).exec(function(err, reference) { + user.reference = reference; + }); req.user = user; next(); @@ -133,11 +161,39 @@ exports.userByUsername = function(req, res, next, username) { if (err) return next(err); if (!user) return next(new Error('Failed to load user ' + username)); + // Make sure we're not sending unsequre content (eg. passwords) + // Pick here fields to send + user = _.pick(user, 'id', + 'displayName', + 'username', + 'gender', + 'tagline', + 'description', + 'locationFrom', + 'locationLiving', + 'birthdate', + 'seen', + 'created', + 'updated' + ); + // Sanitize output - user.description = sanitizeHtml(user.description, userSanitizeOptions); - - req.user = user; - next(); + if(user.description) user.description = sanitizeHtml(user.description, userSanitizeOptions); + + // Check if logged in user has left reference for this profile + console.log('->userByUsername, check if user ' + req.user._id + ' has written reference for ' + user._id); + Reference.findOne({ + userTo: user._id, + userFrom: req.user._id + }).exec(function(err, reference) { + + // Attach reference to profile + user.reference = reference; + + req.user = user; + next(); + }); + }); }; diff --git a/app/models/reference.server.model.js b/app/models/reference.server.model.js index 3ad90642d8..eca2be23b2 100644 --- a/app/models/reference.server.model.js +++ b/app/models/reference.server.model.js @@ -10,20 +10,28 @@ var mongoose = require('mongoose'), * Reference Schema */ var ReferenceSchema = new Schema({ - name: { + reference: { type: String, default: '', - required: 'Please fill Reference name', + required: 'Please fill reference', trim: true }, created: { type: Date, default: Date.now }, - user: { + updated: { + type: Date, + default: Date.now + }, + userFrom: { + type: Schema.ObjectId, + ref: 'User' + }, + userTo: { type: Schema.ObjectId, ref: 'User' } }); -mongoose.model('Reference', ReferenceSchema); \ No newline at end of file +mongoose.model('Reference', ReferenceSchema); diff --git a/app/models/user.server.model.js b/app/models/user.server.model.js index cb1bac43d1..488bc2a722 100755 --- a/app/models/user.server.model.js +++ b/app/models/user.server.model.js @@ -129,6 +129,9 @@ var UserSchema = new Schema({ }], default: ['user'] }, + seen: { + type: Date + }, updated: { type: Date }, diff --git a/app/routes/references.server.routes.js b/app/routes/references.server.routes.js index a227e1da94..ed55561ef2 100644 --- a/app/routes/references.server.routes.js +++ b/app/routes/references.server.routes.js @@ -6,14 +6,17 @@ module.exports = function(app) { // References Routes app.route('/references') - .get(references.list) .post(users.requiresLogin, references.create); + app.route('/references/by/:userId') + .get(users.requiresLogin, references.list); + app.route('/references/:referenceId') - .get(references.read) + .get(users.requiresLogin, references.read) .put(users.requiresLogin, references.hasAuthorization, references.update) .delete(users.requiresLogin, references.hasAuthorization, references.delete); // Finish by binding the Reference middleware + app.param('userId', references.referencesByUser); app.param('referenceId', references.referenceByID); -}; \ No newline at end of file +}; diff --git a/app/routes/users.server.routes.js b/app/routes/users.server.routes.js index ecb90d184e..8df88e2ae8 100644 --- a/app/routes/users.server.routes.js +++ b/app/routes/users.server.routes.js @@ -12,8 +12,8 @@ module.exports = function(app) { // Setting up the users profile api app.route('/users') - .put(users.update) - .get(users.requiresLogin, users.getUser); + .put(users.update); + //.get(users.requiresLogin, users.getUser); app.route('/users/mini/:userId') .get(users.requiresLogin, users.getMiniUser); diff --git a/app/tests/reference.server.model.test.js b/app/tests/reference.server.model.test.js index 7a5d8e654e..09c1bfa94b 100644 --- a/app/tests/reference.server.model.test.js +++ b/app/tests/reference.server.model.test.js @@ -11,30 +11,41 @@ var should = require('should'), /** * Globals */ -var user, reference; +var userFrom, userTo, reference; /** * Unit tests */ describe('Reference Model Unit Tests:', function() { beforeEach(function(done) { - user = new User({ + userFrom = new User({ firstName: 'Full', - lastName: 'Name', - displayName: 'Full Name', + lastName: 'Name From', + displayName: 'Full Name From', email: 'test@test.com', - username: 'username', + username: 'username-from', + password: 'password' + }); + userTo = new User({ + firstName: 'Full', + lastName: 'Name To', + displayName: 'Full Name To', + email: 'test-to@test.com', + username: 'username-to', password: 'password' }); - user.save(function() { - reference = new Reference({ - name: 'Reference Name', - user: user - }); + userTo.save(function() { + userFrom.save(function() { + reference = new Reference({ + reference: 'Reference Contents', + userFrom: userFrom, + userTo: userTo + }); - done(); - }); + done(); + }); + }); }); describe('Method Save', function() { @@ -45,8 +56,8 @@ describe('Reference Model Unit Tests:', function() { }); }); - it('should be able to show an error when try to save without name', function(done) { - reference.name = ''; + it('should be able to show an error when try to save without reference contents', function(done) { + reference.reference = ''; return reference.save(function(err) { should.exist(err); @@ -55,10 +66,11 @@ describe('Reference Model Unit Tests:', function() { }); }); - afterEach(function(done) { - Reference.remove().exec(); - User.remove().exec(); + afterEach(function(done) { + reference.remove().exec(); + userTo.remove().exec(); + userFrom.remove().exec(); done(); }); -}); \ No newline at end of file +}); diff --git a/app/tests/user.server.model.test.js b/app/tests/user.server.model.test.js index 7369d41464..63983d1a15 100644 --- a/app/tests/user.server.model.test.js +++ b/app/tests/user.server.model.test.js @@ -72,4 +72,4 @@ describe('User Model Unit Tests:', function() { User.remove().exec(); done(); }); -}); \ No newline at end of file +}); diff --git a/public/less/application.less b/public/less/application.less index 3157f8769c..69a4ec65d5 100644 --- a/public/less/application.less +++ b/public/less/application.less @@ -115,10 +115,13 @@ @import "../modules/users/less/users.less"; @import "../modules/users/less/authentication.less"; @import "../modules/users/less/editor.less"; -@import "../modules/users/less/references.less"; +@import "../modules/users/less/welcome.less"; + +@import "../modules/references/less/references.less"; + @import "../modules/users/less/images.less"; + @import "../modules/users/less/hosting.less"; -@import "../modules/users/less/welcome.less"; @import "../modules/messages/less/inbox.less"; @import "../modules/messages/less/thread.less"; diff --git a/public/modules/articles/config/articles.client.config.js b/public/modules/articles/config/articles.client.config.js deleted file mode 100644 index 7e1b0ffd27..0000000000 --- a/public/modules/articles/config/articles.client.config.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -// Configuring the Articles module -angular.module('articles').run(['Menus', - function(Menus) { - // Set top bar menu items - Menus.addMenuItem('topbar', 'Articles', 'articles', 'dropdown', '/articles(/create)?'); - Menus.addSubMenuItem('topbar', 'articles', 'List Articles', 'articles'); - Menus.addSubMenuItem('topbar', 'articles', 'New Article', 'articles/create'); - } -]); \ No newline at end of file diff --git a/public/modules/core/controllers/header.client.controller.js b/public/modules/core/controllers/header.client.controller.js index 7c49563fa2..1b9227ff71 100644 --- a/public/modules/core/controllers/header.client.controller.js +++ b/public/modules/core/controllers/header.client.controller.js @@ -14,7 +14,8 @@ angular.module('core').controller('HeaderController', ['$scope', '$log', 'Authen $scope.authentication = Authentication; $scope.isCollapsed = false; $scope.isHidden = false; - $scope.menu = Menus.getMenu('topbar'); + $scope.commonMenu = Menus.getMenu('topbar'); + $scope.userMenu = Menus.getMenu('topuserbar'); $scope.toggleCollapsibleMenu = function() { $scope.isCollapsed = !$scope.isCollapsed; @@ -28,4 +29,4 @@ angular.module('core').controller('HeaderController', ['$scope', '$log', 'Authen }); } -]); \ No newline at end of file +]); diff --git a/public/modules/core/services/menus.client.service.js b/public/modules/core/services/menus.client.service.js index d2366d1de3..3c25c515f4 100644 --- a/public/modules/core/services/menus.client.service.js +++ b/public/modules/core/services/menus.client.service.js @@ -10,7 +10,7 @@ angular.module('core').service('Menus', [ // Define the menus object this.menus = {}; - // A private function for rendering decision + // A private function for rendering decision var shouldRender = function(user) { if (user) { if (!!~this.roles.indexOf('*')) { @@ -79,7 +79,7 @@ angular.module('core').service('Menus', [ }; // Add menu item object - this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles, position) { + this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles, position, icon) { // Validate that the menu exists this.validateMenuExistance(menuId); @@ -93,6 +93,7 @@ angular.module('core').service('Menus', [ isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].isPublic : isPublic), roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].roles : roles), position: position || 0, + icon: icon || false, items: [], shouldRender: shouldRender }); @@ -101,8 +102,31 @@ angular.module('core').service('Menus', [ return this.menus[menuId]; }; + // Add submenu divider object + this.addSubMenuDivider = function(menuId, rootMenuItemURL, position, roles) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item + for (var itemIndex in this.menus[menuId].items) { + if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) { + // Push new submenu divider + this.menus[menuId].items[itemIndex].items.push({ + position: position || 0, + uiRoute: '/',// + rootMenuItemURL, + isDivider: true, + roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : roles), + shouldRender: shouldRender + }); + } + } + + // Return the menu object + return this.menus[menuId]; + }; + // Add submenu item object - this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles, position) { + this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles, position, icon) { // Validate that the menu exists this.validateMenuExistance(menuId); @@ -117,6 +141,7 @@ angular.module('core').service('Menus', [ isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : isPublic), roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : roles), position: position || 0, + icon: icon || false, shouldRender: shouldRender }); } @@ -160,7 +185,11 @@ angular.module('core').service('Menus', [ return this.menus[menuId]; }; - //Adding the topbar menu + + //Adding the general topbar menu this.addMenu('topbar'); + + //Adding the user's topbar menu + this.addMenu('topuserbar'); } -]); \ No newline at end of file +]); diff --git a/public/modules/core/views/header.client.view.html b/public/modules/core/views/header.client.view.html index cfc4b609dd..80344558c1 100644 --- a/public/modules/core/views/header.client.view.html +++ b/public/modules/core/views/header.client.view.html @@ -12,20 +12,29 @@
diff --git a/public/modules/messages/config/messages.client.config.js b/public/modules/messages/config/messages.client.config.js index 2edc20c8d4..28438667d4 100644 --- a/public/modules/messages/config/messages.client.config.js +++ b/public/modules/messages/config/messages.client.config.js @@ -4,6 +4,6 @@ angular.module('messages').run(['Menus', function(Menus) { // Set top bar menu items - Menus.addMenuItem('topbar', 'Messages', 'messages', 'messages'); + Menus.addMenuItem('topuserbar', 'Messages', 'messages', 'messages'); } -]); \ No newline at end of file +]); diff --git a/public/modules/messages/views/thread-messages.client.view.html b/public/modules/messages/views/thread-messages.client.view.html index 7981ec3ab3..545457fd77 100644 --- a/public/modules/messages/views/thread-messages.client.view.html +++ b/public/modules/messages/views/thread-messages.client.view.html @@ -21,7 +21,9 @@
You - — + — + +
diff --git a/public/modules/references/config/references.client.routes.js b/public/modules/references/config/references.client.routes.js deleted file mode 100644 index 8e16f8c04f..0000000000 --- a/public/modules/references/config/references.client.routes.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -//Setting up route -angular.module('references').config(['$stateProvider', - function($stateProvider) { - // References state routing - $stateProvider. - state('listReferences', { - url: '/references', - templateUrl: 'modules/references/views/list-references.client.view.html' - }). - state('createReference', { - url: '/references/create', - templateUrl: 'modules/references/views/create-reference.client.view.html' - }). - state('viewReference', { - url: '/references/:referenceId', - templateUrl: 'modules/references/views/view-reference.client.view.html' - }). - state('editReference', { - url: '/references/:referenceId/edit', - templateUrl: 'modules/references/views/edit-reference.client.view.html' - }); - } -]); \ No newline at end of file diff --git a/public/modules/references/controllers/references.client.controller.js b/public/modules/references/controllers/references.client.controller.js index b0367da457..f4e795a2ec 100644 --- a/public/modules/references/controllers/references.client.controller.js +++ b/public/modules/references/controllers/references.client.controller.js @@ -1,31 +1,51 @@ 'use strict'; // References controller -angular.module('references').controller('ReferencesController', ['$scope', '$stateParams', '$location', 'Authentication', 'References', - function($scope, $stateParams, $location, Authentication, References ) { +angular.module('references').controller('ReferencesController', ['$scope', '$log', '$state', '$stateParams', '$location', '$modal', 'Authentication', 'ReferencesBy', 'References', + function($scope, $log, $state, $stateParams, $location, $modal, Authentication, ReferencesBy, References ) { $scope.authentication = Authentication; // Create new Reference $scope.create = function() { + + $log.log('Reference to: ' + $scope.userTo); + // Create new Reference object var reference = new References ({ - name: this.name + reference: this.reference, + userTo: $scope.userTo }); // Redirect after save reference.$save(function(response) { - $location.path('references/' + response._id); + + + //if(modalInstance) { + // $log.log('Close modal'); + // modalInstance.dismiss('cancel'); + //} + + $log.log('->Success'); + $log.log(response); + + $state.go('profile-reference', {'username': response.userTo.username, 'referenceId': response._id}); // Clear form fields - $scope.name = ''; + //$scope.reference = ''; }, function(errorResponse) { $scope.error = errorResponse.data.message; }); }; // Remove existing Reference - $scope.remove = function( reference ) { - if ( reference ) { reference.$remove(); + $scope.remove = function( reference, profile ) { + $log.log('->remove'); + $log.log($stateParams); + $log.log(reference); + + if ( reference ) { + + reference.$remove(); for (var i in $scope.references ) { if ($scope.references [i] === reference ) { @@ -34,25 +54,32 @@ angular.module('references').controller('ReferencesController', ['$scope', '$sta } } else { $scope.reference.$remove(function() { - $location.path('references'); + //$location.path('references'); + $state.go('profile-tab', {'username': profile.username, 'tab': 'references'}); }); } }; // Update existing Reference - $scope.update = function() { - var reference = $scope.reference ; - + $scope.update = function(profile) { + var reference = $scope.reference; + $log.log('->update'); + $log.log($stateParams); + $log.log(reference); reference.$update(function() { - $location.path('references/' + reference._id); + //$location.path('references/' + reference._id); + $state.go('profile-tab', {'username': profile.username, 'tab': 'references'}); }, function(errorResponse) { $scope.error = errorResponse.data.message; }); }; // Find a list of References - $scope.find = function() { - $scope.references = References.query(); + $scope.list = function(profile) { + $log.log('list references: ' + profile.id); + $scope.references = ReferencesBy.query({ + userId: profile.id || profile._id + }); }; // Find existing Reference @@ -61,5 +88,6 @@ angular.module('references').controller('ReferencesController', ['$scope', '$sta referenceId: $stateParams.referenceId }); }; + } ]); diff --git a/public/modules/references/less/references.less b/public/modules/references/less/references.less index e69de29bb2..54888ecc6d 100644 --- a/public/modules/references/less/references.less +++ b/public/modules/references/less/references.less @@ -0,0 +1,152 @@ +.reference-list { + list-style: none; + display: block; + padding: 0; + margin: 0; + li { + .reference-author { + .text-center(); + font-size: @font-size-small; + .avatar { + margin: 10px auto @padding-small-vertical auto; + } + a { + &, + &:hover, + &:hover { + .text-muted(); + text-decoration: none; + } + } + } + .reference-meta { + .text-right(); + .reference-reputation { + .pull-left(); + } + &, a { + .text-muted(); + } + } + .panel { + .panel-triangle-right(); + } + } +} +/* +.reference-list { + display: block; + list-style: none; + margin: 0; + padding: 0; + li { + .avatar { + float: left; + margin-right: @padding-base-vertical; + } + h4 { + font-size: @font-size-base; + font-weight: bold; + margin: 1px 0; + padding: 0; + } + .reference-body { + padding: 1px 0 0 42px; + clear: both; + } + .reference-reply { + clear: both; + position: relative; + margin-top: 13px; + padding-top: 7px; + margin-left: 30px; + border-top: 1px solid @gray-lighter; + font-style: italic; + color: @gray-darker; + &:after { + content: ''; + position: absolute; + .square(0); + top: 0; + left: 7px; + margin-top: -7px; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid @gray-lighter; + } + h4, + h4 a { + color: @gray-darker; + } + &.reference-reply-neutral { + border-top: 2px solid @experience-neutral; + &:after { + margin-top: -8px; + border-bottom-color: @experience-neutral; + } + } + &.reference-reply-negative { + border-top: 2px solid @experience-negative; + &:after { + margin-top: -8px; + border-bottom-color: @experience-negative; + } + } + } + &.reference-neutral { + .box-shadow(0 1px 3px @experience-neutral); + border-color: @experience-neutral; + } + &.reference-negative { + .box-shadow(0 1px 3px @experience-negative); + border-color: @experience-neutral; + } + } +} +*/ + + +/** + * New reference -form + */ +/* Modal: */ +.profile-reference-new { + .modal-footer { + margin-top: 0; + } +} +.reference-new-reputation { + clear: both; + overflow: hidden; + .form-group { + margin-bottom: 0; + } + .checkbox { + margin: 0; + padding-left: 10px; + } + .help-block { + display: none; + } +} +.reference-new-body { + margin-bottom: 0; + textarea { + min-height: 80px; + padding: 20px; + border: 0; + outline: none; + border: 0; + border-top: 1px solid #ccc; + border-radius: 0; + font-size: 16px; + line-height: 25px; + .box-shadow(none); + resize: none; + &:focus { + border-color: @brand-primary; + outline: none; + .box-shadow(none); + } + } +} diff --git a/public/modules/references/services/references.client.service.js b/public/modules/references/services/references.client.service.js index 371595d638..ec9d3b1d01 100644 --- a/public/modules/references/services/references.client.service.js +++ b/public/modules/references/services/references.client.service.js @@ -1,13 +1,23 @@ 'use strict'; -//References service used to communicate References REST endpoints +//References services used to communicate References REST endpoints + angular.module('references').factory('References', ['$resource', function($resource) { - return $resource('references/:referenceId', { referenceId: '@_id' + return $resource('references/:referenceId', { + referenceId: '@_id' }, { update: { method: 'PUT' } }); } -]); \ No newline at end of file +]); + +angular.module('references').factory('ReferencesBy', ['$resource', + function($resource) { + return $resource('references/by/:userId', { + userId: '@userId' + }); + } +]); diff --git a/public/modules/references/views/create-reference.client.modal.html b/public/modules/references/views/create-reference.client.modal.html new file mode 100644 index 0000000000..4bb0bbfc59 --- /dev/null +++ b/public/modules/references/views/create-reference.client.modal.html @@ -0,0 +1,38 @@ +
+ + + +
diff --git a/public/modules/references/views/create-reference.client.view.html b/public/modules/references/views/create-reference.client.view.html deleted file mode 100644 index 7a04ee0796..0000000000 --- a/public/modules/references/views/create-reference.client.view.html +++ /dev/null @@ -1,23 +0,0 @@ -
- -
-
-
-
- -
- -
-
-
- -
-
- -
-
-
-
-
\ No newline at end of file diff --git a/public/modules/references/views/list-references.client.view.html b/public/modules/references/views/list-references.client.view.html index ea8da0ee03..d571d05756 100644 --- a/public/modules/references/views/list-references.client.view.html +++ b/public/modules/references/views/list-references.client.view.html @@ -1,19 +1,76 @@ -
- + + +
diff --git a/public/modules/search/config/search.client.config.js b/public/modules/search/config/search.client.config.js new file mode 100644 index 0000000000..4984b4d004 --- /dev/null +++ b/public/modules/search/config/search.client.config.js @@ -0,0 +1,9 @@ +'use strict'; + +// Configuring the Search module +angular.module('search').run(['Menus', + function(Menus) { + // Set top bar menu items + Menus.addMenuItem('topbar', 'Search', 'search', 'search'); + } +]); diff --git a/public/modules/users/config/users.client.config.js b/public/modules/users/config/users.client.config.js index 0bfc8b640b..45b495ce7a 100644 --- a/public/modules/users/config/users.client.config.js +++ b/public/modules/users/config/users.client.config.js @@ -1,5 +1,20 @@ 'use strict'; +// Configuring the Users module +angular.module('users').run(['Menus', 'Authentication', + function(Menus, Authentication) { + + // Set top bar menu items + Menus.addMenuItem('topuserbar', Authentication.user.displayName, 'profile', 'dropdown', '/profile'); + Menus.addSubMenuItem('topuserbar', 'profile', 'My profile', 'profile', 'profile', null, null, 0, 'user'); + Menus.addSubMenuItem('topuserbar', 'profile', 'Edit profile', 'profile/' + Authentication.user.username + '/edit', 'profile-edit', null, null, 0, 'edit'); + Menus.addSubMenuItem('topuserbar', 'profile', 'Settings', 'profile-settings', 'profile-settings', null, null, 0, 'cog'); + Menus.addSubMenuItem('topuserbar', 'profile', 'Help', 'contact', 'contact', null, null, 0, 'bolt'); + Menus.addSubMenuDivider('topuserbar', 'profile'); + Menus.addSubMenuItem('topuserbar', 'profile', 'Sign out', 'auth/signout', 'signout', null, null, 0, 'sign-out'); + } +]); + // Config HTTP Error Handling angular.module('users').config(['$httpProvider', function($httpProvider) { @@ -17,7 +32,7 @@ angular.module('users').config(['$httpProvider', $location.path('signin'); break; case 403: - // Add unauthorized behaviour + // Add unauthorized behaviour break; } @@ -27,4 +42,4 @@ angular.module('users').config(['$httpProvider', } ]); } -]); \ No newline at end of file +]); diff --git a/public/modules/users/config/users.client.routes.js b/public/modules/users/config/users.client.routes.js index c2fe15f2c6..b606ee6602 100755 --- a/public/modules/users/config/users.client.routes.js +++ b/public/modules/users/config/users.client.routes.js @@ -9,6 +9,10 @@ angular.module('users').config(['$stateProvider', url: '/welcome', templateUrl: 'modules/users/views/authentication/welcome.client.view.html' }). + state('profile-edit', { + url: '/profile-edit', + templateUrl: 'modules/users/views/profile/edit-profile.client.view.html' + }). state('profile-settings', { url: '/profile-settings', templateUrl: 'modules/users/views/profile/edit-settings.client.view.html' @@ -25,9 +29,9 @@ angular.module('users').config(['$stateProvider', url: '/profile/:username/:tab', templateUrl: 'modules/users/views/profile/view-profile.client.view.html' }). - state('profile-edit', { - url: '/settings/profile', - templateUrl: 'modules/users/views/profile/edit-profile.client.view.html' + state('profile-reference', { + url: '/profile/:username/references/:referenceId', + templateUrl: 'modules/users/views/profile/view-profile.client.view.html' }). state('password', { url: '/settings/password', diff --git a/public/modules/users/controllers/profile.client.controller.js b/public/modules/users/controllers/profile.client.controller.js index 5a5586fd4e..1973ffbda0 100644 --- a/public/modules/users/controllers/profile.client.controller.js +++ b/public/modules/users/controllers/profile.client.controller.js @@ -1,7 +1,7 @@ 'use strict'; -angular.module('users').controller('ProfileController', ['$scope', '$stateParams', '$location', '$log', 'Users', 'UserProfiles', 'Authentication', - function($scope, $stateParams, $location, $log, Users, UserProfiles, Authentication) { +angular.module('users').controller('ProfileController', ['$scope', '$stateParams', '$state', '$location', '$log', '$modal', 'Users', 'UserProfiles', 'Authentication', + function($scope, $stateParams, $state, $location, $log, $modal, Users, UserProfiles, Authentication) { $scope.user = Authentication.user; // Currently logged in user $scope.profile = false; // Profile to show @@ -18,14 +18,14 @@ angular.module('users').controller('ProfileController', ['$scope', '$stateParams // Fetch profile to show (note: not the currently logged in user's profile) $scope.findProfile = function() { if(!$stateParams.username) { - $log.log('No username set, showing your own profile'); - $scope.profile = $scope.user; + // No username set, direct to your own profile + $state.go('profile', {username: $scope.user.username}); } else { - $log.log('Get profile for '+ $stateParams.username); - $scope.profile = UserProfiles.get({ - username: $stateParams.username - }); + // Get profile for $stateParams.username + $scope.profile = UserProfiles.get({ + username: $stateParams.username + }); } }; @@ -34,7 +34,6 @@ angular.module('users').controller('ProfileController', ['$scope', '$stateParams for (var i in $scope.profile.additionalProvidersData) { return true; } - return false; }; @@ -43,7 +42,6 @@ angular.module('users').controller('ProfileController', ['$scope', '$stateParams return $scope.profile.provider === provider || ($scope.profile.additionalProvidersData && $scope.profile.additionalProvidersData[provider]); }; - $scope.tabs = [ { path: 'overview', @@ -54,7 +52,7 @@ angular.module('users').controller('ProfileController', ['$scope', '$stateParams { path: 'references', title: 'References', - content: '/modules/users/views/profile/tab-profile-references.client.view.html', + content: '/modules/references/views/list-references.client.view.html', active: $stateParams.tab && $stateParams.tab === 'references' }, { @@ -71,6 +69,7 @@ angular.module('users').controller('ProfileController', ['$scope', '$stateParams // @todo: change path here? }; + // Birthday input field // @link http://angular-ui.github.io/bootstrap/#/datepicker $scope.birthdateFormat = 'dd-MMMM-yyyy'; $scope.birthdateMin = new Date(99,0,0); @@ -88,6 +87,23 @@ angular.module('users').controller('ProfileController', ['$scope', '$stateParams }; + /** + * Open write/update reference -modal + */ + $scope.referenceModal = function (userTo, $event) { + + if($event) $event.preventDefault(); + + var modalInstance = $modal.open({ + templateUrl: '/modules/references/views/create-reference.client.modal.html', + controller: function ($scope, $modalInstance) { + $scope.profile = profile; + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + } + }); + }; } ]); diff --git a/public/modules/users/less/editor.less b/public/modules/users/less/editor.less index b80f3c9c47..c16e05a6af 100644 --- a/public/modules/users/less/editor.less +++ b/public/modules/users/less/editor.less @@ -37,6 +37,10 @@ .profile-firstname, .profile-lastname, .profile-tagline { + // Make it clear we are editing these fields by coloring it when selected + &:focus { + background: fade(@brand-primary, 10%); + } &.medium-editor-placeholder { display: inline; &:after { diff --git a/public/modules/users/less/references.less b/public/modules/users/less/references.less deleted file mode 100644 index 7e5644ad0b..0000000000 --- a/public/modules/users/less/references.less +++ /dev/null @@ -1,153 +0,0 @@ -.reference-list { - list-style: none; - display: block; - padding: 0; - margin: 0; - li { - .reference-author { - .text-center(); - font-size: @font-size-small; - .avatar { - margin: 10px auto @padding-small-vertical auto; - } - a { - &, - &:hover, - &:hover { - .text-muted(); - text-decoration: none; - } - } - } - .reference-meta { - .text-muted(); - .reference-reputation { - .pull-left(); - } - time { - .text-right(); - .pull-right(); - } - } - .panel { - .panel-triangle-right(); - } - } -} -/* -.reference-list { - display: block; - list-style: none; - margin: 0; - padding: 0; - li { - .avatar { - float: left; - margin-right: @padding-base-vertical; - } - h4 { - font-size: @font-size-base; - font-weight: bold; - margin: 1px 0; - padding: 0; - } - .reference-body { - padding: 1px 0 0 42px; - clear: both; - } - .reference-reply { - clear: both; - position: relative; - margin-top: 13px; - padding-top: 7px; - margin-left: 30px; - border-top: 1px solid @gray-lighter; - font-style: italic; - color: @gray-darker; - &:after { - content: ''; - position: absolute; - .square(0); - top: 0; - left: 7px; - margin-top: -7px; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 6px solid @gray-lighter; - } - h4, - h4 a { - color: @gray-darker; - } - &.reference-reply-neutral { - border-top: 2px solid @experience-neutral; - &:after { - margin-top: -8px; - border-bottom-color: @experience-neutral; - } - } - &.reference-reply-negative { - border-top: 2px solid @experience-negative; - &:after { - margin-top: -8px; - border-bottom-color: @experience-negative; - } - } - } - &.reference-neutral { - .box-shadow(0 1px 3px @experience-neutral); - border-color: @experience-neutral; - } - &.reference-negative { - .box-shadow(0 1px 3px @experience-negative); - border-color: @experience-neutral; - } - } -} -*/ - - -/** - * New reference -form - */ -/* Modal: */ -#profile-reference-new { - .modal-footer { - margin-top: 0; - } -} -#reference-new-reputation { - clear: both; - overflow: hidden; - .form-group { - margin-bottom: 0; - } - .checkbox { - margin: 0; - padding-left: 10px; - } - .help-block { - display: none; - } -} -#reference-new-body { - margin-bottom: 0; - textarea { - min-height: 80px; - padding: 20px; - border: 0; - outline: none; - border: 0; - border-top: 1px solid #ccc; - border-radius: 0; - font-size: 16px; - line-height: 25px; - .box-shadow(none); - resize: none; - &:focus { - border-color: @brand-primary; - outline: none; - .box-shadow(none); - } - } -} \ No newline at end of file diff --git a/public/modules/users/less/users.less b/public/modules/users/less/users.less index d421ae86cf..f48af5afa7 100644 --- a/public/modules/users/less/users.less +++ b/public/modules/users/less/users.less @@ -36,6 +36,9 @@ a { .cursor-pointer; } + .disabled a { + cursor: default; + } } } diff --git a/public/modules/users/views/profile/tab-profile-overview.client.view.html b/public/modules/users/views/profile/tab-profile-overview.client.view.html index 8cd07407d0..e2eec49229 100644 --- a/public/modules/users/views/profile/tab-profile-overview.client.view.html +++ b/public/modules/users/views/profile/tab-profile-overview.client.view.html @@ -9,7 +9,11 @@
-
“Everyone is necessarily the hero of his own life story.”
+ + +
+ “Everyone is necessarily the hero of his own life story.” +
diff --git a/public/modules/users/views/profile/view-profile.client.view.html b/public/modules/users/views/profile/view-profile.client.view.html index 80da15c3fc..340fd1200f 100644 --- a/public/modules/users/views/profile/view-profile.client.view.html +++ b/public/modules/users/views/profile/view-profile.client.view.html @@ -18,10 +18,12 @@ From City, Country

+

Languages

@@ -51,16 +53,24 @@

@{{profile.username -
- This is your profile as others see it. Edit your profile +
+ This is your profile as others see it. +   + Edit your profile
-
+ From e7d2c45443b8f4ce77229211cc148d401239d982 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 6 Oct 2014 19:23:47 +0300 Subject: [PATCH 05/12] Production auth callback urls --- config/env/production.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/env/production.js b/config/env/production.js index 60188c6cc5..97faaa7b52 100644 --- a/config/env/production.js +++ b/config/env/production.js @@ -24,27 +24,27 @@ module.exports = { facebook: { clientID: process.env.FACEBOOK_ID || 'APP_ID', clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', - callbackURL: 'http://localhost:3000/auth/facebook/callback' + callbackURL: 'http://www.trustroots.org/auth/facebook/callback' }, twitter: { clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', - callbackURL: 'http://localhost:3000/auth/twitter/callback' + callbackURL: 'http://www.trustroots.org/auth/twitter/callback' }, google: { clientID: process.env.GOOGLE_ID || 'APP_ID', clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', - callbackURL: 'http://localhost:3000/auth/google/callback' + callbackURL: 'http://www.trustroots.org/auth/google/callback' }, linkedin: { clientID: process.env.LINKEDIN_ID || 'APP_ID', clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', - callbackURL: 'http://localhost:3000/auth/linkedin/callback' + callbackURL: 'http://www.trustroots.org/auth/linkedin/callback' }, github: { clientID: process.env.GITHUB_ID || 'APP_ID', clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', - callbackURL: 'http://localhost:3000/auth/github/callback' + callbackURL: 'http://www.trustroots.org/auth/github/callback' }, mailer: { from: process.env.MAILER_FROM || 'MAILER_FROM', @@ -56,4 +56,4 @@ module.exports = { } } } -}; \ No newline at end of file +}; From 2e2b74978cd3d1f4ff8037d005efa413fb2cfc8f Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 6 Oct 2014 19:35:59 +0300 Subject: [PATCH 06/12] #15 Fixes to remove front logic --- .../controllers/settings.client.controller.js | 24 ++++++++++++------- .../profile/edit-settings.client.view.html | 14 +++++++++-- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/public/modules/users/controllers/settings.client.controller.js b/public/modules/users/controllers/settings.client.controller.js index 94d8c1da58..8860017d13 100644 --- a/public/modules/users/controllers/settings.client.controller.js +++ b/public/modules/users/controllers/settings.client.controller.js @@ -83,18 +83,26 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$s // Remove user permanently + $scope.removalConfirm = false; $scope.removeUser = function() { $scope.success = $scope.error = null; - var duhhAreYouSureYouWantToRemoveYourself = confirm('Are you sure you want to remove your account? This cannot be undone.'); + if($scope.removalConfirm === true) { - if(duhhAreYouSureYouWantToRemoveYourself) { - $http.post('/users/remove').success(function(response) { - // Do something! - }).error(function(response) { - $scope.error = response.message; - }); - }//yup, user is sure + var duhhAreYouSureYouWantToRemoveYourself = confirm('Are you sure you want to remove your account? This cannot be undone.'); + + if(duhhAreYouSureYouWantToRemoveYourself) { + $http.post('/users/remove').success(function(response) { + // Do something! + }).error(function(response) { + $scope.error = response.message; + }); + }//yup, user is sure + + } // Require checkbox + else { + alert('Choose "I understand this cannot be undone"'); + } }; diff --git a/public/modules/users/views/profile/edit-settings.client.view.html b/public/modules/users/views/profile/edit-settings.client.view.html index a0f5eff742..b92fac1370 100644 --- a/public/modules/users/views/profile/edit-settings.client.view.html +++ b/public/modules/users/views/profile/edit-settings.client.view.html @@ -34,6 +34,7 @@

Change your password

+

@@ -41,9 +42,18 @@

Change your password

-

Remove your account

-

+

Remove your account

+

+ +

+

+ +

+

From 272c1b1d21f22cd326a0b4a1d4331a9110154ff2 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 6 Oct 2014 19:36:59 +0300 Subject: [PATCH 07/12] #5 Pass profile for modal --- public/modules/users/controllers/profile.client.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/modules/users/controllers/profile.client.controller.js b/public/modules/users/controllers/profile.client.controller.js index 1973ffbda0..317cefda2e 100644 --- a/public/modules/users/controllers/profile.client.controller.js +++ b/public/modules/users/controllers/profile.client.controller.js @@ -90,7 +90,7 @@ angular.module('users').controller('ProfileController', ['$scope', '$stateParams /** * Open write/update reference -modal */ - $scope.referenceModal = function (userTo, $event) { + $scope.referenceModal = function (profile, $event) { if($event) $event.preventDefault(); From 8d3f83c4b9bf9f2525c965121b612f951deac061 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 6 Oct 2014 19:37:12 +0300 Subject: [PATCH 08/12] #12 Fixes to profile navigation --- public/modules/users/config/users.client.config.js | 4 ++-- public/modules/users/config/users.client.routes.js | 4 ++-- .../modules/users/views/profile/edit-profile.client.view.html | 2 +- .../modules/users/views/profile/view-profile.client.view.html | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/modules/users/config/users.client.config.js b/public/modules/users/config/users.client.config.js index 45b495ce7a..c553ee3a00 100644 --- a/public/modules/users/config/users.client.config.js +++ b/public/modules/users/config/users.client.config.js @@ -6,9 +6,9 @@ angular.module('users').run(['Menus', 'Authentication', // Set top bar menu items Menus.addMenuItem('topuserbar', Authentication.user.displayName, 'profile', 'dropdown', '/profile'); - Menus.addSubMenuItem('topuserbar', 'profile', 'My profile', 'profile', 'profile', null, null, 0, 'user'); + Menus.addSubMenuItem('topuserbar', 'profile', 'My profile', 'profile/' + Authentication.user.username, 'profile', null, null, 0, 'user'); Menus.addSubMenuItem('topuserbar', 'profile', 'Edit profile', 'profile/' + Authentication.user.username + '/edit', 'profile-edit', null, null, 0, 'edit'); - Menus.addSubMenuItem('topuserbar', 'profile', 'Settings', 'profile-settings', 'profile-settings', null, null, 0, 'cog'); + Menus.addSubMenuItem('topuserbar', 'profile', 'Settings', 'profile/' + Authentication.user.username + '/settings', 'profile-settings', null, null, 0, 'cog'); Menus.addSubMenuItem('topuserbar', 'profile', 'Help', 'contact', 'contact', null, null, 0, 'bolt'); Menus.addSubMenuDivider('topuserbar', 'profile'); Menus.addSubMenuItem('topuserbar', 'profile', 'Sign out', 'auth/signout', 'signout', null, null, 0, 'sign-out'); diff --git a/public/modules/users/config/users.client.routes.js b/public/modules/users/config/users.client.routes.js index b606ee6602..735270e6cb 100755 --- a/public/modules/users/config/users.client.routes.js +++ b/public/modules/users/config/users.client.routes.js @@ -10,11 +10,11 @@ angular.module('users').config(['$stateProvider', templateUrl: 'modules/users/views/authentication/welcome.client.view.html' }). state('profile-edit', { - url: '/profile-edit', + url: '/profile/:username/edit', templateUrl: 'modules/users/views/profile/edit-profile.client.view.html' }). state('profile-settings', { - url: '/profile-settings', + url: '/profile/:username/settings', templateUrl: 'modules/users/views/profile/edit-settings.client.view.html' }). state('profile', { diff --git a/public/modules/users/views/profile/edit-profile.client.view.html b/public/modules/users/views/profile/edit-profile.client.view.html index 830488daa6..55f9de197e 100644 --- a/public/modules/users/views/profile/edit-profile.client.view.html +++ b/public/modules/users/views/profile/edit-profile.client.view.html @@ -135,7 +135,7 @@

- Cancel + Cancel
diff --git a/public/modules/users/views/profile/view-profile.client.view.html b/public/modules/users/views/profile/view-profile.client.view.html index 340fd1200f..b2be07abea 100644 --- a/public/modules/users/views/profile/view-profile.client.view.html +++ b/public/modules/users/views/profile/view-profile.client.view.html @@ -56,7 +56,7 @@

@{{profile.username
This is your profile as others see it.   - Edit your profile + Edit your profile
From 9fd8179121c69bb0c83259cd62c34193b3291fdc Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Tue, 7 Oct 2014 13:24:44 +0300 Subject: [PATCH 09/12] #7 Let user choose avatar source FB, Gravatar, none, local upload --- app/controllers/messages.server.controller.js | 16 ++- .../references.server.controller.js | 10 +- .../users/users.profile.server.controller.js | 20 ++- app/models/user.server.model.js | 26 ++-- config/env/all.js | 5 +- config/env/development.js | 5 +- .../views/list-references.client.view.html | 2 +- .../controllers/settings.client.controller.js | 23 +++- .../directives/tr-avatar.client.directive.js | 127 +++++++++++++++++- public/modules/users/less/editor.less | 16 +-- .../directives/tr-avatar.client.view.html | 5 - .../profile/edit-profile.client.view.html | 61 ++++++++- 12 files changed, 263 insertions(+), 53 deletions(-) delete mode 100644 public/modules/users/views/directives/tr-avatar.client.view.html diff --git a/app/controllers/messages.server.controller.js b/app/controllers/messages.server.controller.js index eb3e1b3e16..8efe3b4b18 100755 --- a/app/controllers/messages.server.controller.js +++ b/app/controllers/messages.server.controller.js @@ -5,6 +5,7 @@ */ var mongoose = require('mongoose'), errorHandler = require('./errors'), + config = require('../../config/config'), sanitizeHtml = require('sanitize-html'), Message = mongoose.model('Message'), Thread = mongoose.model('Thread'), @@ -27,6 +28,8 @@ var messageSanitizeOptions = { allowedSchemes: [ 'http', 'https', 'ftp', 'mailto', 'tel' ] }; +// Populate users with these fields +var userPopulateFields = config.app.miniUserProfileFields.join(' '); /** * List of threads aka inbox @@ -42,8 +45,8 @@ exports.inbox = function(req, res) { } ) .sort('updated') - .populate('userFrom', 'displayName username') - .populate('userTo', 'displayName username') + .populate('userFrom', userPopulateFields) + .populate('userTo', userPopulateFields) .populate('message', 'content') .exec(function(err, threads) { if (err) { @@ -139,10 +142,10 @@ exports.send = function(req, res) { // We'll need some info about related users, populate some fields message - .populate('userFrom', 'displayName username') + .populate('userFrom', userPopulateFields) .populate({ path: 'userTo', - select: 'displayName username' + select: userPopulateFields }, function(err, message) { if (err) { return res.status(400).send({ @@ -175,6 +178,7 @@ exports.thread = function(req, res) { * Thread middleware */ exports.threadByUser = function(req, res, next, userId) { + Message.find( { $or: [ @@ -184,8 +188,8 @@ exports.threadByUser = function(req, res, next, userId) { } ) .sort('-created') - .populate('userFrom', 'displayName username') - .populate('userTo', 'displayName username') + .populate('userFrom', userPopulateFields) + .populate('userTo', userPopulateFields) .exec(function(err, messages) { if (err) return next(err); if (!messages) return next(new Error('Failed to load messages.')); diff --git a/app/controllers/references.server.controller.js b/app/controllers/references.server.controller.js index a219986816..483ba0870c 100644 --- a/app/controllers/references.server.controller.js +++ b/app/controllers/references.server.controller.js @@ -5,9 +5,13 @@ */ var mongoose = require('mongoose'), errorHandler = require('./errors'), + config = require('../../config/config'), Reference = mongoose.model('Reference'), _ = require('lodash'); +// Populate users with these fields +var userPopulateFields = config.app.miniUserProfileFields.join(' '); + /** * Create a Reference */ @@ -58,7 +62,7 @@ exports.update = function(req, res) { reference .populate({ path: 'userTo', - select: 'displayName username' + select: userPopulateFields }, function(err, reference) { if (err) { return res.status(400).send({ @@ -136,8 +140,8 @@ exports.referencesByUser = function(req, res, next, userId) { { userTo: userId } ] }) - .populate('userFrom', 'displayName username') - .populate('userTo', 'displayName username') + .populate('userFrom', userPopulateFields) + .populate('userTo', userPopulateFields) .exec(function(err, references) { if (err) return next(err); if (! references) return next(new Error('Failed to load References for user ' + userId)); diff --git a/app/controllers/users/users.profile.server.controller.js b/app/controllers/users/users.profile.server.controller.js index ad2cf74cf1..6233350f65 100644 --- a/app/controllers/users/users.profile.server.controller.js +++ b/app/controllers/users/users.profile.server.controller.js @@ -5,6 +5,7 @@ */ var _ = require('lodash'), errorHandler = require('../errors'), + config = require('../../../config/config'), mongoose = require('mongoose'), passport = require('passport'), sanitizeHtml = require('sanitize-html'), @@ -29,6 +30,8 @@ var userSanitizeOptions = { allowedSchemes: [ 'http', 'https', 'ftp', 'mailto', 'tel' ] }; +// Populate users with these fields +var userPopulateFields = config.app.miniUserProfileFields.join(' '); /** * Update user details @@ -84,7 +87,6 @@ exports.me = function(req, res) { * Show the profile of the user */ exports.getUser = function(req, res) { - console.log(req.user); res.jsonp(req.user || null); }; @@ -93,7 +95,7 @@ exports.getUser = function(req, res) { * Pick only certain fields from whole profile @link http://underscorejs.org/#pick */ exports.getMiniUser = function(req, res) { - res.jsonp( _.pick(req.user, 'id', 'displayName', 'username') || null ); + res.jsonp( _.pick(req.user, userPopulateFields) || null ); }; @@ -134,14 +136,17 @@ exports.userByID = function(req, res, next, id) { 'birthdate', 'seen', 'created', - 'updated' + 'updated', + 'avatarSource', + 'emailHash' // MD5 hashed email to use with Gravatars ); // Sanitize output if(user.description) user.description = sanitizeHtml(user.description, userSanitizeOptions); // Check if logged in user has left reference for this profile - console.log('->userByID, check if user ' + req.user._id + ' has written reference for ' + user._id); + //console.log('->userByID, check if user ' + req.user._id + ' has written reference for ' + user._id); + Reference.findOne({ userTo: user._id, userFrom: req.user._id @@ -174,14 +179,17 @@ exports.userByUsername = function(req, res, next, username) { 'birthdate', 'seen', 'created', - 'updated' + 'updated', + 'avatarSource', + 'emailHash' // MD5 hashed email to use with Gravatars ); // Sanitize output if(user.description) user.description = sanitizeHtml(user.description, userSanitizeOptions); // Check if logged in user has left reference for this profile - console.log('->userByUsername, check if user ' + req.user._id + ' has written reference for ' + user._id); + //console.log('->userByUsername, check if user ' + req.user._id + ' has written reference for ' + user._id); + Reference.findOne({ userTo: user._id, userFrom: req.user._id diff --git a/app/models/user.server.model.js b/app/models/user.server.model.js index 488bc2a722..cd76dbe9ec 100755 --- a/app/models/user.server.model.js +++ b/app/models/user.server.model.js @@ -21,19 +21,18 @@ var validateLocalStrategyPassword = function(password) { return (this.provider !== 'local' || (password && password.length >= 8)); }; - /** * A Validation function for username * - at least 3 characters * - maximum 32 characters * - only a-z0-9_-. * - not in list of illegal usernames - * - no consecutive dots, "." ok, ".." nope + * - no consecutive dots: "." ok, ".." nope */ var validateUsername = function(username) { var usernameRegex = /^[a-z0-9.\-_]{3,32}$/, dotsRegex = /^([^.]+\.?)$/, - illegalUsernames = ['trustroots', 'trust', 'roots', 're', 're:', 'fwd', 'fwd:', 'reply', 'admin', 'administrator', 'user', 'password', 'username', 'unknown', 'anonymous', 'home', 'signup', 'signin', 'edit', 'password', 'username', 'user', ' demo', 'test']; + illegalUsernames = ['trustroots', 'trust', 'roots', 're', 're:', 'fwd', 'fwd:', 'reply', 'admin', 'administrator', 'user', 'password', 'username', 'unknown', 'anonymous', 'home', 'signup', 'signin', 'edit', 'settings', 'password', 'username', 'user', ' demo', 'test']; return (this.provider !== 'local' || ( username && usernameRegex.test(username) && illegalUsernames.indexOf(username) < 0) && @@ -41,8 +40,6 @@ var validateUsername = function(username) { ); }; - - /** * User Schema */ @@ -113,6 +110,9 @@ var UserSchema = new Schema({ default: '', validate: [validateLocalStrategyPassword, 'Password should be more than 8 characters long.'] }, + emailHash: { + type: String, + }, salt: { type: String }, @@ -139,13 +139,20 @@ var UserSchema = new Schema({ type: Date, default: Date.now }, + avatarSource: { + type: [{ + type: String, + enum: ['none','gravatar','facebook','local'] + }], + default: ['gravatar'] + }, /* For reset password */ resetPasswordToken: { type: String }, - resetPasswordExpires: { - type: Date - } + resetPasswordExpires: { + type: Date + } }); /** @@ -157,6 +164,9 @@ UserSchema.pre('save', function(next) { this.password = this.hashPassword(this.password); } + // Pre-cached email hash to use with Gravatar + this.emailHash = crypto.createHash('md5').update( this.email.trim().toLowerCase() ).digest('hex'); + next(); }); diff --git a/config/env/all.js b/config/env/all.js index 6543b7d185..bc87092617 100644 --- a/config/env/all.js +++ b/config/env/all.js @@ -4,7 +4,8 @@ module.exports = { app: { title: 'Trust Roots', description: 'Travellers network', - keywords: 'traveling,hospitality exchange,nomadism' + keywords: 'traveling,hospitality exchange,nomadism', + miniUserProfileFields: ['id', 'displayName', 'username', 'avatarSource', 'emailHash'] }, port: process.env.PORT || 3000, templateEngine: 'swig', @@ -60,4 +61,4 @@ module.exports = { 'public/modules/*/tests/*.js' ] } -}; \ No newline at end of file +}; diff --git a/config/env/development.js b/config/env/development.js index 743a108a71..b12fdf0bfe 100644 --- a/config/env/development.js +++ b/config/env/development.js @@ -3,7 +3,8 @@ module.exports = { db: 'mongodb://localhost/trust-roots-dev', app: { - title: 'Trust Roots - Development Environment' + title: 'Trust Roots - Development Environment', + miniUserProfileFields: ['id', 'displayName', 'username', 'avatarSource', 'emailHash'] }, facebook: { clientID: process.env.FACEBOOK_ID || 'APP_ID', @@ -40,4 +41,4 @@ module.exports = { } } } -}; \ No newline at end of file +}; diff --git a/public/modules/references/views/list-references.client.view.html b/public/modules/references/views/list-references.client.view.html index d571d05756..fa7eae4be4 100644 --- a/public/modules/references/views/list-references.client.view.html +++ b/public/modules/references/views/list-references.client.view.html @@ -44,7 +44,7 @@

Firstname commented back

-
+

{{ reference.userFrom.displayName }}
diff --git a/public/modules/users/controllers/settings.client.controller.js b/public/modules/users/controllers/settings.client.controller.js index 8860017d13..fa0084e288 100644 --- a/public/modules/users/controllers/settings.client.controller.js +++ b/public/modules/users/controllers/settings.client.controller.js @@ -1,7 +1,7 @@ 'use strict'; -angular.module('users').controller('SettingsController', ['$scope', '$http', '$stateParams', '$state', '$location', 'Users', 'Authentication', - function($scope, $http, $stateParams, $state, $location, Users, Authentication) { +angular.module('users').controller('SettingsController', ['$scope', '$modal', '$http', '$stateParams', '$state', '$location', 'Users', 'Authentication', + function($scope, $modal, $http, $stateParams, $state, $location, Users, Authentication) { $scope.user = Authentication.user; $scope.profile = false; @@ -81,6 +81,25 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$s } }; + /** + * Open avatar -modal + */ + $scope.avatarModal = function (user, $event) { + + if($event) $event.preventDefault(); + + var modalInstance = $modal.open({ + templateUrl: 'avatar.client.modal.html', //inline at template + controller: function ($scope, $modalInstance) { + $scope.user = user; + $scope.close = function () { + $modalInstance.dismiss('close'); + }; + } + }); + + }; + // Remove user permanently $scope.removalConfirm = false; diff --git a/public/modules/users/directives/tr-avatar.client.directive.js b/public/modules/users/directives/tr-avatar.client.directive.js index 8fbe02cf36..6d519bea62 100644 --- a/public/modules/users/directives/tr-avatar.client.directive.js +++ b/public/modules/users/directives/tr-avatar.client.directive.js @@ -2,18 +2,137 @@ /** * Produce user's avatar + * + * Basic usage: + *
+ * + * user (User model) + * + * Optional parameters: + * size (default 32) + * source (overwrite user's source selection) + * link (will not wrap into link) + * watch (will start watching user.avatarSource and refresh each time that changes) */ angular.module('users').directive('trAvatar', [ function() { + var validSources = ['none', 'facebook' ,'gravatar', 'locale']; return { - templateUrl: '/modules/users/views/directives/tr-avatar.client.view.html', + //templateUrl: '/modules/users/views/directives/tr-avatar.client.view.html', + template: '', restrict: 'EA', + replace: true, scope: { user: '=user' }, - link: function (scope, element, attr) { - scope.size = attr.size; + controller: ['$scope', function($scope) { + + if($scope.user) { + console.log('->avatar: ' + $scope.user.username); + console.log($scope.user); + } + else console.log('->avatar '); + + // Options + $scope.defaultAvatar = '/modules/users/img/avatar.png'; + + + $scope.determineSource = function() { + + // Determine source for avatar + if($scope.fixedSource) {// && validSources.indexOf(attr.source)==-1) + $scope.source = $scope.fixedSource; + } + else if($scope.user && $scope.user.avatarSource) { + // && validSources.indexOf($scope.user.avatarSource.toString())==-1) + $scope.source = $scope.user.avatarSource.toString(); + console.log('users avatarSource: ' + $scope.source); + } + else { + $scope.source = 'none'; + } + + /** + * Avatar via FB + * @link https://developers.facebook.com/docs/graph-api/reference/user/picture/ + */ + if($scope.source === 'facebook' ) { + console.log('->determineSource: fb'); + + if($scope.user && $scope.user.email) { + var fb_id = '#'; + + $scope.avatar = '//graph.facebook.com/' + fb_id + '/picture/?width=' + $scope.size + '&height=' + $scope.size; + } + else { + console.log('->determineSource facebook: fall to default'); + $scope.avatar = $scope.defaultAvatar; + } + } + + /** + * Avatar via Gravatar + * @link https://en.gravatar.com/site/implement/images/ + * @todo: pre-save email md5 hash to the db + */ + else if($scope.source === 'gravatar') { + console.log('->determineSource gravatar: ' + $scope.user.emailHash); + if($scope.user.emailHash) { + $scope.avatar = '//gravatar.com/avatar/' + $scope.user.emailHash + '?s=' + $scope.size; + + // Gravatar fallback is required to be online. It's defined at settings.json + // If public default avatar is set, send it to Gravatar as failback + // @todo: pass $scope.defaultAvatar with public domain here + $scope.avatar += '&d=' + encodeURIComponent('http://ideas.trustroots.org/wordpress/wp-content/uploads/2014/10/avatar.png'); + } + else { + console.log('->determineSource gravatar: fall to default'); + $scope.avatar = $scope.defaultAvatar; + } + } + + /** + * Locally uploaded image + */ + else if($scope.source === 'locale') { + console.log('->determineSource: locale'); + $scope.avatar = $scope.defaultAvatar + '?locale'; + } + + // Dummy + else { + console.log('->determineSource: none'); + $scope.avatar = $scope.defaultAvatar + '?none'; + } + };// determineSource() + + // Sets $scope.avatar + $scope.determineSource(); + + // If asked to, start watching user.avatarSource and refresh directive each time that changes + //if(attr.watch) { + $scope.$watch('user.avatarSource',function(newSource, oldSource) { + console.log('avatarSource changed. new: ' + newSource + ', old: ' + oldSource); + $scope.source = newSource; + $scope.determineSource(); + }); + //} + + + }], + link: function (scope, element, attr, ctrl) { + + scope.size = attr.size || 32; + + // Wrap it to link by default + //if(!attr.link && scope.user) { + // angular.element(element).wrap(''); + //} + if(attr.source) { + scope.fixedSource = attr.source; + } + } }; } -]); \ No newline at end of file +]); diff --git a/public/modules/users/less/editor.less b/public/modules/users/less/editor.less index c16e05a6af..83d1de7bbd 100644 --- a/public/modules/users/less/editor.less +++ b/public/modules/users/less/editor.less @@ -1,7 +1,7 @@ #profile-editor { .angular-medium-editor { - display: inline; + //display: inline; border: 0; background: transparent; .box-shadow(none); @@ -135,21 +135,13 @@ /* Choose avatar -modal */ #profile-avatar-choises { - list-style: none; - margin: 0; - padding: 0; - clear: both; - overflow: hidden; - li { - float: left; - margin: 0 10px; - padding: 0; - text-align: center; - } .avatar-source-init-upload { display: block; position: relative; .square(144px); + label { + margin: 10px 0; + } img { position: absolute; top: 0; diff --git a/public/modules/users/views/directives/tr-avatar.client.view.html b/public/modules/users/views/directives/tr-avatar.client.view.html deleted file mode 100644 index adca9d261a..0000000000 --- a/public/modules/users/views/directives/tr-avatar.client.view.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/public/modules/users/views/profile/edit-profile.client.view.html b/public/modules/users/views/profile/edit-profile.client.view.html index 55f9de197e..5b69e5914e 100644 --- a/public/modules/users/views/profile/edit-profile.client.view.html +++ b/public/modules/users/views/profile/edit-profile.client.view.html @@ -14,8 +14,9 @@
- -
+
+ +

+ + From b0652db7abff1ac38ae9c5b51aa1994f82f47994 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Tue, 7 Oct 2014 13:25:02 +0300 Subject: [PATCH 10/12] Smarter header menu --- .../controllers/header.client.controller.js | 13 +++---- .../filters/hash-bang-url.client.filter.js | 10 ++++++ .../core/views/header.client.view.html | 34 ++++--------------- .../users/config/users.client.config.js | 2 +- 4 files changed, 24 insertions(+), 35 deletions(-) create mode 100644 public/modules/core/filters/hash-bang-url.client.filter.js diff --git a/public/modules/core/controllers/header.client.controller.js b/public/modules/core/controllers/header.client.controller.js index 1b9227ff71..8a9568584f 100644 --- a/public/modules/core/controllers/header.client.controller.js +++ b/public/modules/core/controllers/header.client.controller.js @@ -3,13 +3,14 @@ angular.module('core').controller('HeaderController', ['$scope', '$log', 'Authentication', 'Menus', 'Socket', function($scope, $log, Authentication, Menus, Socket) { - Socket.on('reconnect', function () { - $log.log('Reconnected to the server'); - }); + // @todo: show info popup when this happens + Socket.on('reconnect', function () { + $log.log('Reconnected to the server'); + }); - Socket.on('reconnecting', function () { - $log.log('Attempting to re-connect to the server'); - }); + Socket.on('reconnecting', function () { + $log.log('Attempting to re-connect to the server'); + }); $scope.authentication = Authentication; $scope.isCollapsed = false; diff --git a/public/modules/core/filters/hash-bang-url.client.filter.js b/public/modules/core/filters/hash-bang-url.client.filter.js new file mode 100644 index 0000000000..405d3c2308 --- /dev/null +++ b/public/modules/core/filters/hash-bang-url.client.filter.js @@ -0,0 +1,10 @@ +'use strict'; + +// Add hashbang to urls that don't start with '/' +angular.module('core').filter('hashbangurl', [ + function() { + return function(url) { + return (url && url.substr(0,1) !== '/') ? '/#!/' + url : url; + }; + } +]); diff --git a/public/modules/core/views/header.client.view.html b/public/modules/core/views/header.client.view.html index 80344558c1..265e29f03f 100644 --- a/public/modules/core/views/header.client.view.html +++ b/public/modules/core/views/header.client.view.html @@ -21,13 +21,13 @@ - + {{item.title}} @@ -37,11 +37,11 @@ @@ -56,40 +56,18 @@ - + {{item.title}} -

diff --git a/public/modules/users/config/users.client.config.js b/public/modules/users/config/users.client.config.js index c553ee3a00..7ceafd8216 100644 --- a/public/modules/users/config/users.client.config.js +++ b/public/modules/users/config/users.client.config.js @@ -11,7 +11,7 @@ angular.module('users').run(['Menus', 'Authentication', Menus.addSubMenuItem('topuserbar', 'profile', 'Settings', 'profile/' + Authentication.user.username + '/settings', 'profile-settings', null, null, 0, 'cog'); Menus.addSubMenuItem('topuserbar', 'profile', 'Help', 'contact', 'contact', null, null, 0, 'bolt'); Menus.addSubMenuDivider('topuserbar', 'profile'); - Menus.addSubMenuItem('topuserbar', 'profile', 'Sign out', 'auth/signout', 'signout', null, null, 0, 'sign-out'); + Menus.addSubMenuItem('topuserbar', 'profile', 'Sign out', '/auth/signout', '/auth/signout', null, null, 0, 'sign-out'); } ]); From db86af176563a2baaad514357bb11f7eaa72059a Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Tue, 7 Oct 2014 13:25:08 +0300 Subject: [PATCH 11/12] Blog url --- public/modules/core/views/footer.client.view.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/modules/core/views/footer.client.view.html b/public/modules/core/views/footer.client.view.html index 6e2313f147..60f20985d5 100644 --- a/public/modules/core/views/footer.client.view.html +++ b/public/modules/core/views/footer.client.view.html @@ -8,7 +8,7 @@
- \ No newline at end of file + From 4e5e12e71bab89d26cedafb2de52e052b0d2a631 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Tue, 7 Oct 2014 14:40:51 +0300 Subject: [PATCH 12/12] Add @aurimus to humans --- public/humans.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/humans.txt b/public/humans.txt index 3fd6604a8f..18013321dc 100755 --- a/public/humans.txt +++ b/public/humans.txt @@ -1,7 +1,7 @@ # humanstxt.org/ # The humans responsible -# TEAM +# CORE TEAM Mikael -- mikaelkorpela.fi Callum -- callum-macdonald.com @@ -9,4 +9,6 @@ # THANKS - Rémi, BeWelcome Welen -project dudes + Rémi + Aurimas + BeWelcome Welen -project dudes