ProjectDeleter.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const { db } = require('../../infrastructure/mongojs')
  15. const { promisify, callbackify } = require('util')
  16. const { Project } = require('../../models/Project')
  17. const { DeletedProject } = require('../../models/DeletedProject')
  18. const Errors = require('../Errors/Errors')
  19. const logger = require('logger-sharelatex')
  20. const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
  21. const TagsHandler = require('../Tags/TagsHandler')
  22. const async = require('async')
  23. const ProjectDetailsHandler = require('./ProjectDetailsHandler')
  24. const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
  25. const DocstoreManager = require('../Docstore/DocstoreManager')
  26. const moment = require('moment')
  27. const ProjectDeleter = {
  28. markAsDeletedByExternalSource(project_id, callback) {
  29. if (callback == null) {
  30. callback = function(error) {}
  31. }
  32. logger.log(
  33. { project_id },
  34. 'marking project as deleted by external data source'
  35. )
  36. const conditions = { _id: project_id }
  37. const update = { deletedByExternalDataSource: true }
  38. return Project.update(conditions, update, {}, err =>
  39. require('../Editor/EditorController').notifyUsersProjectHasBeenDeletedOrRenamed(
  40. project_id,
  41. () => callback()
  42. )
  43. )
  44. },
  45. unmarkAsDeletedByExternalSource(project_id, callback) {
  46. if (callback == null) {
  47. callback = function(error) {}
  48. }
  49. logger.log(
  50. { project_id },
  51. 'removing flag marking project as deleted by external data source'
  52. )
  53. const conditions = { _id: project_id }
  54. const update = { deletedByExternalDataSource: false }
  55. return Project.update(conditions, update, {}, callback)
  56. },
  57. deleteUsersProjects(user_id, callback) {
  58. logger.log({ user_id }, 'deleting users projects')
  59. return Project.find({ owner_ref: user_id }, function(error, projects) {
  60. if (error != null) {
  61. return callback(error)
  62. }
  63. return async.each(
  64. projects,
  65. (project, cb) => ProjectDeleter.deleteProject(project._id, cb),
  66. function(err) {
  67. if (err != null) {
  68. return callback(err)
  69. }
  70. return CollaboratorsHandler.removeUserFromAllProjets(
  71. user_id,
  72. callback
  73. )
  74. }
  75. )
  76. })
  77. },
  78. expireDeletedProjectsAfterDuration(callback) {
  79. const DURATION = 90
  80. DeletedProject.find(
  81. {
  82. 'deleterData.deletedAt': {
  83. $lt: new Date(moment().subtract(DURATION, 'days'))
  84. },
  85. project: {
  86. $ne: null
  87. }
  88. },
  89. function(err, deletedProjects) {
  90. if (err != null) {
  91. logger.err({ err }, 'Problem with finding deletedProject')
  92. return callback(err)
  93. }
  94. if (deletedProjects.length) {
  95. async.eachSeries(
  96. deletedProjects,
  97. function(deletedProject, cb) {
  98. ProjectDeleter.expireDeletedProject(
  99. deletedProject.deletedProjectId,
  100. cb
  101. )
  102. },
  103. function(err) {
  104. if (err != null) {
  105. logger.err({ err })
  106. }
  107. callback(err)
  108. }
  109. )
  110. } else {
  111. logger.log({}, 'No deleted projects for duration were found')
  112. callback(err)
  113. }
  114. }
  115. )
  116. },
  117. archiveProject(project_id, callback) {
  118. if (callback == null) {
  119. callback = function(error) {}
  120. }
  121. logger.log({ project_id }, 'archived project from user request')
  122. return Project.update(
  123. { _id: project_id },
  124. { $set: { archived: true } },
  125. function(err) {
  126. if (err != null) {
  127. logger.warn({ err }, 'problem archived project')
  128. return callback(err)
  129. }
  130. logger.log(
  131. { project_id },
  132. 'successfully archived project from user request'
  133. )
  134. return callback()
  135. }
  136. )
  137. },
  138. restoreProject(project_id, callback) {
  139. if (callback == null) {
  140. callback = function(error) {}
  141. }
  142. return Project.update(
  143. { _id: project_id },
  144. { $unset: { archived: true } },
  145. callback
  146. )
  147. }
  148. }
  149. // Async methods
  150. async function deleteProject(project_id, options = {}) {
  151. logger.log({ project_id }, 'deleting project')
  152. try {
  153. let project = await Project.findOne({ _id: project_id }).exec()
  154. if (!project) {
  155. throw new Errors.NotFoundError('project not found')
  156. }
  157. await DeletedProject.create({
  158. project: project,
  159. deleterData: {
  160. deletedAt: new Date(),
  161. deleterId:
  162. options.deleterUser != null ? options.deleterUser._id : undefined,
  163. deleterIpAddress: options.ipAddress,
  164. deletedProjectId: project._id,
  165. deletedProjectOwnerId: project.owner_ref,
  166. deletedProjectCollaboratorIds: project.collaberator_refs,
  167. deletedProjectReadOnlyIds: project.readOnly_refs,
  168. deletedProjectReadWriteTokenAccessIds:
  169. project.tokenAccessReadAndWrite_refs,
  170. deletedProjectOverleafId: project.overleaf
  171. ? project.overleaf.id
  172. : undefined,
  173. deletedProjectOverleafHistoryId:
  174. project.overleaf && project.overleaf.history
  175. ? project.overleaf.history.id
  176. : undefined,
  177. deletedProjectReadOnlyTokenAccessIds: project.tokenAccessReadOnly_refs,
  178. deletedProjectReadWriteToken: project.tokens.readAndWrite,
  179. deletedProjectReadOnlyToken: project.tokens.readOnly,
  180. deletedProjectLastUpdatedAt: project.lastUpdated
  181. }
  182. })
  183. const flushProjectToMongoAndDelete = promisify(
  184. DocumentUpdaterHandler.flushProjectToMongoAndDelete
  185. )
  186. await flushProjectToMongoAndDelete(project_id)
  187. const getMemberIds = promisify(CollaboratorsHandler.getMemberIds)
  188. let member_ids = await getMemberIds(project_id)
  189. // fire these jobs in the background
  190. Array.from(member_ids).forEach(member_id =>
  191. TagsHandler.removeProjectFromAllTags(member_id, project_id, () => {})
  192. )
  193. await Project.remove({ _id: project_id }).exec()
  194. } catch (err) {
  195. logger.warn({ err }, 'problem deleting project')
  196. throw err
  197. }
  198. logger.log({ project_id }, 'successfully deleted project')
  199. }
  200. async function undeleteProject(project_id) {
  201. let deletedProject = await DeletedProject.findOne({
  202. 'deleterData.deletedProjectId': project_id
  203. }).exec()
  204. if (!deletedProject) {
  205. throw new Errors.NotFoundError('project_not_found')
  206. }
  207. if (!deletedProject.project) {
  208. throw new Errors.NotFoundError('project_too_old_to_restore')
  209. }
  210. let restored = new Project(deletedProject.project)
  211. // if we're undeleting, we want the document to show up
  212. const generateUniqueName = promisify(ProjectDetailsHandler.generateUniqueName)
  213. restored.name = await generateUniqueName(
  214. deletedProject.deleterData.deletedProjectOwnerId,
  215. restored.name + ' (Restored)'
  216. )
  217. restored.archived = undefined
  218. // we can't use Mongoose to re-insert the project, as it won't
  219. // create a new document with an _id already specified. We need to
  220. // insert it directly into the collection
  221. // db.projects.insert doesn't work with promisify
  222. await new Promise((resolve, reject) => {
  223. db.projects.insert(restored, err => {
  224. if (err) {
  225. reject(err)
  226. } else {
  227. resolve()
  228. }
  229. })
  230. })
  231. await DeletedProject.deleteOne({ _id: deletedProject._id }).exec()
  232. }
  233. async function expireDeletedProject(projectId) {
  234. try {
  235. const deletedProject = await DeletedProject.findOne({
  236. 'deleterData.deletedProjectId': projectId
  237. }).exec()
  238. if (!deletedProject) {
  239. throw new Errors.NotFoundError(
  240. `No deleted project found for project id ${projectId}`
  241. )
  242. }
  243. if (!deletedProject.project) {
  244. logger.warn(
  245. { projectId },
  246. `Attempted to expire already-expired deletedProject`
  247. )
  248. return
  249. }
  250. const destroyProject = promisify(DocstoreManager.destroyProject)
  251. await destroyProject(deletedProject.project._id)
  252. await DeletedProject.update(
  253. {
  254. _id: deletedProject._id
  255. },
  256. {
  257. $set: {
  258. 'deleterData.deleterIpAddress': null,
  259. project: null
  260. }
  261. }
  262. ).exec()
  263. logger.log({ projectId }, 'Successfully expired deleted project')
  264. } catch (error) {
  265. logger.warn({ projectId, error }, 'error expiring deleted project')
  266. throw error
  267. }
  268. }
  269. // Exported class
  270. const promises = {
  271. deleteProject: deleteProject,
  272. undeleteProject: undeleteProject,
  273. expireDeletedProject: expireDeletedProject,
  274. deleteUsersProjects: promisify(ProjectDeleter.deleteUsersProjects)
  275. }
  276. ProjectDeleter.promises = promises
  277. ProjectDeleter.deleteProject = callbackify(deleteProject)
  278. ProjectDeleter.undeleteProject = callbackify(undeleteProject)
  279. ProjectDeleter.expireDeletedProject = callbackify(expireDeletedProject)
  280. module.exports = ProjectDeleter