- use latest prettier 3.0 with eslint-plugin-prettier: 5.0.0-alpha.2

- npm normal updates
- add all xslt and style asstes in extra folder public/assets2
- linting corrections
- delete local .env.test from git tracking: git rm --cached .env.test
- add .env.test into .gitignore file
- add edit functionality for editing by submitter
- npm updates
-added xslt3 packeage for builfing sef files
- added Language.ts class vor language table
- added version to datasetxml2oai-pmh.xslt
This commit is contained in:
Kaimbacher 2023-07-17 19:13:30 +02:00
parent f403c3109f
commit 4714dfdd94
70 changed files with 22920 additions and 17407 deletions

View file

@ -20,14 +20,14 @@ export default class RoleController {
if (request.input('sort')) {
type SortOrder = 'asc' | 'desc' | undefined;
let attribute = request.input('sort');
let sort_order: SortOrder = 'asc';
let sortOrder: SortOrder = 'asc';
if (attribute.substr(0, 1) == '-') {
sort_order = 'desc';
if (attribute.substr(0, 1) === '-') {
sortOrder = 'desc';
// attribute = substr(attribute, 1);
attribute = attribute.substr(1);
}
roles.orderBy(attribute, sort_order);
roles.orderBy(attribute, sortOrder);
} else {
// users.orderBy('created_at', 'desc');
roles.orderBy('id', 'asc');

View file

@ -23,16 +23,16 @@ export default class UsersController {
if (request.input('sort')) {
type SortOrder = 'asc' | 'desc' | undefined;
let attribute = request.input('sort');
let sort_order: SortOrder = 'asc';
let sortOrder: SortOrder = 'asc';
// if (strncmp($attribute, '-', 1) === 0) {
if (attribute.substr(0, 1) == '-') {
sort_order = 'desc';
if (attribute.substr(0, 1) === '-') {
sortOrder = 'desc';
// attribute = substr(attribute, 1);
attribute = attribute.substr(1);
}
// $users->orderBy($attribute, $sort_order);
users.orderBy(attribute, sort_order);
users.orderBy(attribute, sortOrder);
} else {
// users.orderBy('created_at', 'desc');
users.orderBy('id', 'asc');

View file

@ -0,0 +1,189 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { RequestContract } from '@ioc:Adonis/Core/Request';
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
import { create } from 'xmlbuilder2';
import dayjs, { Dayjs } from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import { readFileSync } from 'fs';
import { StatusCodes } from 'http-status-codes';
import { transform } from 'saxon-js';
// import { Xslt, xmlParse } from 'xslt-processor'
import { OaiErrorCodes, OaiModelError } from 'App/Exceptions/OaiErrorCodes';
import { OaiModelException } from 'App/Exceptions/OaiModelException';
interface XslTParameter {
[key: string]: any;
}
interface Dictionary {
[index: string]: string;
}
export default class OaiController {
// private deliveringDocumentStates = ["published", "deleted"];
// private sampleRegEx = /^[A-Za-zäüÄÜß0-9\-_.!~]+$/;
private xsltParameter: XslTParameter;
/**
* Holds xml representation of document information to be processed.
*
* @var xmlbuilder.XMLDocument | null Defaults to null.
*/
private xml: XMLBuilder;
private proc;
constructor() {
// Load the XSLT file
this.proc = readFileSync('public/assets2/datasetxml2oai.sef.json');
// tests
// const xslPath = 'assets/datasetxml2oai-pmh.xslt'; // Replace with the actual path to your XSLT file
// this.proc = readFileSync(xslPath, 'utf-8');
// this.configuration = new Configuration();
dayjs.extend(utc);
dayjs.extend(timezone);
}
public async index({ response, request }: HttpContextContract): Promise<void> {
this.xml = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
// this.proc = new XSLTProcessor();
// const stylesheet = readFileSync(__dirname + "/datasetxml2oai.sef.json");
const xsltParameter = (this.xsltParameter = {});
let oaiRequest: Dictionary = {};
if (request.method() === 'POST') {
oaiRequest = request.body();
} else if (request.method() === 'GET') {
oaiRequest = request.qs();
} else {
xsltParameter['oai_error_code'] = 'unknown';
xsltParameter['oai_error_message'] = 'Only POST and GET methods are allowed for OAI-PMH.';
}
// const oaiRequest: OaiParameter = request.body;
try {
this.handleRequest(oaiRequest, request);
} catch (error) {
if (error instanceof OaiModelException) {
const code = error.oaiCode;
let oaiErrorCode: string | undefined = 'Unknown oai error code ' + code;
if (OaiModelError.has(error.oaiCode) && OaiModelError.get(code) !== undefined) {
oaiErrorCode = OaiModelError.get(error.oaiCode);
}
this.xsltParameter['oai_error_code'] = oaiErrorCode;
this.xsltParameter['oai_error_message'] = error.message;
} else {
// // return next(error); // passing to default express middleware error handler
this.xsltParameter['oai_error_code'] = 'unknown';
this.xsltParameter['oai_error_message'] = 'An internal error occured.';
}
}
const xmlString = this.xml.end({ prettyPrint: true });
let xmlOutput;
try {
const result = await transform({
// stylesheetFileName: `${config.TMP_BASE_DIR}/data-quality/rules/iati.sef.json`,
stylesheetText: this.proc,
destination: 'serialized',
// sourceFileName: sourceFile,
sourceText: xmlString,
stylesheetParams: xsltParameter,
// logLevel: 10,
});
xmlOutput = result.principalResult;
} catch (error) {
return response.status(500).json({
message: 'An error occurred while creating the user',
error: error.message,
});
}
response
.header('Content-Type', 'application/xml; charset=utf-8')
.header('Access-Control-Allow-Origin', '*')
.header('Access-Control-Allow-Methods', 'GET,POST');
response.status(StatusCodes.OK).send(xmlOutput);
}
protected handleRequest(oaiRequest: Dictionary, request: RequestContract) {
// Setup stylesheet
// $this->loadStyleSheet('datasetxml2oai-pmh.xslt');
// Set response time
const now: Dayjs = dayjs();
this.xsltParameter['responseDate'] = now.format('YYYY-MM-DDTHH:mm:ss[Z]');
this.xsltParameter['unixTimestamp'] = now.unix();
// set OAI base url
const baseDomain = process.env.BASE_DOMAIN || 'localhost';
this.xsltParameter['baseURL'] = baseDomain + '/oai';
this.xsltParameter['repURL'] = request.protocol() + '://' + request.hostname();
this.xsltParameter['downloadLink'] = request.protocol() + '://' + request.hostname() + '/file/download/';
this.xsltParameter['doiLink'] = 'https://doi.org/';
this.xsltParameter['doiPrefix'] = 'info:eu-repo/semantics/altIdentifier/doi/';
if (oaiRequest['verb']) {
const verb = oaiRequest['verb'];
this.xsltParameter['oai_verb'] = verb;
if (verb === 'Identify') {
this.handleIdentify();
} else if (verb === 'ListMetadataFormats') {
this.handleListMetadataFormats();
}
// else if (verb == "GetRecord") {
// await this.handleGetRecord(oaiRequest);
// } else if (verb == "ListRecords") {
// await this.handleListRecords(oaiRequest);
// } else if (verb == "ListIdentifiers") {
// await this.handleListIdentifiers(oaiRequest);
// } else if (verb == "ListSets") {
// await this.handleListSets();
// }
else {
this.handleIllegalVerb();
}
} else {
// // try {
// // console.log("Async code example.")
// const err = new PageNotFoundException("verb not found");
// throw err;
// // } catch (error) { // manually catching
// // next(error); // passing to default middleware error handler
// // }
throw new OaiModelException(
StatusCodes.INTERNAL_SERVER_ERROR,
'The verb provided in the request is illegal.',
OaiErrorCodes.BADVERB,
);
}
}
protected handleIdentify() {
const email = process.env.OAI_EMAIL || 'repository@geosphere.at';
const repositoryName = 'Tethys RDR';
const repIdentifier = 'tethys.at';
const sampleIdentifier = 'oai:' + repIdentifier + ':1'; //$this->_configuration->getSampleIdentifier();
// Dataset::earliestPublicationDate()->server_date_published->format('Y-m-d\TH:i:s\Z') : null;
// earliestDateFromDb!= null && (this.xsltParameter['earliestDatestamp'] = earliestDateFromDb?.server_date_published);
// set parameters for oai-pmh.xslt
this.xsltParameter['email'] = email;
this.xsltParameter['repositoryName'] = repositoryName;
this.xsltParameter['repIdentifier'] = repIdentifier;
this.xsltParameter['sampleIdentifier'] = sampleIdentifier;
// $this->proc->setParameter('', 'earliestDatestamp', $earliestDateFromDb);
this.xml.root().ele('Datasets');
}
protected handleListMetadataFormats() {
this.xml.root().ele('Datasets');
}
private handleIllegalVerb() {
this.xsltParameter['oai_error_code'] = 'badVerb';
this.xsltParameter['oai_error_message'] = 'The verb provided in the request is illegal.';
}
}

View file

@ -1,12 +1,11 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import User from 'App/Models/User';
import Dataset from 'App/Models/Dataset';
// import Role from 'App/Models/Role';
// import Database from '@ioc:Adonis/Lucid/Database';
import License from 'App/Models/License';
import Project from 'App/Models/Project';
import Title from 'App/Models/Title';
import Description from 'App/Models/Description';
import Language from 'App/Models/Language';
// import CreateUserValidator from 'App/Validators/CreateUserValidator';
// import UpdateUserValidator from 'App/Validators/UpdateUserValidator';
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
@ -16,7 +15,7 @@ import Database from '@ioc:Adonis/Lucid/Database';
import { TransactionClientContract } from '@ioc:Adonis/Lucid/Database';
import Subject from 'App/Models/Subject';
import CreateDatasetValidator from 'App/Validators/CreateDatasetValidator';
import { TitleTypes, DescriptionTypes } from 'Contracts/enums';
import { TitleTypes, DescriptionTypes, ContributorTypes, PersonNameTypes } from 'Contracts/enums';
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
export default class DatasetController {
@ -34,14 +33,14 @@ export default class DatasetController {
if (request.input('sort')) {
type SortOrder = 'asc' | 'desc' | undefined;
let attribute = request.input('sort');
let sort_order: SortOrder = 'asc';
let sortOrder: SortOrder = 'asc';
if (attribute.substr(0, 1) == '-') {
sort_order = 'desc';
if (attribute.substr(0, 1) === '-') {
sortOrder = 'desc';
// attribute = substr(attribute, 1);
attribute = attribute.substr(1);
}
datasets.orderBy(attribute, sort_order);
datasets.orderBy(attribute, sortOrder);
} else {
// users.orderBy('created_at', 'desc');
datasets.orderBy('id', 'asc');
@ -73,7 +72,7 @@ export default class DatasetController {
datasets: myDatasets.serialize(),
filters: request.all(),
can: {
// create: await auth.user?.can(['user-create']),
// create: await auth.user?.can(['dataset-submit']),
edit: await auth.user?.can(['dataset-edit']),
delete: await auth.user?.can(['dataset-delete']),
},
@ -391,7 +390,7 @@ export default class DatasetController {
// $dataKeyword = new Subject($keyword);
// $dataset->subjects()->save($dataKeyword);
const keyword = await Subject.firstOrNew({ value: keywordData.value, type: keywordData.type }, keywordData);
if (keyword.$isNew == true) {
if (keyword.$isNew === true) {
await dataset.useTransaction(trx).related('subjects').save(keyword);
} else {
await dataset.useTransaction(trx).related('subjects').attach([keyword.id]);
@ -485,7 +484,7 @@ export default class DatasetController {
};
// public async release({ params, view }) {
public async release({ request, inertia }: HttpContextContract) {
public async release({ request, inertia, response }: HttpContextContract) {
const id = request.param('id');
const dataset = await Dataset.query()
@ -495,11 +494,17 @@ export default class DatasetController {
.where('id', id)
.firstOrFail();
// const editors = await User.query()
// .whereHas('roles', (builder) => {
// builder.where('name', 'editor');
// })
// .pluck('login', 'id');
const validStates = ['inprogress', 'rejected_editor'];
if (!validStates.includes(dataset.server_state)) {
// session.flash('errors', 'Invalid server state!');
return response
.flash(
'warning',
`Invalid server state. Dataset with id ${id} cannot be released to editor. Datset has server state ${dataset.server_state}.`,
)
.redirect()
.back();
}
return inertia.render('Submitter/Dataset/Release', {
dataset,
@ -509,8 +514,16 @@ export default class DatasetController {
public async releaseUpdate({ request, response }: HttpContextContract) {
const id = request.param('id');
const dataset = await Dataset.query().preload('files').where('id', id).firstOrFail();
const validStates = ['inprogress', 'rejected_editor'];
if (!validStates.includes(dataset.server_state)) {
// throw new Error('Invalid server state!');
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
return dataset.serialize();
}
if (dataset.files.length === 0) {
return response.flash('message', 'At least minimum one file is required.').redirect('back');
return response.flash('warning', 'At least minimum one file is required.').redirect('back');
}
const preferation = request.input('preferation', '');
@ -561,11 +574,106 @@ export default class DatasetController {
input.reject_reviewer_note = null;
}
// if (await dataset.merge(input).save()) {
// return response.redirect().route('publish.workflow.submit.index').flash('flash_message', 'You have released your dataset!');
// }
return response.toRoute('dataset.list').flash('message', 'You have released your dataset');
if (await dataset.merge(input).save()) {
return response.toRoute('dataset.list').flash('message', 'You have released your dataset!');
}
// throw new GeneralException(trans('exceptions.publish.release.update_error'));
}
public async edit({ params, inertia }) {
const dataset = await Dataset.query().where('id', params.id).preload('titles').firstOrFail();
// await dataset.loadMany([
// 'licenses',
// 'authors',
// 'contributors',
// 'titles',
// 'abstracts',
// 'files',
// 'coverage',
// 'subjects',
// 'references',
// ]);
const titleTypes = Object.entries(TitleTypes)
// .filter(([value]) => value !== 'Main')
.map(([key, value]) => ({ value: key, label: value }));
const descriptionTypes = Object.entries(DescriptionTypes)
// .filter(([value]) => value !== 'Abstract')
.map(([key, value]) => ({ value: key, label: value }));
const languages = await Language.query().where('active', true).pluck('part1', 'part1');
// const contributorTypes = Config.get('enums.contributor_types');
const contributorTypes = Object.entries(ContributorTypes).map(([key, value]) => ({ value: key, label: value }));
// const nameTypes = Config.get('enums.name_types');
const nameTypes = Object.entries(PersonNameTypes).map(([key, value]) => ({ value: key, label: value }));
// const messages = await Database.table('messages')
// .pluck('help_text', 'metadata_element');
const projects = await Project.query().pluck('label', 'id');
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const years = Array.from({ length: currentYear - 1990 + 1 }, (_, index) => 1990 + index);
// const licenses = await License.query()
// .select('id', 'name_long', 'link_licence')
// .orderBy('sort_order')
// .fetch();
const licenses = await License.query().select('id', 'name_long', 'link_licence').orderBy('sort_order');
// const checkeds = dataset.licenses.first().id;
const keywordTypes = {
uncontrolled: 'uncontrolled',
swd: 'swd',
};
const referenceTypes = ['DOI', 'Handle', 'ISBN', 'ISSN', 'URL', 'URN'];
const relationTypes = [
'IsSupplementTo',
'IsSupplementedBy',
'IsContinuedBy',
'Continues',
'IsNewVersionOf',
'IsPartOf',
'HasPart',
'Compiles',
'IsVariantFormOf',
];
const doctypes = {
analysisdata: { label: 'Analysis', value: 'analysisdata' },
measurementdata: { label: 'Measurements', value: 'measurementdata' },
monitoring: 'Monitoring',
remotesensing: 'Remote Sensing',
gis: 'GIS',
models: 'Models',
mixedtype: 'Mixed Type',
};
return inertia.render('Submitter/Dataset/Edit', {
dataset,
titletypes : titleTypes,
descriptionTypes,
contributorTypes,
nameTypes,
languages,
// messages,
projects,
licenses,
// checkeds,
years,
// languages,
keywordTypes,
referenceTypes,
relationTypes,
doctypes,
});
}
}

View file

@ -22,14 +22,14 @@ export default class InvalidCredentialException extends Exception {
/**
* Unable to find user
*/
static invalidUid() {
public static invalidUid() {
const error = new this('User not found', 400, 'E_INVALID_AUTH_UID');
return error;
}
/**
* Invalid user password
*/
static invalidPassword() {
public static invalidPassword() {
const error = new this('Password mis-match', 400, 'E_INVALID_AUTH_PASSWORD');
return error;
}
@ -59,7 +59,7 @@ export default class InvalidCredentialException extends Exception {
* Handle this exception by itself
*/
handle(error, ctx) {
public handle(error, ctx) {
// return response.status(403).view.render("errors/unauthorized", {
// error: error,
// });

View file

@ -0,0 +1,48 @@
export enum OaiErrorCodes {
BADVERB = 1010,
BADARGUMENT = 1011,
CANNOTDISSEMINATEFORMAT = 1012,
BADRESUMPTIONTOKEN = 1013,
NORECORDSMATCH = 1014,
IDDOESNOTEXIST = 1015,
}
// 👇️ default export
// export { OaiErrorCodes };
// https://medium.com/@juliapassynkova/map-your-typescript-enums-e402d406b229
export const OaiModelError = new Map<number, string>([
[OaiErrorCodes.BADVERB, 'badVerb'],
[OaiErrorCodes.BADARGUMENT, 'badArgument'],
[OaiErrorCodes.NORECORDSMATCH, 'noRecordsMatch'],
[OaiErrorCodes.CANNOTDISSEMINATEFORMAT, 'cannotDisseminateFormat'],
[OaiErrorCodes.BADRESUMPTIONTOKEN, 'badResumptionToken'],
[OaiErrorCodes.IDDOESNOTEXIST, 'idDoesNotExist'],
]);
// class OaiModelError {
// // const BADVERB = 1010;
// // const BADARGUMENT = 1011;
// // const CANNOTDISSEMINATEFORMAT = 1012;
// // const BADRESUMPTIONTOKEN = 1013;
// // const NORECORDSMATCH = 1014;
// // const IDDOESNOTEXIST = 1015;
// protected static $oaiErrorCodes = {
// OaiErrorCodes. 'badVerb',
// BADARGUMENT : 'badArgument',
// NORECORDSMATCH: 'noRecordsMatch',
// CANNOTDISSEMINATEFORMAT: 'cannotDisseminateFormat',
// BADRESUMPTIONTOKEN: 'badResumptionToken',
// IDDOESNOTEXIST: 'idDoesNotExist',
// };
// public static function mapCode($code)
// {
// if (false === array_key_exists($code, self::$oaiErrorCodes)) {
// throw new OaiModelException("Unknown oai error code $code");
// }
// return self::$oaiErrorCodes[$code];
// }
// }

View file

@ -0,0 +1,77 @@
import { StatusCodes } from 'http-status-codes';
// import HTTPException from './HttpException';
import { OaiErrorCodes } from './OaiErrorCodes';
export class ErrorCode {
public static readonly Unauthenticated = 'Unauthenticated';
public static readonly NotFound = 'NotFound';
public static readonly MaximumAllowedGrade = 'MaximumAllowedGrade';
public static readonly AsyncError = 'AsyncError';
public static readonly UnknownError = 'UnknownError';
}
export class ErrorModel {
/**
* Unique error code which identifies the error.
*/
public code: string;
/**
* Status code of the error.
*/
public status: number;
/**
* Any additional data that is required for translation.
*/
// public metaData?: any;
}
export class OaiModelException extends Error {
public status: number;
public message: string;
public oaiCode: number;
// constructor(status: number, message: string) {
// super(message);
// this.status = status;
// this.message = message;
// }
constructor(status: number, message: string, oaiCode: number) {
super(message);
this.status = status;
this.message = message;
this.oaiCode = oaiCode;
}
// constructor(code: string = ErrorCode.UnknownError, message: any = null) {
// super(code);
// Object.setPrototypeOf(this, new.target.prototype);
// this.name = code;
// this.status = 500;
// this.message = message;
// // switch (code) {
// // case ErrorCode.Unauthenticated:
// // this.status = 401;
// // break;
// // case ErrorCode.MaximumAllowedGrade:
// // this.status = 400;
// // break;
// // case ErrorCode.AsyncError:
// // this.status = 400;
// // break;
// // case ErrorCode.NotFound:
// // this.status = 404;
// // break;
// // default:
// // this.status = 500;
// // break;
// // }
// }
}
export class BadOaiModelException extends OaiModelException {
constructor(message?: string) {
super(StatusCodes.INTERNAL_SERVER_ERROR, message || 'bad Request', OaiErrorCodes.BADARGUMENT);
this.stack = '';
}
}
// export default OaiModelexception;

View file

@ -16,7 +16,7 @@ class FlashResponse extends Response implements ResponseContract {
) {
super(request, response, flashEncryption, flashConfig, flashRouter);
}
nonce: string;
public nonce: string;
public flash(key: string, message: any): this {
// Store the flash message in the session

View file

@ -30,7 +30,7 @@ export default class Dataset extends BaseModel {
public id: number;
@column({})
public server_state: boolean;
public server_state: string;
@column({})
public publisherName: string;
@ -57,10 +57,10 @@ export default class Dataset extends BaseModel {
public reviewer_id: number | null = null;
@column({})
public reject_editor_note: string;
public reject_editor_note: string | null;
@column({})
public reject_reviewer_note: string;
public reject_reviewer_note: string | null;
@column.dateTime({ columnName: 'server_date_published' })
public serverDatePublished: DateTime;
@ -111,10 +111,6 @@ export default class Dataset extends BaseModel {
})
public licenses: ManyToMany<typeof License>;
// public function subjects()
// {
// return $this->belongsToMany(\App\Models\Subject::class, 'link_dataset_subjects', 'document_id', 'subject_id');
// }
@manyToMany(() => Subject, {
pivotForeignKey: 'document_id',
pivotRelatedForeignKey: 'subject_id',

View file

@ -10,7 +10,7 @@ export default class HashValue extends BaseModel {
// return 'type, value'
// }
static get incrementing() {
public static get incrementing() {
return false;
}

35
app/Models/Language.ts Normal file
View file

@ -0,0 +1,35 @@
import { column, BaseModel, SnakeCaseNamingStrategy } from '@ioc:Adonis/Lucid/Orm';
// import { DateTime } from 'luxon';
export default class Language extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'id';
public static table = 'languages';
public static selfAssignPrimaryKey = false;
@column({
isPrimary: true,
})
public id: number;
@column({})
public part2_b: string;
@column({})
public part2_T: string;
@column({})
public description: string;
@column({})
public part1: string;
@column({})
public type: string;
@column({})
public ref_name: string;
@column({})
public active: boolean;
}