Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign of / page #964

Merged
merged 70 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
16013dc
Add temporary home route
MatthiasReumann Feb 28, 2023
64c21f6
minor changes
MatthiasReumann Mar 1, 2023
0b0715f
Update dropdown
MatthiasReumann Mar 1, 2023
f1671b7
Add basic structure
MatthiasReumann Mar 6, 2023
e62f8a7
Minor ui changes
MatthiasReumann Mar 6, 2023
e727cca
Add semesters api endpoint
MatthiasReumann Mar 7, 2023
fbcf576
Add current semester to api
MatthiasReumann Mar 7, 2023
5e22175
Add livestream endpoint
MatthiasReumann Mar 7, 2023
24273cb
Add users endpoint
MatthiasReumann Mar 13, 2023
5c813b5
Add my-courses view
MatthiasReumann Mar 13, 2023
1ab24c9
Introduce views in ts
MatthiasReumann Mar 13, 2023
4e9d376
Clean up fetch function
MatthiasReumann Mar 13, 2023
df4f053
Update public course api
MatthiasReumann Mar 13, 2023
9608a1f
Add live-today
MatthiasReumann Mar 13, 2023
c733215
Add recent-vod list
MatthiasReumann Mar 14, 2023
677913a
Update today and recent lecture ts
MatthiasReumann Mar 14, 2023
d152033
Add progress to start page videos
MatthiasReumann Mar 20, 2023
50f292c
Add query params
MatthiasReumann Mar 20, 2023
de39368
Add ts routing
MatthiasReumann Mar 20, 2023
1dc3807
Clean up typescript
MatthiasReumann Mar 21, 2023
df6bf80
Update grid-cols
MatthiasReumann Mar 21, 2023
59cf649
Merge endpoints
MatthiasReumann Mar 27, 2023
f9efd11
Add batch progress endpoint
MatthiasReumann Mar 27, 2023
c15dbac
Make browser undo work with js
MatthiasReumann Mar 27, 2023
63158da
Add data classes
MatthiasReumann Mar 27, 2023
b28c701
Fix undo bug
MatthiasReumann Mar 27, 2023
e1a1fef
Update Data classes
MatthiasReumann Mar 28, 2023
afd2a52
Add css classes
MatthiasReumann Mar 28, 2023
8085956
Update css
MatthiasReumann Mar 28, 2023
6f21d6e
Add view to history
MatthiasReumann Mar 28, 2023
de58daa
Test config
MatthiasReumann Mar 28, 2023
2e0f624
Merge branch 'dev' into enh/start-page
MatthiasReumann Apr 3, 2023
b5d6175
Fix un-logged-in errors
MatthiasReumann Apr 3, 2023
091529e
Remove old start page
MatthiasReumann Apr 3, 2023
43f819c
Add init.js
MatthiasReumann Apr 3, 2023
54be954
Add nothing to do
MatthiasReumann Apr 3, 2023
f4b07cd
Make notifications usable mobile
MatthiasReumann Apr 3, 2023
0781267
Add comments; Remove old index page files
MatthiasReumann Apr 3, 2023
3309929
Update ToggleableElement
MatthiasReumann Apr 4, 2023
670e8eb
Remove notifications from old header
MatthiasReumann Apr 4, 2023
f9e6246
update side navigation css
MatthiasReumann Apr 4, 2023
48e7a2b
Remove unused function
MatthiasReumann Apr 4, 2023
313e420
Update TS directory structure
MatthiasReumann Apr 4, 2023
c7a2542
Merge branch 'dev' into enh/start-page
MatthiasReumann Apr 10, 2023
e9ee6e2
Add live thumbnails
MatthiasReumann Apr 10, 2023
7395f48
Add correct navigtum urls
MatthiasReumann Apr 10, 2023
ca8e83a
Merge branch 'dev' into enh/start-page
MatthiasReumann Apr 10, 2023
32042a7
Add ts/api folder
MatthiasReumann Apr 10, 2023
bfca4f9
Add comment
MatthiasReumann Apr 18, 2023
4f2ff8a
Clean up handler
MatthiasReumann Apr 18, 2023
5992f0a
Add permissions for thumbnails
MatthiasReumann Apr 18, 2023
9126e3b
Hide scrollbars
MatthiasReumann Apr 18, 2023
bedb93e
Forgot the notification scroll bar ^
MatthiasReumann Apr 18, 2023
0183893
Format css file
MatthiasReumann Apr 19, 2023
4d3422c
Add return after error
MatthiasReumann Apr 19, 2023
a3449d2
Remove unused variables
MatthiasReumann Apr 19, 2023
75768db
Clean up init.js
MatthiasReumann Apr 19, 2023
7b0cf1d
Remove icon code
MatthiasReumann Apr 19, 2023
5788af8
Inline function
MatthiasReumann Apr 19, 2023
fdab5ea
add dtos, fix loading of courses for lecturers
joschahenningsen Apr 20, 2023
2a3d63b
Revert "Remove old start page"
joschahenningsen Apr 20, 2023
d33eda2
restore old start page
joschahenningsen Apr 20, 2023
6b33e23
use /new for new start page
joschahenningsen Apr 20, 2023
baa72cd
allow thumbnails for vod
joschahenningsen Apr 20, 2023
949af9e
use gin.File for live thumbs
joschahenningsen Apr 20, 2023
572c6a1
slightly modify video sizes and fix aspect ratio to 16/9
joschahenningsen Apr 20, 2023
05a1308
merge dev
joschahenningsen Apr 23, 2023
9653b7c
Add server notifications
MatthiasReumann Apr 24, 2023
64f3b3f
Add paginator
MatthiasReumann Apr 24, 2023
d5943b8
Add external_url to lecture hall model
MatthiasReumann Apr 24, 2023
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
8 changes: 6 additions & 2 deletions .idea/TUM-Live-Backend.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

