feat: Allow instructors to set rejected courses to draft and explicitly set course status to rejected upon administrative rejection.
All checks were successful
Build and Deploy Backend / Build Backend Docker Image (push) Successful in 30s
Build and Deploy Backend / Deploy E-learning Backend to Dev Server (push) Successful in 3s
Build and Deploy Backend / Notify Deployment Status (push) Successful in 1s
Build and Deploy Frontend Management to Dev Server / Build Frontend Management Docker Image (push) Successful in 18s
Build and Deploy Frontend Management to Dev Server / Deploy E-learning Frontend Management to Dev Server (push) Successful in 3s
Build and Deploy Frontend Management to Dev Server / Notify Deployment Status (push) Successful in 1s

This commit is contained in:
JakkrapartXD 2026-02-11 17:01:17 +07:00
parent 8c40549766
commit 8edc3770eb
4 changed files with 55 additions and 4 deletions

View file

@ -21,6 +21,7 @@ import {
GetQuizAttemptDetailResponse, GetQuizAttemptDetailResponse,
GetEnrolledStudentDetailResponse, GetEnrolledStudentDetailResponse,
GetCourseApprovalHistoryResponse, GetCourseApprovalHistoryResponse,
setCourseDraftResponse,
} from '../types/CoursesInstructor.types'; } from '../types/CoursesInstructor.types';
import { CreateCourseValidator } from "../validators/CoursesInstructor.validator"; import { CreateCourseValidator } from "../validators/CoursesInstructor.validator";
@ -189,6 +190,22 @@ export class CoursesInstructorController {
return await CoursesInstructorService.sendCourseForReview({ token, course_id: courseId }); return await CoursesInstructorService.sendCourseForReview({ token, course_id: courseId });
} }
/**
* DRAFT
* Set course to draft
* @param courseId - / Course ID
*/
@Post('set-draft/{courseId}')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Course set to draft successfully')
@Response('401', 'Invalid or expired token')
@Response('404', 'Course not found')
public async setCourseDraft(@Request() request: any, @Path() courseId: number): Promise<setCourseDraftResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided')
return await CoursesInstructorService.setCourseDraft({ token, course_id: courseId });
}
/** /**
* *
* Get all course approval history * Get all course approval history

View file

@ -300,11 +300,11 @@ export class AdminCourseApprovalService {
} }
await prisma.$transaction([ await prisma.$transaction([
// Update course status back to DRAFT // Update course status back to REJECTED
prisma.course.update({ prisma.course.update({
where: { id: courseId }, where: { id: courseId },
data: { data: {
status: 'DRAFT', status: 'REJECTED',
rejection_reason: comment, rejection_reason: comment,
approved_by: null, approved_by: null,
approved_at: null approved_at: null
@ -318,7 +318,7 @@ export class AdminCourseApprovalService {
reviewed_by: decoded.id, reviewed_by: decoded.id,
action: 'REJECTED', action: 'REJECTED',
previous_status: course.status, previous_status: course.status,
new_status: 'DRAFT', new_status: 'REJECTED',
comment: comment comment: comment
} }
}) })
@ -331,7 +331,7 @@ export class AdminCourseApprovalService {
entityType: 'Course', entityType: 'Course',
entityId: courseId, entityId: courseId,
oldValue: { status: 'PENDING' }, oldValue: { status: 'PENDING' },
newValue: { status: 'DRAFT' }, newValue: { status: 'REJECTED' },
metadata: { comment: comment } metadata: { comment: comment }
}); });

View file

@ -33,6 +33,8 @@ import {
GetEnrolledStudentDetailInput, GetEnrolledStudentDetailInput,
GetEnrolledStudentDetailResponse, GetEnrolledStudentDetailResponse,
GetCourseApprovalHistoryResponse, GetCourseApprovalHistoryResponse,
setCourseDraft,
setCourseDraftResponse,
} from "../types/CoursesInstructor.types"; } from "../types/CoursesInstructor.types";
import { auditService } from './audit.service'; import { auditService } from './audit.service';
import { AuditAction } from '@prisma/client'; import { AuditAction } from '@prisma/client';
@ -327,6 +329,28 @@ export class CoursesInstructorService {
} }
} }
static async setCourseDraft(setCourseDraft: setCourseDraft): Promise<setCourseDraftResponse> {
try {
await this.validateCourseInstructor(setCourseDraft.token, setCourseDraft.course_id);
await prisma.course.update({
where: {
id: setCourseDraft.course_id,
status: 'REJECTED'
},
data: {
status: 'DRAFT'
}
});
return {
code: 200,
message: 'Set course to draft successfully',
};
} catch (error) {
logger.error('Failed to set course to draft', { error });
throw error;
}
}
static async getCourseApprovals(token: string, courseId: number): Promise<{ static async getCourseApprovals(token: string, courseId: number): Promise<{
code: number; code: number;
message: string; message: string;

View file

@ -171,6 +171,16 @@ export interface sendCourseForReview {
course_id: number; course_id: number;
} }
export interface setCourseDraft {
token: string;
course_id: number;
}
export interface setCourseDraftResponse {
code: number;
message: string;
}
export interface CourseApprovalData { export interface CourseApprovalData {
id: number; id: number;
course_id: number; course_id: number;