websocket
parent
0538e8b3ea
commit
1232a909c1
1
go.mod
1
go.mod
|
@ -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
8
go.sum
|
@ -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=
|
||||
|
|
8
main.go
8
main.go
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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{})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,7 @@ const (
|
|||
type SocketClient struct {
|
||||
SessionId string
|
||||
BrowserTabSession string
|
||||
OrganizationId string
|
||||
UserId string
|
||||
Conn *websocket.Conn
|
||||
connMu sync.Mutex
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
|
@ -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{
|
||||
|
|
|
@ -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(¤tContent).Error; err != nil {
|
||||
var currentContent models.LessonContent
|
||||
if err := tx.Model(&models.LessonContent{}).Where("id = ?", params.ContentId).First(¤tContent).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()
|
||||
|
|
|
@ -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, ¶ms); 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",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
*/
|
|
@ -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, ¶ms); 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",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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(),
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue