- added doi registration
Some checks failed
CI Pipeline / japa-tests (push) Failing after 57s

- npm updates for webpack-encore and postcss-loader
- DatasetExtension.ts: use relation contributors for PersonContributor
- added DoiClient.ts and DoiClientContract.ts
- rozes.ts: addes routes for creating and storing doi identifier
- addes xslt doi_datacite.xslt needed for registering DOI identifier
This commit is contained in:
Kaimbacher 2024-01-26 09:39:03 +01:00
parent ebc62d9117
commit c9ba7d6adc
22 changed files with 1836 additions and 677 deletions

View file

@ -0,0 +1,119 @@
// import { Client } from 'guzzle';
// import { Log } from '@adonisjs/core/build/standalone';
// import { DoiInterface } from './interfaces/DoiInterface';
import DoiClientContract from 'App/Library/Doi/DoiClientContract';
import DoiClientException from 'App/Exceptions/DoiClientException';
import { StatusCodes } from 'http-status-codes';
import Logger from '@ioc:Adonis/Core/Logger';
import axios, {AxiosResponse} from 'axios';
export class DoiClient implements DoiClientContract {
username: string;
password: string;
serviceUrl: string;
// prefix: string;
// base_domain: string;
constructor() {
const datacite_environment = process.env.DATACITE_ENVIRONMENT || 'debug';
if (datacite_environment === 'debug') {
this.username = process.env.DATACITE_TEST_USERNAME || '';
this.password = process.env.DATACITE_TEST_PASSWORD || '';
this.serviceUrl = process.env.DATACITE_TEST_SERVICE_URL || '';
// this.prefix = process.env.DATACITE_TEST_PREFIX || '';
// this.base_domain = process.env.TEST_BASE_DOMAIN || '';
} else if (datacite_environment === 'production') {
this.username = process.env.DATACITE_USERNAME || '';
this.password = process.env.DATACITE_PASSWORD || '';
this.serviceUrl = process.env.DATACITE_SERVICE_URL || '';
// this.prefix = process.env.DATACITE_PREFIX || '';
// this.base_domain = process.env.BASE_DOMAIN || '';
}
if (this.username === '' || this.password === '' || this.serviceUrl === '') {
const message = 'issing configuration settings to properly initialize DOI client';
Logger.error(message);
throw new DoiClientException(StatusCodes.BAD_REQUEST, message);
}
}
/**
* Creates a DOI with the given identifier
*
* @param doiValue The desired DOI identifier e.g. '10.5072/tethys.999',
* @param xmlMeta
* @param landingPageUrl e.g. https://www.tethys.at/dataset/1
*
* @return Promise<number> The http response in the form of a axios response
*/
public async registerDoi(doiValue: string, xmlMeta: string, landingPageUrl: string): Promise<AxiosResponse<any>> {
//step 1: register metadata via xml upload
// state draft
let response;
let url = `${this.serviceUrl}/metadata/${doiValue}`; //https://mds.test.datacite.org/metadata/10.21388/tethys.213
const auth = {
username: this.username,
password: this.password,
};
let headers = {
'Content-Type': 'application/xml;charset=UTF-8',
};
try {
response = await axios.put(url, xmlMeta, {
auth,
headers,
});
} catch (error) {
const message = `request to ${url} failed with ${error.message}`;
// Handle the error, log it, or rethrow as needed
Logger.error(message);
throw new DoiClientException(StatusCodes.SERVICE_UNAVAILABLE, message);
}
// let test = response.data; // 'OK (10.21388/TETHYS.213)'
// Response Codes
// 201 Created: operation successful
// 401 Unauthorised: no login
// 403 Forbidden: login problem, quota exceeded
// 415 Wrong Content Type : Not including content type in the header.
// 422 Unprocessable Entity : invalid XML
if (response.status !== 201) {
const message = 'unexpected DataCite MDS response code ' + response.status;
// $this->log($message, 'err');
throw new DoiClientException(response.status, message);
}
// step 2: Register the DOI name
// // DOI und URL der Frontdoor des zugehörigen Dokuments übergeben: state findable
// const url2 = this.serviceUrl + "/doi/" + doiValue;
url = `${this.serviceUrl}/doi/${doiValue}`; //'https://mds.test.datacite.org/doi/10.21388/tethys.213'
headers = {
'Content-Type': 'text/plain;charset=UTF-8',
};
const data = `doi=${doiValue}\nurl=${landingPageUrl}`;
try {
response = await axios.put(url, data, {
auth,
headers,
});
// Access the response data using response.data
// Do something with the response.data
} catch (error) {
const message = `request to ${url} failed with ${error.message}`;
// Handle the error, log it, or rethrow as needed
throw new DoiClientException(response.status, message);
}
// Response Codes
// 201 Created: operation successful
// 400 Bad Request: request body must be exactly two lines: DOI and URL; wrong domain, wrong prefix;
// 401 Unauthorised: no login
// 403 Forbidden: login problem, quota exceeded
// 412 Precondition failed: metadata must be uploaded first.
if (response.status != 201) {
const message = 'unexpected DataCite MDS response code ' + response.status;
Logger.error(message);
throw new DoiClientException(response.status, message);
}
return response;
}
}

