initial commit

This commit is contained in:
Arno Kaimbacher 2023-03-03 16:54:28 +01:00
commit 4fc3bb0a01
202 changed files with 41729 additions and 0 deletions

View file

@ -0,0 +1,10 @@
// import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
// import File from 'App/Models/File';
// import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
// export default class HomeController {
// public async index({ auth, request, inertia }: HttpContextContract) {
// let files: ModelQueryBuilderContract<typeof File, File> = File.query();
// }
// }

View file

@ -0,0 +1,17 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class HomeController {
public async index({}: HttpContextContract) {}
public async create({}: HttpContextContract) {}
public async store({}: HttpContextContract) {}
public async show({}: HttpContextContract) {}
public async edit({}: HttpContextContract) {}
public async update({}: HttpContextContract) {}
public async destroy({}: HttpContextContract) {}
}

View file

@ -0,0 +1,143 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import Role from 'App/Models/Role';
import Permission from 'App/Models/Permission';
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
import CreateRoleValidator from 'App/Validators/CreateRoleValidator';
import UpdateRoleValidator from 'App/Validators/UpdateRoleValidator';
import { RenderResponse } from '@ioc:EidelLev/Inertia';
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
export default class RoleController {
public async index({ auth, request, inertia }: HttpContextContract) {
let roles: ModelQueryBuilderContract<typeof Role, Role> = Role.query();
if (request.input('search')) {
// users = users.whereRaw('name like %?%', [request.input('search')])
const searchTerm = request.input('search');
roles.where('name', 'ilike', `%${searchTerm}%`);
}
if (request.input('sort')) {
type SortOrder = 'asc' | 'desc' | undefined;
let attribute = request.input('sort');
let sort_order: SortOrder = 'asc';
if (attribute.substr(0, 1) == '-') {
sort_order = 'desc';
// attribute = substr(attribute, 1);
attribute = attribute.substr(1);
}
roles.orderBy(attribute, sort_order);
} else {
// users.orderBy('created_at', 'desc');
roles.orderBy('id', 'asc');
}
// const users = await User.query().orderBy('login').paginate(page, limit);
let rolesResult = await roles;
return inertia.render('Admin/Role/Index', {
// testing: 'this is a test',
roles: rolesResult,
filters: request.all(),
can: {
create: await auth.user?.can(['user-create']),
edit: await auth.user?.can(['user-edit']),
delete: await auth.user?.can(['user-delete']),
},
});
}
public async create({ inertia }: HttpContextContract) {
const permissions = await Permission.query().select('id', 'name').pluck('name', 'id');
return inertia.render('Admin/Role/Create', {
permissions: permissions,
});
}
public async store({ request, response, session }: HttpContextContract) {
// node ace make:validator CreateUser
try {
// Step 2 - Validate request body against the schema
await request.validate(CreateRoleValidator);
// await request.validate({ schema: roleSchema });
// console.log({ payload });
} catch (error) {
// Step 3 - Handle errors
// return response.badRequest(error.messages);
throw error;
}
const input = request.only(['name', 'display_name', 'description']);
const role = await Role.create(input);
if (request.input('permissions')) {
const permissions: Array<number> = request.input('permissions');
await role.related('permissions').attach(permissions);
}
session.flash('message', `Role ${role.name} has been created successfully`);
return response.redirect().toRoute('role.index');
}
public async show({ request, inertia }: HttpContextContract): RenderResponse {
const id = request.param('id');
const role = await Role.query().where('id', id).firstOrFail();
const permissions = await Permission.query().pluck('name', 'id');
// const userHasRoles = user.roles;
const rolePermsissions = await role.related('permissions').query().orderBy('name').pluck('id');
return inertia.render('Admin/Role/Show', {
permissions: permissions,
role: role,
roleHasPermissions: rolePermsissions,
});
}
public async edit({ request, inertia }: HttpContextContract) {
const id = request.param('id');
const role = await Role.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/Role/Edit', {
permissions: permissions,
role: role,
roleHasPermissions: Object.keys(rolerHasPermissions).map((key) => rolerHasPermissions[key]), //convert object to array with role ids
});
}
public async update({ request, response, session }: HttpContextContract) {
// 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);
// 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('role.index');
}
public async destroy({ request, response, session }: HttpContextContract) {
const id = request.param('id');
const role = await Role.findOrFail(id);
await role.delete();
session.flash('message', `Role ${role.name} has been deleted.`);
return response.redirect().toRoute('role.index');
}
}

View file

@ -0,0 +1,204 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import User from 'App/Models/User';
import Role from 'App/Models/Role';
import type { ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Orm';
import CreateUserValidator from 'App/Validators/CreateUserValidator';
import UpdateUserValidator from 'App/Validators/UpdateUserValidator';
import { RenderResponse } from '@ioc:EidelLev/Inertia';
// import { schema, rules } from '@ioc:Adonis/Core/Validator';
export default class UsersController {
public async index({ auth, request, inertia }: HttpContextContract) {
const page = request.input('page', 1);
// const limit = 10
let users: ModelQueryBuilderContract<typeof User, User> = User.query();
if (request.input('search')) {
// users = users.whereRaw('name like %?%', [request.input('search')])
const searchTerm = request.input('search');
users.where('login', 'ilike', `%${searchTerm}%`);
}
if (request.input('sort')) {
type SortOrder = 'asc' | 'desc' | undefined;
let attribute = request.input('sort');
let sort_order: SortOrder = 'asc';
// if (strncmp($attribute, '-', 1) === 0) {
if (attribute.substr(0, 1) == '-') {
sort_order = 'desc';
// attribute = substr(attribute, 1);
attribute = attribute.substr(1);
}
// $users->orderBy($attribute, $sort_order);
users.orderBy(attribute, sort_order);
} else {
// users.orderBy('created_at', 'desc');
users.orderBy('id', 'asc');
}
// const users = await User.query().orderBy('login').paginate(page, limit);
let usersResult = await users // User.query()
// .orderBy('login')
// .filter(qs)
// .preload('focusInterests')
// .preload('role')
.paginate(page, 5);
// var test = request.all();
return inertia.render('Admin/User/Index', {
// testing: 'this is a test',
users: usersResult.toJSON(),
filters: request.all(),
can: {
create: await auth.user?.can(['user-create']),
edit: await auth.user?.can(['user-edit']),
delete: await auth.user?.can(['user-delete']),
},
});
}
public async create({ inertia }: HttpContextContract) {
// let rolesPluck = {};
// (await Role.query().select('id', 'name')).forEach((user) => {
// rolesPluck[user.id] = user.name;
// });
const roles = await Role.query().select('id', 'name').pluck('name', 'id');
return inertia.render('Admin/User/Create', {
roles: roles,
});
}
public async store({ request, response, session }: HttpContextContract) {
// node ace make:validator CreateUser
try {
// Step 2 - Validate request body against the schema
await request.validate(CreateUserValidator);
// console.log({ payload });
} catch (error) {
// Step 3 - Handle errors
// return response.badRequest(error.messages);
throw error;
}
const input = request.only(['login', 'email', 'password']);
const user = await User.create(input);
if (request.input('roles')) {
const roles: Array<number> = request.input('roles');
await user.related('roles').attach(roles);
}
session.flash('message', 'User has been created successfully');
return response.redirect().toRoute('user.index');
}
public async show({ request, inertia }: HttpContextContract) {
const id = request.param('id');
const user = await User.query().where('id', id).firstOrFail();
const roles = await Role.query().pluck('name', 'id');
// const userHasRoles = user.roles;
const userRoles = await user.related('roles').query().orderBy('name').pluck('id');
return inertia.render('Admin/User/Show', {
roles: roles,
user: user,
userHasRoles: userRoles,
});
}
public async edit({ request, inertia }: HttpContextContract) {
const id = request.param('id');
const user = await User.query().where('id', id).firstOrFail();
const roles = await Role.query().pluck('name', 'id');
// const userHasRoles = user.roles;
const userHasRoles = await user.related('roles').query().orderBy('name').pluck('id');
return inertia.render('Admin/User/Edit', {
roles: roles,
user: user,
userHasRoles: Object.keys(userHasRoles).map((key) => userHasRoles[key]), //convert object to array with role ids
});
}
public async update({ request, response, session }: HttpContextContract) {
// node ace make:validator UpdateUser
const id = request.param('id');
const user = await User.query().where('id', id).firstOrFail();
// validate update form
await request.validate(UpdateUserValidator);
// password is optional
let input;
if (request.input('password')) {
input = request.only(['login', 'email', 'password']);
} else {
input = request.only(['login', 'email']);
}
await user.merge(input).save();
// await user.save();
if (request.input('roles')) {
const roles: Array<number> = request.input('roles');
await user.related('roles').sync(roles);
}
session.flash('message', 'User has been updated successfully');
return response.redirect().toRoute('user.index');
}
public async destroy({ request, response, session }: HttpContextContract) {
const id = request.param('id');
const user = await User.findOrFail(id);
await user.delete();
session.flash('message', `User ${user.login} has been deleted.`);
return response.redirect().toRoute('user.index');
}
/**
* Show the user a form to change their personal information & password.
*
* @return \Inertia\Response
*/
public accountInfo({ inertia, auth }: HttpContextContract): RenderResponse {
const user = auth.user;
// const id = request.param('id');
// const user = await User.query().where('id', id).firstOrFail();
return inertia.render('Admin/User/AccountInfo', {
user: user,
});
}
/**
* Save the modified personal information for a user.
*
* @param HttpContextContract ctx
* @return : RedirectContract
*/
public async accountInfoStore({ request, response, auth, session }: HttpContextContract) {
// validate update form
await request.validate(UpdateUserValidator);
const payload = request.only(['login', 'email']);
auth.user?.merge(payload);
const user = await auth.user?.save();
// $user = \Auth::user()->update($request->except(['_token']));
let message;
if (user) {
message = 'Account updated successfully.';
} else {
message = 'Error while saving. Please try again.';
}
session.flash(message);
return response.redirect().toRoute('admin.account.info');
//->with('message', __($message));
}
}

View file

@ -0,0 +1,23 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import Person from 'App/Models/Person';
// import Dataset from 'App/Models/Dataset';
// node ace make:controller Author
export default class AuthorsController {
public async index({}: HttpContextContract) {
// select * from gba.persons
// where exists (select * from gba.documents inner join gba.link_documents_persons on "documents"."id" = "link_documents_persons"."document_id"
// where ("link_documents_persons"."role" = 'author') and ("persons"."id" = "link_documents_persons"."person_id"));
const authors = await Person.query()
.whereHas('datasets', (dQuery) => {
dQuery.wherePivot('role', 'author')
})
.withCount('datasets', (query) => {
query.as('datasets_count')
});
return authors;
}
}

View file

@ -0,0 +1,23 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
// import Person from 'App/Models/Person';
import Dataset from 'App/Models/Dataset';
// node ace make:controller Author
export default class DatasetController {
public async index({}: HttpContextContract) {
// select * from gba.persons
// where exists (select * from gba.documents inner join gba.link_documents_persons on "documents"."id" = "link_documents_persons"."document_id"
// where ("link_documents_persons"."role" = 'author') and ("persons"."id" = "link_documents_persons"."person_id"));
const datasets = await Dataset
.query()
.where('server_state', 'published')
.orWhere('server_state', 'deleted');
return datasets;
}
}

View file

@ -0,0 +1,42 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
// import User from 'App/Models/User';
// import Hash from '@ioc:Adonis/Core/Hash';
// import InvalidCredentialException from 'App/Exceptions/InvalidCredentialException';
import AuthValidator from 'App/Validators/AuthValidator';
export default class AuthController {
// login function
public async login({ request, response, auth, session }: HttpContextContract) {
// console.log({
// registerBody: request.body(),
// });
await request.validate(AuthValidator);
const plainPassword = await request.input('password');
const email = await request.input('email');
// grab uid and password values off request body
// const { email, password } = request.only(['email', 'password'])
try {
// attempt to login
await auth.use("web").attempt(email, plainPassword);
} catch (error) {
// if login fails, return vague form message and redirect back
session.flash('message', 'Your username, email, or password is incorrect')
return response.redirect().back()
}
// otherwise, redirect todashboard
response.redirect('/dashboard');
}
// logout function
public async logout({ auth, response }: HttpContextContract) {
// await auth.logout();
await auth.use('web').logout();
response.redirect('/app/login');
// return response.status(200);
}
}

61
app/Exceptions/Handler.ts Normal file
View file

@ -0,0 +1,61 @@
/*
|--------------------------------------------------------------------------
| Http Exception Handler
|--------------------------------------------------------------------------
|
| AdonisJs will forward all exceptions occurred during an HTTP request to
| the following class. You can learn more about exception handling by
| reading docs.
|
| The exception handler extends a base `HttpExceptionHandler` which is not
| mandatory, however it can do lot of heavy lifting to handle the errors
| properly.
|
*/
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import Logger from '@ioc:Adonis/Core/Logger';
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler';
export default class ExceptionHandler extends HttpExceptionHandler {
protected statusPages = {
'401,403': 'errors/unauthorized',
'404': 'errors/not-found',
'500..599': 'errors/server-error',
};
constructor() {
super(Logger);
}
public async handle(error: any, ctx: HttpContextContract) {
const { response, request, inertia } = ctx;
/**
* Handle failed authentication attempt
*/
// if (['E_INVALID_AUTH_PASSWORD', 'E_INVALID_AUTH_UID'].includes(error.code)) {
// session.flash('errors', { login: error.message });
// return response.redirect('/login');
// }
// if ([401].includes(error.status)) {
// session.flash('errors', { login: error.message });
// return response.redirect('/dashboard');
// }
// https://github.com/inertiajs/inertia-laravel/issues/56
if (request.header('X-Inertia') && [500, 503, 404, 403, 401].includes(response.getStatus())) {
return inertia.render('Error', {
status: response.getStatus(),
message: error.message
});
// ->toResponse($request)
// ->setStatusCode($response->status());
}
/**
* Forward rest of the exceptions to the parent class
*/
return super.handle(error, ctx);
}
}

View file

@ -0,0 +1,68 @@
import { Exception } from "@adonisjs/core/build/standalone";
/*
|--------------------------------------------------------------------------
| Exception
|--------------------------------------------------------------------------
|
| The Exception class imported from '@adonisjs/core' allows defining
| a status code and error code for every exception.
|
| @example
| new InvalidCredentialException('message', 403, 'E_RUNTIME_EXCEPTION')
|
*/
export default class InvalidCredentialException extends Exception {
// constructor() {
// super(...arguments);
// // this.responseText = this.message;
// }
/**
* Unable to find user
*/
static invalidUid() {
const error = new this("User not found", 400, "E_INVALID_AUTH_UID");
return error;
}
/**
* Invalid user password
*/
static invalidPassword() {
const error = new this("Password mis-match", 400, "E_INVALID_AUTH_PASSWORD");
return error;
}
/**
* Flash error message and redirect the user back
*/
private respondWithRedirect(error, ctx) {
// if (!ctx.session) {
// return ctx.response.status(this.status).send(this.responseText);
// }
ctx.session.flashExcept(["_csrf"]);
ctx.session.flash("auth", {
error: error,
/**
* Will be removed in the future
*/
errors: {
uid: this.code === "E_INVALID_AUTH_UID" ? ["Invalid login id"] : null,
password: this.code === "E_INVALID_AUTH_PASSWORD" ? ["Invalid password"] : null,
},
});
ctx.response.redirect("back", true);
}
/**
* Handle this exception by itself
*/
handle(error, ctx) {
// return response.status(403).view.render("errors/unauthorized", {
// error: error,
// });
this.respondWithRedirect(error, ctx);
}
}

76
app/Middleware/Auth.ts Normal file
View file

@ -0,0 +1,76 @@
import { AuthenticationException } from '@adonisjs/auth/build/standalone'
import type { GuardsList } from '@ioc:Adonis/Addons/Auth'
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
/**
* Auth middleware is meant to restrict un-authenticated access to a given route
* or a group of routes.
*
* You must register this middleware inside `start/kernel.ts` file under the list
* of named middleware.
*/
export default class AuthMiddleware {
/**
* The URL to redirect to when request is Unauthorized
*/
protected redirectTo = '/app/login'
/**
* Authenticates the current HTTP request against a custom set of defined
* guards.
*
* The authentication loop stops as soon as the user is authenticated using any
* of the mentioned guards and that guard will be used by the rest of the code
* during the current request.
*/
protected async authenticate(auth: HttpContextContract['auth'], guards: (keyof GuardsList)[]) {
/**
* Hold reference to the guard last attempted within the for loop. We pass
* the reference of the guard to the "AuthenticationException", so that
* it can decide the correct response behavior based upon the guard
* driver
*/
let guardLastAttempted: string | undefined
for (let guard of guards) {
guardLastAttempted = guard
if (await auth.use(guard).check()) {
/**
* Instruct auth to use the given guard as the default guard for
* the rest of the request, since the user authenticated
* succeeded here
*/
auth.defaultGuard = guard
return true
}
}
/**
* Unable to authenticate using any guard
*/
throw new AuthenticationException(
'Unauthorized access',
'E_UNAUTHORIZED_ACCESS',
guardLastAttempted,
this.redirectTo,
)
}
/**
* Handle request
*/
public async handle (
{ auth }: HttpContextContract,
next: () => Promise<void>,
customGuards: (keyof GuardsList)[]
) {
/**
* Uses the user defined guards or the default guard mentioned in
* the config file
*/
const guards = customGuards.length ? customGuards : [auth.name]
await this.authenticate(auth, guards)
await next()
}
}

85
app/Middleware/Can.ts Normal file
View file

@ -0,0 +1,85 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import Config from '@ioc:Adonis/Core/Config';
import Database from '@ioc:Adonis/Lucid/Database';
import User from 'App/Models/User';
import { Exception } from '@adonisjs/core/build/standalone';
const permissionTable = Config.get('rolePermission.permission_table', 'permissions');
const rolePermissionTable = Config.get('rolePermission.role_permission_table', 'role_has_permissions');
const roleTable = Config.get('rolePermission.role_table', 'roles');
const userRoleTable = Config.get('rolePermission.user_role_table', 'link_accounts_roles');
/**
* Permission authentication to check if user has any of the specified permissions
*
* Should be called after auth middleware
*/
export default class Can {
/**
* Handle request
*/
public async handle(
{ auth, response }: HttpContextContract,
next: () => Promise<void>,
permissionNames: string[]
) {
/**
* Check if user is logged-in
*/
let user = await auth.user;
if (!user) {
return response.unauthorized({ error: 'Must be logged in' });
}
let hasPermission = await this.checkHasPermissions(user, permissionNames);
if (!hasPermission) {
// return response.unauthorized({
// error: `Doesn't have required role(s): ${permissionNames.join(',')}`,
// });
throw new Exception(`Doesn't have required permission(s): ${permissionNames.join(',')}`, 401);
}
await next();
}
private async checkHasPermissions(user: User, permissionNames: Array<string>): Promise<boolean> {
let rolePlaceHolder = '(';
let placeholders = new Array(permissionNames.length).fill('?');
rolePlaceHolder += placeholders.join(',');
rolePlaceHolder += ')';
// let test = user
// .related('roles')
// .query()
// .count('permissions.name')
// .innerJoin('gba.role_has_permissions', function () {
// this.on('gba.role_has_permissions.role_id', 'roles.id');
// })
// .innerJoin('gba.permissions', function () {
// this.on('role_has_permissions.permission_id', 'permissions.id');
// })
// .andWhereIn('permissions.name', permissionNames);
// select "permissions"."name"
// from "gba"."roles"
// inner join "gba"."link_accounts_roles" on "roles"."id" = "link_accounts_roles"."role_id"
// inner join "gba"."role_has_permissions" on "gba"."role_has_permissions"."role_id" = "roles"."id"
// inner join "gba"."permissions" on "role_has_permissions"."permission_id" = "permissions"."id"
// where ("permissions"."name" in ('dataset-list', 'dataset-publish'))
// and ("link_accounts_roles"."account_id" = 1)
let {
rows: {
0: { permissioncount },
},
} = await Database.rawQuery(
'SELECT count("p"."name") as permissionCount FROM ' + roleTable +
' r INNER JOIN ' + userRoleTable + ' ur ON ur.role_id=r.id AND "ur"."account_id"=? ' +
' INNER JOIN ' + rolePermissionTable + ' rp ON rp.role_id=r.id ' +
' INNER JOIN ' + permissionTable + ' p ON rp.permission_id=p.id AND "p"."name" in ' +
rolePlaceHolder +
' LIMIT 1',
[user.id, ...permissionNames]
);
return permissioncount > 0;
}
}

66
app/Middleware/Is.ts Normal file
View file

@ -0,0 +1,66 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Config from '@ioc:Adonis/Core/Config'
import Database from '@ioc:Adonis/Lucid/Database'
import User from 'App/Models/User'
// import { Exception } from '@adonisjs/core/build/standalone'
const roleTable = Config.get('rolePermission.role_table', 'roles')
const userRoleTable = Config.get('rolePermission.user_role_table', 'user_roles')
/**
* Role authentication to check if user has any of the specified roles
*
* Should be called after auth middleware
*/
export default class Is {
/**
* Handle request
*/
public async handle(
{ auth, response }: HttpContextContract,
next: () => Promise<void>,
roleNames: string[]
) {
/**
* Check if user is logged-in or not.
*/
let user = await auth.user
if (!user) {
return response.unauthorized({ error: 'Must be logged in' })
}
let hasRole = await this.checkHasRoles(user, roleNames)
if (!hasRole) {
return response.unauthorized({
error: `Doesn't have required role(s): ${roleNames.join(',')}`,
})
// return new Exception(`Doesn't have required role(s): ${roleNames.join(',')}`,
// 401,
// "E_INVALID_AUTH_UID");
}
await next()
}
private async checkHasRoles(user: User, roleNames: Array<string>): Promise<boolean> {
let rolePlaceHolder = '('
let placeholders = new Array(roleNames.length).fill('?')
rolePlaceHolder += placeholders.join(',')
rolePlaceHolder += ')'
let {
0: {
0: { roleCount },
},
} = await Database.rawQuery(
'SELECT count(`ur`.`id`) as roleCount FROM ' +
userRoleTable +
' ur INNER JOIN ' +
roleTable +
' r ON ur.role_id=r.id WHERE `ur`.`user_id`=? AND `r`.`name` in ' +
rolePlaceHolder +
' LIMIT 1',
[user.id, ...roleNames]
)
return roleCount > 0
}
}

83
app/Middleware/Role.ts Normal file
View file

@ -0,0 +1,83 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import Database from '@ioc:Adonis/Lucid/Database';
import Config from '@ioc:Adonis/Core/Config';
import User from 'app/Models/User';
import { Exception } from '@adonisjs/core/build/standalone';
const roleTable = Config.get('rolePermission.role_table', 'roles');
const userRoleTable = Config.get('rolePermission.user_role_table', 'link_accounts_roles');
// node ace make:middleware role
export default class Role {
// .middleware(['auth', 'role:admin,moderator'])
public async handle(
{ auth, response }: HttpContextContract,
next: () => Promise<void>,
userRoles: string[]
) {
// Check if user is logged-in or not.
// let expression = "";
// if (Array.isArray(args)) {
// expression = args.join(" || ");
// }
let user = await auth.user;
if (!user) {
return response.unauthorized({ error: 'Must be logged in' });
}
let hasRole = await this.checkHasRoles(user, userRoles);
if (!hasRole) {
// return response.unauthorized({
// error: `Doesn't have required role(s): ${userRoles.join(',')}`,
// // error: `Doesn't have required role(s)`,
// });
throw new Exception(`Doesn't have required role(s): ${userRoles.join(',')}`, 401);
}
// code for middleware goes here. ABOVE THE NEXT CALL
await next();
}
private async checkHasRoles(user: User, userRoles: string[]): Promise<boolean> {
// await user.load("roles");
// const ok = user.roles.map((role) => role.name);
// const roles = await user.getRoles();
let rolePlaceHolder = '(';
let placeholders = new Array(userRoles.length).fill('?');
rolePlaceHolder += placeholders.join(',');
rolePlaceHolder += ')';
// const roles = await user
// .related('roles')
// .query()
// .count('*') // .select('name')
// .whereIn('name', userRoles);
// // .groupBy('name');
// select count(*) as roleCount
// from gba.roles
// inner join gba.link_accounts_roles
// on "roles"."id" = "link_accounts_roles"."role_id"
// where ("name" in ('administrator', 'editor')) and ("link_accounts_roles"."account_id" = 1)
let {
rows: {
0: { rolecount },
},
} = await Database.rawQuery(
'SELECT count("r"."id") as roleCount FROM ' +
roleTable +
' r INNER JOIN ' +
userRoleTable +
' ur ON r.id=ur.role_id WHERE "ur"."account_id"=? AND "r"."name" in ' +
rolePlaceHolder +
' LIMIT 1',
[user.id, ...userRoles]
);
return rolecount > 0;
}
}

View file

@ -0,0 +1,21 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
/**
* Silent auth middleware can be used as a global middleware to silent check
* if the user is logged-in or not.
*
* The request continues as usual, even when the user is not logged-in.
*/
export default class SilentAuthMiddleware {
/**
* Handle request
*/
public async handle({ auth }: HttpContextContract, next: () => Promise<void>) {
/**
* Check if user is logged-in or not. If yes, then `ctx.auth.user` will be
* set to the instance of the currently logged in user.
*/
await auth.check()
await next()
}
}

52
app/Models/Dataset.ts Normal file
View file

@ -0,0 +1,52 @@
import {
column,
BaseModel,
SnakeCaseNamingStrategy,
// computed,
manyToMany,
ManyToMany,
} from '@ioc:Adonis/Lucid/Orm';
import { DateTime } from 'luxon';
import Person from './Person';
export default class Dataset extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'id';
public static table = 'documents';
public static selfAssignPrimaryKey = false;
@column({ isPrimary: true })
public id: number;
@column({})
public server_state: boolean;
@column({})
public publisherName: string;
@column.dateTime({ columnName: 'embargo_date' })
public EmbargoDate: DateTime;
@column({})
public type: string;
@column.dateTime({ columnName: 'server_date_published' })
public ServerDatePublished: DateTime;
@column.dateTime({ autoCreate: true, columnName: 'created_at' })
public createdAt: DateTime;
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime;
@manyToMany(() => Person, {
pivotForeignKey: 'document_id',
pivotRelatedForeignKey: 'person_id',
pivotTable: 'link_documents_persons',
pivotColumns: ['role', 'sort_order', 'allow_email_contact']
})
public persons: ManyToMany<typeof Person>;
}

