ReferencesHandler.mjs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /* eslint-disable
  2. n/handle-callback-err,
  3. max-len,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS103: Rewrite code to no longer use __guard__
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. import OError from '@overleaf/o-error'
  16. import logger from '@overleaf/logger'
  17. import request from 'request'
  18. import settings from '@overleaf/settings'
  19. import Features from '../../infrastructure/Features.js'
  20. import ProjectGetter from '../Project/ProjectGetter.js'
  21. import UserGetter from '../User/UserGetter.js'
  22. import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.js'
  23. import _ from 'lodash'
  24. import Async from 'async'
  25. import Errors from '../Errors/Errors.js'
  26. import { promisify } from '@overleaf/promise-utils'
  27. let ReferencesHandler
  28. if (!Features.hasFeature('references')) {
  29. logger.debug('references search not enabled')
  30. }
  31. export default ReferencesHandler = {
  32. _buildDocUrl(projectId, docId) {
  33. return {
  34. url: `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}/raw`,
  35. }
  36. },
  37. _findBibFileRefs(project) {
  38. const fileRefs = []
  39. function _process(folder) {
  40. _.forEach(folder.fileRefs || [], function (file) {
  41. if (
  42. __guard__(file != null ? file.name : undefined, x1 =>
  43. x1.match(/^.*\.bib$/)
  44. )
  45. ) {
  46. return fileRefs.push(file)
  47. }
  48. })
  49. return _.forEach(folder.folders || [], folder => _process(folder))
  50. }
  51. _.forEach(project.rootFolder || [], rootFolder => _process(rootFolder))
  52. return fileRefs
  53. },
  54. _findBibDocIds(project) {
  55. const ids = []
  56. function _process(folder) {
  57. _.forEach(folder.docs || [], function (doc) {
  58. if (
  59. __guard__(doc != null ? doc.name : undefined, x1 =>
  60. x1.match(/^.*\.bib$/)
  61. )
  62. ) {
  63. return ids.push(doc._id)
  64. }
  65. })
  66. return _.forEach(folder.folders || [], folder => _process(folder))
  67. }
  68. _.forEach(project.rootFolder || [], rootFolder => _process(rootFolder))
  69. return ids
  70. },
  71. _isFullIndex(project, callback) {
  72. if (callback == null) {
  73. callback = function () {}
  74. }
  75. return UserGetter.getUser(
  76. project.owner_ref,
  77. { features: true },
  78. function (err, owner) {
  79. if (err != null) {
  80. return callback(err)
  81. }
  82. const features = owner != null ? owner.features : undefined
  83. return callback(
  84. null,
  85. (features != null ? features.references : undefined) === true ||
  86. (features != null ? features.referencesSearch : undefined) === true
  87. )
  88. }
  89. )
  90. },
  91. indexAll(projectId, callback) {
  92. if (callback == null) {
  93. callback = function () {}
  94. }
  95. return ProjectGetter.getProject(
  96. projectId,
  97. { rootFolder: true, owner_ref: 1, 'overleaf.history.id': 1 },
  98. function (err, project) {
  99. if (err) {
  100. OError.tag(err, 'error finding project', {
  101. projectId,
  102. })
  103. return callback(err)
  104. }
  105. if (!project) {
  106. return callback(
  107. new Errors.NotFoundError(`project does not exist: ${projectId}`)
  108. )
  109. }
  110. logger.debug({ projectId }, 'indexing all bib files in project')
  111. const docIds = ReferencesHandler._findBibDocIds(project)
  112. const fileRefs = ReferencesHandler._findBibFileRefs(project)
  113. return ReferencesHandler._doIndexOperation(
  114. projectId,
  115. project,
  116. docIds,
  117. fileRefs,
  118. callback
  119. )
  120. }
  121. )
  122. },
  123. _doIndexOperation(projectId, project, docIds, fileRefs, callback) {
  124. if (!Features.hasFeature('references')) {
  125. return callback()
  126. }
  127. const historyId = project?.overleaf?.history?.id
  128. if (!historyId) {
  129. return callback(
  130. new OError('project does not have a history id', { projectId })
  131. )
  132. }
  133. return ReferencesHandler._isFullIndex(project, function (err, isFullIndex) {
  134. if (err) {
  135. OError.tag(err, 'error checking whether to do full index', {
  136. projectId,
  137. })
  138. return callback(err)
  139. }
  140. logger.debug(
  141. { projectId, docIds },
  142. 'flushing docs to mongo before calling references service'
  143. )
  144. return Async.series(
  145. docIds.map(
  146. docId => cb =>
  147. DocumentUpdaterHandler.flushDocToMongo(projectId, docId, cb)
  148. ),
  149. function (err) {
  150. // continue
  151. if (err) {
  152. OError.tag(err, 'error flushing docs to mongo', {
  153. projectId,
  154. docIds,
  155. })
  156. return callback(err)
  157. }
  158. const bibDocUrls = docIds.map(docId =>
  159. ReferencesHandler._buildDocUrl(projectId, docId)
  160. )
  161. const bibFileUrls = fileRefs.map(fileRef => ({
  162. url: `${settings.apis.project_history.url}/project/${historyId}/blob/${fileRef.hash}`,
  163. }))
  164. const sourceURLs = bibDocUrls.concat(bibFileUrls)
  165. return request.post(
  166. {
  167. url: `${settings.apis.references.url}/project/${projectId}/index`,
  168. json: {
  169. docUrls: sourceURLs.map(item => item.url),
  170. sourceURLs,
  171. fullIndex: isFullIndex,
  172. },
  173. },
  174. function (err, res, data) {
  175. if (err) {
  176. OError.tag(err, 'error communicating with references api', {
  177. projectId,
  178. })
  179. return callback(err)
  180. }
  181. if (res.statusCode >= 200 && res.statusCode < 300) {
  182. logger.debug({ projectId }, 'got keys from references api')
  183. return callback(null, data)
  184. } else {
  185. err = new Error(
  186. `references api responded with non-success code: ${res.statusCode}`
  187. )
  188. return callback(err)
  189. }
  190. }
  191. )
  192. }
  193. )
  194. })
  195. },
  196. }
  197. ReferencesHandler.promises = {
  198. indexAll: promisify(ReferencesHandler.indexAll),
  199. }
  200. function __guard__(value, transform) {
  201. return typeof value !== 'undefined' && value !== null
  202. ? transform(value)
  203. : undefined
  204. }