PersistenceManager.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-unsafe-negation,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS101: Remove unnecessary use of Array.from
  12. * DS102: Remove unnecessary code created because of implicit returns
  13. * DS205: Consider reworking code to avoid use of IIFEs
  14. * DS207: Consider shorter variations of null checks
  15. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  16. */
  17. let PersistenceManager
  18. const Settings = require('@overleaf/settings')
  19. const Errors = require('./Errors')
  20. const Metrics = require('./Metrics')
  21. const logger = require('logger-sharelatex')
  22. const request = require('requestretry').defaults({
  23. maxAttempts: 2,
  24. retryDelay: 10,
  25. })
  26. // We have to be quick with HTTP calls because we're holding a lock that
  27. // expires after 30 seconds. We can't let any errors in the rest of the stack
  28. // hold us up, and need to bail out quickly if there is a problem.
  29. const MAX_HTTP_REQUEST_LENGTH = 5000 // 5 seconds
  30. const updateMetric = function (method, error, response) {
  31. // find the status, with special handling for connection timeouts
  32. // https://github.com/request/request#timeouts
  33. const status = (() => {
  34. if ((error != null ? error.connect : undefined) === true) {
  35. return `${error.code} (connect)`
  36. } else if (error != null) {
  37. return error.code
  38. } else if (response != null) {
  39. return response.statusCode
  40. }
  41. })()
  42. Metrics.inc(method, 1, { status })
  43. if ((error != null ? error.attempts : undefined) > 1) {
  44. Metrics.inc(`${method}-retries`, 1, { status: 'error' })
  45. }
  46. if ((response != null ? response.attempts : undefined) > 1) {
  47. return Metrics.inc(`${method}-retries`, 1, { status: 'success' })
  48. }
  49. }
  50. module.exports = PersistenceManager = {
  51. getDoc(project_id, doc_id, _callback) {
  52. if (_callback == null) {
  53. _callback = function (
  54. error,
  55. lines,
  56. version,
  57. ranges,
  58. pathname,
  59. projectHistoryId,
  60. projectHistoryType
  61. ) {}
  62. }
  63. const timer = new Metrics.Timer('persistenceManager.getDoc')
  64. const callback = function (...args) {
  65. timer.done()
  66. return _callback(...Array.from(args || []))
  67. }
  68. const urlPath = `/project/${project_id}/doc/${doc_id}`
  69. return request(
  70. {
  71. url: `${Settings.apis.web.url}${urlPath}`,
  72. method: 'GET',
  73. headers: {
  74. accept: 'application/json',
  75. },
  76. auth: {
  77. user: Settings.apis.web.user,
  78. pass: Settings.apis.web.pass,
  79. sendImmediately: true,
  80. },
  81. jar: false,
  82. timeout: MAX_HTTP_REQUEST_LENGTH,
  83. },
  84. function (error, res, body) {
  85. updateMetric('getDoc', error, res)
  86. if (error != null) {
  87. logger.error(
  88. { err: error, project_id, doc_id },
  89. 'web API request failed'
  90. )
  91. return callback(new Error('error connecting to web API'))
  92. }
  93. if (res.statusCode >= 200 && res.statusCode < 300) {
  94. try {
  95. body = JSON.parse(body)
  96. } catch (e) {
  97. return callback(e)
  98. }
  99. if (body.lines == null) {
  100. return callback(new Error('web API response had no doc lines'))
  101. }
  102. if (body.version == null || !body.version instanceof Number) {
  103. return callback(
  104. new Error('web API response had no valid doc version')
  105. )
  106. }
  107. if (body.pathname == null) {
  108. return callback(
  109. new Error('web API response had no valid doc pathname')
  110. )
  111. }
  112. return callback(
  113. null,
  114. body.lines,
  115. body.version,
  116. body.ranges,
  117. body.pathname,
  118. body.projectHistoryId,
  119. body.projectHistoryType
  120. )
  121. } else if (res.statusCode === 404) {
  122. return callback(
  123. new Errors.NotFoundError(`doc not not found: ${urlPath}`)
  124. )
  125. } else {
  126. return callback(
  127. new Error(`error accessing web API: ${urlPath} ${res.statusCode}`)
  128. )
  129. }
  130. }
  131. )
  132. },
  133. setDoc(
  134. project_id,
  135. doc_id,
  136. lines,
  137. version,
  138. ranges,
  139. lastUpdatedAt,
  140. lastUpdatedBy,
  141. _callback
  142. ) {
  143. if (_callback == null) {
  144. _callback = function (error) {}
  145. }
  146. const timer = new Metrics.Timer('persistenceManager.setDoc')
  147. const callback = function (...args) {
  148. timer.done()
  149. return _callback(...Array.from(args || []))
  150. }
  151. const urlPath = `/project/${project_id}/doc/${doc_id}`
  152. return request(
  153. {
  154. url: `${Settings.apis.web.url}${urlPath}`,
  155. method: 'POST',
  156. json: {
  157. lines,
  158. ranges,
  159. version,
  160. lastUpdatedBy,
  161. lastUpdatedAt,
  162. },
  163. auth: {
  164. user: Settings.apis.web.user,
  165. pass: Settings.apis.web.pass,
  166. sendImmediately: true,
  167. },
  168. jar: false,
  169. timeout: MAX_HTTP_REQUEST_LENGTH,
  170. },
  171. function (error, res, body) {
  172. updateMetric('setDoc', error, res)
  173. if (error != null) {
  174. logger.error(
  175. { err: error, project_id, doc_id },
  176. 'web API request failed'
  177. )
  178. return callback(new Error('error connecting to web API'))
  179. }
  180. if (res.statusCode >= 200 && res.statusCode < 300) {
  181. return callback(null)
  182. } else if (res.statusCode === 404) {
  183. return callback(
  184. new Errors.NotFoundError(`doc not not found: ${urlPath}`)
  185. )
  186. } else {
  187. return callback(
  188. new Error(`error accessing web API: ${urlPath} ${res.statusCode}`)
  189. )
  190. }
  191. }
  192. )
  193. },
  194. }