62
app/Models/File.ts Normal file
View file

@ -0,0 +1,62 @@
import { DateTime } from 'luxon';
import {
column,
BaseModel,
hasMany, HasMany,
// manyToMany,
// ManyToMany,
SnakeCaseNamingStrategy,
} from '@ioc:Adonis/Lucid/Orm';
import HashValue from './HashValue';
export default class File extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'id';
public static table = 'document_files';
public static selfAssignPrimaryKey = false;
@column({
isPrimary: true,
})
public id: number;
@column({})
public pathName: string;
@column()
public label: string;
@column()
public comment: string;
@column()
public mimetype: string;
@column()
public language: string;
@column()
public fileSize: bigint;
@column()
public visibleInOai: boolean;
@column()
public sortOrder: number;
@column.dateTime({ autoCreate: true })
public createdAt: DateTime;
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime;
// public function hashvalues()
// {
// return $this->hasMany(HashValue::class, 'file_id', 'id');
// }
@hasMany(() => HashValue, {
foreignKey: 'file_id',
})
public hashvalues: HasMany<typeof HashValue>;
}

41
app/Models/HashValue.ts Normal file
View file

@ -0,0 +1,41 @@
import {
column,
BaseModel,
belongsTo,
BelongsTo,
SnakeCaseNamingStrategy,
} from '@ioc:Adonis/Lucid/Orm';
import File from './File';
export default class HashValue extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'file_id, type';
public static table = 'file_hashvalues';
// static get primaryKey () {
// return 'type, value'
// }
static get incrementing () {
return false
}
// @column({
// isPrimary: true,
// })
// public id: number;
// Foreign key is still on the same model
@column({})
public file_id: number;
@column({})
public type: string;
@column()
public value: string;
@belongsTo(() => File)
public file: BelongsTo<typeof File>
}

