diff --git a/app/Models/Category.ts b/app/Models/Category.ts index 7f8255a..181ee29 100644 --- a/app/Models/Category.ts +++ b/app/Models/Category.ts @@ -1,16 +1,16 @@ +/* eslint-disable no-param-reassign */ +/* eslint-disable import/no-cycle */ import { DateTime } from 'luxon'; -import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'; +import { nanoid } from 'nanoid'; +import { BaseModel, beforeCreate, column } from '@ioc:Adonis/Lucid/Orm'; export default class Category extends BaseModel { @column({ isPrimary: true }) - public id: number; + public id: string; @column() public title: string; - @column() - public slug: string; - @column() public description: string; @@ -19,4 +19,13 @@ export default class Category extends BaseModel { @column.dateTime({ autoCreate: true, autoUpdate: true }) public updatedAt: DateTime; + + /** + * Assign id to category by nanoid. + * @param category Category + */ + @beforeCreate() + public static assignCategoryId(category: Category) { + category.id = nanoid(); + } } diff --git a/app/Models/Contact.ts b/app/Models/Contact.ts index 91fe82a..b465aaa 100644 --- a/app/Models/Contact.ts +++ b/app/Models/Contact.ts @@ -6,7 +6,7 @@ export default class Contact extends BaseModel { public id: number; @column({ serializeAs: null }) - user_id: string; + public user_id: string; @column() public email: string; diff --git a/app/Models/Course.ts b/app/Models/Course.ts index a554208..02dfd1b 100644 --- a/app/Models/Course.ts +++ b/app/Models/Course.ts @@ -1,9 +1,11 @@ /* eslint-disable no-param-reassign */ import { nanoid } from 'nanoid'; import { DateTime } from 'luxon'; -import { BaseModel, beforeCreate, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm'; -import User from './User'; +import { BaseModel, beforeCreate, BelongsTo, belongsTo, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm'; import Category from './Category'; +import Lesson from './Lesson'; +import User from './User'; +// eslint-disable-next-line import/no-cycle export default class Course extends BaseModel { @column({ isPrimary: true }) @@ -19,13 +21,22 @@ export default class Course extends BaseModel { public teacher_id: string; @column() - public category_id: number; + public category_id: string; - @hasOne(() => User) - public teacher: HasOne; + @belongsTo(() => Category, { + foreignKey: 'category_id', + }) + public category: BelongsTo; - @hasOne(() => Category) - public category: HasOne; + @belongsTo(() => User, { + foreignKey: 'teacher_id', + }) + public teacher: BelongsTo; + + @hasMany(() => Lesson, { + foreignKey: 'course_id', + }) + public lessons: HasMany; @column.dateTime({ autoCreate: true }) public createdAt: DateTime; @@ -35,7 +46,7 @@ export default class Course extends BaseModel { /** * Assign id to course by nanoid. - * @param user User + * @param course Course */ @beforeCreate() public static assignCourseId(course: Course) { diff --git a/app/Models/Lesson.ts b/app/Models/Lesson.ts new file mode 100644 index 0000000..65b27cb --- /dev/null +++ b/app/Models/Lesson.ts @@ -0,0 +1,33 @@ +/* eslint-disable no-param-reassign */ +import { DateTime } from 'luxon'; +import { nanoid } from 'nanoid'; +import { BaseModel, beforeCreate, column } from '@ioc:Adonis/Lucid/Orm'; + +export default class Lesson extends BaseModel { + @column({ isPrimary: true }) + public id: string; + + @column() + public course_id: string; + + @column() + public title: string; + + @column() + public description: string; + + @column.dateTime({ autoCreate: true }) + public createdAt: DateTime; + + @column.dateTime({ autoCreate: true, autoUpdate: true }) + public updatedAt: DateTime; + + /** + * Assign id to lesson by nanoid. + * @param lesson Lesson + */ + @beforeCreate() + public static assignLessonId(lesson: Lesson) { + lesson.id = nanoid(); + } +} diff --git a/app/Models/Role.ts b/app/Models/Role.ts index 241cf91..d9a1958 100644 --- a/app/Models/Role.ts +++ b/app/Models/Role.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-cycle */ import { DateTime } from 'luxon'; -import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'; +import { BaseModel, column, manyToMany, ManyToMany } from '@ioc:Adonis/Lucid/Orm'; +import User from './User'; export default class Role extends BaseModel { @column({ isPrimary: true }) @@ -12,6 +13,11 @@ export default class Role extends BaseModel { @column() public slug: string; + @manyToMany(() => User, { + pivotTable: 'users_roles', + }) + public users: ManyToMany; + @column.dateTime({ autoCreate: true, serializeAs: null }) public createdAt: DateTime; diff --git a/config/cors.ts b/config/cors.ts index dc0e3f6..ec98b5c 100644 --- a/config/cors.ts +++ b/config/cors.ts @@ -56,7 +56,7 @@ const corsConfig: CorsConfig = { | | Following is the list of default methods. Feel free to add more. */ - methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'], + methods: ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'], /* |-------------------------------------------------------------------------- @@ -97,14 +97,7 @@ const corsConfig: CorsConfig = { | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers | */ - exposeHeaders: [ - 'cache-control', - 'content-language', - 'content-type', - 'expires', - 'last-modified', - 'pragma', - ], + exposeHeaders: ['cache-control', 'content-language', 'content-type', 'expires', 'last-modified', 'pragma'], /* |-------------------------------------------------------------------------- diff --git a/database/factories/index.ts b/database/factories/index.ts index de08b6b..5dc64ed 100644 --- a/database/factories/index.ts +++ b/database/factories/index.ts @@ -1 +1,49 @@ -// import Factory from '@ioc:Adonis/Lucid/Factory' +import Factory from '@ioc:Adonis/Lucid/Factory'; +import Category from 'App/Models/Category'; +import Contact from 'App/Models/Contact'; +import Course from 'App/Models/Course'; +import Lesson from 'App/Models/Lesson'; +import User from 'App/Models/User'; + +export const ContactFactory = Factory.define(Contact, ({ faker }) => { + return { + email: faker.internet.exampleEmail(), + }; +}).build(); + +export const UserFactory = Factory.define(User, ({ faker }) => { + return { + first_name: faker.name.firstName(), + last_name: faker.name.lastName(), + login: `${faker.lorem.word(6)}${faker.datatype.number(10)}`, + password: faker.internet.password(6), + }; +}) + .relation('contacts', () => ContactFactory) + .build(); + +export const LessonFactory = Factory.define(Lesson, ({ faker }) => { + return { + title: faker.lorem.sentence(2), + description: faker.lorem.sentence(5), + }; +}).build(); + +export const CategoryFactory = Factory.define(Category, ({ faker }) => { + const title = faker.lorem.word(6); + return { + title: `${title[0].toUpperCase()}${title.slice(1)}`, + description: faker.lorem.sentence(5), + }; +}).build(); + +export const CourseFactory = Factory.define(Course, ({ faker }) => { + return { + title: faker.lorem.sentence(3), + description: faker.lorem.sentence(5), + }; +}) + .relation('category', () => CategoryFactory) + .relation('lessons', () => LessonFactory) + // .relation('teacher', () => UserFactory) + .build(); diff --git a/database/migrations/1627022639849_contacts.ts b/database/migrations/1627022639849_contacts.ts index ecd718a..8494fad 100644 --- a/database/migrations/1627022639849_contacts.ts +++ b/database/migrations/1627022639849_contacts.ts @@ -6,7 +6,7 @@ export default class Contacts extends BaseSchema { 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('user_id', 21).unsigned().references('users.id').unique().onDelete('CASCADE'); table.string('email').unique().notNullable(); table.string('phone_number').unique(); table.string('vk_id').unique(); diff --git a/database/migrations/1627295886598_categories.ts b/database/migrations/1627295886598_categories.ts index 99a5f91..c5a02d2 100644 --- a/database/migrations/1627295886598_categories.ts +++ b/database/migrations/1627295886598_categories.ts @@ -5,9 +5,8 @@ export default class Categories extends BaseSchema { public async up() { this.schema.createTable(this.tableName, table => { - table.increments('id'); + table.string('id', 21).primary(); table.string('title').notNullable(); - table.string('slug').unique().notNullable(); table.string('description'); table.timestamp('created_at', { useTz: true }); table.timestamp('updated_at', { useTz: true }); diff --git a/database/migrations/1627300211260_courses.ts b/database/migrations/1627300211260_courses.ts index ce7df65..7044d45 100644 --- a/database/migrations/1627300211260_courses.ts +++ b/database/migrations/1627300211260_courses.ts @@ -9,7 +9,7 @@ export default class Courses extends BaseSchema { table.string('title').notNullable(); table.string('description').notNullable(); table.string('teacher_id', 21).unsigned().references('users.id').onDelete('CASCADE'); - table.integer('category_id').unsigned().references('categories.id').onDelete('CASCADE'); + table.string('category_id', 21).unsigned().references('categories.id').onDelete('CASCADE'); table.timestamp('created_at', { useTz: true }); table.timestamp('updated_at', { useTz: true }); }); diff --git a/database/migrations/1627310102207_lessons.ts b/database/migrations/1627310102207_lessons.ts new file mode 100644 index 0000000..76b3c9d --- /dev/null +++ b/database/migrations/1627310102207_lessons.ts @@ -0,0 +1,20 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class Lessons extends BaseSchema { + protected tableName = 'lessons'; + + public async up() { + this.schema.createTable(this.tableName, table => { + table.string('id', 21).primary(); + table.string('course_id', 21).unsigned().references('courses.id').onDelete('CASCADE'); + table.string('title').notNullable(); + table.string('description'); + table.timestamp('created_at', { useTz: true }); + table.timestamp('updated_at', { useTz: true }); + }); + } + + public async down() { + this.schema.dropTable(this.tableName); + } +} diff --git a/database/seeders/Category.ts b/database/seeders/Category.ts deleted file mode 100644 index 3287e4e..0000000 --- a/database/seeders/Category.ts +++ /dev/null @@ -1,29 +0,0 @@ -import BaseSeeder from '@ioc:Adonis/Lucid/Seeder'; -import Category from 'App/Models/Category'; - -export default class CategorySeeder extends BaseSeeder { - private Category: typeof Category; - - public async run() { - this.Category = Category; - - const category1 = new this.Category(); - category1.title = 'JavaScript'; - category1.slug = 'javascript'; - category1.description = 'Category 1 description'; - - const category2 = new this.Category(); - category2.title = 'ReactJS'; - category2.slug = 'reactjs'; - category2.description = 'Category 2 description'; - - const category3 = new this.Category(); - category3.title = 'NodeJS'; - category3.slug = 'nodejs'; - category3.description = 'Category 3 description'; - - await category1.save(); - await category2.save(); - await category3.save(); - } -} diff --git a/database/seeders/Course.ts b/database/seeders/Course.ts index b0a6602..79bc1b9 100644 --- a/database/seeders/Course.ts +++ b/database/seeders/Course.ts @@ -1,53 +1,19 @@ import BaseSeeder from '@ioc:Adonis/Lucid/Seeder'; -import Category from 'App/Models/Category'; -import Course from 'App/Models/Course'; import User from 'App/Models/User'; +import { CourseFactory } from 'Database/factories'; export default class CourseSeeder extends BaseSeeder { - private Course: typeof Course; - private User: typeof User; - private Category: typeof Category; - public async run() { - this.Course = Course; this.User = User; - this.Category = Category; - const teacher = await this.User.findByOrFail('login', 'nikolai123'); + const teacher = await this.User.query() + .preload('roles', q => q.where('slug', 'teacher')) + .firstOrFail(); - /** - * Course 1 - */ - const category1 = await this.Category.findByOrFail('slug', 'javascript'); - const course1 = new this.Course(); - course1.title = 'Course 1'; - course1.description = 'Course 1 description'; - course1.teacher_id = teacher.id; - course1.category_id = category1.id; - await course1.save(); - - /** - * Course 2 - */ - const category2 = await this.Category.findByOrFail('slug', 'reactjs'); - const course2 = new this.Course(); - course2.title = 'Course 2'; - course2.description = 'Course 2 description'; - course2.teacher_id = teacher.id; - course2.category_id = category2.id; - await course2.save(); - - /** - * Course 3 - */ - const category3 = await this.Category.findByOrFail('slug', 'nodejs'); - const course3 = new this.Course(); - course3.title = 'Course 3'; - course3.description = 'Course 3 description'; - course3.teacher_id = teacher.id; - course3.category_id = category3.id; - await course3.save(); + await (await CourseFactory.with('category').with('lessons', 3).create()).related('teacher').associate(teacher); + await (await CourseFactory.with('category').with('lessons', 2).create()).related('teacher').associate(teacher); + await (await CourseFactory.with('category').with('lessons', 1).create()).related('teacher').associate(teacher); } } diff --git a/database/seeders/MainSeeder/Index.ts b/database/seeders/MainSeeder/Index.ts index c8a7e53..53e1e56 100644 --- a/database/seeders/MainSeeder/Index.ts +++ b/database/seeders/MainSeeder/Index.ts @@ -19,7 +19,6 @@ export default class IndexSeeder extends BaseSeeder { public async run() { await this.runSeeder(await import('../Role')); await this.runSeeder(await import('../User')); - await this.runSeeder(await import('../Category')); await this.runSeeder(await import('../Course')); } } diff --git a/database/seeders/Role.ts b/database/seeders/Role.ts index dcde9ba..76f7b15 100644 --- a/database/seeders/Role.ts +++ b/database/seeders/Role.ts @@ -1,10 +1,13 @@ -/* 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 { + private Role: typeof Role; + public async run() { - await Role.createMany([ + this.Role = Role; + + await this.Role.createMany([ { name: 'Administrator', slug: 'admin', diff --git a/database/seeders/User.ts b/database/seeders/User.ts index 3dd2a66..0ca0c4f 100644 --- a/database/seeders/User.ts +++ b/database/seeders/User.ts @@ -1,12 +1,12 @@ /* 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'; +import { UserFactory } from 'Database/factories'; export default class UserSeeder extends BaseSeeder { public async run() { /** - * Roles + * Get roles */ const roles = { admin: await Role.findByOrFail('slug', 'admin'), @@ -17,40 +17,19 @@ export default class UserSeeder extends BaseSeeder { /** * 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' }); + const userAdmin = await UserFactory.with('contacts').create(); + await userAdmin.related('roles').attach([roles.admin.id]); /** * 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' }); + const userTeacher = await UserFactory.with('contacts').create(); + await userTeacher.related('roles').attach([roles.teacher.id]); /** * 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' }); + const userStudent = await UserFactory.with('contacts').create(); + await userStudent.related('roles').attach([roles.student.id]); } }