InactiveProjectManager.mjs 3.8 KB

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