소스 검색

Merge pull request #3495 from overleaf/ae-prettier-2

Upgrade Prettier to v2

GitOrigin-RevId: 85aa3fa1acb6332c4f58c46165a43d1a51471f33
Alf Eaton 5 년 전
부모
커밋
1ebc8a79cb
100개의 변경된 파일3099개의 추가작업 그리고 2991개의 파일을 삭제
  1. 1 0
      services/web/.prettierignore
  2. 2 2
      services/web/app.js
  3. 3 3
      services/web/app/src/Features/Analytics/AnalyticsController.js
  4. 107 95
      services/web/app/src/Features/Authentication/AuthenticationController.js
  5. 6 6
      services/web/app/src/Features/Authentication/AuthenticationManager.js
  6. 80 73
      services/web/app/src/Features/Authorization/AuthorizationManager.js
  7. 137 142
      services/web/app/src/Features/Authorization/AuthorizationMiddleware.js
  8. 3 3
      services/web/app/src/Features/BetaProgram/BetaProgramController.js
  9. 3 3
      services/web/app/src/Features/BrandVariations/BrandVariationsHandler.js
  10. 2 2
      services/web/app/src/Features/Captcha/CaptchaMiddleware.js
  11. 9 9
      services/web/app/src/Features/Chat/ChatApiHandler.js
  12. 20 20
      services/web/app/src/Features/Chat/ChatController.js
  13. 1 1
      services/web/app/src/Features/Collaborators/CollaboratorsEmailHandler.js
  14. 57 55
      services/web/app/src/Features/Collaborators/CollaboratorsInviteController.js
  15. 118 113
      services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js
  16. 8 8
      services/web/app/src/Features/Compile/ClsiCookieManager.js
  17. 4 6
      services/web/app/src/Features/Compile/ClsiFormatChecker.js
  18. 37 39
      services/web/app/src/Features/Compile/ClsiStateManager.js
  19. 85 81
      services/web/app/src/Features/Compile/CompileController.js
  20. 99 94
      services/web/app/src/Features/Compile/CompileManager.js
  21. 53 48
      services/web/app/src/Features/Contacts/ContactController.js
  22. 4 4
      services/web/app/src/Features/Contacts/ContactManager.js
  23. 10 10
      services/web/app/src/Features/Cooldown/CooldownManager.js
  24. 15 15
      services/web/app/src/Features/Cooldown/CooldownMiddleware.js
  25. 50 50
      services/web/app/src/Features/Docstore/DocstoreManager.js
  26. 3 3
      services/web/app/src/Features/DocumentUpdater/DocumentUpdaterHandler.js
  27. 41 43
      services/web/app/src/Features/Documents/DocumentController.js
  28. 23 21
      services/web/app/src/Features/Downloads/ProjectDownloadsController.js
  29. 70 64
      services/web/app/src/Features/Downloads/ProjectZipStreamManager.js
  30. 87 74
      services/web/app/src/Features/Editor/EditorController.js
  31. 39 38
      services/web/app/src/Features/Exports/ExportsController.js
  32. 25 24
      services/web/app/src/Features/Exports/ExportsHandler.js
  33. 5 5
      services/web/app/src/Features/FileStore/FileHashManager.js
  34. 20 18
      services/web/app/src/Features/FileStore/FileStoreController.js
  35. 9 9
      services/web/app/src/Features/FileStore/FileStoreHandler.js
  36. 17 17
      services/web/app/src/Features/HealthCheck/HealthCheckController.js
  37. 17 17
      services/web/app/src/Features/History/HistoryController.js
  38. 39 38
      services/web/app/src/Features/History/RestoreManager.js
  39. 2 2
      services/web/app/src/Features/InactiveData/InactiveProjectController.js
  40. 29 28
      services/web/app/src/Features/InactiveData/InactiveProjectManager.js
  41. 4 4
      services/web/app/src/Features/Institutions/InstitutionsAPI.js
  42. 5 9
      services/web/app/src/Features/Institutions/InstitutionsController.js
  43. 12 9
      services/web/app/src/Features/Institutions/InstitutionsFeatures.js
  44. 1 1
      services/web/app/src/Features/Institutions/InstitutionsGetter.js
  45. 9 9
      services/web/app/src/Features/Institutions/InstitutionsManager.js
  46. 39 40
      services/web/app/src/Features/LinkedFiles/LinkedFilesController.js
  47. 53 51
      services/web/app/src/Features/LinkedFiles/LinkedFilesHandler.js
  48. 12 12
      services/web/app/src/Features/LinkedFiles/ProjectFileAgent.js
  49. 44 43
      services/web/app/src/Features/LinkedFiles/ProjectOutputFileAgent.js
  50. 36 34
      services/web/app/src/Features/LinkedFiles/UrlAgent.js
  51. 37 36
      services/web/app/src/Features/Metadata/MetaController.js
  52. 31 25
      services/web/app/src/Features/Metadata/MetaHandler.js
  53. 1 4
      services/web/app/src/Features/Newsletter/NewsletterManager.js
  54. 14 14
      services/web/app/src/Features/Notifications/NotificationsBuilder.js
  55. 15 12
      services/web/app/src/Features/Notifications/NotificationsController.js
  56. 2 2
      services/web/app/src/Features/Notifications/NotificationsHandler.js
  57. 8 8
      services/web/app/src/Features/Project/ProjectApiController.js
  58. 7 7
      services/web/app/src/Features/Project/ProjectCollabratecDetailsHandler.js
  59. 5 8
      services/web/app/src/Features/Project/ProjectController.js
  60. 100 97
      services/web/app/src/Features/Project/ProjectCreationHandler.js
  61. 14 14
      services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js
  62. 36 30
      services/web/app/src/Features/Project/ProjectGetter.js
  63. 1 1
      services/web/app/src/Features/Project/ProjectHelper.js
  64. 57 54
      services/web/app/src/Features/Project/ProjectHistoryHandler.js
  65. 4 3
      services/web/app/src/Features/Project/ProjectLocator.js
  66. 76 74
      services/web/app/src/Features/Project/ProjectRootDocManager.js
  67. 4 4
      services/web/app/src/Features/Project/ProjectUpdateHandler.js
  68. 1 1
      services/web/app/src/Features/Project/SafePath.js
  69. 1 1
      services/web/app/src/Features/Publishers/PublishersGetter.js
  70. 3 3
      services/web/app/src/Features/Referal/ReferalAllocator.js
  71. 3 3
      services/web/app/src/Features/Referal/ReferalFeatures.js
  72. 1 1
      services/web/app/src/Features/Referal/ReferalHandler.js
  73. 2 2
      services/web/app/src/Features/References/ReferencesController.js
  74. 26 25
      services/web/app/src/Features/References/ReferencesHandler.js
  75. 1 1
      services/web/app/src/Features/Security/LoginRateLimiter.js
  76. 4 4
      services/web/app/src/Features/Security/OneTimeTokenHandler.js
  77. 3 3
      services/web/app/src/Features/Security/RateLimiterMiddleware.js
  78. 12 11
      services/web/app/src/Features/ServerAdmin/AdminController.js
  79. 1 1
      services/web/app/src/Features/Spelling/SpellingController.js
  80. 3 3
      services/web/app/src/Features/StaticPages/HomeController.js
  81. 38 38
      services/web/app/src/Features/Subscription/FeaturesUpdater.js
  82. 62 60
      services/web/app/src/Features/Subscription/LimitationsManager.js
  83. 103 102
      services/web/app/src/Features/Subscription/RecurlyWrapper.js
  84. 193 185
      services/web/app/src/Features/Subscription/SubscriptionController.js
  85. 22 22
      services/web/app/src/Features/Subscription/SubscriptionGroupController.js
  86. 7 7
      services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js
  87. 185 186
      services/web/app/src/Features/Subscription/SubscriptionHandler.js
  88. 9 9
      services/web/app/src/Features/Subscription/SubscriptionLocator.js
  89. 76 76
      services/web/app/src/Features/Subscription/SubscriptionUpdater.js
  90. 42 42
      services/web/app/src/Features/Subscription/TeamInvitesController.js
  91. 115 111
      services/web/app/src/Features/Subscription/TeamInvitesHandler.js
  92. 23 22
      services/web/app/src/Features/Subscription/V1SubscriptionManager.js
  93. 4 4
      services/web/app/src/Features/SystemMessages/SystemMessageManager.js
  94. 15 10
      services/web/app/src/Features/Tags/TagsController.js
  95. 8 8
      services/web/app/src/Features/Tags/TagsHandler.js
  96. 1 1
      services/web/app/src/Features/Templates/TemplatesController.js
  97. 7 7
      services/web/app/src/Features/Templates/TemplatesManager.js
  98. 1 1
      services/web/app/src/Features/ThirdPartyDataStore/TpdsController.js
  99. 156 149
      services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js
  100. 4 4
      services/web/app/src/Features/TokenAccess/TokenAccessHandler.js

+ 1 - 0
services/web/.prettierignore

@@ -5,3 +5,4 @@ frontend/js/vendor
 modules/**/frontend/js/vendor
 public/js
 public/minjs
+frontend/stylesheets/components/nvd3.less

+ 2 - 2
services/web/app.js

