// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 package webhooks import ( "fmt" "os" "sourcedock.dev/petrbalvin/interpres/v2" "sourcedock.dev/petrbalvin/volumen/internal/tomlfile" ) // File is the admin-managed hook store: webhooks.toml beside the users // file. The admin edits it through the settings forms, so a change // applies without a restart; the file is written atomically with // owner-only permissions, like the other state files. type File struct { Path string } // LoadFile reads the hook store. A missing file means no hooks, not an // error; a malformed one is, because silently dropping the operator's // endpoints would hide a real fault. func LoadFile(path string) ([]Webhook, error) { raw, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, fmt.Errorf("read %s: %w", path, err) } data, err := interpres.ParseMap(raw) if err != nil { return nil, fmt.Errorf("parse %s: %w", path, err) } var out []Webhook for _, entry := range tomlfile.Tables(data["webhooks"]) { url := tomlfile.String(entry["url"]) if url == "" { continue } out = append(out, Webhook{ URL: url, Secret: tomlfile.String(entry["secret"]), Events: stringList(entry["events"]), Enabled: tomlfile.Bool(entry["enabled"], true), }) } return out, nil } // SaveFile replaces the hook store with the given hooks. func SaveFile(path string, hooks []Webhook) error { rows := make([]map[string]any, 0, len(hooks)) for _, hook := range hooks { rows = append(rows, map[string]any{ "url": hook.URL, "secret": hook.Secret, "events": hook.Events, "enabled": hook.Enabled, }) } if err := tomlfile.Write(path, "webhooks", rows); err != nil { return fmt.Errorf("write %s: %w", path, err) } return nil } // stringList reads a string array that may arrive as either the []any // the parser produces or a typed slice. func stringList(value any) []string { switch list := value.(type) { case []string: return list case []any: out := make([]string, 0, len(list)) for _, item := range list { if s, ok := item.(string); ok && s != "" { out = append(out, s) } } return out } return nil }