Przeglądaj źródła

Add in support for threads for comments

James Allen 9 lat temu
rodzic
commit
cb88e1e41b

+ 2 - 56
services/chat/Gruntfile.coffee

@@ -12,14 +12,6 @@ module.exports = (grunt) ->
 				src: "app.js"
 				src: "app.js"
 
 
 		coffee:
 		coffee:
-			client: 
-				expand: true,
-				flatten: false,
-				cwd: 'public/coffee',
-				src: ['**/*.coffee'],
-				dest: 'public/build/',
-				ext: '.js'
-
 			server: 
 			server: 
 				expand: true,
 				expand: true,
 				flatten: false,
 				flatten: false,
@@ -56,47 +48,7 @@ module.exports = (grunt) ->
 				files: ['app/**/*.coffee', 'test/unit/**/*.coffee']
 				files: ['app/**/*.coffee', 'test/unit/**/*.coffee']
 				tasks: ['compile:server', 'compile:unit_tests', 'mochaTest']
 				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:
 		nodemon:
 			dev:
 			dev:
@@ -127,11 +79,6 @@ module.exports = (grunt) ->
 
 
 	grunt.loadNpmTasks 'grunt-contrib-coffee'
 	grunt.loadNpmTasks 'grunt-contrib-coffee'
 	grunt.loadNpmTasks 'grunt-contrib-watch'
 	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-nodemon'
 	grunt.loadNpmTasks 'grunt-contrib-clean'
 	grunt.loadNpmTasks 'grunt-contrib-clean'
 	grunt.loadNpmTasks 'grunt-concurrent'
 	grunt.loadNpmTasks 'grunt-concurrent'
@@ -142,10 +89,9 @@ module.exports = (grunt) ->
 	grunt.loadNpmTasks 'grunt-forever'
 	grunt.loadNpmTasks 'grunt-forever'
 	
 	
 
 
-	grunt.registerTask 'compile', ['clean',  'copy', 'coffee', 'less', 'jade', 'requirejs']
+	grunt.registerTask 'compile', ['clean', 'coffee']
 	grunt.registerTask 'install', ['compile']
 	grunt.registerTask 'install', ['compile']
 	grunt.registerTask 'default', ['compile', 'bunyan', 'execute']
 	grunt.registerTask 'default', ['compile', 'bunyan', 'execute']
-	grunt.registerTask 'compileAndCompress', ['compile', 'uglify']
 	grunt.registerTask 'test:unit', ['compile', 'mochaTest:unit']
 	grunt.registerTask 'test:unit', ['compile', 'mochaTest:unit']
 	grunt.registerTask 'test:acceptance', ['compile:acceptance_tests', 'mochaTest:acceptance']
 	grunt.registerTask 'test:acceptance', ['compile:acceptance_tests', 'mochaTest:acceptance']
 
 

+ 16 - 0
services/chat/app/coffee/Features/Messages/MessageFormatter.coffee

