import { ICourse } from '../../../shared/models/ICourse' ; import { ICourseDashboard } from '../../../shared/models/ICourseDashboard' ; import { ICourseView } from '../../../shared/models/ICourseView' ; import * as mongoose from 'mongoose' ; import { User , IUserModel , IUserPrivileges } from './User' ; import { ILectureModel , Lecture } from './Lecture' ; import { ILecture } from '../../../shared/models/ILecture' ; import { InternalServerError } from 'routing-controllers' ; import { IUser } from '../../../shared/models/IUser' ; import { Directory } from './mediaManager/Directory' ; import { extractSingleMongoId } from '../utilities/ExtractMongoId' ; import { ChatRoom , IChatRoomModel } from './ChatRoom' ; import { Picture } from './mediaManager/File' ; export interface ICourseUserPrivileges extends IUserPrivileges { courseAdminId : string ; userIsCourseAdmin : boolean ; userIsCourseTeacher : boolean ; userIsCourseStudent : boolean ; userIsCourseMember : boolean ; userCanEditCourse : boolean ; userCanViewCourse : boolean ; } interface ICourseModel extends ICourse , mongoose . Document { exportJSON : ( sanitize ? : boolean , onlyBasicData ? : boolean ) => Promise < ICourse > ; checkPrivileges : ( user : IUser ) => ICourseUserPrivileges ; forDashboard : ( user : IUser ) => ICourseDashboard ; forView : ( user : IUser ) => ICourseView ; populateLecturesFor : ( user : IUser ) => this ; processLecturesFor : ( user : IUser ) => Promise < this > ; } interface ICourseMongoose extends mongoose . Model < ICourseModel > { exportPersonalData : ( user : IUser ) => Promise < ICourse > ; changeCourseAdminFromUser : ( userFrom : IUser , userTo : IUser ) => Promise < ICourseMongoose > ; } let Course ; const courseSchema = new mongoose . Schema ( { name : { type : String , unique : true , required : true } , active : { type : Boolean } , description : { type : String } , courseAdmin : { type : mongoose . Schema . Types . ObjectId , ref : 'User' } , media : { type : mongoose . Schema . Types . ObjectId , ref : 'Directory' } , teachers : [ { type : mongoose . Schema . Types . ObjectId , ref : 'User' } ] , students : [ { type : mongoose . Schema . Types . ObjectId , ref : 'User' } ] , lectures : [ { type : mongoose . Schema . Types . ObjectId , ref : 'Lecture' } ] , accessKey : { type : String } , enrollType : { type : String , enum : [ 'free' , 'whitelist' , 'accesskey' ] , default : 'free' } , whitelist : [ { type : mongoose . Schema . Types . ObjectId , ref : 'WhitelistUser' } ] , image : { type : mongoose . Schema . Types . ObjectId , ref : 'Picture' } , chatRooms : [ { type : mongoose . Schema . Types . ObjectId , ref : 'ChatRoom' } ] , freeTextStyle : { type : String , enum : [ '' , 'theme1' , 'theme2' , 'theme3' , 'theme4' ] , default : '' } , } , { timestamps : true , toObject : { transform : function ( doc , ret , { currentUser } ) { if ( ret . hasOwnProperty ( '_id' ) && ret . _id !== null ) { ret . _id = ret . _id . toString ( ) ; } ret . courseAdmin = extractSingleMongoId ( ret . courseAdmin ) ; ret . hasAccessKey = false ; if ( ret . accessKey ) { ret . hasAccessKey = true ; } if ( currentUser !== undefined ) { if ( doc . populated ( 'teachers' ) !== undefined ) { ret . teachers = doc . teachers . map ( ( user ) => user . forUser ( currentUser ) ) ; } if ( doc . populated ( 'students' ) !== undefined ) { ret . students = doc . students . map ( ( user ) => user . forUser ( currentUser ) ) ; } } if ( ret . chatRooms ) { ret . chatRooms = ret . chatRooms . map ( extractSingleMongoId ) ; } } } } ) ; courseSchema . pre ( 'save' , async function ( ) { const course = < ICourseModel > this ; if ( this . isNew ) { const chatRoom = await ChatRoom . create ( { name : 'General' , description : 'This is a general chat for the course ' + course . name , room : { roomType : 'Course' , roomFor : course } } ) ; course . chatRooms . push ( chatRoom . _id ) ; Object . assign ( this , course ) ; } } ) ; courseSchema . pre ( 'remove' , async function ( ) { const localCourse = < ICourseModel > < any > this ; try { const dic = await Directory . findById ( localCourse . media ) ; if ( dic ) { await dic . remove ( ) ; } for ( const lec of localCourse . lectures ) { const lecDoc = await Lecture . findById ( lec ) ; await lecDoc . remove ( ) ; } if ( localCourse . image ) { const picture = await Picture . findById ( localCourse . image ) ; await picture . remove ( ) ; } } catch ( error ) { throw new Error ( 'Delete Error: ' + error . toString ( ) ) ; } } ) ; courseSchema . methods . exportJSON = async function ( sanitize = true , onlyBasicData = false ) { const obj = this . toObject ( ) ; delete obj . _id ; delete obj . createdAt ; delete obj . __v ; delete obj . updatedAt ; if ( sanitize ) { delete obj . accessKey ; delete obj . active ; delete obj . whitelist ; delete obj . students ; delete obj . courseAdmin ; delete obj . teachers ; delete obj . media ; delete obj . chatRooms ; delete obj . freeTextStyle ; } if ( onlyBasicData ) { delete obj . id ; delete obj . hasAccessKey ; return obj ; } const lectures = obj . lectures ; obj . lectures = [ ] ; for ( const lectureId of lectures ) { const lecture = await Lecture . findById ( lectureId ) ; if ( lecture ) { const lectureExport = await lecture . exportJSON ( ) ; obj . lectures . push ( lectureExport ) ; } } if ( obj . image ) { const imageId = obj . image ; obj . image = await Picture . findById ( imageId ) ; } return obj ; } ; courseSchema . statics . importJSON = async function ( course , admin , active ) { course . courseAdmin = admin ; course . active = ( active === true ) ; const lectures = course . lectures ; course . lectures = [ ] ; try { const origName = course . name ; let isCourseDuplicated = false ; let i = 0 ; do { course . name = origName + ( ( i > 0 ) ? ' (copy' + ( ( i > 1 ) ? ' ' + i : '' ) + ')' : '' ) ; isCourseDuplicated = ( await Course . findOne ( { name : course . name } ) ) !== null ; i ++ ; } while ( isCourseDuplicated ) ; const savedCourse = await new Course ( course ) . save ( ) ; for ( const lecture of lectures ) { await Lecture . schema . statics . importJSON ( lecture , savedCourse . _id ) ; } const newCourse = await Course . findById ( savedCourse . _id ) ; return newCourse . toObject ( ) ; } catch ( err ) { const newError = new InternalServerError ( 'Failed to import course' ) ; newError . stack += '\nCaused by: ' + err . message + '\n' + err . stack ; throw newError ; } } ; courseSchema . statics . exportPersonalData = async function ( user ) { const conditions = { } ; conditions . $or = [ ] ; conditions . $or . push ( { students : user . _id } ) ; conditions . $or . push ( { teachers : user . _id } ) ; conditions . $or . push ( { courseAdmin : user . _id } ) ; const courses = await Course . find ( conditions , 'name description -_id' ) ; return Promise . all ( courses . map ( async ( course ) => { return await course . exportJSON ( true , true ) ; } ) ) ; } ; courseSchema . statics . changeCourseAdminFromUser = async function ( userFrom , userTo ) { return Course . updateMany ( { courseAdmin : userFrom . _id } , { courseAdmin : userTo . _id } ) ; } ; courseSchema . methods . checkPrivileges = function ( user ) : ICourseUserPrivileges { const { userIsAdmin , ... userIs } = User . checkPrivileges ( user ) ; const userId = extractSingleMongoId ( user ) ; const courseAdminId = extractSingleMongoId ( this . courseAdmin ) ; const userIsCourseAdmin = userId === courseAdminId ; const userIsCourseTeacher = this . teachers . some ( ( teacher ) => userId === extractSingleMongoId ( teacher ) ) ; const userIsCourseStudent = this . students . some ( ( student ) => userId === extractSingleMongoId ( student ) ) ; const userIsCourseMember = userIsCourseAdmin || userIsCourseTeacher || userIsCourseStudent ; const userCanEditCourse = userIsAdmin || userIsCourseAdmin || userIsCourseTeacher ; const userCanViewCourse = ( this . active && userIsCourseStudent ) || userCanEditCourse ; return { userIsAdmin , ... userIs , courseAdminId , userIsCourseAdmin , userIsCourseTeacher , userIsCourseStudent , userIsCourseMember , userCanEditCourse , userCanViewCourse } ; } ; param { IUser } user returns { Promise < ICourseDashboard > } courseSchema . methods . forDashboard = async function ( user ) : Promise < ICourseDashboard > { const { name , active , description , enrollType } = this ; const picture = await Picture . findById ( this . image ) ; const image = picture && picture . toObject ( ) ; const { userCanEditCourse , userCanViewCourse , userIsCourseAdmin , userIsCourseTeacher , userIsCourseMember } = this . checkPrivileges ( user ) ; return { _id : extractSingleMongoId ( this ) , name , active , description , enrollType , image , userCanEditCourse , userCanViewCourse , userIsCourseAdmin , userIsCourseTeacher , userIsCourseMember } ; } ; courseSchema . methods . forView = function ( user ) : ICourseView { const { name , description , courseAdmin , teachers , lectures , chatRooms , freeTextStyle , active } = this ; const userCanEditCourse = this . checkPrivileges ( user ) . userCanEditCourse ; return { _id : extractSingleMongoId ( this ) , name , description , active , courseAdmin : User . forCourseView ( courseAdmin ) , teachers : teachers . map ( ( teacher ) => User . forCourseView ( teacher ) ) , lectures : lectures . map ( ( lecture ) => lecture . toObject ( ) ) , chatRooms : chatRooms . map ( extractSingleMongoId ) , freeTextStyle , userCanEditCourse } ; } ; courseSchema . methods . populateLecturesFor = function ( user ) { const isTeacherOrAdmin = ( user . role === 'teacher' || user . role === 'admin' ) ; return this . populate ( { path : 'lectures' , populate : { path : 'units' , virtuals : true , match : { $or : [ { visible : undefined } , { visible : true } , { visible : ! isTeacherOrAdmin } ] } , populate : { path : 'progressData' , match : { user : { $eq : user . _id } } } } } ) ; } ; courseSchema . methods . processLecturesFor = async function ( user ) { this . lectures = await Promise . all ( this . lectures . map ( async ( lecture ) => { return await lecture . processUnitsFor ( user ) ; } ) ) ; return this ; } ; Course = mongoose . model < ICourseModel , ICourseMongoose > ( 'Course' , courseSchema ) ; export { Course , ICourseModel } ;