Commit b4537129 authored by nickstiffler's avatar nickstiffler Committed by Yannis Barlas
Browse files

use datacite api to generate doi

parent a45ea6fe
const axios = require('axios')
const config = require('config')
const cheerio = require('cheerio')
const { Manuscript, ManuscriptVersion } = require('@pubsweet/models')
const baseUrl = config.get('datacite.url')
const username = config.get('datacite.username')
const password = config.get('datacite.password')
const auth = {
username,
password,
}
const headers = {
'content-type': 'application/vnd.api+json',
}
const makeFetchRequest = async (req, res) => {
await axios({
url: `${baseUrl}/${req.query.doi}`,
method: 'get',
headers,
auth,
})
.then(response => res.send(response.data))
.catch(error => {
const { status, title: message } = error
return res.status(status).send(message)
})
}
const makeNewRequest = async (req, res) => {
const { doi } = req.query
const data = {
data: {
type: 'dois',
attributes: {
doi,
},
},
}
await axios({
url: baseUrl,
method: 'post',
headers,
data,
auth,
})
.then(response => res.send(response.data))
.catch(error => {
const { status, data: errorMessages } = error.response
return res.status(status).send(errorMessages.errors)
})
}
const makeFetchLastRequest = async (req, res) => {
await axios({
url: `${baseUrl}/?client-id=caltech.micropub&page[size]=1&sort=-created`,
method: 'get',
headers,
auth,
}).then(response => res.send(response.data))
}
const makePublishRequest = async (req, res) => {
const { id: manuscriptId } = req.query
const version = await ManuscriptVersion.query()
.where({ manuscriptId })
.orderBy('created', 'desc')
.first()
const manuscript = await Manuscript.query().findById(manuscriptId)
const { title, patternDescription, updated, authors } = version
const { doi } = manuscript
const creators = authors.map(author => {
const { firstName, lastName } = author
const creator = {
givenName: firstName,
familyName: lastName,
}
return creator
})
const updatedDate = new Date(Number(updated))
// Grab the first paragraph for the abstract
const $ = cheerio.load(patternDescription)
const abstract = $('p')
.first()
.text()
// Strip the HTML
const strippedTitle = title.replace(/(<([^>]+)>)/gi, '')
const baseDoi = doi.split('/')[1].replace(/\./g, '-')
const url = `https://www.micropublication.org/journals/biology/${baseDoi}`
const data = {
data: {
attributes: {
event: 'publish',
titles: { title: strippedTitle },
publicationYear: updatedDate.getFullYear(),
descriptions: { description: abstract, descriptionType: 'Abstract' },
creators,
types: { resourceTypeGeneral: 'DataPaper' },
publisher: 'microPublication Biology',
url,
},
},
}
await axios({
url: `${baseUrl}/${doi}`,
method: 'put',
headers,
data,
auth,
})
.then(response => res.send(response.data))
.catch(error => {
const { status, data: errorMessages } = error.response
return res.status(status).send(errorMessages.errors)
})
}
const DataCiteApi = app => {
/**
* GET ENDPOINTS
*/
app.get('/api/datacite/new', async (req, res) => {
await makeNewRequest(req, res)
})
app.get('/api/datacite/fetch', async (req, res) => {
await makeFetchRequest(req, res)
})
app.get('/api/datacite/fetchLast', async (req, res) => {
await makeFetchLastRequest(req, res)
})
app.get('/api/datacite/publish', async (req, res) => {
await makePublishRequest(req, res)
})
}
module.exports = DataCiteApi
/* eslint-disable global-require */
module.exports = {
server: () => app => require('./DataCiteApi')(app),
}
import config from 'config'
const { baseUrl } = config['pubsweet-client']
const apiUrl = `${baseUrl}/api/datacite`
const getDoi = doi => {
const url = `${apiUrl}/fetch?doi=${doi}`
return fetch(url)
}
const createDoi = async values => {
// Retrieve the last DOI and increment
const lastSubmission = await fetch(`${apiUrl}/fetchLast`)
let errorMessage = ''
const result = await lastSubmission.json()
if (result.data.length > 0) {
const doi = result.data[0].id
const newDoi = incrementDoi(doi)
const createResult = await fetch(`${apiUrl}/new?doi=${newDoi}`)
if (createResult.status === 200) {
return newDoi
}
const error = await createResult.text()
errorMessage = error
} else {
errorMessage = 'No DOI returned'
}
throw Error(errorMessage)
}
const publishDoi = async manuscriptId => {
const url = `${apiUrl}/publish?id=${manuscriptId}`
const result = await fetch(url)
if (result.status === 200) {
return result
}
const error = await result.text()
throw Error(error)
}
const incrementDoi = doi => {
const doiSplit = doi.split('.')
const doiNumber = parseInt(doiSplit[3], 10) + 1
const paddedNumber = `${doiNumber}`.padStart(6, '0')
return `${doiSplit[0]}.${doiSplit[1]}.${doiSplit[2]}.${paddedNumber}`
}
export { getDoi, createDoi, publishDoi }
......@@ -31,6 +31,7 @@ import {
transformChatMessages,
saveToStorage,
} from '../_helpers/common'
import { publishDoi } from '../../fetch/DataCiteApi'
/* eslint-disable-next-line react/prop-types */
const Label = ({ created, index }) => (
......@@ -216,9 +217,11 @@ const EditorView = props => {
const getSavedChat = () => getFromStorage(`chat_${chatModalThreadId}`)
const saveChat = content =>
saveToStorage(content, `chat_${chatModalThreadId}`)
const finalizeDoi = () => publishDoi(manuscriptId)
if (!editorLoading && editorData) {
const transformed = transform(editorData, {
finalizeDoi,
reinviteReviewerMutation,
sendChatMutation,
setDataTypeMutation,
......@@ -328,6 +331,7 @@ const EditorView = props => {
decisionSubmitted={!!decision}
doi={doi}
editorName={editorName}
finalizeDoi={finalizeDoi}
getSavedDecision={getSavedDecision}
getSavedSOChat={getSavedSOChat}
invitedReviewersCount={invitedReviewersCount}
......
......@@ -18,4 +18,5 @@ module.exports = [
'./app/wbApi',
'./server/export',
'./app/pubMedApi',
'./app/dataCiteApi',
]
module.exports = {
datacite: {
username: 'DATACITE_USERNAME',
password: 'DATACITE_PASSWORD',
url: 'DATACITE_URL',
},
}
......@@ -24,6 +24,9 @@ const logger = new winston.Logger({
})
module.exports = {
datacite: {
url: 'https://api.test.datacite.org/dois',
},
'pubsweet-client': {
baseUrl: deferConfig(
cfg => `http://localhost:${cfg['pubsweet-server'].port}`,
......
module.exports = {
datacite: {
url: 'https://api.test.datacite.org/dois',
},
dbManager: {
admin: true,
email: 'admin@admin.com',
......
......@@ -329,6 +329,7 @@ const updateManuscriptMetadata = async (_, { data, manuscriptId }, ctx) => {
categories,
species,
} = data
await Manuscript.query()
.patch({
dbReferenceId,
......
......@@ -65,6 +65,7 @@ const DecisionSection = props => {
getSavedDecision,
decision,
decisionLetter,
finalizeDoi,
saveDecision,
submitDecision,
submitted,
......@@ -84,6 +85,9 @@ const DecisionSection = props => {
decision: values.decision,
decisionLetter: values.decisionLetter,
})
if (values.decision === 'publish') {
finalizeDoi()
}
}
return (
......@@ -163,6 +167,8 @@ DecisionSection.propTypes = {
decision: PropTypes.oneOf(['accept', 'decline', 'reject', 'revise']),
/** Decision letter text content */
decisionLetter: PropTypes.string,
/** Function to publish the DOI metadata at DataCite */
finalizeDoi: PropTypes.func,
/** Fetch saved (but not submitted) decision */
getSavedDecision: PropTypes.func,
/** Save decision without submitting */
......@@ -176,6 +182,7 @@ DecisionSection.propTypes = {
DecisionSection.defaultProps = {
decision: null,
decisionLetter: null,
finalizeDoi: null,
getSavedDecision: null,
saveDecision: null,
submitDecision: null,
......
......@@ -51,6 +51,7 @@ const EditorPanel = props => {
decisionSubmitted,
doi,
editorName,
finalizeDoi,
getSavedDecision,
getSavedSOChat,
onClickCuratorChat,
......@@ -173,6 +174,7 @@ const EditorPanel = props => {
<DecisionSection
decision={decision}
decisionLetter={decisionLetter}
finalizeDoi={finalizeDoi}
getSavedDecision={getSavedDecision}
saveDecision={saveDecision}
submitDecision={submitDecision}
......@@ -187,6 +189,7 @@ const EditorPanel = props => {
categories={categories}
dbReferenceId={dbReferenceId}
doi={doi}
finalizeDoi={finalizeDoi}
pmcId={pmcId}
pmId={pmId}
species={species}
......@@ -218,6 +221,8 @@ EditorPanel.propTypes = {
decisionLetter: PropTypes.string,
/** Whether the decision has been sent (as opposed to simply saved) */
decisionSubmitted: PropTypes.bool,
/** Function to publish the DOI metadata at DataCite */
finalizeDoi: PropTypes.func,
/** Reference ID from organism database */
dbReferenceId: PropTypes.string,
......@@ -242,6 +247,7 @@ EditorPanel.propTypes = {
curatorName: PropTypes.string,
/** Assigned editor's display name */
editorName: PropTypes.string,
/** Assigned section editor's display name */
sectionEditorName: PropTypes.string,
/** Assigned science officer's display name */
......@@ -375,6 +381,7 @@ EditorPanel.defaultProps = {
decisionSubmitted: false,
doi: null,
editorName: null,
finalizeDoi: null,
getSavedDecision: null,
getSavedSOChat: null,
onClickCuratorChat: null,
......
......@@ -6,9 +6,15 @@ import * as yup from 'yup'
import { grid, th } from '../_helpers'
import { Button, Form, Select, TextField } from '../common'
import ValueList from './ValueList'
import { createDoi } from '../../../app/fetch/DataCiteApi'
const Wrapper = styled.div``
const DoiWrapper = styled.div`
display: flex;
flex-direction: row;
`
const MetadataValues = styled(ValueList)`
margin-bottom: ${grid(2)};
`
......@@ -54,7 +60,9 @@ const MetadataSection = props => {
submissionTypes,
updateMetadata,
} = props
const [showForm, setShowForm] = useState(false)
const [fetchDoi, setFetchDoi] = useState(false)
/**
* Either value list
......@@ -266,7 +274,9 @@ const MetadataSection = props => {
errors,
handleBlur,
handleChange,
setFieldError,
setFieldValue,
setFieldTouched,
touched,
values,
} = formProps
......@@ -302,6 +312,23 @@ const MetadataSection = props => {
const handleSubmissionTypes = newValues =>
handleSelect('submissionTypes', newValues)
const generateDoi = async () => {
setFetchDoi(true)
await createDoi()
.then(newDoi => {
setFieldValue('doi', newDoi)
values.doi = newDoi
updateMetadata(values)
})
.catch(error => {
setFieldError('doi', `Generate DOI Failed: ${error}`)
setFieldTouched('doi', true, false)
})
.finally(() => {
setFetchDoi(false)
})
}
return (
<Wrapper>
<Label>Species</Label>
......@@ -340,15 +367,21 @@ const MetadataSection = props => {
value={currentSubmissionTypesValues}
/>
<TextField
error={errors.doi}
handleBlur={handleBlur}
handleChange={handleChange}
label="DOI"
name="doi"
touched={touched}
value={values.doi}
/>
<DoiWrapper>
<TextField
error={errors.doi}
handleBlur={handleBlur}
handleChange={handleChange}
label="DOI"
name="doi"
touched={touched}
value={values.doi}
/>
<Button loading={fetchDoi} onClick={() => generateDoi()}>
Generate DOI
</Button>
</DoiWrapper>
<FieldWrapper>
<StyledField
error={errors.pmcId}
......@@ -381,6 +414,7 @@ const MetadataSection = props => {
/>
<Button onClick={() => setShowForm(false)}>Cancel</Button>
<Button primary type="submit">
Save
</Button>
......
......@@ -5140,6 +5140,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
 
axios@^0.19.2:
version "0.19.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
dependencies:
follow-redirects "1.5.10"
axobject-query@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9"
......@@ -7922,7 +7929,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.
dependencies:
ms "2.0.0"
 
debug@3.1.0, debug@~3.1.0:
debug@3.1.0, debug@=3.1.0, debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
......@@ -9946,6 +9953,13 @@ focus-lock@^0.6.6:
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.6.tgz#98119a755a38cfdbeda0280eaa77e307eee850c7"
integrity sha512-Dx69IXGCq1qsUExWuG+5wkiMqVM/zGx/reXSJSLogECwp3x6KeNQZ+NAetgxEFpnC41rD8U3+jRCW68+LNzdtw==
 
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"
follow-redirects@^1.0.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.10.0.tgz#01f5263aee921c6a54fb91667f08f4155ce169eb"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment