HttpController.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  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. let HttpController;
  16. const DocumentManager = require("./DocumentManager");
  17. const HistoryManager = require("./HistoryManager");
  18. const ProjectManager = require("./ProjectManager");
  19. const Errors = require("./Errors");
  20. const logger = require("logger-sharelatex");
  21. const Metrics = require("./Metrics");
  22. const ProjectFlusher = require("./ProjectFlusher");
  23. const DeleteQueueManager = require("./DeleteQueueManager");
  24. const async = require("async");
  25. const TWO_MEGABYTES = 2 * 1024 * 1024;
  26. module.exports = (HttpController = {
  27. getDoc(req, res, next) {
  28. let fromVersion;
  29. if (next == null) { next = function(error) {}; }
  30. const {
  31. doc_id
  32. } = req.params;
  33. const {
  34. project_id
  35. } = req.params;
  36. logger.log({project_id, doc_id}, "getting doc via http");
  37. const timer = new Metrics.Timer("http.getDoc");
  38. if ((req.query != null ? req.query.fromVersion : undefined) != null) {
  39. fromVersion = parseInt(req.query.fromVersion, 10);
  40. } else {
  41. fromVersion = -1;
  42. }
  43. return DocumentManager.getDocAndRecentOpsWithLock(project_id, doc_id, fromVersion, function(error, lines, version, ops, ranges, pathname) {
  44. timer.done();
  45. if (error != null) { return next(error); }
  46. logger.log({project_id, doc_id}, "got doc via http");
  47. if ((lines == null) || (version == null)) {
  48. return next(new Errors.NotFoundError("document not found"));
  49. }
  50. return res.json({
  51. id: doc_id,
  52. lines,
  53. version,
  54. ops,
  55. ranges,
  56. pathname
  57. });
  58. });
  59. },
  60. _getTotalSizeOfLines(lines) {
  61. let size = 0;
  62. for (const line of Array.from(lines)) {
  63. size += (line.length + 1);
  64. }
  65. return size;
  66. },
  67. getProjectDocsAndFlushIfOld(req, res, next) {
  68. if (next == null) { next = function(error) {}; }
  69. const {
  70. project_id
  71. } = req.params;
  72. const projectStateHash = req.query != null ? req.query.state : undefined;
  73. // exclude is string of existing docs "id:version,id:version,..."
  74. const excludeItems = __guard__(req.query != null ? req.query.exclude : undefined, x => x.split(',')) || [];
  75. logger.log({project_id, exclude: excludeItems}, "getting docs via http");
  76. const timer = new Metrics.Timer("http.getAllDocs");
  77. const excludeVersions = {};
  78. for (const item of Array.from(excludeItems)) {
  79. const [id,version] = Array.from(item != null ? item.split(':') : undefined);
  80. excludeVersions[id] = version;
  81. }
  82. logger.log({project_id, projectStateHash, excludeVersions}, "excluding versions");
  83. return ProjectManager.getProjectDocsAndFlushIfOld(project_id, projectStateHash, excludeVersions, function(error, result) {
  84. timer.done();
  85. if (error instanceof Errors.ProjectStateChangedError) {
  86. return res.sendStatus(409); // conflict
  87. } else if (error != null) {
  88. return next(error);
  89. } else {
  90. logger.log({project_id, result: ((Array.from(result).map((doc) => `${doc._id}:${doc.v}`)))}, "got docs via http");
  91. return res.send(result);
  92. }
  93. });
  94. },
  95. clearProjectState(req, res, next) {
  96. if (next == null) { next = function(error) {}; }
  97. const {
  98. project_id
  99. } = req.params;
  100. const timer = new Metrics.Timer("http.clearProjectState");
  101. logger.log({project_id}, "clearing project state via http");
  102. return ProjectManager.clearProjectState(project_id, function(error) {
  103. timer.done();
  104. if (error != null) {
  105. return next(error);
  106. } else {
  107. return res.sendStatus(200);
  108. }
  109. });
  110. },
  111. setDoc(req, res, next) {
  112. if (next == null) { next = function(error) {}; }
  113. const {
  114. doc_id
  115. } = req.params;
  116. const {
  117. project_id
  118. } = req.params;
  119. const {lines, source, user_id, undoing} = req.body;
  120. const lineSize = HttpController._getTotalSizeOfLines(lines);
  121. if (lineSize > TWO_MEGABYTES) {
  122. logger.log({project_id, doc_id, source, lineSize, user_id}, "document too large, returning 406 response");
  123. return res.sendStatus(406);
  124. }
  125. logger.log({project_id, doc_id, lines, source, user_id, undoing}, "setting doc via http");
  126. const timer = new Metrics.Timer("http.setDoc");
  127. return DocumentManager.setDocWithLock(project_id, doc_id, lines, source, user_id, undoing, function(error) {
  128. timer.done();
  129. if (error != null) { return next(error); }
  130. logger.log({project_id, doc_id}, "set doc via http");
  131. return res.sendStatus(204);
  132. });
  133. }, // No Content
  134. flushDocIfLoaded(req, res, next) {
  135. if (next == null) { next = function(error) {}; }
  136. const {
  137. doc_id
  138. } = req.params;
  139. const {
  140. project_id
  141. } = req.params;
  142. logger.log({project_id, doc_id}, "flushing doc via http");
  143. const timer = new Metrics.Timer("http.flushDoc");
  144. return DocumentManager.flushDocIfLoadedWithLock(project_id, doc_id, function(error) {
  145. timer.done();
  146. if (error != null) { return next(error); }
  147. logger.log({project_id, doc_id}, "flushed doc via http");
  148. return res.sendStatus(204);
  149. });
  150. }, // No Content
  151. deleteDoc(req, res, next) {
  152. if (next == null) { next = function(error) {}; }
  153. const {
  154. doc_id
  155. } = req.params;
  156. const {
  157. project_id
  158. } = req.params;
  159. const ignoreFlushErrors = req.query.ignore_flush_errors === 'true';
  160. const timer = new Metrics.Timer("http.deleteDoc");
  161. logger.log({project_id, doc_id}, "deleting doc via http");
  162. return DocumentManager.flushAndDeleteDocWithLock(project_id, doc_id, { ignoreFlushErrors }, function(error) {
  163. timer.done();
  164. // There is no harm in flushing project history if the previous call
  165. // failed and sometimes it is required
  166. HistoryManager.flushProjectChangesAsync(project_id);
  167. if (error != null) { return next(error); }
  168. logger.log({project_id, doc_id}, "deleted doc via http");
  169. return res.sendStatus(204);
  170. });
  171. }, // No Content
  172. flushProject(req, res, next) {
  173. if (next == null) { next = function(error) {}; }
  174. const {
  175. project_id
  176. } = req.params;
  177. logger.log({project_id}, "flushing project via http");
  178. const timer = new Metrics.Timer("http.flushProject");
  179. return ProjectManager.flushProjectWithLocks(project_id, function(error) {
  180. timer.done();
  181. if (error != null) { return next(error); }
  182. logger.log({project_id}, "flushed project via http");
  183. return res.sendStatus(204);
  184. });
  185. }, // No Content
  186. deleteProject(req, res, next) {
  187. if (next == null) { next = function(error) {}; }
  188. const {
  189. project_id
  190. } = req.params;
  191. logger.log({project_id}, "deleting project via http");
  192. const options = {};
  193. if (req.query != null ? req.query.background : undefined) { options.background = true; } // allow non-urgent flushes to be queued
  194. if (req.query != null ? req.query.shutdown : undefined) { options.skip_history_flush = true; } // don't flush history when realtime shuts down
  195. if (req.query != null ? req.query.background : undefined) {
  196. return ProjectManager.queueFlushAndDeleteProject(project_id, function(error) {
  197. if (error != null) { return next(error); }
  198. logger.log({project_id}, "queue delete of project via http");
  199. return res.sendStatus(204);
  200. }); // No Content
  201. } else {
  202. const timer = new Metrics.Timer("http.deleteProject");
  203. return ProjectManager.flushAndDeleteProjectWithLocks(project_id, options, function(error) {
  204. timer.done();
  205. if (error != null) { return next(error); }
  206. logger.log({project_id}, "deleted project via http");
  207. return res.sendStatus(204);
  208. });
  209. }
  210. }, // No Content
  211. deleteMultipleProjects(req, res, next) {
  212. if (next == null) { next = function(error) {}; }
  213. const project_ids = (req.body != null ? req.body.project_ids : undefined) || [];
  214. logger.log({project_ids}, "deleting multiple projects via http");
  215. return async.eachSeries(project_ids, function(project_id, cb) {
  216. logger.log({project_id}, "queue delete of project via http");
  217. return ProjectManager.queueFlushAndDeleteProject(project_id, cb);
  218. }
  219. , function(error) {
  220. if (error != null) { return next(error); }
  221. return res.sendStatus(204);
  222. });
  223. }, // No Content
  224. acceptChanges(req, res, next) {
  225. if (next == null) { next = function(error) {}; }
  226. const {project_id, doc_id} = req.params;
  227. let change_ids = req.body != null ? req.body.change_ids : undefined;
  228. if ((change_ids == null)) {
  229. change_ids = [ req.params.change_id ];
  230. }
  231. logger.log({project_id, doc_id}, `accepting ${ change_ids.length } changes via http`);
  232. const timer = new Metrics.Timer("http.acceptChanges");
  233. return DocumentManager.acceptChangesWithLock(project_id, doc_id, change_ids, function(error) {
  234. timer.done();
  235. if (error != null) { return next(error); }
  236. logger.log({project_id, doc_id}, `accepted ${ change_ids.length } changes via http`);
  237. return res.sendStatus(204);
  238. });
  239. }, // No Content
  240. deleteComment(req, res, next) {
  241. if (next == null) { next = function(error) {}; }
  242. const {project_id, doc_id, comment_id} = req.params;
  243. logger.log({project_id, doc_id, comment_id}, "deleting comment via http");
  244. const timer = new Metrics.Timer("http.deleteComment");
  245. return DocumentManager.deleteCommentWithLock(project_id, doc_id, comment_id, function(error) {
  246. timer.done();
  247. if (error != null) { return next(error); }
  248. logger.log({project_id, doc_id, comment_id}, "deleted comment via http");
  249. return res.sendStatus(204);
  250. });
  251. }, // No Content
  252. updateProject(req, res, next) {
  253. if (next == null) { next = function(error) {}; }
  254. const timer = new Metrics.Timer("http.updateProject");
  255. const {
  256. project_id
  257. } = req.params;
  258. const {projectHistoryId, userId, docUpdates, fileUpdates, version} = req.body;
  259. logger.log({project_id, docUpdates, fileUpdates, version}, "updating project via http");
  260. return ProjectManager.updateProjectWithLocks(project_id, projectHistoryId, userId, docUpdates, fileUpdates, version, function(error) {
  261. timer.done();
  262. if (error != null) { return next(error); }
  263. logger.log({project_id}, "updated project via http");
  264. return res.sendStatus(204);
  265. });
  266. }, // No Content
  267. resyncProjectHistory(req, res, next) {
  268. if (next == null) { next = function(error) {}; }
  269. const {
  270. project_id
  271. } = req.params;
  272. const {projectHistoryId, docs, files} = req.body;
  273. logger.log({project_id, docs, files}, "queuing project history resync via http");
  274. return HistoryManager.resyncProjectHistory(project_id, projectHistoryId, docs, files, function(error) {
  275. if (error != null) { return next(error); }
  276. logger.log({project_id}, "queued project history resync via http");
  277. return res.sendStatus(204);
  278. });
  279. },
  280. flushAllProjects(req, res, next ){
  281. if (next == null) { next = function(error){}; }
  282. res.setTimeout(5 * 60 * 1000);
  283. const options = {
  284. limit : req.query.limit || 1000,
  285. concurrency : req.query.concurrency || 5,
  286. dryRun : req.query.dryRun || false
  287. };
  288. return ProjectFlusher.flushAllProjects(options, function(err, project_ids){
  289. if (err != null) {
  290. logger.err({err}, "error bulk flushing projects");
  291. return res.sendStatus(500);
  292. } else {
  293. return res.send(project_ids);
  294. }
  295. });
  296. },
  297. flushQueuedProjects(req, res, next) {
  298. if (next == null) { next = function(error) {}; }
  299. res.setTimeout(10 * 60 * 1000);
  300. const options = {
  301. limit : req.query.limit || 1000,
  302. timeout: 5 * 60 * 1000,
  303. min_delete_age: req.query.min_delete_age || (5 * 60 * 1000)
  304. };
  305. return DeleteQueueManager.flushAndDeleteOldProjects(options, function(err, flushed){
  306. if (err != null) {
  307. logger.err({err}, "error flushing old projects");
  308. return res.sendStatus(500);
  309. } else {
  310. logger.log({flushed}, "flush of queued projects completed");
  311. return res.send({flushed});
  312. }
  313. });
  314. }
  315. });
  316. function __guard__(value, transform) {
  317. return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
  318. }