Files
whatshooked/cmd/cli/commands_hooks.go
Hein b81febafc9
Some checks failed
CI / Test (1.22) (push) Failing after -22m38s
CI / Test (1.23) (push) Failing after -22m21s
CI / Lint (push) Failing after -22m42s
CI / Build (push) Failing after -23m0s
feat(cli): enhance user and hook management with new commands and flags
2026-02-20 21:17:09 +02:00

241 lines
6.2 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"text/tabwriter"
"time"
"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"
)
var hookUser string
var hooksCmd = &cobra.Command{
Use: "hooks",
Short: "Manage webhooks",
Run: func(cmd *cobra.Command, args []string) {
client := NewClient(cliConfig)
listHooks(client)
},
}
var hooksListCmd = &cobra.Command{
Use: "list",
Short: "List all hooks",
Run: func(cmd *cobra.Command, args []string) {
client := NewClient(cliConfig)
listHooks(client)
},
}
var hooksAddCmd = &cobra.Command{
Use: "add",
Short: "Add a new hook",
Run: func(cmd *cobra.Command, args []string) {
client := NewClient(cliConfig)
addHook(client)
},
}
var hooksRemoveCmd = &cobra.Command{
Use: "remove <hook_id>",
Short: "Remove a hook by ID",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
client := NewClient(cliConfig)
removeHook(client, args[0])
},
}
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 []map[string]interface{}
checkError(decodeJSON(resp, &hooks))
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")
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) {
name := promptRequired("Hook Name")
url := promptRequired("Webhook URL")
method := promptLine("HTTP Method", "POST")
fmt.Println("\nAvailable events:")
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)", "")
var events []string
if eventsRaw != "" {
for _, e := range strings.Split(eventsRaw, ",") {
if t := strings.TrimSpace(e); t != "" {
events = append(events, t)
}
}
}
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)
}
}
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 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
}
eventsJSON := "[]"
if len(events) > 0 {
b, _ := json.Marshal(events)
eventsJSON = string(b)
}
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, ", ")
}