feat: update instructor search to exclude self and existing course instructors, add email_verified_at to user responses

Update searchInstructors endpoint to accept courseId parameter and filter out the requesting instructor and instructors already assigned to the course. Add email_verified_at field to UserResponse type and include it in auth and user service responses.
This commit is contained in:
JakkrapartXD 2026-02-03 10:38:59 +07:00
parent 80d7372dfa
commit 48e8f56e22
6 changed files with 29 additions and 8 deletions

View file

@ -48,18 +48,23 @@ export class CoursesInstructorController {
} }
/** /**
* * ()
* Search all instructors in database * Search all instructors in database (excluding self and existing course instructors)
* @param courseId - / Course ID
* @param query - (email username) / Search query (email or username) * @param query - (email username) / Search query (email or username)
*/ */
@Get('search-instructors') @Get('{courseId}/search-instructors')
@Security('jwt', ['instructor']) @Security('jwt', ['instructor'])
@SuccessResponse('200', 'Instructors found') @SuccessResponse('200', 'Instructors found')
@Response('401', 'Invalid or expired token') @Response('401', 'Invalid or expired token')
public async searchInstructors(@Request() request: any, @Query() query: string): Promise<SearchInstructorResponse> { public async searchInstructors(
@Request() request: any,
@Path() courseId: number,
@Query() query: string
): Promise<SearchInstructorResponse> {
const token = request.headers.authorization?.replace('Bearer ', ''); const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided'); if (!token) throw new ValidationError('No token provided');
return await CoursesInstructorService.searchInstructors({ token, query }); return await CoursesInstructorService.searchInstructors({ token, query, course_id: courseId });
} }
/** /**

View file

@ -356,16 +356,26 @@ export class CoursesInstructorService {
static async searchInstructors(input: SearchInstructorInput): Promise<SearchInstructorResponse> { static async searchInstructors(input: SearchInstructorInput): Promise<SearchInstructorResponse> {
try { try {
jwt.verify(input.token, config.jwt.secret) as { id: number }; const decoded = jwt.verify(input.token, config.jwt.secret) as { id: number };
// Search all instructors by email or username // Get existing instructors in the course
const existingInstructors = await prisma.courseInstructor.findMany({
where: { course_id: input.course_id },
select: { user_id: true },
});
const existingInstructorIds = existingInstructors.map(i => i.user_id);
// Search all instructors by email or username, excluding self and existing course instructors
const users = await prisma.user.findMany({ const users = await prisma.user.findMany({
where: { where: {
OR: [ OR: [
{ email: { contains: input.query, mode: 'insensitive' } }, { email: { contains: input.query, mode: 'insensitive' } },
{ username: { contains: input.query, mode: 'insensitive' } }, { username: { contains: input.query, mode: 'insensitive' } },
], ],
role: { code: 'INSTRUCTOR' } role: { code: 'INSTRUCTOR' },
id: {
notIn: [decoded.id, ...existingInstructorIds],
},
}, },
include: { include: {
profile: true profile: true

View file

@ -398,6 +398,7 @@ export class AuthService {
id: user.id, id: user.id,
username: user.username, username: user.username,
email: user.email, email: user.email,
email_verified_at: user.email_verified_at,
updated_at: user.updated_at, updated_at: user.updated_at,
created_at: user.created_at, created_at: user.created_at,
role: { role: {
@ -423,6 +424,7 @@ export class AuthService {
id: user.id, id: user.id,
username: user.username, username: user.username,
email: user.email, email: user.email,
email_verified_at: user.email_verified_at,
updated_at: user.updated_at, updated_at: user.updated_at,
created_at: user.created_at, created_at: user.created_at,
role: { role: {

View file

@ -48,6 +48,7 @@ export class UserService {
id: user.id, id: user.id,
username: user.username, username: user.username,
email: user.email, email: user.email,
email_verified_at: user.email_verified_at,
updated_at: user.updated_at, updated_at: user.updated_at,
created_at: user.created_at, created_at: user.created_at,
role: { role: {
@ -285,6 +286,7 @@ export class UserService {
id: user.id, id: user.id,
username: user.username, username: user.username,
email: user.email, email: user.email,
email_verified_at: user.email_verified_at,
updated_at: user.updated_at, updated_at: user.updated_at,
created_at: user.created_at, created_at: user.created_at,
role: { role: {

View file

@ -97,6 +97,7 @@ export interface addinstructorCourse {
export interface SearchInstructorInput { export interface SearchInstructorInput {
token: string; token: string;
query: string; query: string;
course_id: number;
} }
export interface SearchInstructorResult { export interface SearchInstructorResult {

View file

@ -5,6 +5,7 @@ export interface UserResponse {
id: number; id: number;
username: string; username: string;
email: string; email: string;
email_verified_at: Date | null;
updated_at: Date | null; updated_at: Date | null;
created_at: Date | null; created_at: Date | null;
role: { role: {