InactiveProjectManager.mjs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import OError from '@overleaf/o-error'
  2. import logger from '@overleaf/logger'
  3. import DocstoreManager from '../Docstore/DocstoreManager.mjs'
  4. import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.mjs'
  5. import ProjectUpdateHandler from '../Project/ProjectUpdateHandler.mjs'
  6. import { Project } from '../../models/Project.mjs'
  7. import Modules from '../../infrastructure/Modules.mjs'
  8. import { READ_PREFERENCE_SECONDARY } from '../../infrastructure/mongodb.mjs'
  9. import { callbackifyAll } from '@overleaf/promise-utils'
  10. import Metrics from '@overleaf/metrics'
  11. const MILISECONDS_IN_DAY = 86400000
  12. function findInactiveProjects(limit, daysOld) {
  13. const oldProjectDate = new Date() - MILISECONDS_IN_DAY * daysOld
  14. try {
  15. // use $not $gt to catch non-opened projects where lastOpened is null
  16. // return a cursor instead of executing the query
  17. return Project.find({
  18. lastOpened: { $not: { $gt: oldProjectDate } },
  19. })
  20. .where('active')
  21. .equals(true)
  22. .select(['_id', 'lastOpened'])
  23. .limit(limit)
  24. .read(READ_PREFERENCE_SECONDARY)
  25. .cursor()
  26. } catch (err) {
  27. logger.err({ err }, 'could not get projects for deactivating')
  28. throw err // Re-throw the error to be handled by the caller
  29. }
  30. }
  31. const InactiveProjectManager = {
  32. /**
  33. * @param {{_id: ObjectId, active: boolean }} project
  34. * @return {Promise<void>}
  35. */
  36. async reactivateProjectIfRequired(project) {
  37. const projectId = project._id.toString()
  38. logger.debug(
  39. { projectId, active: project.active },
  40. 'seeing if need to reactivate project'
  41. )
  42. if (project.active) {
  43. return
  44. }
  45. try {
  46. await DocstoreManager.promises.unarchiveProject(projectId)
  47. } catch (err) {
  48. OError.tag(err, 'error reactivating project in docstore', {
  49. project_id: projectId,
  50. })
  51. throw err
  52. }
  53. await ProjectUpdateHandler.promises.markAsActive(projectId)
  54. },
  55. async deactivateOldProjects(limit, daysOld) {
  56. if (limit == null) {
  57. limit = 10
  58. }
  59. if (daysOld == null) {
  60. daysOld = 360
  61. }
  62. logger.debug('deactivating projects')
  63. const processedProjects = []
  64. for await (const project of findInactiveProjects(limit, daysOld)) {
  65. processedProjects.push(project)
  66. try {
  67. await InactiveProjectManager.deactivateProject(project._id)
  68. } catch (err) {
  69. logger.err(
  70. { projectId: project._id, err },
  71. 'unable to deactivate project'
  72. )
  73. }
  74. }
  75. logger.debug(
  76. { numberOfProjects: processedProjects.length },
  77. 'finished deactivating projects'
  78. )
  79. return processedProjects
  80. },
  81. async deactivateProject(projectId) {
  82. logger.debug({ projectId }, 'deactivating inactive project')
  83. // ensure project is removed from document updater (also flushes updates to history)
  84. try {
  85. await DocumentUpdaterHandler.promises.flushProjectToMongoAndDelete(
  86. projectId
  87. )
  88. } catch (err) {
  89. logger.warn(
  90. { err, projectId },
  91. 'error flushing project to mongo when archiving'
  92. )
  93. Metrics.inc('inactive-project', 1, {
  94. method: 'archive',
  95. status: 'flush-error',
  96. })
  97. throw err
  98. }
  99. await Modules.promises.hooks.fire('deactivateProject', projectId)
  100. // now archive the project and mark it as inactive
  101. try {
  102. await DocstoreManager.promises.archiveProject(projectId)
  103. await ProjectUpdateHandler.promises.markAsInactive(projectId)
  104. } catch (err) {
  105. logger.warn({ err, projectId }, 'error deactivating project')
  106. throw err
  107. }
  108. },
  109. }
  110. export default {
  111. ...callbackifyAll(InactiveProjectManager),
  112. promises: InactiveProjectManager,
  113. findInactiveProjects,
  114. }