Skip to content
This repository was archived by the owner on Aug 30, 2021. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions modules/articles/server/models/article.server.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ var ArticleSchema = new Schema({
user: {
type: Schema.ObjectId,
ref: 'User'
},
upVotes: {
type: Number,
default: 0
},
downVotes: {
type: Number,
default: 0
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,66 @@ exports.changeProfilePicture = function (req, res) {
exports.me = function (req, res) {
res.json(req.user || null);
};

/**
* Record User's vote and update Article vote counts
*/
exports.vote = function(req, res) {
var article = req.article;
var user = req.user;
var newVoteDirection = req.params.direction;
var oldVoteDirection;
var upVotesAddend = 0;
var downVotesAddend = 0;
var voteIndex;

// look for existing vote
for (voteIndex = 0; voteIndex < user.votes.length; voteIndex++) {
if (article._id.toString() === user.votes[voteIndex]._id.toString()) {
break;
}
}

// if no vote exists, add one
if (voteIndex === user.votes.length) {
user.votes.push({
_id: article._id,
direction:'none'
});
}

oldVoteDirection = user.votes[voteIndex].direction;
user.votes[voteIndex].direction = newVoteDirection;
if (oldVoteDirection === 'none') {
upVotesAddend = newVoteDirection === 'up' ? 1 : 0;
downVotesAddend = newVoteDirection === 'down' ? 1 : 0;
} else if (newVoteDirection === 'none') {
upVotesAddend = oldVoteDirection === 'up' ? -1 : 0;
downVotesAddend = oldVoteDirection === 'down' ? -1 : 0;
} else if (oldVoteDirection !== newVoteDirection) {
upVotesAddend = newVoteDirection === 'up' ? 1 : -1;
downVotesAddend = upVotesAddend * -1;
}

user.save(function(err) {
if (err) {
return res.status(500).send({
message: errorHandler.getErrorMessage(err)
});
} else {
article.update({ $inc: { upVotes: upVotesAddend, downVotes: downVotesAddend }}, {}, function(err) {
if (err) {
return res.status(500).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json({
oldVoteDirection: oldVoteDirection,
newVoteDirection: newVoteDirection
});
}
});
}
});

};
11 changes: 11 additions & 0 deletions modules/users/server/models/user.server.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ var UserSchema = new Schema({
type: Date,
default: Date.now
},
votes: [{
_id: {
type: Schema.ObjectId,
ref: 'Article',
index: true
},
direction: {
type: String,
enum: ['up', 'down', 'none']
}
}],
/* For reset password */
resetPasswordToken: {
type: String
Expand Down
3 changes: 3 additions & 0 deletions modules/users/server/routes/users.server.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ module.exports = function(app) {
app.route('/api/users/password').post(users.changePassword);
app.route('/api/users/picture').post(users.changeProfilePicture);

// up/down voting on Articles
app.route('/api/users/vote/:direction/:articleId').put(users.requiresLogin, users.vote);

// Finish by binding the user middleware
app.param('userId', users.userByID);
};
138 changes: 138 additions & 0 deletions modules/users/tests/server/user.votes.server.routes.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
'use strict';

var should = require('should'),
request = require('supertest'),
path = require('path'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
Article = mongoose.model('Article'),
express = require(path.resolve('./config/lib/express'));

/**
* Globals
*/
var app, agent, credentials, user, article;
var voteDirections = ['none', 'up', 'down'];

/**
* User vote route tests
*/
describe('User Vote up/down/none on Article Tests:', function() {
before(function(done) {
// Get application
app = express.init(mongoose);
agent = request.agent(app);

done();
});

beforeEach(function(done) {
// Create user credentials
credentials = {
username: 'username',
password: 'password'
};

// Create a new user
user = new User({
firstName: 'Full',
lastName: 'Name',
displayName: 'Full Name',
email: 'test@test.com',
username: credentials.username,
password: credentials.password,
provider: 'local'
});

// Save a user to the test db and create and save new article
user.save(function() {
article = new Article({
title: 'Article Title',
content: 'Article Content',
user: user
});

article.save(function() {
done();
});
});
});

it('should not be able to vote if not logged in', function(done) {
agent.put('/api/users/vote/up/' + article._id)
.expect(401)
.end(function(voteAuthErr, voteAuthRes) {
// Call the assertion callback
done(voteAuthErr);
});
});

it('should not be able to vote if direction is invalid', function(done) {
agent.post('/api/auth/signin')
.send(credentials)
.expect(200)
.end(function(signinErr, signinRes) {
// Handle signin error
if (signinErr) done(signinErr);

agent.put('/api/users/vote/invalidDirection/' + article._id)
.send(credentials)
.expect(500)
.end(function(voteErr, voteRes) {
// Call the assertion callback
done(voteErr);
});
});
});

function testVoteSequence(firstVoteDirection, secondVoteDirection) {
it('should be able to vote ' + firstVoteDirection + ' and then ' + secondVoteDirection + ' on an article if logged in', function(done) {
agent.post('/api/auth/signin')
.send(credentials)
.expect(200)
.end(function(signinErr, signinRes) {
// Handle signin error
if (signinErr) done(signinErr);

// make the first vote
agent.put('/api/users/vote/' + firstVoteDirection + '/' + article._id)
.expect(200)
.end(function(voteErr, voteRes) {
voteRes.body.oldVoteDirection.should.match('none');
voteRes.body.newVoteDirection.should.match(firstVoteDirection);

// make the second vote
agent.put('/api/users/vote/' + secondVoteDirection + '/' + article._id)
.expect(200)
.end(function(voteErr, voteRes) {
voteRes.body.oldVoteDirection.should.match(firstVoteDirection);
voteRes.body.newVoteDirection.should.match(secondVoteDirection);

// make sure Article vote counts are correct
request(app).get('/api/articles/' + article._id)
.end(function(req, res) {
res.body.upVotes.should.be.equal(secondVoteDirection === 'up' ? 1 : 0);
res.body.downVotes.should.be.equal(secondVoteDirection === 'down' ? 1 : 0);
done();
});

});

});

});
});
}

for (var firstIndex in voteDirections) {
for (var secondIndex in voteDirections) {
testVoteSequence(voteDirections[firstIndex], voteDirections[secondIndex]);
}
}

afterEach(function(done) {
User.remove().exec(function() {
Article.remove().exec(done);
});
});
});