add configs from file, continue develpoment on hubs

This commit is contained in:
2026-04-23 14:34:47 +02:00
parent 35827f7214
commit 8a66a905cb
13 changed files with 151 additions and 82 deletions
BIN
View File
Binary file not shown.
+1
View File
@@ -9,6 +9,7 @@ require (
) )
require ( require (
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
+4 -1
View File
@@ -4,7 +4,9 @@ import (
"context" "context"
"log" "log"
"net/http" "net/http"
"strconv"
"go-socket/packages/config"
"go-socket/packages/httpRequest" "go-socket/packages/httpRequest"
"go-socket/packages/minio" "go-socket/packages/minio"
"go-socket/packages/postgresql" "go-socket/packages/postgresql"
@@ -21,6 +23,7 @@ func withCORS(h http.HandlerFunc) http.HandlerFunc {
func main() { func main() {
ctx := context.Background() ctx := context.Background()
config.LoadConfFile()
postgresql.Init(ctx) postgresql.Init(ctx)
minio.Init(ctx) minio.Init(ctx)
@@ -57,5 +60,5 @@ func main() {
http.HandleFunc("GET /ws", wsServer.ServeWsConnection) http.HandleFunc("GET /ws", wsServer.ServeWsConnection)
log.Println("beep boop; server server started") log.Println("beep boop; server server started")
log.Fatal(http.ListenAndServe(":8080", nil)) log.Fatal(http.ListenAndServe(":"+strconv.Itoa(int(config.Port)), nil))
} }
+65
View File
@@ -0,0 +1,65 @@
package config
import (
"log/slog"
"os"
"path/filepath"
"time"
"github.com/BurntSushi/toml"
)
// Array-size constants — must be compile-time values; cannot be overridden via config file.
const (
MaxDirectMsgCache uint32 = 32
MaxHubMsgCache uint32 = 32
)
var (
Port uint32 = 8080
FileStorageBucketName string = "communicator"
MaxRequestBytes uint32 = 4 << 10
MaxRequestWithFileBytes uint32 = 1 << 30
MaxRequestWithAvatarBytes uint = 1 << 20
MaxRequestWithProfileBgBytes uint = 4 << 20
FileProcessingPartBytes uint64 = 12 << 20
FileProcessingThreads uint = 3
FileDownloadLinkTtl time.Duration = 24 * time.Hour
)
type configFile struct {
Port uint32 `toml:"port"`
MaxDirectMsgCache uint32 `toml:"max_direct_messages_cache"`
MaxHubMsgCache uint32 `toml:"max_hub_msg_cache"`
FileStorageBucketName string `toml:"file_storage_bucket_name"`
MaxRequestBytes uint32 `toml:"max_request_bytes"`
MaxRequestWithFileBytes uint32 `toml:"max_request_with_file_bytes"`
MaxRequestWithAvatarBytes uint `toml:"max_request_with_avatar_bytes"`
MaxRequestWithProfileBgBytes uint `toml:"max_request_with_profile_bg_bytes"`
FileProcessingPartBytes uint64 `toml:"file_processing_part_bytes"`
FileProcessingThreads uint `toml:"file_processing_threads"`
FileDownloadLinkTtl time.Duration `toml:"file_download_link_ttl"`
}
func LoadConfFile() {
path, err := os.Executable()
if err != nil {
panic(err)
}
var cfg configFile
if _, err := toml.DecodeFile("config.toml", &cfg); err != nil {
slog.Warn("Failed to load config.toml. Default values will be used", "path", filepath.Dir(path)+"/config.toml")
return
}
Port = cfg.Port
FileStorageBucketName = cfg.FileStorageBucketName
MaxRequestBytes = cfg.MaxRequestBytes
MaxRequestWithFileBytes = cfg.MaxRequestWithFileBytes
MaxRequestWithAvatarBytes = cfg.MaxRequestWithAvatarBytes
MaxRequestWithProfileBgBytes = cfg.MaxRequestWithProfileBgBytes
FileProcessingPartBytes = cfg.FileProcessingPartBytes
FileProcessingThreads = cfg.FileProcessingThreads
FileDownloadLinkTtl = cfg.FileDownloadLinkTtl
}
+7 -7
View File
@@ -17,28 +17,28 @@ func StringToUint32(s string) (uint32, error) {
return uint32(v), err return uint32(v), err
} }
func StringToRgba(str string) (*types.Rgba, error) { func StringToRgba(str string) (types.Rgba, error) {
parts := strings.SplitN(str, ",", 5) parts := strings.SplitN(str, ",", 5)
if len(parts) != 4 { if len(parts) != 4 {
return nil, fmt.Errorf("invalid rgba") return types.Rgba{}, fmt.Errorf("invalid rgba")
} }
rgba := &types.Rgba{} rgba := types.Rgba{}
for i, p := range parts { for i, p := range parts {
n, err := strconv.ParseUint(strings.TrimSpace(p), 10, 8) n, err := strconv.ParseUint(strings.TrimSpace(p), 10, 8)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid component %d: %w", i, err) return types.Rgba{}, fmt.Errorf("invalid component %d: %w", i, err)
} }
rgba[i] = uint8(n) rgba[i] = uint8(n)
} }
return rgba, nil return rgba, nil
} }
func RgbaToUint32(r *types.Rgba) uint32 { func RgbaToUint32(r types.Rgba) uint32 {
return uint32(r[0])<<24 | uint32(r[1])<<16 | uint32(r[2])<<8 | uint32(r[3]) return uint32(r[0])<<24 | uint32(r[1])<<16 | uint32(r[2])<<8 | uint32(r[3])
} }
func Uint32ToRgba(v uint32) *types.Rgba { func Uint32ToRgba(v uint32) types.Rgba {
return &types.Rgba{uint8(v >> 24), uint8(v >> 16), uint8(v >> 8), uint8(v)} return types.Rgba{uint8(v >> 24), uint8(v >> 16), uint8(v >> 8), uint8(v)}
} }
func StringToUuid(str string) (uuid.UUID, error) { func StringToUuid(str string) (uuid.UUID, error) {
-16
View File
@@ -1,16 +0,0 @@
package globals
import "time"
const (
MaxDirectMsgCache uint32 = 32
MaxHubMsgCache
FileStorageBucketName string = "communicator"
MaxRequestBytes uint32 = 4 << 10
MaxRequestWithFileBytes uint32 = 1 << 30
MaxRequestWithAvatarBytes uint = 1 << 20
MaxRequestWithProfileBgBytes uint = 4 << 20
FileProcessingPartBytes uint64 = 12 << 20
FileProcessingThreads uint = 3
FileDownloadLinkTtl time.Duration = 24 * time.Hour
)
+2 -2
View File
@@ -11,8 +11,8 @@ import (
"go-socket/packages/Enums/ConnectionState" "go-socket/packages/Enums/ConnectionState"
"go-socket/packages/Enums/WsEventType" "go-socket/packages/Enums/WsEventType"
"go-socket/packages/cache" "go-socket/packages/cache"
"go-socket/packages/config"
"go-socket/packages/convertions" "go-socket/packages/convertions"
"go-socket/packages/globals"
"go-socket/packages/postgresql" "go-socket/packages/postgresql"
"go-socket/packages/types" "go-socket/packages/types"
"go-socket/packages/wsServer" "go-socket/packages/wsServer"
@@ -158,7 +158,7 @@ func HandleUserGetConnectionMessages(response http.ResponseWriter, request *http
messagesCap, err := convertions.StringToUint32(request.URL.Query().Get("messages")) messagesCap, err := convertions.StringToUint32(request.URL.Query().Get("messages"))
if err != nil { if err != nil {
messagesCap = globals.MaxDirectMsgCache messagesCap = config.MaxDirectMsgCache
} }
buffer, bufferSize := conn.GetSortedMessagesBuff() buffer, bufferSize := conn.GetSortedMessagesBuff()
+3 -3
View File
@@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"go-socket/packages/config"
"go-socket/packages/convertions" "go-socket/packages/convertions"
"go-socket/packages/globals"
"go-socket/packages/minio" "go-socket/packages/minio"
) )
@@ -22,9 +22,9 @@ func HandleAttachmentFileUpload(response http.ResponseWriter, request *http.Requ
return return
} }
request.Body = http.MaxBytesReader(response, request.Body, int64(globals.MaxRequestWithFileBytes)) request.Body = http.MaxBytesReader(response, request.Body, int64(config.MaxRequestWithFileBytes))
if err = request.ParseMultipartForm(int64(globals.MaxRequestBytes)); err != nil { if err = request.ParseMultipartForm(int64(config.MaxRequestBytes)); err != nil {
http.Error(response, "invalid multipart form", http.StatusBadRequest) http.Error(response, "invalid multipart form", http.StatusBadRequest)
return return
} }
+5 -5
View File
@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"go-socket/packages/globals" "go-socket/packages/config"
) )
type postType uint8 type postType uint8
@@ -21,13 +21,13 @@ func validCheckWithResponseOnFail(response *http.ResponseWriter, request *http.R
var maxSize int64 var maxSize int64
switch pt { switch pt {
case file: case file:
maxSize = int64(globals.MaxRequestWithFileBytes) maxSize = int64(config.MaxRequestWithFileBytes)
case avatar: case avatar:
maxSize = int64(globals.MaxRequestWithAvatarBytes) maxSize = int64(config.MaxRequestWithAvatarBytes)
case profileBg: case profileBg:
maxSize = int64(globals.MaxRequestWithProfileBgBytes) maxSize = int64(config.MaxRequestWithProfileBgBytes)
default: default:
maxSize = int64(globals.MaxRequestBytes) maxSize = int64(config.MaxRequestBytes)
} }
if request.ContentLength > maxSize { if request.ContentLength > maxSize {
+5 -5
View File
@@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"time" "time"
"go-socket/packages/config"
"go-socket/packages/convertions" "go-socket/packages/convertions"
"go-socket/packages/globals"
"go-socket/packages/minio" "go-socket/packages/minio"
"go-socket/packages/cache" "go-socket/packages/cache"
@@ -202,9 +202,9 @@ func HandleUserModAvatar(response http.ResponseWriter, request *http.Request) {
return return
} }
request.Body = http.MaxBytesReader(response, request.Body, int64(globals.MaxRequestWithAvatarBytes)) request.Body = http.MaxBytesReader(response, request.Body, int64(config.MaxRequestWithAvatarBytes))
if err = request.ParseMultipartForm(int64(globals.MaxRequestBytes)); err != nil { if err = request.ParseMultipartForm(int64(config.MaxRequestBytes)); err != nil {
http.Error(response, "invalid multipart form", http.StatusBadRequest) http.Error(response, "invalid multipart form", http.StatusBadRequest)
return return
} }
@@ -265,9 +265,9 @@ func HandleUserModProfileBg(response http.ResponseWriter, request *http.Request)
return return
} }
request.Body = http.MaxBytesReader(response, request.Body, int64(globals.MaxRequestWithProfileBgBytes)) request.Body = http.MaxBytesReader(response, request.Body, int64(config.MaxRequestWithProfileBgBytes))
if err = request.ParseMultipartForm(int64(globals.MaxRequestBytes)); err != nil { if err = request.ParseMultipartForm(int64(config.MaxRequestBytes)); err != nil {
http.Error(response, "invalid multipart form", http.StatusBadRequest) http.Error(response, "invalid multipart form", http.StatusBadRequest)
return return
} }
+11 -11
View File
@@ -8,7 +8,7 @@ import (
"strconv" "strconv"
"time" "time"
"go-socket/packages/globals" "go-socket/packages/config"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
@@ -68,15 +68,15 @@ func Init(ctx context.Context) {
panic(err) panic(err)
} }
exists, err := minClient.BucketExists(ctx, globals.FileStorageBucketName) exists, err := minClient.BucketExists(ctx, config.FileStorageBucketName)
if err != nil { if err != nil {
panic(err) panic(err)
} }
if !exists { if !exists {
err = minClient.MakeBucket(ctx, globals.FileStorageBucketName, minio.MakeBucketOptions{}) err = minClient.MakeBucket(ctx, config.FileStorageBucketName, minio.MakeBucketOptions{})
if err != nil { if err != nil {
exists, checkErr := minClient.BucketExists(ctx, globals.FileStorageBucketName) exists, checkErr := minClient.BucketExists(ctx, config.FileStorageBucketName)
if checkErr != nil || !exists { if checkErr != nil || !exists {
panic(err) panic(err)
} }
@@ -87,22 +87,22 @@ func Init(ctx context.Context) {
func Upload(ctx context.Context, key string, body io.Reader, size int64, contentType string, metadata map[string]string) error { func Upload(ctx context.Context, key string, body io.Reader, size int64, contentType string, metadata map[string]string) error {
opt := minio.PutObjectOptions{ opt := minio.PutObjectOptions{
ContentType: contentType, ContentType: contentType,
PartSize: globals.FileProcessingPartBytes, PartSize: config.FileProcessingPartBytes,
NumThreads: globals.FileProcessingThreads, NumThreads: config.FileProcessingThreads,
UserMetadata: metadata, UserMetadata: metadata,
} }
opt.SetMatchETagExcept("*") opt.SetMatchETagExcept("*")
_, err := minClient.PutObject(ctx, globals.FileStorageBucketName, key, body, size, opt) _, err := minClient.PutObject(ctx, config.FileStorageBucketName, key, body, size, opt)
return err return err
} }
func GetDownloadUrlAndMetadata(ctx context.Context, key string) (*url.URL, map[string]string, error) { func GetDownloadUrlAndMetadata(ctx context.Context, key string) (*url.URL, map[string]string, error) {
info, err := minClient.StatObject(ctx, globals.FileStorageBucketName, key, minio.StatObjectOptions{}) info, err := minClient.StatObject(ctx, config.FileStorageBucketName, key, minio.StatObjectOptions{})
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
u, err := minClient.PresignedGetObject(ctx, globals.FileStorageBucketName, key, globals.FileDownloadLinkTtl, nil) u, err := minClient.PresignedGetObject(ctx, config.FileStorageBucketName, key, config.FileDownloadLinkTtl, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -110,11 +110,11 @@ func GetDownloadUrlAndMetadata(ctx context.Context, key string) (*url.URL, map[s
} }
func DoesExist(ctx context.Context, key string) bool { func DoesExist(ctx context.Context, key string) bool {
_, err := minClient.StatObject(ctx, globals.FileStorageBucketName, key, minio.StatObjectOptions{}) _, err := minClient.StatObject(ctx, config.FileStorageBucketName, key, minio.StatObjectOptions{})
return err == nil return err == nil
} }
func Delete(ctx context.Context, key string) error { func Delete(ctx context.Context, key string) error {
err := minClient.RemoveObject(ctx, globals.FileStorageBucketName, key, minio.RemoveObjectOptions{}) err := minClient.RemoveObject(ctx, config.FileStorageBucketName, key, minio.RemoveObjectOptions{})
return err return err
} }
+12 -1
View File
@@ -76,6 +76,9 @@ func Init(ctx context.Context) {
CREATE TABLE IF NOT EXISTS hubs () CREATE TABLE IF NOT EXISTS hubs ()
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
creator_id UUID NOT NULL REFERENCES users(id), creator_id UUID NOT NULL REFERENCES users(id),
name TEXT NOT NULL,
allow_user_color BOOLEAN DEFAULT TRUE,
rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295),
created_at TIMESTAMP NOT NULL DEFAULT NOW() created_at TIMESTAMP NOT NULL DEFAULT NOW()
`) `)
if err != nil { if err != nil {
@@ -118,7 +121,7 @@ func Init(ctx context.Context) {
_, err = dbConn.Exec(ctx, ` _, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS hub_user_roles () CREATE TABLE IF NOT EXISTS hub_user_roles ()
user_id UUID PRIMARY KEY NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id UUID PRIMARY KEY NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_id SMALLINT NOT NULL REFERENCES hub_roles(role_id) role_id SMALLINT NOT NULL REFERENCES hub_roles(role_id) ON DELETE CASCADE
`) `)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -129,6 +132,7 @@ func Init(ctx context.Context) {
hub_id UUID PRIMARY KEY NOT NULL REFERENCES hubs(id) ON DELETE CASCADE, hub_id UUID PRIMARY KEY NOT NULL REFERENCES hubs(id) ON DELETE CASCADE,
role_id SMALLINT NOT NULL DEFAULT 0, role_id SMALLINT NOT NULL DEFAULT 0,
name TEXT NOT NULL, name TEXT NOT NULL,
permissions INTEGER NOT NULL DEFAULT 0,
rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295), rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295),
`) `)
if err != nil { if err != nil {
@@ -316,3 +320,10 @@ func ConnectionGetMessagesBefore(ctx context.Context, before time.Time, connecti
} }
return messages, rows.Err() return messages, rows.Err()
} }
func HubCreate(ctx context.Context, hub *types.Hub, creator *types.User) error {
_, err := dbConn.Exec(ctx, `
INSERT INTO hubs (id, creator_id, name, allow_user_color, rgba, created_at) VALUES ($1, $2, $3, $4, $5)
`, hub.Id, creator.Id, hub.Name, hub.AllowUserColor, convertions.RgbaToUint32(hub.Color), hub.CreatedAt)
return err
}
+36 -31
View File
@@ -8,7 +8,7 @@ import (
"go-socket/packages/Enums/ConnectionState" "go-socket/packages/Enums/ConnectionState"
"go-socket/packages/Enums/WsEventType" "go-socket/packages/Enums/WsEventType"
"go-socket/packages/globals" "go-socket/packages/config"
"github.com/coder/websocket" "github.com/coder/websocket"
"github.com/google/uuid" "github.com/google/uuid"
@@ -17,11 +17,11 @@ import (
type Rgba [4]uint8 type Rgba [4]uint8
type Sha256Hash [sha256.Size]byte type Sha256Hash [sha256.Size]byte
func (r Rgba) GetRandom() *Rgba { func (r Rgba) GetRandom() Rgba {
for i := range r { for i := range r {
r[i] = uint8(rand.IntN(256)) r[i] = uint8(rand.IntN(256))
} }
return &r return r
} }
type User struct { type User struct {
@@ -36,7 +36,7 @@ type User struct {
WsConn *websocket.Conn `json:"-"` WsConn *websocket.Conn `json:"-"`
Id uuid.UUID `json:"-"` Id uuid.UUID `json:"-"`
Connections map[uuid.UUID]*Connection `json:"-"` Connections map[uuid.UUID]*Connection `json:"-"`
Color *Rgba `json:"color"` Color Rgba `json:"color"`
} }
type UserProfileUpdateList struct { type UserProfileUpdateList struct {
@@ -48,31 +48,31 @@ type UserProfileUpdateList struct {
} }
type Connection struct { type Connection struct {
Mu sync.RWMutex `json:"-"` Mu sync.RWMutex `json:"-"`
Id uuid.UUID `json:"id"` Id uuid.UUID `json:"id"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
MessagesBuff [globals.MaxDirectMsgCache]*Message `json:"-"` MessagesBuff [config.MaxDirectMsgCache]*Message `json:"-"`
NextBuffIdx uint32 `json:"-"` NextBuffIdx uint32 `json:"-"`
RequestorId uuid.UUID `json:"requestorId"` RequestorId uuid.UUID `json:"requestorId"`
RecipientId uuid.UUID `json:"recipientId"` RecipientId uuid.UUID `json:"recipientId"`
UserWantingToElevate uuid.UUID `json:"userWantingToElevate"` // TODO add to database UserWantingToElevate uuid.UUID `json:"userWantingToElevate"` // TODO add to database
HaveOverflowed bool `json:"-"` HaveOverflowed bool `json:"-"`
State ConnectionState.ConnectionState `json:"state"` State ConnectionState.ConnectionState `json:"state"`
} }
func (conn *Connection) AddMessageToBuff(message *Message) { func (conn *Connection) AddMessageToBuff(message *Message) {
conn.Mu.Lock() conn.Mu.Lock()
defer conn.Mu.Unlock() defer conn.Mu.Unlock()
conn.MessagesBuff[conn.NextBuffIdx%globals.MaxDirectMsgCache] = message conn.MessagesBuff[conn.NextBuffIdx%config.MaxDirectMsgCache] = message
conn.NextBuffIdx++ conn.NextBuffIdx++
if conn.NextBuffIdx >= globals.MaxDirectMsgCache { if conn.NextBuffIdx >= config.MaxDirectMsgCache {
conn.HaveOverflowed = true conn.HaveOverflowed = true
} }
} }
// GetSortedMessagesBuff returns arr, length // GetSortedMessagesBuff returns arr, length
func (conn *Connection) GetSortedMessagesBuff() (*[globals.MaxDirectMsgCache]*Message, uint32) { func (conn *Connection) GetSortedMessagesBuff() (*[config.MaxDirectMsgCache]*Message, uint32) {
conn.Mu.RLock() conn.Mu.RLock()
defer conn.Mu.RUnlock() defer conn.Mu.RUnlock()
@@ -80,11 +80,11 @@ func (conn *Connection) GetSortedMessagesBuff() (*[globals.MaxDirectMsgCache]*Me
return &conn.MessagesBuff, conn.NextBuffIdx return &conn.MessagesBuff, conn.NextBuffIdx
} }
sorted := new([globals.MaxDirectMsgCache]*Message) sorted := new([config.MaxDirectMsgCache]*Message)
for i := uint32(0); i < globals.MaxDirectMsgCache; i++ { for i := uint32(0); i < config.MaxDirectMsgCache; i++ {
sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%globals.MaxDirectMsgCache] sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%config.MaxDirectMsgCache]
} }
return sorted, globals.MaxDirectMsgCache return sorted, config.MaxDirectMsgCache
} }
type ConnectionStatusSetData struct { type ConnectionStatusSetData struct {
@@ -138,6 +138,7 @@ const (
PermissionChangeRoleName PermissionChangeRoleName
PermissionChangeRoleColor PermissionChangeRoleColor
PermissionChangeRolePermissions PermissionChangeRolePermissions
PermissionOnlySelfRoleRemove
// Channel group permissions // Channel group permissions
PermissionAddChannelGroup PermissionAddChannelGroup
@@ -156,21 +157,24 @@ const (
) )
type Hub struct { type Hub struct {
Mu sync.RWMutex `json:"-"` Mu sync.RWMutex `json:"-"`
Id uuid.UUID `json:"id"` Id uuid.UUID `json:"id"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
MessagesBuff [globals.MaxHubMsgCache]*Message `json:"-"` MessagesBuff [config.MaxHubMsgCache]*Message `json:"-"`
NextBuffIdx uint32 `json:"-"` NextBuffIdx uint32 `json:"-"`
HaveOverflowed bool `json:"-"` HaveOverflowed bool `json:"-"`
Users map[uuid.UUID]*HubUser `json:"-"` Users map[uuid.UUID]*HubUser `json:"-"`
Roles map[uint8]*HubRole `json:"-"` Roles map[uint8]*HubRole `json:"-"`
Creator uuid.UUID `json:"creator"` Creator uuid.UUID `json:"creator"`
Name string `json:"name"`
Color Rgba `json:"color"`
AllowUserColor bool `json:"allowUserColor"`
} }
type HubChannelGroup struct { type HubChannelGroup struct {
Id uuid.UUID `json:"id"` Id uuid.UUID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Color string `json:"color"` Color Rgba `json:"color"`
} }
type HubChannel struct { type HubChannel struct {
@@ -190,6 +194,7 @@ type HubRole struct {
Name string `json:"role"` Name string `json:"role"`
Id uint8 `json:"id"` Id uint8 `json:"id"`
RolePermission RolePermission `json:"rolePermission"` RolePermission RolePermission `json:"rolePermission"`
Color Rgba `json:"color"`
} }
func (h HubRole) GrantPermission(r RolePermission) { func (h HubRole) GrantPermission(r RolePermission) {