235 changes: 235 additions & 0 deletions api/courses.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/RBG-TUM/commons"
"github.com/getsentry/sentry-go"
"github.com/gin-gonic/gin"
"github.com/joschahenningsen/TUM-Live/dao"
Expand All @@ -21,6 +22,7 @@ import (
"net/url"
"os"
"regexp"
"sort"
"strconv"
"strings"
"time"
Expand All @@ -34,6 +36,15 @@ func configGinCourseRouter(router *gin.Engine, daoWrapper dao.DaoWrapper) {

api := router.Group("/api")
{
api.GET("/courses/live", routes.getLive)
api.GET("/courses/public", routes.getPublic)
api.GET("/courses/users", routes.getUsers)
MatthiasReumann marked this conversation as resolved.
Show resolved Hide resolved

courseById := api.Group("/courses/:id")
{
courseById.GET("", routes.getCourse)
}

lecturers := api.Group("")
{
lecturers.Use(tools.AtLeastLecturer)
Expand Down Expand Up @@ -99,6 +110,230 @@ type uploadVodReq struct {
Title string `form:"title"`
}

func (r coursesRoutes) getLive(c *gin.Context) {
tumLiveContext := c.MustGet("TUMLiveContext").(tools.TUMLiveContext)

streams, err := r.GetCurrentLive(context.Background())
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
log.WithError(err).Error("could not get current live streams")
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"message": "Could not load current livestream from database."})
}

type CourseStream struct {
Course model.CourseDTO
Stream model.StreamDTO
LectureHall *model.LectureHallDTO
}

livestreams := make([]CourseStream, 0)

for _, stream := range streams {
courseForLiveStream, _ := r.GetCourseById(context.Background(), stream.CourseID)

// only show streams for logged-in users if they are logged in
if courseForLiveStream.Visibility == "loggedin" && tumLiveContext.User == nil {
continue
}
// only show "enrolled" streams to users which are enrolled or admins
if courseForLiveStream.Visibility == "enrolled" {
if !isUserAllowedToWatchPrivateCourse(courseForLiveStream, tumLiveContext.User) {
continue
}
}
// Only show hidden streams to admins
if courseForLiveStream.Visibility == "hidden" && (tumLiveContext.User == nil || tumLiveContext.User.Role != model.AdminType) {
continue
}
var lectureHall *model.LectureHall
if stream.LectureHallID != 0 {
lh, err := r.LectureHallsDao.GetLectureHallByID(stream.LectureHallID)
if err != nil {
log.WithError(err).Error(err)
} else {
lectureHall = &lh
}
}
livestreams = append(livestreams, CourseStream{
Course: courseForLiveStream.ToDTO(),
Stream: stream.ToDTO(),
LectureHall: lectureHall.ToDTO(),
})
}

c.JSON(http.StatusOK, livestreams)
}

func (r coursesRoutes) getPublic(c *gin.Context) {
tumLiveContext := c.MustGet("TUMLiveContext").(tools.TUMLiveContext)

year, term := tum.GetCurrentSemester()
year, err := strconv.Atoi(c.DefaultQuery("year", strconv.Itoa(year)))
if err != nil {
_ = c.Error(tools.RequestError{
MatthiasReumann marked this conversation as resolved.
Show resolved Hide resolved
Status: http.StatusBadRequest,
CustomMessage: "invalid year",
Err: err,
})
return
}
term = c.DefaultQuery("term", term)

var courses, public []model.Course
if tumLiveContext.User != nil {
public, err = r.GetPublicAndLoggedInCourses(year, term)
} else {
public, err = r.GetPublicCourses(year, term)
}
if err != nil {
courses = []model.Course{}
} else {
sortCourses(public)
courses = commons.Unique(public, func(c model.Course) uint { return c.ID })
}

resp := make([]model.CourseDTO, len(courses))
for i, course := range courses {
resp[i] = course.ToDTO()
}

c.JSON(http.StatusOK, resp)
}

func (r coursesRoutes) getUsers(c *gin.Context) {
tumLiveContext := c.MustGet("TUMLiveContext").(tools.TUMLiveContext)

year, term := tum.GetCurrentSemester()
year, err := strconv.Atoi(c.DefaultQuery("year", strconv.Itoa(year)))
if err != nil {
_ = c.Error(tools.RequestError{
Status: http.StatusBadRequest,
CustomMessage: "invalid year",
Err: err,
})
}
term = c.DefaultQuery("term", term)

var courses []model.Course
if tumLiveContext.User != nil {
switch tumLiveContext.User.Role {
case model.AdminType:
courses = routes.GetAllCoursesForSemester(year, term, c)
case model.LecturerType:
coursesForLecturer, err := r.GetAdministeredCoursesByUserId(c, tumLiveContext.User.ID, term, year)
if err == nil {
courses = append(courses, coursesForLecturer...)
}
default:
courses = tumLiveContext.User.CoursesForSemester(year, term, context.Background())
}
}

sortCourses(courses)
courses = commons.Unique(courses, func(c model.Course) uint { return c.ID })
resp := make([]model.CourseDTO, len(courses))
for i, course := range courses {
resp[i] = course.ToDTO()
}

c.JSON(http.StatusOK, resp)
}

func sortCourses(courses []model.Course) {
sort.Slice(courses, func(i, j int) bool {
return courses[i].CompareTo(courses[j])
})
}

func (r coursesRoutes) getCourse(c *gin.Context) {
tumLiveContext := c.MustGet("TUMLiveContext").(tools.TUMLiveContext)

type coursesByIdURI struct {
ID uint `uri:"id" binding:"required"`
}

var uri coursesByIdURI
if err := c.ShouldBindUri(&uri); err != nil {
_ = c.Error(tools.RequestError{
Err: err,
Status: http.StatusBadRequest,
CustomMessage: "invalid URI",
})
return
}

// watchedStateData is used by the client to track the which VoDs are watched.
type watchedStateData struct {
ID uint `json:"streamID"`
Month string `json:"month"`
Watched bool `json:"watched"`
Recording bool `json:"recording"`
}

type Response struct {
Course model.Course
WatchedState []watchedStateData `json:",omitempty"`
}

course, err := r.CoursesDao.GetCourseById(c, uri.ID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
_ = c.Error(tools.RequestError{
Status: http.StatusBadRequest,
CustomMessage: "can't find course",
})
} else {
sentry.CaptureException(err)
_ = c.Error(tools.RequestError{
Err: err,
Status: http.StatusInternalServerError,
CustomMessage: "can't retrieve course",
})
}
return
}

response := Response{Course: course}
if tumLiveContext.User != nil {
streamsWithWatchState, err := r.StreamsDao.GetStreamsWithWatchState(course.ID, (*tumLiveContext.User).ID)
if err != nil {
sentry.CaptureException(err)
_ = c.Error(tools.RequestError{
Err: err,
Status: http.StatusInternalServerError,
CustomMessage: "loading streamsWithWatchState and progresses for a given course and user failed",
})
}

course.Streams = streamsWithWatchState // Update the course streams to contain the watch state.

var clientWatchState = make([]watchedStateData, 0)
for _, s := range streamsWithWatchState {
clientWatchState = append(clientWatchState, watchedStateData{
ID: s.Model.ID,
Month: s.Start.Month().String(),
Watched: s.Watched,
Recording: s.Recording,
})
}

response = Response{Course: course, WatchedState: clientWatchState}
}

c.JSON(http.StatusOK, response)
}

