A material may be private: only its author and the users it is shared
with see it. Every student read (list, detail, course detail and
counters, the checklist linked material, the token twins) goes through
one visibility fragment. Existing materials stay public with no author.
GET/PUT /_bm/api/v1/materials/{id}/access let the author, a material
manager, toggle the flag and pick the users; the first change of an
author-less material makes the caller its author.
119 lines
3.5 KiB
Go
119 lines
3.5 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
|
|
// visible 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 <= ? AND `+visibleMaterialSQL("m")+`) AS materials_count`, userID, now.UTC(), userID, userID)
|
|
}
|
|
|
|
// 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, userID, 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,
|
|
})
|
|
}
|