Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions apiv2/helpers/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,23 @@ func ParseBookmarkToProto(b model.Bookmark) *protobuf.Bookmark {
}
}

// ParseCourseSummaryToProto converts a course to the reduced representation used
// on list pages. It intentionally skips signed playlist URLs and download payloads,
// because those are only needed on player/detail pages.
func ParseCourseSummaryToProto(c model.Course, u *model.User) *protobuf.Course {
return parseCourseToProto(c, u, false, false)
}

// ParseCourseToProto converts a Course model to its protobuf representation.
//
// Everything derived here is derived for u: the private lectures of a course the
// caller does not administer are left out of the last recording and the next lecture,
// and the pin is the caller's own.
func ParseCourseToProto(c model.Course, u *model.User) *protobuf.Course {
return parseCourseToProto(c, u, true, true)
}

func parseCourseToProto(c model.Course, u *model.User, signPlaylists bool, includeDownloads bool) *protobuf.Course {
course := &protobuf.Course{
Id: uint32(c.ID),
Name: c.Name,
Expand All @@ -87,10 +98,10 @@ func ParseCourseToProto(c model.Course, u *model.User) *protobuf.Course {
// absent rather than sent as a stream with id 0, which every caller would then
// have to know to test for — as the Alpine start page did.
if last := c.GetLastRecording(u); last.ID != 0 {
course.LastRecording = ParseStreamToProto(*last, c, u)
course.LastRecording = parseStreamToProto(*last, c, u, signPlaylists, includeDownloads)
}
if next := c.GetNextLecture(u); next.ID != 0 {
course.NextLecture = ParseStreamToProto(*next, c, u)
course.NextLecture = parseStreamToProto(*next, c, u, signPlaylists, includeDownloads)
}

return course
Expand Down Expand Up @@ -122,9 +133,19 @@ func ParseSemesterToProto(semester model.Semester) *protobuf.Semester {

// ParseStreamToProto converts a Stream model to its protobuf representation.
func ParseStreamToProto(stream model.Stream, course model.Course, user *model.User) *protobuf.Stream {
return parseStreamToProto(stream, course, user, true, true)
}

func ParseStreamSummaryToProto(stream model.Stream, course model.Course, user *model.User) *protobuf.Stream {
return parseStreamToProto(stream, course, user, false, false)
}

func parseStreamToProto(stream model.Stream, course model.Course, user *model.User, signPlaylists bool, includeDownloads bool) *protobuf.Stream {
liveNow := stream.LiveNowTimestamp.After(time.Now())

_ = tools.SetSignedPlaylists(&stream, user, course.DownloadsEnabled)
if signPlaylists {
_ = tools.SetSignedPlaylists(&stream, user, course.DownloadsEnabled)
}

s := &protobuf.Stream{
Id: uint32(stream.ID),
Expand Down Expand Up @@ -169,7 +190,7 @@ func ParseStreamToProto(stream model.Stream, course model.Course, user *model.Us
s.Duration = uint32(duration)
}

if course.DownloadsEnabled {
if includeDownloads && course.DownloadsEnabled {
for _, download := range stream.GetVodFiles() {
s.Downloads = append(s.Downloads, ParseDownloadToProto(download))
}
Expand Down
20 changes: 19 additions & 1 deletion apiv2/server/course.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ package apiv2
import (
"context"
"errors"
"fmt"
"net/http"
"time"

"github.com/RBG-TUM/commons"
"google.golang.org/protobuf/types/known/emptypb"
Expand All @@ -14,6 +16,7 @@ import (
h "github.com/TUM-Dev/gocast/apiv2/helpers"
protobuf "github.com/TUM-Dev/gocast/apiv2/protobuf/server"
"github.com/TUM-Dev/gocast/apiv2/visibility"
"github.com/TUM-Dev/gocast/dao"
"github.com/TUM-Dev/gocast/model"
"github.com/TUM-Dev/gocast/tools/tum"
)
Expand Down Expand Up @@ -106,6 +109,13 @@ func (a *API) GetPublicCourses(ctx context.Context, req *protobuf.GetPublicCours
term = req.Term
}

if user == nil {
key := fmt.Sprintf("publicCoursesSummary-%d-%s", year, term)
if cached, ok := dao.Cache.Get(key); ok {
return &protobuf.GetPublicCoursesResponse{Courses: cached.([]*protobuf.Course)}, nil
}
}

var courses []model.Course

if user != nil {
Expand All @@ -119,7 +129,15 @@ func (a *API) GetPublicCourses(ctx context.Context, req *protobuf.GetPublicCours

resp := make([]*protobuf.Course, len(courses))
for i, course := range courses {
resp[i] = h.ParseCourseToProto(course, user)
if user == nil {
resp[i] = h.ParseCourseSummaryToProto(course, user)
} else {
resp[i] = h.ParseCourseToProto(course, user)
}
}

if user == nil {
dao.Cache.SetWithTTL(fmt.Sprintf("publicCoursesSummary-%d-%s", year, term), resp, 1, 6*time.Hour)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here I'd prefer a minute just to shield us from spikes during hot hours when large streams start.

}

return &protobuf.GetPublicCoursesResponse{Courses: resp}, nil
Expand Down
20 changes: 14 additions & 6 deletions dao/courses.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ type CoursesDaoImpl struct {
usersDao UsersDao
}

func publicCourseStreamFilter() func(*gorm.DB) *gorm.DB {
latestRecording := DB.Model(&model.Stream{}).
Select("MAX(start)").
Where("course_id = streams.course_id AND recording = ?", true)

return func(db *gorm.DB) *gorm.DB {
return db.Where("(recording = ? AND start = (?)) OR start > NOW()", true, latestRecording).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will yield all upcoming streams, no?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like this works?

// Subquery 1: Latest past recording per course
latestRecording := DB.Model(&model.Stream{}).
    Select("MAX(start)").
    Where("course_id = streams.course_id AND recording = ? AND start <= NOW()", true)

// Subquery 2: Next upcoming stream per course
nextUpcoming := DB.Model(&model.Stream{}).
    Select("MIN(start)").
    Where("course_id = streams.course_id AND start > NOW()")

// Combined Filter
func publicCourseStreamFilter() func(*gorm.DB) *gorm.DB {
    return func(db *gorm.DB) *gorm.DB {
        return db.Where("start = (?) OR start = (?)", latestRecording, nextUpcoming).
            Order("start asc")
    }
}

Order("start asc")
}
}

func NewCoursesDao() CoursesDaoImpl {
return CoursesDaoImpl{db: DB, usersDao: NewUsersDao()}
}
Expand Down Expand Up @@ -161,9 +172,8 @@ func (d CoursesDaoImpl) GetPublicCourses(year int, term string) (courses []model
}
var publicCourses []model.Course

err = DB.Preload("Streams", func(db *gorm.DB) *gorm.DB {
return db.Order("start asc")
}).Find(&publicCourses, "visibility = 'public' AND teaching_term = ? AND year = ?",
err = DB.Preload("Streams", publicCourseStreamFilter()).Find(&publicCourses,
"visibility = 'public' AND teaching_term = ? AND year = ?",
term, year).Error

if err == nil {
Expand All @@ -179,9 +189,7 @@ func (d CoursesDaoImpl) GetPublicAndLoggedInCourses(year int, term string) (cour
}
var publicCourses []model.Course

err = DB.Preload("Streams", func(db *gorm.DB) *gorm.DB {
return db.Order("start asc")
}).Find(&publicCourses,
err = DB.Preload("Streams", publicCourseStreamFilter()).Find(&publicCourses,
"(visibility = 'public' OR visibility = 'loggedin') AND teaching_term = ? AND year = ?", term, year).Error
if err == nil {
Cache.SetWithTTL(fmt.Sprintf("publicAndLoggedInCourses%d%v", year, term), publicCourses, 1, time.Minute)
Expand Down
1 change: 1 addition & 0 deletions dao/courses_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package dao

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's that file? Either add tests or delete

Loading