Merge branch 'master' into csh-ho-docker-issue-1338-bulk-upgrade

This commit is contained in:
Christopher Hoskin 2019-01-04 11:30:18 +00:00
commit 1d8e52cdd5
41 changed files with 10150 additions and 1641 deletions

75
services/chat/.eslintrc Normal file
View file

@ -0,0 +1,75 @@
{
"extends": [
"standard",
"standard-react",
"prettier",
"prettier/react",
"prettier/standard",
"plugin:jsx-a11y/recommended"
],
"plugins": [
"prettier",
"jsx-a11y",
"mocha",
"chai-expect",
"chai-friendly"
],
"env": {
"mocha": true
},
"globals": {
"expect": true,
"define": true,
"$": true,
"angular": true,
// Injected in layout.pug
"user_id": true
},
"settings": {
// Tell eslint-plugin-react which version of React we are using
"react": {
"version": "15"
}
},
"rules": {
"max-len": ["error", {
"ignoreUrls": true,
// Ignore long describe/it test blocks, long import/require statements
"ignorePattern": "(^\\s*(it|describe)\\s*\\(['\"]|^import\\s*.*\\s*from\\s*['\"]|^.*\\s*=\\s*require\\(['\"])"
}],
// Fix conflict between prettier & standard by overriding to prefer
// double quotes
"jsx-quotes": ["error", "prefer-double"],
// Override weird behaviour of jsx-a11y label-has-for (says labels must be
// nested *and* have for/id attributes)
"jsx-a11y/label-has-for": [
"error",
{
"required": {
"some": [
"nesting",
"id"
]
}
}
],
// Add some mocha specific rules
"mocha/handle-done-callback": "error",
"mocha/no-exclusive-tests": "error",
"mocha/no-global-tests": "error",
"mocha/no-identical-title": "error",
"mocha/no-nested-tests": "error",
"mocha/no-pending-tests": "error",
"mocha/no-skipped-tests": "error",
// Add some chai specific rules
"chai-expect/missing-assertion": "error",
"chai-expect/terminating-properties": "error",
// Swap the no-unused-expressions rule with a more chai-friendly one
"no-unused-expressions": 0,
"chai-friendly/no-unused-expressions": "error"
}
}

View file

@ -1,12 +1,7 @@
**.swp **.swp
app.js
app/js/
test/unit/js/
test/acceptance/js/
public/build/ public/build/
node_modules/ node_modules/
/public/js/chat.js
plato/ plato/

View file

@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

View file

@ -1,97 +0,0 @@
module.exports = (grunt) ->
# Project configuration.
grunt.initConfig
forever:
app:
options:
index: "app.js"
execute:
app:
src: "app.js"
coffee:
server:
expand: true,
flatten: false,
cwd: 'app/coffee',
src: ['**/*.coffee'],
dest: 'app/js/',
ext: '.js'
app_server:
expand: true,
flatten: false,
src: ['app.coffee'],
dest: './',
ext: '.js'
unit_tests:
expand: true,
flatten: false,
cwd: 'test/unit/coffee',
src: ['**/*.coffee'],
dest: 'test/unit/js/',
ext: '.js'
acceptance_tests:
expand: true,
flatten: false,
cwd: 'test/acceptance/coffee',
src: ['**/*.coffee'],
dest: 'test/acceptance/js/',
ext: '.js'
watch:
server_coffee:
files: ['app/**/*.coffee', 'test/unit/**/*.coffee']
tasks: ['compile:server', 'compile:unit_tests', 'mochaTest']
clean: ["app/js", "test/unit/js"]
nodemon:
dev:
options:
file: 'app.js'
concurrent:
dev:
tasks: ['nodemon', 'watch']
options:
logConcurrentOutput: true
mochaTest:
unit:
options:
reporter: process.env.MOCHA_RUNNER || "spec"
grep: grunt.option("grep")
src: ['test/unit/**/*.js']
acceptance:
options:
reporter: process.env.MOCHA_RUNNER || "spec"
grep: grunt.option("grep")
src: ['test/acceptance/**/*.js']
plato:
your_task:
files: 'plato': ['app/js/**/*.js'],
grunt.loadNpmTasks 'grunt-contrib-coffee'
grunt.loadNpmTasks 'grunt-contrib-watch'
grunt.loadNpmTasks 'grunt-nodemon'
grunt.loadNpmTasks 'grunt-contrib-clean'
grunt.loadNpmTasks 'grunt-concurrent'
grunt.loadNpmTasks 'grunt-mocha-test'
grunt.loadNpmTasks 'grunt-plato'
grunt.loadNpmTasks 'grunt-execute'
grunt.loadNpmTasks 'grunt-bunyan'
grunt.loadNpmTasks 'grunt-forever'
grunt.registerTask 'compile', ['clean', 'coffee']
grunt.registerTask 'install', ['compile']
grunt.registerTask 'default', ['compile', 'bunyan', 'execute']
grunt.registerTask 'test:unit', ['compile', 'mochaTest:unit']
grunt.registerTask 'test:acceptance', ['compile:acceptance_tests', 'mochaTest:acceptance']

129
services/chat/Gruntfile.js Normal file
View file

@ -0,0 +1,129 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
forever: {
app: {
options: {
index: 'app.js'
}
}
},
execute: {
app: {
src: 'app.js'
}
},
coffee: {
server: {
expand: true,
flatten: false,
cwd: 'app/coffee',
src: ['**/*.coffee'],
dest: 'app/js/',
ext: '.js'
},
app_server: {
expand: true,
flatten: false,
src: ['app.coffee'],
dest: './',
ext: '.js'
},
unit_tests: {
expand: true,
flatten: false,
cwd: 'test/unit/coffee',
src: ['**/*.coffee'],
dest: 'test/unit/js/',
ext: '.js'
},
acceptance_tests: {
expand: true,
flatten: false,
cwd: 'test/acceptance/coffee',
src: ['**/*.coffee'],
dest: 'test/acceptance/js/',
ext: '.js'
}
},
watch: {
server_coffee: {
files: ['app/**/*.coffee', 'test/unit/**/*.coffee'],
tasks: ['compile:server', 'compile:unit_tests', 'mochaTest']
}
},
clean: ['app/js', 'test/unit/js'],
nodemon: {
dev: {
options: {
file: 'app.js'
}
}
},
concurrent: {
dev: {
tasks: ['nodemon', 'watch'],
options: {
logConcurrentOutput: true
}
}
},
mochaTest: {
unit: {
options: {
reporter: process.env.MOCHA_RUNNER || 'spec',
grep: grunt.option('grep')
},
src: ['test/unit/**/*.js']
},
acceptance: {
options: {
reporter: process.env.MOCHA_RUNNER || 'spec',
grep: grunt.option('grep')
},
src: ['test/acceptance/**/*.js']
}
},
plato: {
your_task: {
files: { plato: ['app/js/**/*.js'] }
}
}
})
grunt.loadNpmTasks('grunt-contrib-coffee')
grunt.loadNpmTasks('grunt-contrib-watch')
grunt.loadNpmTasks('grunt-nodemon')
grunt.loadNpmTasks('grunt-contrib-clean')
grunt.loadNpmTasks('grunt-concurrent')
grunt.loadNpmTasks('grunt-mocha-test')
grunt.loadNpmTasks('grunt-plato')
grunt.loadNpmTasks('grunt-execute')
grunt.loadNpmTasks('grunt-bunyan')
grunt.loadNpmTasks('grunt-forever')
grunt.registerTask('compile', ['clean', 'coffee'])
grunt.registerTask('install', ['compile'])
grunt.registerTask('default', ['compile', 'bunyan', 'execute'])
grunt.registerTask('test:unit', ['compile', 'mochaTest:unit'])
return grunt.registerTask('test:acceptance', [
'compile:acceptance_tests',
'mochaTest:acceptance'
])
}

