From 635139aad23cad2d3b2b19f619a5d1de35b72204 Mon Sep 17 00:00:00 2001 From: gitGnome Date: Fri, 24 Apr 2026 15:34:58 +0200 Subject: [PATCH] idk how to plan this out --- packages/Enums/permission/permission.go | 58 +++++ packages/httpRequest/connectionsAndDms.go | 2 +- packages/httpRequest/get.go | 2 +- packages/httpRequest/hubs.go | 40 ++-- packages/postgresql/postgresql.go | 261 +--------------------- packages/types/types.go | 136 +++++------ 6 files changed, 140 insertions(+), 359 deletions(-) create mode 100644 packages/Enums/permission/permission.go diff --git a/packages/Enums/permission/permission.go b/packages/Enums/permission/permission.go new file mode 100644 index 0000000..1802e17 --- /dev/null +++ b/packages/Enums/permission/permission.go @@ -0,0 +1,58 @@ +package permission + +type Global uint32 + +const ( + // Hub permissions + GlobalSetHubName Global = 1 << iota + GlobalSetHubBg + GlobalSetHubColor + GlobalRemoveHub + GlobalSetUserColorAllowed + + // User permissions + GlobalAddUser + GlobalRemoveUser + GlobalRenameUser + GlobalMuteUser + + // Role permissions + GlobalAddRole + GlobalRemoveRole + GlobalChangeRoleName + GlobalChangeRoleColor + GlobalChangeRoleGlobals + GlobalOnlySelfRoleRemove + + // Channel group permissions + GlobalAddChannelGroup + GlobalRemoveChannelGroup + GlobalSetChannelGroupName + GlobalSetChannelGroupColor + GlobalSetChannelGroupPermittedVisibleRoles + + // Channel permissions + GlobalAddChannel + GlobalRemoveChannel + GlobalSetChannelName + GlobalSetChannelPermittedVisibleRoles + GlobalSetChannelPermittedSendMessageRoles + GlobalSetChannelPermittedReadHistoryRoles +) + +type ChannelGroup uint16 + +const ( + // Channel group permission + ChannelGroupSetName ChannelGroup = 1 << iota + ChannelGroupSetColor + ChannelGroupSetPermittedVisibleRoles + + // Channel permission + ChannelGroupAddChannel + ChannelGroupRemoveChannel + ChannelGroupSetChannelName + ChannelGroupSetChannelPermittedVisibleRoles + ChannelGroupSetChannelPermittedSendMessageRoles + ChannelGroupSetChannelPermittedReadHistoryRoles +) diff --git a/packages/httpRequest/connectionsAndDms.go b/packages/httpRequest/connectionsAndDms.go index a12678f..5e82fbd 100644 --- a/packages/httpRequest/connectionsAndDms.go +++ b/packages/httpRequest/connectionsAndDms.go @@ -241,7 +241,7 @@ func HandleUserNewConnection(response http.ResponseWriter, request *http.Request } requestor.Mu.RUnlock() - connection := types.NewConnection() + connection := types.CreateConn() connection.CreatedAt = time.Now() connection.RequestorId = requestor.Id connection.RecipientId = recipient.Id diff --git a/packages/httpRequest/get.go b/packages/httpRequest/get.go index 6b14590..76f2c6c 100644 --- a/packages/httpRequest/get.go +++ b/packages/httpRequest/get.go @@ -58,7 +58,7 @@ func getHubByIdStr(ctx context.Context, hubId string) (*types.Hub, error) { hub, ok := cache.GetHubById(hubUuid) if !ok { - hub = types.NewHub() + hub = types.CreateHub() hub.Id = hubUuid if err := postgresql.GetWholeHub(ctx, hub); err != nil { return nil, err diff --git a/packages/httpRequest/hubs.go b/packages/httpRequest/hubs.go index e34ab33..75729ac 100644 --- a/packages/httpRequest/hubs.go +++ b/packages/httpRequest/hubs.go @@ -4,6 +4,7 @@ import ( "net/http" "time" + "go-socket/packages/Enums/permission" "go-socket/packages/cache" "go-socket/packages/postgresql" "go-socket/packages/types" @@ -11,9 +12,18 @@ import ( "github.com/google/uuid" ) -func hasHubUserPermission(user *types.HubUser, hub *types.Hub, permission types.RolePermission) bool { - for _, roleId := range user.Roles { - if role, ok := hub.Roles[roleId]; ok && role.HasPermission(permission) { +func hasHubUserGlobalPermission(user *types.HubUser, hub *types.Hub, perm permission.Global) bool { + for _, roleId := range user.GlobalRoles { + if role, ok := hub.GlobalRoles[roleId]; ok && role.HasPermission(perm) { + return true + } + } + return false +} + +func hasHubUserChannelGroupPermission(user *types.HubUser, hub *types.Hub, perm permission.ChannelGroup) bool { + for _, roleId := range user.ChannelGroupRoles { + if role, ok := hub.ChannelGroupRoles[roleId]; ok && role.HasPermission(perm) { return true } } @@ -38,7 +48,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { return } - hub := types.NewHub() + hub := types.CreateHub() hub.Name = hubName hub.Creator = user.Id hub.Color = types.Rgba{}.GetRandom() @@ -52,19 +62,19 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { } hub.ChannelGroups[rootGrp.Id] = rootGrp - rootRole := &types.HubRole{ + rootRole := &types.HubGlobalRole{ Id: 0, Name: "root", Color: types.Rgba{255, 0, 0, 255}, - RolePermission: ^types.RolePermission(0), + RolePermission: ^permission.Global(0), } - hub.Roles[rootRole.Id] = rootRole + hub.GlobalRoles[rootRole.Id] = rootRole rootUser := &types.HubUser{ - Id: user.Id, - Username: user.Name, - Roles: []uint8{rootRole.Id}, - CreatedAt: hub.CreatedAt, + Id: user.Id, + Username: user.Name, + GlobalRoles: []uint8{rootRole.Id}, + CreatedAt: hub.CreatedAt, } hub.Users[rootUser.Id] = rootUser @@ -73,7 +83,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { http.Error(response, "internal server error", http.StatusInternalServerError) return } - if err = postgresql.HubRoleSave(ctx, hub.Id, rootRole); err != nil { + if err = postgresql.HubGlobalRoleSave(ctx, hub.Id, rootRole); err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } @@ -85,7 +95,7 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) { http.Error(response, "internal server error", http.StatusInternalServerError) return } - if err = postgresql.HubUserRoleAdd(ctx, hub.Id, user.Id, rootRole.Id); err != nil { + if err = postgresql.HubUserGlobalRoleAdd(ctx, hub.Id, user.Id, rootRole.Id); err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } @@ -106,7 +116,7 @@ func HandleHubDelete(response http.ResponseWriter, request *http.Request) { return } - if !hasHubUserPermission(hubUser, hub, types.PermissionRemoveHub) { + if !hasHubUserGlobalPermission(hubUser, hub, permission.GlobalRemoveHub) { http.Error(response, "forbidden", http.StatusForbidden) return } @@ -129,5 +139,5 @@ func HandleHubGet(response http.ResponseWriter, request *http.Request) { if err != nil { return } - + } diff --git a/packages/postgresql/postgresql.go b/packages/postgresql/postgresql.go index 92b723b..b0577e4 100644 --- a/packages/postgresql/postgresql.go +++ b/packages/postgresql/postgresql.go @@ -72,86 +72,6 @@ func Init(ctx context.Context) { panic(err) } - _, err = dbConn.Exec(ctx, ` - CREATE TABLE IF NOT EXISTS hubs ( - id UUID PRIMARY KEY, - creator_id UUID NOT NULL REFERENCES users(id), - name TEXT NOT NULL, - allow_user_color BOOLEAN NOT NULL DEFAULT TRUE, - rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295), - created_at TIMESTAMP NOT NULL DEFAULT NOW() - ) - `) - if err != nil { - panic(err) - } - - _, err = dbConn.Exec(ctx, ` - CREATE TABLE IF NOT EXISTS hub_roles ( - hub_id UUID NOT NULL REFERENCES hubs(id) ON DELETE CASCADE, - role_id SMALLINT NOT NULL, - name TEXT NOT NULL, - permissions INTEGER NOT NULL DEFAULT 0, - rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295), - PRIMARY KEY (hub_id, role_id) - ) - `) - if err != nil { - panic(err) - } - - _, err = dbConn.Exec(ctx, ` - CREATE TABLE IF NOT EXISTS hub_channel_groups ( - channel_group_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - hub_id UUID NOT NULL REFERENCES hubs(id) ON DELETE CASCADE, - name TEXT NOT NULL, - rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295), - position SMALLINT NOT NULL DEFAULT 0 - ) - `) - if err != nil { - panic(err) - } - - _, err = dbConn.Exec(ctx, ` - CREATE TABLE IF NOT EXISTS hub_channels ( - channel_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - hub_id UUID NOT NULL REFERENCES hubs(id) ON DELETE CASCADE, - name TEXT NOT NULL, - parent_group_id UUID REFERENCES hub_channel_groups(channel_group_id) ON DELETE CASCADE, - position SMALLINT NOT NULL DEFAULT 0 - ) - `) - if err != nil { - panic(err) - } - - _, err = dbConn.Exec(ctx, ` - CREATE TABLE IF NOT EXISTS hub_users ( - hub_id UUID NOT NULL REFERENCES hubs(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - name TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - PRIMARY KEY (hub_id, user_id) - ) - `) - if err != nil { - panic(err) - } - - _, err = dbConn.Exec(ctx, ` - CREATE TABLE IF NOT EXISTS hub_user_roles ( - hub_id UUID NOT NULL, - user_id UUID NOT NULL, - role_id SMALLINT NOT NULL, - PRIMARY KEY (hub_id, user_id, role_id), - FOREIGN KEY (hub_id, user_id) REFERENCES hub_users(hub_id, user_id) ON DELETE CASCADE, - FOREIGN KEY (hub_id, role_id) REFERENCES hub_roles(hub_id, role_id) ON DELETE CASCADE - ) - `) - if err != nil { - panic(err) - } } func UserSave(ctx context.Context, user *types.User) error { @@ -277,7 +197,7 @@ func ConnectionsGetBelongingToUser(ctx context.Context, user *types.User) error } for rows.Next() { - conn := types.NewConnection() + conn := types.CreateConn() if err = rows.Scan(&conn.Id, &conn.RequestorId, &conn.RecipientId, &conn.State, &conn.CreatedAt); err != nil { return fmt.Errorf("scanning connection row: %w", err) } @@ -335,182 +255,3 @@ func ConnectionGetMessagesBefore(ctx context.Context, before time.Time, connecti return messages, rows.Err() } -func HubSave(ctx context.Context, hub *types.Hub) error { - _, err := dbConn.Exec(ctx, ` - INSERT INTO hubs (id, creator_id, name, allow_user_color, rgba, created_at) VALUES ($1, $2, $3, $4, $5, $6) - `, hub.Id, hub.Creator, hub.Name, hub.AllowUserColor, convertions.RgbaToUint32(hub.Color), hub.CreatedAt) - return err -} - -func HubDelete(ctx context.Context, hub *types.Hub) error { - _, err := dbConn.Exec(ctx, `DELETE FROM hubs WHERE id = $1`, hub.Id) - return err -} - -func HubGet(ctx context.Context, hub *types.Hub) error { - var rgba uint32 - err := dbConn.QueryRow(ctx, ` - SELECT creator_id, name, allow_user_color, rgba, created_at FROM hubs WHERE id = $1 - `, hub.Id).Scan(&hub.Creator, &hub.Name, &hub.AllowUserColor, &rgba, &hub.CreatedAt) - if err == nil { - hub.Color = convertions.Uint32ToRgba(rgba) - } - return err -} - -func HubsGetBelongingToUser(ctx context.Context, userId uuid.UUID) ([]*types.Hub, error) { - rows, err := dbConn.Query(ctx, ` - SELECT h.id, h.creator_id, h.name, h.allow_user_color, h.rgba, h.created_at - FROM hubs h - JOIN hub_users hu ON h.id = hu.hub_id - WHERE hu.user_id = $1 - `, userId) - if err != nil { - return nil, err - } - defer rows.Close() - - var hubs []*types.Hub - for rows.Next() { - hub := types.NewHub() - var rgba uint32 - if err = rows.Scan(&hub.Id, &hub.Creator, &hub.Name, &hub.AllowUserColor, &rgba, &hub.CreatedAt); err != nil { - return nil, fmt.Errorf("scanning hub row: %w", err) - } - hub.Color = convertions.Uint32ToRgba(rgba) - hubs = append(hubs, hub) - } - return hubs, rows.Err() -} - -func HubGetUsers(ctx context.Context, hub *types.Hub) error { - rows, err := dbConn.Query(ctx, ` - SELECT hu.user_id, hu.name, hu.created_at, COALESCE(array_agg(hur.role_id) FILTER (WHERE hur.role_id IS NOT NULL), '{}') - FROM hub_users hu - LEFT JOIN hub_user_roles hur ON hu.hub_id = hur.hub_id AND hu.user_id = hur.user_id - WHERE hu.hub_id = $1 - GROUP BY hu.user_id, hu.name, hu.created_at - `, hub.Id) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - hu := &types.HubUser{} - var roles []int16 - if err = rows.Scan(&hu.Id, &hu.Username, &hu.CreatedAt, &roles); err != nil { - return fmt.Errorf("scanning hub user row: %w", err) - } - hu.Roles = make([]uint8, len(roles)) - for i, r := range roles { - hu.Roles[i] = uint8(r) - } - hub.Users[hu.Id] = hu - } - return rows.Err() -} - -func HubGetRoles(ctx context.Context, hub *types.Hub) error { - rows, err := dbConn.Query(ctx, ` - SELECT role_id, name, permissions, rgba FROM hub_roles WHERE hub_id = $1 - `, hub.Id) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - hr := &types.HubRole{} - var rgba uint32 - if err = rows.Scan(&hr.Id, &hr.Name, &hr.RolePermission, &rgba); err != nil { - return fmt.Errorf("scanning hub role row: %w", err) - } - hr.Color = convertions.Uint32ToRgba(rgba) - hub.Roles[hr.Id] = hr - } - return rows.Err() -} - -func GetWholeHub(ctx context.Context, hub *types.Hub) error { - if err := HubGet(ctx, hub); err != nil { - return err - } - if err := HubGetUsers(ctx, hub); err != nil { - return err - } - return HubGetRoles(ctx, hub) -} - -func HubUserAdd(ctx context.Context, hubId uuid.UUID, hu *types.HubUser) error { - _, err := dbConn.Exec(ctx, ` - INSERT INTO hub_users (hub_id, user_id, name, created_at) VALUES ($1, $2, $3, $4) - `, hubId, hu.Id, hu.Username, hu.CreatedAt) - return err -} - -func HubUserRemove(ctx context.Context, hubId uuid.UUID, userId uuid.UUID) error { - _, err := dbConn.Exec(ctx, ` - DELETE FROM hub_users WHERE hub_id = $1 AND user_id = $2 - `, hubId, userId) - return err -} - -func HubUserRoleAdd(ctx context.Context, hubId uuid.UUID, userId uuid.UUID, roleId uint8) error { - _, err := dbConn.Exec(ctx, ` - INSERT INTO hub_user_roles (hub_id, user_id, role_id) VALUES ($1, $2, $3) - `, hubId, userId, roleId) - return err -} - -func HubUserRoleRemove(ctx context.Context, hubId uuid.UUID, userId uuid.UUID, roleId uint8) error { - _, err := dbConn.Exec(ctx, ` - DELETE FROM hub_user_roles WHERE hub_id = $1 AND user_id = $2 AND role_id = $3 - `, hubId, userId, roleId) - return err -} - -func HubRoleSave(ctx context.Context, hubId uuid.UUID, role *types.HubRole) error { - _, err := dbConn.Exec(ctx, ` - INSERT INTO hub_roles (hub_id, role_id, name, permissions, rgba) VALUES ($1, $2, $3, $4, $5) - `, hubId, role.Id, role.Name, role.RolePermission, convertions.RgbaToUint32(role.Color)) - return err -} - -func HubRoleDelete(ctx context.Context, hubId uuid.UUID, roleId uint8) error { - _, err := dbConn.Exec(ctx, ` - DELETE FROM hub_roles WHERE hub_id = $1 AND role_id = $2 - `, hubId, roleId) - return err -} - -func HubRoleUpdate(ctx context.Context, hubId uuid.UUID, role *types.HubRole) error { - _, err := dbConn.Exec(ctx, ` - UPDATE hub_roles SET name = $1, permissions = $2, rgba = $3 WHERE hub_id = $4 AND role_id = $5 - `, role.Name, role.RolePermission, convertions.RgbaToUint32(role.Color), hubId, role.Id) - return err -} - -func HubChannelGroupSave(ctx context.Context, hubId uuid.UUID, cg *types.HubChannelGroup) error { - _, err := dbConn.Exec(ctx, ` - INSERT INTO hub_channel_groups (channel_group_id, hub_id, name, rgba, position) VALUES ($1, $2, $3, $4, $5) - `, cg.Id, hubId, cg.Name, convertions.RgbaToUint32(cg.Color), cg.Position) - return err -} - -func HubChannelGroupDelete(ctx context.Context, cgId uuid.UUID) error { - _, err := dbConn.Exec(ctx, `DELETE FROM hub_channel_groups WHERE channel_group_id = $1`, cgId) - return err -} - -func HubChannelSave(ctx context.Context, hubId uuid.UUID, ch *types.HubChannel) error { - return dbConn.QueryRow(ctx, ` - INSERT INTO hub_channels (hub_id, name, parent_group_id, position) VALUES ($1, $2, $3, $4) - RETURNING channel_id - `, hubId, ch.Name, ch.ParentGroupId, ch.Position).Scan(&ch.Id) -} - -func HubChannelDelete(ctx context.Context, chId uuid.UUID) error { - _, err := dbConn.Exec(ctx, `DELETE FROM hub_channels WHERE channel_id = $1`, chId) - return err -} diff --git a/packages/types/types.go b/packages/types/types.go index e7dbd70..8686677 100644 --- a/packages/types/types.go +++ b/packages/types/types.go @@ -2,6 +2,7 @@ package types import ( "crypto/sha256" + "go-socket/packages/Enums/permission" "math/rand/v2" "sync" "time" @@ -24,6 +25,11 @@ func (r Rgba) GetRandom() Rgba { return r } +type pairUuidHubChannelGroupRole struct { + first uuid.UUID + second *HubChannelGroupRole +} + type User struct { Mu sync.RWMutex `json:"-"` Name string `json:"name"` @@ -60,7 +66,7 @@ type Connection struct { State ConnectionState.ConnectionState `json:"state"` } -func NewConnection() *Connection { +func CreateConn() *Connection { return &Connection{ MessagesBuff: make([]*Message, config.MaxDirectMsgCache), } @@ -124,69 +130,27 @@ type WsAuthMessage struct { Error string `json:"error"` } -type RolePermission uint32 - -const ( - // Hub permissions - PermissionSetHubName RolePermission = 1 << iota - PermissionSetHubBg - PermissionSetHubColor - PermissionRemoveHub - PermissionSetUserColorAllowed - - // User permissions - PermissionAddUser - PermissionRemoveUser - PermissionRenameUser - PermissionMuteUser - - // Role permissions - PermissionAddRole - PermissionRemoveRole - PermissionChangeRoleName - PermissionChangeRoleColor - PermissionChangeRolePermissions - PermissionOnlySelfRoleRemove - - // Channel group permissions - PermissionAddChannelGroup - PermissionRemoveChannelGroup - PermissionSetChannelGroupName - PermissionSetChannelGroupColor - PermissionSetChannelGroupPermittedVisibleRoles - - // Channel permissions - PermissionAddChannel - PermissionRemoveChannel - PermissionSetChannelName - PermissionSetChannelPermittedVisibleRoles - PermissionSetChannelPermittedSendMessageRoles - PermissionSetChannelPermittedReadHistoryRoles -) - type Hub struct { - Mu sync.RWMutex `json:"-"` - Id uuid.UUID `json:"id"` - CreatedAt time.Time `json:"createdAt"` - MessagesBuff []*Message `json:"-"` - NextBuffIdx uint32 `json:"-"` - HaveOverflowed bool `json:"-"` - Users map[uuid.UUID]*HubUser `json:"-"` - Roles map[uint8]*HubRole `json:"-"` - ChannelGroups map[uuid.UUID]*HubChannelGroup `json:"-"` - Creator uuid.UUID `json:"creator"` - Name string `json:"name"` - Color Rgba `json:"color"` - AllowUserColor bool `json:"allowUserColor"` + Mu sync.RWMutex `json:"-"` + Id uuid.UUID `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Users map[uuid.UUID]*HubUser `json:"-"` + GlobalRoles map[uint8]*HubGlobalRole `json:"-"` + ChannelGroupRoles map[uint8]*HubChannelGroupRole `json:"-"` + ChannelGroups map[uuid.UUID]*HubChannelGroup `json:"-"` + Creator uuid.UUID `json:"creator"` + Name string `json:"name"` + Color Rgba `json:"color"` + AllowUserColor bool `json:"allowUserColor"` } -func NewHub() *Hub { +func CreateHub() *Hub { return &Hub{ - Id: uuid.New(), - MessagesBuff: make([]*Message, config.MaxHubMsgCache), - Users: make(map[uuid.UUID]*HubUser), - Roles: make(map[uint8]*HubRole), - ChannelGroups: make(map[uuid.UUID]*HubChannelGroup), + Id: uuid.New(), + Users: make(map[uuid.UUID]*HubUser), + GlobalRoles: make(map[uint8]*HubGlobalRole), + ChannelGroupRoles: make(map[uint8]*HubChannelGroupRole), + ChannelGroups: make(map[uuid.UUID]*HubChannelGroup), } } @@ -205,35 +169,43 @@ type HubChannel struct { } type HubUser struct { - Id uuid.UUID `json:"id"` - Username string `json:"username"` - Roles []uint8 `json:"roles"` - CreatedAt time.Time `json:"createdAt"` + Id uuid.UUID `json:"id"` + Username string `json:"username"` + GlobalRoles []uint8 `json:"globalRoles"` + ChannelGroupRoles map[uint8]pairUuidHubChannelGroupRole `json:"ChannelGroupRoles"` + CreatedAt time.Time `json:"createdAt"` } -type HubRole struct { - Name string `json:"role"` - Id uint8 `json:"id"` - RolePermission RolePermission `json:"rolePermission"` - Color Rgba `json:"color"` +type HubGlobalRole struct { + Name string `json:"role"` + Id uint8 `json:"id"` + RolePermission permission.Global `json:"rolePermission"` + Color Rgba `json:"color"` } -func (h *HubRole) GrantPermission(r RolePermission) { +func (h *HubGlobalRole) GrantPermission(r permission.Global) { h.RolePermission |= r } -func (h *HubRole) GrantPermissions(rs []RolePermission) { - for _, r := range rs { - h.RolePermission |= r - } -} -func (h *HubRole) RevokePermission(r RolePermission) { +func (h *HubGlobalRole) RevokePermission(r permission.Global) { h.RolePermission &^= r } -func (h *HubRole) RevokePermissions(rs []RolePermission) { - for _, r := range rs { - h.RolePermission &^= r - } -} -func (h *HubRole) HasPermission(r RolePermission) bool { +func (h *HubGlobalRole) HasPermission(r permission.Global) bool { + return h.RolePermission&r != 0 +} + +type HubChannelGroupRole struct { + Name string `json:"role"` + Id uint8 `json:"id"` + RolePermission permission.ChannelGroup `json:"rolePermission"` + Color Rgba `json:"color"` +} + +func (h *HubChannelGroupRole) GrantPermission(r permission.ChannelGroup) { + h.RolePermission |= r +} +func (h *HubChannelGroupRole) RevokePermission(r permission.ChannelGroup) { + h.RolePermission &^= r +} +func (h *HubChannelGroupRole) HasPermission(r permission.ChannelGroup) bool { return h.RolePermission&r != 0 }