diff --git a/app/components/GrantSearch.jsx b/app/components/GrantSearch.jsx
index 631136102b90377baacadecbbce81c4ccb316a13..8ab5658e2bea05730f5a2659e1f0724d1ad08e67 100755
--- a/app/components/GrantSearch.jsx
+++ b/app/components/GrantSearch.jsx
@@ -188,7 +188,7 @@ class GrantSearch extends React.Component {
Funding
Add all grants that support this manuscript
(
+
+
+ gift
+
+
+ gift
+
+
+ Please note that the Helpdesk will be unavailable from {start} to {end}.
+ Any calls or e-mails will be saved and responded to on our return. Happy
+ Holidays from the Europe PMC team.{' '}
+
+
+)
+
+export default Holiday
diff --git a/app/components/activity/MetaEdit.jsx b/app/components/activity/MetaEdit.jsx
index 061643341797b9889456cbcad9f550ec933826c7..18385af1295b02c4fcab5fa397c23c47732ba10b 100644
--- a/app/components/activity/MetaEdit.jsx
+++ b/app/components/activity/MetaEdit.jsx
@@ -1,53 +1,23 @@
import React from 'react'
import { omit } from 'lodash'
-import styled, { withTheme } from 'styled-components'
-import { th } from '@pubsweet/ui-toolkit'
-import { Action, H2, Button, Icon } from '@pubsweet/ui'
-import { states } from 'config'
-import { Portal, Buttons, CloseModal, Notification } from '../ui'
+import { withTheme } from 'styled-components'
+import { Button } from '@pubsweet/ui'
+import { Portal, Buttons, CloseModal } from '../ui'
import { ManuscriptMutations, NoteMutations } from '../SubmissionMutations'
-import SubmissionCancel from '../SubmissionCancel'
import ResolveDuplicates from '../ResolveDuplicates'
import ReviewerEdit from './ReviewerEdit'
import CitationEdit from './CitationEdit'
import GrantsEdit from './GrantsEdit'
import EmbargoEdit from './EmbargoEdit'
+import StatusEdit from './StatusEdit'
import { QUERY_ACTIVITY_INFO } from './operations'
-const RouteButton = styled(Button)`
- align-items: center;
- display: inline-flex;
-`
-const FlexP = styled.p`
- display: flex;
- align-items: center;
- & > button {
- flex: 0 0 auto;
- }
- & > span {
- flex: 1 1 50%;
- margin-left: ${th('gridUnit')};
- }
-`
export const Exit = ({ close }) => (
)
-const DeleteButton = ({ deleteMan }) => (
- deleteMan()}>Confirm deletion
-)
-
-const RecoverButton = ({ callback, recoverMan }) => (
- recoverMan(callback())}>
-
- refresh-cw
-
- Recover manuscript
-
-)
-
const DuplicatesWithMutations = NoteMutations(ResolveDuplicates)
const ReviewerWithMutations = NoteMutations(ReviewerEdit)
@@ -72,10 +42,7 @@ const MetaEdit = withTheme(
return n
})
: []
- const qa = states.indexOf('submitted')
- const xml = states.indexOf('xml-triage')
- const done = states.indexOf('ncbi-ready')
- const curr = states.indexOf(manuscript.status)
+
if (toEdit === 'dupes' && duplicates) {
return (
- Send/Remove
- {manuscript.deleted ? (
-
-
- {`Manuscript succesfully deleted. If not already sent, please send a message to the user(s) explaining the deletion.`}
-
-
-
-
-
- ) : (
-
- {manuscript.status === 'submission-error' ? (
-
-
- props.setStatus(lastStatus || 'submitted')
- }
- >
-
- check-circle
-
- Cancel submission error
-
-
- Cancel submission error request and send back to
- {(lastStatus &&
- lastStatus === 'xml-triage' &&
- ` XML errors state ('xml-triage')`) ||
- (lastStatus &&
- lastStatus === 'in-review' &&
- ` Initial review state (in-review)`) ||
- ` QA state ('submitted')`}
-
-
- ) : (
-
- {lastStatus && (
-
-
- props.setStatus(lastStatus, close)
- }
- >
-
- skip-back
-
- Send back
-
-
- to previous status of submission: {lastStatus}
-
- Note that no emails will be automatically
- sent!
-
-
- )}
-
- qa && curr !== xml)}
- onClick={() =>
- props.setStatus(
- curr > xml ? 'xml-triage' : 'submitted',
- close,
- )
- }
- >
-
- send
-
- Send for routing
-
- {curr > qa && curr !== xml ? (
-
- {` send to most recent routable status:`}
- {curr > xml ? ' xml-triage' : ' submitted'}
-
- ) : (
-
- {` The submission is already in a routable state (`}
- {manuscript.status}
- {`). You may send it backward or forward from the manuscript view.`}
-
- )}
-
-
- )}
-
- = done}
- onClick={e =>
- e.currentTarget.nextElementSibling.classList.remove(
- 'hidden',
- )
- }
- >
-
- trash-2
-
- Delete manuscript
-
-
- {` Are you certain? `}
-
-
- {curr >= done && (
-
- {` Manuscript has been sent to PMC and must be withdrawn.`}
-
- )}
-
-
- )}
-
-
+
)
default:
return null
diff --git a/app/components/activity/MetaSec.jsx b/app/components/activity/MetaSec.jsx
index 469f13bf49804f396ac67be10b810bbb5e434521..ce0ce3a82a8a494e7f27f58b232896721c732711 100644
--- a/app/components/activity/MetaSec.jsx
+++ b/app/components/activity/MetaSec.jsx
@@ -65,7 +65,7 @@ const DupeButton = styled(Button)`
padding: calc(${th('gridUnit')} / 2) ${th('gridUnit')};
font-size: ${th('fontSizeBaseSmall')};
`
-const EditIcon = () => (
+export const EditIcon = () => (
edit-3
diff --git a/app/components/activity/StatusEdit.jsx b/app/components/activity/StatusEdit.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..83be126f415b525d0811488943acc6ea76c94595
--- /dev/null
+++ b/app/components/activity/StatusEdit.jsx
@@ -0,0 +1,195 @@
+import React from 'react'
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+import { Action, H2, Button, Icon, RadioGroup } from '@pubsweet/ui'
+import { states } from 'config'
+import { Notification } from '../ui'
+import SubmissionCancel from '../SubmissionCancel'
+import { QUERY_ACTIVITY_INFO } from './operations'
+import { Exit } from './MetaEdit'
+import { EditIcon } from './MetaSec'
+
+const FlexP = styled.p`
+ display: flex;
+ align-items: center;
+ & > button {
+ flex: 0 0 auto;
+ }
+ & > span {
+ flex: 1 1 50%;
+ margin-left: ${th('gridUnit')};
+ }
+`
+const RouteButton = styled(Button)`
+ align-items: center;
+ display: inline-flex;
+`
+const StatusDesc = [
+ 'not yet submitted',
+ 'not yet submitted',
+ 'submission error',
+ 'needs reviewer approval',
+ 'needs submission QA',
+ 'tagging',
+ 'needs XML QA',
+ 'has XML errors',
+ 'needs final review',
+ 'needs citation',
+ 'approved for Europe PMC',
+ 'failed NCBI loading',
+ 'available in Europe PMC',
+ 'being withdrawn',
+]
+
+const DeleteButton = ({ deleteMan }) => (
+ deleteMan()}>Confirm deletion
+)
+
+const RecoverButton = ({ callback, recoverMan }) => (
+ recoverMan(callback())}>
+
+ refresh-cw
+
+ Recover manuscript
+
+)
+
+class StatusEdit extends React.Component {
+ state = {
+ edit: !this.props.lastStatus,
+ selected: this.props.lastStatus,
+ }
+ render() {
+ const { manuscript, lastStatus, setStatus, close } = this.props
+ const { edit, selected } = this.state
+ const done = states.indexOf('ncbi-ready')
+ const curr = states.indexOf(manuscript.status)
+ const options = states.map((s, i) => ({
+ value: s,
+ label: `"${s}" – ${StatusDesc[i]}${
+ s === lastStatus ? ' [most recent]' : ''
+ }${s === manuscript.status ? ' [current]' : ''}`,
+ disabled: s === manuscript.status,
+ }))
+ return (
+
+ Send/Remove
+ {manuscript.deleted ? (
+
+
+ {`Manuscript succesfully deleted. If not already sent, please send a message to the user(s) explaining the deletion.`}
+
+
+
+
+
+ ) : (
+
+ {manuscript.status === 'submission-error' && (
+
+ setStatus(lastStatus || 'submitted')}
+ >
+
+ check-circle
+
+ Cancel submission error
+
+
+ Cancel submission error request and send back to
+ {(lastStatus &&
+ lastStatus === 'xml-triage' &&
+ ` XML errors state ('xml-triage')`) ||
+ (lastStatus &&
+ lastStatus === 'in-review' &&
+ ` Initial review state (in-review)`) ||
+ ` QA state ('submitted')`}
+
+
+ )}
+
+
+ {
+ setStatus(selected)
+ close()
+ }}
+ >
+
+ send
+
+ Change status
+
+
+ send{selected === lastStatus && ' back'} to {selected}
+ this.setState({ edit: true })}>
+
+
+
+ {selected === lastStatus && '(most recent status)'}
+
+
+ Please note that no emails will be automatically sent!
+
+
+ {edit && (
+ this.setState({ selected: v })}
+ options={options}
+ value={selected}
+ />
+ )}
+
+ = done}
+ onClick={e =>
+ e.currentTarget.nextElementSibling.classList.remove(
+ 'hidden',
+ )
+ }
+ >
+
+ trash-2
+
+ Delete manuscript
+
+
+ {` Are you certain? `}
+
+
+ {curr >= done && (
+
+ {` Manuscript has been sent to PMC and must be withdrawn.`}
+
+ )}
+
+
+
+ )}
+
+
+ )
+ }
+}
+
+export default StatusEdit
diff --git a/app/components/dashboard/AdminDashboard.jsx b/app/components/dashboard/AdminDashboard.jsx
index 8b356ced9355e951fc4882b1d56b41bec484c263..9969ef1bc366cd00c362ad2d0a2bf55a02761bd8 100755
--- a/app/components/dashboard/AdminDashboard.jsx
+++ b/app/components/dashboard/AdminDashboard.jsx
@@ -4,7 +4,7 @@ import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
import { Link, H3, Icon } from '@pubsweet/ui'
import { Loading, LoadingIcon, Table, Notification } from '../ui'
-import { COUNT_MANUSCRIPTS } from './operations'
+import { COUNT_MANUSCRIPTS, ALERT_MANUSCRIPTS } from './operations'
import DashboardBase from './DashboardBase'
const ListTable = styled(Table)`
@@ -15,6 +15,12 @@ const ListTable = styled(Table)`
text-align: right;
}
`
+const Alert = styled.small`
+ display: inline-flex;
+ align-items: center;
+ color: ${th('colorError')};
+ margin-left: calc(${th('gridUnit')} * 5);
+`
const HelpdeskQueue = {
'needs submission QA': ['submitted'],
'needs XML QA': ['xml-qa'],
@@ -39,7 +45,32 @@ const Completed = {
deleted: ['deleted'],
}
-const QueueTable = ({ title, queue, data }) => {
+const AlertQuery = ({ states, interval }) => (
+
+ {({ data, loading }) => {
+ if (loading) {
+ return
+ }
+ if (data.checkAge && data.checkAge.alert) {
+ return (
+
+
+ alert-octagon
+ {' '}
+ Manuscripts older than {interval}
+
+ )
+ }
+ return null
+ }}
+
+)
+
+const QueueTable = ({ title, queue, data, alerts }) => {
let total = 0
const tableData = Object.keys(queue).map(label => {
const items = data.filter(s => queue[label].includes(s.type))
@@ -64,11 +95,16 @@ const QueueTable = ({ title, queue, data }) => {
{row.count} |
- {row.count ? (
- {row.label}
- ) : (
- row.label
- )}
+
+ {row.count ? (
+ {row.label}
+ ) : (
+ row.label
+ )}
+ {alerts && (
+
+ )}
+
|
))}
@@ -130,6 +166,7 @@ const Dashboard = ({ currentUser }) => (
title="Submitter/Reviewer queue"
/>
Sign in with your Europe PMC plus account
+ {notification.show && (
+
+ {notification.message}
+
+ )}
+ {holiday.show && moment().isBefore(holiday.end) && }
{!isEmpty(errors) && {errors}}
-
-
- gift
-
-
- gift
-
-
- Please note that the Helpdesk will be unavailable from Wednesday, 25
- December to Tuesday 2 January. Any calls or e-mails will be saved
- and responded to on our return. Happy Holidays from the Europe PMC
- team.{' '}
-
-
diff --git a/app/components/ui/molecules/SearchSelect.jsx b/app/components/ui/molecules/SearchSelect.jsx
index bd62affacc5f51b5bb77a0683315777af7665de7..ff8ba081d6ed02847439ea7ed03ac3e7304fe10f 100755
--- a/app/components/ui/molecules/SearchSelect.jsx
+++ b/app/components/ui/molecules/SearchSelect.jsx
@@ -46,6 +46,7 @@ const SelectedLabel = styled.span`
const Selected = styled(Button)`
display: inline-flex;
align-items: center;
+ text-align: left;
margin: 0 ${th('gridUnit')} ${th('gridUnit')} 0;
${override('ui.SearchSelect.Selected')};
`
diff --git a/app/epmc-theme/elements/Checkbox.jsx b/app/epmc-theme/elements/Checkbox.jsx
index c0f0d92e38838c67128a5a03412c26b4b642b114..abbed7b4a4902deaa6e65906fa9cf0c2c2ac85ca 100755
--- a/app/epmc-theme/elements/Checkbox.jsx
+++ b/app/epmc-theme/elements/Checkbox.jsx
@@ -1,5 +1,5 @@
import { css } from 'styled-components'
-import { th } from '@pubsweet/ui-toolkit'
+import { th, lighten } from '@pubsweet/ui-toolkit'
export default {
Root: css`
@@ -13,5 +13,10 @@ export default {
`,
Input: css`
margin-top: calc(${th('gridUnit')} / 2);
+
+ &:disabled + span,
+ &.disabled + span {
+ color: ${lighten('colorText', 75)};
+ }
`,
}
diff --git a/app/epmc-theme/elements/Radio.jsx b/app/epmc-theme/elements/Radio.jsx
index 2762435b603734e8ef117a7542fd92677bfa8d0b..3440ea65af2b588b0ece6d5cfe7239b97a0c3ce0 100755
--- a/app/epmc-theme/elements/Radio.jsx
+++ b/app/epmc-theme/elements/Radio.jsx
@@ -1,5 +1,5 @@
import { css } from 'styled-components'
-import { th } from '@pubsweet/ui-toolkit'
+import { th, lighten } from '@pubsweet/ui-toolkit'
export default {
Root: css`
@@ -14,5 +14,10 @@ export default {
Input: css`
margin-right: ${th('gridUnit')};
height: calc(${th('gridUnit')} * 3);
+
+ &:disabled + span,
+ &.disabled + span {
+ color: ${lighten('colorText', 75)};
+ }
`,
}
diff --git a/config/default.js b/config/default.js
index 383aa26e8c396e5d65addc958f4f1b28c43e79d9..34d0a1c7863f0f79bd1b2e81fb024a31c720eb30 100755
--- a/config/default.js
+++ b/config/default.js
@@ -212,6 +212,7 @@ module.exports = {
'file',
'pageSize',
'states',
+ 'notification',
],
// do we need this elife variable?
elife: {
@@ -234,6 +235,17 @@ module.exports = {
},
},
states,
+ notification: {
+ show: false,
+ type: 'info',
+ message:
+ 'The Europe PMC plus website has recently been upgraded. Please use the email address associated with your account to log in.',
+ holiday: {
+ show: true,
+ start: '2019-12-25T00:00:00Z',
+ end: '2020-01-02T00:00:00Z',
+ },
+ },
user: {
identity: {
default: 'local',
diff --git a/docker-compose.yml b/docker-compose.yml
index 83c54e7bbd563aa2690707f4b2bb1090ada1dc0d..11f3e47fd7df2f2ca51bcd68f11a9a9b3637353c 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,7 +15,7 @@ services:
app:
# user: 'node'
- image: xpubepmc_app:1.5.2
+ image: xpubepmc_app:1.6.0
build:
context: .
dockerfile: ./Dockerfile
diff --git a/k8s/ftp-pv-claim.yaml b/k8s/ftp-pv-claim.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d961f9d0e82013d1c5fca9f33963158cbba8b48e
--- /dev/null
+++ b/k8s/ftp-pv-claim.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: ftp-pv-claim
+ labels:
+ app: ftp-storage-claim
+spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 10Gi
+ storageClassName: gp2
diff --git a/k8s/gp2-storage-class.yaml b/k8s/gp2-storage-class.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..48fd4d3267788ed79713b34fac9b66fd6d8fb0f5
--- /dev/null
+++ b/k8s/gp2-storage-class.yaml
@@ -0,0 +1,11 @@
+kind: StorageClass
+apiVersion: storage.k8s.io/v1
+metadata:
+ name: gp2
+ annotations:
+ storageclass.kubernetes.io/is-default-class: 'true'
+provisioner: kubernetes.io/aws-ebs
+parameters:
+ type: gp2
+ fsType: ext4
+reclaimPolicy: Retain
diff --git a/k8s/xpub-epmc-cluster.yaml b/k8s/xpub-epmc-cluster.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3e4111b0c363e193398439cde0518063f386b35c
--- /dev/null
+++ b/k8s/xpub-epmc-cluster.yaml
@@ -0,0 +1,13 @@
+apiVersion: eksctl.io/v1alpha5
+kind: ClusterConfig
+
+metadata:
+ name: xpub-epmc-cluster
+ region: eu-west-2
+
+nodeGroups:
+ - name: ng-1
+ instanceType: t2.small # 1 vCPU and 2.0 GiB RAM (https://aws.amazon.com/ec2/instance-types/t2/)
+ desiredCapacity: 3
+ ssh: # use existing EC2 key
+ publicKeyName: literature-service
diff --git a/k8s/xpub-epmc-deployment.yaml b/k8s/xpub-epmc-deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a02327fd9fb6b6c4cb08586d526a30dd09bc451f
--- /dev/null
+++ b/k8s/xpub-epmc-deployment.yaml
@@ -0,0 +1,176 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ name: xpub-epmc-deployment
+ labels:
+ app: xpub-epmc
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: xpub-epmc
+ template:
+ metadata:
+ labels:
+ app: xpub-epmc
+ spec:
+ restartPolicy: Always
+ volumes:
+ - name: ftp-storage
+ persistentVolumeClaim:
+ claimName: ftp-pv-claim
+ - name: samba-tmpfs
+ emptyDir:
+ medium: Memory
+ containers:
+ - name: xpub-epmc
+ image: 871979166454.dkr.ecr.eu-west-2.amazonaws.com/xpub-epmc:1.0.5
+ command: ['/bin/sh']
+ args: ['-c', './wrapperScript.sh']
+ ports:
+ - containerPort: 80
+ volumeMounts:
+ - name: ftp-storage
+ mountPath: /home/xpub/ftpdata
+ subPath: ftpdata
+ env:
+ - name: MINIO_ACCESS_KEY
+ valueFrom:
+ secretKeyRef:
+ name: s3secret
+ key: S3AccessKey
+ - name: MINIO_SECRET_KEY
+ valueFrom:
+ secretKeyRef:
+ name: s3secret
+ key: S3SecretKey
+ - name: MINIO_ENDPOINT
+ value: 's3.eu-west-2.amazonaws.com'
+ - name: MINIO_PORT
+ value: '443'
+ - name: MINIO_SECURITY
+ value: 'true'
+ - name: MINIO_BUCKET
+ value: 'xpub-epmc-test'
+ - name: MINIO_UPLOADS_FOLDER_NAME
+ value: 'uploads'
+ - name: MINIO_REGION
+ value: 'eu-west-2'
+ - name: PGHOST
+ valueFrom:
+ secretKeyRef:
+ name: pgsecret
+ key: PGHOST
+ - name: PGPORT
+ value: '5432'
+ - name: PGDATABASE
+ value: postgres
+ - name: PGUSER
+ valueFrom:
+ secretKeyRef:
+ name: pgsecret
+ key: PGUSER
+ - name: PGPASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: pgsecret
+ key: PGPASSWORD
+ - name: POSTGRES_USER
+ valueFrom:
+ secretKeyRef:
+ name: pgsecret
+ key: PGUSER
+ - name: POSTGRES_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: pgsecret
+ key: PGPASSWORD
+ - name: DATABASE_URL
+ valueFrom:
+ secretKeyRef:
+ name: pgsecret
+ key: DATABASE_URL
+ - name: PUBSWEET_URL
+ valueFrom:
+ configMapKeyRef:
+ name: xpub-epmc-config
+ key: PUBSWEET_URL
+ - name: PUBSWEET_HOST
+ valueFrom:
+ configMapKeyRef:
+ name: xpub-epmc-config
+ key: PUBSWEET_HOST
+ - name: PUBSWEET_EMAIL_URL
+ valueFrom:
+ configMapKeyRef:
+ name: xpub-epmc-config
+ key: PUBSWEET_EMAIL_URL
+ - name: PAGE_SIZE
+ value: '25'
+ - name: EMAIL_USR
+ valueFrom:
+ secretKeyRef:
+ name: emailsecret
+ key: User
+ - name: EMAIL_PWD
+ valueFrom:
+ secretKeyRef:
+ name: emailsecret
+ key: PWD
+ - name: APP_SERVER_PORT
+ value: '80'
+ - name: NODE_ENV
+ value: production
+ - name: ftpd-server
+ image: stilliard/pure-ftpd:hardened
+ ports:
+ - containerPort: 21
+ - containerPort: 30000
+ - containerPort: 30001
+ - containerPort: 30002
+ - containerPort: 30003
+ - containerPort: 30004
+ - containerPort: 30005
+ - containerPort: 30006
+ - containerPort: 30007
+ - containerPort: 30008
+ - containerPort: 30009
+ env:
+ - name: PUBLICHOST
+ valueFrom:
+ configMapKeyRef:
+ name: xpub-epmc-config
+ key: PUBLICHOST
+ volumeMounts:
+ - name: ftp-storage
+ mountPath: /home/ftpusers
+ subPath: ftpdata
+ - name: ftp-storage
+ mountPath: /etc/pure-ftpd
+ subPath: ftpconfig
+ - name: ftp-storage
+ mountPath: /etc/ssl/private
+ subPath: ftptls
+ - name: samba
+ image: dperson/samba
+ args:
+ - -s
+ - Data;/mnt
+ env:
+ - name: TZ
+ value: 'EST5EDT'
+ volumeMounts:
+ - name: ftp-storage
+ mountPath: /mnt:z
+ subPath: ftpdata
+ - name: samba-tmpfs
+ mountPath: /tmp
+ ports:
+ - containerPort: 137
+ protocol: UDP
+ - containerPort: 138
+ protocol: UDP
+ - containerPort: 139
+ - containerPort: 445
+ stdin: true
+ tty: true
diff --git a/k8s/xpub-epmc-ftp-service.yaml b/k8s/xpub-epmc-ftp-service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0fe1bc79718ccc81e87d48fc09ebe1d5e999c567
--- /dev/null
+++ b/k8s/xpub-epmc-ftp-service.yaml
@@ -0,0 +1,44 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: xpub-epmc-ftp-service
+ labels:
+ app: xpub-epmc
+spec:
+ type: LoadBalancer
+ ports:
+ - name: '21'
+ port: 21
+ targetPort: 21
+ - name: '30000'
+ port: 30000
+ targetPort: 30000
+ - name: '30001'
+ port: 30001
+ targetPort: 30001
+ - name: '30002'
+ port: 30002
+ targetPort: 30002
+ - name: '30003'
+ port: 30003
+ targetPort: 30003
+ - name: '30004'
+ port: 30004
+ targetPort: 30004
+ - name: '30005'
+ port: 30005
+ targetPort: 30005
+ - name: '30006'
+ port: 30006
+ targetPort: 30006
+ - name: '30007'
+ port: 30007
+ targetPort: 30007
+ - name: '30008'
+ port: 30008
+ targetPort: 30008
+ - name: '30009'
+ port: 30009
+ targetPort: 30009
+ selector:
+ app: xpub-epmc
diff --git a/k8s/xpub-epmc-samba-service.yaml b/k8s/xpub-epmc-samba-service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..01f6187baabb45393958f844a248a5b1766d9264
--- /dev/null
+++ b/k8s/xpub-epmc-samba-service.yaml
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: xpub-epmc-samba-service
+ labels:
+ app: xpub-epmc
+spec:
+ type: LoadBalancer
+ ports:
+ - name: '139'
+ port: 139
+ targetPort: 139
+ - name: '445'
+ port: 445
+ targetPort: 445
+ loadBalancerSourceRanges:
+ - 193.62.194.244/32
+ selector:
+ app: xpub-epmc
diff --git a/k8s/xpub-epmc-service.yaml b/k8s/xpub-epmc-service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..03954bb097852ba19739601ca4c04b24e797b010
--- /dev/null
+++ b/k8s/xpub-epmc-service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: xpub-epmc-service
+ labels:
+ app: xpub-epmc
+spec:
+ type: LoadBalancer
+ ports:
+ - name: '3000'
+ port: 80
+ targetPort: 80
+ protocol: TCP
+ selector:
+ app: xpub-epmc
diff --git a/k8s/xpub-xsweet-deployment.yaml b/k8s/xpub-xsweet-deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ce6e54c350ffb1ffc0c1aa542ff19454369e0ecc
--- /dev/null
+++ b/k8s/xpub-xsweet-deployment.yaml
@@ -0,0 +1,37 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ name: xpub-xsweet-deployment
+ labels:
+ app: xpub-xsweet
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: xpub-xsweet
+ template:
+ metadata:
+ labels:
+ app: xpub-xsweet
+ spec:
+ containers:
+ - name: xpub-xsweet
+ image: pubsweet/job-xsweet:1.3.3
+ command: ['/bin/sh']
+ args: ['-c', 'node src/xsweet.js']
+ env:
+ - name: POSTGRES_USER
+ valueFrom:
+ secretKeyRef:
+ name: pgsecret
+ key: PGUSER
+ - name: POSTGRES_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: pgsecret
+ key: PGPASSWORD
+ - name: DATABASE_URL
+ valueFrom:
+ secretKeyRef:
+ name: pgsecret
+ key: DATABASE_URL
diff --git a/package.json b/package.json
index 8bbafddf8f6dbed60f5d185247ce110062d22bc1..9f29958f42b8ee0177e8e315bdb86f6a5726f184 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "xpub-epmc",
- "version": "1.5.2",
+ "version": "1.6.0",
"private": true,
"description": "xpub configured for Europe PMC Plus manuscript submission system",
"license": "MIT",
diff --git a/server/xpub-model/entities/manuscript/data-access.js b/server/xpub-model/entities/manuscript/data-access.js
index f66c4f20fc94be42b751798ba50236a77cab9017..3c79612c2e334783c11acca03c604123398efe48 100644
--- a/server/xpub-model/entities/manuscript/data-access.js
+++ b/server/xpub-model/entities/manuscript/data-access.js
@@ -361,6 +361,17 @@ class Manuscript extends EpmcBaseModel {
return parseInt(count[0].count, 10)
}
+ static async checkAge(statuses, interval) {
+ const rawString = `"updated" < NOW() - INTERVAL '${interval}'`
+ const alert =
+ (await knex('manuscript')
+ .count('*')
+ .whereIn('status', statuses)
+ .whereRaw(rawString)
+ .whereNull('deleted')) || '0'
+ return parseInt(alert[0].count, 10) > 0
+ }
+
static async countDeleted(user) {
const { isAdmin } = await Manuscript.isAdmin(user)
diff --git a/server/xpub-model/entities/manuscript/index.js b/server/xpub-model/entities/manuscript/index.js
index 83f6f10a9b798420f6476d39c3f98dc0427aa648..6cc7471fe91e49c6874b82bbcb11c1f5c729864f 100755
--- a/server/xpub-model/entities/manuscript/index.js
+++ b/server/xpub-model/entities/manuscript/index.js
@@ -187,6 +187,11 @@ const Manuscript = {
return counts
},
+ checkAge: async (states, interval) => {
+ const older = await ManuscriptAccess.checkAge(states, interval)
+ return { alert: older }
+ },
+
search: async (query, page, pageSize, user) => {
const manuscripts = await ManuscriptAccess.searchByTitleOrUser(
query,
diff --git a/server/xpub-server/entities/email/resolvers.js b/server/xpub-server/entities/email/resolvers.js
index c36af9ee5f7017910b508a480e8a7395ad8c3d17..5e2d0ffff49fab3696aa32ad5a21d72d7f4b3b66 100644
--- a/server/xpub-server/entities/email/resolvers.js
+++ b/server/xpub-server/entities/email/resolvers.js
@@ -116,14 +116,14 @@ const resolvers = {
// Update password reset token
const identity = user.identities.reduce((id, identity) => {
- if (identity.email === email) {
+ if (identity.email.toLowerCase() === email.toLowerCase()) {
return identity
}
return id
}, undefined)
if (!identity) {
- throw new Error(`The user is not associated with email ${email}`)
+ throw new Error(`No user is associated with email ${email}`)
}
let numUpdated = 0
diff --git a/server/xpub-server/entities/manuscript/resolvers.js b/server/xpub-server/entities/manuscript/resolvers.js
index ec14ab1c6c29286dfc4797345350c8011b400324..18c38c2818a736784ed245dc85f4f5931c89252c 100644
--- a/server/xpub-server/entities/manuscript/resolvers.js
+++ b/server/xpub-server/entities/manuscript/resolvers.js
@@ -31,6 +31,12 @@ const resolvers = {
}
return ManuscriptManager.countByStatus(user)
},
+ async checkAge(_, { states, interval }, { user }) {
+ if (!user) {
+ throw new Error('You are not authenticated!')
+ }
+ return ManuscriptManager.checkAge(states, interval)
+ },
async adminManuscripts(_, { external }, { user }) {
if (!user) {
throw new Error('You are not authenticated!')
diff --git a/server/xpub-server/entities/manuscript/typeDefs.graphqls b/server/xpub-server/entities/manuscript/typeDefs.graphqls
index 895b61a3bc5e74549a236c3ef8f5370fe06d3fe0..8c315999a3347095a218711427f47857fd7de740 100644
--- a/server/xpub-server/entities/manuscript/typeDefs.graphqls
+++ b/server/xpub-server/entities/manuscript/typeDefs.graphqls
@@ -3,6 +3,10 @@ type Count {
count: Int
}
+type Older {
+ alert: Boolean
+}
+
type Error {
message: String
}
@@ -28,6 +32,7 @@ extend type Query {
manuscripts: [Manuscript]!
adminManuscripts(external: Boolean): ManuscriptSearchResult!
countByStatus: [Count]!
+ checkAge(states: [String], interval: String): Older!
checkDuplicates(id: ID!, articleIds: [String], title: String!): ManuscriptSearchResult!
searchArticleIds(id: String!): ManuscriptResult!
findByStatus(query: String!, page: Int, pageSize: Int): ManuscriptSearchResult!