mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-04 11:35:57 +00:00
Add in support for threads for comments
This commit is contained in:
parent
2a1e82ce46
commit
cb88e1e41b
17 changed files with 296 additions and 475 deletions
|
@ -12,14 +12,6 @@ module.exports = (grunt) ->
|
|||
src: "app.js"
|
||||
|
||||
coffee:
|
||||
client:
|
||||
expand: true,
|
||||
flatten: false,
|
||||
cwd: 'public/coffee',
|
||||
src: ['**/*.coffee'],
|
||||
dest: 'public/build/',
|
||||
ext: '.js'
|
||||
|
||||
server:
|
||||
expand: true,
|
||||
flatten: false,
|
||||
|
@ -56,47 +48,7 @@ module.exports = (grunt) ->
|
|||
files: ['app/**/*.coffee', 'test/unit/**/*.coffee']
|
||||
tasks: ['compile:server', 'compile:unit_tests', 'mochaTest']
|
||||
|
||||
client_coffee:
|
||||
files: ['public/**/*.coffee']
|
||||
tasks: ['compile']
|
||||
|
||||
less:
|
||||
files: ['public/less/*.less']
|
||||
tasks: ['compile']
|
||||
|
||||
jade:
|
||||
files: ['public/jade/*.jade']
|
||||
tasks: ['compile']
|
||||
|
||||
|
||||
less:
|
||||
production:
|
||||
files:
|
||||
"public/build/css/chat.css": "public/less/chat.less"
|
||||
|
||||
jade:
|
||||
compile:
|
||||
files:
|
||||
"public/build/html/templates.html": ["public/jade/templates.jade"]
|
||||
|
||||
requirejs:
|
||||
compile:
|
||||
options:
|
||||
mainConfigFile: 'public/app.build.js',
|
||||
|
||||
uglify:
|
||||
my_target:
|
||||
files:
|
||||
'public/build/chat.js': ['public/build/chat.js']
|
||||
|
||||
copy:
|
||||
main:
|
||||
expand: true
|
||||
cwd: 'public/js'
|
||||
src: '**'
|
||||
dest: 'public/build/'
|
||||
|
||||
clean: ["public/build", "app/js", "test/unit/js"]
|
||||
clean: ["app/js", "test/unit/js"]
|
||||
|
||||
nodemon:
|
||||
dev:
|
||||
|
@ -127,11 +79,6 @@ module.exports = (grunt) ->
|
|||
|
||||
grunt.loadNpmTasks 'grunt-contrib-coffee'
|
||||
grunt.loadNpmTasks 'grunt-contrib-watch'
|
||||
grunt.loadNpmTasks 'grunt-contrib-copy'
|
||||
grunt.loadNpmTasks 'grunt-contrib-less'
|
||||
grunt.loadNpmTasks 'grunt-contrib-jade'
|
||||
grunt.loadNpmTasks 'grunt-contrib-requirejs'
|
||||
grunt.loadNpmTasks 'grunt-contrib-uglify'
|
||||
grunt.loadNpmTasks 'grunt-nodemon'
|
||||
grunt.loadNpmTasks 'grunt-contrib-clean'
|
||||
grunt.loadNpmTasks 'grunt-concurrent'
|
||||
|
@ -142,10 +89,9 @@ module.exports = (grunt) ->
|
|||
grunt.loadNpmTasks 'grunt-forever'
|
||||
|
||||
|
||||
grunt.registerTask 'compile', ['clean', 'copy', 'coffee', 'less', 'jade', 'requirejs']
|
||||
grunt.registerTask 'compile', ['clean', 'coffee']
|
||||
grunt.registerTask 'install', ['compile']
|
||||
grunt.registerTask 'default', ['compile', 'bunyan', 'execute']
|
||||
grunt.registerTask 'compileAndCompress', ['compile', 'uglify']
|
||||
grunt.registerTask 'test:unit', ['compile', 'mochaTest:unit']
|
||||
grunt.registerTask 'test:acceptance', ['compile:acceptance_tests', 'mochaTest:acceptance']
|
||||
|
||||
|
|
|
@ -14,3 +14,19 @@ module.exports = MessageFormatter =
|
|||
|
||||
formatMessagesForClientSide: (messages) ->
|
||||
(@formatMessageForClientSide(message) for message in messages)
|
||||
|
||||
groupMessagesByThreads: (rooms, messages) ->
|
||||
room_id_to_thread_id = {}
|
||||
for room in rooms
|
||||
room_id_to_thread_id[room._id.toString()] = room.thread_id.toString()
|
||||
|
||||
threads = {}
|
||||
for message in messages
|
||||
thread_id = room_id_to_thread_id[message.room_id.toString()]
|
||||
threads[thread_id] ?= []
|
||||
threads[thread_id].push MessageFormatter.formatMessageForClientSide(message)
|
||||
|
||||
for thread_id, messages of threads
|
||||
messages.sort (a,b) -> a.timestamp - b.timestamp
|
||||
|
||||
return threads
|
|
@ -2,61 +2,70 @@ logger = require "logger-sharelatex"
|
|||
metrics = require "metrics-sharelatex"
|
||||
MessageManager = require "./MessageManager"
|
||||
MessageFormatter = require "./MessageFormatter"
|
||||
RoomManager = require "../Rooms/RoomManager"
|
||||
ThreadManager = require "../Threads/ThreadManager"
|
||||
{ObjectId} = require "../../mongojs"
|
||||
|
||||
module.exports = MessageHttpController =
|
||||
DEFAULT_MESSAGE_LIMIT: 50
|
||||
|
||||
getGlobalMessages: (req, res, next) ->
|
||||
MessageHttpController._getMessages(ThreadManager.GLOBAL_THREAD, req, res, next)
|
||||
|
||||
sendMessage: (req, res, next) ->
|
||||
sendGlobalMessage: (req, res, next) ->
|
||||
MessageHttpController._sendMessage(ThreadManager.GLOBAL_THREAD, req, res, next)
|
||||
|
||||
sendThreadMessage: (req, res, next) ->
|
||||
MessageHttpController._sendMessage(req.params.thread_id, req, res, next)
|
||||
|
||||
getAllThreads: (req, res, next) ->
|
||||
{project_id} = req.params
|
||||
logger.log {project_id}, "getting all threads"
|
||||
ThreadManager.findAllThreadRooms project_id, (error, rooms) ->
|
||||
return next(error) if error?
|
||||
room_ids = rooms.map (r) -> r._id
|
||||
MessageManager.findAllMessagesInRooms room_ids, (error, messages) ->
|
||||
return next(error) if error?
|
||||
MessageManager.populateMessagesWithUsers messages, (error, messages) ->
|
||||
return next(error) if error?
|
||||
threads = MessageFormatter.groupMessagesByThreads rooms, messages
|
||||
res.json threads
|
||||
|
||||
_sendMessage: (client_thread_id, req, res, next) ->
|
||||
{user_id, content} = req?.body
|
||||
{project_id} = req.params
|
||||
|
||||
logger.log user_id: user_id, content: content, "new message recived"
|
||||
RoomManager.findOrCreateRoom project_id: project_id, (error, room) ->
|
||||
if !ObjectId.isValid(user_id)
|
||||
return res.send(400, "Invalid user_id")
|
||||
logger.log {client_thread_id, project_id, user_id, content}, "new message received"
|
||||
ThreadManager.findOrCreateThread project_id, client_thread_id, (error, thread) ->
|
||||
return next(error) if error?
|
||||
newMessageOpts =
|
||||
content: content
|
||||
room_id: room._id
|
||||
user_id: user_id
|
||||
timestamp: Date.now()
|
||||
MessageManager.createMessage newMessageOpts, (error, message) ->
|
||||
if err?
|
||||
logger.err err:error, user_id:user_id, "something went wrong with create message"
|
||||
return next(err)
|
||||
MessageManager.createMessage thread._id, user_id, content, Date.now(), (error, message) ->
|
||||
return next(error) if error?
|
||||
MessageManager.populateMessagesWithUsers [message], (error, messages) ->
|
||||
if error?
|
||||
logger.err err:error, user_id:user_id, "something went wrong populateMessagesWithUsers"
|
||||
return next("something went wrong")
|
||||
return next(error) if error?
|
||||
message = MessageFormatter.formatMessageForClientSide(messages[0])
|
||||
message.room =
|
||||
id: project_id
|
||||
res.send(201, message)
|
||||
|
||||
getMessages: (req, res, next) ->
|
||||
_getMessages: (client_thread_id, req, res, next) ->
|
||||
{project_id} = req.params
|
||||
query = {}
|
||||
if req.query?.before?
|
||||
query.timestamp = $lt: parseInt(req.query.before, 10)
|
||||
before = parseInt(req.query.before, 10)
|
||||
else
|
||||
before = null
|
||||
if req.query?.limit?
|
||||
limit = parseInt(req.query.limit, 10)
|
||||
else
|
||||
limit = MessageHttpController.DEFAULT_MESSAGE_LIMIT
|
||||
options =
|
||||
order_by: "timestamp"
|
||||
sort_order: -1
|
||||
limit: limit
|
||||
logger.log options:options, "get message request recived"
|
||||
RoomManager.findOrCreateRoom project_id: project_id, (error, room) ->
|
||||
logger.log {limit, before, project_id, client_thread_id}, "get message request received"
|
||||
ThreadManager.findOrCreateThread project_id, client_thread_id, (error, thread) ->
|
||||
return next(error) if error?
|
||||
query.room_id = room._id
|
||||
MessageManager.getMessages query, options, (error, messages) ->
|
||||
if error?
|
||||
logger.err err:error, "something went getMessages"
|
||||
return next("something went wrong")
|
||||
thread_object_id = thread._id
|
||||
logger.log {limit, before, project_id, client_thread_id, thread_object_id}, "found or created thread"
|
||||
MessageManager.getMessages thread_object_id, limit, before, (error, messages) ->
|
||||
return next(error) if error?
|
||||
MessageManager.populateMessagesWithUsers messages, (error, messages) ->
|
||||
if error?
|
||||
logger.err err:error, "something went populateMessagesWithUsers"
|
||||
return next("something went wrong")
|
||||
return next(error) if error?
|
||||
messages = MessageFormatter.formatMessagesForClientSide messages
|
||||
logger.log project_id: project_id, "got messages"
|
||||
res.send 200, messages
|
||||
logger.log {project_id, messages}, "got messages"
|
||||
res.send 200, messages
|
||||
|
|
|
@ -5,21 +5,28 @@ WebApiManager = require "../WebApi/WebApiManager"
|
|||
async = require "async"
|
||||
|
||||
module.exports = MessageManager =
|
||||
createMessage: (message, callback = (error, message) ->) ->
|
||||
message = @_ensureIdsAreObjectIds(message)
|
||||
db.messages.save message, callback
|
||||
createMessage: (room_id, user_id, content, timestamp, callback = (error, message) ->) ->
|
||||
newMessageOpts =
|
||||
content: content
|
||||
room_id: room_id
|
||||
user_id: user_id
|
||||
timestamp: timestamp
|
||||
newMessageOpts = @_ensureIdsAreObjectIds(newMessageOpts)
|
||||
db.messages.save newMessageOpts, callback
|
||||
|
||||
getMessages: (query, options, callback = (error, messages) ->) ->
|
||||
getMessages: (room_id, limit, before, callback = (error, messages) ->) ->
|
||||
query =
|
||||
room_id: room_id
|
||||
if before?
|
||||
query.timestamp = { $lt: before }
|
||||
query = @_ensureIdsAreObjectIds(query)
|
||||
cursor = db.messages.find(query)
|
||||
if options.order_by?
|
||||
options.sort_order ||= 1
|
||||
sortQuery = {}
|
||||
sortQuery[options.order_by] = options.sort_order
|
||||
cursor = cursor.sort(sortQuery)
|
||||
if options.limit?
|
||||
cursor = cursor.limit(options.limit)
|
||||
cursor = db.messages.find(query).sort({ timestamp: -1 }).limit(limit)
|
||||
cursor.toArray callback
|
||||
|
||||
findAllMessagesInRooms: (room_ids, callback = (error, messages) ->) ->
|
||||
db.messages.find {
|
||||
room_id: { $in: room_ids }
|
||||
}, callback
|
||||
|
||||
populateMessagesWithUsers: (messages, callback = (error, messages) ->) ->
|
||||
jobs = new Array()
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
mongojs = require("../../mongojs")
|
||||
db = mongojs.db
|
||||
ObjectId = mongojs.ObjectId
|
||||
|
||||
module.exports = RoomManager =
|
||||
findOrCreateRoom: (query, callback = (error, room) ->) ->
|
||||
if query.project_id? and query.project_id not instanceof ObjectId
|
||||
query.project_id = ObjectId(query.project_id)
|
||||
|
||||
db.rooms.findOne query, (error, room) ->
|
||||
return callback(error) if error?
|
||||
if room?
|
||||
callback null, room
|
||||
else
|
||||
db.rooms.save query, (error, room) ->
|
||||
return callback(error) if error?
|
||||
callback null, room
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
mongojs = require("../../mongojs")
|
||||
db = mongojs.db
|
||||
ObjectId = mongojs.ObjectId
|
||||
|
||||
module.exports = ThreadManager =
|
||||
GLOBAL_THREAD: "GLOBAL"
|
||||
|
||||
findOrCreateThread: (project_id, thread_id, callback = (error, thread) ->) ->
|
||||
query =
|
||||
project_id: ObjectId(project_id.toString())
|
||||
|
||||
if thread_id? and thread_id != ThreadManager.GLOBAL_THREAD
|
||||
query.thread_id = ObjectId(thread_id.toString())
|
||||
|
||||
# Threads used to be called rooms, and still are in the DB
|
||||
db.rooms.findOne query, (error, thread) ->
|
||||
return callback(error) if error?
|
||||
if thread?
|
||||
callback null, thread
|
||||
else
|
||||
db.rooms.save query, (error, thread) ->
|
||||
return callback(error) if error?
|
||||
callback null, thread
|
||||
|
||||
findAllThreadRooms: (project_id, callback = (error, rooms) ->) ->
|
||||
db.rooms.find {
|
||||
project_id: ObjectId(project_id.toString())
|
||||
thread_id: { $exists: true }
|
||||
}, {
|
||||
thread_id: 1
|
||||
}, callback
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
Settings = require("settings-sharelatex")
|
||||
mongojs = require "mongojs"
|
||||
db = mongojs.connect(Settings.mongo.url, ["rooms", "messages"])
|
||||
db = mongojs(Settings.mongo.url, ["rooms", "messages"])
|
||||
module.exports =
|
||||
db: db
|
||||
ObjectId: mongojs.ObjectId
|
||||
|
|
|
@ -1,10 +1,38 @@
|
|||
MessageHttpController = require('./Features/Messages/MessageHttpController')
|
||||
{ObjectId} = require "./mongojs"
|
||||
|
||||
module.exports = Router =
|
||||
route: (app) ->
|
||||
app.get "/room/:project_id/messages", MessageHttpController.getMessages
|
||||
app.post "/room/:project_id/messages", MessageHttpController.sendMessage
|
||||
|
||||
app.param 'project_id', (req, res, next, project_id) ->
|
||||
if ObjectId.isValid(project_id)
|
||||
next()
|
||||
else
|
||||
res.send 400, "Invalid project_id"
|
||||
|
||||
app.param 'thread_id', (req, res, next, thread_id) ->
|
||||
if ObjectId.isValid(thread_id)
|
||||
next()
|
||||
else
|
||||
res.send 400, "Invalid thread_id"
|
||||
|
||||
# These are for backwards compatibility
|
||||
app.get "/room/:project_id/messages", MessageHttpController.getGlobalMessages
|
||||
app.post "/room/:project_id/messages", MessageHttpController.sendGlobalMessage
|
||||
|
||||
app.get "/project/:project_id/messages", MessageHttpController.getGlobalMessages
|
||||
app.post "/project/:project_id/messages", MessageHttpController.sendGlobalMessage
|
||||
|
||||
app.post "/project/:project_id/thread/:thread_id/messages", MessageHttpController.sendThreadMessage
|
||||
app.get "/project/:project_id/threads", MessageHttpController.getAllThreads
|
||||
# app.get "/project/:project_id/thread", MessageHttpController.getAllThreadMessages
|
||||
#
|
||||
# app.post "/project/:project_id/thread/:thread_id/messages/:message_id/edit", MessageHttpController.editMessage
|
||||
# app.del "/project/:project_id/thread/:thread_id/messages/:message_id", MessageHttpController.deleteMessage
|
||||
#
|
||||
# app.post "/project/:project_id/thread/:thread_id/resolve", MessageHttpController.resolveThread
|
||||
# app.post "/project/:project_id/thread/:thread_id/reopen", MessageHttpController.reopenThread
|
||||
# app.del "/project/:project_id/thread/:thread_id", MessageHttpController.deleteThread
|
||||
#
|
||||
app.get "/status", (req, res, next) ->
|
||||
res.send("chat is alive")
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
"coffee-script": "~1.7.1",
|
||||
"express": "3.3.1",
|
||||
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master",
|
||||
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.0.0",
|
||||
"mongojs": "0.18.2",
|
||||
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.6.0",
|
||||
"mongojs": "^2.4.0",
|
||||
"redis": "~0.10.1",
|
||||
"request": "^2.79.0",
|
||||
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0"
|
||||
|
@ -25,11 +25,6 @@
|
|||
"grunt-concurrent": "~0.4.2",
|
||||
"grunt-contrib-clean": "~0.5.0",
|
||||
"grunt-contrib-coffee": "~0.7.0",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-contrib-jade": "~0.8.0",
|
||||
"grunt-contrib-less": "~0.8.2",
|
||||
"grunt-contrib-requirejs": "~0.4.1",
|
||||
"grunt-contrib-uglify": "~0.2.7",
|
||||
"grunt-contrib-watch": "~0.5.3",
|
||||
"grunt-execute": "^0.2.2",
|
||||
"grunt-forever": "^0.4.7",
|
||||
|
@ -37,7 +32,6 @@
|
|||
"grunt-nodemon": "~0.1.2",
|
||||
"grunt-notify": "~0.2.16",
|
||||
"grunt-plato": "~0.2.1",
|
||||
"grunt-requirejs": "~0.4.0",
|
||||
"sandboxed-module": "",
|
||||
"sinon": "",
|
||||
"timekeeper": ""
|
||||
|
|
|
@ -25,16 +25,16 @@ describe "Getting messages", ->
|
|||
email: "john@example.com"
|
||||
}
|
||||
|
||||
describe "normally", ->
|
||||
describe "globally", ->
|
||||
before (done) ->
|
||||
@project_id = ObjectId().toString()
|
||||
async.series [
|
||||
(cb) => ChatClient.sendMessage @project_id, @user_id1, @content1, cb
|
||||
(cb) => ChatClient.sendMessage @project_id, @user_id2, @content2, cb
|
||||
(cb) => ChatClient.sendGlobalMessage @project_id, @user_id1, @content1, cb
|
||||
(cb) => ChatClient.sendGlobalMessage @project_id, @user_id2, @content2, cb
|
||||
], done
|
||||
|
||||
it "should contain the messages and populated users when getting the messages", (done) ->
|
||||
ChatClient.getMessages @project_id, (error, response, messages) =>
|
||||
ChatClient.getGlobalMessages @project_id, (error, response, messages) =>
|
||||
expect(messages.length).to.equal 2
|
||||
messages.reverse()
|
||||
expect(messages[0].content).to.equal @content1
|
||||
|
@ -55,17 +55,72 @@ describe "Getting messages", ->
|
|||
}
|
||||
done()
|
||||
|
||||
describe "from all the threads", ->
|
||||
before (done) ->
|
||||
@project_id = ObjectId().toString()
|
||||
@thread_id1 = ObjectId().toString()
|
||||
@thread_id2 = ObjectId().toString()
|
||||
async.series [
|
||||
(cb) => ChatClient.sendMessage @project_id, @thread_id1, @user_id1, "one", cb
|
||||
(cb) => ChatClient.sendMessage @project_id, @thread_id2, @user_id2, "two", cb
|
||||
(cb) => ChatClient.sendMessage @project_id, @thread_id1, @user_id1, "three", cb
|
||||
(cb) => ChatClient.sendMessage @project_id, @thread_id2, @user_id2, "four", cb
|
||||
], done
|
||||
|
||||
it "should contain a dictionary of threads with messages with populated users", (done) ->
|
||||
ChatClient.getThreads @project_id, (error, response, threads) =>
|
||||
expect(Object.keys(threads).length).to.equal 2
|
||||
thread1 = threads[@thread_id1]
|
||||
expect(thread1.length).to.equal 2
|
||||
thread2 = threads[@thread_id2]
|
||||
expect(thread2.length).to.equal 2
|
||||
|
||||
expect(thread1[0].content).to.equal "one"
|
||||
expect(thread1[0].user).to.deep.equal {
|
||||
id: @user_id1
|
||||
first_name: "Jane"
|
||||
last_name: "Smith"
|
||||
email: "jane@example.com"
|
||||
gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("jane@example.com").digest("hex")}"
|
||||
}
|
||||
expect(thread1[1].content).to.equal "three"
|
||||
expect(thread1[1].user).to.deep.equal {
|
||||
id: @user_id1
|
||||
first_name: "Jane"
|
||||
last_name: "Smith"
|
||||
email: "jane@example.com"
|
||||
gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("jane@example.com").digest("hex")}"
|
||||
}
|
||||
|
||||
expect(thread2[0].content).to.equal "two"
|
||||
expect(thread2[0].user).to.deep.equal {
|
||||
id: @user_id2
|
||||
first_name: "John"
|
||||
last_name: "Doe"
|
||||
email: "john@example.com"
|
||||
gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("john@example.com").digest("hex")}"
|
||||
}
|
||||
expect(thread2[1].content).to.equal "four"
|
||||
expect(thread2[1].user).to.deep.equal {
|
||||
id: @user_id2
|
||||
first_name: "John"
|
||||
last_name: "Doe"
|
||||
email: "john@example.com"
|
||||
gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("john@example.com").digest("hex")}"
|
||||
}
|
||||
done()
|
||||
|
||||
describe "when a user doesn't exit", ->
|
||||
before (done) ->
|
||||
@project_id = ObjectId().toString()
|
||||
@user_id3 = ObjectId().toString()
|
||||
async.series [
|
||||
(cb) => ChatClient.sendMessage @project_id, @user_id3, @content1, cb
|
||||
(cb) => ChatClient.sendMessage @project_id, @user_id2, @content2, cb
|
||||
(cb) => ChatClient.sendGlobalMessage @project_id, @user_id3, @content1, cb
|
||||
(cb) => ChatClient.sendGlobalMessage @project_id, @user_id2, @content2, cb
|
||||
], done
|
||||
|
||||
it "should just return null for the user", (done) ->
|
||||
ChatClient.getMessages @project_id, (error, response, messages) =>
|
||||
ChatClient.getGlobalMessages @project_id, (error, response, messages) =>
|
||||
expect(messages.length).to.equal 2
|
||||
messages.reverse()
|
||||
expect(messages[0].content).to.equal @content1
|
||||
|
|
|
@ -5,19 +5,72 @@ MockWebApi = require "./helpers/MockWebApi"
|
|||
ChatClient = require "./helpers/ChatClient"
|
||||
|
||||
describe "Sending a message", ->
|
||||
before (done) ->
|
||||
before ->
|
||||
@project_id = ObjectId().toString()
|
||||
@user_id = ObjectId().toString()
|
||||
@content = "foo bar"
|
||||
ChatClient.sendMessage @project_id, @user_id, @content, (error, response, body) ->
|
||||
expect(error).to.be.null
|
||||
expect(response.statusCode).to.equal 201
|
||||
done()
|
||||
@thread_id = ObjectId().toString()
|
||||
MockWebApi.addUser @user_id, @user = {
|
||||
id: @user_id
|
||||
first_name: "Jane"
|
||||
last_name: "Smith"
|
||||
email: "jane@example.com"
|
||||
}
|
||||
|
||||
describe "globally", ->
|
||||
before (done) ->
|
||||
@content = "global message"
|
||||
ChatClient.sendGlobalMessage @project_id, @user_id, @content, (error, response, body) =>
|
||||
expect(error).to.be.null
|
||||
expect(response.statusCode).to.equal 201
|
||||
expect(body.content).to.equal @content
|
||||
expect(body.user.id).to.equal @user_id
|
||||
expect(body.room.id).to.equal @project_id
|
||||
done()
|
||||
|
||||
it "should then list the message in the project messages", (done) ->
|
||||
ChatClient.getGlobalMessages @project_id, (error, response, messages) =>
|
||||
expect(error).to.be.null
|
||||
expect(response.statusCode).to.equal 200
|
||||
expect(messages.length).to.equal 1
|
||||
expect(messages[0].content).to.equal @content
|
||||
done()
|
||||
|
||||
describe "to a thread", ->
|
||||
before (done) ->
|
||||
@content = "thread message"
|
||||
ChatClient.sendMessage @project_id, @thread_id, @user_id, @content, (error, response, body) =>
|
||||
expect(error).to.be.null
|
||||
expect(response.statusCode).to.equal 201
|
||||
expect(body.content).to.equal @content
|
||||
expect(body.user.id).to.equal @user_id
|
||||
expect(body.room.id).to.equal @project_id
|
||||
done()
|
||||
|
||||
it "should then list the message in the threads", (done) ->
|
||||
ChatClient.getThreads @project_id, (error, response, threads) =>
|
||||
expect(error).to.be.null
|
||||
expect(response.statusCode).to.equal 200
|
||||
expect(threads[@thread_id].length).to.equal 1
|
||||
expect(threads[@thread_id][0].content).to.equal @content
|
||||
done()
|
||||
|
||||
it "should then list the message in project messages", (done) ->
|
||||
ChatClient.getMessages @project_id, (error, response, messages) =>
|
||||
expect(error).to.be.null
|
||||
expect(response.statusCode).to.equal 200
|
||||
expect(messages.length).to.equal 1
|
||||
expect(messages[0].content).to.equal @content
|
||||
done()
|
||||
describe "with a malformed user_id", ->
|
||||
it "should return a graceful error", (done) ->
|
||||
ChatClient.sendMessage @project_id, @thread_id, "malformed-user", "content", (error, response, body) =>
|
||||
expect(response.statusCode).to.equal 400
|
||||
expect(body).to.equal "Invalid user_id"
|
||||
done()
|
||||
|
||||
describe "with a malformed project_id", ->
|
||||
it "should return a graceful error", (done) ->
|
||||
ChatClient.sendMessage "malformed-project", @thread_id, @user_id, "content", (error, response, body) =>
|
||||
expect(response.statusCode).to.equal 400
|
||||
expect(body).to.equal "Invalid project_id"
|
||||
done()
|
||||
|
||||
describe "with a malformed thread_id", ->
|
||||
it "should return a graceful error", (done) ->
|
||||
ChatClient.sendMessage @project_id, "malformed-thread-id", @user_id, "content", (error, response, body) =>
|
||||
expect(response.statusCode).to.equal 400
|
||||
expect(body).to.equal "Invalid thread_id"
|
||||
done()
|
|
@ -1,16 +1,30 @@
|
|||
request = require("request").defaults({baseUrl: "http://localhost:3010"})
|
||||
|
||||
module.exports =
|
||||
sendMessage: (project_id, user_id, content, callback) ->
|
||||
sendGlobalMessage: (project_id, user_id, content, callback) ->
|
||||
request.post {
|
||||
url: "/room/#{project_id}/messages"
|
||||
url: "/project/#{project_id}/messages"
|
||||
json:
|
||||
user_id: user_id
|
||||
content: content
|
||||
}, callback
|
||||
|
||||
getMessages: (project_id, callback) ->
|
||||
getGlobalMessages: (project_id, callback) ->
|
||||
request.get {
|
||||
url: "/room/#{project_id}/messages",
|
||||
url: "/project/#{project_id}/messages",
|
||||
json: true
|
||||
}, callback
|
||||
|
||||
sendMessage: (project_id, thread_id, user_id, content, callback) ->
|
||||
request.post {
|
||||
url: "/project/#{project_id}/thread/#{thread_id}/messages"
|
||||
json:
|
||||
user_id: user_id
|
||||
content: content
|
||||
}, callback
|
||||
|
||||
getThreads: (project_id, callback) ->
|
||||
request.get {
|
||||
url: "/project/#{project_id}/threads",
|
||||
json: true
|
||||
}, callback
|
|
@ -1,13 +0,0 @@
|
|||
sinon = require('sinon')
|
||||
chai = require('chai')
|
||||
should = chai.should()
|
||||
expect = chai.expect
|
||||
modulePath = "../../../../app/js/Features/Messages/MessageFormatter.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
events = require "events"
|
||||
ObjectId = require("mongojs").ObjectId
|
||||
|
||||
describe "MessageFormatter", ->
|
||||
beforeEach ->
|
||||
@MessageFormatter = SandboxedModule.require modulePath, requires: {}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
should = require('chai').should()
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
path = require('path')
|
||||
sinon = require('sinon')
|
||||
modulePath = path.join __dirname, "../../../../app/js/Features/Messages/MessageHttpController"
|
||||
expect = require("chai").expect
|
||||
tk = require("timekeeper")
|
||||
|
||||
describe "MessagesHttpController", ->
|
||||
|
||||
beforeEach ->
|
||||
|
||||
@settings = {}
|
||||
@date = Date.now()
|
||||
tk.freeze(@date)
|
||||
@MessagesHttpController = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex":@settings
|
||||
"logger-sharelatex": log:->
|
||||
"./MessageManager": @MessageManager = {}
|
||||
"./MessageFormatter": @MessageFormatter = {}
|
||||
"../Rooms/RoomManager": @RoomManager = {}
|
||||
|
||||
@req =
|
||||
body:{}
|
||||
@res = {}
|
||||
@project_id = "12321321"
|
||||
@room_id = "Asdfadf adfafd"
|
||||
@user_id = "09832910838239081203981"
|
||||
@content = "my message here"
|
||||
|
||||
|
||||
afterEach ->
|
||||
tk.reset()
|
||||
|
||||
describe "sendMessage", ->
|
||||
|
||||
beforeEach ->
|
||||
@initialMessage = {content:@content}
|
||||
@MessageManager.createMessage = sinon.stub().callsArgWith(1, null, @initialMessage)
|
||||
@req.params =
|
||||
project_id:@project_id
|
||||
@req.body =
|
||||
user_id:@user_id
|
||||
content:@content
|
||||
@singlePopulatedMessage = {data:"here"}
|
||||
@MessageManager.populateMessagesWithUsers = sinon.stub().callsArgWith(1, null, [@singlePopulatedMessage])
|
||||
@RoomManager.findOrCreateRoom = sinon.stub().callsArgWith(1, null, @room = { _id : @room_id })
|
||||
@formattedMessage = {formatted:true}
|
||||
@MessageFormatter.formatMessageForClientSide = sinon.stub().returns(@formattedMessage)
|
||||
|
||||
it "should look up the room for the project", ->
|
||||
@res.send = =>
|
||||
@RoomManager.findOrCreateRoom
|
||||
.calledWith({
|
||||
project_id: @project_id
|
||||
})
|
||||
.should.equal true
|
||||
done()
|
||||
|
||||
it "should create the message with the message manager", (done)->
|
||||
@res.send = =>
|
||||
@MessageManager.createMessage
|
||||
.calledWith({
|
||||
content: @content
|
||||
user_id: @user_id
|
||||
room_id: @room_id
|
||||
timestamp: @date
|
||||
})
|
||||
.should.equal true
|
||||
done()
|
||||
@MessagesHttpController.sendMessage @req, @res
|
||||
|
||||
|
||||
it "should return the formetted message", (done)->
|
||||
|
||||
@res.send = (code, data)=>
|
||||
assert.deepEqual @MessageManager.populateMessagesWithUsers.args[0][0], [@initialMessage]
|
||||
code.should.equal 201
|
||||
data.should.equal @formattedMessage
|
||||
done()
|
||||
|
||||
@MessagesHttpController.sendMessage @req, @res
|
||||
|
||||
|
||||
describe "getMessages", ->
|
||||
|
||||
beforeEach ->
|
||||
@project_id = "room-id-123"
|
||||
@timestamp = Date.now()
|
||||
@limit = 42
|
||||
|
||||
@messages = "messages without users stub"
|
||||
@messagesWithUsers = "messages with users stub"
|
||||
@formattedMessages = "formatted messages stub"
|
||||
@RoomManager.findOrCreateRoom = sinon.stub().callsArgWith(1, null, @room = { _id : @room_id })
|
||||
@MessageManager.getMessages = sinon.stub().callsArgWith(2, null, @messages)
|
||||
@MessageManager.populateMessagesWithUsers = sinon.stub().callsArgWith(1, null, @messagesWithUsers)
|
||||
@MessageFormatter.formatMessagesForClientSide = sinon.stub().returns @formattedMessages
|
||||
|
||||
|
||||
describe "with a timestamp and limit", ->
|
||||
beforeEach ->
|
||||
@req.params =
|
||||
project_id:@project_id
|
||||
@req.query =
|
||||
before: @timestamp,
|
||||
limit: "#{@limit}"
|
||||
|
||||
|
||||
it "should look up the room for the project", ->
|
||||
@res.send = =>
|
||||
@RoomManager.findOrCreateRoom
|
||||
.calledWith({
|
||||
project_id: @project_id
|
||||
})
|
||||
.should.equal true
|
||||
done()
|
||||
|
||||
it "should get the requested messages", ->
|
||||
@res.send = =>
|
||||
@MessageManager.getMessages
|
||||
.calledWith({
|
||||
timestamp: $lt: @timestamp
|
||||
room_id: @room_id
|
||||
}, {
|
||||
limit: @limit
|
||||
order_by: "timestamp"
|
||||
sort_order: -1
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
@MessagesHttpController.getMessages(@req, @res)
|
||||
|
||||
it "should populate the messages with the users", (done)->
|
||||
@res.send = =>
|
||||
@MessageManager.populateMessagesWithUsers.calledWith(@messages).should.equal true
|
||||
done()
|
||||
|
||||
@MessagesHttpController.getMessages(@req, @res)
|
||||
|
||||
it "should return the formatted messages", (done)->
|
||||
@res.send = ()=>
|
||||
@MessageFormatter.formatMessagesForClientSide.calledWith(@messagesWithUsers).should.equal true
|
||||
done()
|
||||
@MessagesHttpController.getMessages(@req, @res)
|
||||
|
||||
it "should send the formated messages back with a 200", (done)->
|
||||
@res.send = (code, data)=>
|
||||
code.should.equal 200
|
||||
data.should.equal @formattedMessages
|
||||
done()
|
||||
@MessagesHttpController.getMessages(@req, @res)
|
||||
|
||||
describe "without a timestamp or limit", ->
|
||||
beforeEach ->
|
||||
@req.params =
|
||||
project_id:@project_id
|
||||
|
||||
|
||||
it "should get a default number of messages from the beginning", ->
|
||||
@res.send = =>
|
||||
@MessageManager.getMessages
|
||||
.calledWith({
|
||||
room_id: @room_id
|
||||
}, {
|
||||
limit: @MessagesHttpController.DEFAULT_MESSAGE_LIMIT
|
||||
order_by: "timestamp"
|
||||
sort_order: -1
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
@MessagesHttpController.getMessages(@req, @res)
|
|
@ -1,61 +0,0 @@
|
|||
sinon = require('sinon')
|
||||
chai = require('chai')
|
||||
should = chai.should()
|
||||
expect = chai.expect
|
||||
modulePath = "../../../../app/js/Features/Messages/MessageManager.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
events = require "events"
|
||||
ObjectId = require("mongojs").ObjectId
|
||||
|
||||
describe "MessageManager", ->
|
||||
beforeEach ->
|
||||
@MessageManager = SandboxedModule.require modulePath, requires:
|
||||
"../WebApi/WebApiManager": @WebApiManager = {}
|
||||
"../../mongojs": {}
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "populateMessagesWithUsers", ->
|
||||
beforeEach ->
|
||||
@user0 =
|
||||
id: ObjectId().toString()
|
||||
first_name: "Adam"
|
||||
@user1 =
|
||||
id: ObjectId().toString()
|
||||
first_name: "Eve"
|
||||
@users = {}
|
||||
@users[@user0.id] = @user0
|
||||
@users[@user1.id] = @user1
|
||||
@messages = [{
|
||||
content: "First message content"
|
||||
user_id: ObjectId(@user0.id)
|
||||
}, {
|
||||
content: "Second message content"
|
||||
user_id: ObjectId(@user0.id)
|
||||
}, {
|
||||
content: "Third message content"
|
||||
user_id: ObjectId(@user1.id)
|
||||
}]
|
||||
@WebApiManager.getUserDetails = (user_id, callback = (error, user) ->) =>
|
||||
callback null, @users[user_id]
|
||||
sinon.spy @WebApiManager, "getUserDetails"
|
||||
@MessageManager.populateMessagesWithUsers @messages, @callback
|
||||
|
||||
it "should insert user objects in the place of user_ids", ->
|
||||
messages = @callback.args[0][1]
|
||||
expect(messages).to.deep.equal [{
|
||||
content: "First message content"
|
||||
user: @user0
|
||||
}, {
|
||||
content: "Second message content"
|
||||
user: @user0
|
||||
}, {
|
||||
content: "Third message content"
|
||||
user: @user1
|
||||
}]
|
||||
|
||||
it "should call getUserDetails once and only once for each user", ->
|
||||
@WebApiManager.getUserDetails.calledWith(@user0.id).should.equal true
|
||||
@WebApiManager.getUserDetails.calledWith(@user1.id).should.equal true
|
||||
@WebApiManager.getUserDetails.calledTwice.should.equal true
|
||||
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
sinon = require('sinon')
|
||||
chai = require('chai')
|
||||
should = chai.should()
|
||||
expect = chai.expect
|
||||
modulePath = "../../../../app/js/Features/Rooms/RoomManager.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
events = require "events"
|
||||
mongojs = require "mongojs"
|
||||
ObjectId = mongojs.ObjectId
|
||||
|
||||
describe "RoomManager", ->
|
||||
beforeEach ->
|
||||
@RoomManager = SandboxedModule.require modulePath, requires:
|
||||
"../../mongojs":
|
||||
db: @db = { rooms: {} }
|
||||
ObjectId: ObjectId
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "findOrCreateRoom", ->
|
||||
describe "when the room exists", ->
|
||||
beforeEach ->
|
||||
@project_id = ObjectId().toString()
|
||||
@room =
|
||||
_id: ObjectId()
|
||||
project_id: ObjectId(@project_id)
|
||||
@db.rooms.findOne = sinon.stub().callsArgWith(1, null, @room)
|
||||
@RoomManager.findOrCreateRoom(project_id: @project_id, @callback)
|
||||
|
||||
it "should look up the room based on the query", ->
|
||||
@db.rooms.findOne
|
||||
.calledWith(project_id: ObjectId(@project_id))
|
||||
.should.equal true
|
||||
|
||||
it "should return the room in the callback", ->
|
||||
@callback
|
||||
.calledWith(null, @room)
|
||||
.should.equal true
|
||||
|
||||
describe "when the room does not exist", ->
|
||||
beforeEach ->
|
||||
@project_id = ObjectId().toString()
|
||||
@room =
|
||||
_id: ObjectId()
|
||||
project_id: ObjectId(@project_id)
|
||||
@db.rooms.findOne = sinon.stub().callsArgWith(1, null, null)
|
||||
@db.rooms.save = sinon.stub().callsArgWith(1, null, @room)
|
||||
@RoomManager.findOrCreateRoom(project_id: @project_id, @callback)
|
||||
|
||||
it "should create the room", ->
|
||||
@db.rooms.save
|
||||
.calledWith(project_id: ObjectId(@project_id))
|
||||
.should.equal true
|
||||
|
||||
it "should return the room in the callback", ->
|
||||
@callback
|
||||
.calledWith(null, @room)
|
||||
.should.equal true
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
sinon = require('sinon')
|
||||
chai = require('chai')
|
||||
should = chai.should()
|
||||
expect = chai.expect
|
||||
modulePath = "../../../../app/js/Features/Users/UserFormatter.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
events = require "events"
|
||||
|
||||
describe "UserFormatter", ->
|
||||
beforeEach ->
|
||||
@UserFormatter = SandboxedModule.require modulePath, requires: {}
|
||||
|
Loading…
Reference in a new issue