From 884a89893dc9721157fb9f3c41c069acbb4730ab Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 27 Apr 2017 16:07:03 +0100 Subject: [PATCH 01/28] Save and read filtering options from local storage. --- .../main/project-list/project-list.coffee | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index 161598d32d..f65fcc90b3 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -2,7 +2,7 @@ define [ "base" ], (App) -> - App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout) -> + App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout, localStorage) -> $scope.projects = window.data.projects $scope.tags = window.data.tags $scope.notifications = window.data.notifications @@ -18,6 +18,8 @@ define [ recalculateProjectListHeight() , 10 + storedUIOpts = JSON.parse(localStorage("project_list")) + recalculateProjectListHeight = () -> topOffset = $(".project-list-card")?.offset()?.top bottomOffset = $("footer").outerHeight() + 25 @@ -52,6 +54,13 @@ define [ project.tags ||= [] project.tags.push tag + markTagAsSelected = (id) -> + for tag in $scope.tags + if tag._id == id + tag.selected = true + else + tag.selected = false + $scope.changePredicate = (newPredicate)-> if $scope.predicate == newPredicate $scope.reverse = !$scope.reverse @@ -126,6 +135,11 @@ define [ else # We don't want hidden selections project.selected = false + + localStorage("project_list", JSON.stringify({ + filter: $scope.filter, + selectedTagId: selectedTag?._id + })) $scope.updateSelectedProjects() $scope.getSelectedTag = () -> @@ -424,8 +438,6 @@ define [ path = "/project/#{selected_project_ids[0]}/download/zip" window.location = path - - $scope.updateVisibleProjects() App.controller "ProjectListItemController", ($scope) -> $scope.ownerName = () -> From 877cd8e01888c9c08555a15746674fe4e2622145 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 27 Apr 2017 16:07:21 +0100 Subject: [PATCH 02/28] Apply stored filtering options. --- .../public/coffee/main/project-list/project-list.coffee | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index f65fcc90b3..fc9c3ea800 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -438,6 +438,13 @@ define [ path = "/project/#{selected_project_ids[0]}/download/zip" window.location = path + + if storedUIOpts?.filter? + if storedUIOpts.filter == "tag" and storedUIOpts.selectedTagId? + markTagAsSelected(storedUIOpts.selectedTagId) + $scope.setFilter(storedUIOpts.filter) + else + $scope.updateVisibleProjects() App.controller "ProjectListItemController", ($scope) -> $scope.ownerName = () -> From 46f693519a7c8cceac7f6f312aff469c535cbe41 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 27 Apr 2017 16:41:53 +0100 Subject: [PATCH 03/28] return 404 for api request on missing doc --- .../Features/Docstore/DocstoreManager.coffee | 9 +++++ .../Docstore/DocstoreManagerTests.coffee | 35 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/services/web/app/coffee/Features/Docstore/DocstoreManager.coffee b/services/web/app/coffee/Features/Docstore/DocstoreManager.coffee index 06dd14c17b..927121a6a1 100644 --- a/services/web/app/coffee/Features/Docstore/DocstoreManager.coffee +++ b/services/web/app/coffee/Features/Docstore/DocstoreManager.coffee @@ -1,6 +1,7 @@ request = require("request").defaults(jar: false) logger = require "logger-sharelatex" settings = require "settings-sharelatex" +Errors = require "../Errors/Errors" module.exports = DocstoreManager = deleteDoc: (project_id, doc_id, callback = (error) ->) -> @@ -10,6 +11,10 @@ module.exports = DocstoreManager = return callback(error) if error? if 200 <= res.statusCode < 300 callback(null) + else if res.statusCode is 404 + error = new Errors.NotFoundError("tried to delete doc not in docstore") + logger.error err: error, project_id: project_id, doc_id: doc_id, "tried to delete doc not in docstore" + callback(error) # maybe suppress the error when delete doc which is not present? else error = new Error("docstore api responded with non-success code: #{res.statusCode}") logger.error err: error, project_id: project_id, doc_id: doc_id, "error deleting doc in docstore" @@ -61,6 +66,10 @@ module.exports = DocstoreManager = if 200 <= res.statusCode < 300 logger.log doc_id: doc_id, project_id: project_id, version: doc.version, rev: doc.rev, "got doc from docstore api" callback(null, doc.lines, doc.rev, doc.version, doc.ranges) + else if res.statusCode is 404 + error = new Errors.NotFoundError("doc not found in docstore") + logger.error err: error, project_id: project_id, doc_id: doc_id, "doc not found in docstore" + callback(error) else error = new Error("docstore api responded with non-success code: #{res.statusCode}") logger.error err: error, project_id: project_id, doc_id: doc_id, "error getting doc from docstore" diff --git a/services/web/test/UnitTests/coffee/Docstore/DocstoreManagerTests.coffee b/services/web/test/UnitTests/coffee/Docstore/DocstoreManagerTests.coffee index abcc55a0b9..ec7a8ce451 100644 --- a/services/web/test/UnitTests/coffee/Docstore/DocstoreManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Docstore/DocstoreManagerTests.coffee @@ -3,6 +3,7 @@ chai.should() sinon = require("sinon") modulePath = "../../../../app/js/Features/Docstore/DocstoreManager" SandboxedModule = require('sandboxed-module') +Errors = require "../../../../app/js/Features/Errors/Errors.js" describe "DocstoreManager", -> beforeEach -> @@ -52,6 +53,23 @@ describe "DocstoreManager", -> }, "error deleting doc in docstore") .should.equal true + describe "with a missing (404) response code", -> + beforeEach -> + @request.del = sinon.stub().callsArgWith(1, null, statusCode: 404, "") + @DocstoreManager.deleteDoc @project_id, @doc_id, @callback + + it "should call the callback with an error", -> + @callback.calledWith(new Errors.NotFoundError("tried to delete doc not in docstore")).should.equal true + + it "should log the error", -> + @logger.error + .calledWith({ + err: new Errors.NotFoundError("tried to delete doc not in docstore") + project_id: @project_id + doc_id: @doc_id + }, "tried to delete doc not in docstore") + .should.equal true + describe "updateDoc", -> beforeEach -> @lines = ["mock", "doc", "lines"] @@ -153,6 +171,23 @@ describe "DocstoreManager", -> it "should call the callback with the lines, version and rev", -> @callback.calledWith(null, @lines, @rev, @version, @ranges).should.equal true + describe "with a missing (404) response code", -> + beforeEach -> + @request.get = sinon.stub().callsArgWith(1, null, statusCode: 404, "") + @DocstoreManager.getDoc @project_id, @doc_id, @callback + + it "should call the callback with an error", -> + @callback.calledWith(new Errors.NotFoundError("doc not found in docstore")).should.equal true + + it "should log the error", -> + @logger.error + .calledWith({ + err: new Errors.NotFoundError("doc not found in docstore") + project_id: @project_id + doc_id: @doc_id + }, "doc not found in docstore") + .should.equal true + describe "getAllDocs", -> describe "with a successful response code", -> beforeEach -> From 84a265825984ba71d1a779928865247c7c0cb67c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 28 Apr 2017 12:11:51 +0100 Subject: [PATCH 04/28] Add an untagged filter in the projects list. --- services/web/app/views/project/list/side-bar.pug | 5 +++++ .../web/public/coffee/main/project-list/project-list.coffee | 4 ++++ .../public/coffee/main/project-list/tag-controllers.coffee | 6 +++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/project/list/side-bar.pug b/services/web/app/views/project/list/side-bar.pug index 1fb0e2bda9..1c946a5664 100644 --- a/services/web/app/views/project/list/side-bar.pug +++ b/services/web/app/views/project/list/side-bar.pug @@ -79,6 +79,11 @@ li a(href, ng-click="deleteTag(tag)", stop-propagation="click") | #{translate("delete")} + li.tag( + ng-if="tags.length", + ng-cloak, + ng-click="selectUntagged()" + ) uncategorized li(ng-cloak) a.tag(href, ng-click="openNewTagModal()") i.fa.fa-fw.fa-plus diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index fc9c3ea800..a46345b167 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -113,6 +113,10 @@ define [ if $scope.filter == "tag" and selectedTag? and project.id not in selectedTag.project_ids visible = false + # Hide tagged projects if we only want to see the uncategorized ones + if $scope.filter == "untagged" and project.tags? + visible = false + # Hide projects we own if we only want to see shared projects if $scope.filter == "shared" and project.accessLevel == "owner" visible = false diff --git a/services/web/public/coffee/main/project-list/tag-controllers.coffee b/services/web/public/coffee/main/project-list/tag-controllers.coffee index cc3415e32e..6a75e7491b 100644 --- a/services/web/public/coffee/main/project-list/tag-controllers.coffee +++ b/services/web/public/coffee/main/project-list/tag-controllers.coffee @@ -15,7 +15,11 @@ define [ $scope._clearTags() tag.selected = true $scope.setFilter("tag") - + + $scope.selectUntagged = () -> + $scope._clearTags() + $scope.setFilter("untagged") + $scope.deleteTag = (tag) -> modalInstance = $modal.open( templateUrl: "deleteTagModalTemplate" From 2d4c5f012a060327978b872a9afd4def20f5d6ac Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 28 Apr 2017 16:57:25 +0100 Subject: [PATCH 05/28] Styling for untagged filter. --- services/web/public/stylesheets/app/project-list.less | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 17f6b7bb25..2cf16e51a4 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -153,6 +153,13 @@ ul.folders-menu { line-height: 1.4; } } + .tag-untagged { + font-style: italic; + &:hover, + &:focus { + text-decoration: none; + } + } .tag-menu { > a { border: 1px solid @gray; From 6e402840998d9fbe98558dab178f81e0d8e45b46 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 28 Apr 2017 16:57:37 +0100 Subject: [PATCH 06/28] Add untagged projects counter. --- services/web/app/views/project/list/side-bar.pug | 6 +++++- .../public/coffee/main/project-list/project-list.coffee | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/services/web/app/views/project/list/side-bar.pug b/services/web/app/views/project/list/side-bar.pug index 1c946a5664..7550f66e2f 100644 --- a/services/web/app/views/project/list/side-bar.pug +++ b/services/web/app/views/project/list/side-bar.pug @@ -83,7 +83,11 @@ ng-if="tags.length", ng-cloak, ng-click="selectUntagged()" - ) uncategorized + ng-class="{active: filter === 'untagged'}", + ) + a.tag-untagged(href) + | #{translate("uncategorized")} + span.subdued ({{ nUntagged }}) li(ng-cloak) a.tag(href, ng-click="openNewTagModal()") i.fa.fa-fw.fa-plus diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index a46345b167..81659983fb 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -10,6 +10,7 @@ define [ $scope.selectedProjects = [] $scope.filter = "all" $scope.predicate = "lastUpdated" + $scope.nUntagged = 0 $scope.reverse = true $scope.searchText = value : "" @@ -18,6 +19,10 @@ define [ recalculateProjectListHeight() , 10 + $scope.$watch(( + () -> $scope.projects.filter((project) -> !project.tags? or project.tags.length == 0).length + ), (newVal) -> $scope.nUntagged = newVal) + storedUIOpts = JSON.parse(localStorage("project_list")) recalculateProjectListHeight = () -> @@ -114,7 +119,7 @@ define [ visible = false # Hide tagged projects if we only want to see the uncategorized ones - if $scope.filter == "untagged" and project.tags? + if $scope.filter == "untagged" and project.tags?.length > 0 visible = false # Hide projects we own if we only want to see shared projects From 9fb166d2b36dc62d9c754e119bcaa9e588613ca9 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 28 Apr 2017 17:09:34 +0100 Subject: [PATCH 07/28] Style improvements. --- .../web/app/views/project/list/side-bar.pug | 4 ++-- .../public/stylesheets/app/project-list.less | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/services/web/app/views/project/list/side-bar.pug b/services/web/app/views/project/list/side-bar.pug index 7550f66e2f..b3a3f11948 100644 --- a/services/web/app/views/project/list/side-bar.pug +++ b/services/web/app/views/project/list/side-bar.pug @@ -79,13 +79,13 @@ li a(href, ng-click="deleteTag(tag)", stop-propagation="click") | #{translate("delete")} - li.tag( + li.tag.untagged( ng-if="tags.length", ng-cloak, ng-click="selectUntagged()" ng-class="{active: filter === 'untagged'}", ) - a.tag-untagged(href) + a.tag-name(href) | #{translate("uncategorized")} span.subdued ({{ nUntagged }}) li(ng-cloak) diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 2cf16e51a4..3fb8cf6044 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -124,6 +124,20 @@ ul.folders-menu { } } } + &.untagged { + font-style: italic; + margin-bottom: @line-height-computed / 4; + a { + line-height: 1.7; + &:hover, + &:focus { + text-decoration: none; + } + } + span.subdued { + font-style: normal; + } + } &:hover { &:not(.active) { background-color: darken(@gray-lightest, 2%); @@ -153,13 +167,6 @@ ul.folders-menu { line-height: 1.4; } } - .tag-untagged { - font-style: italic; - &:hover, - &:focus { - text-decoration: none; - } - } .tag-menu { > a { border: 1px solid @gray; From bca916d50490187aebe354f3a101ce20b8020179 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 1 May 2017 15:04:08 +0100 Subject: [PATCH 08/28] Use header button classes in the PDF button. --- services/web/app/views/project/editor/header.pug | 2 +- services/web/public/stylesheets/app/editor/toolbar.less | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/project/editor/header.pug b/services/web/app/views/project/editor/header.pug index a1c13032f9..1046244713 100644 --- a/services/web/app/views/project/editor/header.pug +++ b/services/web/app/views/project/editor/header.pug @@ -15,7 +15,7 @@ header.toolbar.toolbar-header.toolbar-with-labels( i.fa.fa-fw.fa-level-up span(ng-controller="PdfViewToggleController") - a( + a.btn.btn-full-height.btn-full-height-no-border( href, ng-show="ui.pdfLayout == 'flat' && fileTreeClosed", tooltip="PDF", diff --git a/services/web/public/stylesheets/app/editor/toolbar.less b/services/web/public/stylesheets/app/editor/toolbar.less index a30b2897c4..5f84f64682 100644 --- a/services/web/public/stylesheets/app/editor/toolbar.less +++ b/services/web/public/stylesheets/app/editor/toolbar.less @@ -65,6 +65,10 @@ right: 4px; } } + .btn-full-height-no-border { + border-right: 0; + border-left: 0; + } .toolbar-left { float: left; From 2708fdf15984a6ee9c2602af1b857c7e20c45d95 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 1 May 2017 15:43:44 +0100 Subject: [PATCH 09/28] Add a button to allow inline removal of tags. --- .../app/views/project/list/project-list.pug | 16 +++++++++++----- .../public/stylesheets/app/project-list.less | 18 ++++++++++++++++-- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/services/web/app/views/project/list/project-list.pug b/services/web/app/views/project/list/project-list.pug index 0bb93e8336..38159bfc48 100644 --- a/services/web/app/views/project/list/project-list.pug +++ b/services/web/app/views/project/list/project-list.pug @@ -152,11 +152,17 @@ span( ng-controller="TagListController" ) - a.label.label-default.tag-label( - href, - ng-repeat='tag in project.tags', - ng-click="selectTag(tag)" - ) {{tag.name}} + .tag-label( + ng-repeat='tag in project.tags' + ) + a.label.label-default.tag-label-name( + href, + ng-click="selectTag(tag)" + ) {{tag.name}} + a.label.label-default.tag-label-remove( + href + ) × + .col-xs-2 span.owner {{ownerName()}} .col-xs-4 diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 3fb8cf6044..6697639078 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -260,10 +260,24 @@ ul.project-list { margin-left: @line-height-computed / 4; position: relative; top: -2px; - padding-top: 0.25em; display: inline-block; - color: white; } + .tag-label-name, + .tag-label-remove { + display: inline-block; + padding-top: 0.25em; + color: #FFF; + } + .tag-label-name { + padding-right: 0.3em; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .tag-label-remove { + padding-left: 0.3em; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } } i.tablesort { padding-left: 8px; From 208f021bd019ea451d4597d07ecf47489ce6d11c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 1 May 2017 16:00:25 +0100 Subject: [PATCH 10/28] Actually remove project from tag, inline. --- .../web/app/views/project/list/project-list.pug | 1 + .../main/project-list/project-list.coffee | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/services/web/app/views/project/list/project-list.pug b/services/web/app/views/project/list/project-list.pug index 38159bfc48..eae31a4316 100644 --- a/services/web/app/views/project/list/project-list.pug +++ b/services/web/app/views/project/list/project-list.pug @@ -161,6 +161,7 @@ ) {{tag.name}} a.label.label-default.tag-label-remove( href + ng-click="removeProjectFromTag(project, tag)" ) × .col-xs-2 diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index 81659983fb..ad9d0f3582 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -201,6 +201,23 @@ define [ # the projects from view $scope.updateVisibleProjects() + $scope.removeProjectFromTag = (project, tag) -> + tag.showWhenEmpty = true + + project.tags ||= [] + index = project.tags.indexOf tag + + if index > -1 + $scope._removeProjectIdsFromTagArray(tag, [ project.id ]) + project.tags.splice(index, 1) + queuedHttp({ + method: "DELETE" + url: "/tag/#{tag._id}/project/#{project.id}" + headers: + "X-CSRF-Token": window.csrfToken + }) + $scope.updateVisibleProjects() + $scope.addSelectedProjectsToTag = (tag) -> selected_projects = $scope.getSelectedProjects() event_tracking.send 'project-list-page-interaction', 'project action', 'addSelectedProjectsToTag' From d71296da9f95fac2440fb304183942e2721330fa Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 1 May 2017 17:11:03 +0100 Subject: [PATCH 11/28] Add row clicking in the projects list. --- .../web/app/views/project/list/project-list.pug | 9 +++++++-- .../web/public/coffee/directives/selectAll.coffee | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/services/web/app/views/project/list/project-list.pug b/services/web/app/views/project/list/project-list.pug index eae31a4316..629c3dc62d 100644 --- a/services/web/app/views/project/list/project-list.pug +++ b/services/web/app/views/project/list/project-list.pug @@ -140,20 +140,25 @@ ng-repeat="project in visibleProjects | orderBy:predicate:reverse", ng-controller="ProjectListItemController" ) - .row + .row(select-row) .col-xs-6 input.select-item( select-individual, type="checkbox", ng-model="project.selected" + stop-propagation="click" ) span - a.projectName(href="/project/{{project.id}}") {{project.name}} + a.projectName( + href="/project/{{project.id}}" + stop-propagation="click" + ) {{project.name}} span( ng-controller="TagListController" ) .tag-label( ng-repeat='tag in project.tags' + stop-propagation="click" ) a.label.label-default.tag-label-name( href, diff --git a/services/web/public/coffee/directives/selectAll.coffee b/services/web/public/coffee/directives/selectAll.coffee index d5be2639e6..6400d2d0ad 100644 --- a/services/web/public/coffee/directives/selectAll.coffee +++ b/services/web/public/coffee/directives/selectAll.coffee @@ -58,4 +58,18 @@ define [ scope.$apply () -> scope.ngModel = false ignoreChanges = false + + scope.$on "select-all:row-clicked", () -> + ignoreChanges = true + scope.$apply () -> + scope.ngModel = !scope.ngModel + ignoreChanges = false + } + + App.directive "selectRow", () -> + return { + scope: true + link: (scope, element, attrs) -> + element.on "click", (e) -> + scope.$broadcast "select-all:row-clicked" } \ No newline at end of file From bf872dc94f8795633d83d905481c8f87a23f30b0 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 2 May 2017 12:20:28 +0100 Subject: [PATCH 12/28] Vertically center label text and button. --- services/web/public/stylesheets/app/project-list.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 6697639078..13ca60e181 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -265,7 +265,7 @@ ul.project-list { .tag-label-name, .tag-label-remove { display: inline-block; - padding-top: 0.25em; + padding-top: 0.3em; color: #FFF; } .tag-label-name { From 8449b0417ca8df4b2ffccd898067e7d086caac98 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 4 May 2017 15:22:54 +0100 Subject: [PATCH 13/28] Move all redis end points to be cluster compatible --- .../Features/Compile/ClsiCookieManager.coffee | 4 +- .../Features/Compile/CompileManager.coffee | 4 +- .../DocumentUpdaterHandler.coffee | 14 ---- .../Editor/EditorRealTimeController.coffee | 10 +-- .../HealthCheck/HealthCheckController.coffee | 14 ++-- .../Security/OneTimeTokenHandler.coffee | 4 +- .../Features/User/UserSessionsManager.coffee | 1 - .../Features/User/UserSessionsRedis.coffee | 18 +---- .../coffee/infrastructure/LockManager.coffee | 4 +- .../coffee/infrastructure/RedisWrapper.coffee | 16 +---- .../Compile/ClsiCookieManagerTests.coffee | 4 +- .../coffee/Compile/CompileManagerTests.coffee | 4 +- .../DocumentUpdaterHandlerTests.coffee | 44 ------------- .../EditorRealTimeControllerTests.coffee | 13 ++-- .../Security/OneTimeTokenHandlerTests.coffee | 4 +- .../LockManager/CheckingTheLock.coffee | 6 +- .../LockManager/ReleasingTheLock.coffee | 4 +- .../LockManager/getLockTests.coffee | 4 +- .../LockManager/tryLockTests.coffee | 4 +- .../infrastructure/RedisWrapperTests.coffee | 66 +++++-------------- 20 files changed, 61 insertions(+), 181 deletions(-) diff --git a/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee index c237f212c0..c19c5e02ea 100644 --- a/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee @@ -1,7 +1,7 @@ Settings = require "settings-sharelatex" request = require('request') -redis = require("redis-sharelatex") -rclient = redis.createClient(Settings.redis.web) +RedisWrapper = require("../../infrastructure/RedisWrapper") +rclient = RedisWrapper.client("clsi_cookie") Cookie = require('cookie') logger = require "logger-sharelatex" diff --git a/services/web/app/coffee/Features/Compile/CompileManager.coffee b/services/web/app/coffee/Features/Compile/CompileManager.coffee index 3bfbf8df7d..ad8a2459ec 100755 --- a/services/web/app/coffee/Features/Compile/CompileManager.coffee +++ b/services/web/app/coffee/Features/Compile/CompileManager.coffee @@ -1,6 +1,6 @@ Settings = require('settings-sharelatex') -redis = require("redis-sharelatex") -rclient = redis.createClient(Settings.redis.web) +RedisWrapper = require("../../infrastructure/RedisWrapper") +rclient = RedisWrapper.client("clsi_recently_compiled") DocumentUpdaterHandler = require "../DocumentUpdater/DocumentUpdaterHandler" Project = require("../../models/Project").Project ProjectRootDocManager = require "../Project/ProjectRootDocManager" diff --git a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee index 595fe07971..805f063808 100644 --- a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee +++ b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee @@ -5,24 +5,10 @@ _ = require 'underscore' async = require 'async' logger = require('logger-sharelatex') metrics = require('metrics-sharelatex') -redis = require("redis-sharelatex") -rclient = redis.createClient(settings.redis.web) Project = require("../../models/Project").Project ProjectLocator = require('../../Features/Project/ProjectLocator') module.exports = DocumentUpdaterHandler = - - queueChange : (project_id, doc_id, change, callback = ()->)-> - jsonChange = JSON.stringify change - doc_key = keys.combineProjectIdAndDocId(project_id, doc_id) - multi = rclient.multi() - multi.rpush keys.pendingUpdates(doc_id:doc_id), jsonChange - multi.sadd keys.docsWithPendingUpdates, doc_key - multi.rpush "pending-updates-list", doc_key - multi.exec (error) -> - return callback(error) if error? - callback() - flushProjectToMongo: (project_id, callback = (error) ->)-> logger.log project_id:project_id, "flushing project from document updater" timer = new metrics.Timer("flushing.mongo.project") diff --git a/services/web/app/coffee/Features/Editor/EditorRealTimeController.coffee b/services/web/app/coffee/Features/Editor/EditorRealTimeController.coffee index bd4f6af8e8..5062533cbb 100644 --- a/services/web/app/coffee/Features/Editor/EditorRealTimeController.coffee +++ b/services/web/app/coffee/Features/Editor/EditorRealTimeController.coffee @@ -1,14 +1,10 @@ Settings = require 'settings-sharelatex' -redis = require("redis-sharelatex") -rclientPub = redis.createClient(Settings.redis.web) -rclientSub = redis.createClient(Settings.redis.web) +RedisWrapper = require("../../infrastructure/RedisWrapper") +rclient = RedisWrapper.client("realtime") module.exports = EditorRealTimeController = - rclientPub: rclientPub - rclientSub: rclientSub - emitToRoom: (room_id, message, payload...) -> - @rclientPub.publish "editor-events", JSON.stringify + rclient.publish "editor-events", JSON.stringify room_id: room_id message: message payload: payload diff --git a/services/web/app/coffee/Features/HealthCheck/HealthCheckController.coffee b/services/web/app/coffee/Features/HealthCheck/HealthCheckController.coffee index 51acdbd2f0..544933b5fc 100644 --- a/services/web/app/coffee/Features/HealthCheck/HealthCheckController.coffee +++ b/services/web/app/coffee/Features/HealthCheck/HealthCheckController.coffee @@ -1,8 +1,8 @@ Mocha = require "mocha" Base = require("mocha/lib/reporters/base") -redis = require("redis-sharelatex") +RedisWrapper = require("../../infrastructure/RedisWrapper") +rclient = RedisWrapper.client("health_check") settings = require("settings-sharelatex") -redisCheck = redis.activeHealthCheckRedis(settings.redis.web) logger = require "logger-sharelatex" domain = require "domain" @@ -31,10 +31,12 @@ module.exports = HealthCheckController = delete require.cache[path] checkRedis: (req, res, next)-> - if redisCheck.isAlive() - res.sendStatus 200 - else - res.sendStatus 500 + rclient.healthCheck (error) -> + if error? + logger.err {err: error}, "failed redis health check" + res.sendStatus 500 + else + res.sendStatus 200 Reporter = (res) -> (runner) -> diff --git a/services/web/app/coffee/Features/Security/OneTimeTokenHandler.coffee b/services/web/app/coffee/Features/Security/OneTimeTokenHandler.coffee index b84e1c9b33..3824703efb 100644 --- a/services/web/app/coffee/Features/Security/OneTimeTokenHandler.coffee +++ b/services/web/app/coffee/Features/Security/OneTimeTokenHandler.coffee @@ -1,6 +1,6 @@ Settings = require('settings-sharelatex') -redis = require("redis-sharelatex") -rclient = redis.createClient(Settings.redis.web) +RedisWrapper = require("../../infrastructure/RedisWrapper") +rclient = RedisWrapper.client("one_time_token") crypto = require("crypto") logger = require("logger-sharelatex") diff --git a/services/web/app/coffee/Features/User/UserSessionsManager.coffee b/services/web/app/coffee/Features/User/UserSessionsManager.coffee index 2cd3b17e7f..273c7fe4a9 100644 --- a/services/web/app/coffee/Features/User/UserSessionsManager.coffee +++ b/services/web/app/coffee/Features/User/UserSessionsManager.coffee @@ -3,7 +3,6 @@ logger = require("logger-sharelatex") Async = require('async') _ = require('underscore') UserSessionsRedis = require('./UserSessionsRedis') - rclient = UserSessionsRedis.client() module.exports = UserSessionsManager = diff --git a/services/web/app/coffee/Features/User/UserSessionsRedis.coffee b/services/web/app/coffee/Features/User/UserSessionsRedis.coffee index 89ab7ed192..0c460b4604 100644 --- a/services/web/app/coffee/Features/User/UserSessionsRedis.coffee +++ b/services/web/app/coffee/Features/User/UserSessionsRedis.coffee @@ -1,21 +1,9 @@ -Settings = require 'settings-sharelatex' -redis = require 'redis-sharelatex' -ioredis = require 'ioredis' -logger = require 'logger-sharelatex' - -redisSessionsSettings = Settings.redis.websessions or Settings.redis.web +RedisWrapper = require("../../infrastructure/RedisWrapper") +rclient = RedisWrapper.client("websessions") module.exports = Redis = client: () -> - if redisSessionsSettings?.cluster? - logger.log {}, "using redis cluster for web sessions" - rclient = new ioredis.Cluster(redisSessionsSettings.cluster) - else - rclient = redis.createClient(redisSessionsSettings) return rclient sessionSetKey: (user) -> - if redisSessionsSettings?.cluster? - return "UserSessions:{#{user._id}}" - else - return "UserSessions:#{user._id}" + return "UserSessions:{#{user._id}}" diff --git a/services/web/app/coffee/infrastructure/LockManager.coffee b/services/web/app/coffee/infrastructure/LockManager.coffee index 3e40f9d9dc..370e4c09c9 100644 --- a/services/web/app/coffee/infrastructure/LockManager.coffee +++ b/services/web/app/coffee/infrastructure/LockManager.coffee @@ -1,7 +1,7 @@ metrics = require('metrics-sharelatex') Settings = require('settings-sharelatex') -redis = require("redis-sharelatex") -rclient = redis.createClient(Settings.redis.web) +RedisWrapper = require("./RedisWrapper") +rclient = RedisWrapper.client("lock") logger = require "logger-sharelatex" module.exports = LockManager = diff --git a/services/web/app/coffee/infrastructure/RedisWrapper.coffee b/services/web/app/coffee/infrastructure/RedisWrapper.coffee index 5d8b5836b5..523785427e 100644 --- a/services/web/app/coffee/infrastructure/RedisWrapper.coffee +++ b/services/web/app/coffee/infrastructure/RedisWrapper.coffee @@ -1,28 +1,14 @@ Settings = require 'settings-sharelatex' redis = require 'redis-sharelatex' -ioredis = require 'ioredis' -logger = require 'logger-sharelatex' - # A per-feature interface to Redis, # looks up the feature in `settings.redis` # and returns an appropriate client. # Necessary because we don't want to migrate web over # to redis-cluster all at once. - -# TODO: consider merging into `redis-sharelatex` - - module.exports = Redis = - # feature = 'websessions' | 'ratelimiter' | ... client: (feature) -> redisFeatureSettings = Settings.redis[feature] or Settings.redis.web - if redisFeatureSettings?.cluster? - logger.log {feature}, "creating redis-cluster client" - rclient = new ioredis.Cluster(redisFeatureSettings.cluster) - rclient.__is_redis_cluster = true - else - logger.log {feature}, "creating redis client" - rclient = redis.createClient(redisFeatureSettings) + rclient = redis.createClient(redisFeatureSettings) return rclient diff --git a/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee b/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee index 01d4ad0002..c54f405bb9 100644 --- a/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Compile/ClsiCookieManagerTests.coffee @@ -34,8 +34,8 @@ describe "ClsiCookieManager", -> ttl:Math.random() key: "coooookie" @requires = - "redis-sharelatex" : - createClient: => + "../../infrastructure/RedisWrapper": + client: => @redis "settings-sharelatex": @settings "request": @request diff --git a/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee b/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee index de56443707..3964acea41 100644 --- a/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee @@ -15,8 +15,8 @@ describe "CompileManager", -> @CompileManager = SandboxedModule.require modulePath, requires: "settings-sharelatex": @settings = redis: web: {host: "localhost", port: 42} - "redis-sharelatex": - createClient: () => @rclient = { auth: () -> } + "../../infrastructure/RedisWrapper": + client: () => @rclient = { auth: () -> } "../DocumentUpdater/DocumentUpdaterHandler": @DocumentUpdaterHandler = {} "../Project/ProjectRootDocManager": @ProjectRootDocManager = {} "../../models/Project": Project: @Project = {} diff --git a/services/web/test/UnitTests/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee b/services/web/test/UnitTests/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee index f5519ffa68..ee92d00ae3 100644 --- a/services/web/test/UnitTests/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee @@ -20,10 +20,8 @@ describe 'DocumentUpdaterHandler', -> @request = {} @projectEntityHandler = {} - @rclient = {auth:->} @settings = apis : documentupdater: url : "http://something.com" - redis:{web:{}} @handler = SandboxedModule.require modulePath, requires: 'request': defaults:=> return @request 'settings-sharelatex':@settings @@ -31,52 +29,10 @@ describe 'DocumentUpdaterHandler', -> '../Project/ProjectEntityHandler':@projectEntityHandler "../../models/Project": Project: @Project={} '../../Features/Project/ProjectLocator':{} - 'redis-sharelatex' : createClient: () => @rclient "metrics-sharelatex": Timer:-> done:-> - describe 'queueChange', -> - beforeEach -> - @change = { - "action":"removeText", - "range":{"start":{"row":2,"column":2},"end":{"row":2,"column":3}}, - "text":"e" - } - @rclient.multi = sinon.stub().returns @rclient - @rclient.exec = sinon.stub().callsArg(0) - @rclient.rpush = sinon.stub() - @rclient.sadd = sinon.stub() - @callback = sinon.stub() - - describe "successfully", -> - beforeEach -> - @handler.queueChange(@project_id, @doc_id, @change, @callback) - - it "should push the change", -> - @rclient.rpush - .calledWith("PendingUpdates:#{@doc_id}", JSON.stringify(@change)) - .should.equal true - - it "should notify the doc updater of the change via the pending-updates-list queue", -> - @rclient.rpush - .calledWith("pending-updates-list", "#{@project_id}:#{@doc_id}") - .should.equal true - - it "should push the doc id into the pending updates set", -> - @rclient.sadd - .calledWith("DocsWithPendingUpdates", "#{@project_id}:#{@doc_id}") - .should.equal true - - describe "with error connecting to redis during exec", -> - beforeEach -> - @rclient.exec = sinon.stub().callsArgWith(0, new Error("something went wrong")) - @handler.queueChange(@project_id, @doc_id, @change, @callback) - - it "should return an error", -> - @callback.calledWithExactly(sinon.match(Error)).should.equal true - - describe 'flushProjectToMongo', -> beforeEach -> @callback = sinon.stub() diff --git a/services/web/test/UnitTests/coffee/Editor/EditorRealTimeControllerTests.coffee b/services/web/test/UnitTests/coffee/Editor/EditorRealTimeControllerTests.coffee index bbad9b1ae5..a06bd032dc 100644 --- a/services/web/test/UnitTests/coffee/Editor/EditorRealTimeControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Editor/EditorRealTimeControllerTests.coffee @@ -5,16 +5,13 @@ modulePath = require('path').join __dirname, '../../../../app/js/Features/Editor describe "EditorRealTimeController", -> beforeEach -> + @rclient = + publish: sinon.stub() @EditorRealTimeController = SandboxedModule.require modulePath, requires: - "redis-sharelatex": - createClient: () -> - auth:-> + "../../infrastructure/RedisWrapper": + client: () => @rclient "../../infrastructure/Server" : io: @io = {} "settings-sharelatex":{redis:{}} - @EditorRealTimeController.rclientPub = publish: sinon.stub() - @EditorRealTimeController.rclientSub = - subscribe: sinon.stub() - on: sinon.stub() @room_id = "room-id" @message = "message-to-editor" @@ -25,7 +22,7 @@ describe "EditorRealTimeController", -> @EditorRealTimeController.emitToRoom(@room_id, @message, @payload...) it "should publish the message to redis", -> - @EditorRealTimeController.rclientPub.publish + @rclient.publish .calledWith("editor-events", JSON.stringify( room_id: @room_id, message: @message diff --git a/services/web/test/UnitTests/coffee/Security/OneTimeTokenHandlerTests.coffee b/services/web/test/UnitTests/coffee/Security/OneTimeTokenHandlerTests.coffee index a6305db27c..046acaa720 100644 --- a/services/web/test/UnitTests/coffee/Security/OneTimeTokenHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Security/OneTimeTokenHandlerTests.coffee @@ -23,8 +23,8 @@ describe "OneTimeTokenHandler", -> exec:sinon.stub() self = @ @OneTimeTokenHandler = SandboxedModule.require modulePath, requires: - "redis-sharelatex" : - createClient: => + "../../infrastructure/RedisWrapper" : + client: => auth:-> multi: -> return self.redisMulti diff --git a/services/web/test/UnitTests/coffee/infrastructure/LockManager/CheckingTheLock.coffee b/services/web/test/UnitTests/coffee/infrastructure/LockManager/CheckingTheLock.coffee index 3f56c346ac..8926382f8b 100644 --- a/services/web/test/UnitTests/coffee/infrastructure/LockManager/CheckingTheLock.coffee +++ b/services/web/test/UnitTests/coffee/infrastructure/LockManager/CheckingTheLock.coffee @@ -7,7 +7,7 @@ doc_id = 5678 blockingKey = "Blocking:#{doc_id}" SandboxedModule = require('sandboxed-module') -describe 'Lock Manager - checking the lock', ()-> +describe 'LockManager - checking the lock', ()-> existsStub = sinon.stub() setStub = sinon.stub() @@ -17,8 +17,8 @@ describe 'Lock Manager - checking the lock', ()-> mocks = "logger-sharelatex": log:-> - "redis-sharelatex": - createClient : ()-> + "./RedisWrapper": + client: ()-> auth:-> multi: -> exists: existsStub diff --git a/services/web/test/UnitTests/coffee/infrastructure/LockManager/ReleasingTheLock.coffee b/services/web/test/UnitTests/coffee/infrastructure/LockManager/ReleasingTheLock.coffee index 8eb0f4d75c..fa99b87739 100644 --- a/services/web/test/UnitTests/coffee/infrastructure/LockManager/ReleasingTheLock.coffee +++ b/services/web/test/UnitTests/coffee/infrastructure/LockManager/ReleasingTheLock.coffee @@ -12,8 +12,8 @@ describe 'LockManager - releasing the lock', ()-> mocks = "logger-sharelatex": log:-> - "redis-sharelatex": - createClient : ()-> + "./RedisWrapper": + client: ()-> auth:-> del:deleteStub diff --git a/services/web/test/UnitTests/coffee/infrastructure/LockManager/getLockTests.coffee b/services/web/test/UnitTests/coffee/infrastructure/LockManager/getLockTests.coffee index ec0d0d4950..16ae0a8b8c 100644 --- a/services/web/test/UnitTests/coffee/infrastructure/LockManager/getLockTests.coffee +++ b/services/web/test/UnitTests/coffee/infrastructure/LockManager/getLockTests.coffee @@ -9,8 +9,8 @@ describe 'LockManager - getting the lock', -> beforeEach -> @LockManager = SandboxedModule.require modulePath, requires: "logger-sharelatex": log:-> - "redis-sharelatex": - createClient : () => + "./RedisWrapper": + client: ()-> auth:-> "settings-sharelatex":{redis:{}} "metrics-sharelatex": inc:-> diff --git a/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee b/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee index 98f624c70b..a227ac60bd 100644 --- a/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee +++ b/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee @@ -9,8 +9,8 @@ describe 'LockManager - trying the lock', -> beforeEach -> @LockManager = SandboxedModule.require modulePath, requires: "logger-sharelatex": log:-> - "redis-sharelatex": - createClient : () => + "./RedisWrapper": + client: () => auth:-> set: @set = sinon.stub() "settings-sharelatex":{redis:{}} diff --git a/services/web/test/UnitTests/coffee/infrastructure/RedisWrapperTests.coffee b/services/web/test/UnitTests/coffee/infrastructure/RedisWrapperTests.coffee index 83ea202dcd..4837711f56 100644 --- a/services/web/test/UnitTests/coffee/infrastructure/RedisWrapperTests.coffee +++ b/services/web/test/UnitTests/coffee/infrastructure/RedisWrapperTests.coffee @@ -9,58 +9,28 @@ SandboxedModule = require('sandboxed-module') describe 'RedisWrapper', -> beforeEach -> - @featureName = 'somefeature' - @settings = - redis: - web: - port:"1234" - host:"somewhere" - password: "password" - somefeature: {} - @normalRedisInstance = - thisIsANormalRedisInstance: true - n: 1 - @clusterRedisInstance = - thisIsAClusterRedisInstance: true - n: 2 + @settings = { redis: {} } @redis = - createClient: sinon.stub().returns(@normalRedisInstance) - @ioredis = - Cluster: sinon.stub().returns(@clusterRedisInstance) - @logger = {log: sinon.stub()} - + createClient: sinon.stub() @RedisWrapper = SandboxedModule.require modulePath, requires: - 'logger-sharelatex': @logger 'settings-sharelatex': @settings 'redis-sharelatex': @redis - 'ioredis': @ioredis describe 'client', -> + it "should use the feature settings if present", -> + @settings.redis = + my_feature: + port:"23456" + host:"otherhost" + password: "banana" + @RedisWrapper.client("my_feature") + @redis.createClient.calledWith(@settings.redis.my_feature).should.equal true - beforeEach -> - @call = () => - @RedisWrapper.client(@featureName) - - describe 'when feature uses cluster', -> - - beforeEach -> - @settings.redis.somefeature = - cluster: [1, 2, 3] - - it 'should return a cluster client', -> - client = @call() - expect(client).to.equal @clusterRedisInstance - expect(client.__is_redis_cluster).to.equal true - - describe 'when feature uses normal redis', -> - - beforeEach -> - @settings.redis.somefeature = - port:"1234" - host:"somewhere" - password: "password" - - it 'should return a regular redis client', -> - client = @call() - expect(client).to.equal @normalRedisInstance - expect(client.__is_redis_cluster).to.equal undefined + it "should use the web settings if feature not present", -> + @settings.redis = + web: + port:"43" + host:"otherhost" + password: "banana" + @RedisWrapper.client("my_feature") + @redis.createClient.calledWith(@settings.redis.web).should.equal true From 3f51911513d08850eda13c0656fb937879c89f02 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 4 May 2017 15:25:58 +0100 Subject: [PATCH 14/28] Update to latest redis-sharelatex --- services/web/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/package.json b/services/web/package.json index 85da8a3765..2e78c3e6f3 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -49,7 +49,7 @@ "passport-ldapauth": "^0.6.0", "passport-local": "^1.0.0", "redis": "0.10.1", - "redis-sharelatex": "0.0.9", + "redis-sharelatex": "git+https://github.com/sharelatex/redis-sharelatex.git#v1.0.0", "request": "^2.69.0", "requests": "^0.1.7", "rimraf": "2.2.6", From 014e3afb36a619b2f204644a864e48de709a9562 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 4 May 2017 17:04:20 +0100 Subject: [PATCH 15/28] Don't call sync functions inside async Calling sync functions inside async can trigger the node max stack size. Instead, build up our unique list of ids in advance, so we only call a method in async for each user we actually need to look up, asynchronously. Then use all the cached values synchronously afterwards. --- .../Features/Chat/ChatController.coffee | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/services/web/app/coffee/Features/Chat/ChatController.coffee b/services/web/app/coffee/Features/Chat/ChatController.coffee index b163df8158..fa1f3216e2 100644 --- a/services/web/app/coffee/Features/Chat/ChatController.coffee +++ b/services/web/app/coffee/Features/Chat/ChatController.coffee @@ -34,32 +34,32 @@ module.exports = ChatController = res.json messages _injectUserInfoIntoThreads: (threads, callback = (error, threads) ->) -> - userCache = {} - getUserDetails = (user_id, callback = (error, user) ->) -> - return callback(null, userCache[user_id]) if userCache[user_id]? - UserInfoManager.getPersonalInfo user_id, (err, user) -> - return callback(error) if error? - user = UserInfoController.formatPersonalInfo user - userCache[user_id] = user - callback null, user + # There will be a lot of repitition of user_ids, so first build a list + # of unique ones to perform db look ups on, then use these to populate the + # user fields + user_ids = {} + for thread_id, thread of threads + if thread.resolved + user_ids[thread.resolved_by_user_id] = true + for message in thread.messages + user_ids[message.user_id] = true jobs = [] - for thread_id, thread of threads - do (thread) -> - if thread.resolved - jobs.push (cb) -> - getUserDetails thread.resolved_by_user_id, (error, user) -> - cb(error) if error? - thread.resolved_by_user = user - cb() - for message in thread.messages - do (message) -> - jobs.push (cb) -> - getUserDetails message.user_id, (error, user) -> - cb(error) if error? - message.user = user - cb() - + users = {} + for user_id, _ of user_ids + do (user_id) -> + jobs.push (cb) -> + UserInfoManager.getPersonalInfo user_id, (err, user) -> + return cb(error) if error? + user = UserInfoController.formatPersonalInfo user + users[user_id] = user + cb() + async.series jobs, (error) -> return callback(error) if error? + for thread_id, thread of threads + if thread.resolved + thread.resolved_by_user = users[thread.resolved_by_user_id] + for message in thread.messages + message.user = users[message.user_id] return callback null, threads \ No newline at end of file From c5c0364d49469a7fc1723fad169d7e8f53d031e5 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Mon, 8 May 2017 16:09:59 +0100 Subject: [PATCH 16/28] update to newest mongoose --- .../Project/ProjectCreationHandler.coffee | 2 +- .../Project/ProjectEntityHandler.coffee | 2 +- .../Subscription/SubscriptionLocator.coffee | 2 +- .../Subscription/SubscriptionUpdater.coffee | 2 +- .../app/coffee/infrastructure/Mongoose.coffee | 2 + services/web/app/coffee/models/Doc.coffee | 2 +- services/web/app/coffee/models/File.coffee | 2 +- services/web/app/coffee/models/Folder.coffee | 2 +- services/web/app/coffee/models/Project.coffee | 2 +- .../app/coffee/models/ProjectInvite.coffee | 2 +- .../web/app/coffee/models/Subscription.coffee | 4 +- .../app/coffee/models/SystemMessage.coffee | 2 +- services/web/app/coffee/models/User.coffee | 2 +- services/web/npm-shrinkwrap.json | 142 ++++++++---------- services/web/package.json | 2 +- 15 files changed, 77 insertions(+), 95 deletions(-) diff --git a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee index ffb69abeac..7718524a79 100644 --- a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee @@ -2,7 +2,7 @@ logger = require('logger-sharelatex') async = require("async") metrics = require('metrics-sharelatex') Settings = require('settings-sharelatex') -ObjectId = require('mongoose').Types.ObjectId +ObjectId = require('../../infrastructure/Mongoose').Types.ObjectId Project = require('../../models/Project').Project Folder = require('../../models/Folder').Folder ProjectEntityHandler = require('./ProjectEntityHandler') diff --git a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee index 9948c7ec3d..881c455032 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee @@ -531,7 +531,7 @@ module.exports = ProjectEntityHandler = fileSystem: "#{path.fileSystem}/#{element.name}" mongo: path.mongo id = element._id+'' - element._id = require('mongoose').Types.ObjectId(id) + element._id = require('../../infrastructure/Mongoose').Types.ObjectId(id) conditions = _id:project._id mongopath = "#{path.mongo}.#{type}" update = "$push":{} diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee index beee4a3158..0e17a5640d 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee @@ -1,6 +1,6 @@ Subscription = require('../../models/Subscription').Subscription logger = require("logger-sharelatex") -ObjectId = require('mongoose').Types.ObjectId +ObjectId = require('../../infrastructure/Mongoose').Types.ObjectId module.exports = diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee index a4a0864d4f..6027a2533e 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee @@ -6,7 +6,7 @@ UserFeaturesUpdater = require("./UserFeaturesUpdater") PlansLocator = require("./PlansLocator") Settings = require("settings-sharelatex") logger = require("logger-sharelatex") -ObjectId = require('mongoose').Types.ObjectId +ObjectId = require('../../infrastructure/Mongoose').Types.ObjectId ReferalAllocator = require("../Referal/ReferalAllocator") oneMonthInSeconds = 60 * 60 * 24 * 30 diff --git a/services/web/app/coffee/infrastructure/Mongoose.coffee b/services/web/app/coffee/infrastructure/Mongoose.coffee index 7fbf64fd71..6b9e05759f 100644 --- a/services/web/app/coffee/infrastructure/Mongoose.coffee +++ b/services/web/app/coffee/infrastructure/Mongoose.coffee @@ -2,6 +2,8 @@ mongoose = require('mongoose') Settings = require 'settings-sharelatex' logger = require('logger-sharelatex') +mongoose.Promise = global.Promise + mongoose.connect(Settings.mongo.url, server: poolSize: 10) mongoose.connection.on 'connected', () -> diff --git a/services/web/app/coffee/models/Doc.coffee b/services/web/app/coffee/models/Doc.coffee index 862638acb7..122b6a935b 100644 --- a/services/web/app/coffee/models/Doc.coffee +++ b/services/web/app/coffee/models/Doc.coffee @@ -1,4 +1,4 @@ -mongoose = require 'mongoose' +mongoose = require '../infrastructure/Mongoose' Settings = require 'settings-sharelatex' Schema = mongoose.Schema diff --git a/services/web/app/coffee/models/File.coffee b/services/web/app/coffee/models/File.coffee index 3ca32b8f8f..c6cf64bf1d 100644 --- a/services/web/app/coffee/models/File.coffee +++ b/services/web/app/coffee/models/File.coffee @@ -1,4 +1,4 @@ -mongoose = require 'mongoose' +mongoose = require '../infrastructure/Mongoose' Settings = require 'settings-sharelatex' Schema = mongoose.Schema diff --git a/services/web/app/coffee/models/Folder.coffee b/services/web/app/coffee/models/Folder.coffee index 4c2bf04b64..03058aa5b7 100644 --- a/services/web/app/coffee/models/Folder.coffee +++ b/services/web/app/coffee/models/Folder.coffee @@ -1,4 +1,4 @@ -mongoose = require('mongoose') +mongoose = require('../infrastructure/Mongoose') Settings = require 'settings-sharelatex' DocSchema = require('./Doc').DocSchema FileSchema = require('./File').FileSchema diff --git a/services/web/app/coffee/models/Project.coffee b/services/web/app/coffee/models/Project.coffee index 18387bdc0b..6211b1eff9 100644 --- a/services/web/app/coffee/models/Project.coffee +++ b/services/web/app/coffee/models/Project.coffee @@ -1,4 +1,4 @@ -mongoose = require('mongoose') +mongoose = require('../infrastructure/Mongoose') Settings = require 'settings-sharelatex' _ = require('underscore') FolderSchema = require('./Folder.js').FolderSchema diff --git a/services/web/app/coffee/models/ProjectInvite.coffee b/services/web/app/coffee/models/ProjectInvite.coffee index 9b9e0cb350..9f6fb16c55 100644 --- a/services/web/app/coffee/models/ProjectInvite.coffee +++ b/services/web/app/coffee/models/ProjectInvite.coffee @@ -1,4 +1,4 @@ -mongoose = require 'mongoose' +mongoose = require '../infrastructure/Mongoose' Settings = require 'settings-sharelatex' diff --git a/services/web/app/coffee/models/Subscription.coffee b/services/web/app/coffee/models/Subscription.coffee index cd036fb6f1..84395e7cc6 100644 --- a/services/web/app/coffee/models/Subscription.coffee +++ b/services/web/app/coffee/models/Subscription.coffee @@ -1,4 +1,4 @@ -mongoose = require 'mongoose' +mongoose = require '../infrastructure/Mongoose' Settings = require 'settings-sharelatex' Schema = mongoose.Schema @@ -31,4 +31,4 @@ Subscription = conn.model('Subscription', SubscriptionSchema) mongoose.model 'Subscription', SubscriptionSchema exports.Subscription = Subscription -exports.SubscriptionSchema = SubscriptionSchema \ No newline at end of file +exports.SubscriptionSchema = SubscriptionSchema diff --git a/services/web/app/coffee/models/SystemMessage.coffee b/services/web/app/coffee/models/SystemMessage.coffee index adb665fede..3e1e1689e4 100644 --- a/services/web/app/coffee/models/SystemMessage.coffee +++ b/services/web/app/coffee/models/SystemMessage.coffee @@ -1,4 +1,4 @@ -mongoose = require 'mongoose' +mongoose = require '../infrastructure/Mongoose' Settings = require 'settings-sharelatex' Schema = mongoose.Schema diff --git a/services/web/app/coffee/models/User.coffee b/services/web/app/coffee/models/User.coffee index 099b9ef8e2..d833c3412a 100644 --- a/services/web/app/coffee/models/User.coffee +++ b/services/web/app/coffee/models/User.coffee @@ -1,7 +1,7 @@ Project = require('./Project').Project Settings = require 'settings-sharelatex' _ = require('underscore') -mongoose = require('mongoose') +mongoose = require('../infrastructure/Mongoose') uuid = require('uuid') Schema = mongoose.Schema ObjectId = Schema.ObjectId diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json index 6904ca8272..8fa1a1b2e8 100644 --- a/services/web/npm-shrinkwrap.json +++ b/services/web/npm-shrinkwrap.json @@ -292,20 +292,6 @@ "from": "bson@>=1.0.4 <1.1.0", "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz" }, - "bson-ext": { - "version": "0.1.13", - "from": "bson-ext@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/bson-ext/-/bson-ext-0.1.13.tgz", - "optional": true, - "dependencies": { - "nan": { - "version": "2.0.9", - "from": "nan@>=2.0.9 <2.1.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.0.9.tgz", - "optional": true - } - } - }, "buffer": { "version": "4.9.1", "from": "buffer@4.9.1", @@ -1250,9 +1236,9 @@ "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz" }, "hooks-fixed": { - "version": "1.1.0", - "from": "hooks-fixed@1.1.0", - "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-1.1.0.tgz" + "version": "2.0.0", + "from": "hooks-fixed@2.0.0", + "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-2.0.0.tgz" }, "http-errors": { "version": "1.6.1", @@ -1475,23 +1461,9 @@ } }, "kareem": { - "version": "1.0.1", - "from": "kareem@1.0.1", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.0.1.tgz" - }, - "kerberos": { - "version": "0.0.23", - "from": "kerberos@>=0.0.0 <0.1.0", - "resolved": "https://registry.npmjs.org/kerberos/-/kerberos-0.0.23.tgz", - "optional": true, - "dependencies": { - "nan": { - "version": "2.5.1", - "from": "nan@>=2.5.1 <2.6.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.5.1.tgz", - "optional": true - } - } + "version": "1.4.1", + "from": "kareem@1.4.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.4.1.tgz" }, "kind-of": { "version": "3.1.0", @@ -2096,46 +2068,49 @@ } }, "mongoose": { - "version": "4.1.0", - "from": "mongoose@4.1.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.1.0.tgz", + "version": "4.9.8", + "from": "mongoose@4.9.8", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.9.8.tgz", "dependencies": { "async": { - "version": "0.9.0", - "from": "async@0.9.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" + "version": "2.1.4", + "from": "async@2.1.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz" }, - "bson": { - "version": "0.3.2", - "from": "bson@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-0.3.2.tgz" + "es6-promise": { + "version": "3.2.1", + "from": "es6-promise@3.2.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz" + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" }, "mongodb": { - "version": "2.0.34", - "from": "mongodb@2.0.34", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.0.34.tgz" + "version": "2.2.26", + "from": "mongodb@2.2.26", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.26.tgz" }, "mongodb-core": { - "version": "1.2.0", - "from": "mongodb-core@1.2.0", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-1.2.0.tgz", - "dependencies": { - "bson": { - "version": "0.4.23", - "from": "bson@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-0.4.23.tgz" - } - } + "version": "2.1.10", + "from": "mongodb-core@2.1.10", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.10.tgz" }, "ms": { - "version": "0.1.0", - "from": "ms@0.1.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.1.0.tgz" + "version": "0.7.2", + "from": "ms@0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz" }, "readable-stream": { - "version": "1.0.31", - "from": "readable-stream@1.0.31", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.31.tgz" + "version": "2.2.7", + "from": "readable-stream@2.2.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz" + }, + "string_decoder": { + "version": "1.0.0", + "from": "string_decoder@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.0.tgz" } } }, @@ -2145,24 +2120,24 @@ "resolved": "https://registry.npmjs.org/monocle/-/monocle-1.1.51.tgz" }, "mpath": { - "version": "0.1.1", - "from": "mpath@0.1.1", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.1.1.tgz" + "version": "0.2.1", + "from": "mpath@0.2.1", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.2.1.tgz" }, "mpromise": { - "version": "0.5.4", - "from": "mpromise@0.5.4", - "resolved": "https://registry.npmjs.org/mpromise/-/mpromise-0.5.4.tgz" + "version": "0.5.5", + "from": "mpromise@0.5.5", + "resolved": "https://registry.npmjs.org/mpromise/-/mpromise-0.5.5.tgz" }, "mquery": { - "version": "1.6.1", - "from": "mquery@1.6.1", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-1.6.1.tgz", + "version": "2.3.0", + "from": "mquery@2.3.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-2.3.0.tgz", "dependencies": { "bluebird": { - "version": "2.9.26", - "from": "bluebird@2.9.26", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.26.tgz" + "version": "2.10.2", + "from": "bluebird@2.10.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz" }, "debug": { "version": "2.2.0", @@ -2173,6 +2148,11 @@ "version": "0.7.1", "from": "ms@0.7.1", "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + }, + "sliced": { + "version": "0.0.5", + "from": "sliced@0.0.5", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz" } } }, @@ -2214,9 +2194,9 @@ } }, "muri": { - "version": "1.0.0", - "from": "muri@1.0.0", - "resolved": "https://registry.npmjs.org/muri/-/muri-1.0.0.tgz" + "version": "1.2.1", + "from": "muri@1.2.1", + "resolved": "https://registry.npmjs.org/muri/-/muri-1.2.1.tgz" }, "mv": { "version": "0.0.5", @@ -3202,9 +3182,9 @@ "resolved": "https://registry.npmjs.org/sixpack-client/-/sixpack-client-1.0.0.tgz" }, "sliced": { - "version": "0.0.5", - "from": "sliced@0.0.5", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz" + "version": "1.0.1", + "from": "sliced@1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz" }, "smtp-connection": { "version": "2.0.1", diff --git a/services/web/package.json b/services/web/package.json index 85da8a3765..ff9332f8f0 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -39,7 +39,7 @@ "mimelib": "0.2.14", "mocha": "1.17.1", "mongojs": "2.4.0", - "mongoose": "4.1.0", + "mongoose": "4.9.8", "multer": "^0.1.8", "nodemailer": "2.1.0", "nodemailer-sendgrid-transport": "^0.2.0", From 149e38855f927888eb4fd04e7ab32b55109f6b14 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 9 May 2017 11:40:42 +0100 Subject: [PATCH 17/28] Add a cooldown mechanism for projects which go over limits --- .../Features/Cooldown/CooldownManager.coffee | 24 ++++ .../Cooldown/CooldownMiddlewear.coffee | 15 +++ services/web/app/coffee/router.coffee | 1 + services/web/config/settings.defaults.coffee | 10 ++ .../Cooldown/CooldownManagerTests.coffee | 120 ++++++++++++++++++ .../Cooldown/CooldownMiddlewearTests.coffee | 72 +++++++++++ 6 files changed, 242 insertions(+) create mode 100644 services/web/app/coffee/Features/Cooldown/CooldownManager.coffee create mode 100644 services/web/app/coffee/Features/Cooldown/CooldownMiddlewear.coffee create mode 100644 services/web/test/UnitTests/coffee/Cooldown/CooldownManagerTests.coffee create mode 100644 services/web/test/UnitTests/coffee/Cooldown/CooldownMiddlewearTests.coffee diff --git a/services/web/app/coffee/Features/Cooldown/CooldownManager.coffee b/services/web/app/coffee/Features/Cooldown/CooldownManager.coffee new file mode 100644 index 0000000000..933149a4db --- /dev/null +++ b/services/web/app/coffee/Features/Cooldown/CooldownManager.coffee @@ -0,0 +1,24 @@ +RedisWrapper = require('../../infrastructure/RedisWrapper') +rclient = RedisWrapper.client('cooldown') +logger = require('logger-sharelatex') + + +COOLDOWN_IN_SECONDS = 60 * 10 + + +module.exports = CooldownManager = + + _buildKey: (projectId) -> + "Cooldown:{#{projectId}}" + + putProjectOnCooldown: (projectId, callback=(err)->) -> + logger.log {projectId}, + "[Cooldown] putting project on cooldown for #{COOLDOWN_IN_SECONDS} seconds" + rclient.set(CooldownManager._buildKey(projectId), '1', 'EX', COOLDOWN_IN_SECONDS, callback) + + isProjectOnCooldown: (projectId, callback=(err, isOnCooldown)->) -> + rclient.get CooldownManager._buildKey(projectId), (err, result) -> + if err? + return callback(err) + callback(null, result == "1") + diff --git a/services/web/app/coffee/Features/Cooldown/CooldownMiddlewear.coffee b/services/web/app/coffee/Features/Cooldown/CooldownMiddlewear.coffee new file mode 100644 index 0000000000..b073541945 --- /dev/null +++ b/services/web/app/coffee/Features/Cooldown/CooldownMiddlewear.coffee @@ -0,0 +1,15 @@ +CooldownManager = require('./CooldownManager') +logger = require('logger-sharelatex') + + +module.exports = CooldownMiddlewear = + + freezeProject: (req, res, next) -> + projectId = req.params.Project_id + CooldownManager.isProjectOnCooldown projectId, (err, projectIsOnCooldown) -> + if err? + return next(err) + if projectIsOnCooldown + logger.log {projectId}, "[Cooldown] project is on cooldown, denying request" + return res.sendStatus(429) + next() diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 0556c23c5a..fa799a2dff 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -32,6 +32,7 @@ ChatController = require("./Features/Chat/ChatController") BlogController = require("./Features/Blog/BlogController") Modules = require "./infrastructure/Modules" RateLimiterMiddlewear = require('./Features/Security/RateLimiterMiddlewear') +CooldownMiddlewear = require('./Features/Cooldown/CooldownMiddlewear') RealTimeProxyRouter = require('./Features/RealTimeProxy/RealTimeProxyRouter') InactiveProjectController = require("./Features/InactiveData/InactiveProjectController") ContactRouter = require("./Features/Contacts/ContactRouter") diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index b24c2568ab..8fb00aff31 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -58,6 +58,16 @@ module.exports = settings = # {host: 'localhost', port: 7005} # ] + # cooldown: + # cluster: [ + # {host: 'localhost', port: 7000} + # {host: 'localhost', port: 7001} + # {host: 'localhost', port: 7002} + # {host: 'localhost', port: 7003} + # {host: 'localhost', port: 7004} + # {host: 'localhost', port: 7005} + # ] + api: host: "localhost" port: "6379" diff --git a/services/web/test/UnitTests/coffee/Cooldown/CooldownManagerTests.coffee b/services/web/test/UnitTests/coffee/Cooldown/CooldownManagerTests.coffee new file mode 100644 index 0000000000..96e27a8bf7 --- /dev/null +++ b/services/web/test/UnitTests/coffee/Cooldown/CooldownManagerTests.coffee @@ -0,0 +1,120 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +expect = require('chai').expect +modulePath = require('path').join __dirname, '../../../../app/js/Features/Cooldown/CooldownManager' + + +describe "CooldownManager", -> + + beforeEach -> + @projectId = 'abcdefg' + @rclient = {set: sinon.stub(), get: sinon.stub()} + @RedisWrapper = + client: () => @rclient + @CooldownManager = SandboxedModule.require modulePath, requires: + '../../infrastructure/RedisWrapper': @RedisWrapper + 'logger-sharelatex': {log: sinon.stub()} + + describe '_buildKey', -> + + it 'should build a properly formatted redis key', -> + expect(@CooldownManager._buildKey('ABC')).to.equal 'Cooldown:{ABC}' + + describe 'isProjectOnCooldown', -> + beforeEach -> + @call = (cb) => + @CooldownManager.isProjectOnCooldown @projectId, cb + + describe 'when project is on cooldown', -> + beforeEach -> + @rclient.get = sinon.stub().callsArgWith(1, null, '1') + + it 'should fetch key from redis', (done) -> + @call (err, result) => + @rclient.get.callCount.should.equal 1 + @rclient.get.calledWith('Cooldown:{abcdefg}').should.equal true + done() + + it 'should not produce an error', (done) -> + @call (err, result) => + expect(err).to.equal null + done() + + it 'should produce a true result', (done) -> + @call (err, result) => + expect(result).to.equal true + done() + + describe 'when project is not on cooldown', -> + beforeEach -> + @rclient.get = sinon.stub().callsArgWith(1, null, null) + + it 'should fetch key from redis', (done) -> + @call (err, result) => + @rclient.get.callCount.should.equal 1 + @rclient.get.calledWith('Cooldown:{abcdefg}').should.equal true + done() + + it 'should not produce an error', (done) -> + @call (err, result) => + expect(err).to.equal null + done() + + it 'should produce a false result', (done) -> + @call (err, result) => + expect(result).to.equal false + done() + + describe 'when rclient.get produces an error', -> + beforeEach -> + @rclient.get = sinon.stub().callsArgWith(1, new Error('woops')) + + it 'should fetch key from redis', (done) -> + @call (err, result) => + @rclient.get.callCount.should.equal 1 + @rclient.get.calledWith('Cooldown:{abcdefg}').should.equal true + done() + + it 'should produce an error', (done) -> + @call (err, result) => + expect(err).to.not.equal null + expect(err).to.be.instanceof Error + done() + + describe 'putProjectOnCooldown', -> + + beforeEach -> + @call = (cb) => + @CooldownManager.putProjectOnCooldown @projectId, cb + + describe 'when rclient.set does not produce an error', -> + beforeEach -> + @rclient.set = sinon.stub().callsArgWith(4, null) + + it 'should set a key in redis', (done) -> + @call (err) => + @rclient.set.callCount.should.equal 1 + @rclient.set.calledWith('Cooldown:{abcdefg}').should.equal true + done() + + it 'should not produce an error', (done) -> + @call (err) => + expect(err).to.equal null + done() + + describe 'when rclient.set produces an error', -> + beforeEach -> + @rclient.set = sinon.stub().callsArgWith(4, new Error('woops')) + + it 'should set a key in redis', (done) -> + @call (err) => + @rclient.set.callCount.should.equal 1 + @rclient.set.calledWith('Cooldown:{abcdefg}').should.equal true + done() + + it 'produce an error', (done) -> + @call (err) => + expect(err).to.not.equal null + expect(err).to.be.instanceof Error + done() diff --git a/services/web/test/UnitTests/coffee/Cooldown/CooldownMiddlewearTests.coffee b/services/web/test/UnitTests/coffee/Cooldown/CooldownMiddlewearTests.coffee new file mode 100644 index 0000000000..694ca11fce --- /dev/null +++ b/services/web/test/UnitTests/coffee/Cooldown/CooldownMiddlewearTests.coffee @@ -0,0 +1,72 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +expect = require('chai').expect +modulePath = require('path').join __dirname, '../../../../app/js/Features/Cooldown/CooldownMiddlewear' + + +describe "CooldownMiddlewear", -> + + beforeEach -> + @CooldownManager = + isProjectOnCooldown: sinon.stub() + @CooldownMiddlewear = SandboxedModule.require modulePath, requires: + './CooldownManager': @CooldownManager + 'logger-sharelatex': {log: sinon.stub()} + + describe 'freezeProject', -> + + describe 'when project is on cooldown', -> + beforeEach -> + @CooldownManager.isProjectOnCooldown = sinon.stub().callsArgWith(1, null, true) + @req = {params: {Project_id: 'abc'}} + @res = {sendStatus: sinon.stub()} + @next = sinon.stub() + + it 'should call CooldownManager.isProjectOnCooldown', -> + @CooldownMiddlewear.freezeProject @req, @res, @next + @CooldownManager.isProjectOnCooldown.callCount.should.equal 1 + @CooldownManager.isProjectOnCooldown.calledWith('abc').should.equal true + + it 'should not produce an error', -> + @CooldownMiddlewear.freezeProject @req, @res, @next + @next.callCount.should.equal 0 + + it 'should send a 429 status', -> + @CooldownMiddlewear.freezeProject @req, @res, @next + @res.sendStatus.callCount.should.equal 1 + @res.sendStatus.calledWith(429).should.equal true + + describe 'when project is not on cooldown', -> + beforeEach -> + @CooldownManager.isProjectOnCooldown = sinon.stub().callsArgWith(1, null, false) + @req = {params: {Project_id: 'abc'}} + @res = {sendStatus: sinon.stub()} + @next = sinon.stub() + + it 'should call CooldownManager.isProjectOnCooldown', -> + @CooldownMiddlewear.freezeProject @req, @res, @next + @CooldownManager.isProjectOnCooldown.callCount.should.equal 1 + @CooldownManager.isProjectOnCooldown.calledWith('abc').should.equal true + + it 'call next with no arguments', -> + @CooldownMiddlewear.freezeProject @req, @res, @next + @next.callCount.should.equal 1 + expect(@next.lastCall.args.length).to.equal 0 + + describe 'when isProjectOnCooldown produces an error', -> + beforeEach -> + @CooldownManager.isProjectOnCooldown = sinon.stub().callsArgWith(1, new Error('woops')) + @req = {params: {Project_id: 'abc'}} + @res = {sendStatus: sinon.stub()} + @next = sinon.stub() + + it 'should call CooldownManager.isProjectOnCooldown', -> + @CooldownMiddlewear.freezeProject @req, @res, @next + @CooldownManager.isProjectOnCooldown.callCount.should.equal 1 + @CooldownManager.isProjectOnCooldown.calledWith('abc').should.equal true + + it 'call next with an error', -> + @CooldownMiddlewear.freezeProject @req, @res, @next + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.be.instanceof Error From 8e90b7fb9bea057b17c52d8704f724ce2e6667c7 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 9 May 2017 13:54:11 +0100 Subject: [PATCH 18/28] Add Cooldown to Tpds routes --- services/web/app/coffee/router.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index fa799a2dff..e0fb433b97 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -221,10 +221,10 @@ module.exports = class Router apiRouter.get '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.getDocument apiRouter.post '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.setDocument - apiRouter.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate + apiRouter.post '/user/:user_id/update/*', AuthenticationController.httpAuth, CooldownMiddlewear.freezeProject, TpdsController.mergeUpdate apiRouter.delete '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate - apiRouter.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents + apiRouter.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, CooldownMiddlewear.freezeProject, TpdsController.updateProjectContents apiRouter.delete '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents webRouter.post "/spelling/check", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi From fd0176c0cf3b23385bad4dd44b4694013c196639 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 9 May 2017 14:20:29 +0100 Subject: [PATCH 19/28] if project goes over maximum allowed files, put on cooldown --- .../app/coffee/Features/Project/ProjectEntityHandler.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee index 881c455032..078b4b49f4 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee @@ -15,6 +15,8 @@ docComparitor = require('./DocLinesComparitor') projectUpdateHandler = require('./ProjectUpdateHandler') DocstoreManager = require "../Docstore/DocstoreManager" ProjectGetter = require "./ProjectGetter" +CooldownManager = require '../Cooldown/CooldownManager' + module.exports = ProjectEntityHandler = getAllFolders: (project_id, callback) -> @@ -522,6 +524,7 @@ module.exports = ProjectEntityHandler = ProjectEntityHandler._countElements project, (err, count)-> if count > settings.maxEntitiesPerProject logger.warn project_id:project._id, "project too big, stopping insertions" + CooldownManager.putProjectOnCooldown(project._id) return callback("project_has_to_many_files") projectLocator.findElement {project:project, element_id:folder_id, type:"folders"}, (err, folder, path)=> if err? From e19f5a1a5eb5730090c6c137bb7ee9f3c19f9f25 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 9 May 2017 17:18:04 +0100 Subject: [PATCH 20/28] Update redis-sharelatex and shrinkwrap --- services/web/npm-shrinkwrap.json | 59 ++++++++++++++++---------------- services/web/package.json | 2 +- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json index 6904ca8272..f22ecf0219 100644 --- a/services/web/npm-shrinkwrap.json +++ b/services/web/npm-shrinkwrap.json @@ -740,12 +740,6 @@ "from": "double-ended-queue@>=2.1.0-0 <3.0.0", "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz" }, - "dtrace-provider": { - "version": "0.2.8", - "from": "dtrace-provider@0.2.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", - "optional": true - }, "each-series": { "version": "1.0.0", "from": "each-series@>=1.0.0 <2.0.0", @@ -2742,18 +2736,18 @@ } }, "redis-sharelatex": { - "version": "0.0.9", - "from": "redis-sharelatex@0.0.9", - "resolved": "https://registry.npmjs.org/redis-sharelatex/-/redis-sharelatex-0.0.9.tgz", + "version": "1.0.2", + "from": "git+https://github.com/sharelatex/redis-sharelatex.git#v1.0.2", + "resolved": "git+https://github.com/sharelatex/redis-sharelatex.git#143b7eb192675f36d835080e534a4ac4899f918a", "dependencies": { "ansi-regex": { "version": "0.2.1", - "from": "ansi-regex@^0.2.0", + "from": "ansi-regex@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz" }, "ansi-styles": { "version": "1.1.0", - "from": "ansi-styles@^1.1.0", + "from": "ansi-styles@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz" }, "assertion-error": { @@ -2761,6 +2755,11 @@ "from": "assertion-error@1.0.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz" }, + "async": { + "version": "2.4.0", + "from": "async@>=2.4.0 <3.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.4.0.tgz" + }, "chai": { "version": "1.9.1", "from": "chai@1.9.1", @@ -2768,7 +2767,7 @@ }, "chalk": { "version": "0.5.1", - "from": "chalk@~0.5.0", + "from": "chalk@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz" }, "coffee-script": { @@ -2793,7 +2792,7 @@ "dependencies": { "mkdirp": { "version": "0.5.1", - "from": "mkdirp@^0.5.0", + "from": "mkdirp@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" } } @@ -2815,8 +2814,13 @@ "dependencies": { "coffee-script": { "version": "1.7.1", - "from": "coffee-script@~1.7.0", + "from": "coffee-script@>=1.7.0 <1.8.0", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.7.1.tgz" + }, + "lodash": { + "version": "2.4.2", + "from": "lodash@>=2.4.1 <2.5.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" } } }, @@ -2827,7 +2831,7 @@ }, "has-ansi": { "version": "0.1.0", - "from": "has-ansi@^0.1.0", + "from": "has-ansi@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz" }, "jade": { @@ -2852,15 +2856,10 @@ "from": "jsonfile@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz" }, - "lodash": { - "version": "2.4.2", - "from": "lodash@~2.4.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" - }, "minimatch": { - "version": "3.0.3", + "version": "3.0.4", "from": "minimatch@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz" + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" }, "mkdirp": { "version": "0.3.5", @@ -2884,7 +2883,7 @@ }, "minimatch": { "version": "0.2.14", - "from": "minimatch@~0.2.11", + "from": "minimatch@>=0.2.11 <0.3.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" } } @@ -2904,11 +2903,6 @@ "from": "rimraf@>=2.2.8 <3.0.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz" }, - "samsam": { - "version": "1.1.3", - "from": "samsam@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.3.tgz" - }, "sandboxed-module": { "version": "1.0.1", "from": "sandboxed-module@1.0.1", @@ -2921,12 +2915,12 @@ }, "strip-ansi": { "version": "0.3.0", - "from": "strip-ansi@^0.3.0", + "from": "strip-ansi@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz" }, "supports-color": { "version": "0.2.0", - "from": "supports-color@^0.2.0", + "from": "supports-color@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz" }, "underscore": { @@ -3036,6 +3030,11 @@ "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz", "optional": true }, + "samsam": { + "version": "1.1.2", + "from": "samsam@1.1.2", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz" + }, "sanitizer": { "version": "0.1.1", "from": "sanitizer@0.1.1", diff --git a/services/web/package.json b/services/web/package.json index 2e78c3e6f3..9c7c7b58de 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -49,7 +49,7 @@ "passport-ldapauth": "^0.6.0", "passport-local": "^1.0.0", "redis": "0.10.1", - "redis-sharelatex": "git+https://github.com/sharelatex/redis-sharelatex.git#v1.0.0", + "redis-sharelatex": "git+https://github.com/sharelatex/redis-sharelatex.git#v1.0.2", "request": "^2.69.0", "requests": "^0.1.7", "rimraf": "2.2.6", From b8e4cafd814ae800f23e243ffc6fa2424558e65c Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Wed, 10 May 2017 15:16:36 +0100 Subject: [PATCH 21/28] Fix log line --- .../web/app/coffee/Features/Cooldown/CooldownManager.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/web/app/coffee/Features/Cooldown/CooldownManager.coffee b/services/web/app/coffee/Features/Cooldown/CooldownManager.coffee index 933149a4db..9797cbf04d 100644 --- a/services/web/app/coffee/Features/Cooldown/CooldownManager.coffee +++ b/services/web/app/coffee/Features/Cooldown/CooldownManager.coffee @@ -12,8 +12,7 @@ module.exports = CooldownManager = "Cooldown:{#{projectId}}" putProjectOnCooldown: (projectId, callback=(err)->) -> - logger.log {projectId}, - "[Cooldown] putting project on cooldown for #{COOLDOWN_IN_SECONDS} seconds" + logger.log {projectId}, "[Cooldown] putting project on cooldown for #{COOLDOWN_IN_SECONDS} seconds" rclient.set(CooldownManager._buildKey(projectId), '1', 'EX', COOLDOWN_IN_SECONDS, callback) isProjectOnCooldown: (projectId, callback=(err, isOnCooldown)->) -> From 08567ff2202ef853052a9e846e79742b6f26d632 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Wed, 10 May 2017 15:25:23 +0100 Subject: [PATCH 22/28] if projectId is not defined, error out --- .../Features/Cooldown/CooldownMiddlewear.coffee | 2 ++ .../Cooldown/CooldownMiddlewearTests.coffee | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/services/web/app/coffee/Features/Cooldown/CooldownMiddlewear.coffee b/services/web/app/coffee/Features/Cooldown/CooldownMiddlewear.coffee index b073541945..6d1e158699 100644 --- a/services/web/app/coffee/Features/Cooldown/CooldownMiddlewear.coffee +++ b/services/web/app/coffee/Features/Cooldown/CooldownMiddlewear.coffee @@ -6,6 +6,8 @@ module.exports = CooldownMiddlewear = freezeProject: (req, res, next) -> projectId = req.params.Project_id + if !projectId? + return next(new Error('[Cooldown] No projectId parameter on route')) CooldownManager.isProjectOnCooldown projectId, (err, projectIsOnCooldown) -> if err? return next(err) diff --git a/services/web/test/UnitTests/coffee/Cooldown/CooldownMiddlewearTests.coffee b/services/web/test/UnitTests/coffee/Cooldown/CooldownMiddlewearTests.coffee index 694ca11fce..646e7ffbde 100644 --- a/services/web/test/UnitTests/coffee/Cooldown/CooldownMiddlewearTests.coffee +++ b/services/web/test/UnitTests/coffee/Cooldown/CooldownMiddlewearTests.coffee @@ -70,3 +70,19 @@ describe "CooldownMiddlewear", -> @CooldownMiddlewear.freezeProject @req, @res, @next @next.callCount.should.equal 1 expect(@next.lastCall.args[0]).to.be.instanceof Error + + describe 'when projectId is not part of route', -> + beforeEach -> + @CooldownManager.isProjectOnCooldown = sinon.stub().callsArgWith(1, null, true) + @req = {params: {lol: 'abc'}} + @res = {sendStatus: sinon.stub()} + @next = sinon.stub() + + it 'call next with an error', -> + @CooldownMiddlewear.freezeProject @req, @res, @next + @next.callCount.should.equal 1 + expect(@next.lastCall.args[0]).to.be.instanceof Error + + it 'should not call CooldownManager.isProjectOnCooldown', -> + @CooldownMiddlewear.freezeProject @req, @res, @next + @CooldownManager.isProjectOnCooldown.callCount.should.equal 0 From 01ee104f776a3cfcd0d48bfc3fd1d2a09fd739ea Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Wed, 10 May 2017 15:31:17 +0100 Subject: [PATCH 23/28] Revert "update to newest mongoose" This reverts commit 7b05891ba0e87e4cf00fd6b1ea87d7406dc70989. --- .../Project/ProjectCreationHandler.coffee | 2 +- .../Project/ProjectEntityHandler.coffee | 2 +- .../Subscription/SubscriptionLocator.coffee | 2 +- .../Subscription/SubscriptionUpdater.coffee | 2 +- .../app/coffee/infrastructure/Mongoose.coffee | 2 - services/web/app/coffee/models/Doc.coffee | 2 +- services/web/app/coffee/models/File.coffee | 2 +- services/web/app/coffee/models/Folder.coffee | 2 +- services/web/app/coffee/models/Project.coffee | 2 +- .../app/coffee/models/ProjectInvite.coffee | 2 +- .../web/app/coffee/models/Subscription.coffee | 4 +- .../app/coffee/models/SystemMessage.coffee | 2 +- services/web/app/coffee/models/User.coffee | 2 +- services/web/npm-shrinkwrap.json | 142 ++++++++++-------- services/web/package.json | 2 +- 15 files changed, 95 insertions(+), 77 deletions(-) diff --git a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee index 7718524a79..ffb69abeac 100644 --- a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee @@ -2,7 +2,7 @@ logger = require('logger-sharelatex') async = require("async") metrics = require('metrics-sharelatex') Settings = require('settings-sharelatex') -ObjectId = require('../../infrastructure/Mongoose').Types.ObjectId +ObjectId = require('mongoose').Types.ObjectId Project = require('../../models/Project').Project Folder = require('../../models/Folder').Folder ProjectEntityHandler = require('./ProjectEntityHandler') diff --git a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee index 078b4b49f4..5868d6941a 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee @@ -534,7 +534,7 @@ module.exports = ProjectEntityHandler = fileSystem: "#{path.fileSystem}/#{element.name}" mongo: path.mongo id = element._id+'' - element._id = require('../../infrastructure/Mongoose').Types.ObjectId(id) + element._id = require('mongoose').Types.ObjectId(id) conditions = _id:project._id mongopath = "#{path.mongo}.#{type}" update = "$push":{} diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee index 0e17a5640d..beee4a3158 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee @@ -1,6 +1,6 @@ Subscription = require('../../models/Subscription').Subscription logger = require("logger-sharelatex") -ObjectId = require('../../infrastructure/Mongoose').Types.ObjectId +ObjectId = require('mongoose').Types.ObjectId module.exports = diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee index 6027a2533e..a4a0864d4f 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee @@ -6,7 +6,7 @@ UserFeaturesUpdater = require("./UserFeaturesUpdater") PlansLocator = require("./PlansLocator") Settings = require("settings-sharelatex") logger = require("logger-sharelatex") -ObjectId = require('../../infrastructure/Mongoose').Types.ObjectId +ObjectId = require('mongoose').Types.ObjectId ReferalAllocator = require("../Referal/ReferalAllocator") oneMonthInSeconds = 60 * 60 * 24 * 30 diff --git a/services/web/app/coffee/infrastructure/Mongoose.coffee b/services/web/app/coffee/infrastructure/Mongoose.coffee index 6b9e05759f..7fbf64fd71 100644 --- a/services/web/app/coffee/infrastructure/Mongoose.coffee +++ b/services/web/app/coffee/infrastructure/Mongoose.coffee @@ -2,8 +2,6 @@ mongoose = require('mongoose') Settings = require 'settings-sharelatex' logger = require('logger-sharelatex') -mongoose.Promise = global.Promise - mongoose.connect(Settings.mongo.url, server: poolSize: 10) mongoose.connection.on 'connected', () -> diff --git a/services/web/app/coffee/models/Doc.coffee b/services/web/app/coffee/models/Doc.coffee index 122b6a935b..862638acb7 100644 --- a/services/web/app/coffee/models/Doc.coffee +++ b/services/web/app/coffee/models/Doc.coffee @@ -1,4 +1,4 @@ -mongoose = require '../infrastructure/Mongoose' +mongoose = require 'mongoose' Settings = require 'settings-sharelatex' Schema = mongoose.Schema diff --git a/services/web/app/coffee/models/File.coffee b/services/web/app/coffee/models/File.coffee index c6cf64bf1d..3ca32b8f8f 100644 --- a/services/web/app/coffee/models/File.coffee +++ b/services/web/app/coffee/models/File.coffee @@ -1,4 +1,4 @@ -mongoose = require '../infrastructure/Mongoose' +mongoose = require 'mongoose' Settings = require 'settings-sharelatex' Schema = mongoose.Schema diff --git a/services/web/app/coffee/models/Folder.coffee b/services/web/app/coffee/models/Folder.coffee index 03058aa5b7..4c2bf04b64 100644 --- a/services/web/app/coffee/models/Folder.coffee +++ b/services/web/app/coffee/models/Folder.coffee @@ -1,4 +1,4 @@ -mongoose = require('../infrastructure/Mongoose') +mongoose = require('mongoose') Settings = require 'settings-sharelatex' DocSchema = require('./Doc').DocSchema FileSchema = require('./File').FileSchema diff --git a/services/web/app/coffee/models/Project.coffee b/services/web/app/coffee/models/Project.coffee index 6211b1eff9..18387bdc0b 100644 --- a/services/web/app/coffee/models/Project.coffee +++ b/services/web/app/coffee/models/Project.coffee @@ -1,4 +1,4 @@ -mongoose = require('../infrastructure/Mongoose') +mongoose = require('mongoose') Settings = require 'settings-sharelatex' _ = require('underscore') FolderSchema = require('./Folder.js').FolderSchema diff --git a/services/web/app/coffee/models/ProjectInvite.coffee b/services/web/app/coffee/models/ProjectInvite.coffee index 9f6fb16c55..9b9e0cb350 100644 --- a/services/web/app/coffee/models/ProjectInvite.coffee +++ b/services/web/app/coffee/models/ProjectInvite.coffee @@ -1,4 +1,4 @@ -mongoose = require '../infrastructure/Mongoose' +mongoose = require 'mongoose' Settings = require 'settings-sharelatex' diff --git a/services/web/app/coffee/models/Subscription.coffee b/services/web/app/coffee/models/Subscription.coffee index 84395e7cc6..cd036fb6f1 100644 --- a/services/web/app/coffee/models/Subscription.coffee +++ b/services/web/app/coffee/models/Subscription.coffee @@ -1,4 +1,4 @@ -mongoose = require '../infrastructure/Mongoose' +mongoose = require 'mongoose' Settings = require 'settings-sharelatex' Schema = mongoose.Schema @@ -31,4 +31,4 @@ Subscription = conn.model('Subscription', SubscriptionSchema) mongoose.model 'Subscription', SubscriptionSchema exports.Subscription = Subscription -exports.SubscriptionSchema = SubscriptionSchema +exports.SubscriptionSchema = SubscriptionSchema \ No newline at end of file diff --git a/services/web/app/coffee/models/SystemMessage.coffee b/services/web/app/coffee/models/SystemMessage.coffee index 3e1e1689e4..adb665fede 100644 --- a/services/web/app/coffee/models/SystemMessage.coffee +++ b/services/web/app/coffee/models/SystemMessage.coffee @@ -1,4 +1,4 @@ -mongoose = require '../infrastructure/Mongoose' +mongoose = require 'mongoose' Settings = require 'settings-sharelatex' Schema = mongoose.Schema diff --git a/services/web/app/coffee/models/User.coffee b/services/web/app/coffee/models/User.coffee index d833c3412a..099b9ef8e2 100644 --- a/services/web/app/coffee/models/User.coffee +++ b/services/web/app/coffee/models/User.coffee @@ -1,7 +1,7 @@ Project = require('./Project').Project Settings = require 'settings-sharelatex' _ = require('underscore') -mongoose = require('../infrastructure/Mongoose') +mongoose = require('mongoose') uuid = require('uuid') Schema = mongoose.Schema ObjectId = Schema.ObjectId diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json index 8fa1a1b2e8..6904ca8272 100644 --- a/services/web/npm-shrinkwrap.json +++ b/services/web/npm-shrinkwrap.json @@ -292,6 +292,20 @@ "from": "bson@>=1.0.4 <1.1.0", "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz" }, + "bson-ext": { + "version": "0.1.13", + "from": "bson-ext@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/bson-ext/-/bson-ext-0.1.13.tgz", + "optional": true, + "dependencies": { + "nan": { + "version": "2.0.9", + "from": "nan@>=2.0.9 <2.1.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.0.9.tgz", + "optional": true + } + } + }, "buffer": { "version": "4.9.1", "from": "buffer@4.9.1", @@ -1236,9 +1250,9 @@ "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz" }, "hooks-fixed": { - "version": "2.0.0", - "from": "hooks-fixed@2.0.0", - "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-2.0.0.tgz" + "version": "1.1.0", + "from": "hooks-fixed@1.1.0", + "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-1.1.0.tgz" }, "http-errors": { "version": "1.6.1", @@ -1461,9 +1475,23 @@ } }, "kareem": { - "version": "1.4.1", - "from": "kareem@1.4.1", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.4.1.tgz" + "version": "1.0.1", + "from": "kareem@1.0.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.0.1.tgz" + }, + "kerberos": { + "version": "0.0.23", + "from": "kerberos@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/kerberos/-/kerberos-0.0.23.tgz", + "optional": true, + "dependencies": { + "nan": { + "version": "2.5.1", + "from": "nan@>=2.5.1 <2.6.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.5.1.tgz", + "optional": true + } + } }, "kind-of": { "version": "3.1.0", @@ -2068,49 +2096,46 @@ } }, "mongoose": { - "version": "4.9.8", - "from": "mongoose@4.9.8", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.9.8.tgz", + "version": "4.1.0", + "from": "mongoose@4.1.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.1.0.tgz", "dependencies": { "async": { - "version": "2.1.4", - "from": "async@2.1.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz" + "version": "0.9.0", + "from": "async@0.9.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" }, - "es6-promise": { - "version": "3.2.1", - "from": "es6-promise@3.2.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz" - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + "bson": { + "version": "0.3.2", + "from": "bson@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-0.3.2.tgz" }, "mongodb": { - "version": "2.2.26", - "from": "mongodb@2.2.26", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.26.tgz" + "version": "2.0.34", + "from": "mongodb@2.0.34", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.0.34.tgz" }, "mongodb-core": { - "version": "2.1.10", - "from": "mongodb-core@2.1.10", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.10.tgz" + "version": "1.2.0", + "from": "mongodb-core@1.2.0", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-1.2.0.tgz", + "dependencies": { + "bson": { + "version": "0.4.23", + "from": "bson@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-0.4.23.tgz" + } + } }, "ms": { - "version": "0.7.2", - "from": "ms@0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz" + "version": "0.1.0", + "from": "ms@0.1.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.1.0.tgz" }, "readable-stream": { - "version": "2.2.7", - "from": "readable-stream@2.2.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz" - }, - "string_decoder": { - "version": "1.0.0", - "from": "string_decoder@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.0.tgz" + "version": "1.0.31", + "from": "readable-stream@1.0.31", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.31.tgz" } } }, @@ -2120,24 +2145,24 @@ "resolved": "https://registry.npmjs.org/monocle/-/monocle-1.1.51.tgz" }, "mpath": { - "version": "0.2.1", - "from": "mpath@0.2.1", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.2.1.tgz" + "version": "0.1.1", + "from": "mpath@0.1.1", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.1.1.tgz" }, "mpromise": { - "version": "0.5.5", - "from": "mpromise@0.5.5", - "resolved": "https://registry.npmjs.org/mpromise/-/mpromise-0.5.5.tgz" + "version": "0.5.4", + "from": "mpromise@0.5.4", + "resolved": "https://registry.npmjs.org/mpromise/-/mpromise-0.5.4.tgz" }, "mquery": { - "version": "2.3.0", - "from": "mquery@2.3.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-2.3.0.tgz", + "version": "1.6.1", + "from": "mquery@1.6.1", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-1.6.1.tgz", "dependencies": { "bluebird": { - "version": "2.10.2", - "from": "bluebird@2.10.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz" + "version": "2.9.26", + "from": "bluebird@2.9.26", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.26.tgz" }, "debug": { "version": "2.2.0", @@ -2148,11 +2173,6 @@ "version": "0.7.1", "from": "ms@0.7.1", "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" - }, - "sliced": { - "version": "0.0.5", - "from": "sliced@0.0.5", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz" } } }, @@ -2194,9 +2214,9 @@ } }, "muri": { - "version": "1.2.1", - "from": "muri@1.2.1", - "resolved": "https://registry.npmjs.org/muri/-/muri-1.2.1.tgz" + "version": "1.0.0", + "from": "muri@1.0.0", + "resolved": "https://registry.npmjs.org/muri/-/muri-1.0.0.tgz" }, "mv": { "version": "0.0.5", @@ -3182,9 +3202,9 @@ "resolved": "https://registry.npmjs.org/sixpack-client/-/sixpack-client-1.0.0.tgz" }, "sliced": { - "version": "1.0.1", - "from": "sliced@1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz" + "version": "0.0.5", + "from": "sliced@0.0.5", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz" }, "smtp-connection": { "version": "2.0.1", diff --git a/services/web/package.json b/services/web/package.json index ff9332f8f0..85da8a3765 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -39,7 +39,7 @@ "mimelib": "0.2.14", "mocha": "1.17.1", "mongojs": "2.4.0", - "mongoose": "4.9.8", + "mongoose": "4.1.0", "multer": "^0.1.8", "nodemailer": "2.1.0", "nodemailer-sendgrid-transport": "^0.2.0", From a08dd26ef39b081e6456dbfc45e822d8022518b9 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Thu, 11 May 2017 10:07:04 +0100 Subject: [PATCH 24/28] Remove CooldownMiddlewear from Tpds routes --- services/web/app/coffee/router.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index e0fb433b97..fa799a2dff 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -221,10 +221,10 @@ module.exports = class Router apiRouter.get '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.getDocument apiRouter.post '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.setDocument - apiRouter.post '/user/:user_id/update/*', AuthenticationController.httpAuth, CooldownMiddlewear.freezeProject, TpdsController.mergeUpdate + apiRouter.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate apiRouter.delete '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate - apiRouter.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, CooldownMiddlewear.freezeProject, TpdsController.updateProjectContents + apiRouter.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents apiRouter.delete '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents webRouter.post "/spelling/check", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi From a3ab994ab3213b6e5c102031a785079c839a0cd3 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 11 May 2017 10:17:25 +0100 Subject: [PATCH 25/28] Use the same watcher to control miniRP visibility and trigger Ace resizing. --- services/web/app/views/project/editor/editor.pug | 2 +- services/web/public/coffee/ide.coffee | 1 + .../review-panel/controllers/ReviewPanelController.coffee | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug index 6c062f3cbe..e8217a9319 100644 --- a/services/web/app/views/project/editor/editor.pug +++ b/services/web/app/views/project/editor/editor.pug @@ -16,7 +16,7 @@ div.full-size( 'rp-state-current-file-expanded': (reviewPanel.subView === SubViews.CUR_FILE && ui.reviewPanelOpen),\ 'rp-state-current-file-mini': (reviewPanel.subView === SubViews.CUR_FILE && !ui.reviewPanelOpen),\ 'rp-state-overview': (reviewPanel.subView === SubViews.OVERVIEW),\ - 'rp-size-mini': (!ui.reviewPanelOpen && reviewPanel.hasEntries),\ + 'rp-size-mini': ui.miniReviewPanelVisible,\ 'rp-size-expanded': ui.reviewPanelOpen,\ 'rp-layout-left': reviewPanel.layoutToLeft,\ 'rp-loading-threads': reviewPanel.loadingThreads,\ diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index 9fbbfe9937..dabf166578 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -70,6 +70,7 @@ define [ chatOpen: false pdfLayout: 'sideBySide' reviewPanelOpen: localStorage("ui.reviewPanelOpen.#{window.project_id}") + miniReviewPanelVisible: false } $scope.user = window.user diff --git a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee index bf3f96ffc3..101915a4dc 100644 --- a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee +++ b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee @@ -126,6 +126,12 @@ define [ else $reviewPanelEl.css "right", "0" + $scope.$watch "!ui.reviewPanelOpen && reviewPanel.hasEntries", (open, prevVal) -> + return if !open? + $scope.ui.miniReviewPanelVisible = open + if open != prevVal + $timeout () -> $scope.$broadcast "review-panel:toggle" + $scope.$watch "ui.reviewPanelOpen", (open) -> return if !open? if !open From f2dac28a653940643fa3666c3382cba8cd65e892 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 11 May 2017 10:23:41 +0100 Subject: [PATCH 26/28] Consolidate some watchers. --- .../controllers/ReviewPanelController.coffee | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee index 101915a4dc..cb17d53e04 100644 --- a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee +++ b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee @@ -141,7 +141,10 @@ define [ else # Reset back to what we had when previously open $scope.reviewPanel.subView = $scope.reviewPanel.openSubView - + $timeout () -> + $scope.$broadcast "review-panel:toggle" + $scope.$broadcast "review-panel:layout", false + $scope.$watch "reviewPanel.subView", (view) -> return if !view? updateScrollbar() @@ -171,12 +174,6 @@ define [ ), (nEntries) -> $scope.reviewPanel.hasEntries = nEntries > 0 and $scope.project.features.trackChangesVisible - $scope.$watch "ui.reviewPanelOpen", (reviewPanelOpen) -> - return if !reviewPanelOpen? - $timeout () -> - $scope.$broadcast "review-panel:toggle" - $scope.$broadcast "review-panel:layout", false - regenerateTrackChangesId = (doc) -> old_id = getChangeTracker(doc.doc_id).getIdSeed() new_id = RangesTracker.generateIdSeed() From 7b0aca7f0203bed2f3db87e1016f0ce53e8db29d Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Thu, 11 May 2017 11:29:57 +0100 Subject: [PATCH 27/28] add cooldown to tpds `mergeUpdate` path --- .../Features/Errors/ErrorController.coffee | 3 ++ .../app/coffee/Features/Errors/Errors.coffee | 10 ++++++ .../TpdsUpdateHandler.coffee | 15 +++++--- .../TpdsUpdateHandlerTests.coffee | 36 +++++++++++++++++++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/services/web/app/coffee/Features/Errors/ErrorController.coffee b/services/web/app/coffee/Features/Errors/ErrorController.coffee index 861a1362b2..45a743f282 100644 --- a/services/web/app/coffee/Features/Errors/ErrorController.coffee +++ b/services/web/app/coffee/Features/Errors/ErrorController.coffee @@ -22,6 +22,9 @@ module.exports = ErrorController = if error instanceof Errors.NotFoundError logger.warn {err: error, url: req.url}, "not found error" ErrorController.notFound req, res + else if error instanceof Errors.TooManyRequestsError + logger.warn {err: error, url: req.url}, "too many requests error" + res.sendStatus(429) else logger.error err: error, url:req.url, method:req.method, user:user, "error passed to top level next middlewear" ErrorController.serverError req, res diff --git a/services/web/app/coffee/Features/Errors/Errors.coffee b/services/web/app/coffee/Features/Errors/Errors.coffee index 92228f7b0b..56c0ada7d5 100644 --- a/services/web/app/coffee/Features/Errors/Errors.coffee +++ b/services/web/app/coffee/Features/Errors/Errors.coffee @@ -11,8 +11,18 @@ ServiceNotConfiguredError = (message) -> error.name = "ServiceNotConfiguredError" error.__proto__ = ServiceNotConfiguredError.prototype return error +ServiceNotConfiguredError.prototype.__proto__ = Error.prototype + + +TooManyRequestsError = (message) -> + error = new Error(message) + error.name = "TooManyRequestsError" + error.__proto__ = TooManyRequestsError.prototype + return error +TooManyRequestsError.prototype.__proto__ = Error.prototype module.exports = Errors = NotFoundError: NotFoundError ServiceNotConfiguredError: ServiceNotConfiguredError + TooManyRequestsError: TooManyRequestsError diff --git a/services/web/app/coffee/Features/ThirdPartyDataStore/TpdsUpdateHandler.coffee b/services/web/app/coffee/Features/ThirdPartyDataStore/TpdsUpdateHandler.coffee index 7605f6911b..c78b588e8a 100644 --- a/services/web/app/coffee/Features/ThirdPartyDataStore/TpdsUpdateHandler.coffee +++ b/services/web/app/coffee/Features/ThirdPartyDataStore/TpdsUpdateHandler.coffee @@ -5,6 +5,8 @@ projectCreationHandler = require('../Project/ProjectCreationHandler') projectDeleter = require('../Project/ProjectDeleter') ProjectRootDocManager = require "../Project/ProjectRootDocManager" FileTypeManager = require('../Uploads/FileTypeManager') +CooldownManager = require('../Cooldown/CooldownManager') +Errors = require('../Errors/Errors') commitMessage = "Before update from Dropbox" @@ -24,10 +26,15 @@ module.exports = cb err, project getOrCreateProject (err, project)-> return callback(err) if err? - FileTypeManager.shouldIgnore path, (err, shouldIgnore)-> - if shouldIgnore - return callback() - updateMerger.mergeUpdate user_id, project._id, path, updateRequest, source, callback + CooldownManager.isProjectOnCooldown project._id, (err, projectIsOnCooldown) -> + return callback(err) if err? + if projectIsOnCooldown + logger.log {projectId: project._id}, "project is on cooldown, denying request" + return callback(new Errors.TooManyRequestsError('project on cooldown')) + FileTypeManager.shouldIgnore path, (err, shouldIgnore)-> + if shouldIgnore + return callback() + updateMerger.mergeUpdate user_id, project._id, path, updateRequest, source, callback deleteUpdate: (user_id, projectName, path, source, callback)-> diff --git a/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsUpdateHandlerTests.coffee b/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsUpdateHandlerTests.coffee index 04fd34671e..dedd3ea7c8 100644 --- a/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsUpdateHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/ThirdPartyDataStore/TpdsUpdateHandlerTests.coffee @@ -1,6 +1,7 @@ SandboxedModule = require('sandboxed-module') sinon = require('sinon') require('chai').should() +expect = require('chai').expect modulePath = require('path').join __dirname, '../../../../app/js/Features/ThirdPartyDataStore/TpdsUpdateHandler.js' describe 'TpdsUpdateHandler', -> @@ -19,6 +20,8 @@ describe 'TpdsUpdateHandler', -> @rootDocManager = setRootDocAutomatically:sinon.stub() @FileTypeManager = shouldIgnore: sinon.stub().callsArgWith(1, null, false) + @CooldownManager = + isProjectOnCooldown: sinon.stub().callsArgWith(1, null, false) @handler = SandboxedModule.require modulePath, requires: './UpdateMerger': @updateMerger './Editor/EditorController': @editorController @@ -27,6 +30,7 @@ describe 'TpdsUpdateHandler', -> '../Project/ProjectDeleter': @projectDeleter "../Project/ProjectRootDocManager" : @rootDocManager '../Uploads/FileTypeManager': @FileTypeManager + '../Cooldown/CooldownManager': @CooldownManager 'logger-sharelatex': log:-> @user_id = "dsad29jlkjas" @source = "dropbox" @@ -67,6 +71,38 @@ describe 'TpdsUpdateHandler', -> @updateMerger.mergeUpdate.called.should.equal false done() + it 'should check if the project is on cooldown', (done) -> + @CooldownManager.isProjectOnCooldown = sinon.stub().callsArgWith(1, null, false) + @projectLocator.findUsersProjectByName = sinon.stub().callsArgWith(2) + path = "/path/here" + update = {} + @updateMerger.mergeUpdate = sinon.stub() + @updateMerger.mergeUpdate.withArgs(@user_id, @project_id, path, update, @source).callsArg(5) + @handler.newUpdate @user_id, @project.name, path, update, @source, (err) => + expect(err).to.be.oneOf [null, undefined] + @CooldownManager.isProjectOnCooldown.callCount.should.equal 1 + @CooldownManager.isProjectOnCooldown.calledWith(@project_id).should.equal true + @FileTypeManager.shouldIgnore.callCount.should.equal 1 + @updateMerger.mergeUpdate.callCount.should.equal 1 + done() + + it 'should return error and not proceed with update if project is on cooldown', (done) -> + @CooldownManager.isProjectOnCooldown = sinon.stub().callsArgWith(1, null, true) + @projectLocator.findUsersProjectByName = sinon.stub().callsArgWith(2) + @FileTypeManager.shouldIgnore = sinon.stub().callsArgWith(1, null, false) + path = "/path/here" + update = {} + @updateMerger.mergeUpdate = sinon.stub() + @updateMerger.mergeUpdate.withArgs(@user_id, @project_id, path, update, @source).callsArg(5) + @handler.newUpdate @user_id, @project.name, path, update, @source, (err) => + expect(err).to.not.be.oneOf [null, undefined] + expect(err).to.be.instanceof Error + @CooldownManager.isProjectOnCooldown.callCount.should.equal 1 + @CooldownManager.isProjectOnCooldown.calledWith(@project_id).should.equal true + @FileTypeManager.shouldIgnore.callCount.should.equal 0 + @updateMerger.mergeUpdate.callCount.should.equal 0 + done() + describe 'getting a delete :', -> it 'should call deleteEntity in the collaberation manager', (done)-> path = "/delete/this" From 3bfd92dd9c146ce284c1b13e9fd1d4401a0dfe3b Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 11 May 2017 15:27:01 +0100 Subject: [PATCH 28/28] Rename lock to avoid potential conflict with doc updater --- .../web/app/coffee/infrastructure/LockManager.coffee | 12 ++++++------ .../LockManager/CheckingTheLock.coffee | 2 +- .../LockManager/ReleasingTheLock.coffee | 2 +- .../infrastructure/LockManager/tryLockTests.coffee | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/services/web/app/coffee/infrastructure/LockManager.coffee b/services/web/app/coffee/infrastructure/LockManager.coffee index 370e4c09c9..3064fffabf 100644 --- a/services/web/app/coffee/infrastructure/LockManager.coffee +++ b/services/web/app/coffee/infrastructure/LockManager.coffee @@ -9,17 +9,17 @@ module.exports = LockManager = MAX_LOCK_WAIT_TIME: 10000 # 10s maximum time to spend trying to get the lock REDIS_LOCK_EXPIRY: 30 # seconds. Time until lock auto expires in redis. - _blockingKey : (key)-> "Blocking:"+key + _blockingKey : (key)-> "lock:web:{#{key}}" tryLock : (key, callback = (err, isFree)->)-> rclient.set LockManager._blockingKey(key), "locked", "EX", LockManager.REDIS_LOCK_EXPIRY, "NX", (err, gotLock)-> return callback(err) if err? if gotLock == "OK" - metrics.inc "doc-not-blocking" + metrics.inc "lock-not-blocking" callback err, true else - metrics.inc "doc-blocking" - logger.log key: key, redis_response: gotLock, "doc is locked" + metrics.inc "lock-blocking" + logger.log key: key, redis_response: gotLock, "lock is locked" callback err, false getLock: (key, callback = (error) ->) -> @@ -42,10 +42,10 @@ module.exports = LockManager = return callback(err) if err? exists = parseInt replys[0] if exists == 1 - metrics.inc "doc-blocking" + metrics.inc "lock-blocking" callback err, false else - metrics.inc "doc-not-blocking" + metrics.inc "lock-not-blocking" callback err, true releaseLock: (key, callback)-> diff --git a/services/web/test/UnitTests/coffee/infrastructure/LockManager/CheckingTheLock.coffee b/services/web/test/UnitTests/coffee/infrastructure/LockManager/CheckingTheLock.coffee index 8926382f8b..cf56778ec8 100644 --- a/services/web/test/UnitTests/coffee/infrastructure/LockManager/CheckingTheLock.coffee +++ b/services/web/test/UnitTests/coffee/infrastructure/LockManager/CheckingTheLock.coffee @@ -4,7 +4,7 @@ path = require('path') modulePath = path.join __dirname, '../../../../../app/js/infrastructure/LockManager.js' project_id = 1234 doc_id = 5678 -blockingKey = "Blocking:#{doc_id}" +blockingKey = "lock:web:{#{doc_id}}" SandboxedModule = require('sandboxed-module') describe 'LockManager - checking the lock', ()-> diff --git a/services/web/test/UnitTests/coffee/infrastructure/LockManager/ReleasingTheLock.coffee b/services/web/test/UnitTests/coffee/infrastructure/LockManager/ReleasingTheLock.coffee index fa99b87739..8ccab0a757 100644 --- a/services/web/test/UnitTests/coffee/infrastructure/LockManager/ReleasingTheLock.coffee +++ b/services/web/test/UnitTests/coffee/infrastructure/LockManager/ReleasingTheLock.coffee @@ -21,6 +21,6 @@ describe 'LockManager - releasing the lock', ()-> it 'should put a all data into memory', (done)-> LockManager.releaseLock doc_id, -> - deleteStub.calledWith("Blocking:#{doc_id}").should.equal true + deleteStub.calledWith("lock:web:{#{doc_id}}").should.equal true done() diff --git a/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee b/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee index a227ac60bd..5397e18cb2 100644 --- a/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee +++ b/services/web/test/UnitTests/coffee/infrastructure/LockManager/tryLockTests.coffee @@ -24,7 +24,7 @@ describe 'LockManager - trying the lock', -> @LockManager.tryLock @doc_id, @callback it "should set the lock key with an expiry if it is not set", -> - @set.calledWith("Blocking:#{@doc_id}", "locked", "EX", 30, "NX") + @set.calledWith("lock:web:{#{@doc_id}}", "locked", "EX", 30, "NX") .should.equal true it "should return the callback with true", ->