import Title from '#models/title'; import Description from '#models/description'; import License from '#models/license'; import Person from '#models/person'; import DatasetReference from '#models/dataset_reference'; import DatasetIdentifier from '#models/dataset_identifier'; import Subject from '#models/subject'; import File from '#models/file'; import Coverage from '#models/coverage'; import Collection from '#models/collection'; import { BaseModel as LucidBaseModel } from '@adonisjs/lucid/orm'; import Field from '#app/Library/Field'; import { DateTime } from 'luxon'; // @StaticImplements() // class LucidDatasetModel extends BaseModel{ // @belongsTo(() => Dataset, { // foreignKey: 'dataset_id', // }) // dataset: BelongsTo; // } export type DatasetRelatedModel = | typeof Title | typeof Description | typeof Coverage | typeof DatasetIdentifier | typeof DatasetReference | typeof Description | typeof DatasetIdentifier | typeof File; export default abstract class DatasetExtension extends LucidBaseModel { public abstract id: number; public externalFields: Record = this.getExternalFields(); // which fields shoud#t be published protected internalFields: Record = {}; protected fields: Record = {}; [key: string]: any; private getExternalFields(): Record { // External fields definition return { TitleMain: { model: Title, options: { type: ['Main'] }, fetch: 'eager', }, TitleAdditional: { model: Title, options: { type: ['Alternative', 'Sub', 'Translated', 'Other'] }, fetch: 'eager', }, TitleAbstract: { model: Description, options: { type: ['Abstract', 'Translated'] }, fetch: 'eager', }, TitleAbstractAdditional: { model: Description, options: { type: ['Methods', 'Technical_info', 'Series_information', 'Other'] }, fetch: 'eager', }, Licence: { model: License, through: 'link_documents_licences', relation: 'licenses', fetch: 'eager', }, PersonAuthor: { model: Person, through: 'link_documents_persons', pivot: { role: 'author', sort_order: 'sort_order', allow_email_contact: 'allow_email_contact' }, relation: 'persons', fetch: 'eager', }, PersonContributor: { model: Person, through: 'link_documents_persons', pivot: { role: 'contributor', contributor_type: 'contributor_type', sort_order: 'sort_order', allow_email_contact: 'allow_email_contact', }, relation: 'contributors', fetch: 'eager', }, Reference: { model: DatasetReference, relation: 'references', fetch: 'eager', }, Identifier: { model: DatasetIdentifier, relation: 'identifier', fetch: 'eager', }, Subject: { model: Subject, through: 'link_dataset_subjects', relation: 'subjects', fetch: 'eager', }, File: { model: File, relation: 'files', fetch: 'eager', }, Coverage: { model: Coverage, relation: 'coverage', fetch: 'eager', }, Collection: { model: Collection, through: 'link_documents_collections', relation: 'collections', fetch: 'eager', // 'include': { 'model': CollectionRole, 'relation': 'collectionrole' } }, }; } public initFields(): void { // Initialize internal fields let fields = new Array( 'Id', 'PublisherName', 'PublishId', 'ContributingCorporation', 'CreatingCorporation', 'Language', 'PublishedDate', // 'PublishedYear', 'PublisherName', // 'PublisherPlace', 'PublicationState', 'EmbargoDate', 'CreatedAt', 'ServerDateModified', 'ServerDatePublished', 'ServerDateDeleted', 'ServerState', 'Type', 'BelongsToBibliography', ); fields.forEach((fieldname) => { let field = new Field(fieldname); this.addField(field); }); // Initialize external fields const fieldNames = Object.keys(this.externalFields); for (const fieldName of fieldNames) { // const field = this.externalFields[fieldName]; let field = new Field(fieldName); field.setMultiplicity('*'); this.addField(field); } // // Initialize available date fields and set up date validator // // if the particular field is present let dateFields = new Array('EmbargoDate', 'CreatedAt', 'ServerDateModified', 'ServerDatePublished', 'ServerDateDeleted'); dateFields.forEach((fieldname) => { let dateField = this.getField(fieldname); dateField instanceof Field && dateField.setValueModelClass(DateTime.now()); }); } public async describe(): Promise> { let length: number = Object.keys(this.fields).length; if (length == 0) { await this.fetchValues(); } // Get an array of all field names in the 'fields' object const allFields = Object.keys(this.fields); // Get an array of all field names in the 'internalFields' array const internalFields = Object.keys(this.internalFields); // Use the `filter` method to find field names that are not in 'internalFields' const filteredFields = allFields.filter((fieldName) => !internalFields.includes(fieldName)); return filteredFields; } private addField(field: Field): void { // Add field const fieldName = field.getName(); if (fieldName && this.externalFields[fieldName]) { const options = this.externalFields[fieldName]; // Set ValueModelClass if a model option is given if (options.model) { field.setValueModelClass(options.model); } } this.fields[field.getName()] = field; // field.setOwningModelClass(this.constructor.name); } public getField(name: string): Field | null { // Get field return this.fields[name] !== undefined ? this.fields[name] : null; } public async fetchValues(): Promise { this.initFields(); await this.loadFieldValues(); } private async loadFieldValues(): Promise { for (const [fieldname, field] of Object.entries(this.fields)) { // extern fields via model relation if (this.externalFields.hasOwnProperty(fieldname)) { await this.loadExternal(fieldname); // dataset attributes itself } else { // Field is not external and gets handled by simply reading. to snake_case const property_name = this.convertFieldnameToColumn(fieldname); //id const fieldVal = this[property_name]; //276 // Explicitly set null if the field represents a model except for dates. if (field.getValueModelClass() !== null) { field.setValue(fieldVal === undefined || fieldVal === null ? null : fieldVal); } else { field.setValue(fieldVal); } } } } private async loadExternal(fieldname: string): Promise { const field = this.fields[fieldname]; // let modelclass: typeof Title | typeof Description; let modelclass: DatasetRelatedModel = field.getValueModelClass(); let modelInstance = new modelclass(); // Create a query builder const select = modelclass.query(); // If any declared constraints, add them to the query if (this.externalFields[fieldname]?.options) { const options: Array = this.externalFields[fieldname].options; for (const [column, value] of Object.entries(options)) { if (Array.isArray(value)) { select.whereIn(column, value); } else { select.where(column, value); } } } // Get dependent rows const result: Record[] = []; const datasetId = this.id; let rows: any[] = []; if (this.externalFields[fieldname]?.through) { const relation = this.externalFields[fieldname].relation; // rows = this[relation]; rows = await this.related(relation).query(); if (this.externalFields[fieldname].pivot) { const pivotArray = this.externalFields[fieldname].pivot; const pivotValue = pivotArray.role; // rows = this[relation]().wherePivot('role', pivotValue).get(); rows = await this.related(relation).query().wherePivot('role', pivotValue); } } else if (modelInstance.hasOwnProperty('dataset')) { rows = await select .whereHas('dataset', (q) => { q.where('id', datasetId); }) .orderBy('id') .select(); } // 1 ..n relations for (const row of rows) { const attributes = Object.keys(row.$attributes); if (this.externalFields[fieldname]?.pivot) { const pivotArray = this.externalFields[fieldname].pivot; const arrayKeys = Object.keys(pivotArray); const extendedArrayKeys = arrayKeys.map((pivotAttribute) => { return `pivot_${pivotAttribute}`; }); attributes.push(...extendedArrayKeys); } const objArray: Record = {}; for (const property_name of attributes) { let fieldName = this.convertColumnToFieldname(property_name); let fieldval = ''; if (property_name.startsWith('pivot_')) { const str = property_name.replace('pivot_', ''); fieldName = this.convertColumnToFieldname(str); // fieldval = row.$pivot[str]; fieldval = row.$extras[property_name]; } else if (fieldName === 'Type') { fieldval = row[property_name]?.charAt(0).toUpperCase() + row[property_name]?.slice(1); } else { fieldval = row[property_name]; } objArray[fieldName] = fieldval; } result.push(objArray); } // Set the field value field.setValue(result); } // to snakeCase private convertFieldnameToColumn(fieldname: string): string { return fieldname.replace(/([a-z\d])([A-Z])/g, '$1_$2').toLowerCase(); } private convertColumnToFieldname(columnName: string): string { return columnName .split(/[-_]/) .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(''); } }