From 7e24c913a077d5fc2b6c3308637d8e0f4646800a Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Mon, 9 Aug 2021 23:27:57 +0300 Subject: [PATCH] feat: lesson service --- .../Http/Api/v1/CoursesController.ts | 10 +- .../Http/Api/v1/LessonsController.ts | 91 ++++---- app/Repositories/LessonRepository.ts | 110 +++++++++ app/Services/LessonService.ts | 219 ++++++++++++++++++ 4 files changed, 370 insertions(+), 60 deletions(-) create mode 100644 app/Repositories/LessonRepository.ts create mode 100644 app/Services/LessonService.ts diff --git a/app/Controllers/Http/Api/v1/CoursesController.ts b/app/Controllers/Http/Api/v1/CoursesController.ts index 20218a3..ef5f6e2 100644 --- a/app/Controllers/Http/Api/v1/CoursesController.ts +++ b/app/Controllers/Http/Api/v1/CoursesController.ts @@ -2,10 +2,8 @@ import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone'; import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; /** - * Models + * Services */ -import Course from 'App/Models/Course'; -import User from 'App/Models/User'; import CourseService from 'App/Services/CourseService'; /** @@ -22,15 +20,9 @@ import BaseController from '../../BaseController'; export default class CoursesController extends BaseController { private courseService: CourseService; - private readonly Course: typeof Course; - - private readonly User: typeof User; - constructor(courseService: CourseService) { super(); this.courseService = courseService; - this.Course = Course; - this.User = User; } /** diff --git a/app/Controllers/Http/Api/v1/LessonsController.ts b/app/Controllers/Http/Api/v1/LessonsController.ts index 43d8869..025fbfb 100644 --- a/app/Controllers/Http/Api/v1/LessonsController.ts +++ b/app/Controllers/Http/Api/v1/LessonsController.ts @@ -1,11 +1,10 @@ -import { Exception } from '@adonisjs/core/build/standalone'; +import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone'; import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; /** - * Models + * Services */ -import Lesson from 'App/Models/Lesson'; -import Course from 'App/Models/Course'; +import LessonService from 'App/Services/LessonService'; /** * Validators @@ -15,15 +14,13 @@ import UpdateLessonValidator from 'App/Validators/Lesson/UpdateLessonValidator'; import BaseController from '../../BaseController'; +@inject() export default class LessonsController extends BaseController { - private readonly Lesson: typeof Lesson; + private lessonService: LessonService; - private readonly Course: typeof Course; - - constructor() { + constructor(lessonService: LessonService) { super(); - this.Lesson = Lesson; - this.Course = Course; + this.lessonService = lessonService; } /** @@ -31,9 +28,13 @@ export default class LessonsController extends BaseController { * GET /lessons */ public async list(ctx: HttpContextContract) { - const lessons = await this.Lesson.query(); + const result = await this.lessonService.fetchLessons(); - return this.sendResponse(ctx, lessons, 'Fetched list lessons.'); + 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); } /** @@ -41,9 +42,13 @@ export default class LessonsController extends BaseController { * GET /lessons/:id */ public async show(ctx: HttpContextContract) { - const lesson = await this.Lesson.findOrFail(ctx.params.id); + const result = await this.lessonService.fetchLesson(ctx.params.id); - return this.sendResponse(ctx, lesson, 'Fetched lesson by id.'); + 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); } /** @@ -52,20 +57,13 @@ export default class LessonsController extends BaseController { */ public async create(ctx: HttpContextContract) { const payload = await ctx.request.validate(CreateLessonValidator); - const course = await this.Course.findOrFail(payload.course_id); - const lesson = await this.Lesson.create({ - title: payload.title, - description: payload.description, - }); + const result = await this.lessonService.createLesson(payload); - await lesson.related('course').associate(course); - await lesson.related('content').create({ - video_url: payload.video_url, - }); + if (!result.success && result.error) { + throw new Exception(result.message, result.status, result.error.code); + } - await lesson.load('content'); - - return this.sendResponse(ctx, lesson, 'Created new lesson.'); + return this.sendResponse(ctx, result.data, result.message, result.status); } /** @@ -73,11 +71,13 @@ export default class LessonsController extends BaseController { * DELETE /lessons/:id */ public async delete(ctx: HttpContextContract) { - const lesson = await this.Lesson.findOrFail(ctx.params.id); + const result = await this.lessonService.deleteLesson(ctx.params.id); - await lesson.delete(); + if (!result.success && result.error) { + throw new Exception(result.message, result.status, result.error.code); + } - return this.sendResponse(ctx, lesson, 'Lesson deleted.'); + return this.sendResponse(ctx, result.data, result.message, result.status); } /** @@ -86,17 +86,13 @@ export default class LessonsController extends BaseController { */ public async update(ctx: HttpContextContract) { const payload = await ctx.request.validate(UpdateLessonValidator); - const lesson = await this.Lesson.firstOrFail(ctx.params.id); + const result = await this.lessonService.updateLesson(ctx.params.id, payload); - await lesson - .merge({ - course_id: payload.course_id, - title: payload.title, - description: payload.description, - }) - .save(); + if (!result.success && result.error) { + throw new Exception(result.message, result.status, result.error.code); + } - return this.sendResponse(ctx, lesson, 'Lesson updated.'); + return this.sendResponse(ctx, result.data, result.message, result.status); } /** @@ -104,21 +100,14 @@ export default class LessonsController extends BaseController { * GET /lessons/:id/content */ public async getContent(ctx: HttpContextContract) { - const user = await ctx.auth.use('api').authenticate(); - const lesson = await this.Lesson.query() - .preload('content') - .preload('course') - .where('id', ctx.params.id) - .firstOrFail(); + const result = await this.lessonService.fetchLessonContent(ctx.params.id, ctx.auth); - await user.load('courses'); - - const userHasCourse = user.courses.find(course => course.id === lesson.course.id); - - if (!userHasCourse) { - throw new Exception('The user is not a student of this course.', 403, 'E_ACCESS_DENIED'); + if (!result.success && result.error) { + throw new Exception(result.message, result.status, result.error.code); } - return this.sendResponse(ctx, lesson.content, 'Lesson content fetched.'); + return this.sendResponse(ctx, result.data, result.message, result.status); } } + +new Ioc().make(LessonsController); diff --git a/app/Repositories/LessonRepository.ts b/app/Repositories/LessonRepository.ts new file mode 100644 index 0000000..82e0293 --- /dev/null +++ b/app/Repositories/LessonRepository.ts @@ -0,0 +1,110 @@ +/** + * Models + */ +import Course from 'App/Models/Course'; +import Lesson from 'App/Models/Lesson'; + +/** + * Validators + */ +import CreateLessonValidator from 'App/Validators/Lesson/CreateLessonValidator'; +import UpdateLessonValidator from 'App/Validators/Lesson/UpdateLessonValidator'; + +export default class LessonRepository { + private Lesson: typeof Lesson; + + constructor() { + this.Lesson = Lesson; + } + + /** + * Get all lessons + * + * @returns Lessons + */ + public async getAll(): Promise { + const lessons = await this.Lesson.query(); + return lessons; + } + + /** + * Get one lesson by id + * + * @param id Lesson id + * @returns Lesson or null + */ + public async getById(id: string | number): Promise { + const lesson = await this.Lesson.query().where('id', id).first(); + return lesson; + } + + /** + * Get content of lesson by id + * + * @param id Lesson id + * @returns Content of lesson or null + */ + public async getContentById(id: string | number): Promise { + const lesson = await this.Lesson.query().preload('content').where('id', id).first(); + return lesson && lesson.content; + } + + /** + * Create lesson + * + * @param course Course of lesson + * @returns Lesson + */ + public async create(course: Course, data: CreateLessonValidator['schema']['props']): Promise { + const lesson = await this.Lesson.create({ + title: data.title, + description: data.description, + }); + + await lesson.related('course').associate(course); + await lesson.related('content').create({ video_url: data.video_url }); + await lesson.load('content'); + + return lesson; + } + + /** + * Update lesson + * + * @param id Lesson id + * @returns Updated lesson + */ + public async update(id: string | number, data: UpdateLessonValidator['schema']['props']): Promise { + const lesson = await this.Lesson.query().where('id', id).first(); + + if (lesson) { + await lesson + .merge({ + course_id: data.course_id, + title: data.title, + description: data.description, + }) + .save(); + + return lesson; + } + return null; + } + + /** + * Delete lesson + * + * @param id Lesson id + * @returns Deleted lesson or null + */ + public async delete(id: string | number): Promise { + const lesson = await this.Lesson.query().where('id', id).first(); + + if (lesson) { + await lesson.delete(); + return lesson; + } + + return null; + } +} diff --git a/app/Services/LessonService.ts b/app/Services/LessonService.ts new file mode 100644 index 0000000..bdd0f3c --- /dev/null +++ b/app/Services/LessonService.ts @@ -0,0 +1,219 @@ +import { inject, Ioc } from '@adonisjs/core/build/standalone'; +import { AuthContract } from '@ioc:Adonis/Addons/Auth'; + +/** + * Datatypes + */ +import StatusCodeEnum from 'App/Datatypes/Enums/StatusCodeEnum'; +import IResponse from 'App/Datatypes/Interfaces/IResponse'; +/** + * Repositories + */ +import CourseRepository from 'App/Repositories/CourseRepository'; +import LessonRepository from 'App/Repositories/LessonRepository'; + +/** + * Validators + */ +import CreateLessonValidator from 'App/Validators/Lesson/CreateLessonValidator'; +import UpdateLessonValidator from 'App/Validators/Lesson/UpdateLessonValidator'; + +@inject() +export default class LessonService { + private courseRepository: CourseRepository; + + private lessonRepository: LessonRepository; + + constructor(courseRepository: CourseRepository, lessonRepository: LessonRepository) { + this.courseRepository = courseRepository; + this.lessonRepository = lessonRepository; + } + + /** + * Fetch list of lessons + * + * @returns Response + */ + public async fetchLessons(): Promise { + const lessons = await this.lessonRepository.getAll(); + + return { + success: true, + status: StatusCodeEnum.OK, + message: 'Fetched all lessons.', + data: lessons, + }; + } + + /** + * Fetch one lesson by id + * + * @param id Lesson id + * @returns Response + */ + public async fetchLesson(id: string | number): Promise { + const lesson = await this.lessonRepository.getById(id); + + if (!lesson) { + return { + success: false, + status: StatusCodeEnum.NOT_FOUND, + message: 'Lesson not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + return { + success: true, + status: StatusCodeEnum.OK, + message: 'Fetched lesson.', + data: lesson, + }; + } + + /** + * Create lesson + * + * @param data Data input + * @returns Response + */ + public async createLesson(data: CreateLessonValidator['schema']['props']): Promise { + const course = await this.courseRepository.getById(data.course_id); + + if (!course) { + return { + success: false, + status: StatusCodeEnum.NOT_FOUND, + message: 'Course not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + const lesson = await this.lessonRepository.create(course, data); + + return { + success: true, + status: StatusCodeEnum.CREATED, + message: 'Lesson created.', + data: lesson, + }; + } + + /** + * Delete lesson + * + * @param id Lesson id + * @returns Response + */ + public async deleteLesson(id: string | number): Promise { + const lesson = await this.lessonRepository.delete(id); + + if (!lesson) { + return { + success: false, + status: StatusCodeEnum.NOT_FOUND, + message: 'Lesson not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + return { + success: true, + status: StatusCodeEnum.OK, + message: 'Lesson deleted.', + data: lesson, + }; + } + + /** + * Update lesson + * + * @param id Lesson id + * @param data Data input + * @returns Response + */ + public async updateLesson(id: string | number, data: UpdateLessonValidator['schema']['props']): Promise { + const lesson = await this.lessonRepository.update(id, data); + + if (!lesson) { + return { + success: false, + status: StatusCodeEnum.NOT_FOUND, + message: 'Lesson not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + return { + success: true, + status: StatusCodeEnum.OK, + message: 'Lesson updated.', + data: lesson, + }; + } + + /** + * Fetch content of lesson by id + * + * @param id Lesson id + * @param auth AuthContract + * @returns Response + */ + public async fetchLessonContent(id: string | number, auth: AuthContract): Promise { + const user = await auth.use('api').authenticate(); + await user.load('courses'); + + const lesson = await this.lessonRepository.getById(id); + + if (!lesson) { + return { + success: false, + status: StatusCodeEnum.NOT_FOUND, + message: 'Lesson not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + await lesson.load('course'); + + const userHasCourse = user.courses.find(course => course.id === lesson.course.id); + + if (!userHasCourse) { + return { + success: false, + status: StatusCodeEnum.FORBIDDEN, + message: 'The user is not a student of this course.', + data: {}, + error: { + code: 'E_FORBIDDEN', + }, + }; + } + + await lesson.load('content'); + + return { + success: true, + status: StatusCodeEnum.OK, + message: 'Fetched lesson content.', + data: lesson.content, + }; + } +} + +new Ioc().make(LessonService);