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.
89 lines
2.6 KiB
Go
89 lines
2.6 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"git.golem15.com/jakub/sm-bm-plugin/models"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// dashboardJSON is the answer of /dashboard.
|
|
type dashboardJSON struct {
|
|
NextSessions []sessionJSON `json:"next_sessions"`
|
|
UpcomingDeadlines []deadlineJSON `json:"upcoming_deadlines"`
|
|
CurrentMeeting *meetingJSON `json:"current_meeting"`
|
|
NextMeeting *meetingJSON `json:"next_meeting"`
|
|
Readings readingsSummary `json:"readings"`
|
|
}
|
|
|
|
// Dashboard answers GET /dashboard: the next class sessions (those not yet
|
|
// over), the deadlines not yet due, the meeting under way today and the next
|
|
// one, and the caller's reading progress.
|
|
func (h *Handlers) Dashboard(w http.ResponseWriter, r *http.Request) {
|
|
userID, ok := h.caller(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
db, ok := h.db(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
now := h.now().UTC()
|
|
limit := h.dashboardLimit()
|
|
out := dashboardJSON{}
|
|
|
|
var sessions []models.ClassSession
|
|
if err := preloadSession(db).Where("ends_at > ?", now).Order("starts_at, id").Limit(limit).Find(&sessions).Error; err != nil {
|
|
writeOpaque500(w)
|
|
return
|
|
}
|
|
out.NextSessions = newSessionsJSON(sessions)
|
|
|
|
var deadlines []models.Deadline
|
|
if err := db.Preload("Course").Where("due_at >= ?", now).Order("due_at, id").Limit(limit).Find(&deadlines).Error; err != nil {
|
|
writeOpaque500(w)
|
|
return
|
|
}
|
|
out.UpcomingDeadlines = newDeadlinesJSON(deadlines)
|
|
|
|
day := today(h.now()).Format(models.DateLayout)
|
|
var err error
|
|
if out.CurrentMeeting, err = meetingWhere(db, "starts_on <= ? AND ends_on >= ?", day, day); err != nil {
|
|
writeOpaque500(w)
|
|
return
|
|
}
|
|
if out.NextMeeting, err = meetingWhere(db, "starts_on > ?", day); err != nil {
|
|
writeOpaque500(w)
|
|
return
|
|
}
|
|
|
|
err = db.Raw(`SELECT
|
|
COUNT(*) AS total,
|
|
COUNT(p.id) AS read,
|
|
COUNT(*) FILTER (WHERE r.is_required) AS required_total,
|
|
COUNT(p.id) FILTER (WHERE r.is_required) AS required_read
|
|
FROM jz_bm_readings r
|
|
LEFT JOIN jz_bm_reading_progress p ON p.reading_id = r.id AND p.user_id = ?`, userID).Scan(&out.Readings).Error
|
|
if err != nil {
|
|
writeOpaque500(w)
|
|
return
|
|
}
|
|
writeData(w, out)
|
|
}
|
|
|
|
// meetingWhere returns the first meeting by start date that matches the
|
|
// condition, with its session count, or nil.
|
|
func meetingWhere(db *gorm.DB, cond string, args ...any) (*meetingJSON, error) {
|
|
var m models.Meeting
|
|
res := db.Where(cond, args...).Order("starts_on, number").Limit(1).Find(&m)
|
|
if res.Error != nil || res.RowsAffected == 0 {
|
|
return nil, res.Error
|
|
}
|
|
var sessions int64
|
|
if err := db.Model(&models.ClassSession{}).Where("meeting_id = ?", m.ID).Count(&sessions).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
out := newMeetingJSON(m, sessions)
|
|
return &out, nil
|
|
}
|