From e06fb95d9eb533a8d7d3897a71f533fb52998dcf Mon Sep 17 00:00:00 2001
From: Andy Nicholson <intothemist@gmail.com>
Date: Tue, 19 Jan 2021 16:48:28 +1100
Subject: [PATCH] feat(*): published manuscripts interface enhancements

---
 app/app.js                                    |   3 +-
 app/components/AdminPage.js                   |  33 +-
 .../component-frontpage/src/Frontpage.js      | 168 +++++-----
 .../src/ManuscriptDetails.js                  |   4 +-
 .../component-frontpage/src/queries.js        |  18 +-
 app/components/component-login/src/Login.jsx  |   2 +
 .../src/components/ManuscriptPage.js          |   3 +
 .../component-manuscripts/src/Manuscripts.jsx |   3 +-
 .../src/components/metadata/ReviewMetadata.js |  12 +-
 app/components/component-submit/src/upload.js |   8 +
 app/components/xpub-journal/src/index.js      |  14 +-
 package.json                                  |  13 +-
 server/app.js                                 |  25 +-
 .../src/reviewBackend.js                      |  20 ++
 yarn.lock                                     | 299 ++++++++++++++++--
 15 files changed, 473 insertions(+), 152 deletions(-)

diff --git a/app/app.js b/app/app.js
index 3416289a67..f67147deda 100644
--- a/app/app.js
+++ b/app/app.js
@@ -1,3 +1,4 @@
+/* eslint-disable import/no-extraneous-dependencies */
 import 'regenerator-runtime/runtime'
 import React from 'react'
 import ReactDOM from 'react-dom'
@@ -12,8 +13,6 @@ import { XpubProvider } from './components/xpub-with-context/src'
 import * as journal from '../config/journal'
 import routes from './routes'
 
-
-
 const history = createBrowserHistory()
 
 const rootEl = document.getElementById('root')
