@@ -18,6 +18,7 @@ import { CourseSection } from './entities/course-section.entity';
1818import { Course } from './entities/course.entity' ;
1919import { Enrollment } from './entities/enrollment.entity' ;
2020import { Lesson } from './entities/lesson.entity' ;
21+ import { CourseStatus } from './enums/course-status.enum' ;
2122import { User } from '../users/entities/user.entity' ;
2223import { UserRole } from '../users/enums/user-role.enum' ;
2324
@@ -97,7 +98,7 @@ export class CoursesService {
9798
9899 // 1. UPDATED: Multi-field Search (Title, Description, Tags)
99100 if ( search ) {
100- const searchLower = `%${ search . toLowerCase ( ) } %` ;
101+ const searchLower = `% ${ search . toLowerCase ( ) } % ` ;
101102 queryBuilder . andWhere (
102103 new Brackets ( ( qb ) => {
103104 qb . where ( 'LOWER(course.title) LIKE :search' , { search : searchLower } )
@@ -121,9 +122,9 @@ export class CoursesService {
121122 queryBuilder . andWhere (
122123 new Brackets ( ( qb ) => {
123124 tags . forEach ( ( tag , index ) => {
124- const paramName = `tag_${ index } ` ;
125- qb . orWhere ( `course.tags LIKE :${ paramName } ` , {
126- [ paramName ] : `%${ tag } %` ,
125+ const paramName = `tag_${ index } ` ;
126+ qb . orWhere ( `course.tags LIKE:${ paramName } ` , {
127+ [ paramName ] : `% ${ tag } % ` ,
127128 } ) ;
128129 } ) ;
129130 } ) ,
@@ -442,7 +443,7 @@ export class CoursesService {
442443 . createQueryBuilder ( 'course' )
443444 . leftJoinAndSelect ( 'course.instructor' , 'instructor' )
444445 . loadRelationCountAndMap ( 'course.studentCount' , 'course.enrollments' )
445- . where ( 'course.isPublished = :isPublished ' , { isPublished : true } )
446+ . where ( 'course.status = :status ' , { status : CourseStatus . PUBLISHED } )
446447 . andWhere ( 'course.id NOT IN (:...enrolledIds)' , {
447448 enrolledIds : Array . from ( enrolledCourseIds ) ,
448449 } )
@@ -462,26 +463,26 @@ export class CoursesService {
462463 . createQueryBuilder ( 'course' )
463464 . leftJoinAndSelect ( 'course.instructor' , 'instructor' )
464465 . loadRelationCountAndMap ( 'course.studentCount' , 'course.enrollments' )
465- . where ( 'course.isPublished = :isPublished ' , { isPublished : true } )
466+ . where ( 'course.status = :status ' , { status : CourseStatus . PUBLISHED } )
466467 . andWhere ( 'course.id NOT IN (:...enrolledIds)' , {
467468 enrolledIds : Array . from ( enrolledCourseIds ) ,
468469 } ) ;
469470
470471 // Add tag matching conditions (OR for any tag match)
471472 const tagConditions = Array . from ( userTags ) . map (
472- ( _ , index ) => `LOWER(course.tags) LIKE :tag_${ index } ` ,
473+ ( _ , index ) => `LOWER(course.tags) LIKE:tag_${ index } ` ,
473474 ) ;
474475 if ( tagConditions . length > 0 ) {
475476 queryBuilder . andWhere (
476477 new Brackets ( ( qb ) => {
477478 Array . from ( userTags ) . forEach ( ( tag , index ) => {
478479 if ( index === 0 ) {
479- qb . where ( `LOWER(course.tags) LIKE :tag_${ index } ` , {
480- [ `tag_${ index } ` ] : `% ${ tag } %` ,
480+ qb . where ( `LOWER(course.tags) LIKE:tag_${ index } ` , {
481+ [ `tag_${ index } ` ] : ` % ${ tag } % ` ,
481482 } ) ;
482483 } else {
483- qb . orWhere ( `LOWER(course.tags) LIKE :tag_${ index } ` , {
484- [ `tag_${ index } ` ] : `% ${ tag } %` ,
484+ qb . orWhere ( `LOWER(course.tags) LIKE:tag_${ index } ` , {
485+ [ `tag_${ index } ` ] : ` % ${ tag } % ` ,
485486 } ) ;
486487 }
487488 } ) ;
@@ -516,8 +517,9 @@ export class CoursesService {
516517 ) : Promise < Course > {
517518 const course = this . coursesRepository . create ( {
518519 ...createCourseDto ,
519- instructorId,
520- isPublished : false ,
520+ instructor : { id : instructorId } ,
521+ status : CourseStatus . DRAFT , // Default to draft
522+ isPublished : false , // Legacy field
521523 } ) ;
522524 return this . coursesRepository . save ( course ) ;
523525 }
@@ -665,4 +667,49 @@ export class CoursesService {
665667
666668 await this . lessonRepository . remove ( lesson ) ;
667669 }
670+
671+ // --- Statistics ---
672+
673+ async count ( ) : Promise < number > {
674+ return this . coursesRepository . count ( ) ;
675+ }
676+
677+ async countEnrollments ( ) : Promise < number > {
678+ return this . enrollmentRepository . count ( ) ;
679+ }
680+
681+ // --- Moderation ---
682+
683+ async submitForApproval ( id : string , userId : string ) : Promise < Course > {
684+ const course = await this . findOne ( id , { id : userId } ) ;
685+ if ( course . instructorId !== userId ) {
686+ throw new ForbiddenException (
687+ 'Only the instructor can submit this course' ,
688+ ) ;
689+ }
690+ course . status = CourseStatus . PENDING ;
691+ return this . coursesRepository . save ( course ) ;
692+ }
693+
694+ async approveCourse ( id : string ) : Promise < Course > {
695+ const course = await this . findOne ( id , { role : UserRole . ADMIN } ) ;
696+ course . status = CourseStatus . PUBLISHED ;
697+ course . isPublished = true ;
698+ return this . coursesRepository . save ( course ) ;
699+ }
700+
701+ async rejectCourse ( id : string ) : Promise < Course > {
702+ const course = await this . findOne ( id , { role : UserRole . ADMIN } ) ;
703+ course . status = CourseStatus . REJECTED ;
704+ course . isPublished = false ;
705+ return this . coursesRepository . save ( course ) ;
706+ }
707+
708+ async findAllPending ( ) : Promise < Course [ ] > {
709+ return this . coursesRepository . find ( {
710+ where : { status : CourseStatus . PENDING } ,
711+ relations : [ 'instructor' ] ,
712+ order : { createdAt : 'DESC' } ,
713+ } ) ;
714+ }
668715}
0 commit comments