diff --git a/api/worker_grpc.go b/api/worker_grpc.go index dc52b3146..0487aeae0 100644 --- a/api/worker_grpc.go +++ b/api/worker_grpc.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/TUM-Dev/gocast/tools/pathprovider" "io" "net" "net/http" @@ -18,6 +17,9 @@ import ( "sync" "time" + "github.com/TUM-Dev/gocast/tools/pathprovider" + + "github.com/NaySoftware/go-fcm" go_anel_pwrctrl "github.com/RBG-TUM/go-anel-pwrctrl" "github.com/TUM-Dev/gocast/dao" "github.com/TUM-Dev/gocast/model" @@ -34,7 +36,6 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" "gorm.io/gorm" - "github.com/NaySoftware/go-fcm" ) var mutex = sync.Mutex{} @@ -44,7 +45,7 @@ var lightIndices = []int{0, 1, 2} // turn on all 3 outlets. TODO: make configura type server struct { pb.UnimplementedFromWorkerServer dao.DaoWrapper - *fcm.FcmClient + *fcm.FcmClient } func dialIn(targetWorker model.Worker) (*grpc.ClientConn, error) { @@ -117,7 +118,6 @@ func (s server) NotifySilenceResults(ctx context.Context, request *pb.SilenceRes return nil, err } return &pb.Status{Ok: true}, nil - } // SendSelfStreamRequest handles the request from a worker when a stream starts publishing via obs, etc. @@ -439,12 +439,12 @@ func (s server) NotifyUploadFinished(ctx context.Context, req *pb.UploadFinished logger.Error("Get subscribed devices:", "err", err) } else { logger.Info(fmt.Sprintf("Start sending push notifications to devices: %d", len(deviceTokens))) - + data := map[string]string{ "sum": fmt.Sprintf("%s: New VOD available!", course.Slug), - "msg": fmt.Sprintf("%s %s", stream.Name, stream.Description), + "msg": fmt.Sprintf("%s %s", stream.Name, stream.Description), } - + s.FcmClient.NewFcmRegIdsMsg(deviceTokens, data) status, err := s.FcmClient.Send() if err != nil { @@ -452,7 +452,7 @@ func (s server) NotifyUploadFinished(ctx context.Context, req *pb.UploadFinished return nil, nil } - logger.Info("Sent push notifications to devices: ", "status", fmt.Sprintf("%v", status)) + logger.Info("Sent push notifications to devices: ", "status", fmt.Sprintf("%v", status)) } } return &pb.Status{Ok: true}, nil @@ -705,7 +705,7 @@ func CreateStreamRequest(daoWrapper dao.DaoWrapper, stream model.Stream, course return } var slot model.StreamName - if sourceType == "COMB" { //try to find a transcoding slot for comb view: + if sourceType == "COMB" { // try to find a transcoding slot for comb view: slot, err = daoWrapper.IngestServerDao.GetTranscodedStreamSlot(server.ID) } if sourceType != "COMB" || err != nil { @@ -908,7 +908,7 @@ func getLivePreviewFromWorker(s *model.Stream, workerID string, client pb.ToWork return err } - if err := os.MkdirAll(pathprovider.TUMLiveTemporary, 0750); err != nil { + if err := os.MkdirAll(pathprovider.TUMLiveTemporary, 0o750); err != nil { return err } @@ -1130,7 +1130,7 @@ func ServeWorkerGRPC() { Time: time.Minute * 10, Timeout: time.Second * 20, })) - + // Check if FCM is configured (/FCMServerKey is set) fcmClient, err := tools.Cfg.GetFCMClient() if err != nil { diff --git a/api_v2/api.go b/api_v2/api.go index 3d40cc149..11aafada5 100644 --- a/api_v2/api.go +++ b/api_v2/api.go @@ -5,20 +5,21 @@ package api_v2 import ( "context" "embed" - _ "embed" + "log/slog" + "net" + "net/http" + "os" + "strings" + "time" + "github.com/TUM-Dev/gocast/api_v2/protobuf" "github.com/gin-gonic/gin" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/reflection" "gorm.io/gorm" - "log/slog" - "net" - "net/http" - "os" - "strings" - "time" ) // API is the grpc server for the v2 api @@ -62,7 +63,8 @@ func (a *API) Run(net.Listener) error { func (a *API) Proxy() func(c *gin.Context) { // setup muxing mux := runtime.NewServeMux() - opts := []grpc.DialOption{grpc.WithInsecure()} + // DEPRECATED: opts := []grpc.DialOption{grpc.WithInsecure()} + opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} err := protobuf.RegisterAPIHandlerFromEndpoint(context.Background(), mux, ":12544", opts) if err != nil { a.log.With("err", err).Error("can't register grpc handler") diff --git a/api_v2/authorization.go b/api_v2/authorization.go index fcef0d7bd..3aa413322 100644 --- a/api_v2/authorization.go +++ b/api_v2/authorization.go @@ -4,14 +4,15 @@ package api_v2 import ( "context" "errors" + "net/http" + "strings" + e "github.com/TUM-Dev/gocast/api_v2/errors" "github.com/TUM-Dev/gocast/model" "github.com/TUM-Dev/gocast/tools" "github.com/golang-jwt/jwt/v4" "google.golang.org/grpc/metadata" "gorm.io/gorm" - "net/http" - "strings" ) // getCurrentID retrieves the current user's ID from the JWT claims. @@ -87,7 +88,6 @@ func (a *API) parseJWT(jwtStr string) (*tools.JWTClaims, error) { token, err := jwt.ParseWithClaims(jwtStr, &tools.JWTClaims{}, func(token *jwt.Token) (interface{}, error) { return tools.Cfg.GetJWTKey().Public(), nil }) - if err != nil { a.log.Info("JWT parsing error: ", err) return nil, err @@ -114,6 +114,6 @@ func (a *API) getUserFromClaims(claims *tools.JWTClaims) (*model.User, error) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, e.WithStatus(http.StatusInternalServerError, err) } - + return &u, nil } diff --git a/api_v2/course.go b/api_v2/course.go index e1b417484..3e7d5996d 100644 --- a/api_v2/course.go +++ b/api_v2/course.go @@ -4,12 +4,13 @@ package api_v2 import ( "context" "errors" + "net/http" + e "github.com/TUM-Dev/gocast/api_v2/errors" h "github.com/TUM-Dev/gocast/api_v2/helpers" - s "github.com/TUM-Dev/gocast/api_v2/services" "github.com/TUM-Dev/gocast/api_v2/protobuf" + s "github.com/TUM-Dev/gocast/api_v2/services" "gorm.io/gorm" - "net/http" ) // GetPublicCourses retrieves the public courses based on the context and request. @@ -18,21 +19,21 @@ import ( func (a *API) GetPublicCourses(ctx context.Context, req *protobuf.GetPublicCoursesRequest) (*protobuf.GetPublicCoursesResponse, error) { a.log.Info("GetPublicCourses") - uID, err := a.getCurrentID(ctx) + uID, err := a.getCurrentID(ctx) if err != nil && err.Error() != "missing cookie header" { return nil, e.WithStatus(http.StatusUnauthorized, err) } - courses, err := s.FetchCourses(a.db, req, &uID) + courses, err := s.FetchCourses(a.db, req, &uID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, e.WithStatus(http.StatusInternalServerError, err) } resp := make([]*protobuf.Course, len(courses)) - for i, c := range courses { + for i, c := range courses { resp[i] = h.ParseCourseToProto(c) - } + } return &protobuf.GetPublicCoursesResponse{ Courses: resp, @@ -44,61 +45,61 @@ func (a *API) GetPublicCourses(ctx context.Context, req *protobuf.GetPublicCours func (a *API) GetSemesters(context.Context, *protobuf.GetSemestersRequest) (*protobuf.GetSemestersResponse, error) { a.log.Info("GetSemesters") semesters, err := s.FetchSemesters(a.db) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - resp := make([]*protobuf.Semester, len(semesters)) + resp := make([]*protobuf.Semester, len(semesters)) - for i, semester := range semesters { - resp[i] = h.ParseSemesterToProto(semester) - } + for i, semester := range semesters { + resp[i] = h.ParseSemesterToProto(semester) + } currentSemester, err := s.FetchCurrentSemester(a.db) - if err != nil { - return nil, err - } - - return &protobuf.GetSemestersResponse{ - Current: h.ParseSemesterToProto(currentSemester), - Semesters: resp, - }, nil + if err != nil { + return nil, err + } + + return &protobuf.GetSemestersResponse{ + Current: h.ParseSemesterToProto(currentSemester), + Semesters: resp, + }, nil } func (a *API) GetCourseStreams(ctx context.Context, req *protobuf.GetCourseStreamsRequest) (*protobuf.GetCourseStreamsResponse, error) { - a.log.Info("GetCourseStreams") + a.log.Info("GetCourseStreams") - if req.CourseID == 0 { - return nil, e.WithStatus(http.StatusBadRequest, errors.New("course id must not be empty")) - } + if req.CourseID == 0 { + return nil, e.WithStatus(http.StatusBadRequest, errors.New("course id must not be empty")) + } uID, err := a.getCurrentID(ctx) if err != nil && err.Error() != "missing cookie header" { - return nil, e.WithStatus(http.StatusUnauthorized, err) - } - - c, err := h.CheckAuthorized(a.db, uID, uint(req.CourseID)) - if err != nil { - return nil, err - } - - streams, err := s.GetStreamsByCourseID(a.db, uint(req.CourseID)) - if err != nil { - return nil, err - } - - resp := make([]*protobuf.Stream, len(streams)) - for i, stream := range streams { - if err := h.SignStream(stream, c, uID); err != nil { - return nil, err - } - - s, err := h.ParseStreamToProto(stream) - if err != nil { - return nil, err - } - resp[i] = s - } - - return &protobuf.GetCourseStreamsResponse{Streams: resp}, nil + return nil, e.WithStatus(http.StatusUnauthorized, err) + } + + c, err := h.CheckAuthorized(a.db, uID, uint(req.CourseID)) + if err != nil { + return nil, err + } + + streams, err := s.GetStreamsByCourseID(a.db, uint(req.CourseID)) + if err != nil { + return nil, err + } + + resp := make([]*protobuf.Stream, len(streams)) + for i, stream := range streams { + if err := h.SignStream(stream, c, uID); err != nil { + return nil, err + } + + s, err := h.ParseStreamToProto(stream) + if err != nil { + return nil, err + } + resp[i] = s + } + + return &protobuf.GetCourseStreamsResponse{Streams: resp}, nil } diff --git a/api_v2/errors/errors.go b/api_v2/errors/errors.go index 7d0657f62..d17130563 100644 --- a/api_v2/errors/errors.go +++ b/api_v2/errors/errors.go @@ -2,11 +2,12 @@ package errors import ( - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "fmt" "log/slog" "net/http" - "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) // WithStatus creates a new error with a specific HTTP status code and a given error message. diff --git a/api_v2/helpers/parser.go b/api_v2/helpers/parser.go index f5d320516..7a4d25ee4 100644 --- a/api_v2/helpers/parser.go +++ b/api_v2/helpers/parser.go @@ -2,10 +2,12 @@ package helpers import ( + "time" + "github.com/TUM-Dev/gocast/api_v2/protobuf" "github.com/TUM-Dev/gocast/dao" "github.com/TUM-Dev/gocast/model" - "github.com/golang/protobuf/ptypes" + "google.golang.org/protobuf/types/known/timestamppb" ) // ParseUserToProto converts a User model to its protobuf representation. @@ -109,28 +111,15 @@ func ParseSemesterToProto(semester dao.Semester) *protobuf.Semester { // ParseStreamToProto converts a Stream model to its protobuf representation. // It returns an error if the conversion of timestamps fails. func ParseStreamToProto(stream *model.Stream) (*protobuf.Stream, error) { - start, err := ptypes.TimestampProto(stream.Start) - if err != nil { - return nil, err - } + liveNow := stream.LiveNowTimestamp.After(time.Now()) - end, err := ptypes.TimestampProto(stream.End) - if err != nil { - return nil, err - } - - liveNowTimestamp, err := ptypes.TimestampProto(stream.LiveNowTimestamp) - if err != nil { - return nil, err - } - - s, err := &protobuf.Stream{ + s := &protobuf.Stream{ Id: uint64(stream.ID), Name: stream.Name, Description: stream.Description, CourseID: uint32(stream.CourseID), - Start: start, - End: end, + Start: timestamppb.New(stream.Start), + End: timestamppb.New(stream.End), ChatEnabled: stream.ChatEnabled, RoomName: stream.RoomName, RoomCode: stream.RoomCode, @@ -140,31 +129,29 @@ func ParseStreamToProto(stream *model.Stream) (*protobuf.Stream, error) { PlaylistUrl: stream.PlaylistUrl, PlaylistUrlPRES: stream.PlaylistUrlPRES, PlaylistUrlCAM: stream.PlaylistUrlCAM, - LiveNow: stream.LiveNow, - LiveNowTimestamp: liveNowTimestamp, + LiveNow: liveNow, + LiveNowTimestamp: timestamppb.New(stream.LiveNowTimestamp), Recording: stream.Recording, Premiere: stream.Premiere, Ended: stream.Ended, VodViews: uint32(stream.VodViews), StartOffset: uint32(stream.StartOffset), EndOffset: uint32(stream.EndOffset), - }, nil + } if stream.Duration.Valid { - s.Duration = int32(stream.Duration.Int32) + s.Duration = stream.Duration.Int32 } - return s, err + return s, nil } // Parse Progress To Proto func ParseProgressToProto(progress *model.StreamProgress) *protobuf.Progress { - return &protobuf.Progress{ Progress: float32(progress.Progress), Watched: progress.Watched, StreamID: uint32(progress.StreamID), UserID: uint32(progress.UserID), } - } diff --git a/api_v2/helpers/signer.go b/api_v2/helpers/signer.go index 5ba22c739..63c4df448 100644 --- a/api_v2/helpers/signer.go +++ b/api_v2/helpers/signer.go @@ -2,20 +2,20 @@ package helpers import ( - "github.com/TUM-Dev/gocast/model" - "github.com/TUM-Dev/gocast/tools" - e "github.com/TUM-Dev/gocast/api_v2/errors" - "net/http" "errors" + "net/http" + + e "github.com/TUM-Dev/gocast/api_v2/errors" + "github.com/TUM-Dev/gocast/model" + "github.com/TUM-Dev/gocast/tools" "gorm.io/gorm" ) func SignStream(s *model.Stream, c *model.Course, uID uint) error { - if err := tools.SetSignedPlaylists(s, &model.User{ + if err := tools.SetSignedPlaylists(s, &model.User{ Model: gorm.Model{ID: uID}, }, c.DownloadsEnabled); err != nil { return e.WithStatus(http.StatusInternalServerError, errors.New("can't sign stream")) - } else { - return nil } + return nil } diff --git a/api_v2/helpers/verification.go b/api_v2/helpers/verification.go index 4a5f2b55f..9df2e31f0 100644 --- a/api_v2/helpers/verification.go +++ b/api_v2/helpers/verification.go @@ -2,52 +2,54 @@ package helpers import ( - "gorm.io/gorm" - "errors" + "errors" "net/http" + e "github.com/TUM-Dev/gocast/api_v2/errors" s "github.com/TUM-Dev/gocast/api_v2/services" - "github.com/TUM-Dev/gocast/model" + "github.com/TUM-Dev/gocast/model" + "gorm.io/gorm" ) + // Verification to check if user is authorized to access course/stream func CheckAuthorized(db *gorm.DB, uID uint, courseID uint) (*model.Course, error) { - c, err := s.GetCourseById(db, courseID) - if err != nil { - return nil, err - } + c, err := s.GetCourseById(db, courseID) + if err != nil { + return nil, err + } - switch c.Visibility { - case "public": - return c, nil - case "private": - return nil, e.WithStatus(http.StatusForbidden, errors.New("course is private")) - case "hidden": - return nil, e.WithStatus(http.StatusForbidden, errors.New("course is hidden")) - case "loggedin": - if uID == 0 { + switch c.Visibility { + case "public": + return c, nil + case "private": + return nil, e.WithStatus(http.StatusForbidden, errors.New("course is private")) + case "hidden": + return nil, e.WithStatus(http.StatusForbidden, errors.New("course is hidden")) + case "loggedin": + if uID == 0 { return nil, e.WithStatus(http.StatusForbidden, errors.New("course is only accessible by logged in users")) } else { return c, nil } - case "enrolled": - return checkUserEnrolled(db, uID, c) - default: - return nil, e.WithStatus(http.StatusForbidden, errors.New("course is not accessible")) - } + case "enrolled": + return checkUserEnrolled(db, uID, c) + default: + return nil, e.WithStatus(http.StatusForbidden, errors.New("course is not accessible")) + } } func checkUserEnrolled(db *gorm.DB, uID uint, c *model.Course) (*model.Course, error) { - if uID == 0 { - return nil, e.WithStatus(http.StatusForbidden, errors.New("course can only be accessed by enrolled users")) - } + if uID == 0 { + return nil, e.WithStatus(http.StatusForbidden, errors.New("course can only be accessed by enrolled users")) + } - var count int64 - if err := db.Table("course_users").Where("user_id = ? AND course_id = ?", uID, c.ID).Count(&count).Error; err != nil { - return nil, e.WithStatus(http.StatusInternalServerError, err) - } + var count int64 + if err := db.Table("course_users").Where("user_id = ? AND course_id = ?", uID, c.ID).Count(&count).Error; err != nil { + return nil, e.WithStatus(http.StatusInternalServerError, err) + } - if count == 0 { - return nil, e.WithStatus(http.StatusForbidden, errors.New("user is not enrolled in this course and the course can only be accessed by enrolled users")) - } - return c, nil + if count == 0 { + return nil, e.WithStatus(http.StatusForbidden, errors.New("user is not enrolled in this course and the course can only be accessed by enrolled users")) + } + return c, nil } diff --git a/api_v2/notifications.go b/api_v2/notifications.go index c6bd409c1..ab3af2321 100644 --- a/api_v2/notifications.go +++ b/api_v2/notifications.go @@ -5,6 +5,7 @@ import ( "context" "errors" "net/http" + e "github.com/TUM-Dev/gocast/api_v2/errors" h "github.com/TUM-Dev/gocast/api_v2/helpers" "github.com/TUM-Dev/gocast/api_v2/protobuf" @@ -26,7 +27,6 @@ func (a *API) GetBannerAlerts(ctx context.Context, req *protobuf.GetBannerAlerts return &protobuf.GetBannerAlertsResponse{ BannerAlerts: resp, }, nil - } func (a *API) GetFeatureNotifications(ctx context.Context, req *protobuf.GetFeatureNotificationsRequest) (*protobuf.GetFeatureNotificationsResponse, error) { @@ -37,7 +37,6 @@ func (a *API) GetFeatureNotifications(ctx context.Context, req *protobuf.GetFeat } notifications, err := s.FetchUserNotifications(a.db, u) - if err != nil { return nil, e.WithStatus(http.StatusInternalServerError, err) } @@ -55,18 +54,18 @@ func (a *API) GetFeatureNotifications(ctx context.Context, req *protobuf.GetFeat func (a *API) PostDeviceToken(ctx context.Context, req *protobuf.PostDeviceTokenRequest) (*protobuf.PostDeviceTokenResponse, error) { a.log.Info("PostDeviceToken") - + if req.DeviceToken == "" { - return nil, e.WithStatus(http.StatusBadRequest, errors.New("device_token must not be empty")) - } - + return nil, e.WithStatus(http.StatusBadRequest, errors.New("device_token must not be empty")) + } + u, err := a.getCurrent(ctx) if err != nil { return nil, e.WithStatus(http.StatusUnauthorized, err) } if err = s.PostDeviceToken(a.db, *u, req.DeviceToken); err != nil { - return nil, err + return nil, err } return &protobuf.PostDeviceTokenResponse{}, nil @@ -74,18 +73,18 @@ func (a *API) PostDeviceToken(ctx context.Context, req *protobuf.PostDeviceToken func (a *API) DeleteDeviceToken(ctx context.Context, req *protobuf.DeleteDeviceTokenRequest) (*protobuf.DeleteDeviceTokenResponse, error) { a.log.Info("DeleteDeviceToken") - + if req.DeviceToken == "" { - return nil, e.WithStatus(http.StatusBadRequest, errors.New("device_token must not be empty")) - } - + return nil, e.WithStatus(http.StatusBadRequest, errors.New("device_token must not be empty")) + } + uID, err := a.getCurrentID(ctx) if err != nil { return nil, e.WithStatus(http.StatusUnauthorized, err) } if err = s.DeleteDeviceToken(a.db, uID, req.DeviceToken); err != nil { - return nil, err + return nil, err } return &protobuf.DeleteDeviceTokenResponse{}, nil diff --git a/api_v2/services/course_service.go b/api_v2/services/course_service.go index f1413228c..4b4caf588 100644 --- a/api_v2/services/course_service.go +++ b/api_v2/services/course_service.go @@ -3,24 +3,25 @@ package services import ( "errors" - "github.com/TUM-Dev/gocast/api_v2/protobuf" - "github.com/TUM-Dev/gocast/model" - "github.com/TUM-Dev/gocast/dao" "net/http" + "time" + e "github.com/TUM-Dev/gocast/api_v2/errors" + "github.com/TUM-Dev/gocast/api_v2/protobuf" + "github.com/TUM-Dev/gocast/dao" + "github.com/TUM-Dev/gocast/model" "gorm.io/gorm" - "time" ) // GetCourseById fetches a course from the database based on the provided id. func GetCourseById(db *gorm.DB, id uint) (*model.Course, error) { - c := &model.Course{} - if err := db.Where("id = ?", id).First(c).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound){ - return nil, e.WithStatus(http.StatusInternalServerError, err) + c := &model.Course{} + if err := db.Where("id = ?", id).First(c).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, e.WithStatus(http.StatusInternalServerError, err) } else if errors.Is(err, gorm.ErrRecordNotFound) { return nil, e.WithStatus(http.StatusNotFound, errors.New("course not found")) } - + return c, nil } @@ -58,10 +59,10 @@ func FetchCourses(db *gorm.DB, req *protobuf.GetPublicCoursesRequest, uID *uint) // FetchSemesters fetches all unique semesters from the database. // It returns a slice of Semester models or an error if one occurs. func FetchSemesters(db *gorm.DB) ([]dao.Semester, error) { - var semesters []dao.Semester - err := db.Raw("SELECT year, teaching_term from courses " + - "group by year, teaching_term " + - "order by year desc, teaching_term desc").Scan(&semesters).Error + var semesters []dao.Semester + err := db.Raw("SELECT year, teaching_term from courses " + + "group by year, teaching_term " + + "order by year desc, teaching_term desc").Scan(&semesters).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err } @@ -71,21 +72,21 @@ func FetchSemesters(db *gorm.DB) ([]dao.Semester, error) { // FetchCurrentSemester determines the current semester based on the current date. // It returns the current Semester model or an error if one occurs. func FetchCurrentSemester(db *gorm.DB) (dao.Semester, error) { - var curTerm string - var curYear int - if time.Now().Month() >= 4 && time.Now().Month() < 10 { - curTerm = "S" - curYear = time.Now().Year() - } else { - curTerm = "W" - if time.Now().Month() >= 10 { - curYear = time.Now().Year() - } else { - curYear = time.Now().Year() - 1 - } - } + var curTerm string + var curYear int + if time.Now().Month() >= 4 && time.Now().Month() < 10 { + curTerm = "S" + curYear = time.Now().Year() + } else { + curTerm = "W" + if time.Now().Month() >= 10 { + curYear = time.Now().Year() + } else { + curYear = time.Now().Year() - 1 + } + } return dao.Semester{ - Year: curYear, + Year: curYear, TeachingTerm: curTerm, }, nil } diff --git a/api_v2/services/notification_service.go b/api_v2/services/notification_service.go index a53430eb1..3c77f329c 100644 --- a/api_v2/services/notification_service.go +++ b/api_v2/services/notification_service.go @@ -4,6 +4,7 @@ package services import ( "errors" "net/http" + e "github.com/TUM-Dev/gocast/api_v2/errors" "github.com/TUM-Dev/gocast/model" "gorm.io/gorm" @@ -63,18 +64,18 @@ func getTargetFilter(user model.User) (targetFilter string) { func PostDeviceToken(db *gorm.DB, u model.User, deviceToken string) (err error) { device := model.Device{ - User: u, + User: u, DeviceToken: deviceToken, } var count int64 - if err := db.Table("devices").Where("user_id = ? AND device_token = ?", u.ID, deviceToken).Count(&count).Error; err != nil { - return e.WithStatus(http.StatusInternalServerError, err) - } + if err := db.Table("devices").Where("user_id = ? AND device_token = ?", u.ID, deviceToken).Count(&count).Error; err != nil { + return e.WithStatus(http.StatusInternalServerError, err) + } print("Count = ?", count) - if count != 0 { + if count != 0 { return e.WithStatus(http.StatusConflict, errors.New("device is already registered")) - } + } if err = db.Create(&device).Error; err != nil { return e.WithStatus(http.StatusInternalServerError, err) diff --git a/api_v2/services/stream_service.go b/api_v2/services/stream_service.go index 3cd6da0b8..02a792dec 100644 --- a/api_v2/services/stream_service.go +++ b/api_v2/services/stream_service.go @@ -88,14 +88,15 @@ func SetProgress(db *gorm.DB, streamID uint, userID uint, progress float64) (*mo result := db.Where("stream_id = ? AND user_id = ?", streamID, userID).First(p) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + switch { + case errors.Is(result.Error, gorm.ErrRecordNotFound): p.StreamID = streamID p.UserID = userID p.Progress = progress p.Watched = progress == 1 - } else if result.Error != nil { + case result.Error != nil: return nil, e.WithStatus(http.StatusInternalServerError, result.Error) - } else { + default: p.Progress = progress p.Watched = progress == 1 } @@ -108,9 +109,7 @@ func SetProgress(db *gorm.DB, streamID uint, userID uint, progress float64) (*mo } func MarkAsWatched(db *gorm.DB, streamID uint, userID uint) (*model.StreamProgress, error) { - _, err := GetStreamByID(db, streamID) - if err != nil { return nil, err } @@ -119,14 +118,15 @@ func MarkAsWatched(db *gorm.DB, streamID uint, userID uint) (*model.StreamProgre result := db.Where("stream_id = ? AND user_id = ?", streamID, userID).First(p) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + switch { + case errors.Is(result.Error, gorm.ErrRecordNotFound): p.StreamID = streamID p.UserID = userID p.Progress = 1 p.Watched = true - } else if result.Error != nil { + case result.Error != nil: return nil, e.WithStatus(http.StatusInternalServerError, result.Error) - } else { + default: p.Progress = 1 p.Watched = true } diff --git a/api_v2/services/user_service.go b/api_v2/services/user_service.go index 6dc1127d5..08faf9d81 100644 --- a/api_v2/services/user_service.go +++ b/api_v2/services/user_service.go @@ -11,26 +11,24 @@ import ( "gorm.io/gorm" ) -// FetchUserCourses fetches the courses for a user from the database. -// It filters the courses by year, term, query, limit, and skip if they are specified in the request. -// It returns a slice of Course models or an error if one occurs. -func FetchUserCourses(db *gorm.DB, uID uint, req *protobuf.GetUserCoursesRequest) (courses []model.Course, err error) { - query := db.Unscoped().Table("course_users"). - Joins("join courses on course_users.course_id = courses.id"). +// Helper method to fetch courses for a given table (e.g. course_users or pinned_courses) +func fetchCourses(db *gorm.DB, uID uint, year uint, term string, limit int, skip int, tableName string) (courses []model.Course, err error) { + query := db.Unscoped().Table(tableName). + Joins("join courses on "+tableName+".course_id = courses.id"). Select("courses.*"). - Where("course_users.user_id = ?", uID) + Where(tableName+".user_id = ?", uID) - if req.Year != 0 { - query = query.Where("courses.year = ?", req.Year) + if year != 0 { + query = query.Where("courses.year = ?", year) } - if req.Term != "" { - query = query.Where("courses.teaching_term = ?", req.Term) + if term != "" { + query = query.Where("courses.teaching_term = ?", term) } - if req.Limit > 0 { - query = query.Limit(int(req.Limit)) + if limit > 0 { + query = query.Limit(limit) } - if req.Skip >= 0 { - query = query.Offset(int(req.Skip)) + if skip >= 0 { + query = query.Offset(skip) } err = query.Find(&courses).Error @@ -41,34 +39,18 @@ func FetchUserCourses(db *gorm.DB, uID uint, req *protobuf.GetUserCoursesRequest return courses, nil } +// FetchUserCourses fetches the courses for a user from the database. +// It filters the courses by year, term, query, limit, and skip if they are specified in the request. +// It returns a slice of Course models or an error if one occurs. +func FetchUserCourses(db *gorm.DB, uID uint, req *protobuf.GetUserCoursesRequest) (courses []model.Course, err error) { + return fetchCourses(db, uID, uint(req.Year), req.Term, int(req.Limit), int(req.Skip), "course_users") +} + // FetchUserPinnedCourses fetches the pinned courses for a user from the database. // It filters the courses by year, term, limit, and skip if they are specified in the request. // It returns a slice of Course models or an error if one occurs. func FetchUserPinnedCourses(db *gorm.DB, uID uint, req *protobuf.GetUserPinnedRequest) (courses []model.Course, err error) { - query := db.Unscoped().Table("pinned_courses"). - Joins("join courses on pinned_courses.course_id = courses.id"). - Select("courses.*"). - Where("pinned_courses.user_id = ?", uID) - - if req.Year != 0 { - query = query.Where("courses.year = ?", req.Year) - } - if req.Term != "" { - query = query.Where("courses.teaching_term = ?", req.Term) - } - if req.Limit > 0 { - query = query.Limit(int(req.Limit)) - } - if req.Skip >= 0 { - query = query.Offset(int(req.Skip)) - } - - err = query.Find(&courses).Error - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, err - } - - return courses, nil + return fetchCourses(db, uID, uint(req.Year), req.Term, int(req.Limit), int(req.Skip), "pinned_courses") } // FetchUserAdminCourses fetches the courses where a user is an admin from the database. @@ -174,7 +156,6 @@ func PutUserBookmark(db *gorm.DB, uID uint, req *protobuf.PutBookmarkRequest) (b } func PatchUserBookmark(db *gorm.DB, uID uint, req *protobuf.PatchBookmarkRequest) (bookmark *model.Bookmark, err error) { - // check if bookmark exists otherwise cannot patch if err = db.Where("id = ?", req.BookmarkID).First(&bookmark).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, e.WithStatus(http.StatusInternalServerError, err) @@ -201,7 +182,6 @@ func PatchUserBookmark(db *gorm.DB, uID uint, req *protobuf.PatchBookmarkRequest } func DeleteUserBookmark(db *gorm.DB, uID uint, req *protobuf.DeleteBookmarkRequest) (err error) { - // check if bookmark exists otherwise cannot delete var bookmark model.Bookmark if err = db.Where("id = ?", req.BookmarkID).First(&bookmark).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { @@ -238,7 +218,7 @@ func DeleteUserPinned(db *gorm.DB, u *model.User, courseID uint) (err error) { } // Pin course - if pinCourse(db, false, u, course); err != nil { + if err = pinCourse(db, false, u, course); err != nil { return e.WithStatus(http.StatusInternalServerError, err) } @@ -254,7 +234,7 @@ func PostUserPinned(db *gorm.DB, u *model.User, c *model.Course) (err error) { } // Pin course - if pinCourse(db, true, u, c); err != nil { + if err = pinCourse(db, true, u, c); err != nil { return e.WithStatus(http.StatusInternalServerError, err) } @@ -278,21 +258,6 @@ func checkPinnedByID(db *gorm.DB, uID uint, courseID uint) (bool, error) { func pinCourse(db *gorm.DB, pin bool, u *model.User, c *model.Course) error { if pin { return db.Model(u).Association("PinnedCourses").Append(c) - } else { - return db.Model(u).Association("PinnedCourses").Delete(c) } + return db.Model(u).Association("PinnedCourses").Delete(c) } - -// const ( -// TargetAll = iota + 1 //TargetAll Is any user, regardless if logged in or not -// TargetUser //TargetUser Are all users that are logged in -// TargetStudent //TargetStudent Are all users that are logged in and are students -// TargetLecturer //TargetLecturer Are all users that are logged in and are lecturers -// TargetAdmin //TargetAdmin Are all users that are logged in and are admins - -// ) - -// 1 = admin -// 2 = Lecturer -// 3 = geneeric -// 4 = student diff --git a/api_v2/stream.go b/api_v2/stream.go index ca9977a47..aecbabaab 100644 --- a/api_v2/stream.go +++ b/api_v2/stream.go @@ -142,7 +142,6 @@ func (a *API) GetProgress(ctx context.Context, req *protobuf.GetProgressRequest) } p, err := s.GetProgress(a.db, uint(req.StreamID), uID) - if err != nil { return nil, err } @@ -156,7 +155,6 @@ func (a *API) PutProgress(ctx context.Context, req *protobuf.PutProgressRequest) a.log.Info("SetStreamProgress") uID, err := a.getCurrentID(ctx) - if err != nil { return nil, e.WithStatus(http.StatusUnauthorized, err) } diff --git a/api_v2/user.go b/api_v2/user.go index bfe26b052..8b56f4d5b 100644 --- a/api_v2/user.go +++ b/api_v2/user.go @@ -116,7 +116,6 @@ func (a *API) GetUserBookmarks(ctx context.Context, req *protobuf.GetBookmarksRe } bookmarks, err := s.FetchUserBookmarks(a.db, uID, req) - if err != nil { return nil, e.WithStatus(http.StatusInternalServerError, err) } @@ -148,7 +147,6 @@ func (a *API) PutUserBookmark(ctx context.Context, req *protobuf.PutBookmarkRequ return &protobuf.PutBookmarkResponse{ Bookmark: h.ParseBookmarkToProto(*bookmark), }, nil - } func (a *API) PatchUserBookmark(ctx context.Context, req *protobuf.PatchBookmarkRequest) (*protobuf.PatchBookmarkResponse, error) { @@ -217,7 +215,6 @@ func (a *API) PatchUserSettings(ctx context.Context, req *protobuf.PatchUserSett } settings, err := s.PatchUserSettings(a.db, u, req) - if err != nil { return nil, err } @@ -231,7 +228,6 @@ func (a *API) PatchUserSettings(ctx context.Context, req *protobuf.PatchUserSett return &protobuf.PatchUserSettingsResponse{ UserSettings: resp, }, nil - } func (a *API) PostUserPinned(ctx context.Context, req *protobuf.PostPinnedRequest) (*protobuf.PostPinnedResponse, error) { @@ -246,7 +242,7 @@ func (a *API) PostUserPinned(ctx context.Context, req *protobuf.PostPinnedReques return nil, e.WithStatus(http.StatusUnauthorized, err) } - c, err := h.CheckAuthorized(a.db, uint(u.ID), uint(req.CourseID)) + c, err := h.CheckAuthorized(a.db, u.ID, uint(req.CourseID)) if err != nil { return nil, err } diff --git a/cmd/tumlive/tumlive.go b/cmd/tumlive/tumlive.go index 383b21bd2..60107f3f9 100755 --- a/cmd/tumlive/tumlive.go +++ b/cmd/tumlive/tumlive.go @@ -3,9 +3,9 @@ package main import ( "fmt" "log/slog" + "net" "net/http" _ "net/http/pprof" - "net" "os" "os/signal" "syscall" @@ -96,7 +96,7 @@ func GinServer() (err error) { api.ConfigGinRouter(router) web.ConfigGinRouter(router) err = router.RunListener(l) - //err = router.RunTLS(":443", tools.Cfg.Saml.Cert, tools.Cfg.Saml.Privkey) + // err = router.RunTLS(":443", tools.Cfg.Saml.Cert, tools.Cfg.Saml.Privkey) if err != nil { sentry.CaptureException(err) logger.Error("Error starting tumlive", "err", err) @@ -104,9 +104,7 @@ func GinServer() (err error) { return } -var ( - osSignal chan os.Signal -) +var osSignal chan os.Signal func main() { initAll(initializers) @@ -156,7 +154,6 @@ func main() { PrepareStmt: true, Logger: gormJSONLogger, }) - if err != nil { sentry.CaptureException(err) sentry.Flush(time.Second * 5) @@ -253,13 +250,13 @@ func main() { func initCron() { daoWrapper := dao.NewDaoWrapper() tools.InitCronService() - //Fetch students every 12 hours + // Fetch students every 12 hours _ = tools.Cron.AddFunc("fetchCourses", tum.FetchCourses(daoWrapper), "0 */12 * * *") - //Collect livestream stats (viewers) every minute + // Collect livestream stats (viewers) every minute _ = tools.Cron.AddFunc("collectStats", api.CollectStats(daoWrapper), "0-59 * * * *") - //Flush stale sentry exceptions and transactions every 5 minutes + // Flush stale sentry exceptions and transactions every 5 minutes _ = tools.Cron.AddFunc("sentryFlush", func() { sentry.Flush(time.Minute * 2) }, "0-59/5 * * * *") - //Look for due streams and notify workers about them + // Look for due streams and notify workers about them _ = tools.Cron.AddFunc("triggerDueStreams", api.NotifyWorkers(daoWrapper), "0-59 * * * *") // update courses available _ = tools.Cron.AddFunc("prefetchCourses", tum.PrefetchCourses(daoWrapper), "30 3 * * *") diff --git a/dao/courses.go b/dao/courses.go index 2301d93ed..ec9f7a9d2 100644 --- a/dao/courses.go +++ b/dao/courses.go @@ -324,19 +324,18 @@ func (d coursesDao) GetSubscribedDevices(streamID uint) ([]string, error) { logger.Info("Start finding device tokens for stream", "streamID", fmt.Sprintf("%d", streamID)) var deviceTokens []string err := DB.Table("devices"). - Select("devices.device_token"). - Distinct(). - Joins("JOIN course_users ON devices.user_id = course_users.user_id"). - Joins("JOIN streams ON course_users.course_id = streams.course_id"). - Where("streams.id = ?", streamID). - Pluck("device_token", &deviceTokens).Error - - if err != nil { + Select("devices.device_token"). + Distinct(). + Joins("JOIN course_users ON devices.user_id = course_users.user_id"). + Joins("JOIN streams ON course_users.course_id = streams.course_id"). + Where("streams.id = ?", streamID). + Pluck("device_token", &deviceTokens).Error + if err != nil { logger.Error("Can't find device tokens", "err", err) - return nil, err - } + return nil, err + } - return deviceTokens, nil + return deviceTokens, nil } type Semester struct { diff --git a/model/user.go b/model/user.go index e3da96970..2d8a80bea 100755 --- a/model/user.go +++ b/model/user.go @@ -63,7 +63,7 @@ type UserSetting struct { UserID uint `gorm:"not null"` Type UserSettingType `gorm:"not null"` - Value string `gorm:"not null"` //json encoded setting + Value string `gorm:"not null"` // json encoded setting } // GetPreferredName returns the preferred name of the user if set, otherwise the firstName from TUMOnline @@ -207,7 +207,7 @@ func (u *User) IsEligibleToWatchCourse(course Course) bool { } func (u *User) CoursesForSemester(year int, term string, context context.Context) []Course { - var cMap = make(map[uint]Course) + cMap := make(map[uint]Course) for _, c := range u.Courses { if c.Year == year && c.TeachingTerm == term { cMap[c.ID] = c @@ -368,7 +368,7 @@ func (u *User) BeforeCreate(tx *gorm.DB) (err error) { // Device represents all device tokens for a given user that is subscribed to push notifications type Device struct { gorm.Model - UserID uint // used by gorm - User User `gorm:"foreignKey:user_id;not null;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` // creator of the token - DeviceToken string `gorm:"type:varchar(256); not null"` + UserID uint // used by gorm + User User `gorm:"foreignKey:user_id;not null;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` // creator of the token + DeviceToken string `gorm:"type:varchar(256); not null"` } diff --git a/tools/config.go b/tools/config.go index 169313a41..c0d505530 100644 --- a/tools/config.go +++ b/tools/config.go @@ -7,16 +7,19 @@ import ( "encoding/pem" "errors" "fmt" - "github.com/meilisearch/meilisearch-go" - uuid "github.com/satori/go.uuid" - "github.com/spf13/viper" "os" "time" + "github.com/NaySoftware/go-fcm" + "github.com/meilisearch/meilisearch-go" + uuid "github.com/satori/go.uuid" + "github.com/spf13/viper" ) -var Cfg Config -var Loc *time.Location +var ( + Cfg Config + Loc *time.Location +) func LoadConfig() { initCache() @@ -192,8 +195,6 @@ func (c Config) GetMeiliClient() (*meilisearch.Client, error) { return meilisearch.NewClient(meilisearch.ClientConfig{Host: c.Meili.Host, APIKey: c.Meili.ApiKey}), nil } - -// FCM used for push notifications to mobile devices var ErrFCMNotConfigured = errors.New("Firebase Cloud Messaging is not configured") func (c Config) GetFCMClient() (*fcm.FcmClient, error) {