feat: download material of lesson

This commit is contained in:
Sergey Yarkov 2021-11-17 21:12:49 +03:00
parent 5b635ed164
commit be2f05a780
10 changed files with 128 additions and 12 deletions

View File

@ -1,3 +1,4 @@
import Application from '@ioc:Adonis/Core/Application';
import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
@ -6,6 +7,11 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
*/
import LessonService from 'App/Services/LessonService';
/**
* Models
*/
import LessonMaterial from 'App/Models/LessonMaterial';
/**
* Validators
*/
@ -96,7 +102,7 @@ export default class LessonsController extends BaseController {
}
/**
* Get lesson content by id
* Get lesson content by lesson id
* GET /lessons/:id/content
*/
public async getContent(ctx: HttpContextContract) {
@ -108,6 +114,29 @@ export default class LessonsController extends BaseController {
return this.sendResponse(ctx, result.data, result.message, result.status);
}
/**
* Get lesson materials by file name
* GET /lessons/materials/:file
*/
public async getMaterial(ctx: HttpContextContract) {
const { file } = ctx.request.params();
const { data, message, status, success, error } = await this.lessonService.fetchMaterialFile(ctx, file);
if (!success && error) {
throw new Exception(message, status, error.code);
}
/**
* Send file to user
*/
if (data instanceof LessonMaterial) {
const path = Application.makePath(data.url);
return this.sendFile(ctx, path, data.clientName);
}
return this.sendResponse(ctx, data, message, status);
}
}
new Ioc().make(LessonsController);

View File

