| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- /* eslint-disable
- camelcase,
- handle-callback-err,
- */
- // 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
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
- const { db } = require('../../infrastructure/mongojs')
- const { promisify, callbackify } = require('util')
- const { Project } = require('../../models/Project')
- const { DeletedProject } = require('../../models/DeletedProject')
- const Errors = require('../Errors/Errors')
- const logger = require('logger-sharelatex')
- const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
- const TagsHandler = require('../Tags/TagsHandler')
- const async = require('async')
- const ProjectDetailsHandler = require('./ProjectDetailsHandler')
- const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
- const DocstoreManager = require('../Docstore/DocstoreManager')
- const moment = require('moment')
- const ProjectDeleter = {
- markAsDeletedByExternalSource(project_id, callback) {
- if (callback == null) {
- callback = function(error) {}
- }
- logger.log(
- { project_id },
- 'marking project as deleted by external data source'
- )
- const conditions = { _id: project_id }
- const update = { deletedByExternalDataSource: true }
- return Project.update(conditions, update, {}, err =>
- require('../Editor/EditorController').notifyUsersProjectHasBeenDeletedOrRenamed(
- project_id,
- () => callback()
- )
- )
- },
- unmarkAsDeletedByExternalSource(project_id, callback) {
- if (callback == null) {
- callback = function(error) {}
- }
- logger.log(
- { project_id },
- 'removing flag marking project as deleted by external data source'
- )
- const conditions = { _id: project_id }
- const update = { deletedByExternalDataSource: false }
- return Project.update(conditions, update, {}, callback)
- },
- deleteUsersProjects(user_id, callback) {
- logger.log({ user_id }, 'deleting users projects')
- return Project.find({ owner_ref: user_id }, function(error, projects) {
- if (error != null) {
- return callback(error)
- }
- return async.each(
- projects,
- (project, cb) => ProjectDeleter.deleteProject(project._id, cb),
- function(err) {
- if (err != null) {
- return callback(err)
- }
- return CollaboratorsHandler.removeUserFromAllProjets(
- user_id,
- callback
- )
- }
- )
- })
- },
- expireDeletedProjectsAfterDuration(callback) {
- const DURATION = 90
- DeletedProject.find(
- {
- 'deleterData.deletedAt': {
- $lt: new Date(moment().subtract(DURATION, 'days'))
- },
- project: {
- $ne: null
- }
- },
- function(err, deletedProjects) {
- if (err != null) {
- logger.err({ err }, 'Problem with finding deletedProject')
- return callback(err)
- }
- if (deletedProjects.length) {
- async.eachSeries(
- deletedProjects,
- function(deletedProject, cb) {
- ProjectDeleter.expireDeletedProject(
- deletedProject.deletedProjectId,
- cb
- )
- },
- function(err) {
- if (err != null) {
- logger.err({ err })
- }
- callback(err)
- }
- )
- } else {
- logger.log({}, 'No deleted projects for duration were found')
- callback(err)
- }
- }
- )
- },
- archiveProject(project_id, callback) {
- if (callback == null) {
- callback = function(error) {}
- }
- logger.log({ project_id }, 'archived project from user request')
- return Project.update(
- { _id: project_id },
- { $set: { archived: true } },
- function(err) {
- if (err != null) {
- logger.warn({ err }, 'problem archived project')
- return callback(err)
- }
- logger.log(
- { project_id },
- 'successfully archived project from user request'
- )
- return callback()
- }
- )
- },
- restoreProject(project_id, callback) {
- if (callback == null) {
- callback = function(error) {}
- }
- return Project.update(
- { _id: project_id },
- { $unset: { archived: true } },
- callback
- )
- }
- }
- // Async methods
- async function deleteProject(project_id, options = {}) {
- logger.log({ project_id }, 'deleting project')
- try {
- let project = await Project.findOne({ _id: project_id }).exec()
- if (!project) {
- throw new Errors.NotFoundError('project not found')
- }
- await DeletedProject.create({
- project: project,
- deleterData: {
- deletedAt: new Date(),
- deleterId:
- options.deleterUser != null ? options.deleterUser._id : undefined,
- deleterIpAddress: options.ipAddress,
- deletedProjectId: project._id,
- deletedProjectOwnerId: project.owner_ref,
- deletedProjectCollaboratorIds: project.collaberator_refs,
- deletedProjectReadOnlyIds: project.readOnly_refs,
- deletedProjectReadWriteTokenAccessIds:
- project.tokenAccessReadAndWrite_refs,
- deletedProjectOverleafId: project.overleaf
- ? project.overleaf.id
- : undefined,
- deletedProjectOverleafHistoryId:
- project.overleaf && project.overleaf.history
- ? project.overleaf.history.id
- : undefined,
- deletedProjectReadOnlyTokenAccessIds: project.tokenAccessReadOnly_refs,
- deletedProjectReadWriteToken: project.tokens.readAndWrite,
- deletedProjectReadOnlyToken: project.tokens.readOnly,
- deletedProjectLastUpdatedAt: project.lastUpdated
- }
- })
- const flushProjectToMongoAndDelete = promisify(
- DocumentUpdaterHandler.flushProjectToMongoAndDelete
- )
- await flushProjectToMongoAndDelete(project_id)
- const getMemberIds = promisify(CollaboratorsHandler.getMemberIds)
- let member_ids = await getMemberIds(project_id)
- // fire these jobs in the background
- Array.from(member_ids).forEach(member_id =>
- TagsHandler.removeProjectFromAllTags(member_id, project_id, () => {})
- )
- await Project.remove({ _id: project_id }).exec()
- } catch (err) {
- logger.warn({ err }, 'problem deleting project')
- throw err
- }
- logger.log({ project_id }, 'successfully deleted project')
- }
- async function undeleteProject(project_id) {
- let deletedProject = await DeletedProject.findOne({
- 'deleterData.deletedProjectId': project_id
- }).exec()
- if (!deletedProject) {
- throw new Errors.NotFoundError('project_not_found')
- }
- if (!deletedProject.project) {
- throw new Errors.NotFoundError('project_too_old_to_restore')
- }
- let restored = new Project(deletedProject.project)
- // if we're undeleting, we want the document to show up
- const generateUniqueName = promisify(ProjectDetailsHandler.generateUniqueName)
- restored.name = await generateUniqueName(
- deletedProject.deleterData.deletedProjectOwnerId,
- restored.name + ' (Restored)'
- )
- restored.archived = undefined
- // we can't use Mongoose to re-insert the project, as it won't
- // create a new document with an _id already specified. We need to
- // insert it directly into the collection
- // db.projects.insert doesn't work with promisify
- await new Promise((resolve, reject) => {
- db.projects.insert(restored, err => {
- if (err) {
- reject(err)
- } else {
- resolve()
- }
- })
- })
- await DeletedProject.deleteOne({ _id: deletedProject._id }).exec()
- }
- async function expireDeletedProject(projectId) {
- try {
- const deletedProject = await DeletedProject.findOne({
- 'deleterData.deletedProjectId': projectId
- }).exec()
- if (!deletedProject) {
- throw new Errors.NotFoundError(
- `No deleted project found for project id ${projectId}`
- )
- }
- if (!deletedProject.project) {
- logger.warn(
- { projectId },
- `Attempted to expire already-expired deletedProject`
- )
- return
- }
- const destroyProject = promisify(DocstoreManager.destroyProject)
- await destroyProject(deletedProject.project._id)
- await DeletedProject.update(
- {
- _id: deletedProject._id
- },
- {
- $set: {
- 'deleterData.deleterIpAddress': null,
- project: null
- }
- }
- ).exec()
- logger.log({ projectId }, 'Successfully expired deleted project')
- } catch (error) {
- logger.warn({ projectId, error }, 'error expiring deleted project')
- throw error
- }
- }
- // Exported class
- const promises = {
- deleteProject: deleteProject,
- undeleteProject: undeleteProject,
- expireDeletedProject: expireDeletedProject,
- deleteUsersProjects: promisify(ProjectDeleter.deleteUsersProjects)
- }
- ProjectDeleter.promises = promises
- ProjectDeleter.deleteProject = callbackify(deleteProject)
- ProjectDeleter.undeleteProject = callbackify(undeleteProject)
- ProjectDeleter.expireDeletedProject = callbackify(expireDeletedProject)
- module.exports = ProjectDeleter
|