View file

@ -0,0 +1,12 @@
// import ResumptionToken from './ResumptionToken';
export default interface DoiClientContract {
username: string;
password: string;
serviceUrl: string;
// prefix: string;
// base_domain: string;
registerDoi(doiValue: string, xmlMeta: string, landingPageUrl: string);
// get(key: string): Promise<ResumptionToken | null>;
// set(token: ResumptionToken): Promise<string>;
}

View file

@ -110,7 +110,7 @@ export default class Strategy {
private mapModelAttributes(myObject, childNode: XMLBuilder) {
Object.keys(myObject).forEach((prop) => {
let value = myObject[prop];
console.log(`${prop}: ${value}`);
// console.log(`${prop}: ${value}`);
if (value != null) {
if (value instanceof DateTime) {
value = value.toFormat('yyyy-MM-dd HH:mm:ss').trim();

194
app/Library/Utils/Index.ts Normal file
View file

@ -0,0 +1,194 @@
import Dataset from 'App/Models/Dataset';
import { Client } from '@opensearch-project/opensearch';
import { create } from 'xmlbuilder2';
import { transform } from 'saxon-js';
import XmlModel from 'App/Library/XmlModel';
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
import Logger from '@ioc:Adonis/Core/Logger';
import { readFileSync } from 'fs';
import { DateTime } from 'luxon';
// import Config from '@ioc:Adonis/Core/Config';
import { getDomain } from 'App/Utils/utility-functions';
// const opensearchNode = process.env.OPENSEARCH_HOST || 'localhost';
// const client = new Client({ node: `http://${opensearchNode}` }); // replace with your OpenSearch endpoint
export default {
// opensearchNode: process.env.OPENSEARCH_HOST || 'localhost',
client: new Client({ node: `http://${process.env.OPENSEARCH_HOST || 'localhost'}` }), // replace with your OpenSearch endpoint
async getDoiRegisterString(dataset: Dataset): Promise<string | undefined> {
try {
const proc = readFileSync('public/assets2/doi_datacite.sef.json');
const xsltParameter = {};
let xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
const datasetNode = xml.root().ele('Dataset');
await createXmlRecord(dataset, datasetNode);
const xmlString = xml.end({ prettyPrint: false });
// set timestamp
const date = DateTime.now();
const unixTimestamp = date.toUnixInteger();
xsltParameter['unixTimestamp'] = unixTimestamp;
// set prefix
let prefix = '';
let base_domain = '';
const datacite_environment = process.env.DATACITE_ENVIRONMENT || 'debug';
if (datacite_environment === 'debug') {
prefix = process.env.DATACITE_TEST_PREFIX || '';
base_domain = process.env.TEST_BASE_DOMAIN || '';
} else if (datacite_environment === 'production') {
prefix = process.env.DATACITE_PREFIX || '';
base_domain = process.env.BASE_DOMAIN || '';
}
xsltParameter['prefix'] = prefix;
const repIdentifier = 'tethys';
xsltParameter['repIdentifier'] = repIdentifier;
let xmlOutput; // = xmlString;
try {
const result = await transform({
// stylesheetFileName: `${config.TMP_BASE_DIR}/data-quality/rules/iati.sef.json`,
stylesheetText: proc,
destination: 'serialized',
// sourceFileName: sourceFile,
sourceText: xmlString,
stylesheetParams: xsltParameter,
// logLevel: 10,
});
xmlOutput = result.principalResult;
} catch (error) {
Logger.error('An error occurred while creating the user', error.message);
}
return xmlOutput;
} catch (error) {
Logger.error(`An error occurred while indexing datsaet with publish_id ${dataset.publish_id}.`);
}
},
async indexDocument(dataset: Dataset, index_name: string): Promise<void> {
try {
const proc = readFileSync('public/assets2/solr.sef.json');
const doc: string = await this.getTransformedString(dataset, proc);
let document = JSON.parse(doc);
await this.client.index({
id: dataset.publish_id?.toString(),
index: index_name,
body: document,
refresh: true,
});
Logger.info(`dataset with publish_id ${dataset.publish_id} successfully indexed`);
} catch (error) {
Logger.error(`An error occurred while indexing datsaet with publish_id ${dataset.publish_id}.`);
}
},
async getTransformedString(dataset, proc): Promise<string> {
let xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
const datasetNode = xml.root().ele('Dataset');
await createXmlRecord(dataset, datasetNode);
const xmlString = xml.end({ prettyPrint: false });
try {
const result = await transform({
stylesheetText: proc,
destination: 'serialized',
sourceText: xmlString,
});
return result.principalResult;
} catch (error) {
Logger.error(`An error occurred while creating the user, error: ${error.message},`);
return '';
}
},
};
/**
* Return the default global focus trap stack
*
* @return {import('focus-trap').FocusTrap[]}
*/
// export const indexDocument = async (dataset: Dataset, index_name: string, proc: Buffer): Promise<void> => {
// try {
// const doc = await getJsonString(dataset, proc);
// let document = JSON.parse(doc);
// await client.index({
// id: dataset.publish_id?.toString(),
// index: index_name,
// body: document,
// refresh: true,
// });
// Logger.info(`dataset with publish_id ${dataset.publish_id} successfully indexed`);
// } catch (error) {
// Logger.error(`An error occurred while indexing datsaet with publish_id ${dataset.publish_id}.`);
// }
// };
// const getJsonString = async (dataset, proc): Promise<string> => {
// let xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
// const datasetNode = xml.root().ele('Dataset');
// await createXmlRecord(dataset, datasetNode);
// const xmlString = xml.end({ prettyPrint: false });
// try {
// const result = await transform({
// stylesheetText: proc,
// destination: 'serialized',
// sourceText: xmlString,
// });
// return result.principalResult;
// } catch (error) {
// Logger.error(`An error occurred while creating the user, error: ${error.message},`);
// return '';
// }
// };
const createXmlRecord = async (dataset: Dataset, datasetNode: XMLBuilder): Promise<void> => {
const domNode = await getDatasetXmlDomNode(dataset);
if (domNode) {
// add frontdoor url and data-type
dataset.publish_id && addLandingPageAttribute(domNode, dataset.publish_id.toString());
addSpecInformation(domNode, 'data-type:' + dataset.type);
if (dataset.collections) {
for (const coll of dataset.collections) {
const collRole = coll.collectionRole;
addSpecInformation(domNode, collRole.oai_name + ':' + coll.number);
}
}
datasetNode.import(domNode);
}
};
const getDatasetXmlDomNode = async (dataset: Dataset): Promise<XMLBuilder | null> => {
const xmlModel = new XmlModel(dataset);
// xmlModel.setModel(dataset);
xmlModel.excludeEmptyFields();
xmlModel.caching = true;
// const cache = dataset.xmlCache ? dataset.xmlCache : null;
// dataset.load('xmlCache');
await dataset.load('xmlCache');
if (dataset.xmlCache) {
xmlModel.xmlCache = dataset.xmlCache;
}
// return cache.getDomDocument();
const domDocument: XMLBuilder | null = await xmlModel.getDomDocument();
return domDocument;
};
const addLandingPageAttribute = (domNode: XMLBuilder, dataid: string) => {
const baseDomain = process.env.OAI_BASE_DOMAIN || 'localhost';
const url = 'https://' + getDomain(baseDomain) + '/dataset/' + dataid;
// add attribute du dataset xml element
domNode.att('landingpage', url);
};
const addSpecInformation= (domNode: XMLBuilder, information: string) => {
domNode.ele('SetSpec').att('Value', information);
};