lessons crud & some refactor

This commit is contained in:
Sergey Yarkov 2021-08-03 22:20:55 +03:00
parent 619bc2bc9d
commit 6979e0b912
26 changed files with 255 additions and 20 deletions

View File

@ -1,6 +1,11 @@
import { Exception } from '@adonisjs/core/build/standalone';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
/**
* Datatype
*/
import Role from 'App/Datatype/Role';
/**
* Models
*/
@ -10,10 +15,10 @@ 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';
import UpdateCourseValidator from 'App/Validators/UpdateCourseValidator';
import AddCourseToUserValidator from 'App/Validators/Course/AddCourseToUserValidator';
import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator';
import DelCourseFromUserValidator from 'App/Validators/Course/DelCourseFromUserValidator';
import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator';
export default class CoursesController {
private readonly Course: typeof Course;
@ -130,7 +135,7 @@ export default class CoursesController {
* Check user on role Teacher
*/
const roles = teacher.roles.map(r => r.slug);
if (!roles.includes('teacher')) {
if (!roles.includes(Role.TEACHER)) {
throw new Exception(`User with id "${teacher.id}" not teacher.`, 400, 'ERR_USER_NOT_TEACHER');
}
@ -212,7 +217,7 @@ export default class CoursesController {
const studentRoles = student.roles.map(r => r.slug);
if (!studentRoles.includes('student')) {
if (!studentRoles.includes(Role.STUDENT)) {
throw new Exception(`User cannot be attached to the course without "student" role.`, 400, 'E_USER_ROLE');
}

View File

@ -0,0 +1,110 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
/**
* Models
*/
import Lesson from 'App/Models/Lesson';
import Course from 'App/Models/Course';
/**
* Validators
*/
import CreateLessonValidator from 'App/Validators/Lesson/CreateLessonValidator';
import UpdateLessonValidator from 'App/Validators/Lesson/UpdateLessonValidator';
import BaseController from '../../BaseController';
export default class LessonsController extends BaseController {
private readonly Lesson: typeof Lesson;
private readonly Course: typeof Course;
constructor() {
super();
this.Lesson = Lesson;
this.Course = Course;
}
/**
* List of all lessons
* GET /lessons
*/
public async list(ctx: HttpContextContract) {
const lessons = await this.Lesson.query();
return this.sendResponse(ctx, lessons, 'Fetched list lessons.');
}
/**
* Get one by id
* GET /lessons/:id
*/
public async show(ctx: HttpContextContract) {
const lesson = await this.Lesson.findOrFail(ctx.params.id);
return this.sendResponse(ctx, lesson, 'Fetched lesson by id.');
}
/**
* Create new lesson
* POST /lessons
*/
public async create(ctx: HttpContextContract) {
const payload = await ctx.request.validate(CreateLessonValidator);
const course = await this.Course.findOrFail(payload.course_id);
const lesson = await this.Lesson.create({
title: payload.title,
description: payload.description,
});
await lesson.related('course').associate(course);
await lesson.related('content').create({
video_url: payload.video_url,
});
await lesson.load('content');
return this.sendResponse(ctx, lesson, 'Created new lesson.');
}
/**
* Delete lesson by id.
* DELETE /lessons/:id
*/
public async delete(ctx: HttpContextContract) {
const lesson = await this.Lesson.findOrFail(ctx.params.id);
await lesson.delete();
return this.sendResponse(ctx, lesson, 'Lesson deleted.');
}
/**
* Update lesson by id.
* PUT /lessons/:id
*/
public async update(ctx: HttpContextContract) {
const payload = await ctx.request.validate(UpdateLessonValidator);
const lesson = await this.Lesson.firstOrFail(ctx.params.id);
await lesson
.merge({
course_id: payload.course_id,
title: payload.title,
description: payload.description,
})
.save();
return this.sendResponse(ctx, lesson, 'Lesson updated.');
}
/**
* Get lesson content by id
* GET /lessons/:id/content
*/
public async getContent(ctx: HttpContextContract) {
const lesson = await this.Lesson.query().preload('content').where('id', ctx.params.id).firstOrFail();
return this.sendResponse(ctx, lesson.content, 'Lesson content fetched.');
}
}

View File

@ -11,8 +11,8 @@ import Contact from 'App/Models/Contact';
/**
* Validators
*/
import ChangePasswordValidator from 'App/Validators/ChangePasswordValidator';
import UpdateContactsValidator from 'App/Validators/UpdateContactsValidator';
import ChangePasswordValidator from 'App/Validators/Password/ChangePasswordValidator';
import UpdateContactsValidator from 'App/Validators/Contacts/UpdateContactsValidator';
export default class MeController {
private readonly guard: 'api';

View File

@ -10,17 +10,20 @@ 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';
import AddRoleToUserValidator from 'App/Validators/User/AddRoleToUserValidator';
import CreateUserValidator from 'App/Validators/User/CreateUserValidator';
import DelRoleFromUserValidator from 'App/Validators/User/DelRoleFromUserValidator';
import UpdateUserValidator from 'App/Validators/User/UpdateUserValidator';
export default class UsersController {
import BaseController from '../../BaseController';
export default class UsersController extends BaseController {
private readonly User: typeof User;
private readonly Role: typeof Role;
constructor() {
super();
this.User = User;
this.Role = Role;
}

View File

@ -0,0 +1,14 @@
/* eslint-disable class-methods-use-this */
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
export default class BaseController {
public sendResponse(ctx: HttpContextContract, data: object, message: string, status = 200): void {
const response = {
status,
message,
data,
};
return ctx.response.status(response.status).send(response);
}
}

7
app/Datatype/Role.ts Normal file
View File

@ -0,0 +1,7 @@
enum Role {
ADMIN = 'admin',
TEACHER = 'teacher',
STUDENT = 'student',
}
export default Role;

View File

@ -5,14 +5,14 @@ export default class Role {
error: {
message: string;
status: number;
errorCode: string;
code: string;
};
constructor() {
this.error = {
message: 'You dont have permission to perform that action.',
status: 403,
errorCode: 'E_ACCESS_DENIED',
code: 'E_ACCESS_DENIED',
};
}
@ -28,7 +28,7 @@ export default class Role {
/**
* Failed to verify user rights
*/
throw new Exception(this.error.message, this.error.status, this.error.errorCode);
throw new Exception(this.error.message, this.error.status, this.error.code);
}
}

View File

@ -1,7 +1,9 @@
/* eslint-disable no-param-reassign */
import { DateTime } from 'luxon';
import { nanoid } from 'nanoid';
import { BaseModel, beforeCreate, column } from '@ioc:Adonis/Lucid/Orm';
import { BaseModel, beforeCreate, BelongsTo, belongsTo, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm';
import Course from './Course';
import LessonContent from './LessonContent';
export default class Lesson extends BaseModel {
@column({ isPrimary: true })
@ -16,6 +18,16 @@ export default class Lesson extends BaseModel {
@column()
public description: string;
@belongsTo(() => Course, {
foreignKey: 'course_id',
})
public course: BelongsTo<typeof Course>;
@hasOne(() => LessonContent, {
foreignKey: 'lesson_id',
})
public content: HasOne<typeof LessonContent>;
@column.dateTime({ autoCreate: true, serializeAs: null })
public createdAt: DateTime;

View File

@ -0,0 +1,19 @@
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm';
import { DateTime } from 'luxon';
export default class LessonContent extends BaseModel {
@column({ isPrimary: true })
public id: string;
@column()
public lesson_id: string;
@column()
public video_url: string;
@column.dateTime({ autoCreate: true, serializeAs: null })
public createdAt: DateTime;
@column.dateTime({ autoCreate: true, autoUpdate: true, serializeAs: null })
public updatedAt: DateTime;
}

View File

@ -73,7 +73,7 @@ export default class User extends BaseModel {
});
await user.related('roles').attach(inputRoles.map(r => r.id));
await user.load('roles'); // load updated roles
await user.load('roles');
}
/**

View File

@ -0,0 +1,15 @@
import { schema, rules } from '@ioc:Adonis/Core/Validator';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
export default class CreateLessonValidator {
constructor(protected ctx: HttpContextContract) {}
public schema = schema.create({
course_id: schema.string({}, [rules.exists({ table: 'courses', column: 'id' })]),
title: schema.string({}, [rules.maxLength(255)]),
description: schema.string(),
video_url: schema.string(),
});
public messages = {};
}

View File

@ -0,0 +1,14 @@
import { schema, rules } from '@ioc:Adonis/Core/Validator';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
export default class UpdateLessonValidator {
constructor(protected ctx: HttpContextContract) {}
public schema = schema.create({
course_id: schema.string({}, [rules.exists({ table: 'courses', column: 'id' })]),
title: schema.string({}, [rules.maxLength(255)]),
description: schema.string(),
});
public messages = {};
}

View File

@ -0,0 +1,19 @@
import BaseSchema from '@ioc:Adonis/Lucid/Schema';
export default class LessonContents extends BaseSchema {
protected tableName = 'lesson_contents';
public async up() {
this.schema.createTable(this.tableName, table => {
table.increments('id');
table.string('lesson_id', 21).unsigned().references('lessons.id').unique().onDelete('CASCADE');
table.string('video_url').notNullable();
table.timestamp('created_at', { useTz: true });
table.timestamp('updated_at', { useTz: true });
});
}
public async down() {
this.schema.dropTable(this.tableName);
}
}

View File

@ -24,7 +24,6 @@ Route.group(() => {
* Me module
*
*/
require('./me');
/**
@ -32,8 +31,14 @@ Route.group(() => {
* Courses module
*
*/
require('./courses');
/**
*
* Lessons module
*
*/
require('./lessons');
}).middleware('auth');
})
.prefix('v1')

12
routes/apis/v1/lessons.ts Normal file
View File

@ -0,0 +1,12 @@
import Route from '@ioc:Adonis/Core/Route';
Route.group(() => {
Route.get('/', 'Api/v1/LessonsController.list').middleware('role:admin,teacher,student').as('lessons.list');
Route.post('/', 'Api/v1/LessonsController.create').middleware('role:admin,teacher').as('lessons.create');
Route.get('/:id', 'Api/v1/LessonsController.show').middleware('role:admin,teacher,student').as('lessons.show');
Route.delete('/:id', 'Api/v1/LessonsController.delete').middleware('role:admin,teacher').as('lessons.delete');
Route.put('/:id', 'Api/v1/LessonsController.update').middleware('role:admin,teacher').as('lessons.update');
Route.get('/:id/content', 'Api/v1/LessonsController.getContent')
.middleware('role:admin,teacher,student')
.as('lessons.get-content');
}).prefix('lessons');