From 7149a0f3eb7e86d8bd0a0f8181fb61d9aa52a125 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Thu, 9 Jun 2022 23:50:40 +0300 Subject: [PATCH] fix: remove course files from drive after deleting user or category --- app/Helpers/CourseHelper.ts | 28 +++++++++++++++++++++++ app/Repositories/CourseRepository.ts | 26 +++++++++++++++++++++ app/Services/CategoryService.ts | 24 ++++++++++++++++++-- app/Services/CourseService.ts | 27 ++++++++++++++++++++++ app/Services/UserService.ts | 34 +++++++++++++++++++++++++++- 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 app/Helpers/CourseHelper.ts diff --git a/app/Helpers/CourseHelper.ts b/app/Helpers/CourseHelper.ts new file mode 100644 index 0000000..4d13385 --- /dev/null +++ b/app/Helpers/CourseHelper.ts @@ -0,0 +1,28 @@ +import Course from 'App/Models/Course'; + +export type FileEntry = { + path: string; + name: string; +}; + +export default class CourseHelper { + public static getVideoFileNames(course: Course): Array { + const data = course.lessons.map(l => { + if (!l.video) return null; + return { path: 'videos/', name: l.video.name }; + }); + + return data; + } + + public static getMaterialFileNames(course: Course): FileEntry[] { + const data = course.lessons.map(l => l.materials.map(m => ({ path: 'materials/', name: m.name }))).flat(); + return data; + } + + public static getImageFileName(course: Course): FileEntry | null { + if (!course.image) return null; + const data = { path: 'images/courses/', name: course.image.name }; + return data; + } +} diff --git a/app/Repositories/CourseRepository.ts b/app/Repositories/CourseRepository.ts index a18020f..b78caab 100644 --- a/app/Repositories/CourseRepository.ts +++ b/app/Repositories/CourseRepository.ts @@ -32,6 +32,32 @@ export default class CourseRepository { this.Course = Course; } + /** + * Get list of courses by teacher id + * + * @param id Teacher id + * @returns Array of courses + */ + public async getByTeacherId(id: string | number): Promise { + const data = await this.Course.query() + .preload('lessons', q => q.preload('video').preload('materials')) + .where('teacher_id', id); + return data; + } + + /** + * Get list of courses by category id + * + * @param id Category id + * @returns Array of courses + */ + public async getByCategoryId(id: string | number): Promise { + const data = await this.Course.query() + .preload('lessons', q => q.preload('video').preload('materials')) + .where('category_id', id); + return data; + } + /** * Set status on course * diff --git a/app/Services/CategoryService.ts b/app/Services/CategoryService.ts index cfe78b0..9df57fd 100644 --- a/app/Services/CategoryService.ts +++ b/app/Services/CategoryService.ts @@ -10,6 +10,7 @@ import HttpStatusEnum from 'App/Datatypes/Enums/HttpStatusEnum'; * Repositories */ import CategoryRepository from 'App/Repositories/CategoryRepository'; +import CourseRepository from 'App/Repositories/CourseRepository'; /** * Validators @@ -17,12 +18,27 @@ import CategoryRepository from 'App/Repositories/CategoryRepository'; import CreateCategoryValidator from 'App/Validators/Category/CreateCategoryValidator'; import UpdateCategoryValidator from 'App/Validators/Category/UpdateCategoryValidator'; +/** + * Services + */ +import CourseService from 'App/Services/CourseService'; + @inject() export default class CategoryService { private categoryRepository: CategoryRepository; - constructor(categoryRepository: CategoryRepository) { + private courseRepository: CourseRepository; + + private courseService: CourseService; + + constructor( + categoryRepository: CategoryRepository, + courseRepository: CourseRepository, + courseService: CourseService + ) { this.categoryRepository = categoryRepository; + this.courseRepository = courseRepository; + this.courseService = courseService; } /** @@ -94,7 +110,7 @@ export default class CategoryService { * @returns Deleted category */ public async deleteCategory(id: string | number): Promise { - const data = await this.categoryRepository.delete(id); + const data = await this.categoryRepository.getById(id); if (!data) { return { @@ -108,6 +124,10 @@ export default class CategoryService { }; } + const courses = await this.courseRepository.getByCategoryId(id); + await this.courseService.deleteAllFiles(courses); + await this.categoryRepository.delete(id); + return { success: true, status: HttpStatusEnum.OK, diff --git a/app/Services/CourseService.ts b/app/Services/CourseService.ts index 03fc59b..1535661 100644 --- a/app/Services/CourseService.ts +++ b/app/Services/CourseService.ts @@ -26,6 +26,7 @@ import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator'; import FetchCoursesValidator from 'App/Validators/Course/FetchCoursesValidator'; import Course from 'App/Models/Course'; import Drive from '@ioc:Adonis/Core/Drive'; +import CourseHelper, { FileEntry } from 'App/Helpers/CourseHelper'; @inject() export default class CourseService { @@ -720,6 +721,32 @@ export default class CourseService { data: { count }, }; } + + /** + * This function will delete all associated course files on disk + * + * @param courses List of courses + */ + public async deleteAllFiles(courses: Course[]): Promise { + /** + * Collect file names + */ + const images = courses.map(course => CourseHelper.getImageFileName(course)).filter(Boolean) as FileEntry[]; + const videos = courses + .map(course => CourseHelper.getVideoFileNames(course)) + .flat() + .filter(Boolean) as FileEntry[]; + const materials = courses.map(course => CourseHelper.getMaterialFileNames(course)).flat(); + + /** + * Remove files from disk + */ + const promises = videos + .concat(images, materials) + .map(file => Drive.delete(`${file.path}${file.name.substring(file.name.lastIndexOf('/') + 1)}`)); + + await Promise.all(promises); + } } new Ioc().make(CourseService); diff --git a/app/Services/UserService.ts b/app/Services/UserService.ts index fb9641e..ed80a93 100644 --- a/app/Services/UserService.ts +++ b/app/Services/UserService.ts @@ -22,22 +22,48 @@ import Role from 'App/Models/Role'; import RoleRepository from 'App/Repositories/RoleRepository'; import UserRepository from 'App/Repositories/UserRepository'; +/** + * Services + */ +import CourseService from './CourseService'; + /** * Validators */ import CreateUserValidator from 'App/Validators/User/CreateUserValidator'; import UpdateUserValidator from 'App/Validators/User/UpdateUserValidator'; + +/** + * Datatypes + */ import RoleEnum from 'App/Datatypes/Enums/RoleEnum'; +/** + * Helpers + */ +import RoleHelper from 'App/Helpers/RoleHelper'; +import CourseRepository from 'App/Repositories/CourseRepository'; + @inject() export default class UserService { private userRepository: UserRepository; private roleRepository: RoleRepository; - constructor(userRepository: UserRepository, roleRepository: RoleRepository) { + private courseRepository: CourseRepository; + + private courseService: CourseService; + + constructor( + userRepository: UserRepository, + roleRepository: RoleRepository, + courseRepository: CourseRepository, + courseService: CourseService + ) { this.userRepository = userRepository; this.roleRepository = roleRepository; + this.courseRepository = courseRepository; + this.courseService = courseService; } /** @@ -213,6 +239,12 @@ export default class UserService { } await ctx.bouncer.with('RolePolicy').authorize('manage', user.roles.map(r => r.slug) as [RoleEnum]); + + if (RoleHelper.userContainRoles(user.roles, [RoleEnum.TEACHER, RoleEnum.ADMIN])) { + const courses = await this.courseRepository.getByTeacherId(id); + await this.courseService.deleteAllFiles(courses); + } + await this.userRepository.delete(id); return {