websocket

main
alex 2024-09-08 00:32:01 +02:00
parent 0538e8b3ea
commit 1232a909c1
25 changed files with 688 additions and 249 deletions

1
go.mod
View File

@ -14,6 +14,7 @@ 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

8
go.sum
View File

@ -1,5 +1,13 @@
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=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=

View File

@ -7,13 +7,14 @@ import (
"git.ex.umbach.dev/Alex/roese-utils/rsconfig"
"git.ex.umbach.dev/Alex/roese-utils/rslogger"
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/websocket/v2"
"lms.de/backend/modules/config"
"lms.de/backend/modules/database"
"lms.de/backend/modules/logger"
"lms.de/backend/modules/structs"
"lms.de/backend/modules/permissions"
"lms.de/backend/modules/utils"
"lms.de/backend/routers/router"
"lms.de/backend/socketserver"
@ -51,6 +52,7 @@ MARIADB_DATABASE_NAME=db_database_name`)
utils.ValidatorInit()
database.InitDatabase()
permissions.InitPermissions()
}
func main() {
@ -79,12 +81,12 @@ func main() {
}
// validate ws session
var userSession structs.UserSession
var userSession models.UserSession
database.DB.Select("user_id").First(&userSession, "session = ?", sessionId)
if userSession.UserId != "" {
var user structs.User
var user models.User
database.DB.First(&user, "id = ?", userSession.UserId)

19
modules/cache/permissions.go vendored Normal file
View File

@ -0,0 +1,19 @@
package cache
import "sync"
var masterRolePermissions []uint16
var muMRP sync.RWMutex
func SetMasterRolePermissions(permissions []uint16) {
muMRP.Lock()
masterRolePermissions = permissions
muMRP.Unlock()
}
func GetMasterRolePermissions() []uint16 {
muMRP.RLock()
defer muMRP.RUnlock()
return masterRolePermissions
}

View File

@ -3,11 +3,11 @@ package database
import (
"fmt"
"git.ex.umbach.dev/LMS/libcore/models"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"lms.de/backend/modules/config"
"lms.de/backend/modules/structs"
)
var DB *gorm.DB
@ -35,15 +35,15 @@ func InitDatabase() {
DB = db
db.AutoMigrate(&structs.Organization{})
db.AutoMigrate(&structs.Role{})
db.AutoMigrate(&structs.RolePermission{})
db.AutoMigrate(&structs.User{})
db.AutoMigrate(&structs.UserSession{})
db.AutoMigrate(&structs.Lesson{})
db.AutoMigrate(&structs.LessonContent{})
db.AutoMigrate(&structs.Question{})
db.AutoMigrate(&structs.QuestionLike{})
db.AutoMigrate(&structs.QuestionReply{})
db.AutoMigrate(&structs.QuestionReplyLike{})
db.AutoMigrate(&models.Organization{})
db.AutoMigrate(&models.Role{})
db.AutoMigrate(&models.RolePermission{})
db.AutoMigrate(&models.User{})
db.AutoMigrate(&models.UserSession{})
db.AutoMigrate(&models.Lesson{})
db.AutoMigrate(&models.LessonContent{})
db.AutoMigrate(&models.Question{})
db.AutoMigrate(&models.QuestionLike{})
db.AutoMigrate(&models.QuestionReply{})
db.AutoMigrate(&models.QuestionReplyLike{})
}

View File

@ -0,0 +1,15 @@
package permissions
import (
"lms.de/backend/modules/cache"
"lms.de/backend/modules/database"
"lms.de/backend/modules/utils"
)
func InitPermissions() {
cache.SetMasterRolePermissions(utils.Permissions)
// delete permission from database if not longer in permissions
database.DB.Exec("DELETE FROM role_permissions WHERE permission NOT IN (?)", utils.Permissions)
}

View File

@ -7,28 +7,6 @@ const (
LessonStateDraft = 2
)
type Lesson struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
OrganizationId string `gorm:"type:varchar(36)"`
State uint8 `gorm:"type:tinyint(1)"`
Title string `gorm:"type:varchar(255)"`
ThumbnailUrl string `gorm:"type:varchar(255)"`
CreatorUserId string `gorm:"type:varchar(36)"`
CreatedAt time.Time
UpdatedAt time.Time
}
type LessonContent struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
LessonId string `gorm:"type:varchar(36)"`
Page uint16 `gorm:"type:smallint(5)"` // Page number
Position uint16 `gorm:"type:smallint(5)"` // Position on the page
Type uint8 // Type of content, like text, image, video, etc
Data string `gorm:"type:text"`
CreatedAt time.Time
UpdatedAt time.Time
}
// swagger:model LessonResponse
type LessonResponse struct {
Id string

View File

@ -1,20 +1,5 @@
package structs
import "time"
type Organization struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
Subdomain string `gorm:"type:varchar(255)"`
OwnerUserId string `gorm:"type:varchar(36)"`
CompanyName string `gorm:"type:varchar(255)"`
PrimaryColor string `gorm:"type:varchar(6)"`
LogoUrl string `gorm:"type:varchar(255)"`
BannerUrl string `gorm:"type:varchar(255)"`
SignUpScreenUrl string `gorm:"type:varchar(255)"`
CreatedAt time.Time
UpdatedAt time.Time
}
// swagger:model CreateOrganizationRequest
type CreateOrganizationRequest struct {
Email string

View File

@ -1,38 +1 @@
package structs
import "time"
type Question struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
LessonId string `gorm:"type:varchar(36)"`
Question string `gorm:"type:text"`
Likes uint16 `gorm:"type:smallint(5)"`
CreatorUserId string `gorm:"type:varchar(36)"`
CreatedAt time.Time
UpdatedAt time.Time
}
type QuestionLike struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
QuestionId string `gorm:"type:varchar(36)"`
CreatorUserId string `gorm:"type:varchar(36)"`
CreatedAt time.Time
UpdatedAt time.Time
}
type QuestionReply struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
QuestionId string `gorm:"type:varchar(36)"`
Reply string `gorm:"type:text"`
CreatorUserId string `gorm:"type:varchar(36)"`
CreatedAt time.Time
UpdatedAt time.Time
}
type QuestionReplyLike struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
QuestionReplyId string `gorm:"type:varchar(36)"`
CreatorUserId string `gorm:"type:varchar(36)"`
CreatedAt time.Time
UpdatedAt time.Time
}

View File

@ -1,13 +1,38 @@
package structs
type Role struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
OrganizationId string `gorm:"type:varchar(36)"`
Name string `gorm:"type:varchar(255)"`
type RolesResponse struct {
Roles []HelperRole
}
type RolePermission struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
RoleId string `gorm:"type:varchar(36)"`
Permission string `gorm:"type:varchar(255)"`
type HelperRole struct {
Id string
Permissions []uint16
Users []HelperRoleUser
}
type HelperRoleUser struct {
FirstName string
LastName string
ProfilePictureUrl string
}
/*
type HelperRole struct {
Id string
Name string
Master bool
Permissions []uint16
Users []HelperRoleUser
}
type HelperRoleUser struct {
FirstName string
LastName string
ProfilePictureUrl string
}
// swagger:model CreateRoleRequest
type CreateRoleRequest struct {
Name string
}
*/

View File

@ -19,6 +19,7 @@ const (
type SocketClient struct {
SessionId string
BrowserTabSession string
OrganizationId string
UserId string
Conn *websocket.Conn
connMu sync.Mutex

View File

@ -1,35 +1,5 @@
package structs
import "time"
type User struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
OrganizationId string `gorm:"type:varchar(36)"`
State uint8 `gorm:"type:tinyint(1)"`
Active bool `gorm:"type:tinyint(1)"`
RoleId string `gorm:"type:varchar(36)"`
FirstName string `gorm:"type:varchar(255)"`
LastName string `gorm:"type:varchar(255)"`
Email string `gorm:"type:varchar(255)"`
Password string `gorm:"type:varchar(255)"`
ProfilePictureUrl string `gorm:"type:varchar(255)"`
LastOnlineAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type UserSession struct {
Id string `gorm:"primaryKey;type:varchar(36)"`
UserId string `gorm:"type:varchar(36)"`
OrganizationId string `gorm:"type:varchar(36)"`
Session string `gorm:"type:varchar(36)"`
UserAgent string `gorm:"type:varchar(255)"`
ExpiresAt time.Time
LastUsedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type GetUserResponse struct {
AvatarUrl string
}
@ -54,3 +24,11 @@ type TeamMember struct {
RoleId string
ProfilePictureUrl string
}
type CreateTeamMemberRequest struct {
FirstName string
LastName string
Email string
RoleId string
Password string
}

View File

@ -6,13 +6,15 @@ const (
maxPassword = "64"
MaxPassword = 64
LenHeaderXAuthorization = 36
lenHeaderXAuthorization = "36"
LenUserId = 36
LenHeaderXApiKey = 36
LenHeaderXAuthorization = 36
lenHeaderXAuthorization = "36"
LenUserId = 36
LenHeaderXApiKey = 36
LenHeaderBrowserTabSession = 36
HeaderXAuthorization = "X-Authorization"
HeaderXApiKey = "X-Api-Key"
HeaderXAuthorization = "X-Authorization"
HeaderXApiKey = "X-Api-Key"
HeaderBrowserTabSession = "Browser-Tab-Session"
SessionExpiresAtTime = 7 * 24 * 60 * 60 // 1 week
@ -38,3 +40,59 @@ const (
LessonContentTypeText = 2
LessonContentTypeImage
)
const (
PermissionTeamInviteNewTeamMember = 1
PermissionTeamRemoveTeamMember = 2
)
var Permissions = []uint16{
PermissionTeamInviteNewTeamMember,
PermissionTeamRemoveTeamMember,
}
const (
RoleAdminId = "d0f0fa0d-3f3b-438b-a76f-7febeb8aab57"
RoleModeratorId = "b7359e12-359e-423b-b39c-f0d4069adebc"
RoleUserId = "a1f084ad-d501-4015-b326-4c5c46fd1c5e"
)
// Permissions for each role
var AdminPermissions = Permissions
var ModeratorPermissions = []uint16{
PermissionTeamInviteNewTeamMember,
}
var UserPermissions = []uint16{}
var Roles = map[string][]uint16{
RoleAdminId: AdminPermissions,
RoleModeratorId: ModeratorPermissions,
RoleUserId: UserPermissions,
}
// websocket
// commands sent to websocket clients
const (
SendCmdSettingsUpdated = 1
SendCmdSettingsUpdatedLogo = 2
SendCmdSettingsUpdatedBanner = 3
SendCmdSettingsUpdatedSubdomain = 4
SendCmdTeamAddedMember = 5
)
// commands received from websocket clients
const (
ReceivedCmdSubscribeToTopic = 1
)
const (
SubscribedTopicLessons = "/lessons"
SubscribedTopicTeam = "/team"
SubscribedTopicRoles = "/roles"
SubscribedTopicSettings = "/settings"
SubscribedTopicAccount = "/account"
)

View File

@ -7,11 +7,11 @@ import (
"os"
"time"
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"github.com/rs/zerolog/log"
"lms.de/backend/modules/config"
"lms.de/backend/modules/database"
"lms.de/backend/modules/structs"
)
func GetXAuhorizationHeader(c *fiber.Ctx) string {
@ -32,6 +32,15 @@ func GetXApiKeyHeader(c *fiber.Ctx) string {
return c.GetReqHeaders()[HeaderXApiKey][0]
}
func GetBrowserTabSessionHeader(c *fiber.Ctx) string {
// check if header is set
if len(c.GetReqHeaders()[HeaderBrowserTabSession]) == 0 {
return ""
}
return c.GetReqHeaders()[HeaderBrowserTabSession][0]
}
func GetSessionExpiresAtTime() time.Time {
return time.Now().Add(time.Second * SessionExpiresAtTime)
}
@ -71,7 +80,7 @@ func GenerateSubdomain() string {
}
func IsSubdomainAlreadyInUse(subdomain string) bool {
var organization structs.Organization
var organization models.Organization
if err := database.DB.Where("subdomain = ?", subdomain).First(&organization).Error; err != nil {
return false

BIN
public/demo/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -1,6 +1,7 @@
package app
import (
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"lms.de/backend/modules/database"
"lms.de/backend/modules/structs"
@ -24,17 +25,15 @@ func GetApp(c *fiber.Ctx) error {
// '500':
// description: Failed to fetch app
var user structs.User
var user models.User
database.DB.Model(&structs.User{
database.DB.Model(&models.User{
Id: c.Locals("userId").(string),
}).Select("profile_picture_url").First(&user)
var organization structs.Organization
var organization models.Organization
database.DB.Model(&structs.Organization{
Id: c.Locals("organizationId").(string),
}).Select("company_name", "primary_color", "logo_url", "banner_url").First(&organization)
database.DB.Model(&models.Organization{}).Select("company_name", "primary_color", "logo_url", "banner_url").Where("id = ?", c.Locals("organizationId").(string)).First(&organization)
return c.JSON(structs.GetAppResponse{
User: structs.AppUser{

View File

@ -4,6 +4,7 @@ import (
"sort"
"strings"
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"gorm.io/gorm"
@ -32,7 +33,9 @@ func GetLessons(c *fiber.Ctx) error {
var lessons []structs.LessonResponse
database.DB.Model(&structs.Lesson{}).Where("organization_id = ?", c.Locals("organizationId")).Find(&lessons)
if err := database.DB.Model(&models.Lesson{}).Where("organization_id = ?", c.Locals("organizationId")).Find(&lessons).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(lessons)
}
@ -53,7 +56,7 @@ func CreateLesson(c *fiber.Ctx) error {
// '500':
// description: Failed to create lesson.
lesson := structs.Lesson{
lesson := models.Lesson{
Id: uuid.New().String(),
OrganizationId: c.Locals("organizationId").(string),
State: structs.LessonStateDraft,
@ -61,7 +64,9 @@ func CreateLesson(c *fiber.Ctx) error {
CreatorUserId: c.Locals("userId").(string),
}
database.DB.Create(&lesson)
if err := database.DB.Create(&lesson).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(
structs.CreateLessonResponse{
@ -101,7 +106,9 @@ func GetLessonContents(c *fiber.Ctx) error {
var lessonContents []structs.GetLessonContentsResponse
database.DB.Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).Find(&lessonContents)
if err := database.DB.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).Find(&lessonContents).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
// sort contents by position
@ -141,7 +148,9 @@ func GetLessonSettings(c *fiber.Ctx) error {
var lessonSettings structs.LessonSettings
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).First(&lessonSettings)
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).First(&lessonSettings).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(lessonSettings)
}
@ -183,7 +192,9 @@ func UpdateLessonPreviewTitle(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusBadRequest)
}
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).Update("title", body.Title)
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("title", body.Title); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(
fiber.Map{
@ -237,9 +248,11 @@ func UpdateLessonPreviewThumbnail(c *fiber.Ctx) error {
// get current thumbnail
lesson := structs.Lesson{}
lesson := models.Lesson{}
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).First(&lesson)
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).First(&lesson); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
// delete current thumbnail
@ -253,7 +266,9 @@ func UpdateLessonPreviewThumbnail(c *fiber.Ctx) error {
utils.CreateFolderStructureIfNotExists(publicPath)
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).Update("thumbnail_url", databasePath+fileName)
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("thumbnail_url", databasePath+fileName); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.SaveFile(fileHeader, publicPath+fileName)
}
@ -293,7 +308,9 @@ func UpdateLessonState(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusBadRequest)
}
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).Update("state", body.State)
if err := database.DB.Model(&models.Lesson{}).Where("id = ?", params.LessonId).Update("state", body.State); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(
fiber.Map{
@ -341,13 +358,15 @@ func AddLessonContent(c *fiber.Ctx) error {
// get last position
var lastContent structs.LessonContent
var lastContent models.LessonContent
database.DB.Select("position").Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).Order("position desc").First(&lastContent)
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)
}
// create new content
content := structs.LessonContent{
content := models.LessonContent{
Id: uuid.New().String(),
LessonId: params.LessonId,
Page: 1,
@ -356,7 +375,9 @@ func AddLessonContent(c *fiber.Ctx) error {
Data: body.Data,
}
database.DB.Create(&content)
if err := database.DB.Create(&content); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(
structs.AddLessonContentResponse{
@ -426,9 +447,11 @@ func UploadLessonContentFile(c *fiber.Ctx) error {
// get current file
content := structs.LessonContent{}
content := models.LessonContent{}
database.DB.Select("type", "data").Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).First(&content)
if err := database.DB.Select("type", "data").Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&content); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
// delete current image
@ -442,7 +465,9 @@ func UploadLessonContentFile(c *fiber.Ctx) error {
utils.CreateFolderStructureIfNotExists(publicPath)
database.DB.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).Update("data", databasePath+fileName)
if err := database.DB.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).Update("data", databasePath+fileName).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
if err := c.SaveFile(fileHeader, publicPath+fileName); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
@ -494,7 +519,9 @@ func UpdateLessonContent(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusBadRequest)
}
database.DB.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).Update("data", body.Data)
if err := database.DB.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).Update("data", body.Data).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(
fiber.Map{
@ -553,8 +580,8 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error {
}
// Fetch the current position of the content being moved
var currentContent structs.LessonContent
if err := tx.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).First(&currentContent).Error; err != nil {
var currentContent models.LessonContent
if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&currentContent).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusNotFound)
}
@ -570,7 +597,7 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error {
if oldPosition < newPosition {
// Items between oldPosition and newPosition need to be shifted down
if err := tx.Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).
if err := tx.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).
Where("position > ? AND position <= ?", oldPosition, newPosition).
Update("position", gorm.Expr("position - 1")).Error; err != nil {
tx.Rollback()
@ -578,7 +605,7 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error {
}
} else {
// Items between newPosition and oldPosition need to be shifted up
if err := tx.Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).
if err := tx.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).
Where("position >= ? AND position < ?", newPosition, oldPosition).
Update("position", gorm.Expr("position + 1")).Error; err != nil {
tx.Rollback()
@ -587,7 +614,7 @@ func UpdateLessonContentPosition(c *fiber.Ctx) error {
}
// Update the position of the moved content
if err := tx.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).Update("position", newPosition).Error; err != nil {
if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).Update("position", newPosition).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusInternalServerError)
}
@ -640,8 +667,8 @@ func DeleteLessonContent(c *fiber.Ctx) error {
}
// Fetch the current position of the content being deleted
var content structs.LessonContent
if err := tx.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).First(&content).Error; err != nil {
var content models.LessonContent
if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(&content).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusNotFound)
}
@ -653,7 +680,7 @@ func DeleteLessonContent(c *fiber.Ctx) error {
}
// Shift down the positions of all contents with a higher position
if err := tx.Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).
if err := tx.Model(&models.LessonContent{}).Where("lesson_id = ?", params.LessonId).
Where("position > ?", content.Position).
Update("position", gorm.Expr("position - 1")).Error; err != nil {
tx.Rollback()

View File

@ -4,12 +4,15 @@ import (
"encoding/base64"
"time"
"git.ex.umbach.dev/Alex/roese-utils/rslogger"
"git.ex.umbach.dev/Alex/roese-utils/rsutils"
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt"
"lms.de/backend/modules/database"
"lms.de/backend/modules/logger"
"lms.de/backend/modules/structs"
"lms.de/backend/modules/utils"
)
@ -64,23 +67,42 @@ func CreateOrganization(c *fiber.Ctx) error {
organizationId := uuid.New().String()
userId := uuid.New().String()
subdomain := utils.GenerateSubdomain()
// roleId := uuid.New().String()
database.DB.Create(&structs.Organization{
Id: organizationId,
Subdomain: subdomain,
OwnerUserId: userId,
CompanyName: "Mustermann GmbH",
})
if err := database.DB.Create(&models.Organization{
Id: organizationId,
Subdomain: subdomain,
OwnerUserId: userId,
CompanyName: "Mustermann GmbH",
PrimaryColor: "1677ff", // blue
}).Error; err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to create organization, err: "+err.Error())
return c.SendStatus(fiber.StatusInternalServerError)
}
database.DB.Create(&structs.User{
/*
if err := database.DB.Create(&models.Role{
Id: roleId,
OrganizationId: organizationId,
Master: true,
Name: "Admin",
}).Error; err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to create role, err: "+err.Error())
return c.SendStatus(fiber.StatusInternalServerError)
} */
if err := database.DB.Create(&models.User{
Id: userId,
OrganizationId: organizationId,
Active: true,
FirstName: "Max",
LastName: "Mustermann",
Email: body.Email,
Password: string(hashedPassword),
})
RoleId: utils.RoleAdminId,
}).Error; err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to create user, err: "+err.Error())
return c.SendStatus(fiber.StatusInternalServerError)
}
session, err := rsutils.GenerateSession()
@ -88,7 +110,7 @@ func CreateOrganization(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusInternalServerError)
}
database.DB.Create(&structs.UserSession{
if err := database.DB.Create(&models.UserSession{
Id: session,
OrganizationId: organizationId,
Session: uuid.New().String(),
@ -96,7 +118,10 @@ func CreateOrganization(c *fiber.Ctx) error {
UserAgent: string(c.Context().UserAgent()),
ExpiresAt: utils.GetSessionExpiresAtTime(),
LastUsedAt: time.Now(),
})
}).Error; err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to create user session, err: "+err.Error())
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(structs.CreateOrganizationResponse{
OrganizationSubdomain: subdomain,
@ -131,7 +156,7 @@ func IsSubdomainAvailable(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusBadRequest)
}
var organization structs.Organization
var organization models.Organization
database.DB.Select("Id").Where("subdomain = ?", params.Subdomain).First(&organization)
@ -145,47 +170,3 @@ func IsSubdomainAvailable(c *fiber.Ctx) error {
Available: true,
})
}
func UpdateSubdomain(c *fiber.Ctx) error {
// swagger:operation PATCH /organization/subdomain/{subdomain} organization updateSubdomain
// ---
// summary: Update organization subdomain
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: subdomain
// in: path
// required: true
// type: string
// responses:
// '200':
// description: Subdomain updated successfully
// '400':
// description: Invalid request body
// '500':
// description: Failed to update subdomain
var params structs.SubdomainParam
if err := rsutils.ParamsParserHelper(c, &params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
organization := structs.Organization{
Id: c.Locals("organizationId").(string),
}
database.DB.Select("subdomain").Model(organization).First(&organization)
if organization.Subdomain == "" {
return c.SendStatus(fiber.StatusBadRequest)
}
database.DB.Model(&organization).Update("subdomain", params.Subdomain)
return c.JSON(fiber.Map{
"status": "success",
})
}

View File

@ -0,0 +1,123 @@
package organization
import (
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"lms.de/backend/modules/database"
"lms.de/backend/modules/structs"
"lms.de/backend/modules/utils"
)
func GetRoles(c *fiber.Ctx) error {
// swagger:operation GET /organization/roles organization getRoles
// ---
// summary: Get roles
// produces:
// - application/json
// responses:
// '200':
// description: Roles
// schema:
// "$ref": "#/definitions/RolesResponse"
// '500':
// description: Failed to get roles
/*
var roles []models.Role
if err := database.DB.Model(&models.Role{}).Where("organization_id = ?", c.Locals("organizationId").(string)).Find(&roles).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
if len(roles) == 0 {
return c.SendStatus(fiber.StatusInternalServerError)
}
for _, role := range roles {
var rolePermissions []uint16
if err := database.DB.Model(&models.RolePermission{}).Where("role_id = ?", role.Id).Pluck("permission", &rolePermissions).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
var users []structs.HelperRoleUser
if err := database.DB.Model(&models.User{}).Where("role_id = ?", role.Id).Where("organization_id = ?", c.Locals("organizationId").(string)).Find(&users).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
rolesResponse.Roles = append(rolesResponse.Roles, structs.HelperRole{
Id: role.Id,
Name: role.Name,
Master: role.Master,
Permissions: rolePermissions,
Users: users,
})
} */
var rolesResponse structs.RolesResponse
// order is random so we need to define the order
keys := []string{utils.RoleAdminId, utils.RoleModeratorId, utils.RoleUserId}
for _, key := range keys {
role := utils.Roles[key]
var users []structs.HelperRoleUser
if err := database.DB.Model(&models.User{}).Where("role_id = ?", key).Where("organization_id = ?", c.Locals("organizationId").(string)).Find(&users).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
rolesResponse.Roles = append(rolesResponse.Roles, structs.HelperRole{
Id: key,
Permissions: role,
Users: users,
})
}
return c.JSON(rolesResponse)
}
/*
func CreateRole(c *fiber.Ctx) error {
// swagger:operation POST /organization/roles organization createRole
// ---
// summary: Create role
// produces:
// - application/json
// parameters:
// - name: name
// in: body
// description: Role name
// required: true
// schema:
// "$ref": "#/definitions/CreateRoleRequest"
// responses:
// '200':
// description: Role created
// '400':
// description: Invalid input
// '500':
// description: Failed to create role
var body structs.CreateRoleRequest
if err := c.BodyParser(&body); err != nil {
fmt.Print(err)
return c.SendStatus(fiber.StatusBadRequest)
}
role := models.Role{
Id: uuid.New().String(),
OrganizationId: c.Locals("organizationId").(string),
Name: body.Name,
}
if err := database.DB.Create(&role).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.SendStatus(fiber.StatusOK)
}
*/

View File

@ -1,13 +1,19 @@
package organization
import (
"errors"
"strings"
"time"
"git.ex.umbach.dev/Alex/roese-utils/rsutils"
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"gorm.io/gorm"
"lms.de/backend/modules/database"
"lms.de/backend/modules/structs"
"lms.de/backend/modules/utils"
"lms.de/backend/socketclients"
)
func GetOrganizationSettings(c *fiber.Ctx) error {
@ -30,7 +36,12 @@ func GetOrganizationSettings(c *fiber.Ctx) error {
var organizationSettings structs.GetOrganizationSettingsResponse
database.DB.Model(&structs.Organization{}).Select("subdomain", "company_name", "primary_color", "logo_url", "banner_url").First(&organizationSettings)
if err := database.DB.Model(&models.Organization{}).
Select("subdomain", "company_name", "primary_color", "logo_url", "banner_url").
Where("id = ?", c.Locals("organizationId").(string)).
First(&organizationSettings).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(organizationSettings)
}
@ -62,9 +73,18 @@ func UpdateOrganizationSettings(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusBadRequest)
}
database.DB.Model(&structs.Organization{
Id: c.Locals("organizationId").(string),
}).Updates(organizationSettings)
if err := database.DB.Model(&models.Organization{}).Where("id = ?", c.Locals("organizationId").(string)).Updates(organizationSettings).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
socketclients.BroadcastMessageExceptBrowserTabSession(
c.Locals("organizationId").(string),
c.Locals("browserTabSession").(string),
structs.SendSocketMessage{
Cmd: utils.SendCmdSettingsUpdated,
Body: organizationSettings,
},
)
return c.SendStatus(fiber.StatusOK)
}
@ -120,7 +140,7 @@ func UpdateOrganizationFile(c *fiber.Ctx) error {
// get current file
organization := structs.Organization{}
organization := models.Organization{}
selectField := "logo_url"
@ -128,9 +148,11 @@ func UpdateOrganizationFile(c *fiber.Ctx) error {
selectField = "banner_url"
}
database.DB.Model(&structs.Organization{
if err := database.DB.Model(&models.Organization{
Id: c.Locals("organizationId").(string),
}).Select(selectField).First(&organization)
}).Select(selectField).First(&organization).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
// delete current file
@ -148,7 +170,7 @@ func UpdateOrganizationFile(c *fiber.Ctx) error {
utils.CreateFolderStructureIfNotExists(publicPath)
update := structs.Organization{}
update := models.Organization{}
if params.Type == "logo" {
update.LogoUrl = databasePath + fileName
@ -156,9 +178,9 @@ func UpdateOrganizationFile(c *fiber.Ctx) error {
update.BannerUrl = databasePath + fileName
}
database.DB.Model(&structs.Organization{
Id: c.Locals("organizationId").(string),
}).Updates(update)
if err := database.DB.Model(&models.Organization{}).Where("id = ?", c.Locals("organizationId").(string)).Updates(update).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
if err := c.SaveFile(fileHeader, publicPath+fileName); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
@ -166,7 +188,85 @@ func UpdateOrganizationFile(c *fiber.Ctx) error {
})
}
cmdId := utils.SendCmdSettingsUpdatedLogo
if params.Type == "banner" {
cmdId = utils.SendCmdSettingsUpdatedBanner
}
newPath := databasePath + fileName
socketclients.BroadcastMessageExceptBrowserTabSession(
c.Locals("organizationId").(string),
c.Locals("browserTabSession").(string),
structs.SendSocketMessage{
Cmd: cmdId,
Body: newPath,
},
)
return c.JSON(fiber.Map{
"Data": databasePath + fileName,
"Data": newPath,
})
}
func UpdateSubdomain(c *fiber.Ctx) error {
// swagger:operation PATCH /organization/subdomain/{subdomain} organization updateSubdomain
// ---
// summary: Update organization subdomain
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: subdomain
// in: path
// required: true
// type: string
// responses:
// '200':
// description: Subdomain updated successfully
// '400':
// description: Invalid request body
// '500':
// description: Failed to update subdomain
var params structs.SubdomainParam
if err := rsutils.ParamsParserHelper(c, &params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
var organization models.Organization
if err := database.DB.Select("subdomain").Model(organization).Where("id = ?", c.Locals("organizationId").(string)).First(&organization).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return c.SendStatus(fiber.StatusNotFound)
}
}
if organization.Subdomain == "" {
return c.SendStatus(fiber.StatusBadRequest)
}
if err := database.DB.Model(&organization).Where("id = ?", c.Locals("organizationId")).Update("subdomain", params.Subdomain).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
orgId := c.Locals("organizationId").(string)
// send broadcast with delay
go func() {
time.Sleep(1 * time.Second)
socketclients.BroadcastMessage(orgId,
structs.SendSocketMessage{
Cmd: utils.SendCmdSettingsUpdatedSubdomain,
Body: params.Subdomain,
},
)
}()
return c.JSON(fiber.Map{
"status": "success",
})
}

View File

@ -1,9 +1,18 @@
package organization
import (
"encoding/base64"
"git.ex.umbach.dev/Alex/roese-utils/rsutils"
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt"
"lms.de/backend/modules/database"
"lms.de/backend/modules/structs"
"lms.de/backend/modules/utils"
"lms.de/backend/socketclients"
)
func GetTeamMembers(c *fiber.Ctx) error {
@ -28,7 +37,99 @@ func GetTeamMembers(c *fiber.Ctx) error {
var users []structs.TeamMember
database.DB.Model(&structs.User{}).Where("organization_id = ?", c.Locals("organizationId")).Find(&users)
if err := database.DB.Model(&models.User{}).Where("organization_id = ?", c.Locals("organizationId")).Find(&users).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(users)
}
func CreateTeamMember(c *fiber.Ctx) error {
// swagger:operation POST /organization/team/members organization createTeamMember
// ---
// summary: Create team member
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreateTeamMemberRequest"
// responses:
// '200':
// description: Team member created successfully
// '400':
// description: Invalid request body
// '500':
// description: Failed to create team member
var body structs.CreateTeamMemberRequest
if err := rsutils.BodyParserHelper(c, &body); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
user := models.User{
Id: uuid.New().String(),
FirstName: body.FirstName,
LastName: body.LastName,
Email: body.Email,
OrganizationId: c.Locals("organizationId").(string),
RoleId: body.RoleId,
}
decodedPassword, err := base64.StdEncoding.DecodeString(body.Password)
if err != nil {
log.Error().Msg("Failed to decode base64 password, err: " + err.Error())
return c.SendStatus(fiber.StatusBadRequest)
}
if passwordValid := utils.IsPasswordLengthValid(string(decodedPassword)); !passwordValid {
return c.SendStatus(fiber.StatusBadRequest)
}
// Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(body.Password), bcrypt.DefaultCost)
if err != nil {
log.Error().Msg("Failed to hash password, err: " + err.Error())
return c.SendStatus(fiber.StatusInternalServerError)
}
user.Password = string(hashedPassword)
// Create user
if err := database.DB.Create(&user).Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
socketclients.BroadcastMessageToTopicExceptBrowserTabSession(
c.Locals("organizationId").(string),
utils.SubscribedTopicTeam,
c.Locals("browserTabSession").(string),
structs.SendSocketMessage{
Cmd: utils.SendCmdTeamAddedMember,
Body: struct {
Id string
FirstName string
LastName string
Email string
RoleId string
}{
Id: user.Id,
FirstName: user.FirstName,
LastName: user.LastName,
Email: user.Email,
RoleId: user.RoleId,
},
},
)
return c.JSON(fiber.Map{
"message": "Team member created successfully",
})
}

View File

@ -5,6 +5,7 @@ import (
"git.ex.umbach.dev/Alex/roese-utils/rslogger"
"git.ex.umbach.dev/Alex/roese-utils/rsutils"
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
@ -57,11 +58,14 @@ func UserLogin(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusBadRequest)
}
var user structs.User
var user models.User
organizationId := c.Locals("organizationId").(string)
database.DB.Select("id", "active", "password").First(&user, "email = ? AND organization_id = ?", body.Email, organizationId)
if err := database.DB.Select("id", "disabled", "password").First(&user, "email = ? AND organization_id = ?", body.Email, organizationId).Error; err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to find user with email %s", body.Email)
return c.SendStatus(fiber.StatusBadRequest)
}
if user.Id == "" {
log.Error().Msg("User not found")
@ -73,7 +77,7 @@ func UserLogin(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusBadRequest)
}
if !user.Active {
if user.Disabled {
return c.SendStatus(fiber.StatusUnauthorized)
}
@ -83,13 +87,16 @@ func UserLogin(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusInternalServerError)
}
database.DB.Create(&structs.UserSession{
if err := database.DB.Create(&models.UserSession{
Id: uuid.New().String(),
OrganizationId: organizationId,
Session: session,
UserId: user.Id,
UserAgent: string(c.Context().UserAgent()),
ExpiresAt: utils.GetSessionExpiresAtTime()})
ExpiresAt: utils.GetSessionExpiresAtTime()}).Error; err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to create user session, err: "+err.Error())
return c.SendStatus(fiber.StatusInternalServerError)
}
logger.AddSystemLog(rslogger.LogTypeInfo, "User %s has logged in", user.Id)

View File

@ -3,10 +3,10 @@ package router
import (
"strings"
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/fiber/v2"
"lms.de/backend/modules/config"
"lms.de/backend/modules/database"
"lms.de/backend/modules/structs"
"lms.de/backend/modules/utils"
myapp "lms.de/backend/routers/router/api/v1/app"
"lms.de/backend/routers/router/api/v1/lessons"
@ -22,11 +22,14 @@ func SetupRoutes(app *fiber.App) {
o := v1.Group("/organization")
o.Post("/", organization.CreateOrganization)
o.Get("/team/members", handleOrganizationSubdomain, requestAccessValidation, organization.GetTeamMembers)
o.Post("/team/members", handleOrganizationSubdomain, requestAccessValidation, organization.CreateTeamMember)
o.Get("/settings", handleOrganizationSubdomain, requestAccessValidation, organization.GetOrganizationSettings)
o.Patch("/settings", handleOrganizationSubdomain, requestAccessValidation, organization.UpdateOrganizationSettings)
o.Post("/file/:type", handleOrganizationSubdomain, requestAccessValidation, organization.UpdateOrganizationFile)
o.Get("/subdomain/:subdomain", organization.IsSubdomainAvailable)
o.Patch("/subdomain/:subdomain", handleOrganizationSubdomain, requestAccessValidation, organization.UpdateSubdomain)
o.Get("/roles", handleOrganizationSubdomain, requestAccessValidation, organization.GetRoles)
// o.Post("/roles", handleOrganizationSubdomain, requestAccessValidation, organization.CreateRole)
u := v1.Group("/user")
u.Post("/auth/login", handleOrganizationSubdomain, user.UserLogin)
@ -55,7 +58,7 @@ func userSessionValidation(c *fiber.Ctx) error {
return fiber.ErrUnauthorized
}
var userSession structs.UserSession
var userSession models.UserSession
database.DB.Select("session", "user_id").First(&userSession, "session = ? AND organization_id = ?", xAuthorization, c.Locals("organizationId"))
@ -70,6 +73,14 @@ func userSessionValidation(c *fiber.Ctx) error {
}
func requestAccessValidation(c *fiber.Ctx) error {
// browser tab session - needed for websocket
browserTabSession := utils.GetBrowserTabSessionHeader(c)
if len(browserTabSession) == utils.LenHeaderBrowserTabSession {
c.Locals("browserTabSession", browserTabSession)
}
// user session
xAuthorization := utils.GetXAuhorizationHeader(c)
@ -100,7 +111,7 @@ func handleOrganizationSubdomain(c *fiber.Ctx) error {
subdomain := parts[0]
// get organization id by subdomain from database
organization := structs.Organization{}
organization := models.Organization{}
database.DB.Select("id").First(&organization, "subdomain = ?", subdomain)

View File

@ -0,0 +1,44 @@
package socketclients
import (
"strings"
"lms.de/backend/modules/cache"
"lms.de/backend/modules/structs"
)
func BroadcastMessage(organizationId string, sendSocketMessage structs.SendSocketMessage) {
for _, client := range cache.GetSocketClients() {
if client.OrganizationId == organizationId {
client.SendMessage(sendSocketMessage)
}
}
}
func BroadcastMessageToTopic(organizationId string, topic string, sendSocketMessage structs.SendSocketMessage) {
for _, client := range cache.GetSocketClients() {
if hasClientSubscribedToTopic(topic, client.SubscribedTopic) {
client.SendMessage(sendSocketMessage)
}
}
}
func BroadcastMessageExceptBrowserTabSession(organizationId string, browserTabSession string, sendSocketMessage structs.SendSocketMessage) {
for _, client := range cache.GetSocketClients() {
if client.OrganizationId == organizationId && client.BrowserTabSession != browserTabSession {
client.SendMessage(sendSocketMessage)
}
}
}
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 {
client.SendMessage(sendSocketMessage)
}
}
}
func hasClientSubscribedToTopic(topic string, clientTopic string) bool {
return clientTopic == topic || strings.HasPrefix(clientTopic, topic)
}

View File

@ -7,6 +7,7 @@ import (
"time"
"git.ex.umbach.dev/Alex/roese-utils/rslogger"
"git.ex.umbach.dev/LMS/libcore/models"
"github.com/gofiber/websocket/v2"
"github.com/rs/zerolog/log"
"lms.de/backend/modules/cache"
@ -37,22 +38,23 @@ func RunHub() {
newSocketClient.SessionId = sessionId
newSocketClient.BrowserTabSession = browserTabSession
newSocketClient.UserId = userId
newSocketClient.OrganizationId = fmt.Sprintf("%v", newSocketClient.Conn.Locals("organizationId"))
cache.AddSocketClient(newSocketClient)
// check that user session is not expired
var userSession structs.UserSession
var userSession models.UserSession
database.DB.Select("expires_at").First(&userSession, "session = ?", sessionId)
if !userSession.ExpiresAt.IsZero() && time.Now().After(userSession.ExpiresAt) {
newSocketClient.SendUnauthorizedCloseMessage()
database.DB.Delete(&structs.UserSession{}, "session = ?", sessionId)
database.DB.Delete(&models.UserSession{}, "session = ?", sessionId)
continue
}
// update session last used time
database.DB.Model(&structs.UserSession{}).Where("session = ?", sessionId).Updates(structs.UserSession{
database.DB.Model(&models.UserSession{}).Where("session = ?", sessionId).Updates(models.UserSession{
LastUsedAt: time.Now(),
ExpiresAt: utils.GetSessionExpiresAtTime(),
})
@ -72,7 +74,9 @@ func RunHub() {
log.Debug().Msgf("Received message: %v %v", receivedMessage, receivedMessage.Cmd)
switch receivedMessage.Cmd {
case 1:
case utils.ReceivedCmdSubscribeToTopic:
cache.SubscribeSocketClientToTopic(receivedMessage.Body["browserTabSession"].(string), receivedMessage.Body["topic"].(string))
case 2:
break
default:
log.Error().Msgf("Received unknown message: %v", receivedMessage)
@ -85,7 +89,7 @@ func RunHub() {
userId := connection.Locals("userId").(string)
// sessionId := connection.Locals("sessionId").(string)
database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(structs.User{
database.DB.Model(&models.User{}).Where("id = ?", userId).Updates(models.User{
LastOnlineAt: time.Now(),
})