View file

@ -1,13 +0,0 @@
logger = require 'logger-sharelatex'
settings = require 'settings-sharelatex'
Server = require "./app/js/server"
if !module.parent # Called directly
port = settings.internal?.chat?.port or 3010
host = settings.internal?.chat?.host or "localhost"
Server.server.listen port, host, (error) ->
throw error if error?
logger.info "Chat starting up, listening on #{host}:#{port}"
module.exports = Server.server

39
services/chat/app.js Normal file
View file

@ -0,0 +1,39 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS103: Rewrite code to no longer use __guard__
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const logger = require('logger-sharelatex')
const settings = require('settings-sharelatex')
const Server = require('./app/js/server')
if (!module.parent) {
// Called directly
const port =
__guard__(
settings.internal != null ? settings.internal.chat : undefined,
x => x.port
) || 3010
const host =
__guard__(
settings.internal != null ? settings.internal.chat : undefined,
x1 => x1.host
) || 'localhost'
Server.server.listen(port, host, function(error) {
if (error != null) {
throw error
}
return logger.info(`Chat starting up, listening on ${host}:${port}`)
})
}
module.exports = Server.server
function __guard__(value, transform) {
return typeof value !== 'undefined' && value !== null
? transform(value)
: undefined
}

View file

@ -1,46 +0,0 @@
module.exports = MessageFormatter =
formatMessageForClientSide: (message) ->
if message._id?
message.id = message._id.toString()
delete message._id
formattedMessage =
id: message.id
content: message.content
timestamp: message.timestamp
user_id: message.user_id
if message.edited_at?
formattedMessage.edited_at = message.edited_at
return formattedMessage
formatMessagesForClientSide: (messages) ->
(@formatMessageForClientSide(message) for message in messages)
groupMessagesByThreads: (rooms, messages) ->
rooms_by_id = {}
for room in rooms
rooms_by_id[room._id.toString()] = room
threads = {}
getThread = (room) ->
thread_id = room.thread_id.toString()
if threads[thread_id]?
return threads[thread_id]
else
thread = { messages: [] }
if room.resolved?
thread.resolved = true
thread.resolved_at = room.resolved.ts
thread.resolved_by_user_id = room.resolved.user_id
threads[thread_id] = thread
return thread
for message in messages
room = rooms_by_id[message.room_id.toString()]
if room?
thread = getThread(room)
thread.messages.push MessageFormatter.formatMessageForClientSide(message)
for thread_id, thread of threads
thread.messages.sort (a,b) -> a.timestamp - b.timestamp
return threads

View file

