mirror of
https://github.com/sergeyyarkov/educt-server.git
synced 2026-10-11 02:09:07 +03:00
feat: attaching materials to lesson
This commit is contained in:
parent
d62bccc9dd
commit
5b635ed164
@ -5,9 +5,9 @@ APP_KEY=IP387l45rlNO3t7Qgjd89qr-a2G4oUgz
|
||||
DB_CONNECTION=pg
|
||||
PG_HOST=localhost
|
||||
PG_PORT=5432
|
||||
PG_USER=lucid
|
||||
PG_USER=postgres
|
||||
PG_PASSWORD=
|
||||
PG_DB_NAME=lucid
|
||||
PG_DB_NAME=educt
|
||||
REDIS_CONNECTION=local
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
|
||||
@ -100,7 +100,7 @@ export default class LessonsController extends BaseController {
|
||||
* GET /lessons/:id/content
|
||||
*/
|
||||
public async getContent(ctx: HttpContextContract) {
|
||||
const result = await this.lessonService.fetchLessonContent(ctx.params.id, ctx.auth);
|
||||
const result = await this.lessonService.fetchLessonContent(ctx.params.id, ctx);
|
||||
|
||||
if (!result.success && result.error) {
|
||||
throw new Exception(result.message, result.status, result.error.code);
|
||||
|
||||
@ -28,10 +28,10 @@ export default class Lesson extends BaseModel {
|
||||
})
|
||||
public content: HasOne<typeof LessonContent>;
|
||||
|
||||
@column.dateTime({ autoCreate: true, serializeAs: null })
|
||||
@column.dateTime({ autoCreate: true })
|
||||
public createdAt: DateTime;
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true, serializeAs: null })
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||
public updatedAt: DateTime;
|
||||
|
||||
/**
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm';
|
||||
import { BaseModel, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm';
|
||||
import { DateTime } from 'luxon';
|
||||
import LessonMaterial from './LessonMaterial';
|
||||
|
||||
export default class LessonContent extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
public id: string;
|
||||
public id: number;
|
||||
|
||||
@column()
|
||||
public lesson_id: string;
|
||||
@ -11,6 +12,9 @@ export default class LessonContent extends BaseModel {
|
||||
@column()
|
||||
public video_url: string;
|
||||
|
||||
@hasMany(() => LessonMaterial, { foreignKey: 'lesson_content_id' })
|
||||
public materials: HasMany<typeof LessonMaterial>;
|
||||
|
||||
@column.dateTime({ autoCreate: true, serializeAs: null })
|
||||
public createdAt: DateTime;
|
||||
|
||||
|
||||
25
app/Models/LessonMaterial.ts
Normal file
25
app/Models/LessonMaterial.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export default class LessonMaterial extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
public id: number;
|
||||
|
||||
@column()
|
||||
public url: string;
|
||||
|
||||
@column()
|
||||
public name: string;
|
||||
|
||||
@column()
|
||||
public ext: string;
|
||||
|
||||
@column()
|
||||
public lesson_content_id: number;
|
||||
|
||||
@column.dateTime({ autoCreate: true })
|
||||
public createdAt: DateTime;
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||
public updatedAt: DateTime;
|
||||
}
|
||||
@ -61,9 +61,38 @@ export default class LessonRepository {
|
||||
description: data.description,
|
||||
});
|
||||
|
||||
/**
|
||||
* Attach lesson to course
|
||||
*/
|
||||
await lesson.related('course').associate(course);
|
||||
await lesson.related('content').create({ video_url: data.video_url });
|
||||
await lesson.load('content');
|
||||
|
||||
/**
|
||||
* Create content and materials fields in database
|
||||
*/
|
||||
const content = await lesson.related('content').create({ video_url: data.video_url });
|
||||
|
||||
if (data.materials) {
|
||||
await Promise.all(
|
||||
data.materials.map(async file => {
|
||||
/**
|
||||
* Move each file to disk and then save in database
|
||||
*/
|
||||
await file.moveToDisk('materials');
|
||||
if (file.state === 'moved') {
|
||||
await content.related('materials').create({
|
||||
name: file.clientName,
|
||||
ext: file.extname,
|
||||
url: file.filePath,
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data
|
||||
*/
|
||||
await lesson.load('content', q => q.preload('materials'));
|
||||
|
||||
return lesson;
|
||||
}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
||||
import { AuthContract } from '@ioc:Adonis/Addons/Auth';
|
||||
// import { AuthContract } from '@ioc:Adonis/Addons/Auth';
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||
|
||||
/**
|
||||
* Datatypes
|
||||
*/
|
||||
import HttpStatusEnum from 'App/Datatypes/Enums/HttpStatusEnum';
|
||||
// import RoleEnum from 'App/Datatypes/Enums/RoleEnum';
|
||||
import IResponse from 'App/Datatypes/Interfaces/IResponse';
|
||||
// import RoleHelper from 'App/Helpers/RoleHelper';
|
||||
/**
|
||||
* Repositories
|
||||
*/
|
||||
@ -171,10 +174,7 @@ export default class LessonService {
|
||||
* @param auth AuthContract
|
||||
* @returns Response
|
||||
*/
|
||||
public async fetchLessonContent(id: string | number, auth: AuthContract): Promise<IResponse> {
|
||||
const user = await auth.use('api').authenticate();
|
||||
await user.load('courses');
|
||||
|
||||
public async fetchLessonContent(id: string | number, ctx: HttpContextContract): Promise<IResponse> {
|
||||
const lesson = await this.lessonRepository.getById(id);
|
||||
|
||||
if (!lesson) {
|
||||
@ -189,11 +189,10 @@ export default class LessonService {
|
||||
};
|
||||
}
|
||||
|
||||
await lesson.load('course');
|
||||
|
||||
const userHasCourse = user.courses.find(course => course.id === lesson.course.id);
|
||||
|
||||
if (!userHasCourse) {
|
||||
/**
|
||||
* Allow user to view lesson content
|
||||
*/
|
||||
if (await ctx.bouncer.denies('viewLessonContent', lesson)) {
|
||||
return {
|
||||
success: false,
|
||||
status: HttpStatusEnum.FORBIDDEN,
|
||||
@ -205,7 +204,10 @@ export default class LessonService {
|
||||
};
|
||||
}
|
||||
|
||||
await lesson.load('content');
|
||||
/**
|
||||
* Load lesson content with materials
|
||||
*/
|
||||
await lesson.load('content', q => q.preload('materials'));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@ -9,6 +9,12 @@ export default class CreateLessonValidator {
|
||||
title: schema.string({}, [rules.maxLength(255)]),
|
||||
description: schema.string(),
|
||||
video_url: schema.string({}, [rules.url()]),
|
||||
materials: schema.array.optional().members(
|
||||
schema.file({
|
||||
size: '10mb',
|
||||
extnames: ['pdf', 'zip', 'rar', 'doc', 'docx'],
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
public messages = {};
|
||||
|
||||
@ -20,7 +20,7 @@ const makeFakeUser = (faker: Faker.FakerStatic, loginPrefix?: string) => {
|
||||
return {
|
||||
first_name: faker.name.firstName(),
|
||||
last_name: faker.name.lastName(),
|
||||
login: `${loginPrefix || 'user'}${faker.datatype.number(1000)}`,
|
||||
login: `${loginPrefix || 'user'}${faker.datatype.number(10000)}`,
|
||||
email: faker.internet.exampleEmail(),
|
||||
password: '123456',
|
||||
};
|
||||
@ -76,8 +76,8 @@ export const CategoryFactory = Factory.define(Category, ({ faker }) => {
|
||||
|
||||
export const CourseFactory = Factory.define(Course, ({ faker }) => {
|
||||
return {
|
||||
title: `Course #${faker.datatype.number(200)}`,
|
||||
description: faker.lorem.sentence(5),
|
||||
title: faker.lorem.sentence(8),
|
||||
description: faker.lorem.sentence(20),
|
||||
status: CourseStatusEnum.PUBLISHED,
|
||||
};
|
||||
})
|
||||
|
||||
@ -5,7 +5,7 @@ export default class Images extends BaseSchema {
|
||||
|
||||
public async up() {
|
||||
this.schema.createTable(this.tableName, table => {
|
||||
table.increments('id');
|
||||
table.increments('id').primary();
|
||||
table.string('path').notNullable();
|
||||
table.string('name').notNullable();
|
||||
table.string('ext').notNullable();
|
||||
|
||||
@ -5,7 +5,7 @@ export default class Roles extends BaseSchema {
|
||||
|
||||
public async up() {
|
||||
this.schema.createTable(this.tableName, table => {
|
||||
table.increments('id');
|
||||
table.increments('id').primary();
|
||||
table.string('name').notNullable();
|
||||
table.string('slug').unique().notNullable();
|
||||
table.timestamp('created_at', { useTz: true });
|
||||
|
||||
@ -5,7 +5,7 @@ export default class UsersRoles extends BaseSchema {
|
||||
|
||||
public async up() {
|
||||
this.schema.createTable(this.tableName, table => {
|
||||
table.increments('id');
|
||||
table.increments('id').primary();
|
||||
table.string('user_id', 21).unsigned().references('users.id').onDelete('CASCADE');
|
||||
table.integer('role_id').unsigned().references('roles.id').onDelete('CASCADE');
|
||||
table.unique(['user_id', 'role_id']);
|
||||
|
||||
@ -5,7 +5,7 @@ export default class Contacts extends BaseSchema {
|
||||
|
||||
public async up() {
|
||||
this.schema.createTable(this.tableName, table => {
|
||||
table.increments('id');
|
||||
table.increments('id').primary();
|
||||
table.string('user_id', 21).unsigned().references('users.id').unique().onDelete('CASCADE');
|
||||
table.string('phone_number').unique();
|
||||
table.string('vk_id').unique();
|
||||
|
||||
@ -5,7 +5,7 @@ export default class UsersCourses extends BaseSchema {
|
||||
|
||||
public async up() {
|
||||
this.schema.createTable(this.tableName, table => {
|
||||
table.increments('id');
|
||||
table.increments('id').primary();
|
||||
table.string('user_id', 21).unsigned().references('users.id').onDelete('CASCADE');
|
||||
table.string('course_id', 21).unsigned().references('courses.id').onDelete('CASCADE');
|
||||
table.unique(['user_id', 'course_id']);
|
||||
|
||||
@ -5,7 +5,7 @@ export default class LessonContents extends BaseSchema {
|
||||
|
||||
public async up() {
|
||||
this.schema.createTable(this.tableName, table => {
|
||||
table.increments('id');
|
||||
table.increments('id').primary();
|
||||
table.string('lesson_id', 21).unsigned().references('lessons.id').unique().onDelete('CASCADE');
|
||||
table.string('video_url').notNullable();
|
||||
table.timestamp('created_at', { useTz: true });
|
||||
|
||||
@ -5,7 +5,7 @@ export default class CoursesLikes extends BaseSchema {
|
||||
|
||||
public async up() {
|
||||
this.schema.createTable(this.tableName, table => {
|
||||
table.increments('id');
|
||||
table.increments('id').primary();
|
||||
table.string('user_id', 21).unsigned().references('users.id').onDelete('CASCADE');
|
||||
table.string('course_id', 21).unsigned().references('courses.id').onDelete('CASCADE');
|
||||
table.timestamp('liked_on', { useTz: true });
|
||||
|
||||
21
database/migrations/1637080400230_lesson_materials.ts
Normal file
21
database/migrations/1637080400230_lesson_materials.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import BaseSchema from '@ioc:Adonis/Lucid/Schema';
|
||||
|
||||
export default class LessonMaterials extends BaseSchema {
|
||||
protected tableName = 'lesson_materials';
|
||||
|
||||
public async up() {
|
||||
this.schema.createTable(this.tableName, table => {
|
||||
table.increments('id').primary();
|
||||
table.string('url').notNullable();
|
||||
table.string('name').notNullable();
|
||||
table.string('ext').notNullable();
|
||||
table.integer('lesson_content_id').unsigned().references('lesson_contents.id').onDelete('CASCADE');
|
||||
table.timestamp('created_at', { useTz: true });
|
||||
table.timestamp('updated_at', { useTz: true });
|
||||
});
|
||||
}
|
||||
|
||||
public async down() {
|
||||
this.schema.dropTable(this.tableName);
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@
|
||||
import Bouncer from '@ioc:Adonis/Addons/Bouncer';
|
||||
import RoleEnum from 'App/Datatypes/Enums/RoleEnum';
|
||||
import RoleHelper from 'App/Helpers/RoleHelper';
|
||||
import Lesson from 'App/Models/Lesson';
|
||||
import Role from 'App/Models/Role';
|
||||
import User from 'App/Models/User';
|
||||
|
||||
@ -39,6 +40,15 @@ export const { actions } = Bouncer.define('manageUserRole', async (user: User, r
|
||||
(role.slug === RoleEnum.ADMIN || role.slug === RoleEnum.TEACHER) &&
|
||||
!RoleHelper.userContainRoles(user.roles, [RoleEnum.ADMIN])
|
||||
);
|
||||
}).define('viewLessonContent', async (user: User, lesson: Lesson) => {
|
||||
await user.load(loader => loader.load('roles').load('courses'));
|
||||
await lesson.load('course');
|
||||
|
||||
if (!RoleHelper.userContainRoles(user.roles, [RoleEnum.ADMIN, RoleEnum.TEACHER])) {
|
||||
return !!user.courses.find(course => course.id === lesson.course.id);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user