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 (
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/google/uuid v1.6.0 // indirect
+4 -1
View File
@@ -4,7 +4,9 @@ import (
"context"
"log"
"net/http"
"strconv"
"go-socket/packages/config"
"go-socket/packages/httpRequest"
"go-socket/packages/minio"
"go-socket/packages/postgresql"
@@ -21,6 +23,7 @@ func withCORS(h http.HandlerFunc) http.HandlerFunc {
func main() {
ctx := context.Background()
config.LoadConfFile()
postgresql.Init(ctx)
minio.Init(ctx)
@@ -57,5 +60,5 @@ func main() {
http.HandleFunc("GET /ws", wsServer.ServeWsConnection)
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
}
func StringToRgba(str string) (*types.Rgba, error) {
func StringToRgba(str string) (types.Rgba, error) {
parts := strings.SplitN(str, ",", 5)
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 {
n, err := strconv.ParseUint(strings.TrimSpace(p), 10, 8)
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)
}
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])
}
func Uint32ToRgba(v uint32) *types.Rgba {
return &types.Rgba{uint8(v >> 24), uint8(v >> 16), uint8(v >> 8), uint8(v)}
func Uint32ToRgba(v uint32) types.Rgba {
return types.Rgba{uint8(v >> 24), uint8(v >> 16), uint8(v >> 8), uint8(v)}
}
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/WsEventType"
"go-socket/packages/cache"
"go-socket/packages/config"
"go-socket/packages/convertions"
"go-socket/packages/globals"
"go-socket/packages/postgresql"
"go-socket/packages/types"
"go-socket/packages/wsServer"
@@ -158,7 +158,7 @@ func HandleUserGetConnectionMessages(response http.ResponseWriter, request *http
messagesCap, err := convertions.StringToUint32(request.URL.Query().Get("messages"))
if err != nil {
messagesCap = globals.MaxDirectMsgCache
messagesCap = config.MaxDirectMsgCache
}
buffer, bufferSize := conn.GetSortedMessagesBuff()
+3 -3
View File
@@ -5,8 +5,8 @@ import (
"net/http"
"strings"
"go-socket/packages/config"
"go-socket/packages/convertions"
"go-socket/packages/globals"
"go-socket/packages/minio"
)
@@ -22,9 +22,9 @@ func HandleAttachmentFileUpload(response http.ResponseWriter, request *http.Requ
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)
return
}
+5 -5
View File
@@ -5,7 +5,7 @@ import (
"net/http"
"strings"
"go-socket/packages/globals"
"go-socket/packages/config"
)
type postType uint8
@@ -21,13 +21,13 @@ func validCheckWithResponseOnFail(response *http.ResponseWriter, request *http.R
var maxSize int64
switch pt {
case file:
maxSize = int64(globals.MaxRequestWithFileBytes)
maxSize = int64(config.MaxRequestWithFileBytes)
case avatar:
maxSize = int64(globals.MaxRequestWithAvatarBytes)
maxSize = int64(config.MaxRequestWithAvatarBytes)
case profileBg:
maxSize = int64(globals.MaxRequestWithProfileBgBytes)
maxSize = int64(config.MaxRequestWithProfileBgBytes)
default:
maxSize = int64(globals.MaxRequestBytes)
maxSize = int64(config.MaxRequestBytes)
}
if request.ContentLength > maxSize {
+5 -5
View File
@@ -5,8 +5,8 @@ import (
"net/http"
"time"
"go-socket/packages/config"
"go-socket/packages/convertions"
"go-socket/packages/globals"
"go-socket/packages/minio"
"go-socket/packages/cache"
@@ -202,9 +202,9 @@ func HandleUserModAvatar(response http.ResponseWriter, request *http.Request) {
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)
return
}
@@ -265,9 +265,9 @@ func HandleUserModProfileBg(response http.ResponseWriter, request *http.Request)
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)
return
}
+11 -11
View File
@@ -8,7 +8,7 @@ import (
"strconv"
"time"
"go-socket/packages/globals"
"go-socket/packages/config"
"github.com/google/uuid"
"github.com/minio/minio-go/v7"
@@ -68,15 +68,15 @@ func Init(ctx context.Context) {
panic(err)
}
exists, err := minClient.BucketExists(ctx, globals.FileStorageBucketName)
exists, err := minClient.BucketExists(ctx, config.FileStorageBucketName)
if err != nil {
panic(err)
}
if !exists {
err = minClient.MakeBucket(ctx, globals.FileStorageBucketName, minio.MakeBucketOptions{})
err = minClient.MakeBucket(ctx, config.FileStorageBucketName, minio.MakeBucketOptions{})
if err != nil {
exists, checkErr := minClient.BucketExists(ctx, globals.FileStorageBucketName)
exists, checkErr := minClient.BucketExists(ctx, config.FileStorageBucketName)
if checkErr != nil || !exists {
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 {
opt := minio.PutObjectOptions{
ContentType: contentType,
PartSize: globals.FileProcessingPartBytes,
NumThreads: globals.FileProcessingThreads,
PartSize: config.FileProcessingPartBytes,
NumThreads: config.FileProcessingThreads,
UserMetadata: metadata,
}
opt.SetMatchETagExcept("*")
_, err := minClient.PutObject(ctx, globals.FileStorageBucketName, key, body, size, opt)
_, err := minClient.PutObject(ctx, config.FileStorageBucketName, key, body, size, opt)
return err
}
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 {
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 {
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 {
_, err := minClient.StatObject(ctx, globals.FileStorageBucketName, key, minio.StatObjectOptions{})
_, err := minClient.StatObject(ctx, config.FileStorageBucketName, key, minio.StatObjectOptions{})
return err == nil
}
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
}
+12 -1
View File
@@ -76,6 +76,9 @@ func Init(ctx context.Context) {
CREATE TABLE IF NOT EXISTS hubs ()
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
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()
`)
if err != nil {
@@ -118,7 +121,7 @@ func Init(ctx context.Context) {
_, err = dbConn.Exec(ctx, `
CREATE TABLE IF NOT EXISTS hub_user_roles ()
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 {
panic(err)
@@ -129,6 +132,7 @@ func Init(ctx context.Context) {
hub_id UUID PRIMARY KEY NOT NULL REFERENCES hubs(id) ON DELETE CASCADE,
role_id SMALLINT NOT NULL DEFAULT 0,
name TEXT NOT NULL,
permissions INTEGER NOT NULL DEFAULT 0,
rgba BIGINT NOT NULL DEFAULT 0 CHECK (rgba BETWEEN 0 AND 4294967295),
`)
if err != nil {
@@ -316,3 +320,10 @@ func ConnectionGetMessagesBefore(ctx context.Context, before time.Time, connecti
}
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/WsEventType"
"go-socket/packages/globals"
"go-socket/packages/config"
"github.com/coder/websocket"
"github.com/google/uuid"
@@ -17,11 +17,11 @@ import (
type Rgba [4]uint8
type Sha256Hash [sha256.Size]byte
func (r Rgba) GetRandom() *Rgba {
func (r Rgba) GetRandom() Rgba {
for i := range r {
r[i] = uint8(rand.IntN(256))
}
return &r
return r
}
type User struct {
@@ -36,7 +36,7 @@ type User struct {
WsConn *websocket.Conn `json:"-"`
Id uuid.UUID `json:"-"`
Connections map[uuid.UUID]*Connection `json:"-"`
Color *Rgba `json:"color"`
Color Rgba `json:"color"`
}
type UserProfileUpdateList struct {
@@ -48,31 +48,31 @@ type UserProfileUpdateList struct {
}
type Connection struct {
Mu sync.RWMutex `json:"-"`
Id uuid.UUID `json:"id"`
CreatedAt time.Time `json:"createdAt"`
MessagesBuff [globals.MaxDirectMsgCache]*Message `json:"-"`
NextBuffIdx uint32 `json:"-"`
RequestorId uuid.UUID `json:"requestorId"`
RecipientId uuid.UUID `json:"recipientId"`
UserWantingToElevate uuid.UUID `json:"userWantingToElevate"` // TODO add to database
HaveOverflowed bool `json:"-"`
State ConnectionState.ConnectionState `json:"state"`
Mu sync.RWMutex `json:"-"`
Id uuid.UUID `json:"id"`
CreatedAt time.Time `json:"createdAt"`
MessagesBuff [config.MaxDirectMsgCache]*Message `json:"-"`
NextBuffIdx uint32 `json:"-"`
RequestorId uuid.UUID `json:"requestorId"`
RecipientId uuid.UUID `json:"recipientId"`
UserWantingToElevate uuid.UUID `json:"userWantingToElevate"` // TODO add to database
HaveOverflowed bool `json:"-"`
State ConnectionState.ConnectionState `json:"state"`
}
func (conn *Connection) AddMessageToBuff(message *Message) {
conn.Mu.Lock()
defer conn.Mu.Unlock()
conn.MessagesBuff[conn.NextBuffIdx%globals.MaxDirectMsgCache] = message
conn.MessagesBuff[conn.NextBuffIdx%config.MaxDirectMsgCache] = message
conn.NextBuffIdx++
if conn.NextBuffIdx >= globals.MaxDirectMsgCache {
if conn.NextBuffIdx >= config.MaxDirectMsgCache {
conn.HaveOverflowed = true
}
}
// GetSortedMessagesBuff returns arr, length
func (conn *Connection) GetSortedMessagesBuff() (*[globals.MaxDirectMsgCache]*Message, uint32) {
func (conn *Connection) GetSortedMessagesBuff() (*[config.MaxDirectMsgCache]*Message, uint32) {
conn.Mu.RLock()
defer conn.Mu.RUnlock()
@@ -80,11 +80,11 @@ func (conn *Connection) GetSortedMessagesBuff() (*[globals.MaxDirectMsgCache]*Me
return &conn.MessagesBuff, conn.NextBuffIdx
}
sorted := new([globals.MaxDirectMsgCache]*Message)
for i := uint32(0); i < globals.MaxDirectMsgCache; i++ {
sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%globals.MaxDirectMsgCache]
sorted := new([config.MaxDirectMsgCache]*Message)
for i := uint32(0); i < config.MaxDirectMsgCache; i++ {
sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%config.MaxDirectMsgCache]
}
return sorted, globals.MaxDirectMsgCache
return sorted, config.MaxDirectMsgCache
}
type ConnectionStatusSetData struct {
@@ -138,6 +138,7 @@ const (
PermissionChangeRoleName
PermissionChangeRoleColor
PermissionChangeRolePermissions
PermissionOnlySelfRoleRemove
// Channel group permissions
PermissionAddChannelGroup
@@ -156,21 +157,24 @@ const (
)
type Hub struct {
Mu sync.RWMutex `json:"-"`
Id uuid.UUID `json:"id"`
CreatedAt time.Time `json:"createdAt"`
MessagesBuff [globals.MaxHubMsgCache]*Message `json:"-"`
NextBuffIdx uint32 `json:"-"`
HaveOverflowed bool `json:"-"`
Users map[uuid.UUID]*HubUser `json:"-"`
Roles map[uint8]*HubRole `json:"-"`
Creator uuid.UUID `json:"creator"`
Mu sync.RWMutex `json:"-"`
Id uuid.UUID `json:"id"`
CreatedAt time.Time `json:"createdAt"`
MessagesBuff [config.MaxHubMsgCache]*Message `json:"-"`
NextBuffIdx uint32 `json:"-"`
HaveOverflowed bool `json:"-"`
Users map[uuid.UUID]*HubUser `json:"-"`
Roles map[uint8]*HubRole `json:"-"`
Creator uuid.UUID `json:"creator"`
Name string `json:"name"`
Color Rgba `json:"color"`
AllowUserColor bool `json:"allowUserColor"`
}
type HubChannelGroup struct {
Id uuid.UUID `json:"id"`
Name string `json:"name"`
Color string `json:"color"`
Color Rgba `json:"color"`
}
type HubChannel struct {
@@ -190,6 +194,7 @@ type HubRole struct {
Name string `json:"role"`
Id uint8 `json:"id"`
RolePermission RolePermission `json:"rolePermission"`
Color Rgba `json:"color"`
}
func (h HubRole) GrantPermission(r RolePermission) {