100
app/Models/Permission.ts Normal file
View file

@ -0,0 +1,100 @@
import {
column,
BaseModel,
manyToMany,
ManyToMany,
SnakeCaseNamingStrategy,
beforeUpdate,
beforeCreate,
} from '@ioc:Adonis/Lucid/Orm';
import { DateTime } from 'luxon';
import dayjs from 'dayjs';
import Role from 'App/Models/Role';
export default class Permission extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'id';
public static table = 'permissions';
public static selfAssignPrimaryKey = false;
@column({
isPrimary: true,
})
public id: number;
@column({})
public role_id: number;
@column({})
public display_name: string;
@column({})
public name: string;
@column({})
public description: string;
@column.dateTime({
serialize: (value: Date | null) => {
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
},
autoCreate: true,
})
public created_at: DateTime;
@column.dateTime({
serialize: (value: Date | null) => {
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
},
autoCreate: true,
autoUpdate: true,
})
public updated_at: DateTime;
@beforeCreate()
@beforeUpdate()
public static async resetDate(role) {
role.created_at = this.formatDateTime(role.created_at);
role.updated_at = this.formatDateTime(role.updated_at);
}
// public static boot() {
// super.boot()
// this.before('create', async (_modelInstance) => {
// _modelInstance.created_at = this.formatDateTime(_modelInstance.created_at)
// _modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at)
// })
// this.before('update', async (_modelInstance) => {
// _modelInstance.created_at = this.formatDateTime(_modelInstance.created_at)
// _modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at)
// })
// }
private static formatDateTime(datetime) {
let value = new Date(datetime);
return datetime
? value.getFullYear() +
'-' +
(value.getMonth() + 1) +
'-' +
value.getDate() +
' ' +
value.getHours() +
':' +
value.getMinutes() +
':' +
value.getSeconds()
: datetime;
}
// @belongsTo(() => Role)
// public role: BelongsTo<typeof Role>;
@manyToMany(() => Role, {
pivotForeignKey: 'permission_id',
pivotRelatedForeignKey: 'role_id',
pivotTable: 'role_has_permissions',
})
public roles: ManyToMany<typeof Role>;
}

