mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-31 04:41:32 +02:00
Merge pull request #1717 from overleaf/as-decaffeinate-backend
Decaffeinate backend GitOrigin-RevId: 4ca9f94fc809cab6f47cec8254cacaf1bb3806fa
This commit is contained in:
committed by
sharelatex
parent
d4eb71b525
commit
0ca81de78c
@@ -0,0 +1,888 @@
|
||||
/* eslint-disable
|
||||
camelcase,
|
||||
handle-callback-err,
|
||||
max-len,
|
||||
one-var,
|
||||
standard/no-callback-literal,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS103: Rewrite code to no longer use __guard__
|
||||
* DS201: Simplify complex destructure assignments
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let ProjectEntityMongoUpdateHandler, self
|
||||
const _ = require('underscore')
|
||||
const async = require('async')
|
||||
const logger = require('logger-sharelatex')
|
||||
const path = require('path')
|
||||
const settings = require('settings-sharelatex')
|
||||
const CooldownManager = require('../Cooldown/CooldownManager')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const { Folder } = require('../../models/Folder')
|
||||
const LockManager = require('../../infrastructure/LockManager')
|
||||
const { Project } = require('../../models/Project')
|
||||
const ProjectEntityHandler = require('./ProjectEntityHandler')
|
||||
const ProjectGetter = require('./ProjectGetter')
|
||||
const ProjectLocator = require('./ProjectLocator')
|
||||
const SafePath = require('./SafePath')
|
||||
|
||||
const LOCK_NAMESPACE = 'mongoTransaction'
|
||||
|
||||
const wrapWithLock = function(methodWithoutLock) {
|
||||
// This lock is used whenever we read or write to an existing project's
|
||||
// structure. Some operations to project structure cannot be done atomically
|
||||
// in mongo, this lock is used to prevent reading the structure between two
|
||||
// parts of a staged update.
|
||||
const methodWithLock = function(project_id, ...rest) {
|
||||
const adjustedLength = Math.max(rest.length, 1),
|
||||
args = rest.slice(0, adjustedLength - 1),
|
||||
callback = rest[adjustedLength - 1]
|
||||
return LockManager.runWithLock(
|
||||
LOCK_NAMESPACE,
|
||||
project_id,
|
||||
cb => methodWithoutLock(project_id, ...Array.from(args), cb),
|
||||
callback
|
||||
)
|
||||
}
|
||||
methodWithLock.withoutLock = methodWithoutLock
|
||||
return methodWithLock
|
||||
}
|
||||
|
||||
module.exports = ProjectEntityMongoUpdateHandler = self = {
|
||||
LOCK_NAMESPACE,
|
||||
|
||||
addDoc: wrapWithLock(function(project_id, folder_id, doc, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(err, result) {}
|
||||
}
|
||||
return ProjectGetter.getProjectWithoutLock(
|
||||
project_id,
|
||||
{ rootFolder: true, name: true, overleaf: true },
|
||||
function(err, project) {
|
||||
if (err != null) {
|
||||
logger.err({ project_id, err }, 'error getting project for add doc')
|
||||
return callback(err)
|
||||
}
|
||||
logger.log(
|
||||
{ project_id, folder_id, doc_name: doc.name },
|
||||
'adding doc to project with project'
|
||||
)
|
||||
return self._confirmFolder(project, folder_id, folder_id => {
|
||||
return self._putElement(project, folder_id, doc, 'doc', callback)
|
||||
})
|
||||
}
|
||||
)
|
||||
}),
|
||||
|
||||
addFile: wrapWithLock(function(project_id, folder_id, fileRef, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(error, result, project) {}
|
||||
}
|
||||
return ProjectGetter.getProjectWithoutLock(
|
||||
project_id,
|
||||
{ rootFolder: true, name: true, overleaf: true },
|
||||
function(err, project) {
|
||||
if (err != null) {
|
||||
logger.err({ project_id, err }, 'error getting project for add file')
|
||||
return callback(err)
|
||||
}
|
||||
logger.log(
|
||||
{ project_id: project._id, folder_id, file_name: fileRef.name },
|
||||
'adding file'
|
||||
)
|
||||
return self._confirmFolder(project, folder_id, folder_id =>
|
||||
self._putElement(project, folder_id, fileRef, 'file', callback)
|
||||
)
|
||||
}
|
||||
)
|
||||
}),
|
||||
|
||||
replaceFileWithNew: wrapWithLock(
|
||||
(project_id, file_id, newFileRef, callback) =>
|
||||
ProjectGetter.getProjectWithoutLock(
|
||||
project_id,
|
||||
{ rootFolder: true, name: true, overleaf: true },
|
||||
function(err, project) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
return ProjectLocator.findElement(
|
||||
{ project, element_id: file_id, type: 'file' },
|
||||
(err, fileRef, path) => {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
return ProjectEntityMongoUpdateHandler._insertDeletedFileReference(
|
||||
project_id,
|
||||
fileRef,
|
||||
function(err) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
const conditions = { _id: project._id }
|
||||
const inc = {}
|
||||
// increment the project structure version as we are adding a new file here
|
||||
inc['version'] = 1
|
||||
const set = {}
|
||||
set[`${path.mongo}._id`] = newFileRef._id
|
||||
set[`${path.mongo}.created`] = new Date()
|
||||
set[`${path.mongo}.linkedFileData`] =
|
||||
newFileRef.linkedFileData
|
||||
inc[`${path.mongo}.rev`] = 1
|
||||
set[`${path.mongo}.hash`] = newFileRef.hash
|
||||
const update = {
|
||||
$inc: inc,
|
||||
$set: set
|
||||
}
|
||||
// Note: Mongoose uses new:true to return the modified document
|
||||
// https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
|
||||
// but Mongo uses returnNewDocument:true instead
|
||||
// https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/
|
||||
// We are using Mongoose here, but if we ever switch to a direct mongo call
|
||||
// the next line will need to be updated.
|
||||
return Project.findOneAndUpdate(
|
||||
conditions,
|
||||
update,
|
||||
{ new: true },
|
||||
function(err, newProject) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
return callback(null, fileRef, project, path, newProject)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
),
|
||||
|
||||
mkdirp: wrapWithLock(function(project_id, path, options, callback) {
|
||||
// defaults to case insensitive paths, use options {exactCaseMatch:true}
|
||||
// to make matching case-sensitive
|
||||
let folders = path.split('/')
|
||||
folders = _.select(folders, folder => folder.length !== 0)
|
||||
|
||||
return ProjectGetter.getProjectWithOnlyFolders(
|
||||
project_id,
|
||||
(err, project) => {
|
||||
if (path === '/') {
|
||||
logger.log(
|
||||
{ project_id: project._id },
|
||||
'mkdir is only trying to make path of / so sending back root folder'
|
||||
)
|
||||
return callback(null, [], project.rootFolder[0])
|
||||
}
|
||||
logger.log({ project_id: project._id, path, folders }, 'running mkdirp')
|
||||
|
||||
let builtUpPath = ''
|
||||
const procesFolder = (previousFolders, folderName, callback) => {
|
||||
let parentFolder_id
|
||||
previousFolders = previousFolders || []
|
||||
const parentFolder = previousFolders[previousFolders.length - 1]
|
||||
if (parentFolder != null) {
|
||||
parentFolder_id = parentFolder._id
|
||||
}
|
||||
builtUpPath = `${builtUpPath}/${folderName}`
|
||||
return ProjectLocator.findElementByPath(
|
||||
{
|
||||
project,
|
||||
path: builtUpPath,
|
||||
exactCaseMatch:
|
||||
options != null ? options.exactCaseMatch : undefined
|
||||
},
|
||||
(err, foundFolder) => {
|
||||
if (foundFolder == null) {
|
||||
logger.log(
|
||||
{ path, project_id: project._id, folderName },
|
||||
'making folder from mkdirp'
|
||||
)
|
||||
return self.addFolder.withoutLock(
|
||||
project_id,
|
||||
parentFolder_id,
|
||||
folderName,
|
||||
function(err, newFolder, parentFolder_id) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
newFolder.parentFolder_id = parentFolder_id
|
||||
previousFolders.push(newFolder)
|
||||
return callback(null, previousFolders)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
foundFolder.filterOut = true
|
||||
previousFolders.push(foundFolder)
|
||||
return callback(null, previousFolders)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return async.reduce(folders, [], procesFolder, function(err, folders) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
const lastFolder = folders[folders.length - 1]
|
||||
folders = _.select(folders, folder => !folder.filterOut)
|
||||
return callback(null, folders, lastFolder)
|
||||
})
|
||||
}
|
||||
)
|
||||
}),
|
||||
|
||||
moveEntity: wrapWithLock(function(
|
||||
project_id,
|
||||
entity_id,
|
||||
destFolderId,
|
||||
entityType,
|
||||
callback
|
||||
) {
|
||||
if (callback == null) {
|
||||
callback = function(error) {}
|
||||
}
|
||||
return ProjectGetter.getProjectWithoutLock(
|
||||
project_id,
|
||||
{ rootFolder: true, name: true, overleaf: true },
|
||||
function(err, project) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
return ProjectLocator.findElement(
|
||||
{ project, element_id: entity_id, type: entityType },
|
||||
function(err, entity, entityPath) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
// Prevent top-level docs/files with reserved names (to match v1 behaviour)
|
||||
if (self._blockedFilename(entityPath, entityType)) {
|
||||
return callback(
|
||||
new Errors.InvalidNameError('blocked element name')
|
||||
)
|
||||
}
|
||||
return self._checkValidMove(
|
||||
project,
|
||||
entityType,
|
||||
entity,
|
||||
entityPath,
|
||||
destFolderId,
|
||||
function(error) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return ProjectEntityHandler.getAllEntitiesFromProject(
|
||||
project,
|
||||
function(error, oldDocs, oldFiles) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
// For safety, insert the entity in the destination
|
||||
// location first, and then remove the original. If
|
||||
// there is an error the entity may appear twice. This
|
||||
// will cause some breakage but is better than being
|
||||
// lost, which is what happens if this is done in the
|
||||
// opposite order.
|
||||
return self._putElement(
|
||||
project,
|
||||
destFolderId,
|
||||
entity,
|
||||
entityType,
|
||||
function(err, result) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
// Note: putElement always pushes onto the end of an
|
||||
// array so it will never change an existing mongo
|
||||
// path. Therefore it is safe to remove an element
|
||||
// from the project with an existing path after
|
||||
// calling putElement. But we must be sure that we
|
||||
// have not moved a folder subfolder of itself (which
|
||||
// is done by _checkValidMove above) because that
|
||||
// would lead to it being deleted.
|
||||
return self._removeElementFromMongoArray(
|
||||
Project,
|
||||
project_id,
|
||||
entityPath.mongo,
|
||||
entity_id,
|
||||
function(err, newProject) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
return ProjectEntityHandler.getAllEntitiesFromProject(
|
||||
newProject,
|
||||
function(err, newDocs, newFiles) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
const startPath = entityPath.fileSystem
|
||||
const endPath = result.path.fileSystem
|
||||
const changes = {
|
||||
oldDocs,
|
||||
newDocs,
|
||||
oldFiles,
|
||||
newFiles,
|
||||
newProject
|
||||
}
|
||||
// check that no files have been lost (or duplicated)
|
||||
if (
|
||||
oldFiles.length !== newFiles.length ||
|
||||
oldDocs.length !== newDocs.length
|
||||
) {
|
||||
logger.err(
|
||||
{
|
||||
project_id,
|
||||
oldDocs: oldDocs.length,
|
||||
newDocs: newDocs.length,
|
||||
oldFiles: oldFiles.length,
|
||||
newFiles: newFiles.length,
|
||||
origProject: project,
|
||||
newProject
|
||||
},
|
||||
"project corrupted moving files - shouldn't happen"
|
||||
)
|
||||
return callback(
|
||||
new Error(
|
||||
'unexpected change in project structure'
|
||||
)
|
||||
)
|
||||
}
|
||||
return callback(
|
||||
null,
|
||||
project,
|
||||
startPath,
|
||||
endPath,
|
||||
entity.rev,
|
||||
changes,
|
||||
callback
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}),
|
||||
|
||||
deleteEntity: wrapWithLock((project_id, entity_id, entityType, callback) =>
|
||||
ProjectGetter.getProjectWithoutLock(
|
||||
project_id,
|
||||
{ name: true, rootFolder: true, overleaf: true },
|
||||
function(error, project) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return ProjectLocator.findElement(
|
||||
{ project, element_id: entity_id, type: entityType },
|
||||
function(error, entity, path) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return self._removeElementFromMongoArray(
|
||||
Project,
|
||||
project_id,
|
||||
path.mongo,
|
||||
entity_id,
|
||||
function(error, newProject) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return callback(null, entity, path, project, newProject)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
),
|
||||
|
||||
renameEntity: wrapWithLock(
|
||||
(project_id, entity_id, entityType, newName, callback) =>
|
||||
ProjectGetter.getProjectWithoutLock(
|
||||
project_id,
|
||||
{ rootFolder: true, name: true, overleaf: true },
|
||||
(error, project) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return ProjectEntityHandler.getAllEntitiesFromProject(
|
||||
project,
|
||||
(error, oldDocs, oldFiles) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return ProjectLocator.findElement(
|
||||
{ project, element_id: entity_id, type: entityType },
|
||||
(error, entity, entPath, parentFolder) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
const endPath = path.join(
|
||||
path.dirname(entPath.fileSystem),
|
||||
newName
|
||||
)
|
||||
// Prevent top-level docs/files with reserved names (to match v1 behaviour)
|
||||
if (
|
||||
self._blockedFilename({ fileSystem: endPath }, entityType)
|
||||
) {
|
||||
return callback(
|
||||
new Errors.InvalidNameError('blocked element name')
|
||||
)
|
||||
}
|
||||
// check if the new name already exists in the current folder
|
||||
return self._checkValidElementName(
|
||||
parentFolder,
|
||||
newName,
|
||||
error => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
const conditions = { _id: project_id }
|
||||
const update = { $set: {}, $inc: {} }
|
||||
const namePath = entPath.mongo + '.name'
|
||||
update['$set'][namePath] = newName
|
||||
// we need to increment the project version number for any structure change
|
||||
update['$inc']['version'] = 1
|
||||
return Project.findOneAndUpdate(
|
||||
conditions,
|
||||
update,
|
||||
{ new: true },
|
||||
function(error, newProject) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
return ProjectEntityHandler.getAllEntitiesFromProject(
|
||||
newProject,
|
||||
(error, newDocs, newFiles) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
const startPath = entPath.fileSystem
|
||||
const changes = {
|
||||
oldDocs,
|
||||
newDocs,
|
||||
oldFiles,
|
||||
newFiles,
|
||||
newProject
|
||||
}
|
||||
return callback(
|
||||
null,
|
||||
project,
|
||||
startPath,
|
||||
endPath,
|
||||
entity.rev,
|
||||
changes,
|
||||
callback
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
),
|
||||
|
||||
addFolder: wrapWithLock((project_id, parentFolder_id, folderName, callback) =>
|
||||
ProjectGetter.getProjectWithoutLock(
|
||||
project_id,
|
||||
{ rootFolder: true, name: true, overleaf: true },
|
||||
function(err, project) {
|
||||
if (err != null) {
|
||||
logger.err(
|
||||
{ project_id, err },
|
||||
'error getting project for add folder'
|
||||
)
|
||||
return callback(err)
|
||||
}
|
||||
return self._confirmFolder(
|
||||
project,
|
||||
parentFolder_id,
|
||||
parentFolder_id => {
|
||||
const folder = new Folder({ name: folderName })
|
||||
logger.log(
|
||||
{ project: project._id, parentFolder_id, folderName },
|
||||
'adding new folder'
|
||||
)
|
||||
return self._putElement(
|
||||
project,
|
||||
parentFolder_id,
|
||||
folder,
|
||||
'folder',
|
||||
err => {
|
||||
if (err != null) {
|
||||
logger.err(
|
||||
{ err, project_id: project._id },
|
||||
'error adding folder to project'
|
||||
)
|
||||
return callback(err)
|
||||
}
|
||||
return callback(null, folder, parentFolder_id)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
),
|
||||
|
||||
_removeElementFromMongoArray(model, model_id, path, element_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(err, project) {}
|
||||
}
|
||||
const conditions = { _id: model_id }
|
||||
const pullUpdate = { $pull: {}, $inc: {} }
|
||||
const nonArrayPath = path.slice(0, path.lastIndexOf('.'))
|
||||
// remove specific element from array by id
|
||||
pullUpdate['$pull'][nonArrayPath] = { _id: element_id }
|
||||
// we need to increment the project version number for any structure change
|
||||
pullUpdate['$inc']['version'] = 1
|
||||
return model.findOneAndUpdate(
|
||||
conditions,
|
||||
pullUpdate,
|
||||
{ new: true },
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
_countElements(project) {
|
||||
var countFolder = function(folder) {
|
||||
let total = 0
|
||||
|
||||
for (let subfolder of Array.from(
|
||||
(folder != null ? folder.folders : undefined) || []
|
||||
)) {
|
||||
total += countFolder(subfolder)
|
||||
}
|
||||
|
||||
if (
|
||||
__guard__(folder != null ? folder.folders : undefined, x => x.length) !=
|
||||
null
|
||||
) {
|
||||
total += folder.folders.length
|
||||
}
|
||||
|
||||
if (
|
||||
__guard__(folder != null ? folder.docs : undefined, x1 => x1.length) !=
|
||||
null
|
||||
) {
|
||||
total += folder.docs.length
|
||||
}
|
||||
|
||||
if (
|
||||
__guard__(
|
||||
folder != null ? folder.fileRefs : undefined,
|
||||
x2 => x2.length
|
||||
) != null
|
||||
) {
|
||||
total += folder.fileRefs.length
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
return countFolder(project.rootFolder[0])
|
||||
},
|
||||
|
||||
_putElement(project, folder_id, element, type, callback) {
|
||||
let e
|
||||
if (callback == null) {
|
||||
callback = function(err, path, project) {}
|
||||
}
|
||||
const sanitizeTypeOfElement = function(elementType) {
|
||||
const lastChar = elementType.slice(-1)
|
||||
if (lastChar !== 's') {
|
||||
elementType += 's'
|
||||
}
|
||||
if (elementType === 'files') {
|
||||
elementType = 'fileRefs'
|
||||
}
|
||||
return elementType
|
||||
}
|
||||
|
||||
if (element == null || element._id == null) {
|
||||
e = new Error('no element passed to be inserted')
|
||||
logger.err(
|
||||
{ project_id: project._id, folder_id, element, type },
|
||||
'failed trying to insert element as it was null'
|
||||
)
|
||||
return callback(e)
|
||||
}
|
||||
type = sanitizeTypeOfElement(type)
|
||||
|
||||
// original check path.resolve("/", element.name) isnt "/#{element.name}" or element.name.match("/")
|
||||
// check if name is allowed
|
||||
if (!SafePath.isCleanFilename(element.name)) {
|
||||
e = new Errors.InvalidNameError('invalid element name')
|
||||
logger.err(
|
||||
{ project_id: project._id, folder_id, element, type },
|
||||
'failed trying to insert element as name was invalid'
|
||||
)
|
||||
return callback(e)
|
||||
}
|
||||
|
||||
if (folder_id == null) {
|
||||
folder_id = project.rootFolder[0]._id
|
||||
}
|
||||
|
||||
if (self._countElements(project) > settings.maxEntitiesPerProject) {
|
||||
logger.warn(
|
||||
{ project_id: project._id },
|
||||
'project too big, stopping insertions'
|
||||
)
|
||||
CooldownManager.putProjectOnCooldown(project._id)
|
||||
return callback('project_has_to_many_files')
|
||||
}
|
||||
|
||||
return ProjectLocator.findElement(
|
||||
{ project, element_id: folder_id, type: 'folders' },
|
||||
(err, folder, path) => {
|
||||
if (err != null) {
|
||||
logger.err(
|
||||
{ err, project_id: project._id, folder_id, type, element },
|
||||
'error finding folder for _putElement'
|
||||
)
|
||||
return callback(err)
|
||||
}
|
||||
const newPath = {
|
||||
fileSystem: `${path.fileSystem}/${element.name}`,
|
||||
mongo: path.mongo
|
||||
}
|
||||
// check if the path would be too long
|
||||
if (!SafePath.isAllowedLength(newPath.fileSystem)) {
|
||||
return callback(new Errors.InvalidNameError('path too long'))
|
||||
}
|
||||
// Prevent top-level docs/files with reserved names (to match v1 behaviour)
|
||||
if (self._blockedFilename(newPath, type)) {
|
||||
return callback(new Errors.InvalidNameError('blocked element name'))
|
||||
}
|
||||
return self._checkValidElementName(folder, element.name, err => {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
const id = element._id + ''
|
||||
element._id = require('mongoose').Types.ObjectId(id)
|
||||
const conditions = { _id: project._id }
|
||||
const mongopath = `${path.mongo}.${type}`
|
||||
const update = { $push: {}, $inc: {} }
|
||||
update['$push'][mongopath] = element
|
||||
// we need to increment the project version number for any structure change
|
||||
update['$inc']['version'] = 1 // increment project version number
|
||||
logger.log(
|
||||
{
|
||||
project_id: project._id,
|
||||
element_id: element._id,
|
||||
fileType: type,
|
||||
folder_id,
|
||||
mongopath
|
||||
},
|
||||
'adding element to project'
|
||||
)
|
||||
// We are using Mongoose here, but if we ever switch to a direct mongo call
|
||||
// the next line will need to be updated to {returnNewDocument:true}
|
||||
return Project.findOneAndUpdate(
|
||||
conditions,
|
||||
update,
|
||||
{ new: true },
|
||||
function(err, newProject) {
|
||||
if (err != null) {
|
||||
logger.err(
|
||||
{ err, project_id: project._id },
|
||||
'error saving in putElement project'
|
||||
)
|
||||
return callback(err)
|
||||
}
|
||||
return callback(err, { path: newPath }, newProject)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
_blockedFilename(entityPath, entityType) {
|
||||
// check if name would be blocked in v1
|
||||
// javascript reserved names are forbidden for docs and files
|
||||
// at the top-level (but folders with reserved names are allowed).
|
||||
const isFolder = ['folder', 'folders'].includes(entityType)
|
||||
const [dir, file] = Array.from([
|
||||
path.dirname(entityPath.fileSystem),
|
||||
path.basename(entityPath.fileSystem)
|
||||
])
|
||||
const isTopLevel = dir === '/'
|
||||
if (isTopLevel && !isFolder && SafePath.isBlockedFilename(file)) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
_checkValidElementName(folder, name, callback) {
|
||||
// check if the name is already taken by a doc, file or
|
||||
// folder. If so, return an error "file already exists".
|
||||
if (callback == null) {
|
||||
callback = function(err) {}
|
||||
}
|
||||
const err = new Errors.InvalidNameError('file already exists')
|
||||
for (let doc of Array.from(
|
||||
(folder != null ? folder.docs : undefined) || []
|
||||
)) {
|
||||
if (doc.name === name) {
|
||||
return callback(err)
|
||||
}
|
||||
}
|
||||
for (let file of Array.from(
|
||||
(folder != null ? folder.fileRefs : undefined) || []
|
||||
)) {
|
||||
if (file.name === name) {
|
||||
return callback(err)
|
||||
}
|
||||
}
|
||||
for (folder of Array.from(
|
||||
(folder != null ? folder.folders : undefined) || []
|
||||
)) {
|
||||
if (folder.name === name) {
|
||||
return callback(err)
|
||||
}
|
||||
}
|
||||
return callback()
|
||||
},
|
||||
|
||||
_confirmFolder(project, folder_id, callback) {
|
||||
logger.log(
|
||||
{ folder_id, project_id: project._id },
|
||||
'confirming folder in project'
|
||||
)
|
||||
if (folder_id + '' === 'undefined') {
|
||||
return callback(project.rootFolder[0]._id)
|
||||
} else if (folder_id !== null) {
|
||||
return callback(folder_id)
|
||||
} else {
|
||||
return callback(project.rootFolder[0]._id)
|
||||
}
|
||||
},
|
||||
|
||||
_checkValidMove(
|
||||
project,
|
||||
entityType,
|
||||
entity,
|
||||
entityPath,
|
||||
destFolderId,
|
||||
callback
|
||||
) {
|
||||
if (callback == null) {
|
||||
callback = function(error) {}
|
||||
}
|
||||
return ProjectLocator.findElement(
|
||||
{ project, element_id: destFolderId, type: 'folder' },
|
||||
function(err, destEntity, destFolderPath) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
// check if there is already a doc/file/folder with the same name
|
||||
// in the destination folder
|
||||
return self._checkValidElementName(destEntity, entity.name, function(
|
||||
err
|
||||
) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
if (/folder/.test(entityType)) {
|
||||
logger.log(
|
||||
{
|
||||
destFolderPath: destFolderPath.fileSystem,
|
||||
folderPath: entityPath.fileSystem
|
||||
},
|
||||
'checking folder is not moving into child folder'
|
||||
)
|
||||
const isNestedFolder =
|
||||
destFolderPath.fileSystem.slice(
|
||||
0,
|
||||
entityPath.fileSystem.length
|
||||
) === entityPath.fileSystem
|
||||
if (isNestedFolder) {
|
||||
return callback(
|
||||
new Errors.InvalidNameError(
|
||||
'destination folder is a child folder of me'
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return callback()
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
_insertDeletedDocReference(project_id, doc, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(error) {}
|
||||
}
|
||||
return Project.update(
|
||||
{
|
||||
_id: project_id
|
||||
},
|
||||
{
|
||||
$push: {
|
||||
deletedDocs: {
|
||||
_id: doc._id,
|
||||
name: doc.name,
|
||||
deletedAt: new Date()
|
||||
}
|
||||
}
|
||||
},
|
||||
{},
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
_insertDeletedFileReference(project_id, fileRef, callback) {
|
||||
if (callback == null) {
|
||||
callback = function(error) {}
|
||||
}
|
||||
return Project.update(
|
||||
{
|
||||
_id: project_id
|
||||
},
|
||||
{
|
||||
$push: {
|
||||
deletedFiles: {
|
||||
_id: fileRef._id,
|
||||
name: fileRef.name,
|
||||
linkedFileData: fileRef.linkedFileData,
|
||||
hash: fileRef.hash,
|
||||
deletedAt: new Date()
|
||||
}
|
||||
}
|
||||
},
|
||||
{},
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function __guard__(value, transform) {
|
||||
return typeof value !== 'undefined' && value !== null
|
||||
? transform(value)
|
||||
: undefined
|
||||
}
|
||||
Reference in New Issue
Block a user