DocstoreManager.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. const { promisify } = require('util')
  2. const { promisifyMultiResult } = require('../../util/promises')
  3. const request = require('request').defaults({ jar: false })
  4. const OError = require('@overleaf/o-error')
  5. const logger = require('@overleaf/logger')
  6. const settings = require('@overleaf/settings')
  7. const Errors = require('../Errors/Errors')
  8. const TIMEOUT = 30 * 1000 // request timeout
  9. function deleteDoc(projectId, docId, name, deletedAt, callback) {
  10. const url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}`
  11. const docMetaData = { deleted: true, deletedAt, name }
  12. const options = { url, json: docMetaData, timeout: TIMEOUT }
  13. request.patch(options, (error, res) => {
  14. if (error) {
  15. return callback(error)
  16. }
  17. if (res.statusCode >= 200 && res.statusCode < 300) {
  18. callback(null)
  19. } else if (res.statusCode === 404) {
  20. error = new Errors.NotFoundError({
  21. message: 'tried to delete doc not in docstore',
  22. info: {
  23. projectId,
  24. docId,
  25. },
  26. })
  27. callback(error) // maybe suppress the error when delete doc which is not present?
  28. } else {
  29. error = new OError(
  30. `docstore api responded with non-success code: ${res.statusCode}`,
  31. {
  32. projectId,
  33. docId,
  34. }
  35. )
  36. callback(error)
  37. }
  38. })
  39. }
  40. function getAllDocs(projectId, callback) {
  41. const url = `${settings.apis.docstore.url}/project/${projectId}/doc`
  42. request.get(
  43. {
  44. url,
  45. timeout: TIMEOUT,
  46. json: true,
  47. },
  48. (error, res, docs) => {
  49. if (error) {
  50. return callback(error)
  51. }
  52. if (res.statusCode >= 200 && res.statusCode < 300) {
  53. callback(null, docs)
  54. } else {
  55. error = new OError(
  56. `docstore api responded with non-success code: ${res.statusCode}`,
  57. { projectId }
  58. )
  59. callback(error)
  60. }
  61. }
  62. )
  63. }
  64. function getAllDeletedDocs(projectId, callback) {
  65. const url = `${settings.apis.docstore.url}/project/${projectId}/doc-deleted`
  66. request.get({ url, timeout: TIMEOUT, json: true }, (error, res, docs) => {
  67. if (error) {
  68. callback(OError.tag(error, 'could not get deleted docs from docstore'))
  69. } else if (res.statusCode === 200) {
  70. callback(null, docs)
  71. } else {
  72. callback(
  73. new OError(
  74. `docstore api responded with non-success code: ${res.statusCode}`,
  75. { projectId }
  76. )
  77. )
  78. }
  79. })
  80. }
  81. function getAllRanges(projectId, callback) {
  82. const url = `${settings.apis.docstore.url}/project/${projectId}/ranges`
  83. request.get(
  84. {
  85. url,
  86. timeout: TIMEOUT,
  87. json: true,
  88. },
  89. (error, res, docs) => {
  90. if (error) {
  91. return callback(error)
  92. }
  93. if (res.statusCode >= 200 && res.statusCode < 300) {
  94. callback(null, docs)
  95. } else {
  96. error = new OError(
  97. `docstore api responded with non-success code: ${res.statusCode}`,
  98. { projectId }
  99. )
  100. callback(error)
  101. }
  102. }
  103. )
  104. }
  105. function getDoc(projectId, docId, options, callback) {
  106. if (options == null) {
  107. options = {}
  108. }
  109. if (typeof options === 'function') {
  110. callback = options
  111. options = {}
  112. }
  113. const requestParams = { timeout: TIMEOUT, json: true }
  114. if (options.peek) {
  115. requestParams.url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}/peek`
  116. } else {
  117. requestParams.url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}`
  118. }
  119. if (options.include_deleted) {
  120. requestParams.qs = { include_deleted: 'true' }
  121. }
  122. request.get(requestParams, (error, res, doc) => {
  123. if (error) {
  124. return callback(error)
  125. }
  126. if (res.statusCode >= 200 && res.statusCode < 300) {
  127. logger.log(
  128. { docId, projectId, version: doc.version, rev: doc.rev },
  129. 'got doc from docstore api'
  130. )
  131. callback(null, doc.lines, doc.rev, doc.version, doc.ranges)
  132. } else if (res.statusCode === 404) {
  133. error = new Errors.NotFoundError({
  134. message: 'doc not found in docstore',
  135. info: {
  136. projectId,
  137. docId,
  138. },
  139. })
  140. callback(error)
  141. } else {
  142. error = new OError(
  143. `docstore api responded with non-success code: ${res.statusCode}`,
  144. {
  145. projectId,
  146. docId,
  147. }
  148. )
  149. callback(error)
  150. }
  151. })
  152. }
  153. function isDocDeleted(projectId, docId, callback) {
  154. const url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}/deleted`
  155. request.get({ url, timeout: TIMEOUT, json: true }, (err, res, body) => {
  156. if (err) {
  157. callback(err)
  158. } else if (res.statusCode === 200) {
  159. callback(null, body.deleted)
  160. } else if (res.statusCode === 404) {
  161. callback(
  162. new Errors.NotFoundError({
  163. message: 'doc does not exist in project',
  164. info: { projectId, docId },
  165. })
  166. )
  167. } else {
  168. callback(
  169. new OError(
  170. `docstore api responded with non-success code: ${res.statusCode}`,
  171. { projectId, docId }
  172. )
  173. )
  174. }
  175. })
  176. }
  177. function updateDoc(projectId, docId, lines, version, ranges, callback) {
  178. const url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}`
  179. request.post(
  180. {
  181. url,
  182. timeout: TIMEOUT,
  183. json: {
  184. lines,
  185. version,
  186. ranges,
  187. },
  188. },
  189. (error, res, result) => {
  190. if (error) {
  191. return callback(error)
  192. }
  193. if (res.statusCode >= 200 && res.statusCode < 300) {
  194. logger.log({ projectId, docId }, 'update doc in docstore url finished')
  195. callback(null, result.modified, result.rev)
  196. } else {
  197. error = new OError(
  198. `docstore api responded with non-success code: ${res.statusCode}`,
  199. { projectId, docId }
  200. )
  201. callback(error)
  202. }
  203. }
  204. )
  205. }
  206. function archiveProject(projectId, callback) {
  207. _operateOnProject(projectId, 'archive', callback)
  208. }
  209. function unarchiveProject(projectId, callback) {
  210. _operateOnProject(projectId, 'unarchive', callback)
  211. }
  212. function destroyProject(projectId, callback) {
  213. _operateOnProject(projectId, 'destroy', callback)
  214. }
  215. function _operateOnProject(projectId, method, callback) {
  216. const url = `${settings.apis.docstore.url}/project/${projectId}/${method}`
  217. logger.log({ projectId }, `calling ${method} for project in docstore`)
  218. // use default timeout for archiving/unarchiving/destroying
  219. request.post(url, (err, res, docs) => {
  220. if (err) {
  221. OError.tag(err, `error calling ${method} project in docstore`, {
  222. projectId,
  223. })
  224. return callback(err)
  225. }
  226. if (res.statusCode >= 200 && res.statusCode < 300) {
  227. callback()
  228. } else {
  229. const error = new Error(
  230. `docstore api responded with non-success code: ${res.statusCode}`
  231. )
  232. logger.warn(
  233. { err: error, projectId },
  234. `error calling ${method} project in docstore`
  235. )
  236. callback(error)
  237. }
  238. })
  239. }
  240. module.exports = {
  241. deleteDoc,
  242. getAllDocs,
  243. getAllDeletedDocs,
  244. getAllRanges,
  245. getDoc,
  246. isDocDeleted,
  247. updateDoc,
  248. archiveProject,
  249. unarchiveProject,
  250. destroyProject,
  251. promises: {
  252. deleteDoc: promisify(deleteDoc),
  253. getAllDocs: promisify(getAllDocs),
  254. getAllDeletedDocs: promisify(getAllDeletedDocs),
  255. getAllRanges: promisify(getAllRanges),
  256. getDoc: promisifyMultiResult(getDoc, ['lines', 'rev', 'version', 'ranges']),
  257. isDocDeleted: promisify(isDocDeleted),
  258. updateDoc: promisifyMultiResult(updateDoc, ['modified', 'rev']),
  259. archiveProject: promisify(archiveProject),
  260. unarchiveProject: promisify(unarchiveProject),
  261. destroyProject: promisify(destroyProject),
  262. },
  263. }