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>
312 lines
7.1 KiB
Go
312 lines
7.1 KiB
Go
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"))
|
|
}
|