From fded14df945a50087ed6eacd5deb9eebe0aa14b7 Mon Sep 17 00:00:00 2001 From: thanglp163 Date: Wed, 27 Aug 2025 14:32:26 +0700 Subject: [PATCH] update: Manage course api for admin --- src/controllers/course.controller.ts | 62 +++++++++++++- src/routes/course.route.ts | 121 ++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 4 deletions(-) diff --git a/src/controllers/course.controller.ts b/src/controllers/course.controller.ts index 111a643..788230e 100644 --- a/src/controllers/course.controller.ts +++ b/src/controllers/course.controller.ts @@ -617,7 +617,7 @@ export const publishLesson = catchAsync(async (req: Request, res: Response, next }); // unpublish lesson -export const unPublishLesson = catchAsync(async (req: Request, res: Response, next: NextFunction) => { +export const unpublishLesson = catchAsync(async (req: Request, res: Response, next: NextFunction) => { const courseId = req.params.id; if (!courseId) { @@ -2584,3 +2584,63 @@ export const checkCoursePurchased = catchAsync(async (req: Request, res: Respons isPurchased }); }); + +export const getPublishedCoursesForAdmin = catchAsync(async (req: Request, res: Response, next: NextFunction) => { + const { search, level, sortBy = 'createdAt', sortOrder = 'desc' } = req.query; + + // Build query for published courses + const query: any = { isPublished: true }; + + // Add search filter + if (search) { + query.$or = [ + { name: { $regex: search as string, $options: 'i' } }, + { subTitle: { $regex: search as string, $options: 'i' } }, + { description: { $regex: search as string, $options: 'i' } } + ]; + } + + // Add level filter + if (level) { + query.level = level; + } + + // Build sort object + const sort: any = {}; + sort[sortBy as string] = sortOrder === 'desc' ? -1 : 1; + + // Get all published courses with populate necessary fields + const courses = await CourseModel.find(query) + .populate('authorId', 'name email avatar profession') + .populate('category', 'name') + .populate('subCategory', 'name') + .populate('level', 'name') + .sort(sort) + .lean(); + + // Calculate additional stats for each course + const coursesWithStats = await Promise.all( + courses.map(async (course) => { + // Count lessons + const sectionIds = course.sections || []; + const lessonsCount = await LessonModel.countDocuments({ sectionId: { $in: sectionIds } }); + + // Calculate duration in hours + const durationInMinutes = course.duration || 0; + const durationInHours = (durationInMinutes / 60).toFixed(1); + + return { + ...course, + lessonsCount, + durationInHours: `${durationInHours} hours`, + totalSections: sectionIds.length + }; + }) + ); + + res.status(200).json({ + success: true, + data: coursesWithStats, + totalCourses: coursesWithStats.length + }); +}); diff --git a/src/routes/course.route.ts b/src/routes/course.route.ts index 64c94a3..0f07a1d 100644 --- a/src/routes/course.route.ts +++ b/src/routes/course.route.ts @@ -22,7 +22,7 @@ import { getSignatureForDelete, deleteLesson, publishLesson, - unPublishLesson, + unpublishLesson, publishSection, unpublishSection, deleteSection, @@ -40,7 +40,8 @@ import { getInstructorCourseStats, getLatestCourseStatus, getTopViewing, - getAllAssignedCoursesOfUser + getAllAssignedCoursesOfUser, + getPublishedCoursesForAdmin } from '../controllers/course.controller'; import { getUserInfo, updateAccessToken } from '../controllers/user.controller'; import { createSection, updateSection } from '../controllers/section.controller'; @@ -914,7 +915,7 @@ router.put('/publish-lesson/:id', isAuthenticated, updateAccessToken, publishLes * 401: * description: Not authenticated */ -router.put('/unpublish-lesson/:id', isAuthenticated, updateAccessToken, unPublishLesson); +router.put('/unpublish-lesson/:id', isAuthenticated, updateAccessToken, unpublishLesson); /** * @swagger @@ -1041,4 +1042,118 @@ router.get( getLatestCourseStatus ); +/** + * @swagger + * /api/courses/admin/published: + * get: + * summary: Get all published courses for admin + * tags: [Courses] + * security: + * - bearerAuth: [] + * parameters: + * - in: query + * name: search + * schema: + * type: string + * description: Search term for course name, subtitle, or description + * - in: query + * name: level + * schema: + * type: string + * description: Level ID to filter by + * - in: query + * name: sortBy + * schema: + * type: string + * enum: [createdAt, name, price, rating, purchased] + * default: createdAt + * description: Field to sort by + * - in: query + * name: sortOrder + * schema: + * type: string + * enum: [asc, desc] + * default: desc + * description: Sort order + * responses: + * 200: + * description: List of all published courses + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * data: + * type: array + * items: + * type: object + * properties: + * _id: + * type: string + * name: + * type: string + * subTitle: + * type: string + * price: + * type: number + * isFree: + * type: boolean + * rating: + * type: number + * purchased: + * type: number + * lessonsCount: + * type: number + * durationInHours: + * type: string + * totalSections: + * type: number + * authorId: + * type: object + * properties: + * name: + * type: string + * email: + * type: string + * avatar: + * type: string + * profession: + * type: string + * category: + * type: object + * properties: + * name: + * type: string + * subCategory: + * type: object + * properties: + * name: + * type: string + * level: + * type: object + * properties: + * name: + * type: string + * createdAt: + * type: string + * updatedAt: + * type: string + * totalCourses: + * type: number + * description: Total number of published courses + * 401: + * description: Not authenticated + * 403: + * description: Not authorized (admin role required) + */ +router.get( + '/admin/published', + updateAccessToken, + isAuthenticated, + authorizeRoles('admin'), + getPublishedCoursesForAdmin +); + export = router;