From 6cb438da3ed0b303eee9a5e5b3997f5cedff66d2 Mon Sep 17 00:00:00 2001 From: Adam Barnhard Date: Thu, 28 Aug 2014 16:40:36 -0500 Subject: [PATCH] half-finished messages feature --- app/controllers/messages.js | 10 +++ app/models/message.js | 55 +++++++++++++ app/models/user.js | 13 ++- app/routes/routes.js | 2 + app/views/messages/index.jade | 22 +++++ app/views/messages/show.jade | 0 app/views/shared/nav.jade | 3 +- db/messages.json | 25 ++++++ test/acceptance/message.js | 146 ++++++++++++++++++++++++++++++++++ test/acceptance/users.js | 25 ++++++ test/scripts/clean-db.sh | 2 +- test/unit/message.js | 64 +++++++++++++++ test/unit/user.js | 22 +++++ 13 files changed, 385 insertions(+), 4 deletions(-) create mode 100644 app/controllers/messages.js create mode 100644 app/models/message.js create mode 100644 app/views/messages/index.jade create mode 100644 app/views/messages/show.jade create mode 100644 db/messages.json create mode 100644 test/acceptance/message.js create mode 100644 test/unit/message.js diff --git a/app/controllers/messages.js b/app/controllers/messages.js new file mode 100644 index 0000000..9205cd9 --- /dev/null +++ b/app/controllers/messages.js @@ -0,0 +1,10 @@ +'use strict'; + +var Message = require('../models/message'); + +exports.index = function(req, res){ + Message.find(res.locals.user._id, req.query, function(err, messages){ + res.render('messages/index', {messages:messages, query:req.query}); + }); +}; + diff --git a/app/models/message.js b/app/models/message.js new file mode 100644 index 0000000..834f7f9 --- /dev/null +++ b/app/models/message.js @@ -0,0 +1,55 @@ +'use strict'; + +var User = require('./user'), + async = require('async'); + +function Message(fromId, toId, body){ + this.fromId = fromId; + this.toId = toId; + this.body = body; + this.sent = new Date(); + this.isRead = false; +} + +Object.defineProperty(Message, 'collection', { + get: function(){return global.mongodb.collection('messages');} +}); + +Message.find = function(toId, query, cb){ + var filter = {toId:toId}, + sort = {}; + sort.sent = (query.sort) ? query.sort * 1 : 1; + Message.collection.find(filter).sort(sort).toArray(function(err, objs){ + // console.log('***objs', objs); + async.map(objs, getSenderInfo, function(err2, fullMessages){ + // console.log(fullMessages); + cb(null, fullMessages); + }); + }); +}; + +Message.findOne = function(query, cb){ + Message.collection.findOne(query, cb); +}; + +Message.prototype.save = function(cb){ + Message.collection.save(this, cb); +}; + +Message.countUnreadForUser = function(id, cb){ + Message.collection.count({toId:id, isRead:false}, cb); +}; + +module.exports = Message; + +// helper functions +function getSenderInfo(message, done){ + User.findById(message.fromId, function(err, user){ + console.log(user); + message.fromName = user.name; + message.fromEmail = user.email; + // console.log(message); + done(null, message); + }); +} + diff --git a/app/models/user.js b/app/models/user.js index 98e9e9d..95a6067 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -2,7 +2,8 @@ var bcrypt = require('bcrypt'), _ = require('lodash'), - Mongo = require('mongodb'); + Mongo = require('mongodb'), + Message = require('./message'); function User(){ } @@ -31,7 +32,10 @@ User.authenticate = function(o, cb){ if(!user){return cb();} var isOk = bcrypt.compareSync(o.password, user.password); if(!isOk){return cb();} - cb(user); + Message.countUnreadForUser(user._id, function(err2, count){ + user.unreadMessages = count; + cb(user); + }); }); }; @@ -70,6 +74,7 @@ User.prototype.send = function(receiver, data, cb){ sendEmail(this, receiver.email, data.message, cb); break; case 'internal': + sendInternal(this._id, receiver._id, data.message, cb); } }; @@ -100,3 +105,7 @@ function sendEmail(sender, to, body, cb){ mg.messages().send(data, cb); } +function sendInternal(fromId, toId, body, cb){ + var m = new Message(fromId, toId, body); + m.save(cb); +} diff --git a/app/routes/routes.js b/app/routes/routes.js index e7a9009..9e121d3 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -9,6 +9,7 @@ var morgan = require('morgan'), security = require('../lib/security'), debug = require('../lib/debug'), home = require('../controllers/home'), + messages = require('../controllers/messages'), users = require('../controllers/users'); module.exports = function(app, express){ @@ -36,6 +37,7 @@ module.exports = function(app, express){ app.get('/users', users.index); app.get('/users/:email', users.showProfile); app.post('/message/:userId', users.message); + app.get('/messages', messages.index); console.log('Express: Routes Loaded'); }; diff --git a/app/views/messages/index.jade b/app/views/messages/index.jade new file mode 100644 index 0000000..90d73e1 --- /dev/null +++ b/app/views/messages/index.jade @@ -0,0 +1,22 @@ +extends ../shared/template +block content + h2 Messages + .panel.panel-default + .panel-body + .row + .col-xs-12 + table.table + thead + tr + th From + th Date + th Message + tbody + each message in messages + tr + td: a(href='/users/#{message.fromEmail}')= message.fromName + td= message.sent + td= message.body + +block scripts + diff --git a/app/views/messages/show.jade b/app/views/messages/show.jade new file mode 100644 index 0000000..e69de29 diff --git a/app/views/shared/nav.jade b/app/views/shared/nav.jade index 6609667..60b91cd 100644 --- a/app/views/shared/nav.jade +++ b/app/views/shared/nav.jade @@ -11,7 +11,8 @@ .collapse.navbar-collapse#bs-navbar-collapse ul.nav.navbar-nav.navbar-right if user - li: a(href='/users') All Users + li: a(href='/messages') Messages + li: a(href='/users') Users li: a(href='/profile') Profile li form.navbar-form(method='post', action='/logout') diff --git a/db/messages.json b/db/messages.json new file mode 100644 index 0000000..47fb468 --- /dev/null +++ b/db/messages.json @@ -0,0 +1,25 @@ +{ + "_id":{"$oid":"a00000000000000000000001"}, + "sent":{"$date":477295200000}, + "body":"Hi Bob, long time no see.\n You want to stop and get a drink sometime after work?", + "toId":{"$oid":"000000000000000000000001"}, + "fromId" : {"$oid":"000000000000000000000002"}, + "isRead":true +} +{ + "_id":{"$oid":"a00000000000000000000002"}, + "sent":{"$date":477295200000}, + "body":"Hi Bob, following up on drinks after work. Give me a call any time, or message me with this sweet app.", + "toId":{"$oid":"000000000000000000000001"}, + "fromId" : {"$oid":"000000000000000000000002"}, + "isRead":false +} +{ + "_id":{"$oid":"a00000000000000000000003"}, + "sent":{"$date":477295200000}, + "body":"Hi Sue, greate work on that project.\n If you don't get that promotion they are plain crazy.", + "toId":{"$oid":"000000000000000000000002"}, + "fromId" : {"$oid":"000000000000000000000003"}, + "isRead":true +} + diff --git a/test/acceptance/message.js b/test/acceptance/message.js new file mode 100644 index 0000000..68cec0f --- /dev/null +++ b/test/acceptance/message.js @@ -0,0 +1,146 @@ +/* global describe, before, beforeEach, it */ + +'use strict'; + +process.env.DB = 'facebook-test'; + +var expect = require('chai').expect, + cp = require('child_process'), + app = require('../../app/index'), + cookie = null, + request = require('supertest'); + +describe('users', function(){ + before(function(done){ + request(app).get('/').end(done); + }); + + beforeEach(function(done){ + cp.execFile(__dirname + '/../scripts/clean-db.sh', [process.env.DB], {cwd:__dirname + '/../scripts'}, function(err, stdout, stderr){ + request(app) + .post('/login') + .send('email=nodeapptest%2Bbob%40gmail.com') + .send('password=1234') + .end(function(err, res){ + cookie = res.headers['set-cookie'][0]; + done(); + }); + }); + }); + + describe('get /profile/edit', function(){ + it('should show the edit profile page', function(done){ + request(app) + .get('/profile/edit') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(200); + expect(res.text).to.include('nodeapptest+bob@gmail.com'); + expect(res.text).to.include('Email'); + expect(res.text).to.include('Phone'); + expect(res.text).to.include('Visible'); + done(); + }); + }); + }); + + describe('put /profile', function(){ + it('should edit the profile in database', function(done){ + request(app) + .post('/profile') + .send('_method=put&visible=public&email=a%40b.com&phone=555-555-5555&photo=someUrl&tagline=some+tagline&facebook=facebookUrl&twitter=%40twitterhandle') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(302); + expect(res.headers.location).to.equal('/profile'); + done(); + }); + }); + }); + + describe('get /profile', function(){ + it('should show the logged in users profile page', function(done){ + request(app) + .post('/profile') + .send('_method=put&visible=public&email=a%40b.com&phone=555-555-5555&photo=someUrl&tagline=some+tagline&facebook=facebookUrl&twitter=%40twitterhandle') + .set('cookie', cookie) + .end(function(err, res){ + request(app) + .get('/profile') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(200); + expect(res.text).to.include('Phone'); + expect(res.text).to.include('Twitter'); + expect(res.text).to.include('Facebook'); + done(); + }); + }); + }); + }); + + describe('get /users', function(){ + it('should show links to public user profiles', function(done){ + request(app) + .get('/users') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(200); + expect(res.text).to.not.include('Bob'); + expect(res.text).to.include('John'); + expect(res.text).to.not.include('Sue'); + done(); + }); + }); + }); + + describe('get /users/email', function(){ + it('should return the profile page for a public profile', function(done){ + request(app) + .get('/users/nodeapptest+john@gmail.com') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(200); + expect(res.text).to.include('John'); + done(); + }); + }); + it('should redirect away from private profile', function(done){ + request(app) + .get('/users/nodeapptest+sue@gmail.com') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(302); + expect(res.headers.location).to.equal('/users'); + done(); + }); + }); + }); + + describe('post /message/userId', function(){ + it('should send a text message to recipient', function(done){ + request(app) + .post('/message/000000000000000000000003') + .send('mtype=text&message=hey') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(302); + expect(res.headers.location).to.equal('/users/nodeapptest+john@gmail.com'); + done(); + }); + }); + it('should send an email message to recipient', function(done){ + request(app) + .post('/message/000000000000000000000003') + .send('mtype=email&message=hey') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(302); + expect(res.headers.location).to.equal('/users/nodeapptest+john@gmail.com'); + done(); + }); + }); + }); + +}); + diff --git a/test/acceptance/users.js b/test/acceptance/users.js index 68cec0f..c9a21fa 100644 --- a/test/acceptance/users.js +++ b/test/acceptance/users.js @@ -140,6 +140,31 @@ describe('users', function(){ done(); }); }); + it('should send an inmail message to recipient', function(done){ + request(app) + .post('/message/000000000000000000000003') + .send('mtype=internal&message=hey') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(302); + expect(res.headers.location).to.equal('/users/nodeapptest+john@gmail.com'); + done(); + }); + }); + }); + + describe('get /messages', function(){ + it('should show messages page for logged in user', function(done){ + request(app) + .get('/messages') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(200); + expect(res.text).to.include('Sue'); + expect(res.text).to.include('Message'); + done(); + }); + }); }); }); diff --git a/test/scripts/clean-db.sh b/test/scripts/clean-db.sh index 55e6db7..5b394e6 100755 --- a/test/scripts/clean-db.sh +++ b/test/scripts/clean-db.sh @@ -5,6 +5,6 @@ if [ -z "$1" ] ; then exit 1 fi -mongoimport --jsonArray --drop --db $1 --collection examples --file ../../db/example.json +mongoimport --jsonArray --drop --db $1 --collection messages --file ../../db/messages.json mongoimport --jsonArray --drop --db $1 --collection users --file ../../db/users.json diff --git a/test/unit/message.js b/test/unit/message.js new file mode 100644 index 0000000..e41552f --- /dev/null +++ b/test/unit/message.js @@ -0,0 +1,64 @@ +/* jshint expr:true */ +/* global describe, it, before, beforeEach */ + +'use strict'; + +var expect = require('chai').expect, + Message = require('../../app/models/message'), + dbConnect = require('../../app/lib/mongodb'), + cp = require('child_process'), + Mongo = require('mongodb'), + db = 'facebook-test'; + +describe('Message', function(){ + before(function(done){ + dbConnect(db, function(){ + done(); + }); + }); + + beforeEach(function(done){ + cp.execFile(__dirname + '/../scripts/clean-db.sh', [db], {cwd:__dirname + '/../scripts'}, function(err, stdout, stderr){ + done(); + }); + }); + + describe('constructor', function(){ + it('should create a new Message object', function(){ + var fromId = Mongo.ObjectID(), + toId = Mongo.ObjectID(), + body = 'This is the text of the message', + m = new Message(fromId, toId, body); + expect(m).to.be.instanceof(Message); + expect(m.sent).to.be.instanceof(Date); + expect(m.toId).to.be.instanceof(Mongo.ObjectID); + expect(m.fromId).to.be.instanceof(Mongo.ObjectID); + }); + }); + + describe('.find', function(){ + it('should return all messages for a user', function(done){ + var id = Mongo.ObjectID('000000000000000000000001'); + Message.find(id, {}, function(err, messages){ + expect(messages).to.have.length(2); + expect(messages[0].fromName).to.equal('Sue'); + done(); + }); + }); + }); + + describe('#save', function(){ + it('should save a message in the database', function(done){ + var from = Mongo.ObjectID(), + toId = Mongo.ObjectID(), + body = 'This is the text of the message', + m = new Message(from, toId, body); + m.save(function(){ + expect(m._id).to.be.instanceof(Mongo.ObjectID); + done(); + }); + }); + }); + +}); + diff --git a/test/unit/user.js b/test/unit/user.js index e64ac93..0dc6465 100644 --- a/test/unit/user.js +++ b/test/unit/user.js @@ -7,6 +7,7 @@ var expect = require('chai').expect, User = require('../../app/models/user'), dbConnect = require('../../app/lib/mongodb'), cp = require('child_process'), + Mongo = require('mongodb'), db = 'facebook-test'; describe('User', function(){ @@ -61,6 +62,17 @@ describe('User', function(){ }); }); + describe('.authenticate', function(){ + it('should return an authenticated user', function(done){ + var data = {email:'nodeapptest+bob@gmail.com', password:'1234'}; + User.authenticate(data, function(user){ + expect(user).to.be.ok; + expect(user.unreadMessages).to.equal(1); + done(); + }); + }); + }); + describe('#send', function(){ it('should send a text message to a user', function(done){ User.findById('000000000000000000000001', function(err, sender){ @@ -82,6 +94,16 @@ describe('User', function(){ }); }); }); + it('should send an internal message to a user', function(done){ + User.findById('000000000000000000000001', function(err, sender){ + User.findById('000000000000000000000003', function(err, receiver){ + sender.send(receiver, {mtype:'internal', message:'yo'}, function(err, response){ + expect(response._id).to.be.instanceof(Mongo.ObjectID); + done(); + }); + }); + }); + }); }); });