revork structures for hub

This commit is contained in:
2026-04-25 16:04:53 +02:00
parent 635139aad2
commit df1e969d49
9 changed files with 165 additions and 157 deletions
-58
View File
@@ -1,58 +0,0 @@
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
)
+11 -9
View File
@@ -12,28 +12,30 @@ import (
var ( var (
Port uint32 = 8080 Port uint32 = 8080
MaxDirectMsgCache uint32 = 32 MaxDirectMsgCache uint32 = 32
MaxHubMsgCache uint32 = 32 MaxHubChannelMsgCache uint32 = 16
MaxUserHubRoles uint8 = 64
FileStorageBucketName string = "communicator" FileStorageBucketName string = "communicator"
MaxRequestBytes uint32 = 4 << 10 MaxRequestBytes uint32 = 4 << 10
MaxRequestWithFileBytes uint32 = 1 << 30 MaxRequestWithFileBytes uint32 = 1 << 30
MaxRequestWithAvatarBytes uint = 1 << 20 MaxRequestWithAvatarBytes uint32 = 1 << 20
MaxRequestWithProfileBgBytes uint = 4 << 20 MaxRequestWithProfileBgBytes uint32 = 4 << 20
FileProcessingPartBytes uint64 = 12 << 20 FileProcessingPartBytes uint64 = 12 << 20
FileProcessingThreads uint = 3 FileProcessingThreads uint32 = 3
FileDownloadLinkTtl time.Duration = 24 * time.Hour FileDownloadLinkTtl time.Duration = 24 * time.Hour
) )
type configFile struct { type configFile struct {
Port uint32 `toml:"port"` Port uint32 `toml:"port"`
MaxDirectMsgCache uint32 `toml:"max_direct_messages_cache"` MaxDirectMsgCache uint32 `toml:"max_direct_messages_cache"`
MaxHubMsgCache uint32 `toml:"max_hub_msg_cache"` MaxHubChannelMsgCache uint32 `toml:"max_hub_channel_msg_cache"`
MaxUserHubRoles uint8 `toml:"max_hub_roles"`
FileStorageBucketName string `toml:"file_storage_bucket_name"` FileStorageBucketName string `toml:"file_storage_bucket_name"`
MaxRequestBytes uint32 `toml:"max_request_bytes"` MaxRequestBytes uint32 `toml:"max_request_bytes"`
MaxRequestWithFileBytes uint32 `toml:"max_request_with_file_bytes"` MaxRequestWithFileBytes uint32 `toml:"max_request_with_file_bytes"`
MaxRequestWithAvatarBytes uint `toml:"max_request_with_avatar_bytes"` MaxRequestWithAvatarBytes uint32 `toml:"max_request_with_avatar_bytes"`
MaxRequestWithProfileBgBytes uint `toml:"max_request_with_profile_bg_bytes"` MaxRequestWithProfileBgBytes uint32 `toml:"max_request_with_profile_bg_bytes"`
FileProcessingPartBytes uint64 `toml:"file_processing_part_bytes"` FileProcessingPartBytes uint64 `toml:"file_processing_part_bytes"`
FileProcessingThreads uint `toml:"file_processing_threads"` FileProcessingThreads uint32 `toml:"file_processing_threads"`
FileDownloadLinkTtl time.Duration `toml:"file_download_link_ttl"` FileDownloadLinkTtl time.Duration `toml:"file_download_link_ttl"`
} }
@@ -51,7 +53,7 @@ func LoadConfFile() {
Port = cfg.Port Port = cfg.Port
MaxDirectMsgCache = cfg.MaxDirectMsgCache MaxDirectMsgCache = cfg.MaxDirectMsgCache
MaxHubMsgCache = cfg.MaxHubMsgCache MaxHubChannelMsgCache = cfg.MaxHubChannelMsgCache
FileStorageBucketName = cfg.FileStorageBucketName FileStorageBucketName = cfg.FileStorageBucketName
MaxRequestBytes = cfg.MaxRequestBytes MaxRequestBytes = cfg.MaxRequestBytes
MaxRequestWithFileBytes = cfg.MaxRequestWithFileBytes MaxRequestWithFileBytes = cfg.MaxRequestWithFileBytes
+1 -1
View File
@@ -241,7 +241,7 @@ func HandleUserNewConnection(response http.ResponseWriter, request *http.Request
} }
requestor.Mu.RUnlock() requestor.Mu.RUnlock()
connection := types.CreateConn() connection := types.NewConn()
connection.CreatedAt = time.Now() connection.CreatedAt = time.Now()
connection.RequestorId = requestor.Id connection.RequestorId = requestor.Id
connection.RecipientId = recipient.Id connection.RecipientId = recipient.Id
+4 -4
View File
@@ -84,12 +84,12 @@ func HandleGetUserAvatar(response http.ResponseWriter, request *http.Request) {
return return
} }
if target.Avatar == "" { if target.AvatarUrl == "" {
http.Error(response, "no avatar", http.StatusNotFound) http.Error(response, "no avatar", http.StatusNotFound)
return return
} }
url, _, err := minio.GetDownloadUrlAndMetadata(ctx, target.Avatar) url, _, err := minio.GetDownloadUrlAndMetadata(ctx, target.AvatarUrl)
if err != nil { if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError) http.Error(response, "internal server error", http.StatusInternalServerError)
return return
@@ -122,12 +122,12 @@ func HandleGetUserProfileBg(response http.ResponseWriter, request *http.Request)
return return
} }
if target.ProfileBg == "" { if target.ProfileBgUrl == "" {
http.Error(response, "no profile background", http.StatusNotFound) http.Error(response, "no profile background", http.StatusNotFound)
return return
} }
url, _, err := minio.GetDownloadUrlAndMetadata(ctx, target.ProfileBg) url, _, err := minio.GetDownloadUrlAndMetadata(ctx, target.ProfileBgUrl)
if err != nil { if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError) http.Error(response, "internal server error", http.StatusInternalServerError)
return return
+5 -5
View File
@@ -62,11 +62,11 @@ func HandleHubCreate(response http.ResponseWriter, request *http.Request) {
} }
hub.ChannelGroups[rootGrp.Id] = rootGrp hub.ChannelGroups[rootGrp.Id] = rootGrp
rootRole := &types.HubGlobalRole{ rootRole := &types.HubRole{
Id: 0, Id: 0,
Name: "root", Name: "root",
Color: types.Rgba{255, 0, 0, 255}, Color: types.Rgba{255, 0, 0, 255},
RolePermission: ^permission.Global(0), Permissions: ^permission.Global(0),
} }
hub.GlobalRoles[rootRole.Id] = rootRole hub.GlobalRoles[rootRole.Id] = rootRole
+6 -6
View File
@@ -222,8 +222,8 @@ func HandleUserModAvatar(response http.ResponseWriter, request *http.Request) {
return return
} }
if user.Avatar != "" { if user.AvatarUrl != "" {
err = minio.Delete(ctx, user.Avatar) err = minio.Delete(ctx, user.AvatarUrl)
if err != nil { if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError) http.Error(response, "internal server error", http.StatusInternalServerError)
return return
@@ -243,7 +243,7 @@ func HandleUserModAvatar(response http.ResponseWriter, request *http.Request) {
return return
} }
user.Avatar = key user.AvatarUrl = key
err = postgresql.UserUpdateProfile(ctx, user, types.UserProfileUpdateList{Avatar: true}) err = postgresql.UserUpdateProfile(ctx, user, types.UserProfileUpdateList{Avatar: true})
if err != nil { if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError) http.Error(response, "internal server error", http.StatusInternalServerError)
@@ -285,8 +285,8 @@ func HandleUserModProfileBg(response http.ResponseWriter, request *http.Request)
return return
} }
if user.ProfileBg != "" { if user.ProfileBgUrl != "" {
err = minio.Delete(ctx, user.ProfileBg) err = minio.Delete(ctx, user.ProfileBgUrl)
if err != nil { if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError) http.Error(response, "internal server error", http.StatusInternalServerError)
return return
@@ -306,7 +306,7 @@ func HandleUserModProfileBg(response http.ResponseWriter, request *http.Request)
return return
} }
user.ProfileBg = key user.ProfileBgUrl = key
err = postgresql.UserUpdateProfile(ctx, user, types.UserProfileUpdateList{ProfileBg: true}) err = postgresql.UserUpdateProfile(ctx, user, types.UserProfileUpdateList{ProfileBg: true})
if err != nil { if err != nil {
http.Error(response, "internal server error", http.StatusInternalServerError) http.Error(response, "internal server error", http.StatusInternalServerError)
+5 -6
View File
@@ -95,7 +95,7 @@ func UserGetStandardInfoByName(ctx context.Context, user *types.User) error {
var rgba int64 var rgba int64
err := dbConn.QueryRow(ctx, ` err := dbConn.QueryRow(ctx, `
SELECT id, name, pass_hash, COALESCE(pronouns, ''), rgba, created_at, COALESCE(avatar, ''), COALESCE(profile_bg, '') FROM users WHERE name = $1 SELECT id, name, pass_hash, COALESCE(pronouns, ''), rgba, created_at, COALESCE(avatar, ''), COALESCE(profile_bg, '') FROM users WHERE name = $1
`, user.Name).Scan(&user.Id, &user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt, &user.Avatar, &user.ProfileBg) `, user.Name).Scan(&user.Id, &user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt, &user.AvatarUrl, &user.ProfileBgUrl)
if err == nil { if err == nil {
user.Color = convertions.Uint32ToRgba(uint32(rgba)) user.Color = convertions.Uint32ToRgba(uint32(rgba))
} }
@@ -106,7 +106,7 @@ func UserGetById(ctx context.Context, user *types.User) error {
var rgba int64 var rgba int64
err := dbConn.QueryRow(ctx, ` err := dbConn.QueryRow(ctx, `
SELECT name, pass_hash, COALESCE(pronouns, ''), rgba, created_at, COALESCE(avatar, ''), COALESCE(profile_bg, '') FROM users WHERE id = $1 SELECT name, pass_hash, COALESCE(pronouns, ''), rgba, created_at, COALESCE(avatar, ''), COALESCE(profile_bg, '') FROM users WHERE id = $1
`, user.Id).Scan(&user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt, &user.Avatar, &user.ProfileBg) `, user.Id).Scan(&user.Name, &user.PasswordHash, &user.Pronouns, &rgba, &user.CreatedAt, &user.AvatarUrl, &user.ProfileBgUrl)
if err == nil { if err == nil {
user.Color = convertions.Uint32ToRgba(uint32(rgba)) user.Color = convertions.Uint32ToRgba(uint32(rgba))
} }
@@ -135,12 +135,12 @@ func UserUpdateProfile(ctx context.Context, user *types.User, updateList types.U
} }
if updateList.Avatar { if updateList.Avatar {
setClauses = append(setClauses, fmt.Sprintf("avatar = $%d", argIdx)) setClauses = append(setClauses, fmt.Sprintf("avatar = $%d", argIdx))
args = append(args, user.Avatar) args = append(args, user.AvatarUrl)
argIdx++ argIdx++
} }
if updateList.ProfileBg { if updateList.ProfileBg {
setClauses = append(setClauses, fmt.Sprintf("profile_bg = $%d", argIdx)) setClauses = append(setClauses, fmt.Sprintf("profile_bg = $%d", argIdx))
args = append(args, user.ProfileBg) args = append(args, user.ProfileBgUrl)
argIdx++ argIdx++
} }
@@ -197,7 +197,7 @@ func ConnectionsGetBelongingToUser(ctx context.Context, user *types.User) error
} }
for rows.Next() { for rows.Next() {
conn := types.CreateConn() conn := types.NewConn()
if err = rows.Scan(&conn.Id, &conn.RequestorId, &conn.RecipientId, &conn.State, &conn.CreatedAt); err != nil { if err = rows.Scan(&conn.Id, &conn.RequestorId, &conn.RecipientId, &conn.State, &conn.CreatedAt); err != nil {
return fmt.Errorf("scanning connection row: %w", err) return fmt.Errorf("scanning connection row: %w", err)
} }
@@ -254,4 +254,3 @@ func ConnectionGetMessagesBefore(ctx context.Context, before time.Time, connecti
} }
return messages, rows.Err() return messages, rows.Err()
} }
+132 -68
View File
@@ -2,7 +2,6 @@ package types
import ( import (
"crypto/sha256" "crypto/sha256"
"go-socket/packages/Enums/permission"
"math/rand/v2" "math/rand/v2"
"sync" "sync"
"time" "time"
@@ -25,18 +24,13 @@ func (r Rgba) GetRandom() Rgba {
return r return r
} }
type pairUuidHubChannelGroupRole struct {
first uuid.UUID
second *HubChannelGroupRole
}
type User struct { type User struct {
Mu sync.RWMutex `json:"-"` Mu sync.RWMutex `json:"-"`
Name string `json:"name"` Name string `json:"name"`
Pronouns string `json:"pronouns"` Pronouns string `json:"pronouns"`
Description string `json:"description"` Description string `json:"description"`
Avatar string `json:"avatar"` AvatarUrl string `json:"avatarUrl"`
ProfileBg string `json:"profileBackground"` ProfileBgUrl string `json:"profileBackgroundUrl"`
PasswordHash string `json:"-"` PasswordHash string `json:"-"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
WsConn *websocket.Conn `json:"-"` WsConn *websocket.Conn `json:"-"`
@@ -66,12 +60,11 @@ type Connection struct {
State ConnectionState.ConnectionState `json:"state"` State ConnectionState.ConnectionState `json:"state"`
} }
func CreateConn() *Connection { func NewConn() *Connection {
return &Connection{ return &Connection{
MessagesBuff: make([]*Message, config.MaxDirectMsgCache), MessagesBuff: make([]*Message, config.MaxDirectMsgCache),
} }
} }
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()
@@ -130,82 +123,153 @@ type WsAuthMessage struct {
Error string `json:"error"` Error string `json:"error"`
} }
type Permissions uint32
const (
// Hub permissions
PermissionSetHubName Permissions = 1 << iota
PermissionSetHubIcon
PermissionSetHubBg
PermissionSetHubColor
PermissionRemoveHub
PermissionSetUserColorAllowed
// User permissions
PermissionAddUser
PermissionRemoveUser
PermissionRenameUser
PermissionMuteUser
// Role permissions
PermissionAddRole
PermissionRemoveRole
PermissionChangeRoleName
PermissionChangeRoleColor
PermissionChangeRoleGlobals
PermissionOnlySelfRoleRemove
// Channel group permissions
PermissionAddChannelGroup
PermissionRemoveChannelGroup
PermissionSetChannelGroupName
PermissionSetChannelGroupColor
PermissionSetChannelGroupPermittedVisibleRoles
// Channel permissions
PermissionAddChannel
PermissionRemoveChannel
PermissionSetChannelName
PermissionSetChannelIcon
PermissionSetChannelPermittedVisibleRoles
PermissionSetChannelPermittedSendMessageRoles
PermissionSetChannelPermittedReadHistoryRoles
)
type Hub struct { type Hub struct {
Mu sync.RWMutex `json:"-"` Mu sync.RWMutex `json:"-"`
Id uuid.UUID `json:"id"` Roles []*HubRole `json:"-"`
CreatedAt time.Time `json:"createdAt"` Users []*HubUser `json:"-"`
Users map[uuid.UUID]*HubUser `json:"-"` Groups []*HubGroup `json:"-"`
GlobalRoles map[uint8]*HubGlobalRole `json:"-"` Channels []*HubChannel `json:"-"`
ChannelGroupRoles map[uint8]*HubChannelGroupRole `json:"-"` Name string `json:"name"`
ChannelGroups map[uuid.UUID]*HubChannelGroup `json:"-"` IconUrl string `json:"iconUrl"`
Creator uuid.UUID `json:"creator"` BgUrl string `json:"bgUrl"`
Name string `json:"name"` Id uuid.UUID `json:"id"`
Color Rgba `json:"color"` Color Rgba `json:"color"`
AllowUserColor bool `json:"allowUserColor"` UserColorAllowed bool `json:"userColorAllowed"`
} }
func CreateHub() *Hub { func NewHub() *Hub {
return &Hub{ return &Hub{
Id: uuid.New(), Roles: make([]*HubRole, 0, 255),
Users: make(map[uuid.UUID]*HubUser), Users: make([]*HubUser, 0, 255),
GlobalRoles: make(map[uint8]*HubGlobalRole), Groups: make([]*HubGroup, 0, 255),
ChannelGroupRoles: make(map[uint8]*HubChannelGroupRole), Channels: make([]*HubChannel, 0, 255),
ChannelGroups: make(map[uuid.UUID]*HubChannelGroup),
} }
} }
type HubChannelGroup struct { type HubRole struct {
Id uuid.UUID `json:"id"` Name string `json:"role"`
Name string `json:"name"` CreatedAt time.Time `json:"createdAt"`
Color Rgba `json:"color"` Permissions Permissions `json:"permissions"`
Position uint8 `json:"position"` Color Rgba `json:"color"`
Id uint8 `json:"id"`
BoundedGroup uint8 `json:"boundedGroup"` // BoundedGroup 0 for global
} }
type HubChannel struct { func (h *HubRole) GrantPermission(r Permissions) {
Id uuid.UUID `json:"id"` h.Permissions |= r
Name uuid.UUID `json:"name"` }
ParentGroupId uuid.UUID `json:"parentGroupId"` func (h *HubRole) RevokePermission(r Permissions) {
Position uint8 `json:"position"` h.Permissions &^= r
}
func (h *HubRole) HasPermission(r Permissions) bool {
return h.Permissions&r != 0
}
type HubGroup struct {
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
Color Rgba `json:"color"`
Id uint8 `json:"id"` // Id 0 for unused
} }
type HubUser struct { type HubUser struct {
Id uuid.UUID `json:"id"` Mu sync.RWMutex `json:"mu"`
Username string `json:"username"` Roles []*HubRole `json:"roles"`
GlobalRoles []uint8 `json:"globalRoles"` Name string `json:"name"`
ChannelGroupRoles map[uint8]pairUuidHubChannelGroupRole `json:"ChannelGroupRoles"` OriginalId uuid.UUID `json:"originalId"`
CreatedAt time.Time `json:"createdAt"` IsMuted bool `json:"isMuted"`
} }
type HubGlobalRole struct { func NewHubUser() *HubUser {
Name string `json:"role"` return &HubUser{
Id uint8 `json:"id"` Roles: make([]*HubRole, 0, config.MaxUserHubRoles),
RolePermission permission.Global `json:"rolePermission"` }
Color Rgba `json:"color"`
} }
func (h *HubGlobalRole) GrantPermission(r permission.Global) { type HubChannel struct {
h.RolePermission |= r Mu sync.RWMutex `json:"-"`
} MessagesBuff []*Message `json:"-"`
func (h *HubGlobalRole) RevokePermission(r permission.Global) { Name string `json:"name"`
h.RolePermission &^= r Description string `json:"description"`
} IconUrl string `json:"iconUrl"`
func (h *HubGlobalRole) HasPermission(r permission.Global) bool { CreatedAt time.Time `json:"createdAt"`
return h.RolePermission&r != 0 NextBuffIdx uint32 `json:"-"`
Id uint8 `json:"id"`
HaveOverflowed bool `json:"-"`
} }
type HubChannelGroupRole struct { func NewHubChannel() *HubChannel {
Name string `json:"role"` return &HubChannel{
Id uint8 `json:"id"` MessagesBuff: make([]*Message, config.MaxHubChannelMsgCache),
RolePermission permission.ChannelGroup `json:"rolePermission"` }
Color Rgba `json:"color"` }
func (conn *HubChannel) AddMessageToBuff(message *Message) {
conn.Mu.Lock()
defer conn.Mu.Unlock()
size := uint32(len(conn.MessagesBuff))
conn.MessagesBuff[conn.NextBuffIdx%size] = message
conn.NextBuffIdx++
if conn.NextBuffIdx >= size {
conn.HaveOverflowed = true
}
} }
func (h *HubChannelGroupRole) GrantPermission(r permission.ChannelGroup) { // GetSortedMessagesBuff returns slice and its valid length.
h.RolePermission |= r func (conn *HubChannel) GetSortedMessagesBuff() ([]*Message, uint32) {
} conn.Mu.RLock()
func (h *HubChannelGroupRole) RevokePermission(r permission.ChannelGroup) { defer conn.Mu.RUnlock()
h.RolePermission &^= r
} size := uint32(len(conn.MessagesBuff))
func (h *HubChannelGroupRole) HasPermission(r permission.ChannelGroup) bool { if !conn.HaveOverflowed {
return h.RolePermission&r != 0 return conn.MessagesBuff, conn.NextBuffIdx
}
sorted := make([]*Message, size)
for i := uint32(0); i < size; i++ {
sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%size]
}
return sorted, size
} }
+1
View File
@@ -1,6 +1,7 @@
when user not ws connected collect count of unread messages for each conn (add db table in future) when user not ws connected collect count of unread messages for each conn (add db table in future)
add hubs add hubs
check when mutex needed
user banners user banners