diff --git a/main.go b/main.go index f1fe0aa..9e86db8 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "io" "net/http" "os" + "strconv" "strings" "time" @@ -313,6 +314,134 @@ func (c *KVSClient) handleDelete(args []string) { } } +// handleMeta is the dispatcher for "meta" sub-commands +func (c *KVSClient) handleMeta(args []string) { + if len(args) == 0 { + fmt.Println(red("Usage: meta [options]")) + return + } + + subCmd := args[0] + switch subCmd { + case "get": + c.handleMetaGet(args[1:]) + case "set": + c.handleMetaSet(args[1:]) + default: + fmt.Println(red("Unknown meta command:"), subCmd) + fmt.Println("Available commands: get, set") + } +} + +// handleMetaGet fetches and displays metadata for a key +func (c *KVSClient) handleMetaGet(args []string) { + if len(args) < 1 { + fmt.Println(red("Usage: meta get ")) + return + } + key := args[0] + + respBody, status, err := c.doRequest("GET", "/kv/"+key+"/metadata", nil) + if err != nil { + fmt.Println(red("Error:"), err) + return + } + + if status == http.StatusNotFound { + fmt.Println(yellow("No metadata found for key:"), key) + return + } + + if status != http.StatusOK { + fmt.Printf(red("Error getting metadata (status %d):\n"), status) + fmt.Println(string(respBody)) + return + } + + // Struct to parse the metadata response + type MetaResponse struct { + OwnerUUID string `json:"owner_uuid"` + GroupUUID string `json:"group_uuid"` + Permissions int `json:"permissions"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + } + + var meta MetaResponse + if err := json.Unmarshal(respBody, &meta); err != nil { + fmt.Println(red("Error parsing metadata response:"), err) + return + } + + fmt.Println(cyan("Metadata for key:"), key) + fmt.Printf(" Owner UUID: %s\n", meta.OwnerUUID) + fmt.Printf(" Group UUID: %s\n", meta.GroupUUID) + fmt.Printf(" Permissions: %d\n", meta.Permissions) + fmt.Printf(" Created At: %s\n", time.Unix(meta.CreatedAt, 0).Format(time.RFC3339)) + fmt.Printf(" Updated At: %s\n", time.Unix(meta.UpdatedAt, 0).Format(time.RFC3339)) +} + +// handleMetaSet updates metadata for a key using flags +func (c *KVSClient) handleMetaSet(args []string) { + if len(args) < 3 { + fmt.Println(red("Usage: meta set --owner | --group | --permissions ")) + return + } + key := args[0] + + // Use a map to build the JSON payload for partial updates + payload := make(map[string]interface{}) + + // Parse command-line flags + for i := 1; i < len(args); i++ { + switch args[i] { + case "--owner": + if i+1 < len(args) { + payload["owner_uuid"] = args[i+1] + i++ // Skip the value + } + case "--group": + if i+1 < len(args) { + payload["group_uuid"] = args[i+1] + i++ // Skip the value + } + case "--permissions": + if i+1 < len(args) { + perms, err := strconv.Atoi(args[i+1]) + if err != nil { + fmt.Println(red("Invalid permissions value:"), args[i+1]) + return + } + payload["permissions"] = perms + i++ // Skip the value + } + } + } + + if len(payload) == 0 { + fmt.Println(red("No valid metadata fields provided to set.")) + return + } + + respBody, status, err := c.doRequest("PUT", "/kv/"+key+"/metadata", payload) + if err != nil { + fmt.Println(red("Error:"), err) + return + } + + if status != http.StatusOK { + fmt.Printf(red("Error setting metadata (status %d):\n"), status) + fmt.Println(string(respBody)) + return + } + + fmt.Println(green("Metadata updated successfully for key:"), key) + // The response body contains the full updated metadata, so we can print it for confirmation + var pretty bytes.Buffer + json.Indent(&pretty, respBody, "", " ") + fmt.Println(pretty.String()) +} + func (c *KVSClient) handleMembers(args []string) { respBody, status, err := c.doRequest("GET", "/members/", nil) if err != nil { @@ -408,6 +537,43 @@ func (c *KVSClient) handleUserGet(args []string) { fmt.Printf(" Updated: %s\n", time.Unix(user.UpdatedAt, 0).Format(time.RFC3339)) } +func (c *KVSClient) handleUserCreate(args []string) { + if len(args) < 1 { + fmt.Println(red("Usage: user create ")) + return + } + + // The request body requires a JSON object like {"nickname": "..."} + requestPayload := map[string]string{ + "nickname": args[0], + } + + respBody, status, err := c.doRequest("POST", "/api/users", requestPayload) + if err != nil { + fmt.Println(red("Error:"), err) + return + } + + // The backend returns 200 OK on success + if status != http.StatusOK { + fmt.Printf(red("Error creating user (status %d):\n"), status) + fmt.Println(string(respBody)) + return + } + + // Parse the successful response to get the new user's UUID + var response struct { + UUID string `json:"uuid"` + } + if err := json.Unmarshal(respBody, &response); err != nil { + fmt.Println(red("Error parsing successful response:"), err) + return + } + + fmt.Println(green("User created successfully")) + fmt.Println(cyan("UUID:"), response.UUID) +} + func (c *KVSClient) handleHelp(args []string) { help := ` KVS Interactive Shell - Available Commands: @@ -425,6 +591,11 @@ Key-Value Operations: put - Store JSON value at key delete - Delete key +Resource Metadata: + meta get - Get metadata (owner, group) for a key + meta set [flags] - Set metadata for a key + Flags: --owner , --group , --permissions + Cluster Management: members - List cluster members health - Check service health @@ -500,14 +671,25 @@ func main() { client.handlePut(args) case "delete": client.handleDelete(args) + case "meta": + client.handleMeta(args) case "members": client.handleMembers(args) case "health": client.handleHealth(args) case "user": - if len(args) > 0 && args[0] == "get" { - client.handleUserGet(args[1:]) + if len(args) > 0 { + switch args[0] { + case "get": + client.handleUserGet(args[1:]) + case "create": + client.handleUserCreate(args[1:]) + default: + fmt.Println(red("Unknown user command:"), args[0]) + fmt.Println("Type 'help' for available commands") + } } else { + // This was the original behavior, which notes that listing users is not implemented. client.handleUserList(args) } default: @@ -529,6 +711,14 @@ var completer = readline.NewPrefixCompleter( readline.PcItem("get"), readline.PcItem("put"), readline.PcItem("delete"), + readline.PcItem("meta", + readline.PcItem("get"), + readline.PcItem("set", + readline.PcItem("--owner"), + readline.PcItem("--group"), + readline.PcItem("--permissions"), + ), + ), readline.PcItem("members"), readline.PcItem("health"), readline.PcItem("user",