- replaced validation library @adonisjs/validator with @vinejs/vine (performance)
Some checks failed
CI Pipeline / japa-tests (push) Failing after 56s

- npm updates
This commit is contained in:
Kaimbacher 2024-05-16 13:47:06 +02:00
parent 08c2edca3b
commit ec17d79cf2
32 changed files with 1677 additions and 1670 deletions

View File

@ -25,15 +25,9 @@ export default defineConfig({
preloads: [ preloads: [
() => import('./start/routes.js'), () => import('./start/routes.js'),
() => import('./start/kernel.js'), () => import('./start/kernel.js'),
// { () => import('#start/validator'),
// file: () => import('./start/inertia.js'), () => import('#start/rules/unique'),
// environment: ["web"], () => import('#start/rules/translated_language')
// },
// () => import('#start/events'),
// {
// file: () => import('./start/validator.js'),
// environment: ["web"],
// }
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -68,8 +62,9 @@ export default defineConfig({
() => import('#providers/stardust_provider'), () => import('#providers/stardust_provider'),
() => import('#providers/query_builder_provider'), () => import('#providers/query_builder_provider'),
() => import('#providers/token_worker_provider'), () => import('#providers/token_worker_provider'),
() => import('#providers/validator_provider'), // () => import('#providers/validator_provider'),
() => import('#providers/drive/provider/drive_provider') () => import('#providers/drive/provider/drive_provider'),
() => import('@adonisjs/core/providers/vinejs_provider')
], ],
metaFiles: [ metaFiles: [
{ {
@ -90,21 +85,21 @@ export default defineConfig({
| and add additional suites. | and add additional suites.
| |
*/ */
tests: { tests: {
suites: [ suites: [
{ {
files: ['tests/unit/**/*.spec(.ts|.js)'], files: ['tests/unit/**/*.spec(.ts|.js)'],
name: 'unit', name: 'unit',
timeout: 2000, timeout: 2000,
}, },
{ {
files: ['tests/functional/**/*.spec(.ts|.js)'], files: ['tests/functional/**/*.spec(.ts|.js)'],
name: 'functional', name: 'functional',
timeout: 30000, timeout: 30000,
}, },
], ],
forceExit: false, forceExit: false,
}, },

View File

@ -1,15 +1,13 @@
import type { HttpContext } from '@adonisjs/core/http'; import type { HttpContext } from '@adonisjs/core/http';
import User from '#models/user'; import User from '#models/user';
import Role from '#models/role'; import Role from '#models/role';
import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model"; import { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
import CreateUserValidator from '#validators/create_user_validator'; import { createUserValidator, updateUserValidator } from '#validators/user';
import UpdateUserValidator from '#validators/update_user_validator';
// import { schema, rules } from '@ioc:Adonis/Core/Validator'; // import { schema, rules } from '@ioc:Adonis/Core/Validator';
// import Hash from '@ioc:Adonis/Core/Hash'; // import Hash from '@ioc:Adonis/Core/Hash';
// import { schema, rules } from '@ioc:Adonis/Core/Validator'; // import { schema, rules } from '@ioc:Adonis/Core/Validator';
export default class AdminuserController { export default class AdminuserController {
public async index({ auth, request, inertia }: HttpContext) { public async index({ auth, request, inertia }: HttpContext) {
const page = request.input('page', 1); const page = request.input('page', 1);
// const limit = 10 // const limit = 10
@ -48,7 +46,7 @@ export default class AdminuserController {
// .preload('focusInterests') // .preload('focusInterests')
// .preload('role') // .preload('role')
.paginate(page, 5); .paginate(page, 5);
// var test = request.all(); // var test = request.all();
return inertia.render('Admin/User/Index', { return inertia.render('Admin/User/Index', {
@ -79,7 +77,8 @@ export default class AdminuserController {
// node ace make:validator CreateUser // node ace make:validator CreateUser
try { try {
// Step 2 - Validate request body against the schema // Step 2 - Validate request body against the schema
await request.validate(CreateUserValidator); // await request.validate(CreateUserValidator);
await request.validateUsing(createUserValidator);
// console.log({ payload }); // console.log({ payload });
} catch (error) { } catch (error) {
// Step 3 - Handle errors // Step 3 - Handle errors
@ -94,7 +93,7 @@ export default class AdminuserController {
} }
session.flash('message', 'User has been created successfully'); session.flash('message', 'User has been created successfully');
return response.redirect().toRoute('user.index'); return response.redirect().toRoute('settings.user.index');
} }
public async show({ request, inertia }: HttpContext) { public async show({ request, inertia }: HttpContext) {
@ -133,7 +132,11 @@ export default class AdminuserController {
const user = await User.query().where('id', id).firstOrFail(); const user = await User.query().where('id', id).firstOrFail();
// validate update form // validate update form
await request.validate(UpdateUserValidator); await request.validateUsing(updateUserValidator, {
meta: {
userId: user.id,
},
});
// password is optional // password is optional
let input; let input;
@ -160,7 +163,7 @@ export default class AdminuserController {
await user.delete(); await user.delete();
session.flash('message', `User ${user.login} has been deleted.`); session.flash('message', `User ${user.login} has been deleted.`);
return response.redirect().toRoute('user.index'); return response.redirect().toRoute('settings.user.index');
} }
// private async syncRoles(userId: number, roleIds: Array<number>) { // private async syncRoles(userId: number, roleIds: Array<number>) {

View File

@ -1,9 +1,8 @@
import type { HttpContext } from '@adonisjs/core/http'; import type { HttpContext } from '@adonisjs/core/http';
import Role from '#models/role'; import Role from '#models/role';
import Permission from '#models/permission'; import Permission from '#models/permission';
import CreateRoleValidator from '#validators/create_role_validator'; import { createRoleValidator, updateRoleValidator } from '#validators/role';
import UpdateRoleValidator from '#validators/update_role_validator'; import type { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model';
import type { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
// import { schema, rules } from '@ioc:Adonis/Core/Validator'; // import { schema, rules } from '@ioc:Adonis/Core/Validator';
@ -59,7 +58,8 @@ export default class RoleController {
// node ace make:validator CreateUser // node ace make:validator CreateUser
try { try {
// Step 2 - Validate request body against the schema // Step 2 - Validate request body against the schema
await request.validate(CreateRoleValidator); // await request.validate(CreateRoleValidator);
await request.validateUsing(createRoleValidator);
// await request.validate({ schema: roleSchema }); // await request.validate({ schema: roleSchema });
// console.log({ payload }); // console.log({ payload });
} catch (error) { } catch (error) {
@ -76,7 +76,7 @@ export default class RoleController {
} }
session.flash('message', `Role ${role.name} has been created successfully`); session.flash('message', `Role ${role.name} has been created successfully`);
return response.redirect().toRoute('role.index'); return response.redirect().toRoute('settings.role.index');
} }
public async show({ request, inertia }: HttpContext) { public async show({ request, inertia }: HttpContext) {
@ -115,7 +115,12 @@ export default class RoleController {
const role = await Role.query().where('id', id).firstOrFail(); const role = await Role.query().where('id', id).firstOrFail();
// validate update form // validate update form
await request.validate(UpdateRoleValidator); // await request.validate(UpdateRoleValidator);
await request.validateUsing(updateRoleValidator, {
meta: {
roleId: role.id,
},
});
// password is optional // password is optional
@ -138,6 +143,6 @@ export default class RoleController {
await role.delete(); await role.delete();
session.flash('message', `Role ${role.name} has been deleted.`); session.flash('message', `Role ${role.name} has been deleted.`);
return response.redirect().toRoute('role.index'); return response.redirect().toRoute('settings.role.index');
} }
} }

View File

@ -2,7 +2,7 @@ import type { HttpContext } from '@adonisjs/core/http';
import User from '#models/user'; import User from '#models/user';
// import Hash from '@ioc:Adonis/Core/Hash'; // import Hash from '@ioc:Adonis/Core/Hash';
// import InvalidCredentialException from 'App/Exceptions/InvalidCredentialException'; // import InvalidCredentialException from 'App/Exceptions/InvalidCredentialException';
import AuthValidator from '#validators/auth_validator'; import { authValidator } from '#validators/auth';
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider'; import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
// import { Authenticator } from '@adonisjs/auth'; // import { Authenticator } from '@adonisjs/auth';
@ -18,7 +18,8 @@ export default class AuthController {
// console.log({ // console.log({
// registerBody: request.body(), // registerBody: request.body(),
// }); // });
await request.validate(AuthValidator); // await request.validate(AuthValidator);
await request.validateUsing(authValidator);
// const plainPassword = await request.input('password'); // const plainPassword = await request.input('password');
// const email = await request.input('email'); // const email = await request.input('email');

View File

@ -3,7 +3,8 @@ import User from '#models/user';
// import { RenderResponse } from '@ioc:EidelLev/Inertia'; // import { RenderResponse } from '@ioc:EidelLev/Inertia';
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider'; import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
import hash from '@adonisjs/core/services/hash'; import hash from '@adonisjs/core/services/hash';
import { schema, rules } from '@adonisjs/validator'; // import { schema, rules } from '@adonisjs/validator';
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
// Here we are generating secret and recovery codes for the user thats enabling 2FA and storing them to our database. // Here we are generating secret and recovery codes for the user thats enabling 2FA and storing them to our database.
export default class UserController { export default class UserController {
@ -26,13 +27,23 @@ export default class UserController {
} }
public async accountInfoStore({ auth, request, response, session }: HttpContext) { public async accountInfoStore({ auth, request, response, session }: HttpContext) {
const passwordSchema = schema.create({ // const passwordSchema = schema.create({
old_password: schema.string({ trim: true }, [rules.required()]), // old_password: schema.string({ trim: true }, [rules.required()]),
new_password: schema.string({ trim: true }, [rules.minLength(8), rules.maxLength(255), rules.confirmed('confirm_password')]), // new_password: schema.string({ trim: true }, [rules.minLength(8), rules.maxLength(255), rules.confirmed('confirm_password')]),
confirm_password: schema.string({ trim: true }, [rules.required()]), // confirm_password: schema.string({ trim: true }, [rules.required()]),
// });
const passwordSchema = vine.object({
// first step
old_password: vine
.string()
.trim()
.regex(/^[a-zA-Z0-9]+$/),
new_password: vine.string().confirmed({ confirmationField: 'confirm_password' }).trim().minLength(8).maxLength(255),
}); });
try { try {
await request.validate({ schema: passwordSchema }); // await request.validate({ schema: passwordSchema });
const validator = vine.compile(passwordSchema);
await request.validateUsing(validator);
} catch (error) { } catch (error) {
// return response.badRequest(error.messages); // return response.badRequest(error.messages);
throw error; throw error;
@ -58,7 +69,7 @@ export default class UserController {
// return response.status(200).send({ message: 'Password updated successfully.' }); // return response.status(200).send({ message: 'Password updated successfully.' });
session.flash({ message: 'Password updated successfully.' }); session.flash({ message: 'Password updated successfully.' });
return response.redirect().toRoute('settings.user.index'); return response.redirect().toRoute('settings.user');
} catch (error) { } catch (error) {
// return response.status(500).send({ message: 'Internal server error.' }); // return response.status(500).send({ message: 'Internal server error.' });
return response.flash('warning', `Invalid server state. Internal server error.`).redirect().back(); return response.flash('warning', `Invalid server state. Internal server error.`).redirect().back();

View File

@ -8,7 +8,6 @@ import { XMLBuilder } from 'xmlbuilder2/lib/interfaces.js';
import { create } from 'xmlbuilder2'; import { create } from 'xmlbuilder2';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import SaxonJS from 'saxon-js'; import SaxonJS from 'saxon-js';
import { schema } from '@adonisjs/validator';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import Index from '#app/Library/Utils/Index'; import Index from '#app/Library/Utils/Index';
import { getDomain } from '#app/utils/utility-functions'; import { getDomain } from '#app/utils/utility-functions';
@ -17,14 +16,14 @@ import DoiClientException from '#app/exceptions/DoiClientException';
import logger from '@adonisjs/core/services/logger'; import logger from '@adonisjs/core/services/logger';
import { HttpException } from 'node-exceptions'; import { HttpException } from 'node-exceptions';
import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model"; import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
import { CustomMessages } from "@adonisjs/validator/types"; import vine, { SimpleMessagesProvider } from '@vinejs/vine';
// 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
export default class DatasetsController { export default class DatasetsController {
private proc; private proc;
public messages: CustomMessages = { public messages = {
// 'required': '{{ field }} is required', // 'required': '{{ field }} is required',
// 'licenses.minLength': 'at least {{ options.minLength }} permission must be defined', // 'licenses.minLength': 'at least {{ options.minLength }} permission must be defined',
'reviewer_id.required': 'reviewer_id must be defined', 'reviewer_id.required': 'reviewer_id must be defined',
@ -184,11 +183,13 @@ export default class DatasetsController {
} }
public async approveUpdate({ request, response }: HttpContext) { public async approveUpdate({ request, response }: HttpContext) {
const approveDatasetSchema = schema.create({ const approveDatasetSchema = vine.object({
reviewer_id: schema.number(), reviewer_id: vine.number(),
}); });
try { try {
await request.validate({ schema: approveDatasetSchema, messages: this.messages }); // await request.validate({ schema: approveDatasetSchema, messages: this.messages });
const validator = vine.compile(approveDatasetSchema);
await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
} catch (error) { } catch (error) {
// return response.badRequest(error.messages); // return response.badRequest(error.messages);
throw error; throw error;
@ -252,13 +253,14 @@ export default class DatasetsController {
} }
public async publishUpdate({ request, response }: HttpContext) { public async publishUpdate({ request, response }: HttpContext) {
const publishDatasetSchema = schema.create({ const publishDatasetSchema = vine.object({
publisher_name: schema.string({ trim: true }), publisher_name: vine.string().alphaNumeric().trim(),
}); });
try { try {
await request.validate({ schema: publishDatasetSchema, messages: this.messages }); // await request.validate({ schema: publishDatasetSchema, messages: this.messages });
const validator = vine.compile(publishDatasetSchema);
await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
} catch (error) { } catch (error) {
// return response.badRequest(error.messages);
throw error; throw error;
} }
const id = request.param('id'); const id = request.param('id');

View File

@ -4,8 +4,8 @@ import Dataset from '#models/dataset';
import Field from '#app/Library/Field'; import Field from '#app/Library/Field';
import BaseModel from '#models/base_model'; import BaseModel from '#models/base_model';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { schema, rules } from '@adonisjs/validator';
import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model"; import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
import vine from '@vinejs/vine';
interface Dictionary { interface Dictionary {
[index: string]: string; [index: string]: string;
@ -227,13 +227,19 @@ export default class DatasetsController {
}) })
.firstOrFail(); .firstOrFail();
const newSchema = schema.create({ // const newSchema = schema.create({
server_state: schema.string({ trim: true }), // server_state: schema.string({ trim: true }),
reject_reviewer_note: schema.string({ trim: true }, [rules.minLength(10), rules.maxLength(500)]), // 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),
}); });
try { try {
await request.validate({ schema: newSchema }); // await request.validate({ schema: newSchema });
const validator = vine.compile(newSchema);
await request.validateUsing(validator);
} catch (error) { } catch (error) {
// return response.badRequest(error.messages); // return response.badRequest(error.messages);
throw error; throw error;

View File

@ -8,15 +8,14 @@ import Description from '#models/description';
import Language from '#models/language'; import Language from '#models/language';
import Coverage from '#models/coverage'; import Coverage from '#models/coverage';
import Collection from '#models/collection'; import Collection from '#models/collection';
import { schema, rules } from '@adonisjs/validator';
import { CustomMessages } from '@adonisjs/validator/types';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import Person from '#models/person'; import Person from '#models/person';
import db from '@adonisjs/lucid/services/db'; import db from '@adonisjs/lucid/services/db';
import { TransactionClientContract } from '@adonisjs/lucid/types/database'; import { TransactionClientContract } from '@adonisjs/lucid/types/database';
import Subject from '#models/subject'; import Subject from '#models/subject';
import CreateDatasetValidator from '#validators/create_dataset_validator'; // import CreateDatasetValidator from '#validators/create_dataset_validator';
import UpdateDatasetValidator from '#validators/update_dataset_validator'; import { createDatasetValidator, updateDatasetValidator } from '#validators/dataset';
// import UpdateDatasetValidator from '#validators/update_dataset_validator';
import { import {
TitleTypes, TitleTypes,
DescriptionTypes, DescriptionTypes,
@ -32,7 +31,7 @@ import DatasetReference from '#models/dataset_reference';
import { cuid } from '@adonisjs/core/helpers'; import { cuid } from '@adonisjs/core/helpers';
import File from '#models/file'; import File from '#models/file';
import ClamScan from 'clamscan'; import ClamScan from 'clamscan';
import { ValidationException } from '@adonisjs/validator'; // import { ValidationException } from '@adonisjs/validator';
// import Drive from '@ioc:Adonis/Core/Drive'; // import Drive from '@ioc:Adonis/Core/Drive';
import drive from '#services/drive'; import drive from '#services/drive';
import { Exception } from '@adonisjs/core/exceptions'; import { Exception } from '@adonisjs/core/exceptions';
@ -41,6 +40,7 @@ import * as crypto from 'crypto';
interface Dictionary { interface Dictionary {
[index: string]: string; [index: string]: string;
} }
import vine, { SimpleMessagesProvider, errors } from '@vinejs/vine';
export default class DatasetController { export default class DatasetController {
public async index({ auth, request, inertia }: HttpContext) { public async index({ auth, request, inertia }: HttpContext) {
@ -125,8 +125,6 @@ export default class DatasetController {
// mixedtype: 'Mixed Type', // mixedtype: 'Mixed Type',
// vocabulary: 'Vocabulary', // vocabulary: 'Vocabulary',
// }; // };
// const languages = await Database.from('languages').select('*').where('active', true);
return inertia.render('Submitter/Dataset/Create', { return inertia.render('Submitter/Dataset/Create', {
licenses: licenses, licenses: licenses,
doctypes: DatasetTypes, doctypes: DatasetTypes,
@ -146,75 +144,98 @@ export default class DatasetController {
} }
public async firstStep({ request, response }: HttpContext) { public async firstStep({ request, response }: HttpContext) {
const newDatasetSchema = schema.create({ // const newDatasetSchema = schema.create({
language: schema.string({ trim: true }, [ // language: schema.string({ trim: true }, [
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores // 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 // licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset
rights: schema.string([rules.equalTo('true')]), // rights: schema.string([rules.equalTo('true')]),
// });
const newDatasetSchema = vine.object({
// first step
language: vine
.string()
.trim()
.regex(/^[a-zA-Z0-9]+$/),
licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
rights: vine.string().in(['true']),
}); });
// await request.validate({ schema: newDatasetSchema, messages: this.messages }); // await request.validate({ schema: newDatasetSchema, messages: this.messages });
try { try {
// Step 2 - Validate request body against the schema // Step 2 - Validate request body against the schema
// await request.validate({ schema: newDatasetSchema, messages: this.messages });
await request.validate({ schema: newDatasetSchema, messages: this.messages }); const validator = vine.compile(newDatasetSchema);
// console.log({ payload }); await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
} catch (error) { } catch (error) {
// Step 3 - Handle errors // Step 3 - Handle errors
// return response.badRequest(error.messages);
throw error; throw error;
} }
return response.redirect().back(); return response.redirect().back();
} }
public async secondStep({ request, response }: HttpContext) { public async secondStep({ request, response }: HttpContext) {
const newDatasetSchema = schema.create({ const newDatasetSchema = vine.object({
// first step // first step
language: schema.string({ trim: true }, [ language: vine
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores .string()
]), .trim()
licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset .regex(/^[a-zA-Z0-9]+$/),
rights: schema.string([rules.equalTo('true')]), licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
rights: vine.string().in(['true']),
// second step // second step
type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), type: vine.string().trim().minLength(3).maxLength(255),
creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), creating_corporation: vine.string().trim().minLength(3).maxLength(255),
titles: schema.array([rules.minLength(1)]).members( titles: vine
schema.object().members({ .array(
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), vine.object({
type: schema.enum(Object.values(TitleTypes)), value: vine.string().trim().minLength(3).maxLength(255),
language: schema.string({ trim: true }, [ type: vine.enum(Object.values(TitleTypes)),
rules.minLength(2), language: vine
rules.maxLength(255), .string()
rules.translatedLanguage('/language', 'type'), .trim()
]), .minLength(2)
}), .maxLength(255)
), .translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
descriptions: schema.array([rules.minLength(1)]).members( }),
schema.object().members({ )
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), .minLength(1),
type: schema.enum(Object.values(DescriptionTypes)), descriptions: vine
language: schema.string({ trim: true }, [ .array(
rules.minLength(2), vine.object({
rules.maxLength(255), value: vine.string().trim().minLength(3).maxLength(255),
rules.translatedLanguage('/language', 'type'), type: vine.enum(Object.values(DescriptionTypes)),
]), language: vine
}), .string()
), .trim()
authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })), .minLength(2)
contributors: schema.array.optional().members( .maxLength(255)
schema.object().members({ .translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
email: schema.string({ trim: true }), }),
pivot_contributor_type: schema.enum(Object.keys(ContributorTypes)), )
}), .minLength(1),
), authors: vine
// project_id: schema.number(), .array(
vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(),
}),
)
.minLength(1),
contributors: vine
.array(
vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(),
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
}),
)
.optional(),
project_id: vine.number().optional(),
}); });
try { try {
// Step 2 - Validate request body against the schema // Step 2 - Validate request body against the schema
await request.validate({ schema: newDatasetSchema, messages: this.messages }); // await request.validate({ schema: newDatasetSchema, messages: this.messages });
// console.log({ payload }); const validator = vine.compile(newDatasetSchema);
await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
} catch (error) { } catch (error) {
// Step 3 - Handle errors // Step 3 - Handle errors
// return response.badRequest(error.messages); // return response.badRequest(error.messages);
@ -224,84 +245,112 @@ export default class DatasetController {
} }
public async thirdStep({ request, response }: HttpContext) { public async thirdStep({ request, response }: HttpContext) {
const newDatasetSchema = schema.create({ const newDatasetSchema = vine.object({
// first step // first step
language: schema.string({ trim: true }, [ language: vine
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores .string()
]), .trim()
licenses: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one license for the new dataset .regex(/^[a-zA-Z0-9]+$/),
rights: schema.string([rules.equalTo('true')]), licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
rights: vine.string().in(['true']),
// second step // second step
type: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), type: vine.string().trim().minLength(3).maxLength(255),
creating_corporation: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), creating_corporation: vine.string().trim().minLength(3).maxLength(255),
titles: schema.array([rules.minLength(1)]).members( titles: vine
schema.object().members({ .array(
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), vine.object({
type: schema.enum(Object.values(TitleTypes)), value: vine.string().trim().minLength(3).maxLength(255),
language: schema.string({ trim: true }, [ type: vine.enum(Object.values(TitleTypes)),
rules.minLength(2), language: vine
rules.maxLength(255), .string()
rules.translatedLanguage('/language', 'type'), .trim()
]), .minLength(2)
}), .maxLength(255)
), .translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
descriptions: schema.array([rules.minLength(1)]).members( }),
schema.object().members({ )
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), .minLength(1),
type: schema.enum(Object.values(DescriptionTypes)), descriptions: vine
language: schema.string({ trim: true }, [ .array(
rules.minLength(2), vine.object({
rules.maxLength(255), value: vine.string().trim().minLength(3).maxLength(255),
rules.translatedLanguage('/language', 'type'), type: vine.enum(Object.values(DescriptionTypes)),
]), language: vine
}), .string()
), .trim()
authors: schema.array([rules.minLength(1)]).members(schema.object().members({ email: schema.string({ trim: true }) })), .minLength(2)
contributors: schema.array.optional().members( .maxLength(255)
schema.object().members({ .translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
email: schema.string({ trim: true }), }),
pivot_contributor_type: schema.enum(Object.keys(ContributorTypes)), )
}), .minLength(1),
), authors: vine
.array(
vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(),
}),
)
.minLength(1),
contributors: vine
.array(
vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(),
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
}),
)
.optional(),
// third step // third step
project_id: schema.number.optional(), project_id: vine.number().optional(),
embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]), // embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
coverage: schema.object().members({ embargo_date: vine
x_min: schema.number(), .date({
x_max: schema.number(), formats: ['YYYY-MM-DD'],
y_min: schema.number(), })
y_max: schema.number(), .afterOrEqual((_field) => {
elevation_absolut: schema.number.optional(), return dayjs().add(10, 'day').format('YYYY-MM-DD');
elevation_min: schema.number.optional([rules.requiredIfExists('elevation_max')]), })
elevation_max: schema.number.optional([rules.requiredIfExists('elevation_min')]), .optional(),
depth_absolut: schema.number.optional(), coverage: vine.object({
depth_min: schema.number.optional([rules.requiredIfExists('depth_max')]), x_min: vine.number(),
depth_max: schema.number.optional([rules.requiredIfExists('depth_min')]), x_max: vine.number(),
y_min: vine.number(),
y_max: vine.number(),
elevation_absolut: vine.number().optional(),
elevation_min: vine.number().optional().requiredIfExists('elevation_max'),
elevation_max: vine.number().optional().requiredIfExists('elevation_min'),
// type: vine.enum(Object.values(DescriptionTypes)),
depth_absolut: vine.number().optional(),
depth_min: vine.number().optional().requiredIfExists('depth_max'),
depth_max: vine.number().optional().requiredIfExists('depth_min'),
}), }),
references: schema.array.optional([rules.uniqueArray('value')]).members( references: vine
schema.object().members({ .array(
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), vine.object({
type: schema.enum(Object.values(ReferenceIdentifierTypes)), value: vine.string().trim().minLength(3).maxLength(255),
relation: schema.enum(Object.values(RelationTypes)), type: vine.enum(Object.values(ReferenceIdentifierTypes)),
label: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]), relation: vine.enum(Object.values(RelationTypes)),
}), label: vine.string().trim().minLength(2).maxLength(255),
), }),
subjects: schema.array([rules.minLength(3), rules.uniqueArray('value')]).members( )
schema.object().members({ .optional(),
value: schema.string({ trim: true }, [ subjects: vine
rules.minLength(3), .array(
rules.maxLength(255), vine.object({
// rules.unique({ table: 'dataset_subjects', column: 'value' }), value: vine.string().trim().minLength(3).maxLength(255),
]), // pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
// type: schema.enum(Object.values(TitleTypes)), language: vine.string().trim().minLength(2).maxLength(255),
language: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]), }),
}), )
), .minLength(3)
.distinct('value')
.optional(),
}); });
try { try {
// Step 2 - Validate request body against the schema // Step 3 - Validate request body against the schema
await request.validate({ schema: newDatasetSchema, messages: this.messages }); // await request.validate({ schema: newDatasetSchema, messages: this.messages });
const validator = vine.compile(newDatasetSchema);
await request.validateUsing(validator, { messagesProvider: new SimpleMessagesProvider(this.messages) });
// console.log({ payload }); // console.log({ payload });
} catch (error) { } catch (error) {
// Step 3 - Handle errors // Step 3 - Handle errors
@ -316,7 +365,8 @@ export default class DatasetController {
try { try {
// Step 2 - Validate request body against the schema // Step 2 - Validate request body against the schema
// await request.validate({ schema: newDatasetSchema, messages: this.messages }); // await request.validate({ schema: newDatasetSchema, messages: this.messages });
await request.validate(CreateDatasetValidator); // await request.validate(CreateDatasetValidator);
await request.validateUsing(createDatasetValidator);
// console.log({ payload }); // console.log({ payload });
} catch (error) { } catch (error) {
// Step 3 - Handle errors // Step 3 - Handle errors
@ -505,13 +555,15 @@ export default class DatasetController {
const { file, isInfected, viruses } = await clamscan.isInfected(filePath); const { file, isInfected, viruses } = await clamscan.isInfected(filePath);
if (isInfected) { if (isInfected) {
console.log(`${file} is infected with ${viruses}!`); console.log(`${file} is infected with ${viruses}!`);
reject(new ValidationException(true, { 'upload error': `File ${file} is infected!` })); // reject(new ValidationException(true, { 'upload error': `File ${file} is infected!` }));
reject(new errors.E_VALIDATION_ERROR({ 'upload error': `File ${file} is infected!` }));
} else { } else {
resolve(); resolve();
} }
} catch (error) { } catch (error) {
// If there's an error scanning the file, throw a validation exception // If there's an error scanning the file, throw a validation exception
reject(new ValidationException(true, { 'upload error': `${error.message}` })); // reject(new ValidationException(true, { 'upload error': `${error.message}` }));
reject(new errors.E_VALIDATION_ERROR({ 'upload error': `${error.message}!` }));
} }
}); });
} }
@ -553,24 +605,24 @@ export default class DatasetController {
return pivotAttributes; return pivotAttributes;
} }
public messages: CustomMessages = { public messages = {
'minLength': '{{ field }} must be at least {{ options.minLength }} characters long', 'minLength': '{{ field }} must be at least {{ min }} characters long',
'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long', 'maxLength': '{{ field }} must be less then {{ max }} characters long',
'required': '{{ field }} is required', 'required': '{{ field }} is required',
'unique': '{{ field }} must be unique, and this value is already taken', 'unique': '{{ field }} must be unique, and this value is already taken',
// 'confirmed': '{{ field }} is not correct', // 'confirmed': '{{ field }} is not correct',
'licenses.minLength': 'at least {{ options.minLength }} permission must be defined', 'licenses.minLength': 'at least {{ min }} permission must be defined',
'licenses.*.number': 'Define roles as valid numbers', 'licenses.*.number': 'Define roles as valid numbers',
'rights.equalTo': 'you must agree to continue', 'rights.in': 'you must agree to continue',
'titles.0.value.minLength': 'Main Title must be at least {{ options.minLength }} characters long', 'titles.0.value.minLength': 'Main Title must be at least {{ min }} characters long',
'titles.0.value.required': 'Main Title is required', 'titles.0.value.required': 'Main Title is required',
'titles.*.value.required': 'Additional title is required, if defined', 'titles.*.value.required': 'Additional title is required, if defined',
'titles.*.type.required': 'Additional title type is required', 'titles.*.type.required': 'Additional title type is required',
'titles.*.language.required': 'Additional title language 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', '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.minLength': 'Main Abstract must be at least {{ min }} characters long',
'descriptions.0.value.required': 'Main Abstract is required', 'descriptions.0.value.required': 'Main Abstract is required',
'descriptions.*.value.required': 'Additional description is required, if defined', 'descriptions.*.value.required': 'Additional description is required, if defined',
'descriptions.*.type.required': 'Additional description type is required', 'descriptions.*.type.required': 'Additional description type is required',
@ -578,26 +630,26 @@ export default class DatasetController {
'descriptions.*.language.translatedLanguage': 'descriptions.*.language.translatedLanguage':
'The language of the translated description must be different from the language of the dataset', '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', 'authors.array.minLength': 'at least {{ min }} author must be defined',
'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined', 'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined',
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`, 'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,
'subjects.minLength': 'at least {{ options.minLength }} keywords must be defined', 'subjects.array.minLength': 'at least {{ min }} 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.required': 'keyword value is required',
'subjects.*.value.minLength': 'keyword value must be at least {{ options.minLength }} characters long', 'subjects.*.value.minLength': 'keyword value must be at least {{ min }} characters long',
'subjects.*.type.required': 'keyword type is required', 'subjects.*.type.required': 'keyword type is required',
'subjects.*.language.required': 'language of keyword is required', 'subjects.*.language.required': 'language of keyword is required',
'subjects.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
'references.*.value.required': 'Additional reference value is required, if defined', 'references.*.value.required': 'Additional reference value is required, if defined',
'references.*.type.required': 'Additional reference identifier type is required', 'references.*.type.required': 'Additional reference identifier type is required',
'references.*.relation.required': 'Additional reference relation type is required', 'references.*.relation.required': 'Additional reference relation type is required',
'references.*.label.required': 'Additional reference label is required', 'references.*.label.required': 'Additional reference label is required',
'files.minLength': 'At least {{ options.minLength }} file upload is required.', 'files.array.minLength': 'At least {{ min }} file upload is required.',
'files.*.size': 'file size is to big', 'files.*.size': 'file size is to big',
'files.extnames': 'file extension is not supported', 'files.*.extnames': 'file extension is not supported',
}; };
// public async release({ params, view }) { // public async release({ params, view }) {
@ -654,16 +706,18 @@ export default class DatasetController {
const preferredReviewerEmail = request.input('preferred_reviewer_email'); const preferredReviewerEmail = request.input('preferred_reviewer_email');
if (preferation === 'yes_preferation') { if (preferation === 'yes_preferation') {
const newSchema = schema.create({ const newSchema = vine.object({
preferred_reviewer: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]), preferred_reviewer: vine.string().alphaNumeric().trim().minLength(3).maxLength(255),
preferred_reviewer_email: schema.string([rules.email()]), preferred_reviewer_email: vine.string().maxLength(255).email().normalizeEmail(),
}); });
try { try {
await request.validate({ // await request.validate({
schema: newSchema, // schema: newSchema,
// reporter: validator.reporters.vanilla, // // reporter: validator.reporters.vanilla,
}); // });
const validator = vine.compile(newSchema);
await request.validateUsing(validator);
} catch (error) { } catch (error) {
// return response.badRequest(error.messages); // return response.badRequest(error.messages);
throw error; throw error;
@ -799,8 +853,8 @@ export default class DatasetController {
public async update({ request, response, session }: HttpContext) { public async update({ request, response, session }: HttpContext) {
try { try {
// await request.validate({ schema: newDatasetSchema, messages: this.messages }); // await request.validate(UpdateDatasetValidator);
await request.validate(UpdateDatasetValidator); await request.validateUsing(updateDatasetValidator);
} catch (error) { } catch (error) {
// - Handle errors // - Handle errors
// return response.badRequest(error.messages); // return response.badRequest(error.messages);
@ -1068,7 +1122,7 @@ export default class DatasetController {
} }
} }
} catch (error) { } catch (error) {
if (error instanceof ValidationException) { if (error instanceof errors.E_VALIDATION_ERROR) {
// Validation exception handling // Validation exception handling
throw error; throw error;
} else if (error instanceof Exception) { } else if (error instanceof Exception) {

34
app/models/mime_type.ts Normal file
View File

@ -0,0 +1,34 @@
import { column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
import BaseModel from './base_model.js';
export default class MimeType extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'id';
public static table = 'mime_types';
public static fillable: string[] = ['name', 'file_extension', 'enabled'];
@column({
isPrimary: true,
})
public id: number;
@column({})
public name: string;
@column({})
public file_extension: string;
@column({})
public enabled: boolean;
@column({})
public visible_frontdoor: boolean;
public visible_oai: boolean;
// @hasMany(() => Collection, {
// foreignKey: 'role_id',
// })
// public collections: HasMany<typeof Collection>;
}

20
app/validators/auth.ts Normal file
View File

@ -0,0 +1,20 @@
import vine from '@vinejs/vine';
// public schema = schema.create({
// email: schema.string({ trim: true }, [
// rules.email(),
// // rules.unique({ table: 'accounts', column: 'email' })
// ]),
// password: schema.string({}, [rules.minLength(6)]),
// });
/**
* Validates the role's creation action
* node ace make:validator role
*/
export const authValidator = vine.compile(
vine.object({
email: vine.string().maxLength(255).email().normalizeEmail(),
password: vine.string().trim().minLength(6),
}),
);

View File

@ -1,47 +0,0 @@
import { schema, rules } from '@adonisjs/validator';
import type { HttpContext } from '@adonisjs/core/http';
import { CustomMessages } from "@adonisjs/validator/types";
export default class AuthValidator {
constructor(protected ctx: HttpContext) {}
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
public schema = schema.create({
email: schema.string({ trim: true }, [
rules.email(),
// rules.unique({ table: 'accounts', column: 'email' })
]),
password: schema.string({}, [rules.minLength(6)]),
});
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
public messages: CustomMessages = {};
}

View File

@ -1,179 +0,0 @@
import { schema, rules } from '@adonisjs/validator';
import type { HttpContext } from '@adonisjs/core/http';
import dayjs from 'dayjs';
import { TitleTypes, DescriptionTypes, RelationTypes, ReferenceIdentifierTypes, ContributorTypes } from '#contracts/enums';
import { CustomMessages } from "@adonisjs/validator/types";
export default class CreateDatasetValidator {
constructor(protected ctx: HttpContext) {}
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
public schema = 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 }) })),
contributors: schema.array.optional().members(
schema.object().members({
email: schema.string({ trim: true }),
pivot_contributor_type: schema.enum(Object.keys(ContributorTypes)),
}),
),
// 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')]),
}),
references: schema.array.optional([rules.uniqueArray('value')]).members(
schema.object().members({
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
type: schema.enum(Object.values(ReferenceIdentifierTypes)),
relation: schema.enum(Object.values(RelationTypes)),
label: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]),
}),
),
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)]),
}),
),
// file: schema.file({
// size: '100mb',
// extnames: ['jpg', 'gif', 'png'],
// }),
files: schema.array([rules.minLength(1)]).members(
schema.file({
size: '512mb',
extnames: ['jpg', 'gif', 'png', 'tif', 'pdf', 'zip', 'fgb', 'nc', 'qml', 'ovr', 'gpkg', 'gml', 'gpx', 'kml', 'kmz', 'json'],
}),
),
// upload: schema.object().members({
// label: schema.string({ trim: true }, [rules.maxLength(255)]),
// // label: schema.string({ trim: true }, [
// // // rules.minLength(3),
// // // rules.maxLength(255),
// // ]),
// }),
});
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
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',
'licenses.minLength': 'at least {{ options.minLength }} licenses must be defined',
'licenses.*.number': 'Define licences 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',
'contributors.*.pivot_contributor_type.required': 'contributor type is required, if 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',
'references.*.value.required': 'Additional reference value is required, if defined',
'references.*.type.required': 'Additional reference identifier type is required',
'references.*.relation.required': 'Additional reference relation type is required',
'references.*.label.required': 'Additional reference label is required',
'files.minLength': 'At least {{ options.minLength }} file upload is required.',
'files.*.size': 'file size is to big',
'files.extnames': 'file extension is not supported',
};
}

View File

@ -1,70 +0,0 @@
import { schema, rules } from '@adonisjs/validator';
import type { HttpContext } from '@adonisjs/core/http';
import { CustomMessages } from "@adonisjs/validator/types";
export default class CreateRoleValidator {
constructor(protected ctx: HttpContext) {}
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
public schema = schema.create({
name: schema.string({ trim: true }, [
rules.minLength(3),
rules.maxLength(255),
rules.unique({ table: 'roles', column: 'name' }),
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
]),
display_name: schema.string.optional({ trim: true }, [
rules.minLength(3),
rules.maxLength(255),
rules.unique({ table: 'roles', column: 'name' }),
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
]),
description: schema.string.optional({}, [rules.minLength(3), rules.maxLength(255)]),
permissions: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new role
});
// emails: schema
// .array([rules.minLength(1)])
// .members(
// schema.object().members({ email: schema.string({}, [rules.email()]) })
// ),
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
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',
'permissions.minLength': 'at least {{ options.minLength }} permission must be defined',
'permissions.*.number': 'Define roles as valid numbers',
};
}

View File

@ -1,65 +0,0 @@
import { schema, rules } from '@adonisjs/validator';
import type { HttpContext } from '@adonisjs/core/http';
import { CustomMessages } from "@adonisjs/validator/types";
export default class CreateUserValidator {
constructor(protected ctx: HttpContext) {}
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
public schema = schema.create({
login: schema.string({ trim: true }, [
rules.minLength(3),
rules.maxLength(50),
rules.unique({ table: 'accounts', column: 'login' }),
rules.regex(/^[a-zA-Z0-9-_]+$/), //Must be alphanumeric with hyphens or underscores
]),
email: schema.string({}, [rules.email(), rules.unique({ table: 'accounts', column: 'email' })]),
password: schema.string([rules.confirmed(), rules.minLength(6)]),
roles: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
});
// emails: schema
// .array([rules.minLength(1)])
// .members(
// schema.object().members({ email: schema.string({}, [rules.email()]) })
// ),
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
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',
'roles.minLength': 'at least {{ options.minLength }} role must be defined',
'roles.*.number': 'Define roles as valid numbers',
};
}

296
app/validators/dataset.ts Normal file
View File

@ -0,0 +1,296 @@
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
import { TitleTypes, DescriptionTypes, ContributorTypes, ReferenceIdentifierTypes, RelationTypes } from '#contracts/enums';
import dayjs from 'dayjs';
import MimeType from '#models/mime_type';
const enabledExtensions = await MimeType.query().select('file_extension').where('enabled', true).exec();
const extensions = enabledExtensions.map((extension)=> {
return extension.file_extension.split('|')
}).flat();
/**
* Validates the dataset's creation action
* node ace make:validator dataset
*/
export const createDatasetValidator = vine.compile(
vine.object({
// first step
language: vine
.string()
.trim()
.regex(/^[a-zA-Z0-9]+$/),
licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
rights: vine.string().in(['true']),
// second step
type: vine.string().trim().minLength(3).maxLength(255),
creating_corporation: vine.string().trim().minLength(3).maxLength(255),
titles: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
type: vine.enum(Object.values(TitleTypes)),
language: vine
.string()
.trim()
.minLength(2)
.maxLength(255)
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
.minLength(1),
descriptions: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
type: vine.enum(Object.values(DescriptionTypes)),
language: vine
.string()
.trim()
.minLength(2)
.maxLength(255)
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
.minLength(1),
authors: vine
.array(
vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(),
}),
)
.minLength(1),
contributors: vine
.array(
vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(),
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
}),
)
.optional(),
// third step
project_id: vine.number().optional(),
// embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
embargo_date: vine
.date({
formats: ['YYYY-MM-DD'],
})
.afterOrEqual((_field) => {
return dayjs().add(10, 'day').format('YYYY-MM-DD');
})
.optional(),
coverage: vine.object({
x_min: vine.number(),
x_max: vine.number(),
y_min: vine.number(),
y_max: vine.number(),
elevation_absolut: vine.number().optional(),
elevation_min: vine.number().optional().requiredIfExists('elevation_max'),
elevation_max: vine.number().optional().requiredIfExists('elevation_min'),
// type: vine.enum(Object.values(DescriptionTypes)),
depth_absolut: vine.number().optional(),
depth_min: vine.number().optional().requiredIfExists('depth_max'),
depth_max: vine.number().optional().requiredIfExists('depth_min'),
}),
references: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
relation: vine.enum(Object.values(RelationTypes)),
label: vine.string().trim().minLength(2).maxLength(255),
}),
)
.optional(),
subjects: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
// pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
language: vine.string().trim().minLength(2).maxLength(255),
}),
)
.minLength(3)
.distinct('value'),
// last step
files: vine
.array(
vine.file({
size: '512mb',
extnames: extensions,
}),
)
.minLength(1),
}),
);
/**
* Validates the dataset's update action
*/
export const updateDatasetValidator = vine.compile(
vine.object({
// first step
language: vine
.string()
.trim()
.regex(/^[a-zA-Z0-9]+$/),
licenses: vine.array(vine.number()).minLength(1), // define at least one license for the new dataset
rights: vine.string().in(['true']),
// second step
type: vine.string().trim().minLength(3).maxLength(255),
creating_corporation: vine.string().trim().minLength(3).maxLength(255),
titles: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
type: vine.enum(Object.values(TitleTypes)),
language: vine
.string()
.trim()
.minLength(2)
.maxLength(255)
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
.minLength(1),
descriptions: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
type: vine.enum(Object.values(DescriptionTypes)),
language: vine
.string()
.trim()
.minLength(2)
.maxLength(255)
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
}),
)
.minLength(1),
authors: vine
.array(
vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(),
}),
)
.minLength(1),
contributors: vine
.array(
vine.object({
email: vine.string().trim().maxLength(255).email().normalizeEmail(),
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
}),
)
.optional(),
// third step
project_id: vine.number().optional(),
// embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
embargo_date: vine
.date({
formats: ['YYYY-MM-DD'],
})
.afterOrEqual((_field) => {
return dayjs().add(10, 'day').format('YYYY-MM-DD');
})
.optional(),
coverage: vine.object({
x_min: vine.number(),
x_max: vine.number(),
y_min: vine.number(),
y_max: vine.number(),
elevation_absolut: vine.number().optional(),
elevation_min: vine.number().optional().requiredIfExists('elevation_max'),
elevation_max: vine.number().optional().requiredIfExists('elevation_min'),
// type: vine.enum(Object.values(DescriptionTypes)),
depth_absolut: vine.number().optional(),
depth_min: vine.number().optional().requiredIfExists('depth_max'),
depth_max: vine.number().optional().requiredIfExists('depth_min'),
}),
references: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
type: vine.enum(Object.values(ReferenceIdentifierTypes)),
relation: vine.enum(Object.values(RelationTypes)),
label: vine.string().trim().minLength(2).maxLength(255),
}),
)
.optional(),
subjects: vine
.array(
vine.object({
value: vine.string().trim().minLength(3).maxLength(255),
// pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
language: vine.string().trim().minLength(2).maxLength(255),
}),
)
.minLength(3)
.distinct('value'),
// last step
files: vine
.array(
vine.file({
size: '512mb',
extnames: extensions,
}),
),
// .minLength(1),
}),
);
// files: schema.array([rules.minLength(1)]).members(
// schema.file({
// size: '512mb',
// extnames: ['jpg', 'gif', 'png', 'tif', 'pdf', 'zip', 'fgb', 'nc', 'qml', 'ovr', 'gpkg', 'gml', 'gpx', 'kml', 'kmz', 'json'],
// }),
// ),
let messagesProvider = new SimpleMessagesProvider({
'minLength': '{{ field }} must be at least {{ min }} characters long',
'maxLength': '{{ field }} must be less then {{ max }} characters long',
'required': '{{ field }} is required',
'unique': '{{ field }} must be unique, and this value is already taken',
// 'confirmed': '{{ field }} is not correct',
'licenses.minLength': 'at least {{ min }} permission must be defined',
'licenses.*.number': 'Define roles as valid numbers',
'rights.in': 'you must agree to continue',
'titles.0.value.minLength': 'Main Title must be at least {{ min }} 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 {{ min }} 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.array.minLength': 'at least {{ min }} author must be defined',
'contributors.*.pivot_contributor_type.required': 'contributor type is required, if defined',
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,
'subjects.array.minLength': 'at least {{ min }} keywords must be defined',
'subjects.*.value.required': 'keyword value is required',
'subjects.*.value.minLength': 'keyword value must be at least {{ min }} characters long',
'subjects.*.type.required': 'keyword type is required',
'subjects.*.language.required': 'language of keyword is required',
'subjects.distinct': 'The {{ field }} array must have unique values based on the {{ fields }} attribute.',
'references.*.value.required': 'Additional reference value is required, if defined',
'references.*.type.required': 'Additional reference identifier type is required',
'references.*.relation.required': 'Additional reference relation type is required',
'references.*.label.required': 'Additional reference label is required',
'files.array.minLength': 'At least {{ min }} file upload is required.',
'files.*.size': 'file size is to big',
'files.*.extnames': 'file extension is not supported',
});
createDatasetValidator.messagesProvider = messagesProvider;
updateDatasetValidator.messagesProvider = messagesProvider;
// export default createDatasetValidator;

64
app/validators/role.ts Normal file
View File

@ -0,0 +1,64 @@
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
/**
* Validates the role's creation action
* node ace make:validator role
*/
export const createRoleValidator = vine.compile(
vine.object({
name: vine
.string()
.isUnique({ table: 'roles', column: 'name' })
.trim()
.minLength(3)
.maxLength(255)
.regex(/^[a-zA-Z0-9]+$/), //Must be alphanumeric with hyphens or underscores
display_name: vine
.string()
.isUnique({ table: 'roles', column: 'display_name' })
.trim()
.minLength(3)
.maxLength(255)
.regex(/^[a-zA-Z0-9]+$/),
description: vine.string().trim().escape().minLength(3).maxLength(255).optional(),
permissions: vine.array(vine.number()).minLength(1), // define at least one permission for the new role
}),
);
export const updateRoleValidator = vine.withMetaData<{ roleId: number }>().compile(
vine.object({
name: vine
.string()
// .unique(async (db, value, field) => {
// const result = await db.from('roles').select('id').whereNot('id', field.meta.roleId).where('name', value).first();
// return result.length ? false : true;
// })
.isUnique({
table: 'roles',
column: 'name',
whereNot: (field) => field.meta.roleId,
})
.trim()
.minLength(3)
.maxLength(255),
description: vine.string().trim().escape().minLength(3).maxLength(255).optional(),
permissions: vine.array(vine.number()).minLength(1), // define at least one permission for the new role
}),
);
let messagesProvider = new SimpleMessagesProvider({
// Applicable for all fields
'required': 'The {{ field }} field is required',
'unique': '{{ field }} must be unique, and this value is already taken',
'string': 'The value of {{ field }} field must be a string',
'email': 'The value is not a valid email address',
// 'contacts.0.email.required': 'The primary email of the contact is required',
// 'contacts.*.email.required': 'Contact email is required',
'permissions.minLength': 'at least {{ options.minLength }} permission must be defined',
'permissions.*.number': 'Define permissions as valid numbers',
});
createRoleValidator.messagesProvider = messagesProvider;
updateRoleValidator.messagesProvider = messagesProvider;

View File

@ -1,180 +0,0 @@
import { schema, rules } from '@adonisjs/validator';
import type { HttpContext } from '@adonisjs/core/http';
import dayjs from 'dayjs';
import { TitleTypes, DescriptionTypes, RelationTypes, ReferenceIdentifierTypes, ContributorTypes } from '#contracts/enums';
import { CustomMessages } from "@adonisjs/validator/types";
export default class UpdateDatasetValidator {
constructor(protected ctx: HttpContext) {}
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
public schema = 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 }) })),
contributors: schema.array.optional().members(
schema.object().members({
email: schema.string({ trim: true }),
pivot_contributor_type: schema.enum(Object.keys(ContributorTypes)),
}),
),
// 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')]),
}),
references: schema.array.optional([rules.uniqueArray('value')]).members(
schema.object().members({
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
type: schema.enum(Object.values(ReferenceIdentifierTypes)),
relation: schema.enum(Object.values(RelationTypes)),
label: schema.string({ trim: true }, [rules.minLength(2), rules.maxLength(255)]),
}),
),
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)]),
}),
),
// file: schema.file({
// size: '100mb',
// extnames: ['jpg', 'gif', 'png'],
// }),
files: schema.array.optional().members(
schema.file({
size: '100mb',
extnames: ['jpg', 'gif', 'png', 'tif', 'pdf'],
}),
)
// upload: schema.object().members({
// label: schema.string({ trim: true }, [rules.maxLength(255)]),
// // label: schema.string({ trim: true }, [
// // // rules.minLength(3),
// // // rules.maxLength(255),
// // ]),
// }),
});
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
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',
'licenses.minLength': 'at least {{ options.minLength }} licenses must be defined',
'licenses.*.number': 'Define licences 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',
'contributors.*.pivot_contributor_type.required': 'contributor type is required, if 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',
'references.*.value.required': 'Additional reference value is required, if defined',
'references.*.type.required': 'Additional reference identifier type is required',
'references.*.relation.required': 'Additional reference relation type is required',
'references.*.label.required': 'Additional reference label is required',
'files.minLength': 'At least {{ options.minLength }} file upload is required.',
'files.*.size': 'file size is to big',
'files.extnames': 'file extension is not supported',
};
}

View File

@ -1,99 +0,0 @@
import { schema, rules } from '@adonisjs/validator';
import type { HttpContext } from '@adonisjs/core/http';
import { CustomMessages } from "@adonisjs/validator/types";
// import { Request } from '@adonisjs/core/build/standalone';
export default class UpdateRoleValidator {
protected ctx: HttpContext;
public schema;
constructor(ctx: HttpContext) {
this.ctx = ctx;
this.schema = this.createSchema();
}
// public get schema() {
// return this._schema;
// }
private createSchema() {
return schema.create({
name: schema.string({ trim: true }, [
rules.minLength(3),
rules.maxLength(50),
rules.unique({
table: 'roles',
column: 'name',
whereNot: { id: this.ctx?.params.id },
}),
rules.regex(/^[a-zA-Z0-9-_]+$/),
//Must be alphanumeric with hyphens or underscores
]),
description: schema.string.optional({}, [rules.minLength(3), rules.maxLength(255)]),
permissions: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one permission for the new role
});
}
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
// public refs = schema.refs({
// id: this.ctx.params.id
// })
// public schema = schema.create({
// login: schema.string({ trim: true }, [
// rules.minLength(3),
// rules.maxLength(50),
// rules.unique({
// table: 'accounts',
// column: 'login',
// // whereNot: { id: this.refs.id }
// whereNot: { id: this.ctx?.params.id },
// }),
// // rules.regex(/^[a-zA-Z0-9-_]+$/),
// //Must be alphanumeric with hyphens or underscores
// ]),
// email: schema.string({}, [rules.email(), rules.unique({ table: 'accounts', column: 'email' })]),
// password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
// roles: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
// });
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
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',
'permissions.minLength': 'at least {{ options.minLength }} permission must be defined',
'permissions.*.number': 'Define permissions as valid numbers',
};
}

View File

@ -1,105 +0,0 @@
import { schema, rules } from '@adonisjs/validator';
import type { HttpContext } from '@adonisjs/core/http';
import { CustomMessages } from "@adonisjs/validator/types";
// import { Request } from '@adonisjs/core/build/standalone';
export default class UpdateUserValidator {
protected ctx: HttpContext;
public schema;
constructor(ctx: HttpContext) {
this.ctx = ctx;
this.schema = this.createSchema();
}
// public get schema() {
// return this._schema;
// }
private createSchema() {
return schema.create({
login: schema.string({ trim: true }, [
rules.minLength(3),
rules.maxLength(50),
rules.unique({
table: 'accounts',
column: 'login',
// whereNot: { id: this.refs.id }
whereNot: { id: this.ctx?.params.id },
}),
// rules.regex(/^[a-zA-Z0-9-_]+$/),
//Must be alphanumeric with hyphens or underscores
]),
email: schema.string({}, [
rules.email(),
rules.unique({ table: 'accounts', column: 'email', whereNot: { id: this.ctx?.params.id } }),
]),
password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
roles: schema.array.optional([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
});
}
/*
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
*
* For example:
* 1. The username must be of data type string. But then also, it should
* not contain special characters or numbers.
* ```
* schema.string({}, [ rules.alpha() ])
* ```
*
* 2. The email must be of data type string, formatted as a valid
* email. But also, not used by any other user.
* ```
* schema.string({}, [
* rules.email(),
* rules.unique({ table: 'users', column: 'email' }),
* ])
* ```
*/
// public refs = schema.refs({
// id: this.ctx.params.id
// })
// public schema = schema.create({
// login: schema.string({ trim: true }, [
// rules.minLength(3),
// rules.maxLength(50),
// rules.unique({
// table: 'accounts',
// column: 'login',
// // whereNot: { id: this.refs.id }
// whereNot: { id: this.ctx?.params.id },
// }),
// // rules.regex(/^[a-zA-Z0-9-_]+$/),
// //Must be alphanumeric with hyphens or underscores
// ]),
// email: schema.string({}, [rules.email(), rules.unique({ table: 'accounts', column: 'email' })]),
// password: schema.string.optional([rules.confirmed(), rules.minLength(6)]),
// roles: schema.array([rules.minLength(1)]).members(schema.number()), // define at least one role for the new user
// });
/**
* Custom messages for validation failures. You can make use of dot notation `(.)`
* for targeting nested fields and array expressions `(*)` for targeting all
* children of an array. For example:
*
* {
* 'profile.username.required': 'Username is required',
* 'scores.*.number': 'Define scores as valid numbers'
* }
*
*/
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',
'roles.minLength': 'at least {{ options.minLength }} role must be defined',
'roles.*.number': 'Define roles as valid numbers',
};
}

60
app/validators/user.ts Normal file
View File

@ -0,0 +1,60 @@
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
/**
* Validates the role's creation action
* node ace make:validator user
*/
export const createUserValidator = vine.compile(
vine.object({
login: vine
.string()
.trim()
.minLength(3)
.maxLength(20)
.isUnique({ table: 'accounts', column: 'login' })
.regex(/^[a-zA-Z0-9]+$/), //Must be alphanumeric with hyphens or underscores
email: vine.string().maxLength(255).email().normalizeEmail().isUnique({ table: 'accounts', column: 'email' }),
password: vine.string().confirmed().trim().minLength(3).maxLength(60),
roles: vine.array(vine.number()).minLength(1), // define at least one role for the new user
}),
);
/**
* Validates the role's update action
* node ace make:validator user
*/
export const updateUserValidator = vine.withMetaData<{ userId: number }>().compile(
vine.object({
login: vine
.string()
.trim()
.minLength(3)
.maxLength(20)
.isUnique({ table: 'accounts', column: 'login', whereNot: (field) => field.meta.userId })
.regex(/^[a-zA-Z0-9]+$/), //Must be alphanumeric with hyphens or underscores
email: vine
.string()
.maxLength(255)
.email()
.normalizeEmail()
.isUnique({ table: 'accounts', column: 'email', whereNot: (field) => field.meta.userId }),
password: vine.string().confirmed().trim().minLength(3).maxLength(60),
roles: vine.array(vine.number()).minLength(1), // define at least one role for the new user
}),
);
let messagesProvider = new SimpleMessagesProvider({
// Applicable for all fields
'required': 'The {{ field }} field is required',
'unique': '{{ field }} must be unique, and this value is already taken',
'string': 'The value of {{ field }} field must be a string',
'email': 'The value is not a valid email address',
'minLength': '{{ field }} must be at least {{ options.minLength }} characters long',
'maxLength': '{{ field }} must be less then {{ options.maxLength }} characters long',
'confirmed': 'Oops! The confirmation of {{ field }} is not correct. Please double-check and ensure they match.',
'roles.minLength': 'at least {{ options.minLength }} role must be defined',
'roles.*.number': 'Define roles as valid numbers',
});
createUserValidator.messagesProvider = messagesProvider;
updateUserValidator.messagesProvider = messagesProvider;

View File

@ -0,0 +1,169 @@
// import { ValidationError } from '../errors/validation_error.js';
import { errors } from '@vinejs/vine';
import type { ErrorReporterContract, FieldContext } from '@vinejs/vine/types';
import string from '@poppinss/utils/string';
/**
* Shape of the Vanilla error node
*/
export type VanillaErrorNode = {
[field: string]: string[];
};
export interface MessagesBagContract {
get(pointer: string, rule: string, message: string, arrayExpressionPointer?: string, args?: any): string;
}
/**
* Message bag exposes the API to pull the most appropriate message for a
* given validation failure.
*/
export class MessagesBag implements MessagesBagContract {
messages;
wildCardCallback;
constructor(messages: string[]) {
this.messages = messages;
this.wildCardCallback = typeof this.messages['*'] === 'function' ? this.messages['*'] : undefined;
}
/**
* Transform message by replace placeholders with runtime values
*/
transform(message: any, rule: string, pointer: string, args: any) {
/**
* No interpolation required
*/
if (!message.includes('{{')) {
return message;
}
return string.interpolate(message, { rule, field: pointer, options: args || {} });
}
/**
* Returns the most appropriate message for the validation failure.
*/
get(pointer: string, rule: string, message: string, arrayExpressionPointer: string, args: any) {
let validationMessage = this.messages[`${pointer}.${rule}`];
/**
* Fetch message for the array expression pointer if it exists
*/
if (!validationMessage && arrayExpressionPointer) {
validationMessage = this.messages[`${arrayExpressionPointer}.${rule}`];
}
/**
* Fallback to the message for the rule
*/
if (!validationMessage) {
validationMessage = this.messages[rule];
}
/**
* Transform and return message. The wildcard callback is invoked when custom message
* is not defined
*/
return validationMessage
? this.transform(validationMessage, rule, pointer, args)
: this.wildCardCallback
? this.wildCardCallback(pointer, rule, arrayExpressionPointer, args)
: message;
}
}
/**
* Shape of the error message collected by the SimpleErrorReporter
*/
// type SimpleError = {
// message: string;
// field: string;
// rule: string;
// index?: number;
// meta?: Record<string, any>;
// };
/**
* Simple error reporter collects error messages as an array of object.
* Each object has following properties.
*
* - message: string
* - field: string
* - rule: string
* - index?: number (in case of an array member)
* - args?: Record<string, any>
*/
export class VanillaErrorReporter implements ErrorReporterContract {
private messages;
// private bail;
/**
* Boolean to know one or more errors have been reported
*/
hasErrors: boolean = false;
/**
* Collection of errors
*/
// errors: SimpleError[] = [];
errors = {};
/**
* Report an error.
*/
// constructor(messages: MessagesBagContract) {
// this.messages = messages;
// }
report(message: string, rule: string, field: FieldContext, meta?: Record<string, any> | undefined): void {
// const error: SimpleError = {
// message,
// rule,
// field: field.getFieldPath()
// };
// if (meta) {
// error.meta = meta;
// }
// if (field.isArrayMember) {
// error.index = field.name as number;
// }
// this.errors.push(error);
this.hasErrors = true;
const error = {
message,
rule,
field: field.getFieldPath(),
};
// field: 'titles.0.value'
// message: 'Main Title is required'
// rule: 'required' "required"
if (meta) {
error.meta = meta;
}
// if (field.isArrayMember) {
// error.index = field.name;
// }
this.hasErrors = true;
// this.errors.push(error);
if (this.errors[error.field]) {
this.errors[error.field]?.push(message);
} else {
this.errors[error.field] = [message];
}
/**
* Collecting errors as per the JSONAPI spec
*/
// this.errors.push({
// code: rule,
// detail: message,
// source: {
// pointer: field.wildCardPath,
// },
// ...(meta ? { meta } : {}),
// });
// let pointer: string = field.wildCardPath as string; //'display_name'
// // if (field.isArrayMember) {
// // this.errors[pointer] = field.name;
// // }
// this.errors[pointer] = this.errors[pointer] || [];
// // this.errors[pointer].push(message);
// this.errors[pointer].push(this.messages.get(pointer, rule, message, arrayExpressionPointer, args));
}
/**
* Returns an instance of the validation error
*/
createError() {
return new errors.E_VALIDATION_ERROR(this.errors);
}
}
export {};

970
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -37,7 +37,7 @@
"@mdi/js": "^7.1.96", "@mdi/js": "^7.1.96",
"@poppinss/utils": "^6.7.2", "@poppinss/utils": "^6.7.2",
"@swc/core": "^1.4.2", "@swc/core": "^1.4.2",
"@symfony/webpack-encore": "^4.5.0", "@symfony/webpack-encore": "^4.6.1",
"@tailwindcss/forms": "^0.5.2", "@tailwindcss/forms": "^0.5.2",
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/clamscan": "^2.0.4", "@types/clamscan": "^2.0.4",
@ -53,15 +53,14 @@
"babel-preset-typescript-vue3": "^2.0.17", "babel-preset-typescript-vue3": "^2.0.17",
"chart.js": "^4.2.0", "chart.js": "^4.2.0",
"dotenv-webpack": "^8.0.1", "dotenv-webpack": "^8.0.1",
"eslint": "^8.32.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-adonis": "^2.1.1", "eslint-plugin-adonis": "^2.1.1",
"eslint-plugin-prettier": "^5.0.0-alpha.2", "eslint-plugin-prettier": "^5.0.0-alpha.2",
"naive-ui": "^2.35.0",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"pinia": "^2.0.30", "pinia": "^2.0.30",
"pino-pretty": "^11.0.0", "pino-pretty": "^11.0.0",
"postcss-loader": "^7.3.0", "postcss-loader": "^7.3.4",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",
@ -85,7 +84,6 @@
"@adonisjs/session": "^7.1.1", "@adonisjs/session": "^7.1.1",
"@adonisjs/shield": "^8.1.1", "@adonisjs/shield": "^8.1.1",
"@adonisjs/static": "^1.1.1", "@adonisjs/static": "^1.1.1",
"@adonisjs/validator": "^13.0.2",
"@eidellev/adonis-stardust": "^3.0.0", "@eidellev/adonis-stardust": "^3.0.0",
"@fontsource/archivo-black": "^5.0.1", "@fontsource/archivo-black": "^5.0.1",
"@fontsource/inter": "^5.0.1", "@fontsource/inter": "^5.0.1",
@ -93,6 +91,7 @@
"@inertiajs/vue3": "^1.0.0", "@inertiajs/vue3": "^1.0.0",
"@opensearch-project/opensearch": "^2.4.0", "@opensearch-project/opensearch": "^2.4.0",
"@phc/format": "^1.0.0", "@phc/format": "^1.0.0",
"@vinejs/vine": "^2.0.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"clamscan": "^2.1.2", "clamscan": "^2.1.2",

View File

@ -88,6 +88,10 @@ const submit = async () => {
<FormField label="Permissions" wrap-body> <FormField label="Permissions" wrap-body>
<FormCheckRadioGroup v-model="form.permissions" name="permissions" is-column :options="props.permissions" /> <FormCheckRadioGroup v-model="form.permissions" name="permissions" is-column :options="props.permissions" />
</FormField> </FormField>
<div class="text-red-400 text-sm" v-if="form.errors.permissions && Array.isArray(form.errors.permissions)">
<!-- {{ errors.password_confirmation }} -->
{{ form.errors.permissions.join(', ') }}
</div>
<template #footer> <template #footer>
<BaseButtons> <BaseButtons>

View File

@ -2,7 +2,7 @@
// import { Head, Link, useForm, usePage } from '@inertiajs/inertia-vue3'; // import { Head, Link, useForm, usePage } from '@inertiajs/inertia-vue3';
import { Head, Link, useForm, usePage } from '@inertiajs/vue3'; import { Head, Link, useForm, usePage } from '@inertiajs/vue3';
import { ComputedRef } from 'vue'; import { ComputedRef } from 'vue';
import { mdiAccountKey, mdiPlus, mdiSquareEditOutline, mdiAlertBoxOutline } from '@mdi/js'; import { mdiAccountKey, mdiPlus, mdiSquareEditOutline, mdiAlertBoxOutline, mdiTrashCan } from '@mdi/js';
import { computed } from 'vue'; import { computed } from 'vue';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue'; import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionMain from '@/Components/SectionMain.vue'; import SectionMain from '@/Components/SectionMain.vue';
@ -49,13 +49,13 @@ const form = useForm({
search: props.filters.search, search: props.filters.search,
}); });
// const formDelete = useForm({}); const formDelete = useForm({});
// async function destroy(id) { // async function destroy(id) {
// const destroy = async (id) => { const destroy = async (id: number) => {
// if (confirm('Are you sure you want to delete?')) { if (confirm('Are you sure you want to delete?')) {
// await formDelete.delete(stardust.route('settings.user.destroy', [id])); await formDelete.delete(stardust.route('settings.user.destroy', [id]));
// } }
// }; };
</script> </script>
<template> <template>
@ -131,7 +131,7 @@ const form = useForm({
:icon="mdiSquareEditOutline" :icon="mdiSquareEditOutline"
small small
/> />
<!-- <BaseButton v-if="can.delete" color="danger" :icon="mdiTrashCan" small @click="destroy(user.id)" /> --> <BaseButton v-if="can.delete" color="danger" :icon="mdiTrashCan" small @click="destroy(user.id)" />
</BaseButtons> </BaseButtons>
</td> </td>
</tr> </tr>

View File

@ -24,7 +24,8 @@ import router from '@adonisjs/core/services/router';
import type { HttpContext } from '@adonisjs/core/http'; import type { HttpContext } from '@adonisjs/core/http';
// import Inertia from '@ioc:EidelLev/Inertia'; // import Inertia from '@ioc:EidelLev/Inertia';
import AuthValidator from '#validators/auth_validator'; // import AuthValidator from '#validators/auth_validator';
import { authValidator } from '#validators/auth';
// import HealthCheck from '@ioc:Adonis/Core/HealthCheck'; // import HealthCheck from '@ioc:Adonis/Core/HealthCheck';
import User from '#models/user'; import User from '#models/user';
import AuthController from '#controllers/Http/Auth/AuthController'; import AuthController from '#controllers/Http/Auth/AuthController';
@ -101,7 +102,7 @@ router.group(() => {
registerBody: request.body(), registerBody: request.body(),
}); });
const data = await request.validate(AuthValidator); const data = await request.validateUsing(authValidator);
console.log({ data }); console.log({ data });
return response.redirect().toRoute('app.index'); return response.redirect().toRoute('app.index');
@ -140,10 +141,10 @@ router.group(() => {
.as('user.update') .as('user.update')
.where('id', router.matchers.number()) .where('id', router.matchers.number())
.use(middleware.can(['user-edit'])); .use(middleware.can(['user-edit']));
// // Route.delete('/user/:id', [AdminuserController, 'destroy']) router.delete('/user/:id', [AdminuserController, 'destroy'])
// // .as('user.destroy') .as('user.destroy')
// // .where('id', Route.matchers.number()) .where('id', router.matchers.number())
// // .use(middleware.can(['user-delete'])); .use(middleware.can(['user-delete']));
// // Route.resource('user', 'AdminuserController'); // // Route.resource('user', 'AdminuserController');
router.get('/role', [RoleController, 'index']).as('role.index').use(middleware.can(['user-list'])); router.get('/role', [RoleController, 'index']).as('role.index').use(middleware.can(['user-list']));

View File

@ -0,0 +1,48 @@
/*
|--------------------------------------------------------------------------
| Preloaded File - node ace make:preload rules/translatedLanguage
|--------------------------------------------------------------------------
|*/
import { FieldContext } from '@vinejs/vine/types';
import vine from '@vinejs/vine';
import { VineString } from '@vinejs/vine';
/**
* Options accepted by the unique rule
*/
type Options = {
mainLanguageField: string;
typeField: string;
};
async function translatedLanguage(value: unknown, options: Options, field: FieldContext) {
if (typeof value !== 'string' && typeof value != 'number') {
return;
}
// const type = validator.helpers.getFieldValue(typeField, root, tip); //'type' = 'Translated'
const typeValue = vine.helpers.getNestedValue(options.typeField, field); //'Main' or 'Translated'
// const mainLanguage = validator.helpers.getFieldValue(mainLanguageField, root, tip);
const mainLanguage = field.data[options.mainLanguageField]; // 'en' or 'de'
if (typeValue === 'Translated') {
if (value === mainLanguage) {
// report thattranlated language field is same as main language field of dataset
field.report('The tranlated {{ field }} hast the same language seted as dataset language', 'translatedLanguage', field);
}
}
}
export const translatedLanguageRule = vine.createRule(translatedLanguage);
declare module '@vinejs/vine' {
interface VineString {
translatedLanguage(options: Options): this;
}
}
VineString.macro('translatedLanguage', function (this: VineString, options: Options) {
return this.use(translatedLanguageRule(options));
});

60
start/rules/unique.ts Normal file
View File

@ -0,0 +1,60 @@
/*
|--------------------------------------------------------------------------
| Preloaded File - node ace make:preload rules/unique
|--------------------------------------------------------------------------
|*/
import { FieldContext } from '@vinejs/vine/types';
import db from '@adonisjs/lucid/services/db';
import vine from '@vinejs/vine';
import { VineString, VineNumber } from '@vinejs/vine';
/**
* Options accepted by the unique rule
*/
type Options = {
table: string;
column: string;
whereNot?: ((field: FieldContext) => string);
};
async function isUnique(value: unknown, options: Options, field: FieldContext) {
if (typeof value !== 'string' && typeof value != 'number') {
return;
}
let ignoreId: string | undefined;
if (options.whereNot) {
ignoreId = options.whereNot(field);
}
const builder = db.from(options.table).select(options.column).where(options.column, value);
if (ignoreId) {
builder.whereNot('id', '=', ignoreId);
}
const result = await builder.first();
if (result) {
// report that value is NOT unique
field.report('The {{ field }} field is not unique', 'isUnique', field);
}
}
export const isUniqueRule = vine.createRule(isUnique);
declare module '@vinejs/vine' {
interface VineString {
isUnique(options: Options): this;
}
interface VineNumber {
isUnique(options: Options): this;
}
}
VineString.macro('isUnique', function (this: VineString, options: Options) {
return this.use(isUniqueRule(options));
});
VineNumber.macro('isUnique', function (this: VineNumber, options: Options) {
return this.use(isUniqueRule(options));
});

View File

@ -1,6 +1,6 @@
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Preloaded File | Preloaded File - node ace make:preload validator
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Any code written inside this file will be executed during the application | Any code written inside this file will be executed during the application
@ -8,133 +8,71 @@
https://issuehunt.io/r/adonisjs/validator/issues/84 https://issuehunt.io/r/adonisjs/validator/issues/84
| |
*/ */
// import { string } from '@ioc:Adonis/Core/Helpers'; import vine from '@vinejs/vine';
import { validator } from '@adonisjs/validator'; // import { FieldContext } from '@vinejs/vine/types';
// import db from '@adonisjs/lucid/services/db';
import { VanillaErrorReporter } from '#validators/vanilla_error_reporter';
// import { SimpleErrorReporter } from '@vinejs/vine';
validator.rule('uniqueArray', (dataArray, [field], { pointer, arrayExpressionPointer, errorReporter }) => {
const array = dataArray; //validator.helpers.getFieldValue(data, field, tip);
if (!Array.isArray(array)) {
throw new Error(`The ${pointer} must be an array.`);
}
const uniqueValues = new Set(); // vine.messagesProvider = new SimpleMessagesProvider({
for (let i = 0; i < array.length; i++) { // // Applicable for all fields
const item = array[i]; // 'required': 'The {{ field }} field is required',
const attributeValue = item[field]; // Extract the attribute value for uniqueness check // 'string': 'The value of {{ field }} field must be a string',
// 'email': 'The value is not a valid email address',
// // 'contacts.0.email.required': 'The primary email of the contact is required',
// // 'contacts.*.email.required': 'Contact email is required',
// 'permissions.minLength': 'at least {{ options.minLength }} permission must be defined',
// 'permissions.*.number': 'Define permissions as valid numbers',
// })
vine.errorReporter = () => new VanillaErrorReporter();
if (uniqueValues.has(attributeValue)) { // /**
// throw new Error(`The ${field} array contains duplicate values for the ${field} attribute.`) // * Options accepted by the unique rule
errorReporter.report( // */
pointer, // type Options = {
'uniqueArray', // Keep an eye on this // table: string;
`The ${pointer} array contains duplicate values for the ${field} attribute.`, // column: string;
arrayExpressionPointer, // };
{ field, array: pointer },
);
return;
}
uniqueValues.add(attributeValue); // /**
} // * Implementation
}); // */
// async function unique(value: unknown, options: Options, field: FieldContext) {
// /**
// * We do not want to deal with non-string
// * values. The "string" rule will handle the
// * the validation.
// */
// if (typeof value !== 'string') {
// return;
// }
validator.rule( // const builder = await db
'translatedLanguage', // .from(options.table)
(value, [mainLanguageField, typeField], { root, tip, pointer, arrayExpressionPointer, errorReporter }) => { // .select(options.column)
if (typeof value !== 'string') { // .where(options.column, value).first();
return; // const row = await builder.first();
}
// const fieldValue = validator. getValue(data, field)
// this should return the "category_id" value present in "root", but i got undefined
const mainLanguage = validator.helpers.getFieldValue(mainLanguageField, root, tip);
const type = validator.helpers.getFieldValue(typeField, root, tip);
if (type && type === 'Translated') { // if (row) {
if (value === mainLanguage) { // field.report('The {{ field }} field is not unique', 'unique', field);
errorReporter.report( // }
pointer, // }
'translatedLanguage', // Keep an eye on this
'translatedLanguage validation failed',
arrayExpressionPointer,
{ mainLanguage },
);
}
}
// if (value !== string.camelCase(value)) { // /**
// options.errorReporter.report( // * Converting a function to a VineJS rule
// options.pointer, // */
// 'camelCase', // export const uniqueRule = vine.createRule(unique);
// 'camelCase validation failed',
// options.arrayExpressionPointer
// );
// }
},
);
validator.rule('fileExtension', async (value, [extensions], { pointer, arrayExpressionPointer, errorReporter }) => { // VineString.macro('unique', function (this: VineString, options: Options) {
const allowedExtensions = extensions.map((ext: string) => ext.toLowerCase()); // return this.use(uniqueRule(options));
const uploadedFile = value;
if (!uploadedFile) {
return;
}
const extension = uploadedFile.extname.toLowerCase().replace('.', '');
if (!allowedExtensions.includes(extension)) {
errorReporter.report(
pointer,
'fileExtension',
'Invalid file extension. Only {{ extensions }} files are allowed.',
arrayExpressionPointer,
);
}
});
// validator.rule(
// 'clamavScan',
// (value, [field], { root, tip, pointer, arrayExpressionPointer, errorReporter }) => {
// if (typeof value !== 'object') {
// return;
// }
// const uploadedFile = validator.helpers.getFieldValue(field, root, tip);
// // return rules.file({}, [
// // async (file) => {
// // const clamdhost = process.env['CLAMD_HOST'] ?? '127.0.0.1';
// // const clamdport = Number(process.env['CLAMD_PORT']) ?? '3310';
// // try {
// // var isInfected = await scanFileForViruses(file.tmpPath, clamdhost, clamdport);
// // } catch (error) {
// // throw new Error(`${pointer}: ${error.message}`);
// // }
// // },
// // ]);
// }); // });
// async function scanFileForViruses(filePath, host, port): Promise<boolean> { // // declare module '@vinejs/vine' {
// // const clamscan = await (new ClamScan().init()); // // interface VineString {
// const opts: ClamScan.Options = { // // unique(options: Options): this;
// preference: 'clamdscan', // // }
// clamdscan: { // // }
// active: true,
// host,
// port,
// multiscan: true,
// },
// };
// const clamscan = await new ClamScan().init(opts);
// return new Promise((resolve, reject) => {
// clamscan.isInfected(filePath, (err, file, isInfected: boolean) => {
// if (err) {
// reject(err);
// } else if (isInfected) {
// reject(new Error(`File ${file} is infected!`));
// } else {
// resolve(isInfected);
// }
// });
// });
// }

140
start/validator_old.ts Normal file
View File

@ -0,0 +1,140 @@
/*
|--------------------------------------------------------------------------
| Preloaded File
|--------------------------------------------------------------------------
|
| Any code written inside this file will be executed during the application
| boot.
https://issuehunt.io/r/adonisjs/validator/issues/84
|
*/
// import { string } from '@ioc:Adonis/Core/Helpers';
import { validator } from '@adonisjs/validator';
validator.rule('uniqueArray', (dataArray, [field], { pointer, arrayExpressionPointer, errorReporter }) => {
const array = dataArray; //validator.helpers.getFieldValue(data, field, tip);
if (!Array.isArray(array)) {
throw new Error(`The ${pointer} must be an array.`);
}
const uniqueValues = new Set();
for (let i = 0; i < array.length; i++) {
const item = array[i];
const attributeValue = item[field]; // Extract the attribute value for uniqueness check
if (uniqueValues.has(attributeValue)) {
// throw new Error(`The ${field} array contains duplicate values for the ${field} attribute.`)
errorReporter.report(
pointer,
'uniqueArray', // Keep an eye on this
`The ${pointer} array contains duplicate values for the ${field} attribute.`,
arrayExpressionPointer,
{ field, array: pointer },
);
return;
}
uniqueValues.add(attributeValue);
}
});
validator.rule(
'translatedLanguage',
(value, [mainLanguageField, typeField], { root, tip, pointer, arrayExpressionPointer, errorReporter }) => {
if (typeof value !== 'string') {
return;
}
// const fieldValue = validator. getValue(data, field)
// this should return the "category_id" value present in "root", but i got undefined
const mainLanguage = validator.helpers.getFieldValue(mainLanguageField, root, tip);
const type = validator.helpers.getFieldValue(typeField, root, tip);
if (type && type === 'Translated') {
if (value === mainLanguage) {
errorReporter.report(
pointer,
'translatedLanguage', // Keep an eye on this
'translatedLanguage validation failed',
arrayExpressionPointer,
{ mainLanguage },
);
}
}
// if (value !== string.camelCase(value)) {
// options.errorReporter.report(
// options.pointer,
// 'camelCase',
// 'camelCase validation failed',
// options.arrayExpressionPointer
// );
// }
},
);
validator.rule('fileExtension', async (value, [extensions], { pointer, arrayExpressionPointer, errorReporter }) => {
const allowedExtensions = extensions.map((ext: string) => ext.toLowerCase());
const uploadedFile = value;
if (!uploadedFile) {
return;
}
const extension = uploadedFile.extname.toLowerCase().replace('.', '');
if (!allowedExtensions.includes(extension)) {
errorReporter.report(
pointer,
'fileExtension',
'Invalid file extension. Only {{ extensions }} files are allowed.',
arrayExpressionPointer,
);
}
});
// validator.rule(
// 'clamavScan',
// (value, [field], { root, tip, pointer, arrayExpressionPointer, errorReporter }) => {
// if (typeof value !== 'object') {
// return;
// }
// const uploadedFile = validator.helpers.getFieldValue(field, root, tip);
// // return rules.file({}, [
// // async (file) => {
// // const clamdhost = process.env['CLAMD_HOST'] ?? '127.0.0.1';
// // const clamdport = Number(process.env['CLAMD_PORT']) ?? '3310';
// // try {
// // var isInfected = await scanFileForViruses(file.tmpPath, clamdhost, clamdport);
// // } catch (error) {
// // throw new Error(`${pointer}: ${error.message}`);
// // }
// // },
// // ]);
// });
// async function scanFileForViruses(filePath, host, port): Promise<boolean> {
// // const clamscan = await (new ClamScan().init());
// const opts: ClamScan.Options = {
// preference: 'clamdscan',
// clamdscan: {
// active: true,
// host,
// port,
// multiscan: true,
// },
// };
// const clamscan = await new ClamScan().init(opts);
// return new Promise((resolve, reject) => {
// clamscan.isInfected(filePath, (err, file, isInfected: boolean) => {
// if (err) {
// reject(err);
// } else if (isInfected) {
// reject(new Error(`File ${file} is infected!`));
// } else {
// resolve(isInfected);
// }
// });
// });
// }

View File

@ -73,7 +73,7 @@
}, },
"types": [ "types": [
// "@eidellev/inertia-adonisjs", // "@eidellev/inertia-adonisjs",
"naive-ui/volar" // "naive-ui/volar"
] ]
}, },