mirror of
https://github.com/sergeyyarkov/educt-server.git
synced 2026-10-11 10:19:20 +03:00
seeders, migrations & roles
This commit is contained in:
parent
bb12cc9bd4
commit
05fc06e744
@ -20,7 +20,9 @@
|
|||||||
"providers": [
|
"providers": [
|
||||||
"./providers/AppProvider",
|
"./providers/AppProvider",
|
||||||
"@adonisjs/core",
|
"@adonisjs/core",
|
||||||
"@adonisjs/lucid"
|
"@adonisjs/lucid",
|
||||||
|
"@adonisjs/auth",
|
||||||
|
"@adonisjs/redis"
|
||||||
],
|
],
|
||||||
"aceProviders": [
|
"aceProviders": [
|
||||||
"@adonisjs/repl"
|
"@adonisjs/repl"
|
||||||
|
|||||||
@ -8,3 +8,7 @@ PG_PORT=5432
|
|||||||
PG_USER=lucid
|
PG_USER=lucid
|
||||||
PG_PASSWORD=
|
PG_PASSWORD=
|
||||||
PG_DB_NAME=lucid
|
PG_DB_NAME=lucid
|
||||||
|
REDIS_CONNECTION=local
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
|||||||
@ -4,5 +4,7 @@
|
|||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"jsxSingleQuote": true,
|
"jsxSingleQuote": true,
|
||||||
"arrowParens": "avoid"
|
"arrowParens": "avoid",
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"printWidth": 100
|
||||||
}
|
}
|
||||||
|
|||||||
55
app/Controllers/Http/AuthController.ts
Normal file
55
app/Controllers/Http/AuthController.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import Logger from '@ioc:Adonis/Core/Logger';
|
||||||
|
import Hash from '@ioc:Adonis/Core/Hash';
|
||||||
|
import User from 'App/Models/User';
|
||||||
|
import { Exception } from '@adonisjs/core/build/standalone';
|
||||||
|
|
||||||
|
export default class AuthController {
|
||||||
|
private user: typeof User;
|
||||||
|
|
||||||
|
private guard: 'api';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.user = User;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new token for user and returns it.
|
||||||
|
* POST /login
|
||||||
|
*
|
||||||
|
* @param {HttpContextContract} context
|
||||||
|
* @returns {object} - Token data
|
||||||
|
*/
|
||||||
|
|
||||||
|
public async login({ auth, request }: HttpContextContract) {
|
||||||
|
const login = request.input('login');
|
||||||
|
const password = request.input('password');
|
||||||
|
const user = await this.user.query().preload('roles').where('login', login).firstOrFail();
|
||||||
|
|
||||||
|
if (!(await Hash.verify(user.password, password))) {
|
||||||
|
throw new Exception('Invalid credentials.', 401, 'E_INVALID_CREDENTIALS');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await auth.use(this.guard).generate(user, {
|
||||||
|
expiresIn: '1d',
|
||||||
|
userRoles: user.roles.map(role => role.slug),
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.info(`A token for user with id: "${token.user.id}" was successfully generated.`);
|
||||||
|
|
||||||
|
return token.toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke a token if user logged in.
|
||||||
|
* POST /logout
|
||||||
|
*
|
||||||
|
* @param {HttpContextContract} context
|
||||||
|
* @returns {object} - Revoked message
|
||||||
|
*/
|
||||||
|
|
||||||
|
public async logout({ auth }: HttpContextContract) {
|
||||||
|
await auth.use(this.guard).revoke();
|
||||||
|
return { message: 'REVOKED' };
|
||||||
|
}
|
||||||
|
}
|
||||||
4
app/Controllers/Http/ContactsController.ts
Normal file
4
app/Controllers/Http/ContactsController.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
|
export default class ContactsController {
|
||||||
|
}
|
||||||
83
app/Controllers/Http/RolesController.ts
Normal file
83
app/Controllers/Http/RolesController.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { Exception } from '@adonisjs/core/build/standalone';
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
|
||||||
|
import Role from 'App/Models/Role';
|
||||||
|
import User from 'App/Models/User';
|
||||||
|
import AddRoleToUserValidator from 'App/Validators/AddRoleToUserValidator';
|
||||||
|
|
||||||
|
export default class RolesController {
|
||||||
|
private readonly user: typeof User;
|
||||||
|
|
||||||
|
private readonly role: typeof Role;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.user = User;
|
||||||
|
this.role = Role;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findRolesBySlug(input: any) {
|
||||||
|
const roles = await this.role.query().whereIn('slug', input);
|
||||||
|
|
||||||
|
if (roles.length !== input.length) {
|
||||||
|
throw new Exception(
|
||||||
|
'Unable to find any role using input value "roles"',
|
||||||
|
404,
|
||||||
|
'E_ROLE_NOT_FOUND'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a role to a user by "id"
|
||||||
|
* POST /user/:id/roles
|
||||||
|
*
|
||||||
|
* @param {HttpContextContract} context
|
||||||
|
* @returns {Promise<User>} - Updated user.
|
||||||
|
*/
|
||||||
|
public async store({ request, params }: HttpContextContract): Promise<Role[]> {
|
||||||
|
try {
|
||||||
|
await request.validate(AddRoleToUserValidator);
|
||||||
|
|
||||||
|
const input = request.input('roles');
|
||||||
|
const user = await this.user.findOrFail(params.id);
|
||||||
|
const roles = await this.findRolesBySlug(input);
|
||||||
|
|
||||||
|
await user.related('roles').attach(roles.map(r => r.id));
|
||||||
|
await user.load('roles');
|
||||||
|
|
||||||
|
return user.roles;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === '23505') {
|
||||||
|
throw new Exception('Some role already attached to that user.', 400, 'E_ROLE_ATTACHED');
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach role from user by "id"
|
||||||
|
* DELETE /users/:id/roles
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public async destroy({ request, params }: HttpContextContract): Promise<Role[]> {
|
||||||
|
const input = request.input('roles');
|
||||||
|
const user = await this.user.query().preload('roles').where('id', params.id).firstOrFail();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach all when "roles" input not provided
|
||||||
|
*/
|
||||||
|
if (input === undefined) {
|
||||||
|
user.related('roles').detach([]);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const roles = await this.findRolesBySlug(input);
|
||||||
|
|
||||||
|
user.related('roles').detach(roles.map(r => r.id));
|
||||||
|
await user.load('roles');
|
||||||
|
|
||||||
|
return user.roles;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,21 @@
|
|||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import Role from 'App/Models/Role';
|
||||||
import User from 'App/Models/User';
|
import User from 'App/Models/User';
|
||||||
|
import Contact from 'App/Models/Contact';
|
||||||
import CreateUserValidator from 'App/Validators/CreateUserValidator';
|
import CreateUserValidator from 'App/Validators/CreateUserValidator';
|
||||||
import UpdateUserValidator from 'App/Validators/UpdateUserValidator';
|
import UpdateUserValidator from 'App/Validators/UpdateUserValidator';
|
||||||
|
|
||||||
export default class UsersController {
|
export default class UsersController {
|
||||||
private readonly userModel: typeof User;
|
private readonly user: typeof User;
|
||||||
|
|
||||||
|
private readonly role: typeof Role;
|
||||||
|
|
||||||
|
private readonly contact: typeof Contact;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.userModel = User;
|
this.user = User;
|
||||||
|
this.role = Role;
|
||||||
|
this.contact = Contact;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,7 +26,7 @@ export default class UsersController {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public async index(): Promise<User[]> {
|
public async index(): Promise<User[]> {
|
||||||
const users = this.userModel.all();
|
const users = await this.user.query().preload('contacts').preload('roles');
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +39,11 @@ export default class UsersController {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public async show({ params }: HttpContextContract): Promise<User> {
|
public async show({ params }: HttpContextContract): Promise<User> {
|
||||||
const user = await this.userModel.findOrFail(params.user_id);
|
const user = await this.user.findOrFail(params.id);
|
||||||
|
|
||||||
|
await user.load('contacts');
|
||||||
|
await user.load('roles');
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,21 +57,27 @@ export default class UsersController {
|
|||||||
|
|
||||||
public async store({ request }: HttpContextContract): Promise<User> {
|
public async store({ request }: HttpContextContract): Promise<User> {
|
||||||
await request.validate(CreateUserValidator);
|
await request.validate(CreateUserValidator);
|
||||||
const user = await this.userModel.create({
|
|
||||||
name: request.input('name'),
|
const roles = await this.role.query().whereIn('slug', request.input('roles'));
|
||||||
surname: request.input('surname'),
|
const user = await this.user.create({
|
||||||
patronymic: request.input('patronymic'),
|
first_name: request.input('first_name'),
|
||||||
|
last_name: request.input('last_name'),
|
||||||
login: request.input('login'),
|
login: request.input('login'),
|
||||||
email: request.input('email'),
|
|
||||||
password: request.input('password'),
|
password: request.input('password'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await user.related('contacts').create({ email: request.input('email') });
|
||||||
|
await user.related('roles').attach(roles.map(r => r.id));
|
||||||
|
|
||||||
|
await user.load('roles');
|
||||||
|
await user.load('contacts');
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a user in a system by "id".
|
* Update a user in a system by "id".
|
||||||
* PUT /users
|
* PATCH /users/:id
|
||||||
*
|
*
|
||||||
* @param {HttpContextContract} context
|
* @param {HttpContextContract} context
|
||||||
* @returns {Promise<User>}
|
* @returns {Promise<User>}
|
||||||
@ -67,14 +85,35 @@ export default class UsersController {
|
|||||||
|
|
||||||
public async update({ request, params }: HttpContextContract): Promise<User> {
|
public async update({ request, params }: HttpContextContract): Promise<User> {
|
||||||
await request.validate(UpdateUserValidator);
|
await request.validate(UpdateUserValidator);
|
||||||
const user = await this.userModel.findOrFail(params.id);
|
|
||||||
user.name = request.input('name');
|
/**
|
||||||
user.surname = request.input('surname');
|
* User update
|
||||||
user.patronymic = request.input('patronymic');
|
*/
|
||||||
|
const user = await this.user.findOrFail(params.id);
|
||||||
|
user.first_name = request.input('first_name');
|
||||||
|
user.last_name = request.input('last_name');
|
||||||
user.login = request.input('login');
|
user.login = request.input('login');
|
||||||
user.email = request.input('email');
|
|
||||||
user.password = request.input('password');
|
user.password = request.input('password');
|
||||||
|
|
||||||
|
// await user.load('roles');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contacts update
|
||||||
|
*/
|
||||||
|
const contacts = await this.contact.findByOrFail('user_id', params.id);
|
||||||
|
contacts.email = request.input('email');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the updated data
|
||||||
|
*/
|
||||||
await user.save();
|
await user.save();
|
||||||
|
await contacts.save();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the updated data to return
|
||||||
|
*/
|
||||||
|
await user.load('roles');
|
||||||
|
await user.load('contacts');
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@ -88,7 +127,7 @@ export default class UsersController {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public async destroy({ params }: HttpContextContract): Promise<User> {
|
public async destroy({ params }: HttpContextContract): Promise<User> {
|
||||||
const user = await this.userModel.findOrFail(params.id);
|
const user = await this.user.findOrFail(params.id);
|
||||||
await user.delete();
|
await user.delete();
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
|||||||
68
app/Middleware/Auth.ts
Normal file
68
app/Middleware/Auth.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { GuardsList } from '@ioc:Adonis/Addons/Auth';
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import { AuthenticationException } from '@adonisjs/auth/build/standalone';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = '/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;
|
||||||
|
const authPromises: Promise<boolean>[] = guards.map(guard => auth.use(guard).check());
|
||||||
|
const authResults: boolean[] = await Promise.all(authPromises);
|
||||||
|
|
||||||
|
for (let i = 0; i < authResults.length; i += 1) {
|
||||||
|
/**
|
||||||
|
* User has been successfully authenticated
|
||||||
|
*/
|
||||||
|
if (authResults[i]) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unable to authenticate using any guard
|
||||||
|
*/
|
||||||
|
throw new AuthenticationException(
|
||||||
|
'Unauthorized access',
|
||||||
|
'E_UNAUTHORIZED_ACCESS',
|
||||||
|
guardLastAttempted,
|
||||||
|
this.redirectTo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Middleware/Role.ts
Normal file
40
app/Middleware/Role.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Exception } from '@adonisjs/core/build/standalone';
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
|
||||||
|
export default class Role {
|
||||||
|
error: {
|
||||||
|
message: string;
|
||||||
|
status: number;
|
||||||
|
errorCode: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.error = {
|
||||||
|
message: 'You dont have permission to perform that action.',
|
||||||
|
status: 403,
|
||||||
|
errorCode: 'E_ACCESS_DENIED',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks an array of roles for the current user
|
||||||
|
* If the user role is not found in the array of roles for the route, an error is thrown
|
||||||
|
*
|
||||||
|
* @param {string[]} roles - Array of roles for route
|
||||||
|
* @param {string[]} userRole - Array of roles of current user
|
||||||
|
*/
|
||||||
|
protected checkRoles(roles: string[], userRoles: string[]): void {
|
||||||
|
if (!roles.some(role => userRoles.includes(role))) {
|
||||||
|
/**
|
||||||
|
* Failed to verify user rights
|
||||||
|
*/
|
||||||
|
throw new Exception(this.error.message, this.error.status, this.error.errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handle({ auth }: HttpContextContract, next: () => Promise<void>, roles: string[]) {
|
||||||
|
const { userRoles } = auth.use('api').token?.meta;
|
||||||
|
this.checkRoles(roles, userRoles);
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Models/Contact.ts
Normal file
31
app/Models/Contact.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
|
||||||
|
export default class Contact extends BaseModel {
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({ serializeAs: null })
|
||||||
|
user_id: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public email: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public phone_number: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public vk_id: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public twitter_id: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public telegram_id: string;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, serializeAs: null })
|
||||||
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, autoUpdate: true, serializeAs: null })
|
||||||
|
public updatedAt: DateTime;
|
||||||
|
}
|
||||||
20
app/Models/Role.ts
Normal file
20
app/Models/Role.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/* eslint-disable import/no-cycle */
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
|
||||||
|
export default class Role extends BaseModel {
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public slug: string;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, serializeAs: null })
|
||||||
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, autoUpdate: true, serializeAs: null })
|
||||||
|
public updatedAt: DateTime;
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable import/no-cycle */
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import {
|
import {
|
||||||
@ -5,36 +6,46 @@ import {
|
|||||||
beforeCreate,
|
beforeCreate,
|
||||||
beforeSave,
|
beforeSave,
|
||||||
column,
|
column,
|
||||||
|
HasOne,
|
||||||
|
hasOne,
|
||||||
|
ManyToMany,
|
||||||
|
manyToMany,
|
||||||
} from '@ioc:Adonis/Lucid/Orm';
|
} from '@ioc:Adonis/Lucid/Orm';
|
||||||
import Hash from '@ioc:Adonis/Core/Hash';
|
import Hash from '@ioc:Adonis/Core/Hash';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
import Role from './Role';
|
||||||
|
import Contact from './Contact';
|
||||||
|
|
||||||
export default class User extends BaseModel {
|
export default class User extends BaseModel {
|
||||||
@column({ isPrimary: true })
|
@column({ isPrimary: true })
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
public name: string;
|
public first_name: string;
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
public surname: string;
|
public last_name: string;
|
||||||
|
|
||||||
@column()
|
@column({ serializeAs: null })
|
||||||
public patronymic: string;
|
|
||||||
|
|
||||||
@column()
|
|
||||||
public login: string;
|
public login: string;
|
||||||
|
|
||||||
@column()
|
|
||||||
public email: string;
|
|
||||||
|
|
||||||
@column({ serializeAs: null })
|
@column({ serializeAs: null })
|
||||||
public password: string;
|
public password: string;
|
||||||
|
|
||||||
@column.dateTime({ autoCreate: true })
|
@hasOne(() => Contact, {
|
||||||
|
foreignKey: 'user_id',
|
||||||
|
})
|
||||||
|
public contacts: HasOne<typeof Contact>;
|
||||||
|
|
||||||
|
@manyToMany(() => Role, {
|
||||||
|
pivotTable: 'users_roles',
|
||||||
|
})
|
||||||
|
public roles: ManyToMany<typeof Role>;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, serializeAs: null })
|
||||||
public createdAt: DateTime;
|
public createdAt: DateTime;
|
||||||
|
|
||||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
@column.dateTime({ autoCreate: true, autoUpdate: true, serializeAs: null })
|
||||||
public updatedAt: DateTime;
|
public updatedAt: DateTime;
|
||||||
|
|
||||||
@beforeCreate()
|
@beforeCreate()
|
||||||
|
|||||||
12
app/Validators/AddRoleToUserValidator.ts
Normal file
12
app/Validators/AddRoleToUserValidator.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { schema } from '@ioc:Adonis/Core/Validator';
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
|
||||||
|
export default class AddRoleToUserValidator {
|
||||||
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
|
|
||||||
|
public schema = schema.create({
|
||||||
|
roles: schema.array().members(schema.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
public messages = {};
|
||||||
|
}
|
||||||
@ -24,14 +24,13 @@ export default class CreateUserValidator {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public schema = schema.create({
|
public schema = schema.create({
|
||||||
name: schema.string(),
|
first_name: schema.string(),
|
||||||
surname: schema.string(),
|
last_name: schema.string(),
|
||||||
patronymic: schema.string(),
|
login: schema.string({}, [
|
||||||
login: schema.string(),
|
rules.maxLength(128),
|
||||||
email: schema.string({}, [
|
rules.unique({ table: 'users', column: 'login' }),
|
||||||
rules.email(),
|
|
||||||
rules.unique({ table: 'users', column: 'email' }),
|
|
||||||
]),
|
]),
|
||||||
|
email: schema.string({}, [rules.email(), rules.unique({ table: 'contacts', column: 'email' })]),
|
||||||
password: schema.string(),
|
password: schema.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,27 +5,15 @@ export default class UpdateUserValidator {
|
|||||||
constructor(protected ctx: HttpContextContract) {}
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
|
|
||||||
public schema = schema.create({
|
public schema = schema.create({
|
||||||
name: schema.string.optional(),
|
first_name: schema.string.optional(),
|
||||||
surname: schema.string.optional(),
|
last_name: schema.string.optional(),
|
||||||
patronymic: schema.string.optional(),
|
login: schema.string.optional({}, [rules.unique({ table: 'users', column: 'login' })]),
|
||||||
login: schema.string.optional(),
|
|
||||||
email: schema.string.optional({}, [
|
email: schema.string.optional({}, [
|
||||||
rules.email(),
|
rules.email(),
|
||||||
rules.unique({ table: 'users', column: 'email' }),
|
rules.unique({ table: 'contacts', column: 'email' }),
|
||||||
]),
|
]),
|
||||||
password: schema.string.optional(),
|
password: schema.string.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 = {};
|
public messages = {};
|
||||||
}
|
}
|
||||||
|
|||||||
112
config/auth.ts
Normal file
112
config/auth.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Config source: https://git.io/JY0mp
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AuthConfig } from '@ioc:Adonis/Addons/Auth';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authentication Mapping
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| List of available authentication mapping. You must first define them
|
||||||
|
| inside the `contracts/auth.ts` file before mentioning them here.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
const authConfig: AuthConfig = {
|
||||||
|
guard: 'api',
|
||||||
|
guards: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| OAT Guard
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| OAT (Opaque access tokens) guard uses database backed tokens to authenticate
|
||||||
|
| HTTP request. This guard DOES NOT rely on sessions or cookies and uses
|
||||||
|
| Authorization header value for authentication.
|
||||||
|
|
|
||||||
|
| Use this guard to authenticate mobile apps or web clients that cannot rely
|
||||||
|
| on cookies/sessions.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
api: {
|
||||||
|
driver: 'oat',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Redis provider for managing tokens
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Uses Redis for managing tokens. We recommend using the "redis" driver
|
||||||
|
| over the "database" driver when the tokens based auth is the
|
||||||
|
| primary authentication mode.
|
||||||
|
|
|
||||||
|
| Redis ensure that all the expired tokens gets cleaned up automatically.
|
||||||
|
| Whereas with SQL, you have to cleanup expired tokens manually.
|
||||||
|
|
|
||||||
|
| The foreignKey column is used to make the relationship between the user
|
||||||
|
| and the token. You are free to use any column name here.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
tokenProvider: {
|
||||||
|
type: 'api',
|
||||||
|
driver: 'redis',
|
||||||
|
redisConnection: 'local',
|
||||||
|
foreignKey: 'user_id',
|
||||||
|
},
|
||||||
|
|
||||||
|
provider: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Name of the driver
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
driver: 'lucid',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Identifier key
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The identifier key is the unique key on the model. In most cases specifying
|
||||||
|
| the primary key is the right choice.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
identifierKey: 'id',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Uids
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Uids are used to search a user against one of the mentioned columns. During
|
||||||
|
| login, the auth module will search the user mentioned value against one
|
||||||
|
| of the mentioned columns to find their user record.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
uids: ['email'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Model
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The model to use for fetching or finding users. The model is imported
|
||||||
|
| lazily since the config files are read way earlier in the lifecycle
|
||||||
|
| of booting the app and the models may not be in a usable state at
|
||||||
|
| that time.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
model: () => import('App/Models/User'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default authConfig;
|
||||||
@ -44,6 +44,10 @@ const databaseConfig: DatabaseConfig = {
|
|||||||
},
|
},
|
||||||
migrations: {
|
migrations: {
|
||||||
naturalSort: true,
|
naturalSort: true,
|
||||||
|
tableName: 'migrations',
|
||||||
|
},
|
||||||
|
seeders: {
|
||||||
|
paths: ['./database/seeders/MainSeeder'],
|
||||||
},
|
},
|
||||||
healthCheck: true,
|
healthCheck: true,
|
||||||
debug: true,
|
debug: true,
|
||||||
|
|||||||
48
config/redis.ts
Normal file
48
config/redis.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Config source: https://git.io/JemcF
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Env from '@ioc:Adonis/Core/Env';
|
||||||
|
import { RedisConfig } from '@ioc:Adonis/Addons/Redis';
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Redis configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Following is the configuration used by the Redis provider to connect to
|
||||||
|
| the redis server and execute redis commands.
|
||||||
|
|
|
||||||
|
| Do make sure to pre-define the connections type inside `contracts/redis.ts`
|
||||||
|
| file for AdonisJs to recognize connections.
|
||||||
|
|
|
||||||
|
| Make sure to check `contracts/redis.ts` file for defining extra connections
|
||||||
|
*/
|
||||||
|
const redisConfig: RedisConfig = {
|
||||||
|
connection: Env.get('REDIS_CONNECTION'),
|
||||||
|
|
||||||
|
connections: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| The default connection
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The main connection you want to use to execute redis commands. The same
|
||||||
|
| connection will be used by the session provider, if you rely on the
|
||||||
|
| redis driver.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
local: {
|
||||||
|
host: Env.get('REDIS_HOST'),
|
||||||
|
port: Env.get('REDIS_PORT'),
|
||||||
|
password: Env.get('REDIS_PASSWORD', ''),
|
||||||
|
db: 0,
|
||||||
|
keyPrefix: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default redisConfig;
|
||||||
72
contracts/auth.ts
Normal file
72
contracts/auth.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* Contract source: https://git.io/JOdz5
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import User from 'App/Models/User'
|
||||||
|
|
||||||
|
declare module '@ioc:Adonis/Addons/Auth' {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The providers are used to fetch users. The Auth module comes pre-bundled
|
||||||
|
| with two providers that are `Lucid` and `Database`. Both uses database
|
||||||
|
| to fetch user details.
|
||||||
|
|
|
||||||
|
| You can also create and register your own custom providers.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
interface ProvidersList {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| User Provider
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The following provider uses Lucid models as a driver for fetching user
|
||||||
|
| details from the database for authentication.
|
||||||
|
|
|
||||||
|
| You can create multiple providers using the same underlying driver with
|
||||||
|
| different Lucid models.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
user: {
|
||||||
|
implementation: LucidProviderContract<typeof User>,
|
||||||
|
config: LucidProviderConfig<typeof User>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Guards
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The guards are used for authenticating users using different drivers.
|
||||||
|
| The auth module comes with 3 different guards.
|
||||||
|
|
|
||||||
|
| - SessionGuardContract
|
||||||
|
| - BasicAuthGuardContract
|
||||||
|
| - OATGuardContract ( Opaque access token )
|
||||||
|
|
|
||||||
|
| Every guard needs a provider for looking up users from the database.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
interface GuardsList {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| OAT Guard
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| OAT, stands for (Opaque access tokens) guard uses database backed tokens
|
||||||
|
| to authenticate requests.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
api: {
|
||||||
|
implementation: OATGuardContract<'user', 'api'>,
|
||||||
|
config: OATGuardConfig<'user'>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
12
contracts/redis.ts
Normal file
12
contracts/redis.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Contract source: https://git.io/JemcN
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module '@ioc:Adonis/Addons/Redis' {
|
||||||
|
interface RedisConnectionsList {
|
||||||
|
local: RedisConnectionConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,12 +5,10 @@ export default class Users extends BaseSchema {
|
|||||||
|
|
||||||
public async up() {
|
public async up() {
|
||||||
this.schema.createTable(this.tableName, table => {
|
this.schema.createTable(this.tableName, table => {
|
||||||
table.string('id', 21).primary().notNullable();
|
table.string('id', 21).primary();
|
||||||
table.string('name', 24).notNullable();
|
table.string('first_name', 24).notNullable();
|
||||||
table.string('surname', 24).notNullable();
|
table.string('last_name', 24).notNullable();
|
||||||
table.string('patronymic', 24).notNullable();
|
|
||||||
table.string('login', 128).notNullable().unique();
|
table.string('login', 128).notNullable().unique();
|
||||||
table.string('email', 254).notNullable().unique();
|
|
||||||
table.string('password').notNullable();
|
table.string('password').notNullable();
|
||||||
table.timestamp('created_at', { useTz: true });
|
table.timestamp('created_at', { useTz: true });
|
||||||
table.timestamp('updated_at', { useTz: true });
|
table.timestamp('updated_at', { useTz: true });
|
||||||
19
database/migrations/1626964029348_roles.ts
Normal file
19
database/migrations/1626964029348_roles.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema';
|
||||||
|
|
||||||
|
export default class Roles extends BaseSchema {
|
||||||
|
protected tableName = 'roles';
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, table => {
|
||||||
|
table.increments('id');
|
||||||
|
table.string('name');
|
||||||
|
table.string('slug');
|
||||||
|
table.timestamp('created_at', { useTz: true });
|
||||||
|
table.timestamp('updated_at', { useTz: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
database/migrations/1626970448054_users_roles.ts
Normal file
18
database/migrations/1626970448054_users_roles.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema';
|
||||||
|
|
||||||
|
export default class UsersRoles extends BaseSchema {
|
||||||
|
protected tableName = 'users_roles';
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, table => {
|
||||||
|
table.increments('id');
|
||||||
|
table.string('user_id', 21).unsigned().references('users.id').onDelete('CASCADE');
|
||||||
|
table.integer('role_id').unsigned().references('roles.id').onDelete('CASCADE');
|
||||||
|
table.unique(['user_id', 'role_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
database/migrations/1627022639849_contacts.ts
Normal file
23
database/migrations/1627022639849_contacts.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema';
|
||||||
|
|
||||||
|
export default class Contacts extends BaseSchema {
|
||||||
|
protected tableName = 'contacts';
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, table => {
|
||||||
|
table.increments('id');
|
||||||
|
table.string('user_id', 21).unsigned().references('users.id').onDelete('CASCADE');
|
||||||
|
table.string('email').unique().notNullable();
|
||||||
|
table.string('phone_number').unique();
|
||||||
|
table.string('vk_id').unique();
|
||||||
|
table.string('twitter_id').unique();
|
||||||
|
table.string('telegram_id').unique();
|
||||||
|
table.timestamp('created_at', { useTz: true });
|
||||||
|
table.timestamp('updated_at', { useTz: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
database/seeders/MainSeeder/Index.ts
Normal file
23
database/seeders/MainSeeder/Index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import BaseSeeder from '@ioc:Adonis/Lucid/Seeder';
|
||||||
|
import Application from '@ioc:Adonis/Core/Application';
|
||||||
|
|
||||||
|
export default class IndexSeeder extends BaseSeeder {
|
||||||
|
private async runSeeder(seeder: { default: typeof BaseSeeder }) {
|
||||||
|
const Seeder = seeder.default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not run when not in dev mode and seeder is development
|
||||||
|
* only
|
||||||
|
*/
|
||||||
|
if (Seeder.developmentOnly && !Application.inDev) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Seeder(this.client).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
await this.runSeeder(await import('../Role'));
|
||||||
|
await this.runSeeder(await import('../User'));
|
||||||
|
}
|
||||||
|
}
|
||||||
22
database/seeders/Role.ts
Normal file
22
database/seeders/Role.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/* eslint-disable class-methods-use-this */
|
||||||
|
import BaseSeeder from '@ioc:Adonis/Lucid/Seeder';
|
||||||
|
import Role from 'App/Models/Role';
|
||||||
|
|
||||||
|
export default class RoleSeeder extends BaseSeeder {
|
||||||
|
public async run() {
|
||||||
|
await Role.createMany([
|
||||||
|
{
|
||||||
|
name: 'Administrator',
|
||||||
|
slug: 'admin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Teacher',
|
||||||
|
slug: 'teacher',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Student',
|
||||||
|
slug: 'student',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
database/seeders/User.ts
Normal file
56
database/seeders/User.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/* eslint-disable class-methods-use-this */
|
||||||
|
import BaseSeeder from '@ioc:Adonis/Lucid/Seeder';
|
||||||
|
import Role from 'App/Models/Role';
|
||||||
|
import User from 'App/Models/User';
|
||||||
|
|
||||||
|
export default class UserSeeder extends BaseSeeder {
|
||||||
|
public async run() {
|
||||||
|
/**
|
||||||
|
* Roles
|
||||||
|
*/
|
||||||
|
const roles = {
|
||||||
|
admin: await Role.findByOrFail('slug', 'admin'),
|
||||||
|
teacher: await Role.findByOrFail('slug', 'teacher'),
|
||||||
|
student: await Role.findByOrFail('slug', 'student'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Administrator
|
||||||
|
*/
|
||||||
|
const user1 = new User();
|
||||||
|
user1.first_name = 'Ivan';
|
||||||
|
user1.last_name = 'Ivanov';
|
||||||
|
user1.login = 'ivan123';
|
||||||
|
user1.password = 'ivan123';
|
||||||
|
|
||||||
|
await user1.save();
|
||||||
|
await user1.related('roles').attach([roles.admin.id]);
|
||||||
|
await user1.related('contacts').create({ email: 'ivan_ivanov@ya.ru' });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teacher
|
||||||
|
*/
|
||||||
|
const user2 = new User();
|
||||||
|
user2.first_name = 'Nikolai';
|
||||||
|
user2.last_name = 'Nikolaev';
|
||||||
|
user2.login = 'nikolai123';
|
||||||
|
user2.password = 'nikolai123';
|
||||||
|
|
||||||
|
await user2.save();
|
||||||
|
await user2.related('roles').attach([roles.teacher.id]);
|
||||||
|
await user2.related('contacts').create({ email: 'nikolai_niko123@ya.ru' });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Student
|
||||||
|
*/
|
||||||
|
const user3 = new User();
|
||||||
|
user3.first_name = 'Oleg';
|
||||||
|
user3.last_name = 'Olegov';
|
||||||
|
user3.login = 'oleg123';
|
||||||
|
user3.password = 'oleg123';
|
||||||
|
|
||||||
|
await user3.save();
|
||||||
|
await user3.related('roles').attach([roles.student.id]);
|
||||||
|
await user3.related('contacts').create({ email: 'oleg_olegov123@ya.ru' });
|
||||||
|
}
|
||||||
|
}
|
||||||
4
env.ts
4
env.ts
@ -25,4 +25,8 @@ export default Env.rules({
|
|||||||
PG_USER: Env.schema.string(),
|
PG_USER: Env.schema.string(),
|
||||||
PG_PASSWORD: Env.schema.string.optional(),
|
PG_PASSWORD: Env.schema.string.optional(),
|
||||||
PG_DB_NAME: Env.schema.string(),
|
PG_DB_NAME: Env.schema.string(),
|
||||||
|
REDIS_CONNECTION: Env.schema.enum(['local'] as const),
|
||||||
|
REDIS_HOST: Env.schema.string({ format: 'host' }),
|
||||||
|
REDIS_PORT: Env.schema.number(),
|
||||||
|
REDIS_PASSWORD: Env.schema.string.optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
85
package-lock.json
generated
85
package-lock.json
generated
@ -237,6 +237,16 @@
|
|||||||
"jest-worker": "^27.0.6"
|
"jest-worker": "^27.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@adonisjs/redis": {
|
||||||
|
"version": "7.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@adonisjs/redis/-/redis-7.0.7.tgz",
|
||||||
|
"integrity": "sha512-M9nn2uaE8IJ25mMkoPQtrsvfjewjZSfOr2F2RzySDq/ymoOQ5RcBDrgZ40E/JUqPqDOV5zmU2E0ClEwujQFPmg==",
|
||||||
|
"requires": {
|
||||||
|
"@poppinss/utils": "^3.1.3",
|
||||||
|
"@types/ioredis": "^4.26.4",
|
||||||
|
"ioredis": "^4.27.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@adonisjs/repl": {
|
"@adonisjs/repl": {
|
||||||
"version": "3.1.4",
|
"version": "3.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@adonisjs/repl/-/repl-3.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@adonisjs/repl/-/repl-3.1.4.tgz",
|
||||||
@ -668,6 +678,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/he/-/he-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/he/-/he-1.1.2.tgz",
|
||||||
"integrity": "sha512-kSJPcLO1x+oolc0R89pUl2kozldQ/fVQ1C1p5mp8fPoLdF/ZcBvckaTC2M8xXh3GYendXvCpy5m/a2eSbfgNgw=="
|
"integrity": "sha512-kSJPcLO1x+oolc0R89pUl2kozldQ/fVQ1C1p5mp8fPoLdF/ZcBvckaTC2M8xXh3GYendXvCpy5m/a2eSbfgNgw=="
|
||||||
},
|
},
|
||||||
|
"@types/ioredis": {
|
||||||
|
"version": "4.26.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.26.5.tgz",
|
||||||
|
"integrity": "sha512-aP/SUSFShzoVYcHHKKEq7+kQ5YaDsMRe64rcHCp+w4Unkfw2tico1vA6siPnMDe7+jIxuZRU/h8km6uZjEtswg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/json-schema": {
|
"@types/json-schema": {
|
||||||
"version": "7.0.8",
|
"version": "7.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz",
|
||||||
@ -1643,6 +1661,11 @@
|
|||||||
"string-width": "^4.2.0"
|
"string-width": "^4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cluster-key-slot": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
|
||||||
|
},
|
||||||
"co-compose": {
|
"co-compose": {
|
||||||
"version": "6.1.3",
|
"version": "6.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/co-compose/-/co-compose-6.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/co-compose/-/co-compose-6.1.3.tgz",
|
||||||
@ -1938,6 +1961,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||||
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
|
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
|
||||||
},
|
},
|
||||||
|
"denque": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ=="
|
||||||
|
},
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||||
@ -3381,6 +3409,30 @@
|
|||||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
|
||||||
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw=="
|
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw=="
|
||||||
},
|
},
|
||||||
|
"ioredis": {
|
||||||
|
"version": "4.27.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.27.6.tgz",
|
||||||
|
"integrity": "sha512-6W3ZHMbpCa8ByMyC1LJGOi7P2WiOKP9B3resoZOVLDhi+6dDBOW+KNsRq3yI36Hmnb2sifCxHX+YSarTeXh48A==",
|
||||||
|
"requires": {
|
||||||
|
"cluster-key-slot": "^1.1.0",
|
||||||
|
"debug": "^4.3.1",
|
||||||
|
"denque": "^1.1.0",
|
||||||
|
"lodash.defaults": "^4.2.0",
|
||||||
|
"lodash.flatten": "^4.4.0",
|
||||||
|
"p-map": "^2.1.0",
|
||||||
|
"redis-commands": "1.7.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0",
|
||||||
|
"standard-as-callback": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"p-map": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ipaddr.js": {
|
"ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@ -3835,6 +3887,16 @@
|
|||||||
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
|
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.defaults": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
|
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
||||||
|
},
|
||||||
|
"lodash.flatten": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||||
|
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
|
||||||
|
},
|
||||||
"lodash.merge": {
|
"lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@ -5266,6 +5328,24 @@
|
|||||||
"esprima": "~4.0.0"
|
"esprima": "~4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redis-commands": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
|
||||||
|
},
|
||||||
|
"redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
|
||||||
|
},
|
||||||
|
"redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
|
||||||
|
"requires": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"reflect-metadata": {
|
"reflect-metadata": {
|
||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||||
@ -5824,6 +5904,11 @@
|
|||||||
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
|
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"standard-as-callback": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
|
||||||
|
},
|
||||||
"static-extend": {
|
"static-extend": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
"@adonisjs/auth": "^8.0.7",
|
"@adonisjs/auth": "^8.0.7",
|
||||||
"@adonisjs/core": "^5.1.9",
|
"@adonisjs/core": "^5.1.9",
|
||||||
"@adonisjs/lucid": "^15.0.3",
|
"@adonisjs/lucid": "^15.0.3",
|
||||||
|
"@adonisjs/redis": "^7.0.7",
|
||||||
"@adonisjs/repl": "^3.1.4",
|
"@adonisjs/repl": "^3.1.4",
|
||||||
"luxon": "^1.28.0",
|
"luxon": "^1.28.0",
|
||||||
"nanoid": "^3.1.23",
|
"nanoid": "^3.1.23",
|
||||||
|
|||||||
@ -38,4 +38,7 @@ Server.middleware.register([() => import('@ioc:Adonis/Core/BodyParser')]);
|
|||||||
| Route.get('dashboard', 'UserController.dashboard').middleware('auth')
|
| Route.get('dashboard', 'UserController.dashboard').middleware('auth')
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
Server.middleware.registerNamed({});
|
Server.middleware.registerNamed({
|
||||||
|
auth: () => import('App/Middleware/Auth'),
|
||||||
|
role: () => import('App/Middleware/Role'),
|
||||||
|
});
|
||||||
|
|||||||
@ -23,9 +23,18 @@ import Route from '@ioc:Adonis/Core/Route';
|
|||||||
Route.get('/', () => `API REST Server.`);
|
Route.get('/', () => `API REST Server.`);
|
||||||
|
|
||||||
Route.group(() => {
|
Route.group(() => {
|
||||||
Route.resource('users', 'UsersController').apiOnly();
|
/* Auth */
|
||||||
// Route.get('/users', 'UsersController.index');
|
Route.post('login', 'AuthController.login');
|
||||||
// Route.get('/users/:id', 'UsersController.show');
|
Route.post('logout', 'AuthController.logout').middleware('auth');
|
||||||
// Route.post('/users', 'UsersController.store');
|
|
||||||
// Route.put('/users/:id', 'UsersController.update');
|
/* API */
|
||||||
|
Route.group(() => {
|
||||||
|
Route.get('users', 'UsersController.index').middleware('role:admin,teacher,student');
|
||||||
|
Route.get('users/:id', 'UsersController.show').middleware('role:admin,teacher,student');
|
||||||
|
Route.post('users', 'UsersController.store').middleware('role:admin');
|
||||||
|
Route.patch('users/:id', 'UsersController.update').middleware('role:admin');
|
||||||
|
Route.delete('users', 'UsersController.destroy').middleware('role:admin');
|
||||||
|
Route.post('users/:id/roles', 'RolesController.store').middleware('role:admin');
|
||||||
|
Route.delete('users/:id/roles', 'RolesController.destroy').middleware('role:admin');
|
||||||
|
}).middleware('auth');
|
||||||
}).prefix('api');
|
}).prefix('api');
|
||||||
|
|||||||
@ -28,7 +28,9 @@
|
|||||||
"types": [
|
"types": [
|
||||||
"@adonisjs/core",
|
"@adonisjs/core",
|
||||||
"@adonisjs/repl",
|
"@adonisjs/repl",
|
||||||
"@adonisjs/lucid"
|
"@adonisjs/lucid",
|
||||||
|
"@adonisjs/auth",
|
||||||
|
"@adonisjs/redis"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user