feat(cli): enhance user and hook management with new commands and flags
This commit is contained in:
@@ -1,16 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/config"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/models"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/storage"
|
||||
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// hooksCmd is the parent command for hook management
|
||||
var hookUser string
|
||||
|
||||
var hooksCmd = &cobra.Command{
|
||||
Use: "hooks",
|
||||
Short: "Manage webhooks",
|
||||
@@ -40,7 +47,7 @@ var hooksAddCmd = &cobra.Command{
|
||||
|
||||
var hooksRemoveCmd = &cobra.Command{
|
||||
Use: "remove <hook_id>",
|
||||
Short: "Remove a hook",
|
||||
Short: "Remove a hook by ID",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
client := NewClient(cliConfig)
|
||||
@@ -49,17 +56,31 @@ var hooksRemoveCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
hooksAddCmd.Flags().StringVarP(&hookUser, "user", "u", "", "Owner username for DB mode (default: first admin)")
|
||||
hooksCmd.AddCommand(hooksListCmd)
|
||||
hooksCmd.AddCommand(hooksAddCmd)
|
||||
hooksCmd.AddCommand(hooksRemoveCmd)
|
||||
}
|
||||
|
||||
func listHooks(client *Client) {
|
||||
if serverAvailable(client) {
|
||||
listHooksHTTP(client)
|
||||
} else {
|
||||
fmt.Println("[server unavailable, reading from database]")
|
||||
if !tryInitDB() {
|
||||
fmt.Println("Error: server unreachable and no database config found. Use --server-config to specify config path.")
|
||||
return
|
||||
}
|
||||
listHooksDB()
|
||||
}
|
||||
}
|
||||
|
||||
func listHooksHTTP(client *Client) {
|
||||
resp, err := client.Get("/api/hooks")
|
||||
checkError(err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
var hooks []config.Hook
|
||||
var hooks []map[string]interface{}
|
||||
checkError(decodeJSON(resp, &hooks))
|
||||
|
||||
if len(hooks) == 0 {
|
||||
@@ -67,98 +88,153 @@ func listHooks(client *Client) {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Configured hooks (%d):\n\n", len(hooks))
|
||||
for _, hook := range hooks {
|
||||
status := "inactive"
|
||||
if hook.Active {
|
||||
status = "active"
|
||||
}
|
||||
fmt.Printf("ID: %s\n", hook.ID)
|
||||
fmt.Printf("Name: %s\n", hook.Name)
|
||||
fmt.Printf("URL: %s\n", hook.URL)
|
||||
fmt.Printf("Method: %s\n", hook.Method)
|
||||
fmt.Printf("Status: %s\n", status)
|
||||
if len(hook.Events) > 0 {
|
||||
fmt.Printf("Events: %v\n", hook.Events)
|
||||
} else {
|
||||
fmt.Printf("Events: all (no filter)\n")
|
||||
}
|
||||
if hook.Description != "" {
|
||||
fmt.Printf("Description: %s\n", hook.Description)
|
||||
}
|
||||
fmt.Println()
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tNAME\tURL\tMETHOD\tACTIVE")
|
||||
for _, h := range hooks {
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n",
|
||||
h["id"], h["name"], h["url"], h["method"], h["active"])
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func listHooksDB() {
|
||||
var hooks []models.ModelPublicHook
|
||||
err := storage.DB.NewSelect().Model(&hooks).OrderExpr("created_at ASC").Scan(context.Background())
|
||||
checkError(err)
|
||||
|
||||
if len(hooks) == 0 {
|
||||
fmt.Println("No hooks configured")
|
||||
return
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tNAME\tURL\tMETHOD\tACTIVE\tEVENTS")
|
||||
for _, h := range hooks {
|
||||
active := "yes"
|
||||
if !h.Active {
|
||||
active = "no"
|
||||
}
|
||||
events := parseEventsJSON(h.Events.String())
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
h.ID.String(),
|
||||
h.Name.String(),
|
||||
h.URL.String(),
|
||||
h.Method.String(),
|
||||
active,
|
||||
events,
|
||||
)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func addHook(client *Client) {
|
||||
var hook config.Hook
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
name := promptRequired("Hook Name")
|
||||
url := promptRequired("Webhook URL")
|
||||
method := promptLine("HTTP Method", "POST")
|
||||
|
||||
fmt.Print("Hook ID: ")
|
||||
if _, err := fmt.Scanln(&hook.ID); err != nil {
|
||||
checkError(fmt.Errorf("error reading hook ID: %v", err))
|
||||
}
|
||||
|
||||
fmt.Print("Hook Name: ")
|
||||
if _, err := fmt.Scanln(&hook.Name); err != nil {
|
||||
checkError(fmt.Errorf("error reading hook name: %v", err))
|
||||
}
|
||||
|
||||
fmt.Print("Webhook URL: ")
|
||||
if _, err := fmt.Scanln(&hook.URL); err != nil {
|
||||
checkError(fmt.Errorf("error reading webhook URL: %v", err))
|
||||
}
|
||||
|
||||
fmt.Print("HTTP Method (POST): ")
|
||||
if _, err := fmt.Scanln(&hook.Method); err == nil {
|
||||
// Successfully read input
|
||||
fmt.Printf("Selected Method %s", hook.Method)
|
||||
}
|
||||
if hook.Method == "" {
|
||||
hook.Method = "POST"
|
||||
}
|
||||
|
||||
// Prompt for events with helpful examples
|
||||
fmt.Println("\nAvailable events:")
|
||||
fmt.Println(" WhatsApp: whatsapp.connected, whatsapp.disconnected, whatsapp.qr.code")
|
||||
fmt.Println(" Messages: message.received, message.sent, message.delivered, message.read")
|
||||
fmt.Println(" Hooks: hook.triggered, hook.success, hook.failed")
|
||||
fmt.Print("\nEvents (comma-separated, or press Enter for all): ")
|
||||
fmt.Println(" whatsapp.connected, whatsapp.disconnected, whatsapp.qr.code")
|
||||
fmt.Println(" message.received, message.sent, message.delivered, message.read")
|
||||
fmt.Println(" hook.triggered, hook.success, hook.failed")
|
||||
eventsRaw := promptLine("\nEvents (comma-separated, or Enter for all)", "")
|
||||
description := promptLine("Description (optional)", "")
|
||||
|
||||
scanner.Scan()
|
||||
eventsInput := strings.TrimSpace(scanner.Text())
|
||||
|
||||
if eventsInput != "" {
|
||||
// Split by comma and trim whitespace
|
||||
eventsList := strings.Split(eventsInput, ",")
|
||||
hook.Events = make([]string, 0, len(eventsList))
|
||||
for _, event := range eventsList {
|
||||
trimmed := strings.TrimSpace(event)
|
||||
if trimmed != "" {
|
||||
hook.Events = append(hook.Events, trimmed)
|
||||
var events []string
|
||||
if eventsRaw != "" {
|
||||
for _, e := range strings.Split(eventsRaw, ",") {
|
||||
if t := strings.TrimSpace(e); t != "" {
|
||||
events = append(events, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print("\nDescription (optional): ")
|
||||
scanner.Scan()
|
||||
hook.Description = strings.TrimSpace(scanner.Text())
|
||||
if serverAvailable(client) {
|
||||
addHookHTTP(client, name, url, method, description, events)
|
||||
} else {
|
||||
fmt.Println("[server unavailable, writing to database]")
|
||||
if !tryInitDB() {
|
||||
fmt.Println("Error: server unreachable and no database config found. Use --server-config to specify config path.")
|
||||
return
|
||||
}
|
||||
addHookDB(name, url, method, description, events)
|
||||
}
|
||||
}
|
||||
|
||||
hook.Active = true
|
||||
|
||||
resp, err := client.Post("/api/hooks/add", hook)
|
||||
func addHookHTTP(client *Client, name, url, method, description string, events []string) {
|
||||
payload := map[string]interface{}{
|
||||
"name": name,
|
||||
"url": url,
|
||||
"method": method,
|
||||
"description": description,
|
||||
"events": events,
|
||||
"active": true,
|
||||
}
|
||||
resp, err := client.Post("/api/hooks/add", payload)
|
||||
checkError(err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
fmt.Println("Hook added successfully")
|
||||
}
|
||||
|
||||
func removeHook(client *Client, id string) {
|
||||
req := map[string]string{"id": id}
|
||||
func addHookDB(name, url, method, description string, events []string) {
|
||||
userID := dbOwnerUserID(hookUser)
|
||||
if userID == "" {
|
||||
fmt.Println("Error: no users found in database. Create a user first with: users add")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := client.Post("/api/hooks/remove", req)
|
||||
checkError(err)
|
||||
defer resp.Body.Close()
|
||||
eventsJSON := "[]"
|
||||
if len(events) > 0 {
|
||||
b, _ := json.Marshal(events)
|
||||
eventsJSON = string(b)
|
||||
}
|
||||
|
||||
fmt.Println("Hook removed successfully")
|
||||
id := uuid.New().String()
|
||||
now := time.Now()
|
||||
hook := &models.ModelPublicHook{
|
||||
ID: resolvespec_common.NewSqlString(id),
|
||||
Name: resolvespec_common.NewSqlString(name),
|
||||
URL: resolvespec_common.NewSqlString(url),
|
||||
Method: resolvespec_common.NewSqlString(method),
|
||||
Description: resolvespec_common.NewSqlString(description),
|
||||
Events: resolvespec_common.NewSqlString(eventsJSON),
|
||||
UserID: resolvespec_common.NewSqlString(userID),
|
||||
Active: true,
|
||||
CreatedAt: resolvespec_common.NewSqlTimeStamp(now),
|
||||
UpdatedAt: resolvespec_common.NewSqlTimeStamp(now),
|
||||
}
|
||||
|
||||
repo := storage.NewHookRepository(storage.DB)
|
||||
checkError(repo.Create(context.Background(), hook))
|
||||
|
||||
fmt.Printf("Hook '%s' added (ID: %s)\n", name, id)
|
||||
}
|
||||
|
||||
func removeHook(client *Client, id string) {
|
||||
if serverAvailable(client) {
|
||||
resp, err := client.Post("/api/hooks/remove", map[string]string{"id": id})
|
||||
checkError(err)
|
||||
defer resp.Body.Close()
|
||||
fmt.Println("Hook removed successfully")
|
||||
} else {
|
||||
fmt.Println("[server unavailable, removing from database]")
|
||||
if !tryInitDB() {
|
||||
fmt.Println("Error: server unreachable and no database config found. Use --server-config to specify config path.")
|
||||
return
|
||||
}
|
||||
repo := storage.NewHookRepository(storage.DB)
|
||||
checkError(repo.Delete(context.Background(), id))
|
||||
fmt.Printf("Hook '%s' removed\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
// parseEventsJSON parses a JSON events string into a comma-separated display string.
|
||||
func parseEventsJSON(raw string) string {
|
||||
if raw == "" || raw == "[]" {
|
||||
return "all"
|
||||
}
|
||||
var events []string
|
||||
if err := json.Unmarshal([]byte(raw), &events); err != nil {
|
||||
return raw
|
||||
}
|
||||
return strings.Join(events, ", ")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user