feat: add group management commands
Implemented roadmap #5: Group management commands with full CRUD. Features: - group create <name> [members] - Create new group with optional initial members - group get <uuid> - Display group details (uuid, name_hash, members, timestamps) - group update <uuid> <members> - Replace entire member list - group delete <uuid> - Delete group - group add-member <uuid> <user-uuid> - Add single member (incremental) - group remove-member <uuid> <user-uuid> - Remove single member (incremental) The add-member and remove-member commands fetch current members, modify the list, and update atomically - providing a better UX than replacing the entire list. Backend API endpoints used: - POST /api/groups - Create group - GET /api/groups/{uuid} - Get group details - PUT /api/groups/{uuid} - Update members - DELETE /api/groups/{uuid} - Delete group 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
311
cmd_group.go
Normal file
311
cmd_group.go
Normal file
@@ -0,0 +1,311 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// handleGroup dispatches group management sub-commands
|
||||
func (c *KVSClient) handleGroup(args []string) {
|
||||
if len(args) == 0 {
|
||||
fmt.Println(red("Usage: group <create|get|update|delete|add-member|remove-member> [options]"))
|
||||
return
|
||||
}
|
||||
|
||||
subCmd := args[0]
|
||||
switch subCmd {
|
||||
case "create":
|
||||
c.handleGroupCreate(args[1:])
|
||||
case "get":
|
||||
c.handleGroupGet(args[1:])
|
||||
case "update":
|
||||
c.handleGroupUpdate(args[1:])
|
||||
case "delete":
|
||||
c.handleGroupDelete(args[1:])
|
||||
case "add-member":
|
||||
c.handleGroupAddMember(args[1:])
|
||||
case "remove-member":
|
||||
c.handleGroupRemoveMember(args[1:])
|
||||
default:
|
||||
fmt.Println(red("Unknown group command:"), subCmd)
|
||||
fmt.Println("Available commands: create, get, update, delete, add-member, remove-member")
|
||||
}
|
||||
}
|
||||
|
||||
// handleGroupCreate creates a new group
|
||||
func (c *KVSClient) handleGroupCreate(args []string) {
|
||||
if len(args) < 1 {
|
||||
fmt.Println(red("Usage: group create <groupname> [member-uuid1,member-uuid2,...]"))
|
||||
return
|
||||
}
|
||||
|
||||
groupname := args[0]
|
||||
var members []string
|
||||
|
||||
// Parse optional members list
|
||||
if len(args) >= 2 {
|
||||
members = strings.Split(args[1], ",")
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"groupname": groupname,
|
||||
}
|
||||
if len(members) > 0 {
|
||||
payload["members"] = members
|
||||
}
|
||||
|
||||
respBody, status, err := c.doRequest("POST", "/api/groups", payload)
|
||||
if err != nil {
|
||||
fmt.Println(red("Error:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
fmt.Printf(red("Error creating group (status %d):\n"), status)
|
||||
fmt.Println(string(respBody))
|
||||
return
|
||||
}
|
||||
|
||||
var response struct {
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
if err := json.Unmarshal(respBody, &response); err != nil {
|
||||
fmt.Println(red("Error parsing response:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(green("Group created successfully"))
|
||||
fmt.Println(cyan("UUID:"), response.UUID)
|
||||
}
|
||||
|
||||
// handleGroupGet retrieves group details
|
||||
func (c *KVSClient) handleGroupGet(args []string) {
|
||||
if len(args) < 1 {
|
||||
fmt.Println(red("Usage: group get <group-uuid>"))
|
||||
return
|
||||
}
|
||||
|
||||
groupUUID := args[0]
|
||||
|
||||
respBody, status, err := c.doRequest("GET", "/api/groups/"+groupUUID, nil)
|
||||
if err != nil {
|
||||
fmt.Println(red("Error:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if status == http.StatusNotFound {
|
||||
fmt.Println(yellow("Group not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
fmt.Printf(red("Error getting group (status %d):\n"), status)
|
||||
fmt.Println(string(respBody))
|
||||
return
|
||||
}
|
||||
|
||||
var group Group
|
||||
if err := json.Unmarshal(respBody, &group); err != nil {
|
||||
fmt.Println(red("Error parsing response:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(cyan("Group Details:"))
|
||||
fmt.Printf(" UUID: %s\n", group.UUID)
|
||||
fmt.Printf(" Name Hash: %s\n", group.NameHash)
|
||||
fmt.Printf(" Members: %v\n", group.Members)
|
||||
fmt.Printf(" Created: %s\n", time.Unix(group.CreatedAt, 0).Format(time.RFC3339))
|
||||
fmt.Printf(" Updated: %s\n", time.Unix(group.UpdatedAt, 0).Format(time.RFC3339))
|
||||
}
|
||||
|
||||
// handleGroupUpdate replaces all members in a group
|
||||
func (c *KVSClient) handleGroupUpdate(args []string) {
|
||||
if len(args) < 2 {
|
||||
fmt.Println(red("Usage: group update <group-uuid> <member-uuid1,member-uuid2,...>"))
|
||||
fmt.Println("Note: This replaces ALL members. Use add-member/remove-member for incremental changes.")
|
||||
return
|
||||
}
|
||||
|
||||
groupUUID := args[0]
|
||||
members := strings.Split(args[1], ",")
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"members": members,
|
||||
}
|
||||
|
||||
respBody, status, err := c.doRequest("PUT", "/api/groups/"+groupUUID, payload)
|
||||
if err != nil {
|
||||
fmt.Println(red("Error:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if status == http.StatusNotFound {
|
||||
fmt.Println(yellow("Group not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
fmt.Printf(red("Error updating group (status %d):\n"), status)
|
||||
fmt.Println(string(respBody))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(green("Group updated successfully"))
|
||||
}
|
||||
|
||||
// handleGroupDelete deletes a group
|
||||
func (c *KVSClient) handleGroupDelete(args []string) {
|
||||
if len(args) < 1 {
|
||||
fmt.Println(red("Usage: group delete <group-uuid>"))
|
||||
return
|
||||
}
|
||||
|
||||
groupUUID := args[0]
|
||||
|
||||
respBody, status, err := c.doRequest("DELETE", "/api/groups/"+groupUUID, nil)
|
||||
if err != nil {
|
||||
fmt.Println(red("Error:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if status == http.StatusNotFound {
|
||||
fmt.Println(yellow("Group not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
fmt.Printf(red("Error deleting group (status %d):\n"), status)
|
||||
fmt.Println(string(respBody))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(green("Group deleted successfully"))
|
||||
}
|
||||
|
||||
// handleGroupAddMember adds a member to an existing group
|
||||
func (c *KVSClient) handleGroupAddMember(args []string) {
|
||||
if len(args) < 2 {
|
||||
fmt.Println(red("Usage: group add-member <group-uuid> <user-uuid>"))
|
||||
return
|
||||
}
|
||||
|
||||
groupUUID := args[0]
|
||||
userUUID := args[1]
|
||||
|
||||
// First, get current members
|
||||
respBody, status, err := c.doRequest("GET", "/api/groups/"+groupUUID, nil)
|
||||
if err != nil {
|
||||
fmt.Println(red("Error:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
fmt.Printf(red("Error getting group (status %d):\n"), status)
|
||||
fmt.Println(string(respBody))
|
||||
return
|
||||
}
|
||||
|
||||
var group Group
|
||||
if err := json.Unmarshal(respBody, &group); err != nil {
|
||||
fmt.Println(red("Error parsing response:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user is already a member
|
||||
for _, member := range group.Members {
|
||||
if member == userUUID {
|
||||
fmt.Println(yellow("User is already a member of this group"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add new member
|
||||
group.Members = append(group.Members, userUUID)
|
||||
|
||||
// Update group with new members list
|
||||
payload := map[string]interface{}{
|
||||
"members": group.Members,
|
||||
}
|
||||
|
||||
respBody, status, err = c.doRequest("PUT", "/api/groups/"+groupUUID, payload)
|
||||
if err != nil {
|
||||
fmt.Println(red("Error:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
fmt.Printf(red("Error updating group (status %d):\n"), status)
|
||||
fmt.Println(string(respBody))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(green("Member added successfully"))
|
||||
}
|
||||
|
||||
// handleGroupRemoveMember removes a member from an existing group
|
||||
func (c *KVSClient) handleGroupRemoveMember(args []string) {
|
||||
if len(args) < 2 {
|
||||
fmt.Println(red("Usage: group remove-member <group-uuid> <user-uuid>"))
|
||||
return
|
||||
}
|
||||
|
||||
groupUUID := args[0]
|
||||
userUUID := args[1]
|
||||
|
||||
// First, get current members
|
||||
respBody, status, err := c.doRequest("GET", "/api/groups/"+groupUUID, nil)
|
||||
if err != nil {
|
||||
fmt.Println(red("Error:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
fmt.Printf(red("Error getting group (status %d):\n"), status)
|
||||
fmt.Println(string(respBody))
|
||||
return
|
||||
}
|
||||
|
||||
var group Group
|
||||
if err := json.Unmarshal(respBody, &group); err != nil {
|
||||
fmt.Println(red("Error parsing response:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove member from list
|
||||
newMembers := []string{}
|
||||
found := false
|
||||
for _, member := range group.Members {
|
||||
if member != userUUID {
|
||||
newMembers = append(newMembers, member)
|
||||
} else {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
fmt.Println(yellow("User is not a member of this group"))
|
||||
return
|
||||
}
|
||||
|
||||
// Update group with new members list
|
||||
payload := map[string]interface{}{
|
||||
"members": newMembers,
|
||||
}
|
||||
|
||||
respBody, status, err = c.doRequest("PUT", "/api/groups/"+groupUUID, payload)
|
||||
if err != nil {
|
||||
fmt.Println(red("Error:"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if status != http.StatusOK {
|
||||
fmt.Printf(red("Error updating group (status %d):\n"), status)
|
||||
fmt.Println(string(respBody))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(green("Member removed successfully"))
|
||||
}
|
@@ -52,6 +52,14 @@ User Management:
|
||||
user get <uuid> - Get user details
|
||||
user create <nickname> - Create new user (admin only)
|
||||
|
||||
Group Management:
|
||||
group create <name> [members] - Create new group
|
||||
group get <uuid> - Get group details
|
||||
group update <uuid> <members> - Replace all group members
|
||||
group delete <uuid> - Delete group
|
||||
group add-member <uuid> <user-uuid> - Add member to group
|
||||
group remove-member <uuid> <user-uuid> - Remove member from group
|
||||
|
||||
System:
|
||||
help - Show this help
|
||||
exit, quit - Exit shell
|
||||
|
2
main.go
2
main.go
@@ -95,6 +95,8 @@ func main() {
|
||||
} else {
|
||||
client.handleUserList(args)
|
||||
}
|
||||
case "group":
|
||||
client.handleGroup(args)
|
||||
default:
|
||||
fmt.Println(red("Unknown command:"), cmd)
|
||||
fmt.Println("Type 'help' for available commands")
|
||||
|
8
utils.go
8
utils.go
@@ -119,6 +119,14 @@ var completer = readline.NewPrefixCompleter(
|
||||
readline.PcItem("get"),
|
||||
readline.PcItem("create"),
|
||||
),
|
||||
readline.PcItem("group",
|
||||
readline.PcItem("create"),
|
||||
readline.PcItem("get"),
|
||||
readline.PcItem("update"),
|
||||
readline.PcItem("delete"),
|
||||
readline.PcItem("add-member"),
|
||||
readline.PcItem("remove-member"),
|
||||
),
|
||||
readline.PcItem("help"),
|
||||
readline.PcItem("exit"),
|
||||
readline.PcItem("quit"),
|
||||
|
Reference in New Issue
Block a user