@ -1,112 +0,0 @@
logger = require "logger-sharelatex"
metrics = require "metrics-sharelatex"
MessageManager = require "./MessageManager"
MessageFormatter = require "./MessageFormatter"
ThreadManager = require "../Threads/ThreadManager"
{ObjectId} = require "../../mongojs"
module.exports = MessageHttpController =
DEFAULT_MESSAGE_LIMIT: 50
MAX_MESSAGE_LENGTH: 10 * 1024 # 10kb, about 1,500 words
getGlobalMessages: (req, res, next) ->
MessageHttpController._getMessages(ThreadManager.GLOBAL_THREAD, 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?
threads = MessageFormatter.groupMessagesByThreads rooms, messages
res.json threads
resolveThread: (req, res, next) ->
{project_id, thread_id} = req.params
{user_id} = req.body
logger.log {user_id, project_id, thread_id}, "marking thread as resolved"
ThreadManager.resolveThread project_id, thread_id, user_id, (error) ->
return next(error) if error?
res.send 204 # No content
reopenThread: (req, res, next) ->
{project_id, thread_id} = req.params
logger.log {project_id, thread_id}, "reopening thread"
ThreadManager.reopenThread project_id, thread_id, (error) ->
return next(error) if error?
res.send 204 # No content
deleteThread: (req, res, next) ->
{project_id, thread_id} = req.params
logger.log {project_id, thread_id}, "deleting thread"
ThreadManager.deleteThread project_id, thread_id, (error, room_id) ->
return next(error) if error?
MessageManager.deleteAllMessagesInRoom room_id, (error) ->
return next(error) if error?
res.send 204 # No content
editMessage: (req, res, next) ->
{content} = req?.body
{project_id, thread_id, message_id} = req.params
logger.log {project_id, thread_id, message_id, content}, "editing message"
ThreadManager.findOrCreateThread project_id, thread_id, (error, room) ->
return next(error) if error?
MessageManager.updateMessage room._id, message_id, content, Date.now(), (error) ->
return next(error) if error?
res.send(204)
deleteMessage: (req, res, next) ->
{project_id, thread_id, message_id} = req.params
logger.log {project_id, thread_id, message_id}, "deleting message"
ThreadManager.findOrCreateThread project_id, thread_id, (error, room) ->
return next(error) if error?
MessageManager.deleteMessage room._id, message_id, (error, message) ->
return next(error) if error?
res.send(204)
_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")
if !content?
return res.send(400, "No content provided")
if content.length > @MAX_MESSAGE_LENGTH
return res.send(400, "Content too long (> #{@MAX_MESSAGE_LENGTH} bytes)")
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?
MessageManager.createMessage thread._id, user_id, content, Date.now(), (error, message) ->
return next(error) if error?
message = MessageFormatter.formatMessageForClientSide(message)
message.room_id = project_id
res.send(201, message)
_getMessages: (client_thread_id, req, res, next) ->
{project_id} = req.params
if req.query?.before?
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
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?
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?
messages = MessageFormatter.formatMessagesForClientSide messages
logger.log {project_id, messages}, "got messages"
res.send 200, messages

View file

@ -1,76 +0,0 @@
mongojs = require "../../mongojs"
db = mongojs.db
ObjectId = mongojs.ObjectId
async = require "async"
metrics = require 'metrics-sharelatex'
logger = require 'logger-sharelatex'
module.exports = MessageManager =
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: (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).sort({ timestamp: -1 }).limit(limit)
cursor.toArray callback
findAllMessagesInRooms: (room_ids, callback = (error, messages) ->) ->
db.messages.find {
room_id: { $in: room_ids }
}, callback
deleteAllMessagesInRoom: (room_id, callback = (error) ->) ->
db.messages.remove {
room_id: room_id
}, callback
updateMessage: (room_id, message_id, content, timestamp, callback = (error, message) ->) ->
query = @_ensureIdsAreObjectIds(
_id: message_id
room_id: room_id
)
db.messages.update query, {
$set:
content: content
edited_at: timestamp
}, (error) ->
return callback(error) if error?
return callback()
deleteMessage: (room_id, message_id, callback = (error) ->) ->
query = @_ensureIdsAreObjectIds(
_id: message_id
room_id: room_id
)
db.messages.remove query, (error) ->
return callback(error) if error?
return callback()
_ensureIdsAreObjectIds: (query) ->
if query.user_id? and query.user_id not instanceof ObjectId
query.user_id = ObjectId(query.user_id)
if query.room_id? and query.room_id not instanceof ObjectId
query.room_id = ObjectId(query.room_id)
if query._id? and query._id not instanceof ObjectId
query._id = ObjectId(query._id)
return query
[
'createMessage',
'getMessages',
'findAllMessagesInRooms',
'updateMessage',
'deleteMessage'
].map (method) ->
metrics.timeAsyncMethod(MessageManager, method, 'mongo.MessageManager', logger)

View file

@ -1,88 +0,0 @@
mongojs = require("../../mongojs")
db = mongojs.db
ObjectId = mongojs.ObjectId
logger = require('logger-sharelatex')
metrics = require('metrics-sharelatex')
module.exports = ThreadManager =
GLOBAL_THREAD: "GLOBAL"
findOrCreateThread: (project_id, thread_id, callback = (error, thread) ->) ->
project_id = ObjectId(project_id.toString())
if thread_id != ThreadManager.GLOBAL_THREAD
thread_id = ObjectId(thread_id.toString())
if thread_id == ThreadManager.GLOBAL_THREAD
query = {
project_id: project_id
thread_id: { $exists: false }
}
update = {
project_id: project_id
}
else
query = {
project_id: project_id
thread_id: thread_id
}
update = {
project_id: project_id
thread_id: thread_id
}
db.rooms.update query, update, { upsert: true }, (error) ->
return callback(error) if error?
db.rooms.find query, (error, rooms = []) ->
return callback(error) if error?
return callback null, rooms[0]
findAllThreadRooms: (project_id, callback = (error, rooms) ->) ->
db.rooms.find {
project_id: ObjectId(project_id.toString())
thread_id: { $exists: true }
}, {
thread_id: 1,
resolved: 1
}, callback
resolveThread: (project_id, thread_id, user_id, callback = (error) ->) ->
db.rooms.update {
project_id: ObjectId(project_id.toString())
thread_id: ObjectId(thread_id.toString())
}, {
$set: {
resolved: {
user_id: user_id
ts: new Date()
}
}
}, callback
reopenThread: (project_id, thread_id, callback = (error) ->) ->
db.rooms.update {
project_id: ObjectId(project_id.toString())
thread_id: ObjectId(thread_id.toString())
}, {
$unset: {
resolved: true
}
}, callback
deleteThread: (project_id, thread_id, callback = (error, room_id) ->) ->
@findOrCreateThread project_id, thread_id, (error, room) ->
return callback(error) if error?
db.rooms.remove {
_id: room._id
}, (error) ->
return callback(error) if error?
return callback null, room._id
[
'findOrCreateThread',
'findAllThreadRooms',
'resolveThread',
'reopenThread',
'deleteThread',
].map (method) ->
metrics.timeAsyncMethod(ThreadManager, method, 'mongo.ThreadManager', logger)

View file

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

View file

@ -1,39 +0,0 @@
MessageHttpController = require('./Features/Messages/MessageHttpController')
{ObjectId} = require "./mongojs"
module.exports = Router =
route: (app) ->
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.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")

View file

@ -1,39 +0,0 @@
logger = require 'logger-sharelatex'
logger.initialize("chat-sharelatex")
metrics = require("metrics-sharelatex")
metrics.initialize("chat")
Path = require("path")
express = require("express")
app = express()
server = require("http").createServer(app)
Router = require "./router"
app.use express.bodyParser()
app.use metrics.http.monitor(logger)
if (app.get 'env') == 'development'
console.log "Development Enviroment"
app.use express.errorHandler({ dumpExceptions: true, showStack: true })
if (app.get 'env') == 'production'
console.log "Production Enviroment"
app.use express.logger()
app.use express.errorHandler()
profiler = require "v8-profiler"
app.get "/profile", (req, res) ->
time = parseInt(req.query.time || "1000")
profiler.startProfiling("test")
setTimeout () ->
profile = profiler.stopProfiling("test")
res.json(profile)
, time
Router.route(app)
module.exports = {
server: server
app: app
}

View file

@ -0,0 +1,80 @@
/* eslint-disable
camelcase,
max-len,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let MessageFormatter
module.exports = MessageFormatter = {
formatMessageForClientSide(message) {
if (message._id != null) {
message.id = message._id.toString()
delete message._id
}
const formattedMessage = {
id: message.id,
content: message.content,
timestamp: message.timestamp,
user_id: message.user_id
}
if (message.edited_at != null) {
formattedMessage.edited_at = message.edited_at
}
return formattedMessage
},
formatMessagesForClientSide(messages) {
return Array.from(messages).map(message =>
this.formatMessageForClientSide(message)
)
},
groupMessagesByThreads(rooms, messages) {
let room, thread
const rooms_by_id = {}
for (room of Array.from(rooms)) {
rooms_by_id[room._id.toString()] = room
}
const threads = {}
const getThread = function(room) {
const thread_id = room.thread_id.toString()
if (threads[thread_id] != null) {
return threads[thread_id]
} else {
const thread = { messages: [] }
if (room.resolved != null) {
thread.resolved = true
thread.resolved_at = room.resolved.ts
thread.resolved_by_user_id = room.resolved.user_id
}
threads[thread_id] = thread
return thread
}
}
for (let message of Array.from(messages)) {
room = rooms_by_id[message.room_id.toString()]
if (room != null) {
thread = getThread(room)
thread.messages.push(
MessageFormatter.formatMessageForClientSide(message)
)
}
}
for (let thread_id in threads) {
thread = threads[thread_id]
thread.messages.sort((a, b) => a.timestamp - b.timestamp)
}
return threads
}
}

View file

@ -0,0 +1,258 @@
/* eslint-disable
camelcase,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let MessageHttpController
const logger = require('logger-sharelatex')
const metrics = require('metrics-sharelatex')
const MessageManager = require('./MessageManager')
const MessageFormatter = require('./MessageFormatter')
const ThreadManager = require('../Threads/ThreadManager')
const { ObjectId } = require('../../mongojs')
module.exports = MessageHttpController = {
DEFAULT_MESSAGE_LIMIT: 50,
MAX_MESSAGE_LENGTH: 10 * 1024, // 10kb, about 1,500 words
getGlobalMessages(req, res, next) {
return MessageHttpController._getMessages(
ThreadManager.GLOBAL_THREAD,
req,
res,
next
)
},
sendGlobalMessage(req, res, next) {
return MessageHttpController._sendMessage(
ThreadManager.GLOBAL_THREAD,
req,
res,
next
)
},
sendThreadMessage(req, res, next) {
return MessageHttpController._sendMessage(
req.params.thread_id,
req,
res,
next
)
},
getAllThreads(req, res, next) {
const { project_id } = req.params
logger.log({ project_id }, 'getting all threads')
return ThreadManager.findAllThreadRooms(project_id, function(error, rooms) {
if (error != null) {
return next(error)
}
const room_ids = rooms.map(r => r._id)
return MessageManager.findAllMessagesInRooms(room_ids, function(
error,
messages
) {
if (error != null) {
return next(error)
}
const threads = MessageFormatter.groupMessagesByThreads(rooms, messages)
return res.json(threads)
})
})
},
resolveThread(req, res, next) {
const { project_id, thread_id } = req.params
const { user_id } = req.body
logger.log({ user_id, project_id, thread_id }, 'marking thread as resolved')
return ThreadManager.resolveThread(project_id, thread_id, user_id, function(
error
) {
if (error != null) {
return next(error)
}
return res.send(204)
})
}, // No content
reopenThread(req, res, next) {
const { project_id, thread_id } = req.params
logger.log({ project_id, thread_id }, 'reopening thread')
return ThreadManager.reopenThread(project_id, thread_id, function(error) {
if (error != null) {
return next(error)
}
return res.send(204)
})
}, // No content
deleteThread(req, res, next) {
const { project_id, thread_id } = req.params
logger.log({ project_id, thread_id }, 'deleting thread')
return ThreadManager.deleteThread(project_id, thread_id, function(
error,
room_id
) {
if (error != null) {
return next(error)
}
return MessageManager.deleteAllMessagesInRoom(room_id, function(error) {
if (error != null) {
return next(error)
}
return res.send(204)
})
})
}, // No content
editMessage(req, res, next) {
const { content } = req != null ? req.body : undefined
const { project_id, thread_id, message_id } = req.params
logger.log(
{ project_id, thread_id, message_id, content },
'editing message'
)
return ThreadManager.findOrCreateThread(project_id, thread_id, function(
error,
room
) {
if (error != null) {
return next(error)
}
return MessageManager.updateMessage(
room._id,
message_id,
content,
Date.now(),
function(error) {
if (error != null) {
return next(error)
}
return res.send(204)
}
)
})
},
deleteMessage(req, res, next) {
const { project_id, thread_id, message_id } = req.params
logger.log({ project_id, thread_id, message_id }, 'deleting message')
return ThreadManager.findOrCreateThread(project_id, thread_id, function(
error,
room
) {
if (error != null) {
return next(error)
}
return MessageManager.deleteMessage(room._id, message_id, function(
error,
message
) {
if (error != null) {
return next(error)
}
return res.send(204)
})
})
},
_sendMessage(client_thread_id, req, res, next) {
const { user_id, content } = req != null ? req.body : undefined
const { project_id } = req.params
if (!ObjectId.isValid(user_id)) {
return res.send(400, 'Invalid user_id')
}
if (content == null) {
return res.send(400, 'No content provided')
}
if (content.length > this.MAX_MESSAGE_LENGTH) {
return res.send(
400,
`Content too long (> ${this.MAX_MESSAGE_LENGTH} bytes)`
)
}
logger.log(
{ client_thread_id, project_id, user_id, content },
'new message received'
)
return ThreadManager.findOrCreateThread(
project_id,
client_thread_id,
function(error, thread) {
if (error != null) {
return next(error)
}
return MessageManager.createMessage(
thread._id,
user_id,
content,
Date.now(),
function(error, message) {
if (error != null) {
return next(error)
}
message = MessageFormatter.formatMessageForClientSide(message)
message.room_id = project_id
return res.send(201, message)
}
)
}
)
},
_getMessages(client_thread_id, req, res, next) {
let before, limit
const { project_id } = req.params
if ((req.query != null ? req.query.before : undefined) != null) {
before = parseInt(req.query.before, 10)
} else {
before = null
}
if ((req.query != null ? req.query.limit : undefined) != null) {
limit = parseInt(req.query.limit, 10)
} else {
limit = MessageHttpController.DEFAULT_MESSAGE_LIMIT
}
logger.log(
{ limit, before, project_id, client_thread_id },
'get message request received'
)
return ThreadManager.findOrCreateThread(
project_id,
client_thread_id,
function(error, thread) {
if (error != null) {
return next(error)
}
const thread_object_id = thread._id
logger.log(
{ limit, before, project_id, client_thread_id, thread_object_id },
'found or created thread'
)
return MessageManager.getMessages(
thread_object_id,
limit,
before,
function(error, messages) {
if (error != null) {
return next(error)
}
messages = MessageFormatter.formatMessagesForClientSide(messages)
logger.log({ project_id, messages }, 'got messages')
return res.send(200, messages)
}
)
}
)
}
}

View file

@ -0,0 +1,146 @@
/* eslint-disable
camelcase,
handle-callback-err,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let MessageManager
const mongojs = require('../../mongojs')
const { db } = mongojs
const { ObjectId } = mongojs
const async = require('async')
const metrics = require('metrics-sharelatex')
const logger = require('logger-sharelatex')
module.exports = MessageManager = {
createMessage(room_id, user_id, content, timestamp, callback) {
if (callback == null) {
callback = function(error, message) {}
}
let newMessageOpts = {
content,
room_id,
user_id,
timestamp
}
newMessageOpts = this._ensureIdsAreObjectIds(newMessageOpts)
return db.messages.save(newMessageOpts, callback)
},
getMessages(room_id, limit, before, callback) {
if (callback == null) {
callback = function(error, messages) {}
}
let query = { room_id }
if (before != null) {
query.timestamp = { $lt: before }
}
query = this._ensureIdsAreObjectIds(query)
const cursor = db.messages
.find(query)
.sort({ timestamp: -1 })
.limit(limit)
return cursor.toArray(callback)
},
findAllMessagesInRooms(room_ids, callback) {
if (callback == null) {
callback = function(error, messages) {}
}
return db.messages.find(
{
room_id: { $in: room_ids }
},
callback
)
},
deleteAllMessagesInRoom(room_id, callback) {
if (callback == null) {
callback = function(error) {}
}
return db.messages.remove(
{
room_id
},
callback
)
},
updateMessage(room_id, message_id, content, timestamp, callback) {
if (callback == null) {
callback = function(error, message) {}
}
const query = this._ensureIdsAreObjectIds({
_id: message_id,
room_id
})
return db.messages.update(
query,
{
$set: {
content,
edited_at: timestamp
}
},
function(error) {
if (error != null) {
return callback(error)
}
return callback()
}
)
},
deleteMessage(room_id, message_id, callback) {
if (callback == null) {
callback = function(error) {}
}
const query = this._ensureIdsAreObjectIds({
_id: message_id,
room_id
})
return db.messages.remove(query, function(error) {
if (error != null) {
return callback(error)
}
return callback()
})
},
_ensureIdsAreObjectIds(query) {
if (query.user_id != null && !(query.user_id instanceof ObjectId)) {
query.user_id = ObjectId(query.user_id)
}
if (query.room_id != null && !(query.room_id instanceof ObjectId)) {
query.room_id = ObjectId(query.room_id)
}
if (query._id != null && !(query._id instanceof ObjectId)) {
query._id = ObjectId(query._id)
}
return query
}
}
;[
'createMessage',
'getMessages',
'findAllMessagesInRooms',
'updateMessage',
'deleteMessage'
].map(method =>
metrics.timeAsyncMethod(
MessageManager,
method,
'mongo.MessageManager',
logger
)
)

View file

@ -0,0 +1,159 @@
/* eslint-disable
camelcase,
handle-callback-err,
max-len,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let ThreadManager
const mongojs = require('../../mongojs')
const { db } = mongojs
const { ObjectId } = mongojs
const logger = require('logger-sharelatex')
const metrics = require('metrics-sharelatex')
module.exports = ThreadManager = {
GLOBAL_THREAD: 'GLOBAL',
findOrCreateThread(project_id, thread_id, callback) {
let query, update
if (callback == null) {
callback = function(error, thread) {}
}
project_id = ObjectId(project_id.toString())
if (thread_id !== ThreadManager.GLOBAL_THREAD) {
thread_id = ObjectId(thread_id.toString())
}
if (thread_id === ThreadManager.GLOBAL_THREAD) {
query = {
project_id,
thread_id: { $exists: false }
}
update = {
project_id
}
} else {
query = {
project_id,
thread_id
}
update = {
project_id,
thread_id
}
}
return db.rooms.update(query, update, { upsert: true }, function(error) {
if (error != null) {
return callback(error)
}
return db.rooms.find(query, function(error, rooms) {
if (rooms == null) {
rooms = []
}
if (error != null) {
return callback(error)
}
return callback(null, rooms[0])
})
})
},
findAllThreadRooms(project_id, callback) {
if (callback == null) {
callback = function(error, rooms) {}
}
return db.rooms.find(
{
project_id: ObjectId(project_id.toString()),
thread_id: { $exists: true }
},
{
thread_id: 1,
resolved: 1
},
callback
)
},
resolveThread(project_id, thread_id, user_id, callback) {
if (callback == null) {
callback = function(error) {}
}
return db.rooms.update(
{
project_id: ObjectId(project_id.toString()),
thread_id: ObjectId(thread_id.toString())
},
{
$set: {
resolved: {
user_id,
ts: new Date()
}
}
},
callback
)
},
reopenThread(project_id, thread_id, callback) {
if (callback == null) {
callback = function(error) {}
}
return db.rooms.update(
{
project_id: ObjectId(project_id.toString()),
thread_id: ObjectId(thread_id.toString())
},
{
$unset: {
resolved: true
}
},
callback
)
},
deleteThread(project_id, thread_id, callback) {
if (callback == null) {
callback = function(error, room_id) {}
}
return this.findOrCreateThread(project_id, thread_id, function(
error,
room
) {
if (error != null) {
return callback(error)
}
return db.rooms.remove(
{
_id: room._id
},
function(error) {
if (error != null) {
return callback(error)
}
return callback(null, room._id)
}
)
})
}
}
;[
'findOrCreateThread',
'findAllThreadRooms',
'resolveThread',
'reopenThread',
'deleteThread'
].map(method =>
metrics.timeAsyncMethod(ThreadManager, method, 'mongo.ThreadManager', logger)
)

View file

@ -0,0 +1,9 @@
// TODO: This file was created by bulk-decaffeinate.
// Sanity-check the conversion and remove this comment.
const Settings = require('settings-sharelatex')
const mongojs = require('mongojs')
const db = mongojs(Settings.mongo.url, ['rooms', 'messages'])
module.exports = {
db,
ObjectId: mongojs.ObjectId
}

View file

@ -0,0 +1,84 @@
/* eslint-disable
camelcase,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let Router
const MessageHttpController = require('./Features/Messages/MessageHttpController')
const { ObjectId } = require('./mongojs')
module.exports = Router = {
route(app) {
app.param('project_id', function(req, res, next, project_id) {
if (ObjectId.isValid(project_id)) {
return next()
} else {
return res.send(400, 'Invalid project_id')
}
})
app.param('thread_id', function(req, res, next, thread_id) {
if (ObjectId.isValid(thread_id)) {
return next()
} else {
return 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.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
)
return app.get('/status', (req, res, next) => res.send('chat is alive'))
}
}

View file

@ -0,0 +1,50 @@
/* eslint-disable
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const logger = require('logger-sharelatex')
logger.initialize('chat-sharelatex')
const metrics = require('metrics-sharelatex')
metrics.initialize('chat')
const Path = require('path')
const express = require('express')
const app = express()
const server = require('http').createServer(app)
const Router = require('./router')
app.use(express.bodyParser())
app.use(metrics.http.monitor(logger))
if (app.get('env') === 'development') {
console.log('Development Enviroment')
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }))
}
if (app.get('env') === 'production') {
console.log('Production Enviroment')
app.use(express.logger())
app.use(express.errorHandler())
}
const profiler = require('v8-profiler')
app.get('/profile', function(req, res) {
const time = parseInt(req.query.time || '1000')
profiler.startProfiling('test')
return setTimeout(function() {
const profile = profiler.stopProfiling('test')
return res.json(profile)
}, time)
})
Router.route(app)
module.exports = {
server,
app
}

View file

@ -1,23 +0,0 @@
settings =
internal:
chat:
host: process.env['LISTEN_ADDRESS'] or "localhost"
port: 3010
apis:
web:
url: "http://#{process.env['WEB_HOST'] || "localhost"}:3000"
user: "sharelatex"
pass: "password"
mongo:
url: process.env['MONGO_CONNECTION_STRING'] or "mongodb://#{process.env["MONGO_HOST"] or "localhost"}/sharelatex"
redis:
web:
host: process.env['REDIS_HOST'] || "localhost"
port: "6379"
password: process.env['REDIS_PASSWORD'] || ""
module.exports = settings

View file

@ -0,0 +1,30 @@
module.exports = {
internal: {
chat: {
host: process.env['LISTEN_ADDRESS'] || 'localhost',
port: 3010
}
},
apis: {
web: {
url: `http://${process.env['WEB_HOST'] || 'localhost'}:${process.env[
'WEB_PORT'
] || 3000}`,
user: 'sharelatex',
pass: 'password'
}
},
mongo: {
url: process.env['MONGO_CONNECTION_STRING'] || `mongodb://${process.env['MONGO_HOST'] || 'localhost'}/sharelatex`
},
redis: {
web: {
host: process.env['REDIS_HOST'] || 'localhost',
port: '6379',
password: process.env['REDIS_PASSWORD'] || ''
}
}
}

53
services/chat/decaffeinate.sh Executable file
View file

@ -0,0 +1,53 @@
set -ex
curl -o .eslintrc https://raw.githubusercontent.com/sharelatex/web-sharelatex/master/.eslintrc
curl -o .prettierrc https://raw.githubusercontent.com/sharelatex/web-sharelatex/master/.prettierrc
git add .
git commit -m "Decaffeinate: add eslint and prettier rc files"
npx bulk-decaffeinate convert --dir app/coffee
npx bulk-decaffeinate clean
git mv app/coffee app/js
git commit -m "Rename app/coffee dir to app/js"
npx prettier-eslint 'app/js/**/*.js' --write
git add .
git commit -m "Prettier: convert app/js decaffeinated files to Prettier format"
npx bulk-decaffeinate convert --dir test/acceptance/coffee
npx bulk-decaffeinate clean
git mv test/acceptance/coffee test/acceptance/js
git commit -m "Rename test/acceptance/coffee to test/acceptance/js"
npx prettier-eslint 'test/acceptance/js/**/*.js' --write
git add .
git commit -m "Prettier: convert test/acceptance decaffeinated files to Prettier format"
git mv app.coffee app.js
git mv Gruntfile.coffee Gruntfile.js
git mv config/settings.defaults.coffee config/settings.defaults.js
git commit -m "Rename individual coffee files to js files"
decaffeinate app.js
decaffeinate Gruntfile.js
decaffeinate config/settings.defaults.js
git add .
git commit -m "Decaffeinate: convert individual files to js"
npx prettier-eslint 'app.js' 'Gruntfile.js' 'config/settings.defaults.js' --write
git add .
git commit -m "Prettier: convert individual decaffeinated files to Prettier format"
echo "done"

File diff suppressed because it is too large Load diff

View file

@ -1,32 +0,0 @@
{ObjectId} = require "../../../app/js/mongojs"
expect = require("chai").expect
ChatClient = require "./helpers/ChatClient"
ChatApp = require "./helpers/ChatApp"
describe "Deleting a message", ->
before (done) ->
@project_id = ObjectId().toString()
@user_id = ObjectId().toString()
@thread_id = ObjectId().toString()
ChatApp.ensureRunning done
describe "in a thread", ->
before (done) ->
ChatClient.sendMessage @project_id, @thread_id, @user_id, "first message", (error, response, @message) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 201
ChatClient.sendMessage @project_id, @thread_id, @user_id, "deleted message", (error, response, @message) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 201
ChatClient.deleteMessage @project_id, @thread_id, @message.id, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 204
done()
it "should then remove the message from 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].messages.length).to.equal 1
done()

View file

@ -1,31 +0,0 @@
{ObjectId} = require "../../../app/js/mongojs"
expect = require("chai").expect
crypto = require "crypto"
ChatClient = require "./helpers/ChatClient"
ChatApp = require "./helpers/ChatApp"
describe "Deleting a thread", ->
before (done) ->
@project_id = ObjectId().toString()
@user_id = ObjectId().toString()
ChatApp.ensureRunning done
describe "with a thread that is deleted", ->
before (done) ->
@thread_id = ObjectId().toString()
@content = "deleted 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
ChatClient.deleteThread @project_id, @thread_id, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 204
done()
it "should then not list the thread for the project", (done) ->
ChatClient.getThreads @project_id, (error, response, threads) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 200
expect(Object.keys(threads).length).to.equal 0
done()

View file

@ -1,34 +0,0 @@
{ObjectId} = require "../../../app/js/mongojs"
expect = require("chai").expect
ChatClient = require "./helpers/ChatClient"
ChatApp = require "./helpers/ChatApp"
describe "Editing a message", ->
before (done) ->
@project_id = ObjectId().toString()
@user_id = ObjectId().toString()
@thread_id = ObjectId().toString()
ChatApp.ensureRunning done
describe "in a thread", ->
before (done) ->
@content = "thread message"
@new_content = "updated thread message"
ChatClient.sendMessage @project_id, @thread_id, @user_id, @content, (error, response, @message) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 201
expect(@message.id).to.exist
expect(@message.content).to.equal @content
ChatClient.editMessage @project_id, @thread_id, @message.id, @new_content, (error, response, @new_message) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 204
done()
it "should then list the updated 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].messages.length).to.equal 1
expect(threads[@thread_id].messages[0].content).to.equal @new_content
done()

View file

@ -1,64 +0,0 @@
{ObjectId} = require "../../../app/js/mongojs"
expect = require("chai").expect
async = require "async"
crypto = require "crypto"
ChatClient = require "./helpers/ChatClient"
ChatApp = require "./helpers/ChatApp"
describe "Getting messages", ->
before (done) ->
@user_id1 = ObjectId().toString()
@user_id2 = ObjectId().toString()
@content1 = "foo bar"
@content2 = "hello world"
ChatApp.ensureRunning done
describe "globally", ->
before (done) ->
@project_id = ObjectId().toString()
async.series [
(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.getGlobalMessages @project_id, (error, response, messages) =>
expect(messages.length).to.equal 2
messages.reverse()
expect(messages[0].content).to.equal @content1
expect(messages[0].user_id).to.equal @user_id1
expect(messages[1].content).to.equal @content2
expect(messages[1].user_id).to.equal @user_id2
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.messages.length).to.equal 2
thread2 = threads[@thread_id2]
expect(thread2.messages.length).to.equal 2
expect(thread1.messages[0].content).to.equal "one"
expect(thread1.messages[0].user_id).to.equal @user_id1
expect(thread1.messages[1].content).to.equal "three"
expect(thread1.messages[1].user_id).to.equal @user_id1
expect(thread2.messages[0].content).to.equal "two"
expect(thread2.messages[0].user_id).to.equal @user_id2
expect(thread2.messages[1].content).to.equal "four"
expect(thread2.messages[1].user_id).to.equal @user_id2
done()

View file

@ -1,72 +0,0 @@
{ObjectId} = require "../../../app/js/mongojs"
expect = require("chai").expect
crypto = require "crypto"
ChatClient = require "./helpers/ChatClient"
ChatApp = require "./helpers/ChatApp"
describe "Resolving a thread", ->
before (done) ->
@project_id = ObjectId().toString()
@user_id = ObjectId().toString()
ChatApp.ensureRunning done
describe "with a resolved thread", ->
before (done) ->
@thread_id = ObjectId().toString()
@content = "resolved message"
ChatClient.sendMessage @project_id, @thread_id, @user_id, @content, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 201
ChatClient.resolveThread @project_id, @thread_id, @user_id, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 204
done()
it "should then list the thread as resolved", (done) ->
ChatClient.getThreads @project_id, (error, response, threads) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 200
expect(threads[@thread_id].resolved).to.equal true
expect(threads[@thread_id].resolved_by_user_id).to.equal @user_id
resolved_at = new Date(threads[@thread_id].resolved_at)
expect(new Date() - resolved_at).to.be.below 1000
done()
describe "when a thread is not resolved", ->
before (done) ->
@thread_id = ObjectId().toString()
@content = "open message"
ChatClient.sendMessage @project_id, @thread_id, @user_id, @content, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 201
done()
it "should not list the thread as resolved", (done) ->
ChatClient.getThreads @project_id, (error, response, threads) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 200
expect(threads[@thread_id].resolved).to.be.undefined
done()
describe "when a thread is resolved then reopened", ->
before (done) ->
@thread_id = ObjectId().toString()
@content = "resolved message"
ChatClient.sendMessage @project_id, @thread_id, @user_id, @content, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 201
ChatClient.resolveThread @project_id, @thread_id, @user_id, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 204
ChatClient.reopenThread @project_id, @thread_id, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 204
done()
it "should not list the thread as resolved", (done) ->
ChatClient.getThreads @project_id, (error, response, threads) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 200
expect(threads[@thread_id].resolved).to.be.undefined
done()

View file

@ -1,101 +0,0 @@
{ObjectId} = require "../../../app/js/mongojs"
expect = require("chai").expect
ChatClient = require "./helpers/ChatClient"
ChatApp = require "./helpers/ChatApp"
describe "Sending a message", ->
before (done) ->
ChatApp.ensureRunning done
describe "globally", ->
before (done) ->
@project_id = ObjectId().toString()
@user_id = ObjectId().toString()
@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) ->
@project_id = ObjectId().toString()
@user_id = ObjectId().toString()
@thread_id = ObjectId().toString()
@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].messages.length).to.equal 1
expect(threads[@thread_id].messages[0].content).to.equal @content
done()
it "should not appear in the global 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 0
done()
describe "failure cases", ->
before () ->
@project_id = ObjectId().toString()
@user_id = ObjectId().toString()
@thread_id = ObjectId().toString()
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()
describe "with no content", ->
it "should return a graceful error", (done) ->
ChatClient.sendMessage @project_id, @thread_id, @user_id, null, (error, response, body) =>
expect(response.statusCode).to.equal 400
expect(body).to.equal "No content provided"
done()
describe "with very long content", ->
it "should return a graceful error", (done) ->
content = new Buffer(10240).toString("hex")
ChatClient.sendMessage @project_id, @thread_id, @user_id, content, (error, response, body) =>
expect(response.statusCode).to.equal 400
expect(body).to.equal "Content too long (> 10240 bytes)"
done()

