Create joinProject socket.io endpoint

This commit is contained in:
James Allen 2014-11-10 11:27:08 +00:00
parent fbf983c2ff
commit 02c0a3a867
10 changed files with 294 additions and 4 deletions

View file

@ -1,5 +1,6 @@
Metrics = require "metrics-sharelatex"
logger = require "logger-sharelatex"
WebsocketController = require "./WebsocketController"
module.exports = Router =
configure: (app, io, session) ->
@ -14,7 +15,11 @@ module.exports = Router =
logger.log session: session, "got session"
user = session.user
if !user?
if !user? or !user._id?
logger.log "terminating session without authenticated user"
client.disconnect()
return
return
client.on "joinProject", (data = {}, callback) ->
WebsocketController.joinProject(client, user, data.project_id, callback)

View file

@ -0,0 +1,21 @@
request = require "request"
settings = require "settings-sharelatex"
logger = require "logger-sharelatex"
module.exports = WebApiManager =
joinProject: (project_id, user_id, callback = (error, project, privilegeLevel) ->) ->
logger.log {project_id, user_id}, "sending join project request to web"
url = "#{settings.apis.web.url}/project/#{project_id}/join"
request.post {
url: url
qs: {user_id}
json: true
jar: false
}, (error, response, data) ->
return callback(error) if error?
if 200 <= response.statusCode < 300
callback null, data?.project, data?.privilegeLevel
else
err = new Error("non-success status code from web: #{response.statusCode}")
logger.error {err, project_id, user_id}, "error accessing web api"
callback err

View file

@ -0,0 +1,31 @@
logger = require "logger-sharelatex"
WebApiManager = require "./WebApiManager"
module.exports = WebsocketController =
# If the protocol version changes when the client reconnects,
# it will force a full refresh of the page. Useful for non-backwards
# compatible protocol changes. Use only in extreme need.
PROTOCOL_VERSION: 2
joinProject: (client, user, project_id, callback = (error, project, privilegeLevel, protocolVersion) ->) ->
user_id = user?._id
logger.log {user_id, project_id}, "user joining project"
WebApiManager.joinProject project_id, user_id, (error, project, privilegeLevel) ->
return callback(error) if error?
if !privilegeLevel or privilegeLevel == ""
err = new Error("not authorized")
logger.error {err, project_id, user_id}, "user is not authorized to join project"
return callback(err)
client.set("user_id", user_id)
client.set("project_id", project_id)
client.set("owner_id", project?.owner?._id)
client.set("first_name", user?.first_name)
client.set("last_name", user?.last_name)
client.set("email", user?.email)
client.set("connected_time", new Date())
client.set("signup_date", user?.signUpDate)
client.set("login_count", user?.loginCount)
callback null, project, privilegeLevel, WebsocketController.PROTOCOL_VERSION

View file

@ -10,6 +10,10 @@ module.exports =
port: 3026
host: "localhost"
apis:
web:
url: "http://localhost:3000"
security:
sessionSecret: "secret-please-change"

View file

@ -34,6 +34,7 @@
"request": "~2.34.0",
"sandboxed-module": "~0.3.0",
"sinon": "~1.5.2",
"uid-safe": "^1.0.1"
"uid-safe": "^1.0.1",
"timekeeper": "0.0.4"
}
}

View file

@ -0,0 +1,44 @@
chai = require("chai")
expect = chai.expect
chai.should()
RealTimeClient = require "./helpers/RealTimeClient"
MockWebClient = require "./helpers/MockWebClient"
describe "joinProject", ->
before (done) ->
@user_id = "mock-user-id"
@project_id = "mock-project-id"
privileges = {}
privileges[@user_id] = "owner"
MockWebClient.createMockProject(@project_id, privileges, {
name: "Test Project"
})
MockWebClient.run (error) =>
throw error if error?
RealTimeClient.setSession {
user: { _id: @user_id }
}, (error) =>
throw error if error?
@client = RealTimeClient.connect()
@client.emit "joinProject", {
project_id: @project_id
}, (error, @project, @privilegeLevel, @protocolVersion) =>
throw error if error?
done()
it "should get the project from web", ->
MockWebClient.joinProject
.calledWith(@project_id, @user_id)
.should.equal true
it "should return the project", ->
@project.should.deep.equal {
name: "Test Project"
}
it "should return the privilege level", ->
@privilegeLevel.should.equal "owner"
it "should return the protocolVersion", ->
@protocolVersion.should.equal 2

View file