@ -1,5 +1,6 @@
/* eslint-disable class-methods-use-this */
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { ResponseContract } from '@ioc:Adonis/Core/Response';
export default class BaseController {
public sendResponse(ctx: HttpContextContract, data: object, message: string, status = 200, meta?: any): void {
@ -13,7 +14,7 @@ export default class BaseController {
return ctx.response.status(response.status).send(response);
}
public sendFile(ctx: HttpContextContract, path: string) {
return ctx.response.download(path);
public sendFile(ctx: HttpContextContract, ...args: Parameters<ResponseContract['attachment']>) {
return ctx.response.attachment(...args);
}
}

View File

@ -1,8 +1,8 @@
export default interface IResponse {
export default interface IResponse<T = {}> {
success: boolean;
status: number;
message: string;
data: object;
data: T | {};
meta?: {
pagination?: any;
};

View File

@ -3,6 +3,7 @@ import { DateTime } from 'luxon';
import { nanoid } from 'nanoid';
import { BaseModel, beforeCreate, BelongsTo, belongsTo, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm';
import Course from './Course';
// eslint-disable-next-line import/no-cycle
import LessonContent from './LessonContent';
export default class Lesson extends BaseModel {

View File

@ -1,5 +1,7 @@
import { BaseModel, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm';
/* eslint-disable import/no-cycle */
import { BaseModel, BelongsTo, belongsTo, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm';
import { DateTime } from 'luxon';
import Lesson from './Lesson';
import LessonMaterial from './LessonMaterial';
export default class LessonContent extends BaseModel {
@ -15,6 +17,9 @@ export default class LessonContent extends BaseModel {
@hasMany(() => LessonMaterial, { foreignKey: 'lesson_content_id' })
public materials: HasMany<typeof LessonMaterial>;
@belongsTo(() => Lesson, { foreignKey: 'lesson_id' })
public lesson: BelongsTo<typeof Lesson>;
@column.dateTime({ autoCreate: true, serializeAs: null })
public createdAt: DateTime;

View File

@ -1,5 +1,7 @@
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm';
import { BaseModel, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm';
import { DateTime } from 'luxon';
// eslint-disable-next-line import/no-cycle
import LessonContent from './LessonContent';
export default class LessonMaterial extends BaseModel {
@column({ isPrimary: true })
@ -11,12 +13,20 @@ export default class LessonMaterial extends BaseModel {
@column()
public name: string;
@column()
public clientName: string;
@column()
public ext: string;
@column()
public lesson_content_id: number;
@belongsTo(() => LessonContent, {
foreignKey: 'lesson_content_id',
})
public content: BelongsTo<typeof LessonContent>;
@column.dateTime({ autoCreate: true })
public createdAt: DateTime;

View File

@ -1,8 +1,10 @@
/**
* Models
*/
import Drive from '@ioc:Adonis/Core/Drive';
import Course from 'App/Models/Course';
import Lesson from 'App/Models/Lesson';
import LessonMaterial from 'App/Models/LessonMaterial';
/**
* Validators
@ -13,8 +15,11 @@ import UpdateLessonValidator from 'App/Validators/Lesson/UpdateLessonValidator';
export default class LessonRepository {
private Lesson: typeof Lesson;
private LessonMaterial: typeof LessonMaterial;
constructor() {
this.Lesson = Lesson;
this.LessonMaterial = LessonMaterial;
}
/**
@ -49,6 +54,17 @@ export default class LessonRepository {
return lesson && lesson.content;
}
/**
* Get lesson material
*
* @param fileName Name of file
* @returns Lesson material file or null
*/
public async getMaterialFileByName(fileName: string): Promise<LessonMaterial | null> {
const material = await this.LessonMaterial.query().where('name', fileName).first();
return material;
}
/**
* Create lesson
*
@ -79,10 +95,12 @@ export default class LessonRepository {
*/
await file.moveToDisk('materials');
if (file.state === 'moved') {
const url = await Drive.getUrl(`materials/${file.fileName}`);
await content.related('materials').create({
name: file.clientName,
name: file.fileName,
clientName: file.clientName,
ext: file.extname,
url: file.filePath,
url,
});
}
})

View File

@ -1,14 +1,13 @@
import { inject, Ioc } from '@adonisjs/core/build/standalone';
// 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';
import LessonMaterial from 'App/Models/LessonMaterial';
/**
* Repositories
*/
@ -77,6 +76,55 @@ export default class LessonService {
};
}
/**
* Fetch lesson material by file name
*
* @param fileName Name of file
* @returns Response
*/
public async fetchMaterialFile(ctx: HttpContextContract, fileName: string): Promise<IResponse<LessonMaterial>> {
const material = await this.lessonRepository.getMaterialFileByName(fileName);
if (!material) {
return {
success: false,
status: HttpStatusEnum.NOT_FOUND,
data: {},
message: 'Material not found.',
error: {
code: 'E_NOT_FOUND',
},
};
}
/**
* Allow user to view lesson material
*/
await material.load('content', q => q.preload('lesson'));
const {
content: { lesson },
} = material;
if (await ctx.bouncer.denies('viewLessonContent', lesson)) {
return {
success: false,
status: HttpStatusEnum.FORBIDDEN,
message: 'You are not able to view this file.',
data: {},
error: {
code: 'E_FORBIDDEN',
},
};
}
return {
success: true,
status: HttpStatusEnum.OK,
message: 'Fetched lesson material.',
data: material,
};
}
/**
* Create lesson
*

View File

@ -8,6 +8,7 @@ export default class LessonMaterials extends BaseSchema {
table.increments('id').primary();
table.string('url').notNullable();
table.string('name').notNullable();
table.string('client_name').notNullable();
table.string('ext').notNullable();
table.integer('lesson_content_id').unsigned().references('lesson_contents.id').onDelete('CASCADE');
table.timestamp('created_at', { useTz: true });

View File

@ -9,4 +9,7 @@ Route.group(() => {
Route.get('/:id/content', 'Api/v1/LessonsController.getContent')
.middleware('role:admin,teacher,student')
.as('lessons.get-content');
Route.get('/materials/:file', 'Api/v1/LessonsController.getMaterial')
.middleware('role:admin,teacher,student')
.as('lessons.get-material');
}).prefix('lessons');