- 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

@ -42,7 +42,8 @@
"@eidellev/adonis-stardust", "@eidellev/adonis-stardust",
"./providers/QueryBuilderProvider", "./providers/QueryBuilderProvider",
"./providers/TokenWorkerProvider", "./providers/TokenWorkerProvider",
"@adonisjs/redis" "@adonisjs/redis",
"./providers/DoiProvider"
], ],
"metaFiles": [ "metaFiles": [
{ {

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { Client } from '@opensearch-project/opensearch'; import { Client } from '@opensearch-project/opensearch';
import User from 'App/Models/User'; import User from 'App/Models/User';
import Dataset from 'App/Models/Dataset'; import Dataset from 'App/Models/Dataset';
import DatasetIdentifier from 'App/Models/DatasetIdentifier';
import XmlModel from 'App/Library/XmlModel'; import XmlModel from 'App/Library/XmlModel';
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces'; import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
import { create } from 'xmlbuilder2'; import { create } from 'xmlbuilder2';
@ -10,6 +11,12 @@ import { transform } from 'saxon-js';
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm'; import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
import { schema, CustomMessages } from '@ioc:Adonis/Core/Validator'; import { schema, CustomMessages } from '@ioc:Adonis/Core/Validator';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import Index from 'App/Library/Utils/Index';
import { getDomain } from 'App/Utils/utility-functions';
import { DoiClient } from 'App/Library/Doi/DoiClient';
import DoiClientException from 'App/Exceptions/DoiClientException';
import Logger from '@ioc:Adonis/Core/Logger';
import { HttpException } from 'node-exceptions';
// Create a new instance of the client // Create a new instance of the client
const client = new Client({ node: 'http://localhost:9200' }); // replace with your OpenSearch endpoint const client = new Client({ node: 'http://localhost:9200' }); // replace with your OpenSearch endpoint
@ -66,6 +73,7 @@ export default class DatasetsController {
.where('editor_id', user.id) .where('editor_id', user.id)
.doesntHave('identifier', 'and'); .doesntHave('identifier', 'and');
}) })
// .preload('identifier')
.preload('titles') .preload('titles')
.preload('user', (query) => query.select('id', 'login')) .preload('user', (query) => query.select('id', 'login'))
.preload('editor', (query) => query.select('id', 'login')) .preload('editor', (query) => query.select('id', 'login'))
@ -273,13 +281,79 @@ export default class DatasetsController {
dataset.publisher_name = publisherName; dataset.publisher_name = publisherName;
if (await dataset.save()) { if (await dataset.save()) {
const index_name = 'tethys-records';
await Index.indexDocument(dataset, index_name);
return response.toRoute('editor.dataset.list').flash('message', 'You have successfully published the dataset!'); return response.toRoute('editor.dataset.list').flash('message', 'You have successfully published the dataset!');
} }
} }
public async create({}: HttpContextContract) {} public async doiCreate({ request, inertia }: HttpContextContract) {
const id = request.param('id');
const dataset = await Dataset.query()
.where('id', id)
.preload('titles')
.preload('descriptions')
// .preload('identifier')
.preload('authors')
.firstOrFail();
return inertia.render('Editor/Dataset/Doi', {
dataset,
});
}
public async store({}: HttpContextContract) {} public async doiStore({ request, response }: HttpContextContract) {
const dataId = request.param('publish_id');
const dataset = await Dataset.query()
// .preload('xmlCache')
.where('publish_id', dataId)
.firstOrFail();
const xmlMeta = (await Index.getDoiRegisterString(dataset)) as string;
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 || '';
}
// register DOI:
const doiValue = prefix + '/tethys.' + dataset.publish_id; //'10.21388/tethys.213'
const landingPageUrl = 'https://doi.' + getDomain(base_domain) + '/' + prefix + '/tethys.' + dataset.publish_id; //https://doi.dev.tethys.at/10.21388/tethys.213
const doiClient = new DoiClient();
const dataciteResponse = await doiClient.registerDoi(doiValue, xmlMeta, landingPageUrl);
if (dataciteResponse?.status === 201) {
// if response OK 201; save the Identifier value into db
const doiIdentifier = new DatasetIdentifier();
doiIdentifier.value = doiValue;
doiIdentifier.dataset_id = dataset.id;
doiIdentifier.type = 'doi';
doiIdentifier.status = 'findable';
// save modified date of datset for re-caching model in db an update the search index
dataset.server_date_modified = DateTime.now();
// save updated dataset to db an index to OpenSearch
try {
await dataset.related('identifier').save(doiIdentifier);
const index_name = 'tethys-records';
await Index.indexDocument(dataset, index_name);
} catch (error) {
Logger.error(`${__filename}: Indexing document ${dataset.id} failed: ${error.message}`);
// Log the error or handle it as needed
throw new HttpException(error.message);
}
return response.toRoute('editor.dataset.list').flash('message', 'You have successfully created a DOI for the dataset!');
} else {
const message = `Unexpected DataCite MDS response code ${dataciteResponse?.status}`;
// Log the error or handle it as needed
throw new DoiClientException(dataciteResponse?.status, message);
}
// return response.toRoute('editor.dataset.list').flash('message', xmlMeta);
}
public async show({}: HttpContextContract) {} public async show({}: HttpContextContract) {}
@ -404,8 +478,6 @@ export default class DatasetsController {
public async destroy({}: HttpContextContract) {} public async destroy({}: HttpContextContract) {}
public async syncOpensearch({}: HttpContextContract) {}
private async createXmlRecord(dataset: Dataset, datasetNode: XMLBuilder) { private async createXmlRecord(dataset: Dataset, datasetNode: XMLBuilder) {
const domNode = await this.getDatasetXmlDomNode(dataset); const domNode = await this.getDatasetXmlDomNode(dataset);
if (domNode) { if (domNode) {

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<Dataset>
<Rdr_Dataset Id="306" PublisherName="GeoSphere Austria" PublishId="213"
CreatingCorporation="Tethys RDR" Language="en" ServerState="published"
Type="measurementdata">
<CreatedAt Year="2023" Month="11" Day="30" Hour="10" Minute="20" Second="58"
UnixTimestamp="1701336058" Timezone="Europe/Berlin" />
<ServerDateModified Year="2024" Month="1" Day="22" Hour="12" Minute="28" Second="17"
UnixTimestamp="1705922897" Timezone="Europe/Berlin" />
<ServerDatePublished Year="2024" Month="1" Day="22" Hour="12" Minute="28" Second="8"
UnixTimestamp="1705922888" Timezone="Europe/Berlin" />
<TitleMain Id="682" DocumentId="306" Type="Main" Value="rewerewr" Language="en" />
<TitleAbstract Id="1017" DocumentId="306" Type="Abstract" Value="rewrewr" Language="en" />
<Licence Id="1" Active="true"
LinkLicence="https://creativecommons.org/licenses/by/4.0/deed.en"
LinkLogo="https://licensebuttons.net/l/by/4.0/88x31.png"
NameLong="Creative Commons Attribution 4.0 International (CC BY 4.0)"
Name="CC-BY-4.0" SortOrder="1" />
<PersonAuthor Id="1" Email="m.moser@univie.ac.at" FirstName="Michael" LastName="Moser"
Status="true" NameType="Personal" Role="author" SortOrder="1"
AllowEmailContact="false" />
<PersonContributor Id="28" Email="juergen.reitner@geologie.ac.at" FirstName="Jürgen"
LastName="Reitner" Status="false" NameType="Personal" Role="contributor"
SortOrder="1" AllowEmailContact="false" />
<Subject Id="143" Language="de" Type="Geoera" Value="Aletshausen-Langenneufnach Störung"
CreatedAt="2023-11-21 17:17:43" UpdatedAt="2023-11-21 17:17:43" />
<Subject Id="164" Language="de" Type="Geoera" Value="Wolfersberg-Moosach Störung"
ExternalKey="https://data.geoscience.earth/ncl/geoera/hotLime/faults/3503"
CreatedAt="2023-11-30 10:20:58" UpdatedAt="2023-11-30 10:20:58" />
<Subject Id="165" Language="en" Type="Uncontrolled" Value="wefwef"
CreatedAt="2023-11-30 10:20:58" UpdatedAt="2023-11-30 10:20:58" />
<File Id="1037" DocumentId="306" PathName="files/306/file-clpkzkkgq0001nds14fua5um6.png"
Label="freieIP.png" MimeType="image/png" FileSize="112237" VisibleInFrontdoor="true"
VisibleInOai="true" SortOrder="0" CreatedAt="2023-11-30 10:21:14"
UpdatedAt="2023-11-30 10:21:14" />
<Coverage Id="284" DatasetId="306" XMin="11.71142578125" XMax="14.414062500000002"
YMin="46.58906908309185" YMax="47.45780853075031" CreatedAt="2023-11-30 10:20:58"
UpdatedAt="2023-11-30 10:20:58" />
<Collection Id="21" RoleId="3" Number="551" Name="Geology, hydrology, meteorology"
ParentId="20" Visible="true" VisiblePublish="true" />
</Rdr_Dataset>
</Dataset>
</root>
<?xml version="1.0" encoding="utf-8"?>
<resource xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://datacite.org/schema/kernel-4"
xsi:schemaLocation="http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4.3/metadata.xsd">
<identifier identifierType="DOI">10.21388/tethys.213</identifier>
<creators>
<creator>
<creatorName nameType="Personal">Moser, Michael</creatorName>
<givenName>Michael</givenName>
<familyName>Moser</familyName>
<affiliation>GBA</affiliation>
</creator>
</creators>
<titles>
<title xml:lang="en">rewerewr</title>
</titles>
<publisher>Tethys RDR</publisher>
<publicationYear>2024</publicationYear>
<subjects>
<subject xml:lang="de">Aletshausen-Langenneufnach Störung</subject>
<subject xml:lang="de">Wolfersberg-Moosach Störung</subject>
<subject xml:lang="en">wefwef</subject>
</subjects>
<language>en</language>
<contributors>
<contributor contributorType="RegistrationAuthority">
<contributorName>Jürgen Reitner</contributorName>
</contributor>
</contributors>
<dates>
<date dateType="Created">2023-11-30</date>
</dates>
<version>1</version>
<resourceType resourceTypeGeneral="Dataset">Dataset</resourceType>
<alternateIdentifiers>
<alternateIdentifier alternateIdentifierType="url">https://www.tethys.at/dataset/213</alternateIdentifier>
</alternateIdentifiers>
<rightsList>
<rights xml:lang="" rightsURI="https://creativecommons.org/licenses/by/4.0/deed.en"
schemeURI="https://spdx.org/licenses/" rightsIdentifierScheme="SPDX"
rightsIdentifier="CC-BY-4.0">Creative Commons Attribution 4.0 International (CC BY 4.0)</rights>
<rights rightsURI="info:eu-repo/semantics/openAccess">Open Access</rights>
</rightsList>
<sizes>
<size>1 datasets</size>
</sizes>
<formats>
<format>image/png</format>
</formats>
<descriptions>
<description xml:lang="en" descriptionType="Abstract">rewrewr</description>
</descriptions>
<geoLocations>
<geoLocation>
<geoLocationBox>
<westBoundLongitude>11.71142578125</westBoundLongitude>
<eastBoundLongitude>14.414062500000002</eastBoundLongitude>
<southBoundLatitude>46.58906908309185</southBoundLatitude>
<northBoundLatitude>47.45780853075031</northBoundLatitude>
</geoLocationBox>
</geoLocation>
</geoLocations>
</resource>

View File

@ -0,0 +1,12 @@
class DoiClientException extends Error {
public status: number;
public message: string;
constructor(status: number, message: string) {
super(message);
this.status = status;
this.message = message;
}
}
export default DoiClientException;

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) { private mapModelAttributes(myObject, childNode: XMLBuilder) {
Object.keys(myObject).forEach((prop) => { Object.keys(myObject).forEach((prop) => {
let value = myObject[prop]; let value = myObject[prop];
console.log(`${prop}: ${value}`); // console.log(`${prop}: ${value}`);
if (value != null) { if (value != null) {
if (value instanceof DateTime) { if (value instanceof DateTime) {
value = value.toFormat('yyyy-MM-dd HH:mm:ss').trim(); 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);
};

View File

@ -20,6 +20,9 @@ export default class DatasetIdentifier extends BaseModel {
@column({}) @column({})
public type: string; public type: string;
@column({})
public status: string;
@column({}) @column({})
public value: string; public value: string;
@ -38,4 +41,9 @@ export default class DatasetIdentifier extends BaseModel {
foreignKey: 'dataset_id', foreignKey: 'dataset_id',
}) })
public dataset: BelongsTo<typeof Dataset>; public dataset: BelongsTo<typeof Dataset>;
// // Specify the relationships to touch when this model is updated
// public static get touches() {
// return ['dataset'];
// }
} }

View File

@ -82,7 +82,7 @@ export default abstract class DatasetExtension extends LucidBaseModel {
sort_order: 'sort_order', sort_order: 'sort_order',
allow_email_contact: 'allow_email_contact', allow_email_contact: 'allow_email_contact',
}, },
relation: 'persons', relation: 'contributors',
fetch: 'eager', fetch: 'eager',
}, },
Reference: { Reference: {

View File

@ -1,5 +1,5 @@
import { BaseCommand, flags } from '@adonisjs/core/build/standalone'; import { BaseCommand, flags } from '@adonisjs/core/build/standalone';
import Logger from '@ioc:Adonis/Core/Logger'; // import Logger from '@ioc:Adonis/Core/Logger';
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces'; import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
import { create } from 'xmlbuilder2'; import { create } from 'xmlbuilder2';
import Dataset from 'App/Models/Dataset'; import Dataset from 'App/Models/Dataset';
@ -7,6 +7,7 @@ import XmlModel from 'App/Library/XmlModel';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { transform } from 'saxon-js'; import { transform } from 'saxon-js';
import { Client } from '@opensearch-project/opensearch'; import { Client } from '@opensearch-project/opensearch';
import { getDomain } from 'App/Utils/utility-functions';
const opensearchNode = process.env.OPENSEARCH_HOST || 'localhost'; const opensearchNode = process.env.OPENSEARCH_HOST || 'localhost';
const client = new Client({ node: `http://${opensearchNode}` }); // replace with your OpenSearch endpoint const client = new Client({ node: `http://${opensearchNode}` }); // replace with your OpenSearch endpoint
@ -43,9 +44,9 @@ export default class IndexDatasets extends BaseCommand {
for (var dataset of datasets) { for (var dataset of datasets) {
// Logger.info(`File publish_id ${dataset.publish_id}`); // Logger.info(`File publish_id ${dataset.publish_id}`);
const jsonString = await this.getJsonString(dataset, proc); // const jsonString = await this.getJsonString(dataset, proc);
// console.log(jsonString); // console.log(jsonString);
await this.indexDocument(dataset, index_name, jsonString); await this.indexDocument(dataset, index_name, proc);
} }
} }
@ -57,6 +58,23 @@ export default class IndexDatasets extends BaseCommand {
return await query; return await query;
} }
private async indexDocument(dataset: Dataset, index_name: string, proc: Buffer): Promise<void> {
try {
const doc = await this.getJsonString(dataset, proc);
let document = JSON.parse(doc);
await client.index({
id: dataset.publish_id?.toString(),
index: index_name,
body: document,
refresh: true,
});
this.logger.info(`dataset with publish_id ${dataset.publish_id} successfully indexed`);
} catch (error) {
this.logger.error(`An error occurred while indexing datsaet with publish_id ${dataset.publish_id}.`);
}
}
private async getJsonString(dataset, proc) { private async getJsonString(dataset, proc) {
let xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>'); let xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
const datasetNode = xml.root().ele('Dataset'); const datasetNode = xml.root().ele('Dataset');
@ -71,29 +89,16 @@ export default class IndexDatasets extends BaseCommand {
}); });
return result.principalResult; return result.principalResult;
} catch (error) { } catch (error) {
Logger.error(`An error occurred while creating the user, error: ${error.message},`); this.logger.error(`An error occurred while creating the user, error: ${error.message},`);
return ''; return '';
} }
} }
private async indexDocument(dataset: Dataset, index_name: string, doc: string): Promise<void> {
try {
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}.`);
}
}
private async createXmlRecord(dataset: Dataset, datasetNode: XMLBuilder): Promise<void> { private async createXmlRecord(dataset: Dataset, datasetNode: XMLBuilder): Promise<void> {
const domNode = await this.getDatasetXmlDomNode(dataset); const domNode = await this.getDatasetXmlDomNode(dataset);
if (domNode) { if (domNode) {
dataset.publish_id && this.addLandingPageAttribute(domNode, dataset.publish_id.toString());
this.addSpecInformation(domNode, 'data-type:' + dataset.type);
datasetNode.import(domNode); datasetNode.import(domNode);
} }
} }
@ -113,4 +118,15 @@ export default class IndexDatasets extends BaseCommand {
const domDocument: XMLBuilder | null = await xmlModel.getDomDocument(); const domDocument: XMLBuilder | null = await xmlModel.getDomDocument();
return domDocument; return domDocument;
} }
private addSpecInformation(domNode: XMLBuilder, information: string) {
domNode.ele('SetSpec').att('Value', information);
}
private 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);
}
} }

34
package-lock.json generated
View File

@ -56,7 +56,7 @@
"@japa/preset-adonis": "^1.2.0", "@japa/preset-adonis": "^1.2.0",
"@japa/runner": "^2.5.1", "@japa/runner": "^2.5.1",
"@mdi/js": "^7.1.96", "@mdi/js": "^7.1.96",
"@symfony/webpack-encore": "^4.2.0", "@symfony/webpack-encore": "^4.5.0",
"@tailwindcss/forms": "^0.5.2", "@tailwindcss/forms": "^0.5.2",
"@types/clamscan": "^2.0.4", "@types/clamscan": "^2.0.4",
"@types/leaflet": "^1.9.3", "@types/leaflet": "^1.9.3",
@ -78,7 +78,7 @@
"numeral": "^2.0.6", "numeral": "^2.0.6",
"pinia": "^2.0.30", "pinia": "^2.0.30",
"pino-pretty": "^10.0.0", "pino-pretty": "^10.0.0",
"postcss-loader": "^7.0.2", "postcss-loader": "^7.3.0",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",
@ -3304,9 +3304,9 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.21", "version": "0.3.22",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
"integrity": "sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g==", "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
@ -4206,9 +4206,9 @@
} }
}, },
"node_modules/@types/luxon": { "node_modules/@types/luxon": {
"version": "3.4.1", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.1.tgz", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
"integrity": "sha512-m1KQEZZCITtheRhMVq5jDvAl0HwFhunLs7x6tpFFvUTJpKfmewS/Ymg+YA97/s8w1I1nC4pJyi0aAnn+vf3yew==" "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA=="
}, },
"node_modules/@types/md5": { "node_modules/@types/md5": {
"version": "2.3.5", "version": "2.3.5",
@ -7010,9 +7010,9 @@
} }
}, },
"node_modules/core-js-compat": { "node_modules/core-js-compat": {
"version": "3.35.0", "version": "3.35.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz",
"integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"browserslist": "^4.22.2" "browserslist": "^4.22.2"
@ -7939,9 +7939,9 @@
} }
}, },
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "16.3.1", "version": "16.3.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz",
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -8128,9 +8128,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.639", "version": "1.4.640",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.639.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.640.tgz",
"integrity": "sha512-CkKf3ZUVZchr+zDpAlNLEEy2NJJ9T64ULWaDgy3THXXlPVPkLu3VOs9Bac44nebVtdwl2geSj6AxTtGDOxoXhg==", "integrity": "sha512-z/6oZ/Muqk4BaE7P69bXhUhpJbUM9ZJeka43ZwxsDshKtePns4mhBlh8bU5+yrnOnz3fhG82XLzGUXazOmsWnA==",
"dev": true "dev": true
}, },
"node_modules/emittery": { "node_modules/emittery": {

View File

@ -7,6 +7,7 @@
"dev": "node ace serve --watch", "dev": "node ace serve --watch",
"compress:xslt": "./node_modules/xslt3/xslt3.js -xsl:public/assets2/datasetxml2oai-pmh.xslt -export:public/assets2/datasetxml2oai.sef.json -t -nogo '-ns:##html5'", "compress:xslt": "./node_modules/xslt3/xslt3.js -xsl:public/assets2/datasetxml2oai-pmh.xslt -export:public/assets2/datasetxml2oai.sef.json -t -nogo '-ns:##html5'",
"compress:solr": "./node_modules/xslt3/xslt3.js -xsl:public/assets2/solr.xslt -export:public/assets2/solr.sef.json -t -nogo '-ns:##html5'", "compress:solr": "./node_modules/xslt3/xslt3.js -xsl:public/assets2/solr.xslt -export:public/assets2/solr.sef.json -t -nogo '-ns:##html5'",
"compress:doi": "./node_modules/xslt3/xslt3.js -xsl:public/assets2/doi_datacite.xslt -export:public/assets2/doi_datacite.sef.json -t -nogo '-ns:##html5'",
"build": "node ace build --production", "build": "node ace build --production",
"start": "node server.js", "start": "node server.js",
"lint": "eslint . --ext=.ts", "lint": "eslint . --ext=.ts",
@ -31,7 +32,7 @@
"@japa/preset-adonis": "^1.2.0", "@japa/preset-adonis": "^1.2.0",
"@japa/runner": "^2.5.1", "@japa/runner": "^2.5.1",
"@mdi/js": "^7.1.96", "@mdi/js": "^7.1.96",
"@symfony/webpack-encore": "^4.2.0", "@symfony/webpack-encore": "^4.5.0",
"@tailwindcss/forms": "^0.5.2", "@tailwindcss/forms": "^0.5.2",
"@types/clamscan": "^2.0.4", "@types/clamscan": "^2.0.4",
"@types/leaflet": "^1.9.3", "@types/leaflet": "^1.9.3",
@ -53,7 +54,7 @@
"numeral": "^2.0.6", "numeral": "^2.0.6",
"pinia": "^2.0.30", "pinia": "^2.0.30",
"pino-pretty": "^10.0.0", "pino-pretty": "^10.0.0",
"postcss-loader": "^7.0.2", "postcss-loader": "^7.3.0",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",

View File

@ -0,0 +1,40 @@
import type { ApplicationContract } from '@ioc:Adonis/Core/Application'
/*
|--------------------------------------------------------------------------
| Provider
|--------------------------------------------------------------------------
|
| Your application is not ready when this file is loaded by the framework.
| Hence, the top level imports relying on the IoC container will not work.
| You must import them inside the life-cycle methods defined inside
| the provider class.
|
| @example:
|
| public async ready () {
| const Database = this.app.container.resolveBinding('Adonis/Lucid/Database')
| const Event = this.app.container.resolveBinding('Adonis/Core/Event')
| Event.on('db:query', Database.prettyPrint)
| }
|
*/
export default class DoiProvider {
constructor(protected app: ApplicationContract) {}
public register() {
// Register your own bindings
}
public async boot() {
// All bindings are ready, feel free to use them
}
public async ready() {
// App is ready
}
public async shutdown() {
// Cleanup, since app is going down
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,443 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* This file is part of TETHYS.
*
* LICENCE
* TETHYS is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the Licence, or any later version.
* OPUS is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details. You should have received a copy of the GNU General Public License
* along with OPUS; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @category Application
* @package Module_Oai
* @author Arno Kaimbacher <arno.kaimbacher@geologie.ac.at>
* @copyright Copyright (c) 2018-2019, GBA TETHYS development team
* @license http://www.gnu.org/licenses/gpl.html General Public License
*/
-->
<!--
/**
* Transforms the xml representation of an TETHYS model dataset to datacite
* xml as required by the OAI-PMH protocol.
*/
-->
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" encoding="utf-8" indent="yes" />
<xsl:param name="unixTimestamp" />
<xsl:param name="prefix" />
<xsl:param name="repIdentifier" />
<xsl:template match="Rdr_Dataset">
<resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://datacite.org/schema/kernel-4" xsi:schemaLocation="http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4.3/metadata.xsd">
<!-- <isReferenceQuality>true</isReferenceQuality>
<schemaVersion>4.3</schemaVersion>
<datacentreSymbol>RDR.GBA</datacentreSymbol> -->
<xsl:choose>
<xsl:when test="Identifier">
<xsl:apply-templates select="Identifier" mode="oai_datacite" />
</xsl:when>
<xsl:otherwise>
<identifier identifierType="DOI">
<xsl:value-of select="$prefix" />
<xsl:text>/</xsl:text>
<xsl:value-of select="$repIdentifier" />
<xsl:text>.</xsl:text>
<xsl:value-of select="@PublishId" />
</identifier>
</xsl:otherwise>
</xsl:choose>
<!--<datacite:creator>-->
<creators>
<xsl:apply-templates select="PersonAuthor" mode="oai_datacite">
<xsl:sort select="@SortOrder"/>
</xsl:apply-templates>
</creators>
<titles>
<xsl:apply-templates select="TitleMain" mode="oai_datacite" />
<xsl:apply-templates select="TitleAdditional" mode="oai_datacite" />
</titles>
<publisher>
<!-- <xsl:value-of select="@PublisherName" /> -->
<xsl:value-of select="@CreatingCorporation" />
</publisher>
<publicationYear>
<xsl:value-of select="ServerDatePublished/@Year" />
</publicationYear>
<subjects>
<xsl:apply-templates select="Subject" mode="oai_datacite" />
</subjects>
<language>
<xsl:value-of select="@Language" />
</language>
<xsl:if test="PersonContributor">
<contributors>
<xsl:apply-templates select="PersonContributor" mode="oai_datacite">
<xsl:sort select="@SortOrder"/>
</xsl:apply-templates>
</contributors>
</xsl:if>
<dates>
<xsl:call-template name="RdrDate2" />
</dates>
<version>
<xsl:choose>
<xsl:when test="@Version">
<xsl:value-of select="@Version" />
</xsl:when>
<xsl:otherwise>
<xsl:text>1</xsl:text>
</xsl:otherwise>
</xsl:choose>
</version>
<resourceType resourceTypeGeneral="Dataset">
<xsl:text>Dataset</xsl:text>
<!-- <xsl:value-of select="@Type" /> -->
</resourceType>
<alternateIdentifiers>
<xsl:call-template name="AlternateIdentifier" />
</alternateIdentifiers>
<xsl:if test="Reference">
<relatedIdentifiers>
<xsl:apply-templates select="Reference" mode="oai_datacite" />
</relatedIdentifiers>
</xsl:if>
<rightsList>
<xsl:apply-templates select="Licence" mode="oai_datacite" />
</rightsList>
<sizes>
<size>
<xsl:value-of select="count(File)" />
<xsl:text> datasets</xsl:text>
</size>
</sizes>
<formats>
<xsl:apply-templates select="File/@MimeType" mode="oai_datacite" />
</formats>
<descriptions>
<xsl:apply-templates select="TitleAbstract" mode="oai_datacite" />
<xsl:apply-templates select="TitleAbstractAdditional" mode="oai_datacite" />
</descriptions>
<geoLocations>
<xsl:apply-templates select="Coverage" mode="oai_datacite" />
<!-- <geoLocation>
<geoLocationBox>
<westBoundLongitude>6.58987</westBoundLongitude>
<eastBoundLongitude>6.83639</eastBoundLongitude>
<southBoundLatitude>50.16</southBoundLatitude>
<northBoundLatitude>50.18691</northBoundLatitude>
</geoLocationBox>
</geoLocation> -->
</geoLocations>
</resource>
</xsl:template>
<xsl:template name="RdrDate2"
xmlns="http://datacite.org/schema/kernel-4">
<xsl:if test="EmbargoDate and ($unixTimestamp &lt; EmbargoDate/@UnixTimestamp)">
<date>
<xsl:attribute name="dateType">Available</xsl:attribute>
<xsl:variable name="embargoDate" select="concat(
EmbargoDate/@Year, '-',
format-number(number(EmbargoDate/@Month),'00'), '-',
format-number(number(EmbargoDate/@Day),'00')
)" />
<xsl:value-of select="$embargoDate" />
</date>
</xsl:if>
<xsl:if test="CreatedAt">
<date>
<xsl:attribute name="dateType">Created</xsl:attribute>
<xsl:variable name="createdAt" select="concat(
CreatedAt/@Year, '-',
format-number(number(CreatedAt/@Month),'00'), '-',
format-number(number(CreatedAt/@Day),'00')
)" />
<xsl:value-of select="$createdAt" />
</date>
</xsl:if>
</xsl:template>
<xsl:template match="Coverage" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<geoLocation>
<geoLocationBox>
<westBoundLongitude>
<xsl:value-of select="@XMin" />
</westBoundLongitude>
<eastBoundLongitude>
<xsl:value-of select="@XMax" />
</eastBoundLongitude>
<southBoundLatitude>
<xsl:value-of select="@YMin" />
</southBoundLatitude>
<northBoundLatitude>
<xsl:value-of select="@YMax" />
</northBoundLatitude>
</geoLocationBox>
</geoLocation>
</xsl:template>
<xsl:template match="TitleAbstract" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<description>
<xsl:attribute name="xml:lang">
<xsl:value-of select="@Language" />
</xsl:attribute>
<xsl:if test="@Type != ''">
<xsl:attribute name="descriptionType">
<!-- <xsl:value-of select="@Type" /> -->
<xsl:text>Abstract</xsl:text>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="@Value" />
</description>
</xsl:template>
<xsl:template match="TitleAbstractAdditional" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<description>
<xsl:attribute name="xml:lang">
<xsl:value-of select="@Language" />
</xsl:attribute>
<xsl:if test="@Type != ''">
<xsl:attribute name="descriptionType">
<xsl:call-template name="CamelCaseWord">
<xsl:with-param name="text" select="@Type" />
</xsl:call-template>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="@Value" />
</description>
</xsl:template>
<xsl:template name="CamelCaseWord">
<xsl:param name="text" />
<xsl:param name="firstLower" select="true()" />
<xsl:variable name="Upper">ABCDEFGHIJKLMNOPQRSTUVQXYZ</xsl:variable>
<xsl:variable name="Lower">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:for-each select="tokenize($text,'_')">
<xsl:choose>
<xsl:when test="position()=1 and $firstLower = true()">
<xsl:value-of select="substring(.,1,1)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="translate(substring(.,1,1),$Lower,$Upper)" />
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="substring(.,2,string-length(.))" />
</xsl:for-each>
</xsl:template>
<xsl:template match="Identifier" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<identifier>
<xsl:attribute name="identifierType">
<xsl:text>DOI</xsl:text>
</xsl:attribute>
<xsl:value-of select="@Value" />
</identifier>
</xsl:template>
<xsl:template match="TitleMain" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<title>
<xsl:if test="@Language != ''">
<xsl:attribute name="xml:lang">
<xsl:value-of select="@Language" />
</xsl:attribute>
</xsl:if>
<xsl:if test="@Type != '' and @Type != 'Main'">
<xsl:attribute name="titleType">
<xsl:value-of select="@Type" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="@Value" />
</title>
</xsl:template>
<xsl:template match="TitleAdditional" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<title>
<xsl:if test="@Language != ''">
<xsl:attribute name="xml:lang">
<xsl:value-of select="@Language" />
</xsl:attribute>
</xsl:if>
<xsl:choose>
<xsl:when test="@Type != '' and @Type != 'Sub' and @Type != 'Main'">
<xsl:attribute name="titleType">
<xsl:value-of select="@Type" />
<xsl:text>Title</xsl:text>
</xsl:attribute>
</xsl:when>
<xsl:when test="@Type = 'Sub'">
<xsl:attribute name="titleType">
<xsl:value-of select="@Type" />
<xsl:text>title</xsl:text>
</xsl:attribute>
</xsl:when>
</xsl:choose>
<xsl:value-of select="@Value" />
</title>
</xsl:template>
<xsl:template match="Subject" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<subject>
<xsl:if test="@Language != ''">
<xsl:attribute name="xml:lang">
<xsl:value-of select="@Language" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="@Value" />
</subject>
</xsl:template>
<xsl:template name="AlternateIdentifier" match="AlternateIdentifier" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<alternateIdentifier>
<xsl:attribute name="alternateIdentifierType">
<xsl:text>url</xsl:text>
</xsl:attribute>
<!-- <xsl:variable name="identifier" select="concat($repURL, '/dataset/', @Id)" /> -->
<xsl:value-of select="@landingpage" />
</alternateIdentifier>
</xsl:template>
<xsl:template match="Reference" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<relatedIdentifier>
<xsl:attribute name="relatedIdentifierType">
<xsl:value-of select="@Type" />
</xsl:attribute>
<xsl:attribute name="relationType">
<xsl:value-of select="@Relation" />
</xsl:attribute>
<xsl:value-of select="@Value" />
</relatedIdentifier>
</xsl:template>
<xsl:template match="PersonContributor" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<contributor>
<xsl:if test="@ContributorType != ''">
<xsl:attribute name="contributorType">
<xsl:value-of select="@ContributorType" />
</xsl:attribute>
</xsl:if>
<contributorName>
<!-- <xsl:if test="@NameType != ''">
<xsl:attribute name="nameType">
<xsl:value-of select="@NameType" />
</xsl:attribute>
</xsl:if> -->
<xsl:value-of select="concat(@FirstName, ' ',@LastName)" />
</contributorName>
</contributor>
</xsl:template>
<xsl:template match="PersonAuthor" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<creator>
<creatorName>
<xsl:if test="@NameType != ''">
<xsl:attribute name="nameType">
<xsl:value-of select="@NameType" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="@LastName" />
<xsl:if test="@FirstName != ''">
<xsl:text>, </xsl:text>
</xsl:if>
<xsl:value-of select="@FirstName" />
<xsl:if test="@AcademicTitle != ''">
<xsl:text> (</xsl:text>
<xsl:value-of select="@AcademicTitle" />
<xsl:text>)</xsl:text>
</xsl:if>
</creatorName>
<xsl:if test="@NameType = 'Personal'">
<givenName>
<xsl:value-of select="@FirstName" />
</givenName>
<familyName>
<xsl:value-of select="@LastName" />
</familyName>
<xsl:if test="@IdentifierOrcid != ''">
<nameIdentifier schemeURI="http://orcid.org/" nameIdentifierScheme="ORCID">
<xsl:value-of select="@IdentifierOrcid" />
</nameIdentifier>
</xsl:if>
<affiliation>GBA</affiliation>
</xsl:if>
<xsl:if test="@NameType = 'Organizational'">
<xsl:if test="@IdentifierOrcid != ''">
<nameIdentifier schemeURI="http://orcid.org/" nameIdentifierScheme="ORCID">
<xsl:value-of select="@IdentifierOrcid" />
</nameIdentifier>
</xsl:if>
</xsl:if>
<!--
<nameType><xsl:value-of select="@NameType" /></nameType>
</xsl:if> -->
</creator>
</xsl:template>
<xsl:template match="File/@MimeType" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<format>
<xsl:value-of select="." />
</format>
</xsl:template>
<xsl:template match="Licence" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4">
<rights>
<xsl:attribute name="xml:lang">
<xsl:value-of select="@Language" />
</xsl:attribute>
<xsl:if test="@LinkLicence != ''">
<xsl:attribute name="rightsURI">
<xsl:value-of select="@LinkLicence" />
</xsl:attribute>
</xsl:if>
<xsl:attribute name="schemeURI">
<xsl:text>https://spdx.org/licenses/</xsl:text>
</xsl:attribute>
<xsl:attribute name="rightsIdentifierScheme">
<xsl:text>SPDX</xsl:text>
</xsl:attribute>
<xsl:attribute name="rightsIdentifier">
<xsl:value-of select="@Name" />
</xsl:attribute>
<xsl:value-of select="@NameLong" />
</rights>
<xsl:if test="@Name = 'CC-BY-4.0' or @Name = 'CC-BY-SA-4.0'">
<rights>
<xsl:attribute name="rightsURI">
<xsl:text>info:eu-repo/semantics/openAccess</xsl:text>
</xsl:attribute>
<xsl:text>Open Access</xsl:text>
</rights>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

View File

@ -158,7 +158,7 @@
</xsl:if> </xsl:if>
<xsl:if test="CreatedAt"> <xsl:if test="CreatedAt">
<date> <date>
<xsl:attribute name="dateType">created</xsl:attribute> <xsl:attribute name="dateType">Created</xsl:attribute>
<xsl:variable name="createdAt" select="concat( <xsl:variable name="createdAt" select="concat(
CreatedAt/@Year, '-', CreatedAt/@Year, '-',
format-number(number(CreatedAt/@Month),'00'), '-', format-number(number(CreatedAt/@Month),'00'), '-',
@ -221,7 +221,6 @@
</description> </description>
</xsl:template> </xsl:template>
<xsl:template name="CamelCaseWord"> <xsl:template name="CamelCaseWord">
<xsl:param name="text" /> <xsl:param name="text" />
<xsl:param name="firstLower" select="true()" /> <xsl:param name="firstLower" select="true()" />
@ -240,7 +239,6 @@
</xsl:for-each> </xsl:for-each>
</xsl:template> </xsl:template>
<xsl:template match="Identifier" mode="oai_datacite" <xsl:template match="Identifier" mode="oai_datacite"
xmlns="http://datacite.org/schema/kernel-4"> xmlns="http://datacite.org/schema/kernel-4">
<identifier> <identifier>
@ -376,13 +374,20 @@
<familyName> <familyName>
<xsl:value-of select="@LastName" /> <xsl:value-of select="@LastName" />
</familyName> </familyName>
<xsl:if test="@IdentifierOrcid != ''">
<nameIdentifier schemeURI="http://orcid.org/" nameIdentifierScheme="ORCID">
<xsl:value-of select="@IdentifierOrcid" />
</nameIdentifier>
</xsl:if>
<affiliation>GBA</affiliation> <affiliation>GBA</affiliation>
</xsl:if> </xsl:if>
<xsl:if test="@IdentifierOrcid != ''"> <xsl:if test="@NameType = 'Organizational'">
<nameIdentifier schemeURI="http://orcid.org/" nameIdentifierScheme="ORCID"> <xsl:if test="@IdentifierOrcid != ''">
<xsl:value-of select="@IdentifierOrcid" /> <nameIdentifier schemeURI="http://orcid.org/" nameIdentifierScheme="ORCID">
</nameIdentifier> <xsl:value-of select="@IdentifierOrcid" />
</nameIdentifier>
</xsl:if>
</xsl:if> </xsl:if>
<!-- <!--
<nameType><xsl:value-of select="@NameType" /></nameType> <nameType><xsl:value-of select="@NameType" /></nameType>

View File

@ -0,0 +1,110 @@
<script setup lang="ts">
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import { router, Head, usePage } from '@inertiajs/vue3';
import { computed, Ref } from 'vue';
import CardBox from '@/Components/CardBox.vue';
import BaseButton from '@/Components/BaseButton.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import { stardust } from '@eidellev/adonis-stardust/client';
import { mdiArrowLeftBoldOutline, mdiIdentifier } from '@mdi/js';
import FormValidationErrors from '@/Components/FormValidationErrors.vue';
import Notification from '@/utils/toast';
const props = defineProps({
dataset: {
type: Object,
default: () => ({}),
},
});
const flash: Ref<any> = computed(() => {
return usePage().props.flash;
});
const errors: Ref<any> = computed(() => {
return usePage().props.errors;
});
// const form = useForm({
// preferred_reviewer: '',
// preferred_reviewer_email: '',
// preferation: 'yes_preferation',
// // preferation: '',
// // isPreferationRequired: false,
// });
// const isPreferationRequired = computed(() => form.preferation === 'yes_preferation');
const handleSubmit = async (e) => {
e.preventDefault();
Notification.showInfo(`doi implementation is in developement. Create DOI for dataset ${props.dataset.publish_id} later on`);
await router.put(stardust.route('editor.dataset.doiStore', [props.dataset.publish_id]));
// await form.put(stardust.route('dataset.releaseUpdate', [props.dataset.id]));
};
</script>
<template>
<LayoutAuthenticated>
<Head title="Mint DOI" />
<SectionMain>
<SectionTitleLineWithButton :icon="mdiIdentifier" title="Mint DOI for dataset" main>
<BaseButton
:route-name="stardust.route('editor.dataset.list')"
:icon="mdiArrowLeftBoldOutline"
label="Back"
color="white"
rounded-full
small
/>
</SectionTitleLineWithButton>
<CardBox form @submit.prevent="handleSubmit">
<FormValidationErrors v-bind:errors="errors" />
<!-- <div class="flex flex-col md:flex-row items-center"> -->
<!-- <div class="w-full">
<label class="block font-bold mb-2" for="owner">Submitter:</label>
<div class="w-full p-2 rounded-md">{{ dataset.user.login }}</div>
</div> -->
<div class="w-full">
<label class="block font-bold mb-2" for="title">Tethys Publish ID:</label>
<div class="w-full p-2 rounded-md" v-if="dataset.publish_id">
{{ dataset.publish_id }}
</div>
</div>
<div class="w-full">
<label class="block font-bold mb-2" for="title">Dataset main title:</label>
<div class="w-full p-2 rounded-md" v-if="dataset.main_title">
{{ dataset.main_title }}
</div>
<div class="w-full p-2 rounded-md" v-else>No title available</div>
</div>
<div class="w-full">
<label class="block font-bold mb-2" for="title">Dataset abstract:</label>
<div class="w-full p-2 rounded-md" v-if="dataset.main_abstract">
{{ dataset.main_abstract }}
</div>
<div class="w-full p-2 rounded-md" v-else>No abstract available</div>
</div>
<!-- </div> -->
<div v-if="flash && flash.warning" class="flex flex-col mt-6 animate-fade-in">
<div class="bg-yellow-500 border-l-4 border-orange-400 text-white p-4" role="alert">
<p class="font-bold">Be Warned</p>
<p>{{ flash.warning }}</p>
</div>
</div>
<template #footer>
<BaseButtons>
<!-- <BaseButton type="submit" color="info" label="Receive"
:class="{ 'opacity-25': router.processing }" :disabled="form.processing" /> -->
<BaseButton type="submit" color="info" label="Mint DOI" />
</BaseButtons>
</template>
</CardBox>
</SectionMain>
</LayoutAuthenticated>
</template>

View File

@ -158,6 +158,11 @@ const getRowClass = (dataset) => {
:route-name="stardust.route('editor.dataset.publish', [dataset.id])" :route-name="stardust.route('editor.dataset.publish', [dataset.id])"
color="info" :icon="mdiBookEdit" :label="'Publish'" small /> color="info" :icon="mdiBookEdit" :label="'Publish'" small />
<BaseButton
v-if="can.publish && (dataset.server_state == 'published' && !dataset.identifier)"
:route-name="stardust.route('editor.dataset.doi', [dataset.id])"
color="info" :icon="mdiBookEdit" :label="'Mint DOI'" small />
</BaseButtons> </BaseButtons>
</td> </td>
</tr> </tr>

View File

@ -242,6 +242,16 @@ Route.group(() => {
.as('editor.dataset.publishUpdate') .as('editor.dataset.publishUpdate')
.where('id', Route.matchers.number()) .where('id', Route.matchers.number())
.middleware(['auth', 'can:dataset-publish']); .middleware(['auth', 'can:dataset-publish']);
Route.get('dataset/:id/doi', 'DatasetController.doiCreate')
.as('editor.dataset.doi')
.where('id', Route.matchers.number())
.middleware(['auth', 'can:dataset-publish']);
Route.put('dataset/:publish_id/doi', 'DatasetController.doiStore')
.as('editor.dataset.doiStore')
.where('id', Route.matchers.number())
.middleware(['auth', 'can:dataset-publish']);
Route.put('/dataset/:id/update', 'DatasetController.update') Route.put('/dataset/:id/update', 'DatasetController.update')
.as('editor.dataset.update') .as('editor.dataset.update')