2014-02-12 05:23:40 -05:00
|
|
|
ProjectGetter = require("./ProjectGetter")
|
2014-03-28 08:47:15 -04:00
|
|
|
UserGetter = require("../User/UserGetter")
|
2014-02-12 05:23:40 -05:00
|
|
|
Project = require('../../models/Project').Project
|
2018-11-14 09:42:21 -05:00
|
|
|
ObjectId = require("mongojs").ObjectId
|
2014-02-12 05:23:40 -05:00
|
|
|
logger = require("logger-sharelatex")
|
2014-04-07 10:37:40 -04:00
|
|
|
tpdsUpdateSender = require '../ThirdPartyDataStore/TpdsUpdateSender'
|
2014-04-07 11:07:44 -04:00
|
|
|
_ = require("underscore")
|
2016-03-15 10:35:01 -04:00
|
|
|
PublicAccessLevels = require("../Authorization/PublicAccessLevels")
|
2017-03-28 05:12:52 -04:00
|
|
|
Errors = require("../Errors/Errors")
|
2017-10-04 11:31:24 -04:00
|
|
|
ProjectTokenGenerator = require('./ProjectTokenGenerator')
|
2019-03-22 08:14:49 -04:00
|
|
|
ProjectEntityHandler = require('./ProjectEntityHandler')
|
2018-12-06 05:52:32 -05:00
|
|
|
ProjectHelper = require('./ProjectHelper')
|
2019-01-08 07:45:26 -05:00
|
|
|
settings = require('settings-sharelatex')
|
2014-02-12 05:23:40 -05:00
|
|
|
|
2019-02-11 06:24:23 -05:00
|
|
|
|
2017-05-19 11:21:02 -04:00
|
|
|
module.exports = ProjectDetailsHandler =
|
2014-02-12 05:23:40 -05:00
|
|
|
getDetails: (project_id, callback)->
|
2017-09-26 05:19:30 -04:00
|
|
|
ProjectGetter.getProject project_id, {name:true, description:true, compiler:true, features:true, owner_ref:true, overleaf:true}, (err, project)->
|
2017-03-28 05:12:52 -04:00
|
|
|
if err?
|
2014-02-12 05:23:40 -05:00
|
|
|
logger.err err:err, project_id:project_id, "error getting project"
|
|
|
|
return callback(err)
|
2017-03-28 05:12:52 -04:00
|
|
|
return callback(new Errors.NotFoundError("project not found")) if !project?
|
2014-03-28 08:47:15 -04:00
|
|
|
UserGetter.getUser project.owner_ref, (err, user) ->
|
|
|
|
return callback(err) if err?
|
|
|
|
details =
|
|
|
|
name : project.name
|
|
|
|
description: project.description
|
|
|
|
compiler: project.compiler
|
2019-01-08 07:45:26 -05:00
|
|
|
features: user?.features or settings.defaultFeatures
|
2017-09-26 05:19:30 -04:00
|
|
|
|
|
|
|
if project.overleaf?
|
|
|
|
details.overleaf = project.overleaf
|
|
|
|
|
|
|
|
logger.log project_id:project_id, details: details, "getting project details"
|
2014-03-28 08:47:15 -04:00
|
|
|
callback(err, details)
|
2014-02-12 05:23:40 -05:00
|
|
|
|
2014-07-16 06:27:47 -04:00
|
|
|
getProjectDescription: (project_id, callback)->
|
2018-02-15 07:18:43 -05:00
|
|
|
ProjectGetter.getProject project_id, description: true, (err, project)->
|
2014-07-16 06:56:22 -04:00
|
|
|
callback(err, project?.description)
|
2014-07-16 06:27:47 -04:00
|
|
|
|
2014-02-12 05:23:40 -05:00
|
|
|
setProjectDescription: (project_id, description, callback)->
|
|
|
|
conditions = _id:project_id
|
|
|
|
update = description:description
|
|
|
|
logger.log conditions:conditions, update:update, project_id:project_id, description:description, "setting project description"
|
|
|
|
Project.update conditions, update, (err)->
|
|
|
|
if err?
|
|
|
|
logger.err err:err, "something went wrong setting project description"
|
|
|
|
callback(err)
|
2014-04-07 10:37:40 -04:00
|
|
|
|
2019-05-16 06:11:42 -04:00
|
|
|
transferOwnership: (project_id, user_id, suffix = "", callback)->
|
|
|
|
if typeof suffix is 'function'
|
|
|
|
callback = suffix
|
|
|
|
suffix = ''
|
|
|
|
ProjectGetter.getProject project_id, {owner_ref: true}, (err, project)->
|
2019-03-22 08:14:49 -04:00
|
|
|
return callback(err) if err?
|
|
|
|
return callback(new Errors.NotFoundError("project not found")) unless project?
|
|
|
|
return callback() if project.owner_ref == user_id
|
|
|
|
|
|
|
|
UserGetter.getUser user_id, (err, user) ->
|
|
|
|
return callback(err) if err?
|
|
|
|
return callback(new Errors.NotFoundError("user not found")) unless user?
|
|
|
|
|
2019-05-16 06:11:42 -04:00
|
|
|
ProjectDetailsHandler.generateUniqueName user_id, project.name + suffix, (err, name) ->
|
2019-04-23 10:21:13 -04:00
|
|
|
return callback(err) if err?
|
|
|
|
|
|
|
|
Project.update {_id: project_id},
|
|
|
|
{
|
|
|
|
$set: {
|
|
|
|
owner_ref: user_id,
|
|
|
|
name: name
|
|
|
|
},
|
|
|
|
$pull: {
|
|
|
|
readOnly_refs: user_id,
|
|
|
|
collaberator_refs: user_id,
|
|
|
|
tokenAccessReadAndWrite_refs: user_id,
|
|
|
|
tokenAccessReadOnly_refs: user_id
|
|
|
|
}}, (err) ->
|
|
|
|
return callback(err) if err?
|
|
|
|
ProjectEntityHandler.flushProjectToThirdPartyDataStore project_id, callback
|
2019-03-22 08:14:49 -04:00
|
|
|
|
2014-04-07 10:37:40 -04:00
|
|
|
renameProject: (project_id, newName, callback = ->)->
|
2017-05-19 11:21:02 -04:00
|
|
|
ProjectDetailsHandler.validateProjectName newName, (error) ->
|
|
|
|
return callback(error) if error?
|
|
|
|
logger.log project_id: project_id, newName:newName, "renaming project"
|
|
|
|
ProjectGetter.getProject project_id, {name:true}, (err, project)->
|
|
|
|
if err? or !project?
|
|
|
|
logger.err err:err, project_id:project_id, "error getting project or could not find it todo project rename"
|
2014-04-07 10:37:40 -04:00
|
|
|
return callback(err)
|
2017-05-19 11:21:02 -04:00
|
|
|
oldProjectName = project.name
|
|
|
|
Project.update _id:project_id, {name: newName}, (err, project)=>
|
|
|
|
if err?
|
|
|
|
return callback(err)
|
|
|
|
tpdsUpdateSender.moveEntity {project_id:project_id, project_name:oldProjectName, newProjectName:newName}, callback
|
|
|
|
|
|
|
|
MAX_PROJECT_NAME_LENGTH: 150
|
|
|
|
validateProjectName: (name, callback = (error) ->) ->
|
2017-10-12 11:03:12 -04:00
|
|
|
if !name? or name.length == 0
|
2017-05-19 11:21:02 -04:00
|
|
|
return callback(new Errors.InvalidNameError("Project name cannot be blank"))
|
|
|
|
else if name.length > @MAX_PROJECT_NAME_LENGTH
|
|
|
|
return callback(new Errors.InvalidNameError("Project name is too long"))
|
|
|
|
else if name.indexOf("/") > -1
|
2018-11-06 03:20:19 -05:00
|
|
|
return callback(new Errors.InvalidNameError("Project name cannot contain / characters"))
|
|
|
|
else if name.indexOf("\\") > -1
|
|
|
|
return callback(new Errors.InvalidNameError("Project name cannot contain \\ characters"))
|
2017-05-19 11:21:02 -04:00
|
|
|
else
|
|
|
|
return callback()
|
2014-04-07 11:07:44 -04:00
|
|
|
|
2018-10-16 09:43:40 -04:00
|
|
|
generateUniqueName: (user_id, name, suffixes = [], callback = (error, newName) -> ) ->
|
|
|
|
if arguments.length is 3 && typeof suffixes is 'function' # make suffixes an optional argument
|
|
|
|
callback = suffixes
|
|
|
|
suffixes = []
|
2018-11-30 08:03:35 -05:00
|
|
|
ProjectDetailsHandler.ensureProjectNameIsUnique user_id, name, suffixes, callback
|
2018-09-14 06:08:03 -04:00
|
|
|
|
|
|
|
# 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)->) ->
|
2018-09-27 11:41:45 -04:00
|
|
|
ProjectGetter.findAllUsersProjects user_id, {name: 1}, (error, allUsersProjectNames) ->
|
2018-09-14 06:08:03 -04:00
|
|
|
return callback(error) if error?
|
2018-09-27 11:41:45 -04:00
|
|
|
# allUsersProjectNames is returned as a hash {owned: [name1, name2, ...], readOnly: [....]}
|
|
|
|
# collect all of the names and flatten them into a single array
|
2018-10-12 07:10:35 -04:00
|
|
|
projectNameList = _.pluck(_.flatten(_.values(allUsersProjectNames)),'name')
|
2018-12-06 05:52:32 -05:00
|
|
|
ProjectHelper.ensureNameIsUnique projectNameList, name, suffixes, ProjectDetailsHandler.MAX_PROJECT_NAME_LENGTH, callback
|
2018-09-25 06:15:32 -04:00
|
|
|
|
|
|
|
fixProjectName: (name) ->
|
2018-11-14 09:42:21 -05:00
|
|
|
if name == "" || !name
|
2018-09-25 06:15:32 -04:00
|
|
|
name = "Untitled"
|
|
|
|
if name.indexOf('/') > -1
|
|
|
|
# v2 does not allow / in a project name
|
|
|
|
name = name.replace(/\//g, '-')
|
2018-11-06 03:20:19 -05:00
|
|
|
if name.indexOf('\\') > -1
|
|
|
|
# backslashes in project name will prevent syncing to dropbox
|
|
|
|
name = name.replace(/\\/g, '')
|
2018-09-25 06:15:32 -04:00
|
|
|
if name.length > @MAX_PROJECT_NAME_LENGTH
|
|
|
|
name = name.substr(0, @MAX_PROJECT_NAME_LENGTH)
|
|
|
|
return name
|
2018-09-14 06:08:03 -04:00
|
|
|
|
2014-04-07 11:07:44 -04:00
|
|
|
setPublicAccessLevel : (project_id, newAccessLevel, callback = ->)->
|
|
|
|
logger.log project_id: project_id, level: newAccessLevel, "set public access level"
|
2017-10-12 06:00:39 -04:00
|
|
|
# DEPRECATED: `READ_ONLY` and `READ_AND_WRITE` are still valid in, but should no longer
|
|
|
|
# be passed here. Remove after token-based access has been live for a while
|
2017-10-04 11:31:24 -04:00
|
|
|
if project_id? && newAccessLevel? and _.include [
|
|
|
|
PublicAccessLevels.READ_ONLY,
|
|
|
|
PublicAccessLevels.READ_AND_WRITE,
|
|
|
|
PublicAccessLevels.PRIVATE,
|
|
|
|
PublicAccessLevels.TOKEN_BASED
|
|
|
|
], newAccessLevel
|
2014-04-07 11:07:44 -04:00
|
|
|
Project.update {_id:project_id},{publicAccesLevel:newAccessLevel}, (err)->
|
2017-10-26 11:39:24 -04:00
|
|
|
callback(err)
|
2017-10-04 11:31:24 -04:00
|
|
|
|
|
|
|
ensureTokensArePresent: (project_id, callback=(err, tokens)->) ->
|
|
|
|
ProjectGetter.getProject project_id, {tokens: 1}, (err, project) ->
|
|
|
|
return callback(err) if err?
|
|
|
|
if project.tokens? and project.tokens.readOnly? and project.tokens.readAndWrite?
|
2017-10-12 06:25:16 -04:00
|
|
|
logger.log {project_id}, "project already has tokens"
|
2017-10-04 11:31:24 -04:00
|
|
|
return callback(null, project.tokens)
|
|
|
|
else
|
2017-10-12 06:25:16 -04:00
|
|
|
logger.log {
|
|
|
|
project_id,
|
|
|
|
has_tokens: project.tokens?,
|
|
|
|
has_readOnly: project?.tokens?.readOnly?,
|
|
|
|
has_readAndWrite: project?.tokens?.readAndWrite?
|
|
|
|
}, "generating tokens for project"
|
2019-02-11 06:24:23 -05:00
|
|
|
ProjectDetailsHandler._generateTokens project, (err) ->
|
2017-10-04 11:31:24 -04:00
|
|
|
return callback(err) if err?
|
2019-02-11 06:24:23 -05:00
|
|
|
Project.update {_id: project_id}, {$set: {tokens: project.tokens}}, (err) ->
|
|
|
|
return callback(err) if err?
|
|
|
|
callback(null, project.tokens)
|
|
|
|
|
|
|
|
_generateTokens: (project, callback=(err)->) ->
|
|
|
|
project.tokens ||= {}
|
|
|
|
tokens = project.tokens
|
|
|
|
if !tokens.readAndWrite?
|
2019-02-14 05:45:48 -05:00
|
|
|
{ token, numericPrefix } = ProjectTokenGenerator.readAndWriteToken()
|
|
|
|
tokens.readAndWrite = token
|
|
|
|
tokens.readAndWritePrefix = numericPrefix
|
2019-02-11 06:24:23 -05:00
|
|
|
if !tokens.readOnly?
|
|
|
|
ProjectTokenGenerator.generateUniqueReadOnlyToken (err, token) ->
|
|
|
|
return callback(err) if err?
|
|
|
|
tokens.readOnly = token
|
|
|
|
callback()
|
|
|
|
else
|
|
|
|
callback()
|