Files
sm-bm-plugin/controllers/api/json.go
Jakub Zych ebd9117f8c feat: lecturer on class sessions and in the session course ref
The schedule showed a lecturer only where the session notes named one
(Globalizacja), so the student plan had nobody under the other courses.
The session JSON now carries the course lecturer in its course ref and an
optional per-session lecturer (new nullable column, admin field, seed key)
for courses shared between two people. The student app shows the
session's own lecturer or, failing that, the course's.
2026-10-17 11:20:11 +02:00

255 lines
7.1 KiB
Go

package api
import "git.golem15.com/jakub/sm-bm-plugin/models"
// idOf is the value of an optional id, 0 when it is nil. The API serves only
// sessions and readings that have a course, so their course_id is set.
func idOf(id *uint) uint {
if id == nil {
return 0
}
return *id
}
// courseRef is the short form of a course embedded in other records.
type courseRef struct {
ID uint `json:"id"`
Code *string `json:"code"`
Name string `json:"name"`
Form string `json:"form"`
Lecturer *string `json:"lecturer"`
Semester int `json:"semester"`
}
func newCourseRef(c *models.Course) *courseRef {
if c == nil {
return nil
}
return &courseRef{ID: c.ID, Code: c.Code, Name: c.Name, Form: c.Form, Lecturer: c.Lecturer, Semester: c.Semester}
}
// courseJSON is a course's own fields.
type courseJSON struct {
ID uint `json:"id"`
Code *string `json:"code"`
Name string `json:"name"`
Form string `json:"form"`
Lecturer *string `json:"lecturer"`
Semester int `json:"semester"`
ECTS *float64 `json:"ects"`
Assessment *string `json:"assessment"`
ElectiveGroup *string `json:"elective_group"`
Description *string `json:"description"`
SyllabusURL *string `json:"syllabus_url"`
SortOrder int `json:"sort_order"`
}
func newCourseJSON(c models.Course) courseJSON {
return courseJSON{
ID: c.ID,
Code: c.Code,
Name: c.Name,
Form: c.Form,
Lecturer: c.Lecturer,
Semester: c.Semester,
ECTS: c.ECTS,
Assessment: c.Assessment,
ElectiveGroup: c.ElectiveGroup,
Description: c.Description,
SyllabusURL: c.SyllabusURL,
SortOrder: c.SortOrder,
}
}
// courseCounts are the per-course counters of /courses and /courses/{id}.
type courseCounts struct {
Sessions int64 `json:"sessions"`
Readings int64 `json:"readings"`
ReadingsRead int64 `json:"readings_read"`
Materials int64 `json:"materials"`
}
type courseListJSON struct {
courseJSON
Counts courseCounts `json:"counts"`
}
type courseDetailJSON struct {
courseJSON
Counts courseCounts `json:"counts"`
Sessions []sessionJSON `json:"sessions"`
Readings []readingJSON `json:"readings"`
Materials []materialListJSON `json:"materials"`
}
// meetingJSON is one zjazd.
type meetingJSON struct {
ID uint `json:"id"`
Number int `json:"number"`
StartsOn string `json:"starts_on"`
EndsOn string `json:"ends_on"`
Notes *string `json:"notes"`
SessionsCount int64 `json:"sessions_count"`
}
func newMeetingJSON(m models.Meeting, sessions int64) meetingJSON {
return meetingJSON{ID: m.ID, Number: m.Number, StartsOn: m.StartsOn.String(), EndsOn: m.EndsOn.String(), Notes: m.Notes, SessionsCount: sessions}
}
// sessionJSON is one class session with its course.
type sessionJSON struct {
ID uint `json:"id"`
CourseID uint `json:"course_id"`
MeetingID *uint `json:"meeting_id"`
MeetingNumber *int `json:"meeting_number"`
StartsAt string `json:"starts_at"`
EndsAt string `json:"ends_at"`
Room *string `json:"room"`
Kind *string `json:"kind"`
Lecturer *string `json:"lecturer"`
Notes *string `json:"notes"`
IsOnline bool `json:"is_online"`
Course *courseRef `json:"course"`
}
func newSessionJSON(s models.ClassSession) sessionJSON {
out := sessionJSON{
ID: s.ID,
CourseID: idOf(s.CourseID),
MeetingID: s.MeetingID,
StartsAt: models.FormatDateTime(s.StartsAt),
EndsAt: models.FormatDateTime(s.EndsAt),
Room: s.Room,
Kind: s.Kind,
Lecturer: s.Lecturer,
Notes: s.Notes,
IsOnline: s.IsOnline,
Course: newCourseRef(s.Course),
}
if s.Meeting != nil {
n := s.Meeting.Number
out.MeetingNumber = &n
}
return out
}
func newSessionsJSON(rows []models.ClassSession) []sessionJSON {
out := make([]sessionJSON, 0, len(rows))
for _, s := range rows {
out = append(out, newSessionJSON(s))
}
return out
}
// deadlineJSON is one terminarz entry.
type deadlineJSON struct {
ID uint `json:"id"`
Title string `json:"title"`
DueAt string `json:"due_at"`
Kind string `json:"kind"`
Description *string `json:"description"`
CourseID *uint `json:"course_id"`
Course *courseRef `json:"course"`
}
func newDeadlinesJSON(rows []models.Deadline) []deadlineJSON {
out := make([]deadlineJSON, 0, len(rows))
for _, d := range rows {
out = append(out, deadlineJSON{
ID: d.ID,
Title: d.Title,
DueAt: models.FormatDateTime(d.DueAt),
Kind: d.Kind,
Description: d.Description,
CourseID: d.CourseID,
Course: newCourseRef(d.Course),
})
}
return out
}
// readingJSON is one reading with the caller's read state.
type readingJSON struct {
ID uint `json:"id"`
CourseID uint `json:"course_id"`
Course *courseRef `json:"course"`
Title string `json:"title"`
Author *string `json:"author"`
Source *string `json:"source"`
URL *string `json:"url"`
IsRequired bool `json:"is_required"`
SortOrder int `json:"sort_order"`
ReadAt *string `json:"read_at"`
}
// tagJSON is one material tag.
type tagJSON struct {
ID uint `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
}
func newTagsJSON(tags []models.Tag) []tagJSON {
out := make([]tagJSON, 0, len(tags))
for _, t := range tags {
out = append(out, tagJSON{ID: t.ID, Name: t.Name, Slug: t.Slug})
}
return out
}
// materialListJSON is a published material in a list.
type materialListJSON struct {
ID uint `json:"id"`
CourseID *uint `json:"course_id"`
Course *courseRef `json:"course"`
Title string `json:"title"`
Type string `json:"type"`
Source *string `json:"source"`
URL *string `json:"url"`
PublishedAt string `json:"published_at"`
Tags []tagJSON `json:"tags"`
FilesCount int64 `json:"files_count"`
}
func newMaterialListJSON(m models.Material, files int64) materialListJSON {
out := materialListJSON{
ID: m.ID,
CourseID: m.CourseID,
Course: newCourseRef(m.Course),
Title: m.Title,
Type: m.Type,
Source: m.Source,
URL: m.URL,
Tags: newTagsJSON(m.Tags),
FilesCount: files,
}
if m.PublishedAt != nil {
out.PublishedAt = models.FormatDateTime(*m.PublishedAt)
}
return out
}
// materialDetailJSON is a published material with its body and files.
type materialDetailJSON struct {
materialListJSON
Body *string `json:"body"`
Files []fileJSON `json:"files"`
}
// fileJSON is one attached file.
type fileJSON struct {
ID uint `json:"id"`
URL string `json:"url"`
Name string `json:"name"`
Size int64 `json:"size"`
Mime string `json:"mime"`
}
// readingsSummary is the reading progress block of /dashboard.
type readingsSummary struct {
Total int64 `json:"total"`
Read int64 `json:"read"`
RequiredTotal int64 `json:"required_total"`
RequiredRead int64 `json:"required_read"`
}