// officeHour package repositories import ( "database/sql" "errors" "fmt" "log" "officeHours/config" "officeHours/models" ) type OfficeHourRepo struct { db *sql.DB roomRepo *RoomRepo tutorRepo *TutorRepo courseRepo *CourseRepo config config.Config } func NewOfficeHourRepo(db *sql.DB, roomRepo *RoomRepo, tutorRepo *TutorRepo, courseRepo *CourseRepo, conf config.Config) *OfficeHourRepo { return &OfficeHourRepo{ db: db, roomRepo: roomRepo, tutorRepo: tutorRepo, courseRepo: courseRepo, config: conf, } } func (r *OfficeHourRepo) GetAll(activeOnly bool) ([]models.OfficeHour, error) { var rows *sql.Rows var err error if activeOnly { rows, err = r.db.Query("SELECT * FROM officeHour WHERE active") } else { rows, err = r.db.Query("SELECT * FROM officeHour") } if err != nil { err = fmt.Errorf("Error getting all officeHours from database: %w", err) if !errors.Is(err, sql.ErrNoRows) { log.Println(err.Error()) } return nil, err } defer rows.Close() return r.getFromRows(rows) } func (r *OfficeHourRepo) FindByCourse(course models.Course, activeOnly bool) ([]models.OfficeHour, error) { var rows *sql.Rows var err error if activeOnly { rows, err = r.db.Query("SELECT * FROM officeHour WHERE course=? and active", course.Id) } else { rows, err = r.db.Query("SELECT * FROM officeHour WHERE course=?", course.Id) } defer rows.Close() if err != nil { err = fmt.Errorf("Error getting officeHours by course from database: %w", err) if !errors.Is(err, sql.ErrNoRows) { log.Println(err.Error()) } return nil, err } return r.getFromRows(rows) } func (r *OfficeHourRepo) FindByRoom(room models.Room, activeOnly bool) ([]models.OfficeHour, error) { var rows *sql.Rows var err error if activeOnly { rows, err = r.db.Query("SELECT * FROM officeHour WHERE room=? AND active", room.Id) } else { rows, err = r.db.Query("SELECT * FROM officeHour WHERE room=?", room.Id) } if err != nil { err = fmt.Errorf("Error getting officeHours by room from database: %w", err) if !errors.Is(err, sql.ErrNoRows) { log.Println(err.Error()) } return nil, err } defer rows.Close() return r.getFromRows(rows) } func (r *OfficeHourRepo) FindById(id int) (models.OfficeHour, error) { return r.getFromRow(r.db.QueryRow("SELECT * FROM officeHour WHERE id=?", id)) } // Add an office hour if it doesn't exist yet. // Also add the incluyey tutor if it doesn't exist yet. // // Returns the id of the new office hour. func (r *OfficeHourRepo) Add(officeHour models.OfficeHour) (int, error) { // Find correct tutor or add if not existent _, err := r.tutorRepo.Add(officeHour.Tutor) if err != nil { return 0, err } officeHour.Tutor, err = r.tutorRepo.FindByNameAndEmail(officeHour.Tutor.Name, officeHour.Tutor.Email) if err != nil { err = fmt.Errorf("Newly added tutor not found: %w", err) log.Println(err.Error()) return 0, err } //Don't add identical officeHours officeHours, err := r.FindByCourse(officeHour.Course, false) if err != nil { return 0, err } for _, oldOfficeHour := range officeHours { if officeHour.Tutor == oldOfficeHour.Tutor && officeHour.Date == oldOfficeHour.Date && officeHour.Room == oldOfficeHour.Room && // officeHour.Course == oldOfficeHour.Course && already filtered above officeHour.Info == oldOfficeHour.Info && officeHour.Active == oldOfficeHour.Active && officeHour.Duration == oldOfficeHour.Duration { return oldOfficeHour.Id, nil } } sqlResult, err := r.db.Exec("INSERT INTO `officeHour` (tutor, day, hour, minute, room, roomname, course, week, info, active, duration) VALUES (?,?,?,?,?,?,?,?,?,?,?)", officeHour.Tutor.Id, officeHour.Date.Day, officeHour.Date.Hour, officeHour.Date.Minute, officeHour.Room.Id, officeHour.RoomName, officeHour.Course.Id, officeHour.Date.Week, officeHour.Info, officeHour.Active, officeHour.Duration) if err != nil { return 0, fmt.Errorf("SQL-error inserting new office hour: %w", err) } id, lastInsertIdErr := sqlResult.LastInsertId() if lastInsertIdErr != nil { log.Printf("Error getting Id for new tutor: %s", lastInsertIdErr.Error()) } return int(id), err } func (r *OfficeHourRepo) Delete(officeHour models.OfficeHour) error { _, err := r.db.Exec("DELETE FROM officeHour WHERE id=?", officeHour.Id) if err != nil { err = fmt.Errorf("Error deleting officeHour from database: %w", err) log.Println(err.Error()) return err } return nil } func (r *OfficeHourRepo) getFromRow(row *sql.Row) (models.OfficeHour, error) { var officeHour models.OfficeHour var week, day, hour, minute, tutorId, courseId, roomId int err := row.Scan(&officeHour.Id, &tutorId, &day, &hour, &minute, &roomId, &officeHour.RoomName, &courseId, &week, &officeHour.Info, &officeHour.Active, &officeHour.Duration) if err != nil { err = fmt.Errorf("Error getting single officeHour row from database: %w", err) log.Println(err.Error()) return models.OfficeHour{}, err } officeHour.Date = models.Date{Week: week, Day: day, Hour: hour, Minute: minute} officeHour.Room, err = r.roomRepo.FindById(roomId) if err != nil { err = fmt.Errorf("Error finding room by id %d for office hour: %w", roomId, err) log.Println(err.Error()) return officeHour, err } officeHour.Tutor, err = r.tutorRepo.FindById(tutorId) if err != nil { err = fmt.Errorf("Error finding tutor by id %d for office hour: %w", tutorId, err) log.Println(err.Error()) return officeHour, err } officeHour.Course, err = r.courseRepo.FindById(courseId) if err != nil { err = fmt.Errorf("Error finding course by id %d for office hour: %w", courseId, err) log.Println(err.Error()) return officeHour, err } return officeHour, nil } func (r *OfficeHourRepo) getFromRows(rows *sql.Rows) ([]models.OfficeHour, error) { var officeHours []models.OfficeHour for rows.Next() { var officeHour models.OfficeHour var week, day, hour, minute, tutorId, roomId, courseId int var err error if err := rows.Scan(&officeHour.Id, &tutorId, &day, &hour, &minute, &roomId, &officeHour.RoomName, &courseId, &week, &officeHour.Info, &officeHour.Active, &officeHour.Duration); err != nil { return officeHours, fmt.Errorf("Error getting multiple officeHour rows from database: %w", err) } officeHour.Date = models.Date{Week: week, Day: day, Hour: hour, Minute: minute} officeHour.Room, err = r.roomRepo.FindById(roomId) if err != nil { err = fmt.Errorf("Error finding room by id %d for office hour: %w", roomId, err) log.Println(err.Error()) return officeHours, err } officeHour.Tutor, err = r.tutorRepo.FindById(tutorId) if err != nil { err = fmt.Errorf("Error finding tutor by id %d for office hour: %w", tutorId, err) log.Println(err.Error()) return officeHours, err } officeHour.Course, err = r.courseRepo.FindById(courseId) if err != nil { err = fmt.Errorf("Error finding course by id %d for office hour: %w", courseId, err) log.Println(err.Error()) return officeHours, err } officeHours = append(officeHours, officeHour) } return officeHours, nil } // Get the number of office hours that are maximally parallel during a time span. func (r *OfficeHourRepo) NumberByTimeSpanAndRoom(date models.Date, duration int, room models.Room, activeOnly bool) (int, error) { var rows *sql.Rows var err error if activeOnly { rows, err = r.db.Query("SELECT * FROM officeHour WHERE room=? AND day =? AND active", room.Id, date.Day) } else { rows, err = r.db.Query("SELECT * FROM officeHour WHERE room=?", room.Id) } if err != nil { err = fmt.Errorf("Error getting officeHours by timespan and room from database: %w", err) log.Println(err.Error()) return 0, err } defer rows.Close() officeHours, err := r.getFromRows(rows) if err != nil { return 0, err } var count int // iterate over all points in the new officehour to get the maximum parallel officehours for minute := 0; minute < duration; minute += r.config.Date.MinuteGranularity { var minuteCount int = 0 for _, officeHour := range officeHours { // increase count if officehour starts before this point in time and ends later if models.DateLess(officeHour.Date, models.GetEndDate(date, minute, false)) && models.DateLess(models.GetEndDate(date, minute, false), models.GetEndDate(officeHour.Date, officeHour.Duration, false)) { if date.Week == 0 || officeHour.Week == 0 || date.Week == officeHour.Week { // office hours in alternating weeks should not collide minuteCount += 1 } } } if minuteCount > count { count = minuteCount } } return count, nil } // Check whether the room capacity allows for another office hour during a time span. func (r *OfficeHourRepo) AllowedAt(date models.Date, duration int, room models.Room, activeOnly bool) (bool, error) { numberOfOfficeHours, err := r.NumberByTimeSpanAndRoom(date, duration, room, activeOnly) if err != nil { return false, err } return numberOfOfficeHours < room.MaxOccupy, nil }