Spaces:
Sleeping
Sleeping
| import { | |
| Controller, | |
| Post, | |
| Get, | |
| Delete, | |
| Param, | |
| Body, | |
| Query, | |
| UseGuards, | |
| Request, | |
| Patch, | |
| UseInterceptors, | |
| UploadedFile, | |
| HttpCode, | |
| HttpStatus, | |
| } from '@nestjs/common'; | |
| import { | |
| ApiTags, | |
| ApiOperation, | |
| ApiResponse, | |
| ApiBearerAuth, | |
| ApiParam, | |
| ApiBody, | |
| ApiConsumes, | |
| } from '@nestjs/swagger'; | |
| import { FileInterceptor } from '@nestjs/platform-express'; | |
| 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'; | |
| import { AssignmentsService } from '../services'; | |
| import { | |
| CreateAssignmentDto, | |
| UpdateAssignmentDto, | |
| SubmitAssignmentDto, | |
| GradeSubmissionDto, | |
| AssignmentQueryDto, | |
| UploadAssignmentInstructionDto, | |
| UploadAssignmentSubmissionDto, | |
| } from '../dto'; | |
| import { AssignmentStatus } from '../enums'; | |
| type UploadedAssignmentFile = { | |
| originalname: string; | |
| mimetype: string; | |
| buffer: Buffer; | |
| }; | |
| ('📝 Assignments') | |
| ('api/assignments') | |
| (JwtAuthGuard, RolesGuard) | |
| ('JWT-auth') | |
| export class AssignmentsController { | |
| constructor(private readonly assignmentsService: AssignmentsService) {} | |
| () | |
| ({ | |
| summary: 'List assignments', | |
| description: | |
| 'Filter by course, status, due dates. Supports pagination and sorting.', | |
| }) | |
| ({ status: 200, description: 'Paginated list of assignments' }) | |
| async findAll(() query: AssignmentQueryDto, () req) { | |
| const userId = req.user.userId || req.user.id; | |
| const roles = req.user.roles?.map((role: any) => role.roleName || role.name || role) || []; | |
| return this.assignmentsService.findAll(query, userId, roles); | |
| } | |
| () | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| ({ | |
| summary: 'Create assignment', | |
| description: | |
| 'Create a new assignment for a course. Only instructors, TAs, and admins.', | |
| }) | |
| ({ status: 201, description: 'Assignment created' }) | |
| async create(() dto: CreateAssignmentDto, () req) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.assignmentsService.create(dto, userId); | |
| } | |
| (':id') | |
| ({ summary: 'Get assignment details', description: 'Returns assignment with course info and submissions.' }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| ({ | |
| status: 200, | |
| description: 'Assignment details with submissions', | |
| }) | |
| async findOne(('id') id: number) { | |
| return this.assignmentsService.findOne(+id); | |
| } | |
| (':id') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| ({ summary: 'Update assignment' }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| ({ status: 200, description: 'Assignment updated' }) | |
| async update( | |
| ('id') id: number, | |
| () dto: UpdateAssignmentDto, | |
| () req, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.assignmentsService.update(+id, dto, userId); | |
| } | |
| (':id') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| ({ summary: 'Delete assignment (soft delete)' }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| ({ status: 200, description: 'Assignment deleted' }) | |
| async remove(('id') id: number) { | |
| return this.assignmentsService.remove(+id); | |
| } | |
| (':id/status') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| ({ | |
| summary: 'Change assignment status (publish/close/archive)', | |
| }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| ({ | |
| schema: { | |
| properties: { | |
| status: { | |
| type: 'string', | |
| enum: ['draft', 'published', 'closed', 'archived'], | |
| example: 'published', | |
| }, | |
| }, | |
| }, | |
| }) | |
| async changeStatus( | |
| ('id') id: number, | |
| ('status') status: AssignmentStatus, | |
| () req, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.assignmentsService.changeStatus(+id, status, userId); | |
| } | |
| (':id/submit') | |
| (RoleName.STUDENT) | |
| ({ | |
| summary: 'Submit assignment', | |
| description: 'Student submits their work', | |
| }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| async submit( | |
| ('id') id: number, | |
| () dto: SubmitAssignmentDto, | |
| () req, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.assignmentsService.submit(+id, dto, userId); | |
| } | |
| // /submissions/my must be defined BEFORE /:subId to avoid route conflicts | |
| (':id/submissions/my') | |
| (RoleName.STUDENT) | |
| ({ summary: "Get student's own submission" }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| async getMySubmission(('id') id: number, () req) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.assignmentsService.getMySubmission(+id, userId); | |
| } | |
| (':id/submissions') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| ({ summary: 'List all submissions for an assignment' }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| async getSubmissions(('id') id: number) { | |
| return this.assignmentsService.getSubmissions(+id); | |
| } | |
| (':id/submissions/:subId/grade') | |
| (RoleName.INSTRUCTOR, RoleName.TA) | |
| ({ summary: 'Grade a submission (creates grade record)' }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| ({ name: 'subId', type: Number, description: 'Submission ID', example: 1 }) | |
| async gradeSubmission( | |
| ('id') id: number, | |
| ('subId') subId: number, | |
| () dto: GradeSubmissionDto, | |
| () req, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.assignmentsService.gradeSubmission(+id, +subId, dto, userId); | |
| } | |
| // ============ GOOGLE DRIVE UPLOADS ============ | |
| (':id/instructions/upload') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| (FileInterceptor('file')) | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Upload assignment instruction to Google Drive', | |
| description: 'Upload an assignment instruction file (PDF, DOCX, etc.) directly to Google Drive. Creates folder structure automatically.', | |
| }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| ('multipart/form-data') | |
| ({ | |
| schema: { | |
| type: 'object', | |
| required: ['file'], | |
| properties: { | |
| file: { | |
| type: 'string', | |
| format: 'binary', | |
| description: 'Instruction file (PDF, DOCX, etc.)', | |
| }, | |
| title: { | |
| type: 'string', | |
| description: 'Instruction title', | |
| example: 'Assignment 1 - Database Design Guide', | |
| }, | |
| orderIndex: { | |
| type: 'integer', | |
| description: 'Order index for sorting', | |
| example: 1, | |
| }, | |
| }, | |
| }, | |
| }) | |
| ({ status: 201, description: 'Instruction uploaded successfully' }) | |
| ({ status: 400, description: 'No file provided' }) | |
| ({ status: 404, description: 'Assignment not found' }) | |
| async uploadInstruction( | |
| ('id') id: number, | |
| () file: UploadedAssignmentFile, | |
| () dto: UploadAssignmentInstructionDto, | |
| () req, | |
| ) { | |
| if (!file) { | |
| throw new Error('No file provided'); | |
| } | |
| const userId = req.user.userId || req.user.id; | |
| return this.assignmentsService.uploadInstructionToDrive( | |
| +id, | |
| file, | |
| dto.title, | |
| dto.orderIndex || 0, | |
| userId, | |
| ); | |
| } | |
| (':assignmentId/instructions/:driveId') | |
| (RoleName.INSTRUCTOR, RoleName.TA, RoleName.ADMIN) | |
| (HttpStatus.OK) | |
| ({ | |
| summary: 'Delete assignment instruction file', | |
| description: 'Delete an uploaded instruction file from an assignment. Removes the file from Google Drive and the database record.', | |
| }) | |
| ({ name: 'assignmentId', type: Number, description: 'Assignment ID', example: 3 }) | |
| ({ name: 'driveId', type: String, description: 'Google Drive file ID (driveId from instructionFiles array)', example: '1IOjnr6y7JRO7hcoMXkpoRIw8txm7WZlV' }) | |
| ({ status: 200, description: 'Instruction file deleted successfully' }) | |
| ({ status: 400, description: 'Instruction file not found or invalid driveId' }) | |
| ({ status: 403, description: 'Forbidden — user not authorized' }) | |
| ({ status: 404, description: 'Assignment not found' }) | |
| async deleteInstruction( | |
| ('assignmentId') assignmentId: number, | |
| ('driveId') driveId: string, | |
| () req, | |
| ) { | |
| const userId = req.user.userId || req.user.id; | |
| return this.assignmentsService.deleteInstructionFile( | |
| +assignmentId, | |
| driveId, | |
| userId, | |
| ); | |
| } | |
| (':id/submissions/upload') | |
| (RoleName.STUDENT) | |
| (FileInterceptor('file')) | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Upload assignment submission to Google Drive', | |
| description: 'Upload assignment submission file directly to Google Drive. Creates student folder automatically. Auto-detects late submissions.', | |
| }) | |
| ({ name: 'id', type: Number, description: 'Assignment ID', example: 3 }) | |
| ('multipart/form-data') | |
| ({ | |
| schema: { | |
| type: 'object', | |
| required: ['file'], | |
| properties: { | |
| file: { | |
| type: 'string', | |
| format: 'binary', | |
| description: 'Submission file', | |
| }, | |
| submissionText: { | |
| type: 'string', | |
| description: 'Optional submission notes/comments', | |
| example: 'Completed all tasks as instructed.', | |
| }, | |
| submissionLink: { | |
| type: 'string', | |
| description: 'Optional link to external submission (e.g., GitHub)', | |
| example: 'https://github.com/student/project', | |
| }, | |
| }, | |
| }, | |
| }) | |
| ({ status: 201, description: 'Submission uploaded successfully' }) | |
| ({ status: 400, description: 'No file provided' }) | |
| ({ status: 404, description: 'Assignment not found' }) | |
| async uploadSubmission( | |
| ('id') id: number, | |
| () file: UploadedAssignmentFile, | |
| () dto: UploadAssignmentSubmissionDto, | |
| () req, | |
| ) { | |
| if (!file) { | |
| throw new Error('No file provided'); | |
| } | |
| const userId = req.user.userId || req.user.id; | |
| return this.assignmentsService.uploadSubmissionToDrive( | |
| +id, | |
| file, | |
| dto.submissionText, | |
| dto.submissionLink, | |
| userId, | |
| ); | |
| } | |
| } | |