Просмотр исходного кода

Create joinProject socket.io endpoint

James Allen 12 лет назад
Родитель
Сommit
02c0a3a867

+ 7 - 2
services/real-time/app/coffee/Router.coffee

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

+ 21 - 0
services/real-time/app/coffee/WebApiManager.coffee

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

+ 31 - 0
services/real-time/app/coffee/WebsocketController.coffee

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

+ 4 - 0
services/real-time/config/settings.defaults.coffee

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

+ 2 - 1
services/real-time/package.json

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

+ 44 - 0
services/real-time/test/acceptance/coffee/JoinProjectTests.coffee

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

+ 2 - 1
services/real-time/test/acceptance/coffee/SessionTests.coffee

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

+ 39 - 0
services/real-time/test/acceptance/coffee/helpers/MockWebClient.coffee

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

+ 55 - 0
services/real-time/test/unit/coffee/WebApiManagerTests.coffee

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

+ 89 - 0
services/real-time/test/unit/coffee/WebsocketControllerTests.coffee

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