mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-24 21:12:38 -04:00
430 lines
13 KiB
JavaScript
430 lines
13 KiB
JavaScript
|
/* eslint-disable
|
||
|
camelcase,
|
||
|
handle-callback-err,
|
||
|
max-len,
|
||
|
no-unused-vars,
|
||
|
*/
|
||
|
// TODO: This file was created by bulk-decaffeinate.
|
||
|
// Fix any style issues and re-enable lint.
|
||
|
/*
|
||
|
* decaffeinate suggestions:
|
||
|
* DS102: Remove unnecessary code created because of implicit returns
|
||
|
* DS103: Rewrite code to no longer use __guard__
|
||
|
* DS207: Consider shorter variations of null checks
|
||
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||
|
*/
|
||
|
let ProjectDetailsHandler
|
||
|
const ProjectGetter = require('./ProjectGetter')
|
||
|
const UserGetter = require('../User/UserGetter')
|
||
|
const { Project } = require('../../models/Project')
|
||
|
const { ObjectId } = require('mongojs')
|
||
|
const logger = require('logger-sharelatex')
|
||
|
const tpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender')
|
||
|
const _ = require('underscore')
|
||
|
const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
|
||
|
const Errors = require('../Errors/Errors')
|
||
|
const ProjectTokenGenerator = require('./ProjectTokenGenerator')
|
||
|
const ProjectEntityHandler = require('./ProjectEntityHandler')
|
||
|
const ProjectHelper = require('./ProjectHelper')
|
||
|
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
|
||
|
const settings = require('settings-sharelatex')
|
||
|
|
||
|
module.exports = ProjectDetailsHandler = {
|
||
|
getDetails(project_id, callback) {
|
||
|
return ProjectGetter.getProject(
|
||
|
project_id,
|
||
|
{
|
||
|
name: true,
|
||
|
description: true,
|
||
|
compiler: true,
|
||
|
features: true,
|
||
|
owner_ref: true,
|
||
|
overleaf: true
|
||
|
},
|
||
|
function(err, project) {
|
||
|
if (err != null) {
|
||
|
logger.err({ err, project_id }, 'error getting project')
|
||
|
return callback(err)
|
||
|
}
|
||
|
if (project == null) {
|
||
|
return callback(new Errors.NotFoundError('project not found'))
|
||
|
}
|
||
|
return UserGetter.getUser(project.owner_ref, function(err, user) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
const details = {
|
||
|
name: project.name,
|
||
|
description: project.description,
|
||
|
compiler: project.compiler,
|
||
|
features:
|
||
|
(user != null ? user.features : undefined) ||
|
||
|
settings.defaultFeatures
|
||
|
}
|
||
|
|
||
|
if (project.overleaf != null) {
|
||
|
details.overleaf = project.overleaf
|
||
|
}
|
||
|
|
||
|
logger.log({ project_id, details }, 'getting project details')
|
||
|
return callback(err, details)
|
||
|
})
|
||
|
}
|
||
|
)
|
||
|
},
|
||
|
|
||
|
getProjectDescription(project_id, callback) {
|
||
|
return ProjectGetter.getProject(
|
||
|
project_id,
|
||
|
{ description: true },
|
||
|
(err, project) =>
|
||
|
callback(err, project != null ? project.description : undefined)
|
||
|
)
|
||
|
},
|
||
|
|
||
|
setProjectDescription(project_id, description, callback) {
|
||
|
const conditions = { _id: project_id }
|
||
|
const update = { description }
|
||
|
logger.log(
|
||
|
{ conditions, update, project_id, description },
|
||
|
'setting project description'
|
||
|
)
|
||
|
return Project.update(conditions, update, function(err) {
|
||
|
if (err != null) {
|
||
|
logger.err({ err }, 'something went wrong setting project description')
|
||
|
}
|
||
|
return callback(err)
|
||
|
})
|
||
|
},
|
||
|
|
||
|
transferOwnership(project_id, user_id, suffix, callback) {
|
||
|
if (suffix == null) {
|
||
|
suffix = ''
|
||
|
}
|
||
|
if (typeof suffix === 'function') {
|
||
|
callback = suffix
|
||
|
suffix = ''
|
||
|
}
|
||
|
return ProjectGetter.getProject(
|
||
|
project_id,
|
||
|
{ owner_ref: true, name: true },
|
||
|
function(err, project) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
if (project == null) {
|
||
|
return callback(new Errors.NotFoundError('project not found'))
|
||
|
}
|
||
|
if (project.owner_ref === user_id) {
|
||
|
return callback()
|
||
|
}
|
||
|
|
||
|
return UserGetter.getUser(user_id, function(err, user) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
if (user == null) {
|
||
|
return callback(new Errors.NotFoundError('user not found'))
|
||
|
}
|
||
|
|
||
|
// we make sure the user to which the project is transferred is not a collaborator for the project,
|
||
|
// this prevents any conflict during unique name generation
|
||
|
return CollaboratorsHandler.removeUserFromProject(
|
||
|
project_id,
|
||
|
user_id,
|
||
|
function(err) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
return ProjectDetailsHandler.generateUniqueName(
|
||
|
user_id,
|
||
|
project.name + suffix,
|
||
|
function(err, name) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
return Project.update(
|
||
|
{ _id: project_id },
|
||
|
{
|
||
|
$set: {
|
||
|
owner_ref: user_id,
|
||
|
name
|
||
|
}
|
||
|
},
|
||
|
function(err) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
return ProjectEntityHandler.flushProjectToThirdPartyDataStore(
|
||
|
project_id,
|
||
|
callback
|
||
|
)
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
)
|
||
|
})
|
||
|
}
|
||
|
)
|
||
|
},
|
||
|
|
||
|
renameProject(project_id, newName, callback) {
|
||
|
if (callback == null) {
|
||
|
callback = function() {}
|
||
|
}
|
||
|
return ProjectDetailsHandler.validateProjectName(newName, function(error) {
|
||
|
if (error != null) {
|
||
|
return callback(error)
|
||
|
}
|
||
|
logger.log({ project_id, newName }, 'renaming project')
|
||
|
return ProjectGetter.getProject(project_id, { name: true }, function(
|
||
|
err,
|
||
|
project
|
||
|
) {
|
||
|
if (err != null || project == null) {
|
||
|
logger.err(
|
||
|
{ err, project_id },
|
||
|
'error getting project or could not find it todo project rename'
|
||
|
)
|
||
|
return callback(err)
|
||
|
}
|
||
|
const oldProjectName = project.name
|
||
|
return Project.update(
|
||
|
{ _id: project_id },
|
||
|
{ name: newName },
|
||
|
(err, project) => {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
return tpdsUpdateSender.moveEntity(
|
||
|
{
|
||
|
project_id,
|
||
|
project_name: oldProjectName,
|
||
|
newProjectName: newName
|
||
|
},
|
||
|
callback
|
||
|
)
|
||
|
}
|
||
|
)
|
||
|
})
|
||
|
})
|
||
|
},
|
||
|
|
||
|
MAX_PROJECT_NAME_LENGTH: 150,
|
||
|
validateProjectName(name, callback) {
|
||
|
if (callback == null) {
|
||
|
callback = function(error) {}
|
||
|
}
|
||
|
if (name == null || name.length === 0) {
|
||
|
return callback(
|
||
|
new Errors.InvalidNameError('Project name cannot be blank')
|
||
|
)
|
||
|
} else if (name.length > this.MAX_PROJECT_NAME_LENGTH) {
|
||
|
return callback(new Errors.InvalidNameError('Project name is too long'))
|
||
|
} else if (name.indexOf('/') > -1) {
|
||
|
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')
|
||
|
)
|
||
|
} else {
|
||
|
return callback()
|
||
|
}
|
||
|
},
|
||
|
|
||
|
generateUniqueName(user_id, name, suffixes, callback) {
|
||
|
if (suffixes == null) {
|
||
|
suffixes = []
|
||
|
}
|
||
|
if (callback == null) {
|
||
|
callback = function(error, newName) {}
|
||
|
}
|
||
|
if (arguments.length === 3 && typeof suffixes === 'function') {
|
||
|
// make suffixes an optional argument
|
||
|
callback = suffixes
|
||
|
suffixes = []
|
||
|
}
|
||
|
return ProjectDetailsHandler.ensureProjectNameIsUnique(
|
||
|
user_id,
|
||
|
name,
|
||
|
suffixes,
|
||
|
callback
|
||
|
)
|
||
|
},
|
||
|
|
||
|
// 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) {
|
||
|
if (suffixes == null) {
|
||
|
suffixes = []
|
||
|
}
|
||
|
if (callback == null) {
|
||
|
callback = function(error, name, changed) {}
|
||
|
}
|
||
|
return ProjectGetter.findAllUsersProjects(user_id, { name: 1 }, function(
|
||
|
error,
|
||
|
allUsersProjectNames
|
||
|
) {
|
||
|
if (error != null) {
|
||
|
return callback(error)
|
||
|
}
|
||
|
// allUsersProjectNames is returned as a hash {owned: [name1, name2, ...], readOnly: [....]}
|
||
|
// collect all of the names and flatten them into a single array
|
||
|
const projectNameList = _.pluck(
|
||
|
_.flatten(_.values(allUsersProjectNames)),
|
||
|
'name'
|
||
|
)
|
||
|
return ProjectHelper.ensureNameIsUnique(
|
||
|
projectNameList,
|
||
|
name,
|
||
|
suffixes,
|
||
|
ProjectDetailsHandler.MAX_PROJECT_NAME_LENGTH,
|
||
|
callback
|
||
|
)
|
||
|
})
|
||
|
},
|
||
|
|
||
|
fixProjectName(name) {
|
||
|
if (name === '' || !name) {
|
||
|
name = 'Untitled'
|
||
|
}
|
||
|
if (name.indexOf('/') > -1) {
|
||
|
// v2 does not allow / in a project name
|
||
|
name = name.replace(/\//g, '-')
|
||
|
}
|
||
|
if (name.indexOf('\\') > -1) {
|
||
|
// backslashes in project name will prevent syncing to dropbox
|
||
|
name = name.replace(/\\/g, '')
|
||
|
}
|
||
|
if (name.length > this.MAX_PROJECT_NAME_LENGTH) {
|
||
|
name = name.substr(0, this.MAX_PROJECT_NAME_LENGTH)
|
||
|
}
|
||
|
return name
|
||
|
},
|
||
|
|
||
|
setPublicAccessLevel(project_id, newAccessLevel, callback) {
|
||
|
if (callback == null) {
|
||
|
callback = function() {}
|
||
|
}
|
||
|
logger.log({ project_id, level: newAccessLevel }, 'set public access level')
|
||
|
// 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
|
||
|
if (
|
||
|
project_id != null &&
|
||
|
newAccessLevel != null &&
|
||
|
_.include(
|
||
|
[
|
||
|
PublicAccessLevels.READ_ONLY,
|
||
|
PublicAccessLevels.READ_AND_WRITE,
|
||
|
PublicAccessLevels.PRIVATE,
|
||
|
PublicAccessLevels.TOKEN_BASED
|
||
|
],
|
||
|
newAccessLevel
|
||
|
)
|
||
|
) {
|
||
|
return Project.update(
|
||
|
{ _id: project_id },
|
||
|
{ publicAccesLevel: newAccessLevel },
|
||
|
err => callback(err)
|
||
|
)
|
||
|
}
|
||
|
},
|
||
|
|
||
|
ensureTokensArePresent(project_id, callback) {
|
||
|
if (callback == null) {
|
||
|
callback = function(err, tokens) {}
|
||
|
}
|
||
|
return ProjectGetter.getProject(project_id, { tokens: 1 }, function(
|
||
|
err,
|
||
|
project
|
||
|
) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
if (
|
||
|
project.tokens != null &&
|
||
|
project.tokens.readOnly != null &&
|
||
|
project.tokens.readAndWrite != null
|
||
|
) {
|
||
|
logger.log({ project_id }, 'project already has tokens')
|
||
|
return callback(null, project.tokens)
|
||
|
} else {
|
||
|
logger.log(
|
||
|
{
|
||
|
project_id,
|
||
|
has_tokens: project.tokens != null,
|
||
|
has_readOnly:
|
||
|
__guard__(
|
||
|
project != null ? project.tokens : undefined,
|
||
|
x => x.readOnly
|
||
|
) != null,
|
||
|
has_readAndWrite:
|
||
|
__guard__(
|
||
|
project != null ? project.tokens : undefined,
|
||
|
x1 => x1.readAndWrite
|
||
|
) != null
|
||
|
},
|
||
|
'generating tokens for project'
|
||
|
)
|
||
|
return ProjectDetailsHandler._generateTokens(project, function(err) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
return Project.update(
|
||
|
{ _id: project_id },
|
||
|
{ $set: { tokens: project.tokens } },
|
||
|
function(err) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
return callback(null, project.tokens)
|
||
|
}
|
||
|
)
|
||
|
})
|
||
|
}
|
||
|
})
|
||
|
},
|
||
|
|
||
|
_generateTokens(project, callback) {
|
||
|
if (callback == null) {
|
||
|
callback = function(err) {}
|
||
|
}
|
||
|
if (!project.tokens) {
|
||
|
project.tokens = {}
|
||
|
}
|
||
|
const { tokens } = project
|
||
|
if (tokens.readAndWrite == null) {
|
||
|
const { token, numericPrefix } = ProjectTokenGenerator.readAndWriteToken()
|
||
|
tokens.readAndWrite = token
|
||
|
tokens.readAndWritePrefix = numericPrefix
|
||
|
}
|
||
|
if (tokens.readOnly == null) {
|
||
|
return ProjectTokenGenerator.generateUniqueReadOnlyToken(function(
|
||
|
err,
|
||
|
token
|
||
|
) {
|
||
|
if (err != null) {
|
||
|
return callback(err)
|
||
|
}
|
||
|
tokens.readOnly = token
|
||
|
return callback()
|
||
|
})
|
||
|
} else {
|
||
|
return callback()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function __guard__(value, transform) {
|
||
|
return typeof value !== 'undefined' && value !== null
|
||
|
? transform(value)
|
||
|
: undefined
|
||
|
}
|