ProjectDeleter.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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 ProjectHelper = require('./ProjectHelper')
  24. const ProjectDetailsHandler = require('./ProjectDetailsHandler')
  25. const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
  26. const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
  27. const DocstoreManager = require('../Docstore/DocstoreManager')
  28. const moment = require('moment')
  29. const ProjectDeleter = {
  30. markAsDeletedByExternalSource(project_id, callback) {
  31. if (callback == null) {
  32. callback = function(error) {}
  33. }
  34. logger.log(
  35. { project_id },
  36. 'marking project as deleted by external data source'
  37. )
  38. const conditions = { _id: project_id }
  39. const update = { deletedByExternalDataSource: true }
  40. return Project.update(conditions, update, {}, err =>
  41. require('../Editor/EditorController').notifyUsersProjectHasBeenDeletedOrRenamed(
  42. project_id,
  43. () => callback()
  44. )
  45. )
  46. },
  47. unmarkAsDeletedByExternalSource(project_id, callback) {
  48. if (callback == null) {
  49. callback = function(error) {}
  50. }
  51. logger.log(
  52. { project_id },
  53. 'removing flag marking project as deleted by external data source'
  54. )
  55. const conditions = { _id: project_id }
  56. const update = { deletedByExternalDataSource: false }
  57. return Project.update(conditions, update, {}, callback)
  58. },
  59. deleteUsersProjects(user_id, callback) {
  60. logger.log({ user_id }, 'deleting users projects')
  61. return Project.find({ owner_ref: user_id }, function(error, projects) {
  62. if (error != null) {
  63. return callback(error)
  64. }
  65. return async.each(
  66. projects,
  67. (project, cb) => ProjectDeleter.deleteProject(project._id, cb),
  68. function(err) {
  69. if (err != null) {
  70. return callback(err)
  71. }
  72. return CollaboratorsHandler.removeUserFromAllProjects(
  73. user_id,
  74. callback
  75. )
  76. }
  77. )
  78. })
  79. },
  80. expireDeletedProjectsAfterDuration(callback) {
  81. const DURATION = 90
  82. DeletedProject.find(
  83. {
  84. 'deleterData.deletedAt': {
  85. $lt: new Date(moment().subtract(DURATION, 'days'))
  86. },
  87. project: {
  88. $ne: null
  89. }
  90. },
  91. function(err, deletedProjects) {
  92. if (err != null) {
  93. logger.err({ err }, 'Problem with finding deletedProject')
  94. return callback(err)
  95. }
  96. if (deletedProjects.length) {
  97. async.eachSeries(
  98. deletedProjects,
  99. function(deletedProject, cb) {
  100. ProjectDeleter.expireDeletedProject(
  101. deletedProject.deleterData.deletedProjectId,
  102. cb
  103. )
  104. },
  105. function(err) {
  106. if (err != null) {
  107. logger.err({ err })
  108. }
  109. callback(err)
  110. }
  111. )
  112. } else {
  113. logger.log({}, 'No deleted projects for duration were found')
  114. callback(err)
  115. }
  116. }
  117. )
  118. },
  119. legacyArchiveProject(project_id, callback) {
  120. if (callback == null) {
  121. callback = function(error) {}
  122. }
  123. logger.log({ project_id }, 'archived project from user request')
  124. return Project.update(
  125. { _id: project_id },
  126. { $set: { archived: true } },
  127. function(err) {
  128. if (err != null) {
  129. logger.warn({ err }, 'problem archived project')
  130. return callback(err)
  131. }
  132. logger.log(
  133. { project_id },
  134. 'successfully archived project from user request'
  135. )
  136. return callback()
  137. }
  138. )
  139. },
  140. restoreProject(project_id, callback) {
  141. if (callback == null) {
  142. callback = function(error) {}
  143. }
  144. return Project.update(
  145. { _id: project_id },
  146. { $unset: { archived: true } },
  147. callback
  148. )
  149. }
  150. }
  151. // Async methods
  152. async function archiveProject(project_id, userId) {
  153. logger.log({ project_id }, 'archiving project from user request')
  154. try {
  155. let project = await Project.findOne({ _id: project_id }).exec()
  156. if (!project) {
  157. throw new Errors.NotFoundError('project not found')
  158. }
  159. const archived = ProjectHelper.calculateArchivedArray(
  160. project,
  161. userId,
  162. 'ARCHIVE'
  163. )
  164. await Project.update({ _id: project_id }, { $set: { archived: archived } })
  165. } catch (err) {
  166. logger.warn({ err }, 'problem archiving project')
  167. throw err
  168. }
  169. }
  170. async function unarchiveProject(project_id, userId) {
  171. logger.log({ project_id }, 'unarchiving project from user request')
  172. try {
  173. let project = await Project.findOne({ _id: project_id }).exec()
  174. if (!project) {
  175. throw new Errors.NotFoundError('project not found')
  176. }
  177. const archived = ProjectHelper.calculateArchivedArray(
  178. project,
  179. userId,
  180. 'UNARCHIVE'
  181. )
  182. await Project.update({ _id: project_id }, { $set: { archived: archived } })
  183. } catch (err) {
  184. logger.warn({ err }, 'problem unarchiving project')
  185. throw err
  186. }
  187. }
  188. async function deleteProject(project_id, options = {}) {
  189. logger.log({ project_id }, 'deleting project')
  190. try {
  191. let project = await Project.findOne({ _id: project_id }).exec()
  192. if (!project) {
  193. throw new Errors.NotFoundError('project not found')
  194. }
  195. let deleterData = {
  196. deletedAt: new Date(),
  197. deleterId:
  198. options.deleterUser != null ? options.deleterUser._id : undefined,
  199. deleterIpAddress: options.ipAddress,
  200. deletedProjectId: project._id,
  201. deletedProjectOwnerId: project.owner_ref,
  202. deletedProjectCollaboratorIds: project.collaberator_refs,
  203. deletedProjectReadOnlyIds: project.readOnly_refs,
  204. deletedProjectReadWriteTokenAccessIds:
  205. project.tokenAccessReadAndWrite_refs,
  206. deletedProjectOverleafId: project.overleaf
  207. ? project.overleaf.id
  208. : undefined,
  209. deletedProjectOverleafHistoryId:
  210. project.overleaf && project.overleaf.history
  211. ? project.overleaf.history.id
  212. : undefined,
  213. deletedProjectReadOnlyTokenAccessIds: project.tokenAccessReadOnly_refs,
  214. deletedProjectReadWriteToken: project.tokens.readAndWrite,
  215. deletedProjectReadOnlyToken: project.tokens.readOnly,
  216. deletedProjectLastUpdatedAt: project.lastUpdated
  217. }
  218. Object.keys(deleterData).forEach(
  219. key => (deleterData[key] === undefined ? delete deleterData[key] : '')
  220. )
  221. await DeletedProject.create({
  222. project: project,
  223. deleterData: deleterData
  224. })
  225. const flushProjectToMongoAndDelete = promisify(
  226. DocumentUpdaterHandler.flushProjectToMongoAndDelete
  227. )
  228. await flushProjectToMongoAndDelete(project_id)
  229. let member_ids = await CollaboratorsGetter.promises.getMemberIds(project_id)
  230. // fire these jobs in the background
  231. Array.from(member_ids).forEach(member_id =>
  232. TagsHandler.removeProjectFromAllTags(member_id, project_id, () => {})
  233. )
  234. await Project.remove({ _id: project_id }).exec()
  235. } catch (err) {
  236. logger.warn({ err }, 'problem deleting project')
  237. throw err
  238. }
  239. logger.log({ project_id }, 'successfully deleted project')
  240. }
  241. async function undeleteProject(project_id) {
  242. let deletedProject = await DeletedProject.findOne({
  243. 'deleterData.deletedProjectId': project_id
  244. }).exec()
  245. if (!deletedProject) {
  246. throw new Errors.NotFoundError('project_not_found')
  247. }
  248. if (!deletedProject.project) {
  249. throw new Errors.NotFoundError('project_too_old_to_restore')
  250. }
  251. let restored = new Project(deletedProject.project)
  252. // if we're undeleting, we want the document to show up
  253. restored.name = await ProjectDetailsHandler.promises.generateUniqueName(
  254. deletedProject.deleterData.deletedProjectOwnerId,
  255. restored.name + ' (Restored)'
  256. )
  257. restored.archived = undefined
  258. // we can't use Mongoose to re-insert the project, as it won't
  259. // create a new document with an _id already specified. We need to
  260. // insert it directly into the collection
  261. // db.projects.insert doesn't work with promisify
  262. await new Promise((resolve, reject) => {
  263. db.projects.insert(restored, err => {
  264. if (err) {
  265. reject(err)
  266. } else {
  267. resolve()
  268. }
  269. })
  270. })
  271. await DeletedProject.deleteOne({ _id: deletedProject._id }).exec()
  272. }
  273. async function expireDeletedProject(projectId) {
  274. try {
  275. const deletedProject = await DeletedProject.findOne({
  276. 'deleterData.deletedProjectId': projectId
  277. }).exec()
  278. if (!deletedProject) {
  279. throw new Errors.NotFoundError(
  280. `No deleted project found for project id ${projectId}`
  281. )
  282. }
  283. if (!deletedProject.project) {
  284. logger.warn(
  285. { projectId },
  286. `Attempted to expire already-expired deletedProject`
  287. )
  288. return
  289. }
  290. const destroyProject = promisify(DocstoreManager.destroyProject)
  291. await destroyProject(deletedProject.project._id)
  292. await DeletedProject.update(
  293. {
  294. _id: deletedProject._id
  295. },
  296. {
  297. $set: {
  298. 'deleterData.deleterIpAddress': null,
  299. project: null
  300. }
  301. }
  302. ).exec()
  303. logger.log({ projectId }, 'Successfully expired deleted project')
  304. } catch (error) {
  305. logger.warn({ projectId, error }, 'error expiring deleted project')
  306. throw error
  307. }
  308. }
  309. // Exported class
  310. const promises = {
  311. archiveProject: archiveProject,
  312. unarchiveProject: unarchiveProject,
  313. deleteProject: deleteProject,
  314. undeleteProject: undeleteProject,
  315. expireDeletedProject: expireDeletedProject,
  316. deleteUsersProjects: promisify(ProjectDeleter.deleteUsersProjects)
  317. }
  318. ProjectDeleter.promises = promises
  319. ProjectDeleter.archiveProject = callbackify(archiveProject)
  320. ProjectDeleter.unarchiveProject = callbackify(unarchiveProject)
  321. ProjectDeleter.deleteProject = callbackify(deleteProject)
  322. ProjectDeleter.undeleteProject = callbackify(undeleteProject)
  323. ProjectDeleter.expireDeletedProject = callbackify(expireDeletedProject)
  324. module.exports = ProjectDeleter