import { Body , Get , Put , Delete , Param , JsonController , UseBefore , NotFoundError , BadRequestError , Post , Authorized , CurrentUser , UploadedFile , Res , UploadedFiles , InternalServerError , ForbiddenError } from 'routing-controllers' ; import passportJwtMiddleware from '../security/passportJwtMiddleware' ; import { errorCodes } from '../config/errorCodes' ; import { Course } from '../models/Course' ; import { Lecture } from '../models/Lecture' ; import { IUnitModel , Unit , AssignmentUnit } from '../models/units/Unit' ; import { IUser } from '../../../shared/models/IUser' ; import { IAssignment } from '../../../shared/models/assignment/IAssignment' ; import config from '../config/main' ; import { File } from '../models/mediaManager/File' ; import { IFile } from '../../../shared/models/mediaManager/IFile' ; import { IAssignmentUnit } from '../../../shared/models/units/IAssignmentUnit' ; import { IProgress } from '../../build/../../shared/models/progress/IProgress' ; import { Progress } from '../models/progress/Progress' ; import { IAssignmentUnitProgress } from '../../../shared/models/progress/IAssignmentProgress' ; import { User } from '../models/User' ; import crypto = require ( 'crypto' ) ; import { IAssignmentUnitModel } from '../models/units/AssignmentUnit' ; import { promisify } from 'util' ; import { Response } from 'express' ; const multer = require ( 'multer' ) ; const path = require ( 'path' ) ; const fs = require ( 'fs' ) ; const archiver = require ( 'archiver' ) ; const uploadOptions = { storage : multer . diskStorage ( { destination : ( req , file , cb ) => { cb ( null , config . uploadFolder ) ; } , filename : ( req , file , cb ) => { crypto . pseudoRandomBytes ( 16 , ( err , raw ) => { cb ( err , err ? undefined : raw . toString ( 'hex' ) + path . extname ( file . originalname ) ) ; } ) ; } } ) , } ; @ JsonController ( '/units' ) @ UseBefore ( passportJwtMiddleware ) export class UnitController { protected waitForArchiveToFinish ( output , archive ) { return new Promise ( ( resolve , reject ) => { output . on ( 'close' , ( ) => { resolve ( ) ; } ) ; archive . finalize ( ) ; } ) ; } api apiName apiGroup apiParam apiSuccess apiSuccessExample apiError apiError @ Get ( '/:id' ) async getUnit ( @ Param ( 'id' ) id , @ CurrentUser ( ) currentUser ) { const unit = await this . getUnitFor ( id , currentUser , 'userCanViewCourse' ) ; return unit . toObject ( ) ; } api apiName apiGroup apiPermission apiPermission apiParam apiParam apiSuccess apiSuccessExample apiError apiError apiError apiError apiError apiError apiError @ Authorized ( [ 'teacher' , 'admin' ] ) @ Post ( '/' ) async addUnit ( @ Body ( ) data , @ CurrentUser ( ) currentUser ) { this . checkPostParam ( data ) ; const course = await Course . findById ( data . model . _course ) ; if ( ! course . checkPrivileges ( currentUser ) . userCanEditCourse ) { throw new ForbiddenError ( ) ; } data . model . unitCreator = currentUser . _id ; try { const createdUnit = await Unit . create ( data . model ) ; return await this . pushToLecture ( data . lectureId , createdUnit ) ; } catch ( err ) { if ( err . name === 'ValidationError' ) { throw err ; } else { throw new BadRequestError ( err ) ; } } } api apiName apiGroup apiPermission apiPermission apiParam apiParam apiParam apiSuccess apiSuccessExample apiError apiError apiError apiError apiError @ Authorized ( [ 'teacher' , 'admin' ] ) @ Put ( '/:id' ) async updateUnit ( @ Param ( 'id' ) id , @ Body ( ) data , @ CurrentUser ( ) currentUser ) { const oldUnit = await this . getUnitFor ( id , currentUser , 'userCanEditCourse' ) ; try { oldUnit . set ( data ) ; const updatedUnit = await oldUnit . save ( ) ; return updatedUnit . toObject ( ) ; } catch ( err ) { if ( err . name === 'ValidationError' ) { throw err ; } else { throw new BadRequestError ( err ) ; } } } api apiName apiGroup apiPermission apiPermission apiParam apiSuccess apiSuccessExample apiError apiError @ Authorized ( [ 'teacher' , 'admin' ] ) @ Delete ( '/:id' ) async deleteUnit ( @ Param ( 'id' ) id , @ CurrentUser ( ) currentUser ) { const unit = await this . getUnitFor ( id , currentUser , 'userCanEditCourse' ) ; await Lecture . updateMany ( { } , { $pull : { units : id } } ) ; await unit . remove ( ) ; return { } ; } private async getUnitFor ( unitId , currentUser , privilege ) { const unit = await Unit . findById ( unitId ) . orFail ( new NotFoundError ( ) ) ; const course = await Course . findById ( unit . _course ) ; if ( ! course . checkPrivileges ( currentUser ) [ privilege ] ) { throw new ForbiddenError ( ) ; } return unit ; } api apiName apiGroup apiPermission apiSuccess apiSuccessExample apiError apiError apiError apiError apiError @ Authorized ( [ 'student' ] ) @ Post ( '/:id/assignment' ) async addAssignment ( @ Param ( 'id' ) id , @ CurrentUser ( ) currentUser ) { const assignmentUnit = < IAssignmentUnitModel > await Unit . findById ( id ) . orFail ( new NotFoundError ( ) ) ; let assignment = assignmentUnit . assignments . find ( submittedAssignment => { return submittedAssignment . user . _id . toString ( ) === currentUser . _id ; } ) ; if ( ! ! assignment ) { throw new BadRequestError ( ) ; } try { assignment = { _id : null , files : [ ] , user : currentUser . _id , submitted : false , checked : - 1 , submittedDate : new Date ( ) } ; assignmentUnit . assignments . push ( assignment ) ; await assignmentUnit . save ( ) ; return assignment ; } catch ( err ) { throw new BadRequestError ( err ) ; } } apiParam apiParam apiParam apiError @ Authorized ( [ 'student' ] ) @ Put ( '/:id/assignment/files' ) async addFileToAssignment ( @ Param ( 'id' ) unitId , @ UploadedFile ( 'file' , { options : uploadOptions } ) uploadedFile , @ CurrentUser ( ) currentUser ) { const assignmentUnit = < IAssignmentUnitModel > await Unit . findById ( unitId ) . orFail ( new NotFoundError ( ) ) ; const assignment = assignmentUnit . assignments . find ( submittedAssignment => { return submittedAssignment . user . _id . toString ( ) === currentUser . _id ; } ) ; if ( ! assignment ) { throw new NotFoundError ( ) ; } if ( assignment . submitted ) { throw new BadRequestError ( ) ; } const fileMetadata = new File ( { name : uploadedFile . originalname , physicalPath : uploadedFile . path , link : uploadedFile . filename , size : uploadedFile . size , mimeType : uploadedFile . mimetype , } ) ; try { const file = await new File ( fileMetadata ) . save ( ) ; if ( ! assignment . files ) { assignment . files = [ ] ; } assignment . files . push ( file . _id ) ; await assignmentUnit . save ( ) ; return file . toObject ( ) ; } catch ( error ) { throw new InternalServerError ( error . message ) ; } } api apiName apiGroup apiPermission apiPermission apiParam apiParam apiParam apiSuccess apiSuccessExample apiError apiError apiError apiError @ Authorized ( [ 'teacher' , 'admin' , 'student' ] ) @ Put ( '/:id/assignment' ) async updateAssignment ( @ Param ( 'id' ) id , @ Body ( ) data , @ CurrentUser ( ) currentUser ) { const assignmentUnit = < IAssignmentUnitModel > await Unit . findById ( id ) . orFail ( new NotFoundError ( ) ) ; let assignment = null ; if ( currentUser . role === 'teacher' || currentUser . role === 'admin' ) { if ( ! data . user . _id ) { throw new BadRequestError ( ) ; } assignment = assignmentUnit . assignments . find ( submittedAssignment => `${ submittedAssignment . user }` === data . user . _id ) ; assignment . checked = data . checked ; } else { assignment = assignmentUnit . assignments . find ( submittedAssignment => `${ submittedAssignment . user }` === currentUser . _id ) ; if ( ! assignment . submitted ) { assignment . submitted = data . submitted ; assignment . submittedDate = new Date ( ) ; } } try { await assignmentUnit . save ( ) ; } catch ( err ) { if ( err . name === 'ValidationError' ) { throw err ; } else { throw new BadRequestError ( err ) ; } } return true ; } api apiName apiGroup apiPermission apiParam apiSuccess apiSuccessExample apiError @ Authorized ( [ 'student' ] ) @ Delete ( '/:id/assignment' ) async deleteAssignment ( @ Param ( 'id' ) id , @ CurrentUser ( ) currentUser ) { const assignmentUnit = < IAssignmentUnitModel > await Unit . findById ( id ) ; if ( ! assignmentUnit ) { throw new NotFoundError ( ) ; } for ( const assignment of assignmentUnit . assignments ) { if ( assignment . user . _id . toString ( ) === currentUser . _id ) { if ( assignment . submitted ) { throw new BadRequestError ( ) ; } const index = assignmentUnit . assignments . indexOf ( assignment , 0 ) ; assignmentUnit . assignments . splice ( index , 1 ) ; await assignmentUnit . save ( ) ; return true ; } } } api apiName apiGroup apiParam apiParam apiSuccess apiSuccessExample @ Get ( '/:id/assignments/:user/files' ) @ Authorized ( [ 'teacher' ] ) async downloadFilesOfSingleAssignment ( @ Param ( 'id' ) id , @ Param ( 'user' ) userId , @ Res ( ) response ) { const assignmentUnit = < IAssignmentUnitModel > await Unit . findById ( id ) ; if ( ! assignmentUnit ) { throw new NotFoundError ( ) ; } const assignment = assignmentUnit . assignments . find ( row => `${ row . user . _id }` === userId ) ; if ( ! assignment . files . length ) { return ; } const user = await User . findById ( assignment . user ) ; const filepath = `${ config . tmpFileCacheFolder }${ user . profile . lastName }_${ assignmentUnit . name }.zip` ; const output = fs . createWriteStream ( filepath ) ; const archive = archiver ( 'zip' , { zlib : { level : 9 } } ) ; archive . pipe ( output ) ; for ( const fileMetadata of assignment . files ) { const file = await File . findById ( fileMetadata ) ; archive . file ( `uploads/${ file . link }` , { name : user . profile . lastName + '_' + user . profile . firstName + '_' + file . name } ) ; } archive . on ( 'error' , ( ) => { throw new NotFoundError ( ) ; } ) ; await this . waitForArchiveToFinish ( output , archive ) ; response . status ( 200 ) ; response . setHeader ( 'Connection' , 'keep-alive' ) ; response . setHeader ( 'Access-Control-Expose-Headers' , 'Content-Disposition' ) ; await promisify < string , void > ( response . download . bind ( response ) ) ( filepath ) ; return response ; } api apiName apiGroup apiParam apiSuccess apiSuccessExample @ Get ( '/:id/assignments/files' ) @ Authorized ( [ 'teacher' ] ) async getAllAssignments ( @ Param ( 'id' ) id , @ Res ( ) response ) { const assignmentUnit = < IAssignmentUnitModel > await Unit . findById ( id ) ; if ( ! assignmentUnit ) { throw new NotFoundError ( ) ; } const filepath = config . tmpFileCacheFolder + assignmentUnit . name + '.zip' ; const output = fs . createWriteStream ( filepath ) ; const archive = archiver ( 'zip' , { zlib : { level : 9 } } ) ; archive . pipe ( output ) ; for ( const assignment of assignmentUnit . assignments ) { const user = await User . findById ( assignment . user ) ; for ( const fileMetadata of assignment . files ) { const file = await File . findById ( fileMetadata ) ; archive . file ( `uploads/${ file . link }` , { name : user . profile . lastName + '_' + user . profile . firstName + '_' + file . name } ) ; } } archive . on ( 'error' , ( ) => { throw new NotFoundError ( ) ; } ) ; await this . waitForArchiveToFinish ( output , archive ) ; response . setHeader ( 'Connection' , 'keep-alive' ) ; response . setHeader ( 'Access-Control-Expose-Headers' , 'Content-Disposition' ) ; await promisify < string , void > ( response . download . bind ( response ) ) ( filepath ) ; return response ; } private pushToLecture ( lectureId , unit ) { return Lecture . findById ( lectureId ) . then ( ( lecture ) => { lecture . units . push ( unit ) ; return lecture . save ( ) ; } ) . then ( ( ) => { return unit . populateUnit ( ) ; } ) . then ( ( populatedUnit ) => { return populatedUnit . toObject ( ) ; } ) . catch ( ( err ) => { throw new BadRequestError ( err ) ; } ) ; } private checkPostParam ( data ) { if ( ! data . lectureId ) { throw new BadRequestError ( errorCodes . unit . postMissingLectureId . text ) ; } if ( ! data . model ) { throw new BadRequestError ( errorCodes . unit . postMissingUnit . text ) ; } if ( ! data . model . _course ) { throw new BadRequestError ( errorCodes . unit . postMissingCourse . text ) ; } } }