func isUserAllowedToWatchPrivateCourse(course model.Course, user *model.User) bool {
if user != nil {
for _, c := range user.Courses {
if c.ID == course.ID {
return true
}
}
return user.IsEligibleToWatchCourse(course)
}
return false
}

func (r coursesRoutes) uploadVOD(c *gin.Context) {
log.Info("uploadVOD")
var req uploadVodReq
Expand Down
13 changes: 13 additions & 0 deletions api/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func configNotificationsRouter(r *gin.Engine, daoWrapper dao.DaoWrapper) {
notifications := r.Group("/api/notifications")
{
notifications.GET("/", routes.getNotifications)
notifications.GET("/server", routes.getServerNotifications)
notifications.POST("/", tools.Admin, routes.createNotification)
notifications.DELETE("/:id", tools.Admin, routes.deleteNotification)
}
Expand Down Expand Up @@ -52,6 +53,18 @@ func (r notificationRoutes) getNotifications(c *gin.Context) {
c.JSON(http.StatusOK, notifications)
}

func (r notificationRoutes) getServerNotifications(c *gin.Context) {
notifications, err := r.ServerNotificationDao.GetCurrentServerNotifications()
if err != nil {
_ = c.Error(tools.RequestError{
Status: http.StatusInternalServerError,
CustomMessage: "can not bind body",
Err: err,
})
}
c.JSON(http.StatusOK, notifications)
}

func (r notificationRoutes) createNotification(c *gin.Context) {
var notification model.Notification
if err := c.BindJSON(&notification); err != nil {
Expand Down
57 changes: 57 additions & 0 deletions api/progress.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package api

import (
"errors"
"github.com/getsentry/sentry-go"
"github.com/joschahenningsen/TUM-Live/dao"
"github.com/joschahenningsen/TUM-Live/model"
"github.com/joschahenningsen/TUM-Live/tools"
"gorm.io/gorm"
"net/http"
"strconv"
"sync"
"time"

Expand Down Expand Up @@ -67,6 +71,7 @@ func configProgressRouter(router *gin.Engine, daoWrapper dao.DaoWrapper) {
go progressBuff.run()
router.POST("/api/progressReport", routes.saveProgress)
router.POST("/api/watched", routes.markWatched)
router.GET("/api/progress/streams", routes.getProgressBatch)
}

// progressRoutes contains a DaoWrapper object and all route functions dangle from it.
Expand Down Expand Up @@ -169,3 +174,55 @@ func (r progressRoutes) markWatched(c *gin.Context) {
return
}
}

func (r progressRoutes) getProgressBatch(c *gin.Context) {
tumLiveContext := c.MustGet("TUMLiveContext").(tools.TUMLiveContext)

if tumLiveContext.User == nil {
_ = c.Error(tools.RequestError{
Status: http.StatusForbidden,
CustomMessage: "Not logged-in",
})
return
}

var stringIds []string
var ok bool
if stringIds, ok = c.GetQueryArray("[]ids"); !ok {
_ = c.Error(tools.RequestError{
Status: http.StatusBadRequest,
CustomMessage: "invalid query 'ids'",
})
return
}

ids := make([]uint, len(stringIds))
for i, stringId := range stringIds {
id, err := strconv.Atoi(stringId)
if err != nil {
continue
}
ids[i] = uint(id)
}

streamProgresses := make([]model.StreamProgress, len(ids))
for i, id := range ids {
p, err := r.LoadProgress(tumLiveContext.User.ID, id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
streamProgresses[i] = model.StreamProgress{StreamID: id}
} else {
sentry.CaptureException(err)
_ = c.Error(tools.RequestError{
Err: err,
Status: http.StatusInternalServerError,
CustomMessage: "can't retrieve streamProgresses for user",
})
}
} else {
streamProgresses[i] = p
}
}

c.JSON(http.StatusOK, streamProgresses)
}
1 change: 1 addition & 0 deletions api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ func ConfigGinRouter(router *gin.Engine) {
configAuditRouter(router, daoWrapper)
configGinBookmarksRouter(router, daoWrapper)
configMaintenanceRouter(router, daoWrapper)
configSemestersRouter(router, daoWrapper)
}
Loading