83
app/Models/Person.ts Normal file
View file

@ -0,0 +1,83 @@
import {
column,
BaseModel,
SnakeCaseNamingStrategy,
computed,
manyToMany,
ManyToMany,
} from '@ioc:Adonis/Lucid/Orm';
import { DateTime } from 'luxon';
import dayjs from 'dayjs';
import Dataset from './Dataset';
export default class Person extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'id';
public static table = 'persons';
public static selfAssignPrimaryKey = false;
@column({
isPrimary: true,
})
public id: number;
@column({})
public academicTitle: string;
@column()
public email: string;
@column({})
public firstName: string;
@column({})
public lastName: string;
@column({})
public identifierOrcid: string;
@column({})
public status: boolean;
@column({})
public nameType: string;
@column.dateTime({
serialize: (value: Date | null) => {
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
},
autoCreate: true,
})
public registeredAt: DateTime;
@computed({
serializeAs: 'name'
})
public get fullName() {
return this.firstName + ' ' + this.lastName;
}
@computed()
public get progress(): number {
return 50;
}
@computed()
public get created_at() {
return '2023-02-17 08:45:56';
}
@computed()
public get datasetCount() {
const stock = this.$extras.datasets_count //my pivot column name was "stock"
return stock
}
@manyToMany(() => Dataset, {
pivotForeignKey: 'person_id',
pivotRelatedForeignKey: 'document_id',
pivotTable: 'link_documents_persons',
pivotColumns: ['role', 'sort_order', 'allow_email_contact']
})
public datasets: ManyToMany<typeof Dataset>;
}

