From 14122905d6e70c78b6fb396ce8ae3baa8661972f Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Wed, 24 Nov 2021 22:16:25 +0300 Subject: [PATCH] feat: added save-order route --- .../Http/Api/v1/LessonsController.ts | 20 ++++++++++++++++ app/Models/Lesson.ts | 2 +- app/Repositories/CourseRepository.ts | 2 +- app/Services/LessonService.ts | 23 +++++++++++++++++++ database/factories/index.ts | 1 + database/migrations/1617351321869_colors.ts | 3 ++- database/migrations/1627300211260_courses.ts | 4 ++-- database/seeders/Course.ts | 2 +- routes/apis/v1/lessons.ts | 3 +++ 9 files changed, 54 insertions(+), 6 deletions(-) diff --git a/app/Controllers/Http/Api/v1/LessonsController.ts b/app/Controllers/Http/Api/v1/LessonsController.ts index 3ac125c..90a9101 100644 --- a/app/Controllers/Http/Api/v1/LessonsController.ts +++ b/app/Controllers/Http/Api/v1/LessonsController.ts @@ -1,6 +1,7 @@ import Application from '@ioc:Adonis/Core/Application'; import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone'; import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import { schema } from '@ioc:Adonis/Core/Validator'; /** * Services @@ -137,6 +138,25 @@ export default class LessonsController extends BaseController { return this.sendResponse(ctx, data, message, status); } + + /** + * Save order of lessons + * POST /lessons/save-order + */ + // eslint-disable-next-line class-methods-use-this + public async saveOrder(ctx: HttpContextContract) { + const saveOrderSchema = schema.create({ + ids: schema.array().members(schema.string()), + }); + const payload = await ctx.request.validate({ schema: saveOrderSchema }); + const { data, message, status, success, error } = await this.lessonService.updateOrder(payload.ids); + + if (!success && error) { + throw new Exception(message, status, error.code); + } + + return this.sendResponse(ctx, data, message, status); + } } new Ioc().make(LessonsController); diff --git a/app/Models/Lesson.ts b/app/Models/Lesson.ts index 26ad5e8..a7f6b22 100644 --- a/app/Models/Lesson.ts +++ b/app/Models/Lesson.ts @@ -17,7 +17,7 @@ export default class Lesson extends BaseModel { @column() public title: string; - @column() + @column({ serializeAs: null }) public display_order: number; @column() diff --git a/app/Repositories/CourseRepository.ts b/app/Repositories/CourseRepository.ts index a9fa990..28fb993 100644 --- a/app/Repositories/CourseRepository.ts +++ b/app/Repositories/CourseRepository.ts @@ -82,7 +82,7 @@ export default class CourseRepository { .preload('teacher') .preload('color') .preload('category') - .preload('lessons', q => q.preload('color')) + .preload('lessons', q => q.preload('color').orderBy('display_order', 'asc')) .preload('students') .where('id', id) .first(); diff --git a/app/Services/LessonService.ts b/app/Services/LessonService.ts index b3401cd..2e226e7 100644 --- a/app/Services/LessonService.ts +++ b/app/Services/LessonService.ts @@ -6,6 +6,7 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; */ import HttpStatusEnum from 'App/Datatypes/Enums/HttpStatusEnum'; import IResponse from 'App/Datatypes/Interfaces/IResponse'; +import Lesson from 'App/Models/Lesson'; import LessonMaterial from 'App/Models/LessonMaterial'; /** @@ -264,6 +265,28 @@ export default class LessonService { data: lesson.content, }; } + + // eslint-disable-next-line class-methods-use-this + public async updateOrder(ids: string[]): Promise> { + /** + * Update order of lessons + */ + // TODO can be transformed into one query + await Promise.all( + ids.map(async (id, i) => { + await Lesson.query() + .where('id', id) + .update({ display_order: i + 1 }); + }) + ); + + return { + success: true, + status: HttpStatusEnum.OK, + message: 'Order updated.', + data: {}, + }; + } } new Ioc().make(LessonService); diff --git a/database/factories/index.ts b/database/factories/index.ts index 18f2dc0..1678e0c 100644 --- a/database/factories/index.ts +++ b/database/factories/index.ts @@ -64,6 +64,7 @@ export const LessonFactory = Factory.define(Lesson, ({ faker }) => { return { title: faker.lorem.sentence(6), description: faker.lorem.sentence(7), + display_order: 1, duration: DateTime.fromObject({ hour: faker.datatype.number(2), minute: faker.datatype.number({ min: 30, max: 59 }), diff --git a/database/migrations/1617351321869_colors.ts b/database/migrations/1617351321869_colors.ts index b531583..f26702f 100644 --- a/database/migrations/1617351321869_colors.ts +++ b/database/migrations/1617351321869_colors.ts @@ -11,7 +11,7 @@ export default class Colors extends BaseSchema { .enu('name', Object.keys(ColorEnum), { useNative: true, enumName: 'color_name_enum', - existingType: true, + existingType: false, schemaName: 'public', }) .notNullable(); @@ -20,6 +20,7 @@ export default class Colors extends BaseSchema { } public async down() { + await this.schema.raw('DROP TYPE IF EXISTS "color_name_enum" CASCADE'); this.schema.dropTable(this.tableName); } } diff --git a/database/migrations/1627300211260_courses.ts b/database/migrations/1627300211260_courses.ts index be8408e..9657c28 100644 --- a/database/migrations/1627300211260_courses.ts +++ b/database/migrations/1627300211260_courses.ts @@ -21,7 +21,7 @@ export default class Courses extends BaseSchema { .enu('status', Object.values(CourseStatusEnum), { useNative: true, enumName: 'course_status_enum', - existingType: true, + existingType: false, schemaName: 'public', }) .notNullable(); @@ -31,7 +31,7 @@ export default class Courses extends BaseSchema { } public async down() { - await this.schema.raw('DROP TYPE IF EXISTS "course_status" CASCADE'); + await this.schema.raw('DROP TYPE IF EXISTS "course_status_enum" CASCADE'); this.schema.dropTable(this.tableName); } } diff --git a/database/seeders/Course.ts b/database/seeders/Course.ts index 4a21c77..78ca18d 100644 --- a/database/seeders/Course.ts +++ b/database/seeders/Course.ts @@ -7,7 +7,7 @@ export default class CourseSeeder extends BaseSeeder { public async run() { this.CourseFactory = CourseFactory; - await this.CourseFactory.with('lessons', 2, lessonFactory => lessonFactory.with('content')) + await this.CourseFactory.with('lessons', 10, lessonFactory => lessonFactory.with('content')) .with('category') .with('teacher') .createMany(3); diff --git a/routes/apis/v1/lessons.ts b/routes/apis/v1/lessons.ts index e8d14ab..f2fa3f4 100644 --- a/routes/apis/v1/lessons.ts +++ b/routes/apis/v1/lessons.ts @@ -3,6 +3,9 @@ import Route from '@ioc:Adonis/Core/Route'; Route.group(() => { Route.get('/', 'Api/v1/LessonsController.list').middleware('role:admin,teacher,student').as('lessons.list'); Route.post('/', 'Api/v1/LessonsController.create').middleware('role:admin,teacher').as('lessons.create'); + Route.post('/save-order', 'Api/v1/LessonsController.saveOrder') + .middleware('role:admin,teacher') + .as('lessons.update-order'); Route.get('/:id', 'Api/v1/LessonsController.show').middleware('role:admin,teacher,student').as('lessons.show'); Route.delete('/:id', 'Api/v1/LessonsController.delete').middleware('role:admin,teacher').as('lessons.delete'); Route.put('/:id', 'Api/v1/LessonsController.update').middleware('role:admin,teacher').as('lessons.update');