diff --git a/app/Controllers/Http/Api/AssetsController.ts b/app/Controllers/Http/Api/AssetsController.ts new file mode 100644 index 0000000..c5914b7 --- /dev/null +++ b/app/Controllers/Http/Api/AssetsController.ts @@ -0,0 +1,39 @@ +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone'; + +/** + * Services + */ +import AssetsService from 'App/Services/AssetsService'; + +import BaseController from '../BaseController'; + +@inject() +export default class AssetsController extends BaseController { + private assetsService: AssetsService; + + constructor(assetsService: AssetsService) { + super(); + this.assetsService = assetsService; + } + + /** + * Send background image of course + * GET /media/images/courses/:fileName + */ + public async serveCourseImage(ctx: HttpContextContract) { + const result = await this.assetsService.getCourseImagePath(ctx.params.fileName); + + if (typeof result !== 'string') { + if (!result.success && result.error) { + throw new Exception(result.message, result.status, result.error.code); + } + + return this.sendResponse(ctx, result.data, result.message, result.status); + } + + return this.sendFile(ctx, result); + } +} + +new Ioc().make(AssetsController); diff --git a/app/Controllers/Http/BaseController.ts b/app/Controllers/Http/BaseController.ts index c4ad5d5..7acac39 100644 --- a/app/Controllers/Http/BaseController.ts +++ b/app/Controllers/Http/BaseController.ts @@ -12,4 +12,8 @@ export default class BaseController { return ctx.response.status(response.status).send(response); } + + public sendFile(ctx: HttpContextContract, path: string) { + return ctx.response.download(path); + } } diff --git a/app/Models/Image.ts b/app/Models/Image.ts index 680deb1..3cebc7a 100644 --- a/app/Models/Image.ts +++ b/app/Models/Image.ts @@ -5,7 +5,7 @@ export default class Image extends BaseModel { @column({ isPrimary: true }) public id: number; - @column() + @column({ serializeAs: null }) public path: string; @column() diff --git a/app/Repositories/CourseRepository.ts b/app/Repositories/CourseRepository.ts index 2eba327..be52301 100644 --- a/app/Repositories/CourseRepository.ts +++ b/app/Repositories/CourseRepository.ts @@ -209,9 +209,11 @@ export default class CourseRepository { */ public async delete(id: string | number): Promise { const course = await this.Course.query() - .preload('teacher') .preload('category') - .preload('lessons') + .preload('image') + .withCount('students') + .withCount('likes') + .withCount('lessons') .where('id', id) .first(); diff --git a/app/Repositories/ImageRepository.ts b/app/Repositories/ImageRepository.ts index cceb755..82f0c78 100644 --- a/app/Repositories/ImageRepository.ts +++ b/app/Repositories/ImageRepository.ts @@ -15,14 +15,26 @@ export default class ImageRepository { return image; } + public async getByFileName(name: string): Promise { + const image = await this.Image.query().where('name', name).first(); + return image; + } + public async getAll(): Promise { const images = await this.Image.query(); return images; } public async create(file: MultipartFileContract): Promise { - await file.moveToDisk('./images'); - const image = await this.Image.create({ name: file.clientName, path: file.filePath, ext: file.extname }); + /** + * Save image to disk + */ + await file.moveToDisk('images/courses'); + + /** + * Save image to database + */ + const image = await this.Image.create({ name: file.fileName, path: file.filePath, ext: file.extname }); return image; } @@ -30,7 +42,14 @@ export default class ImageRepository { const image = await this.Image.query().where('id', id).first(); if (image) { + /** + * Delete from drive + */ await Drive.delete(image.path); + + /** + * Delete from database + */ await image.delete(); return image; } diff --git a/app/Services/AssetsService.ts b/app/Services/AssetsService.ts new file mode 100644 index 0000000..c5850f3 --- /dev/null +++ b/app/Services/AssetsService.ts @@ -0,0 +1,47 @@ +import { inject, Ioc } from '@adonisjs/core/build/standalone'; +import Application from '@ioc:Adonis/Core/Application'; + +/** + * Datatypes + */ +import IResponse from 'App/Datatypes/Interfaces/IResponse'; +import HttpStatusEnum from 'App/Datatypes/Enums/HttpStatusEnum'; + +/** + * Reposirories + */ +import ImageRepository from 'App/Repositories/ImageRepository'; + +@inject() +export default class AssetsService { + private imageRepository: ImageRepository; + + constructor(imageRepository: ImageRepository) { + this.imageRepository = imageRepository; + } + + /** + * + * @param name Image name + * @returns Pathname + */ + public async getCourseImagePath(name: string): Promise { + const image = await this.imageRepository.getByFileName(name); + + if (!image) { + return { + success: false, + status: HttpStatusEnum.NOT_FOUND, + message: 'Image not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + return Application.tmpPath(`uploads/images/courses/${image.name}`); + } +} + +new Ioc().make(AssetsService); diff --git a/routes/api.ts b/routes/api.ts index 510a5d1..a496f70 100644 --- a/routes/api.ts +++ b/routes/api.ts @@ -2,7 +2,15 @@ import Route from '@ioc:Adonis/Core/Route'; Route.group(() => { + /** + * v1 + */ require('./apis/v1/index'); + + /** + * Assets routes + */ + require('./apis/assets/index'); }) .prefix('api') .as('api'); diff --git a/routes/apis/assets/index.ts b/routes/apis/assets/index.ts new file mode 100644 index 0000000..674ee26 --- /dev/null +++ b/routes/apis/assets/index.ts @@ -0,0 +1,10 @@ +import Route from '@ioc:Adonis/Core/Route'; + +Route.group(() => { + /** + * Serve background image for course + */ + Route.get('/images/courses/:fileName', 'Api/AssetsController.serveCourseImage').as('serve.course.image'); +}) + .prefix('assets') + .as('assets'); diff --git a/routes/index.ts b/routes/index.ts index 1172edc..d614d40 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -22,5 +22,8 @@ import Route from '@ioc:Adonis/Core/Route'; */ Route.group(() => { + /** + * Api routes + */ require('./api'); });