basic Course crud & fix seeder

This commit is contained in:
Sergey Yarkov 2021-07-29 23:58:34 +03:00
parent 4143ed2ca5
commit a96ad1e7b5
9 changed files with 248 additions and 15 deletions

View File

@ -3,11 +3,7 @@
"es6": true, "es6": true,
"node": true "node": true
}, },
"extends": [ "extends": ["airbnb-typescript/base", "prettier", "plugin:prettier/recommended"],
"airbnb-typescript/base",
"prettier",
"plugin:prettier/recommended"
],
"globals": { "globals": {
"Atomics": "readonly", "Atomics": "readonly",
"SharedArrayBuffer": "readonly" "SharedArrayBuffer": "readonly"
@ -17,7 +13,6 @@
"ecmaVersion": 2018, "ecmaVersion": 2018,
"sourceType": "module", "sourceType": "module",
"project": "./tsconfig.json", "project": "./tsconfig.json",
"tsconfigRootDir": "server",
"include": ["src/**/*.ts", "src/**/*.js", "test/**/*.ts"] "include": ["src/**/*.ts", "src/**/*.js", "test/**/*.ts"]
}, },
"plugins": ["@typescript-eslint"], "plugins": ["@typescript-eslint"],

View File

@ -0,0 +1,181 @@
import { Exception } from '@adonisjs/core/build/standalone';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import Course from 'App/Models/Course';
import User from 'App/Models/User';
import CreateCourseValidator from 'App/Validators/CreateCourseValidator';
import UpdateCourseValidator from 'App/Validators/UpdateCourseValidator';
export default class CoursesController {
private readonly Course: typeof Course;
private readonly User: typeof User;
constructor() {
this.Course = Course;
this.User = User;
}
/**
* List of all Courses
* GET /courses
*/
public async index({ response }: HttpContextContract) {
const courses = await this.Course.query().preload('teacher').preload('category').preload('lessons');
return response.ok({
message: 'Fetched all courses.',
data: courses,
});
}
/**
* Show Course by "id"
* GET /courses/:id
*/
public async show({ response, params }: HttpContextContract) {
const course = await this.Course.query()
.preload('teacher')
.preload('category')
.preload('lessons')
.where('id', params.id)
.firstOrFail();
return response.ok({
message: `Fetched course with id: "${course.id}"`,
data: course,
});
}
/**
* Show Teacher of Course with "id"
* GET /courses/:id/teacher
*/
public async showTeacher({ response, params }: HttpContextContract) {
const course = await this.Course.query()
.select('teacher_id')
.preload('teacher', user => user.preload('contacts').preload('roles'))
.where('id', params.id)
.firstOrFail();
return response.ok({
message: `Fetched teacher by courseId: "${params.id}"`,
data: course.teacher,
});
}
/**
* Show Category of Course with "id"
* GET /courses/:id/category
*/
public async showCategory({ response, params }: HttpContextContract) {
const course = await this.Course.query()
.select('category_id')
.preload('category')
.where('id', params.id)
.firstOrFail();
return response.ok({
message: `Fetched category by courseId: "${params.id}"`,
data: course.category,
});
}
/**
* Show Lessons of ourse with "id"
* GET /courses/:id/lessons
*/
public async showLessons({ response, params }: HttpContextContract) {
const course = await this.Course.query().select('id').preload('lessons').where('id', params.id).firstOrFail();
return response.ok({
message: `Fetched lessons by courseId: "${params.id}"`,
data: course.lessons,
});
}
/**
* Show Students of Course with "id"
*/
public async showStudents({ response, params }: HttpContextContract) {}
/**
* Create new Course in a system
* POST /courses
*/
public async create({ response, request }: HttpContextContract) {
const payload = await request.validate(CreateCourseValidator);
const teacher = await this.User.findOrFail(payload.teacher_id);
await teacher.load('roles');
/**
* Check user on role Teacher
*/
const roles = teacher.roles.map(r => r.slug);
if (!roles.includes('teacher')) {
throw new Exception(`User with id "${teacher.id}" not teacher.`, 400, 'ERR_USER_NOT_TEACHER');
}
const course = await this.Course.create({
title: payload.title,
description: payload.description,
teacher_id: teacher.id,
category_id: payload.category_id,
});
await course.save();
return response.created({
message: 'Course has been created',
data: course,
});
}
/**
* Delete Course by "id"
* DELETE /courses/:id
*/
async destroy({ response, params }: HttpContextContract) {
const course = await this.Course.query()
.preload('teacher')
.preload('category')
.preload('lessons')
.where('id', params.id)
.firstOrFail();
await course.delete();
return response.ok({
message: `Course with id "${params.id}" has been deleted.`,
data: course,
});
}
/**
* Update Course by "id"
* PATCH /courses/:id
*/
async update({ response, request, params }: HttpContextContract) {
const payload = await request.validate(UpdateCourseValidator);
const course = await this.Course.findOrFail(params.id);
/**
* Update course
*/
Object.keys(payload).forEach(k => {
if (payload[k] !== undefined) {
course[k] = payload[k];
}
});
await course.save();
await course.load('category');
await course.load('lessons');
await course.load('teacher');
return response.ok({
message: `Course with id: "${course.id}" was successfully updated.`,
data: course,
});
}
}

