feat(cli): search, messages send, webhook verify, sso verify
The CLI now covers the whole Redmine 507 surface: `search TERM [-page N]` (operators pass through quoted), `messages send -title -to user1,user2 [-group/-email] [-file|-raw]`, plus the two secret helpers `webhook verify SIG` (delivery body on stdin, exit 0 only on a valid HMAC) and `sso verify SSO SIG` (prints the decoded Discourse Connect payload as JSON). The helpers run before client construction so they work with only their secret env set, no instance credentials needed. Usage text lists the new env vars. Part of Redmine 507 (Discourse Go client).
This commit is contained in:
@@ -144,6 +144,60 @@ func runPosts(ctx context.Context, c *discourse.Client, args []string) (any, err
|
||||
return nil, fmt.Errorf("unknown posts subcommand %q", args[0])
|
||||
}
|
||||
|
||||
func runSearch(ctx context.Context, c *discourse.Client, args []string) (any, error) {
|
||||
fs := flag.NewFlagSet("search", flag.ContinueOnError)
|
||||
page := fs.Int("page", 1, "result page, 1-based")
|
||||
if err := fs.Parse(flagsFirst(args)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fs.NArg() != 1 {
|
||||
return nil, fmt.Errorf("search needs exactly one TERM (quote it; @user, #category and order:latest operators pass through)")
|
||||
}
|
||||
return c.Search(ctx, discourse.SearchRequest{Term: fs.Arg(0), Page: *page})
|
||||
}
|
||||
|
||||
func runMessages(ctx context.Context, c *discourse.Client, args []string) (any, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, fmt.Errorf("messages needs a subcommand (send)")
|
||||
}
|
||||
switch args[0] {
|
||||
case "send":
|
||||
fs := flag.NewFlagSet("messages send", flag.ContinueOnError)
|
||||
title := fs.String("title", "", "message title (required)")
|
||||
to := fs.String("to", "", "recipient usernames, comma-separated (repeatable targets below)")
|
||||
group := fs.String("group", "", "recipient group names, comma-separated")
|
||||
email := fs.String("email", "", "recipient emails, comma-separated (invite external users)")
|
||||
raw := fs.String("raw", "", "markdown body inline")
|
||||
file := fs.String("file", "", "markdown body from file (overrides -raw)")
|
||||
if err := fs.Parse(flagsFirst(args[1:])); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := bodyFrom(*file, *raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.SendMessage(ctx, discourse.SendMessageRequest{
|
||||
Title: *title,
|
||||
Raw: body,
|
||||
TargetUsernames: splitComma(*to),
|
||||
TargetGroupNames: splitComma(*group),
|
||||
TargetEmails: splitComma(*email),
|
||||
})
|
||||
}
|
||||
return nil, fmt.Errorf("unknown messages subcommand %q", args[0])
|
||||
}
|
||||
|
||||
// splitComma splits a comma-separated flag value, dropping empties.
|
||||
func splitComma(s string) []string {
|
||||
var out []string
|
||||
for _, v := range strings.Split(s, ",") {
|
||||
if v = strings.TrimSpace(v); v != "" {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func runRaw(ctx context.Context, c *discourse.Client, args []string) (any, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, fmt.Errorf("raw needs METHOD and PATH")
|
||||
|
||||
Reference in New Issue
Block a user