diff --git a/packages/cache/cache.go b/packages/cache/cache.go index 13f4e90..0334cbb 100644 --- a/packages/cache/cache.go +++ b/packages/cache/cache.go @@ -13,6 +13,7 @@ var ( Mu sync.RWMutex Users = make(map[uuid.UUID]*types.User) connectionsUnreadMessages = make(map[uuid.UUID]map[uuid.UUID]uint32) + hubs = make(map[uuid.UUID]*types.Hub) ) func GetUserById(id uuid.UUID) (*types.User, error) { @@ -25,7 +26,6 @@ func GetUserById(id uuid.UUID) (*types.User, error) { } return user, nil } - func GetUserByName(name string) (*types.User, error) { Mu.RLock() defer Mu.RUnlock() @@ -37,14 +37,12 @@ func GetUserByName(name string) (*types.User, error) { } return nil, fmt.Errorf("user %s not found", name) } - func SaveUser(user *types.User) { Mu.Lock() defer Mu.Unlock() Users[user.Id] = user } - func DeleteUser(id uuid.UUID) { Mu.Lock() defer Mu.Unlock() @@ -64,7 +62,6 @@ func AddConnection(a, b *types.User, conn *types.Connection) { second.Mu.Unlock() first.Mu.Unlock() } - func DeleteConnection(a, b *types.User, id uuid.UUID) { first, second := a, b if a.Id.String() > b.Id.String() { @@ -77,7 +74,6 @@ func DeleteConnection(a, b *types.User, id uuid.UUID) { second.Mu.Unlock() first.Mu.Unlock() } - func GetConnection(user *types.User, id uuid.UUID) (*types.Connection, bool) { user.Mu.RLock() defer user.Mu.RUnlock() @@ -98,7 +94,6 @@ func IncrementConnectionsUnreadMessages(userId uuid.UUID, connId uuid.UUID) { } connectionsUnreadMessages[connId][userId]++ } - func DeallocateConnectionsUnreadMessages(userId uuid.UUID, connId uuid.UUID) { Mu.Lock() defer Mu.Unlock() @@ -107,7 +102,6 @@ func DeallocateConnectionsUnreadMessages(userId uuid.UUID, connId uuid.UUID) { delete(connectionsUnreadMessages[connId], userId) } } - func GetConnectionsUnreadMessages(userId uuid.UUID, connId uuid.UUID) (uint32, bool) { Mu.RLock() defer Mu.RUnlock() @@ -117,3 +111,22 @@ func GetConnectionsUnreadMessages(userId uuid.UUID, connId uuid.UUID) (uint32, b } return connectionsUnreadMessages[connId][userId], true } + +func SaveHub(hub *types.Hub) { + Mu.Lock() + defer Mu.Unlock() + + hubs[hub.Id] = hub +} +func DeleteHub(hub *types.Hub) { + Mu.Lock() + defer Mu.Unlock() + + delete(hubs, hub.Id) +} +func GetHubById(id uuid.UUID) (*types.Hub, bool) { + Mu.RLock() + defer Mu.RUnlock() + hub, ok := hubs[id] + return hub, ok +} diff --git a/packages/config/config.go b/packages/config/config.go index 413d60e..f71bdc0 100644 --- a/packages/config/config.go +++ b/packages/config/config.go @@ -9,14 +9,10 @@ import ( "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 + MaxDirectMsgCache uint32 = 32 + MaxHubMsgCache uint32 = 32 FileStorageBucketName string = "communicator" MaxRequestBytes uint32 = 4 << 10 MaxRequestWithFileBytes uint32 = 1 << 30 @@ -54,6 +50,8 @@ func LoadConfFile() { } Port = cfg.Port + MaxDirectMsgCache = cfg.MaxDirectMsgCache + MaxHubMsgCache = cfg.MaxHubMsgCache FileStorageBucketName = cfg.FileStorageBucketName MaxRequestBytes = cfg.MaxRequestBytes MaxRequestWithFileBytes = cfg.MaxRequestWithFileBytes diff --git a/packages/httpRequest/connectionsAndDms.go b/packages/httpRequest/connectionsAndDms.go index 7d3b57b..a12678f 100644 --- a/packages/httpRequest/connectionsAndDms.go +++ b/packages/httpRequest/connectionsAndDms.go @@ -241,12 +241,11 @@ func HandleUserNewConnection(response http.ResponseWriter, request *http.Request } requestor.Mu.RUnlock() - connection := &types.Connection{ - CreatedAt: time.Now(), - RequestorId: requestor.Id, - RecipientId: recipient.Id, - State: ConnectionState.Stranger, - } + connection := types.NewConnection() + connection.CreatedAt = time.Now() + connection.RequestorId = requestor.Id + connection.RecipientId = recipient.Id + connection.State = ConnectionState.Stranger err = postgresql.ConnectionSave(ctx, connection) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) diff --git a/packages/httpRequest/hubs.go b/packages/httpRequest/hubs.go new file mode 100644 index 0000000..b80846a --- /dev/null +++ b/packages/httpRequest/hubs.go @@ -0,0 +1,45 @@ +package httpRequest + +import ( + "net/http" + "time" + + "go-socket/packages/cache" + "go-socket/packages/postgresql" + "go-socket/packages/types" +) + +func HandleHubCreate(response http.ResponseWriter, request *http.Request) { + if !validCheckWithResponseOnFail(&response, request, normal) { + return + } + + ctx := request.Context() + user, err := getUserByToken(ctx, request.Header.Get("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + hubName := request.Header.Get("hubname") + if hubName == "" { + http.Error(response, "hub name is required", http.StatusBadRequest) + return + } + + hub := types.NewHub() + hub.Name = hubName + hub.Creator = user.Id + hub.Color = types.Rgba{}.GetRandom() + hub.CreatedAt = time.Now() + + err = postgresql.HubSave(ctx, hub) + if err != nil { + http.Error(response, "failed to save hub", http.StatusInternalServerError) + return + } + cache.SaveHub(hub) + + response.WriteHeader(http.StatusCreated) + response.Write([]byte(hub.Id.String())) +} diff --git a/packages/postgresql/postgresql.go b/packages/postgresql/postgresql.go index d3603e2..1341f24 100644 --- a/packages/postgresql/postgresql.go +++ b/packages/postgresql/postgresql.go @@ -263,7 +263,7 @@ func ConnectionsGetBelongingToUser(ctx context.Context, user *types.User) error } for rows.Next() { - conn := &types.Connection{} + conn := types.NewConnection() if err = rows.Scan(&conn.Id, &conn.RequestorId, &conn.RecipientId, &conn.State, &conn.CreatedAt); err != nil { return fmt.Errorf("scanning connection row: %w", err) } @@ -321,9 +321,182 @@ 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) +func HubSave(ctx context.Context, hub *types.Hub) error { + return dbConn.QueryRow(ctx, ` + INSERT INTO hubs (creator_id, name, allow_user_color, rgba, created_at) VALUES ($1, $2, $3, $4, $5) + RETURNING id + `, hub.Creator, hub.Name, hub.AllowUserColor, convertions.RgbaToUint32(hub.Color), hub.CreatedAt).Scan(&hub.Id) +} + +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.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, userId uuid.UUID, roleId uint8) error { + _, err := dbConn.Exec(ctx, ` + INSERT INTO hub_user_roles (user_id, role_id) VALUES ($1, $2) + `, userId, roleId) + return err +} + +func HubUserRoleRemove(ctx context.Context, userId uuid.UUID, roleId uint8) error { + _, err := dbConn.Exec(ctx, ` + DELETE FROM hub_user_roles WHERE user_id = $1 AND role_id = $2 + `, 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 { + return dbConn.QueryRow(ctx, ` + INSERT INTO hub_channel_groups (hub_id, name, rgba) VALUES ($1, $2, $3) + RETURNING channel_group_id + `, hubId, cg.Name, convertions.RgbaToUint32(cg.Color)).Scan(&cg.Id) +} + +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) VALUES ($1, $2, $3) + RETURNING channel_id + `, hubId, ch.Name, ch.ParentGroupId).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 b69507b..f955177 100644 --- a/packages/types/types.go +++ b/packages/types/types.go @@ -48,43 +48,51 @@ type UserProfileUpdateList struct { } type Connection struct { - 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"` + Mu sync.RWMutex `json:"-"` + Id uuid.UUID `json:"id"` + CreatedAt time.Time `json:"createdAt"` + MessagesBuff []*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 NewConnection() *Connection { + return &Connection{ + MessagesBuff: make([]*Message, config.MaxDirectMsgCache), + } } func (conn *Connection) AddMessageToBuff(message *Message) { conn.Mu.Lock() defer conn.Mu.Unlock() - conn.MessagesBuff[conn.NextBuffIdx%config.MaxDirectMsgCache] = message + size := uint32(len(conn.MessagesBuff)) + conn.MessagesBuff[conn.NextBuffIdx%size] = message conn.NextBuffIdx++ - if conn.NextBuffIdx >= config.MaxDirectMsgCache { + if conn.NextBuffIdx >= size { conn.HaveOverflowed = true } } -// GetSortedMessagesBuff returns arr, length -func (conn *Connection) GetSortedMessagesBuff() (*[config.MaxDirectMsgCache]*Message, uint32) { +// GetSortedMessagesBuff returns slice and its valid length. +func (conn *Connection) GetSortedMessagesBuff() ([]*Message, uint32) { conn.Mu.RLock() defer conn.Mu.RUnlock() + size := uint32(len(conn.MessagesBuff)) if !conn.HaveOverflowed { - return &conn.MessagesBuff, conn.NextBuffIdx + return conn.MessagesBuff, conn.NextBuffIdx } - sorted := new([config.MaxDirectMsgCache]*Message) - for i := uint32(0); i < config.MaxDirectMsgCache; i++ { - sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%config.MaxDirectMsgCache] + sorted := make([]*Message, size) + for i := uint32(0); i < size; i++ { + sorted[i] = conn.MessagesBuff[(conn.NextBuffIdx+i)%size] } - return sorted, config.MaxDirectMsgCache + return sorted, size } type ConnectionStatusSetData struct { @@ -157,18 +165,26 @@ const ( ) type Hub struct { - 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"` + 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:"-"` + Creator uuid.UUID `json:"creator"` + Name string `json:"name"` + Color Rgba `json:"color"` + AllowUserColor bool `json:"allowUserColor"` +} + +func NewHub() *Hub { + return &Hub{ + MessagesBuff: make([]*Message, config.MaxHubMsgCache), + Users: make(map[uuid.UUID]*HubUser), + Roles: make(map[uint8]*HubRole), + } } type HubChannelGroup struct {