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.
263 lines
7.5 KiB
Go
263 lines
7.5 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 and owned state.
|
|
type readingJSON struct {
|
|
ID uint `json:"id"`
|
|
CourseID uint `json:"course_id"`
|
|
Course *courseRef `json:"course"`
|
|
MeetingID *uint `json:"meeting_id"`
|
|
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"`
|
|
OwnedAt *string `json:"owned_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 the caller may see, in a list.
|
|
type materialListJSON struct {
|
|
ID uint `json:"id"`
|
|
CourseID *uint `json:"course_id"`
|
|
Course *courseRef `json:"course"`
|
|
MeetingID *uint `json:"meeting_id"`
|
|
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"`
|
|
IsPrivate bool `json:"is_private"`
|
|
}
|
|
|
|
func newMaterialListJSON(m models.Material, files int64) materialListJSON {
|
|
out := materialListJSON{
|
|
ID: m.ID,
|
|
CourseID: m.CourseID,
|
|
Course: newCourseRef(m.Course),
|
|
MeetingID: m.MeetingID,
|
|
Title: m.Title,
|
|
Type: m.Type,
|
|
Source: m.Source,
|
|
URL: m.URL,
|
|
Tags: newTagsJSON(m.Tags),
|
|
FilesCount: files,
|
|
IsPrivate: m.IsPrivate,
|
|
}
|
|
if m.PublishedAt != nil {
|
|
out.PublishedAt = models.FormatDateTime(*m.PublishedAt)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// materialDetailJSON is a published material the caller may see, with its
|
|
// body and files. CanManage is true for the caller who may change its access.
|
|
type materialDetailJSON struct {
|
|
materialListJSON
|
|
Body *string `json:"body"`
|
|
Files []fileJSON `json:"files"`
|
|
CanManage bool `json:"can_manage"`
|
|
}
|
|
|
|
// 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"`
|
|
}
|