mirror of
https://github.com/sergeyyarkov/educt-server.git
synced 2026-10-11 18:29:15 +03:00
feat: download material of lesson
This commit is contained in:
parent
5b635ed164
commit
be2f05a780
@ -1,3 +1,4 @@
|
|||||||
|
import Application from '@ioc:Adonis/Core/Application';
|
||||||
import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone';
|
import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone';
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
|
||||||
@ -6,6 +7,11 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
|||||||
*/
|
*/
|
||||||
import LessonService from 'App/Services/LessonService';
|
import LessonService from 'App/Services/LessonService';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models
|
||||||
|
*/
|
||||||
|
import LessonMaterial from 'App/Models/LessonMaterial';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validators
|
* 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
|
* GET /lessons/:id/content
|
||||||
*/
|
*/
|
||||||
public async getContent(ctx: HttpContextContract) {
|
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);
|
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);
|
new Ioc().make(LessonsController);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable class-methods-use-this */
|
/* eslint-disable class-methods-use-this */
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import { ResponseContract } from '@ioc:Adonis/Core/Response';
|
||||||
|
|
||||||
export default class BaseController {
|
export default class BaseController {
|
||||||
public sendResponse(ctx: HttpContextContract, data: object, message: string, status = 200, meta?: any): void {
|
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);
|
return ctx.response.status(response.status).send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendFile(ctx: HttpContextContract, path: string) {
|
public sendFile(ctx: HttpContextContract, ...args: Parameters<ResponseContract['attachment']>) {
|
||||||
return ctx.response.download(path);
|
return ctx.response.attachment(...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
export default interface IResponse {
|
export default interface IResponse<T = {}> {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
status: number;
|
status: number;
|
||||||
message: string;
|
message: string;
|
||||||
data: object;
|
data: T | {};
|
||||||
meta?: {
|
meta?: {
|
||||||
pagination?: any;
|
pagination?: any;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { DateTime } from 'luxon';
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { BaseModel, beforeCreate, BelongsTo, belongsTo, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm';
|
import { BaseModel, beforeCreate, BelongsTo, belongsTo, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm';
|
||||||
import Course from './Course';
|
import Course from './Course';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import LessonContent from './LessonContent';
|
import LessonContent from './LessonContent';
|
||||||
|
|
||||||
export default class Lesson extends BaseModel {
|
export default class Lesson extends BaseModel {
|
||||||
|
|||||||
@ -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 { DateTime } from 'luxon';
|
||||||
|
import Lesson from './Lesson';
|
||||||
import LessonMaterial from './LessonMaterial';
|
import LessonMaterial from './LessonMaterial';
|
||||||
|
|
||||||
export default class LessonContent extends BaseModel {
|
export default class LessonContent extends BaseModel {
|
||||||
@ -15,6 +17,9 @@ export default class LessonContent extends BaseModel {
|
|||||||
@hasMany(() => LessonMaterial, { foreignKey: 'lesson_content_id' })
|
@hasMany(() => LessonMaterial, { foreignKey: 'lesson_content_id' })
|
||||||
public materials: HasMany<typeof LessonMaterial>;
|
public materials: HasMany<typeof LessonMaterial>;
|
||||||
|
|
||||||
|
@belongsTo(() => Lesson, { foreignKey: 'lesson_id' })
|
||||||
|
public lesson: BelongsTo<typeof Lesson>;
|
||||||
|
|
||||||
@column.dateTime({ autoCreate: true, serializeAs: null })
|
@column.dateTime({ autoCreate: true, serializeAs: null })
|
||||||
public createdAt: DateTime;
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
|||||||
@ -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';
|
import { DateTime } from 'luxon';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import LessonContent from './LessonContent';
|
||||||
|
|
||||||
export default class LessonMaterial extends BaseModel {
|
export default class LessonMaterial extends BaseModel {
|
||||||
@column({ isPrimary: true })
|
@column({ isPrimary: true })
|
||||||
@ -11,12 +13,20 @@ export default class LessonMaterial extends BaseModel {
|
|||||||
@column()
|
@column()
|
||||||
public name: string;
|
public name: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public clientName: string;
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
public ext: string;
|
public ext: string;
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
public lesson_content_id: number;
|
public lesson_content_id: number;
|
||||||
|
|
||||||
|
@belongsTo(() => LessonContent, {
|
||||||
|
foreignKey: 'lesson_content_id',
|
||||||
|
})
|
||||||
|
public content: BelongsTo<typeof LessonContent>;
|
||||||
|
|
||||||
@column.dateTime({ autoCreate: true })
|
@column.dateTime({ autoCreate: true })
|
||||||
public createdAt: DateTime;
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Models
|
* Models
|
||||||
*/
|
*/
|
||||||
|
import Drive from '@ioc:Adonis/Core/Drive';
|
||||||
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 LessonMaterial from 'App/Models/LessonMaterial';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validators
|
* Validators
|
||||||
@ -13,8 +15,11 @@ import UpdateLessonValidator from 'App/Validators/Lesson/UpdateLessonValidator';
|
|||||||
export default class LessonRepository {
|
export default class LessonRepository {
|
||||||
private Lesson: typeof Lesson;
|
private Lesson: typeof Lesson;
|
||||||
|
|
||||||
|
private LessonMaterial: typeof LessonMaterial;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.Lesson = Lesson;
|
this.Lesson = Lesson;
|
||||||
|
this.LessonMaterial = LessonMaterial;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,6 +54,17 @@ export default class LessonRepository {
|
|||||||
return lesson && lesson.content;
|
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
|
* Create lesson
|
||||||
*
|
*
|
||||||
@ -79,10 +95,12 @@ export default class LessonRepository {
|
|||||||
*/
|
*/
|
||||||
await file.moveToDisk('materials');
|
await file.moveToDisk('materials');
|
||||||
if (file.state === 'moved') {
|
if (file.state === 'moved') {
|
||||||
|
const url = await Drive.getUrl(`materials/${file.fileName}`);
|
||||||
await content.related('materials').create({
|
await content.related('materials').create({
|
||||||
name: file.clientName,
|
name: file.fileName,
|
||||||
|
clientName: file.clientName,
|
||||||
ext: file.extname,
|
ext: file.extname,
|
||||||
url: file.filePath,
|
url,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
||||||
// import { AuthContract } from '@ioc:Adonis/Addons/Auth';
|
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Datatypes
|
* Datatypes
|
||||||
*/
|
*/
|
||||||
import HttpStatusEnum from 'App/Datatypes/Enums/HttpStatusEnum';
|
import HttpStatusEnum from 'App/Datatypes/Enums/HttpStatusEnum';
|
||||||
// import RoleEnum from 'App/Datatypes/Enums/RoleEnum';
|
|
||||||
import IResponse from 'App/Datatypes/Interfaces/IResponse';
|
import IResponse from 'App/Datatypes/Interfaces/IResponse';
|
||||||
// import RoleHelper from 'App/Helpers/RoleHelper';
|
import LessonMaterial from 'App/Models/LessonMaterial';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repositories
|
* 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
|
* Create lesson
|
||||||
*
|
*
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export default class LessonMaterials extends BaseSchema {
|
|||||||
table.increments('id').primary();
|
table.increments('id').primary();
|
||||||
table.string('url').notNullable();
|
table.string('url').notNullable();
|
||||||
table.string('name').notNullable();
|
table.string('name').notNullable();
|
||||||
|
table.string('client_name').notNullable();
|
||||||
table.string('ext').notNullable();
|
table.string('ext').notNullable();
|
||||||
table.integer('lesson_content_id').unsigned().references('lesson_contents.id').onDelete('CASCADE');
|
table.integer('lesson_content_id').unsigned().references('lesson_contents.id').onDelete('CASCADE');
|
||||||
table.timestamp('created_at', { useTz: true });
|
table.timestamp('created_at', { useTz: true });
|
||||||
|
|||||||
@ -9,4 +9,7 @@ Route.group(() => {
|
|||||||
Route.get('/:id/content', 'Api/v1/LessonsController.getContent')
|
Route.get('/:id/content', 'Api/v1/LessonsController.getContent')
|
||||||
.middleware('role:admin,teacher,student')
|
.middleware('role:admin,teacher,student')
|
||||||
.as('lessons.get-content');
|
.as('lessons.get-content');
|
||||||
|
Route.get('/materials/:file', 'Api/v1/LessonsController.getMaterial')
|
||||||
|
.middleware('role:admin,teacher,student')
|
||||||
|
.as('lessons.get-material');
|
||||||
}).prefix('lessons');
|
}).prefix('lessons');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user