-
Notifications
You must be signed in to change notification settings - Fork 0
/
register-doi.js
219 lines (190 loc) · 7.88 KB
/
register-doi.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// easyDB Datacite Webhook plugin
const util = require('util');
const fs = require('fs');
const querystring = require('querystring');
const crypto = require('crypto');
const fetch = require('node-fetch');
const bufferEq = require('buffer-equal-constant-time');
// Read configuration
const config = require('../../config.js');
function returnAndLogJsonError(error, status = 500) {
let description;
if (error instanceof Error) {
log(error.stack, 'ERROR');
description = error.toString();
}
else {
log(error, 'ERROR');
description = error;
}
ez5.respondError('error.api.generic', {description}, status)
}
function authHeader(username, password) {
return 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
}
function log(messageOrObject, level='INFO') {
if (typeof messageOrObject === 'object' && messageOrObject !== null) {
messageOrObject = JSON.stringify(messageOrObject, null, 2);
}
fs.appendFileSync(config.logFile, `${new Date().toISOString()} ${level} ${messageOrObject}\n`);
}
async function authenticateEasyDb(username, password, easyDbUrl) {
// get session token
const sessionPath = '/api/v1/session';
let response = await fetch(easyDbUrl + sessionPath);
let token;
if (response.ok) {
({ token } = await response.json());
}
else {
const responseText = await response.text();
throw Error(`Failed calling ${sessionPath}: ${response.status} ${response.statusText} ${responseText}`);
}
log(`authenticateEasyDb: session token ${token}`);
let queryString = querystring.stringify({token, login: username, password});
log(`authenticateEasyDb: authenticate user ${username}`);
const authenticateResp = await fetch(`${easyDbUrl}/api/v1/session/authenticate?${queryString}`,
{ method: 'post' });
if (authenticateResp.ok) {
const session = await authenticateResp.json();
if(session.authenticated) {
log(`authenticateEasyDb: authenticated with easyDb, token: ${session.token}`);
return session;
}
else {
const responseText = JSON.stringify(session);
throw Error(`Failed easyDb authentication, response: ${responseText}`);
}
}
else {
const responseText = await authenticateResp.text();
throw Error('Failed easyDb authentication:'
+ `${authenticateResp.status} ${authenticateResp.statusText}, response: ${responseText}`);
}
}
async function registerDoiForObject(dbObject, easyDbOpts, dataciteOpts) {
const { username, password, endpoint: dataciteEndpoint, doiPrefix} = dataciteOpts;
const { xsltName, token: easyDbToken, collector , url: easyDbUrl} = easyDbOpts;
const dataciteAuth = authHeader(username, password);
const systemObjectId = dbObject._system_object_id;
const doi = `${doiPrefix}${systemObjectId}`;
let metadataXml = await getMetadataFromEasyDb(dbObject._uuid, xsltName, easyDbUrl);
metadataXml = metadataXml.replace('___DOI_PLACEHOLDER___', doi);
const dataciteMetadataUrl = dataciteEndpoint + '/metadata/' + doi;
log(`PUT metadata to ${dataciteMetadataUrl}`);
const dataciteMetadataResponse = await fetch(dataciteMetadataUrl, {
method: 'put',
body: metadataXml,
headers: {
'Content-Type': 'application/xml',
'Authorization': dataciteAuth,
}
})
const dataciteMetadataResponseText = await dataciteMetadataResponse.text();
if (!dataciteMetadataResponse.ok) {
throw Error('Failed Datacite metadata registration: '
+ `${dataciteMetadataResponse.status} ${dataciteMetadataResponse.statusText}, response: ${dataciteMetadataResponseText}, request body: ${metadataXml}`);
}
log('Success, response: ' + dataciteMetadataResponseText);
const objectDetailUrl = `${easyDbUrl}/detail/${systemObjectId}`;
const dataciteMintUrl = `${dataciteEndpoint}/doi/${doi}`;
const body = `doi=${doi}\nurl=${objectDetailUrl}\n`
log(`PUT ${dataciteMintUrl} with body: ${body}`);
const dataciteMintResponse = await fetch(dataciteMintUrl, {
method: 'PUT',
body,
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
'Authorization': dataciteAuth,
}
})
const dataciteMintResponseText = await dataciteMintResponse.text();
if (!dataciteMintResponse.ok) {
throw Error('Failed Datacite url registration: '
+ `${dataciteMintResponse.status} ${dataciteMintResponse.statusText}, response: ${dataciteMintResponseText}`);
}
log('Success, response: ' + dataciteMintResponseText);
const publish = {
system_object_id: systemObjectId,
collector,
publish_uri: 'https://doi.org/' + doi,
easydb_uri: objectDetailUrl
}
return { published: await postPublishedDoiToEasyDb(publish, easyDbUrl, easyDbToken) };
}
async function getMetadataFromEasyDb(objectUuid, xsltName, easyDbUrl) {
// TODO Check if authentication as api-user is necessary?
const metadataUrl = easyDbUrl + '/api/v1/objects/uuid/' + objectUuid + '/format/xslt/' + xsltName;
const metadataResponse = await fetch(metadataUrl);
const metadataResponseBody = await metadataResponse.text();
if (!metadataResponse.ok) {
throw Error('Failed getting metadata from easyDb: '
+ `${metadataResponse.status} ${metadataResponse.statusText}, response: ${dataciteMetadataResponseText}`);
}
log(`Got metadata for ${objectUuid}`);
return metadataResponseBody;
}
async function postPublishedDoiToEasyDb(publishObject, easyDbUrl, easyDbToken) {
const publishApiUrl = easyDbUrl + '/api/v1/publish?token=' + easyDbToken;
log(`POST ${publishApiUrl}, with publish object:`);
log(publishObject);
const publishResponse = await fetch(publishApiUrl,
{
method: 'post',
body: JSON.stringify([{publish: publishObject}])
})
const publishResponseObject = await publishResponse.json();
if (!publishResponse.ok ) {
throw Error(`Failed easyDb API publish: ${publishResponse.status} ${publishResponse.statusText}, response: `
+ JSON.stringify(publishResponseObject));
}
log('Success, response:');
log(publishResponseObject);
return publishResponseObject;
}
async function registerAllDOIs(objects, useConfig, easyDbUrl) {
const session = await authenticateEasyDb(config.easyDb.user, config.easyDb.password, easyDbUrl);
const easyDbOpts = Object.assign({ token: session.token, url: easyDbUrl}, config.easyDb);
// New or updated object call async register doi and await all result
return Promise.all(objects.map( dbObject => registerDoiForObject(dbObject, easyDbOpts, config.datacite[useConfig]) ));
}
/* How to write different responses
ez5.respondSuccess(body, statusCode = 200)
ez5.respondError(messageCode, parameters = {}, statusCode = 400)
messageCode is an localisation Key for the message.
TODO: Check how to write custom messages
See https://docs.easydb.de/en/technical/node-runner/ for documentation on how
the script is run from the easyDb and which modules are available per default.
See https://docs.easydb.de/en/technical/plugins/webhooks/webhook/ for the
description of the parameter of the main function.
*/
function main(context) {
const easyDbUrl = context.config.system.server.external_url;
// Parse query parameters
var {useConfig = 'test'} = context.request.query_string_parameters;
// Parse body
if (!context.request.body) {
returnAndLogJsonError('Missing request body', 400);
return;
}
log(`Using config ${useConfig}`);
try {
const transition = JSON.parse(context.request.body);
log(context.request);
if (!['UPDATE', 'INSERT'].includes(transition.operation)) {
returnAndLogJsonError('Invalid JSON body, expected "operation" key with value "UPDATE" or "INSERT" operation')
return;
}
registerAllDOIs(transition.objects, useConfig, easyDbUrl).then( statuses => {
log('All registerDoiForObject finished successfully');
ez5.respondSuccess({status: statuses});
}).catch(error => {
returnAndLogJsonError(error);
})
}
catch (error) {
returnAndLogJsonError(error);
}
}
// easyDb node-runner expects a function exported as "main"
module.exports = { main }