From c2b43d7ba2b9e44eadadb2793ad05e41b8008479 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Sat, 9 Oct 2021 21:06:33 +0300 Subject: [PATCH] feat: categories controller & fetch courses params --- .../Http/Api/v1/CategoriesController.ts | 83 ++++++++++++ .../Http/Api/v1/CoursesController.ts | 4 +- app/Models/Course.ts | 2 +- app/Repositories/CategoryRepository.ts | 49 +++++++- app/Repositories/CourseRepository.ts | 8 +- app/Services/CategoryService.ts | 119 ++++++++++++++++++ app/Services/CourseService.ts | 5 +- .../Category/CreateCategoryValidator.ts | 13 ++ .../Course/FetchCoursesValidator.ts | 13 ++ routes/apis/v1/categories.ts | 8 ++ routes/apis/v1/index.ts | 7 ++ 11 files changed, 304 insertions(+), 7 deletions(-) create mode 100644 app/Controllers/Http/Api/v1/CategoriesController.ts create mode 100644 app/Services/CategoryService.ts create mode 100644 app/Validators/Category/CreateCategoryValidator.ts create mode 100644 app/Validators/Course/FetchCoursesValidator.ts create mode 100644 routes/apis/v1/categories.ts diff --git a/app/Controllers/Http/Api/v1/CategoriesController.ts b/app/Controllers/Http/Api/v1/CategoriesController.ts new file mode 100644 index 0000000..7d778f5 --- /dev/null +++ b/app/Controllers/Http/Api/v1/CategoriesController.ts @@ -0,0 +1,83 @@ +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone'; + +/** + * Services + */ +import CategoryService from 'App/Services/CategoryService'; + +/** + * Validatoes + */ +import CreateCategoryValidator from 'App/Validators/Category/CreateCategoryValidator'; + +import BaseController from '../../BaseController'; + +@inject() +export default class CategoriesController extends BaseController { + private categoryService: CategoryService; + + constructor(categoryService: CategoryService) { + super(); + this.categoryService = categoryService; + } + + /** + * List of Categories + * GET /categories + */ + public async list(ctx: HttpContextContract) { + const result = await this.categoryService.fetchCategories(); + + 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); + } + + /** + * Show category by id + * GET /category/:id + */ + public async show(ctx: HttpContextContract) { + const result = await this.categoryService.fetchCategory(ctx.params.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); + } + + /** + * Create new category + * POST /categories/:id + */ + public async create(ctx: HttpContextContract) { + const payload = await ctx.request.validate(CreateCategoryValidator); + const result = await this.categoryService.createCategory(payload); + + 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); + } + + /** + * Delete category by id + * DELETE /categories/:id + */ + public async delete(ctx: HttpContextContract) { + const result = await this.categoryService.deleteCategory(ctx.params.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); + } +} + +new Ioc().make(CategoriesController); diff --git a/app/Controllers/Http/Api/v1/CoursesController.ts b/app/Controllers/Http/Api/v1/CoursesController.ts index 27b9162..e39c128 100644 --- a/app/Controllers/Http/Api/v1/CoursesController.ts +++ b/app/Controllers/Http/Api/v1/CoursesController.ts @@ -14,6 +14,7 @@ import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator'; import DelCourseFromUserValidator from 'App/Validators/Course/DelCourseFromUserValidator'; import SetCourseStatusValidator from 'App/Validators/Course/SetCourseStatusValidator'; import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator'; +import FetchCoursesValidator from 'App/Validators/Course/FetchCoursesValidator'; import BaseController from '../../BaseController'; @@ -31,7 +32,8 @@ export default class CoursesController extends BaseController { * GET /courses */ public async list(ctx: HttpContextContract) { - const result = await this.courseService.fetchCourses(); + const payload = await ctx.request.validate(FetchCoursesValidator); + const result = await this.courseService.fetchCourses(payload); if (!result.success && result.error) { throw new Exception(result.message, result.status, result.error.code); diff --git a/app/Models/Course.ts b/app/Models/Course.ts index 59a040e..e45a175 100644 --- a/app/Models/Course.ts +++ b/app/Models/Course.ts @@ -38,7 +38,7 @@ export default class Course extends BaseModel { @column({ serializeAs: null }) public category_id: string; - @column({ serializeAs: null }) + @column() public status: CourseStatusEnum; @belongsTo(() => Category, { diff --git a/app/Repositories/CategoryRepository.ts b/app/Repositories/CategoryRepository.ts index c8283e6..a4f3912 100644 --- a/app/Repositories/CategoryRepository.ts +++ b/app/Repositories/CategoryRepository.ts @@ -3,6 +3,11 @@ */ import Category from 'App/Models/Category'; +/** + * Validators + */ +import CreateCategoryValidator from 'App/Validators/Category/CreateCategoryValidator'; + export default class CategoryRepository { private Category: typeof Category; @@ -10,14 +15,56 @@ export default class CategoryRepository { this.Category = Category; } + /** + * Get all categories + * + * @returns Array of categories + */ + public async getAll(): Promise { + const categories = await this.Category.query(); + return categories; + } + /** * Get category by id * * @param id Category id - * @returns Caregory or null + * @returns Category or null */ public async getById(id: string | number): Promise { const category = await this.Category.query().where('id', id).first(); return category; } + + /** + * Create new category + * + * @param data Data input + * @returns Created category + */ + public async create(data: CreateCategoryValidator['schema']['props']): Promise { + const category = await this.Category.create({ + title: data.title, + description: data.description, + }); + + return category; + } + + /** + * Delete Category + * + * @param id Category id + * @returns Deleted category or null + */ + public async delete(id: string | number): Promise { + const category = await this.Category.query().where('id', id).first(); + + if (category) { + await category.delete(); + return category; + } + + return null; + } } diff --git a/app/Repositories/CourseRepository.ts b/app/Repositories/CourseRepository.ts index 13ea5e5..af37c93 100644 --- a/app/Repositories/CourseRepository.ts +++ b/app/Repositories/CourseRepository.ts @@ -16,6 +16,7 @@ import User from 'App/Models/User'; * Validators */ import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator'; +import FetchCoursesValidator from 'App/Validators/Course/FetchCoursesValidator'; import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator'; export default class CourseRepository { @@ -44,14 +45,17 @@ export default class CourseRepository { * * @returns List of courses */ - public async getAll(): Promise { + public async getAll(params?: FetchCoursesValidator['schema']['props']): Promise { + const { status = CourseStatusEnum.PUBLISHED } = params || {}; + const courses = await this.Course.query() .preload('teacher') .preload('category') .preload('lessons') .preload('students') .preload('image') - .where('status', CourseStatusEnum.PUBLISHED); + .where('status', status); + return courses; } diff --git a/app/Services/CategoryService.ts b/app/Services/CategoryService.ts new file mode 100644 index 0000000..3b2454b --- /dev/null +++ b/app/Services/CategoryService.ts @@ -0,0 +1,119 @@ +import { inject, Ioc } from '@adonisjs/core/build/standalone'; + +/** + * Datatypes + */ +import IResponse from 'App/Datatypes/Interfaces/IResponse'; +import HttpStatusEnum from 'App/Datatypes/Enums/HttpStatusEnum'; + +/** + * Repositories + */ +import CategoryRepository from 'App/Repositories/CategoryRepository'; + +/** + * Validators + */ +import CreateCategoryValidator from 'App/Validators/Category/CreateCategoryValidator'; + +@inject() +export default class CategoryService { + private categoryRepository: CategoryRepository; + + constructor(categoryRepository: CategoryRepository) { + this.categoryRepository = categoryRepository; + } + + /** + * Fetch list of categories + * + * @returns Response + */ + public async fetchCategories(): Promise { + const data = await this.categoryRepository.getAll(); + + return { + success: true, + status: HttpStatusEnum.OK, + message: 'Fetched all categories.', + data, + }; + } + + /** + * Fetch category by id + * + * @param id Category id + * @returns Response + */ + public async fetchCategory(id: string | number): Promise { + const data = await this.categoryRepository.getById(id); + + if (!data) { + return { + success: false, + status: HttpStatusEnum.NOT_FOUND, + message: 'Category not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + return { + success: true, + status: HttpStatusEnum.OK, + message: 'Fetched category.', + data, + }; + } + + /** + * Create new category + * + * @param data Data for create category + * @returns Response + */ + public async createCategory(data: CreateCategoryValidator['schema']['props']): Promise { + const category = await this.categoryRepository.create(data); + + return { + success: true, + status: HttpStatusEnum.CREATED, + message: 'Category created.', + data: category, + }; + } + + /** + * Delete category by id + * + * @param id Category id + * @returns Deleted category + */ + public async deleteCategory(id: string | number): Promise { + const data = await this.categoryRepository.delete(id); + + if (!data) { + return { + success: false, + status: HttpStatusEnum.NOT_FOUND, + message: 'Category not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + return { + success: true, + status: HttpStatusEnum.OK, + message: 'Category deleted.', + data, + }; + } +} + +new Ioc().make(CategoryService); diff --git a/app/Services/CourseService.ts b/app/Services/CourseService.ts index e0dcf5f..d5ac807 100644 --- a/app/Services/CourseService.ts +++ b/app/Services/CourseService.ts @@ -23,6 +23,7 @@ import ImageRepository from 'App/Repositories/ImageRepository'; */ import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator'; import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator'; +import FetchCoursesValidator from 'App/Validators/Course/FetchCoursesValidator'; @inject() export default class CourseService { @@ -51,8 +52,8 @@ export default class CourseService { * * @returns Response */ - public async fetchCourses(): Promise { - const data = await this.courseRepository.getAll(); + public async fetchCourses(params?: FetchCoursesValidator['schema']['props']): Promise { + const data = await this.courseRepository.getAll(params); return { success: true, diff --git a/app/Validators/Category/CreateCategoryValidator.ts b/app/Validators/Category/CreateCategoryValidator.ts new file mode 100644 index 0000000..1895f46 --- /dev/null +++ b/app/Validators/Category/CreateCategoryValidator.ts @@ -0,0 +1,13 @@ +import { schema } from '@ioc:Adonis/Core/Validator'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; + +export default class CreateCategoryValidator { + constructor(protected ctx: HttpContextContract) {} + + public schema = schema.create({ + title: schema.string(), + description: schema.string(), + }); + + public messages = {}; +} diff --git a/app/Validators/Course/FetchCoursesValidator.ts b/app/Validators/Course/FetchCoursesValidator.ts new file mode 100644 index 0000000..046c052 --- /dev/null +++ b/app/Validators/Course/FetchCoursesValidator.ts @@ -0,0 +1,13 @@ +import { schema } from '@ioc:Adonis/Core/Validator'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import CourseStatusEnum from 'App/Datatypes/Enums/CourseStatusEnum'; + +export default class FetchCoursesValidator { + constructor(protected ctx: HttpContextContract) {} + + public schema = schema.create({ + status: schema.enum.optional(Object.values(CourseStatusEnum)), + }); + + public messages = {}; +} diff --git a/routes/apis/v1/categories.ts b/routes/apis/v1/categories.ts new file mode 100644 index 0000000..1a31707 --- /dev/null +++ b/routes/apis/v1/categories.ts @@ -0,0 +1,8 @@ +import Route from '@ioc:Adonis/Core/Route'; + +Route.group(() => { + Route.get('/', 'Api/v1/CategoriesController.list').middleware('role:admin,teacher,student').as('categories.list'); + Route.get('/:id', 'Api/v1/CategoriesController.show').middleware('role:admin,teacher,student').as('categories.show'); + Route.post('/', 'Api/v1/CategoriesController.create').middleware('role:admin,teacher').as('categories.create'); + Route.delete('/:id', 'Api/v1/CategoriesController.delete').middleware('role:admin,teacher').as('categories.delete'); +}).prefix('categories'); diff --git a/routes/apis/v1/index.ts b/routes/apis/v1/index.ts index a5657ee..257caf2 100644 --- a/routes/apis/v1/index.ts +++ b/routes/apis/v1/index.ts @@ -55,6 +55,13 @@ Route.group(() => { * */ require('./images'); + + /** + * + * Categories module + * + */ + require('./categories'); }).middleware('auth'); }) .prefix('v1')