diff --git a/app/components/AdminPage.js b/app/components/AdminPage.js
index 19c02ee7cc..1f1151738b 100644
--- a/app/components/AdminPage.js
+++ b/app/components/AdminPage.js
@@ -11,17 +11,17 @@ import {
 import { JournalContext } from './xpub-journal/src'
 import { XpubContext } from './xpub-with-context/src'
 
-import UsersManager from '../components/component-users-manager/src/UsersManager'
-import Manuscripts from '../components/component-manuscripts/src/Manuscripts'
-import Dashboard from '../components/component-dashboard/src/components/Dashboard'
-import SubmitPage from '../components/component-submit/src/components/SubmitPage'
-import ManuscriptPage from '../components/component-manuscript/src/components/ManuscriptPage'
-import ReviewersPage from '../components/component-review/src/components/ReviewersPage'
-import ReviewPage from '../components/component-review/src/components/ReviewPage'
-import DecisionPage from '../components/component-review/src/components/DecisionPage'
-import FormBuilderPage from '../components/component-formbuilder/src/components/FormBuilderPage'
-import NewSubmissionPage from '../components/component-submit/src/components/NewSubmissionPage'
-import { Profile } from '../components/component-profile/src'
+import UsersManager from './component-users-manager/src/UsersManager'
+import Manuscripts from './component-manuscripts/src/Manuscripts'
+import Dashboard from './component-dashboard/src/components/Dashboard'
+import SubmitPage from './component-submit/src/components/SubmitPage'
+import ManuscriptPage from './component-manuscript/src/components/ManuscriptPage'
+import ReviewersPage from './component-review/src/components/ReviewersPage'
+import ReviewPage from './component-review/src/components/ReviewPage'
+import DecisionPage from './component-review/src/components/DecisionPage'
+import FormBuilderPage from './component-formbuilder/src/components/FormBuilderPage'
+import NewSubmissionPage from './component-submit/src/components/NewSubmissionPage'
+import { Profile } from './component-profile/src'
 
 import { GET_CURRENT_USER } from '../queries'
 
@@ -38,11 +38,10 @@ const getParams = routerPath => {
 
 const Root = styled.div`
   display: grid;
-  grid-template-columns: 200px auto;
   grid-template-areas: 'menu main';
-  max-height: 100vh;
+  grid-template-columns: 200px auto;
   height: 100vh;
-  overflow: hidden;
+  max-height: 100vh;
   ${({ converting }) =>
     converting &&
     `
@@ -51,9 +50,11 @@ const Root = styled.div`
        pointer-events: none;
      }
   `};
+  overflow: hidden;
 `
 
 // TODO: Redirect if token expires
+// eslint-disable-next-line react/prop-types
 const PrivateRoute = ({ component: Component, ...rest }) => (
   <Route
     {...rest}
@@ -67,8 +68,10 @@ const PrivateRoute = ({ component: Component, ...rest }) => (
   />
 )
 
+// eslint-disable-next-line consistent-return
 const updateStuff = data => {
   if (data?.currentUser) {
+    // eslint-disable-next-line no-underscore-dangle
     return currentRolesVar(data.currentUser._currentRoles)
   }
 }
@@ -81,6 +84,7 @@ const AdminPage = () => {
   const { loading, error, data } = useQuery(GET_CURRENT_USER, {
     fetchPolicy: 'network-only',
     // TODO: useCallback used because of bug: https://github.com/apollographql/apollo-client/issues/6301
+    // eslint-disable-next-line no-shadow
     onCompleted: useCallback(data => updateStuff(data), []),
   })
 
@@ -92,6 +96,7 @@ const AdminPage = () => {
   }
 
   let notice = ''
+
   if (error) {
     if (error.networkError) {
       notice = 'You are offline.'
diff --git a/app/components/component-frontpage/src/Frontpage.js b/app/components/component-frontpage/src/Frontpage.js
index ff48860f1b..a3daede1e2 100644
--- a/app/components/component-frontpage/src/Frontpage.js
+++ b/app/components/component-frontpage/src/Frontpage.js
@@ -3,7 +3,6 @@ import { useQuery } from '@apollo/client'
 import { JournalContext } from '../../xpub-journal/src'
 import queries from './queries'
 import { Container, Placeholder, VisualAbstract } from './style'
-import Wax from '../../wax-collab/src/Editoria'
 
 import {
   Spinner,
@@ -16,21 +15,23 @@ import {
   Pagination,
 } from '../../shared'
 
+import { UserAction as Action } from '../../component-manuscripts/src/style'
 
+// eslint-disable-next-line react/prop-types
 const Frontpage = ({ history, ...props }) => {
-
+  // eslint-disable-next-line no-unused-vars
   const [sortName, setSortName] = useState('created')
+  // eslint-disable-next-line no-unused-vars
   const [sortDirection, setSortDirection] = useState('DESC')
   const [page, setPage] = useState(1)
-  const limit = 2
+  const limit = 10
   const sort = sortName && sortDirection && `${sortName}_${sortDirection}`
- 
-  const skipXSweet = file =>
-  !(
-    file.mimeType ===
-    'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
-  )
 
+  const skipXSweet = file =>
+    !(
+      file.mimeType ===
+      'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+    )
 
   const { loading, data, error } = useQuery(queries.frontpage, {
     variables: {
@@ -40,15 +41,15 @@ const Frontpage = ({ history, ...props }) => {
     },
     fetchPolicy: 'network-only',
   })
-  
+
   const journal = useContext(JournalContext)
 
   if (loading) return <Spinner />
   if (error) return JSON.stringify(error)
 
-  const totalCount = data.publishedManuscripts.totalCount
+  const { totalCount } = data.publishedManuscripts
 
-  const frontpage  = (data.publishedManuscripts?.manuscripts || []).map(m => {
+  const frontpage = (data.publishedManuscripts?.manuscripts || []).map(m => {
     const visualAbstract = m.files?.find(f => f.fileType === 'visualAbstract')
     return {
       ...m,
@@ -57,86 +58,96 @@ const Frontpage = ({ history, ...props }) => {
     }
   })
 
- 
-
   return (
     <Container>
       <HeadingWithAction>
         <Heading>Recent publications in {journal.metadata.name}</Heading>
       </HeadingWithAction>
       <Pagination
-          limit={limit}
-          page={page}
-          setPage={setPage}
-          totalCount={totalCount}
-          />
+        limit={limit}
+        page={page}
+        setPage={setPage}
+        totalCount={totalCount}
+      />
 
       {frontpage.length > 0 ? (
         frontpage.map(manuscript => (
+          // eslint-disable-next-line react/jsx-key
           <SectionContent>
             <SectionHeader>
               <Title>{manuscript.meta.title}</Title>
             </SectionHeader>
             <SectionRow key={`manuscript-${manuscript.id}`}>
-
-              {manuscript.submission?.abstract ? 
-              <p>Abstract: {manuscript.submission?.abstract}</p>
-              :  <br/> }
-              {manuscript.visualAbstract ? 
-              <p>
-                Visual abstract:{' '}
-                <VisualAbstract
-                  alt="Visual abstract"
-                  src={manuscript.visualAbstract}
-                />
-              </p> : <br/> }
-              
-              {manuscript.files.length > 0 ? 
-
-              <div>
-                {manuscript.files.map(file => (
-		  skipXSweet(file) && 
-                  <p>
-                    <a
-                      href={file.url}
-                      rel="noopener noreferrer"
-                      target="_blank"
-                    >
-                      {file.filename}
-                    </a>
-                  </p>
-                ))}
-                {manuscript.files.map(file => (
-		  (! skipXSweet(file)) && 
-
-		    <p> 
-			<Wax 
-			content={manuscript.meta.source}
-			readonly
-			/>
-		    </p>
-
-                ))}
-
-
-              </div> : <br/> }
-
-              {manuscript.submission?.links ? 
-              <div>
-                Submitted research objects:
-                {manuscript.submission?.links?.map(link => (
-                  <p>
-                    <a
-                      href={link.url}
-                      rel="noopener noreferrer"
-                      target="_blank"
-                    >
-                      {link.url}
-                    </a>
-                  </p>
-                ))}
-              </div> : <br/> }
-
+              {manuscript.submission?.abstract ? (
+                <p>Abstract: {manuscript.submission?.abstract}</p>
+              ) : (
+                <br />
+              )}
+              {manuscript.visualAbstract ? (
+                <p>
+                  Visual abstract:{' '}
+                  <VisualAbstract
+                    alt="Visual abstract"
+                    src={manuscript.visualAbstract}
+                  />
+                </p>
+              ) : (
+                <br />
+              )}
+
+              {manuscript.files.length > 0 ? (
+                <div>
+                  {manuscript.files.map(
+                    file =>
+                      skipXSweet(file) &&
+                      file.fileType !== 'visualAbstract' && (
+                        <p>
+                          <a
+                            href={file.url}
+                            rel="noopener noreferrer"
+                            target="_blank"
+                          >
+                            {file.filename}
+                          </a>
+                        </p>
+                      ),
+                  )}
+                  {manuscript.files.map(
+                    file =>
+                      !skipXSweet(file) && (
+                        <p>
+                          <Action
+                            to={`/journal/versions/${manuscript.id}/manuscript`}
+                          >
+                            View Manuscript Text
+                          </Action>
+                        </p>
+                      ),
+                  )}
+                </div>
+              ) : (
+                <br />
+              )}
+
+              {manuscript.submission?.links ? (
+                <div>
+                  Submitted research objects:
+                  {manuscript.submission?.links?.map(link => (
+                    // eslint-disable-next-line react/jsx-key
+                    <p>
+                      <a
+                        href={link.url}
+                        rel="noopener noreferrer"
+                        target="_blank"
+                      >
+                        {link.url}
+                      </a>
+                    </p>
+                  ))}
+                </div>
+              ) : (
+                <br />
+              )}
             </SectionRow>
           </SectionContent>
         ))
@@ -146,4 +157,5 @@ const Frontpage = ({ history, ...props }) => {
     </Container>
   )
 }
+
 export default Frontpage
diff --git a/app/components/component-frontpage/src/ManuscriptDetails.js b/app/components/component-frontpage/src/ManuscriptDetails.js
index 6b5bfdab4e..1bbcc7c544 100644
--- a/app/components/component-frontpage/src/ManuscriptDetails.js
+++ b/app/components/component-frontpage/src/ManuscriptDetails.js
@@ -1,5 +1,7 @@
 import React from 'react'
 
-const ManuscriptDetails = () => <div>Nothing here yet.</div>
+const ManuscriptDetails = () => (
+  <div>Nothing here yet in ManuscriptDetails.</div>
+)
 
 export default ManuscriptDetails
diff --git a/app/components/component-frontpage/src/queries.js b/app/components/component-frontpage/src/queries.js
index 3e60696493..effb23f6e8 100644
--- a/app/components/component-frontpage/src/queries.js
+++ b/app/components/component-frontpage/src/queries.js
@@ -1,18 +1,10 @@
+// eslint-disable-next-line import/no-extraneous-dependencies
 import gql from 'graphql-tag'
 
 export default {
   frontpage: gql`
-   
-  query publishedManuscripts(
-    $sort: String
-    $offset: Int
-    $limit: Int
-  ) {
-    publishedManuscripts(
-      sort: $sort
-      offset: $offset
-      limit: $limit
-    ) {
+    query publishedManuscripts($sort: String, $offset: Int, $limit: Int) {
+      publishedManuscripts(sort: $sort, offset: $offset, limit: $limit) {
         totalCount
         manuscripts {
           id
@@ -36,14 +28,14 @@ export default {
             url
             filename
             fileType
-	    mimeType
+            mimeType
           }
           meta {
             manuscriptId
             title
             articleSections
             articleType
-	    source
+            source
             history {
               type
               date
diff --git a/app/components/component-login/src/Login.jsx b/app/components/component-login/src/Login.jsx
index d04d53a279..b3a8a330ce 100644
--- a/app/components/component-login/src/Login.jsx
+++ b/app/components/component-login/src/Login.jsx
@@ -19,9 +19,11 @@ const getNextUrl = () => {
 
 const getToken = props => {
   const { location } = props
+
   if (location && location.search && location.search.match(/^\?token=/)) {
     return location.search.replace(/^\?token=/, '')
   }
+
   return null
 }
 
diff --git a/app/components/component-manuscript/src/components/ManuscriptPage.js b/app/components/component-manuscript/src/components/ManuscriptPage.js
index 651957b5a5..8dc454b8d4 100644
--- a/app/components/component-manuscript/src/components/ManuscriptPage.js
+++ b/app/components/component-manuscript/src/components/ManuscriptPage.js
@@ -37,9 +37,11 @@ const query = gql`
   }
 `
 
+// eslint-disable-next-line react/prop-types
 const ManuscriptPage = ({ match, ...props }) => {
   const { data, loading, error } = useQuery(query, {
     variables: {
+      // eslint-disable-next-line react/prop-types
       id: match.params.version,
     },
   })
@@ -56,4 +58,5 @@ const ManuscriptPage = ({ match, ...props }) => {
     />
   )
 }
+
 export default ManuscriptPage
diff --git a/app/components/component-manuscripts/src/Manuscripts.jsx b/app/components/component-manuscripts/src/Manuscripts.jsx
index 4f940b0720..14b1829504 100644
--- a/app/components/component-manuscripts/src/Manuscripts.jsx
+++ b/app/components/component-manuscripts/src/Manuscripts.jsx
@@ -141,9 +141,10 @@ const Manuscripts = () => {
             </tr>
           </Header>
           <tbody>
-            {manuscripts.map((manuscript, key) => {
+            {manuscripts.map(function (manuscript, key) {
               const latestVersion =
                 manuscript.manuscriptVersions?.[0] || manuscript
+
               return (
                 <Manuscript
                   key={latestVersion.id}
diff --git a/app/components/component-review/src/components/metadata/ReviewMetadata.js b/app/components/component-review/src/components/metadata/ReviewMetadata.js
index ece543a6f2..3b198faffb 100644
--- a/app/components/component-review/src/components/metadata/ReviewMetadata.js
+++ b/app/components/component-review/src/components/metadata/ReviewMetadata.js
@@ -1,3 +1,4 @@
+/* eslint-disable react/prop-types */
 import React from 'react'
 import styled from 'styled-components'
 import { get } from 'lodash'
@@ -11,9 +12,9 @@ import { SectionContent } from '../../../../shared'
 
 const Heading = styled.span`
   font-weight: inherit;
+  overflow: hidden;
   padding: 0 1em 0 0;
   text-overflow: ellipsis;
-  overflow: hidden;
   white-space: nowrap;
 `
 
@@ -30,8 +31,8 @@ const Heading = styled.span`
 // `
 
 const Cell = styled.span`
-  padding: 0;
   grid-column: span 2 / span 2;
+  padding: 0;
 `
 
 const getNote = (notes, type) =>
@@ -45,6 +46,7 @@ const getSupplementaryFiles = supplementary =>
 
 const showFieldData = (manuscript, fieldName) => {
   const data = get(manuscript, fieldName)
+
   // TODO: Make this generic somehow. Perhaps with an additional fieldType?
   if (Array.isArray(data) && fieldName === 'submission.links') {
     return data.map(link => (
@@ -54,11 +56,15 @@ const showFieldData = (manuscript, fieldName) => {
         </a>
       </p>
     ))
-  } else if (Array.isArray(data)) {
+  }
+
+  if (Array.isArray(data)) {
     return data.join(', ')
   }
+
   return data
 }
+
 // Due to migration to new Data Model
 // Attachement component needs different data structure to work
 // needs to change the pubsweet ui Attachement to support the new Data Model
diff --git a/app/components/component-submit/src/upload.js b/app/components/component-submit/src/upload.js
index 2a33bc2cdd..dc0c4f3485 100644
--- a/app/components/component-submit/src/upload.js
+++ b/app/components/component-submit/src/upload.js
@@ -1,5 +1,6 @@
 import config from 'config'
 import request from 'pubsweet-client/src/helpers/api'
+// eslint-disable-next-line import/no-extraneous-dependencies
 import gql from 'graphql-tag'
 import currentRolesVar from '../../../shared/currentRolesVar'
 
@@ -86,6 +87,7 @@ const createManuscriptMutation = gql`
 
 const uploadPromise = (files, client) => {
   const [file] = files
+
   if (files.length > 1) {
     throw new Error('Only one manuscript file can be uploaded')
   }
@@ -127,6 +129,7 @@ const createManuscriptPromise = (
   let source
   let title
   let files = []
+
   if (file) {
     source = typeof response === 'string' ? response : undefined
     title = extractTitle(response) || generateTitle(file.name)
@@ -201,14 +204,17 @@ export default ({
   journals,
   currentUser,
   setConversion,
+  // eslint-disable-next-line consistent-return
 }) => async files => {
   setConversion({ converting: true })
   let manuscriptData
   let uploadResponse
+
   try {
     if (files) {
       const [file] = files
       const { data } = await uploadPromise(files, client)
+
       if (skipXSweet(file)) {
         uploadResponse = {
           fileURL: data.upload.url,
@@ -217,6 +223,7 @@ export default ({
       } else {
         uploadResponse = await DocxToHTMLPromise(file, data)
       }
+
       manuscriptData = await createManuscriptPromise(
         file,
         client,
@@ -234,6 +241,7 @@ export default ({
         undefined,
       )
     }
+
     return redirectPromise(
       setConversion,
       journals,
diff --git a/app/components/xpub-journal/src/index.js b/app/components/xpub-journal/src/index.js
index 053ce7c3fd..c4a699b76f 100644
--- a/app/components/xpub-journal/src/index.js
+++ b/app/components/xpub-journal/src/index.js
@@ -1,13 +1,13 @@
+/* eslint-disable react/destructuring-assignment */
 import React from 'react'
 
 const JournalContext = React.createContext()
 
-const JournalProvider = props => {
-  return (
-    <JournalContext.Provider value={props.journal}>
-      {props.children}
-    </JournalContext.Provider>
-  )
-}
+const JournalProvider = props => (
+  // eslint-disable-next-line react/prop-types
+  <JournalContext.Provider value={props.journal}>
+    {props.children}
+  </JournalContext.Provider>
+)
 
 export { JournalContext, JournalProvider }
diff --git a/package.json b/package.json
index 480aa0abe0..335d253b17 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
     "server": "pubsweet start:server",
     "server:test": "node node_modules/pubsweet-server/src/start",
     "start:server-and-client": "start-test server 'http://localhost:3000/healthcheck' client",
-    "start:services": "docker-compose up postgres",
+    "start:services": "docker-compose up db",
     "test:all:chrome": "start-test test:server-and-client 4000 test:chrome",
     "test:all:firefox": "start-test test:server-and-client 4000 test:firefox",
     "test:chrome": "cypress run --browser chrome",
@@ -74,6 +74,7 @@
     "apollo-link-context": "1.0.20",
     "apollo-link-schema": "1.2.5",
     "apollo-upload-client": "14.1.2",
+    "bcrypt": "^5.0.0",
     "config": "3.3.2",
     "faker": "4.1.0",
     "font-awesome": "4.7.0",
@@ -84,6 +85,7 @@
     "graphql-shield": "7.3.7",
     "graphql-tools": "4.0.8",
     "history": "4.10.1",
+    "husky": "^4.3.8",
     "jimp": "0.16.1",
     "joi": "10.6.0",
     "loadable-components": "0.4.0",
@@ -104,7 +106,8 @@
     "prop-types": "15.7.2",
     "pubsweet": "5.1.14",
     "pubsweet-client": "10.2.5",
-    "pubsweet-server": "13.11.1",
+    "pubsweet-server": "^13.11.6",
+    "re2": "^1.15.9",
     "react": "16.13.1",
     "react-dom": "16.13.1",
     "react-dropzone": "10.2.2",
@@ -119,6 +122,7 @@
     "react-visibility-sensor": "5.1.1",
     "styled-components": "4.4.1",
     "supertest": "3.4.2",
+    "url-regex-safe": "^1.0.2",
     "wax-prosemirror-core": "0.0.10",
     "wax-prosemirror-plugins": "0.0.10",
     "wax-prosemirror-schema": "0.0.10",
@@ -146,7 +150,7 @@
     "@babel/plugin-syntax-import-meta": "7.10.4",
     "@babel/preset-env": "7.11.5",
     "@babel/preset-react": "7.10.4",
-    "@coko/lint": "^1.3.0",
+    "@coko/lint": "^1.4.0",
     "babel-core": "7.0.0-bridge.0",
     "babel-jest": "23.6.0",
     "babel-loader": "8.1.0",
@@ -156,8 +160,9 @@
     "compression-webpack-plugin": "3.1.0",
     "copy-webpack-plugin": "4.6.0",
     "css-loader": "0.28.11",
-    "cypress": "5.3.0",
+    "cypress": "^6.2.0",
     "cypress-file-upload": "4.1.1",
+    "cypress-log-to-output": "^1.1.2",
     "extract-text-webpack-plugin": "3.0.2",
     "file-loader": "1.1.11",
     "html-webpack-plugin": "3.2.0",
diff --git a/server/app.js b/server/app.js
index 157e621c65..4d22992bf1 100644
--- a/server/app.js
+++ b/server/app.js
@@ -1,3 +1,6 @@
+/* eslint-disable no-param-reassign */
+/* eslint-disable global-require */
+/* eslint-disable import/no-extraneous-dependencies */
 const path = require('path')
 const config = require('config')
 
@@ -10,7 +13,6 @@ const helmet = require('helmet')
 const cookieParser = require('cookie-parser')
 const bodyParser = require('body-parser')
 const passport = require('passport')
-const gqlApi = require('./graphql')
 // const index = require('./routes/index')
 // const api = require('./routes/api')
 const logger = require('@pubsweet/logger')
@@ -22,9 +24,10 @@ const registerComponents = require('pubsweet-server/src/register-components') //
 
 // Wax Collab requirements
 const WebSocket = require('ws')
+const EventEmitter = require('events')
 const wsUtils = require('./wax-collab/server-util.js')
 // const cookie = require('cookie')
-const EventEmitter = require('events')
+const gqlApi = require('./graphql')
 
 const configureApp = app => {
   const models = require('@pubsweet/models')
@@ -35,6 +38,7 @@ const configureApp = app => {
   app.use(bodyParser.json({ limit: '50mb' }))
   morgan.token('graphql', ({ body }, res, type) => {
     if (!body.operationName) return ''
+
     switch (type) {
       case 'query':
         return body.query.replace(/\s+/g, ' ')
@@ -66,6 +70,7 @@ const configureApp = app => {
       ),
     )
   }
+
   // Passport strategies
   app.use(passport.initialize())
   const authentication = require('pubsweet-server/src/authentication')
@@ -103,13 +108,20 @@ const configureApp = app => {
 
     if (err.name === 'ValidationError') {
       return res.status(STATUS.BAD_REQUEST).json({ message: err.message })
-    } else if (err.name === 'ConflictError') {
+    }
+
+    if (err.name === 'ConflictError') {
       return res.status(STATUS.CONFLICT).json({ message: err.message })
-    } else if (err.name === 'AuthorizationError') {
+    }
+
+    if (err.name === 'AuthorizationError') {
       return res.status(err.status).json({ message: err.message })
-    } else if (err.name === 'AuthenticationError') {
+    }
+
+    if (err.name === 'AuthenticationError') {
       return res.status(STATUS.UNAUTHORIZED).json({ message: err.message })
     }
+
     return res
       .status(err.status || STATUS.INTERNAL_SERVER_ERROR)
       .json({ message: err.message })
@@ -133,10 +145,12 @@ const configureApp = app => {
         serverProxy.emit('upgrade', request, socket, head, ...rest)
       } else {
         let user = null
+
         if (request.headers.cookie) {
           // const cookies = cookie.parse(request.headers.cookie)
           // const user = cookies.user_identifier
         }
+
         // TODO: Do real auth for Wax-collab
         user = 'test' // shortcut
 
@@ -145,6 +159,7 @@ const configureApp = app => {
           socket.destroy()
           return
         }
+
         wss.handleUpgrade(request, socket, head, ws => {
           wss.emit('connection', ws, request)
         })
diff --git a/server/component-xpub-review-backend/src/reviewBackend.js b/server/component-xpub-review-backend/src/reviewBackend.js
index 1132bc19e4..316488528c 100644
--- a/server/component-xpub-review-backend/src/reviewBackend.js
+++ b/server/component-xpub-review-backend/src/reviewBackend.js
@@ -1,3 +1,5 @@
+/* eslint-disable global-require */
+/* eslint-disable import/no-extraneous-dependencies */
 const { pick } = require('lodash')
 const config = require('config')
 const passport = require('passport')
@@ -32,6 +34,7 @@ module.exports = app => {
       }
 
       const canViewVersion = await authsome.can(req.user, 'GET', version)
+
       const canPatchVersion = await authsome.can(
         req.user,
         'PATCH',
@@ -40,9 +43,11 @@ module.exports = app => {
 
       if (!canPatchVersion || !canViewVersion) throw new AuthorizationError()
       let versionUpdateData = req.body.reviewers
+
       if (canPatchVersion.filter) {
         versionUpdateData = canPatchVersion.filter(versionUpdateData)
       }
+
       await version.updateProperties({ reviewers: versionUpdateData })
       await version.save()
 
@@ -71,11 +76,13 @@ module.exports = app => {
       }
 
       const canViewProject = await authsome.can(req.user, 'GET', project)
+
       const canPatchProject = await authsome.can(
         req.user,
         'PATCH',
         currentAndUpdateProject,
       )
+
       if (!canPatchProject || !canViewProject) throw new AuthorizationError()
 
       await Team.query().upsertGraphAndFetch(
@@ -118,6 +125,7 @@ module.exports = app => {
       }
 
       const canViewVersion = await authsome.can(req.user, 'GET', version)
+
       const canPatchVersion = await authsome.can(
         req.user,
         'PATCH',
@@ -126,14 +134,17 @@ module.exports = app => {
 
       if (!canPatchVersion || !canViewVersion) throw new AuthorizationError()
       let versionUpdateData = { decision: req.body.decision }
+
       if (canPatchVersion.filter) {
         versionUpdateData = canPatchVersion.filter(versionUpdateData)
       }
+
       await version.updateProperties(versionUpdateData)
 
       let nextVersionData
       let projectUpdateData = {}
       let message
+
       switch (version.decision.recommendation) {
         case 'accept':
           projectUpdateData.status = 'accepted'
@@ -159,6 +170,7 @@ module.exports = app => {
             'files',
             'notes',
           ])
+
           nextVersionData = {
             fragmentType: 'version',
             created: new Date(),
@@ -177,16 +189,20 @@ module.exports = app => {
 
       let nextVersion
       let canViewNextVersion
+
       if (nextVersionData) {
         const canCreateVersion = await authsome.can(req.user, 'POST', {
           path: '/collections/:collectionId/fragments',
           collection: project,
           fragment: nextVersionData,
         })
+
         if (!canCreateVersion) throw new AuthorizationError()
+
         if (canCreateVersion.filter) {
           nextVersionData = canCreateVersion.filter(nextVersionData)
         }
+
         nextVersion = new Fragment(nextVersionData)
 
         canViewNextVersion = await authsome.can(req.user, 'GET', nextVersion)
@@ -195,15 +211,19 @@ module.exports = app => {
       currentAndUpdate = { current: project, update: req.body }
 
       const canViewProject = await authsome.can(req.user, 'GET', project)
+
       const canPatchProject = await authsome.can(
         req.user,
         'PATCH',
         currentAndUpdate,
       )
+
       if (!canPatchProject || !canViewProject) throw new AuthorizationError()
+
       if (canPatchProject.filter) {
         projectUpdateData = canPatchProject.filter(projectUpdateData)
       }
+
       await project.updateProperties(projectUpdateData)
 
       await Promise.all([
diff --git a/yarn.lock b/yarn.lock
index 35239ae6a6..b367c0e140 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1365,10 +1365,10 @@
     lodash "^4.17.19"
     to-fast-properties "^2.0.0"
 
-"@coko/lint@^1.3.0":
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/@coko/lint/-/lint-1.3.0.tgz#b51464ad04162a378a681a7ae22514e3ad18c2df"
-  integrity sha512-jBLcukH05btHV+EBZfwSou5FFAu6UrZKZPjP4wslLU7fGLFpOBl28mlLAKg8PpCcEISe0PK0TWZGtEJNQ1YDqA==
+"@coko/lint@^1.4.0":
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/@coko/lint/-/lint-1.4.0.tgz#476986325676607d89a23cca2566da31d1ebd5b3"
+  integrity sha512-ed9WswpDJoxB8VVja/CgFUGjD0NxoQDvD/BXJ5kM/X/AaFYeanY7nr1bI4nCcas/IHHDb9nnEPRulqfMCuMBLA==
   dependencies:
     "@commitlint/cli" "^8.3.5"
     "@commitlint/config-conventional" "^8.3.4"
@@ -2338,6 +2338,21 @@
     tmp-promise "^2.0.0"
     umzug "^2.1.0"
 
+"@pubsweet/db-manager@^3.1.15":
+  version "3.1.15"
+  resolved "https://registry.yarnpkg.com/@pubsweet/db-manager/-/db-manager-3.1.15.tgz#692ef08407e1682b766296efd248bcb613d5bcd4"
+  integrity sha512-+Wu1uf1MoXRALW/VjXLZLviHK/ot8ILYg5EukPZJ8BOxiX533gEkM/d8x93UdIPVsTPibGeCbkczfPTgZMn/SA==
+  dependencies:
+    "@hapi/joi" "^14.3.0"
+    "@pubsweet/logger" "^0.2.50"
+    fs-extra "^8.1.0"
+    knex "^0.21.1"
+    lodash "^4.17.11"
+    objection "^2.1.3"
+    pg "^7.8.0"
+    tmp-promise "^2.0.0"
+    umzug "^2.1.0"
+
 "@pubsweet/errors@^2.0.36":
   version "2.0.36"
   resolved "https://registry.yarnpkg.com/@pubsweet/errors/-/errors-2.0.36.tgz#afa254f26578468b6d1f8839ba6310e2fd7c6177"
@@ -2345,6 +2360,13 @@
   dependencies:
     http-status-codes "^1.3.0"
 
+"@pubsweet/errors@^2.0.40":
+  version "2.0.40"
+  resolved "https://registry.yarnpkg.com/@pubsweet/errors/-/errors-2.0.40.tgz#911448a01dd42cc9e72666b8d966083af73c3acd"
+  integrity sha512-/xbJvsdt8oCWebr9uqFdiytPbpBMxReVxK0BtbHg4LA9Z2GSU/PP1vk+1IcyIxI4lhOMgC5lwkKWbufI4sOJ3g==
+  dependencies:
+    http-status-codes "^1.3.0"
+
 "@pubsweet/job-xsweet@2.1.11":
   version "2.1.11"
   resolved "https://registry.yarnpkg.com/@pubsweet/job-xsweet/-/job-xsweet-2.1.11.tgz#a667f29912ce28df2a5929871cd1f4b4f2dcbb0a"
@@ -2374,6 +2396,14 @@
     "@hapi/joi" "^14.5.0"
     config "^3.0.1"
 
+"@pubsweet/logger@^0.2.50":
+  version "0.2.50"
+  resolved "https://registry.yarnpkg.com/@pubsweet/logger/-/logger-0.2.50.tgz#b2553d94c7e83f8a152ae0f315a24154931a123f"
+  integrity sha512-Z36ZVgI6XCLHqqQf8L21uirJjeJOrb3FbCqHFy7ISQobDQ/907w2S9JR21jaYK8vyqtb0bpAWseh1niF91mw/Q==
+  dependencies:
+    "@hapi/joi" "^14.5.0"
+    config "^3.0.1"
+
 "@pubsweet/model-user@6.0.3":
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/@pubsweet/model-user/-/model-user-6.0.3.tgz#b0ad3e307e293d9794f20bcc3797164732eb221b"
@@ -2399,6 +2429,13 @@
   dependencies:
     "@pubsweet/logger" "^0.2.49"
 
+"@pubsweet/models@^0.3.15":
+  version "0.3.15"
+  resolved "https://registry.yarnpkg.com/@pubsweet/models/-/models-0.3.15.tgz#30170590045e2748f907930b7b3922f6a822fc74"
+  integrity sha512-CuE+5ZQNWS6UC5JGWzrMpoitK/bQjZ0aAAKqaPj5jaFnV3mtoYAv+D4NE40lzDRQDVFrRrNZbb9APvRiWiVrFg==
+  dependencies:
+    "@pubsweet/logger" "^0.2.50"
+
 "@pubsweet/ui-toolkit@^2.3.2":
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/@pubsweet/ui-toolkit/-/ui-toolkit-2.3.2.tgz#44b9b97b399027ef90bf0c30768475284f437fb4"
@@ -4445,6 +4482,14 @@ bcrypt@^3.0.6:
     nan "2.14.0"
     node-pre-gyp "0.14.0"
 
+bcrypt@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.0.tgz#051407c7cd5ffbfb773d541ca3760ea0754e37e2"
+  integrity sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==
+  dependencies:
+    node-addon-api "^3.0.0"
+    node-pre-gyp "0.15.0"
+
 bfj@^6.1.1:
   version "6.1.2"
   resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f"
@@ -5209,6 +5254,14 @@ chownr@^2.0.0:
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
   integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
 
+chrome-remote-interface@^0.27.1:
+  version "0.27.2"
+  resolved "https://registry.yarnpkg.com/chrome-remote-interface/-/chrome-remote-interface-0.27.2.tgz#e5605605f092b7ef8575d95304e004039c9d0ab9"
+  integrity sha512-pVLljQ29SAx8KIv5tSa9sIf8GrEsAZdPJoeWOmY3/nrIzFmE+EryNNHvDkddGod0cmAFTv+GmPG0uvzxi2NWsA==
+  dependencies:
+    commander "2.11.x"
+    ws "^6.1.0"
+
 chrome-trace-event@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
@@ -5542,6 +5595,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
+commander@2.11.x:
+  version "2.11.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
+  integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==
+
 commander@2.17.x:
   version "2.17.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
@@ -5552,11 +5610,6 @@ commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, comm
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
 
-commander@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
-  integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
-
 commander@^5.1.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
@@ -6196,10 +6249,18 @@ cypress-file-upload@4.1.1:
   dependencies:
     mime "^2.4.4"
 
-cypress@5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.3.0.tgz#91122219ae66ab910058970dbf36619ab0fbde6c"
-  integrity sha512-XgebyqL7Th6/8YenE1ddb7+d4EiCG2Jvg/5c8+HPfFFY/gXnOVhoCVUU3KW8qg3JL7g0B+iJbHd5hxuCqbd1RQ==
+cypress-log-to-output@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/cypress-log-to-output/-/cypress-log-to-output-1.1.2.tgz#b9635a907ed06961cd53cc1bff8d6f55a981a773"
+  integrity sha512-C1+ECMc/XXc4HqAEHdlw0X2wFhcoZZ/4qXHZkOAU/rRXMQXnbiO7JJtpLCKrLfOXlxB+jFwDAIdlPxPMfV3cFw==
+  dependencies:
+    chalk "^2.4.2"
+    chrome-remote-interface "^0.27.1"
+
+cypress@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.2.0.tgz#1a8a7dd5bd08db3064551a9f12072963cc9337bf"
+  integrity sha512-m/rkcogYM9MTy8rbsZgyS5wT2L/J+B5V2bY2ztkDNMyqhk/oZgUF4KTWVLzkW2I+scg0iAddca95tLlt7XnAtw==
   dependencies:
     "@cypress/listr-verbose-renderer" "^0.4.1"
     "@cypress/request" "^2.88.5"
@@ -6213,7 +6274,7 @@ cypress@5.3.0:
     chalk "^4.1.0"
     check-more-types "^2.24.0"
     cli-table3 "~0.6.0"
-    commander "^4.1.1"
+    commander "^5.1.0"
     common-tags "^1.8.0"
     debug "^4.1.1"
     eventemitter2 "^6.4.2"
@@ -6231,10 +6292,10 @@ cypress@5.3.0:
     minimist "^1.2.5"
     moment "^2.27.0"
     ospath "^1.2.2"
-    pretty-bytes "^5.3.0"
+    pretty-bytes "^5.4.1"
     ramda "~0.26.1"
     request-progress "^3.0.0"
-    supports-color "^7.1.0"
+    supports-color "^7.2.0"
     tmp "~0.2.1"
     untildify "^4.0.0"
     url "^0.11.0"
@@ -8174,6 +8235,14 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
+find-up@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+  dependencies:
+    locate-path "^6.0.0"
+    path-exists "^4.0.0"
+
 find-versions@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e"
@@ -8181,6 +8250,13 @@ find-versions@^3.2.0:
   dependencies:
     semver-regex "^2.0.0"
 
+find-versions@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965"
+  integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==
+  dependencies:
+    semver-regex "^3.1.2"
+
 findup-sync@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
@@ -9456,6 +9532,22 @@ husky@^4.2.3:
     slash "^3.0.0"
     which-pm-runs "^1.0.0"
 
+husky@^4.3.8:
+  version "4.3.8"
+  resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d"
+  integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==
+  dependencies:
+    chalk "^4.0.0"
+    ci-info "^2.0.0"
+    compare-versions "^3.6.0"
+    cosmiconfig "^7.0.0"
+    find-versions "^4.0.0"
+    opencollective-postinstall "^2.0.2"
+    pkg-dir "^5.0.0"
+    please-upgrade-node "^3.2.0"
+    slash "^3.0.0"
+    which-pm-runs "^1.0.0"
+
 i@0.3.x:
   version "0.3.6"
   resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d"
@@ -9695,6 +9787,11 @@ install-artifact-from-github@^1.0.2:
   resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.0.2.tgz#e1e478dd29880b9112ecd684a84029603e234a9d"
   integrity sha512-yuMFBSVIP3vD0SDBGUqeIpgOAIlFx8eQFknQObpkYEM5gsl9hy6R9Ms3aV+Vw9MMyYsoPMeex0XDnfgY7uzc+Q==
 
+install-artifact-from-github@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.2.0.tgz#adcbd123c16a4337ec44ea76d0ebf253cc16b074"
+  integrity sha512-3OxCPcY55XlVM3kkfIpeCgmoSKnMsz2A3Dbhsq0RXpIknKQmrX1YiznCeW9cD2ItFmDxziA3w6Eg8d80AoL3oA==
+
 internal-ip@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
@@ -11450,6 +11547,13 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+locate-path@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+  dependencies:
+    p-locate "^5.0.0"
+
 lodash-es@^4.17.11, lodash-es@^4.17.14:
   version "4.17.15"
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
@@ -12412,6 +12516,11 @@ nan@^2.12.1, nan@^2.14.1:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
   integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
 
+nan@^2.14.2:
+  version "2.14.2"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
+  integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
+
 nanomatch@^1.2.9:
   version "1.2.13"
   resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -12478,6 +12587,15 @@ needle@^2.2.1:
     iconv-lite "^0.4.4"
     sax "^1.2.4"
 
+needle@^2.5.0:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.2.tgz#cf1a8fce382b5a280108bba90a14993c00e4010a"
+  integrity sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==
+  dependencies:
+    debug "^3.2.6"
+    iconv-lite "^0.4.4"
+    sax "^1.2.4"
+
 negotiator@0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -12510,6 +12628,11 @@ nocache@2.1.0:
   resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f"
   integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==
 
+node-addon-api@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239"
+  integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==
+
 node-dev@^4.0.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/node-dev/-/node-dev-4.3.0.tgz#de98164a86bbb6dbd7378e23f3d6638041d4a81f"
@@ -12561,6 +12684,22 @@ node-gyp@^7.0.0:
     tar "^6.0.1"
     which "^2.0.2"
 
+node-gyp@^7.1.2:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae"
+  integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==
+  dependencies:
+    env-paths "^2.2.0"
+    glob "^7.1.4"
+    graceful-fs "^4.2.3"
+    nopt "^5.0.0"
+    npmlog "^4.1.2"
+    request "^2.88.2"
+    rimraf "^3.0.2"
+    semver "^7.3.2"
+    tar "^6.0.2"
+    which "^2.0.2"
+
 node-int64@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -12622,6 +12761,22 @@ node-pre-gyp@0.14.0:
     semver "^5.3.0"
     tar "^4.4.2"
 
+node-pre-gyp@0.15.0:
+  version "0.15.0"
+  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz#c2fc383276b74c7ffa842925241553e8b40f1087"
+  integrity sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==
+  dependencies:
+    detect-libc "^1.0.2"
+    mkdirp "^0.5.3"
+    needle "^2.5.0"
+    nopt "^4.0.1"
+    npm-packlist "^1.1.6"
+    npmlog "^4.0.2"
+    rc "^1.2.7"
+    rimraf "^2.6.1"
+    semver "^5.3.0"
+    tar "^4.4.2"
+
 node-releases@^1.1.58:
   version "1.1.59"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.59.tgz#4d648330641cec704bff10f8e4fe28e453ab8e8e"
@@ -12661,6 +12816,13 @@ nopt@^4.0.1, nopt@^4.0.3:
     abbrev "1"
     osenv "^0.1.4"
 
+nopt@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
+  integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
+  dependencies:
+    abbrev "1"
+
 nopt@~1.0.10:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
@@ -13166,6 +13328,13 @@ p-limit@^2.0.0, p-limit@^2.2.0:
   dependencies:
     p-try "^2.0.0"
 
+p-limit@^3.0.2:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+  integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+  dependencies:
+    yocto-queue "^0.1.0"
+
 p-locate@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
@@ -13187,6 +13356,13 @@ p-locate@^4.1.0:
   dependencies:
     p-limit "^2.2.0"
 
+p-locate@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+  dependencies:
+    p-limit "^3.0.2"
+
 p-map@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
@@ -13731,6 +13907,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
   dependencies:
     find-up "^4.0.0"
 
+pkg-dir@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760"
+  integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==
+  dependencies:
+    find-up "^5.0.0"
+
 pkginfo@0.3.x:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
@@ -14183,10 +14366,10 @@ prettier@^2.0.2:
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
   integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
 
-pretty-bytes@^5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2"
-  integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==
+pretty-bytes@^5.4.1:
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.5.0.tgz#0cecda50a74a941589498011cf23275aa82b339e"
+  integrity sha512-p+T744ZyjjiaFlMUZZv6YPC5JrkNj8maRmPaQCWFJFplUAzpIUTRaTcS+7wmZtUoFXHtESJb23ISliaWyz3SHA==
 
 pretty-error@^2.0.2:
   version "2.1.1"
@@ -14526,7 +14709,7 @@ pubsweet-client@10.2.5:
     styled-normalize "^8.0.6"
     subscriptions-transport-ws "^0.9.12"
 
-pubsweet-server@13.11.1, pubsweet-server@^13.11.1:
+pubsweet-server@^13.11.1:
   version "13.11.1"
   resolved "https://registry.yarnpkg.com/pubsweet-server/-/pubsweet-server-13.11.1.tgz#8ebed92e8cfddee3aa65543473896f0cc66062e2"
   integrity sha512-MT6KincsVjgYjd3FtNqQd2AcuM39vM9Ebe6AlhFUJVhgNmIuFAXrg3p/pjxDxkCWKDs/oI9V8brldndzvU29UQ==
@@ -14563,11 +14746,53 @@ pubsweet-server@13.11.1, pubsweet-server@^13.11.1:
     subscriptions-transport-ws "^0.9.12"
     waait "^1.0.2"
 
+pubsweet-server@^13.11.6:
+  version "13.11.6"
+  resolved "https://registry.yarnpkg.com/pubsweet-server/-/pubsweet-server-13.11.6.tgz#780e2a250de7f737c3b9feeee03dd7e03513c0f6"
+  integrity sha512-9LZfE6PYbtM4NLgFufKbp96CVv0sfMp0LyO+8vsCnEZW5gGXcPSSkokDoAtIoxxje4OcnQkAfoKv1CqxKaWiLA==
+  dependencies:
+    "@pubsweet/db-manager" "^3.1.15"
+    "@pubsweet/errors" "^2.0.40"
+    "@pubsweet/logger" "^0.2.50"
+    "@pubsweet/models" "^0.3.15"
+    apollo-server-express "^2.4.8"
+    authsome "^0.1.0"
+    body-parser "^1.15.2"
+    config "^3.0.1"
+    cookie-parser "^1.4.3"
+    dataloader "^1.4.0"
+    dotenv "^4.0.0"
+    express "^4.16.1"
+    fs-extra "^8.1.0"
+    graphql "^14.2.1"
+    graphql-postgres-subscriptions "^1.0.4"
+    helmet "^3.8.1"
+    http-status-codes "^1.0.6"
+    jsonwebtoken "^8.4.0"
+    lodash "^4.0.0"
+    morgan "^1.8.2"
+    multer "^1.1.0"
+    objection "^2.1.3"
+    passport "^0.4.0"
+    passport-anonymous "^1.0.1"
+    passport-http-bearer "^1.0.1"
+    passport-local "^1.0.0"
+    pg "^7.4.1"
+    pg-boss "^3.1.2"
+    pubsweet-sse "^1.0.44"
+    subscriptions-transport-ws "^0.9.12"
+    waait "^1.0.2"
+
 pubsweet-sse@^1.0.40:
   version "1.0.40"
   resolved "https://registry.yarnpkg.com/pubsweet-sse/-/pubsweet-sse-1.0.40.tgz#93736085ca472ba28b11d08a212a5b96dfaab314"
   integrity sha512-1hVcG3D4wxMyA979pllqIIBrnK8i9BilEvF1xsIu20zTh053eA5bMbJXxR5mm8OUGNqXdqqoz8LsZuJqR43XNQ==
 
+pubsweet-sse@^1.0.44:
+  version "1.0.44"
+  resolved "https://registry.yarnpkg.com/pubsweet-sse/-/pubsweet-sse-1.0.44.tgz#71f7d4bb8faad1bb35b40b0c54b89c95397cad67"
+  integrity sha512-vstlq2qiJHO3vn8nLpMFWRdIIHmotJKECjm8FXbU+pqiqs28/U1LsFOADVRhRn9nRtobdSF2soQYWA4FpTD6yg==
+
 pubsweet@5.1.14:
   version "5.1.14"
   resolved "https://registry.yarnpkg.com/pubsweet/-/pubsweet-5.1.14.tgz#a4f8756e72b3a8c63b24344d52e72d7bdd3a5180"
@@ -14794,6 +15019,15 @@ re2@^1.15.4:
     nan "^2.14.1"
     node-gyp "^7.0.0"
 
+re2@^1.15.9:
+  version "1.15.9"
+  resolved "https://registry.yarnpkg.com/re2/-/re2-1.15.9.tgz#9ed16171edcb0bc4f0e239bf55229ff3f26acbe3"
+  integrity sha512-AXWEhpMTBdC+3oqbjdU07dk0pBCvxh5vbOMLERL6Y8FYBSGn4vXlLe8cYszn64Yy7H8keVMrgPzoSvOd4mePpg==
+  dependencies:
+    install-artifact-from-github "^1.2.0"
+    nan "^2.14.2"
+    node-gyp "^7.1.2"
+
 react-base16-styling@^0.5.1:
   version "0.5.3"
   resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.5.3.tgz#3858f24e9c4dd8cbd3f702f3f74d581ca2917269"
@@ -15910,6 +16144,11 @@ semver-regex@^2.0.0:
   resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338"
   integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==
 
+semver-regex@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807"
+  integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==
+
 "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -16926,6 +17165,13 @@ supports-color@^7.1.0:
   dependencies:
     has-flag "^4.0.0"
 
+supports-color@^7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+  dependencies:
+    has-flag "^4.0.0"
+
 svg-tags@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
@@ -17011,7 +17257,7 @@ tar@^4.4.2:
     safe-buffer "^5.1.2"
     yallist "^3.0.3"
 
-tar@^6.0.1:
+tar@^6.0.1, tar@^6.0.2:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f"
   integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==
@@ -17783,7 +18029,7 @@ url-parse@^1.4.3:
     querystringify "^2.1.1"
     requires-port "^1.0.0"
 
-url-regex-safe@~1.0.2:
+url-regex-safe@^1.0.2, url-regex-safe@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/url-regex-safe/-/url-regex-safe-1.0.2.tgz#822cbab107d7f4f80342f0f0798acf63cac9fd1d"
   integrity sha512-t1doIKbYDBRyqXZz7A98AXH/zimKmYapxFjBZwcz3BmqjB921rUPEUokAaShKyenaX3McOM8FbkWLvFvX3Jsyw==
@@ -18640,7 +18886,7 @@ ws@^5.2.0:
   dependencies:
     async-limiter "~1.0.0"
 
-ws@^6.0.0, ws@^6.2.1:
+ws@^6.0.0, ws@^6.1.0, ws@^6.2.1:
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
   integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
@@ -18890,6 +19136,11 @@ yjs@13.3.0:
   dependencies:
     lib0 "^0.2.32"
 
+yocto-queue@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+  integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+
 yup@^0.29.3:
   version "0.29.3"
   resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea"
-- 
GitLab