diff --git a/.adonisrc.json b/.adonisrc.json index 0b74065..a13c766 100644 --- a/.adonisrc.json +++ b/.adonisrc.json @@ -13,18 +13,7 @@ "Database": "database", "Contracts": "contracts" }, - "preloads": [ - "./start/routes", - "./start/kernel" - ], - "providers": [ - "./providers/AppProvider", - "@adonisjs/core", - "@adonisjs/lucid", - "@adonisjs/auth", - "@adonisjs/redis" - ], - "aceProviders": [ - "@adonisjs/repl" - ] + "preloads": ["./routes/index", "./start/kernel"], + "providers": ["./providers/AppProvider", "@adonisjs/core", "@adonisjs/lucid", "@adonisjs/auth", "@adonisjs/redis"], + "aceProviders": ["@adonisjs/repl"] } diff --git a/app/Controllers/Http/AuthController.ts b/app/Controllers/Http/AuthController.ts index 23daf78..2ba8314 100644 --- a/app/Controllers/Http/AuthController.ts +++ b/app/Controllers/Http/AuthController.ts @@ -1,16 +1,20 @@ 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'; +/** + * Models + */ +import User from 'App/Models/User'; + export default class AuthController { - private user: typeof User; + private User: typeof User; private guard: 'api'; constructor() { - this.user = User; + this.User = User; } /** @@ -21,7 +25,7 @@ export default class AuthController { 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(); + const user = await this.User.query().preload('roles').where('login', login).firstOrFail(); if (!(await Hash.verify(user.password, password))) { throw new Exception('Invalid credentials.', 403, 'E_INVALID_CREDENTIALS'); diff --git a/app/Controllers/Http/CoursesController.ts b/app/Controllers/Http/CoursesController.ts index 79d62b2..a0b4ad2 100644 --- a/app/Controllers/Http/CoursesController.ts +++ b/app/Controllers/Http/CoursesController.ts @@ -1,7 +1,15 @@ import { Exception } from '@adonisjs/core/build/standalone'; import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; + +/** + * Models + */ import Course from 'App/Models/Course'; import User from 'App/Models/User'; + +/** + * Validators + */ import AddCourseToUserValidator from 'App/Validators/AddCourseToUserValidator'; import CreateCourseValidator from 'App/Validators/CreateCourseValidator'; import DelCourseFromUserValidator from 'App/Validators/DelCourseFromUserValidator'; @@ -21,7 +29,7 @@ export default class CoursesController { * List of all Courses * GET /courses */ - public async index({ response }: HttpContextContract) { + public async list({ response }: HttpContextContract) { const courses = await this.Course.query().preload('teacher').preload('category').preload('lessons'); return response.ok({ @@ -145,7 +153,7 @@ export default class CoursesController { * Delete Course by "id" * DELETE /courses/:id */ - async destroy({ response, params }: HttpContextContract) { + public async delete({ response, params }: HttpContextContract) { const course = await this.Course.query() .preload('teacher') .preload('category') @@ -165,7 +173,7 @@ export default class CoursesController { * Update Course by "id" * PATCH /courses/:id */ - async update({ response, request, params }: HttpContextContract) { + public async update({ response, request, params }: HttpContextContract) { const payload = await request.validate(UpdateCourseValidator); const course = await this.Course.findOrFail(params.id); @@ -193,7 +201,7 @@ export default class CoursesController { * Attach student to course * POST /courses/:id/attach-student */ - async attachStudent({ response, request, params }: HttpContextContract) { + public async attachStudent({ response, request, params }: HttpContextContract) { try { const payload = await request.validate(AddCourseToUserValidator); const student = await this.User.findOrFail(payload.student_id); @@ -225,7 +233,7 @@ export default class CoursesController { /** * Detach student from course */ - async detachStudent({ response, request, params }: HttpContextContract) { + public async detachStudent({ response, request, params }: HttpContextContract) { const payload = await request.validate(DelCourseFromUserValidator); const course = await this.Course.query().preload('students').where('id', params.id).firstOrFail(); const candidate = course.students.find(student => student.id === payload.student_id); diff --git a/app/Controllers/Http/MeController.ts b/app/Controllers/Http/MeController.ts index 87ed611..e91237d 100644 --- a/app/Controllers/Http/MeController.ts +++ b/app/Controllers/Http/MeController.ts @@ -2,24 +2,32 @@ import { Exception } from '@adonisjs/core/build/standalone'; import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; import Hash from '@ioc:Adonis/Core/Hash'; import Logger from '@ioc:Adonis/Core/Logger'; -import ChangePasswordValidator from 'App/Validators/ChangePasswordValidator'; + +/** + * Models + */ import Contact from 'App/Models/Contact'; + +/** + * Validators + */ +import ChangePasswordValidator from 'App/Validators/ChangePasswordValidator'; import UpdateContactsValidator from 'App/Validators/UpdateContactsValidator'; export default class MeController { private readonly guard: 'api'; - private readonly contact: typeof Contact; + private readonly Contact: typeof Contact; constructor() { - this.contact = Contact; + this.Contact = Contact; } /** * Shows user info of the access token resource owner. * GET /me */ - public async index({ response, auth }: HttpContextContract) { + public async show({ response, auth }: HttpContextContract) { const user = await auth.use(this.guard).authenticate(); await user.load('contacts'); @@ -73,7 +81,7 @@ export default class MeController { public async updateContacts({ request, response, auth }: HttpContextContract) { const payload = await request.validate(UpdateContactsValidator); const user = await auth.use(this.guard).authenticate(); - const contacts = await this.contact.findByOrFail('user_id', user.id); + const contacts = await this.Contact.findByOrFail('user_id', user.id); /** * Update contacts diff --git a/app/Controllers/Http/UsersController.ts b/app/Controllers/Http/UsersController.ts index 92de751..70c9d19 100644 --- a/app/Controllers/Http/UsersController.ts +++ b/app/Controllers/Http/UsersController.ts @@ -1,20 +1,28 @@ import { Exception } from '@adonisjs/core/build/standalone'; import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; + +/** + * Models + */ import Role from 'App/Models/Role'; import User from 'App/Models/User'; + +/** + * Validators + */ import AddRoleToUserValidator from 'App/Validators/AddRoleToUserValidator'; import CreateUserValidator from 'App/Validators/CreateUserValidator'; import DelRoleFromUserValidator from 'App/Validators/DelRoleFromUserValidator'; import UpdateUserValidator from 'App/Validators/UpdateUserValidator'; export default class UsersController { - private readonly user: typeof User; + private readonly User: typeof User; - private readonly role: typeof Role; + private readonly Role: typeof Role; constructor() { - this.user = User; - this.role = Role; + this.User = User; + this.Role = Role; } /** @@ -22,8 +30,8 @@ export default class UsersController { * GET /users */ - public async index({ response }: HttpContextContract) { - const users = await this.user.query().preload('contacts').preload('roles'); + public async list({ response }: HttpContextContract) { + const users = await this.User.query().preload('contacts').preload('roles'); return response.ok({ message: 'Fetched all users.', @@ -37,7 +45,7 @@ export default class UsersController { */ public async show({ response, params }: HttpContextContract) { - const user = await this.user.findOrFail(params.id); + const user = await this.User.findOrFail(params.id); await user.load('contacts'); await user.load('roles'); @@ -56,7 +64,7 @@ export default class UsersController { public async create({ response, request }: HttpContextContract) { await request.validate(CreateUserValidator); - const user = await this.user.create({ + const user = await this.User.create({ first_name: request.input('first_name'), last_name: request.input('last_name'), login: request.input('login'), @@ -77,7 +85,7 @@ export default class UsersController { public async update({ request, response, params }: HttpContextContract) { await request.validate(UpdateUserValidator); - const user = await this.user.findOrFail(params.id); + const user = await this.User.findOrFail(params.id); const input: Pick = { first_name: request.input('first_name'), last_name: request.input('last_name'), @@ -109,8 +117,8 @@ export default class UsersController { * DELETE /users/:id */ - public async destroy({ response, params }: HttpContextContract) { - const user = await this.user.query().preload('contacts').preload('roles').where('id', params.id).firstOrFail(); + public async delete({ response, params }: HttpContextContract) { + const user = await this.User.query().preload('contacts').preload('roles').where('id', params.id).firstOrFail(); await user.delete(); @@ -128,10 +136,10 @@ export default class UsersController { await request.validate(AddRoleToUserValidator); const input: string[] | undefined = request.input('roles'); - const user = await this.user.query().preload('roles').where('id', params.id).firstOrFail(); + const user = await this.User.query().preload('roles').where('id', params.id).firstOrFail(); const roles = await this.findRolesBySlug(input); - await this.user.attachRoles(user, roles); + await this.User.attachRoles(user, roles); return response.ok({ message: 'Roles attached', @@ -147,10 +155,10 @@ export default class UsersController { await request.validate(DelRoleFromUserValidator); const input: string[] | undefined = request.input('roles'); - const user = await this.user.query().preload('roles').where('id', params.id).firstOrFail(); + const user = await this.User.query().preload('roles').where('id', params.id).firstOrFail(); const roles = await this.findRolesBySlug(input); - await this.user.detachRoles(user, roles); + await this.User.detachRoles(user, roles); return response.ok({ message: 'Roles detached', @@ -169,7 +177,7 @@ export default class UsersController { throw new Exception('Unable to find "roles" field in input.', 400, 'E_ROLES_FIELD_NOT_PROVIDED'); } - const roles = await this.role.query().whereIn('slug', input); + const roles = await this.Role.query().whereIn('slug', input); input.forEach(val => { if (!roles.map(r => r.slug).includes(val)) { diff --git a/routes/api.ts b/routes/api.ts new file mode 100644 index 0000000..510a5d1 --- /dev/null +++ b/routes/api.ts @@ -0,0 +1,8 @@ +/* eslint-disable global-require */ +import Route from '@ioc:Adonis/Core/Route'; + +Route.group(() => { + require('./apis/v1/index'); +}) + .prefix('api') + .as('api'); diff --git a/routes/apis/v1/auth.ts b/routes/apis/v1/auth.ts new file mode 100644 index 0000000..9c6cd22 --- /dev/null +++ b/routes/apis/v1/auth.ts @@ -0,0 +1,6 @@ +import Route from '@ioc:Adonis/Core/Route'; + +Route.group(() => { + Route.post('login', 'AuthController.login').as('auth.login'); + Route.post('logout', 'AuthController.logout').middleware('auth').as('auth.logout'); +}).prefix('auth'); diff --git a/routes/apis/v1/courses.ts b/routes/apis/v1/courses.ts new file mode 100644 index 0000000..87b3cda --- /dev/null +++ b/routes/apis/v1/courses.ts @@ -0,0 +1,32 @@ +import Route from '@ioc:Adonis/Core/Route'; + +Route.group(() => { + Route.get('/', 'CoursesController.list').middleware('role:admin,teacher,student').as('courses.list'); + Route.post('/', 'CoursesController.create').middleware('role:admin,teacher').as('courses.create'); + Route.get('/:id', 'CoursesController.show').middleware('role:admin,teacher,student').as('courses.show'); + Route.delete('/:id', 'CoursesController.delete').middleware('role:admin,teacher').as('courses.delete'); + Route.patch('/:id', 'CoursesController.update').middleware('role:admin,teacher').as('courses.update'); + Route.get('/:id/teacher', 'CoursesController.showTeacher') + .middleware('role:admin,teacher,student') + .as('courses.show-teacher'); + + Route.get('/:id/category', 'CoursesController.showCategory') + .middleware('role:admin,teacher,student') + .as('courses.show-category'); + + Route.get('/:id/lessons', 'CoursesController.showLessons') + .middleware('role:admin,teacher,student') + .as('courses.show-lessons'); + + Route.get('/:id/students', 'CoursesController.showStudents') + .middleware('role:admin,teacher,student') + .as('courses.show-students'); + + Route.post('/:id/attach-student', 'CoursesController.attachStudent') + .middleware('role:admin,teacher') + .as('courses.attach-student'); + + Route.delete('/:id/detach-student', 'CoursesController.detachStudent') + .middleware('role:admin,teacher') + .as('courses.detach-student'); +}).prefix('courses'); diff --git a/routes/apis/v1/index.ts b/routes/apis/v1/index.ts new file mode 100644 index 0000000..edbe4c0 --- /dev/null +++ b/routes/apis/v1/index.ts @@ -0,0 +1,40 @@ +/* eslint-disable global-require */ +import Route from '@ioc:Adonis/Core/Route'; + +Route.group(() => { + Route.get('/', () => `Educt Backend API.`).as('index'); + + /** + * + * Auth module + * + */ + require('./auth'); + + Route.group(() => { + /** + * + * Users module + * + */ + require('./users'); + + /** + * + * Me module + * + */ + + require('./me'); + + /** + * + * Courses module + * + */ + + require('./courses'); + }).middleware('auth'); +}) + .prefix('v1') + .as('v1'); diff --git a/routes/apis/v1/me.ts b/routes/apis/v1/me.ts new file mode 100644 index 0000000..c648283 --- /dev/null +++ b/routes/apis/v1/me.ts @@ -0,0 +1,11 @@ +import Route from '@ioc:Adonis/Core/Route'; + +Route.group(() => { + Route.get('/', 'MeController.show').middleware('role:admin,teacher,student').as('me.show'); + Route.patch('/password', 'MeController.changePassword') + .middleware('role:admin,teacher,student') + .as('me.change-password'); + Route.put('/contacts', 'MeController.updateContacts') + .middleware('role:admin,teacher,student') + .as('me.update-contacts'); +}).prefix('me'); diff --git a/routes/apis/v1/users.ts b/routes/apis/v1/users.ts new file mode 100644 index 0000000..5a580ce --- /dev/null +++ b/routes/apis/v1/users.ts @@ -0,0 +1,11 @@ +import Route from '@ioc:Adonis/Core/Route'; + +Route.group(() => { + Route.get('/', 'UsersController.list').middleware('role:admin,teacher,student').as('users.list'); + Route.post('/', 'UsersController.create').middleware('role:admin').as('users.create'); + Route.get('/:id', 'UsersController.show').middleware('role:admin,teacher,student').as('users.show'); + Route.patch('/:id', 'UsersController.update').middleware('role:admin').as('users.update'); + Route.delete('/:id', 'UsersController.delete').middleware('role:admin').as('users.delete'); + Route.post('/:id/attach-roles', 'UsersController.attachRoles').middleware('role:admin').as('users.attach-role'); + Route.delete('/:id/detach-roles', 'UsersController.detachRoles').middleware('role:admin').as('users.detach-role'); +}).prefix('users'); diff --git a/routes/index.ts b/routes/index.ts new file mode 100644 index 0000000..1172edc --- /dev/null +++ b/routes/index.ts @@ -0,0 +1,26 @@ +/* eslint-disable global-require */ +import Route from '@ioc:Adonis/Core/Route'; + +/* +|-------------------------------------------------------------------------- +| Routes +|-------------------------------------------------------------------------- +| +| This file is dedicated for defining HTTP routes. A single file is enough +| for majority of projects, however you can define routes in different +| files and just make sure to import them inside this file. For example +| +| Define routes in following two files +| ├── start/routes/cart.ts +| ├── start/routes/customer.ts +| +| and then import them inside `start/routes.ts` as follows +| +| import './routes/cart' +| import './routes/customer' +| +*/ + +Route.group(() => { + require('./api'); +}); diff --git a/start/routes.ts b/start/routes.ts deleted file mode 100644 index 5ce9541..0000000 --- a/start/routes.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* -|-------------------------------------------------------------------------- -| Routes -|-------------------------------------------------------------------------- -| -| This file is dedicated for defining HTTP routes. A single file is enough -| for majority of projects, however you can define routes in different -| files and just make sure to import them inside this file. For example -| -| Define routes in following two files -| ├── start/routes/cart.ts -| ├── start/routes/customer.ts -| -| and then import them inside `start/routes.ts` as follows -| -| import './routes/cart' -| import './routes/customer' -| -*/ - -import Route from '@ioc:Adonis/Core/Route'; - -Route.get('/', () => `Educt Backend API.`); - -Route.group(() => { - /* Auth controller */ - Route.post('login', 'AuthController.login').as('login'); - Route.post('logout', 'AuthController.logout').middleware('auth').as('logout'); - - /* API */ - Route.group(() => { - /** - * Users controller - */ - Route.get('users', 'UsersController.index').middleware('role:admin,teacher,student').as('showAllUsers'); - Route.post('users', 'UsersController.create').middleware('role:admin').as('createUser'); - Route.get('users/:id', 'UsersController.show').middleware('role:admin,teacher,student').as('showUserById'); - Route.patch('users/:id', 'UsersController.update').middleware('role:admin').as('updateUser'); - Route.delete('users/:id', 'UsersController.destroy').middleware('role:admin').as('deleteUser'); - Route.post('users/:id/attach-roles', 'UsersController.attachRoles').middleware('role:admin').as('attachUserRole'); - Route.delete('users/:id/detach-roles', 'UsersController.detachRoles').middleware('role:admin').as('detachUserRole'); - - /** - * Me controller - */ - Route.get('me', 'MeController.index').middleware('role:admin,teacher,student').as('showMe'); - Route.patch('me/password', 'MeController.changePassword') - .middleware('role:admin,teacher,student') - .as('changePassword'); - - Route.put('me/contacts', 'MeController.updateContacts') - .middleware('role:admin,teacher,student') - .as('updateContacts'); - - /** - * Courses controller - */ - Route.get('courses', 'CoursesController.index').middleware('role:admin,teacher,student').as('showAllCourses'); - Route.post('courses', 'CoursesController.create').middleware('role:admin,teacher').as('createCourse'); - Route.get('courses/:id', 'CoursesController.show').middleware('role:admin,teacher,student').as('showCourseById'); - Route.delete('courses/:id', 'CoursesController.destroy').middleware('role:admin,teacher').as('deleteCourseById'); - Route.patch('courses/:id', 'CoursesController.update').middleware('role:admin,teacher').as('updateCourse'); - Route.get('courses/:id/teacher', 'CoursesController.showTeacher') - .middleware('role:admin,teacher,student') - .as('showCourseTeacher'); - - Route.get('courses/:id/category', 'CoursesController.showCategory') - .middleware('role:admin,teacher,student') - .as('showCourseCategory'); - - Route.get('courses/:id/lessons', 'CoursesController.showLessons') - .middleware('role:admin,teacher,student') - .as('showCourseLessons'); - - Route.get('courses/:id/students', 'CoursesController.showStudents') - .middleware('role:admin,teacher,student') - .as('showCourseStudents'); - - Route.post('courses/:id/attach-student', 'CoursesController.attachStudent') - .middleware('role:admin,teacher') - .as('attachUserCourse'); - - Route.delete('courses/:id/detach-student', 'CoursesController.detachStudent') - .middleware('role:admin,teacher') - .as('detachUserCourse'); - }).middleware('auth'); -}) - .prefix('/api/v1') - .as('api');