@ -6,6 +6,7 @@ RealTimeClient = require "./helpers/RealTimeClient"
describe "Session", ->
describe "with an established session", ->
beforeEach (done) ->
@user_id = "mock-user-id"
RealTimeClient.setSession {
user: { _id: @user_id }
}, (error) =>
@ -33,7 +34,7 @@ describe "Session", ->
@client.on "disconnect", () ->
done()
describe "with a user set on the session", ->
describe "without a valid user set on the session", ->
beforeEach (done) ->
RealTimeClient.setSession {
foo: "bar"

View file

@ -0,0 +1,39 @@
sinon = require "sinon"
express = require "express"
module.exports = MockWebClient =
projects: {}
privileges: {}
createMockProject: (project_id, privileges, project) ->
MockWebClient.privileges[project_id] = privileges
MockWebClient.projects[project_id] = project
joinProject: (project_id, user_id, callback = (error, project, privilegeLevel) ->) ->
callback(
null,
MockWebClient.projects[project_id],
MockWebClient.privileges[project_id][user_id]
)
joinProjectRequest: (req, res, next) ->
{project_id} = req.params
{user_id} = req.query
MockWebClient.joinProject project_id, user_id, (error, project, privilegeLevel) ->
return next(error) if error?
res.json {
project: project
privilegeLevel: privilegeLevel
}
running: false
run: (callback = (error) ->) ->
if MockWebClient.running
return callback()
app = express()
app.post "/project/:project_id/join", MockWebClient.joinProjectRequest
app.listen 3000, (error) ->
MockWebClient.running = true
callback(error)
sinon.spy MockWebClient, "joinProject"

View file

@ -0,0 +1,55 @@
chai = require('chai')
should = chai.should()
sinon = require("sinon")
modulePath = "../../../app/js/WebApiManager.js"
SandboxedModule = require('sandboxed-module')
describe 'WebApiManager', ->
beforeEach ->
@project_id = "project-id-123"
@user_id = "user-id-123"
@callback = sinon.stub()
@WebApiManager = SandboxedModule.require modulePath, requires:
"request": @request = {}
"settings-sharelatex": @settings =
apis:
web:
url: "http://web.example.com"
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
describe "joinProject", ->
describe "successfully", ->
beforeEach ->
@response = {
project: { name: "Test project" }
privilegeLevel: "owner"
}
@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 200}, @response)
@WebApiManager.joinProject @project_id, @user_id, @callback
it "should send a request to web to join the project", ->
@request.post
.calledWith({
url: "#{@settings.apis.web.url}/project/#{@project_id}/join"
qs:
user_id: @user_id
json: true
jar: false
})
.should.equal true
it "should return the project and privilegeLevel", ->
@callback
.calledWith(null, @response.project, @response.privilegeLevel)
.should.equal true
describe "with an error from web", ->
beforeEach ->
@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 500}, null)
@WebApiManager.joinProject @project_id, @user_id, @callback
it "should call the callback with an error", ->
@callback
.calledWith(new Error("non-success code from web: 500"))
.should.equal true

View file

@ -0,0 +1,89 @@
chai = require('chai')
should = chai.should()
sinon = require("sinon")
modulePath = "../../../app/js/WebsocketController.js"
SandboxedModule = require('sandboxed-module')
tk = require "timekeeper"
describe 'WebsocketController', ->
beforeEach ->
tk.freeze(new Date())
@project_id = "project-id-123"
@user = {
_id: "user-id-123"
first_name: "James"
last_name: "Allen"
email: "james@example.com"
signUpDate: new Date("2014-01-01")
loginCount: 42
}
@callback = sinon.stub()
@client =
set: sinon.stub()
join: sinon.stub()
@WebsocketController = SandboxedModule.require modulePath, requires:
"./WebApiManager": @WebApiManager = {}
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
afterEach ->
tk.reset()
describe "joinProject", ->
describe "when authorised", ->
beforeEach ->
@project = {
name: "Test Project"
owner: {
_id: @owner_id = "mock-owner-id-123"
}
}
@privilegeLevel = "owner"
@WebApiManager.joinProject = sinon.stub().callsArgWith(2, null, @project, @privilegeLevel)
@WebsocketController.joinProject @client, @user, @project_id, @callback
it "should load the project from web", ->
@WebApiManager.joinProject
.calledWith(@project_id, @user._id)
.should.equal true
it "should set the user's id on the client", ->
@client.set.calledWith("user_id", @user._id).should.equal true
it "should set the user's email on the client", ->
@client.set.calledWith("email", @user.email).should.equal true
it "should set the user's first_name on the client", ->
@client.set.calledWith("first_name", @user.first_name).should.equal true
it "should set the user's last_name on the client", ->
@client.set.calledWith("last_name", @user.last_name).should.equal true
it "should set the user's sign up date on the client", ->
@client.set.calledWith("signup_date", @user.signUpDate).should.equal true
it "should set the user's login_count on the client", ->
@client.set.calledWith("login_count", @user.loginCount).should.equal true
it "should set the connected time on the client", ->
@client.set.calledWith("connected_time", new Date()).should.equal true
it "should set the project_id on the client", ->
@client.set.calledWith("project_id", @project_id).should.equal true
it "should set the project owner id on the client", ->
@client.set.calledWith("owner_id", @owner_id).should.equal true
it "should call the callback with the project, privilegeLevel and protocolVersion", ->
@callback
.calledWith(null, @project, @privilegeLevel, @WebsocketController.PROTOCOL_VERSION)
.should.equal true
describe "when not authorized", ->
beforeEach ->
@WebApiManager.joinProject = sinon.stub().callsArgWith(2, null, null, null)
@WebsocketController.joinProject @client, @user, @project_id, @callback
it "should return an error", ->
@callback
.calledWith(new Error("not authorized"))
.should.equal true