View file

@ -1,20 +0,0 @@
app = require('../../../../app')
require("logger-sharelatex").logger.level("error")
module.exports =
running: false
initing: false
callbacks: []
ensureRunning: (callback = (error) ->) ->
if @running
return callback()
else if @initing
@callbacks.push callback
else
@initing = true
@callbacks.push callback
app.listen 3010, "localhost", (error) =>
throw error if error?
@running = true
for callback in @callbacks
callback()

View file

@ -1,60 +0,0 @@
request = require("request").defaults({baseUrl: "http://localhost:3010"})
module.exports =
sendGlobalMessage: (project_id, user_id, content, callback) ->
request.post {
url: "/project/#{project_id}/messages"
json:
user_id: user_id
content: content
}, callback
getGlobalMessages: (project_id, callback) ->
request.get {
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
resolveThread: (project_id, thread_id, user_id, callback) ->
request.post {
url: "/project/#{project_id}/thread/#{thread_id}/resolve",
json: {
user_id: user_id
}
}, callback
reopenThread: (project_id, thread_id, callback) ->
request.post {
url: "/project/#{project_id}/thread/#{thread_id}/reopen",
}, callback
deleteThread: (project_id, thread_id, callback) ->
request.del {
url: "/project/#{project_id}/thread/#{thread_id}",
}, callback
editMessage: (project_id, thread_id, message_id, content, callback) ->
request.post {
url: "/project/#{project_id}/thread/#{thread_id}/messages/#{message_id}/edit"
json:
content: content
}, callback
deleteMessage: (project_id, thread_id, message_id, callback) ->
request.del {
url: "/project/#{project_id}/thread/#{thread_id}/messages/#{message_id}",
}, callback

View file

@ -0,0 +1,73 @@
/* eslint-disable
max-len,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { ObjectId } = require('../../../app/js/mongojs')
const { expect } = require('chai')
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
describe('Deleting a message', function() {
before(function(done) {
this.project_id = ObjectId().toString()
this.user_id = ObjectId().toString()
this.thread_id = ObjectId().toString()
return ChatApp.ensureRunning(done)
})
return describe('in a thread', function() {
before(function(done) {
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
'first message',
(error, response, message) => {
this.message = message
expect(error).to.be.null
expect(response.statusCode).to.equal(201)
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
'deleted message',
(error, response, message1) => {
this.message = message1
expect(error).to.be.null
expect(response.statusCode).to.equal(201)
return ChatClient.deleteMessage(
this.project_id,
this.thread_id,
this.message.id,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(204)
return done()
}
)
}
)
}
)
})
return it('should then remove the message from the threads', function(done) {
return ChatClient.getThreads(
this.project_id,
(error, response, threads) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(200)
expect(threads[this.thread_id].messages.length).to.equal(1)
return done()
}
)
})
})
})

View file

@ -0,0 +1,63 @@
/* eslint-disable
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { ObjectId } = require('../../../app/js/mongojs')
const { expect } = require('chai')
const crypto = require('crypto')
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
describe('Deleting a thread', function() {
before(function(done) {
this.project_id = ObjectId().toString()
this.user_id = ObjectId().toString()
return ChatApp.ensureRunning(done)
})
return describe('with a thread that is deleted', function() {
before(function(done) {
this.thread_id = ObjectId().toString()
this.content = 'deleted thread message'
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
this.content,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(201)
return ChatClient.deleteThread(
this.project_id,
this.thread_id,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(204)
return done()
}
)
}
)
})
return it('should then not list the thread for the project', function(done) {
return ChatClient.getThreads(
this.project_id,
(error, response, threads) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(200)
expect(Object.keys(threads).length).to.equal(0)
return done()
}
)
})
})
})

View file

@ -0,0 +1,72 @@
/* eslint-disable
camelcase,
max-len,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { ObjectId } = require('../../../app/js/mongojs')
const { expect } = require('chai')
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
describe('Editing a message', function() {
before(function(done) {
this.project_id = ObjectId().toString()
this.user_id = ObjectId().toString()
this.thread_id = ObjectId().toString()
return ChatApp.ensureRunning(done)
})
return describe('in a thread', function() {
before(function(done) {
this.content = 'thread message'
this.new_content = 'updated thread message'
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
this.content,
(error, response, message) => {
this.message = message
expect(error).to.be.null
expect(response.statusCode).to.equal(201)
expect(this.message.id).to.exist
expect(this.message.content).to.equal(this.content)
return ChatClient.editMessage(
this.project_id,
this.thread_id,
this.message.id,
this.new_content,
(error, response, new_message) => {
this.new_message = new_message
expect(error).to.be.null
expect(response.statusCode).to.equal(204)
return done()
}
)
}
)
})
return it('should then list the updated message in the threads', function(done) {
return ChatClient.getThreads(
this.project_id,
(error, response, threads) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(200)
expect(threads[this.thread_id].messages.length).to.equal(1)
expect(threads[this.thread_id].messages[0].content).to.equal(
this.new_content
)
return done()
}
)
})
})
})

View file

@ -0,0 +1,138 @@
/* eslint-disable
handle-callback-err,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { ObjectId } = require('../../../app/js/mongojs')
const { expect } = require('chai')
const async = require('async')
const crypto = require('crypto')
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
describe('Getting messages', function() {
before(function(done) {
this.user_id1 = ObjectId().toString()
this.user_id2 = ObjectId().toString()
this.content1 = 'foo bar'
this.content2 = 'hello world'
return ChatApp.ensureRunning(done)
})
describe('globally', function() {
before(function(done) {
this.project_id = ObjectId().toString()
return async.series(
[
cb =>
ChatClient.sendGlobalMessage(
this.project_id,
this.user_id1,
this.content1,
cb
),
cb =>
ChatClient.sendGlobalMessage(
this.project_id,
this.user_id2,
this.content2,
cb
)
],
done
)
})
return it('should contain the messages and populated users when getting the messages', function(done) {
return ChatClient.getGlobalMessages(
this.project_id,
(error, response, messages) => {
expect(messages.length).to.equal(2)
messages.reverse()
expect(messages[0].content).to.equal(this.content1)
expect(messages[0].user_id).to.equal(this.user_id1)
expect(messages[1].content).to.equal(this.content2)
expect(messages[1].user_id).to.equal(this.user_id2)
return done()
}
)
})
})
return describe('from all the threads', function() {
before(function(done) {
this.project_id = ObjectId().toString()
this.thread_id1 = ObjectId().toString()
this.thread_id2 = ObjectId().toString()
return async.series(
[
cb =>
ChatClient.sendMessage(
this.project_id,
this.thread_id1,
this.user_id1,
'one',
cb
),
cb =>
ChatClient.sendMessage(
this.project_id,
this.thread_id2,
this.user_id2,
'two',
cb
),
cb =>
ChatClient.sendMessage(
this.project_id,
this.thread_id1,
this.user_id1,
'three',
cb
),
cb =>
ChatClient.sendMessage(
this.project_id,
this.thread_id2,
this.user_id2,
'four',
cb
)
],
done
)
})
return it('should contain a dictionary of threads with messages with populated users', function(done) {
return ChatClient.getThreads(
this.project_id,
(error, response, threads) => {
expect(Object.keys(threads).length).to.equal(2)
const thread1 = threads[this.thread_id1]
expect(thread1.messages.length).to.equal(2)
const thread2 = threads[this.thread_id2]
expect(thread2.messages.length).to.equal(2)
expect(thread1.messages[0].content).to.equal('one')
expect(thread1.messages[0].user_id).to.equal(this.user_id1)
expect(thread1.messages[1].content).to.equal('three')
expect(thread1.messages[1].user_id).to.equal(this.user_id1)
expect(thread2.messages[0].content).to.equal('two')
expect(thread2.messages[0].user_id).to.equal(this.user_id2)
expect(thread2.messages[1].content).to.equal('four')
expect(thread2.messages[1].user_id).to.equal(this.user_id2)
return done()
}
)
})
})
})

View file

@ -0,0 +1,147 @@
/* eslint-disable
camelcase,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { ObjectId } = require('../../../app/js/mongojs')
const { expect } = require('chai')
const crypto = require('crypto')
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
describe('Resolving a thread', function() {
before(function(done) {
this.project_id = ObjectId().toString()
this.user_id = ObjectId().toString()
return ChatApp.ensureRunning(done)
})
describe('with a resolved thread', function() {
before(function(done) {
this.thread_id = ObjectId().toString()
this.content = 'resolved message'
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
this.content,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(201)
return ChatClient.resolveThread(
this.project_id,
this.thread_id,
this.user_id,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(204)
return done()
}
)
}
)
})
return it('should then list the thread as resolved', function(done) {
return ChatClient.getThreads(
this.project_id,
(error, response, threads) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(200)
expect(threads[this.thread_id].resolved).to.equal(true)
expect(threads[this.thread_id].resolved_by_user_id).to.equal(
this.user_id
)
const resolved_at = new Date(threads[this.thread_id].resolved_at)
expect(new Date() - resolved_at).to.be.below(1000)
return done()
}
)
})
})
describe('when a thread is not resolved', function() {
before(function(done) {
this.thread_id = ObjectId().toString()
this.content = 'open message'
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
this.content,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(201)
return done()
}
)
})
return it('should not list the thread as resolved', function(done) {
return ChatClient.getThreads(
this.project_id,
(error, response, threads) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(200)
expect(threads[this.thread_id].resolved).to.be.undefined
return done()
}
)
})
})
return describe('when a thread is resolved then reopened', function() {
before(function(done) {
this.thread_id = ObjectId().toString()
this.content = 'resolved message'
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
this.content,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(201)
return ChatClient.resolveThread(
this.project_id,
this.thread_id,
this.user_id,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(204)
return ChatClient.reopenThread(
this.project_id,
this.thread_id,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(204)
return done()
}
)
}
)
}
)
})
return it('should not list the thread as resolved', function(done) {
return ChatClient.getThreads(
this.project_id,
(error, response, threads) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(200)
expect(threads[this.thread_id].resolved).to.be.undefined
return done()
}
)
})
})
})

View file

@ -0,0 +1,190 @@
/* eslint-disable
handle-callback-err,
max-len,
no-return-assign,
node/no-deprecated-api,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { ObjectId } = require('../../../app/js/mongojs')
const { expect } = require('chai')
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
describe('Sending a message', function() {
before(done => ChatApp.ensureRunning(done))
describe('globally', function() {
before(function(done) {
this.project_id = ObjectId().toString()
this.user_id = ObjectId().toString()
this.content = 'global message'
return ChatClient.sendGlobalMessage(
this.project_id,
this.user_id,
this.content,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(201)
expect(body.content).to.equal(this.content)
expect(body.user_id).to.equal(this.user_id)
expect(body.room_id).to.equal(this.project_id)
return done()
}
)
})
return it('should then list the message in the project messages', function(done) {
return ChatClient.getGlobalMessages(
this.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(this.content)
return done()
}
)
})
})
describe('to a thread', function() {
before(function(done) {
this.project_id = ObjectId().toString()
this.user_id = ObjectId().toString()
this.thread_id = ObjectId().toString()
this.content = 'thread message'
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
this.content,
(error, response, body) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(201)
expect(body.content).to.equal(this.content)
expect(body.user_id).to.equal(this.user_id)
expect(body.room_id).to.equal(this.project_id)
return done()
}
)
})
it('should then list the message in the threads', function(done) {
return ChatClient.getThreads(
this.project_id,
(error, response, threads) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(200)
expect(threads[this.thread_id].messages.length).to.equal(1)
expect(threads[this.thread_id].messages[0].content).to.equal(
this.content
)
return done()
}
)
})
return it('should not appear in the global messages', function(done) {
return ChatClient.getGlobalMessages(
this.project_id,
(error, response, messages) => {
expect(error).to.be.null
expect(response.statusCode).to.equal(200)
expect(messages.length).to.equal(0)
return done()
}
)
})
})
return describe('failure cases', function() {
before(function() {
this.project_id = ObjectId().toString()
this.user_id = ObjectId().toString()
return (this.thread_id = ObjectId().toString())
})
describe('with a malformed user_id', () =>
it('should return a graceful error', function(done) {
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
'malformed-user',
'content',
(error, response, body) => {
expect(response.statusCode).to.equal(400)
expect(body).to.equal('Invalid user_id')
return done()
}
)
}))
describe('with a malformed project_id', () =>
it('should return a graceful error', function(done) {
return ChatClient.sendMessage(
'malformed-project',
this.thread_id,
this.user_id,
'content',
(error, response, body) => {
expect(response.statusCode).to.equal(400)
expect(body).to.equal('Invalid project_id')
return done()
}
)
}))
describe('with a malformed thread_id', () =>
it('should return a graceful error', function(done) {
return ChatClient.sendMessage(
this.project_id,
'malformed-thread-id',
this.user_id,
'content',
(error, response, body) => {
expect(response.statusCode).to.equal(400)
expect(body).to.equal('Invalid thread_id')
return done()
}
)
}))
describe('with no content', () =>
it('should return a graceful error', function(done) {
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
null,
(error, response, body) => {
expect(response.statusCode).to.equal(400)
expect(body).to.equal('No content provided')
return done()
}
)
}))
return describe('with very long content', () =>
it('should return a graceful error', function(done) {
const content = new Buffer(10240).toString('hex')
return ChatClient.sendMessage(
this.project_id,
this.thread_id,
this.user_id,
content,
(error, response, body) => {
expect(response.statusCode).to.equal(400)
expect(body).to.equal('Content too long (> 10240 bytes)')
return done()
}
)
}))
})
})

View file

@ -0,0 +1,47 @@
/* eslint-disable
handle-callback-err,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const app = require('../../../../app')
require('logger-sharelatex').logger.level('error')
module.exports = {
running: false,
initing: false,
callbacks: [],
ensureRunning(callback) {
if (callback == null) {
callback = function(error) {}
}
if (this.running) {
return callback()
} else if (this.initing) {
return this.callbacks.push(callback)
} else {
this.initing = true
this.callbacks.push(callback)
return app.listen(3010, 'localhost', error => {
if (error != null) {
throw error
}
this.running = true
return (() => {
const result = []
for (callback of Array.from(this.callbacks)) {
result.push(callback())
}
return result
})()
})
}
}
}

View file

@ -0,0 +1,113 @@
/* eslint-disable
camelcase,
max-len,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const request = require('request').defaults({
baseUrl: 'http://localhost:3010'
})
module.exports = {
sendGlobalMessage(project_id, user_id, content, callback) {
return request.post(
{
url: `/project/${project_id}/messages`,
json: {
user_id,
content
}
},
callback
)
},
getGlobalMessages(project_id, callback) {
return request.get(
{
url: `/project/${project_id}/messages`,
json: true
},
callback
)
},
sendMessage(project_id, thread_id, user_id, content, callback) {
return request.post(
{
url: `/project/${project_id}/thread/${thread_id}/messages`,
json: {
user_id,
content
}
},
callback
)
},
getThreads(project_id, callback) {
return request.get(
{
url: `/project/${project_id}/threads`,
json: true
},
callback
)
},
resolveThread(project_id, thread_id, user_id, callback) {
return request.post(
{
url: `/project/${project_id}/thread/${thread_id}/resolve`,
json: {
user_id
}
},
callback
)
},
reopenThread(project_id, thread_id, callback) {
return request.post(
{
url: `/project/${project_id}/thread/${thread_id}/reopen`
},
callback
)
},
deleteThread(project_id, thread_id, callback) {
return request.del(
{
url: `/project/${project_id}/thread/${thread_id}`
},
callback
)
},
editMessage(project_id, thread_id, message_id, content, callback) {
return request.post(
{
url: `/project/${project_id}/thread/${thread_id}/messages/${message_id}/edit`,
json: {
content
}
},
callback
)
},
deleteMessage(project_id, thread_id, message_id, callback) {
return request.del(
{
url: `/project/${project_id}/thread/${thread_id}/messages/${message_id}`
},
callback
)
}
}