From b8690f5093a514fb9123ee2e4a615e83581f6e4f Mon Sep 17 00:00:00 2001 From: Sisi Date: Thu, 2 Apr 2026 14:00:21 +0200 Subject: [PATCH] add group color, owner manipulation --- convertions.go | 22 ++++- database.go | 20 +++++ http.go | 231 +++++++++++++++++++++++++++++-------------------- todo.txt | 4 - 4 files changed, 178 insertions(+), 99 deletions(-) diff --git a/convertions.go b/convertions.go index d9fc265..06195ed 100644 --- a/convertions.go +++ b/convertions.go @@ -1,8 +1,28 @@ package main -import "strconv" +import ( + "fmt" + "strconv" + "strings" +) func ConvertStringUint32(s string) (uint32, error) { v, err := strconv.ParseUint(s, 10, 32) return uint32(v), err } + +func ConvertStringToRgb(str string) ([3]uint8, error) { + parts := strings.SplitN(str, ",", 4) + if len(parts) != 3 { + return [3]uint8{}, fmt.Errorf("invalid rgb") + } + var rgb [3]uint8 + for i, p := range parts { + n, err := strconv.ParseUint(strings.TrimSpace(p), 10, 8) + if err != nil { + return [3]uint8{}, fmt.Errorf("invalid component %d: %w", i, err) + } + rgb[i] = uint8(n) + } + return rgb, nil +} diff --git a/database.go b/database.go index 3a43ea5..014bccd 100644 --- a/database.go +++ b/database.go @@ -261,3 +261,23 @@ func DbSetClientGroups(ctx context.Context, client *Client) error { } return rows.Err() } + +// DbSetGroupColor set group's color based on id +// return: error if not successful +func DbSetGroupColor(ctx context.Context, group *Group) error { + _, err := dbConn.Exec(ctx, ` + UPDATE chat_groups SET color_red = $1, color_green = $2, color_blue = $3 WHERE id = $4 + `, group.Color[0], group.Color[1], group.Color[2], group.Id) + + return err +} + +// DbSetGroupOwnerId set group's owner based on id +// return: error if not successful +func DbSetGroupOwnerId(ctx context.Context, group *Group) error { + _, err := dbConn.Exec(ctx, ` + UPDATE chat_groups SET owner_id = $1 WHERE id = $2 + `, group.OwnerId, group.Id) + + return err +} diff --git a/http.go b/http.go index 1ea053c..4e5d96e 100644 --- a/http.go +++ b/http.go @@ -4,7 +4,6 @@ import ( "context" "encoding/binary" json2 "encoding/json" - "fmt" "maps" "net/http" "slices" @@ -23,20 +22,69 @@ func isMethodAllowed(response *http.ResponseWriter, request *http.Request) bool return true } -func parseRgb(str string) ([3]uint8, error) { - parts := strings.SplitN(str, ",", 4) - if len(parts) != 3 { - return [3]uint8{}, fmt.Errorf("invalid rgb") +func getClient(ctx context.Context, token string) (*Client, error) { + clientId, err := TokenValidateGetId(token) + if err != nil { + return nil, err } - var rgb [3]uint8 - for i, p := range parts { - n, err := strconv.ParseUint(strings.TrimSpace(p), 10, 8) + + client, err := CacheGetClientById(clientId) + if err != nil { + client = &Client{Id: clientId} + err = DbSetClientById(ctx, client) if err != nil { - return [3]uint8{}, fmt.Errorf("invalid component %d: %w", i, err) + return nil, err } - rgb[i] = uint8(n) + CacheSaveClient(client) } - return rgb, nil + + return client, nil +} + +func getGroup(ctx context.Context, groupId uint32) (*Group, error) { + group, err := CacheGetGroup(groupId) + if err != nil { + group = &Group{Id: groupId} + err = DbSetGroupById(ctx, group) + if err != nil { + return nil, err + } + CacheSaveGroup(group) + } + return group, nil +} + +func isOwner(client *Client, group *Group) bool { + if group.OwnerId == client.Id { + return true + } + return false +} + +func getIfOwnerClientAndGroup(ctx context.Context, response *http.ResponseWriter, request *http.Request) (*Client, *Group, error) { + client, err := getClient(ctx, request.FormValue("token")) + if err != nil { + http.Error(*response, "invalid token", http.StatusUnauthorized) + return nil, nil, err + } + + affectedGroupId, err := ConvertStringUint32(request.FormValue("groupid")) + if err != nil { + http.Error(*response, "no such group", http.StatusUnauthorized) + return nil, nil, err + } + + group, err := getGroup(ctx, affectedGroupId) + if err != nil { + http.Error(*response, "no such group", http.StatusUnauthorized) + return nil, nil, err + } + + if !isOwner(client, group) { + http.Error(*response, "no such group", http.StatusUnauthorized) + return nil, nil, err + } + return client, group, nil } func HttpHandleNewUser(response http.ResponseWriter, request *http.Request) { @@ -56,7 +104,7 @@ func HttpHandleNewUser(response http.ResponseWriter, request *http.Request) { return } - color, err := parseRgb(request.FormValue("color")) + color, err := ConvertStringToRgb(request.FormValue("color")) if err != nil { http.Error(response, "bad color", http.StatusBadRequest) return @@ -158,7 +206,7 @@ func HttpHandeNewGroup(response http.ResponseWriter, request *http.Request) { } colorString := request.FormValue("color") - color, err := parseRgb(colorString) + color, err := ConvertStringToRgb(colorString) if err != nil { var ok bool color, ok = Colors[colorString] @@ -193,66 +241,15 @@ func HttpHandeNewGroup(response http.ResponseWriter, request *http.Request) { response.Write(groupIdBytes) } -func getClient(ctx context.Context, token string) (*Client, error) { - clientId, err := TokenValidateGetId(token) - if err != nil { - return nil, err - } - - client, err := CacheGetClientById(clientId) - if err != nil { - client = &Client{Id: clientId} - err = DbSetClientById(ctx, client) - if err != nil { - return nil, err - } - CacheSaveClient(client) - } - - return client, nil -} - -func getGroup(ctx context.Context, groupId uint32) (*Group, error) { - group, err := CacheGetGroup(groupId) - if err != nil { - group = &Group{Id: groupId} - err = DbSetGroupById(ctx, group) - if err != nil { - return nil, err - } - CacheSaveGroup(group) - } - return group, nil -} - func HttpHandleGroupAddClient(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } - token := request.FormValue("token") - clientId, err := TokenValidateGetId(token) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - affectedGroupId, err := ConvertStringUint32(request.FormValue("groupid")) - if err != nil { - http.Error(response, "no such group", http.StatusUnauthorized) - return - } - ctx := request.Context() - group, err := getGroup(ctx, affectedGroupId) + _, group, err := getIfOwnerClientAndGroup(ctx, &response, request) if err != nil { - http.Error(response, "no such group", http.StatusUnauthorized) - return - } - - if group.OwnerId != clientId { - http.Error(response, "no such group", http.StatusUnauthorized) return } @@ -310,29 +307,10 @@ func HttpHandleGroupRemoveClient(response http.ResponseWriter, request *http.Req return } - token := request.FormValue("token") - clientId, err := TokenValidateGetId(token) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - - affectedGroupId, err := ConvertStringUint32(request.FormValue("groupid")) - if err != nil { - http.Error(response, "no such group", http.StatusUnauthorized) - return - } - ctx := request.Context() - group, err := getGroup(ctx, affectedGroupId) + _, group, err := getIfOwnerClientAndGroup(ctx, &response, request) if err != nil { - http.Error(response, "no such group", http.StatusUnauthorized) - return - } - - if group.OwnerId != clientId { - http.Error(response, "no such group", http.StatusUnauthorized) return } @@ -372,13 +350,84 @@ func HttpHandleGroupRemoveClient(response http.ResponseWriter, request *http.Req response.Write([]byte(strconv.Itoa(count))) } +func HttpHandleGroupChangeColor(response http.ResponseWriter, request *http.Request) { + if !isMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + _, group, err := getIfOwnerClientAndGroup(ctx, &response, request) + if err != nil { + return + } + + color, err := ConvertStringToRgb(request.FormValue("color")) + if err != nil { + http.Error(response, "invalid color", http.StatusBadRequest) + return + } + + group.Color = color + err = DbSetGroupColor(ctx, group) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusAccepted) + response.Write([]byte("changed")) +} + +func HttpHandleGroupChangeOwner(response http.ResponseWriter, request *http.Request) { + if !isMethodAllowed(&response, request) { + return + } + + ctx := request.Context() + client, group, err := getIfOwnerClientAndGroup(ctx, &response, request) + if err != nil { + return + } + + newOwnerName := request.FormValue("newOwner") + + newOwner, err := CacheGetClientByName(newOwnerName) + if err != nil { + newOwner = &Client{Name: newOwnerName} + err = DbSetClientByName(ctx, newOwner) + if err != nil { + http.Error(response, "client not in group", http.StatusBadRequest) + return + } + + CacheSaveClient(client) + } + + _, ok := group.Clients[newOwner.Id] + if !ok { + http.Error(response, "client not in group", http.StatusBadRequest) + return + } + + group.OwnerId = newOwner.Id + err = DbSetGroupOwnerId(ctx, group) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + return + } + + response.WriteHeader(http.StatusAccepted) + response.Write([]byte("changed")) +} + func HttpHandleNewMessage(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } - token := request.FormValue("token") - if token == "" { + ctx := request.Context() + client, err := getClient(ctx, request.FormValue("token")) + if err != nil { http.Error(response, "invalid token", http.StatusUnauthorized) return } @@ -400,13 +449,7 @@ func HttpHandleNewMessage(response http.ResponseWriter, request *http.Request) { return } - clientId, err := TokenValidateGetId(token) - if err != nil { - http.Error(response, "invalid token", http.StatusUnauthorized) - return - } - ctx := request.Context() - err = WsSendToGroup(ctx, targetId, clientId, content) + err = WsSendToGroup(ctx, targetId, client.Id, content) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return diff --git a/todo.txt b/todo.txt index 41a0a9b..2fb63eb 100644 --- a/todo.txt +++ b/todo.txt @@ -1,6 +1,2 @@ -group manipulation: - - color - - owner - debug gui chat history \ No newline at end of file