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

Merge pull request #938 from sharelatex/bg-fix-project-names-on-import

add support for creating unique project names
Brian Gough 8 лет назад
Родитель
Сommit
d6e0574be3

+ 32 - 0
services/web/app/coffee/Features/Project/ProjectDetailsHandler.coffee

@@ -67,6 +67,38 @@ module.exports = ProjectDetailsHandler =
 		else
 			return callback()
 
+	_addSuffixToProjectName: (name, suffix = '') ->
+		# append the suffix and truncate the project title if needed
+		truncatedLength = ProjectDetailsHandler.MAX_PROJECT_NAME_LENGTH - suffix.length
+		return name.substr(0, truncatedLength) + suffix
+
+	# FIXME: we should put a lock around this to make it completely safe, but we would need to do that at
+	# the point of project creation, rather than just checking the name at the start of the import.
+	# If we later move this check into ProjectCreationHandler we can ensure all new projects are created
+	# with a unique name.  But that requires thinking through how we would handle incoming projects from
+	# dropbox for example.
+	ensureProjectNameIsUnique: (user_id, name, suffixes = [], callback = (error, name, changed)->) ->
+		ProjectGetter.findAllUsersProjects user_id, {name: 1}, (error, allUsersProjectNames) ->
+			return callback(error) if error?
+			# allUsersProjectNames is returned as a hash {owned: [name1, name2, ...], readOnly: [....]}
+			# collect all of the names and flatten them into a single array
+			projectNameList = _.flatten(_.values(allUsersProjectNames))
+			# create a set of all project names
+			allProjectNames = new Set()
+			for projectName in projectNameList
+				allProjectNames.add(projectName)
+			isUnique = (x) -> !allProjectNames.has(x)
+			# check if the supplied name is already unique
+			if isUnique(name)
+				return callback(null, name, false)
+			# the name already exists, try adding the user-supplied suffixes to generate a unique name
+			for suffix in suffixes
+				candidateName = ProjectDetailsHandler._addSuffixToProjectName(name, suffix)
+				if isUnique(candidateName)
+					return callback(null, candidateName, true)
+			# we couldn't make the name unique, something is wrong
+			return callback new Errors.InvalidNameError("Project name could not be made unique")
+
 	setPublicAccessLevel : (project_id, newAccessLevel, callback = ->)->
 		logger.log project_id: project_id, level: newAccessLevel, "set public access level"
 		# DEPRECATED: `READ_ONLY` and `READ_AND_WRITE` are still valid in, but should no longer

+ 42 - 0
services/web/test/unit/coffee/Project/ProjectDetailsHandlerTests.coffee

@@ -155,6 +155,48 @@ describe 'ProjectDetailsHandler', ->
 				expect(error).to.not.exist
 				done()
 
+	describe "ensureProjectNameIsUnique", ->
+		beforeEach ->
+			@result = {
+				owned: ["name", "name1", "name11"]
+				readAndWrite: ["name2", "name22"]
+				readOnly: ["name3", "name33"]
+				tokenReadAndWrite: ["name4", "name44"]
+				tokenReadOnly: ["name5", "name55", "x".repeat(15)]
+			}
+			@ProjectGetter.findAllUsersProjects = sinon.stub().callsArgWith(2, null, @result)
+
+		it "should leave a unique name unchanged", (done) ->
+			@handler.ensureProjectNameIsUnique @user_id, "unique-name", ["-test-suffix"], (error, name, changed) ->
+				expect(name).to.equal "unique-name"
+				expect(changed).to.equal false
+				done()
+
+		it "should append a suffix to an existing name", (done) ->
+			@handler.ensureProjectNameIsUnique @user_id, "name1", ["-test-suffix"], (error, name, changed) ->
+				expect(name).to.equal "name1-test-suffix"
+				expect(changed).to.equal true
+				done()
+
+		it "should fallback to a second suffix when needed", (done) ->
+			@handler.ensureProjectNameIsUnique @user_id, "name1", ["1", "-test-suffix"], (error, name, changed) ->
+				expect(name).to.equal "name1-test-suffix"
+				expect(changed).to.equal true
+				done()
+
+		it "should truncate the name when append a suffix if the result is too long", (done) ->
+			@handler.MAX_PROJECT_NAME_LENGTH = 20
+			@handler.ensureProjectNameIsUnique @user_id, "x".repeat(15), ["-test-suffix"], (error, name, changed) ->
+				expect(name).to.equal "x".repeat(8) + "-test-suffix"
+				expect(changed).to.equal true
+				done()
+
+		it "should return an error if the name cannot be made unique", (done) ->
+			@handler.ensureProjectNameIsUnique @user_id, "name", ["1", "5", "55"], (error, name, changed) ->
+				expect(error).to.eql new Errors.InvalidNameError("Project name could not be made unique")
+				done()
+
+
 	describe "setPublicAccessLevel", ->
 		beforeEach ->
 			@ProjectModel.update.callsArgWith(2)