mirror of
https://github.com/sergeyyarkov/educt-server.git
synced 2026-10-11 18:29:15 +03:00
refactor: aseets route & database seeders
This commit is contained in:
parent
ee25d495e1
commit
98a67f1744
@ -19,7 +19,7 @@ export default class AssetsController extends BaseController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Send background image of course
|
* Send background image of course
|
||||||
* GET /media/images/courses/:fileName
|
* GET /assets/images/courses/:fileName
|
||||||
*/
|
*/
|
||||||
public async serveCourseImage(ctx: HttpContextContract) {
|
public async serveCourseImage(ctx: HttpContextContract) {
|
||||||
const result = await this.assetsService.getCourseImagePath(ctx.params.fileName);
|
const result = await this.assetsService.getCourseImagePath(ctx.params.fileName);
|
||||||
|
|||||||
@ -17,14 +17,14 @@ import Category from 'App/Models/Category';
|
|||||||
import Lesson from 'App/Models/Lesson';
|
import Lesson from 'App/Models/Lesson';
|
||||||
import User from 'App/Models/User';
|
import User from 'App/Models/User';
|
||||||
import CourseStatusEnum from 'App/Datatypes/Enums/CourseStatusEnum';
|
import CourseStatusEnum from 'App/Datatypes/Enums/CourseStatusEnum';
|
||||||
import Image from './Image';
|
// import Image from './Image';
|
||||||
|
|
||||||
export default class Course extends BaseModel {
|
export default class Course extends BaseModel {
|
||||||
@column({ isPrimary: true })
|
@column({ isPrimary: true })
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
@column({ serializeAs: null })
|
@column()
|
||||||
public bg_image_id: number;
|
public bg_image_path: string;
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
public title: string;
|
public title: string;
|
||||||
@ -46,10 +46,10 @@ export default class Course extends BaseModel {
|
|||||||
})
|
})
|
||||||
public category: BelongsTo<typeof Category>;
|
public category: BelongsTo<typeof Category>;
|
||||||
|
|
||||||
@belongsTo(() => Image, {
|
// @belongsTo(() => Image, {
|
||||||
foreignKey: 'bg_image_id',
|
// foreignKey: 'bg_image_id',
|
||||||
})
|
// })
|
||||||
public image: BelongsTo<typeof Image>;
|
// public image: BelongsTo<typeof Image>;
|
||||||
|
|
||||||
@belongsTo(() => User, {
|
@belongsTo(() => User, {
|
||||||
foreignKey: 'teacher_id',
|
foreignKey: 'teacher_id',
|
||||||
|
|||||||
@ -5,7 +5,7 @@ export default class Image extends BaseModel {
|
|||||||
@column({ isPrimary: true })
|
@column({ isPrimary: true })
|
||||||
public id: number;
|
public id: number;
|
||||||
|
|
||||||
@column({ serializeAs: null })
|
@column()
|
||||||
public path: string;
|
public path: string;
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
|
|||||||
@ -59,12 +59,7 @@ export default class CourseRepository {
|
|||||||
query.andWhere('status', status);
|
query.andWhere('status', status);
|
||||||
}
|
}
|
||||||
|
|
||||||
const courses = await query
|
const courses = await query.preload('category').withCount('students').withCount('likes').withCount('lessons');
|
||||||
.preload('category')
|
|
||||||
.preload('image')
|
|
||||||
.withCount('students')
|
|
||||||
.withCount('likes')
|
|
||||||
.withCount('lessons');
|
|
||||||
|
|
||||||
return courses;
|
return courses;
|
||||||
}
|
}
|
||||||
@ -81,7 +76,6 @@ export default class CourseRepository {
|
|||||||
.preload('category')
|
.preload('category')
|
||||||
.preload('lessons')
|
.preload('lessons')
|
||||||
.preload('students')
|
.preload('students')
|
||||||
.preload('image')
|
|
||||||
.where('id', id)
|
.where('id', id)
|
||||||
.where('status', CourseStatusEnum.PUBLISHED)
|
.where('status', CourseStatusEnum.PUBLISHED)
|
||||||
.first();
|
.first();
|
||||||
@ -190,7 +184,7 @@ export default class CourseRepository {
|
|||||||
* Create new course
|
* Create new course
|
||||||
*/
|
*/
|
||||||
const course = await this.Course.create({
|
const course = await this.Course.create({
|
||||||
bg_image_id: data.bg_image_id,
|
bg_image_path: data.image?.filePath,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
status: data.status,
|
status: data.status,
|
||||||
@ -210,7 +204,6 @@ export default class CourseRepository {
|
|||||||
public async delete(id: string | number): Promise<Course | null> {
|
public async delete(id: string | number): Promise<Course | null> {
|
||||||
const course = await this.Course.query()
|
const course = await this.Course.query()
|
||||||
.preload('category')
|
.preload('category')
|
||||||
.preload('image')
|
|
||||||
.withCount('students')
|
.withCount('students')
|
||||||
.withCount('likes')
|
.withCount('likes')
|
||||||
.withCount('lessons')
|
.withCount('lessons')
|
||||||
|
|||||||
@ -25,11 +25,11 @@ export default class ImageRepository {
|
|||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async create(file: MultipartFileContract): Promise<Image> {
|
public async create({ file, location }: { file: MultipartFileContract; location: string }): Promise<Image> {
|
||||||
/**
|
/**
|
||||||
* Save image to disk
|
* Save image to disk
|
||||||
*/
|
*/
|
||||||
await file.moveToDisk('images/courses');
|
await file.moveToDisk(location);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save image to database
|
* Save image to database
|
||||||
|
|||||||
@ -312,11 +312,6 @@ export default class CourseService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete background image from course
|
|
||||||
*/
|
|
||||||
await this.imageRepository.delete(course.bg_image_id);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
status: HttpStatusEnum.OK,
|
status: HttpStatusEnum.OK,
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export default class ImageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async createImage(data: CreateImageValidator['schema']['props']): Promise<IResponse> {
|
public async createImage(data: CreateImageValidator['schema']['props']): Promise<IResponse> {
|
||||||
const image = await this.imageRepository.create(data.image);
|
const image = await this.imageRepository.create({ file: data.image, location: 'images' });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@ -10,11 +10,14 @@ export default class UpdateContactsValidator {
|
|||||||
locales: ['ru-RU', 'en-US'],
|
locales: ['ru-RU', 'en-US'],
|
||||||
strict: true,
|
strict: true,
|
||||||
}),
|
}),
|
||||||
|
rules.unique({ table: 'contacts', column: 'phone_number' }),
|
||||||
]),
|
]),
|
||||||
vk_id: schema.string.optional({}, [rules.regex(/^([a-zA-Z0-9_]){1,64}$/)]),
|
vk_id: schema.string.optional({}, [rules.regex(/^([a-zA-Z0-9_]){1,64}$/)]),
|
||||||
twitter_id: schema.string.optional({}, [rules.regex(/(^|[^@\w])@(\w{1,15})\b/)]),
|
twitter_id: schema.string.optional({}, [rules.regex(/(^|[^@\w])@(\w{1,15})\b/)]),
|
||||||
telegram_id: schema.string.optional({}, [rules.regex(/(^|[^@\w])@(\w{1,64})\b/)]),
|
telegram_id: schema.string.optional({}, [rules.regex(/(^|[^@\w])@(\w{1,64})\b/)]),
|
||||||
});
|
});
|
||||||
|
|
||||||
public messages = {};
|
public messages = {
|
||||||
|
'phone_number.unique': 'This phone number is not available',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,10 @@ export default class CreateCourseValidator {
|
|||||||
public schema = schema.create({
|
public schema = schema.create({
|
||||||
title: schema.string(),
|
title: schema.string(),
|
||||||
description: schema.string(),
|
description: schema.string(),
|
||||||
bg_image_id: schema.number.optional([rules.exists({ table: 'images', column: 'id' })]),
|
image: schema.file.optional({
|
||||||
|
size: '10mb',
|
||||||
|
extnames: ['jpg', 'jpeg', 'png'],
|
||||||
|
}),
|
||||||
teacher_id: schema.string({}, [rules.exists({ table: 'users', column: 'id' })]),
|
teacher_id: schema.string({}, [rules.exists({ table: 'users', column: 'id' })]),
|
||||||
category_id: schema.string({}, [rules.exists({ table: 'categories', column: 'id' })]),
|
category_id: schema.string({}, [rules.exists({ table: 'categories', column: 'id' })]),
|
||||||
status: schema.enum(Object.values(CourseStatusEnum)),
|
status: schema.enum(Object.values(CourseStatusEnum)),
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import Contact from 'App/Models/Contact';
|
|||||||
import Course from 'App/Models/Course';
|
import Course from 'App/Models/Course';
|
||||||
import Lesson from 'App/Models/Lesson';
|
import Lesson from 'App/Models/Lesson';
|
||||||
import LessonContent from 'App/Models/LessonContent';
|
import LessonContent from 'App/Models/LessonContent';
|
||||||
|
import Role from 'App/Models/Role';
|
||||||
import User from 'App/Models/User';
|
import User from 'App/Models/User';
|
||||||
|
|
||||||
export const ContactFactory = Factory.define(Contact, ({ faker }) => {
|
export const ContactFactory = Factory.define(Contact, ({ faker }) => {
|
||||||
@ -25,7 +26,7 @@ export const UserFactory = Factory.define(User, ({ faker }) => {
|
|||||||
return {
|
return {
|
||||||
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(12)}${faker.datatype.number(100)}`,
|
||||||
email: faker.internet.exampleEmail(),
|
email: faker.internet.exampleEmail(),
|
||||||
password: '123456',
|
password: '123456',
|
||||||
};
|
};
|
||||||
@ -33,6 +34,25 @@ export const UserFactory = Factory.define(User, ({ faker }) => {
|
|||||||
.relation('contacts', () => ContactFactory)
|
.relation('contacts', () => ContactFactory)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
export const TeacherFactory = Factory.define(User, ({ faker }) => {
|
||||||
|
return {
|
||||||
|
first_name: faker.name.firstName(),
|
||||||
|
last_name: faker.name.lastName(),
|
||||||
|
login: `${faker.lorem.word(12)}${faker.datatype.number(100)}`,
|
||||||
|
email: faker.internet.exampleEmail(),
|
||||||
|
password: '123456',
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.relation('contacts', () => ContactFactory)
|
||||||
|
.after('create', async (_, user: User) => {
|
||||||
|
/**
|
||||||
|
* Get teacher role to attach after create new user
|
||||||
|
*/
|
||||||
|
const role = await Role.findByOrFail('slug', 'teacher');
|
||||||
|
user.related('roles').attach([role.id]);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
export const LessonContentFactory = Factory.define(LessonContent, ({ faker }) => {
|
export const LessonContentFactory = Factory.define(LessonContent, ({ faker }) => {
|
||||||
return {
|
return {
|
||||||
video_url: `https://www.youtube.com/embed/${faker.datatype.string(10)}`,
|
video_url: `https://www.youtube.com/embed/${faker.datatype.string(10)}`,
|
||||||
@ -64,5 +84,5 @@ export const CourseFactory = Factory.define(Course, ({ faker }) => {
|
|||||||
})
|
})
|
||||||
.relation('category', () => CategoryFactory)
|
.relation('category', () => CategoryFactory)
|
||||||
.relation('lessons', () => LessonFactory)
|
.relation('lessons', () => LessonFactory)
|
||||||
// .relation('teacher', () => UserFactory)
|
.relation('teacher', () => TeacherFactory)
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export default class Courses extends BaseSchema {
|
|||||||
public async up() {
|
public async up() {
|
||||||
this.schema.createTable(this.tableName, table => {
|
this.schema.createTable(this.tableName, table => {
|
||||||
table.string('id', 21).primary();
|
table.string('id', 21).primary();
|
||||||
table.integer('bg_image_id').unsigned().references('images.id').onDelete('SET NULL');
|
table.string('bg_image_path');
|
||||||
table.string('title').notNullable();
|
table.string('title').notNullable();
|
||||||
table.string('description').notNullable();
|
table.string('description').notNullable();
|
||||||
table.string('teacher_id', 21).unsigned().references('users.id').onDelete('CASCADE');
|
table.string('teacher_id', 21).unsigned().references('users.id').onDelete('CASCADE');
|
||||||
|
|||||||
@ -1,21 +1,15 @@
|
|||||||
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 { CourseFactory } from 'Database/factories';
|
import { CourseFactory } from 'Database/factories';
|
||||||
|
|
||||||
export default class CourseSeeder extends BaseSeeder {
|
export default class CourseSeeder extends BaseSeeder {
|
||||||
private User: typeof User;
|
private CourseFactory: typeof CourseFactory;
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
this.User = User;
|
this.CourseFactory = CourseFactory;
|
||||||
|
|
||||||
const teacher = await this.User.query()
|
await this.CourseFactory.with('lessons', 2, lessonFactory => lessonFactory.with('content'))
|
||||||
.preload('roles')
|
.with('category')
|
||||||
.whereHas('roles', q => q.where('slug', 'teacher'))
|
.with('teacher')
|
||||||
.firstOrFail();
|
.createMany(3);
|
||||||
|
|
||||||
await CourseFactory.with('category')
|
|
||||||
.with('lessons', 2, lessonFactory => lessonFactory.with('content'))
|
|
||||||
.createMany(3, (course: Course) => course.related('teacher').associate(teacher));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ export default class UserSeeder extends BaseSeeder {
|
|||||||
*/
|
*/
|
||||||
const roles = {
|
const roles = {
|
||||||
admin: await Role.findByOrFail('slug', 'admin'),
|
admin: await Role.findByOrFail('slug', 'admin'),
|
||||||
teacher: await Role.findByOrFail('slug', 'teacher'),
|
|
||||||
student: await Role.findByOrFail('slug', 'student'),
|
student: await Role.findByOrFail('slug', 'student'),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,12 +26,6 @@ export default class UserSeeder extends BaseSeeder {
|
|||||||
});
|
});
|
||||||
await userAdmin.related('roles').attach([roles.admin.id]);
|
await userAdmin.related('roles').attach([roles.admin.id]);
|
||||||
|
|
||||||
/**
|
|
||||||
* Teacher
|
|
||||||
*/
|
|
||||||
const userTeacher = await UserFactory.with('contacts').create();
|
|
||||||
await userTeacher.related('roles').attach([roles.teacher.id]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Student
|
* Student
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -6,11 +6,6 @@ Route.group(() => {
|
|||||||
* v1
|
* v1
|
||||||
*/
|
*/
|
||||||
require('./apis/v1/index');
|
require('./apis/v1/index');
|
||||||
|
|
||||||
/**
|
|
||||||
* Assets routes
|
|
||||||
*/
|
|
||||||
require('./apis/assets/index');
|
|
||||||
})
|
})
|
||||||
.prefix('api')
|
.prefix('api')
|
||||||
.as('api');
|
.as('api');
|
||||||
|
|||||||
@ -26,4 +26,9 @@ Route.group(() => {
|
|||||||
* Api routes
|
* Api routes
|
||||||
*/
|
*/
|
||||||
require('./api');
|
require('./api');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serve assets
|
||||||
|
*/
|
||||||
|
require('./assets');
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user