feat: add course approval history endpoint for instructors to view rejection reasons and approval timeline

This commit is contained in:
JakkrapartXD 2026-02-06 14:52:10 +07:00
parent 11c747edab
commit 832a8f5067
3 changed files with 106 additions and 0 deletions

View file

@ -20,6 +20,7 @@ import {
GetQuizScoresResponse, GetQuizScoresResponse,
GetQuizAttemptDetailResponse, GetQuizAttemptDetailResponse,
GetEnrolledStudentDetailResponse, GetEnrolledStudentDetailResponse,
GetCourseApprovalHistoryResponse,
} from '../types/CoursesInstructor.types'; } from '../types/CoursesInstructor.types';
import { CreateCourseValidator } from "../validators/CoursesInstructor.validator"; import { CreateCourseValidator } from "../validators/CoursesInstructor.validator";
@ -398,4 +399,24 @@ export class CoursesInstructorController {
student_id: studentId, student_id: studentId,
}); });
} }
/**
*
* Get course approval history for instructor to see rejection reasons
* @param courseId - / Course ID
*/
@Get('{courseId}/approval-history')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Approval history retrieved successfully')
@Response('401', 'Invalid or expired token')
@Response('403', 'Not an instructor of this course')
@Response('404', 'Course not found')
public async getCourseApprovalHistory(
@Request() request: any,
@Path() courseId: number
): Promise<GetCourseApprovalHistoryResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
return await CoursesInstructorService.getCourseApprovalHistory(token, courseId);
}
} }

View file

@ -32,6 +32,7 @@ import {
GetQuizAttemptDetailResponse, GetQuizAttemptDetailResponse,
GetEnrolledStudentDetailInput, GetEnrolledStudentDetailInput,
GetEnrolledStudentDetailResponse, GetEnrolledStudentDetailResponse,
GetCourseApprovalHistoryResponse,
} 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';
@ -1103,4 +1104,60 @@ export class CoursesInstructorService {
throw error; throw error;
} }
} }
/**
*
* Get course approval history for instructor to see rejection reasons
*/
static async getCourseApprovalHistory(token: string, courseId: number): Promise<GetCourseApprovalHistoryResponse> {
try {
const decoded = jwt.verify(token, config.jwt.secret) as { id: number };
// Validate instructor access
await this.validateCourseInstructor(token, courseId);
// Get course with approval history
const course = await prisma.course.findUnique({
where: { id: courseId },
include: {
courseApprovals: {
orderBy: { created_at: 'desc' },
include: {
submitter: {
select: { id: true, username: true, email: true }
},
reviewer: {
select: { id: true, username: true, email: true }
}
}
}
}
});
if (!course) {
throw new NotFoundError('Course not found');
}
return {
code: 200,
message: 'Course approval history retrieved successfully',
data: {
course_id: course.id,
course_title: course.title as { th: string; en: string },
current_status: course.status,
approval_history: course.courseApprovals.map(a => ({
id: a.id,
action: a.action,
comment: a.comment,
created_at: a.created_at,
submitter: a.submitter,
reviewer: a.reviewer
}))
}
};
} catch (error) {
logger.error(`Error getting course approval history: ${error}`);
throw error;
}
}
} }

View file

@ -390,3 +390,31 @@ export interface GetEnrolledStudentDetailResponse {
data: EnrolledStudentDetailData; data: EnrolledStudentDetailData;
} }
// Course Approval History Types
export interface ApprovalHistoryItem {
id: number;
action: string;
comment: string | null;
created_at: Date;
submitter: {
id: number;
username: string;
email: string;
} | null;
reviewer: {
id: number;
username: string;
email: string;
} | null;
}
export interface GetCourseApprovalHistoryResponse {
code: number;
message: string;
data: {
course_id: number;
course_title: { th: string; en: string };
current_status: string;
approval_history: ApprovalHistoryItem[];
};
}