github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/database/gormdb_course.go (about) 1 package database 2 3 import ( 4 "errors" 5 6 "github.com/quickfeed/quickfeed/qf" 7 ) 8 9 // CreateCourse creates a new course if user with given ID is admin, enrolls user as course teacher. 10 // The provided course must have a unique (GitHub) OrganizationID not already associated with existing course. 11 // Similarly, the course must have a unique course code and year. 12 func (db *GormDB) CreateCourse(courseCreatorID uint64, course *qf.Course) error { 13 courseCreator, err := db.GetUser(courseCreatorID) 14 if err != nil { 15 return err 16 } 17 if !courseCreator.IsAdmin { 18 return ErrInsufficientAccess 19 } 20 21 var courses int64 22 if err := db.conn.Model(&qf.Course{}).Where(&qf.Course{ 23 ScmOrganizationID: course.ScmOrganizationID, 24 }).Or(&qf.Course{ 25 Code: course.Code, 26 Year: course.Year, 27 }).Count(&courses).Error; err != nil { 28 return err 29 } 30 if courses > 0 { 31 return ErrCourseExists 32 } 33 34 course.CourseCreatorID = courseCreatorID 35 36 tx := db.conn.Begin() 37 if err := tx.Create(course).Error; err != nil { 38 tx.Rollback() 39 return err 40 } 41 42 // enroll course creator as teacher for course and mark as visible 43 if err := tx.Create(&qf.Enrollment{ 44 UserID: courseCreatorID, 45 CourseID: course.ID, 46 Status: qf.Enrollment_TEACHER, 47 State: qf.Enrollment_VISIBLE, 48 }).Error; err != nil { 49 tx.Rollback() 50 return err 51 } 52 53 return tx.Commit().Error 54 } 55 56 // GetCourse fetches course by ID. If withInfo is true, preloads course 57 // assignments, active enrollments and groups. 58 func (db *GormDB) GetCourse(courseID uint64, withEnrollments bool) (*qf.Course, error) { 59 m := db.conn 60 var course qf.Course 61 62 if withEnrollments { 63 // we only want submission from users enrolled in the course 64 userStates := []qf.Enrollment_UserStatus{ 65 qf.Enrollment_STUDENT, 66 qf.Enrollment_TEACHER, 67 } 68 // and only group submissions from approved groups 69 modelGroup := &qf.Group{Status: qf.Group_APPROVED, CourseID: courseID} 70 if err := m.Preload("Assignments"). 71 Preload("Enrollments", "status in (?)", userStates). 72 Preload("Enrollments.User"). 73 Preload("Enrollments.Group"). 74 Preload("Enrollments.UsedSlipDays"). 75 Preload("Groups", modelGroup). 76 First(&course, courseID).Error; err != nil { 77 return nil, err 78 } 79 80 // Set number of remaining slip days for each course enrollment 81 for _, e := range course.Enrollments { 82 e.SetSlipDays(&course) 83 } 84 for _, g := range course.Groups { 85 // Set number of remaining slip days for each group enrollment 86 for _, e := range g.Enrollments { 87 e.SetSlipDays(&course) 88 } 89 } 90 } else { 91 if err := m.First(&course, courseID).Error; err != nil { 92 return nil, err 93 } 94 } 95 return &course, nil 96 } 97 98 // GetCourseByOrganizationID fetches course by organization ID. 99 func (db *GormDB) GetCourseByOrganizationID(did uint64) (*qf.Course, error) { 100 var course qf.Course 101 if err := db.conn.First(&course, &qf.Course{ScmOrganizationID: did}).Error; err != nil { 102 return nil, err 103 } 104 return &course, nil 105 } 106 107 // GetCourses returns a list of courses. If one or more course ids are provided, 108 // the corresponding courses are returned. Otherwise, all courses are returned. 109 func (db *GormDB) GetCourses(courseIDs ...uint64) ([]*qf.Course, error) { 110 m := db.conn 111 if len(courseIDs) > 0 { 112 m = m.Where(courseIDs) 113 } 114 var courses []*qf.Course 115 if err := m.Find(&courses).Error; err != nil { 116 return nil, err 117 } 118 return courses, nil 119 } 120 121 // GetCoursesByUser returns all courses (with enrollment status) 122 // for the given user id. 123 // If enrollment statuses is provided, the set of courses returned 124 // is filtered according to these enrollment statuses. 125 func (db *GormDB) GetCoursesByUser(userID uint64, statuses ...qf.Enrollment_UserStatus) ([]*qf.Course, error) { 126 enrollments, err := db.getEnrollments(&qf.User{ID: userID}, statuses...) 127 if err != nil { 128 return nil, err 129 } 130 131 var courseIDs []uint64 132 m := make(map[uint64]*qf.Enrollment) 133 for _, enrollment := range enrollments { 134 m[enrollment.CourseID] = enrollment 135 courseIDs = append(courseIDs, enrollment.CourseID) 136 } 137 138 if len(statuses) == 0 { 139 courseIDs = nil 140 } else if len(courseIDs) == 0 { 141 // No need to query database since user have no enrolled courses. 142 return []*qf.Course{}, nil 143 } 144 courses, err := db.GetCourses(courseIDs...) 145 if err != nil { 146 return nil, err 147 } 148 149 for _, course := range courses { 150 course.Enrolled = qf.Enrollment_NONE 151 if enrollment, ok := m[course.ID]; ok { 152 course.Enrolled = enrollment.Status 153 } 154 } 155 return courses, nil 156 } 157 158 // GetCourseTeachers returns a list of all teachers in a course. 159 func (db *GormDB) GetCourseTeachers(query *qf.Course) ([]*qf.User, error) { 160 var course qf.Course 161 if err := db.conn.Where(query).Preload("Enrollments").First(&course).Error; err != nil { 162 return nil, err 163 } 164 teachers := []*qf.User{} 165 for _, teacherEnrollment := range course.TeacherEnrollments() { 166 teacher, err := db.GetUser(teacherEnrollment.GetUserID()) 167 if err != nil { 168 return nil, err 169 } 170 teachers = append(teachers, teacher) 171 } 172 if len(teachers) == 0 { 173 return nil, errors.New("course has no teachers") 174 } 175 return teachers, nil 176 } 177 178 // UpdateCourse updates course information. 179 func (db *GormDB) UpdateCourse(course *qf.Course) error { 180 return db.conn.Model(&qf.Course{}). 181 Where(&qf.Course{ID: course.GetID()}). 182 Updates(course).Error 183 }