feat: enhance user management, mimetype creation, and validation
Some checks failed
CI Pipeline / japa-tests (push) Failing after 1m8s
Some checks failed
CI Pipeline / japa-tests (push) Failing after 1m8s
- **AdminuserController.ts**: enable editing `first_name` and `last_name` for user creation and updates - **MimetypeController.ts**: add creation support for mimetypes with selectable extensions - **Models**: add `Mimetype` model (mime_type.ts); add `SnakeCaseNamingStrategy` for User model - **Validators**: - **updateDatasetValidator**: increase title length to 255 and description length to 2500 - **User Validators**: refine `createUserValidator` and `updateUserValidator` to include `first_name` and `last_name` - **vanilla_error_reporter**: improve error reporting for wildcard fields - **SKOS Query**: refine keyword request in `SearchCategoryAutocomplete.vue` - **UI Enhancements**: - improve icon design in wizard (Wizard.vue) - add components for mimetype creation (Create.vue and button in Index.vue) - **Routes**: update `routes.ts` to include new AdonisJS routes
This commit is contained in:
parent
2235f3905a
commit
49bd96ee77
24 changed files with 1548 additions and 945 deletions
|
@ -85,7 +85,7 @@ export default class AdminuserController {
|
|||
// return response.badRequest(error.messages);
|
||||
throw error;
|
||||
}
|
||||
const input = request.only(['login', 'email', 'password']);
|
||||
const input = request.only(['login', 'email', 'password', 'first_name', 'last_name']);
|
||||
const user = await User.create(input);
|
||||
if (request.input('roles')) {
|
||||
const roles: Array<number> = request.input('roles');
|
||||
|
@ -141,9 +141,9 @@ export default class AdminuserController {
|
|||
// password is optional
|
||||
let input;
|
||||
if (request.input('password')) {
|
||||
input = request.only(['login', 'email', 'password']);
|
||||
input = request.only(['login', 'email', 'password', 'first_name', 'last_name']);
|
||||
} else {
|
||||
input = request.only(['login', 'email']);
|
||||
input = request.only(['login', 'email', 'first_name', 'last_name']);
|
||||
}
|
||||
await user.merge(input).save();
|
||||
// await user.save();
|
||||
|
|
|
@ -1,34 +1,124 @@
|
|||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import MimeType from '#models/mime_type';
|
||||
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
|
||||
|
||||
export default class MimetypeController {
|
||||
public async index({ auth, inertia }: HttpContext) {
|
||||
const direction = 'asc'; // or 'desc'
|
||||
const mimetypes = await MimeType.query().orderBy('name', direction).exec();
|
||||
const mimetypes = await MimeType.query().orderBy('name', direction).exec();
|
||||
|
||||
return inertia.render('Admin/Mimetype/Index', {
|
||||
mimetypes: mimetypes,
|
||||
can: {
|
||||
create: await auth.user?.can(['settings']),
|
||||
edit: await auth.user?.can(['settings']),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async create({ inertia }: HttpContext) {
|
||||
// const permissions = await Permission.query().select('id', 'name').pluck('name', 'id');
|
||||
return inertia.render('Admin/Mimetype/Create', {});
|
||||
}
|
||||
|
||||
public async store({ request, response, session }: HttpContext) {
|
||||
const newDatasetSchema = vine.object({
|
||||
name: vine.string().trim().isUnique({ table: 'mime_types', column: 'name' }),
|
||||
file_extension: vine.array(vine.string()).minLength(1), // define at least one extension for the new mimetype
|
||||
enabled: vine.boolean(),
|
||||
});
|
||||
// await request.validate({ schema: newDatasetSchema, messages: this.messages });
|
||||
try {
|
||||
// Step 2 - Validate request body against the schema
|
||||
// await request.validate({ schema: newDatasetSchema, messages: this.messages });
|
||||
const validator = vine.compile(newDatasetSchema);
|
||||
validator.messagesProvider = new SimpleMessagesProvider(this.messages);
|
||||
await request.validateUsing(validator);
|
||||
} catch (error) {
|
||||
// Step 3 - Handle errors
|
||||
// return response.badRequest(error.messages);
|
||||
throw error;
|
||||
}
|
||||
const input = request.only(['name', 'enabled', 'file_extension']);
|
||||
// Concatenate the file_extensions array into a string with '|' as the separator
|
||||
if (Array.isArray(input.file_extension)) {
|
||||
input.file_extension = input.file_extension.join('|');
|
||||
}
|
||||
await MimeType.create(input);
|
||||
// if (request.input('roles')) {
|
||||
// const roles: Array<number> = request.input('roles');
|
||||
// await user.related('roles').attach(roles);
|
||||
// }
|
||||
|
||||
session.flash('message', 'MimeType has been created successfully');
|
||||
return response.redirect().toRoute('settings.mimetype.index');
|
||||
}
|
||||
|
||||
public messages = {
|
||||
'minLength': '{{ field }} must be at least {{ min }} characters long',
|
||||
'maxLength': '{{ field }} must be less then {{ max }} characters long',
|
||||
'isUnique': '{{ field }} must be unique, and this value is already taken',
|
||||
'required': '{{ field }} is required',
|
||||
'file_extension.minLength': 'at least {{ min }} mimetypes must be defined',
|
||||
'file_extension.*.string': 'Each file extension must be a valid string', // Adjusted to match the type
|
||||
};
|
||||
|
||||
public async edit({ request, inertia }: HttpContext) {
|
||||
const id = request.param('id');
|
||||
const mimetype = await MimeType.query().where('id', id).firstOrFail();
|
||||
|
||||
// const permissions = await Permission.query().pluck('name', 'id');
|
||||
// // const userHasRoles = user.roles;
|
||||
// const rolerHasPermissions = await role.related('permissions').query().orderBy('name').pluck('id');
|
||||
|
||||
return inertia.render('Admin/Mimetype/Edit', {
|
||||
mimetype: mimetype,
|
||||
});
|
||||
}
|
||||
|
||||
// public async update({ request, response, session }: HttpContext) {
|
||||
// // node ace make:validator UpdateUser
|
||||
// const id = request.param('id');
|
||||
// const role = await Role.query().where('id', id).firstOrFail();
|
||||
|
||||
// // validate update form
|
||||
// // await request.validate(UpdateRoleValidator);
|
||||
// await request.validateUsing(updateRoleValidator, {
|
||||
// meta: {
|
||||
// roleId: role.id,
|
||||
// },
|
||||
// });
|
||||
|
||||
// // password is optional
|
||||
|
||||
// const input = request.only(['name', 'description']);
|
||||
// await role.merge(input).save();
|
||||
// // await user.save();
|
||||
|
||||
// if (request.input('permissions')) {
|
||||
// const permissions: Array<number> = request.input('permissions');
|
||||
// await role.related('permissions').sync(permissions);
|
||||
// }
|
||||
|
||||
// session.flash('message', 'Role has been updated successfully');
|
||||
// return response.redirect().toRoute('settings.role.index');
|
||||
// }
|
||||
|
||||
public async down({ request, response }: HttpContext) {
|
||||
const id = request.param('id');
|
||||
const mimetype = await MimeType.findOrFail(id);
|
||||
const mimetype = await MimeType.findOrFail(id);
|
||||
mimetype.enabled = false;
|
||||
await mimetype .save();
|
||||
await mimetype.save();
|
||||
|
||||
// session.flash({ message: 'person has been deactivated!' });
|
||||
return response.flash('mimetype has been deactivated!', 'message').toRoute('settings.mimetype.index')
|
||||
return response.flash('mimetype has been deactivated!', 'message').toRoute('settings.mimetype.index');
|
||||
}
|
||||
|
||||
public async up({ request, response }: HttpContext) {
|
||||
const id = request.param('id');
|
||||
const mimetype = await MimeType.findOrFail(id);
|
||||
const mimetype = await MimeType.findOrFail(id);
|
||||
mimetype.enabled = true;
|
||||
await mimetype .save();
|
||||
await mimetype.save();
|
||||
|
||||
// session.flash({ message: 'person has been activated!' });
|
||||
return response.flash('mimetype has been activated!', 'message').toRoute('settings.mimetype.index');
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||
import BaseModel from './base_model.js';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export default class MimeType extends BaseModel {
|
||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||
|
@ -21,11 +22,16 @@ export default class MimeType extends BaseModel {
|
|||
@column({})
|
||||
public enabled: boolean;
|
||||
|
||||
@column({})
|
||||
public visible_frontdoor: boolean;
|
||||
@column.dateTime({
|
||||
autoCreate: true,
|
||||
})
|
||||
public created_at: DateTime;
|
||||
|
||||
|
||||
public visible_oai: boolean;
|
||||
@column.dateTime({
|
||||
autoCreate: true,
|
||||
autoUpdate: true,
|
||||
})
|
||||
public updated_at: DateTime;
|
||||
|
||||
// @hasMany(() => Collection, {
|
||||
// foreignKey: 'role_id',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { DateTime } from 'luxon';
|
||||
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid';
|
||||
import { column, manyToMany, hasMany } from '@adonisjs/lucid/orm';
|
||||
import { column, manyToMany, hasMany, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm';
|
||||
import hash from '@adonisjs/core/services/hash';
|
||||
import Role from './role.js';
|
||||
import db from '@adonisjs/lucid/services/db';
|
||||
|
@ -40,6 +40,7 @@ const AuthFinder = withAuthFinder(() => hash.use('laravel'), {
|
|||
|
||||
export default class User extends compose(BaseModel, AuthFinder) {
|
||||
// export default class User extends BaseModel {
|
||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||
public static table = 'accounts';
|
||||
|
||||
@column({ isPrimary: true })
|
||||
|
@ -48,6 +49,13 @@ export default class User extends compose(BaseModel, AuthFinder) {
|
|||
@column()
|
||||
public login: string;
|
||||
|
||||
|
||||
@column()
|
||||
public firstName: string;
|
||||
|
||||
@column()
|
||||
public lastName: string;
|
||||
|
||||
@column()
|
||||
public email: string;
|
||||
|
||||
|
@ -113,8 +121,6 @@ export default class User extends compose(BaseModel, AuthFinder) {
|
|||
})
|
||||
public backupcodes: HasMany<typeof BackupCode>;
|
||||
|
||||
|
||||
|
||||
public async getBackupCodes(this: User): Promise<BackupCode[]> {
|
||||
const test = await this.related('backupcodes').query();
|
||||
// return test.map((role) => role.code);
|
||||
|
|
|
@ -164,7 +164,7 @@ export const updateDatasetValidator = vine.compile(
|
|||
titles: vine
|
||||
.array(
|
||||
vine.object({
|
||||
value: vine.string().trim().minLength(3).maxLength(2500),
|
||||
value: vine.string().trim().minLength(3).maxLength(255),
|
||||
type: vine.enum(Object.values(TitleTypes)),
|
||||
language: vine
|
||||
.string()
|
||||
|
@ -178,7 +178,7 @@ export const updateDatasetValidator = vine.compile(
|
|||
descriptions: vine
|
||||
.array(
|
||||
vine.object({
|
||||
value: vine.string().trim().minLength(3).maxLength(255),
|
||||
value: vine.string().trim().minLength(3).maxLength(2500),
|
||||
type: vine.enum(Object.values(DescriptionTypes)),
|
||||
language: vine
|
||||
.string()
|
||||
|
@ -295,6 +295,7 @@ let messagesProvider = new SimpleMessagesProvider({
|
|||
'rights.in': 'you must agree to continue',
|
||||
|
||||
'titles.0.value.minLength': 'Main Title must be at least {{ min }} characters long',
|
||||
'titles.0.value.maxLength': 'Main Title must be less than {{ max }} 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',
|
||||
|
|
|
@ -56,7 +56,7 @@ let messagesProvider = new SimpleMessagesProvider({
|
|||
|
||||
// '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.minLength': 'at least {{min }} permission must be defined',
|
||||
'permissions.*.number': 'Define permissions as valid numbers',
|
||||
});
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ export const createUserValidator = vine.compile(
|
|||
.maxLength(20)
|
||||
.isUnique({ table: 'accounts', column: 'login' })
|
||||
.regex(/^[a-zA-Z0-9]+$/), //Must be alphanumeric with hyphens or underscores
|
||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
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
|
||||
|
@ -30,8 +32,10 @@ export const updateUserValidator = vine.withMetaData<{ objId: number }>().compil
|
|||
.trim()
|
||||
.minLength(3)
|
||||
.maxLength(20)
|
||||
.isUnique({ table: 'accounts', column: 'login', whereNot: (field) => field.meta.objId })
|
||||
.regex(/^[a-zA-Z0-9]+$/), //Must be alphanumeric with hyphens or underscores
|
||||
.isUnique({ table: 'accounts', column: 'login', whereNot: (field) => field.meta.objId }),
|
||||
// .regex(/^[a-zA-Z0-9]+$/), //Must be alphanumeric with hyphens or underscores
|
||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
email: vine
|
||||
.string()
|
||||
.maxLength(255)
|
||||
|
@ -49,10 +53,10 @@ let messagesProvider = new SimpleMessagesProvider({
|
|||
'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',
|
||||
'minLength': '{{ field }} must be at least {{ min }} characters long',
|
||||
'maxLength': '{{ field }} must be less then {{ max }} 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.minLength': 'at least {{ min }} role must be defined',
|
||||
'roles.*.number': 'Define roles as valid numbers',
|
||||
});
|
||||
|
||||
|
|
|
@ -128,11 +128,12 @@ export class VanillaErrorReporter implements ErrorReporterContract {
|
|||
const error: SimpleError = {
|
||||
message,
|
||||
rule,
|
||||
field: field.getFieldPath(),
|
||||
field: field.wildCardPath ?field.wildCardPath.split('.')[0] : field.getFieldPath(),
|
||||
};
|
||||
// field: 'titles.0.value'
|
||||
// message: 'Main Title is required'
|
||||
// rule: 'required' "required"
|
||||
|
||||
if (meta) {
|
||||
error.meta = meta;
|
||||
}
|
||||
|
@ -140,10 +141,25 @@ export class VanillaErrorReporter implements ErrorReporterContract {
|
|||
// error.index = field.name;
|
||||
// }
|
||||
this.hasErrors = true;
|
||||
|
||||
// this.errors.push(error);
|
||||
if (this.errors[error.field]) {
|
||||
this.errors[error.field]?.push(message);
|
||||
// if (this.errors[error.field]) {
|
||||
// this.errors[error.field]?.push(message);
|
||||
// }
|
||||
if (field.isArrayMember) {
|
||||
// Check if the field has wildCardPath and if the error field already exists
|
||||
if (this.errors[error.field] && field.wildCardPath) {
|
||||
// Do nothing, as we don't want to push further messages
|
||||
} else {
|
||||
// If the error field already exists, push the message
|
||||
if (this.errors[error.field]) {
|
||||
this.errors[error.field].push(message);
|
||||
} else {
|
||||
this.errors[error.field] = [message];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// normal field
|
||||
this.errors[error.field] = [message];
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue