mirror of
https://github.com/sergeyyarkov/educt-server.git
synced 2026-10-11 18:29:15 +03:00
lessons crud & some refactor
This commit is contained in:
parent
619bc2bc9d
commit
6979e0b912
@ -1,6 +1,11 @@
|
|||||||
import { Exception } from '@adonisjs/core/build/standalone';
|
import { Exception } from '@adonisjs/core/build/standalone';
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Datatype
|
||||||
|
*/
|
||||||
|
import Role from 'App/Datatype/Role';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Models
|
* Models
|
||||||
*/
|
*/
|
||||||
@ -10,10 +15,10 @@ import User from 'App/Models/User';
|
|||||||
/**
|
/**
|
||||||
* Validators
|
* Validators
|
||||||
*/
|
*/
|
||||||
import AddCourseToUserValidator from 'App/Validators/AddCourseToUserValidator';
|
import AddCourseToUserValidator from 'App/Validators/Course/AddCourseToUserValidator';
|
||||||
import CreateCourseValidator from 'App/Validators/CreateCourseValidator';
|
import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator';
|
||||||
import DelCourseFromUserValidator from 'App/Validators/DelCourseFromUserValidator';
|
import DelCourseFromUserValidator from 'App/Validators/Course/DelCourseFromUserValidator';
|
||||||
import UpdateCourseValidator from 'App/Validators/UpdateCourseValidator';
|
import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator';
|
||||||
|
|
||||||
export default class CoursesController {
|
export default class CoursesController {
|
||||||
private readonly Course: typeof Course;
|
private readonly Course: typeof Course;
|
||||||
@ -130,7 +135,7 @@ export default class CoursesController {
|
|||||||
* Check user on role Teacher
|
* Check user on role Teacher
|
||||||
*/
|
*/
|
||||||
const roles = teacher.roles.map(r => r.slug);
|
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');
|
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);
|
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');
|
throw new Exception(`User cannot be attached to the course without "student" role.`, 400, 'E_USER_ROLE');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
110
app/Controllers/Http/Api/v1/LessonsController.ts
Normal file
110
app/Controllers/Http/Api/v1/LessonsController.ts
Normal 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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,8 +11,8 @@ import Contact from 'App/Models/Contact';
|
|||||||
/**
|
/**
|
||||||
* Validators
|
* Validators
|
||||||
*/
|
*/
|
||||||
import ChangePasswordValidator from 'App/Validators/ChangePasswordValidator';
|
import ChangePasswordValidator from 'App/Validators/Password/ChangePasswordValidator';
|
||||||
import UpdateContactsValidator from 'App/Validators/UpdateContactsValidator';
|
import UpdateContactsValidator from 'App/Validators/Contacts/UpdateContactsValidator';
|
||||||
|
|
||||||
export default class MeController {
|
export default class MeController {
|
||||||
private readonly guard: 'api';
|
private readonly guard: 'api';
|
||||||
|
|||||||
@ -10,17 +10,20 @@ import User from 'App/Models/User';
|
|||||||
/**
|
/**
|
||||||
* Validators
|
* Validators
|
||||||
*/
|
*/
|
||||||
import AddRoleToUserValidator from 'App/Validators/AddRoleToUserValidator';
|
import AddRoleToUserValidator from 'App/Validators/User/AddRoleToUserValidator';
|
||||||
import CreateUserValidator from 'App/Validators/CreateUserValidator';
|
import CreateUserValidator from 'App/Validators/User/CreateUserValidator';
|
||||||
import DelRoleFromUserValidator from 'App/Validators/DelRoleFromUserValidator';
|
import DelRoleFromUserValidator from 'App/Validators/User/DelRoleFromUserValidator';
|
||||||
import UpdateUserValidator from 'App/Validators/UpdateUserValidator';
|
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 User: typeof User;
|
||||||
|
|
||||||
private readonly Role: typeof Role;
|
private readonly Role: typeof Role;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
this.User = User;
|
this.User = User;
|
||||||
this.Role = Role;
|
this.Role = Role;
|
||||||
}
|
}
|
||||||
|
|||||||
14
app/Controllers/Http/BaseController.ts
Normal file
14
app/Controllers/Http/BaseController.ts
Normal 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
7
app/Datatype/Role.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
enum Role {
|
||||||
|
ADMIN = 'admin',
|
||||||
|
TEACHER = 'teacher',
|
||||||
|
STUDENT = 'student',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Role;
|
||||||
@ -5,14 +5,14 @@ export default class Role {
|
|||||||
error: {
|
error: {
|
||||||
message: string;
|
message: string;
|
||||||
status: number;
|
status: number;
|
||||||
errorCode: string;
|
code: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.error = {
|
this.error = {
|
||||||
message: 'You dont have permission to perform that action.',
|
message: 'You dont have permission to perform that action.',
|
||||||
status: 403,
|
status: 403,
|
||||||
errorCode: 'E_ACCESS_DENIED',
|
code: 'E_ACCESS_DENIED',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ export default class Role {
|
|||||||
/**
|
/**
|
||||||
* Failed to verify user rights
|
* 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { nanoid } from 'nanoid';
|
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 {
|
export default class Lesson extends BaseModel {
|
||||||
@column({ isPrimary: true })
|
@column({ isPrimary: true })
|
||||||
@ -16,6 +18,16 @@ export default class Lesson extends BaseModel {
|
|||||||
@column()
|
@column()
|
||||||
public description: string;
|
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 })
|
@column.dateTime({ autoCreate: true, serializeAs: null })
|
||||||
public createdAt: DateTime;
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
|||||||
19
app/Models/LessonContent.ts
Normal file
19
app/Models/LessonContent.ts
Normal 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;
|
||||||
|
}
|
||||||
@ -73,7 +73,7 @@ export default class User extends BaseModel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await user.related('roles').attach(inputRoles.map(r => r.id));
|
await user.related('roles').attach(inputRoles.map(r => r.id));
|
||||||
await user.load('roles'); // load updated roles
|
await user.load('roles');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
15
app/Validators/Lesson/CreateLessonValidator.ts
Normal file
15
app/Validators/Lesson/CreateLessonValidator.ts
Normal 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 = {};
|
||||||
|
}
|
||||||
14
app/Validators/Lesson/UpdateLessonValidator.ts
Normal file
14
app/Validators/Lesson/UpdateLessonValidator.ts
Normal 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 = {};
|
||||||
|
}
|
||||||
19
database/migrations/1628017319337_lesson_contents.ts
Normal file
19
database/migrations/1628017319337_lesson_contents.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,7 +24,6 @@ Route.group(() => {
|
|||||||
* Me module
|
* Me module
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require('./me');
|
require('./me');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,8 +31,14 @@ Route.group(() => {
|
|||||||
* Courses module
|
* Courses module
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require('./courses');
|
require('./courses');
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Lessons module
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
require('./lessons');
|
||||||
}).middleware('auth');
|
}).middleware('auth');
|
||||||
})
|
})
|
||||||
.prefix('v1')
|
.prefix('v1')
|
||||||
|
|||||||
12
routes/apis/v1/lessons.ts
Normal file
12
routes/apis/v1/lessons.ts
Normal 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');
|
||||||
Loading…
x
Reference in New Issue
Block a user