feat: add course approval history endpoint for instructors to view rejection reasons and approval timeline
This commit is contained in:
parent
11c747edab
commit
832a8f5067
3 changed files with 106 additions and 0 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue