websocket events for live editing
parent
4948c72882
commit
63c8702899
4
go.mod
4
go.mod
|
@ -4,7 +4,9 @@ go 1.21.0
|
|||
|
||||
require (
|
||||
git.ex.umbach.dev/Alex/roese-utils v1.0.21
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.6
|
||||
github.com/gofiber/fiber/v2 v2.52.5
|
||||
github.com/gofiber/websocket/v2 v2.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/rs/zerolog v1.31.0
|
||||
|
@ -14,7 +16,6 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.6 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/fasthttp/websocket v1.5.3 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
|
@ -22,7 +23,6 @@ require (
|
|||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.15.5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/gofiber/websocket/v2 v2.2.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
|
|
6
go.sum
6
go.sum
|
@ -1,11 +1,5 @@
|
|||
git.ex.umbach.dev/Alex/roese-utils v1.0.21 h1:ae1AHQh8UHJVLbpk5Gf4S5IP0EEWGD1JFIeX9cIcYcc=
|
||||
git.ex.umbach.dev/Alex/roese-utils v1.0.21/go.mod h1:hFcnKQl6nuGFEMCxK/eVQBUD6ixBFlAaiy4E2aQqUL8=
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.0 h1:0vhIxeFNHdo4ftVHEGxZ35Mf0jkRuSs9QkfelWFDySc=
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.0/go.mod h1:BvbMJWYQ83dblDAB4ooLfwuorjdy6G3txdOrjRfIKPA=
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.1 h1:J9sRarL6OJ4JOaE8UH1yykOYdjq5H6TehMDq86269iA=
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.1/go.mod h1:BvbMJWYQ83dblDAB4ooLfwuorjdy6G3txdOrjRfIKPA=
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.2 h1:RvE0e+Eja/9HOU5reEG2UU18mgDgKYD8gXJOrOntRmc=
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.2/go.mod h1:BvbMJWYQ83dblDAB4ooLfwuorjdy6G3txdOrjRfIKPA=
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.6 h1:Af+2jD4aC3+4qMgSmTn4nvRosAS5ETTX9JR3gznlGPI=
|
||||
git.ex.umbach.dev/LMS/libcore v1.0.6/go.mod h1:BvbMJWYQ83dblDAB4ooLfwuorjdy6G3txdOrjRfIKPA=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
|
|
|
@ -80,13 +80,22 @@ var Roles = map[string][]uint16{
|
|||
|
||||
// commands sent to websocket clients
|
||||
const (
|
||||
SendCmdSettingsUpdated = 1
|
||||
SendCmdSettingsUpdatedLogo = 2
|
||||
SendCmdSettingsUpdatedBanner = 3
|
||||
SendCmdSettingsUpdatedSubdomain = 4
|
||||
SendCmdTeamAddedMember = 5
|
||||
SendCmdTeamUpdatedMemberRole = 6
|
||||
SendCmdTeamDeletedMember = 7
|
||||
SendCmdSettingsUpdated = 1
|
||||
SendCmdSettingsUpdatedLogo = 2
|
||||
SendCmdSettingsUpdatedBanner = 3
|
||||
SendCmdSettingsUpdatedSubdomain = 4
|
||||
SendCmdTeamAddedMember = 5
|
||||
SendCmdTeamUpdatedMemberRole = 6
|
||||
SendCmdTeamDeletedMember = 7
|
||||
SendCmdLessonCreated = 8
|
||||
SendCmdLessonPreviewTitleUpdated = 9
|
||||
SendCmdLessonPreviewThumbnailUpdated = 10
|
||||
SendCmdLessonStateUpdated = 11
|
||||
SendCmdLessonAddedContent = 12
|
||||
SendCmdLessonDeletedContent = 13
|
||||
SendCmdLessonContentUpdated = 14
|
||||
SendCmdLessonContentUpdatedPosition = 15
|
||||
SendCmdLessonContentFileUpdated = 16
|
||||
)
|
||||
|
||||
// commands received from websocket clients
|
||||
|
@ -101,3 +110,11 @@ const (
|
|||
SubscribedTopicSettings = "/settings"
|
||||
SubscribedTopicAccount = "/account"
|
||||
)
|
||||
|
||||
func SubscribedTopicLessonsId(organizationId string) string {
|
||||
return SubscribedTopicLessons + "/" + organizationId
|
||||
}
|
||||
|
||||
func SubscribedTopicLessonsEditorId(organizationId string) string {
|
||||
return SubscribedTopicLessons + "/" + organizationId + "/editor"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package lessons
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
"lms.de/backend/modules/database"
|
||||
"lms.de/backend/modules/structs"
|
||||
"lms.de/backend/modules/utils"
|
||||
"lms.de/backend/socketclients"
|
||||
)
|
||||
|
||||
func GetLessons(c *fiber.Ctx) error {
|
||||
|
@ -68,6 +70,16 @@ func CreateLesson(c *fiber.Ctx) error {
|
|||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
socketclients.BroadcastMessageToTopicExceptBrowserTabSession(
|
||||
c.Locals("organizationId").(string),
|
||||
utils.SubscribedTopicLessons,
|
||||
c.Locals("browserTabSession").(string),
|
||||
structs.SendSocketMessage{
|
||||
Cmd: utils.SendCmdLessonCreated,
|
||||
Body: lesson,
|
||||
},
|
||||
)
|
||||
|
||||
return c.JSON(
|
||||
structs.CreateLessonResponse{
|
||||
Id: lesson.Id,
|
||||
|
@ -192,10 +204,26 @@ func UpdateLessonPreviewTitle(c *fiber.Ctx) error {
|
|||
return c.SendStatus(fiber.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("title", body.Title); err != nil {
|
||||
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("title", body.Title).Error; err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
socketclients.BroadcastMessageToTopicsExceptBrowserTabSession(
|
||||
c.Locals("organizationId").(string),
|
||||
[]string{utils.SubscribedTopicLessons, utils.SubscribedTopicLessonsEditorId(params.LessonId)},
|
||||
c.Locals("browserTabSession").(string),
|
||||
structs.SendSocketMessage{
|
||||
Cmd: utils.SendCmdLessonPreviewTitleUpdated,
|
||||
Body: struct {
|
||||
LessonId string
|
||||
Title string
|
||||
}{
|
||||
LessonId: params.LessonId,
|
||||
Title: body.Title,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return c.JSON(
|
||||
fiber.Map{
|
||||
"message": "Lesson preview updated successfully",
|
||||
|
@ -250,7 +278,7 @@ func UpdateLessonPreviewThumbnail(c *fiber.Ctx) error {
|
|||
|
||||
lesson := models.Lesson{}
|
||||
|
||||
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).First(&lesson); err != nil {
|
||||
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).First(&lesson).Error; err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
|
@ -266,10 +294,27 @@ func UpdateLessonPreviewThumbnail(c *fiber.Ctx) error {
|
|||
|
||||
utils.CreateFolderStructureIfNotExists(publicPath)
|
||||
|
||||
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("thumbnail_url", databasePath+fileName); err != nil {
|
||||
thumbnailUrl := databasePath + fileName
|
||||
|
||||
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("thumbnail_url", thumbnailUrl).Error; err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
socketclients.BroadcastMessageToTopics(
|
||||
c.Locals("organizationId").(string),
|
||||
[]string{utils.SubscribedTopicLessons, utils.SubscribedTopicLessonsEditorId(params.LessonId)},
|
||||
structs.SendSocketMessage{
|
||||
Cmd: utils.SendCmdLessonPreviewThumbnailUpdated,
|
||||
Body: struct {
|
||||
LessonId string
|
||||
ThumbnailUrl string
|
||||
}{
|
||||
LessonId: params.LessonId,
|
||||
ThumbnailUrl: thumbnailUrl,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return c.SaveFile(fileHeader, publicPath+fileName)
|
||||
}
|
||||
|
||||
|
@ -308,10 +353,29 @@ func UpdateLessonState(c *fiber.Ctx) error {
|
|||
return c.SendStatus(fiber.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("state", body.State); err != nil {
|
||||
if err := database.DB.Model(&models.Lesson{}).
|
||||
Where("id = ?", params.LessonId).
|
||||
Where("organization_id = ?", c.Locals("organizationId").(string)).
|
||||
Update("state", body.State).Error; err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
socketclients.BroadcastMessageToTopicStartsWithExceptBrowserTabSession(
|
||||
c.Locals("organizationId").(string),
|
||||
utils.SubscribedTopicLessonsId(params.LessonId),
|
||||
c.Locals("browserTabSession").(string),
|
||||
structs.SendSocketMessage{
|
||||
Cmd: utils.SendCmdLessonStateUpdated,
|
||||
Body: struct {
|
||||
LessonId string
|
||||
State uint8
|
||||
}{
|
||||
LessonId: params.LessonId,
|
||||
State: body.State,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return c.JSON(
|
||||
fiber.Map{
|
||||
"message": "Lesson state updated successfully",
|
||||
|
@ -360,8 +424,13 @@ func AddLessonContent(c *fiber.Ctx) error {
|
|||
|
||||
var lastContent models.LessonContent
|
||||
|
||||
if err := database.DB.Select("position").Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).Order("position desc").First(&lastContent); err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
if err := database.DB.Select("position").Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).Order("position desc").First(&lastContent).Error; err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
|
||||
// no content found, set position to 1
|
||||
lastContent.Position = 1
|
||||
}
|
||||
|
||||
// create new content
|
||||
|
@ -375,10 +444,20 @@ func AddLessonContent(c *fiber.Ctx) error {
|
|||
Data: body.Data,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&content); err != nil {
|
||||
if err := database.DB.Create(&content).Error; err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
socketclients.BroadcastMessageToTopicStartsWithExceptBrowserTabSession(
|
||||
c.Locals("organizationId").(string),
|
||||
utils.SubscribedTopicLessonsId(params.LessonId),
|
||||
c.Locals("browserTabSession").(string),
|
||||
structs.SendSocketMessage{
|
||||
Cmd: utils.SendCmdLessonAddedContent,
|
||||
Body: content,
|
||||
},
|
||||
)
|
||||
|
||||
return c.JSON(
|
||||
structs.AddLessonContentResponse{
|
||||
Id: content.Id,
|
||||
|
@ -449,7 +528,7 @@ func UploadLessonContentFile(c *fiber.Ctx) error {
|
|||
|
||||
content := models.LessonContent{}
|
||||
|
||||
if err := database.DB.Select("type", "data").Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&content); err != nil {
|
||||
if err := database.DB.Select("type", "data").Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&content).Error; err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
|
@ -475,6 +554,24 @@ func UploadLessonContentFile(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
socketclients.BroadcastMessageToTopicStartsWithExceptBrowserTabSession(
|
||||
c.Locals("organizationId").(string),
|
||||
utils.SubscribedTopicLessonsId(params.LessonId),
|
||||
c.Locals("browserTabSession").(string),
|
||||
structs.SendSocketMessage{
|
||||
Cmd: utils.SendCmdLessonContentFileUpdated,
|
||||
Body: struct {
|
||||
ContentId string
|
||||
LessonId string
|
||||
Data string
|
||||
}{
|
||||
ContentId: params.ContentId,
|
||||
LessonId: params.LessonId,
|
||||
Data: databasePath + fileName,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"Data": databasePath + fileName,
|
||||
})
|
||||
|
@ -523,6 +620,24 @@ func UpdateLessonContent(c *fiber.Ctx) error {
|
|||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
socketclients.BroadcastMessageToTopicStartsWithExceptBrowserTabSession(
|
||||
c.Locals("organizationId").(string),
|
||||
utils.SubscribedTopicLessonsId(params.LessonId),
|
||||
c.Locals("browserTabSession").(string),
|
||||
structs.SendSocketMessage{
|
||||
Cmd: utils.SendCmdLessonContentUpdated,
|
||||
Body: struct {
|
||||
ContentId string
|
||||
LessonId string
|
||||
Data string
|
||||
}{
|
||||
ContentId: params.ContentId,
|
||||
LessonId: params.LessonId,
|
||||
Data: body.Data,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return c.JSON(
|
||||
fiber.Map{
|
||||
"message": "Lesson content updated successfully",
|
||||
|
@ -624,6 +739,24 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error {
|
|||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
socketclients.BroadcastMessageToTopicStartsWithExceptBrowserTabSession(
|
||||
c.Locals("organizationId").(string),
|
||||
utils.SubscribedTopicLessonsId(params.LessonId),
|
||||
c.Locals("browserTabSession").(string),
|
||||
structs.SendSocketMessage{
|
||||
Cmd: utils.SendCmdLessonContentUpdatedPosition,
|
||||
Body: struct {
|
||||
ContentId string
|
||||
LessonId string
|
||||
Position uint16
|
||||
}{
|
||||
ContentId: params.ContentId,
|
||||
LessonId: params.LessonId,
|
||||
Position: body.Position,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return c.JSON(
|
||||
fiber.Map{
|
||||
"message": "Lesson content position updated successfully",
|
||||
|
@ -692,6 +825,22 @@ func DeleteLessonContent(c *fiber.Ctx) error {
|
|||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
socketclients.BroadcastMessageToTopicStartsWithExceptBrowserTabSession(
|
||||
c.Locals("organizationId").(string),
|
||||
utils.SubscribedTopicLessonsId(params.LessonId),
|
||||
c.Locals("browserTabSession").(string),
|
||||
structs.SendSocketMessage{
|
||||
Cmd: utils.SendCmdLessonDeletedContent,
|
||||
Body: struct {
|
||||
ContentId string
|
||||
LessonId string
|
||||
}{
|
||||
ContentId: params.ContentId,
|
||||
LessonId: params.LessonId,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return c.JSON(
|
||||
fiber.Map{
|
||||
"message": "Lesson content deleted successfully",
|
||||
|
|
|
@ -33,12 +33,44 @@ func BroadcastMessageExceptBrowserTabSession(organizationId string, browserTabSe
|
|||
|
||||
func BroadcastMessageToTopicExceptBrowserTabSession(organizationId string, topic string, browserTabSession string, sendSocketMessage structs.SendSocketMessage) {
|
||||
for _, client := range cache.GetSocketClients() {
|
||||
if hasClientSubscribedToTopic(topic, client.SubscribedTopic) && client.BrowserTabSession != browserTabSession && client.OrganizationId == organizationId {
|
||||
if client.SubscribedTopic == topic && client.BrowserTabSession != browserTabSession && client.OrganizationId == organizationId {
|
||||
client.SendMessage(sendSocketMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BroadcastMessageToTopicStartsWithExceptBrowserTabSession(organizationId string, topic string, browserTabSession string, sendSocketMessage structs.SendSocketMessage) {
|
||||
for _, client := range cache.GetSocketClients() {
|
||||
if strings.HasPrefix(client.SubscribedTopic, topic) && client.BrowserTabSession != browserTabSession && client.OrganizationId == organizationId {
|
||||
client.SendMessage(sendSocketMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BroadcastMessageToTopicsExceptBrowserTabSession(organizationId string, topics []string, browserTabSession string, sendSocketMessage structs.SendSocketMessage) {
|
||||
for _, client := range cache.GetSocketClients() {
|
||||
if client.OrganizationId == organizationId && client.BrowserTabSession != browserTabSession {
|
||||
for _, topic := range topics {
|
||||
if client.SubscribedTopic == topic {
|
||||
client.SendMessage(sendSocketMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BroadcastMessageToTopics(organizationId string, topics []string, sendSocketMessage structs.SendSocketMessage) {
|
||||
for _, client := range cache.GetSocketClients() {
|
||||
if client.OrganizationId == organizationId {
|
||||
for _, topic := range topics {
|
||||
if client.SubscribedTopic == topic {
|
||||
client.SendMessage(sendSocketMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasClientSubscribedToTopic(topic string, clientTopic string) bool {
|
||||
return clientTopic == topic || strings.HasPrefix(clientTopic, topic)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue