Spaces:
Sleeping
Sleeping
| import { | |
| Controller, | |
| Post, | |
| Get, | |
| Delete, | |
| Param, | |
| Body, | |
| Query, | |
| UseGuards, | |
| Request, | |
| BadRequestException, | |
| HttpCode, | |
| HttpStatus, | |
| ParseIntPipe, | |
| } from '@nestjs/common'; | |
| import { | |
| ApiTags, | |
| ApiOperation, | |
| ApiResponse, | |
| ApiBearerAuth, | |
| ApiParam, | |
| ApiQuery, | |
| ApiBody, | |
| } from '@nestjs/swagger'; | |
| import { EnrollmentsService } from '../services'; | |
| import { EnrollCourseDto } from '../dto/enroll-course.dto'; | |
| import { EnrollmentResponseDto } from '../dto/enrollment-response.dto'; | |
| import { | |
| AvailableCoursesFilterDto, | |
| AvailableCoursesDto, | |
| } from '../dto/available-courses.dto'; | |
| import { DropCourseDto } from '../dto/drop-course.dto'; | |
| import { | |
| AssignInstructorDto, | |
| InstructorAssignmentResponseDto, | |
| } from '../dto/assign-instructor.dto'; | |
| import { AssignTADto, TAAssignmentResponseDto } from '../dto/assign-ta.dto'; | |
| import { Roles } from '../../auth/roles.decorator'; | |
| import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; | |
| import { RolesGuard } from '../../auth/guards/roles.guard'; | |
| import { RoleName } from '../../auth/entities/role.entity'; | |
| ('✅ Enrollments') | |
| ('api/enrollments') | |
| (JwtAuthGuard, RolesGuard) | |
| ('JWT-auth') | |
| export class EnrollmentsController { | |
| constructor(private readonly enrollmentsService: EnrollmentsService) {} | |
| /** | |
| * GET /api/enrollments/periods | |
| * Get enrollment periods (derived from semesters with registration dates) | |
| */ | |
| ('periods') | |
| ( | |
| RoleName.IT_ADMIN, | |
| RoleName.ADMIN, | |
| RoleName.INSTRUCTOR, | |
| RoleName.TA, | |
| RoleName.STUDENT, | |
| ) | |
| ({ | |
| summary: 'Get enrollment periods', | |
| description: | |
| 'Returns semesters with registration date ranges as enrollment periods.', | |
| }) | |
| ({ | |
| name: 'departmentId', | |
| required: false, | |
| type: Number, | |
| description: | |
| 'When set, course and enrollment counts are limited to this department.', | |
| }) | |
| ({ status: 200, description: 'List of enrollment periods' }) | |
| async getEnrollmentPeriods( | |
| ('departmentId') departmentId?: string, | |
| ): Promise<any[]> { | |
| const id = departmentId ? parseInt(departmentId, 10) : undefined; | |
| return this.enrollmentsService.getEnrollmentPeriods( | |
| id !== undefined && Number.isFinite(id) ? id : undefined, | |
| ); | |
| } | |
| /** | |
| * GET /api/enrollments/my-courses | |
| * Get all courses enrolled by the authenticated student | |
| */ | |
| ('my-courses') | |
| (RoleName.STUDENT) | |
| ({ | |
| summary: 'Get my enrolled courses', | |
| description: ` | |
| ## Get Student's Enrolled Courses | |
| Retrieves all courses the authenticated student is enrolled in. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: STUDENT only | |
| ### Filtering | |
| Use \`semester\` query parameter to filter by specific semester. | |
| ### Response Includes | |
| - Course information | |
| - Section details | |
| - Enrollment status | |
| - Grades (if available) | |
| `, | |
| }) | |
| ({ | |
| name: 'semester', | |
| required: false, | |
| type: Number, | |
| description: 'Semester ID to filter', | |
| }) | |
| ({ status: 200, description: 'List of enrolled courses' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ | |
| status: 403, | |
| description: 'Forbidden - Student role required', | |
| }) | |
| async getMyEnrollments( | |
| () req, | |
| ('semester') semester?: number, | |
| ): Promise<EnrollmentResponseDto[]> { | |
| const userId = req.user.userId || req.user.id; | |
| return this.enrollmentsService.getMyEnrollments(userId, semester); | |
| } | |
| /** | |
| * GET /api/enrollments/available | |
| * Get list of available courses for student enrollment | |
| */ | |
| ('available') | |
| (RoleName.STUDENT) | |
| ({ | |
| summary: 'Get available courses', | |
| description: ` | |
| ## Get Available Courses for Enrollment | |
| Retrieves courses the student can enroll in based on prerequisites and availability. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: STUDENT only | |
| ### Availability Criteria | |
| - Prerequisites completed | |
| - Section has available seats | |
| - No schedule conflicts | |
| - Within enrollment period | |
| ### Filtering Options | |
| Available through query parameters (see AvailableCoursesFilterDto). | |
| `, | |
| }) | |
| ({ status: 200, description: 'List of available courses' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ | |
| status: 403, | |
| description: 'Forbidden - Student role required', | |
| }) | |
| async getAvailableCourses( | |
| () req, | |
| () filters: AvailableCoursesFilterDto, | |
| ): Promise<AvailableCoursesDto[]> { | |
| return this.enrollmentsService.getAvailableCourses( | |
| req.user.userId, | |
| filters, | |
| ); | |
| } | |
| /** | |
| * POST /api/enrollments/register | |
| * Enroll a student in a course section | |
| */ | |
| ('register') | |
| (RoleName.STUDENT) | |
| ({ | |
| summary: 'Enroll in a course', | |
| description: ` | |
| ## Register for Course Section | |
| Enrolls the authenticated student in a course section. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: STUDENT only | |
| ### Enrollment Process | |
| 1. Validates prerequisites are met | |
| 2. Checks section capacity | |
| 3. Checks for schedule conflicts | |
| 4. Creates enrollment record | |
| 5. Updates section enrollment count | |
| ### Notes | |
| - If section is full, student is added to waitlist | |
| - Enrollment may be pending approval | |
| `, | |
| }) | |
| ({ type: EnrollCourseDto }) | |
| ({ status: 201, description: 'Successfully enrolled' }) | |
| ({ | |
| status: 400, | |
| description: 'Prerequisites not met or schedule conflict', | |
| }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ | |
| status: 403, | |
| description: 'Forbidden - Student role required', | |
| }) | |
| ({ status: 409, description: 'Already enrolled in this course' }) | |
| async enrollCourse( | |
| () req, | |
| () enrollCourseDto: EnrollCourseDto, | |
| ): Promise<EnrollmentResponseDto> { | |
| const userId = req.user.userId || req.user.id; | |
| if (!userId) { | |
| throw new BadRequestException( | |
| 'User ID not found in authentication token', | |
| ); | |
| } | |
| return this.enrollmentsService.enrollStudent(userId, enrollCourseDto); | |
| } | |
| /** | |
| * GET /api/enrollments/teaching | |
| * Get all courses taught by the authenticated instructor | |
| */ | |
| ('teaching') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| ({ | |
| summary: 'Get teaching courses', | |
| description: | |
| 'Retrieves all course sections assigned to the authenticated instructor.', | |
| }) | |
| ({ status: 200, description: 'List of teaching courses' }) | |
| async getTeachingCourses(() req): Promise<any[]> { | |
| const userId = req.user.userId || req.user.id; | |
| return this.enrollmentsService.getTeachingCourses(userId); | |
| } | |
| /** | |
| * GET /api/enrollments/student/:userId | |
| * List enrollments for a student (admin / IT admin / department head) | |
| */ | |
| ('student/:userId') | |
| (RoleName.ADMIN, RoleName.IT_ADMIN, RoleName.DEPARTMENT_HEAD) | |
| ({ | |
| summary: 'Get enrollments for a student (staff)', | |
| description: | |
| 'Returns the same enrollment payload as the student “my courses” view, for roster and admin tools.', | |
| }) | |
| ({ name: 'userId', description: 'Student user ID', type: Number }) | |
| ({ | |
| name: 'semester', | |
| required: false, | |
| type: Number, | |
| description: 'Optional semester ID to filter sections', | |
| }) | |
| ({ status: 200, description: 'List of enrollments' }) | |
| async getEnrollmentsForStudent( | |
| ('userId', ParseIntPipe) userId: number, | |
| ('semester') semester?: number, | |
| ): Promise<EnrollmentResponseDto[]> { | |
| return this.enrollmentsService.getMyEnrollments(userId, semester); | |
| } | |
| /** | |
| * GET /api/enrollments/:id | |
| * Get enrollment details by ID | |
| */ | |
| (':id') | |
| ({ | |
| summary: 'Get enrollment by ID', | |
| description: ` | |
| ## Get Enrollment Details | |
| Retrieves details of a specific enrollment. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: Any authenticated user (own enrollments) or ADMIN/INSTRUCTOR | |
| `, | |
| }) | |
| ({ name: 'id', description: 'Enrollment ID', type: Number }) | |
| ({ status: 200, description: 'Enrollment details' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 404, description: 'Enrollment not found' }) | |
| async getEnrollmentById( | |
| ('id') id: number, | |
| ): Promise<EnrollmentResponseDto> { | |
| return this.enrollmentsService.getEnrollmentById(id); | |
| } | |
| /** | |
| * DELETE /api/enrollments/:id | |
| * Drop/withdraw from a course enrollment | |
| */ | |
| (':id') | |
| ({ | |
| summary: 'Drop/withdraw from course', | |
| description: ` | |
| ## Drop Course Enrollment | |
| Withdraws from an enrolled course. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: STUDENT (own enrollment) or ADMIN | |
| ### Withdrawal Rules | |
| - Before deadline: Full withdrawal | |
| - After deadline: May incur penalties or show as "W" grade | |
| - Admins can override restrictions | |
| ### Notes | |
| Dropping may move waitlisted students into the section. | |
| `, | |
| }) | |
| ({ name: 'id', description: 'Enrollment ID', type: Number }) | |
| ({ type: DropCourseDto, required: false }) | |
| ({ status: 200, description: 'Successfully dropped' }) | |
| ({ status: 400, description: 'Drop deadline passed' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 404, description: 'Enrollment not found' }) | |
| async dropCourse( | |
| () req, | |
| ('id') enrollmentId: number, | |
| () dropCourseDto?: DropCourseDto, | |
| ): Promise<EnrollmentResponseDto> { | |
| const staffRoles = [ | |
| RoleName.ADMIN, | |
| RoleName.IT_ADMIN, | |
| RoleName.DEPARTMENT_HEAD, | |
| ]; | |
| const isAdmin = req.user.roles?.some((r) => staffRoles.includes(r.roleName)); | |
| return this.enrollmentsService.dropCourse( | |
| enrollmentId, | |
| req.user.userId, | |
| !!isAdmin, | |
| ); | |
| } | |
| /** | |
| * GET /api/courses/:courseId/enrollments | |
| * Get all enrollments for a specific course (instructor/admin only) | |
| */ | |
| ('course/:courseId/list') | |
| (RoleName.INSTRUCTOR, RoleName.ADMIN) | |
| ({ | |
| summary: 'Get course enrollments', | |
| description: ` | |
| ## Get All Enrollments for Course | |
| Retrieves all enrollments across all sections of a course. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: INSTRUCTOR, ADMIN only | |
| `, | |
| }) | |
| ({ name: 'courseId', description: 'Course ID', type: Number }) | |
| ({ status: 200, description: 'List of enrollments' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ | |
| status: 403, | |
| description: 'Forbidden - Instructor or Admin required', | |
| }) | |
| async getCourseEnrollments( | |
| ('courseId') courseId: number, | |
| ): Promise<EnrollmentResponseDto[]> { | |
| // This would list all sections for the course | |
| // Implementation depends on course structure | |
| return []; | |
| } | |
| /** | |
| * GET /api/enrollments/section/:sectionId/students/count | |
| * Get enrolled students count for a section (instructor/admin only) | |
| */ | |
| ('section/:sectionId/students/count') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| ({ | |
| summary: 'Get enrolled students count for section', | |
| description: 'Returns the actual count of enrolled students (status=ENROLLED) for a section.', | |
| }) | |
| ({ name: 'sectionId', description: 'Section ID', type: Number }) | |
| ({ status: 200, description: 'Student count retrieved' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ | |
| status: 403, | |
| description: 'Forbidden - Instructor, TA, or Admin required', | |
| }) | |
| ({ status: 404, description: 'Section not found' }) | |
| async getSectionStudentsCount( | |
| () req, | |
| ('sectionId', ParseIntPipe) sectionId: number, | |
| ): Promise<{ count: number }> { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.enrollmentsService.getSectionStudentsCount(sectionId, userId, roles); | |
| } | |
| /** | |
| * GET /api/sections/:sectionId/students | |
| * Get all enrolled students in a section (instructor/admin only) | |
| */ | |
| ('section/:sectionId/students') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| ({ | |
| summary: 'Get section students', | |
| description: ` | |
| ## Get Enrolled Students in Section | |
| Retrieves all students enrolled in a specific section. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: INSTRUCTOR, ADMIN only | |
| ### Response Includes | |
| - Student information | |
| - Enrollment status | |
| - Enrollment date | |
| `, | |
| }) | |
| ({ name: 'sectionId', description: 'Section ID', type: Number }) | |
| ({ status: 200, description: 'List of enrolled students' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ | |
| status: 403, | |
| description: 'Forbidden - Instructor or Admin required', | |
| }) | |
| ({ status: 404, description: 'Section not found' }) | |
| async getSectionStudents( | |
| () req, | |
| ('sectionId') sectionId: number, | |
| ): Promise<EnrollmentResponseDto[]> { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.enrollmentsService.getSectionStudents(sectionId, userId, roles); | |
| } | |
| /** | |
| * GET /api/sections/:sectionId/waitlist | |
| * Get waitlist for a section (instructor/admin only) | |
| */ | |
| ('section/:sectionId/waitlist') | |
| (RoleName.INSTRUCTOR, RoleName.ADMIN) | |
| ({ | |
| summary: 'Get section waitlist', | |
| description: ` | |
| ## Get Section Waitlist | |
| Retrieves all students on the waitlist for a section. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: INSTRUCTOR, ADMIN only | |
| ### Response | |
| Students are ordered by waitlist position. | |
| `, | |
| }) | |
| ({ name: 'sectionId', description: 'Section ID', type: Number }) | |
| ({ status: 200, description: 'Waitlist students' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ | |
| status: 403, | |
| description: 'Forbidden - Instructor or Admin required', | |
| }) | |
| ({ status: 404, description: 'Section not found' }) | |
| async getSectionWaitlist( | |
| ('sectionId') sectionId: number, | |
| ): Promise<EnrollmentResponseDto[]> { | |
| return this.enrollmentsService.getWaitlist(sectionId); | |
| } | |
| /** | |
| * PUT /api/enrollments/:id/status | |
| * Update enrollment status (admin only) | |
| */ | |
| (':id/status') | |
| (RoleName.ADMIN) | |
| ({ | |
| summary: 'Update enrollment status', | |
| description: ` | |
| ## Update Enrollment Status | |
| Manually updates the status of an enrollment. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: ADMIN only | |
| ### Available Statuses | |
| - enrolled, waitlisted, dropped, completed, failed | |
| `, | |
| }) | |
| ({ name: 'id', description: 'Enrollment ID', type: Number }) | |
| ({ | |
| schema: { | |
| type: 'object', | |
| properties: { | |
| status: { type: 'string', example: 'enrolled' }, | |
| }, | |
| }, | |
| }) | |
| ({ status: 200, description: 'Status updated' }) | |
| ({ status: 400, description: 'Invalid status' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - Admin required' }) | |
| ({ status: 404, description: 'Enrollment not found' }) | |
| async updateEnrollmentStatus( | |
| ('id') enrollmentId: number, | |
| () body: { status: string }, | |
| ): Promise<EnrollmentResponseDto> { | |
| return this.enrollmentsService.updateEnrollmentStatus(enrollmentId, body.status as any); | |
| } | |
| // ─── Admin: Instructor Assignment Endpoints ─────────────────────────────── | |
| /** | |
| * POST /api/enrollments/sections/:sectionId/instructors | |
| * Assign an instructor to a course section (admin only) | |
| */ | |
| ('sections/:sectionId/instructors') | |
| (RoleName.ADMIN) | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Assign instructor to section', | |
| description: ` | |
| ## Assign Instructor to Course Section | |
| Assigns a user with the Instructor role to a specific course section. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: ADMIN only | |
| ### Instructor Roles | |
| - \`primary\` – Main instructor (default) | |
| - \`co_instructor\` – Secondary instructor | |
| - \`guest\` – Guest lecturer | |
| `, | |
| }) | |
| ({ | |
| name: 'sectionId', | |
| description: 'Course section ID', | |
| type: Number, | |
| }) | |
| ({ type: AssignInstructorDto }) | |
| ({ | |
| status: 201, | |
| description: 'Instructor assigned successfully', | |
| type: InstructorAssignmentResponseDto, | |
| }) | |
| ({ status: 400, description: 'Invalid request body' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - Admin required' }) | |
| ({ status: 404, description: 'Section or user not found' }) | |
| ({ | |
| status: 409, | |
| description: 'Instructor already assigned to this section', | |
| }) | |
| async assignInstructor( | |
| ('sectionId', ParseIntPipe) sectionId: number, | |
| () dto: AssignInstructorDto, | |
| ): Promise<InstructorAssignmentResponseDto> { | |
| return this.enrollmentsService.assignInstructor(sectionId, dto); | |
| } | |
| /** | |
| * DELETE /api/enrollments/sections/:sectionId/instructors/:assignmentId | |
| * Remove an instructor from a course section (admin only) | |
| */ | |
| ('sections/:sectionId/instructors/:assignmentId') | |
| (RoleName.ADMIN) | |
| (HttpStatus.NO_CONTENT) | |
| ({ | |
| summary: 'Remove instructor from section', | |
| description: ` | |
| ## Remove Instructor Assignment | |
| Removes an instructor assignment from a course section. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: ADMIN only | |
| `, | |
| }) | |
| ({ | |
| name: 'sectionId', | |
| description: 'Course section ID', | |
| type: Number, | |
| }) | |
| ({ | |
| name: 'assignmentId', | |
| description: 'Instructor assignment ID', | |
| type: Number, | |
| }) | |
| ({ status: 204, description: 'Instructor removed successfully' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - Admin required' }) | |
| ({ status: 404, description: 'Section or assignment not found' }) | |
| async removeInstructor( | |
| ('sectionId', ParseIntPipe) sectionId: number, | |
| ('assignmentId', ParseIntPipe) assignmentId: number, | |
| ): Promise<void> { | |
| return this.enrollmentsService.removeInstructor(sectionId, assignmentId); | |
| } | |
| /** | |
| * GET /api/enrollments/sections/:sectionId/instructors | |
| * List all instructors for a section (admin/instructor) | |
| */ | |
| ('sections/:sectionId/instructors') | |
| (RoleName.ADMIN, RoleName.INSTRUCTOR, RoleName.TA) | |
| ({ | |
| summary: 'Get instructors for a section', | |
| description: ` | |
| ## List Section Instructors | |
| Returns all instructors assigned to a course section. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: ADMIN, INSTRUCTOR | |
| `, | |
| }) | |
| ({ | |
| name: 'sectionId', | |
| description: 'Course section ID', | |
| type: Number, | |
| }) | |
| ({ | |
| status: 200, | |
| description: 'List of instructor assignments', | |
| type: [InstructorAssignmentResponseDto], | |
| }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ | |
| status: 403, | |
| description: 'Forbidden - Admin or Instructor required', | |
| }) | |
| ({ status: 404, description: 'Section not found' }) | |
| async getSectionInstructors( | |
| () req, | |
| ('sectionId', ParseIntPipe) sectionId: number, | |
| ): Promise<InstructorAssignmentResponseDto[]> { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.enrollmentsService.getSectionInstructors( | |
| sectionId, | |
| userId, | |
| roles, | |
| ); | |
| } | |
| /** | |
| * GET /api/enrollments/section/:sectionId/instructor | |
| * Get the primary instructor summary for a section | |
| */ | |
| ('section/:sectionId/instructor') | |
| (RoleName.ADMIN, RoleName.INSTRUCTOR, RoleName.TA, RoleName.STUDENT) | |
| ({ | |
| summary: 'Get assigned instructor for a section', | |
| description: | |
| 'Returns the assigned instructor summary for a course section, including user ID, full name, and email.', | |
| }) | |
| ({ | |
| name: 'sectionId', | |
| description: 'Course section ID', | |
| type: Number, | |
| }) | |
| ({ | |
| status: 200, | |
| description: 'Assigned instructor summary', | |
| schema: { | |
| type: 'object', | |
| properties: { | |
| instructorId: { type: 'number', example: 58 }, | |
| instructor: { | |
| type: 'object', | |
| properties: { | |
| userId: { type: 'number', example: 58 }, | |
| fullName: { type: 'string', example: 'Tarek Instructor' }, | |
| email: { type: 'string', example: 'tarek@example.com' }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }) | |
| async getSectionInstructorSummary( | |
| () req, | |
| ('sectionId', ParseIntPipe) sectionId: number, | |
| ): Promise<{ | |
| instructorId: number | null; | |
| instructor: { userId: number; fullName: string; email: string } | null; | |
| }> { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.enrollmentsService.getSectionInstructorSummary( | |
| sectionId, | |
| userId, | |
| roles, | |
| ); | |
| } | |
| // ─── Admin: TA Assignment Endpoints ────────────────────────────────────── | |
| /** | |
| * POST /api/enrollments/sections/:sectionId/tas | |
| * Assign a Teaching Assistant to a course section (admin only) | |
| */ | |
| ('sections/:sectionId/tas') | |
| (RoleName.ADMIN) | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Assign Teaching Assistant to section', | |
| description: ` | |
| ## Assign TA to Course Section | |
| Assigns a user with the Teaching Assistant role to a specific course section. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: ADMIN only | |
| ### Optional Fields | |
| - \`responsibilities\` – Free-text description of TA duties (e.g., "Grading labs, office hours Mon/Wed") | |
| `, | |
| }) | |
| ({ | |
| name: 'sectionId', | |
| description: 'Course section ID', | |
| type: Number, | |
| }) | |
| ({ type: AssignTADto }) | |
| ({ | |
| status: 201, | |
| description: 'TA assigned successfully', | |
| type: TAAssignmentResponseDto, | |
| }) | |
| ({ status: 400, description: 'Invalid request body' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - Admin required' }) | |
| ({ status: 404, description: 'Section or user not found' }) | |
| ({ | |
| status: 409, | |
| description: 'TA already assigned to this section', | |
| }) | |
| async assignTA( | |
| ('sectionId', ParseIntPipe) sectionId: number, | |
| () dto: AssignTADto, | |
| ): Promise<TAAssignmentResponseDto> { | |
| return this.enrollmentsService.assignTA(sectionId, dto); | |
| } | |
| /** | |
| * DELETE /api/enrollments/sections/:sectionId/tas/:assignmentId | |
| * Remove a Teaching Assistant from a course section (admin only) | |
| */ | |
| ('sections/:sectionId/tas/:assignmentId') | |
| (RoleName.ADMIN) | |
| (HttpStatus.NO_CONTENT) | |
| ({ | |
| summary: 'Remove Teaching Assistant from section', | |
| description: ` | |
| ## Remove TA Assignment | |
| Removes a Teaching Assistant assignment from a course section. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: ADMIN only | |
| `, | |
| }) | |
| ({ | |
| name: 'sectionId', | |
| description: 'Course section ID', | |
| type: Number, | |
| }) | |
| ({ | |
| name: 'assignmentId', | |
| description: 'TA assignment ID', | |
| type: Number, | |
| }) | |
| ({ status: 204, description: 'TA removed successfully' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - Admin required' }) | |
| ({ status: 404, description: 'Section or assignment not found' }) | |
| async removeTA( | |
| ('sectionId', ParseIntPipe) sectionId: number, | |
| ('assignmentId', ParseIntPipe) assignmentId: number, | |
| ): Promise<void> { | |
| return this.enrollmentsService.removeTA(sectionId, assignmentId); | |
| } | |
| /** | |
| * GET /api/enrollments/sections/:sectionId/tas | |
| * List all TAs for a section (admin/instructor) | |
| */ | |
| ('sections/:sectionId/tas') | |
| (RoleName.ADMIN, RoleName.INSTRUCTOR, RoleName.TA) | |
| ({ | |
| summary: 'Get Teaching Assistants for a section', | |
| description: ` | |
| ## List Section Teaching Assistants | |
| Returns all Teaching Assistants assigned to a course section. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: ADMIN, INSTRUCTOR | |
| `, | |
| }) | |
| ({ | |
| name: 'sectionId', | |
| description: 'Course section ID', | |
| type: Number, | |
| }) | |
| ({ | |
| status: 200, | |
| description: 'List of TA assignments', | |
| type: [TAAssignmentResponseDto], | |
| }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ | |
| status: 403, | |
| description: 'Forbidden - Admin or Instructor required', | |
| }) | |
| ({ status: 404, description: 'Section not found' }) | |
| async getSectionTAs( | |
| () req, | |
| ('sectionId', ParseIntPipe) sectionId: number, | |
| ): Promise<TAAssignmentResponseDto[]> { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.enrollmentsService.getSectionTAs(sectionId, userId, roles); | |
| } | |
| /** | |
| * GET /api/enrollments/section/:sectionId/tas | |
| * Get simplified TA summaries for a section | |
| */ | |
| ('section/:sectionId/tas') | |
| (RoleName.ADMIN, RoleName.INSTRUCTOR, RoleName.TA, RoleName.STUDENT) | |
| ({ | |
| summary: 'Get assigned TAs for a section', | |
| description: | |
| 'Returns simplified Teaching Assistant summaries for a course section, including user ID, full name, and email.', | |
| }) | |
| ({ | |
| name: 'sectionId', | |
| description: 'Course section ID', | |
| type: Number, | |
| }) | |
| ({ | |
| status: 200, | |
| description: 'Assigned TA summaries', | |
| schema: { | |
| type: 'array', | |
| items: { | |
| type: 'object', | |
| properties: { | |
| userId: { type: 'number', example: 60 }, | |
| fullName: { type: 'string', example: 'Tarek TA' }, | |
| email: { type: 'string', example: 'ta@example.com' }, | |
| }, | |
| }, | |
| }, | |
| }) | |
| async getSectionTASummaries( | |
| () req, | |
| ('sectionId', ParseIntPipe) sectionId: number, | |
| ): Promise<Array<{ userId: number; fullName: string; email: string }>> { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = this.extractRoles(req.user); | |
| return this.enrollmentsService.getSectionTASummaries( | |
| sectionId, | |
| userId, | |
| roles, | |
| ); | |
| } | |
| private extractRoles(user: any): string[] { | |
| if (!user.roles) return []; | |
| return user.roles.map((r: any) => r.roleName || r.name || r); | |
| } | |
| } | |