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  }