Files
kvs-sh/cmd_group.go
ryyst 33201227ca 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>
2025-10-05 23:50:27 +03:00

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"))
}