Router.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import OError from '@overleaf/o-error'
  2. import * as HttpController from './HttpController.js'
  3. import { Joi, validate } from './Validation.js'
  4. export function initialize(app) {
  5. app.use(
  6. validate({
  7. params: Joi.object({
  8. project_id: Joi.string().regex(/^[0-9a-f]{24}$/),
  9. user_id: Joi.string().regex(/^[0-9a-f]{24}$/),
  10. label_id: Joi.string().regex(/^[0-9a-f]{24}$/),
  11. version: Joi.number().integer(),
  12. }),
  13. })
  14. )
  15. // use an extended timeout on all endpoints, to allow for long requests to history-v1
  16. app.use(longerTimeout)
  17. app.post('/project', HttpController.initializeProject)
  18. app.delete('/project/:project_id', HttpController.deleteProject)
  19. app.get('/project/:project_id/snapshot', HttpController.getLatestSnapshot)
  20. app.get(
  21. '/project/:project_id/latest/history',
  22. HttpController.getMostRecentChunk
  23. )
  24. app.get(
  25. '/project/:project_id/diff',
  26. validate({
  27. query: {
  28. pathname: Joi.string().required(),
  29. from: Joi.number().integer().required(),
  30. to: Joi.number().integer().required(),
  31. },
  32. }),
  33. HttpController.getDiff
  34. )
  35. app.get(
  36. '/project/:project_id/filetree/diff',
  37. validate({
  38. query: {
  39. from: Joi.number().integer().required(),
  40. to: Joi.number().integer().required(),
  41. },
  42. }),
  43. HttpController.getFileTreeDiff
  44. )
  45. app.get(
  46. '/project/:project_id/updates',
  47. validate({
  48. query: {
  49. before: Joi.number().integer(),
  50. min_count: Joi.number().integer(),
  51. },
  52. }),
  53. HttpController.getUpdates
  54. )
  55. app.get(
  56. '/project/:project_id/changes',
  57. validate({
  58. query: {
  59. since: Joi.number().integer().min(0),
  60. },
  61. }),
  62. HttpController.getChangesSince
  63. )
  64. app.get(
  65. '/project/:project_id/changes-in-chunk',
  66. validate({
  67. query: {
  68. since: Joi.number().integer().min(0),
  69. },
  70. }),
  71. HttpController.getChangesInChunkSince
  72. )
  73. app.get('/project/:project_id/version', HttpController.latestVersion)
  74. app.post(
  75. '/project/:project_id/flush',
  76. validate({
  77. query: {
  78. debug: Joi.boolean().default(false),
  79. bisect: Joi.boolean().default(false),
  80. },
  81. }),
  82. HttpController.flushProject
  83. )
  84. app.post(
  85. '/project/:project_id/resync',
  86. validate({
  87. query: {
  88. force: Joi.boolean().default(false),
  89. },
  90. body: {
  91. force: Joi.boolean().default(false),
  92. origin: Joi.object({
  93. kind: Joi.string().required(),
  94. }),
  95. historyRangesMigration: Joi.string()
  96. .optional()
  97. .valid('forwards', 'backwards'),
  98. },
  99. }),
  100. HttpController.resyncProject
  101. )
  102. app.get(
  103. '/project/:project_id/dump',
  104. validate({
  105. query: {
  106. count: Joi.number().integer(),
  107. },
  108. }),
  109. HttpController.dumpProject
  110. )
  111. app.get('/project/:project_id/labels', HttpController.getLabels)
  112. app.post(
  113. '/project/:project_id/labels',
  114. validate({
  115. body: {
  116. version: Joi.number().integer().required(),
  117. comment: Joi.string().required(),
  118. created_at: Joi.string(),
  119. validate_exists: Joi.boolean().default(true),
  120. user_id: Joi.string(),
  121. },
  122. }),
  123. HttpController.createLabel
  124. )
  125. // Temporarily maintaining both paths for createLabel to support backwards
  126. // compatibility while rolling out - https://github.com/overleaf/internal/pull/20200
  127. app.post(
  128. '/project/:project_id/user/:user_id/labels',
  129. validate({
  130. body: {
  131. version: Joi.number().integer().required(),
  132. comment: Joi.string().required(),
  133. created_at: Joi.string(),
  134. validate_exists: Joi.boolean().default(true),
  135. },
  136. }),
  137. HttpController.createLabel
  138. )
  139. app.delete(
  140. '/project/:project_id/user/:user_id/labels/:label_id',
  141. validate({
  142. params: Joi.object({
  143. project_id: Joi.string().regex(/^[0-9a-f]{24}$/),
  144. user_id: Joi.string().regex(/^[0-9a-f]{24}$/),
  145. label_id: Joi.string().regex(/^[0-9a-f]{24}$/),
  146. }),
  147. }),
  148. HttpController.deleteLabelForUser
  149. )
  150. app.delete(
  151. '/project/:project_id/labels/:label_id',
  152. validate({
  153. params: Joi.object({
  154. project_id: Joi.string().regex(/^[0-9a-f]{24}$/),
  155. label_id: Joi.string().regex(/^[0-9a-f]{24}$/),
  156. }),
  157. }),
  158. HttpController.deleteLabel
  159. )
  160. app.post(
  161. '/user/:from_user/labels/transfer/:to_user',
  162. HttpController.transferLabels
  163. )
  164. app.get(
  165. '/project/:project_id/version/:version/:pathname',
  166. HttpController.getFileSnapshot
  167. )
  168. app.get(
  169. '/project/:project_id/ranges/version/:version/:pathname',
  170. HttpController.getRangesSnapshot
  171. )
  172. app.get(
  173. '/project/:project_id/metadata/version/:version/:pathname',
  174. HttpController.getFileMetadataSnapshot
  175. )
  176. app.get(
  177. '/project/:project_id/version/:version',
  178. HttpController.getProjectSnapshot
  179. )
  180. app.get(
  181. '/project/:project_id/paths/version/:version',
  182. HttpController.getPathsAtVersion
  183. )
  184. app.post(
  185. '/project/:project_id/force',
  186. validate({
  187. query: {
  188. clear: Joi.boolean().default(false),
  189. },
  190. }),
  191. HttpController.forceDebugProject
  192. )
  193. app.get('/project/:history_id/blob/:hash', HttpController.getProjectBlob)
  194. app.get('/status/failures', HttpController.getFailures)
  195. app.get('/status/queue', HttpController.getQueueCounts)
  196. app.post(
  197. '/retry/failures',
  198. validate({
  199. query: {
  200. failureType: Joi.string().valid('soft', 'hard'),
  201. // bail out after this time limit
  202. timeout: Joi.number().integer().default(300),
  203. // maximum number of projects to check
  204. limit: Joi.number().integer().default(100),
  205. callbackUrl: Joi.string(),
  206. },
  207. }),
  208. HttpController.retryFailures
  209. )
  210. app.post(
  211. '/flush/old',
  212. validate({
  213. query: {
  214. // flush projects with queued ops older than this
  215. maxAge: Joi.number()
  216. .integer()
  217. .default(6 * 3600),
  218. // pause this amount of time between checking queues
  219. queueDelay: Joi.number().integer().default(100),
  220. // maximum number of queues to check
  221. limit: Joi.number().integer().default(1000),
  222. // maximum amount of time allowed
  223. timeout: Joi.number()
  224. .integer()
  225. .default(60 * 1000),
  226. // whether to run in the background
  227. background: Joi.boolean().falsy('0').truthy('1').default(false),
  228. },
  229. }),
  230. HttpController.flushOld
  231. )
  232. app.get('/status', (req, res, next) => res.send('project-history is up'))
  233. app.get('/oops', function (req, res, next) {
  234. throw new OError('dummy test error')
  235. })
  236. app.get('/check_lock', HttpController.checkLock)
  237. app.get('/health_check', HttpController.healthCheck)
  238. }
  239. function longerTimeout(req, res, next) {
  240. res.setTimeout(6 * 60 * 1000)
  241. next()
  242. }