package main import ( "context" json2 "encoding/json" "fmt" "maps" "net/http" "slices" "strconv" "strings" "time" "golang.org/x/crypto/bcrypt" ) func isMethodAllowed(response *http.ResponseWriter, request *http.Request) bool { if request.Method != http.MethodPost { http.Error(*response, "POST only", http.StatusMethodNotAllowed) return false } return true } 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 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 HttpHandleNewClient(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } username := request.FormValue("username") if len(username) < 4 { http.Error(response, "no or short username", http.StatusBadRequest) return } password := request.FormValue("password") if len(password) < 8 { http.Error(response, "no or short password", http.StatusBadRequest) return } color, err := ConvertStringToRgb(request.FormValue("color")) if err != nil { http.Error(response, "bad color", http.StatusBadRequest) return } hashedPassword, err := PasswordHash(password) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } newClient := &Client{ Name: username, PasswordHash: hashedPassword, Color: color, CreatedAt: time.Now(), } ctx := request.Context() err = DbSaveClientWithoutGroups(ctx, newClient) if err != nil { http.Error(response, "name taken", http.StatusUnauthorized) return } response.WriteHeader(http.StatusCreated) response.Write([]byte("created")) } func HttpHandleNewToken(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } username := request.FormValue("username") if len(username) < 4 { http.Error(response, "no or short username", http.StatusBadRequest) return } password := request.FormValue("password") if len(password) < 8 { http.Error(response, "no or short password", http.StatusBadRequest) return } var ( client *Client err error ctx = request.Context() ) client, err = CacheGetClientByName(username) if err != nil { client = &Client{Name: username} err := DbSetClientByName(ctx, client) if err != nil { http.Error(response, "bad login1", http.StatusUnauthorized) return } CacheSaveClient(client) } err = bcrypt.CompareHashAndPassword([]byte(client.PasswordHash), []byte(password)) if err != nil { http.Error(response, "bad login2", http.StatusUnauthorized) return } token, err := TokenCreate(client.Id) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } response.WriteHeader(http.StatusAccepted) response.Write([]byte(token)) } func HttpHandeGroupCreate(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } ctx := request.Context() client, err := getClient(ctx, request.FormValue("token")) if err != nil { http.Error(response, "invalid token", http.StatusUnauthorized) return } name := request.FormValue("name") if name == "" { name = "Best group ever" } colorString := request.FormValue("color") color, err := ConvertStringToRgb(colorString) group := Group{ Name: name, CreatedAt: time.Now(), OwnerId: client.Id, CreatorId: client.Id, Color: color, Clients: map[uint32]struct{}{client.Id: {}}, } enableClientColors := request.FormValue("enableClientColors") if enableClientColors == "1" { group.EnableClientColors = true } err = DbSaveGroupWithoutClients(ctx, &group) if err != nil { http.Error(response, err.Error(), http.StatusInternalServerError) return } response.WriteHeader(http.StatusCreated) fmt.Fprintf(response, "%d", group.Id) } func HttpHandleGroupRemove(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } ctx := request.Context() _, group, err := getIfOwnerClientAndGroup(ctx, &response, request) if err != nil { return } err = DbDeleteGroup(ctx, group) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } response.WriteHeader(http.StatusAccepted) } func HttpHandleGroupAddClient(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } ctx := request.Context() _, group, err := getIfOwnerClientAndGroup(ctx, &response, request) if err != nil { return } clientsString := request.FormValue("clients") var remainingUsersCount = int(MaxClientsInGroup) - len(group.Clients) if remainingUsersCount < 1 { http.Error(response, "max users", http.StatusUnauthorized) return } clientsStringSlice := strings.SplitN(clientsString, ",", remainingUsersCount+1) if len(clientsStringSlice) == 0 { http.Error(response, "no users to add", http.StatusBadRequest) return } var ids [MaxClientsInGroup]uint32 var idx uint32 = 0 for _, s := range clientsStringSlice { if idx >= MaxClientsInGroup { break } id, err := ConvertStringUint32(strings.TrimSpace(s)) if err != nil { continue } ids[idx] = id idx++ } if idx == 0 { http.Error(response, "no valid users", http.StatusBadRequest) return } err = DbAddClientsToGroup(ctx, group.Id, &ids) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } for i := uint32(0); i < idx; i++ { group.Clients[ids[i]] = struct{}{} } response.WriteHeader(http.StatusAccepted) } func HttpHandleGroupRemoveClient(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } ctx := request.Context() _, group, err := getIfOwnerClientAndGroup(ctx, &response, request) if err != nil { return } clientsString := request.FormValue("clients") clientsStringSlice := strings.SplitN(clientsString, ",", int(MaxClientsInGroup)+1) var ids [MaxClientsInGroup]uint32 var idx uint32 = 0 for _, s := range clientsStringSlice { if idx >= MaxClientsInGroup { break } id, err := ConvertStringUint32(strings.TrimSpace(s)) if err != nil { continue } ids[idx] = id idx++ } if idx == 0 { http.Error(response, "no valid users", http.StatusBadRequest) return } count, err := DbRemoveClientsFromGroup(ctx, group.Id, &ids) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } for i := uint32(0); i < idx; i++ { delete(group.Clients, ids[i]) } response.WriteHeader(http.StatusAccepted) 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) } func HttpHandleNewMessage(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } ctx := request.Context() client, err := getClient(ctx, request.FormValue("token")) if err != nil { http.Error(response, "invalid token", http.StatusUnauthorized) return } targetStr := request.FormValue("subject") if targetStr == "" { http.Error(response, "invalid subject", http.StatusBadRequest) return } targetId, err := ConvertStringUint32(targetStr) if err != nil { http.Error(response, "invalid subject", http.StatusBadRequest) return } content := request.FormValue("content") if content == "" { http.Error(response, "invalid content", http.StatusBadRequest) return } err = WsSendToGroup(ctx, targetId, client.Id, content) if err != nil { http.Error(response, err.Error(), http.StatusBadRequest) return } response.WriteHeader(http.StatusAccepted) } func HttpHandleGroupsGetWithoutMembers(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } ctx := request.Context() client, err := getClient(ctx, request.FormValue("token")) if err != nil { http.Error(response, "invalid token", http.StatusUnauthorized) return } groups := make([]GroupNoMembers, 0, len(client.Groups)) for groupId := range client.Groups { group, err := getGroup(ctx, groupId) if err != nil { continue } groups = append(groups, GroupNoMembers{ Id: groupId, Name: group.Name, CreatedAt: group.CreatedAt, CreatorId: group.CreatorId, OwnerId: group.OwnerId, Color: group.Color, EnableClientsColors: group.EnableClientColors, }) } json, err := json2.Marshal(groups) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } response.WriteHeader(http.StatusAccepted) response.Write(json) } func HttpHandleGroupMembersGet(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return } ctx := request.Context() client, err := getClient(ctx, request.FormValue("token")) if err != nil { http.Error(response, "invalid token", http.StatusUnauthorized) return } groupStr := request.FormValue("group") groupId, err := ConvertStringUint32(groupStr) if err != nil { http.Error(response, "invalid group", http.StatusBadRequest) return } _, ok := client.Groups[groupId] if !ok { http.Error(response, "no such group", http.StatusUnauthorized) return } group, err := getGroup(ctx, groupId) if err != nil { http.Error(response, "no such group", http.StatusUnauthorized) return } groupMembers := slices.Collect(maps.Keys(group.Clients)) json, err := json2.Marshal(groupMembers) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return } response.WriteHeader(http.StatusAccepted) response.Write(json) }