105
app/Models/Role.ts Normal file
View file

@ -0,0 +1,105 @@
import {
column,
BaseModel,
SnakeCaseNamingStrategy,
manyToMany,
ManyToMany,
beforeCreate,
beforeUpdate,
} from '@ioc:Adonis/Lucid/Orm';
import { DateTime } from 'luxon';
// import moment from 'moment';
import dayjs from 'dayjs';
import User from './User';
import Permission from 'App/Models/Permission';
export default class Role extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy();
public static primaryKey = 'id';
public static table = 'roles';
public static selfAssignPrimaryKey = false;
@column({
isPrimary: true,
})
public id: number;
@column({})
public display_name: string;
@column({})
public name: string;
@column({})
public description: string;
@column.dateTime({
serialize: (value: Date | null) => {
// return value ? moment(value).format('MMMM Do YYYY, HH:mm:ss') : value;
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
},
autoCreate: true,
})
public created_at: DateTime;
@column.dateTime({
serialize: (value: Date | null) => {
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
},
autoCreate: true,
autoUpdate: true
})
public updated_at: DateTime;
@beforeCreate()
@beforeUpdate()
public static async resetDate(role) {
role.created_at = this.formatDateTime(role.created_at);
role.updated_at = this.formatDateTime(role.updated_at);
}
// public static boot() {
// super.boot();
// this.before('create', async (_modelInstance) => {
// _modelInstance.created_at = this.formatDateTime(_modelInstance.created_at);
// _modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at);
// });
// this.before('update', async (_modelInstance) => {
// _modelInstance.created_at = this.formatDateTime(_modelInstance.created_at);
// _modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at);
// });
// }
private static formatDateTime(datetime) {
let value = new Date(datetime);
return datetime
? value.getFullYear() +
'-' +
(value.getMonth() + 1) +
'-' +
value.getDate() +
' ' +
value.getHours() +
':' +
value.getMinutes() +
':' +
value.getSeconds()
: datetime;
}
@manyToMany(() => User, {
pivotForeignKey: 'role_id',
pivotRelatedForeignKey: 'account_id',
pivotTable: 'link_accounts_roles',
})
public users: ManyToMany<typeof User>;
@manyToMany(() => Permission, {
pivotForeignKey: 'role_id',
pivotRelatedForeignKey: 'permission_id',
pivotTable: 'role_has_permissions',
})
public permissions: ManyToMany<typeof Permission>;
}

