Arno Kaimbacher
f67b736a88
Some checks failed
CI Pipeline / japa-tests (push) Failing after 1m0s
- Added preloads 'allowed_extensions_mimetypes' and 'dependent_array_min_length' in adonisrc.ts - Updated @symfony/webpack-encore from ^4.6.1 to ^5.0.1 - AdminuserController: Implemented pagination for 10 records in index method - Enabled reviewers to reject datasets to editors with email notifications (DatasetController.ts) - Submitter DatasetController: Files now loaded in ascending order (sort_order) in edit mode - file.ts: Removed serialization of fileData due to browser issues - Modified FileUpload.vue to mark already uploaded files as deleted - Improved keyword search in SearchCategoryAutocomplete.vue - Started development on Category.vue for submitters to categorize DDC - Added new route /dataset/categorize in routes.ts - Introduced 2 new rules in start/rules: allowed_extensions_mimetypes.ts and dependent_array_min_length.ts - Performed npm updates
311 lines
13 KiB
TypeScript
311 lines
13 KiB
TypeScript
import type { HttpContext } from '@adonisjs/core/http';
|
|
import User from '#models/user';
|
|
import Dataset from '#models/dataset';
|
|
import Field from '#app/Library/Field';
|
|
import BaseModel from '#models/base_model';
|
|
import { DateTime } from 'luxon';
|
|
import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
|
|
import vine from '@vinejs/vine';
|
|
import mail from '@adonisjs/mail/services/main';
|
|
import logger from '@adonisjs/core/services/logger';
|
|
import { validate } from 'deep-email-validator';
|
|
|
|
interface Dictionary {
|
|
[index: string]: string;
|
|
}
|
|
|
|
export default class DatasetsController {
|
|
public async index({ auth, request, inertia }: HttpContext) {
|
|
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 sortOrder: SortOrder = 'asc';
|
|
|
|
if (attribute.substr(0, 1) === '-') {
|
|
sortOrder = 'desc';
|
|
// attribute = substr(attribute, 1);
|
|
attribute = attribute.substr(1);
|
|
}
|
|
datasets.orderBy(attribute, sortOrder);
|
|
} else {
|
|
// users.orderBy('created_at', 'desc');
|
|
datasets.orderBy('id', 'asc');
|
|
}
|
|
|
|
// const users = await User.query().orderBy('login').paginate(page, limit);
|
|
const myDatasets = await datasets
|
|
.where('server_state', 'approved')
|
|
.where('reviewer_id', user.id)
|
|
|
|
.preload('titles')
|
|
.preload('user', (query) => query.select('id', 'login'))
|
|
.preload('editor', (query) => query.select('id', 'login'))
|
|
.paginate(page, 10);
|
|
|
|
return inertia.render('Reviewer/Dataset/Index', {
|
|
datasets: myDatasets.serialize(),
|
|
filters: request.all(),
|
|
can: {
|
|
review: await auth.user?.can(['dataset-review']),
|
|
reject: await auth.user?.can(['dataset-review-reject']),
|
|
},
|
|
});
|
|
}
|
|
|
|
public async review({ request, inertia, response }: HttpContext) {
|
|
const id = request.param('id');
|
|
const dataset = await Dataset.query()
|
|
.where('id', id)
|
|
// .preload('titles')
|
|
// .preload('descriptions')
|
|
.preload('user', (builder) => {
|
|
builder.select('id', 'login');
|
|
})
|
|
.firstOrFail();
|
|
|
|
const validStates = ['approved'];
|
|
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 reviewed. Datset has server state ${dataset.server_state}.`,
|
|
)
|
|
.redirect()
|
|
.toRoute('reviewer.dataset.list');
|
|
}
|
|
|
|
const fieldnames: Array<string> = await dataset.describe();
|
|
const fields: Dictionary = {};
|
|
for (const fieldName of fieldnames) {
|
|
const field: Field = dataset.getField(fieldName) as Field;
|
|
const modelClass = field.getValueModelClass();
|
|
let fieldValues = field.getValue();
|
|
let value = '';
|
|
|
|
if (fieldValues === null || fieldValues == undefined) {
|
|
continue;
|
|
}
|
|
|
|
if (modelClass === null) {
|
|
if (typeof fieldValues === 'number') {
|
|
// If the field values are a number, use them as is
|
|
value = fieldValues.toString();
|
|
} else {
|
|
// If the field values are not a number, use the replace() function to remove non-printable characters
|
|
value = fieldValues.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '\xEF\xBF\xBD ');
|
|
}
|
|
} else {
|
|
if (!Array.isArray(fieldValues)) {
|
|
fieldValues = [fieldValues];
|
|
}
|
|
|
|
for (const fieldValue of fieldValues) {
|
|
if (fieldValue === null) {
|
|
continue;
|
|
}
|
|
if (modelClass.prototype instanceof BaseModel) {
|
|
// this.mapModelAttributes(fieldValue, childNode);
|
|
value = '<ul>';
|
|
Object.keys(fieldValue).forEach((prop) => {
|
|
let modelValue = fieldValue[prop];
|
|
// console.log(`${prop}: ${value}`);
|
|
if (modelValue != null) {
|
|
if (modelValue instanceof DateTime) {
|
|
modelValue = modelValue.toFormat('yyyy-MM-dd HH:mm:ss').trim();
|
|
} else {
|
|
modelValue = modelValue.toString().trim();
|
|
}
|
|
|
|
// Replace invalid XML-1.0-Characters by UTF-8 replacement character.
|
|
modelValue = modelValue.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '\xEF\xBF\xBD ');
|
|
|
|
value = value + '<li>' + prop + ' : ' + modelValue + '</li>';
|
|
}
|
|
});
|
|
value = value + '</ul>';
|
|
} else if (modelClass instanceof DateTime) {
|
|
// console.log('Value is a luxon date');
|
|
// this.mapDateAttributes(fieldValue, childNode);
|
|
value = value + ' Year ' + modelClass.year.toString();
|
|
value = value + ' Month ' + modelClass.month.toString();
|
|
value = value + ' Day ' + modelClass.day.toString();
|
|
value = value + ' Hour ' + modelClass.hour.toString();
|
|
value = value + ' Minute ' + modelClass.minute.toString();
|
|
value = value + ' Second ' + modelClass.second.toString();
|
|
value = value + ' UnixTimestamp ' + modelClass.toUnixInteger().toString();
|
|
let zoneName = modelClass.zoneName ? modelClass.zoneName : '';
|
|
value = value + ' Timezone ' + zoneName;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (value != '') {
|
|
fields[fieldName] = value;
|
|
}
|
|
}
|
|
|
|
return inertia.render('Reviewer/Dataset/Review', {
|
|
dataset,
|
|
fields: fields,
|
|
});
|
|
}
|
|
|
|
public async reviewUpdate({ request, response }: HttpContext) {
|
|
const id = request.param('id');
|
|
// const { id } = params;
|
|
const dataset = await Dataset.findOrFail(id);
|
|
|
|
const validStates = ['approved'];
|
|
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 response
|
|
.flash(
|
|
'warning',
|
|
`Invalid server state. Dataset with id ${id} cannot be reviewed. Datset has server state ${dataset.server_state}.`,
|
|
)
|
|
.redirect()
|
|
.toRoute('reviewer.dataset.list');
|
|
}
|
|
|
|
dataset.server_state = 'reviewed';
|
|
|
|
try {
|
|
// await dataset.related('editor').associate(user); // speichert schon ab
|
|
await dataset.save();
|
|
return response.toRoute('reviewer.dataset.list').flash('message', `You have successfully reviewed dataset ${dataset.id}!`);
|
|
} catch (error) {
|
|
// Handle any errors
|
|
console.error(error);
|
|
return response.status(500).json({ error: 'An error occurred while reviewing the data.' });
|
|
}
|
|
}
|
|
|
|
public async reject({ request, inertia, response }: HttpContext) {
|
|
const id = request.param('id');
|
|
const dataset = await Dataset.query()
|
|
.where('id', id)
|
|
// .preload('titles')
|
|
// .preload('descriptions')
|
|
.preload('editor', (builder) => {
|
|
builder.select('id', 'login', 'email');
|
|
})
|
|
.firstOrFail();
|
|
|
|
const validStates = ['approved'];
|
|
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 rejected. Datset has server state ${dataset.server_state}.`,
|
|
)
|
|
.redirect()
|
|
.toRoute('reviewer.dataset.list');
|
|
}
|
|
|
|
return inertia.render('Reviewer/Dataset/Reject', {
|
|
dataset,
|
|
});
|
|
}
|
|
|
|
public async rejectUpdate({ request, response, auth }: HttpContext) {
|
|
const authUser = auth.user!;
|
|
|
|
const id = request.param('id');
|
|
const dataset = await Dataset.query()
|
|
.where('id', id)
|
|
.preload('editor', (builder) => {
|
|
builder.select('id', 'login', 'email');
|
|
})
|
|
.firstOrFail();
|
|
|
|
// const newSchema = schema.create({
|
|
// server_state: schema.string({ trim: true }),
|
|
// reject_reviewer_note: schema.string({ trim: true }, [rules.minLength(10), rules.maxLength(500)]),
|
|
// });
|
|
const newSchema = vine.object({
|
|
server_state: vine.string().trim(),
|
|
reject_reviewer_note: vine.string().trim().minLength(10).maxLength(500),
|
|
send_mail: vine.boolean().optional(),
|
|
});
|
|
|
|
try {
|
|
// await request.validate({ schema: newSchema });
|
|
const validator = vine.compile(newSchema);
|
|
await request.validateUsing(validator);
|
|
} catch (error) {
|
|
// return response.badRequest(error.messages);
|
|
throw error;
|
|
}
|
|
|
|
const validStates = ['approved'];
|
|
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 response
|
|
.flash(
|
|
`Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
|
|
'warning',
|
|
)
|
|
.redirect()
|
|
.toRoute('reviewer.dataset.list');
|
|
}
|
|
|
|
// dataset.server_state = 'reviewed';
|
|
dataset.server_state = 'rejected_reviewer';
|
|
const rejectReviewerNote = request.input('reject_reviewer_note', '');
|
|
dataset.reject_reviewer_note = rejectReviewerNote;
|
|
|
|
// add logic for sending reject message
|
|
const sendMail = request.input('send_email', false);
|
|
// const validRecipientEmail = await this.checkEmailDomain('arno.kaimbacher@outlook.at');
|
|
const validationResult = await validate({
|
|
email: dataset.editor.email,
|
|
validateSMTP: false,
|
|
});
|
|
const validRecipientEmail: boolean = validationResult.valid;
|
|
let emailStatusMessage = '';
|
|
|
|
if (sendMail == true) {
|
|
if (dataset.editor.email && validRecipientEmail) {
|
|
try {
|
|
await mail.send((message) => {
|
|
message.to(dataset.editor.email).subject('Dataset Rejection Notification').html(`
|
|
<p>Dear editor ${dataset.editor.login},</p>
|
|
<p>Your approved dataset with ID ${dataset.id} has been rejected.</p>
|
|
<p>Reason for rejection: ${rejectReviewerNote}</p>
|
|
<p>Best regards,<br>Your Tethys reviewer: ${authUser.login}</p>
|
|
`);
|
|
});
|
|
emailStatusMessage = ` A rejection email was successfully sent to ${dataset.editor.email}.`;
|
|
} catch (error) {
|
|
logger.error(error);
|
|
return response
|
|
.flash('Dataset has not been rejected due to an email error: ' + error.message, 'error')
|
|
.toRoute('reviewer.dataset.list');
|
|
}
|
|
} else {
|
|
emailStatusMessage = ` However, the email could not be sent because the editor's email address (${dataset.editor.email}) is not valid.`;
|
|
}
|
|
}
|
|
|
|
await dataset.save();
|
|
|
|
return response
|
|
.toRoute('reviewer.dataset.list')
|
|
.flash(`You have rejected dataset ${dataset.id}! to editor ${dataset.editor.login}`, 'message');
|
|
}
|
|
}
|