package api import ( "net/http" "time" "git.golem15.com/golem15/summercms/modules/surf" "git.golem15.com/jakub/sm-bm-plugin/models" "gorm.io/gorm" ) // courseRow is a course with its counters, as the counting query returns it. type courseRow struct { models.Course `gorm:"embedded"` SessionsCount int64 `gorm:"column:sessions_count"` ReadingsCount int64 `gorm:"column:readings_count"` ReadingsRead int64 `gorm:"column:readings_read"` MaterialsCount int64 `gorm:"column:materials_count"` } func (c courseRow) counts() courseCounts { return courseCounts{Sessions: c.SessionsCount, Readings: c.ReadingsCount, ReadingsRead: c.ReadingsRead, Materials: c.MaterialsCount} } // coursesWithCounts selects courses with their session, reading and // published material counts and the caller's read readings. func coursesWithCounts(db *gorm.DB, userID uint, now time.Time) *gorm.DB { return db.Table("jz_bm_courses AS c").Select(`c.*, (SELECT COUNT(*) FROM jz_bm_class_sessions s WHERE s.course_id = c.id) AS sessions_count, (SELECT COUNT(*) FROM jz_bm_readings r WHERE r.course_id = c.id) AS readings_count, (SELECT COUNT(*) FROM jz_bm_reading_progress p JOIN jz_bm_readings r ON r.id = p.reading_id WHERE r.course_id = c.id AND p.user_id = ?) AS readings_read, (SELECT COUNT(*) FROM jz_bm_materials m WHERE m.course_id = c.id AND m.published_at IS NOT NULL AND m.published_at <= ?) AS materials_count`, userID, now.UTC()) } // Courses answers GET /courses?semester=N: every course in programme order // with its counters. func (h *Handlers) Courses(w http.ResponseWriter, r *http.Request) { userID, ok := h.caller(w, r) if !ok { return } in := query(r, "semester") if !h.validate(w, r, in, coursesRules) { return } db, ok := h.db(w, r) if !ok { return } q := coursesWithCounts(db, userID, h.now()) if semester := intParam(in, "semester", 0); semester != 0 { q = q.Where("c.semester = ?", semester) } var rows []courseRow if err := q.Order("c.semester, c.sort_order, c.name, c.id").Scan(&rows).Error; err != nil { writeOpaque500(w) return } out := make([]courseListJSON, 0, len(rows)) for _, row := range rows { out = append(out, courseListJSON{courseJSON: newCourseJSON(row.Course), Counts: row.counts()}) } writeData(w, out) } // Course answers GET /courses/{id}: the course with its counters, all its // class sessions, its readings with the caller's read state and its // published materials. func (h *Handlers) Course(w http.ResponseWriter, r *http.Request) { userID, ok := h.caller(w, r) if !ok { return } db, ok := h.db(w, r) if !ok { return } id, ok := surf.IntParam(r, "id") if !ok { writeNotFound(w) return } now := h.now() var rows []courseRow if err := coursesWithCounts(db, userID, now).Where("c.id = ?", id).Limit(1).Scan(&rows).Error; err != nil { writeOpaque500(w) return } if len(rows) == 0 { writeNotFound(w) return } row := rows[0] var sessions []models.ClassSession if err := preloadSession(db).Where("course_id = ?", row.ID).Order("starts_at, id").Find(&sessions).Error; err != nil { writeOpaque500(w) return } readings, err := listReadings(db, userID, row.ID) if err != nil { writeOpaque500(w) return } materials, err := publishedMaterialsOfCourse(db, now, row.ID) if err != nil { writeOpaque500(w) return } writeData(w, courseDetailJSON{ courseJSON: newCourseJSON(row.Course), Counts: row.counts(), Sessions: newSessionsJSON(sessions), Readings: readings, Materials: materials, }) }