103
app/Models/User.ts Normal file
View file

@ -0,0 +1,103 @@
import { DateTime } from 'luxon';
import { BaseModel, column, beforeSave, manyToMany, ManyToMany } from '@ioc:Adonis/Lucid/Orm';
import Hash from '@ioc:Adonis/Core/Hash';
import Role from './Role';
import Database from '@ioc:Adonis/Lucid/Database';
import Config from '@ioc:Adonis/Core/Config';
// export default interface IUser {
// id: number;
// login: string;
// email: string;
// // password: string;
// // createdAt: DateTime;
// // updatedAt: DateTime;
// // async (user): Promise<void>;
// }
const permissionTable = Config.get('rolePermission.permission_table', 'permissions');
const rolePermissionTable = Config.get('rolePermission.role_permission_table', 'role_has_permissions');
const roleTable = Config.get('rolePermission.role_table', 'roles');
const userRoleTable = Config.get('rolePermission.user_role_table', 'link_accounts_roles');
export default class User extends BaseModel {
public static table = 'accounts';
@column({ isPrimary: true })
public id: number;
@column()
public login: string;
@column()
public email: string;
@column({ serializeAs: null })
public password: string;
@column.dateTime({ autoCreate: true })
public createdAt: DateTime;
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime;
@beforeSave()
public static async hashPassword(user) {
if (user.$dirty.password) {
user.password = await Hash.make(user.password);
}
}
@manyToMany(() => Role, {
pivotForeignKey: 'account_id',
pivotRelatedForeignKey: 'role_id',
pivotTable: 'link_accounts_roles',
})
public roles: ManyToMany<typeof Role>;
// https://github.com/adonisjs/core/discussions/1872#discussioncomment-132289
public async getRoles(this: User): Promise<string[]> {
const test = await this.related('roles').query();
return test.map((role) => role.name);
}
public async can(permissionNames: Array<string>): Promise<boolean> {
// const permissions = await this.getPermissions()
// return Acl.check(expression, operand => _.includes(permissions, operand))
const hasPermission = await this.checkHasPermissions(this, permissionNames);
return hasPermission;
}
private async checkHasPermissions(user: User, permissionNames: Array<string>): Promise<boolean> {
let permissionPlaceHolder = '(';
let placeholders = new Array(permissionNames.length).fill('?');
permissionPlaceHolder += placeholders.join(',');
permissionPlaceHolder += ')';
let {
rows: {
0: { permissioncount },
},
} = await Database.rawQuery(
'SELECT count("p"."name") as permissionCount FROM ' +
roleTable +
' r INNER JOIN ' +
userRoleTable +
' ur ON ur.role_id=r.id AND "ur"."account_id"=? ' +
' INNER JOIN ' +
rolePermissionTable +
' rp ON rp.role_id=r.id ' +
' INNER JOIN ' +
permissionTable +
' p ON rp.permission_id=p.id AND "p"."name" in ' +
permissionPlaceHolder +
' LIMIT 1',
[user.id, ...permissionNames]
);
return permissioncount > 0;
}
}
// export default User;

