Dashboard, courses with counters, schedule, meetings, deadlines, readings with per-user progress (mark and unmark) and paginated published materials with tags and files, behind golem15.user's jwt.auth. Responses are built field by field with Warsaw RFC 3339 timestamps; bad query parameters answer 422 in Laravel's shape.
119 lines
3.4 KiB
Go
119 lines
3.4 KiB
Go
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,
|
|
})
|
|
}
|