@@ -45,7 +45,7 @@ if (!module.parent) {
   }
   Promise.all([mongodb.waitForDb(), mongoose.connectionPromise])
     .then(() => {
-      Server.server.listen(port, host, function() {
+      Server.server.listen(port, host, function () {
         logger.info(`web starting up, listening on ${host}:${port}`)
         logger.info(`${require('http').globalAgent.maxSockets} sockets enabled`)
         // wait until the process is ready before monitoring the event loop
@@ -59,7 +59,7 @@ if (!module.parent) {
 }
 
 // handle SIGTERM for graceful shutdown in kubernetes
-process.on('SIGTERM', function(signal) {
+process.on('SIGTERM', function (signal) {
   logger.warn({ signal: signal }, 'received signal, shutting down')
   Settings.shuttingDown = true
 })

+ 3 - 3
services/web/app/src/Features/Analytics/AnalyticsController.js

@@ -15,7 +15,7 @@ module.exports = {
     let countryCode = null
 
     if (userId) {
-      GeoIpLookup.getDetails(req.ip, function(err, geoDetails) {
+      GeoIpLookup.getDetails(req.ip, function (err, geoDetails) {
         if (err) {
           metrics.inc('analytics_geo_ip_lookup_errors')
         } else if (geoDetails && geoDetails.country_code) {
@@ -43,7 +43,7 @@ module.exports = {
       req.query.start_date,
       req.query.end_date,
       req.query.lag,
-      function(error, licences) {
+      function (error, licences) {
         if (error) {
           return next(error)
         }
@@ -58,7 +58,7 @@ module.exports = {
       req.query.start_date,
       req.query.end_date,
       req.query.lag,
-      function(error, licences) {
+      function (error, licences) {
         if (error) {
           return next(error)
         }

+ 107 - 95
services/web/app/src/Features/Authentication/AuthenticationController.js

@@ -58,7 +58,7 @@ const AuthenticationController = {
     // This function is middleware which wraps the passport.authenticate middleware,
     // so we can send back our custom `{message: {text: "", type: ""}}` responses on failure,
     // and send a `{redir: ""}` response on success
-    passport.authenticate('local', function(err, user, info) {
+    passport.authenticate('local', function (err, user, info) {
       if (err) {
         return next(err)
       }
@@ -81,86 +81,96 @@ const AuthenticationController = {
     } // OAuth2 'state' mismatch
 
     const Modules = require('../../infrastructure/Modules')
-    Modules.hooks.fire('preFinishLogin', req, res, user, function(
-      error,
-      results
-    ) {
-      if (error) {
-        return next(error)
-      }
-      if (results.some(result => result && result.doNotFinish)) {
-        return
-      }
-
-      if (user.must_reconfirm) {
-        return AuthenticationController._redirectToReconfirmPage(req, res, user)
-      }
+    Modules.hooks.fire(
+      'preFinishLogin',
+      req,
+      res,
+      user,
+      function (error, results) {
+        if (error) {
+          return next(error)
+        }
+        if (results.some(result => result && result.doNotFinish)) {
+          return
+        }
 
-      const redir =
-        AuthenticationController._getRedirectFromSession(req) || '/project'
-      _loginAsyncHandlers(req, user)
-      const userId = user._id
-      UserAuditLogHandler.addEntry(userId, 'login', userId, req.ip, err => {
-        if (err) {
-          return next(err)
+        if (user.must_reconfirm) {
+          return AuthenticationController._redirectToReconfirmPage(
+            req,
+            res,
+            user
+          )
         }
-        _afterLoginSessionSetup(req, user, function(err) {
+
+        const redir =
+          AuthenticationController._getRedirectFromSession(req) || '/project'
+        _loginAsyncHandlers(req, user)
+        const userId = user._id
+        UserAuditLogHandler.addEntry(userId, 'login', userId, req.ip, err => {
           if (err) {
             return next(err)
           }
-          AuthenticationController._clearRedirectFromSession(req)
-          AsyncFormHelper.redirect(req, res, redir)
+          _afterLoginSessionSetup(req, user, function (err) {
+            if (err) {
+              return next(err)
+            }
+            AuthenticationController._clearRedirectFromSession(req)
+            AsyncFormHelper.redirect(req, res, redir)
+          })
         })
-      })
-    })
+      }
+    )
   },
 
   doPassportLogin(req, username, password, done) {
     const email = username.toLowerCase()
     const Modules = require('../../infrastructure/Modules')
-    Modules.hooks.fire('preDoPassportLogin', req, email, function(
-      err,
-      infoList
-    ) {
-      if (err) {
-        return done(err)
-      }
-      const info = infoList.find(i => i != null)
-      if (info != null) {
-        return done(null, false, info)
-      }
-      LoginRateLimiter.processLoginRequest(email, function(err, isAllowed) {
+    Modules.hooks.fire(
+      'preDoPassportLogin',
+      req,
+      email,
+      function (err, infoList) {
         if (err) {
           return done(err)
         }
-        if (!isAllowed) {
-          logger.log({ email }, 'too many login requests')
-          return done(null, null, {
-            text: req.i18n.translate('to_many_login_requests_2_mins'),
-            type: 'error'
-          })
+        const info = infoList.find(i => i != null)
+        if (info != null) {
+          return done(null, false, info)
         }
-        AuthenticationManager.authenticate({ email }, password, function(
-          error,
-          user
-        ) {
-          if (error != null) {
-            return done(error)
+        LoginRateLimiter.processLoginRequest(email, function (err, isAllowed) {
+          if (err) {
+            return done(err)
           }
-          if (user != null) {
-            // async actions
-            done(null, user)
-          } else {
-            AuthenticationController._recordFailedLogin()
-            logger.log({ email }, 'failed log in')
-            done(null, false, {
-              text: req.i18n.translate('email_or_password_wrong_try_again'),
+          if (!isAllowed) {
+            logger.log({ email }, 'too many login requests')
+            return done(null, null, {
+              text: req.i18n.translate('to_many_login_requests_2_mins'),
               type: 'error'
             })
           }
+          AuthenticationManager.authenticate(
+            { email },
+            password,
+            function (error, user) {
+              if (error != null) {
+                return done(error)
+              }
+              if (user != null) {
+                // async actions
+                done(null, user)
+              } else {
+                AuthenticationController._recordFailedLogin()
+                logger.log({ email }, 'failed log in')
+                done(null, false, {
+                  text: req.i18n.translate('email_or_password_wrong_try_again'),
+                  type: 'error'
+                })
+              }
+            }
+          )
         })
-      })
-    })
+      }
+    )
   },
 
   ipMatchCheck(req, user) {
@@ -215,9 +225,9 @@ const AuthenticationController = {
   },
 
   requireLogin() {
-    const doRequest = function(req, res, next) {
+    const doRequest = function (req, res, next) {
       if (next == null) {
-        next = function() {}
+        next = function () {}
       }
       if (!AuthenticationController.isUserLoggedIn(req)) {
         if (acceptsJson(req)) return send401WithChallenge(res)
@@ -234,45 +244,47 @@ const AuthenticationController = {
   requireOauth() {
     // require this here because module may not be included in some versions
     const Oauth2Server = require('../../../../modules/oauth2-server/app/src/Oauth2Server')
-    return function(req, res, next) {
+    return function (req, res, next) {
       if (next == null) {
-        next = function() {}
+        next = function () {}
       }
       const request = new Oauth2Server.Request(req)
       const response = new Oauth2Server.Response(res)
-      return Oauth2Server.server.authenticate(request, response, {}, function(
-        err,
-        token
-      ) {
-        if (err) {
-          // use a 401 status code for malformed header for git-bridge
-          if (
-            err.code === 400 &&
-            err.message === 'Invalid request: malformed authorization header'
-          ) {
-            err.code = 401
+      return Oauth2Server.server.authenticate(
+        request,
+        response,
+        {},
+        function (err, token) {
+          if (err) {
+            // use a 401 status code for malformed header for git-bridge
+            if (
+              err.code === 400 &&
+              err.message === 'Invalid request: malformed authorization header'
+            ) {
+              err.code = 401
+            }
+            // send all other errors
+            return res
+              .status(err.code)
+              .json({ error: err.name, error_description: err.message })
           }
-          // send all other errors
-          return res
-            .status(err.code)
-            .json({ error: err.name, error_description: err.message })
+          req.oauth = { access_token: token.accessToken }
+          req.oauth_token = token
+          req.oauth_user = token.user
+          return next()
         }
-        req.oauth = { access_token: token.accessToken }
-        req.oauth_token = token
-        req.oauth_user = token.user
-        return next()
-      })
+      )
     }
   },
 
-  validateUserSession: function() {
+  validateUserSession: function () {
     // Middleware to check that the user's session is still good on key actions,
     // such as opening a a project. Could be used to check that session has not
     // exceeded a maximum lifetime (req.session.session_created), or for session
     // hijacking checks (e.g. change of ip address, req.session.ip_address). For
     // now, just check that the session has been loaded from the session store
     // correctly.
-    return function(req, res, next) {
+    return function (req, res, next) {
       // check that the session store is returning valid results
       if (req.session && !SessionStoreManager.hasValidationToken(req)) {
         // force user to update session
@@ -349,7 +361,7 @@ const AuthenticationController = {
     return next()
   },
 
-  httpAuth: basicAuth(function(user, pass) {
+  httpAuth: basicAuth(function (user, pass) {
     let expectedPassword = Settings.httpAuthUsers[user]
     const isValid =
       expectedPassword &&
@@ -424,7 +436,7 @@ const AuthenticationController = {
 
   _recordSuccessfulLogin(userId, callback) {
     if (callback == null) {
-      callback = function() {}
+      callback = function () {}
     }
     UserUpdater.updateUser(
       userId.toString(),
@@ -432,7 +444,7 @@ const AuthenticationController = {
         $set: { lastLoggedIn: new Date() },
         $inc: { loginCount: 1 }
       },
-      function(error) {
+      function (error) {
         if (error != null) {
           callback(error)
         }
@@ -465,9 +477,9 @@ const AuthenticationController = {
 
 function _afterLoginSessionSetup(req, user, callback) {
   if (callback == null) {
-    callback = function() {}
+    callback = function () {}
   }
-  req.login(user, function(err) {
+  req.login(user, function (err) {
     if (err) {
       OError.tag(err, 'error from req.login', {
         user_id: user._id
@@ -477,7 +489,7 @@ function _afterLoginSessionSetup(req, user, callback) {
     // Regenerate the session to get a new sessionID (cookie value) to
     // protect against session fixation attacks
     const oldSession = req.session
-    req.session.destroy(function(err) {
+    req.session.destroy(function (err) {
       if (err) {
         OError.tag(err, 'error when trying to destroy old session', {
           user_id: user._id
@@ -493,14 +505,14 @@ function _afterLoginSessionSetup(req, user, callback) {
           req.session[key] = value
         }
       }
-      req.session.save(function(err) {
+      req.session.save(function (err) {
         if (err) {
           OError.tag(err, 'error saving regenerated session after login', {
             user_id: user._id
           })
           return callback(err)
         }
-        UserSessionsManager.trackSession(user, req.sessionID, function() {})
+        UserSessionsManager.trackSession(user, req.sessionID, function () {})
         callback(null)
       })
     })

+ 6 - 6
services/web/app/src/Features/Authentication/AuthenticationManager.js

@@ -12,7 +12,7 @@ const util = require('util')
 const BCRYPT_ROUNDS = Settings.security.bcryptRounds || 12
 const BCRYPT_MINOR_VERSION = Settings.security.bcryptMinorVersion || 'a'
 
-const _checkWriteResult = function(result, callback) {
+const _checkWriteResult = function (result, callback) {
   // for MongoDB
   if (result && result.modifiedCount === 1) {
     callback(null, true)
@@ -33,7 +33,7 @@ const AuthenticationManager = {
       if (!user || !user.hashedPassword) {
         return callback(null, null)
       }
-      bcrypt.compare(password, user.hashedPassword, function(error, match) {
+      bcrypt.compare(password, user.hashedPassword, function (error, match) {
         if (error) {
           return callback(error)
         }
@@ -44,7 +44,7 @@ const AuthenticationManager = {
           user,
           user.hashedPassword,
           password,
-          function(err) {
+          function (err) {
             if (err) {
               return callback(err)
             }
@@ -146,7 +146,7 @@ const AuthenticationManager = {
   },
 
   hashPassword(password, callback) {
-    bcrypt.genSalt(BCRYPT_ROUNDS, BCRYPT_MINOR_VERSION, function(error, salt) {
+    bcrypt.genSalt(BCRYPT_ROUNDS, BCRYPT_MINOR_VERSION, function (error, salt) {
       if (error) {
         return callback(error)
       }
@@ -162,7 +162,7 @@ const AuthenticationManager = {
     if (validationError) {
       return callback(validationError)
     }
-    this.hashPassword(password, function(error, hash) {
+    this.hashPassword(password, function (error, hash) {
       if (error) {
         return callback(error)
       }
@@ -178,7 +178,7 @@ const AuthenticationManager = {
             password: true
           }
         },
-        function(updateError, result) {
+        function (updateError, result) {
           if (updateError) {
             return callback(updateError)
           }

+ 80 - 73
services/web/app/src/Features/Authorization/AuthorizationManager.js

@@ -54,20 +54,21 @@ const AuthorizationManager = {
       return callback(new Error('invalid project id'))
     }
     // Note, the Project property in the DB is `publicAccesLevel`, without the second `s`
-    ProjectGetter.getProject(projectId, { publicAccesLevel: 1 }, function(
-      error,
-      project
-    ) {
-      if (error) {
-        return callback(error)
-      }
-      if (!project) {
-        return callback(
-          new Errors.NotFoundError(`no project found with id ${projectId}`)
-        )
+    ProjectGetter.getProject(
+      projectId,
+      { publicAccesLevel: 1 },
+      function (error, project) {
+        if (error) {
+          return callback(error)
+        }
+        if (!project) {
+          return callback(
+            new Errors.NotFoundError(`no project found with id ${projectId}`)
+          )
+        }
+        callback(null, project.publicAccesLevel)
       }
-      callback(null, project.publicAccesLevel)
-    })
+    )
   },
 
   // Get the privilege level that the user has for the project
@@ -95,72 +96,78 @@ const AuthorizationManager = {
 
   // User is present, get their privilege level from database
   getPrivilegeLevelForProjectWithUser(userId, projectId, token, callback) {
-    CollaboratorsGetter.getMemberIdPrivilegeLevel(userId, projectId, function(
-      error,
-      privilegeLevel
-    ) {
-      if (error) {
-        return callback(error)
-      }
-      if (privilegeLevel && privilegeLevel !== PrivilegeLevels.NONE) {
-        // The user has direct access
-        return callback(null, privilegeLevel, false, false)
-      }
-      AuthorizationManager.isUserSiteAdmin(userId, function(error, isAdmin) {
+    CollaboratorsGetter.getMemberIdPrivilegeLevel(
+      userId,
+      projectId,
+      function (error, privilegeLevel) {
         if (error) {
           return callback(error)
         }
-        if (isAdmin) {
-          return callback(null, PrivilegeLevels.OWNER, false, true)
+        if (privilegeLevel && privilegeLevel !== PrivilegeLevels.NONE) {
+          // The user has direct access
+          return callback(null, privilegeLevel, false, false)
         }
-        // Legacy public-access system
-        // User is present (not anonymous), but does not have direct access
-        AuthorizationManager.getPublicAccessLevel(projectId, function(
-          err,
-          publicAccessLevel
-        ) {
-          if (err) {
-            return callback(err)
+        AuthorizationManager.isUserSiteAdmin(userId, function (error, isAdmin) {
+          if (error) {
+            return callback(error)
           }
-          if (publicAccessLevel === PublicAccessLevels.READ_ONLY) {
-            return callback(null, PrivilegeLevels.READ_ONLY, true, false)
+          if (isAdmin) {
+            return callback(null, PrivilegeLevels.OWNER, false, true)
           }
-          if (publicAccessLevel === PublicAccessLevels.READ_AND_WRITE) {
-            return callback(null, PrivilegeLevels.READ_AND_WRITE, true, false)
-          }
-          callback(null, PrivilegeLevels.NONE, false, false)
+          // Legacy public-access system
+          // User is present (not anonymous), but does not have direct access
+          AuthorizationManager.getPublicAccessLevel(
+            projectId,
+            function (err, publicAccessLevel) {
+              if (err) {
+                return callback(err)
+              }
+              if (publicAccessLevel === PublicAccessLevels.READ_ONLY) {
+                return callback(null, PrivilegeLevels.READ_ONLY, true, false)
+              }
+              if (publicAccessLevel === PublicAccessLevels.READ_AND_WRITE) {
+                return callback(
+                  null,
+                  PrivilegeLevels.READ_AND_WRITE,
+                  true,
+                  false
+                )
+              }
+              callback(null, PrivilegeLevels.NONE, false, false)
+            }
+          )
         })
-      })
-    })
+      }
+    )
   },
 
   // User is Anonymous, Try Token-based access
   getPrivilegeLevelForProjectWithoutUser(projectId, token, callback) {
-    AuthorizationManager.getPublicAccessLevel(projectId, function(
-      err,
-      publicAccessLevel
-    ) {
-      if (err) {
-        return callback(err)
-      }
-      if (publicAccessLevel === PublicAccessLevels.READ_ONLY) {
-        // Legacy public read-only access for anonymous user
-        return callback(null, PrivilegeLevels.READ_ONLY, true, false)
-      }
-      if (publicAccessLevel === PublicAccessLevels.READ_AND_WRITE) {
-        // Legacy public read-write access for anonymous user
-        return callback(null, PrivilegeLevels.READ_AND_WRITE, true, false)
-      }
-      if (publicAccessLevel === PublicAccessLevels.TOKEN_BASED) {
-        return AuthorizationManager.getPrivilegeLevelForProjectWithToken(
-          projectId,
-          token,
-          callback
-        )
+    AuthorizationManager.getPublicAccessLevel(
+      projectId,
+      function (err, publicAccessLevel) {
+        if (err) {
+          return callback(err)
+        }
+        if (publicAccessLevel === PublicAccessLevels.READ_ONLY) {
+          // Legacy public read-only access for anonymous user
+          return callback(null, PrivilegeLevels.READ_ONLY, true, false)
+        }
+        if (publicAccessLevel === PublicAccessLevels.READ_AND_WRITE) {
+          // Legacy public read-write access for anonymous user
+          return callback(null, PrivilegeLevels.READ_AND_WRITE, true, false)
+        }
+        if (publicAccessLevel === PublicAccessLevels.TOKEN_BASED) {
+          return AuthorizationManager.getPrivilegeLevelForProjectWithToken(
+            projectId,
+            token,
+            callback
+          )
+        }
+        // Deny anonymous user access
+        callback(null, PrivilegeLevels.NONE, false, false)
       }
-      // Deny anonymous user access
-      callback(null, PrivilegeLevels.NONE, false, false)
-    })
+    )
   },
 
   getPrivilegeLevelForProjectWithToken(projectId, token, callback) {
@@ -170,7 +177,7 @@ const AuthorizationManager = {
     TokenAccessHandler.validateTokenForAnonymousAccess(
       projectId,
       token,
-      function(err, isValidReadAndWrite, isValidReadOnly) {
+      function (err, isValidReadAndWrite, isValidReadOnly) {
         if (err) {
           return callback(err)
         }
@@ -193,7 +200,7 @@ const AuthorizationManager = {
       userId,
       projectId,
       token,
-      function(error, privilegeLevel) {
+      function (error, privilegeLevel) {
         if (error) {
           return callback(error)
         }
@@ -214,7 +221,7 @@ const AuthorizationManager = {
       userId,
       projectId,
       token,
-      function(error, privilegeLevel) {
+      function (error, privilegeLevel) {
         if (error) {
           return callback(error)
         }
@@ -233,7 +240,7 @@ const AuthorizationManager = {
       userId,
       projectId,
       token,
-      function(error, privilegeLevel, becausePublic) {
+      function (error, privilegeLevel, becausePublic) {
         if (error) {
           return callback(error)
         }
@@ -256,7 +263,7 @@ const AuthorizationManager = {
       userId,
       projectId,
       token,
-      function(error, privilegeLevel, becausePublic, becauseSiteAdmin) {
+      function (error, privilegeLevel, becausePublic, becauseSiteAdmin) {
         if (error) {
           return callback(error)
         }
@@ -273,7 +280,7 @@ const AuthorizationManager = {
     if (!userId) {
       return callback(null, false)
     }
-    User.findOne({ _id: userId }, { isAdmin: 1 }, function(error, user) {
+    User.findOne({ _id: userId }, { isAdmin: 1 }, function (error, user) {
       if (error) {
         return callback(error)
       }

+ 137 - 142
services/web/app/src/Features/Authorization/AuthorizationMiddleware.js

@@ -11,7 +11,7 @@ const TokenAccessHandler = require('../TokenAccess/TokenAccessHandler')
 module.exports = AuthorizationMiddleware = {
   ensureUserCanReadMultipleProjects(req, res, next) {
     const projectIds = (req.query.project_ids || '').split(',')
-    AuthorizationMiddleware._getUserId(req, function(error, userId) {
+    AuthorizationMiddleware._getUserId(req, function (error, userId) {
       if (error) {
         return next(error)
       }
@@ -19,13 +19,13 @@ module.exports = AuthorizationMiddleware = {
       // errors in callbacks
       async.rejectSeries(
         projectIds,
-        function(projectId, cb) {
+        function (projectId, cb) {
           const token = TokenAccessHandler.getRequestToken(req, projectId)
           AuthorizationManager.canUserReadProject(
             userId,
             projectId,
             token,
-            function(error, canRead) {
+            function (error, canRead) {
               if (error) {
                 return next(error)
               }
@@ -33,7 +33,7 @@ module.exports = AuthorizationMiddleware = {
             }
           )
         },
-        function(unauthorizedProjectIds) {
+        function (unauthorizedProjectIds) {
           if (unauthorizedProjectIds.length > 0) {
             return AuthorizationMiddleware.redirectToRestricted(req, res, next)
           }
@@ -44,178 +44,173 @@ module.exports = AuthorizationMiddleware = {
   },
 
   blockRestrictedUserFromProject(req, res, next) {
-    AuthorizationMiddleware._getUserAndProjectId(req, function(
-      error,
-      userId,
-      projectId
-    ) {
-      if (error) {
-        return next(error)
-      }
-      const token = TokenAccessHandler.getRequestToken(req, projectId)
-      AuthorizationManager.isRestrictedUserForProject(
-        userId,
-        projectId,
-        token,
-        (err, isRestrictedUser) => {
-          if (err) {
-            return next(err)
-          }
-          if (isRestrictedUser) {
-            return res.sendStatus(403)
-          }
-          next()
+    AuthorizationMiddleware._getUserAndProjectId(
+      req,
+      function (error, userId, projectId) {
+        if (error) {
+          return next(error)
         }
-      )
-    })
+        const token = TokenAccessHandler.getRequestToken(req, projectId)
+        AuthorizationManager.isRestrictedUserForProject(
+          userId,
+          projectId,
+          token,
+          (err, isRestrictedUser) => {
+            if (err) {
+              return next(err)
+            }
+            if (isRestrictedUser) {
+              return res.sendStatus(403)
+            }
+            next()
+          }
+        )
+      }
+    )
   },
 
   ensureUserCanReadProject(req, res, next) {
-    AuthorizationMiddleware._getUserAndProjectId(req, function(
-      error,
-      userId,
-      projectId
-    ) {
-      if (error) {
-        return next(error)
-      }
-      const token = TokenAccessHandler.getRequestToken(req, projectId)
-      AuthorizationManager.canUserReadProject(
-        userId,
-        projectId,
-        token,
-        function(error, canRead) {
-          if (error) {
-            return next(error)
-          }
-          if (canRead) {
+    AuthorizationMiddleware._getUserAndProjectId(
+      req,
+      function (error, userId, projectId) {
+        if (error) {
+          return next(error)
+        }
+        const token = TokenAccessHandler.getRequestToken(req, projectId)
+        AuthorizationManager.canUserReadProject(
+          userId,
+          projectId,
+          token,
+          function (error, canRead) {
+            if (error) {
+              return next(error)
+            }
+            if (canRead) {
+              logger.log(
+                { userId, projectId },
+                'allowing user read access to project'
+              )
+              return next()
+            }
             logger.log(
               { userId, projectId },
-              'allowing user read access to project'
+              'denying user read access to project'
             )
-            return next()
+            HttpErrorHandler.forbidden(req, res)
           }
-          logger.log(
-            { userId, projectId },
-            'denying user read access to project'
-          )
-          HttpErrorHandler.forbidden(req, res)
-        }
-      )
-    })
+        )
+      }
+    )
   },
 
   ensureUserCanWriteProjectSettings(req, res, next) {
-    AuthorizationMiddleware._getUserAndProjectId(req, function(
-      error,
-      userId,
-      projectId
-    ) {
-      if (error) {
-        return next(error)
-      }
-      const token = TokenAccessHandler.getRequestToken(req, projectId)
-      AuthorizationManager.canUserWriteProjectSettings(
-        userId,
-        projectId,
-        token,
-        function(error, canWrite) {
-          if (error) {
-            return next(error)
-          }
-          if (canWrite) {
+    AuthorizationMiddleware._getUserAndProjectId(
+      req,
+      function (error, userId, projectId) {
+        if (error) {
+          return next(error)
+        }
+        const token = TokenAccessHandler.getRequestToken(req, projectId)
+        AuthorizationManager.canUserWriteProjectSettings(
+          userId,
+          projectId,
+          token,
+          function (error, canWrite) {
+            if (error) {
+              return next(error)
+            }
+            if (canWrite) {
+              logger.log(
+                { userId, projectId },
+                'allowing user write access to project settings'
+              )
+              return next()
+            }
             logger.log(
               { userId, projectId },
-              'allowing user write access to project settings'
+              'denying user write access to project settings'
             )
-            return next()
+            HttpErrorHandler.forbidden(req, res)
           }
-          logger.log(
-            { userId, projectId },
-            'denying user write access to project settings'
-          )
-          HttpErrorHandler.forbidden(req, res)
-        }
-      )
-    })
+        )
+      }
+    )
   },
 
   ensureUserCanWriteProjectContent(req, res, next) {
-    AuthorizationMiddleware._getUserAndProjectId(req, function(
-      error,
-      userId,
-      projectId
-    ) {
-      if (error) {
-        return next(error)
-      }
-      const token = TokenAccessHandler.getRequestToken(req, projectId)
-      AuthorizationManager.canUserWriteProjectContent(
-        userId,
-        projectId,
-        token,
-        function(error, canWrite) {
-          if (error) {
-            return next(error)
-          }
-          if (canWrite) {
+    AuthorizationMiddleware._getUserAndProjectId(
+      req,
+      function (error, userId, projectId) {
+        if (error) {
+          return next(error)
+        }
+        const token = TokenAccessHandler.getRequestToken(req, projectId)
+        AuthorizationManager.canUserWriteProjectContent(
+          userId,
+          projectId,
+          token,
+          function (error, canWrite) {
+            if (error) {
+              return next(error)
+            }
+            if (canWrite) {
+              logger.log(
+                { userId, projectId },
+                'allowing user write access to project content'
+              )
+              return next()
+            }
             logger.log(
               { userId, projectId },
-              'allowing user write access to project content'
+              'denying user write access to project settings'
             )
-            return next()
+            HttpErrorHandler.forbidden(req, res)
           }
-          logger.log(
-            { userId, projectId },
-            'denying user write access to project settings'
-          )
-          HttpErrorHandler.forbidden(req, res)
-        }
-      )
-    })
+        )
+      }
+    )
   },
 
   ensureUserCanAdminProject(req, res, next) {
-    AuthorizationMiddleware._getUserAndProjectId(req, function(
-      error,
-      userId,
-      projectId
-    ) {
-      if (error) {
-        return next(error)
-      }
-      const token = TokenAccessHandler.getRequestToken(req, projectId)
-      AuthorizationManager.canUserAdminProject(
-        userId,
-        projectId,
-        token,
-        function(error, canAdmin) {
-          if (error) {
-            return next(error)
-          }
-          if (canAdmin) {
+    AuthorizationMiddleware._getUserAndProjectId(
+      req,
+      function (error, userId, projectId) {
+        if (error) {
+          return next(error)
+        }
+        const token = TokenAccessHandler.getRequestToken(req, projectId)
+        AuthorizationManager.canUserAdminProject(
+          userId,
+          projectId,
+          token,
+          function (error, canAdmin) {
+            if (error) {
+              return next(error)
+            }
+            if (canAdmin) {
+              logger.log(
+                { userId, projectId },
+                'allowing user admin access to project'
+              )
+              return next()
+            }
             logger.log(
               { userId, projectId },
-              'allowing user admin access to project'
+              'denying user admin access to project'
             )
-            return next()
+            HttpErrorHandler.forbidden(req, res)
           }
-          logger.log(
-            { userId, projectId },
-            'denying user admin access to project'
-          )
-          HttpErrorHandler.forbidden(req, res)
-        }
-      )
-    })
+        )
+      }
+    )
   },
 
   ensureUserIsSiteAdmin(req, res, next) {
-    AuthorizationMiddleware._getUserId(req, function(error, userId) {
+    AuthorizationMiddleware._getUserId(req, function (error, userId) {
       if (error) {
         return next(error)
       }
-      AuthorizationManager.isUserSiteAdmin(userId, function(error, isAdmin) {
+      AuthorizationManager.isUserSiteAdmin(userId, function (error, isAdmin) {
         if (error) {
           return next(error)
         }
@@ -239,7 +234,7 @@ module.exports = AuthorizationMiddleware = {
         new Errors.NotFoundError(`invalid projectId: ${projectId}`)
       )
     }
-    AuthorizationMiddleware._getUserId(req, function(error, userId) {
+    AuthorizationMiddleware._getUserId(req, function (error, userId) {
       if (error) {
         return callback(error)
       }

+ 3 - 3
services/web/app/src/Features/BetaProgram/BetaProgramController.js

@@ -12,7 +12,7 @@ const BetaProgramController = {
     if (userId == null) {
       return next(new Error('no user id in session'))
     }
-    BetaProgramHandler.optIn(userId, function(err) {
+    BetaProgramHandler.optIn(userId, function (err) {
       if (err) {
         return next(err)
       }
@@ -26,7 +26,7 @@ const BetaProgramController = {
     if (userId == null) {
       return next(new Error('no user id in session'))
     }
-    BetaProgramHandler.optOut(userId, function(err) {
+    BetaProgramHandler.optOut(userId, function (err) {
       if (err) {
         return next(err)
       }
@@ -37,7 +37,7 @@ const BetaProgramController = {
   optInPage(req, res, next) {
     const userId = AuthenticationController.getLoggedInUserId(req)
     logger.log({ user_id: userId }, 'showing beta participation page for user')
-    UserGetter.getUser(userId, function(err, user) {
+    UserGetter.getUser(userId, function (err, user) {
       if (err) {
         OError.tag(err, 'error fetching user', {
           userId

+ 3 - 3
services/web/app/src/Features/BrandVariations/BrandVariationsHandler.js

@@ -22,7 +22,7 @@ const V1Api = require('../V1/V1Api')
 module.exports = BrandVariationsHandler = {
   getBrandVariationById(brandVariationId, callback) {
     if (callback == null) {
-      callback = function(error, brandVariationDetails) {}
+      callback = function (error, brandVariationDetails) {}
     }
     if (brandVariationId == null || brandVariationId === '') {
       return callback(new Error('Branding variation id not provided'))
@@ -32,7 +32,7 @@ module.exports = BrandVariationsHandler = {
       {
         uri: `/api/v2/brand_variations/${brandVariationId}`
       },
-      function(error, response, brandVariationDetails) {
+      function (error, response, brandVariationDetails) {
         if (error != null) {
           OError.tag(error, 'error getting brand variation details', {
             brandVariationId
@@ -46,7 +46,7 @@ module.exports = BrandVariationsHandler = {
   }
 }
 
-var _formatBrandVariationDetails = function(details) {
+var _formatBrandVariationDetails = function (details) {
   if (details.export_url != null) {
     details.export_url = _setV1AsHostIfRelativeURL(details.export_url)
   }

+ 2 - 2
services/web/app/src/Features/Captcha/CaptchaMiddleware.js

@@ -17,7 +17,7 @@ const Settings = require('settings-sharelatex')
 
 module.exports = CaptchaMiddleware = {
   validateCaptcha(action) {
-    return function(req, res, next) {
+    return function (req, res, next) {
       if (
         (Settings.recaptcha != null ? Settings.recaptcha.siteKey : undefined) ==
         null
@@ -42,7 +42,7 @@ module.exports = CaptchaMiddleware = {
       return request.post(
         'https://www.google.com/recaptcha/api/siteverify',
         options,
-        function(error, response, body) {
+        function (error, response, body) {
           if (error != null) {
             return next(error)
           }

+ 9 - 9
services/web/app/src/Features/Chat/ChatApiHandler.js

@@ -19,9 +19,9 @@ const settings = require('settings-sharelatex')
 module.exports = ChatApiHandler = {
   _apiRequest(opts, callback) {
     if (callback == null) {
-      callback = function(error, data) {}
+      callback = function (error, data) {}
     }
-    return request(opts, function(error, response, data) {
+    return request(opts, function (error, response, data) {
       if (error != null) {
         return callback(error)
       }
@@ -71,7 +71,7 @@ module.exports = ChatApiHandler = {
 
   sendComment(project_id, thread_id, user_id, content, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return ChatApiHandler._apiRequest(
       {
@@ -85,7 +85,7 @@ module.exports = ChatApiHandler = {
 
   getThreads(project_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return ChatApiHandler._apiRequest(
       {
@@ -99,7 +99,7 @@ module.exports = ChatApiHandler = {
 
   resolveThread(project_id, thread_id, user_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return ChatApiHandler._apiRequest(
       {
@@ -113,7 +113,7 @@ module.exports = ChatApiHandler = {
 
   reopenThread(project_id, thread_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return ChatApiHandler._apiRequest(
       {
@@ -126,7 +126,7 @@ module.exports = ChatApiHandler = {
 
   deleteThread(project_id, thread_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return ChatApiHandler._apiRequest(
       {
@@ -139,7 +139,7 @@ module.exports = ChatApiHandler = {
 
   editMessage(project_id, thread_id, message_id, content, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return ChatApiHandler._apiRequest(
       {
@@ -155,7 +155,7 @@ module.exports = ChatApiHandler = {
 
   deleteMessage(project_id, thread_id, message_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return ChatApiHandler._apiRequest(
       {

+ 20 - 20
services/web/app/src/Features/Chat/ChatController.js

@@ -36,25 +36,25 @@ module.exports = ChatController = {
       project_id,
       user_id,
       content,
-      function(err, message) {
+      function (err, message) {
         if (err != null) {
           return next(err)
         }
-        return UserInfoManager.getPersonalInfo(message.user_id, function(
-          err,
-          user
-        ) {
-          if (err != null) {
-            return next(err)
+        return UserInfoManager.getPersonalInfo(
+          message.user_id,
+          function (err, user) {
+            if (err != null) {
+              return next(err)
+            }
+            message.user = UserInfoController.formatPersonalInfo(user)
+            EditorRealTimeController.emitToRoom(
+              project_id,
+              'new-chat-message',
+              message
+            )
+            return res.sendStatus(204)
           }
-          message.user = UserInfoController.formatPersonalInfo(user)
-          EditorRealTimeController.emitToRoom(
-            project_id,
-            'new-chat-message',
-            message
-          )
-          return res.sendStatus(204)
-        })
+        )
       }
     )
   },
@@ -66,13 +66,13 @@ module.exports = ChatController = {
       project_id,
       query.limit,
       query.before,
-      function(err, messages) {
+      function (err, messages) {
         if (err != null) {
           return next(err)
         }
         return ChatController._injectUserInfoIntoThreads(
           { global: { messages } },
-          function(err) {
+          function (err) {
             if (err != null) {
               return next(err)
             }
@@ -89,7 +89,7 @@ module.exports = ChatController = {
     // user fields
     let message, thread, thread_id, user_id
     if (callback == null) {
-      callback = function(error, threads) {}
+      callback = function (error, threads) {}
     }
     const user_ids = {}
     for (thread_id in threads) {
@@ -108,7 +108,7 @@ module.exports = ChatController = {
       const _ = user_ids[user_id]
       ;(user_id =>
         jobs.push(cb =>
-          UserInfoManager.getPersonalInfo(user_id, function(error, user) {
+          UserInfoManager.getPersonalInfo(user_id, function (error, user) {
             if (error != null) return cb(error)
             user = UserInfoController.formatPersonalInfo(user)
             users[user_id] = user
@@ -117,7 +117,7 @@ module.exports = ChatController = {
         ))(user_id)
     }
 
-    return async.series(jobs, function(error) {
+    return async.series(jobs, function (error) {
       if (error != null) {
         return callback(error)
       }

+ 1 - 1
services/web/app/src/Features/Collaborators/CollaboratorsEmailHandler.js

@@ -30,7 +30,7 @@ module.exports = CollaboratorsEmailHandler = {
     return Project.findOne({ _id: project_id })
       .select('name owner_ref')
       .populate('owner_ref')
-      .exec(function(err, project) {
+      .exec(function (err, project) {
         const emailOptions = {
           to: email,
           replyTo: project.owner_ref.email,

+ 57 - 55
services/web/app/src/Features/Collaborators/CollaboratorsInviteController.js

@@ -31,37 +31,38 @@ module.exports = CollaboratorsInviteController = {
   getAllInvites(req, res, next) {
     const projectId = req.params.Project_id
     logger.log({ projectId }, 'getting all active invites for project')
-    return CollaboratorsInviteHandler.getAllInvites(projectId, function(
-      err,
-      invites
-    ) {
-      if (err != null) {
-        OError.tag(err, 'error getting invites for project', {
-          projectId
-        })
-        return next(err)
+    return CollaboratorsInviteHandler.getAllInvites(
+      projectId,
+      function (err, invites) {
+        if (err != null) {
+          OError.tag(err, 'error getting invites for project', {
+            projectId
+          })
+          return next(err)
+        }
+        return res.json({ invites })
       }
-      return res.json({ invites })
-    })
+    )
   },
 
   _checkShouldInviteEmail(email, callback) {
     if (callback == null) {
-      callback = function(err, shouldAllowInvite) {}
+      callback = function (err, shouldAllowInvite) {}
     }
     if (Settings.restrictInvitesToExistingAccounts === true) {
       logger.log({ email }, 'checking if user exists with this email')
-      return UserGetter.getUserByAnyEmail(email, { _id: 1 }, function(
-        err,
-        user
-      ) {
-        if (err != null) {
-          return callback(err)
+      return UserGetter.getUserByAnyEmail(
+        email,
+        { _id: 1 },
+        function (err, user) {
+          if (err != null) {
+            return callback(err)
+          }
+          const userExists =
+            user != null && (user != null ? user._id : undefined) != null
+          return callback(null, userExists)
         }
-        const userExists =
-          user != null && (user != null ? user._id : undefined) != null
-        return callback(null, userExists)
-      })
+      )
     } else {
       return callback(null, true)
     }
@@ -69,11 +70,11 @@ module.exports = CollaboratorsInviteController = {
 
   _checkRateLimit(user_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return LimitationsManager.allowedNumberOfCollaboratorsForUser(
       user_id,
-      function(err, collabLimit) {
+      function (err, collabLimit) {
         if (collabLimit == null) {
           collabLimit = 1
         }
@@ -134,7 +135,7 @@ module.exports = CollaboratorsInviteController = {
         }
         return CollaboratorsInviteController._checkRateLimit(
           sendingUserId,
-          function(error, underRateLimit) {
+          function (error, underRateLimit) {
             if (error != null) {
               return next(error)
             }
@@ -143,7 +144,7 @@ module.exports = CollaboratorsInviteController = {
             }
             return CollaboratorsInviteController._checkShouldInviteEmail(
               email,
-              function(err, shouldAllowInvite) {
+              function (err, shouldAllowInvite) {
                 if (err != null) {
                   OError.tag(
                     err,
@@ -171,7 +172,7 @@ module.exports = CollaboratorsInviteController = {
                   sendingUser,
                   email,
                   privileges,
-                  function(err, invite) {
+                  function (err, invite) {
                     if (err != null) {
                       OError.tag(err, 'error creating project invite', {
                         projectId,
@@ -207,7 +208,7 @@ module.exports = CollaboratorsInviteController = {
     return CollaboratorsInviteHandler.revokeInvite(
       projectId,
       inviteId,
-      function(err) {
+      function (err) {
         if (err != null) {
           OError.tag(err, 'error revoking invite', {
             projectId,
@@ -232,7 +233,7 @@ module.exports = CollaboratorsInviteController = {
     const sendingUser = AuthenticationController.getSessionUser(req)
     return CollaboratorsInviteController._checkRateLimit(
       sendingUser._id,
-      function(error, underRateLimit) {
+      function (error, underRateLimit) {
         if (error != null) {
           return next(error)
         }
@@ -243,7 +244,7 @@ module.exports = CollaboratorsInviteController = {
           projectId,
           sendingUser,
           inviteId,
-          function(err) {
+          function (err) {
             if (err != null) {
               OError.tag(err, 'error resending invite', {
                 projectId,
@@ -261,7 +262,7 @@ module.exports = CollaboratorsInviteController = {
   viewInvite(req, res, next) {
     const projectId = req.params.Project_id
     const { token } = req.params
-    const _renderInvalidPage = function() {
+    const _renderInvalidPage = function () {
       logger.log(
         { projectId, token },
         'invite not valid, rendering not-valid page'
@@ -273,7 +274,7 @@ module.exports = CollaboratorsInviteController = {
     return CollaboratorsGetter.isUserInvitedMemberOfProject(
       currentUser._id,
       projectId,
-      function(err, isMember) {
+      function (err, isMember) {
         if (err != null) {
           OError.tag(err, 'error checking if user is member of project', {
             projectId
@@ -291,7 +292,7 @@ module.exports = CollaboratorsInviteController = {
         return CollaboratorsInviteHandler.getInviteByToken(
           projectId,
           token,
-          function(err, invite) {
+          function (err, invite) {
             if (err != null) {
               OError.tag(err, 'error getting invite by token', {
                 projectId,
@@ -308,7 +309,7 @@ module.exports = CollaboratorsInviteController = {
             return UserGetter.getUser(
               { _id: invite.sendingUserId },
               { email: 1, first_name: 1, last_name: 1 },
-              function(err, owner) {
+              function (err, owner) {
                 if (err != null) {
                   OError.tag(err, 'error getting project owner', {
                     projectId
@@ -320,28 +321,29 @@ module.exports = CollaboratorsInviteController = {
                   return _renderInvalidPage()
                 }
                 // fetch the project name
-                return ProjectGetter.getProject(projectId, {}, function(
-                  err,
-                  project
-                ) {
-                  if (err != null) {
-                    OError.tag(err, 'error getting project', {
-                      projectId
+                return ProjectGetter.getProject(
+                  projectId,
+                  {},
+                  function (err, project) {
+                    if (err != null) {
+                      OError.tag(err, 'error getting project', {
+                        projectId
+                      })
+                      return next(err)
+                    }
+                    if (project == null) {
+                      logger.log({ projectId }, 'no project found')
+                      return _renderInvalidPage()
+                    }
+                    // finally render the invite
+                    return res.render('project/invite/show', {
+                      invite,
+                      project,
+                      owner,
+                      title: 'Project Invite'
                     })
-                    return next(err)
-                  }
-                  if (project == null) {
-                    logger.log({ projectId }, 'no project found')
-                    return _renderInvalidPage()
                   }
-                  // finally render the invite
-                  return res.render('project/invite/show', {
-                    invite,
-                    project,
-                    owner,
-                    title: 'Project Invite'
-                  })
-                })
+                )
               }
             )
           }
@@ -362,7 +364,7 @@ module.exports = CollaboratorsInviteController = {
       projectId,
       token,
       currentUser,
-      function(err) {
+      function (err) {
         if (err != null) {
           OError.tag(err, 'error accepting invite by token', {
             projectId,

+ 118 - 113
services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js

@@ -26,10 +26,10 @@ const { promisifyAll } = require('../../util/promises')
 const CollaboratorsInviteHandler = {
   getAllInvites(projectId, callback) {
     if (callback == null) {
-      callback = function(err, invites) {}
+      callback = function (err, invites) {}
     }
     logger.log({ projectId }, 'fetching invites for project')
-    return ProjectInvite.find({ projectId }, function(err, invites) {
+    return ProjectInvite.find({ projectId }, function (err, invites) {
       if (err != null) {
         OError.tag(err, 'error getting invites from mongo', {
           projectId
@@ -46,10 +46,10 @@ const CollaboratorsInviteHandler = {
 
   getInviteCount(projectId, callback) {
     if (callback == null) {
-      callback = function(err, count) {}
+      callback = function (err, count) {}
     }
     logger.log({ projectId }, 'counting invites for project')
-    return ProjectInvite.countDocuments({ projectId }, function(err, count) {
+    return ProjectInvite.countDocuments({ projectId }, function (err, count) {
       if (err != null) {
         OError.tag(err, 'error getting invites from mongo', {
           projectId
@@ -62,55 +62,57 @@ const CollaboratorsInviteHandler = {
 
   _trySendInviteNotification(projectId, sendingUser, invite, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     const { email } = invite
-    return UserGetter.getUserByAnyEmail(email, { _id: 1 }, function(
-      err,
-      existingUser
-    ) {
-      if (err != null) {
-        OError.tag(err, 'error checking if user exists', {
-          projectId,
-          email
-        })
-        return callback(err)
-      }
-      if (existingUser == null) {
-        logger.log({ projectId, email }, 'no existing user found, returning')
-        return callback(null)
-      }
-      return ProjectGetter.getProject(projectId, { _id: 1, name: 1 }, function(
-        err,
-        project
-      ) {
+    return UserGetter.getUserByAnyEmail(
+      email,
+      { _id: 1 },
+      function (err, existingUser) {
         if (err != null) {
-          OError.tag(err, 'error getting project', {
+          OError.tag(err, 'error checking if user exists', {
             projectId,
             email
           })
           return callback(err)
         }
-        if (project == null) {
-          logger.log(
-            { projectId },
-            'no project found while sending notification, returning'
-          )
+        if (existingUser == null) {
+          logger.log({ projectId, email }, 'no existing user found, returning')
           return callback(null)
         }
-        return NotificationsBuilder.projectInvite(
-          invite,
-          project,
-          sendingUser,
-          existingUser
-        ).create(callback)
-      })
-    })
+        return ProjectGetter.getProject(
+          projectId,
+          { _id: 1, name: 1 },
+          function (err, project) {
+            if (err != null) {
+              OError.tag(err, 'error getting project', {
+                projectId,
+                email
+              })
+              return callback(err)
+            }
+            if (project == null) {
+              logger.log(
+                { projectId },
+                'no project found while sending notification, returning'
+              )
+              return callback(null)
+            }
+            return NotificationsBuilder.projectInvite(
+              invite,
+              project,
+              sendingUser,
+              existingUser
+            ).create(callback)
+          }
+        )
+      }
+    )
   },
 
   _tryCancelInviteNotification(inviteId, callback) {
     if (callback == null) {
-      callback = function() {}
+      callback = function () {}
     }
     return NotificationsBuilder.projectInvite(
       { _id: inviteId },
@@ -122,7 +124,7 @@ const CollaboratorsInviteHandler = {
 
   _sendMessages(projectId, sendingUser, invite, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     logger.log(
       { projectId, inviteId: invite._id },
@@ -133,7 +135,7 @@ const CollaboratorsInviteHandler = {
       invite.email,
       invite,
       sendingUser,
-      function(err) {
+      function (err) {
         if (err != null) {
           return callback(err)
         }
@@ -141,7 +143,7 @@ const CollaboratorsInviteHandler = {
           projectId,
           sendingUser,
           invite,
-          function(err) {
+          function (err) {
             if (err != null) {
               return callback(err)
             }
@@ -154,13 +156,13 @@ const CollaboratorsInviteHandler = {
 
   inviteToProject(projectId, sendingUser, email, privileges, callback) {
     if (callback == null) {
-      callback = function(err, invite) {}
+      callback = function (err, invite) {}
     }
     logger.log(
       { projectId, sendingUserId: sendingUser._id, email, privileges },
       'adding invite'
     )
-    return Crypto.randomBytes(24, function(err, buffer) {
+    return Crypto.randomBytes(24, function (err, buffer) {
       if (err != null) {
         OError.tag(err, 'error generating random token', {
           projectId,
@@ -177,7 +179,7 @@ const CollaboratorsInviteHandler = {
         projectId,
         privileges
       })
-      return invite.save(function(err, invite) {
+      return invite.save(function (err, invite) {
         if (err != null) {
           OError.tag(err, 'error saving token', {
             projectId,
@@ -191,7 +193,7 @@ const CollaboratorsInviteHandler = {
           projectId,
           sendingUser,
           invite,
-          function(err) {
+          function (err) {
             if (err != null) {
               return logger.err(
                 { err, projectId, email },
@@ -207,98 +209,101 @@ const CollaboratorsInviteHandler = {
 
   revokeInvite(projectId, inviteId, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     logger.log({ projectId, inviteId }, 'removing invite')
-    return ProjectInvite.deleteOne({ projectId, _id: inviteId }, function(err) {
-      if (err != null) {
-        OError.tag(err, 'error removing invite', {
-          projectId,
-          inviteId
-        })
-        return callback(err)
+    return ProjectInvite.deleteOne(
+      { projectId, _id: inviteId },
+      function (err) {
+        if (err != null) {
+          OError.tag(err, 'error removing invite', {
+            projectId,
+            inviteId
+          })
+          return callback(err)
+        }
+        CollaboratorsInviteHandler._tryCancelInviteNotification(
+          inviteId,
+          function () {}
+        )
+        return callback(null)
       }
-      CollaboratorsInviteHandler._tryCancelInviteNotification(
-        inviteId,
-        function() {}
-      )
-      return callback(null)
-    })
+    )
   },
 
   resendInvite(projectId, sendingUser, inviteId, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     logger.log({ projectId, inviteId }, 'resending invite email')
-    return ProjectInvite.findOne({ _id: inviteId, projectId }, function(
-      err,
-      invite
-    ) {
-      if (err != null) {
-        OError.tag(err, 'error finding invite', {
+    return ProjectInvite.findOne(
+      { _id: inviteId, projectId },
+      function (err, invite) {
+        if (err != null) {
+          OError.tag(err, 'error finding invite', {
+            projectId,
+            inviteId
+          })
+          return callback(err)
+        }
+        if (invite == null) {
+          logger.err(
+            { err, projectId, inviteId },
+            'no invite found, nothing to resend'
+          )
+          return callback(null)
+        }
+        return CollaboratorsInviteHandler._sendMessages(
           projectId,
-          inviteId
-        })
-        return callback(err)
-      }
-      if (invite == null) {
-        logger.err(
-          { err, projectId, inviteId },
-          'no invite found, nothing to resend'
+          sendingUser,
+          invite,
+          function (err) {
+            if (err != null) {
+              OError.tag(err, 'error resending invite messages', {
+                projectId,
+                inviteId
+              })
+              return callback(err)
+            }
+            return callback(null)
+          }
         )
-        return callback(null)
       }
-      return CollaboratorsInviteHandler._sendMessages(
-        projectId,
-        sendingUser,
-        invite,
-        function(err) {
-          if (err != null) {
-            OError.tag(err, 'error resending invite messages', {
-              projectId,
-              inviteId
-            })
-            return callback(err)
-          }
-          return callback(null)
-        }
-      )
-    })
+    )
   },
 
   getInviteByToken(projectId, tokenString, callback) {
     if (callback == null) {
-      callback = function(err, invite) {}
+      callback = function (err, invite) {}
     }
     logger.log({ projectId, tokenString }, 'fetching invite by token')
-    return ProjectInvite.findOne({ projectId, token: tokenString }, function(
-      err,
-      invite
-    ) {
-      if (err != null) {
-        OError.tag(err, 'error fetching invite', {
-          projectId
-        })
-        return callback(err)
-      }
-      if (invite == null) {
-        logger.err({ err, projectId, token: tokenString }, 'no invite found')
-        return callback(null, null)
+    return ProjectInvite.findOne(
+      { projectId, token: tokenString },
+      function (err, invite) {
+        if (err != null) {
+          OError.tag(err, 'error fetching invite', {
+            projectId
+          })
+          return callback(err)
+        }
+        if (invite == null) {
+          logger.err({ err, projectId, token: tokenString }, 'no invite found')
+          return callback(null, null)
+        }
+        return callback(null, invite)
       }
-      return callback(null, invite)
-    })
+    )
   },
 
   acceptInvite(projectId, tokenString, user, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     logger.log({ projectId, userId: user._id, tokenString }, 'accepting invite')
     return CollaboratorsInviteHandler.getInviteByToken(
       projectId,
       tokenString,
-      function(err, invite) {
+      function (err, invite) {
         if (err != null) {
           OError.tag(err, 'error finding invite', {
             projectId,
@@ -320,7 +325,7 @@ const CollaboratorsInviteHandler = {
           invite.sendingUserId,
           user._id,
           invite.privileges,
-          function(err) {
+          function (err) {
             if (err != null) {
               OError.tag(err, 'error adding user to project', {
                 projectId,
@@ -331,7 +336,7 @@ const CollaboratorsInviteHandler = {
             }
             // Remove invite
             logger.log({ projectId, inviteId }, 'removing invite')
-            return ProjectInvite.deleteOne({ _id: inviteId }, function(err) {
+            return ProjectInvite.deleteOne({ _id: inviteId }, function (err) {
               if (err != null) {
                 OError.tag(err, 'error removing invite', {
                   projectId,
@@ -341,7 +346,7 @@ const CollaboratorsInviteHandler = {
               }
               CollaboratorsInviteHandler._tryCancelInviteNotification(
                 inviteId,
-                function() {}
+                function () {}
               )
               return callback()
             })

+ 8 - 8
services/web/app/src/Features/Compile/ClsiCookieManager.js

@@ -27,7 +27,7 @@ const clsiCookiesEnabled =
   (Settings.clsiCookie != null ? Settings.clsiCookie.key : undefined) != null &&
   Settings.clsiCookie.key.length !== 0
 
-module.exports = function(backendGroup) {
+module.exports = function (backendGroup) {
   return {
     buildKey(project_id) {
       if (backendGroup != null) {
@@ -39,7 +39,7 @@ module.exports = function(backendGroup) {
 
     _getServerId(project_id, callback) {
       if (callback == null) {
-        callback = function(err, serverId) {}
+        callback = function (err, serverId) {}
       }
       return rclient.get(this.buildKey(project_id), (err, serverId) => {
         if (err != null) {
@@ -55,7 +55,7 @@ module.exports = function(backendGroup) {
 
     _populateServerIdViaRequest(project_id, callback) {
       if (callback == null) {
-        callback = function(err, serverId) {}
+        callback = function (err, serverId) {}
       }
       const url = `${Settings.apis.clsi.url}/project/${project_id}/status`
       return request.post(url, (err, res, body) => {
@@ -65,7 +65,7 @@ module.exports = function(backendGroup) {
           })
           return callback(err)
         }
-        return this.setServerId(project_id, res, function(err, serverId) {
+        return this.setServerId(project_id, res, function (err, serverId) {
           if (err != null) {
             logger.warn(
               { err, project_id },
@@ -88,7 +88,7 @@ module.exports = function(backendGroup) {
 
     setServerId(project_id, response, callback) {
       if (callback == null) {
-        callback = function(err, serverId) {}
+        callback = function (err, serverId) {}
       }
       if (!clsiCookiesEnabled) {
         return callback()
@@ -112,7 +112,7 @@ module.exports = function(backendGroup) {
 
     _setServerIdInRedis(rclient, project_id, serverId, callback) {
       if (callback == null) {
-        callback = function(err) {}
+        callback = function (err) {}
       }
       rclient.setex(
         this.buildKey(project_id),
@@ -124,7 +124,7 @@ module.exports = function(backendGroup) {
 
     clearServerId(project_id, callback) {
       if (callback == null) {
-        callback = function(err) {}
+        callback = function (err) {}
       }
       if (!clsiCookiesEnabled) {
         return callback()
@@ -134,7 +134,7 @@ module.exports = function(backendGroup) {
 
     getCookieJar(project_id, callback) {
       if (callback == null) {
-        callback = function(err, jar) {}
+        callback = function (err, jar) {}
       }
       if (!clsiCookiesEnabled) {
         return callback(null, request.jar())

+ 4 - 6
services/web/app/src/Features/Compile/ClsiFormatChecker.js

@@ -26,7 +26,7 @@ module.exports = ClsiFormatChecker = {
       }
     }
 
-    return async.series(jobs, function(err, problems) {
+    return async.series(jobs, function (err, problems) {
       if (err != null) {
         return callback(err)
       }
@@ -44,7 +44,7 @@ module.exports = ClsiFormatChecker = {
   _checkForConflictingPaths(resources, callback) {
     const paths = resources.map(resource => resource.path)
 
-    const conflicts = _.filter(paths, function(path) {
+    const conflicts = _.filter(paths, function (path) {
       const matchingPaths = _.filter(
         paths,
         checkPath => checkPath.indexOf(path + '/') !== -1
@@ -63,7 +63,7 @@ module.exports = ClsiFormatChecker = {
 
     let totalSize = 0
 
-    let sizedResources = resources.map(function(resource) {
+    let sizedResources = resources.map(function (resource) {
       const result = { path: resource.path }
       if (resource.content != null) {
         result.size = resource.content.replace(/\n/g, '').length
@@ -79,9 +79,7 @@ module.exports = ClsiFormatChecker = {
     if (!tooLarge) {
       return callback()
     } else {
-      sizedResources = _.sortBy(sizedResources, 'size')
-        .reverse()
-        .slice(0, 10)
+      sizedResources = _.sortBy(sizedResources, 'size').reverse().slice(0, 10)
       return callback(null, { resources: sizedResources, totalSize })
     }
   }

+ 37 - 39
services/web/app/src/Features/Compile/ClsiStateManager.js

@@ -34,50 +34,48 @@ const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
 // unsetting it if it removes any documents from the doc updater.
 
 const buildState = s =>
-  crypto
-    .createHash('sha1')
-    .update(s, 'utf8')
-    .digest('hex')
+  crypto.createHash('sha1').update(s, 'utf8').digest('hex')
 
 module.exports = ClsiStateManager = {
   computeHash(project, options, callback) {
     if (callback == null) {
-      callback = function(err, hash) {}
+      callback = function (err, hash) {}
     }
-    return ProjectEntityHandler.getAllEntitiesFromProject(project, function(
-      err,
-      docs,
-      files
-    ) {
-      const fileList = Array.from(files || []).map(
-        f => `${f.file._id}:${f.file.rev}:${f.file.created}:${f.path}`
-      )
-      const docList = Array.from(docs || []).map(d => `${d.doc._id}:${d.path}`)
-      const sortedEntityList = [
-        ...Array.from(docList),
-        ...Array.from(fileList)
-      ].sort()
-      // ignore the isAutoCompile options as it doesn't affect the
-      // output, but include all other options e.g. draft
-      const optionsList = (() => {
-        const result = []
-        const object = options || {}
-        for (let key in object) {
-          const value = object[key]
-          if (!['isAutoCompile'].includes(key)) {
-            result.push(`option ${key}:${value}`)
+    return ProjectEntityHandler.getAllEntitiesFromProject(
+      project,
+      function (err, docs, files) {
+        const fileList = Array.from(files || []).map(
+          f => `${f.file._id}:${f.file.rev}:${f.file.created}:${f.path}`
+        )
+        const docList = Array.from(docs || []).map(
+          d => `${d.doc._id}:${d.path}`
+        )
+        const sortedEntityList = [
+          ...Array.from(docList),
+          ...Array.from(fileList)
+        ].sort()
+        // ignore the isAutoCompile options as it doesn't affect the
+        // output, but include all other options e.g. draft
+        const optionsList = (() => {
+          const result = []
+          const object = options || {}
+          for (let key in object) {
+            const value = object[key]
+            if (!['isAutoCompile'].includes(key)) {
+              result.push(`option ${key}:${value}`)
+            }
           }
-        }
-        return result
-      })()
-      const sortedOptionsList = optionsList.sort()
-      const hash = buildState(
-        [
-          ...Array.from(sortedEntityList),
-          ...Array.from(sortedOptionsList)
-        ].join('\n')
-      )
-      return callback(null, hash)
-    })
+          return result
+        })()
+        const sortedOptionsList = optionsList.sort()
+        const hash = buildState(
+          [
+            ...Array.from(sortedEntityList),
+            ...Array.from(sortedOptionsList)
+          ].join('\n')
+        )
+        return callback(null, hash)
+      }
+    )
   }
 }

+ 85 - 81
services/web/app/src/Features/Compile/CompileController.js

@@ -104,11 +104,11 @@ module.exports = CompileController = {
 
   stopCompile(req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     const project_id = req.params.Project_id
     const user_id = AuthenticationController.getLoggedInUserId(req)
-    return CompileManager.stopCompile(project_id, user_id, function(error) {
+    return CompileManager.stopCompile(project_id, user_id, function (error) {
       if (error != null) {
         return next(error)
       }
@@ -119,7 +119,7 @@ module.exports = CompileController = {
   // Used for submissions through the public API
   compileSubmission(req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     res.setTimeout(COMPILE_TIMEOUT_MS)
     const { submission_id } = req.params
@@ -150,7 +150,7 @@ module.exports = CompileController = {
       submission_id,
       req.body,
       options,
-      function(error, status, outputFiles, clsiServerId, validationProblems) {
+      function (error, status, outputFiles, clsiServerId, validationProblems) {
         if (error != null) {
           return next(error)
         }
@@ -189,13 +189,13 @@ module.exports = CompileController = {
 
   downloadPdf(req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     Metrics.inc('pdf-downloads')
     const project_id = req.params.Project_id
     const isPdfjsPartialDownload =
       req.query != null ? req.query.pdfng : undefined
-    const rateLimit = function(callback) {
+    const rateLimit = function (callback) {
       if (isPdfjsPartialDownload) {
         return callback(null, true)
       } else {
@@ -209,51 +209,52 @@ module.exports = CompileController = {
       }
     }
 
-    return ProjectGetter.getProject(project_id, { name: 1 }, function(
-      err,
-      project
-    ) {
-      res.contentType('application/pdf')
-      const filename = `${CompileController._getSafeProjectName(project)}.pdf`
-
-      if (req.query.popupDownload) {
-        res.setContentDisposition('attachment', { filename })
-      } else {
-        res.setContentDisposition('', { filename })
-      }
+    return ProjectGetter.getProject(
+      project_id,
+      { name: 1 },
+      function (err, project) {
+        res.contentType('application/pdf')
+        const filename = `${CompileController._getSafeProjectName(project)}.pdf`
 
-      return rateLimit(function(err, canContinue) {
-        if (err != null) {
-          logger.err({ err }, 'error checking rate limit for pdf download')
-          return res.sendStatus(500)
-        } else if (!canContinue) {
-          logger.log(
-            { project_id, ip: req.ip },
-            'rate limit hit downloading pdf'
-          )
-          return res.sendStatus(500)
+        if (req.query.popupDownload) {
+          res.setContentDisposition('attachment', { filename })
         } else {
-          return CompileController._downloadAsUser(req, function(
-            error,
-            user_id
-          ) {
-            const url = CompileController._getFileUrl(
-              project_id,
-              user_id,
-              req.params.build_id,
-              'output.pdf'
+          res.setContentDisposition('', { filename })
+        }
+
+        return rateLimit(function (err, canContinue) {
+          if (err != null) {
+            logger.err({ err }, 'error checking rate limit for pdf download')
+            return res.sendStatus(500)
+          } else if (!canContinue) {
+            logger.log(
+              { project_id, ip: req.ip },
+              'rate limit hit downloading pdf'
             )
-            return CompileController.proxyToClsi(
-              project_id,
-              url,
+            return res.sendStatus(500)
+          } else {
+            return CompileController._downloadAsUser(
               req,
-              res,
-              next
+              function (error, user_id) {
+                const url = CompileController._getFileUrl(
+                  project_id,
+                  user_id,
+                  req.params.build_id,
+                  'output.pdf'
+                )
+                return CompileController.proxyToClsi(
+                  project_id,
+                  url,
+                  req,
+                  res,
+                  next
+                )
+              }
             )
-          })
-        }
-      })
-    })
+          }
+        })
+      }
+    )
   },
 
   _getSafeProjectName(project) {
@@ -264,18 +265,21 @@ module.exports = CompileController = {
   deleteAuxFiles(req, res, next) {
     const project_id = req.params.Project_id
     const { clsiserverid } = req.query
-    return CompileController._compileAsUser(req, function(error, user_id) {
+    return CompileController._compileAsUser(req, function (error, user_id) {
       if (error != null) {
         return next(error)
       }
-      CompileManager.deleteAuxFiles(project_id, user_id, clsiserverid, function(
-        error
-      ) {
-        if (error != null) {
-          return next(error)
+      CompileManager.deleteAuxFiles(
+        project_id,
+        user_id,
+        clsiserverid,
+        function (error) {
+          if (error != null) {
+            return next(error)
+          }
+          return res.sendStatus(200)
         }
-        return res.sendStatus(200)
-      })
+      )
     })
   },
 
@@ -283,7 +287,7 @@ module.exports = CompileController = {
   compileAndDownloadPdf(req, res, next) {
     const { project_id } = req.params
     // pass user_id as null, since templates are an "anonymous" compile
-    return CompileManager.compile(project_id, null, {}, function(err) {
+    return CompileManager.compile(project_id, null, {}, function (err) {
       if (err != null) {
         logger.err(
           { err, project_id },
@@ -298,10 +302,10 @@ module.exports = CompileController = {
 
   getFileFromClsi(req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     const project_id = req.params.Project_id
-    return CompileController._downloadAsUser(req, function(error, user_id) {
+    return CompileController._downloadAsUser(req, function (error, user_id) {
       if (error != null) {
         return next(error)
       }
@@ -317,7 +321,7 @@ module.exports = CompileController = {
 
   getFileFromClsiWithoutUser(req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     const { submission_id } = req.params
     const url = CompileController._getFileUrl(
@@ -367,7 +371,7 @@ module.exports = CompileController = {
 
   proxySyncPdf(req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     const project_id = req.params.Project_id
     const { page, h, v } = req.query
@@ -381,7 +385,7 @@ module.exports = CompileController = {
       return next(new Error('invalid v parameter'))
     }
     // whether this request is going to a per-user container
-    return CompileController._compileAsUser(req, function(error, user_id) {
+    return CompileController._compileAsUser(req, function (error, user_id) {
       if (error != null) {
         return next(error)
       }
@@ -403,7 +407,7 @@ module.exports = CompileController = {
 
   proxySyncCode(req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     const project_id = req.params.Project_id
     const { file, line, column } = req.query
@@ -425,7 +429,7 @@ module.exports = CompileController = {
     if (!(column != null ? column.match(/^\d+$/) : undefined)) {
       return next(new Error('invalid column parameter'))
     }
-    return CompileController._compileAsUser(req, function(error, user_id) {
+    return CompileController._compileAsUser(req, function (error, user_id) {
       if (error != null) {
         return next(error)
       }
@@ -447,7 +451,7 @@ module.exports = CompileController = {
 
   proxyToClsi(project_id, url, req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     if (req.query != null ? req.query.compileGroup : undefined) {
       return CompileController.proxyToClsiWithLimits(
@@ -459,28 +463,28 @@ module.exports = CompileController = {
         next
       )
     } else {
-      return CompileManager.getProjectCompileLimits(project_id, function(
-        error,
-        limits
-      ) {
-        if (error != null) {
-          return next(error)
+      return CompileManager.getProjectCompileLimits(
+        project_id,
+        function (error, limits) {
+          if (error != null) {
+            return next(error)
+          }
+          return CompileController.proxyToClsiWithLimits(
+            project_id,
+            url,
+            limits,
+            req,
+            res,
+            next
+          )
         }
-        return CompileController.proxyToClsiWithLimits(
-          project_id,
-          url,
-          limits,
-          req,
-          res,
-          next
-        )
-      })
+      )
     }
   },
 
   proxyToClsiWithLimits(project_id, url, limits, req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     _getPersistenceOptions(req, project_id, (err, persistenceOptions) => {
       let qs
@@ -541,7 +545,7 @@ module.exports = CompileController = {
     const project_id = req.params.Project_id
     const file = req.query.file || false
     const { clsiserverid } = req.query
-    return CompileController._compileAsUser(req, function(error, user_id) {
+    return CompileController._compileAsUser(req, function (error, user_id) {
       if (error != null) {
         return next(error)
       }
@@ -550,7 +554,7 @@ module.exports = CompileController = {
         user_id,
         file,
         clsiserverid,
-        function(error, body) {
+        function (error, body) {
           if (error != null) {
             return next(error)
           }

+ 99 - 94
services/web/app/src/Features/Compile/CompileManager.js

@@ -30,10 +30,10 @@ module.exports = CompileManager = {
       options = {}
     }
     if (_callback == null) {
-      _callback = function(error) {}
+      _callback = function (error) {}
     }
     const timer = new Metrics.Timer('editor.compile')
-    const callback = function(...args) {
+    const callback = function (...args) {
       timer.done()
       return _callback(...Array.from(args || []))
     }
@@ -41,7 +41,7 @@ module.exports = CompileManager = {
     return CompileManager._checkIfRecentlyCompiled(
       project_id,
       user_id,
-      function(error, recentlyCompiled) {
+      function (error, recentlyCompiled) {
         if (error != null) {
           return callback(error)
         }
@@ -52,20 +52,20 @@ module.exports = CompileManager = {
         return CompileManager._checkIfAutoCompileLimitHasBeenHit(
           options.isAutoCompile,
           'everyone',
-          function(err, canCompile) {
+          function (err, canCompile) {
             if (!canCompile) {
               return callback(null, 'autocompile-backoff', [])
             }
 
             return ProjectRootDocManager.ensureRootDocumentIsSet(
               project_id,
-              function(error) {
+              function (error) {
                 if (error != null) {
                   return callback(error)
                 }
                 return CompileManager.getProjectCompileLimits(
                   project_id,
-                  function(error, limits) {
+                  function (error, limits) {
                     if (error != null) {
                       return callback(error)
                     }
@@ -77,7 +77,7 @@ module.exports = CompileManager = {
                     return CompileManager._checkCompileGroupAutoCompileLimit(
                       options.isAutoCompile,
                       limits.compileGroup,
-                      function(err, canCompile) {
+                      function (err, canCompile) {
                         if (!canCompile) {
                           return callback(null, 'autocompile-backoff', [])
                         }
@@ -89,7 +89,7 @@ module.exports = CompileManager = {
                           project_id,
                           compileAsUser,
                           options,
-                          function(
+                          function (
                             error,
                             status,
                             outputFiles,
@@ -123,100 +123,105 @@ module.exports = CompileManager = {
 
   stopCompile(project_id, user_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return CompileManager.getProjectCompileLimits(project_id, function(
-      error,
-      limits
-    ) {
-      if (error != null) {
-        return callback(error)
+    return CompileManager.getProjectCompileLimits(
+      project_id,
+      function (error, limits) {
+        if (error != null) {
+          return callback(error)
+        }
+        return ClsiManager.stopCompile(project_id, user_id, limits, callback)
       }
-      return ClsiManager.stopCompile(project_id, user_id, limits, callback)
-    })
+    )
   },
 
   deleteAuxFiles(project_id, user_id, clsiserverid, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return CompileManager.getProjectCompileLimits(project_id, function(
-      error,
-      limits
-    ) {
-      if (error != null) {
-        return callback(error)
+    return CompileManager.getProjectCompileLimits(
+      project_id,
+      function (error, limits) {
+        if (error != null) {
+          return callback(error)
+        }
+        ClsiManager.deleteAuxFiles(
+          project_id,
+          user_id,
+          limits,
+          clsiserverid,
+          callback
+        )
       }
-      ClsiManager.deleteAuxFiles(
-        project_id,
-        user_id,
-        limits,
-        clsiserverid,
-        callback
-      )
-    })
+    )
   },
 
   getProjectCompileLimits(project_id, callback) {
     if (callback == null) {
-      callback = function(error, limits) {}
+      callback = function (error, limits) {}
     }
-    return ProjectGetter.getProject(project_id, { owner_ref: 1 }, function(
-      error,
-      project
-    ) {
-      if (error != null) {
-        return callback(error)
-      }
-      return UserGetter.getUser(
-        project.owner_ref,
-        { alphaProgram: 1, betaProgram: 1, features: 1 },
-        function(err, owner) {
-          if (error != null) {
-            return callback(error)
-          }
-          let ownerFeatures = (owner && owner.features) || {}
-          // put alpha users into their own compile group
-          if (owner && owner.alphaProgram) {
-            ownerFeatures.compileGroup = 'alpha'
-          }
-          return callback(null, {
-            timeout:
-              ownerFeatures.compileTimeout ||
-              Settings.defaultFeatures.compileTimeout,
-            compileGroup:
-              ownerFeatures.compileGroup ||
-              Settings.defaultFeatures.compileGroup
-          })
+    return ProjectGetter.getProject(
+      project_id,
+      { owner_ref: 1 },
+      function (error, project) {
+        if (error != null) {
+          return callback(error)
         }
-      )
-    })
+        return UserGetter.getUser(
+          project.owner_ref,
+          { alphaProgram: 1, betaProgram: 1, features: 1 },
+          function (err, owner) {
+            if (error != null) {
+              return callback(error)
+            }
+            let ownerFeatures = (owner && owner.features) || {}
+            // put alpha users into their own compile group
+            if (owner && owner.alphaProgram) {
+              ownerFeatures.compileGroup = 'alpha'
+            }
+            return callback(null, {
+              timeout:
+                ownerFeatures.compileTimeout ||
+                Settings.defaultFeatures.compileTimeout,
+              compileGroup:
+                ownerFeatures.compileGroup ||
+                Settings.defaultFeatures.compileGroup
+            })
+          }
+        )
+      }
+    )
   },
 
   COMPILE_DELAY: 1, // seconds
   _checkIfRecentlyCompiled(project_id, user_id, callback) {
     if (callback == null) {
-      callback = function(error, recentlyCompiled) {}
+      callback = function (error, recentlyCompiled) {}
     }
     const key = `compile:${project_id}:${user_id}`
-    return rclient.set(key, true, 'EX', this.COMPILE_DELAY, 'NX', function(
-      error,
-      ok
-    ) {
-      if (error != null) {
-        return callback(error)
-      }
-      if (ok === 'OK') {
-        return callback(null, false)
-      } else {
-        return callback(null, true)
+    return rclient.set(
+      key,
+      true,
+      'EX',
+      this.COMPILE_DELAY,
+      'NX',
+      function (error, ok) {
+        if (error != null) {
+          return callback(error)
+        }
+        if (ok === 'OK') {
+          return callback(null, false)
+        } else {
+          return callback(null, true)
+        }
       }
-    })
+    )
   },
 
   _checkCompileGroupAutoCompileLimit(isAutoCompile, compileGroup, callback) {
     if (callback == null) {
-      callback = function(err, canCompile) {}
+      callback = function (err, canCompile) {}
     }
     if (!isAutoCompile) {
       return callback(null, true)
@@ -236,7 +241,7 @@ module.exports = CompileManager = {
 
   _checkIfAutoCompileLimitHasBeenHit(isAutoCompile, compileGroup, callback) {
     if (callback == null) {
-      callback = function(err, canCompile) {}
+      callback = function (err, canCompile) {}
     }
     if (!isAutoCompile) {
       return callback(null, true)
@@ -248,7 +253,7 @@ module.exports = CompileManager = {
       subjectName: compileGroup,
       throttle: Settings.rateLimit.autoCompile[compileGroup] || 25
     }
-    return rateLimiter.addCount(opts, function(err, canCompile) {
+    return rateLimiter.addCount(opts, function (err, canCompile) {
       if (err != null) {
         canCompile = false
       }
@@ -261,23 +266,23 @@ module.exports = CompileManager = {
 
   wordCount(project_id, user_id, file, clsiserverid, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return CompileManager.getProjectCompileLimits(project_id, function(
-      error,
-      limits
-    ) {
-      if (error != null) {
-        return callback(error)
+    return CompileManager.getProjectCompileLimits(
+      project_id,
+      function (error, limits) {
+        if (error != null) {
+          return callback(error)
+        }
+        ClsiManager.wordCount(
+          project_id,
+          user_id,
+          file,
+          limits,
+          clsiserverid,
+          callback
+        )
       }
-      ClsiManager.wordCount(
-        project_id,
-        user_id,
-        file,
-        limits,
-        clsiserverid,
-        callback
-      )
-    })
+    )
   }
 }

+ 53 - 48
services/web/app/src/Features/Contacts/ContactController.js

@@ -22,58 +22,63 @@ const Modules = require('../../infrastructure/Modules')
 module.exports = ContactsController = {
   getContacts(req, res, next) {
     const user_id = AuthenticationController.getLoggedInUserId(req)
-    return ContactManager.getContactIds(user_id, { limit: 50 }, function(
-      error,
-      contact_ids
-    ) {
-      if (error != null) {
-        return next(error)
-      }
-      return UserGetter.getUsers(
-        contact_ids,
-        {
-          email: 1,
-          first_name: 1,
-          last_name: 1,
-          holdingAccount: 1
-        },
-        function(error, contacts) {
-          if (error != null) {
-            return next(error)
-          }
+    return ContactManager.getContactIds(
+      user_id,
+      { limit: 50 },
+      function (error, contact_ids) {
+        if (error != null) {
+          return next(error)
+        }
+        return UserGetter.getUsers(
+          contact_ids,
+          {
+            email: 1,
+            first_name: 1,
+            last_name: 1,
+            holdingAccount: 1
+          },
+          function (error, contacts) {
+            if (error != null) {
+              return next(error)
+            }
 
-          // UserGetter.getUsers may not preserve order so put them back in order
-          const positions = {}
-          for (let i = 0; i < contact_ids.length; i++) {
-            const contact_id = contact_ids[i]
-            positions[contact_id] = i
-          }
-          contacts.sort(
-            (a, b) =>
-              positions[a._id != null ? a._id.toString() : undefined] -
-              positions[b._id != null ? b._id.toString() : undefined]
-          )
+            // UserGetter.getUsers may not preserve order so put them back in order
+            const positions = {}
+            for (let i = 0; i < contact_ids.length; i++) {
+              const contact_id = contact_ids[i]
+              positions[contact_id] = i
+            }
+            contacts.sort(
+              (a, b) =>
+                positions[a._id != null ? a._id.toString() : undefined] -
+                positions[b._id != null ? b._id.toString() : undefined]
+            )
 
-          // Don't count holding accounts to discourage users from repeating mistakes (mistyped or wrong emails, etc)
-          contacts = contacts.filter(c => !c.holdingAccount)
+            // Don't count holding accounts to discourage users from repeating mistakes (mistyped or wrong emails, etc)
+            contacts = contacts.filter(c => !c.holdingAccount)
 
-          contacts = contacts.map(ContactsController._formatContact)
+            contacts = contacts.map(ContactsController._formatContact)
 
-          return Modules.hooks.fire('getContacts', user_id, contacts, function(
-            error,
-            additional_contacts
-          ) {
-            if (error != null) {
-              return next(error)
-            }
-            contacts = contacts.concat(...Array.from(additional_contacts || []))
-            return res.send({
-              contacts
-            })
-          })
-        }
-      )
-    })
+            return Modules.hooks.fire(
+              'getContacts',
+              user_id,
+              contacts,
+              function (error, additional_contacts) {
+                if (error != null) {
+                  return next(error)
+                }
+                contacts = contacts.concat(
+                  ...Array.from(additional_contacts || [])
+                )
+                return res.send({
+                  contacts
+                })
+              }
+            )
+          }
+        )
+      }
+    )
   },
 
   _formatContact(contact) {

+ 4 - 4
services/web/app/src/Features/Contacts/ContactManager.js

@@ -23,7 +23,7 @@ module.exports = ContactManager = {
       options = { limits: 50 }
     }
     if (callback == null) {
-      callback = function(error, contacts) {}
+      callback = function (error, contacts) {}
     }
     const url = `${settings.apis.contacts.url}/user/${user_id}/contacts`
     return request.get(
@@ -33,7 +33,7 @@ module.exports = ContactManager = {
         json: true,
         jar: false
       },
-      function(error, res, data) {
+      function (error, res, data) {
         if (error != null) {
           return callback(error)
         }
@@ -55,7 +55,7 @@ module.exports = ContactManager = {
 
   addContact(user_id, contact_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     const url = `${settings.apis.contacts.url}/user/${user_id}/contacts`
     return request.post(
@@ -66,7 +66,7 @@ module.exports = ContactManager = {
         },
         jar: false
       },
-      function(error, res, data) {
+      function (error, res, data) {
         if (error != null) {
           return callback(error)
         }

+ 10 - 10
services/web/app/src/Features/Cooldown/CooldownManager.js

@@ -24,7 +24,7 @@ module.exports = CooldownManager = {
 
   putProjectOnCooldown(projectId, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     logger.log(
       { projectId },
@@ -41,16 +41,16 @@ module.exports = CooldownManager = {
 
   isProjectOnCooldown(projectId, callback) {
     if (callback == null) {
-      callback = function(err, isOnCooldown) {}
+      callback = function (err, isOnCooldown) {}
     }
-    return rclient.get(CooldownManager._buildKey(projectId), function(
-      err,
-      result
-    ) {
-      if (err != null) {
-        return callback(err)
+    return rclient.get(
+      CooldownManager._buildKey(projectId),
+      function (err, result) {
+        if (err != null) {
+          return callback(err)
+        }
+        return callback(null, result === '1')
       }
-      return callback(null, result === '1')
-    })
+    )
   }
 }

+ 15 - 15
services/web/app/src/Features/Cooldown/CooldownMiddleware.js

@@ -20,21 +20,21 @@ module.exports = CooldownMiddleware = {
     if (projectId == null) {
       return next(new Error('[Cooldown] No projectId parameter on route'))
     }
-    return CooldownManager.isProjectOnCooldown(projectId, function(
-      err,
-      projectIsOnCooldown
-    ) {
-      if (err != null) {
-        return next(err)
+    return CooldownManager.isProjectOnCooldown(
+      projectId,
+      function (err, projectIsOnCooldown) {
+        if (err != null) {
+          return next(err)
+        }
+        if (projectIsOnCooldown) {
+          logger.log(
+            { projectId },
+            '[Cooldown] project is on cooldown, denying request'
+          )
+          return res.sendStatus(429)
+        }
+        return next()
       }
-      if (projectIsOnCooldown) {
-        logger.log(
-          { projectId },
-          '[Cooldown] project is on cooldown, denying request'
-        )
-        return res.sendStatus(429)
-      }
-      return next()
-    })
+    )
   }
 }

+ 50 - 50
services/web/app/src/Features/Docstore/DocstoreManager.js

@@ -24,12 +24,12 @@ const TIMEOUT = 30 * 1000 // request timeout
 const DocstoreManager = {
   deleteDoc(project_id, doc_id, name, deletedAt, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     const url = `${settings.apis.docstore.url}/project/${project_id}/doc/${doc_id}`
     const docMetaData = { deleted: true, deletedAt, name }
     const options = { url, json: docMetaData, timeout: TIMEOUT }
-    request.patch(options, function(error, res) {
+    request.patch(options, function (error, res) {
       if (error != null) {
         return callback(error)
       }
@@ -59,7 +59,7 @@ const DocstoreManager = {
 
   getAllDocs(project_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     const url = `${settings.apis.docstore.url}/project/${project_id}/doc`
     return request.get(
@@ -68,7 +68,7 @@ const DocstoreManager = {
         timeout: TIMEOUT,
         json: true
       },
-      function(error, res, docs) {
+      function (error, res, docs) {
         if (error != null) {
           return callback(error)
         }
@@ -87,29 +87,30 @@ const DocstoreManager = {
 
   getAllDeletedDocs(project_id, callback) {
     const url = `${settings.apis.docstore.url}/project/${project_id}/doc-deleted`
-    request.get({ url, timeout: TIMEOUT, json: true }, function(
-      error,
-      res,
-      docs
-    ) {
-      if (error) {
-        callback(OError.tag(error, 'could not get deleted docs from docstore'))
-      } else if (res.statusCode === 200) {
-        callback(null, docs)
-      } else {
-        callback(
-          new OError(
-            `docstore api responded with non-success code: ${res.statusCode}`,
-            { project_id }
+    request.get(
+      { url, timeout: TIMEOUT, json: true },
+      function (error, res, docs) {
+        if (error) {
+          callback(
+            OError.tag(error, 'could not get deleted docs from docstore')
           )
-        )
+        } else if (res.statusCode === 200) {
+          callback(null, docs)
+        } else {
+          callback(
+            new OError(
+              `docstore api responded with non-success code: ${res.statusCode}`,
+              { project_id }
+            )
+          )
+        }
       }
-    })
+    )
   },
 
   getAllRanges(project_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     const url = `${settings.apis.docstore.url}/project/${project_id}/ranges`
     return request.get(
@@ -118,7 +119,7 @@ const DocstoreManager = {
         timeout: TIMEOUT,
         json: true
       },
-      function(error, res, docs) {
+      function (error, res, docs) {
         if (error != null) {
           return callback(error)
         }
@@ -140,7 +141,7 @@ const DocstoreManager = {
       options = {}
     }
     if (callback == null) {
-      callback = function(error, lines, rev, version) {}
+      callback = function (error, lines, rev, version) {}
     }
     if (typeof options === 'function') {
       callback = options
@@ -156,7 +157,7 @@ const DocstoreManager = {
         timeout: TIMEOUT,
         json: true
       },
-      function(error, res, doc) {
+      function (error, res, doc) {
         if (error != null) {
           return callback(error)
         }
@@ -191,36 +192,35 @@ const DocstoreManager = {
 
   isDocDeleted(project_id, doc_id, callback) {
     const url = `${settings.apis.docstore.url}/project/${project_id}/doc/${doc_id}/deleted`
-    request.get({ url, timeout: TIMEOUT, json: true }, function(
-      err,
-      res,
-      body
-    ) {
-      if (err) {
-        callback(err)
-      } else if (res.statusCode === 200) {
-        callback(null, body.deleted)
-      } else if (res.statusCode === 404) {
-        callback(
-          new Errors.NotFoundError({
-            message: 'doc does not exist in project',
-            info: { project_id, doc_id }
-          })
-        )
-      } else {
-        callback(
-          new OError(
-            `docstore api responded with non-success code: ${res.statusCode}`,
-            { project_id, doc_id }
+    request.get(
+      { url, timeout: TIMEOUT, json: true },
+      function (err, res, body) {
+        if (err) {
+          callback(err)
+        } else if (res.statusCode === 200) {
+          callback(null, body.deleted)
+        } else if (res.statusCode === 404) {
+          callback(
+            new Errors.NotFoundError({
+              message: 'doc does not exist in project',
+              info: { project_id, doc_id }
+            })
           )
-        )
+        } else {
+          callback(
+            new OError(
+              `docstore api responded with non-success code: ${res.statusCode}`,
+              { project_id, doc_id }
+            )
+          )
+        }
       }
-    })
+    )
   },
 
   updateDoc(project_id, doc_id, lines, version, ranges, callback) {
     if (callback == null) {
-      callback = function(error, modified, rev) {}
+      callback = function (error, modified, rev) {}
     }
     const url = `${settings.apis.docstore.url}/project/${project_id}/doc/${doc_id}`
     return request.post(
@@ -233,7 +233,7 @@ const DocstoreManager = {
           ranges
         }
       },
-      function(error, res, result) {
+      function (error, res, result) {
         if (error != null) {
           return callback(error)
         }
@@ -270,7 +270,7 @@ const DocstoreManager = {
     const url = `${settings.apis.docstore.url}/project/${project_id}/${method}`
     logger.log({ project_id }, `calling ${method} for project in docstore`)
     // use default timeout for archiving/unarchiving/destroying
-    request.post(url, function(err, res, docs) {
+    request.post(url, function (err, res, docs) {
       if (err != null) {
         OError.tag(err, `error calling ${method} project in docstore`, {
           project_id

+ 3 - 3
services/web/app/src/Features/DocumentUpdater/DocumentUpdaterHandler.js

@@ -101,7 +101,7 @@ function getDocument(projectId, docId, fromVersion, callback) {
     },
     projectId,
     'get-document',
-    function(error, doc) {
+    function (error, doc) {
       if (error) {
         return callback(error)
       }
@@ -133,7 +133,7 @@ function getProjectDocsIfMatch(projectId, projectStateHash, callback) {
   // fall back to getting them from mongo.
   const timer = new metrics.Timer('get-project-docs')
   const url = `${settings.apis.documentupdater.url}/project/${projectId}/get_and_flush_if_old?state=${projectStateHash}`
-  request.post(url, function(error, res, body) {
+  request.post(url, function (error, res, body) {
     timer.done()
     if (error) {
       OError.tag(error, 'error getting project docs from doc updater', {
@@ -300,7 +300,7 @@ function _makeRequest(options, projectId, metricsKey, callback) {
       json: options.json,
       method: options.method || 'GET'
     },
-    function(error, res, body) {
+    function (error, res, body) {
       timer.done()
       if (error) {
         logger.warn(

+ 41 - 43
services/web/app/src/Features/Documents/DocumentController.js

@@ -23,7 +23,7 @@ const _ = require('lodash')
 module.exports = {
   getDocument(req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     const project_id = req.params.Project_id
     const { doc_id } = req.params
@@ -32,7 +32,7 @@ module.exports = {
     return ProjectGetter.getProject(
       project_id,
       { rootFolder: true, overleaf: true },
-      function(error, project) {
+      function (error, project) {
         if (error != null) {
           return next(error)
         }
@@ -41,7 +41,7 @@ module.exports = {
         }
         return ProjectLocator.findElement(
           { project, element_id: doc_id, type: 'doc' },
-          function(error, doc, path) {
+          function (error, doc, path) {
             if (error != null) {
               OError.tag(error, 'error finding element for getDocument', {
                 doc_id,
@@ -49,45 +49,43 @@ module.exports = {
               })
               return next(error)
             }
-            return ProjectEntityHandler.getDoc(project_id, doc_id, function(
-              error,
-              lines,
-              rev,
-              version,
-              ranges
-            ) {
-              if (error != null) {
-                OError.tag(
-                  error,
-                  'error finding doc contents for getDocument',
-                  {
-                    doc_id,
-                    project_id
-                  }
-                )
-                return next(error)
+            return ProjectEntityHandler.getDoc(
+              project_id,
+              doc_id,
+              function (error, lines, rev, version, ranges) {
+                if (error != null) {
+                  OError.tag(
+                    error,
+                    'error finding doc contents for getDocument',
+                    {
+                      doc_id,
+                      project_id
+                    }
+                  )
+                  return next(error)
+                }
+                if (plain) {
+                  res.type('text/plain')
+                  return res.send(lines.join('\n'))
+                } else {
+                  const projectHistoryId = _.get(project, 'overleaf.history.id')
+                  const projectHistoryType = _.get(
+                    project,
+                    'overleaf.history.display'
+                  )
+                    ? 'project-history'
+                    : undefined // for backwards compatibility, don't send anything if the project is still on track-changes
+                  return res.json({
+                    lines,
+                    version,
+                    ranges,
+                    pathname: path.fileSystem,
+                    projectHistoryId,
+                    projectHistoryType
+                  })
+                }
               }
-              if (plain) {
-                res.type('text/plain')
-                return res.send(lines.join('\n'))
-              } else {
-                const projectHistoryId = _.get(project, 'overleaf.history.id')
-                const projectHistoryType = _.get(
-                  project,
-                  'overleaf.history.display'
-                )
-                  ? 'project-history'
-                  : undefined // for backwards compatibility, don't send anything if the project is still on track-changes
-                return res.json({
-                  lines,
-                  version,
-                  ranges,
-                  pathname: path.fileSystem,
-                  projectHistoryId,
-                  projectHistoryType
-                })
-              }
-            })
+            )
           }
         )
       }
@@ -96,7 +94,7 @@ module.exports = {
 
   setDocument(req, res, next) {
     if (next == null) {
-      next = function(error) {}
+      next = function (error) {}
     }
     const project_id = req.params.Project_id
     const { doc_id } = req.params
@@ -109,7 +107,7 @@ module.exports = {
       ranges,
       lastUpdatedAt,
       lastUpdatedBy,
-      function(error) {
+      function (error) {
         if (error != null) {
           OError.tag(error, 'error finding element for getDocument', {
             doc_id,

+ 23 - 21
services/web/app/src/Features/Downloads/ProjectDownloadsController.js

@@ -22,34 +22,36 @@ module.exports = ProjectDownloadsController = {
   downloadProject(req, res, next) {
     const project_id = req.params.Project_id
     Metrics.inc('zip-downloads')
-    return DocumentUpdaterHandler.flushProjectToMongo(project_id, function(
-      error
-    ) {
-      if (error != null) {
-        return next(error)
-      }
-      return ProjectGetter.getProject(project_id, { name: true }, function(
-        error,
-        project
-      ) {
+    return DocumentUpdaterHandler.flushProjectToMongo(
+      project_id,
+      function (error) {
         if (error != null) {
           return next(error)
         }
-        return ProjectZipStreamManager.createZipStreamForProject(
+        return ProjectGetter.getProject(
           project_id,
-          function(error, stream) {
+          { name: true },
+          function (error, project) {
             if (error != null) {
               return next(error)
             }
-            res.setContentDisposition('attachment', {
-              filename: `${project.name}.zip`
-            })
-            res.contentType('application/zip')
-            return stream.pipe(res)
+            return ProjectZipStreamManager.createZipStreamForProject(
+              project_id,
+              function (error, stream) {
+                if (error != null) {
+                  return next(error)
+                }
+                res.setContentDisposition('attachment', {
+                  filename: `${project.name}.zip`
+                })
+                res.contentType('application/zip')
+                return stream.pipe(res)
+              }
+            )
           }
         )
-      })
-    })
+      }
+    )
   },
 
   downloadMultipleProjects(req, res, next) {
@@ -57,13 +59,13 @@ module.exports = ProjectDownloadsController = {
     Metrics.inc('zip-downloads-multiple')
     return DocumentUpdaterHandler.flushMultipleProjectsToMongo(
       project_ids,
-      function(error) {
+      function (error) {
         if (error != null) {
           return next(error)
         }
         return ProjectZipStreamManager.createZipStreamForMultipleProjects(
           project_ids,
-          function(error, stream) {
+          function (error, stream) {
             if (error != null) {
               return next(error)
             }

+ 70 - 64
services/web/app/src/Features/Downloads/ProjectZipStreamManager.js

@@ -26,7 +26,7 @@ module.exports = ProjectZipStreamManager = {
     // We'll build up a zip file that contains multiple zip files
 
     if (callback == null) {
-      callback = function(error, stream) {}
+      callback = function (error, stream) {}
     }
     const archive = archiver('zip')
     archive.on('error', err =>
@@ -41,38 +41,39 @@ module.exports = ProjectZipStreamManager = {
     for (let project_id of Array.from(project_ids || [])) {
       ;(project_id =>
         jobs.push(callback =>
-          ProjectGetter.getProject(project_id, { name: true }, function(
-            error,
-            project
-          ) {
-            if (error != null) {
-              return callback(error)
-            }
-            logger.log(
-              { project_id, name: project.name },
-              'appending project to zip stream'
-            )
-            return ProjectZipStreamManager.createZipStreamForProject(
-              project_id,
-              function(error, stream) {
-                if (error != null) {
-                  return callback(error)
-                }
-                archive.append(stream, { name: `${project.name}.zip` })
-                return stream.on('end', function() {
-                  logger.log(
-                    { project_id, name: project.name },
-                    'zip stream ended'
-                  )
-                  return callback()
-                })
+          ProjectGetter.getProject(
+            project_id,
+            { name: true },
+            function (error, project) {
+              if (error != null) {
+                return callback(error)
               }
-            )
-          })
+              logger.log(
+                { project_id, name: project.name },
+                'appending project to zip stream'
+              )
+              return ProjectZipStreamManager.createZipStreamForProject(
+                project_id,
+                function (error, stream) {
+                  if (error != null) {
+                    return callback(error)
+                  }
+                  archive.append(stream, { name: `${project.name}.zip` })
+                  return stream.on('end', function () {
+                    logger.log(
+                      { project_id, name: project.name },
+                      'zip stream ended'
+                    )
+                    return callback()
+                  })
+                }
+              )
+            }
+          )
         ))(project_id)
     }
 
-    return async.series(jobs, function() {
+    return async.series(jobs, function () {
       logger.log(
         { project_ids },
         'finished creating zip stream of multiple projects'
@@ -83,7 +84,7 @@ module.exports = ProjectZipStreamManager = {
 
   createZipStreamForProject(project_id, callback) {
     if (callback == null) {
-      callback = function(error, stream) {}
+      callback = function (error, stream) {}
     }
     const archive = archiver('zip')
     // return stream immediately before we start adding things to it
@@ -115,20 +116,20 @@ module.exports = ProjectZipStreamManager = {
 
   addAllDocsToArchive(project_id, archive, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return ProjectEntityHandler.getAllDocs(project_id, function(error, docs) {
+    return ProjectEntityHandler.getAllDocs(project_id, function (error, docs) {
       if (error != null) {
         return callback(error)
       }
       const jobs = []
       for (let path in docs) {
         const doc = docs[path]
-        ;(function(path, doc) {
+        ;(function (path, doc) {
           if (path[0] === '/') {
             path = path.slice(1)
           }
-          return jobs.push(function(callback) {
+          return jobs.push(function (callback) {
             logger.log({ project_id }, 'Adding doc')
             archive.append(doc.lines.join('\n'), { name: path })
             return callback()
@@ -141,37 +142,42 @@ module.exports = ProjectZipStreamManager = {
 
   addAllFilesToArchive(project_id, archive, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return ProjectEntityHandler.getAllFiles(project_id, function(error, files) {
-      if (error != null) {
-        return callback(error)
-      }
-      const jobs = []
-      for (let path in files) {
-        const file = files[path]
-        ;((path, file) =>
-          jobs.push(callback =>
-            FileStoreHandler.getFileStream(project_id, file._id, {}, function(
-              error,
-              stream
-            ) {
-              if (error != null) {
-                logger.warn(
-                  { err: error, project_id, file_id: file._id },
-                  'something went wrong adding file to zip archive'
-                )
-                return callback(err)
-              }
-              if (path[0] === '/') {
-                path = path.slice(1)
-              }
-              archive.append(stream, { name: path })
-              return stream.on('end', () => callback())
-            })
-          ))(path, file)
+    return ProjectEntityHandler.getAllFiles(
+      project_id,
+      function (error, files) {
+        if (error != null) {
+          return callback(error)
+        }
+        const jobs = []
+        for (let path in files) {
+          const file = files[path]
+          ;((path, file) =>
+            jobs.push(callback =>
+              FileStoreHandler.getFileStream(
+                project_id,
+                file._id,
+                {},
+                function (error, stream) {
+                  if (error != null) {
+                    logger.warn(
+                      { err: error, project_id, file_id: file._id },
+                      'something went wrong adding file to zip archive'
+                    )
+                    return callback(err)
+                  }
+                  if (path[0] === '/') {
+                    path = path.slice(1)
+                  }
+                  archive.append(stream, { name: path })
+                  return stream.on('end', () => callback())
+                }
+              )
+            ))(path, file)
+        }
+        return async.parallelLimit(jobs, 5, callback)
       }
-      return async.parallelLimit(jobs, 5, callback)
-    })
+    )
   }
 }

+ 87 - 74
services/web/app/src/Features/Editor/EditorController.js

@@ -31,7 +31,7 @@ const { promisifyAll } = require('../../util/promises')
 const EditorController = {
   addDoc(project_id, folder_id, docName, docLines, source, user_id, callback) {
     if (callback == null) {
-      callback = function(error, doc) {}
+      callback = function (error, doc) {}
     }
     return EditorController.addDocWithRanges(
       project_id,
@@ -56,7 +56,7 @@ const EditorController = {
     callback
   ) {
     if (callback == null) {
-      callback = function(error, doc) {}
+      callback = function (error, doc) {}
     }
     docName = docName.trim()
     Metrics.inc('editor.add-doc')
@@ -99,7 +99,7 @@ const EditorController = {
     callback
   ) {
     if (callback == null) {
-      callback = function(error, file) {}
+      callback = function (error, file) {}
     }
     fileName = fileName.trim()
     Metrics.inc('editor.add-file')
@@ -143,7 +143,7 @@ const EditorController = {
     callback
   ) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     return ProjectEntityUpdateHandler.upsertDoc(
       project_id,
@@ -152,7 +152,7 @@ const EditorController = {
       docLines,
       source,
       user_id,
-      function(err, doc, didAddNewDoc) {
+      function (err, doc, didAddNewDoc) {
         if (didAddNewDoc) {
           EditorRealTimeController.emitToRoom(
             project_id,
@@ -179,7 +179,7 @@ const EditorController = {
     callback
   ) {
     if (callback == null) {
-      callback = function(err, file) {}
+      callback = function (err, file) {}
     }
     return ProjectEntityUpdateHandler.upsertFile(
       project_id,
@@ -188,7 +188,7 @@ const EditorController = {
       fsPath,
       linkedFileData,
       user_id,
-      function(err, newFile, didAddFile, existingFile) {
+      function (err, newFile, didAddFile, existingFile) {
         if (err != null) {
           return callback(err)
         }
@@ -230,14 +230,14 @@ const EditorController = {
       docLines,
       source,
       user_id,
-      function(err, doc, didAddNewDoc, newFolders, lastFolder) {
+      function (err, doc, didAddNewDoc, newFolders, lastFolder) {
         if (err != null) {
           return callback(err)
         }
         return EditorController._notifyProjectUsersOfNewFolders(
           project_id,
           newFolders,
-          function(err) {
+          function (err) {
             if (err != null) {
               return callback(err)
             }
@@ -273,14 +273,21 @@ const EditorController = {
       fsPath,
       linkedFileData,
       user_id,
-      function(err, newFile, didAddFile, existingFile, newFolders, lastFolder) {
+      function (
+        err,
+        newFile,
+        didAddFile,
+        existingFile,
+        newFolders,
+        lastFolder
+      ) {
         if (err != null) {
           return callback(err)
         }
         return EditorController._notifyProjectUsersOfNewFolders(
           project_id,
           newFolders,
-          function(err) {
+          function (err) {
             if (err != null) {
               return callback(err)
             }
@@ -312,7 +319,7 @@ const EditorController = {
 
   addFolder(project_id, folder_id, folderName, source, userId, callback) {
     if (callback == null) {
-      callback = function(error, folder) {}
+      callback = function (error, folder) {}
     }
     folderName = folderName.trim()
     Metrics.inc('editor.add-folder')
@@ -335,7 +342,7 @@ const EditorController = {
           folder_id,
           folder,
           userId,
-          function(err) {
+          function (err) {
             if (err != null) {
               return callback(err)
             }
@@ -348,7 +355,7 @@ const EditorController = {
 
   mkdirp(project_id, path, callback) {
     if (callback == null) {
-      callback = function(error, newFolders, lastFolder) {}
+      callback = function (error, newFolders, lastFolder) {}
     }
     logger.log({ project_id, path }, "making directories if they don't exist")
     return ProjectEntityUpdateHandler.mkdirp(
@@ -366,7 +373,7 @@ const EditorController = {
         return EditorController._notifyProjectUsersOfNewFolders(
           project_id,
           newFolders,
-          function(err) {
+          function (err) {
             if (err != null) {
               return callback(err)
             }
@@ -379,7 +386,7 @@ const EditorController = {
 
   deleteEntity(project_id, entity_id, entityType, source, userId, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     Metrics.inc('editor.delete-entity')
     return ProjectEntityUpdateHandler.deleteEntity(
@@ -387,7 +394,7 @@ const EditorController = {
       entity_id,
       entityType,
       userId,
-      function(err) {
+      function (err) {
         if (err != null) {
           OError.tag(err, 'could not delete entity', {
             project_id,
@@ -416,7 +423,7 @@ const EditorController = {
       project_id,
       path,
       user_id,
-      function(err, entity_id) {
+      function (err, entity_id) {
         if (err != null) {
           return callback(err)
         }
@@ -433,13 +440,13 @@ const EditorController = {
 
   updateProjectDescription(project_id, description, callback) {
     if (callback == null) {
-      callback = function() {}
+      callback = function () {}
     }
     logger.log({ project_id, description }, 'updating project description')
     return ProjectDetailsHandler.setProjectDescription(
       project_id,
       description,
-      function(err) {
+      function (err) {
         if (err != null) {
           OError.tag(
             err,
@@ -468,7 +475,7 @@ const EditorController = {
 
   renameEntity(project_id, entity_id, entityType, newName, userId, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     newName = sanitize.escape(newName)
     Metrics.inc('editor.rename-entity')
@@ -478,7 +485,7 @@ const EditorController = {
       entityType,
       newName,
       userId,
-      function(err) {
+      function (err) {
         if (err != null) {
           OError.tag(err, 'error renaming entity', {
             project_id,
@@ -503,7 +510,7 @@ const EditorController = {
 
   moveEntity(project_id, entity_id, folder_id, entityType, userId, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     Metrics.inc('editor.move-entity')
     return ProjectEntityUpdateHandler.moveEntity(
@@ -512,7 +519,7 @@ const EditorController = {
       folder_id,
       entityType,
       userId,
-      function(err) {
+      function (err) {
         if (err != null) {
           OError.tag(err, 'error moving entity', {
             project_id,
@@ -534,73 +541,79 @@ const EditorController = {
 
   renameProject(project_id, newName, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
-    return ProjectDetailsHandler.renameProject(project_id, newName, function(
-      err
-    ) {
-      if (err != null) {
-        OError.tag(err, 'error renaming project', {
+    return ProjectDetailsHandler.renameProject(
+      project_id,
+      newName,
+      function (err) {
+        if (err != null) {
+          OError.tag(err, 'error renaming project', {
+            project_id,
+            newName
+          })
+          return callback(err)
+        }
+        EditorRealTimeController.emitToRoom(
           project_id,
+          'projectNameUpdated',
           newName
-        })
-        return callback(err)
+        )
+        return callback()
       }
-      EditorRealTimeController.emitToRoom(
-        project_id,
-        'projectNameUpdated',
-        newName
-      )
-      return callback()
-    })
+    )
   },
 
   setCompiler(project_id, compiler, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
-    return ProjectOptionsHandler.setCompiler(project_id, compiler, function(
-      err
-    ) {
-      if (err != null) {
-        return callback(err)
+    return ProjectOptionsHandler.setCompiler(
+      project_id,
+      compiler,
+      function (err) {
+        if (err != null) {
+          return callback(err)
+        }
+        EditorRealTimeController.emitToRoom(
+          project_id,
+          'compilerUpdated',
+          compiler
+        )
+        return callback()
       }
-      EditorRealTimeController.emitToRoom(
-        project_id,
-        'compilerUpdated',
-        compiler
-      )
-      return callback()
-    })
+    )
   },
 
   setImageName(project_id, imageName, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
-    return ProjectOptionsHandler.setImageName(project_id, imageName, function(
-      err
-    ) {
-      if (err != null) {
-        return callback(err)
+    return ProjectOptionsHandler.setImageName(
+      project_id,
+      imageName,
+      function (err) {
+        if (err != null) {
+          return callback(err)
+        }
+        EditorRealTimeController.emitToRoom(
+          project_id,
+          'imageNameUpdated',
+          imageName
+        )
+        return callback()
       }
-      EditorRealTimeController.emitToRoom(
-        project_id,
-        'imageNameUpdated',
-        imageName
-      )
-      return callback()
-    })
+    )
   },
 
   setSpellCheckLanguage(project_id, languageCode, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     return ProjectOptionsHandler.setSpellCheckLanguage(
       project_id,
       languageCode,
-      function(err) {
+      function (err) {
         if (err != null) {
           return callback(err)
         }
@@ -616,12 +629,12 @@ const EditorController = {
 
   setPublicAccessLevel(project_id, newAccessLevel, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     return ProjectDetailsHandler.setPublicAccessLevel(
       project_id,
       newAccessLevel,
-      function(err) {
+      function (err) {
         if (err != null) {
           return callback(err)
         }
@@ -633,7 +646,7 @@ const EditorController = {
         if (newAccessLevel === PublicAccessLevels.TOKEN_BASED) {
           return ProjectDetailsHandler.ensureTokensArePresent(
             project_id,
-            function(err, tokens) {
+            function (err, tokens) {
               if (err != null) {
                 return callback(err)
               }
@@ -654,12 +667,12 @@ const EditorController = {
 
   setRootDoc(project_id, newRootDocID, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     return ProjectEntityUpdateHandler.setRootDoc(
       project_id,
       newRootDocID,
-      function(err) {
+      function (err) {
         if (err != null) {
           return callback(err)
         }
@@ -675,7 +688,7 @@ const EditorController = {
 
   _notifyProjectUsersOfNewFolders(project_id, folders, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return async.eachSeries(
       folders,
@@ -699,7 +712,7 @@ const EditorController = {
     callback
   ) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     EditorRealTimeController.emitToRoom(
       project_id,

+ 39 - 38
services/web/app/src/Features/Exports/ExportsController.js

@@ -49,41 +49,41 @@ module.exports = {
       }
     }
 
-    return ExportsHandler.exportProject(export_params, function(
-      err,
-      export_data
-    ) {
-      if (err != null) {
-        if (err.forwardResponse != null) {
-          logger.log(
-            { responseError: err.forwardResponse },
-            'forwarding response'
-          )
-          const statusCode = err.forwardResponse.status || 500
-          return res.status(statusCode).json(err.forwardResponse)
-        } else {
-          return next(err)
+    return ExportsHandler.exportProject(
+      export_params,
+      function (err, export_data) {
+        if (err != null) {
+          if (err.forwardResponse != null) {
+            logger.log(
+              { responseError: err.forwardResponse },
+              'forwarding response'
+            )
+            const statusCode = err.forwardResponse.status || 500
+            return res.status(statusCode).json(err.forwardResponse)
+          } else {
+            return next(err)
+          }
         }
+        logger.log(
+          {
+            user_id,
+            project_id,
+            brand_variation_id,
+            export_v1_id: export_data.v1_id
+          },
+          'exported project'
+        )
+        return res.json({
+          export_v1_id: export_data.v1_id,
+          message: export_data.message
+        })
       }
-      logger.log(
-        {
-          user_id,
-          project_id,
-          brand_variation_id,
-          export_v1_id: export_data.v1_id
-        },
-        'exported project'
-      )
-      return res.json({
-        export_v1_id: export_data.v1_id,
-        message: export_data.message
-      })
-    })
+    )
   },
 
   exportStatus(req, res) {
     const { export_id } = req.params
-    return ExportsHandler.fetchExport(export_id, function(err, export_json) {
+    return ExportsHandler.fetchExport(export_id, function (err, export_json) {
       let json
       if (err != null) {
         json = {
@@ -112,15 +112,16 @@ module.exports = {
     const { type, export_id } = req.params
 
     AuthenticationController.getLoggedInUserId(req)
-    return ExportsHandler.fetchDownload(export_id, type, function(
-      err,
-      export_file_url
-    ) {
-      if (err != null) {
-        return next(err)
-      }
+    return ExportsHandler.fetchDownload(
+      export_id,
+      type,
+      function (err, export_file_url) {
+        if (err != null) {
+          return next(err)
+        }
 
-      return res.redirect(export_file_url)
-    })
+        return res.redirect(export_file_url)
+      }
+    )
   }
 }

+ 25 - 24
services/web/app/src/Features/Exports/ExportsHandler.js

@@ -30,13 +30,13 @@ settings = require('settings-sharelatex')
 module.exports = ExportsHandler = self = {
   exportProject(export_params, callback) {
     if (callback == null) {
-      callback = function(error, export_data) {}
+      callback = function (error, export_data) {}
     }
-    return self._buildExport(export_params, function(err, export_data) {
+    return self._buildExport(export_params, function (err, export_data) {
       if (err != null) {
         return callback(err)
       }
-      return self._requestExport(export_data, function(err, body) {
+      return self._requestExport(export_data, function (err, body) {
         if (err != null) {
           return callback(err)
         }
@@ -50,7 +50,7 @@ module.exports = ExportsHandler = self = {
 
   _buildExport(export_params, callback) {
     if (callback == null) {
-      callback = function(err, export_data) {}
+      callback = function (err, export_data) {}
     }
     const {
       project_id,
@@ -70,17 +70,18 @@ module.exports = ExportsHandler = self = {
       rootDoc: [
         'project',
         (cb, results) =>
-          ProjectRootDocManager.ensureRootDocumentIsValid(project_id, function(
-            error
-          ) {
-            if (error != null) {
-              return callback(error)
+          ProjectRootDocManager.ensureRootDocumentIsValid(
+            project_id,
+            function (error) {
+              if (error != null) {
+                return callback(error)
+              }
+              return ProjectLocator.findRootDoc(
+                { project: results.project, project_id },
+                cb
+              )
             }
-            return ProjectLocator.findRootDoc(
-              { project: results.project, project_id },
-              cb
-            )
-          })
+          )
       ],
       user(cb) {
         return UserGetter.getUser(
@@ -92,7 +93,7 @@ module.exports = ExportsHandler = self = {
       historyVersion(cb) {
         return ProjectHistoryHandler.ensureHistoryExistsForProject(
           project_id,
-          function(error) {
+          function (error) {
             if (error != null) {
               return callback(error)
             }
@@ -102,7 +103,7 @@ module.exports = ExportsHandler = self = {
       }
     }
 
-    return async.auto(jobs, function(err, results) {
+    return async.auto(jobs, function (err, results) {
       if (err != null) {
         OError.tag(err, 'error building project export', {
           project_id,
@@ -167,7 +168,7 @@ module.exports = ExportsHandler = self = {
 
   _requestExport(export_data, callback) {
     if (callback == null) {
-      callback = function(err, export_v1_id) {}
+      callback = function (err, export_v1_id) {}
     }
     return request.post(
       {
@@ -175,7 +176,7 @@ module.exports = ExportsHandler = self = {
         auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
         json: export_data
       },
-      function(err, res, body) {
+      function (err, res, body) {
         if (err != null) {
           OError.tag(err, 'error making request to v1 export', {
             export: export_data
@@ -197,14 +198,14 @@ module.exports = ExportsHandler = self = {
 
   _requestVersion(project_id, callback) {
     if (callback == null) {
-      callback = function(err, export_v1_id) {}
+      callback = function (err, export_v1_id) {}
     }
     return request.get(
       {
         url: `${settings.apis.project_history.url}/project/${project_id}/version`,
         json: true
       },
-      function(err, res, body) {
+      function (err, res, body) {
         if (err != null) {
           OError.tag(err, 'error making request to project history', {
             project_id
@@ -225,14 +226,14 @@ module.exports = ExportsHandler = self = {
 
   fetchExport(export_id, callback) {
     if (callback == null) {
-      callback = function(err, export_json) {}
+      callback = function (err, export_json) {}
     }
     return request.get(
       {
         url: `${settings.apis.v1.url}/api/v1/sharelatex/exports/${export_id}`,
         auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass }
       },
-      function(err, res, body) {
+      function (err, res, body) {
         if (err != null) {
           OError.tag(err, 'error making request to v1 export', {
             export: export_id
@@ -253,14 +254,14 @@ module.exports = ExportsHandler = self = {
 
   fetchDownload(export_id, type, callback) {
     if (callback == null) {
-      callback = function(err, file_url) {}
+      callback = function (err, file_url) {}
     }
     return request.get(
       {
         url: `${settings.apis.v1.url}/api/v1/sharelatex/exports/${export_id}/${type}_url`,
         auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass }
       },
-      function(err, res, body) {
+      function (err, res, body) {
         if (err != null) {
           OError.tag(err, 'error making request to v1 export', {
             export: export_id

+ 5 - 5
services/web/app/src/Features/FileStore/FileHashManager.js

@@ -20,7 +20,7 @@ const _ = require('underscore')
 module.exports = FileHashManager = {
   computeHash(filePath, callback) {
     if (callback == null) {
-      callback = function(error, hashValue) {}
+      callback = function (error, hashValue) {}
     }
     callback = _.once(callback) // avoid double callbacks
 
@@ -28,20 +28,20 @@ module.exports = FileHashManager = {
     const getGitBlobHeader = byteLength => `blob ${byteLength}` + '\x00'
 
     const getByteLengthOfFile = cb =>
-      fs.stat(filePath, function(err, stats) {
+      fs.stat(filePath, function (err, stats) {
         if (err != null) {
           return cb(err)
         }
         return cb(null, stats.size)
       })
 
-    return getByteLengthOfFile(function(err, byteLength) {
+    return getByteLengthOfFile(function (err, byteLength) {
       if (err != null) {
         return callback(err)
       }
 
       const input = fs.createReadStream(filePath)
-      input.on('error', function(err) {
+      input.on('error', function (err) {
         logger.warn({ filePath, err }, 'error opening file in computeHash')
         return callback(err)
       })
@@ -49,7 +49,7 @@ module.exports = FileHashManager = {
       const hash = crypto.createHash('sha1')
       hash.setEncoding('hex')
       hash.update(getGitBlobHeader(byteLength))
-      hash.on('readable', function() {
+      hash.on('readable', function () {
         const result = hash.read()
         if (result != null) {
           return callback(null, result.toString('hex'))

+ 20 - 18
services/web/app/src/Features/FileStore/FileStoreController.js

@@ -12,7 +12,7 @@ module.exports = {
     const userAgent = req.get('User-Agent')
     ProjectLocator.findElement(
       { project_id: projectId, element_id: fileId, type: 'file' },
-      function(err, file) {
+      function (err, file) {
         if (err) {
           logger.err(
             { err, projectId, fileId, queryString },
@@ -20,24 +20,26 @@ module.exports = {
           )
           return res.sendStatus(500)
         }
-        FileStoreHandler.getFileStream(projectId, fileId, queryString, function(
-          err,
-          stream
-        ) {
-          if (err) {
-            logger.err(
-              { err, projectId, fileId, queryString },
-              'error getting file stream for downloading file'
-            )
-            return res.sendStatus(500)
+        FileStoreHandler.getFileStream(
+          projectId,
+          fileId,
+          queryString,
+          function (err, stream) {
+            if (err) {
+              logger.err(
+                { err, projectId, fileId, queryString },
+                'error getting file stream for downloading file'
+              )
+              return res.sendStatus(500)
+            }
+            // mobile safari will try to render html files, prevent this
+            if (isMobileSafari(userAgent) && isHtml(file)) {
+              res.setHeader('Content-Type', 'text/plain')
+            }
+            res.setContentDisposition('attachment', { filename: file.name })
+            stream.pipe(res)
           }
-          // mobile safari will try to render html files, prevent this
-          if (isMobileSafari(userAgent) && isHtml(file)) {
-            res.setHeader('Content-Type', 'text/plain')
-          }
-          res.setContentDisposition('attachment', { filename: file.name })
-          stream.pipe(res)
-        })
+        )
       }
     )
   },

+ 9 - 9
services/web/app/src/Features/FileStore/FileStoreHandler.js

@@ -17,7 +17,7 @@ const FileStoreHandler = {
   RETRY_ATTEMPTS: 3,
 
   uploadFileFromDisk(projectId, fileArgs, fsPath, callback) {
-    fs.lstat(fsPath, function(err, stat) {
+    fs.lstat(fsPath, function (err, stat) {
       if (err) {
         logger.warn({ err, projectId, fileArgs, fsPath }, 'error stating file')
         callback(err)
@@ -45,7 +45,7 @@ const FileStoreHandler = {
             fsPath,
             cb
           ),
-        function(err, result) {
+        function (err, result) {
           if (err) {
             OError.tag(err, 'Error uploading file, retries failed', {
               projectId,
@@ -62,21 +62,21 @@ const FileStoreHandler = {
   _doUploadFileFromDisk(projectId, fileArgs, fsPath, callback) {
     const callbackOnce = _.once(callback)
 
-    FileHashManager.computeHash(fsPath, function(err, hashValue) {
+    FileHashManager.computeHash(fsPath, function (err, hashValue) {
       if (err) {
         return callbackOnce(err)
       }
       const fileRef = new File(Object.assign({}, fileArgs, { hash: hashValue }))
       const fileId = fileRef._id
       const readStream = fs.createReadStream(fsPath)
-      readStream.on('error', function(err) {
+      readStream.on('error', function (err) {
         logger.warn(
           { err, projectId, fileId, fsPath },
           'something went wrong on the read stream of uploadFileFromDisk'
         )
         callbackOnce(err)
       })
-      readStream.on('open', function() {
+      readStream.on('open', function () {
         const url = FileStoreHandler._buildUrl(projectId, fileId)
         const opts = {
           method: 'post',
@@ -87,14 +87,14 @@ const FileStoreHandler = {
           } // send the hash to the filestore as a custom header so it can be checked
         }
         const writeStream = request(opts)
-        writeStream.on('error', function(err) {
+        writeStream.on('error', function (err) {
           logger.warn(
             { err, projectId, fileId, fsPath },
             'something went wrong on the write stream of uploadFileFromDisk'
           )
           callbackOnce(err)
         })
-        writeStream.on('response', function(response) {
+        writeStream.on('response', function (response) {
           if (![200, 201].includes(response.statusCode)) {
             err = new OError(
               `non-ok response from filestore for upload: ${response.statusCode}`,
@@ -168,7 +168,7 @@ const FileStoreHandler = {
       uri: this._buildUrl(projectId, fileId),
       timeout: FIVE_MINS_IN_MS
     }
-    return request(opts, function(err, response) {
+    return request(opts, function (err, response) {
       if (err) {
         logger.warn(
           { err, projectId, fileId },
@@ -217,7 +217,7 @@ const FileStoreHandler = {
       uri: this._buildUrl(newProjectId, newFileId),
       timeout: FIVE_MINS_IN_MS
     }
-    return request(opts, function(err, response) {
+    return request(opts, function (err, response) {
       if (err) {
         OError.tag(
           err,

+ 17 - 17
services/web/app/src/Features/HealthCheck/HealthCheckController.js

@@ -61,7 +61,7 @@ module.exports = {
   },
 
   checkRedis(req, res, next) {
-    return rclient.healthCheck(function(error) {
+    return rclient.healthCheck(function (error) {
       if (error != null) {
         logger.err({ err: error }, 'failed redis health check')
         return res.sendStatus(500)
@@ -72,23 +72,23 @@ module.exports = {
   },
 
   checkMongo(req, res, next) {
-    return UserGetter.getUserEmail(settings.smokeTest.userId, function(
-      err,
-      email
-    ) {
-      if (err != null) {
-        logger.err({ err }, 'mongo health check failed, error present')
-        return res.sendStatus(500)
-      } else if (email == null) {
-        logger.err(
-          { err },
-          'mongo health check failed, no emai present in find result'
-        )
-        return res.sendStatus(500)
-      } else {
-        return res.sendStatus(200)
+    return UserGetter.getUserEmail(
+      settings.smokeTest.userId,
+      function (err, email) {
+        if (err != null) {
+          logger.err({ err }, 'mongo health check failed, error present')
+          return res.sendStatus(500)
+        } else if (email == null) {
+          logger.err(
+            { err },
+            'mongo health check failed, no emai present in find result'
+          )
+          return res.sendStatus(500)
+        } else {
+          return res.sendStatus(200)
+        }
       }
-    })
+    )
   }
 }
 

+ 17 - 17
services/web/app/src/Features/History/HistoryController.js

@@ -17,7 +17,7 @@ module.exports = HistoryController = {
   selectHistoryApi(req, res, next) {
     const { Project_id: projectId } = req.params
     // find out which type of history service this project uses
-    ProjectDetailsHandler.getDetails(projectId, function(err, project) {
+    ProjectDetailsHandler.getDetails(projectId, function (err, project) {
       if (err) {
         return next(err)
       }
@@ -52,7 +52,7 @@ module.exports = HistoryController = {
       }
     })
     getReq.pipe(res)
-    getReq.on('error', function(err) {
+    getReq.on('error', function (err) {
       logger.warn({ url, err }, 'history API error')
       next(err)
     })
@@ -71,11 +71,11 @@ module.exports = HistoryController = {
           'X-User-Id': userId
         }
       },
-      function(err, body) {
+      function (err, body) {
         if (err) {
           return next(err)
         }
-        HistoryManager.injectUserDetails(body, function(err, data) {
+        HistoryManager.injectUserDetails(body, function (err, data) {
           if (err) {
             return next(err)
           }
@@ -97,7 +97,7 @@ module.exports = HistoryController = {
 
   resyncProjectHistory(req, res, next) {
     const projectId = req.params.Project_id
-    ProjectEntityUpdateHandler.resyncProjectHistory(projectId, function(err) {
+    ProjectEntityUpdateHandler.resyncProjectHistory(projectId, function (err) {
       if (err instanceof Errors.ProjectHistoryDisabledError) {
         return res.sendStatus(404)
       }
@@ -117,7 +117,7 @@ module.exports = HistoryController = {
       projectId,
       version,
       pathname,
-      function(err, entity) {
+      function (err, entity) {
         if (err) {
           return next(err)
         }
@@ -158,7 +158,7 @@ module.exports = HistoryController = {
         url: `${settings.apis.project_history.url}/project/${projectId}/labels`,
         json: true
       },
-      function(err, labels) {
+      function (err, labels) {
         if (err) {
           return next(err)
         }
@@ -182,7 +182,7 @@ module.exports = HistoryController = {
         url: `${settings.apis.project_history.url}/project/${projectId}/user/${userId}/labels`,
         json: { comment, version }
       },
-      function(err, label) {
+      function (err, label) {
         if (err) {
           return next(err)
         }
@@ -230,7 +230,7 @@ module.exports = HistoryController = {
     UserGetter.getUsers(
       Array.from(uniqueUsers),
       { first_name: 1, last_name: 1, email: 1 },
-      function(err, rawUsers) {
+      function (err, rawUsers) {
         if (err) {
           return callback(err)
         }
@@ -274,7 +274,7 @@ module.exports = HistoryController = {
         method: 'DELETE',
         url: `${settings.apis.project_history.url}/project/${projectId}/user/${userId}/labels/${labelId}`
       },
-      function(err) {
+      function (err) {
         if (err) {
           return next(err)
         }
@@ -284,7 +284,7 @@ module.exports = HistoryController = {
   },
 
   _makeRequest(options, callback) {
-    return request(options, function(err, response, body) {
+    return request(options, function (err, response, body) {
       if (err) {
         return callback(err)
       }
@@ -301,7 +301,7 @@ module.exports = HistoryController = {
 
   downloadZipOfVersion(req, res, next) {
     const { project_id: projectId, version } = req.params
-    ProjectDetailsHandler.getDetails(projectId, function(err, project) {
+    ProjectDetailsHandler.getDetails(projectId, function (err, project) {
       if (err) {
         return next(err)
       }
@@ -344,7 +344,7 @@ module.exports = HistoryController = {
       method: 'post',
       url
     }
-    request(options, function(err, response, body) {
+    request(options, function (err, response, body) {
       if (err) {
         OError.tag(err, 'history API error', {
           v1ProjectId,
@@ -362,7 +362,7 @@ module.exports = HistoryController = {
       async.retry(
         40,
         callback =>
-          setTimeout(function() {
+          setTimeout(function () {
             if (req.aborted) {
               // client has disconnected -- skip s3 download
               return callback() // stop async.retry loop
@@ -384,7 +384,7 @@ module.exports = HistoryController = {
               req.off('aborted', abortS3Request)
               res.off('timeout', abortS3Request)
             }
-            getReq.on('response', function(response) {
+            getReq.on('response', function (response) {
               if (response.statusCode !== 200) {
                 cleanupAbortTrigger()
                 return callback(new Error('invalid response'))
@@ -407,7 +407,7 @@ module.exports = HistoryController = {
               })
               callback()
             })
-            getReq.on('error', function(err) {
+            getReq.on('error', function (err) {
               logger.warn(
                 { err, v1ProjectId, version, retryAttempt },
                 'history s3 download error'
@@ -416,7 +416,7 @@ module.exports = HistoryController = {
               callback(err)
             })
           }, retryDelay),
-        function(err) {
+        function (err) {
           if (err) {
             OError.tag(err, 'history s3 download failed', {
               v1ProjectId,

+ 39 - 38
services/web/app/src/Features/History/RestoreManager.js

@@ -28,13 +28,13 @@ module.exports = RestoreManager = {
     // It looks up the deleted doc's contents, and then creates a new doc with the same content.
     // We don't actually remove the deleted doc entry, just create a new one from its lines.
     if (callback == null) {
-      callback = function(error, doc, folder_id) {}
+      callback = function (error, doc, folder_id) {}
     }
     return ProjectEntityHandler.getDoc(
       project_id,
       doc_id,
       { include_deleted: true },
-      function(error, lines) {
+      function (error, lines) {
         if (error != null) {
           return callback(error)
         }
@@ -59,13 +59,13 @@ module.exports = RestoreManager = {
 
   restoreFileFromV2(user_id, project_id, version, pathname, callback) {
     if (callback == null) {
-      callback = function(error, entity) {}
+      callback = function (error, entity) {}
     }
     return RestoreManager._writeFileVersionToDisk(
       project_id,
       version,
       pathname,
-      function(error, fsPath) {
+      function (error, fsPath) {
         if (error != null) {
           return callback(error)
         }
@@ -75,54 +75,55 @@ module.exports = RestoreManager = {
           // no directory
           dirname = ''
         }
-        return RestoreManager._findOrCreateFolder(project_id, dirname, function(
-          error,
-          parent_folder_id
-        ) {
-          if (error != null) {
-            return callback(error)
-          }
-          const addEntityWithName = (name, callback) =>
-            FileSystemImportManager.addEntity(
-              user_id,
-              project_id,
-              parent_folder_id,
-              name,
-              fsPath,
-              false,
+        return RestoreManager._findOrCreateFolder(
+          project_id,
+          dirname,
+          function (error, parent_folder_id) {
+            if (error != null) {
+              return callback(error)
+            }
+            const addEntityWithName = (name, callback) =>
+              FileSystemImportManager.addEntity(
+                user_id,
+                project_id,
+                parent_folder_id,
+                name,
+                fsPath,
+                false,
+                callback
+              )
+            return RestoreManager._addEntityWithUniqueName(
+              addEntityWithName,
+              basename,
               callback
             )
-          return RestoreManager._addEntityWithUniqueName(
-            addEntityWithName,
-            basename,
-            callback
-          )
-        })
+          }
+        )
       }
     )
   },
 
   _findOrCreateFolder(project_id, dirname, callback) {
     if (callback == null) {
-      callback = function(error, folder_id) {}
+      callback = function (error, folder_id) {}
     }
-    return EditorController.mkdirp(project_id, dirname, function(
-      error,
-      newFolders,
-      lastFolder
-    ) {
-      if (error != null) {
-        return callback(error)
+    return EditorController.mkdirp(
+      project_id,
+      dirname,
+      function (error, newFolders, lastFolder) {
+        if (error != null) {
+          return callback(error)
+        }
+        return callback(null, lastFolder != null ? lastFolder._id : undefined)
       }
-      return callback(null, lastFolder != null ? lastFolder._id : undefined)
-    })
+    )
   },
 
   _addEntityWithUniqueName(addEntityWithName, basename, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return addEntityWithName(basename, function(error, entity) {
+    return addEntityWithName(basename, function (error, entity) {
       if (error != null) {
         if (error instanceof Errors.InvalidNameError) {
           // likely a duplicate name, so try with a prefix
@@ -146,7 +147,7 @@ module.exports = RestoreManager = {
 
   _writeFileVersionToDisk(project_id, version, pathname, callback) {
     if (callback == null) {
-      callback = function(error, fsPath) {}
+      callback = function (error, fsPath) {}
     }
     const url = `${
       Settings.apis.project_history.url

+ 2 - 2
services/web/app/src/Features/InactiveData/InactiveProjectController.js

@@ -22,7 +22,7 @@ module.exports = {
     return InactiveProjectManager.deactivateOldProjects(
       numberOfProjectsToArchive,
       ageOfProjects,
-      function(err, projectsDeactivated) {
+      function (err, projectsDeactivated) {
         if (err != null) {
           return res.sendStatus(500)
         } else {
@@ -34,7 +34,7 @@ module.exports = {
 
   deactivateProject(req, res) {
     const { project_id } = req.params
-    return InactiveProjectManager.deactivateProject(project_id, function(err) {
+    return InactiveProjectManager.deactivateProject(project_id, function (err) {
       if (err != null) {
         return res.sendStatus(500)
       } else {

+ 29 - 28
services/web/app/src/Features/InactiveData/InactiveProjectManager.js

@@ -24,35 +24,36 @@ const { ObjectId } = require('mongodb')
 const MILISECONDS_IN_DAY = 86400000
 module.exports = InactiveProjectManager = {
   reactivateProjectIfRequired(project_id, callback) {
-    return ProjectGetter.getProject(project_id, { active: true }, function(
-      err,
-      project
-    ) {
-      if (err != null) {
-        OError.tag(err, 'error getting project', {
-          project_id
-        })
-        return callback(err)
-      }
-      logger.log(
-        { project_id, active: project.active },
-        'seeing if need to reactivate project'
-      )
-
-      if (project.active) {
-        return callback()
-      }
-
-      return DocstoreManager.unarchiveProject(project_id, function(err) {
+    return ProjectGetter.getProject(
+      project_id,
+      { active: true },
+      function (err, project) {
         if (err != null) {
-          OError.tag(err, 'error reactivating project in docstore', {
+          OError.tag(err, 'error getting project', {
             project_id
           })
           return callback(err)
         }
-        return ProjectUpdateHandler.markAsActive(project_id, callback)
-      })
-    })
+        logger.log(
+          { project_id, active: project.active },
+          'seeing if need to reactivate project'
+        )
+
+        if (project.active) {
+          return callback()
+        }
+
+        return DocstoreManager.unarchiveProject(project_id, function (err) {
+          if (err != null) {
+            OError.tag(err, 'error reactivating project in docstore', {
+              project_id
+            })
+            return callback(err)
+          }
+          return ProjectUpdateHandler.markAsActive(project_id, callback)
+        })
+      }
+    )
   },
 
   deactivateOldProjects(limit, daysOld, callback) {
@@ -73,12 +74,12 @@ module.exports = InactiveProjectManager = {
       .sort({ _id: 1 })
       .limit(limit)
       .read('secondary')
-      .exec(function(err, projects) {
+      .exec(function (err, projects) {
         if (err != null) {
           logger.err({ err }, 'could not get projects for deactivating')
         }
         const jobs = _.map(projects, project => cb =>
-          InactiveProjectManager.deactivateProject(project._id, function(err) {
+          InactiveProjectManager.deactivateProject(project._id, function (err) {
             if (err) {
               logger.err(
                 { project_id: project._id, err: err },
@@ -92,7 +93,7 @@ module.exports = InactiveProjectManager = {
           { numberOfProjects: projects && projects.length },
           'deactivating projects'
         )
-        async.series(jobs, function(err) {
+        async.series(jobs, function (err) {
           if (err != null) {
             logger.warn({ err }, 'error deactivating projects')
           }
@@ -107,7 +108,7 @@ module.exports = InactiveProjectManager = {
       cb => DocstoreManager.archiveProject(project_id, cb),
       cb => ProjectUpdateHandler.markAsInactive(project_id, cb)
     ]
-    return async.series(jobs, function(err) {
+    return async.series(jobs, function (err) {
       if (err != null) {
         logger.warn({ err, project_id }, 'error deactivating project')
       }

+ 4 - 4
services/web/app/src/Features/Institutions/InstitutionsAPI.js

@@ -99,7 +99,7 @@ const InstitutionsAPI = {
         },
         defaultErrorMessage: "Couldn't create affiliation"
       },
-      function(error, body) {
+      function (error, body) {
         if (error) {
           if (error.info && error.info.statusCode === 422) {
             return callback(
@@ -115,7 +115,7 @@ const InstitutionsAPI = {
         // have notifications delete any ip matcher notifications for this university
         NotificationsBuilder.ipMatcherAffiliation(userId).read(
           university.id,
-          function(err) {
+          function (err) {
             if (err) {
               // log and ignore error
               logger.err(
@@ -191,7 +191,7 @@ const InstitutionsAPI = {
   }
 }
 
-var makeAffiliationRequest = function(requestOptions, callback) {
+var makeAffiliationRequest = function (requestOptions, callback) {
   if (!settings.apis.v1.url) {
     return callback(null)
   } // service is not configured
@@ -207,7 +207,7 @@ var makeAffiliationRequest = function(requestOptions, callback) {
       json: true,
       timeout: 20 * 1000
     },
-    function(error, response, body) {
+    function (error, response, body) {
       if (error) {
         return callback(
           new V1ConnectionError('error getting affiliations from v1').withCause(

+ 5 - 9
services/web/app/src/Features/Institutions/InstitutionsController.js

@@ -8,7 +8,7 @@ const ASYNC_AFFILIATIONS_LIMIT = 10
 module.exports = {
   confirmDomain(req, res, next) {
     const { hostname } = req.body
-    affiliateUsers(hostname, function(error) {
+    affiliateUsers(hostname, function (error) {
       if (error) {
         return next(error)
       }
@@ -17,13 +17,9 @@ module.exports = {
   }
 }
 
-var affiliateUsers = function(hostname, callback) {
-  const reversedHostname = hostname
-    .trim()
-    .split('')
-    .reverse()
-    .join('')
-  UserGetter.getUsersByHostname(hostname, { _id: 1 }, function(error, users) {
+var affiliateUsers = function (hostname, callback) {
+  const reversedHostname = hostname.trim().split('').reverse().join('')
+  UserGetter.getUsersByHostname(hostname, { _id: 1 }, function (error, users) {
     if (error) {
       OError.tag(error, 'problem fetching users by hostname')
       return callback(error)
@@ -44,7 +40,7 @@ var affiliateUsers = function(hostname, callback) {
   })
 }
 
-var affiliateUserByReversedHostname = function(
+var affiliateUserByReversedHostname = function (
   user,
   reversedHostname,
   callback

+ 12 - 9
services/web/app/src/Features/Institutions/InstitutionsFeatures.js

@@ -5,18 +5,21 @@ const Settings = require('settings-sharelatex')
 
 module.exports = InstitutionsFeatures = {
   getInstitutionsFeatures(userId, callback) {
-    InstitutionsFeatures.getInstitutionsPlan(userId, function(error, planCode) {
-      if (error) {
-        return callback(error)
+    InstitutionsFeatures.getInstitutionsPlan(
+      userId,
+      function (error, planCode) {
+        if (error) {
+          return callback(error)
+        }
+        const plan = planCode && PlansLocator.findLocalPlanInSettings(planCode)
+        const features = plan && plan.features
+        callback(null, features || {})
       }
-      const plan = planCode && PlansLocator.findLocalPlanInSettings(planCode)
-      const features = plan && plan.features
-      callback(null, features || {})
-    })
+    )
   },
 
   getInstitutionsPlan(userId, callback) {
-    InstitutionsFeatures.hasLicence(userId, function(error, hasLicence) {
+    InstitutionsFeatures.hasLicence(userId, function (error, hasLicence) {
       if (error) {
         return callback(error)
       }
@@ -28,7 +31,7 @@ module.exports = InstitutionsFeatures = {
   },
 
   hasLicence(userId, callback) {
-    UserGetter.getUserFullEmails(userId, function(error, emailsData) {
+    UserGetter.getUserFullEmails(userId, function (error, emailsData) {
       if (error) {
         return callback(error)
       }

+ 1 - 1
services/web/app/src/Features/Institutions/InstitutionsGetter.js

@@ -5,7 +5,7 @@ const UserMembershipEntityConfigs = require('../UserMembership/UserMembershipEnt
 
 module.exports = InstitutionsGetter = {
   getConfirmedAffiliations(userId, callback) {
-    UserGetter.getUserFullEmails(userId, function(error, emailsData) {
+    UserGetter.getUserFullEmails(userId, function (error, emailsData) {
       if (error) {
         return callback(error)
       }

+ 9 - 9
services/web/app/src/Features/Institutions/InstitutionsManager.js

@@ -18,8 +18,8 @@ module.exports = {
     async.waterfall(
       [
         cb => fetchInstitutionAndAffiliations(institutionId, cb),
-        function(institution, affiliations, cb) {
-          affiliations = _.map(affiliations, function(affiliation) {
+        function (institution, affiliations, cb) {
+          affiliations = _.map(affiliations, function (affiliation) {
             affiliation.institutionName = institution.name
             affiliation.institutionId = institutionId
             return affiliation
@@ -50,7 +50,7 @@ module.exports = {
   },
 
   getInstitutionUsersSubscriptions(institutionId, callback) {
-    getInstitutionAffiliations(institutionId, function(error, affiliations) {
+    getInstitutionAffiliations(institutionId, function (error, affiliations) {
       if (error) {
         return callback(error)
       }
@@ -84,12 +84,12 @@ var fetchInstitutionAndAffiliations = (institutionId, callback) =>
     callback
   )
 
-var refreshFeatures = function(affiliation, callback) {
+var refreshFeatures = function (affiliation, callback) {
   const userId = ObjectId(affiliation.user_id)
   FeaturesUpdater.refreshFeatures(userId, callback)
 }
 
-var refreshFeaturesAndNotify = function(affiliation, callback) {
+var refreshFeaturesAndNotify = function (affiliation, callback) {
   const userId = ObjectId(affiliation.user_id)
   async.waterfall(
     [
@@ -124,7 +124,7 @@ var getUserInfo = (userId, callback) =>
 var notifyUser = (user, affiliation, subscription, featuresChanged, callback) =>
   async.parallel(
     [
-      function(cb) {
+      function (cb) {
         if (featuresChanged) {
           NotificationsBuilder.featuresUpgradedByAffiliation(
             affiliation,
@@ -134,7 +134,7 @@ var notifyUser = (user, affiliation, subscription, featuresChanged, callback) =>
           cb()
         }
       },
-      function(cb) {
+      function (cb) {
         if (
           subscription &&
           !subscription.planCode.match(/(free|trial)/) &&
@@ -152,7 +152,7 @@ var notifyUser = (user, affiliation, subscription, featuresChanged, callback) =>
     callback
   )
 
-var checkFeatures = function(institutionId, users) {
+var checkFeatures = function (institutionId, users) {
   const usersSummary = {
     confirmedEmailUsers: {
       total: users.length, // all users are confirmed email users
@@ -167,7 +167,7 @@ var checkFeatures = function(institutionId, users) {
       nonProUsers: []
     }
   }
-  users.forEach(function(user) {
+  users.forEach(function (user) {
     let isSSOEntitled = SAMLIdentityManager.userHasEntitlement(
       user,
       institutionId

+ 39 - 40
services/web/app/src/Features/LinkedFiles/LinkedFilesController.js

@@ -75,7 +75,7 @@ module.exports = LinkedFilesController = {
       name,
       parent_folder_id,
       user_id,
-      function(err, newFileId) {
+      function (err, newFileId) {
         if (err != null) {
           return LinkedFilesController.handleError(err, req, res, next)
         }
@@ -88,47 +88,46 @@ module.exports = LinkedFilesController = {
     const { project_id, file_id } = req.params
     const user_id = AuthenticationController.getLoggedInUserId(req)
 
-    return LinkedFilesHandler.getFileById(project_id, file_id, function(
-      err,
-      file,
-      path,
-      parentFolder
-    ) {
-      if (err != null) {
-        return next(err)
-      }
-      if (file == null) {
-        return res.sendStatus(404)
-      }
-      const { name } = file
-      const { linkedFileData } = file
-      if (
-        linkedFileData == null ||
-        (linkedFileData != null ? linkedFileData.provider : undefined) == null
-      ) {
-        return res.sendStatus(409)
-      }
-      const { provider } = linkedFileData
-      const parent_folder_id = parentFolder._id
-      const Agent = LinkedFilesController._getAgent(provider)
-      if (Agent == null) {
-        return res.sendStatus(400)
-      }
+    return LinkedFilesHandler.getFileById(
+      project_id,
+      file_id,
+      function (err, file, path, parentFolder) {
+        if (err != null) {
+          return next(err)
+        }
+        if (file == null) {
+          return res.sendStatus(404)
+        }
+        const { name } = file
+        const { linkedFileData } = file
+        if (
+          linkedFileData == null ||
+          (linkedFileData != null ? linkedFileData.provider : undefined) == null
+        ) {
+          return res.sendStatus(409)
+        }
+        const { provider } = linkedFileData
+        const parent_folder_id = parentFolder._id
+        const Agent = LinkedFilesController._getAgent(provider)
+        if (Agent == null) {
+          return res.sendStatus(400)
+        }
 
-      return Agent.refreshLinkedFile(
-        project_id,
-        linkedFileData,
-        name,
-        parent_folder_id,
-        user_id,
-        function(err, newFileId) {
-          if (err != null) {
-            return LinkedFilesController.handleError(err, req, res, next)
+        return Agent.refreshLinkedFile(
+          project_id,
+          linkedFileData,
+          name,
+          parent_folder_id,
+          user_id,
+          function (err, newFileId) {
+            if (err != null) {
+              return LinkedFilesController.handleError(err, req, res, next)
+            }
+            return res.json({ new_file_id: newFileId })
           }
-          return res.json({ new_file_id: newFileId })
-        }
-      )
-    })
+        )
+      }
+    )
   },
 
   handleError(error, req, res, next) {

+ 53 - 51
services/web/app/src/Features/LinkedFiles/LinkedFilesHandler.js

@@ -28,7 +28,7 @@ const {
 module.exports = LinkedFilesHandler = {
   getFileById(project_id, file_id, callback) {
     if (callback == null) {
-      callback = function(err, file) {}
+      callback = function (err, file) {}
     }
     return ProjectLocator.findElement(
       {
@@ -36,7 +36,7 @@ module.exports = LinkedFilesHandler = {
         element_id: file_id,
         type: 'file'
       },
-      function(err, file, path, parentFolder) {
+      function (err, file, path, parentFolder) {
         if (err != null) {
           return callback(err)
         }
@@ -47,14 +47,14 @@ module.exports = LinkedFilesHandler = {
 
   getSourceProject(data, callback) {
     if (callback == null) {
-      callback = function(err, project) {}
+      callback = function (err, project) {}
     }
     const projection = { _id: 1, name: 1 }
     if (data.v1_source_doc_id != null) {
       return Project.findOne(
         { 'overleaf.id': data.v1_source_doc_id },
         projection,
-        function(err, project) {
+        function (err, project) {
           if (err != null) {
             return callback(err)
           }
@@ -68,7 +68,7 @@ module.exports = LinkedFilesHandler = {
       return ProjectGetter.getProject(
         data.source_project_id,
         projection,
-        function(err, project) {
+        function (err, project) {
           if (err != null) {
             return callback(err)
           }
@@ -93,32 +93,33 @@ module.exports = LinkedFilesHandler = {
     callback
   ) {
     if (callback == null) {
-      callback = function(err, file) {}
+      callback = function (err, file) {}
     }
     callback = _.once(callback)
-    return FileWriter.writeStreamToDisk(project_id, readStream, function(
-      err,
-      fsPath
-    ) {
-      if (err != null) {
-        return callback(err)
-      }
-      return EditorController.upsertFile(
-        project_id,
-        parent_folder_id,
-        name,
-        fsPath,
-        linkedFileData,
-        'upload',
-        user_id,
-        (err, file) => {
-          if (err != null) {
-            return callback(err)
-          }
-          return callback(null, file)
+    return FileWriter.writeStreamToDisk(
+      project_id,
+      readStream,
+      function (err, fsPath) {
+        if (err != null) {
+          return callback(err)
         }
-      )
-    })
+        return EditorController.upsertFile(
+          project_id,
+          parent_folder_id,
+          name,
+          fsPath,
+          linkedFileData,
+          'upload',
+          user_id,
+          (err, file) => {
+            if (err != null) {
+              return callback(err)
+            }
+            return callback(null, file)
+          }
+        )
+      }
+    )
   },
 
   importContent(
@@ -131,31 +132,32 @@ module.exports = LinkedFilesHandler = {
     callback
   ) {
     if (callback == null) {
-      callback = function(err, file) {}
+      callback = function (err, file) {}
     }
     callback = _.once(callback)
-    return FileWriter.writeContentToDisk(project_id, content, function(
-      err,
-      fsPath
-    ) {
-      if (err != null) {
-        return callback(err)
-      }
-      return EditorController.upsertFile(
-        project_id,
-        parent_folder_id,
-        name,
-        fsPath,
-        linkedFileData,
-        'upload',
-        user_id,
-        (err, file) => {
-          if (err != null) {
-            return callback(err)
-          }
-          return callback(null, file)
+    return FileWriter.writeContentToDisk(
+      project_id,
+      content,
+      function (err, fsPath) {
+        if (err != null) {
+          return callback(err)
         }
-      )
-    })
+        return EditorController.upsertFile(
+          project_id,
+          parent_folder_id,
+          name,
+          fsPath,
+          linkedFileData,
+          'upload',
+          user_id,
+          (err, file) => {
+            if (err != null) {
+              return callback(err)
+            }
+            return callback(null, file)
+          }
+        )
+      }
+    )
   }
 }

+ 12 - 12
services/web/app/src/Features/LinkedFiles/ProjectFileAgent.js

@@ -73,7 +73,7 @@ module.exports = ProjectFileAgent = {
 
   _prepare(project_id, linkedFileData, user_id, callback) {
     if (callback == null) {
-      callback = function(err, linkedFileData) {}
+      callback = function (err, linkedFileData) {}
     }
     return this._checkAuth(
       project_id,
@@ -118,7 +118,7 @@ module.exports = ProjectFileAgent = {
               return DocstoreManager.getDoc(
                 source_project._id,
                 entity._id,
-                function(err, lines) {
+                function (err, lines) {
                   if (err != null) {
                     return callback(err)
                   }
@@ -129,7 +129,7 @@ module.exports = ProjectFileAgent = {
                     name,
                     parent_folder_id,
                     user_id,
-                    function(err, file) {
+                    function (err, file) {
                       if (err != null) {
                         return callback(err)
                       }
@@ -143,7 +143,7 @@ module.exports = ProjectFileAgent = {
                 source_project._id,
                 entity._id,
                 null,
-                function(err, fileStream) {
+                function (err, fileStream) {
                   if (err != null) {
                     return callback(err)
                   }
@@ -154,7 +154,7 @@ module.exports = ProjectFileAgent = {
                     name,
                     parent_folder_id,
                     user_id,
-                    function(err, file) {
+                    function (err, file) {
                       if (err != null) {
                         return callback(err)
                       }
@@ -174,18 +174,18 @@ module.exports = ProjectFileAgent = {
 
   _getEntity(linkedFileData, current_user_id, callback) {
     if (callback == null) {
-      callback = function(err, entity, type) {}
+      callback = function (err, entity, type) {}
     }
     callback = _.once(callback)
     const { source_entity_path } = linkedFileData
-    return this._getSourceProject(linkedFileData, function(err, project) {
+    return this._getSourceProject(linkedFileData, function (err, project) {
       if (err != null) {
         return callback(err)
       }
       const source_project_id = project._id
       return DocumentUpdaterHandler.flushProjectToMongo(
         source_project_id,
-        function(err) {
+        function (err) {
           if (err != null) {
             return callback(err)
           }
@@ -195,7 +195,7 @@ module.exports = ProjectFileAgent = {
               path: source_entity_path,
               exactCaseMatch: true
             },
-            function(err, entity, type) {
+            function (err, entity, type) {
               if (err != null) {
                 if (/^not found.*/.test(err.toString())) {
                   err = new SourceFileNotFoundError()
@@ -236,13 +236,13 @@ module.exports = ProjectFileAgent = {
 
   _checkAuth(project_id, data, current_user_id, callback) {
     if (callback == null) {
-      callback = function(error, allowed) {}
+      callback = function (error, allowed) {}
     }
     callback = _.once(callback)
     if (!ProjectFileAgent._validate(data)) {
       return callback(new BadDataError())
     }
-    return this._getSourceProject(data, function(err, project) {
+    return this._getSourceProject(data, function (err, project) {
       if (err != null) {
         return callback(err)
       }
@@ -250,7 +250,7 @@ module.exports = ProjectFileAgent = {
         current_user_id,
         project._id,
         null,
-        function(err, canRead) {
+        function (err, canRead) {
           if (err != null) {
             return callback(err)
           }

+ 44 - 43
services/web/app/src/Features/LinkedFiles/ProjectOutputFileAgent.js

@@ -32,7 +32,7 @@ const logger = require('logger-sharelatex')
 module.exports = ProjectOutputFileAgent = {
   _prepare(project_id, linkedFileData, user_id, callback) {
     if (callback == null) {
-      callback = function(err, linkedFileData) {}
+      callback = function (err, linkedFileData) {}
     }
     return this._checkAuth(
       project_id,
@@ -91,7 +91,7 @@ module.exports = ProjectOutputFileAgent = {
                   name,
                   parent_folder_id,
                   user_id,
-                  function(err, file) {
+                  function (err, file) {
                     if (err != null) {
                       return callback(err)
                     }
@@ -147,7 +147,7 @@ module.exports = ProjectOutputFileAgent = {
                   name,
                   parent_folder_id,
                   user_id,
-                  function(err, file) {
+                  function (err, file) {
                     if (err != null) {
                       return callback(err)
                     }
@@ -197,13 +197,13 @@ module.exports = ProjectOutputFileAgent = {
 
   _checkAuth(project_id, data, current_user_id, callback) {
     if (callback == null) {
-      callback = function(err, allowed) {}
+      callback = function (err, allowed) {}
     }
     callback = _.once(callback)
     if (!this._validate(data)) {
       return callback(new BadDataError())
     }
-    return this._getSourceProject(data, function(err, project) {
+    return this._getSourceProject(data, function (err, project) {
       if (err != null) {
         return callback(err)
       }
@@ -211,7 +211,7 @@ module.exports = ProjectOutputFileAgent = {
         current_user_id,
         project._id,
         null,
-        function(err, canRead) {
+        function (err, canRead) {
           if (err != null) {
             return callback(err)
           }
@@ -223,11 +223,11 @@ module.exports = ProjectOutputFileAgent = {
 
   _getFileStream(linkedFileData, user_id, callback) {
     if (callback == null) {
-      callback = function(err, fileStream) {}
+      callback = function (err, fileStream) {}
     }
     callback = _.once(callback)
     const { source_output_file_path, build_id } = linkedFileData
-    return this._getSourceProject(linkedFileData, function(err, project) {
+    return this._getSourceProject(linkedFileData, function (err, project) {
       if (err != null) {
         return callback(err)
       }
@@ -237,7 +237,7 @@ module.exports = ProjectOutputFileAgent = {
         user_id,
         build_id,
         source_output_file_path,
-        function(err, readStream) {
+        function (err, readStream) {
           if (err != null) {
             return callback(err)
           }
@@ -250,48 +250,49 @@ module.exports = ProjectOutputFileAgent = {
 
   _compileAndGetFileStream(linkedFileData, user_id, callback) {
     if (callback == null) {
-      callback = function(err, stream, build_id) {}
+      callback = function (err, stream, build_id) {}
     }
     callback = _.once(callback)
     const { source_output_file_path } = linkedFileData
-    return this._getSourceProject(linkedFileData, function(err, project) {
+    return this._getSourceProject(linkedFileData, function (err, project) {
       if (err != null) {
         return callback(err)
       }
       const source_project_id = project._id
-      return CompileManager.compile(source_project_id, user_id, {}, function(
-        err,
-        status,
-        outputFiles
-      ) {
-        if (err != null) {
-          return callback(err)
-        }
-        if (status !== 'success') {
-          return callback(new OutputFileFetchFailedError())
-        }
-        const outputFile = _.find(
-          outputFiles,
-          o => o.path === source_output_file_path
-        )
-        if (outputFile == null) {
-          return callback(new OutputFileFetchFailedError())
-        }
-        const build_id = outputFile.build
-        return ClsiManager.getOutputFileStream(
-          source_project_id,
-          user_id,
-          build_id,
-          source_output_file_path,
-          function(err, readStream) {
-            if (err != null) {
-              return callback(err)
-            }
-            readStream.pause()
-            return callback(null, readStream, build_id)
+      return CompileManager.compile(
+        source_project_id,
+        user_id,
+        {},
+        function (err, status, outputFiles) {
+          if (err != null) {
+            return callback(err)
           }
-        )
-      })
+          if (status !== 'success') {
+            return callback(new OutputFileFetchFailedError())
+          }
+          const outputFile = _.find(
+            outputFiles,
+            o => o.path === source_output_file_path
+          )
+          if (outputFile == null) {
+            return callback(new OutputFileFetchFailedError())
+          }
+          const build_id = outputFile.build
+          return ClsiManager.getOutputFileStream(
+            source_project_id,
+            user_id,
+            build_id,
+            source_output_file_path,
+            function (err, readStream) {
+              if (err != null) {
+                return callback(err)
+              }
+              readStream.pause()
+              return callback(null, readStream, build_id)
+            }
+          )
+        }
+      )
     })
   }
 }

+ 36 - 34
services/web/app/src/Features/LinkedFiles/UrlAgent.js

@@ -30,40 +30,42 @@ module.exports = UrlAgent = {
     callback
   ) {
     linkedFileData = this._sanitizeData(linkedFileData)
-    return this._getUrlStream(project_id, linkedFileData, user_id, function(
-      err,
-      readStream
-    ) {
-      if (err != null) {
-        return callback(err)
-      }
-      readStream.on('error', callback)
-      return readStream.on('response', function(response) {
-        if (response.statusCode >= 200 && response.statusCode < 300) {
-          readStream.resume()
-          return LinkedFilesHandler.importFromStream(
-            project_id,
-            readStream,
-            linkedFileData,
-            name,
-            parent_folder_id,
-            user_id,
-            function(err, file) {
-              if (err != null) {
-                return callback(err)
-              }
-              return callback(null, file._id)
-            }
-          ) // Created
-        } else {
-          const error = new UrlFetchFailedError(
-            `url fetch failed: ${linkedFileData.url}`
-          )
-          error.statusCode = response.statusCode
-          return callback(error)
+    return this._getUrlStream(
+      project_id,
+      linkedFileData,
+      user_id,
+      function (err, readStream) {
+        if (err != null) {
+          return callback(err)
         }
-      })
-    })
+        readStream.on('error', callback)
+        return readStream.on('response', function (response) {
+          if (response.statusCode >= 200 && response.statusCode < 300) {
+            readStream.resume()
+            return LinkedFilesHandler.importFromStream(
+              project_id,
+              readStream,
+              linkedFileData,
+              name,
+              parent_folder_id,
+              user_id,
+              function (err, file) {
+                if (err != null) {
+                  return callback(err)
+                }
+                return callback(null, file._id)
+              }
+            ) // Created
+          } else {
+            const error = new UrlFetchFailedError(
+              `url fetch failed: ${linkedFileData.url}`
+            )
+            error.statusCode = response.statusCode
+            return callback(error)
+          }
+        })
+      }
+    )
   },
 
   refreshLinkedFile(
@@ -93,7 +95,7 @@ module.exports = UrlAgent = {
 
   _getUrlStream(project_id, data, current_user_id, callback) {
     if (callback == null) {
-      callback = function(error, fsPath) {}
+      callback = function (error, fsPath) {}
     }
     callback = _.once(callback)
     let { url } = data

+ 37 - 36
services/web/app/src/Features/Metadata/MetaController.js

@@ -21,22 +21,22 @@ module.exports = MetaController = {
   getMetadata(req, res, next) {
     const { project_id } = req.params
     logger.log({ project_id }, 'getting all labels for project')
-    return MetaHandler.getAllMetaForProject(project_id, function(
-      err,
-      projectMeta
-    ) {
-      if (err != null) {
-        OError.tag(
-          err,
-          '[MetaController] error getting all labels from project',
-          {
-            project_id
-          }
-        )
-        return next(err)
+    return MetaHandler.getAllMetaForProject(
+      project_id,
+      function (err, projectMeta) {
+        if (err != null) {
+          OError.tag(
+            err,
+            '[MetaController] error getting all labels from project',
+            {
+              project_id
+            }
+          )
+          return next(err)
+        }
+        return res.json({ projectId: project_id, projectMeta })
       }
-      return res.json({ projectId: project_id, projectMeta })
-    })
+    )
   },
 
   broadcastMetadataForDoc(req, res, next) {
@@ -44,27 +44,28 @@ module.exports = MetaController = {
     const { doc_id } = req.params
     const { broadcast } = req.body
     logger.log({ project_id, doc_id, broadcast }, 'getting labels for doc')
-    return MetaHandler.getMetaForDoc(project_id, doc_id, function(
-      err,
-      docMeta
-    ) {
-      if (err != null) {
-        OError.tag(err, '[MetaController] error getting labels from doc', {
-          project_id,
-          doc_id
-        })
-        return next(err)
+    return MetaHandler.getMetaForDoc(
+      project_id,
+      doc_id,
+      function (err, docMeta) {
+        if (err != null) {
+          OError.tag(err, '[MetaController] error getting labels from doc', {
+            project_id,
+            doc_id
+          })
+          return next(err)
+        }
+        // default to broadcasting, unless explicitly disabled (for backwards compatibility)
+        if (broadcast !== false) {
+          EditorRealTimeController.emitToRoom(project_id, 'broadcastDocMeta', {
+            docId: doc_id,
+            meta: docMeta
+          })
+          return res.sendStatus(200)
+        } else {
+          return res.json({ docId: doc_id, meta: docMeta })
+        }
       }
-      // default to broadcasting, unless explicitly disabled (for backwards compatibility)
-      if (broadcast !== false) {
-        EditorRealTimeController.emitToRoom(project_id, 'broadcastDocMeta', {
-          docId: doc_id,
-          meta: docMeta
-        })
-        return res.sendStatus(200)
-      } else {
-        return res.json({ docId: doc_id, meta: docMeta })
-      }
-    })
+    )
   }
 }

+ 31 - 25
services/web/app/src/Features/Metadata/MetaHandler.js

@@ -33,43 +33,49 @@ module.exports = MetaHandler = {
 
   getAllMetaForProject(projectId, callback) {
     if (callback == null) {
-      callback = function(err, projectMeta) {}
+      callback = function (err, projectMeta) {}
     }
-    return DocumentUpdaterHandler.flushProjectToMongo(projectId, function(err) {
-      if (err != null) {
-        return callback(err)
-      }
-      return ProjectEntityHandler.getAllDocs(projectId, function(err, docs) {
+    return DocumentUpdaterHandler.flushProjectToMongo(
+      projectId,
+      function (err) {
         if (err != null) {
           return callback(err)
         }
-        const projectMeta = MetaHandler.extractMetaFromProjectDocs(docs)
-        return callback(null, projectMeta)
-      })
-    })
+        return ProjectEntityHandler.getAllDocs(projectId, function (err, docs) {
+          if (err != null) {
+            return callback(err)
+          }
+          const projectMeta = MetaHandler.extractMetaFromProjectDocs(docs)
+          return callback(null, projectMeta)
+        })
+      }
+    )
   },
 
   getMetaForDoc(projectId, docId, callback) {
     if (callback == null) {
-      callback = function(err, docMeta) {}
+      callback = function (err, docMeta) {}
     }
-    return DocumentUpdaterHandler.flushDocToMongo(projectId, docId, function(
-      err
-    ) {
-      if (err != null) {
-        return callback(err)
-      }
-      return ProjectEntityHandler.getDoc(projectId, docId, function(
-        err,
-        lines
-      ) {
+    return DocumentUpdaterHandler.flushDocToMongo(
+      projectId,
+      docId,
+      function (err) {
         if (err != null) {
           return callback(err)
         }
-        const docMeta = MetaHandler.extractMetaFromDoc(lines)
-        return callback(null, docMeta)
-      })
-    })
+        return ProjectEntityHandler.getDoc(
+          projectId,
+          docId,
+          function (err, lines) {
+            if (err != null) {
+              return callback(err)
+            }
+            const docMeta = MetaHandler.extractMetaFromDoc(lines)
+            return callback(null, docMeta)
+          }
+        )
+      }
+    )
   },
 
   extractMetaFromDoc(lines) {

+ 1 - 4
services/web/app/src/Features/Newsletter/NewsletterManager.js

@@ -180,10 +180,7 @@ function makeMailchimpProvider() {
   }
 
   function hashEmail(email) {
-    return crypto
-      .createHash('md5')
-      .update(email.toLowerCase())
-      .digest('hex')
+    return crypto.createHash('md5').update(email.toLowerCase()).digest('hex')
   }
 
   function getMergeFields(user) {

+ 14 - 14
services/web/app/src/Features/Notifications/NotificationsBuilder.js

@@ -8,7 +8,7 @@ function dropboxDuplicateProjectNames(userId) {
     key: `dropboxDuplicateProjectNames-${userId}`,
     create(projectName, callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       NotificationsHandler.createNotification(
         userId,
@@ -22,7 +22,7 @@ function dropboxDuplicateProjectNames(userId) {
     },
     read(callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       NotificationsHandler.markAsReadWithKey(userId, this.key, callback)
     }
@@ -34,7 +34,7 @@ function featuresUpgradedByAffiliation(affiliation, user) {
     key: `features-updated-by=${affiliation.institutionId}`,
     create(callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       const messageOpts = { institutionName: affiliation.institutionName }
       NotificationsHandler.createNotification(
@@ -49,7 +49,7 @@ function featuresUpgradedByAffiliation(affiliation, user) {
     },
     read(callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
     }
@@ -61,7 +61,7 @@ function redundantPersonalSubscription(affiliation, user) {
     key: `redundant-personal-subscription-${affiliation.institutionId}`,
     create(callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       const messageOpts = { institutionName: affiliation.institutionName }
       NotificationsHandler.createNotification(
@@ -76,7 +76,7 @@ function redundantPersonalSubscription(affiliation, user) {
     },
     read(callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
     }
@@ -88,7 +88,7 @@ function projectInvite(invite, project, sendingUser, user) {
     key: `project-invite-${invite._id}`,
     create(callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       const messageOpts = {
         userName: sendingUser.first_name,
@@ -107,7 +107,7 @@ function projectInvite(invite, project, sendingUser, user) {
     },
     read(callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
     }
@@ -118,7 +118,7 @@ function ipMatcherAffiliation(userId) {
   return {
     create(ip, callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       if (!settings.apis.v1.url) {
         // service is not configured
@@ -133,7 +133,7 @@ function ipMatcherAffiliation(userId) {
           json: true,
           timeout: 20 * 1000
         },
-        function(error, response, body) {
+        function (error, response, body) {
           if (error != null) {
             return callback(error)
           }
@@ -167,7 +167,7 @@ function ipMatcherAffiliation(userId) {
 
     read(universityId, callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       const key = `ip-matched-affiliation-${universityId}`
       NotificationsHandler.markAsReadWithKey(userId, key, callback)
@@ -180,7 +180,7 @@ function tpdsFileLimit(userId) {
     key: `tpdsFileLimit-${userId}`,
     create(projectName, callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       const messageOpts = {
         projectName: projectName
@@ -197,7 +197,7 @@ function tpdsFileLimit(userId) {
     },
     read(callback) {
       if (callback == null) {
-        callback = function() {}
+        callback = function () {}
       }
       NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
     }
@@ -215,7 +215,7 @@ const NotificationsBuilder = {
 }
 
 NotificationsBuilder.promises = {
-  redundantPersonalSubscription: function(affiliation, user) {
+  redundantPersonalSubscription: function (affiliation, user) {
     return promisifyAll(redundantPersonalSubscription(affiliation, user))
   }
 }

+ 15 - 12
services/web/app/src/Features/Notifications/NotificationsController.js

@@ -17,19 +17,22 @@ const _ = require('underscore')
 module.exports = {
   getAllUnreadNotifications(req, res) {
     const user_id = AuthenticationController.getLoggedInUserId(req)
-    return NotificationsHandler.getUserNotifications(user_id, function(
-      err,
-      unreadNotifications
-    ) {
-      unreadNotifications = _.map(unreadNotifications, function(notification) {
-        notification.html = req.i18n.translate(
-          notification.templateKey,
-          notification.messageOpts
+    return NotificationsHandler.getUserNotifications(
+      user_id,
+      function (err, unreadNotifications) {
+        unreadNotifications = _.map(
+          unreadNotifications,
+          function (notification) {
+            notification.html = req.i18n.translate(
+              notification.templateKey,
+              notification.messageOpts
+            )
+            return notification
+          }
         )
-        return notification
-      })
-      return res.send(unreadNotifications)
-    })
+        return res.send(unreadNotifications)
+      }
+    )
   },
 
   markNotificationAsRead(req, res) {

+ 2 - 2
services/web/app/src/Features/Notifications/NotificationsHandler.js

@@ -17,7 +17,7 @@ const logger = require('logger-sharelatex')
 
 const oneSecond = 1000
 
-const makeRequest = function(opts, callback) {
+const makeRequest = function (opts, callback) {
   if (
     (settings.apis.notifications != null
       ? settings.apis.notifications.url
@@ -41,7 +41,7 @@ module.exports = {
       timeout: oneSecond,
       method: 'GET'
     }
-    return makeRequest(opts, function(err, res, unreadNotifications) {
+    return makeRequest(opts, function (err, res, unreadNotifications) {
       const statusCode = res != null ? res.statusCode : 500
       if (err != null || statusCode !== 200) {
         const e = new Error(

+ 8 - 8
services/web/app/src/Features/Project/ProjectApiController.js

@@ -17,14 +17,14 @@ const logger = require('logger-sharelatex')
 module.exports = {
   getProjectDetails(req, res, next) {
     const { project_id } = req.params
-    return ProjectDetailsHandler.getDetails(project_id, function(
-      err,
-      projDetails
-    ) {
-      if (err != null) {
-        return next(err)
+    return ProjectDetailsHandler.getDetails(
+      project_id,
+      function (err, projDetails) {
+        if (err != null) {
+          return next(err)
+        }
+        return res.json(projDetails)
       }
-      return res.json(projDetails)
-    })
+    )
   }
 }

+ 7 - 7
services/web/app/src/Features/Project/ProjectCollabratecDetailsHandler.js

@@ -25,7 +25,7 @@ module.exports = ProjectCollabratecDetailsHandler = {
     callback
   ) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     return ProjectCollabratecDetailsHandler.setCollabratecUsers(
       project_id,
@@ -36,7 +36,7 @@ module.exports = ProjectCollabratecDetailsHandler = {
 
   isLinkedCollabratecUserProject(project_id, user_id, callback) {
     if (callback == null) {
-      callback = function(err, isLinked) {}
+      callback = function (err, isLinked) {}
     }
     try {
       project_id = ObjectId(project_id)
@@ -53,7 +53,7 @@ module.exports = ProjectCollabratecDetailsHandler = {
         }
       }
     }
-    return Project.findOne(query, { _id: 1 }, function(err, project) {
+    return Project.findOne(query, { _id: 1 }, function (err, project) {
       if (err != null) {
         callback(err)
       }
@@ -68,7 +68,7 @@ module.exports = ProjectCollabratecDetailsHandler = {
     callback
   ) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     try {
       project_id = ObjectId(project_id)
@@ -102,7 +102,7 @@ module.exports = ProjectCollabratecDetailsHandler = {
   setCollabratecUsers(project_id, collabratec_users, callback) {
     let err
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     try {
       project_id = ObjectId(project_id)
@@ -127,7 +127,7 @@ module.exports = ProjectCollabratecDetailsHandler = {
 
   unlinkCollabratecUserProject(project_id, user_id, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     try {
       project_id = ObjectId(project_id)
@@ -149,7 +149,7 @@ module.exports = ProjectCollabratecDetailsHandler = {
 
   updateCollabratecUserIds(old_user_id, new_user_id, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     try {
       old_user_id = ObjectId(old_user_id)

+ 5 - 8
services/web/app/src/Features/Project/ProjectController.js

@@ -64,10 +64,7 @@ const ProjectController = {
       return true
     }
     const data = `${rolloutName}:${objectId.toString()}`
-    const md5hash = crypto
-      .createHash('md5')
-      .update(data)
-      .digest('hex')
+    const md5hash = crypto.createHash('md5').update(data).digest('hex')
     const counter = parseInt(md5hash.slice(26, 32), 16)
     return counter % 100 < percentage
   },
@@ -162,7 +159,7 @@ const ProjectController = {
     const projectId = req.params.Project_id
     const userId = AuthenticationController.getLoggedInUserId(req)
 
-    ProjectDeleter.archiveProject(projectId, userId, function(err) {
+    ProjectDeleter.archiveProject(projectId, userId, function (err) {
       if (err != null) {
         return next(err)
       } else {
@@ -175,7 +172,7 @@ const ProjectController = {
     const projectId = req.params.Project_id
     const userId = AuthenticationController.getLoggedInUserId(req)
 
-    ProjectDeleter.unarchiveProject(projectId, userId, function(err) {
+    ProjectDeleter.unarchiveProject(projectId, userId, function (err) {
       if (err != null) {
         return next(err)
       } else {
@@ -188,7 +185,7 @@ const ProjectController = {
     const projectId = req.params.project_id
     const userId = AuthenticationController.getLoggedInUserId(req)
 
-    ProjectDeleter.trashProject(projectId, userId, function(err) {
+    ProjectDeleter.trashProject(projectId, userId, function (err) {
       if (err != null) {
         return next(err)
       } else {
@@ -201,7 +198,7 @@ const ProjectController = {
     const projectId = req.params.project_id
     const userId = AuthenticationController.getLoggedInUserId(req)
 
-    ProjectDeleter.untrashProject(projectId, userId, function(err) {
+    ProjectDeleter.untrashProject(projectId, userId, function (err) {
       if (err != null) {
         return next(err)
       } else {

+ 100 - 97
services/web/app/src/Features/Project/ProjectCreationHandler.js

@@ -33,7 +33,7 @@ const AnalyticsManager = require('../Analytics/AnalyticsManager')
 const ProjectCreationHandler = {
   createBlankProject(owner_id, projectName, attributes, callback) {
     if (callback == null) {
-      callback = function(error, project) {}
+      callback = function (error, project) {}
     }
     metrics.inc('project-creation')
     if (arguments.length === 3) {
@@ -41,59 +41,60 @@ const ProjectCreationHandler = {
       attributes = {}
     }
 
-    return ProjectDetailsHandler.validateProjectName(projectName, function(
-      error
-    ) {
-      if (error != null) {
-        return callback(error)
-      }
-      if (attributes.overleaf !== undefined && attributes.overleaf != null) {
-        return ProjectCreationHandler._createBlankProject(
-          owner_id,
-          projectName,
-          attributes,
-          function(error, project) {
-            if (error != null) {
-              return callback(error)
-            }
-            AnalyticsManager.recordEvent(owner_id, 'project-imported', {
-              projectId: project._id,
-              attributes
-            })
-            return callback(error, project)
-          }
-        )
-      } else {
-        return HistoryManager.initializeProject(function(error, history) {
-          if (error != null) {
-            return callback(error)
-          }
-          attributes.overleaf = {
-            history: { id: history != null ? history.overleaf_id : undefined }
-          }
+    return ProjectDetailsHandler.validateProjectName(
+      projectName,
+      function (error) {
+        if (error != null) {
+          return callback(error)
+        }
+        if (attributes.overleaf !== undefined && attributes.overleaf != null) {
           return ProjectCreationHandler._createBlankProject(
             owner_id,
             projectName,
             attributes,
-            function(error, project) {
+            function (error, project) {
               if (error != null) {
                 return callback(error)
               }
-              AnalyticsManager.recordEvent(owner_id, 'project-created', {
+              AnalyticsManager.recordEvent(owner_id, 'project-imported', {
                 projectId: project._id,
                 attributes
               })
               return callback(error, project)
             }
           )
-        })
+        } else {
+          return HistoryManager.initializeProject(function (error, history) {
+            if (error != null) {
+              return callback(error)
+            }
+            attributes.overleaf = {
+              history: { id: history != null ? history.overleaf_id : undefined }
+            }
+            return ProjectCreationHandler._createBlankProject(
+              owner_id,
+              projectName,
+              attributes,
+              function (error, project) {
+                if (error != null) {
+                  return callback(error)
+                }
+                AnalyticsManager.recordEvent(owner_id, 'project-created', {
+                  projectId: project._id,
+                  attributes
+                })
+                return callback(error, project)
+              }
+            )
+          })
+        }
       }
-    })
+    )
   },
 
   _createBlankProject(owner_id, projectName, attributes, callback) {
     if (callback == null) {
-      callback = function(error, project) {}
+      callback = function (error, project) {}
     }
     const rootFolder = new Folder({ name: 'rootFolder' })
 
@@ -113,28 +114,29 @@ const ProjectCreationHandler = {
       }
     }
     project.rootFolder[0] = rootFolder
-    return User.findById(owner_id, 'ace.spellCheckLanguage', function(
-      err,
-      user
-    ) {
-      project.spellCheckLanguage = user.ace.spellCheckLanguage
-      return project.save(function(err) {
-        if (err != null) {
-          return callback(err)
-        }
-        return callback(err, project)
-      })
-    })
+    return User.findById(
+      owner_id,
+      'ace.spellCheckLanguage',
+      function (err, user) {
+        project.spellCheckLanguage = user.ace.spellCheckLanguage
+        return project.save(function (err) {
+          if (err != null) {
+            return callback(err)
+          }
+          return callback(err, project)
+        })
+      }
+    )
   },
 
   createProjectFromSnippet(owner_id, projectName, docLines, callback) {
     if (callback == null) {
-      callback = function(error, project) {}
+      callback = function (error, project) {}
     }
     return ProjectCreationHandler.createBlankProject(
       owner_id,
       projectName,
-      function(error, project) {
+      function (error, project) {
         if (error != null) {
           return callback(error)
         }
@@ -150,12 +152,12 @@ const ProjectCreationHandler = {
 
   createBasicProject(owner_id, projectName, callback) {
     if (callback == null) {
-      callback = function(error, project) {}
+      callback = function (error, project) {}
     }
     return ProjectCreationHandler.createBlankProject(
       owner_id,
       projectName,
-      function(error, project) {
+      function (error, project) {
         if (error != null) {
           return callback(error)
         }
@@ -163,7 +165,7 @@ const ProjectCreationHandler = {
           'mainbasic.tex',
           owner_id,
           projectName,
-          function(error, docLines) {
+          function (error, docLines) {
             if (error != null) {
               return callback(error)
             }
@@ -181,12 +183,12 @@ const ProjectCreationHandler = {
 
   createExampleProject(owner_id, projectName, callback) {
     if (callback == null) {
-      callback = function(error, project) {}
+      callback = function (error, project) {}
     }
     return ProjectCreationHandler.createBlankProject(
       owner_id,
       projectName,
-      function(error, project) {
+      function (error, project) {
         if (error != null) {
           return callback(error)
         }
@@ -197,7 +199,7 @@ const ProjectCreationHandler = {
                 'main.tex',
                 owner_id,
                 projectName,
-                function(error, docLines) {
+                function (error, docLines) {
                   if (error != null) {
                     return callback(error)
                   }
@@ -214,7 +216,7 @@ const ProjectCreationHandler = {
                 'references.bib',
                 owner_id,
                 projectName,
-                function(error, docLines) {
+                function (error, docLines) {
                   if (error != null) {
                     return callback(error)
                   }
@@ -228,7 +230,7 @@ const ProjectCreationHandler = {
                   )
                 }
               ),
-            function(callback) {
+            function (callback) {
               const universePath = Path.resolve(
                 __dirname + '/../../../templates/project_files/universe.jpg'
               )
@@ -251,7 +253,7 @@ const ProjectCreationHandler = {
 
   _createRootDoc(project, owner_id, docLines, callback) {
     if (callback == null) {
-      callback = function(error, project) {}
+      callback = function (error, project) {}
     }
     return ProjectEntityUpdateHandler.addDoc(
       project._id,
@@ -259,7 +261,7 @@ const ProjectCreationHandler = {
       'main.tex',
       docLines,
       owner_id,
-      function(error, doc) {
+      function (error, doc) {
         if (error != null) {
           OError.tag(error, 'error adding root doc when creating project')
           return callback(error)
@@ -275,47 +277,48 @@ const ProjectCreationHandler = {
 
   _buildTemplate(template_name, user_id, project_name, callback) {
     if (callback == null) {
-      callback = function(error, output) {}
+      callback = function (error, output) {}
     }
-    return User.findById(user_id, 'first_name last_name', function(
-      error,
-      user
-    ) {
-      if (error != null) {
-        return callback(error)
-      }
-      const monthNames = [
-        'January',
-        'February',
-        'March',
-        'April',
-        'May',
-        'June',
-        'July',
-        'August',
-        'September',
-        'October',
-        'November',
-        'December'
-      ]
-
-      const templatePath = Path.resolve(
-        __dirname + `/../../../templates/project_files/${template_name}`
-      )
-      return fs.readFile(templatePath, function(error, template) {
+    return User.findById(
+      user_id,
+      'first_name last_name',
+      function (error, user) {
         if (error != null) {
           return callback(error)
         }
-        const data = {
-          project_name,
-          user,
-          year: new Date().getUTCFullYear(),
-          month: monthNames[new Date().getUTCMonth()]
-        }
-        const output = _.template(template.toString(), data)
-        return callback(null, output.split('\n'))
-      })
-    })
+        const monthNames = [
+          'January',
+          'February',
+          'March',
+          'April',
+          'May',
+          'June',
+          'July',
+          'August',
+          'September',
+          'October',
+          'November',
+          'December'
+        ]
+
+        const templatePath = Path.resolve(
+          __dirname + `/../../../templates/project_files/${template_name}`
+        )
+        return fs.readFile(templatePath, function (error, template) {
+          if (error != null) {
+            return callback(error)
+          }
+          const data = {
+            project_name,
+            user,
+            year: new Date().getUTCFullYear(),
+            month: monthNames[new Date().getUTCMonth()]
+          }
+          const output = _.template(template.toString(), data)
+          return callback(null, output.split('\n'))
+        })
+      }
+    )
   }
 }
 

+ 14 - 14
services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js

@@ -308,7 +308,7 @@ const ProjectEntityUpdateHandler = {
 
   addDocWithRanges: wrapWithLock({
     beforeLock(next) {
-      return function(
+      return function (
         projectId,
         folderId,
         docName,
@@ -457,7 +457,7 @@ const ProjectEntityUpdateHandler = {
 
   addFile: wrapWithLock({
     beforeLock(next) {
-      return function(
+      return function (
         projectId,
         folderId,
         fileName,
@@ -544,7 +544,7 @@ const ProjectEntityUpdateHandler = {
 
   replaceFile: wrapWithLock({
     beforeLock(next) {
-      return function(
+      return function (
         projectId,
         fileId,
         fsPath,
@@ -648,7 +648,7 @@ const ProjectEntityUpdateHandler = {
     }
   }),
 
-  upsertDoc: wrapWithLock(function(
+  upsertDoc: wrapWithLock(function (
     projectId,
     folderId,
     docName,
@@ -796,7 +796,7 @@ const ProjectEntityUpdateHandler = {
 
   upsertFile: wrapWithLock({
     beforeLock(next) {
-      return function(
+      return function (
         projectId,
         folderId,
         fileName,
@@ -974,7 +974,7 @@ const ProjectEntityUpdateHandler = {
     }
   }),
 
-  upsertDocWithPath: wrapWithLock(function(
+  upsertDocWithPath: wrapWithLock(function (
     projectId,
     elementPath,
     docLines,
@@ -1014,7 +1014,7 @@ const ProjectEntityUpdateHandler = {
 
   upsertFileWithPath: wrapWithLock({
     beforeLock(next) {
-      return function(
+      return function (
         projectId,
         elementPath,
         fsPath,
@@ -1102,7 +1102,7 @@ const ProjectEntityUpdateHandler = {
     }
   }),
 
-  deleteEntity: wrapWithLock(function(
+  deleteEntity: wrapWithLock(function (
     projectId,
     entityId,
     entityType,
@@ -1174,7 +1174,7 @@ const ProjectEntityUpdateHandler = {
     )
   ),
 
-  mkdirp: wrapWithLock(function(projectId, path, callback) {
+  mkdirp: wrapWithLock(function (projectId, path, callback) {
     for (let folder of path.split('/')) {
       if (folder.length > 0 && !SafePath.isCleanFilename(folder)) {
         return callback(new Errors.InvalidNameError('invalid element name'))
@@ -1188,7 +1188,7 @@ const ProjectEntityUpdateHandler = {
     )
   }),
 
-  mkdirpWithExactCase: wrapWithLock(function(projectId, path, callback) {
+  mkdirpWithExactCase: wrapWithLock(function (projectId, path, callback) {
     for (let folder of path.split('/')) {
       if (folder.length > 0 && !SafePath.isCleanFilename(folder)) {
         return callback(new Errors.InvalidNameError('invalid element name'))
@@ -1202,7 +1202,7 @@ const ProjectEntityUpdateHandler = {
     )
   }),
 
-  addFolder: wrapWithLock(function(
+  addFolder: wrapWithLock(function (
     projectId,
     parentFolderId,
     folderName,
@@ -1219,7 +1219,7 @@ const ProjectEntityUpdateHandler = {
     )
   }),
 
-  moveEntity: wrapWithLock(function(
+  moveEntity: wrapWithLock(function (
     projectId,
     entityId,
     destFolderId,
@@ -1272,7 +1272,7 @@ const ProjectEntityUpdateHandler = {
     )
   }),
 
-  renameEntity: wrapWithLock(function(
+  renameEntity: wrapWithLock(function (
     projectId,
     entityId,
     entityType,
@@ -1572,7 +1572,7 @@ const ProjectEntityUpdateHandler = {
 
   convertDocToFile: wrapWithLock({
     beforeLock(next) {
-      return function(projectId, docId, userId, callback) {
+      return function (projectId, docId, userId, callback) {
         DocumentUpdaterHandler.flushDocToMongo(projectId, docId, err => {
           if (err) {
             return callback(err)

+ 36 - 30
services/web/app/src/Features/Project/ProjectGetter.js

@@ -72,7 +72,7 @@ const ProjectGetter = {
       return callback(err)
     }
 
-    db.projects.findOne(query, { projection }, function(err, project) {
+    db.projects.findOne(query, { projection }, function (err, project) {
       if (err) {
         OError.tag(err, 'error getting project', {
           query,
@@ -85,43 +85,49 @@ const ProjectGetter = {
   },
 
   getProjectIdByReadAndWriteToken(token, callback) {
-    Project.findOne({ 'tokens.readAndWrite': token }, { _id: 1 }, function(
-      err,
-      project
-    ) {
-      if (err) {
-        return callback(err)
-      }
-      if (project == null) {
-        return callback()
+    Project.findOne(
+      { 'tokens.readAndWrite': token },
+      { _id: 1 },
+      function (err, project) {
+        if (err) {
+          return callback(err)
+        }
+        if (project == null) {
+          return callback()
+        }
+        callback(null, project._id)
       }
-      callback(null, project._id)
-    })
+    )
   },
 
   findAllUsersProjects(userId, fields, callback) {
     const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
-    Project.find({ owner_ref: userId }, fields, function(error, ownedProjects) {
-      if (error) {
-        return callback(error)
-      }
-      CollaboratorsGetter.getProjectsUserIsMemberOf(userId, fields, function(
-        error,
-        projects
-      ) {
+    Project.find(
+      { owner_ref: userId },
+      fields,
+      function (error, ownedProjects) {
         if (error) {
           return callback(error)
         }
-        const result = {
-          owned: ownedProjects || [],
-          readAndWrite: projects.readAndWrite || [],
-          readOnly: projects.readOnly || [],
-          tokenReadAndWrite: projects.tokenReadAndWrite || [],
-          tokenReadOnly: projects.tokenReadOnly || []
-        }
-        callback(null, result)
-      })
-    })
+        CollaboratorsGetter.getProjectsUserIsMemberOf(
+          userId,
+          fields,
+          function (error, projects) {
+            if (error) {
+              return callback(error)
+            }
+            const result = {
+              owned: ownedProjects || [],
+              readAndWrite: projects.readAndWrite || [],
+              readOnly: projects.readOnly || [],
+              tokenReadAndWrite: projects.tokenReadAndWrite || [],
+              tokenReadOnly: projects.tokenReadOnly || []
+            }
+            callback(null, result)
+          }
+        )
+      }
+    )
   },
 
   /**

+ 1 - 1
services/web/app/src/Features/Project/ProjectHelper.js

@@ -126,7 +126,7 @@ function _addSuffixToProjectName(name, suffix, maxLength) {
 
 function _addNumericSuffixToProjectName(name, allProjectNames, maxLength) {
   const NUMERIC_SUFFIX_MATCH = / \((\d+)\)$/
-  const suffixedName = function(basename, number) {
+  const suffixedName = function (basename, number) {
     const suffix = ` (${number})`
     return basename.substr(0, maxLength - suffix.length) + suffix
   }

+ 57 - 54
services/web/app/src/Features/Project/ProjectHistoryHandler.js

@@ -25,7 +25,7 @@ const ProjectHistoryHandler = {
   setHistoryId(project_id, history_id, callback) {
     // reject invalid history ids
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     if (!history_id || typeof history_id !== 'number') {
       return callback(new Error('invalid history id'))
@@ -34,7 +34,7 @@ const ProjectHistoryHandler = {
     return Project.updateOne(
       { _id: project_id, 'overleaf.history.id': { $exists: false } },
       { 'overleaf.history.id': history_id },
-      function(err, result) {
+      function (err, result) {
         if (err != null) {
           return callback(err)
         }
@@ -48,29 +48,32 @@ const ProjectHistoryHandler = {
 
   getHistoryId(project_id, callback) {
     if (callback == null) {
-      callback = function(err, result) {}
+      callback = function (err, result) {}
     }
-    return ProjectDetailsHandler.getDetails(project_id, function(err, project) {
-      if (err != null) {
-        return callback(err)
-      } // n.b. getDetails returns an error if the project doesn't exist
-      return callback(
-        null,
-        __guard__(
+    return ProjectDetailsHandler.getDetails(
+      project_id,
+      function (err, project) {
+        if (err != null) {
+          return callback(err)
+        } // n.b. getDetails returns an error if the project doesn't exist
+        return callback(
+          null,
           __guard__(
-            project != null ? project.overleaf : undefined,
-            x1 => x1.history
-          ),
-          x => x.id
+            __guard__(
+              project != null ? project.overleaf : undefined,
+              x1 => x1.history
+            ),
+            x => x.id
+          )
         )
-      )
-    })
+      }
+    )
   },
 
   upgradeHistory(project_id, callback) {
     // project must have an overleaf.history.id before allowing display of new history
     if (callback == null) {
-      callback = function(err, result) {}
+      callback = function (err, result) {}
     }
     return Project.updateOne(
       { _id: project_id, 'overleaf.history.id': { $exists: true } },
@@ -78,7 +81,7 @@ const ProjectHistoryHandler = {
         'overleaf.history.display': true,
         'overleaf.history.upgradedAt': new Date()
       },
-      function(err, result) {
+      function (err, result) {
         if (err != null) {
           return callback(err)
         }
@@ -93,7 +96,7 @@ const ProjectHistoryHandler = {
 
   downgradeHistory(project_id, callback) {
     if (callback == null) {
-      callback = function(err, result) {}
+      callback = function (err, result) {}
     }
     return Project.updateOne(
       { _id: project_id, 'overleaf.history.upgradedAt': { $exists: true } },
@@ -101,7 +104,7 @@ const ProjectHistoryHandler = {
         'overleaf.history.display': false,
         $unset: { 'overleaf.history.upgradedAt': 1 }
       },
-      function(err, result) {
+      function (err, result) {
         if (err != null) {
           return callback(err)
         }
@@ -120,45 +123,45 @@ const ProjectHistoryHandler = {
     // state. Setting a history id when one wasn't present before is ok,
     // because undefined history ids aren't cached.
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
-    return ProjectHistoryHandler.getHistoryId(project_id, function(
-      err,
-      history_id
-    ) {
-      if (err != null) {
-        return callback(err)
-      }
-      if (history_id != null) {
-        return callback()
-      } // history already exists, success
-      return HistoryManager.initializeProject(function(err, history) {
+    return ProjectHistoryHandler.getHistoryId(
+      project_id,
+      function (err, history_id) {
         if (err != null) {
           return callback(err)
         }
-        if (!(history != null ? history.overleaf_id : undefined)) {
-          return callback(new Error('failed to initialize history id'))
-        }
-        return ProjectHistoryHandler.setHistoryId(
-          project_id,
-          history.overleaf_id,
-          function(err) {
-            if (err != null) {
-              return callback(err)
-            }
-            return ProjectEntityUpdateHandler.resyncProjectHistory(
-              project_id,
-              function(err) {
-                if (err != null) {
-                  return callback(err)
-                }
-                return HistoryManager.flushProject(project_id, callback)
-              }
-            )
+        if (history_id != null) {
+          return callback()
+        } // history already exists, success
+        return HistoryManager.initializeProject(function (err, history) {
+          if (err != null) {
+            return callback(err)
           }
-        )
-      })
-    })
+          if (!(history != null ? history.overleaf_id : undefined)) {
+            return callback(new Error('failed to initialize history id'))
+          }
+          return ProjectHistoryHandler.setHistoryId(
+            project_id,
+            history.overleaf_id,
+            function (err) {
+              if (err != null) {
+                return callback(err)
+              }
+              return ProjectEntityUpdateHandler.resyncProjectHistory(
+                project_id,
+                function (err) {
+                  if (err != null) {
+                    return callback(err)
+                  }
+                  return HistoryManager.flushProject(project_id, callback)
+                }
+              )
+            }
+          )
+        })
+      }
+    )
   }
 }
 

+ 4 - 3
services/web/app/src/Features/Project/ProjectLocator.js

@@ -19,11 +19,12 @@ function findElement(options, _callback) {
   const elementType = sanitizeTypeOfElement(type)
 
   let count = 0
-  const endOfBranch = function() {
+  const endOfBranch = function () {
     if (--count === 0) {
       logger.warn(
-        `element ${elementId} could not be found for project ${projectId ||
-          project._id}`
+        `element ${elementId} could not be found for project ${
+          projectId || project._id
+        }`
       )
       callback(new Errors.NotFoundError('entity not found'))
     }

+ 76 - 74
services/web/app/src/Features/Project/ProjectRootDocManager.js

@@ -28,9 +28,9 @@ const _ = require('underscore')
 module.exports = ProjectRootDocManager = {
   setRootDocAutomatically(project_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return ProjectEntityHandler.getAllDocs(project_id, function(error, docs) {
+    return ProjectEntityHandler.getAllDocs(project_id, function (error, docs) {
       if (error != null) {
         return callback(error)
       }
@@ -38,23 +38,23 @@ module.exports = ProjectRootDocManager = {
       const jobs = _.map(
         docs,
         (doc, path) =>
-          function(cb) {
+          function (cb) {
             if (
               ProjectEntityUpdateHandler.isPathValidForRootDoc(path) &&
               DocumentHelper.contentHasDocumentclass(doc.lines)
             ) {
-              async.setImmediate(function() {
+              async.setImmediate(function () {
                 cb(doc._id)
               })
             } else {
-              async.setImmediate(function() {
+              async.setImmediate(function () {
                 cb(null)
               })
             }
           }
       )
 
-      return async.series(jobs, function(root_doc_id) {
+      return async.series(jobs, function (root_doc_id) {
         if (root_doc_id != null) {
           return ProjectEntityUpdateHandler.setRootDoc(
             project_id,
@@ -70,7 +70,7 @@ module.exports = ProjectRootDocManager = {
 
   findRootDocFileFromDirectory(directoryPath, callback) {
     if (callback == null) {
-      callback = function(error, path, content) {}
+      callback = function (error, path, content) {}
     }
     const filePathsPromise = globby(['**/*.{tex,Rtex}'], {
       cwd: directoryPath,
@@ -88,7 +88,7 @@ module.exports = ProjectRootDocManager = {
         ProjectRootDocManager._sortFileList(
           unsortedFiles,
           directoryPath,
-          function(err, files) {
+          function (err, files) {
             if (err != null) {
               return callback(err)
             }
@@ -96,12 +96,12 @@ module.exports = ProjectRootDocManager = {
 
             return async.until(
               () => doc != null || files.length === 0,
-              function(cb) {
+              function (cb) {
                 const file = files.shift()
                 return fs.readFile(
                   Path.join(directoryPath, file),
                   'utf8',
-                  function(error, content) {
+                  function (error, content) {
                     if (error != null) {
                       return cb(error)
                     }
@@ -131,11 +131,11 @@ module.exports = ProjectRootDocManager = {
 
   setRootDocFromName(project_id, rootDocName, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return ProjectEntityHandler.getAllDocPathsFromProjectById(
       project_id,
-      function(error, docPaths) {
+      function (error, docPaths) {
         let doc_id, path
         if (error != null) {
           return callback(error)
@@ -180,89 +180,91 @@ module.exports = ProjectRootDocManager = {
 
   ensureRootDocumentIsSet(project_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return ProjectGetter.getProject(project_id, { rootDoc_id: 1 }, function(
-      error,
-      project
-    ) {
-      if (error != null) {
-        return callback(error)
-      }
-      if (project == null) {
-        return callback(new Error('project not found'))
-      }
+    return ProjectGetter.getProject(
+      project_id,
+      { rootDoc_id: 1 },
+      function (error, project) {
+        if (error != null) {
+          return callback(error)
+        }
+        if (project == null) {
+          return callback(new Error('project not found'))
+        }
 
-      if (project.rootDoc_id != null) {
-        return callback()
-      } else {
-        return ProjectRootDocManager.setRootDocAutomatically(
-          project_id,
-          callback
-        )
+        if (project.rootDoc_id != null) {
+          return callback()
+        } else {
+          return ProjectRootDocManager.setRootDocAutomatically(
+            project_id,
+            callback
+          )
+        }
       }
-    })
+    )
   },
 
   ensureRootDocumentIsValid(project_id, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return ProjectGetter.getProject(project_id, { rootDoc_id: 1 }, function(
-      error,
-      project
-    ) {
-      if (error != null) {
-        return callback(error)
-      }
-      if (project == null) {
-        return callback(new Error('project not found'))
-      }
+    return ProjectGetter.getProject(
+      project_id,
+      { rootDoc_id: 1 },
+      function (error, project) {
+        if (error != null) {
+          return callback(error)
+        }
+        if (project == null) {
+          return callback(new Error('project not found'))
+        }
 
-      if (project.rootDoc_id != null) {
-        return ProjectEntityHandler.getAllDocPathsFromProjectById(
-          project_id,
-          function(error, docPaths) {
-            if (error != null) {
-              return callback(error)
-            }
-            let rootDocValid = false
-            for (let doc_id in docPaths) {
-              const _path = docPaths[doc_id]
-              if (doc_id === project.rootDoc_id) {
-                rootDocValid = true
+        if (project.rootDoc_id != null) {
+          return ProjectEntityHandler.getAllDocPathsFromProjectById(
+            project_id,
+            function (error, docPaths) {
+              if (error != null) {
+                return callback(error)
               }
-            }
-            if (rootDocValid) {
-              return callback()
-            } else {
-              return ProjectEntityUpdateHandler.unsetRootDoc(project_id, () =>
-                ProjectRootDocManager.setRootDocAutomatically(
-                  project_id,
-                  callback
+              let rootDocValid = false
+              for (let doc_id in docPaths) {
+                const _path = docPaths[doc_id]
+                if (doc_id === project.rootDoc_id) {
+                  rootDocValid = true
+                }
+              }
+              if (rootDocValid) {
+                return callback()
+              } else {
+                return ProjectEntityUpdateHandler.unsetRootDoc(project_id, () =>
+                  ProjectRootDocManager.setRootDocAutomatically(
+                    project_id,
+                    callback
+                  )
                 )
-              )
+              }
             }
-          }
-        )
-      } else {
-        return ProjectRootDocManager.setRootDocAutomatically(
-          project_id,
-          callback
-        )
+          )
+        } else {
+          return ProjectRootDocManager.setRootDocAutomatically(
+            project_id,
+            callback
+          )
+        }
       }
-    })
+    )
   },
 
   _sortFileList(listToSort, rootDirectory, callback) {
     if (callback == null) {
-      callback = function(error, result) {}
+      callback = function (error, result) {}
     }
     return async.mapLimit(
       listToSort,
       5,
       (filePath, cb) =>
-        fs.stat(Path.join(rootDirectory, filePath), function(err, stat) {
+        fs.stat(Path.join(rootDirectory, filePath), function (err, stat) {
           if (err != null) {
             return cb(err)
           }
@@ -273,7 +275,7 @@ module.exports = ProjectRootDocManager = {
             name: Path.basename(filePath)
           })
         }),
-      function(err, files) {
+      function (err, files) {
         if (err != null) {
           return callback(err)
         }

+ 4 - 4
services/web/app/src/Features/Project/ProjectUpdateHandler.js

@@ -17,7 +17,7 @@ const logger = require('logger-sharelatex')
 module.exports = {
   markAsUpdated(projectId, lastUpdatedAt, lastUpdatedBy, callback) {
     if (callback == null) {
-      callback = function() {}
+      callback = function () {}
     }
     if (lastUpdatedAt == null) {
       lastUpdatedAt = new Date()
@@ -38,7 +38,7 @@ module.exports = {
   markAsOpened(project_id, callback) {
     const conditions = { _id: project_id }
     const update = { lastOpened: Date.now() }
-    return Project.updateOne(conditions, update, {}, function(err) {
+    return Project.updateOne(conditions, update, {}, function (err) {
       if (callback != null) {
         return callback()
       }
@@ -48,7 +48,7 @@ module.exports = {
   markAsInactive(project_id, callback) {
     const conditions = { _id: project_id }
     const update = { active: false }
-    return Project.updateOne(conditions, update, {}, function(err) {
+    return Project.updateOne(conditions, update, {}, function (err) {
       if (callback != null) {
         return callback()
       }
@@ -58,7 +58,7 @@ module.exports = {
   markAsActive(project_id, callback) {
     const conditions = { _id: project_id }
     const update = { active: true }
-    return Project.updateOne(conditions, update, {}, function(err) {
+    return Project.updateOne(conditions, update, {}, function (err) {
       if (callback != null) {
         return callback()
       }

+ 1 - 1
services/web/app/src/Features/Project/SafePath.js

@@ -18,7 +18,7 @@
 //   frontend/js/ide/directives/SafePath.js
 //   frontend/js/features/file-tree/util/safe-path.js
 
-const load = function() {
+const load = function () {
   let SafePath
   const BADCHAR_RX = new RegExp(
     `\

+ 1 - 1
services/web/app/src/Features/Publishers/PublishersGetter.js

@@ -21,7 +21,7 @@ const _ = require('underscore')
 module.exports = PublishersGetter = {
   getManagedPublishers(user_id, callback) {
     if (callback == null) {
-      callback = function(error, managedPublishers) {}
+      callback = function (error, managedPublishers) {}
     }
     return UserMembershipsHandler.getEntitiesByUser(
       UserMembershipEntityConfigs.publisher,

+ 3 - 3
services/web/app/src/Features/Referal/ReferalAllocator.js

@@ -5,14 +5,14 @@ const FeaturesUpdater = require('../Subscription/FeaturesUpdater')
 module.exports = {
   allocate(referalId, newUserId, referalSource, referalMedium, callback) {
     if (callback == null) {
-      callback = function() {}
+      callback = function () {}
     }
     if (referalId == null) {
       return callback(null)
     }
 
     const query = { referal_id: referalId }
-    return User.findOne(query, { _id: 1 }, function(error, user) {
+    return User.findOne(query, { _id: 1 }, function (error, user) {
       if (error != null) {
         return callback(error)
       }
@@ -32,7 +32,7 @@ module.exports = {
             }
           },
           {},
-          function(err) {
+          function (err) {
             if (err != null) {
               OError.tag(err, 'something went wrong allocating referal', {
                 referalId,

+ 3 - 3
services/web/app/src/Features/Referal/ReferalFeatures.js

@@ -7,10 +7,10 @@ let ReferalFeatures
 module.exports = ReferalFeatures = {
   getBonusFeatures(userId, callback) {
     if (callback == null) {
-      callback = function() {}
+      callback = function () {}
     }
     const query = { _id: userId }
-    User.findOne(query, { refered_user_count: 1 }, function(error, user) {
+    User.findOne(query, { refered_user_count: 1 }, function (error, user) {
       if (error) {
         return callback(error)
       }
@@ -37,7 +37,7 @@ module.exports = ReferalFeatures = {
 
   _getBonusLevel(user) {
     let highestBonusLevel = 0
-    _.each(_.keys(Settings.bonus_features), function(level) {
+    _.each(_.keys(Settings.bonus_features), function (level) {
       const levelIsLessThanUser = level <= user.refered_user_count
       const levelIsMoreThanCurrentHighest = level >= highestBonusLevel
       if (levelIsLessThanUser && levelIsMoreThanCurrentHighest) {

+ 1 - 1
services/web/app/src/Features/Referal/ReferalHandler.js

@@ -3,7 +3,7 @@ const { User } = require('../../models/User')
 module.exports = {
   getReferedUsers(userId, callback) {
     const projection = { refered_users: 1, refered_user_count: 1 }
-    User.findById(userId, projection, function(err, user) {
+    User.findById(userId, projection, function (err, user) {
       if (err) {
         return callback(err)
       }

+ 2 - 2
services/web/app/src/Features/References/ReferencesController.js

@@ -28,7 +28,7 @@ module.exports = ReferencesController = {
       )
       return res.sendStatus(400)
     }
-    return ReferencesHandler.index(projectId, docIds, function(err, data) {
+    return ReferencesHandler.index(projectId, docIds, function (err, data) {
       if (err != null) {
         logger.err({ err, projectId }, 'error indexing all references')
         return res.sendStatus(500)
@@ -46,7 +46,7 @@ module.exports = ReferencesController = {
   indexAll(req, res) {
     const projectId = req.params.Project_id
     const { shouldBroadcast } = req.body
-    return ReferencesHandler.indexAll(projectId, function(err, data) {
+    return ReferencesHandler.indexAll(projectId, function (err, data) {
       if (err != null) {
         logger.err({ err, projectId }, 'error indexing all references')
         return res.sendStatus(500)

+ 26 - 25
services/web/app/src/Features/References/ReferencesHandler.js

@@ -42,8 +42,8 @@ module.exports = ReferencesHandler = {
 
   _findBibFileIds(project) {
     const ids = []
-    var _process = function(folder) {
-      _.each(folder.fileRefs || [], function(file) {
+    var _process = function (folder) {
+      _.each(folder.fileRefs || [], function (file) {
         if (
           __guard__(file != null ? file.name : undefined, x1 =>
             x1.match(/^.*\.bib$/)
@@ -60,8 +60,8 @@ module.exports = ReferencesHandler = {
 
   _findBibDocIds(project) {
     const ids = []
-    var _process = function(folder) {
-      _.each(folder.docs || [], function(doc) {
+    var _process = function (folder) {
+      _.each(folder.docs || [], function (doc) {
         if (
           __guard__(doc != null ? doc.name : undefined, x1 =>
             x1.match(/^.*\.bib$/)
@@ -78,32 +78,33 @@ module.exports = ReferencesHandler = {
 
   _isFullIndex(project, callback) {
     if (callback == null) {
-      callback = function(err, result) {}
+      callback = function (err, result) {}
     }
-    return UserGetter.getUser(project.owner_ref, { features: true }, function(
-      err,
-      owner
-    ) {
-      if (err != null) {
-        return callback(err)
+    return UserGetter.getUser(
+      project.owner_ref,
+      { features: true },
+      function (err, owner) {
+        if (err != null) {
+          return callback(err)
+        }
+        const features = owner != null ? owner.features : undefined
+        return callback(
+          null,
+          (features != null ? features.references : undefined) === true ||
+            (features != null ? features.referencesSearch : undefined) === true
+        )
       }
-      const features = owner != null ? owner.features : undefined
-      return callback(
-        null,
-        (features != null ? features.references : undefined) === true ||
-          (features != null ? features.referencesSearch : undefined) === true
-      )
-    })
+    )
   },
 
   indexAll(projectId, callback) {
     if (callback == null) {
-      callback = function(err, data) {}
+      callback = function (err, data) {}
     }
     return ProjectGetter.getProject(
       projectId,
       { rootFolder: true, owner_ref: 1 },
-      function(err, project) {
+      function (err, project) {
         if (err) {
           OError.tag(err, 'error finding project', {
             projectId
@@ -126,12 +127,12 @@ module.exports = ReferencesHandler = {
 
   index(projectId, docIds, callback) {
     if (callback == null) {
-      callback = function(err, data) {}
+      callback = function (err, data) {}
     }
     return ProjectGetter.getProject(
       projectId,
       { rootFolder: true, owner_ref: 1 },
-      function(err, project) {
+      function (err, project) {
         if (err) {
           OError.tag(err, 'error finding project', {
             projectId
@@ -153,7 +154,7 @@ module.exports = ReferencesHandler = {
     if (!Features.hasFeature('references')) {
       return callback()
     }
-    return ReferencesHandler._isFullIndex(project, function(err, isFullIndex) {
+    return ReferencesHandler._isFullIndex(project, function (err, isFullIndex) {
       if (err) {
         OError.tag(err, 'error checking whether to do full index', {
           projectId
@@ -168,7 +169,7 @@ module.exports = ReferencesHandler = {
         docIds.map(docId => cb =>
           DocumentUpdaterHandler.flushDocToMongo(projectId, docId, cb)
         ),
-        function(err) {
+        function (err) {
           // continue
           if (err) {
             OError.tag(err, 'error flushing docs to mongo', {
@@ -192,7 +193,7 @@ module.exports = ReferencesHandler = {
                 fullIndex: isFullIndex
               }
             },
-            function(err, res, data) {
+            function (err, res, data) {
               if (err) {
                 OError.tag(err, 'error communicating with references api', {
                   projectId

+ 1 - 1
services/web/app/src/Features/Security/LoginRateLimiter.js

@@ -16,7 +16,7 @@ function processLoginRequest(email, callback) {
 
 function recordSuccessfulLogin(email, callback) {
   if (callback == null) {
-    callback = function() {}
+    callback = function () {}
   }
   RateLimiter.clearRateLimit('login', email, callback)
 }

+ 4 - 4
services/web/app/src/Features/Security/OneTimeTokenHandler.js

@@ -26,7 +26,7 @@ module.exports = {
       options = {}
     }
     if (callback == null) {
-      callback = function(error, data) {}
+      callback = function (error, data) {}
     }
     if (typeof options === 'function') {
       callback = options
@@ -44,7 +44,7 @@ module.exports = {
         createdAt,
         expiresAt
       },
-      function(error) {
+      function (error) {
         if (error != null) {
           return callback(error)
         }
@@ -55,7 +55,7 @@ module.exports = {
 
   getValueFromTokenAndExpire(use, token, callback) {
     if (callback == null) {
-      callback = function(error, data) {}
+      callback = function (error, data) {}
     }
     const now = new Date()
     return db.tokens.findOneAndUpdate(
@@ -70,7 +70,7 @@ module.exports = {
           usedAt: now
         }
       },
-      function(error, result) {
+      function (error, result) {
         if (error != null) {
           return callback(error)
         }

+ 3 - 3
services/web/app/src/Features/Security/RateLimiterMiddleware.js

@@ -16,7 +16,7 @@ const settings = require('settings-sharelatex')
   Unique clients are identified by user_id if logged in, and IP address if not.
 */
 function rateLimit(opts) {
-  return function(req, res, next) {
+  return function (req, res, next) {
     const userId = AuthenticationController.getLoggedInUserId(req) || req.ip
     if (
       settings.smokeTest &&
@@ -41,7 +41,7 @@ function rateLimit(opts) {
       subjectName,
       throttle: opts.maxRequests || 6
     }
-    return RateLimiter.addCount(options, function(error, canContinue) {
+    return RateLimiter.addCount(options, function (error, canContinue) {
       if (error != null) {
         return next(error)
       }
@@ -62,7 +62,7 @@ function loginRateLimit(req, res, next) {
   if (!email) {
     return next()
   }
-  LoginRateLimiter.processLoginRequest(email, function(err, isAllowed) {
+  LoginRateLimiter.processLoginRequest(email, function (err, isAllowed) {
     if (err) {
       return next(err)
     }

+ 12 - 11
services/web/app/src/Features/ServerAdmin/AdminController.js

@@ -27,7 +27,7 @@ const SystemMessageManager = require('../SystemMessages/SystemMessageManager')
 
 const oneMinInMs = 60 * 1000
 
-var updateOpenConnetionsMetrics = function() {
+var updateOpenConnetionsMetrics = function () {
   metrics.gauge(
     'open_connections.socketio',
     __guard__(
@@ -79,7 +79,7 @@ const AdminController = {
       })()
     }
 
-    return SystemMessageManager.getMessagesFromDB(function(
+    return SystemMessageManager.getMessagesFromDB(function (
       error,
       systemMessages
     ) {
@@ -124,7 +124,7 @@ const AdminController = {
   writeAllToMongo(req, res) {
     logger.log('writing all docs to mongo')
     Settings.mongo.writeAll = true
-    return DocumentUpdaterHandler.flushAllDocsToMongo(function() {
+    return DocumentUpdaterHandler.flushAllDocsToMongo(function () {
       logger.log('all docs have been saved to mongo')
       return res.sendStatus(200)
     })
@@ -144,18 +144,19 @@ const AdminController = {
   },
 
   createMessage(req, res, next) {
-    return SystemMessageManager.createMessage(req.body.content, function(
-      error
-    ) {
-      if (error != null) {
-        return next(error)
+    return SystemMessageManager.createMessage(
+      req.body.content,
+      function (error) {
+        if (error != null) {
+          return next(error)
+        }
+        return res.sendStatus(200)
       }
-      return res.sendStatus(200)
-    })
+    )
   },
 
   clearMessages(req, res, next) {
-    return SystemMessageManager.clearMessages(function(error) {
+    return SystemMessageManager.clearMessages(function (error) {
       if (error != null) {
         return next(error)
       }

+ 1 - 1
services/web/app/src/Features/Spelling/SpellingController.js

@@ -38,7 +38,7 @@ module.exports = {
       json: req.body,
       timeout: TEN_SECONDS
     })
-      .on('error', function(error) {
+      .on('error', function (error) {
         logger.error({ err: error }, 'Spelling API error')
         return res.status(500).end()
       })

+ 3 - 3
services/web/app/src/Features/StaticPages/HomeController.js

@@ -48,14 +48,14 @@ module.exports = HomeController = {
   },
 
   externalPage(page, title) {
-    return function(req, res, next) {
+    return function (req, res, next) {
       if (next == null) {
-        next = function(error) {}
+        next = function (error) {}
       }
       const path = Path.resolve(
         __dirname + `/../../../views/external/${page}.pug`
       )
-      return fs.exists(path, function(exists) {
+      return fs.exists(path, function (exists) {
         // No error in this callback - old method in Node.js!
         if (exists) {
           return res.render(`external/${page}.pug`, { title })

+ 38 - 38
services/web/app/src/Features/Subscription/FeaturesUpdater.js

@@ -67,7 +67,7 @@ const FeaturesUpdater = {
         FeaturesUpdater._getFeaturesOverrides(userId, cb)
       }
     }
-    async.series(jobs, function(err, results) {
+    async.series(jobs, function (err, results) {
       if (err) {
         OError.tag(
           err,
@@ -158,26 +158,25 @@ const FeaturesUpdater = {
   },
 
   _getV1Features(userId, callback) {
-    V1SubscriptionManager.getPlanCodeFromV1(userId, function(
-      err,
-      planCode,
-      v1Id
-    ) {
-      if (err) {
-        if ((err ? err.name : undefined) === 'NotFoundError') {
-          return callback(null, [])
+    V1SubscriptionManager.getPlanCodeFromV1(
+      userId,
+      function (err, planCode, v1Id) {
+        if (err) {
+          if ((err ? err.name : undefined) === 'NotFoundError') {
+            return callback(null, [])
+          }
+          return callback(err)
         }
-        return callback(err)
-      }
 
-      callback(
-        err,
-        FeaturesUpdater._mergeFeatures(
-          V1SubscriptionManager.getGrandfatheredFeaturesForV1User(v1Id) || {},
-          FeaturesUpdater._planCodeToFeatures(planCode)
+        callback(
+          err,
+          FeaturesUpdater._mergeFeatures(
+            V1SubscriptionManager.getGrandfatheredFeaturesForV1User(v1Id) || {},
+            FeaturesUpdater._planCodeToFeatures(planCode)
+          )
         )
-      )
-    })
+      }
+    )
   },
 
   _mergeFeatures(featuresA, featuresB) {
@@ -277,31 +276,32 @@ const FeaturesUpdater = {
 
   doSyncFromV1(v1UserId, callback) {
     logger.log({ v1UserId }, '[AccountSync] starting account sync')
-    return UserGetter.getUser({ 'overleaf.id': v1UserId }, { _id: 1 }, function(
-      err,
-      user
-    ) {
-      if (err != null) {
-        OError.tag(err, '[AccountSync] error getting user', {
-          v1UserId
-        })
-        return callback(err)
-      }
-      if ((user != null ? user._id : undefined) == null) {
-        logger.warn({ v1UserId }, '[AccountSync] no user found for v1 id')
-        return callback(null)
+    return UserGetter.getUser(
+      { 'overleaf.id': v1UserId },
+      { _id: 1 },
+      function (err, user) {
+        if (err != null) {
+          OError.tag(err, '[AccountSync] error getting user', {
+            v1UserId
+          })
+          return callback(err)
+        }
+        if ((user != null ? user._id : undefined) == null) {
+          logger.warn({ v1UserId }, '[AccountSync] no user found for v1 id')
+          return callback(null)
+        }
+        logger.log(
+          { v1UserId, userId: user._id },
+          '[AccountSync] updating user subscription and features'
+        )
+        return FeaturesUpdater.refreshFeatures(user._id, callback)
       }
-      logger.log(
-        { v1UserId, userId: user._id },
-        '[AccountSync] updating user subscription and features'
-      )
-      return FeaturesUpdater.refreshFeatures(user._id, callback)
-    })
+    )
   }
 }
 
 const refreshFeaturesPromise = userId =>
-  new Promise(function(resolve, reject) {
+  new Promise(function (resolve, reject) {
     FeaturesUpdater.refreshFeatures(
       userId,
       (error, features, featuresChanged) => {

+ 62 - 60
services/web/app/src/Features/Subscription/LimitationsManager.js

@@ -41,7 +41,7 @@ module.exports = LimitationsManager = {
   },
 
   allowedNumberOfCollaboratorsForUser(user_id, callback) {
-    return UserGetter.getUser(user_id, { features: 1 }, function(error, user) {
+    return UserGetter.getUser(user_id, { features: 1 }, function (error, user) {
       if (error != null) {
         return callback(error)
       }
@@ -55,7 +55,7 @@ module.exports = LimitationsManager = {
 
   canAddXCollaborators(project_id, x_collaborators, callback) {
     if (callback == null) {
-      callback = function(error, allowed) {}
+      callback = function (error, allowed) {}
     }
     return this.allowedNumberOfCollaboratorsInProject(
       project_id,
@@ -94,7 +94,7 @@ module.exports = LimitationsManager = {
 
   hasPaidSubscription(user, callback) {
     if (callback == null) {
-      callback = function(err, hasSubscriptionOrIsMember) {}
+      callback = function (err, hasSubscriptionOrIsMember) {}
     }
     return this.userHasV2Subscription(
       user,
@@ -132,27 +132,27 @@ module.exports = LimitationsManager = {
 
   userHasV2Subscription(user, callback) {
     if (callback == null) {
-      callback = function(err, hasSubscription, subscription) {}
+      callback = function (err, hasSubscription, subscription) {}
     }
-    return SubscriptionLocator.getUsersSubscription(user._id, function(
-      err,
-      subscription
-    ) {
-      if (err != null) {
-        return callback(err)
+    return SubscriptionLocator.getUsersSubscription(
+      user._id,
+      function (err, subscription) {
+        if (err != null) {
+          return callback(err)
+        }
+        const hasValidSubscription =
+          subscription != null &&
+          (subscription.recurlySubscription_id != null ||
+            (subscription != null ? subscription.customAccount : undefined) ===
+              true)
+        return callback(err, hasValidSubscription, subscription)
       }
-      const hasValidSubscription =
-        subscription != null &&
-        (subscription.recurlySubscription_id != null ||
-          (subscription != null ? subscription.customAccount : undefined) ===
-            true)
-      return callback(err, hasValidSubscription, subscription)
-    })
+    )
   },
 
   userHasV1OrV2Subscription(user, callback) {
     if (callback == null) {
-      callback = function(err, hasSubscription) {}
+      callback = function (err, hasSubscription) {}
     }
     return this.userHasV2Subscription(user, (err, hasV2Subscription) => {
       if (err != null) {
@@ -175,35 +175,37 @@ module.exports = LimitationsManager = {
 
   userIsMemberOfGroupSubscription(user, callback) {
     if (callback == null) {
-      callback = function(error, isMember, subscriptions) {}
+      callback = function (error, isMember, subscriptions) {}
     }
-    return SubscriptionLocator.getMemberSubscriptions(user._id, function(
-      err,
-      subscriptions
-    ) {
-      if (subscriptions == null) {
-        subscriptions = []
-      }
-      if (err != null) {
-        return callback(err)
+    return SubscriptionLocator.getMemberSubscriptions(
+      user._id,
+      function (err, subscriptions) {
+        if (subscriptions == null) {
+          subscriptions = []
+        }
+        if (err != null) {
+          return callback(err)
+        }
+        return callback(err, subscriptions.length > 0, subscriptions)
       }
-      return callback(err, subscriptions.length > 0, subscriptions)
-    })
+    )
   },
 
   userHasV1Subscription(user, callback) {
     if (callback == null) {
-      callback = function(error, hasV1Subscription) {}
+      callback = function (error, hasV1Subscription) {}
     }
-    return V1SubscriptionManager.getSubscriptionsFromV1(user._id, function(
-      err,
-      v1Subscription
-    ) {
-      return callback(
-        err,
-        !!(v1Subscription != null ? v1Subscription.has_subscription : undefined)
-      )
-    })
+    return V1SubscriptionManager.getSubscriptionsFromV1(
+      user._id,
+      function (err, v1Subscription) {
+        return callback(
+          err,
+          !!(v1Subscription != null
+            ? v1Subscription.has_subscription
+            : undefined)
+        )
+      }
+    )
   },
 
   teamHasReachedMemberLimit(subscription) {
@@ -217,27 +219,27 @@ module.exports = LimitationsManager = {
 
   hasGroupMembersLimitReached(subscriptionId, callback) {
     if (callback == null) {
-      callback = function(err, limitReached, subscription) {}
+      callback = function (err, limitReached, subscription) {}
     }
-    return SubscriptionLocator.getSubscription(subscriptionId, function(
-      err,
-      subscription
-    ) {
-      if (err != null) {
-        OError.tag(err, 'error getting subscription', {
-          subscriptionId
-        })
-        return callback(err)
-      }
-      if (subscription == null) {
-        logger.warn({ subscriptionId }, 'no subscription found')
-        return callback(new Error('no subscription found'))
-      }
+    return SubscriptionLocator.getSubscription(
+      subscriptionId,
+      function (err, subscription) {
+        if (err != null) {
+          OError.tag(err, 'error getting subscription', {
+            subscriptionId
+          })
+          return callback(err)
+        }
+        if (subscription == null) {
+          logger.warn({ subscriptionId }, 'no subscription found')
+          return callback(new Error('no subscription found'))
+        }
 
-      const limitReached = LimitationsManager.teamHasReachedMemberLimit(
-        subscription
-      )
-      return callback(err, limitReached, subscription)
-    })
+        const limitReached = LimitationsManager.teamHasReachedMemberLimit(
+          subscription
+        )
+        return callback(err, limitReached, subscription)
+      }
+    )
   }
 }

+ 103 - 102
services/web/app/src/Features/Subscription/RecurlyWrapper.js

@@ -65,7 +65,7 @@ const RecurlyWrapper = {
           method: 'GET',
           expect404: true
         },
-        function(error, response, responseBody) {
+        function (error, response, responseBody) {
           if (error) {
             OError.tag(
               error,
@@ -86,20 +86,20 @@ const RecurlyWrapper = {
             return next(null, cache)
           }
           logger.log({ user_id: user._id }, 'user appears to exist in recurly')
-          return RecurlyWrapper._parseAccountXml(responseBody, function(
-            err,
-            account
-          ) {
-            if (err) {
-              OError.tag(err, 'error parsing account', {
-                user_id: user._id
-              })
-              return next(err)
+          return RecurlyWrapper._parseAccountXml(
+            responseBody,
+            function (err, account) {
+              if (err) {
+                OError.tag(err, 'error parsing account', {
+                  user_id: user._id
+                })
+                return next(err)
+              }
+              cache.userExists = true
+              cache.account = account
+              return next(null, cache)
             }
-            cache.userExists = true
-            cache.account = account
-            return next(null, cache)
-          })
+          )
         }
       )
     },
@@ -142,19 +142,19 @@ const RecurlyWrapper = {
             )
             return next(error)
           }
-          return RecurlyWrapper._parseAccountXml(responseBody, function(
-            err,
-            account
-          ) {
-            if (err) {
-              OError.tag(err, 'error creating account', {
-                user_id: user._id
-              })
-              return next(err)
+          return RecurlyWrapper._parseAccountXml(
+            responseBody,
+            function (err, account) {
+              if (err) {
+                OError.tag(err, 'error creating account', {
+                  user_id: user._id
+                })
+                return next(err)
+              }
+              cache.account = account
+              return next(null, cache)
             }
-            cache.account = account
-            return next(null, cache)
-          })
+          )
         }
       )
     },
@@ -189,20 +189,20 @@ const RecurlyWrapper = {
             )
             return next(error)
           }
-          return RecurlyWrapper._parseBillingInfoXml(responseBody, function(
-            err,
-            billingInfo
-          ) {
-            if (err) {
-              OError.tag(err, 'error creating billing info', {
-                user_id: user._id,
-                accountCode
-              })
-              return next(err)
+          return RecurlyWrapper._parseBillingInfoXml(
+            responseBody,
+            function (err, billingInfo) {
+              if (err) {
+                OError.tag(err, 'error creating billing info', {
+                  user_id: user._id,
+                  accountCode
+                })
+                return next(err)
+              }
+              cache.billingInfo = billingInfo
+              return next(null, cache)
             }
-            cache.billingInfo = billingInfo
-            return next(null, cache)
-          })
+          )
         }
       )
     },
@@ -255,19 +255,19 @@ const RecurlyWrapper = {
             )
             return next(error)
           }
-          return RecurlyWrapper._parseBillingInfoXml(responseBody, function(
-            err,
-            billingInfo
-          ) {
-            if (err) {
-              OError.tag(err, 'error updating billing info', {
-                user_id: user._id
-              })
-              return next(err)
+          return RecurlyWrapper._parseBillingInfoXml(
+            responseBody,
+            function (err, billingInfo) {
+              if (err) {
+                OError.tag(err, 'error updating billing info', {
+                  user_id: user._id
+                })
+                return next(err)
+              }
+              cache.billingInfo = billingInfo
+              return next(null, cache)
             }
-            cache.billingInfo = billingInfo
-            return next(null, cache)
-          })
+          )
         }
       )
     },
@@ -308,19 +308,19 @@ const RecurlyWrapper = {
             )
             return next(error)
           }
-          return RecurlyWrapper._parseSubscriptionXml(responseBody, function(
-            err,
-            subscription
-          ) {
-            if (err) {
-              OError.tag(err, 'error creating subscription', {
-                user_id: user._id
-              })
-              return next(err)
+          return RecurlyWrapper._parseSubscriptionXml(
+            responseBody,
+            function (err, subscription) {
+              if (err) {
+                OError.tag(err, 'error creating subscription', {
+                  user_id: user._id
+                })
+                return next(err)
+              }
+              cache.subscription = subscription
+              return next(null, cache)
             }
-            cache.subscription = subscription
-            return next(null, cache)
-          })
+          )
         }
       )
     }
@@ -348,7 +348,7 @@ const RecurlyWrapper = {
         RecurlyWrapper._paypal.setAddressAndCompanyBillingInfo,
         RecurlyWrapper._paypal.createSubscription
       ],
-      function(err, result) {
+      function (err, result) {
         if (err) {
           OError.tag(err, 'error in paypal subscription creation process', {
             user_id: user._id
@@ -449,7 +449,7 @@ const RecurlyWrapper = {
     const { expect404, expect422 } = options
     delete options.expect404
     delete options.expect422
-    return request(options, function(error, response, body) {
+    return request(options, function (error, response, body) {
       if (
         error == null &&
         response.statusCode !== 200 &&
@@ -532,16 +532,16 @@ const RecurlyWrapper = {
                 )
               }
 
-              return RecurlyWrapper.getAccount(accountId, function(
-                error,
-                account
-              ) {
-                if (error != null) {
-                  return callback(error)
+              return RecurlyWrapper.getAccount(
+                accountId,
+                function (error, account) {
+                  if (error != null) {
+                    return callback(error)
+                  }
+                  recurlySubscription.account = account
+                  return callback(null, recurlySubscription)
                 }
-                recurlySubscription.account = account
-                return callback(null, recurlySubscription)
-              })
+              )
             } else {
               return callback(null, recurlySubscription)
             }
@@ -566,7 +566,7 @@ const RecurlyWrapper = {
         if (error != null) {
           return callback(error)
         }
-        return RecurlyWrapper._parseXml(body, function(err, data) {
+        return RecurlyWrapper._parseXml(body, function (err, data) {
           if (err != null) {
             logger.warn({ err }, 'could not get accoutns')
             callback(err)
@@ -620,29 +620,30 @@ const RecurlyWrapper = {
         if (error != null) {
           return callback(error)
         }
-        return RecurlyWrapper._parseRedemptionsXml(body, function(
-          error,
-          redemptions
-        ) {
-          if (error != null) {
-            return callback(error)
-          }
-          const activeRedemptions = redemptions.filter(
-            redemption => redemption.state === 'active'
-          )
-          const couponCodes = activeRedemptions.map(
-            redemption => redemption.coupon_code
-          )
-          return Async.map(couponCodes, RecurlyWrapper.getCoupon, function(
-            error,
-            coupons
-          ) {
+        return RecurlyWrapper._parseRedemptionsXml(
+          body,
+          function (error, redemptions) {
             if (error != null) {
               return callback(error)
             }
-            return callback(null, coupons)
-          })
-        })
+            const activeRedemptions = redemptions.filter(
+              redemption => redemption.state === 'active'
+            )
+            const couponCodes = activeRedemptions.map(
+              redemption => redemption.coupon_code
+            )
+            return Async.map(
+              couponCodes,
+              RecurlyWrapper.getCoupon,
+              function (error, coupons) {
+                if (error != null) {
+                  return callback(error)
+                }
+                return callback(null, coupons)
+              }
+            )
+          }
+        )
       }
     )
   },
@@ -776,9 +777,9 @@ const RecurlyWrapper = {
         url: `subscriptions/${subscriptionId}/cancel`,
         method: 'put'
       },
-      function(error, response, body) {
+      function (error, response, body) {
         if (error != null) {
-          return RecurlyWrapper._parseXml(body, function(_err, parsed) {
+          return RecurlyWrapper._parseXml(body, function (_err, parsed) {
             if (
               __guard__(
                 parsed != null ? parsed.error : undefined,
@@ -870,7 +871,7 @@ const RecurlyWrapper = {
 
   listAccountActiveSubscriptions(account_id, callback) {
     if (callback == null) {
-      callback = function(error, subscriptions) {}
+      callback = function (error, subscriptions) {}
     }
     return RecurlyWrapper.apiRequest(
       {
@@ -880,7 +881,7 @@ const RecurlyWrapper = {
         },
         expect404: true
       },
-      function(error, response, body) {
+      function (error, response, body) {
         if (error != null) {
           return callback(error)
         }
@@ -975,7 +976,7 @@ const RecurlyWrapper = {
   },
 
   _parseXmlAndGetAttribute(xml, attribute, callback) {
-    return RecurlyWrapper._parseXml(xml, function(error, data) {
+    return RecurlyWrapper._parseXml(xml, function (error, data) {
       if (error != null) {
         return callback(error)
       }
@@ -990,7 +991,7 @@ const RecurlyWrapper = {
   },
 
   _parseXml(xml, callback) {
-    var convertDataTypes = function(data) {
+    var convertDataTypes = function (data) {
       let key, value
       if (data != null && data.$ != null) {
         if (data.$.nil === 'nil') {
@@ -1033,7 +1034,7 @@ const RecurlyWrapper = {
       explicitArray: false,
       emptyTag: ''
     })
-    return parser.parseString(xml, function(error, data) {
+    return parser.parseString(xml, function (error, data) {
       if (error != null) {
         return callback(error)
       }

+ 193 - 185
services/web/app/src/Features/Subscription/SubscriptionController.js

@@ -41,7 +41,7 @@ module.exports = SubscriptionController = {
 
     return GeoIpLookup.getCurrencyCode(
       (req.query != null ? req.query.ip : undefined) || req.ip,
-      function(err, recomendedCurrency) {
+      function (err, recomendedCurrency) {
         if (err != null) {
           return next(err)
         }
@@ -57,16 +57,17 @@ module.exports = SubscriptionController = {
           })
         const user_id = AuthenticationController.getLoggedInUserId(req)
         if (user_id != null) {
-          return UserGetter.getUser(user_id, { signUpDate: 1 }, function(
-            err,
-            user
-          ) {
-            if (err != null) {
-              return next(err)
+          return UserGetter.getUser(
+            user_id,
+            { signUpDate: 1 },
+            function (err, user) {
+              if (err != null) {
+                return next(err)
+              }
+              currentUser = user
+              return render()
             }
-            currentUser = user
-            return render()
-          })
+          )
         } else {
           return render()
         }
@@ -81,68 +82,68 @@ module.exports = SubscriptionController = {
     if (!plan) {
       return HttpErrorHandler.unprocessableEntity(req, res, 'Plan not found')
     }
-    return LimitationsManager.userHasV1OrV2Subscription(user, function(
-      err,
-      hasSubscription
-    ) {
-      if (err != null) {
-        return next(err)
-      }
-      if (hasSubscription) {
-        return res.redirect('/user/subscription?hasSubscription=true')
-      } else {
-        // LimitationsManager.userHasV2Subscription only checks Mongo. Double check with
-        // Recurly as well at this point (we don't do this most places for speed).
-        return SubscriptionHandler.validateNoSubscriptionInRecurly(
-          user._id,
-          function(error, valid) {
-            if (error != null) {
-              return next(error)
-            }
-            if (!valid) {
-              res.redirect('/user/subscription?hasSubscription=true')
-            } else {
-              let currency =
-                req.query.currency != null
-                  ? req.query.currency.toUpperCase()
-                  : undefined
-              return GeoIpLookup.getCurrencyCode(
-                (req.query != null ? req.query.ip : undefined) || req.ip,
-                function(err, recomendedCurrency, countryCode) {
-                  if (err != null) {
-                    return next(err)
-                  }
-                  if (recomendedCurrency != null && currency == null) {
-                    currency = recomendedCurrency
-                  }
-                  return res.render('subscriptions/new', {
-                    title: 'subscribe',
-                    currency,
-                    countryCode,
-                    plan,
-                    showStudentPlan: req.query.ssp === 'true',
-                    recurlyConfig: JSON.stringify({
+    return LimitationsManager.userHasV1OrV2Subscription(
+      user,
+      function (err, hasSubscription) {
+        if (err != null) {
+          return next(err)
+        }
+        if (hasSubscription) {
+          return res.redirect('/user/subscription?hasSubscription=true')
+        } else {
+          // LimitationsManager.userHasV2Subscription only checks Mongo. Double check with
+          // Recurly as well at this point (we don't do this most places for speed).
+          return SubscriptionHandler.validateNoSubscriptionInRecurly(
+            user._id,
+            function (error, valid) {
+              if (error != null) {
+                return next(error)
+              }
+              if (!valid) {
+                res.redirect('/user/subscription?hasSubscription=true')
+              } else {
+                let currency =
+                  req.query.currency != null
+                    ? req.query.currency.toUpperCase()
+                    : undefined
+                return GeoIpLookup.getCurrencyCode(
+                  (req.query != null ? req.query.ip : undefined) || req.ip,
+                  function (err, recomendedCurrency, countryCode) {
+                    if (err != null) {
+                      return next(err)
+                    }
+                    if (recomendedCurrency != null && currency == null) {
+                      currency = recomendedCurrency
+                    }
+                    return res.render('subscriptions/new', {
+                      title: 'subscribe',
                       currency,
-                      subdomain: Settings.apis.recurly.subdomain
-                    }),
-                    showCouponField: !!req.query.scf,
-                    showVatField: !!req.query.svf,
-                    gaOptimize: true
-                  })
-                }
-              )
+                      countryCode,
+                      plan,
+                      showStudentPlan: req.query.ssp === 'true',
+                      recurlyConfig: JSON.stringify({
+                        currency,
+                        subdomain: Settings.apis.recurly.subdomain
+                      }),
+                      showCouponField: !!req.query.scf,
+                      showVatField: !!req.query.svf,
+                      gaOptimize: true
+                    })
+                  }
+                )
+              }
             }
-          }
-        )
+          )
+        }
       }
-    })
+    )
   },
 
   userSubscriptionPage(req, res, next) {
     const user = AuthenticationController.getSessionUser(req)
     return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
       user,
-      function(error, results) {
+      function (error, results) {
         if (error != null) {
           return next(error)
         }
@@ -155,31 +156,31 @@ module.exports = SubscriptionController = {
           managedPublishers,
           v1SubscriptionStatus
         } = results
-        return LimitationsManager.userHasV1OrV2Subscription(user, function(
-          err,
-          hasSubscription
-        ) {
-          if (error != null) {
-            return next(error)
-          }
-          const fromPlansPage = req.query.hasSubscription
-          const plans = SubscriptionViewModelBuilder.buildPlansList()
-          const data = {
-            title: 'your_subscription',
-            plans,
-            user,
-            hasSubscription,
-            fromPlansPage,
-            personalSubscription,
-            memberGroupSubscriptions,
-            managedGroupSubscriptions,
-            confirmedMemberAffiliations,
-            managedInstitutions,
-            managedPublishers,
-            v1SubscriptionStatus
+        return LimitationsManager.userHasV1OrV2Subscription(
+          user,
+          function (err, hasSubscription) {
+            if (error != null) {
+              return next(error)
+            }
+            const fromPlansPage = req.query.hasSubscription
+            const plans = SubscriptionViewModelBuilder.buildPlansList()
+            const data = {
+              title: 'your_subscription',
+              plans,
+              user,
+              hasSubscription,
+              fromPlansPage,
+              personalSubscription,
+              memberGroupSubscriptions,
+              managedGroupSubscriptions,
+              confirmedMemberAffiliations,
+              managedInstitutions,
+              managedPublishers,
+              v1SubscriptionStatus
+            }
+            return res.render('subscriptions/dashboard', data)
           }
-          return res.render('subscriptions/dashboard', data)
-        })
+        )
       }
     )
   },
@@ -193,54 +194,54 @@ module.exports = SubscriptionController = {
     }
     const { subscriptionDetails } = req.body
 
-    return LimitationsManager.userHasV1OrV2Subscription(user, function(
-      err,
-      hasSubscription
-    ) {
-      if (err != null) {
-        return next(err)
-      }
-      if (hasSubscription) {
-        logger.warn({ user_id: user._id }, 'user already has subscription')
-        return res.sendStatus(409) // conflict
-      }
-      return SubscriptionHandler.createSubscription(
-        user,
-        subscriptionDetails,
-        recurlyTokenIds,
-        function(err) {
-          if (!err) {
-            return res.sendStatus(201)
-          }
+    return LimitationsManager.userHasV1OrV2Subscription(
+      user,
+      function (err, hasSubscription) {
+        if (err != null) {
+          return next(err)
+        }
+        if (hasSubscription) {
+          logger.warn({ user_id: user._id }, 'user already has subscription')
+          return res.sendStatus(409) // conflict
+        }
+        return SubscriptionHandler.createSubscription(
+          user,
+          subscriptionDetails,
+          recurlyTokenIds,
+          function (err) {
+            if (!err) {
+              return res.sendStatus(201)
+            }
+
+            if (
+              err instanceof SubscriptionErrors.RecurlyTransactionError ||
+              err instanceof Errors.InvalidError
+            ) {
+              logger.error({ err }, 'recurly transaction error, potential 422')
+              return HttpErrorHandler.unprocessableEntity(
+                req,
+                res,
+                err.message,
+                OError.getFullInfo(err).public
+              )
+            }
 
-          if (
-            err instanceof SubscriptionErrors.RecurlyTransactionError ||
-            err instanceof Errors.InvalidError
-          ) {
-            logger.error({ err }, 'recurly transaction error, potential 422')
-            return HttpErrorHandler.unprocessableEntity(
-              req,
-              res,
-              err.message,
-              OError.getFullInfo(err).public
+            logger.warn(
+              { err, user_id: user._id },
+              'something went wrong creating subscription'
             )
+            next(err)
           }
-
-          logger.warn(
-            { err, user_id: user._id },
-            'something went wrong creating subscription'
-          )
-          next(err)
-        }
-      )
-    })
+        )
+      }
+    )
   },
 
   successful_subscription(req, res, next) {
     const user = AuthenticationController.getSessionUser(req)
     return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
       user,
-      function(error, { personalSubscription }) {
+      function (error, { personalSubscription }) {
         if (error != null) {
           return next(error)
         }
@@ -258,7 +259,7 @@ module.exports = SubscriptionController = {
   cancelSubscription(req, res, next) {
     const user = AuthenticationController.getSessionUser(req)
     logger.log({ user_id: user._id }, 'canceling subscription')
-    return SubscriptionHandler.cancelSubscription(user, function(err) {
+    return SubscriptionHandler.cancelSubscription(user, function (err) {
       if (err != null) {
         OError.tag(err, 'something went wrong canceling subscription', {
           user_id: user._id
@@ -281,7 +282,7 @@ module.exports = SubscriptionController = {
   cancelV1Subscription(req, res, next) {
     const user_id = AuthenticationController.getLoggedInUserId(req)
     logger.log({ user_id }, 'canceling v1 subscription')
-    return V1SubscriptionManager.cancelV1Subscription(user_id, function(err) {
+    return V1SubscriptionManager.cancelV1Subscription(user_id, function (err) {
       if (err != null) {
         OError.tag(err, 'something went wrong canceling v1 subscription', {
           user_id
@@ -310,7 +311,7 @@ module.exports = SubscriptionController = {
       user,
       planCode,
       null,
-      function(err) {
+      function (err) {
         if (err != null) {
           OError.tag(err, 'something went wrong updating subscription', {
             user_id: user._id
@@ -324,20 +325,22 @@ module.exports = SubscriptionController = {
 
   updateAccountEmailAddress(req, res, next) {
     const user = AuthenticationController.getSessionUser(req)
-    RecurlyWrapper.updateAccountEmailAddress(user._id, user.email, function(
-      error
-    ) {
-      if (error) {
-        return next(error)
+    RecurlyWrapper.updateAccountEmailAddress(
+      user._id,
+      user.email,
+      function (error) {
+        if (error) {
+          return next(error)
+        }
+        res.sendStatus(200)
       }
-      res.sendStatus(200)
-    })
+    )
   },
 
   reactivateSubscription(req, res, next) {
     const user = AuthenticationController.getSessionUser(req)
     logger.log({ user_id: user._id }, 'reactivating subscription')
-    return SubscriptionHandler.reactivateSubscription(user, function(err) {
+    return SubscriptionHandler.reactivateSubscription(user, function (err) {
       if (err != null) {
         OError.tag(err, 'something went wrong reactivating subscription', {
           user_id: user._id
@@ -363,7 +366,7 @@ module.exports = SubscriptionController = {
       return SubscriptionHandler.syncSubscription(
         recurlySubscription,
         { ip: req.ip },
-        function(err) {
+        function (err) {
           if (err != null) {
             return next(err)
           }
@@ -374,7 +377,7 @@ module.exports = SubscriptionController = {
       const recurlyAccountCode = eventData.account.account_code
       return SubscriptionHandler.attemptPaypalInvoiceCollection(
         recurlyAccountCode,
-        function(err) {
+        function (err) {
           if (err) {
             return next(err)
           }
@@ -388,36 +391,38 @@ module.exports = SubscriptionController = {
 
   renderUpgradeToAnnualPlanPage(req, res, next) {
     const user = AuthenticationController.getSessionUser(req)
-    return LimitationsManager.userHasV2Subscription(user, function(
-      err,
-      hasSubscription,
-      subscription
-    ) {
-      let planName
-      if (err != null) {
-        return next(err)
-      }
-      const planCode =
-        subscription != null ? subscription.planCode.toLowerCase() : undefined
-      if ((planCode != null ? planCode.indexOf('annual') : undefined) !== -1) {
-        planName = 'annual'
-      } else if (
-        (planCode != null ? planCode.indexOf('student') : undefined) !== -1
-      ) {
-        planName = 'student'
-      } else if (
-        (planCode != null ? planCode.indexOf('collaborator') : undefined) !== -1
-      ) {
-        planName = 'collaborator'
-      }
-      if (!hasSubscription) {
-        return res.redirect('/user/subscription/plans')
+    return LimitationsManager.userHasV2Subscription(
+      user,
+      function (err, hasSubscription, subscription) {
+        let planName
+        if (err != null) {
+          return next(err)
+        }
+        const planCode =
+          subscription != null ? subscription.planCode.toLowerCase() : undefined
+        if (
+          (planCode != null ? planCode.indexOf('annual') : undefined) !== -1
+        ) {
+          planName = 'annual'
+        } else if (
+          (planCode != null ? planCode.indexOf('student') : undefined) !== -1
+        ) {
+          planName = 'student'
+        } else if (
+          (planCode != null ? planCode.indexOf('collaborator') : undefined) !==
+          -1
+        ) {
+          planName = 'collaborator'
+        }
+        if (!hasSubscription) {
+          return res.redirect('/user/subscription/plans')
+        }
+        return res.render('subscriptions/upgradeToAnnual', {
+          title: 'Upgrade to annual',
+          planName
+        })
       }
-      return res.render('subscriptions/upgradeToAnnual', {
-        title: 'Upgrade to annual',
-        planName
-      })
-    })
+    )
   },
 
   processUpgradeToAnnualPlan(req, res, next) {
@@ -433,7 +438,7 @@ module.exports = SubscriptionController = {
       user,
       annualPlanName,
       coupon_code,
-      function(err) {
+      function (err) {
         if (err != null) {
           OError.tag(err, 'error updating subscription', {
             user_id: user._id
@@ -447,29 +452,32 @@ module.exports = SubscriptionController = {
 
   extendTrial(req, res, next) {
     const user = AuthenticationController.getSessionUser(req)
-    return LimitationsManager.userHasV2Subscription(user, function(
-      err,
-      hasSubscription,
-      subscription
-    ) {
-      if (err != null) {
-        return next(err)
-      }
-      return SubscriptionHandler.extendTrial(subscription, 14, function(err) {
+    return LimitationsManager.userHasV2Subscription(
+      user,
+      function (err, hasSubscription, subscription) {
         if (err != null) {
-          return res.sendStatus(500)
-        } else {
-          return res.sendStatus(200)
+          return next(err)
         }
-      })
-    })
+        return SubscriptionHandler.extendTrial(
+          subscription,
+          14,
+          function (err) {
+            if (err != null) {
+              return res.sendStatus(500)
+            } else {
+              return res.sendStatus(200)
+            }
+          }
+        )
+      }
+    )
   },
 
   recurlyNotificationParser(req, res, next) {
     let xml = ''
     req.on('data', chunk => (xml += chunk))
     return req.on('end', () =>
-      RecurlyWrapper._parseXml(xml, function(error, body) {
+      RecurlyWrapper._parseXml(xml, function (error, body) {
         if (error != null) {
           return next(error)
         }
@@ -481,7 +489,7 @@ module.exports = SubscriptionController = {
 
   refreshUserFeatures(req, res, next) {
     const { user_id } = req.params
-    return FeaturesUpdater.refreshFeatures(user_id, function(error) {
+    return FeaturesUpdater.refreshFeatures(user_id, function (error) {
       if (error != null) {
         return next(error)
       }

+ 22 - 22
services/web/app/src/Features/Subscription/SubscriptionGroupController.js

@@ -30,7 +30,7 @@ module.exports = {
     return SubscriptionGroupHandler.removeUserFromGroup(
       subscription._id,
       userToRemove_id,
-      function(err) {
+      function (err) {
         if (err != null) {
           OError.tag(err, 'error removing user from group', {
             subscriptionId: subscription._id,
@@ -46,28 +46,28 @@ module.exports = {
   removeSelfFromGroup(req, res, next) {
     const subscriptionId = req.query.subscriptionId
     const userToRemove_id = AuthenticationController.getLoggedInUserId(req)
-    return SubscriptionLocator.getSubscription(subscriptionId, function(
-      error,
-      subscription
-    ) {
-      if (error != null) {
-        return next(error)
-      }
+    return SubscriptionLocator.getSubscription(
+      subscriptionId,
+      function (error, subscription) {
+        if (error != null) {
+          return next(error)
+        }
 
-      return SubscriptionGroupHandler.removeUserFromGroup(
-        subscription._id,
-        userToRemove_id,
-        function(err) {
-          if (err != null) {
-            logger.err(
-              { err, userToRemove_id, subscriptionId },
-              'error removing self from group'
-            )
-            return res.sendStatus(500)
+        return SubscriptionGroupHandler.removeUserFromGroup(
+          subscription._id,
+          userToRemove_id,
+          function (err) {
+            if (err != null) {
+              logger.err(
+                { err, userToRemove_id, subscriptionId },
+                'error removing self from group'
+              )
+              return res.sendStatus(500)
+            }
+            return res.sendStatus(200)
           }
-          return res.sendStatus(200)
-        }
-      )
-    })
+        )
+      }
+    )
   }
 }

+ 7 - 7
services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js

@@ -41,7 +41,7 @@ const SubscriptionGroupHandler = {
     return Subscription.updateOne(
       { admin_id: oldId },
       { admin_id: newId },
-      function(error) {
+      function (error) {
         if (error != null) {
           return callback(error)
         }
@@ -51,7 +51,7 @@ const SubscriptionGroupHandler = {
           'manager_ids',
           oldId,
           newId,
-          function(error) {
+          function (error) {
             if (error != null) {
               return callback(error)
             }
@@ -71,12 +71,12 @@ const SubscriptionGroupHandler = {
 
   isUserPartOfGroup(user_id, subscription_id, callback) {
     if (callback == null) {
-      callback = function(err, partOfGroup) {}
+      callback = function (err, partOfGroup) {}
     }
     return SubscriptionLocator.getSubscriptionByMemberIdAndId(
       user_id,
       subscription_id,
-      function(err, subscription) {
+      function (err, subscription) {
         let partOfGroup
         if (subscription != null) {
           partOfGroup = true
@@ -90,7 +90,7 @@ const SubscriptionGroupHandler = {
 
   getTotalConfirmedUsersInGroup(subscription_id, callback) {
     if (callback == null) {
-      callback = function(err, totalUsers) {}
+      callback = function (err, totalUsers) {}
     }
     return SubscriptionLocator.getSubscription(
       subscription_id,
@@ -106,7 +106,7 @@ const SubscriptionGroupHandler = {
   }
 }
 
-var replaceInArray = function(model, property, oldValue, newValue, callback) {
+var replaceInArray = function (model, property, oldValue, newValue, callback) {
   // Mongo won't let us pull and addToSet in the same query, so do it in
   // two. Note we need to add first, since the query is based on the old user.
   const query = {}
@@ -118,7 +118,7 @@ var replaceInArray = function(model, property, oldValue, newValue, callback) {
   const setOldValue = {}
   setOldValue[property] = oldValue
 
-  model.updateMany(query, { $addToSet: setNewValue }, function(error) {
+  model.updateMany(query, { $addToSet: setNewValue }, function (error) {
     if (error) {
       return callback(error)
     }

+ 185 - 186
services/web/app/src/Features/Subscription/SubscriptionHandler.js

@@ -11,228 +11,227 @@ const Analytics = require('../Analytics/AnalyticsManager')
 const SubscriptionHandler = {
   validateNoSubscriptionInRecurly(userId, callback) {
     if (callback == null) {
-      callback = function() {}
+      callback = function () {}
     }
-    RecurlyWrapper.listAccountActiveSubscriptions(userId, function(
-      error,
-      subscriptions
-    ) {
-      if (subscriptions == null) {
-        subscriptions = []
-      }
-      if (error != null) {
-        return callback(error)
-      }
-      if (subscriptions.length > 0) {
-        SubscriptionUpdater.syncSubscription(subscriptions[0], userId, function(
-          error
-        ) {
-          if (error != null) {
-            return callback(error)
-          }
-          callback(null, false)
-        })
-      } else {
-        callback(null, true)
-      }
-    })
-  },
-
-  createSubscription(user, subscriptionDetails, recurlyTokenIds, callback) {
-    SubscriptionHandler.validateNoSubscriptionInRecurly(user._id, function(
-      error,
-      valid
-    ) {
-      if (error != null) {
-        return callback(error)
-      }
-      if (!valid) {
-        return callback(new Error('user already has subscription in recurly'))
-      }
-      RecurlyWrapper.createSubscription(
-        user,
-        subscriptionDetails,
-        recurlyTokenIds,
-        function(error, recurlySubscription) {
-          if (error != null) {
-            return callback(error)
-          }
-          return SubscriptionUpdater.syncSubscription(
-            recurlySubscription,
-            user._id,
-            function(error) {
+    RecurlyWrapper.listAccountActiveSubscriptions(
+      userId,
+      function (error, subscriptions) {
+        if (subscriptions == null) {
+          subscriptions = []
+        }
+        if (error != null) {
+          return callback(error)
+        }
+        if (subscriptions.length > 0) {
+          SubscriptionUpdater.syncSubscription(
+            subscriptions[0],
+            userId,
+            function (error) {
               if (error != null) {
                 return callback(error)
               }
-              return callback()
+              callback(null, false)
             }
           )
+        } else {
+          callback(null, true)
         }
-      )
-    })
+      }
+    )
   },
 
-  updateSubscription(user, planCode, couponCode, callback) {
-    LimitationsManager.userHasV2Subscription(user, function(
-      err,
-      hasSubscription,
-      subscription
-    ) {
-      if (err) {
-        logger.warn(
-          { err, user_id: user._id, hasSubscription },
-          'there was an error checking user v2 subscription'
+  createSubscription(user, subscriptionDetails, recurlyTokenIds, callback) {
+    SubscriptionHandler.validateNoSubscriptionInRecurly(
+      user._id,
+      function (error, valid) {
+        if (error != null) {
+          return callback(error)
+        }
+        if (!valid) {
+          return callback(new Error('user already has subscription in recurly'))
+        }
+        RecurlyWrapper.createSubscription(
+          user,
+          subscriptionDetails,
+          recurlyTokenIds,
+          function (error, recurlySubscription) {
+            if (error != null) {
+              return callback(error)
+            }
+            return SubscriptionUpdater.syncSubscription(
+              recurlySubscription,
+              user._id,
+              function (error) {
+                if (error != null) {
+                  return callback(error)
+                }
+                return callback()
+              }
+            )
+          }
         )
       }
-      if (!hasSubscription) {
-        return callback()
-      } else {
-        return async.series(
-          [
-            function(cb) {
-              if (couponCode == null) {
-                return cb()
-              }
-              RecurlyWrapper.getSubscription(
-                subscription.recurlySubscription_id,
-                { includeAccount: true },
-                function(err, usersSubscription) {
-                  if (err != null) {
-                    return callback(err)
-                  }
-                  RecurlyWrapper.redeemCoupon(
-                    usersSubscription.account.account_code,
-                    couponCode,
-                    cb
-                  )
+    )
+  },
+
+  updateSubscription(user, planCode, couponCode, callback) {
+    LimitationsManager.userHasV2Subscription(
+      user,
+      function (err, hasSubscription, subscription) {
+        if (err) {
+          logger.warn(
+            { err, user_id: user._id, hasSubscription },
+            'there was an error checking user v2 subscription'
+          )
+        }
+        if (!hasSubscription) {
+          return callback()
+        } else {
+          return async.series(
+            [
+              function (cb) {
+                if (couponCode == null) {
+                  return cb()
                 }
-              )
-            },
-            cb =>
-              RecurlyWrapper.updateSubscription(
-                subscription.recurlySubscription_id,
-                { plan_code: planCode, timeframe: 'now' },
-                function(error, recurlySubscription) {
-                  if (error != null) {
-                    return callback(error)
+                RecurlyWrapper.getSubscription(
+                  subscription.recurlySubscription_id,
+                  { includeAccount: true },
+                  function (err, usersSubscription) {
+                    if (err != null) {
+                      return callback(err)
+                    }
+                    RecurlyWrapper.redeemCoupon(
+                      usersSubscription.account.account_code,
+                      couponCode,
+                      cb
+                    )
                   }
-                  SubscriptionUpdater.syncSubscription(
-                    recurlySubscription,
-                    user._id,
-                    cb
-                  )
-                }
-              )
-          ],
-          callback
-        )
+                )
+              },
+              cb =>
+                RecurlyWrapper.updateSubscription(
+                  subscription.recurlySubscription_id,
+                  { plan_code: planCode, timeframe: 'now' },
+                  function (error, recurlySubscription) {
+                    if (error != null) {
+                      return callback(error)
+                    }
+                    SubscriptionUpdater.syncSubscription(
+                      recurlySubscription,
+                      user._id,
+                      cb
+                    )
+                  }
+                )
+            ],
+            callback
+          )
+        }
       }
-    })
+    )
   },
 
   cancelSubscription(user, callback) {
-    LimitationsManager.userHasV2Subscription(user, function(
-      err,
-      hasSubscription,
-      subscription
-    ) {
-      if (err) {
-        logger.warn(
-          { err, user_id: user._id, hasSubscription },
-          'there was an error checking user v2 subscription'
-        )
-      }
-      if (hasSubscription) {
-        RecurlyWrapper.cancelSubscription(
-          subscription.recurlySubscription_id,
-          function(error) {
-            if (error != null) {
-              return callback(error)
-            }
-            const emailOpts = {
-              to: user.email,
-              first_name: user.first_name
-            }
-            const ONE_HOUR_IN_MS = 1000 * 60 * 60
-            setTimeout(
-              () =>
-                EmailHandler.sendEmail(
-                  'canceledSubscription',
-                  emailOpts,
-                  err => {
-                    if (err != null) {
-                      logger.warn(
-                        { err },
-                        'failed to send confirmation email for subscription cancellation'
-                      )
+    LimitationsManager.userHasV2Subscription(
+      user,
+      function (err, hasSubscription, subscription) {
+        if (err) {
+          logger.warn(
+            { err, user_id: user._id, hasSubscription },
+            'there was an error checking user v2 subscription'
+          )
+        }
+        if (hasSubscription) {
+          RecurlyWrapper.cancelSubscription(
+            subscription.recurlySubscription_id,
+            function (error) {
+              if (error != null) {
+                return callback(error)
+              }
+              const emailOpts = {
+                to: user.email,
+                first_name: user.first_name
+              }
+              const ONE_HOUR_IN_MS = 1000 * 60 * 60
+              setTimeout(
+                () =>
+                  EmailHandler.sendEmail(
+                    'canceledSubscription',
+                    emailOpts,
+                    err => {
+                      if (err != null) {
+                        logger.warn(
+                          { err },
+                          'failed to send confirmation email for subscription cancellation'
+                        )
+                      }
                     }
-                  }
-                ),
-              ONE_HOUR_IN_MS
-            )
-            Analytics.recordEvent(user._id, 'subscription-canceled')
-            callback()
-          }
-        )
-      } else {
-        callback()
+                  ),
+                ONE_HOUR_IN_MS
+              )
+              Analytics.recordEvent(user._id, 'subscription-canceled')
+              callback()
+            }
+          )
+        } else {
+          callback()
+        }
       }
-    })
+    )
   },
 
   reactivateSubscription(user, callback) {
-    LimitationsManager.userHasV2Subscription(user, function(
-      err,
-      hasSubscription,
-      subscription
-    ) {
-      if (err) {
-        logger.warn(
-          { err, user_id: user._id, hasSubscription },
-          'there was an error checking user v2 subscription'
-        )
-      }
-      if (hasSubscription) {
-        RecurlyWrapper.reactivateSubscription(
-          subscription.recurlySubscription_id,
-          function(error) {
-            if (error != null) {
-              return callback(error)
-            }
-            EmailHandler.sendEmail(
-              'reactivatedSubscription',
-              { to: user.email },
-              err => {
-                if (err != null) {
-                  logger.warn(
-                    { err },
-                    'failed to send reactivation confirmation email'
-                  )
-                }
+    LimitationsManager.userHasV2Subscription(
+      user,
+      function (err, hasSubscription, subscription) {
+        if (err) {
+          logger.warn(
+            { err, user_id: user._id, hasSubscription },
+            'there was an error checking user v2 subscription'
+          )
+        }
+        if (hasSubscription) {
+          RecurlyWrapper.reactivateSubscription(
+            subscription.recurlySubscription_id,
+            function (error) {
+              if (error != null) {
+                return callback(error)
               }
-            )
-            Analytics.recordEvent(user._id, 'subscription-reactivated')
-            callback()
-          }
-        )
-      } else {
-        callback()
+              EmailHandler.sendEmail(
+                'reactivatedSubscription',
+                { to: user.email },
+                err => {
+                  if (err != null) {
+                    logger.warn(
+                      { err },
+                      'failed to send reactivation confirmation email'
+                    )
+                  }
+                }
+              )
+              Analytics.recordEvent(user._id, 'subscription-reactivated')
+              callback()
+            }
+          )
+        } else {
+          callback()
+        }
       }
-    })
+    )
   },
 
   syncSubscription(recurlySubscription, requesterData, callback) {
     RecurlyWrapper.getSubscription(
       recurlySubscription.uuid,
       { includeAccount: true },
-      function(error, recurlySubscription) {
+      function (error, recurlySubscription) {
         if (error != null) {
           return callback(error)
         }
         User.findById(
           recurlySubscription.account.account_code,
           { _id: 1 },
-          function(error, user) {
+          function (error, user) {
             if (error != null) {
               return callback(error)
             }

+ 9 - 9
services/web/app/src/Features/Subscription/SubscriptionLocator.js

@@ -21,20 +21,20 @@ require('./GroupPlansData') // make sure dynamic group plans are loaded
 const SubscriptionLocator = {
   getUsersSubscription(user_or_id, callback) {
     const user_id = SubscriptionLocator._getUserId(user_or_id)
-    return Subscription.findOne({ admin_id: user_id }, function(
-      err,
-      subscription
-    ) {
-      logger.log({ user_id }, 'got users subscription')
-      return callback(err, subscription)
-    })
+    return Subscription.findOne(
+      { admin_id: user_id },
+      function (err, subscription) {
+        logger.log({ user_id }, 'got users subscription')
+        return callback(err, subscription)
+      }
+    )
   },
 
   getUserIndividualSubscription(user_or_id, callback) {
     const user_id = SubscriptionLocator._getUserId(user_or_id)
     return Subscription.findOne(
       { admin_id: user_id, groupPlan: false },
-      function(err, subscription) {
+      function (err, subscription) {
         logger.log({ user_id }, 'got users individual subscription')
         return callback(err, subscription)
       }
@@ -43,7 +43,7 @@ const SubscriptionLocator = {
 
   getManagedGroupSubscriptions(user_or_id, callback) {
     if (callback == null) {
-      callback = function(error, managedSubscriptions) {}
+      callback = function (error, managedSubscriptions) {}
     }
     const user_id = SubscriptionLocator._getUserId(user_or_id)
     return Subscription.find({

+ 76 - 76
services/web/app/src/Features/Subscription/SubscriptionUpdater.js

@@ -45,37 +45,37 @@ const SubscriptionUpdater = {
       callback = requesterData
       requesterData = {}
     }
-    SubscriptionLocator.getUsersSubscription(adminUserId, function(
-      err,
-      subscription
-    ) {
-      if (err != null) {
-        return callback(err)
-      }
-      if (subscription != null) {
-        SubscriptionUpdater._updateSubscriptionFromRecurly(
-          recurlySubscription,
-          subscription,
-          requesterData,
-          callback
-        )
-      } else {
-        SubscriptionUpdater._createNewSubscription(adminUserId, function(
-          err,
-          subscription
-        ) {
-          if (err != null) {
-            return callback(err)
-          }
+    SubscriptionLocator.getUsersSubscription(
+      adminUserId,
+      function (err, subscription) {
+        if (err != null) {
+          return callback(err)
+        }
+        if (subscription != null) {
           SubscriptionUpdater._updateSubscriptionFromRecurly(
             recurlySubscription,
             subscription,
             requesterData,
             callback
           )
-        })
+        } else {
+          SubscriptionUpdater._createNewSubscription(
+            adminUserId,
+            function (err, subscription) {
+              if (err != null) {
+                return callback(err)
+              }
+              SubscriptionUpdater._updateSubscriptionFromRecurly(
+                recurlySubscription,
+                subscription,
+                requesterData,
+                callback
+              )
+            }
+          )
+        }
       }
-    })
+    )
   },
 
   addUserToGroup(subscriptionId, userId, callback) {
@@ -86,7 +86,7 @@ const SubscriptionUpdater = {
     SubscriptionUpdater.addUsersToGroupWithoutFeaturesRefresh(
       subscriptionId,
       memberIds,
-      function(err) {
+      function (err) {
         if (err != null) {
           return callback(err)
         }
@@ -104,7 +104,7 @@ const SubscriptionUpdater = {
 
   removeUserFromGroups(filter, userId, callback) {
     const removeOperation = { $pull: { member_ids: userId } }
-    Subscription.updateMany(filter, removeOperation, function(err) {
+    Subscription.updateMany(filter, removeOperation, function (err) {
       if (err != null) {
         OError.tag(err, 'error removing user from groups', {
           filter,
@@ -112,7 +112,7 @@ const SubscriptionUpdater = {
         })
         return callback(err)
       }
-      UserGetter.getUser(userId, function(error, user) {
+      UserGetter.getUser(userId, function (error, user) {
         if (error) {
           return callback(error)
         }
@@ -130,23 +130,23 @@ const SubscriptionUpdater = {
   },
 
   removeUserFromAllGroups(userId, callback) {
-    SubscriptionLocator.getMemberSubscriptions(userId, function(
-      error,
-      subscriptions
-    ) {
-      if (error) {
-        return callback(error)
-      }
-      if (!subscriptions) {
-        return callback()
+    SubscriptionLocator.getMemberSubscriptions(
+      userId,
+      function (error, subscriptions) {
+        if (error) {
+          return callback(error)
+        }
+        if (!subscriptions) {
+          return callback()
+        }
+        const subscriptionIds = subscriptions.map(sub => sub._id)
+        SubscriptionUpdater.removeUserFromGroups(
+          { _id: subscriptionIds },
+          userId,
+          callback
+        )
       }
-      const subscriptionIds = subscriptions.map(sub => sub._id)
-      SubscriptionUpdater.removeUserFromGroups(
-        { _id: subscriptionIds },
-        userId,
-        callback
-      )
-    })
+    )
   },
 
   deleteWithV1Id(v1TeamId, callback) {
@@ -155,7 +155,7 @@ const SubscriptionUpdater = {
 
   deleteSubscription(subscription, deleterData, callback) {
     if (callback == null) {
-      callback = function() {}
+      callback = function () {}
     }
     async.series(
       [
@@ -178,38 +178,38 @@ const SubscriptionUpdater = {
   },
 
   restoreSubscription(subscriptionId, callback) {
-    SubscriptionLocator.getDeletedSubscription(subscriptionId, function(
-      err,
-      deletedSubscription
-    ) {
-      if (err) {
-        return callback(err)
+    SubscriptionLocator.getDeletedSubscription(
+      subscriptionId,
+      function (err, deletedSubscription) {
+        if (err) {
+          return callback(err)
+        }
+        let subscription = deletedSubscription.subscription
+        async.series(
+          [
+            cb =>
+              // 1. upsert subscription
+              db.subscriptions.updateOne(
+                { _id: subscription._id },
+                subscription,
+                { upsert: true },
+                cb
+              ),
+            cb =>
+              // 2. refresh users features. Do this before removing the
+              //    subscription so the restore can be retried if this fails
+              SubscriptionUpdater._refreshUsersFeatures(subscription, cb),
+            cb =>
+              // 3. remove deleted subscription
+              DeletedSubscription.deleteOne(
+                { 'subscription._id': subscription._id },
+                callback
+              )
+          ],
+          callback
+        )
       }
-      let subscription = deletedSubscription.subscription
-      async.series(
-        [
-          cb =>
-            // 1. upsert subscription
-            db.subscriptions.updateOne(
-              { _id: subscription._id },
-              subscription,
-              { upsert: true },
-              cb
-            ),
-          cb =>
-            // 2. refresh users features. Do this before removing the
-            //    subscription so the restore can be retried if this fails
-            SubscriptionUpdater._refreshUsersFeatures(subscription, cb),
-          cb =>
-            // 3. remove deleted subscription
-            DeletedSubscription.deleteOne(
-              { 'subscription._id': subscription._id },
-              callback
-            )
-        ],
-        callback
-      )
-    })
+    )
   },
 
   _refreshUsersFeatures(subscription, callback) {
@@ -284,7 +284,7 @@ const SubscriptionUpdater = {
         })
       }
     }
-    subscription.save(function(error) {
+    subscription.save(function (error) {
       if (error) {
         return callback(error)
       }

+ 42 - 42
services/web/app/src/Features/Subscription/TeamInvitesController.js

@@ -37,7 +37,7 @@ module.exports = {
       teamManagerId,
       subscription,
       email,
-      function(err, inviteUserData) {
+      function (err, inviteUserData) {
         if (err != null) {
           if (err.alreadyInTeam) {
             return res.status(400).json({
@@ -66,58 +66,58 @@ module.exports = {
     const { token } = req.params
     const userId = AuthenticationController.getLoggedInUserId(req)
 
-    return TeamInvitesHandler.getInvite(token, function(
-      err,
-      invite,
-      teamSubscription
-    ) {
-      if (err != null) {
-        return next(err)
-      }
-
-      if (!invite) {
-        return ErrorController.notFound(req, res, next)
-      }
-
-      return SubscriptionLocator.getUsersSubscription(userId, function(
-        err,
-        personalSubscription
-      ) {
+    return TeamInvitesHandler.getInvite(
+      token,
+      function (err, invite, teamSubscription) {
         if (err != null) {
           return next(err)
         }
 
-        const hasIndividualRecurlySubscription =
-          personalSubscription != null &&
-          personalSubscription.planCode.match(/(free|trial)/) == null &&
-          personalSubscription.groupPlan === false &&
-          personalSubscription.recurlySubscription_id != null &&
-          personalSubscription.recurlySubscription_id !== ''
+        if (!invite) {
+          return ErrorController.notFound(req, res, next)
+        }
 
-        return res.render('subscriptions/team/invite', {
-          inviterName: invite.inviterName,
-          inviteToken: invite.token,
-          hasIndividualRecurlySubscription,
-          appName: settings.appName,
-          expired: req.query.expired
-        })
-      })
-    })
+        return SubscriptionLocator.getUsersSubscription(
+          userId,
+          function (err, personalSubscription) {
+            if (err != null) {
+              return next(err)
+            }
+
+            const hasIndividualRecurlySubscription =
+              personalSubscription != null &&
+              personalSubscription.planCode.match(/(free|trial)/) == null &&
+              personalSubscription.groupPlan === false &&
+              personalSubscription.recurlySubscription_id != null &&
+              personalSubscription.recurlySubscription_id !== ''
+
+            return res.render('subscriptions/team/invite', {
+              inviterName: invite.inviterName,
+              inviteToken: invite.token,
+              hasIndividualRecurlySubscription,
+              appName: settings.appName,
+              expired: req.query.expired
+            })
+          }
+        )
+      }
+    )
   },
 
   acceptInvite(req, res, next) {
     const { token } = req.params
     const userId = AuthenticationController.getLoggedInUserId(req)
 
-    return TeamInvitesHandler.acceptInvite(token, userId, function(
-      err,
-      results
-    ) {
-      if (err != null) {
-        return next(err)
+    return TeamInvitesHandler.acceptInvite(
+      token,
+      userId,
+      function (err, results) {
+        if (err != null) {
+          return next(err)
+        }
+        return res.sendStatus(204)
       }
-      return res.sendStatus(204)
-    })
+    )
   },
 
   revokeInvite(req, res) {
@@ -132,7 +132,7 @@ module.exports = {
       teamManagerId,
       subscription,
       email,
-      function(err, results) {
+      function (err, results) {
         if (err != null) {
           return next(err)
         }

+ 115 - 111
services/web/app/src/Features/Subscription/TeamInvitesHandler.js

@@ -20,20 +20,20 @@ const Errors = require('../Errors/Errors')
 
 module.exports = TeamInvitesHandler = {
   getInvite(token, callback) {
-    return Subscription.findOne({ 'teamInvites.token': token }, function(
-      err,
-      subscription
-    ) {
-      if (err) {
-        return callback(err)
-      }
-      if (!subscription) {
-        return callback(new Errors.NotFoundError('team not found'))
-      }
+    return Subscription.findOne(
+      { 'teamInvites.token': token },
+      function (err, subscription) {
+        if (err) {
+          return callback(err)
+        }
+        if (!subscription) {
+          return callback(new Errors.NotFoundError('team not found'))
+        }
 
-      const invite = subscription.teamInvites.find(i => i.token === token)
-      callback(null, invite, subscription)
-    })
+        const invite = subscription.teamInvites.find(i => i.token === token)
+        callback(null, invite, subscription)
+      }
+    )
   },
 
   createInvite(teamManagerId, subscription, email, callback) {
@@ -41,12 +41,12 @@ module.exports = TeamInvitesHandler = {
     if (!email) {
       return callback(new Error('invalid email'))
     }
-    return UserGetter.getUser(teamManagerId, function(error, teamManager) {
+    return UserGetter.getUser(teamManagerId, function (error, teamManager) {
       if (error) {
         return callback(error)
       }
 
-      removeLegacyInvite(subscription.id, email, function(error) {
+      removeLegacyInvite(subscription.id, email, function (error) {
         if (error) {
           return callback(error)
         }
@@ -56,31 +56,31 @@ module.exports = TeamInvitesHandler = {
   },
 
   importInvite(subscription, inviterName, email, token, sentAt, callback) {
-    checkIfInviteIsPossible(subscription, email, function(
-      error,
-      possible,
-      reason
-    ) {
-      if (error) {
-        return callback(error)
-      }
-      if (!possible) {
-        return callback(reason)
-      }
+    checkIfInviteIsPossible(
+      subscription,
+      email,
+      function (error, possible, reason) {
+        if (error) {
+          return callback(error)
+        }
+        if (!possible) {
+          return callback(reason)
+        }
 
-      subscription.teamInvites.push({
-        email,
-        inviterName,
-        token,
-        sentAt
-      })
+        subscription.teamInvites.push({
+          email,
+          inviterName,
+          token,
+          sentAt
+        })
 
-      subscription.save(callback)
-    })
+        subscription.save(callback)
+      }
+    )
   },
 
   acceptInvite(token, userId, callback) {
-    TeamInvitesHandler.getInvite(token, function(err, invite, subscription) {
+    TeamInvitesHandler.getInvite(token, function (err, invite, subscription) {
       if (err) {
         return callback(err)
       }
@@ -88,15 +88,17 @@ module.exports = TeamInvitesHandler = {
         return callback(new Errors.NotFoundError('invite not found'))
       }
 
-      SubscriptionUpdater.addUserToGroup(subscription._id, userId, function(
-        err
-      ) {
-        if (err) {
-          return callback(err)
-        }
+      SubscriptionUpdater.addUserToGroup(
+        subscription._id,
+        userId,
+        function (err) {
+          if (err) {
+            return callback(err)
+          }
 
-        removeInviteFromTeam(subscription.id, invite.email, callback)
-      })
+          removeInviteFromTeam(subscription.id, invite.email, callback)
+        }
+      )
     })
   },
 
@@ -112,7 +114,7 @@ module.exports = TeamInvitesHandler = {
   // email is in Subscription.invited_emails when they join. We'll remove this
   // after a short while.
   createTeamInvitesForLegacyInvitedEmail(email, callback) {
-    SubscriptionLocator.getGroupsWithEmailInvite(email, function(err, teams) {
+    SubscriptionLocator.getGroupsWithEmailInvite(email, function (err, teams) {
       if (err) {
         return callback(err)
       }
@@ -127,81 +129,83 @@ module.exports = TeamInvitesHandler = {
   }
 }
 
-var createInvite = function(subscription, email, inviter, callback) {
-  checkIfInviteIsPossible(subscription, email, function(
-    error,
-    possible,
-    reason
-  ) {
-    if (error) {
-      return callback(error)
-    }
-    if (!possible) {
-      return callback(reason)
-    }
+var createInvite = function (subscription, email, inviter, callback) {
+  checkIfInviteIsPossible(
+    subscription,
+    email,
+    function (error, possible, reason) {
+      if (error) {
+        return callback(error)
+      }
+      if (!possible) {
+        return callback(reason)
+      }
 
-    // don't send invites when inviting self; add user directly to the group
-    const isInvitingSelf = inviter.emails.some(
-      emailData => emailData.email === email
-    )
-    if (isInvitingSelf) {
-      return SubscriptionUpdater.addUserToGroup(
-        subscription._id,
-        inviter._id,
-        err => {
-          if (err) {
-            return callback(err)
+      // don't send invites when inviting self; add user directly to the group
+      const isInvitingSelf = inviter.emails.some(
+        emailData => emailData.email === email
+      )
+      if (isInvitingSelf) {
+        return SubscriptionUpdater.addUserToGroup(
+          subscription._id,
+          inviter._id,
+          err => {
+            if (err) {
+              return callback(err)
+            }
+
+            // legacy: remove any invite that might have been created in the past
+            removeInviteFromTeam(subscription._id, email, error => {
+              const inviteUserData = {
+                email: inviter.email,
+                first_name: inviter.first_name,
+                last_name: inviter.last_name,
+                invite: false
+              }
+              callback(error, inviteUserData)
+            })
           }
+        )
+      }
 
-          // legacy: remove any invite that might have been created in the past
-          removeInviteFromTeam(subscription._id, email, error => {
-            const inviteUserData = {
-              email: inviter.email,
-              first_name: inviter.first_name,
-              last_name: inviter.last_name,
-              invite: false
-            }
-            callback(error, inviteUserData)
-          })
-        }
+      const inviterName = getInviterName(inviter)
+      let invite = subscription.teamInvites.find(
+        invite => invite.email === email
       )
-    }
-
-    const inviterName = getInviterName(inviter)
-    let invite = subscription.teamInvites.find(invite => invite.email === email)
 
-    if (invite) {
-      invite.sentAt = new Date()
-    } else {
-      invite = {
-        email,
-        inviterName,
-        token: crypto.randomBytes(32).toString('hex'),
-        sentAt: new Date()
+      if (invite) {
+        invite.sentAt = new Date()
+      } else {
+        invite = {
+          email,
+          inviterName,
+          token: crypto.randomBytes(32).toString('hex'),
+          sentAt: new Date()
+        }
+        subscription.teamInvites.push(invite)
       }
-      subscription.teamInvites.push(invite)
-    }
 
-    subscription.save(function(error) {
-      if (error) {
-        return callback(error)
-      }
+      subscription.save(function (error) {
+        if (error) {
+          return callback(error)
+        }
 
-      const opts = {
-        to: email,
-        inviter,
-        acceptInviteUrl: `${settings.siteUrl}/subscription/invites/${invite.token}/`,
-        appName: settings.appName
-      }
-      EmailHandler.sendEmail('verifyEmailToJoinTeam', opts, error => {
-        Object.assign(invite, { invite: true })
-        callback(error, invite)
+        const opts = {
+          to: email,
+          inviter,
+          acceptInviteUrl: `${settings.siteUrl}/subscription/invites/${invite.token}/`,
+          appName: settings.appName
+        }
+        EmailHandler.sendEmail('verifyEmailToJoinTeam', opts, error => {
+          Object.assign(invite, { invite: true })
+          callback(error, invite)
+        })
       })
-    })
-  })
+    }
+  )
 }
 
-var removeInviteFromTeam = function(subscriptionId, email, callback) {
+var removeInviteFromTeam = function (subscriptionId, email, callback) {
   const searchConditions = { _id: new ObjectId(subscriptionId.toString()) }
   const removeInvite = { $pull: { teamInvites: { email } } }
 
@@ -227,7 +231,7 @@ var removeLegacyInvite = (subscriptionId, email, callback) =>
     callback
   )
 
-var checkIfInviteIsPossible = function(subscription, email, callback) {
+var checkIfInviteIsPossible = function (subscription, email, callback) {
   if (!subscription.groupPlan) {
     logger.log(
       { subscriptionId: subscription.id },
@@ -244,7 +248,7 @@ var checkIfInviteIsPossible = function(subscription, email, callback) {
     return callback(null, false, { limitReached: true })
   }
 
-  UserGetter.getUserByAnyEmail(email, function(error, existingUser) {
+  UserGetter.getUserByAnyEmail(email, function (error, existingUser) {
     if (error) {
       return callback(error)
     }
@@ -268,7 +272,7 @@ var checkIfInviteIsPossible = function(subscription, email, callback) {
   })
 }
 
-var getInviterName = function(inviter) {
+var getInviterName = function (inviter) {
   let inviterName
   if (inviter.first_name && inviter.last_name) {
     inviterName = `${inviter.first_name} ${inviter.last_name} (${inviter.email})`

+ 23 - 22
services/web/app/src/Features/Subscription/V1SubscriptionManager.js

@@ -26,7 +26,7 @@ module.exports = V1SubscriptionManager = {
   //   - 'v1_free'
   getPlanCodeFromV1(userId, callback) {
     if (callback == null) {
-      callback = function(err, planCode, v1Id) {}
+      callback = function (err, planCode, v1Id) {}
     }
     return V1SubscriptionManager._v1Request(
       userId,
@@ -36,7 +36,7 @@ module.exports = V1SubscriptionManager = {
           return `/api/v1/sharelatex/users/${v1Id}/plan_code`
         }
       },
-      function(error, body, v1Id) {
+      function (error, body, v1Id) {
         if (error != null) {
           return callback(error)
         }
@@ -54,7 +54,7 @@ module.exports = V1SubscriptionManager = {
 
   getSubscriptionsFromV1(userId, callback) {
     if (callback == null) {
-      callback = function(err, subscriptions, v1Id) {}
+      callback = function (err, subscriptions, v1Id) {}
     }
     return V1SubscriptionManager._v1Request(
       userId,
@@ -70,7 +70,7 @@ module.exports = V1SubscriptionManager = {
 
   getSubscriptionStatusFromV1(userId, callback) {
     if (callback == null) {
-      callback = function(err, status) {}
+      callback = function (err, status) {}
     }
     return V1SubscriptionManager._v1Request(
       userId,
@@ -86,7 +86,7 @@ module.exports = V1SubscriptionManager = {
 
   cancelV1Subscription(userId, callback) {
     if (callback == null) {
-      callback = function(err) {}
+      callback = function (err) {}
     }
     return V1SubscriptionManager._v1Request(
       userId,
@@ -102,22 +102,23 @@ module.exports = V1SubscriptionManager = {
 
   v1IdForUser(userId, callback) {
     if (callback == null) {
-      callback = function(err, v1Id) {}
+      callback = function (err, v1Id) {}
     }
-    return UserGetter.getUser(userId, { 'overleaf.id': 1 }, function(
-      err,
-      user
-    ) {
-      if (err != null) {
-        return callback(err)
-      }
-      const v1Id = __guard__(
-        user != null ? user.overleaf : undefined,
-        x => x.id
-      )
+    return UserGetter.getUser(
+      userId,
+      { 'overleaf.id': 1 },
+      function (err, user) {
+        if (err != null) {
+          return callback(err)
+        }
+        const v1Id = __guard__(
+          user != null ? user.overleaf : undefined,
+          x => x.id
+        )
 
-      return callback(null, v1Id)
-    })
+        return callback(null, v1Id)
+      }
+    )
   },
 
   // v1 accounts created before migration to v2 had github and mendeley for free
@@ -140,13 +141,13 @@ module.exports = V1SubscriptionManager = {
 
   _v1Request(userId, options, callback) {
     if (callback == null) {
-      callback = function(err, body, v1Id) {}
+      callback = function (err, body, v1Id) {}
     }
     if (!settings.apis.v1.url) {
       return callback(null, null)
     }
 
-    return V1SubscriptionManager.v1IdForUser(userId, function(err, v1Id) {
+    return V1SubscriptionManager.v1IdForUser(userId, function (err, v1Id) {
       if (err != null) {
         return callback(err)
       }
@@ -167,7 +168,7 @@ module.exports = V1SubscriptionManager = {
           json: true,
           timeout: 15 * 1000
         },
-        function(error, response, body) {
+        function (error, response, body) {
           if (error != null) {
             return callback(
               new V1ConnectionError({

+ 4 - 4
services/web/app/src/Features/SystemMessages/SystemMessageManager.js

@@ -15,28 +15,28 @@ const { SystemMessage } = require('../../models/SystemMessage')
 module.exports = SystemMessageManager = {
   getMessages(callback) {
     if (callback == null) {
-      callback = function(error, messages) {}
+      callback = function (error, messages) {}
     }
     callback(null, this._cachedMessages)
   },
 
   getMessagesFromDB(callback) {
     if (callback == null) {
-      callback = function(error, messages) {}
+      callback = function (error, messages) {}
     }
     return SystemMessage.find({}, callback)
   },
 
   clearMessages(callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     return SystemMessage.deleteMany({}, callback)
   },
 
   createMessage(content, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
     const message = new SystemMessage({ content })
     return message.save(callback)

+ 15 - 10
services/web/app/src/Features/Tags/TagsController.js

@@ -7,7 +7,7 @@ const TagsController = {
     if (!userId) {
       return next(new Errors.NotFoundError())
     }
-    TagsHandler.getAllTags(userId, function(error, allTags) {
+    TagsHandler.getAllTags(userId, function (error, allTags) {
       if (error != null) {
         return next(error)
       }
@@ -28,7 +28,7 @@ const TagsController = {
   createTag(req, res, next) {
     const userId = AuthenticationController.getLoggedInUserId(req)
     const { name } = req.body
-    TagsHandler.createTag(userId, name, function(error, tag) {
+    TagsHandler.createTag(userId, name, function (error, tag) {
       if (error != null) {
         return next(error)
       }
@@ -39,7 +39,7 @@ const TagsController = {
   addProjectToTag(req, res, next) {
     const userId = AuthenticationController.getLoggedInUserId(req)
     const { tagId, projectId } = req.params
-    TagsHandler.addProjectToTag(userId, tagId, projectId, function(error) {
+    TagsHandler.addProjectToTag(userId, tagId, projectId, function (error) {
       if (error) {
         return next(error)
       }
@@ -50,18 +50,23 @@ const TagsController = {
   removeProjectFromTag(req, res, next) {
     const userId = AuthenticationController.getLoggedInUserId(req)
     const { tagId, projectId } = req.params
-    TagsHandler.removeProjectFromTag(userId, tagId, projectId, function(error) {
-      if (error) {
-        return next(error)
+    TagsHandler.removeProjectFromTag(
+      userId,
+      tagId,
+      projectId,
+      function (error) {
+        if (error) {
+          return next(error)
+        }
+        res.status(204).end()
       }
-      res.status(204).end()
-    })
+    )
   },
 
   deleteTag(req, res, next) {
     const userId = AuthenticationController.getLoggedInUserId(req)
     const { tagId } = req.params
-    TagsHandler.deleteTag(userId, tagId, function(error) {
+    TagsHandler.deleteTag(userId, tagId, function (error) {
       if (error) {
         return next(error)
       }
@@ -76,7 +81,7 @@ const TagsController = {
     if (!name) {
       return res.status(400).end()
     }
-    TagsHandler.renameTag(userId, tagId, name, function(error) {
+    TagsHandler.renameTag(userId, tagId, name, function (error) {
       if (error) {
         return next(error)
       }

+ 8 - 8
services/web/app/src/Features/Tags/TagsHandler.js

@@ -7,9 +7,9 @@ function getAllTags(userId, callback) {
 
 function createTag(userId, name, callback) {
   if (!callback) {
-    callback = function() {}
+    callback = function () {}
   }
-  Tag.create({ user_id: userId, name }, function(err, tag) {
+  Tag.create({ user_id: userId, name }, function (err, tag) {
     // on duplicate key error return existing tag
     if (err && err.code === 11000) {
       return Tag.findOne({ user_id: userId, name }, callback)
@@ -20,7 +20,7 @@ function createTag(userId, name, callback) {
 
 function renameTag(userId, tagId, name, callback) {
   if (!callback) {
-    callback = function() {}
+    callback = function () {}
   }
   Tag.updateOne(
     {
@@ -38,7 +38,7 @@ function renameTag(userId, tagId, name, callback) {
 
 function deleteTag(userId, tagId, callback) {
   if (!callback) {
-    callback = function() {}
+    callback = function () {}
   }
   Tag.deleteOne(
     {
@@ -52,7 +52,7 @@ function deleteTag(userId, tagId, callback) {
 // TODO: unused?
 function updateTagUserIds(oldUserId, newUserId, callback) {
   if (!callback) {
-    callback = function() {}
+    callback = function () {}
   }
   const searchOps = { user_id: oldUserId }
   const updateOperation = { $set: { user_id: newUserId } }
@@ -61,7 +61,7 @@ function updateTagUserIds(oldUserId, newUserId, callback) {
 
 function removeProjectFromTag(userId, tagId, projectId, callback) {
   if (!callback) {
-    callback = function() {}
+    callback = function () {}
   }
   const searchOps = {
     _id: tagId,
@@ -73,7 +73,7 @@ function removeProjectFromTag(userId, tagId, projectId, callback) {
 
 function addProjectToTag(userId, tagId, projectId, callback) {
   if (!callback) {
-    callback = function() {}
+    callback = function () {}
   }
   const searchOps = {
     _id: tagId,
@@ -85,7 +85,7 @@ function addProjectToTag(userId, tagId, projectId, callback) {
 
 function addProjectToTagName(userId, name, projectId, callback) {
   if (!callback) {
-    callback = function() {}
+    callback = function () {}
   }
   const searchOps = {
     name,

+ 1 - 1
services/web/app/src/Features/Templates/TemplatesController.js

@@ -57,7 +57,7 @@ module.exports = TemplatesController = {
       req.body.templateVersionId,
       user_id,
       req.body.imageName,
-      function(err, project) {
+      function (err, project) {
         if (err != null) {
           return next(err)
         }

+ 7 - 7
services/web/app/src/Features/Templates/TemplatesManager.js

@@ -49,11 +49,11 @@ const TemplatesManager = {
       },
       timeout: 60 * 1000
     })
-    zipReq.on('error', function(err) {
+    zipReq.on('error', function (err) {
       logger.warn({ err }, 'error getting zip from template API')
       return callback(err)
     })
-    return FileWriter.ensureDumpFolderExists(function(err) {
+    return FileWriter.ensureDumpFolderExists(function (err) {
       if (err != null) {
         return callback(err)
       }
@@ -65,7 +65,7 @@ const TemplatesManager = {
         fromV1TemplateId: templateId,
         fromV1TemplateVersionId: templateVersionId
       }
-      writeStream.on('close', function() {
+      writeStream.on('close', function () {
         if (zipReq.response.statusCode !== 200) {
           logger.warn(
             { uri: zipUrl, statusCode: zipReq.response.statusCode },
@@ -78,7 +78,7 @@ const TemplatesManager = {
           projectName,
           dumpPath,
           attributes,
-          function(err, project) {
+          function (err, project) {
             if (err != null) {
               OError.tag(err, 'problem building project from zip', {
                 zipReq
@@ -97,11 +97,11 @@ const TemplatesManager = {
                     cb
                   )
               ],
-              function(err) {
+              function (err) {
                 if (err != null) {
                   return callback(err)
                 }
-                fs.unlink(dumpPath, function(err) {
+                fs.unlink(dumpPath, function (err) {
                   if (err != null) {
                     return logger.err({ err }, 'error unlinking template zip')
                   }
@@ -114,7 +114,7 @@ const TemplatesManager = {
                   { _id: project._id },
                   update,
                   {},
-                  function(err) {
+                  function (err) {
                     if (err != null) {
                       return callback(err)
                     }

+ 1 - 1
services/web/app/src/Features/ThirdPartyDataStore/TpdsController.js

@@ -119,7 +119,7 @@ module.exports = {
     }
   },
 
-  parseParams: (parseParams = function(req) {
+  parseParams: (parseParams = function (req) {
     let filePath, projectName
     let path = req.params[0]
     const userId = req.params.user_id

+ 156 - 149
services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js

@@ -25,166 +25,173 @@ const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
 module.exports = UpdateMerger = {
   mergeUpdate(user_id, project_id, path, updateRequest, source, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return FileWriter.writeStreamToDisk(project_id, updateRequest, function(
-      err,
-      fsPath
-    ) {
-      if (err != null) {
-        return callback(err)
+    return FileWriter.writeStreamToDisk(
+      project_id,
+      updateRequest,
+      function (err, fsPath) {
+        if (err != null) {
+          return callback(err)
+        }
+        return UpdateMerger._mergeUpdate(
+          user_id,
+          project_id,
+          path,
+          fsPath,
+          source,
+          mergeErr =>
+            fs.unlink(fsPath, function (deleteErr) {
+              if (deleteErr != null) {
+                logger.err({ project_id, fsPath }, 'error deleting file')
+              }
+              return callback(mergeErr)
+            })
+        )
       }
-      return UpdateMerger._mergeUpdate(
-        user_id,
-        project_id,
-        path,
-        fsPath,
-        source,
-        mergeErr =>
-          fs.unlink(fsPath, function(deleteErr) {
-            if (deleteErr != null) {
-              logger.err({ project_id, fsPath }, 'error deleting file')
-            }
-            return callback(mergeErr)
-          })
-      )
-    })
+    )
   },
 
   _findExistingFileType(project_id, path, callback) {
-    ProjectEntityHandler.getAllEntities(project_id, function(err, docs, files) {
-      if (err != null) {
-        return callback(err)
-      }
-      var existingFileType = null
-      if (_.some(files, f => f.path === path)) {
-        existingFileType = 'file'
-      }
-      if (_.some(docs, d => d.path === path)) {
-        existingFileType = 'doc'
+    ProjectEntityHandler.getAllEntities(
+      project_id,
+      function (err, docs, files) {
+        if (err != null) {
+          return callback(err)
+        }
+        var existingFileType = null
+        if (_.some(files, f => f.path === path)) {
+          existingFileType = 'file'
+        }
+        if (_.some(docs, d => d.path === path)) {
+          existingFileType = 'doc'
+        }
+        callback(null, existingFileType)
       }
-      callback(null, existingFileType)
-    })
+    )
   },
 
   _determineFileType(project_id, path, fsPath, callback) {
     if (callback == null) {
-      callback = function(err, fileType) {}
+      callback = function (err, fileType) {}
     }
     // check if there is an existing file with the same path (we either need
     // to overwrite it or delete it)
-    UpdateMerger._findExistingFileType(project_id, path, function(
-      err,
-      existingFileType
-    ) {
-      if (err) {
-        return callback(err)
-      }
-      // determine whether the update should create a doc or binary file
-      FileTypeManager.getType(path, fsPath, function(
-        err,
-        { binary, encoding }
-      ) {
-        if (err != null) {
+    UpdateMerger._findExistingFileType(
+      project_id,
+      path,
+      function (err, existingFileType) {
+        if (err) {
           return callback(err)
         }
+        // determine whether the update should create a doc or binary file
+        FileTypeManager.getType(
+          path,
+          fsPath,
+          function (err, { binary, encoding }) {
+            if (err != null) {
+              return callback(err)
+            }
 
-        // If we receive a non-utf8 encoding, we won't be able to keep things in
-        // sync, so we'll treat non-utf8 files as binary
-        const isBinary = binary || encoding !== 'utf-8'
+            // If we receive a non-utf8 encoding, we won't be able to keep things in
+            // sync, so we'll treat non-utf8 files as binary
+            const isBinary = binary || encoding !== 'utf-8'
 
-        // Existing | Update    | Action
-        // ---------|-----------|-------
-        // file     | isBinary  | existing-file
-        // file     | !isBinary | existing-file
-        // doc      | isBinary  | new-file, delete-existing-doc
-        // doc      | !isBinary | existing-doc
-        // null     | isBinary  | new-file
-        // null     | !isBinary | new-doc
+            // Existing | Update    | Action
+            // ---------|-----------|-------
+            // file     | isBinary  | existing-file
+            // file     | !isBinary | existing-file
+            // doc      | isBinary  | new-file, delete-existing-doc
+            // doc      | !isBinary | existing-doc
+            // null     | isBinary  | new-file
+            // null     | !isBinary | new-doc
 
-        // if a binary file already exists, always keep it as a binary file
-        // even if the update looks like a text file
-        if (existingFileType === 'file') {
-          return callback(null, 'existing-file')
-        }
+            // if a binary file already exists, always keep it as a binary file
+            // even if the update looks like a text file
+            if (existingFileType === 'file') {
+              return callback(null, 'existing-file')
+            }
 
-        // if there is an existing doc, keep it as a doc except when the
-        // incoming update is binary. In that case delete the doc and replace
-        // it with a new file.
-        if (existingFileType === 'doc') {
-          if (isBinary) {
-            return callback(null, 'new-file', 'delete-existing-doc')
-          } else {
-            return callback(null, 'existing-doc')
+            // if there is an existing doc, keep it as a doc except when the
+            // incoming update is binary. In that case delete the doc and replace
+            // it with a new file.
+            if (existingFileType === 'doc') {
+              if (isBinary) {
+                return callback(null, 'new-file', 'delete-existing-doc')
+              } else {
+                return callback(null, 'existing-doc')
+              }
+            }
+            // if there no existing file, create a file or doc as needed
+            return callback(null, isBinary ? 'new-file' : 'new-doc')
           }
-        }
-        // if there no existing file, create a file or doc as needed
-        return callback(null, isBinary ? 'new-file' : 'new-doc')
-      })
-    })
+        )
+      }
+    )
   },
 
   _mergeUpdate(user_id, project_id, path, fsPath, source, callback) {
     if (callback == null) {
-      callback = function(error) {}
+      callback = function (error) {}
     }
-    return UpdateMerger._determineFileType(project_id, path, fsPath, function(
-      err,
-      fileType,
-      deleteOriginalEntity
-    ) {
-      if (err != null) {
-        return callback(err)
-      }
-      async.series(
-        [
-          function(cb) {
-            if (deleteOriginalEntity) {
-              // currently we only delete docs
-              UpdateMerger.deleteUpdate(user_id, project_id, path, source, cb)
-            } else {
-              cb()
-            }
-          },
-          function(cb) {
-            if (['existing-file', 'new-file'].includes(fileType)) {
-              return UpdateMerger.p.processFile(
-                project_id,
-                fsPath,
-                path,
-                source,
-                user_id,
-                cb
-              )
-            } else if (['existing-doc', 'new-doc'].includes(fileType)) {
-              return UpdateMerger.p.processDoc(
-                project_id,
-                user_id,
-                fsPath,
-                path,
-                source,
-                cb
-              )
-            } else {
-              return cb(new Error('unrecognized file'))
+    return UpdateMerger._determineFileType(
+      project_id,
+      path,
+      fsPath,
+      function (err, fileType, deleteOriginalEntity) {
+        if (err != null) {
+          return callback(err)
+        }
+        async.series(
+          [
+            function (cb) {
+              if (deleteOriginalEntity) {
+                // currently we only delete docs
+                UpdateMerger.deleteUpdate(user_id, project_id, path, source, cb)
+              } else {
+                cb()
+              }
+            },
+            function (cb) {
+              if (['existing-file', 'new-file'].includes(fileType)) {
+                return UpdateMerger.p.processFile(
+                  project_id,
+                  fsPath,
+                  path,
+                  source,
+                  user_id,
+                  cb
+                )
+              } else if (['existing-doc', 'new-doc'].includes(fileType)) {
+                return UpdateMerger.p.processDoc(
+                  project_id,
+                  user_id,
+                  fsPath,
+                  path,
+                  source,
+                  cb
+                )
+              } else {
+                return cb(new Error('unrecognized file'))
+              }
             }
-          }
-        ],
-        callback
-      )
-    })
+          ],
+          callback
+        )
+      }
+    )
   },
 
   deleteUpdate(user_id, project_id, path, source, callback) {
     if (callback == null) {
-      callback = function() {}
+      callback = function () {}
     }
     return EditorController.deleteEntityWithPath(
       project_id,
       path,
       source,
       user_id,
-      function() {
+      function () {
         return callback()
       }
     )
@@ -192,32 +199,32 @@ module.exports = UpdateMerger = {
 
   p: {
     processDoc(project_id, user_id, fsPath, path, source, callback) {
-      return UpdateMerger.p.readFileIntoTextArray(fsPath, function(
-        err,
-        docLines
-      ) {
-        if (err != null) {
-          OError.tag(
-            err,
-            'error reading file into text array for process doc update',
-            {
-              project_id
+      return UpdateMerger.p.readFileIntoTextArray(
+        fsPath,
+        function (err, docLines) {
+          if (err != null) {
+            OError.tag(
+              err,
+              'error reading file into text array for process doc update',
+              {
+                project_id
+              }
+            )
+            return callback(err)
+          }
+          logger.log({ docLines }, 'processing doc update from tpds')
+          return EditorController.upsertDocWithPath(
+            project_id,
+            path,
+            docLines,
+            source,
+            user_id,
+            function (err) {
+              return callback(err)
             }
           )
-          return callback(err)
         }
-        logger.log({ docLines }, 'processing doc update from tpds')
-        return EditorController.upsertDocWithPath(
-          project_id,
-          path,
-          docLines,
-          source,
-          user_id,
-          function(err) {
-            return callback(err)
-          }
-        )
-      })
+      )
     },
 
     processFile(project_id, fsPath, path, source, user_id, callback) {
@@ -228,14 +235,14 @@ module.exports = UpdateMerger = {
         null,
         source,
         user_id,
-        function(err) {
+        function (err) {
           return callback(err)
         }
       )
     },
 
     readFileIntoTextArray(path, callback) {
-      return fs.readFile(path, 'utf8', function(error, content) {
+      return fs.readFile(path, 'utf8', function (error, content) {
         if (content == null) {
           content = ''
         }

+ 4 - 4
services/web/app/src/Features/TokenAccess/TokenAccessHandler.js

@@ -121,7 +121,7 @@ const TokenAccessHandler = {
       {
         'tokens.readAndWritePrefix': numerics
       },
-      function(err, project) {
+      function (err, project) {
         if (err != null) {
           return callback(err)
         }
@@ -263,7 +263,7 @@ const TokenAccessHandler = {
     }
     V1Api.request(
       { url: `/api/v1/sharelatex/docs/${token}/is_published` },
-      function(err, response, body) {
+      function (err, response, body) {
         if (err != null) {
           return callback(err)
         }
@@ -279,7 +279,7 @@ const TokenAccessHandler = {
         exported: false
       })
     }
-    UserGetter.getUser(v2UserId, { overleaf: 1 }, function(err, user) {
+    UserGetter.getUser(v2UserId, { overleaf: 1 }, function (err, user) {
       if (err != null) {
         return callback(err)
       }
@@ -289,7 +289,7 @@ const TokenAccessHandler = {
       }
       V1Api.request(
         { url: `/api/v1/sharelatex/users/${v1UserId}/docs/${token}/info` },
-        function(err, response, body) {
+        function (err, response, body) {
           if (err != null) {
             return callback(err)
           }

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.