74
app/Models/UserRole.ts Normal file
View file

@ -0,0 +1,74 @@
import {column, BaseModel, belongsTo, BelongsTo, SnakeCaseNamingStrategy} from '@ioc:Adonis/Lucid/Orm'
import User from 'App/Models/User'
import Role from 'App/Models/Role'
import { DateTime } from 'luxon'
// import moment from 'moment'
export default class UserRole extends BaseModel {
public static namingStrategy = new SnakeCaseNamingStrategy()
public static primaryKey = 'id'
public static table = 'user_roles'
public static selfAssignPrimaryKey = false
@column({
isPrimary: true,
})
public id: number
@column({})
public user_id: number
@column({})
public role_id: number
@column({
// serialize: (value: DateTime | null) => {
// return value ? moment(value).format('lll') : value
// },
})
public created_at: DateTime
@column({
// serialize: (value: DateTime | null) => {
// return value ? moment(value).format('lll') : value
// },
})
public updated_at: DateTime
public static boot() {
super.boot()
this.before('create', async (_modelInstance) => {
_modelInstance.created_at = this.formatDateTime(_modelInstance.created_at)
_modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at)
})
this.before('update', async (_modelInstance) => {
_modelInstance.created_at = this.formatDateTime(_modelInstance.created_at)
_modelInstance.updated_at = this.formatDateTime(_modelInstance.updated_at)
})
}
private static formatDateTime(datetime) {
let value = new Date(datetime)
return datetime
? value.getFullYear() +
'-' +
(value.getMonth() + 1) +
'-' +
value.getDate() +
' ' +
value.getHours() +
':' +
value.getMinutes() +
':' +
value.getSeconds()
: datetime
}
@belongsTo(() => User)
public user: BelongsTo<typeof User>
@belongsTo(() => Role)
public role: BelongsTo<typeof Role>
}

22
app/Models/utils.ts Normal file
View file

@ -0,0 +1,22 @@
import Database, {
// DatabaseQueryBuilderContract,
QueryClientContract,
TransactionClientContract,
} from "@ioc:Adonis/Lucid/Database";
import Config from "@ioc:Adonis/Core/Config";
export function getUserRoles(
userId: number,
trx?: TransactionClientContract
): Promise<Array<string>> {
const { userRole } = Config.get("acl.joinTables");
return ((trx || Database) as QueryClientContract | TransactionClientContract)
.query()
.from("roles")
.distinct("roles.slug")
.leftJoin(userRole, `${userRole}.role_id`, "roles.id")
.where(`${userRole}.user_id`, userId)
.then((res) => {
return res.map((r) => r.slug);
});
}

View file

@ -0,0 +1,46 @@
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
export default class AuthValidator {
constructor(protected ctx: HttpContextContract) {}
/*
* 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

@ -0,0 +1,69 @@
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
export default class CreateRoleValidator {
constructor(protected ctx: HttpContextContract) {}
/*
* 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

@ -0,0 +1,64 @@
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
export default class CreateUserValidator {
constructor(protected ctx: HttpContextContract) {}
/*
* 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'
};
}

View file

@ -0,0 +1,97 @@
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
// import { Request } from '@adonisjs/core/build/standalone';
export default class UpdateRoleValidator {
protected ctx: HttpContextContract;
public schema;
constructor(ctx: HttpContextContract) {
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

@ -0,0 +1,103 @@
import { schema, CustomMessages, rules } from '@ioc:Adonis/Core/Validator';
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
// import { Request } from '@adonisjs/core/build/standalone';
export default class UpdateUserValidator {
protected ctx: HttpContextContract;
public schema;
constructor(ctx: HttpContextContract) {
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',
};
}