Initial commit
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Assisted-by: GLM 5.3
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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
|
||||
}
|
||||
Reference in New Issue
Block a user