Skip to content

Commit 5cebb01

Browse files
buty4649claude
andauthored
system: Webhook設定の取得・登録・更新・削除サブコマンドを追加 (#35)
フォームに紐づく Webhook 設定の CRUD を xpoint system webhook list/add/update/delete で提供する。取得・削除は --fqdn 必須、更新はリクエストボディの fqdn が必須で、 送信先URLの FQDN 完全一致時のみ操作可能。 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent e48b7bb commit 5cebb01

File tree

7 files changed

+461
-0
lines changed

7 files changed

+461
-0
lines changed

cmd/system_webhook.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
"text/tabwriter"
8+
9+
"github.com/pepabo/xpoint-cli/internal/xpoint"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var (
14+
systemWebhookListFQDN string
15+
systemWebhookListOutput string
16+
systemWebhookListJQ string
17+
18+
systemWebhookAddURL string
19+
systemWebhookAddRemarks string
20+
systemWebhookAddJQ string
21+
22+
systemWebhookUpdateFQDN string
23+
systemWebhookUpdateURL string
24+
systemWebhookUpdateRemarks string
25+
systemWebhookUpdateJQ string
26+
27+
systemWebhookDeleteFQDN string
28+
)
29+
30+
var systemWebhookCmd = &cobra.Command{
31+
Use: "webhook",
32+
Short: "Manage X-point form webhooks (admin)",
33+
}
34+
35+
var systemWebhookListCmd = &cobra.Command{
36+
Use: "list <form_code|form_id>",
37+
Short: "List webhook configs for a form (admin)",
38+
Long: `List webhook configs via GET /api/v1/system/{fid}/webhooks.
39+
40+
The argument may be a form_code or a numeric form_id. --fqdn is
41+
required and only configs whose destination URL FQDN matches are
42+
returned. Requires an administrator account.`,
43+
Args: cobra.ExactArgs(1),
44+
RunE: runSystemWebhookList,
45+
}
46+
47+
var systemWebhookAddCmd = &cobra.Command{
48+
Use: "add <form_code|form_id>",
49+
Short: "Register a webhook config for a form (admin)",
50+
Long: `Register a webhook config via POST /api/v1/system/{fid}/webhooks.
51+
52+
The argument may be a form_code or a numeric form_id.`,
53+
Args: cobra.ExactArgs(1),
54+
RunE: runSystemWebhookAdd,
55+
}
56+
57+
var systemWebhookUpdateCmd = &cobra.Command{
58+
Use: "update <form_code|form_id> <webhook_id>",
59+
Short: "Update a webhook config (admin)",
60+
Long: `Update an existing webhook config via
61+
PATCH /api/v1/system/{fid}/webhooks/{webhookId}.
62+
63+
--fqdn is required and must match the current destination URL FQDN.
64+
Omit --url / --remarks to leave them unchanged.`,
65+
Args: cobra.ExactArgs(2),
66+
RunE: runSystemWebhookUpdate,
67+
}
68+
69+
var systemWebhookDeleteCmd = &cobra.Command{
70+
Use: "delete <form_code|form_id> <webhook_id>",
71+
Short: "Delete a webhook config (admin)",
72+
Long: `Delete a webhook config via
73+
DELETE /api/v1/system/{fid}/webhooks/{webhookId}.
74+
75+
--fqdn is required and must match the current destination URL FQDN.`,
76+
Args: cobra.ExactArgs(2),
77+
RunE: runSystemWebhookDelete,
78+
}
79+
80+
func init() {
81+
systemCmd.AddCommand(systemWebhookCmd)
82+
systemWebhookCmd.AddCommand(systemWebhookListCmd)
83+
systemWebhookCmd.AddCommand(systemWebhookAddCmd)
84+
systemWebhookCmd.AddCommand(systemWebhookUpdateCmd)
85+
systemWebhookCmd.AddCommand(systemWebhookDeleteCmd)
86+
87+
lf := systemWebhookListCmd.Flags()
88+
lf.StringVar(&systemWebhookListFQDN, "fqdn", "", "destination URL FQDN to filter by (required, e.g. example.com)")
89+
lf.StringVarP(&systemWebhookListOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
90+
lf.StringVar(&systemWebhookListJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
91+
_ = systemWebhookListCmd.MarkFlagRequired("fqdn")
92+
93+
af := systemWebhookAddCmd.Flags()
94+
af.StringVar(&systemWebhookAddURL, "url", "", "destination URL (required)")
95+
af.StringVar(&systemWebhookAddRemarks, "remarks", "", "remarks")
96+
af.StringVar(&systemWebhookAddJQ, "jq", "", "apply a gojq filter to the JSON response")
97+
_ = systemWebhookAddCmd.MarkFlagRequired("url")
98+
99+
uf := systemWebhookUpdateCmd.Flags()
100+
uf.StringVar(&systemWebhookUpdateFQDN, "fqdn", "", "current destination URL FQDN (required, e.g. example.com)")
101+
uf.StringVar(&systemWebhookUpdateURL, "url", "", "new destination URL (omit to leave unchanged)")
102+
uf.StringVar(&systemWebhookUpdateRemarks, "remarks", "", "new remarks (omit to leave unchanged)")
103+
uf.StringVar(&systemWebhookUpdateJQ, "jq", "", "apply a gojq filter to the JSON response")
104+
_ = systemWebhookUpdateCmd.MarkFlagRequired("fqdn")
105+
106+
df := systemWebhookDeleteCmd.Flags()
107+
df.StringVar(&systemWebhookDeleteFQDN, "fqdn", "", "destination URL FQDN of the config to delete (required)")
108+
_ = systemWebhookDeleteCmd.MarkFlagRequired("fqdn")
109+
}
110+
111+
func runSystemWebhookList(cmd *cobra.Command, args []string) error {
112+
client, err := newClientFromFlags(cmd.Context())
113+
if err != nil {
114+
return err
115+
}
116+
formID, err := resolveSystemFormID(cmd.Context(), client, args[0])
117+
if err != nil {
118+
return err
119+
}
120+
res, err := client.ListWebhookConfigs(cmd.Context(), formID, systemWebhookListFQDN)
121+
if err != nil {
122+
return err
123+
}
124+
125+
return render(res, resolveOutputFormat(systemWebhookListOutput), systemWebhookListJQ, func() error {
126+
fmt.Fprintf(os.Stdout, "FORM: %s TYPE: %s\n", res.FormName, res.FormType)
127+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
128+
defer w.Flush()
129+
fmt.Fprintln(w, "ID\tURL\tREMARKS")
130+
for _, h := range res.Webhooks {
131+
fmt.Fprintf(w, "%d\t%s\t%s\n", h.ID, h.URL, h.Remarks)
132+
}
133+
return nil
134+
})
135+
}
136+
137+
func runSystemWebhookAdd(cmd *cobra.Command, args []string) error {
138+
client, err := newClientFromFlags(cmd.Context())
139+
if err != nil {
140+
return err
141+
}
142+
formID, err := resolveSystemFormID(cmd.Context(), client, args[0])
143+
if err != nil {
144+
return err
145+
}
146+
req := xpoint.CreateWebhookRequest{
147+
URL: systemWebhookAddURL,
148+
Remarks: systemWebhookAddRemarks,
149+
}
150+
res, err := client.CreateWebhookConfig(cmd.Context(), formID, req)
151+
if err != nil {
152+
return err
153+
}
154+
if systemWebhookAddJQ != "" {
155+
return runJQ(res, systemWebhookAddJQ)
156+
}
157+
return writeJSON(os.Stdout, res)
158+
}
159+
160+
func runSystemWebhookUpdate(cmd *cobra.Command, args []string) error {
161+
webhookID := strings.TrimSpace(args[1])
162+
if webhookID == "" {
163+
return fmt.Errorf("webhook_id is required")
164+
}
165+
client, err := newClientFromFlags(cmd.Context())
166+
if err != nil {
167+
return err
168+
}
169+
formID, err := resolveSystemFormID(cmd.Context(), client, args[0])
170+
if err != nil {
171+
return err
172+
}
173+
174+
req := xpoint.UpdateWebhookRequest{FQDN: systemWebhookUpdateFQDN}
175+
if cmd.Flags().Changed("url") {
176+
v := systemWebhookUpdateURL
177+
req.URL = &v
178+
}
179+
if cmd.Flags().Changed("remarks") {
180+
v := systemWebhookUpdateRemarks
181+
req.Remarks = &v
182+
}
183+
184+
res, err := client.UpdateWebhookConfig(cmd.Context(), formID, webhookID, req)
185+
if err != nil {
186+
return err
187+
}
188+
if systemWebhookUpdateJQ != "" {
189+
return runJQ(res, systemWebhookUpdateJQ)
190+
}
191+
return writeJSON(os.Stdout, res)
192+
}
193+
194+
func runSystemWebhookDelete(cmd *cobra.Command, args []string) error {
195+
webhookID := strings.TrimSpace(args[1])
196+
if webhookID == "" {
197+
return fmt.Errorf("webhook_id is required")
198+
}
199+
client, err := newClientFromFlags(cmd.Context())
200+
if err != nil {
201+
return err
202+
}
203+
formID, err := resolveSystemFormID(cmd.Context(), client, args[0])
204+
if err != nil {
205+
return err
206+
}
207+
out, err := client.DeleteWebhookConfig(cmd.Context(), formID, webhookID, systemWebhookDeleteFQDN)
208+
if err != nil {
209+
return err
210+
}
211+
if len(out) == 0 {
212+
fmt.Fprintln(os.Stdout, "deleted")
213+
return nil
214+
}
215+
return writeJSON(os.Stdout, out)
216+
}

internal/schema/schema_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ func TestAliases_Sorted(t *testing.T) {
4343
"system.master.list",
4444
"system.master.show",
4545
"system.master.upload",
46+
"system.webhook.add",
47+
"system.webhook.delete",
48+
"system.webhook.list",
49+
"system.webhook.update",
4650
"system.webhooklog.list",
4751
"system.webhooklog.show",
4852
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"method": "POST",
3+
"path": "/api/v1/system/{fid}/webhooks",
4+
"summary": "Webhook設定登録",
5+
"description": "指定したフォームに Webhook 設定を登録する。\n管理者権限が必要。\n",
6+
"parameters": [
7+
{
8+
"name": "fid",
9+
"in": "path",
10+
"required": true,
11+
"type": "integer",
12+
"description": "フォームID"
13+
}
14+
],
15+
"requestBody": {
16+
"contentType": "application/json",
17+
"type": "object",
18+
"properties": {
19+
"url": {
20+
"type": "string",
21+
"required": true,
22+
"description": "送信先URL (無効な URL はエラー)"
23+
},
24+
"remarks": {
25+
"type": "string",
26+
"description": "備考"
27+
}
28+
}
29+
},
30+
"response": {
31+
"type": "object",
32+
"properties": {
33+
"id": { "type": "integer", "description": "Webhook ID" },
34+
"url": { "type": "string", "description": "送信先URL" },
35+
"remarks": { "type": "string", "description": "備考" }
36+
}
37+
}
38+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"method": "DELETE",
3+
"path": "/api/v1/system/{fid}/webhooks/{webhookId}",
4+
"summary": "Webhook設定削除",
5+
"description": "指定したフォームの指定した Webhook 設定を削除する。\nfqdn の指定が必須で、指定 FQDN と設定中の送信先URLの FQDN が完全一致する場合のみ削除可能。\n管理者権限が必要。\n",
6+
"parameters": [
7+
{
8+
"name": "fid",
9+
"in": "path",
10+
"required": true,
11+
"type": "integer",
12+
"description": "フォームID"
13+
},
14+
{
15+
"name": "webhookId",
16+
"in": "path",
17+
"required": true,
18+
"type": "string",
19+
"description": "Webhook ID"
20+
},
21+
{
22+
"name": "fqdn",
23+
"in": "query",
24+
"required": true,
25+
"type": "string",
26+
"description": "削除対象の Webhook 送信先 FQDN (例: example.com)"
27+
}
28+
],
29+
"response": {
30+
"type": "object",
31+
"description": "削除成功時のレスポンス"
32+
}
33+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"method": "GET",
3+
"path": "/api/v1/system/{fid}/webhooks",
4+
"summary": "Webhook設定取得",
5+
"description": "指定したフォームの Webhook 設定情報を取得する。\nfqdn の指定が必須で、指定 FQDN と送信先URLの FQDN が完全一致する設定のみ取得する。\n管理者権限が必要。\n",
6+
"parameters": [
7+
{
8+
"name": "fid",
9+
"in": "path",
10+
"required": true,
11+
"type": "integer",
12+
"description": "フォームID"
13+
},
14+
{
15+
"name": "fqdn",
16+
"in": "query",
17+
"required": true,
18+
"type": "string",
19+
"description": "取得対象の Webhook 送信先 FQDN (例: example.com)"
20+
}
21+
],
22+
"response": {
23+
"type": "object",
24+
"properties": {
25+
"form_name": { "type": "string", "description": "フォーム名称" },
26+
"form_type": { "type": "string", "description": "フォーム種別: standard | workflow" },
27+
"webhooks": {
28+
"type": "array",
29+
"description": "Webhook 設定 (存在しない場合は空配列)",
30+
"items": {
31+
"type": "object",
32+
"properties": {
33+
"id": { "type": "integer", "description": "Webhook ID" },
34+
"url": { "type": "string", "description": "送信先URL" },
35+
"remarks": { "type": "string", "description": "備考" }
36+
}
37+
}
38+
}
39+
}
40+
}
41+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"method": "PATCH",
3+
"path": "/api/v1/system/{fid}/webhooks/{webhookId}",
4+
"summary": "Webhook設定更新",
5+
"description": "指定したフォームの指定した Webhook 設定を更新する。\nリクエストボディの fqdn 指定が必須で、指定 FQDN と設定中の送信先URLの FQDN が完全一致する場合のみ更新可能。\n管理者権限が必要。\n",
6+
"parameters": [
7+
{
8+
"name": "fid",
9+
"in": "path",
10+
"required": true,
11+
"type": "integer",
12+
"description": "フォームID"
13+
},
14+
{
15+
"name": "webhookId",
16+
"in": "path",
17+
"required": true,
18+
"type": "string",
19+
"description": "Webhook ID"
20+
}
21+
],
22+
"requestBody": {
23+
"contentType": "application/json",
24+
"type": "object",
25+
"properties": {
26+
"fqdn": {
27+
"type": "string",
28+
"required": true,
29+
"description": "更新対象の Webhook 送信先 FQDN (例: example.com)"
30+
},
31+
"url": {
32+
"type": "string",
33+
"description": "送信先URL (無効な URL はエラー)"
34+
},
35+
"remarks": {
36+
"type": "string",
37+
"description": "備考"
38+
}
39+
}
40+
},
41+
"response": {
42+
"type": "object",
43+
"properties": {
44+
"id": { "type": "integer", "description": "Webhook ID" },
45+
"url": { "type": "string", "description": "送信先URL" },
46+
"remarks": { "type": "string", "description": "備考" }
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)