clear_filestore_404.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #!/usr/bin/env node
  2. // To run in dev:
  3. //
  4. // docker compose run --rm project-history scripts/clear_deleted.js
  5. //
  6. // In production:
  7. //
  8. // docker run --rm $(docker ps -lq) scripts/clear_deleted.js
  9. import async from 'async'
  10. import logger from '@overleaf/logger'
  11. import Settings from '@overleaf/settings'
  12. import redis from '@overleaf/redis-wrapper'
  13. import { db, ObjectId } from '../app/js/mongodb.js'
  14. import { fetchStringWithResponse } from '@overleaf/fetch-utils'
  15. logger.logger.level('fatal')
  16. const rclient = redis.createClient(Settings.redis.project_history)
  17. const Keys = Settings.redis.project_history.key_schema
  18. const argv = process.argv.slice(2)
  19. const limit = parseInt(argv[0], 10) || null
  20. const force = argv[1] === 'force' || false
  21. let projectNotFoundErrors = 0
  22. let projectImportedFromV1Errors = 0
  23. const projectsNotFound = []
  24. const projectsImportedFromV1 = []
  25. function checkAndClear(project, callback) {
  26. const projectId = project.project_id
  27. console.log('checking project', projectId)
  28. // These can probably also be reset and their overleaf.history.id unset
  29. // (unless they are v1 projects).
  30. function checkNotV1Project(cb) {
  31. db.projects.findOne(
  32. { _id: new ObjectId(projectId) },
  33. { projection: { overleaf: true } },
  34. (err, result) => {
  35. console.log(
  36. '1. looking in mongo projects collection: err',
  37. err,
  38. 'result',
  39. JSON.stringify(result)
  40. )
  41. if (err) {
  42. return cb(err)
  43. }
  44. if (!result) {
  45. return cb(new Error('project not found in mongo'))
  46. }
  47. if (result && result.overleaf && !result.overleaf.id) {
  48. console.log(' - project is not imported from v1 - ok to clear')
  49. cb()
  50. } else {
  51. cb(new Error('project is imported from v1 - will not clear it'))
  52. }
  53. }
  54. )
  55. }
  56. function clearProjectHistoryInMongo(cb) {
  57. if (force) {
  58. console.log('2. deleting overleaf.history.id in mongo project', projectId)
  59. // Accessing mongo projects collection directly - BE CAREFUL!
  60. db.projects.updateOne(
  61. { _id: new ObjectId(projectId) },
  62. { $unset: { 'overleaf.history.id': '' } },
  63. (err, result) => {
  64. console.log(' - got result from remove', err, result)
  65. if (err) {
  66. return err
  67. }
  68. if (
  69. result &&
  70. (result.modifiedCount === 1 || result.modifiedCount === 0)
  71. ) {
  72. return cb()
  73. } else {
  74. return cb(
  75. new Error('error: problem trying to unset overleaf.history.id')
  76. )
  77. }
  78. }
  79. )
  80. } else {
  81. console.log(
  82. '2. would delete overleaf.history.id for',
  83. projectId,
  84. 'from mongo'
  85. )
  86. cb()
  87. }
  88. }
  89. function clearDocUpdaterCache(cb) {
  90. const url = Settings.apis.documentupdater.url + '/project/' + projectId
  91. if (force) {
  92. console.log('3. making request to clear docupdater', url)
  93. fetchStringWithResponse(url, { method: 'DELETE' }).then(
  94. ({ response, body }) => {
  95. console.log(' - result of request: success', response.status, body)
  96. cb()
  97. },
  98. err => {
  99. console.log(' - result of request: error', err)
  100. cb(err)
  101. }
  102. )
  103. } else {
  104. console.log('3. dry run, would request DELETE on url', url)
  105. cb()
  106. }
  107. }
  108. function clearRedisQueue(cb) {
  109. const key = Keys.projectHistoryOps({ project_id: projectId })
  110. if (force) {
  111. console.log('4. deleting redis queue key', key)
  112. rclient.del(key, err => {
  113. cb(err)
  114. })
  115. } else {
  116. console.log('4. dry run, would delete redis key', key)
  117. cb()
  118. }
  119. }
  120. function clearMongoEntry(cb) {
  121. if (force) {
  122. console.log('5. deleting key in mongo projectHistoryFailures', projectId)
  123. db.projectHistoryFailures.deleteOne(
  124. { project_id: projectId },
  125. (err, result) => {
  126. console.log(' - got result from remove', err, result)
  127. cb(err)
  128. }
  129. )
  130. } else {
  131. console.log('5. would delete failure record for', projectId, 'from mongo')
  132. cb()
  133. }
  134. }
  135. // do the checks and deletions
  136. async.waterfall(
  137. [
  138. checkNotV1Project,
  139. clearProjectHistoryInMongo,
  140. clearDocUpdaterCache,
  141. clearRedisQueue,
  142. clearMongoEntry,
  143. ],
  144. err => {
  145. if (!err) {
  146. return setTimeout(callback, 1000) // include a 1 second delay
  147. } else if (err.message === 'project not found in mongo') {
  148. projectNotFoundErrors++
  149. projectsNotFound.push(projectId)
  150. return callback()
  151. } else if (
  152. err.message === 'project is imported from v1 - will not clear it'
  153. ) {
  154. projectImportedFromV1Errors++
  155. projectsImportedFromV1.push(projectId)
  156. return callback()
  157. } else {
  158. console.log('error:', err)
  159. return callback(err)
  160. }
  161. }
  162. )
  163. }
  164. // find all the broken projects from the failure records
  165. async function main() {
  166. const results = await db.projectHistoryFailures
  167. .find({ error: 'Error: bad response from filestore: 404' })
  168. .toArray()
  169. console.log('number of queues without filestore 404 =', results.length)
  170. // now check if the project is truly deleted in mongo
  171. async.eachSeries(results.slice(0, limit), checkAndClear, err => {
  172. console.log('Final error status', err)
  173. console.log(
  174. 'Project not found errors',
  175. projectNotFoundErrors,
  176. projectsNotFound
  177. )
  178. console.log(
  179. 'Project imported from V1 errors',
  180. projectImportedFromV1Errors,
  181. projectsImportedFromV1
  182. )
  183. process.exit()
  184. })
  185. }
  186. main().catch(error => {
  187. console.error(error)
  188. process.exit(1)
  189. })