فهرست منبع

Add method to transfer projects from one user_id to another

James Allen 9 سال پیش
والد
کامیت
9c1faa4395

+ 70 - 0
services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee

@@ -157,3 +157,73 @@ module.exports = CollaboratorsHandler =
 				return callback(error)
 			{owner, members} = ProjectEditorHandler.buildOwnerAndMembersViews(rawMembers)
 			callback(null, members)
+
+	transferProjects: (from_user_id, to_user_id, callback=(err, projects) ->) ->
+		MEMBER_KEYS = ['collaberator_refs', 'readOnly_refs']
+
+		# jobs = []
+		# project_ids = []
+		# jobs.push (cb) ->
+		# 	Project.find { owner_ref: from_user_id }, { _id: 1 }, (error, projects = []) ->
+		# 		return callback(error) if error?
+		# 		project_ids = project_ids.concat(projects.map (p) -> p._id)
+		# 		Project.update { owner_ref: from_user_id }, { $set: { owner_ref: to_user_id }}, { multi: true }, cb
+
+		# for key in MEMBER_KEYS
+		# 	do (key) ->
+		# 		jobs.push (cb) ->
+		# 			query = {}
+		# 			query[]
+		# 			Project.find { owner_ref: from_user_id }, { _id: 1 }, (error, projects = []) ->
+		# 		return callback(error) if error?
+		# 		project_ids = project_ids.concat(projects.map (p) -> p._id)
+		# 		Project.update { owner_ref: from_user_id }, { $set: { owner_ref: to_user_id }}, { multi: true }, cb
+
+		# Find all the projects this user is part of so we can flush them to TPDS
+		query =
+			$or:
+				[{ owner_ref: from_user_id }]
+				.concat(
+					MEMBER_KEYS.map (key) ->
+						q = {}
+						q[key] = from_user_id
+						return q
+				) # [{ collaberator_refs: from_user_id }, ...]
+		Project.find query, { _id: 1 }, (error, projects = []) ->
+			return callback(error) if error?
+
+			project_ids = projects.map (p) -> p._id
+			logger.log {project_ids, from_user_id, to_user_id}, "transferring projects"
+
+			update_jobs = []
+			update_jobs.push (cb) ->
+				Project.update { owner_ref: from_user_id }, { $set: { owner_ref: to_user_id }}, { multi: true }, cb
+			for key in MEMBER_KEYS
+				do (key) ->
+					update_jobs.push (cb) ->
+						query = {}
+						addNewUserUpdate = $addToSet: {}
+						removeOldUserUpdate = $pull: {}
+						query[key] = from_user_id
+						removeOldUserUpdate.$pull[key] = from_user_id
+						addNewUserUpdate.$addToSet[key] = to_user_id
+						# Mongo won't let us pull and addToSet in the same query, so do it in
+						# two. Note we need to add first, since the query is based on the old user.
+						Project.update query, addNewUserUpdate, { multi: true }, (error) ->
+							return cb(error) if error?
+							Project.update query, removeOldUserUpdate, { multi: true }, cb
+
+			# Flush each project to TPDS to add files to new user's Dropbox
+			ProjectEntityHandler = require("../Project/ProjectEntityHandler")
+			flush_jobs = []
+			for project_id in project_ids
+				do (project_id) ->
+					flush_jobs.push (cb) ->
+						ProjectEntityHandler.flushProjectToThirdPartyDataStore project_id, cb
+
+			# Flush in background, no need to block on this
+			async.series flush_jobs, (error) ->
+				if error?
+					logger.err {err: error, project_ids, from_user_id, to_user_id}, "error flushing tranferred projects to TPDS"
+
+			async.series update_jobs, callback

+ 100 - 0
services/web/test/UnitTests/coffee/Collaborators/CollaboratorsHandlerTests.coffee

@@ -344,3 +344,103 @@ describe "CollaboratorsHandler", ->
 
 			it 'should not call ProjectEditorHandler.buildOwnerAndMembersViews', ->
 				@ProjectEditorHandler.buildOwnerAndMembersViews.callCount.should.equal 0
+
+	describe 'transferProjects', ->
+		beforeEach ->
+			@from_user_id = "from-user-id"
+			@to_user_id = "to-user-id"
+			@projects = [{
+				_id: "project-id-1"
+			}, {
+				_id: "project-id-2"
+			}]
+			@Project.find = sinon.stub().yields(null, @projects)
+			@Project.update = sinon.stub().yields()
+			@ProjectEntityHandler.flushProjectToThirdPartyDataStore = sinon.stub().yields()
+
+		describe "successfully", ->
+			beforeEach ->
+				@CollaboratorHandler.transferProjects @from_user_id, @to_user_id, @callback
+
+			it "should look up the affected projects", ->
+				@Project.find
+					.calledWith({
+						$or : [
+							{ owner_ref: @from_user_id }
+							{ collaberator_refs: @from_user_id }
+							{ readOnly_refs: @from_user_id }
+						]
+					})
+					.should.equal true
+
+			it "should transfer owned projects", ->
+				@Project.update
+					.calledWith({
+						owner_ref: @from_user_id	
+					}, {
+						$set: { owner_ref: @to_user_id }
+					}, {
+						multi: true
+					})
+					.should.equal true
+
+			it "should transfer collaborator projects", ->
+				@Project.update
+					.calledWith({
+						collaberator_refs: @from_user_id	
+					}, {
+						$addToSet: { collaberator_refs: @to_user_id }
+					}, {
+						multi: true
+					})
+					.should.equal true
+				@Project.update
+					.calledWith({
+						collaberator_refs: @from_user_id	
+					}, {
+						$pull: { collaberator_refs: @from_user_id }
+					}, {
+						multi: true
+					})
+					.should.equal true
+
+			it "should transfer read only collaborator projects", ->
+				@Project.update
+					.calledWith({
+						readOnly_refs: @from_user_id	
+					}, {
+						$addToSet: { readOnly_refs: @to_user_id }
+					}, {
+						multi: true
+					})
+					.should.equal true
+				@Project.update
+					.calledWith({
+						readOnly_refs: @from_user_id	
+					}, {
+						$pull: { readOnly_refs: @from_user_id }
+					}, {
+						multi: true
+					})
+					.should.equal true
+
+			it "should flush each project to the TPDS", ->
+				for project in @projects
+					@ProjectEntityHandler.flushProjectToThirdPartyDataStore
+						.calledWith(project._id)
+						.should.equal true
+
+			it "should call the callback", ->
+				@callback.called.should.equal true
+
+		describe "when flushing to TPDS fails", ->
+			beforeEach ->
+				@ProjectEntityHandler.flushProjectToThirdPartyDataStore = sinon.stub().yields(new Error('oops'))
+				@CollaboratorHandler.transferProjects @from_user_id, @to_user_id, @callback
+
+			it "should log an error", ->
+				@logger.err.called.should.equal true
+
+			it "should not return an error since it happens in the background", ->
+				@callback.called.should.equal true
+				@callback.calledWith(new Error('oops')).should.equal false