View File

@ -14,10 +14,10 @@ export default class Category extends BaseModel {
@column() @column()
public description: string; public description: string;
@column.dateTime({ autoCreate: true }) @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;
/** /**

View File

@ -16,10 +16,10 @@ export default class Lesson extends BaseModel {
@column() @column()
public description: string; public description: string;
@column.dateTime({ autoCreate: true }) @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;
/** /**

View File

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

View File

@ -0,0 +1,15 @@
import { schema, rules } from '@ioc:Adonis/Core/Validator';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
export default class UpdateCourseValidator {
constructor(protected ctx: HttpContextContract) {}
public schema = schema.create({
title: schema.string.optional({}, [rules.unique({ table: 'courses', column: 'title' })]),
description: schema.string.optional(),
teacher_id: schema.string.optional({}, [rules.exists({ table: 'users', column: 'id' })]),
category_id: schema.string.optional({}, [rules.exists({ table: 'categories', column: 'id' })]),
});
public messages = {};
}

View File

@ -16,7 +16,7 @@ export const UserFactory = Factory.define(User, ({ faker }) => {
first_name: faker.name.firstName(), first_name: faker.name.firstName(),
last_name: faker.name.lastName(), last_name: faker.name.lastName(),
login: `${faker.lorem.word(6)}${faker.datatype.number(10)}`, login: `${faker.lorem.word(6)}${faker.datatype.number(10)}`,
password: faker.internet.password(6), password: '12345',
}; };
}) })
.relation('contacts', () => ContactFactory) .relation('contacts', () => ContactFactory)

View File

@ -1,4 +1,5 @@
import BaseSeeder from '@ioc:Adonis/Lucid/Seeder'; import BaseSeeder from '@ioc:Adonis/Lucid/Seeder';
import Course from 'App/Models/Course';
import User from 'App/Models/User'; import User from 'App/Models/User';
import { CourseFactory } from 'Database/factories'; import { CourseFactory } from 'Database/factories';
@ -9,11 +10,12 @@ export default class CourseSeeder extends BaseSeeder {
this.User = User; this.User = User;
const teacher = await this.User.query() const teacher = await this.User.query()
.preload('roles', q => q.where('slug', 'teacher')) .preload('roles')
.whereHas('roles', q => q.where('slug', 'teacher'))
.firstOrFail(); .firstOrFail();
await (await CourseFactory.with('category').with('lessons', 3).create()).related('teacher').associate(teacher); await CourseFactory.with('category')
await (await CourseFactory.with('category').with('lessons', 2).create()).related('teacher').associate(teacher); .with('lessons', 2)
await (await CourseFactory.with('category').with('lessons', 1).create()).related('teacher').associate(teacher); .createMany(3, (course: Course) => course.related('teacher').associate(teacher));
} }
} }

View File

@ -47,9 +47,34 @@ Route.group(() => {
Route.patch('me/password', 'MeController.changePassword') Route.patch('me/password', 'MeController.changePassword')
.middleware('role:admin,teacher,student') .middleware('role:admin,teacher,student')
.as('changePassword'); .as('changePassword');
Route.put('me/contacts', 'MeController.updateContacts') Route.put('me/contacts', 'MeController.updateContacts')
.middleware('role:admin,teacher,student') .middleware('role:admin,teacher,student')
.as('updateContacts'); .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', 'CourseController.showStudents')
.middleware('role:admin,teacher,student')
.as('showCourseStudents');
}).middleware('auth'); }).middleware('auth');
}) })
.prefix('/api/v1') .prefix('/api/v1')