tethys.backend/app/Controllers/Http/Submitter/DatasetController.ts
Arno Kaimbacher f403c3109f
All checks were successful
CI Pipeline / japa-tests (push) Successful in 54s
- add methods for releasing datasets from submitter
- npm updates
- side menu with child items
- flash messages via HttpContext response (extended via macro)
2023-06-27 18:23:18 +02:00

572 lines
25 KiB
TypeScript

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 CreateUserValidator from 'App/Validators/CreateUserValidator';
// import UpdateUserValidator from 'App/Validators/UpdateUserValidator';
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
import dayjs from 'dayjs';
import Person from 'App/Models/Person';
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 type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
export default class DatasetController {
public async index({ auth, request, inertia }: HttpContextContract) {
const user = (await User.find(auth.user?.id)) as User;
const page = request.input('page', 1);
let datasets: ModelQueryBuilderContract<typeof Dataset, Dataset> = Dataset.query();
// if (request.input('search')) {
// // users = users.whereRaw('name like %?%', [request.input('search')])
// const searchTerm = request.input('search');
// datasets.where('name', 'ilike', `%${searchTerm}%`);
// }
if (request.input('sort')) {
type SortOrder = 'asc' | 'desc' | undefined;
let attribute = request.input('sort');
let sort_order: SortOrder = 'asc';
if (attribute.substr(0, 1) == '-') {
sort_order = 'desc';
// attribute = substr(attribute, 1);
attribute = attribute.substr(1);
}
datasets.orderBy(attribute, sort_order);
} else {
// users.orderBy('created_at', 'desc');
datasets.orderBy('id', 'asc');
}
// const users = await User.query().orderBy('login').paginate(page, limit);
const myDatasets = await datasets
.whereIn('server_state', [
'inprogress',
'released',
'editor_accepted',
'approved',
'reviewed',
'rejected_editor',
'rejected_reviewer',
])
.where('account_id', user.id)
.preload('titles')
.preload('user', (query) => query.select('id', 'login'))
// .preload('titles', (builder) => {
// // pull the actual preload data
// builder.where('type', 'Main');
// })
.paginate(page, 10);
return inertia.render('Submitter/Dataset/Index', {
// testing: 'this is a test',
datasets: myDatasets.serialize(),
filters: request.all(),
can: {
// create: await auth.user?.can(['user-create']),
edit: await auth.user?.can(['dataset-edit']),
delete: await auth.user?.can(['dataset-delete']),
},
});
}
public async create({ inertia }: HttpContextContract) {
const licenses = await License.query().select('id', 'name_long').pluck('name_long', 'id');
const projects = await Project.query().pluck('label', 'id');
const doctypes = {
analysisdata: { label: 'Analysis', value: 'analysisdata' },
measurementdata: { label: 'Measurements', value: 'measurementdata' },
monitoring: 'Monitoring',
remotesensing: 'Remote Sensing',
gis: 'GIS',
models: 'Models',
mixedtype: 'Mixed Type',
};
// const titletypes = {
// Sub: 'Sub',
// Alternative: 'Alternative',
// Translated: 'Translated',
// Other: 'Other',
// };
// let test = Object.entries(TitleTypes).map(([key, value]) => ({id: key, value: value}))
// const languages = await Database.from('languages').select('*').where('active', true);
return inertia.render('Submitter/Dataset/Create', {
licenses: licenses,
doctypes: doctypes,
titletypes: Object.entries(TitleTypes)
.filter(([value]) => value !== 'Main')
.map(([key, value]) => ({ value: key, label: value })),
descriptiontypes: Object.entries(DescriptionTypes)
.filter(([value]) => value !== 'Abstract')
.map(([key, value]) => ({ value: key, label: value })),
// descriptiontypes: DescriptionTypes
projects: projects,
});
}
public async firstStep({ request, response }: HttpContextContract) {
const newDatasetSchema = schema.create({
language: schema.string({ trim: true }, [
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
]),
licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
rights: schema.string([rules.equalTo('true')]),
});
try {
// Step 2 - Validate request body against the schema
await request.validate({ schema: newDatasetSchema, messages: this.messages });
// console.log({ payload });
} catch (error) {
// Step 3 - Handle errors
// return response.badRequest(error.messages);
throw error;
}
return response.redirect().back();
}
public async secondStep({ request, response }: HttpContextContract) {
const newDatasetSchema = schema.create({
// first step
language: schema.string({ trim: true }, [
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
]),
licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
rights: schema.string([rules.equalTo('true')]),
// second step
type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
titles: schema.array([rules.minLength(1)]).members(
schema.object().members({
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
type: schema.enum(Object.values(TitleTypes)),
language: schema.string({ trim: true }, [
rules.minLength(2),
rules.maxLength(255),
rules.translatedLanguage('/language', 'type'),
]),
}),
),
descriptions: schema.array([rules.minLength(1)]).members(
schema.object().members({
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
type: schema.enum(Object.values(DescriptionTypes)),
language: schema.string({ trim: true }, [
rules.minLength(2),
rules.maxLength(255),
rules.translatedLanguage('/language', 'type'),
]),
}),
),
authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })),
// project_id: schema.number(),
});
try {
// Step 2 - Validate request body against the schema
await request.validate({ schema: newDatasetSchema, messages: this.messages });
// console.log({ payload });
} catch (error) {
// Step 3 - Handle errors
// return response.badRequest(error.messages);
throw error;
}
return response.redirect().back();
}
public async thirdStep({ request, response }: HttpContextContract) {
const newDatasetSchema = schema.create({
// first step
language: schema.string({ trim: true }, [
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
]),
licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
rights: schema.string([rules.equalTo('true')]),
// second step
type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
titles: schema.array([rules.minLength(1)]).members(
schema.object().members({
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
type: schema.enum(Object.values(TitleTypes)),
language: schema.string({ trim: true }, [
rules.minLength(2),
rules.maxLength(255),
rules.translatedLanguage('/language', 'type'),
]),
}),
),
descriptions: schema.array([rules.minLength(1)]).members(
schema.object().members({
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
type: schema.enum(Object.values(DescriptionTypes)),
language: schema.string({ trim: true }, [
rules.minLength(2),
rules.maxLength(255),
rules.translatedLanguage('/language', 'type'),
]),
}),
),
authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })),
// third step
project_id: schema.number.optional(),
embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
coverage: schema.object().members({
x_min: schema.number(),
x_max: schema.number(),
y_min: schema.number(),
y_max: schema.number(),
elevation_absolut: schema.number.optional(),
elevation_min: schema.number.optional([rules.requiredIfExists('elevation_max')]),
elevation_max: schema.number.optional([rules.requiredIfExists('elevation_min')]),
depth_absolut: schema.number.optional(),
depth_min: schema.number.optional([rules.requiredIfExists('depth_max')]),
depth_max: schema.number.optional([rules.requiredIfExists('depth_min')]),
}),
subjects: schema.array([rules.minLength(3), rules.uniqueArray('value')]).members(
schema.object().members({
value: schema.string({ trim: true }, [
rules.minLength(3),
rules.maxLength(255),
// rules.unique({ table: 'dataset_subjects', column: 'value' }),
]),
// type: schema.enum(Object.values(TitleTypes)),
language: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]),
}),
),
});
try {
// Step 2 - Validate request body against the schema
await request.validate({ schema: newDatasetSchema, messages: this.messages });
// console.log({ payload });
} catch (error) {
// Step 3 - Handle errors
// return response.badRequest(error.messages);
throw error;
}
return response.redirect().back();
}
public async store({ auth, request, response, session }: HttpContextContract) {
// node ace make:validator CreateDataset
try {
// Step 2 - Validate request body against the schema
// await request.validate(CreateUserValidator);
// await request.validate({ schema: newDatasetSchema, messages: this.messages });
await request.validate(CreateDatasetValidator);
// console.log({ payload });
} catch (error) {
// Step 3 - Handle errors
// return response.badRequest(error.messages);
throw error;
}
// const user = new User();
// user.email = 'example@example.com';
// user.password = 'password';
// await user.useTransaction(trx).save();
let trx: TransactionClientContract | null = null;
try {
trx = await Database.transaction();
const user = (await User.find(auth.user?.id)) as User;
// const dataset = await user.related('datasets').create({
// type: request.input('type'),
// creatingCorporation: request.input('creating_corporation'),
// language: request.input('language'),
// });
// Create a new instance of the Dataset model:
const dataset = new Dataset();
dataset.type = request.input('type');
dataset.creatingCorporation = request.input('creating_corporation');
dataset.language = request.input('language');
dataset.embargoDate = request.input('embargo_date');
//await dataset.related('user').associate(user); // speichert schon ab
// Dataset.$getRelation('user').boot();
// Dataset.$getRelation('user').setRelated(dataset, user);
// dataset.$setRelated('user', user);
await user.useTransaction(trx).related('datasets').save(dataset);
//store licenses:
const licenses: number[] = request.input('licenses', []);
dataset.useTransaction(trx).related('licenses').sync(licenses);
const authors = request.input('authors', []);
for (const [key, person] of authors.entries()) {
const pivotData = { role: 'author', sort_order: key + 1 };
if (person.id !== undefined) {
await dataset
.useTransaction(trx)
.related('persons')
.attach({
[person.id]: {
role: pivotData.role,
sort_order: pivotData.sort_order,
allow_email_contact: false,
},
});
} else {
const dataPerson = new Person();
// use overwritten fill method
dataPerson.fill(person);
await dataset.useTransaction(trx).related('persons').save(dataPerson, false, {
role: pivotData.role,
sort_order: pivotData.sort_order,
allow_email_contact: false,
});
}
}
const contributors = request.input('contributors', []);
for (const [key, person] of contributors.entries()) {
const pivotData = { role: 'contributor', sort_order: key + 1 };
if (person.id !== undefined) {
await dataset
.useTransaction(trx)
.related('persons')
.attach({
[person.id]: {
role: pivotData.role,
sort_order: pivotData.sort_order,
allow_email_contact: false,
},
});
} else {
const dataPerson = new Person();
// use overwritten fill method
dataPerson.fill(person);
await dataset.useTransaction(trx).related('persons').save(dataPerson, false, {
role: pivotData.role,
sort_order: pivotData.sort_order,
allow_email_contact: false,
});
}
}
//save main and additional titles
const titles = request.input('titles', []);
for (const titleData of titles) {
const title = new Title();
title.value = titleData.value;
title.language = titleData.language;
title.type = titleData.type;
await dataset.useTransaction(trx).related('titles').save(title);
}
//save abstract and additional descriptions
const descriptions = request.input('descriptions', []);
for (const descriptionData of descriptions) {
// $descriptionReference = new Description($description);
// $dataset->abstracts()->save($descriptionReference);
const description = new Description();
description.value = descriptionData.value;
description.language = descriptionData.language;
description.type = descriptionData.type;
await dataset.useTransaction(trx).related('descriptions').save(description);
}
//save keywords
const keywords = request.input('subjects', []);
for (const keywordData of keywords) {
// $dataKeyword = new Subject($keyword);
// $dataset->subjects()->save($dataKeyword);
const keyword = await Subject.firstOrNew({ value: keywordData.value, type: keywordData.type }, keywordData);
if (keyword.$isNew == true) {
await dataset.useTransaction(trx).related('subjects').save(keyword);
} else {
await dataset.useTransaction(trx).related('subjects').attach([keyword.id]);
}
}
// Dataset.$getRelation('persons').boot();
// Dataset.$getRelation('persons').pushRelated(dataset, person)
// dataset.$pushRelated('persons', person);
await trx.commit();
console.log('Users and posts created successfully');
} catch (error) {
if (trx !== null) {
await trx.rollback();
}
console.error('Failed to create dataset and related models:', error);
// Handle the error and exit the controller code accordingly
// session.flash('message', 'Failed to create dataset and relaed models');
// return response.redirect().back();
throw error;
}
// save data files:
const coverImage = request.files('files')[0];
if (coverImage) {
// clientName: 'Gehaltsschema.png'
// extname: 'png'
// fieldName: 'file'
// size: 135624
//const datasetFolder = 'files/' . dataset->id;
// await coverImage.moveToDisk('./')
await coverImage.moveToDisk(
'/test_dataset2',
{
name: 'renamed-file-name.jpg',
overwrite: true, // overwrite in case of conflict
},
'local',
);
// let path = coverImage.filePath;
}
session.flash('message', 'Dataset has been created successfully');
// return response.redirect().toRoute('user.index');
return response.redirect().back();
}
public messages: CustomMessages = {
'minLength': '{{ field }} must be at least {{ options.minLength }} characters long',
'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long',
'required': '{{ field }} is required',
'unique': '{{ field }} must be unique, and this value is already taken',
// 'confirmed': '{{ field }} is not correct',
'licences.minLength': 'at least {{ options.minLength }} permission must be defined',
'licences.*.number': 'Define roles as valid numbers',
'rights.equalTo': 'you must agree to continue',
'titles.0.value.minLength': 'Main Title must be at least {{ options.minLength }} characters long',
'titles.0.value.required': 'Main Title is required',
'titles.*.value.required': 'Additional title is required, if defined',
'titles.*.type.required': 'Additional title type is required',
'titles.*.language.required': 'Additional title language is required',
'titles.*.language.translatedLanguage': 'The language of the translated title must be different from the language of the dataset',
'descriptions.0.value.minLength': 'Main Abstract must be at least {{ options.minLength }} characters long',
'descriptions.0.value.required': 'Main Abstract is required',
'descriptions.*.value.required': 'Additional description is required, if defined',
'descriptions.*.type.required': 'Additional description type is required',
'descriptions.*.language.required': 'Additional description language is required',
'descriptions.*.language.translatedLanguage':
'The language of the translated description must be different from the language of the dataset',
'authors.minLength': 'at least {{ options.minLength }} author must be defined',
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,
'subjects.minLength': 'at least {{ options.minLength }} keywords must be defined',
'subjects.uniqueArray': 'The {{ options.array }} array must have unique values based on the {{ options.field }} attribute.',
'subjects.*.value.required': 'keyword value is required',
'subjects.*.value.minLength': 'keyword value must be at least {{ options.minLength }} characters long',
'subjects.*.type.required': 'keyword type is required',
'subjects.*.language.required': 'language of keyword is required',
'files.*.size': 'file size is to big',
'files.extnames': 'file extension is not supported',
};
// public async release({ params, view }) {
public async release({ request, inertia }: HttpContextContract) {
const id = request.param('id');
const dataset = await Dataset.query()
.preload('user', (builder) => {
builder.select('id', 'login');
})
.where('id', id)
.firstOrFail();
// const editors = await User.query()
// .whereHas('roles', (builder) => {
// builder.where('name', 'editor');
// })
// .pluck('login', 'id');
return inertia.render('Submitter/Dataset/Release', {
dataset,
});
}
public async releaseUpdate({ request, response }: HttpContextContract) {
const id = request.param('id');
const dataset = await Dataset.query().preload('files').where('id', id).firstOrFail();
if (dataset.files.length === 0) {
return response.flash('message', 'At least minimum one file is required.').redirect('back');
}
const preferation = request.input('preferation', '');
const preferredReviewer = request.input('preferred_reviewer');
const preferredReviewerEmail = request.input('preferred_reviewer_email');
if (preferation === 'yes_preferation') {
const newSchema = schema.create({
preferred_reviewer: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
preferred_reviewer_email: schema.string([rules.email()]),
});
try {
await request.validate({ schema: newSchema });
} catch (error) {
// return response.badRequest(error.messages);
throw error;
}
}
const input = {
preferred_reviewer: preferredReviewer || null,
preferred_reviewer_email: preferredReviewerEmail || null,
server_state: 'released',
editor_id: null,
reviewer_id: null,
reject_editor_note: null,
reject_reviewer_note: null,
};
// Clear editor_id if it exists
if (dataset.editor_id !== null) {
input.editor_id = null;
}
// Clear reject_editor_note if it exists
if (dataset.reject_editor_note !== null) {
input.reject_editor_note = null;
}
// Clear reviewer_id if it exists
if (dataset.reviewer_id !== null) {
input.reviewer_id = null;
}
// Clear reject_reviewer_note if it exists
if (dataset.reject_reviewer_note !== null) {
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');
// throw new GeneralException(trans('exceptions.publish.release.update_error'));
}
}