@@ -14,3 +14,19 @@ module.exports = MessageFormatter =
 
 
 	formatMessagesForClientSide: (messages) ->
 	formatMessagesForClientSide: (messages) ->
 		(@formatMessageForClientSide(message) for message in 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

+ 45 - 36
services/chat/app/coffee/Features/Messages/MessageHttpController.coffee

@@ -2,61 +2,70 @@ logger = require "logger-sharelatex"
 metrics = require "metrics-sharelatex"
 metrics = require "metrics-sharelatex"
 MessageManager = require "./MessageManager"
 MessageManager = require "./MessageManager"
 MessageFormatter = require "./MessageFormatter"
 MessageFormatter = require "./MessageFormatter"
-RoomManager = require "../Rooms/RoomManager"
+ThreadManager = require "../Threads/ThreadManager"
+{ObjectId} = require "../../mongojs"
 
 
 module.exports = MessageHttpController =
 module.exports = MessageHttpController =
 	DEFAULT_MESSAGE_LIMIT: 50
 	DEFAULT_MESSAGE_LIMIT: 50
+	
+	getGlobalMessages: (req, res, next) ->
+		MessageHttpController._getMessages(ThreadManager.GLOBAL_THREAD, req, res, next)
 
 
-	sendMessage: (req, res, next) ->
-		{user_id, content} = req?.body
+	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
 		{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
 
 
-		logger.log user_id: user_id, content: content, "new message recived"
-		RoomManager.findOrCreateRoom project_id: project_id, (error, room) ->
+	_sendMessage: (client_thread_id, req, res, next) ->
+		{user_id, content} = req?.body
+		{project_id} = req.params
+		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?
 			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) ->
 				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 = MessageFormatter.formatMessageForClientSide(messages[0])
 					message.room =
 					message.room =
 						id: project_id
 						id: project_id
 					res.send(201, message)
 					res.send(201, message)
 
 
-	getMessages: (req, res, next) ->
+	_getMessages: (client_thread_id, req, res, next) ->
 		{project_id} = req.params
 		{project_id} = req.params
-		query = {}
 		if req.query?.before?
 		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?
 		if req.query?.limit?
 			limit = parseInt(req.query.limit, 10)
 			limit = parseInt(req.query.limit, 10)
 		else
 		else
 			limit = MessageHttpController.DEFAULT_MESSAGE_LIMIT
 			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?
 			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) ->
 				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
 					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

+ 19 - 12
services/chat/app/coffee/Features/Messages/MessageManager.coffee

@@ -5,21 +5,28 @@ WebApiManager = require "../WebApi/WebApiManager"
 async = require "async"
 async = require "async"
 
 
 module.exports = MessageManager =
 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)
 		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
 		cursor.toArray callback
+	
+	findAllMessagesInRooms: (room_ids, callback = (error, messages) ->) ->
+		db.messages.find {
+			room_id: { $in: room_ids }
+		}, callback
 
 
 	populateMessagesWithUsers: (messages, callback = (error, messages) ->) ->
 	populateMessagesWithUsers: (messages, callback = (error, messages) ->) ->
 		jobs = new Array()
 		jobs = new Array()

+ 0 - 18
services/chat/app/coffee/Features/Rooms/RoomManager.coffee

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

+ 34 - 0
services/chat/app/coffee/Features/Threads/ThreadManager.coffee

@@ -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 - 1
services/chat/app/coffee/mongojs.coffee

@@ -1,6 +1,6 @@
 Settings = require("settings-sharelatex")
 Settings = require("settings-sharelatex")
 mongojs = require "mongojs"
 mongojs = require "mongojs"
-db = mongojs.connect(Settings.mongo.url, ["rooms", "messages"])
+db = mongojs(Settings.mongo.url, ["rooms", "messages"])
 module.exports =
 module.exports =
 	db: db
 	db: db
 	ObjectId: mongojs.ObjectId
 	ObjectId: mongojs.ObjectId

+ 31 - 3
services/chat/app/coffee/router.coffee

@@ -1,10 +1,38 @@
 MessageHttpController = require('./Features/Messages/MessageHttpController')
 MessageHttpController = require('./Features/Messages/MessageHttpController')
+{ObjectId} = require "./mongojs"
 
 
 module.exports = Router =
 module.exports = Router =
 	route: (app) ->
 	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) ->
 		app.get "/status", (req, res, next) ->
 			res.send("chat is alive")
 			res.send("chat is alive")
 
 

+ 2 - 8
services/chat/package.json

@@ -11,8 +11,8 @@
     "coffee-script": "~1.7.1",
     "coffee-script": "~1.7.1",
     "express": "3.3.1",
     "express": "3.3.1",
     "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master",
     "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",
     "redis": "~0.10.1",
     "request": "^2.79.0",
     "request": "^2.79.0",
     "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0"
     "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0"
@@ -25,11 +25,6 @@
     "grunt-concurrent": "~0.4.2",
     "grunt-concurrent": "~0.4.2",
     "grunt-contrib-clean": "~0.5.0",
     "grunt-contrib-clean": "~0.5.0",
     "grunt-contrib-coffee": "~0.7.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-contrib-watch": "~0.5.3",
     "grunt-execute": "^0.2.2",
     "grunt-execute": "^0.2.2",
     "grunt-forever": "^0.4.7",
     "grunt-forever": "^0.4.7",
