mirror of
https://github.com/sergeyyarkov/educt-server.git
synced 2026-10-11 18:29:15 +03:00
Merge branch 'release/1.0.0'
This commit is contained in:
commit
38a911a771
@ -27,7 +27,6 @@
|
|||||||
"preloads": ["./routes/index", "./start/kernel", "./start/event", "./start/bouncer"],
|
"preloads": ["./routes/index", "./start/kernel", "./start/event", "./start/bouncer"],
|
||||||
"providers": [
|
"providers": [
|
||||||
"./providers/AppProvider",
|
"./providers/AppProvider",
|
||||||
"./providers/CloudinaryProvider",
|
|
||||||
"@adonisjs/core",
|
"@adonisjs/core",
|
||||||
"@adonisjs/lucid",
|
"@adonisjs/lucid",
|
||||||
"@adonisjs/auth",
|
"@adonisjs/auth",
|
||||||
|
|||||||
@ -12,10 +12,6 @@ REDIS_CONNECTION=local
|
|||||||
REDIS_HOST=127.0.0.1
|
REDIS_HOST=127.0.0.1
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
CLOUDINARY_CLOUD_NAME=
|
|
||||||
CLOUDINARY_API_KEY=
|
|
||||||
CLOUDINARY_API_SECRET=
|
|
||||||
CLOUDINARY_SECURE=true
|
|
||||||
SMTP_HOST=localhost
|
SMTP_HOST=localhost
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_USERNAME=<username>
|
SMTP_USERNAME=<username>
|
||||||
|
|||||||
20
CHANGELOG.md
Normal file
20
CHANGELOG.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
- API deployment with Docker and Nginx web server.
|
||||||
|
|
||||||
|
## [1.0.0] - 2021-11-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Base CRUD operations on models
|
||||||
|
- Displaying a background by API for each course
|
||||||
|
- Authentication with API tokens
|
||||||
|
- Email updating with verification
|
||||||
|
- Updating the password of an authenticated user
|
||||||
|
- Downloading materials from each lesson
|
||||||
|
- Implemented simple role-based route access control
|
||||||
|
- Course likes system
|
||||||
|
- Access to lesson materials
|
||||||
|
- Lesson ordering
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import Application from '@ioc:Adonis/Core/Application';
|
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';
|
||||||
|
import { schema } from '@ioc:Adonis/Core/Validator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Services
|
* Services
|
||||||
@ -137,6 +138,25 @@ export default class LessonsController extends BaseController {
|
|||||||
|
|
||||||
return this.sendResponse(ctx, data, message, status);
|
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);
|
new Ioc().make(LessonsController);
|
||||||
|
|||||||
@ -11,15 +11,6 @@ enum ColorEnum {
|
|||||||
Purple = '#800080',
|
Purple = '#800080',
|
||||||
Indigo = '#4B0082',
|
Indigo = '#4B0082',
|
||||||
Teal = '#008080',
|
Teal = '#008080',
|
||||||
LightGray = '#D3D3D3',
|
|
||||||
LightSlateGray = '#778899',
|
|
||||||
LightGreen = '#90EE90',
|
|
||||||
LightSeaGreen = '#20B2AA',
|
|
||||||
LightCoral = '#FFA07A',
|
|
||||||
LightCyan = '#E0FFFF',
|
|
||||||
LightSteelBlue = '#B0C4DE',
|
|
||||||
LightBlue = '#ADD8E6',
|
|
||||||
LightSkyBlue = '#87CEFA',
|
|
||||||
MediumVioletRed = '#C71585',
|
MediumVioletRed = '#C71585',
|
||||||
MediumOrchid = '#BA55D3',
|
MediumOrchid = '#BA55D3',
|
||||||
MediumPurple = '#9370DB',
|
MediumPurple = '#9370DB',
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import { DateTime } from 'luxon';
|
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 ColorHelper from 'App/Helpers/ColorHelper';
|
||||||
import Course from './Course';
|
import Course from './Course';
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import LessonContent from './LessonContent';
|
import LessonContent from './LessonContent';
|
||||||
@ -17,6 +18,9 @@ export default class Lesson extends BaseModel {
|
|||||||
@column()
|
@column()
|
||||||
public title: string;
|
public title: string;
|
||||||
|
|
||||||
|
@column({ serializeAs: null })
|
||||||
|
public display_order: number;
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
public description: string;
|
public description: string;
|
||||||
|
|
||||||
@ -55,4 +59,13 @@ export default class Lesson extends BaseModel {
|
|||||||
public static assignLessonId(lesson: Lesson) {
|
public static assignLessonId(lesson: Lesson) {
|
||||||
lesson.id = nanoid();
|
lesson.id = nanoid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@beforeCreate()
|
||||||
|
public static async assignRandomColor(course: Course) {
|
||||||
|
const color = await ColorHelper.generateRandomColor();
|
||||||
|
|
||||||
|
if (color) {
|
||||||
|
course.color_id = color.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,7 @@ export default class CourseRepository {
|
|||||||
.preload('teacher')
|
.preload('teacher')
|
||||||
.preload('color')
|
.preload('color')
|
||||||
.preload('category')
|
.preload('category')
|
||||||
.preload('lessons', q => q.preload('color'))
|
.preload('lessons', q => q.preload('color').orderBy('display_order', 'asc'))
|
||||||
.preload('students')
|
.preload('students')
|
||||||
.where('id', id)
|
.where('id', id)
|
||||||
.first();
|
.first();
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser';
|
import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser';
|
||||||
// import CloudinaryService from '@ioc:Adonis/Addons/Cloudinary';
|
|
||||||
import Drive from '@ioc:Adonis/Core/Drive';
|
import Drive from '@ioc:Adonis/Core/Drive';
|
||||||
import Image from 'App/Models/Image';
|
import Image from 'App/Models/Image';
|
||||||
|
|
||||||
@ -56,35 +55,4 @@ export default class ImageRepository {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create image in cloudinary cloud
|
|
||||||
*
|
|
||||||
* @param file File
|
|
||||||
* @param publicId Image public id
|
|
||||||
* @returns Created image
|
|
||||||
*/
|
|
||||||
// public async createInCloudinaryCloud(file: MultipartFileContract, publicId: string | undefined): Promise<Image> {
|
|
||||||
// const response = await CloudinaryService.upload(file, publicId);
|
|
||||||
// const image = await this.Image.create({ name: publicId, path: response.secure_url, ext: response.format });
|
|
||||||
// return image;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete image from cloudinary cloud
|
|
||||||
*
|
|
||||||
* @param id Image id
|
|
||||||
* @returns Result or null
|
|
||||||
*/
|
|
||||||
// public async deleteFormCloudinaryCloud(id: number): Promise<Image | null> {
|
|
||||||
// const image = await this.Image.query().where('id', id).first();
|
|
||||||
|
|
||||||
// if (image) {
|
|
||||||
// await CloudinaryService.destroy(image.name);
|
|
||||||
// await image.delete();
|
|
||||||
// return image;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,10 +72,13 @@ export default class LessonRepository {
|
|||||||
* @returns Lesson
|
* @returns Lesson
|
||||||
*/
|
*/
|
||||||
public async create(course: Course, data: CreateLessonValidator['schema']['props']): Promise<Lesson> {
|
public async create(course: Course, data: CreateLessonValidator['schema']['props']): Promise<Lesson> {
|
||||||
|
await course.loadCount('lessons');
|
||||||
|
|
||||||
const lesson = await this.Lesson.create({
|
const lesson = await this.Lesson.create({
|
||||||
title: data.title,
|
title: data.title,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
duration: data.duration.toFormat('HH:mm:ss'),
|
duration: data.duration.toFormat('HH:mm:ss'),
|
||||||
|
display_order: Number.parseInt(course.$extras.lessons_count, 10) + 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,67 +0,0 @@
|
|||||||
import * as cloudinary from 'cloudinary';
|
|
||||||
import { UploadApiResponse, UploadApiOptions, ResponseCallback, ResourceType, DeliveryType } from 'cloudinary';
|
|
||||||
import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser';
|
|
||||||
import { CloudinaryConfig, CloudinaryInterface } from '@ioc:Adonis/Addons/Cloudinary';
|
|
||||||
|
|
||||||
export default class CloudinaryService implements CloudinaryInterface {
|
|
||||||
private readonly config: CloudinaryConfig;
|
|
||||||
|
|
||||||
private cloudinary: typeof cloudinary;
|
|
||||||
|
|
||||||
constructor(config: CloudinaryConfig) {
|
|
||||||
/**
|
|
||||||
* Cloudinary config
|
|
||||||
*/
|
|
||||||
cloudinary.v2.config({
|
|
||||||
api_key: config.apiKey,
|
|
||||||
api_secret: config.apiSecret,
|
|
||||||
cloud_name: config.cloudName,
|
|
||||||
secure: config.secure,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.cloudinary = cloudinary;
|
|
||||||
this.config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async upload(
|
|
||||||
file: MultipartFileContract,
|
|
||||||
publicId: string | undefined = undefined,
|
|
||||||
uploadOptions?: UploadApiOptions,
|
|
||||||
callback?: ResponseCallback
|
|
||||||
): Promise<UploadApiResponse> {
|
|
||||||
const path = CloudinaryService.getPathFromFile(file);
|
|
||||||
const response = await this.cloudinary.v2.uploader.upload(
|
|
||||||
path,
|
|
||||||
{ public_id: publicId, ...uploadOptions },
|
|
||||||
callback
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async destroy(
|
|
||||||
publicId: string,
|
|
||||||
options?: {
|
|
||||||
resource_type?: ResourceType;
|
|
||||||
type?: DeliveryType;
|
|
||||||
invalidate?: boolean;
|
|
||||||
}
|
|
||||||
): Promise<any> {
|
|
||||||
const response = await this.cloudinary.v2.uploader.destroy(publicId, options);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConfig(): CloudinaryConfig {
|
|
||||||
return this.config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getPathFromFile(file: MultipartFileContract): string {
|
|
||||||
const path = file.tmpPath ?? file.filePath;
|
|
||||||
|
|
||||||
if (!path) {
|
|
||||||
throw new Error('Cannot get file path from file');
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||||
|
import Database from '@ioc:Adonis/Lucid/Database';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Datatypes
|
* Datatypes
|
||||||
@ -264,6 +265,32 @@ export default class LessonService {
|
|||||||
data: lesson.content,
|
data: lesson.content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
public async updateOrder(ids: string[]): Promise<IResponse<{}>> {
|
||||||
|
/**
|
||||||
|
* Update order of lessons
|
||||||
|
*/
|
||||||
|
const params = ids
|
||||||
|
.map((id, i) => ({ id, i: i + 1 }))
|
||||||
|
.reduce((result: Array<number | string>, { id, i }) => result.concat(id, i), []);
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
UPDATE lessons
|
||||||
|
SET display_order = t.display_order
|
||||||
|
FROM (VALUES ${ids.map(() => `(?, ??)`).join(',')}) as t(id, display_order)
|
||||||
|
WHERE t.id = lessons.id
|
||||||
|
`;
|
||||||
|
|
||||||
|
await Database.rawQuery(query, params);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
status: HttpStatusEnum.OK,
|
||||||
|
message: 'Order updated.',
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new Ioc().make(LessonService);
|
new Ioc().make(LessonService);
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
declare module '@ioc:Adonis/Addons/Cloudinary' {
|
|
||||||
import {
|
|
||||||
ResponseCallback,
|
|
||||||
TransformationOptions,
|
|
||||||
UploadApiOptions,
|
|
||||||
UploadApiResponse,
|
|
||||||
ResourceType,
|
|
||||||
DeliveryType,
|
|
||||||
} from 'cloudinary';
|
|
||||||
import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser';
|
|
||||||
|
|
||||||
export interface CloudinaryConfig {
|
|
||||||
cloudName: string;
|
|
||||||
apiKey: string;
|
|
||||||
apiSecret: string;
|
|
||||||
secure: boolean;
|
|
||||||
[key: string]: TransformationOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CloudinaryInterface {
|
|
||||||
upload(
|
|
||||||
file: MultipartFileContract,
|
|
||||||
publicId: string | undefined,
|
|
||||||
uploadOptions?: UploadApiOptions,
|
|
||||||
callback?: ResponseCallback
|
|
||||||
): Promise<UploadApiResponse>;
|
|
||||||
|
|
||||||
destroy(
|
|
||||||
publicId: string,
|
|
||||||
options?: {
|
|
||||||
resource_type?: ResourceType;
|
|
||||||
type?: DeliveryType;
|
|
||||||
invalidate?: boolean;
|
|
||||||
}
|
|
||||||
): Promise<any>;
|
|
||||||
|
|
||||||
getConfig(): CloudinaryConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CloudinaryService: CloudinaryInterface;
|
|
||||||
export default CloudinaryService;
|
|
||||||
}
|
|
||||||
@ -64,12 +64,13 @@ export const LessonFactory = Factory.define(Lesson, ({ faker }) => {
|
|||||||
return {
|
return {
|
||||||
title: faker.lorem.sentence(6),
|
title: faker.lorem.sentence(6),
|
||||||
description: faker.lorem.sentence(7),
|
description: faker.lorem.sentence(7),
|
||||||
|
display_order: 1,
|
||||||
duration: DateTime.fromObject({
|
duration: DateTime.fromObject({
|
||||||
hour: faker.datatype.number(2),
|
hour: faker.datatype.number(2),
|
||||||
minute: faker.datatype.number({ min: 30, max: 59 }),
|
minute: faker.datatype.number({ min: 30, max: 59 }),
|
||||||
second: 0,
|
second: 0,
|
||||||
}).toFormat('HH:mm:ss'),
|
}).toFormat('HH:mm:ss'),
|
||||||
color_id: faker.datatype.number({ min: 1, max: 25 }),
|
color_id: faker.datatype.number({ min: 1, max: 15 }),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.relation('content', () => LessonContentFactory)
|
.relation('content', () => LessonContentFactory)
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export default class Colors extends BaseSchema {
|
|||||||
.enu('name', Object.keys(ColorEnum), {
|
.enu('name', Object.keys(ColorEnum), {
|
||||||
useNative: true,
|
useNative: true,
|
||||||
enumName: 'color_name_enum',
|
enumName: 'color_name_enum',
|
||||||
existingType: true,
|
existingType: false,
|
||||||
schemaName: 'public',
|
schemaName: 'public',
|
||||||
})
|
})
|
||||||
.notNullable();
|
.notNullable();
|
||||||
@ -20,6 +20,7 @@ export default class Colors extends BaseSchema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down() {
|
public async down() {
|
||||||
|
await this.schema.raw('DROP TYPE IF EXISTS "color_name_enum" CASCADE');
|
||||||
this.schema.dropTable(this.tableName);
|
this.schema.dropTable(this.tableName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export default class Courses extends BaseSchema {
|
|||||||
.enu('status', Object.values(CourseStatusEnum), {
|
.enu('status', Object.values(CourseStatusEnum), {
|
||||||
useNative: true,
|
useNative: true,
|
||||||
enumName: 'course_status_enum',
|
enumName: 'course_status_enum',
|
||||||
existingType: true,
|
existingType: false,
|
||||||
schemaName: 'public',
|
schemaName: 'public',
|
||||||
})
|
})
|
||||||
.notNullable();
|
.notNullable();
|
||||||
@ -31,7 +31,7 @@ export default class Courses extends BaseSchema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async down() {
|
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);
|
this.schema.dropTable(this.tableName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export default class Lessons extends BaseSchema {
|
|||||||
table.string('id', 21).primary();
|
table.string('id', 21).primary();
|
||||||
table.string('course_id', 21).unsigned().references('courses.id').onDelete('CASCADE');
|
table.string('course_id', 21).unsigned().references('courses.id').onDelete('CASCADE');
|
||||||
table.string('title').notNullable();
|
table.string('title').notNullable();
|
||||||
|
table.integer('display_order').notNullable();
|
||||||
table.integer('color_id').unsigned().references('colors.id');
|
table.integer('color_id').unsigned().references('colors.id');
|
||||||
table.string('description');
|
table.string('description');
|
||||||
table.time('duration').notNullable();
|
table.time('duration').notNullable();
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export default class CourseSeeder extends BaseSeeder {
|
|||||||
public async run() {
|
public async run() {
|
||||||
this.CourseFactory = CourseFactory;
|
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('category')
|
||||||
.with('teacher')
|
.with('teacher')
|
||||||
.createMany(3);
|
.createMany(3);
|
||||||
|
|||||||
4
env.ts
4
env.ts
@ -36,8 +36,4 @@ export default Env.rules({
|
|||||||
SMTP_PASSWORD: Env.schema.string(),
|
SMTP_PASSWORD: Env.schema.string(),
|
||||||
MAILGUN_API_KEY: Env.schema.string(),
|
MAILGUN_API_KEY: Env.schema.string(),
|
||||||
MAILGUN_DOMAIN: Env.schema.string(),
|
MAILGUN_DOMAIN: Env.schema.string(),
|
||||||
CLOUDINARY_CLOUD_NAME: Env.schema.string(),
|
|
||||||
CLOUDINARY_API_KEY: Env.schema.string(),
|
|
||||||
CLOUDINARY_API_SECRET: Env.schema.string(),
|
|
||||||
CLOUDINARY_SECURE: Env.schema.boolean(),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
|
|
||||||
import CloudinaryService from 'App/Services/CloudinaryService';
|
|
||||||
|
|
||||||
export default class CloudinaryProvider {
|
|
||||||
constructor(protected app: ApplicationContract) {}
|
|
||||||
|
|
||||||
public register() {
|
|
||||||
this.app.container.singleton('Adonis/Addons/Cloudinary', () => {
|
|
||||||
const config = this.app.container.resolveBinding('Adonis/Core/Config').get('cloudinary', {});
|
|
||||||
|
|
||||||
return new CloudinaryService(config);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,6 +3,9 @@ import Route from '@ioc:Adonis/Core/Route';
|
|||||||
Route.group(() => {
|
Route.group(() => {
|
||||||
Route.get('/', 'Api/v1/LessonsController.list').middleware('role:admin,teacher,student').as('lessons.list');
|
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('/', '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.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.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');
|
Route.put('/:id', 'Api/v1/LessonsController.update').middleware('role:admin,teacher').as('lessons.update');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user