@@ -37,7 +32,6 @@
     "grunt-nodemon": "~0.1.2",
     "grunt-nodemon": "~0.1.2",
     "grunt-notify": "~0.2.16",
     "grunt-notify": "~0.2.16",
     "grunt-plato": "~0.2.1",
     "grunt-plato": "~0.2.1",
-    "grunt-requirejs": "~0.4.0",
     "sandboxed-module": "",
     "sandboxed-module": "",
     "sinon": "",
     "sinon": "",
     "timekeeper": ""
     "timekeeper": ""

+ 62 - 7
services/chat/test/acceptance/coffee/GettingMessagesTests.coffee

@@ -25,16 +25,16 @@ describe "Getting messages", ->
 			email: "john@example.com"
 			email: "john@example.com"
 		}
 		}
 
 
-	describe "normally", ->
+	describe "globally", ->
 		before (done) ->
 		before (done) ->
 			@project_id = ObjectId().toString()
 			@project_id = ObjectId().toString()
 			async.series [
 			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
 			], done
 		
 		
 		it "should contain the messages and populated users when getting the messages", (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
 				expect(messages.length).to.equal 2
 				messages.reverse()
 				messages.reverse()
 				expect(messages[0].content).to.equal @content1
 				expect(messages[0].content).to.equal @content1
@@ -55,17 +55,72 @@ describe "Getting messages", ->
 				}
 				}
 				done()
 				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", ->
 	describe "when a user doesn't exit", ->
 		before (done) ->
 		before (done) ->
 			@project_id = ObjectId().toString()
 			@project_id = ObjectId().toString()
 			@user_id3 = ObjectId().toString()
 			@user_id3 = ObjectId().toString()
 			async.series [
 			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
 			], done
 		
 		
 		it "should just return null for the user", (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
 				expect(messages.length).to.equal 2
 				messages.reverse()
 				messages.reverse()
 				expect(messages[0].content).to.equal @content1
 				expect(messages[0].content).to.equal @content1

+ 66 - 13
services/chat/test/acceptance/coffee/SendingAMessageTests.coffee

@@ -5,19 +5,72 @@ MockWebApi = require "./helpers/MockWebApi"
 ChatClient = require "./helpers/ChatClient"
 ChatClient = require "./helpers/ChatClient"
 
 
 describe "Sending a message", ->
 describe "Sending a message", ->
-	before (done) ->
+	before ->
 		@project_id = ObjectId().toString()
 		@project_id = ObjectId().toString()
 		@user_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()
+	
+	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()
 	
 	
-	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 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()

+ 18 - 4
services/chat/test/acceptance/coffee/helpers/ChatClient.coffee

@@ -1,16 +1,30 @@
 request = require("request").defaults({baseUrl: "http://localhost:3010"})
 request = require("request").defaults({baseUrl: "http://localhost:3010"})
 
 
 module.exports =
 module.exports =
-	sendMessage: (project_id, user_id, content, callback) ->
+	sendGlobalMessage: (project_id, user_id, content, callback) ->
 		request.post {
 		request.post {
-			url: "/room/#{project_id}/messages"
+			url: "/project/#{project_id}/messages"
 			json:
 			json:
 				user_id: user_id
 				user_id: user_id
 				content: content
 				content: content
 		}, callback
 		}, callback
 	
 	
-	getMessages: (project_id, callback) ->
+	getGlobalMessages: (project_id, callback) ->
 		request.get {
 		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
 			json: true
 		}, callback
 		}, callback

+ 0 - 13
services/chat/test/unit/coffee/Messages/MessageFormatterTests.coffee

@@ -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: {}
-

+ 0 - 173
services/chat/test/unit/coffee/Messages/MessageHttpControllerTests.coffee

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

+ 0 - 61
services/chat/test/unit/coffee/Messages/MessageManagerTests.coffee

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

+ 0 - 58
services/chat/test/unit/coffee/Rooms/RoomManagerTests.coffee

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

+ 0 - 12
services/chat/test/unit/coffee/Users/UserFormatterTests.coffee

@@ -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: {}
-