Skip to content

Commit e48b7bb

Browse files
buty4649claude
andauthored
system: Webhookログ取得・詳細取得サブコマンドを追加 (#34)
Webhook 送信ログの検索 (list) と詳細取得 (show) を行うサブコマンドを追加。 詳細レスポンスはリクエスト/レスポンスボディの形状が不定のため json.RawMessage で返し、--jq フィルタを提供する。 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 03a6562 commit e48b7bb

File tree

5 files changed

+348
-0
lines changed

5 files changed

+348
-0
lines changed

cmd/system_webhooklog.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
systemWebhooklogListFrom string
15+
systemWebhooklogListTo string
16+
systemWebhooklogListDocID int
17+
systemWebhooklogListFormCode string
18+
systemWebhooklogListRouteCode string
19+
systemWebhooklogListStatus string
20+
systemWebhooklogListURL string
21+
systemWebhooklogListLimit int
22+
systemWebhooklogListOffset int
23+
systemWebhooklogListOutput string
24+
systemWebhooklogListJQ string
25+
26+
systemWebhooklogShowJQ string
27+
)
28+
29+
var systemWebhooklogCmd = &cobra.Command{
30+
Use: "webhooklog",
31+
Short: "Inspect X-point webhook delivery logs (admin)",
32+
}
33+
34+
var systemWebhooklogListCmd = &cobra.Command{
35+
Use: "list",
36+
Short: "List webhook delivery logs",
37+
Long: `List webhook delivery logs via GET /api/v1/system/webhooklog.
38+
39+
Filters are all optional. --status accepts all | success | failed.
40+
Requires an administrator account.`,
41+
RunE: runSystemWebhooklogList,
42+
}
43+
44+
var systemWebhooklogShowCmd = &cobra.Command{
45+
Use: "show <uuid>",
46+
Short: "Show webhook delivery log detail (request/response)",
47+
Long: `Fetch detailed request/response data for a single webhook log entry via
48+
GET /api/v1/system/webhooklog/{uuid}. Requires an administrator account.
49+
50+
The response is emitted as JSON because the request/response body shape
51+
depends on the remote endpoint. Use --jq to filter the output.`,
52+
Args: cobra.ExactArgs(1),
53+
RunE: runSystemWebhooklogShow,
54+
}
55+
56+
func init() {
57+
systemCmd.AddCommand(systemWebhooklogCmd)
58+
systemWebhooklogCmd.AddCommand(systemWebhooklogListCmd)
59+
systemWebhooklogCmd.AddCommand(systemWebhooklogShowCmd)
60+
61+
lf := systemWebhooklogListCmd.Flags()
62+
lf.StringVar(&systemWebhooklogListFrom, "from", "", "send date from (e.g. 2022/10/01)")
63+
lf.StringVar(&systemWebhooklogListTo, "to", "", "send date to (e.g. 2022/12/01)")
64+
lf.IntVar(&systemWebhooklogListDocID, "docid", 0, "document id (0 = omit)")
65+
lf.StringVar(&systemWebhooklogListFormCode, "form-code", "", "form code")
66+
lf.StringVar(&systemWebhooklogListRouteCode, "route-code", "", "approval route code")
67+
lf.StringVar(&systemWebhooklogListStatus, "status", "", "status: all | success | failed")
68+
lf.StringVar(&systemWebhooklogListURL, "url", "", "destination URL (partial match)")
69+
lf.IntVar(&systemWebhooklogListLimit, "limit", 0, "fetch count (0 = omit; server default 50)")
70+
lf.IntVar(&systemWebhooklogListOffset, "offset", 0, "offset (0 = omit; server default 0)")
71+
lf.StringVarP(&systemWebhooklogListOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
72+
lf.StringVar(&systemWebhooklogListJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
73+
74+
sf := systemWebhooklogShowCmd.Flags()
75+
sf.StringVar(&systemWebhooklogShowJQ, "jq", "", "apply a gojq filter to the JSON response")
76+
}
77+
78+
func runSystemWebhooklogList(cmd *cobra.Command, _ []string) error {
79+
p := xpoint.WebhooklogListParams{
80+
From: systemWebhooklogListFrom,
81+
To: systemWebhooklogListTo,
82+
FormCode: systemWebhooklogListFormCode,
83+
RouteCode: systemWebhooklogListRouteCode,
84+
Status: systemWebhooklogListStatus,
85+
URL: systemWebhooklogListURL,
86+
}
87+
if cmd.Flags().Changed("docid") {
88+
v := systemWebhooklogListDocID
89+
p.DocID = &v
90+
}
91+
if cmd.Flags().Changed("limit") {
92+
v := systemWebhooklogListLimit
93+
p.Limit = &v
94+
}
95+
if cmd.Flags().Changed("offset") {
96+
v := systemWebhooklogListOffset
97+
p.Offset = &v
98+
}
99+
if p.Status != "" {
100+
switch strings.ToLower(p.Status) {
101+
case "all", "success", "failed":
102+
default:
103+
return fmt.Errorf("--status must be all|success|failed, got %q", p.Status)
104+
}
105+
}
106+
107+
client, err := newClientFromFlags(cmd.Context())
108+
if err != nil {
109+
return err
110+
}
111+
res, err := client.ListWebhooklog(cmd.Context(), p)
112+
if err != nil {
113+
return err
114+
}
115+
116+
return render(res, resolveOutputFormat(systemWebhooklogListOutput), systemWebhooklogListJQ, func() error {
117+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
118+
defer w.Flush()
119+
fmt.Fprintln(w, "SEND_DATE\tSTATUS\tDOCID\tFORM_CODE\tROUTE_CODE\tTITLE\tURL\tUUID")
120+
for _, e := range res.Data {
121+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
122+
e.SendDate, e.StatusCode, e.DocID, e.FormCode, e.RouteCode, e.Title1, e.URL, e.UUID,
123+
)
124+
}
125+
return nil
126+
})
127+
}
128+
129+
func runSystemWebhooklogShow(cmd *cobra.Command, args []string) error {
130+
uuid := strings.TrimSpace(args[0])
131+
if uuid == "" {
132+
return fmt.Errorf("uuid is required")
133+
}
134+
client, err := newClientFromFlags(cmd.Context())
135+
if err != nil {
136+
return err
137+
}
138+
out, err := client.GetWebhooklog(cmd.Context(), uuid)
139+
if err != nil {
140+
return err
141+
}
142+
if systemWebhooklogShowJQ != "" {
143+
return runJQ(out, systemWebhooklogShowJQ)
144+
}
145+
return writeJSON(os.Stdout, out)
146+
}

internal/schema/schema_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ func TestAliases_Sorted(t *testing.T) {
4343
"system.master.list",
4444
"system.master.show",
4545
"system.master.upload",
46+
"system.webhooklog.list",
47+
"system.webhooklog.show",
4648
}
4749
if len(got) != len(want) {
4850
t.Fatalf("aliases = %v", got)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"method": "GET",
3+
"path": "/api/v1/system/webhooklog",
4+
"summary": "Webhookログ取得",
5+
"description": "Webhook の送信日時等を指定して Webhook 送信ログを検索・取得する。\n管理者権限が必要。\n",
6+
"parameters": [
7+
{
8+
"name": "from",
9+
"in": "query",
10+
"type": "string",
11+
"description": "送信日時 From (例: 2022/10/01)"
12+
},
13+
{
14+
"name": "to",
15+
"in": "query",
16+
"type": "string",
17+
"description": "送信日時 To (例: 2022/12/01)"
18+
},
19+
{
20+
"name": "docid",
21+
"in": "query",
22+
"type": "integer",
23+
"description": "書類ID"
24+
},
25+
{
26+
"name": "form_code",
27+
"in": "query",
28+
"type": "string",
29+
"description": "フォームコード"
30+
},
31+
{
32+
"name": "route_code",
33+
"in": "query",
34+
"type": "string",
35+
"description": "承認ルートコード"
36+
},
37+
{
38+
"name": "status",
39+
"in": "query",
40+
"type": "string",
41+
"description": "ステータス: all | success | failed"
42+
},
43+
{
44+
"name": "url",
45+
"in": "query",
46+
"type": "string",
47+
"description": "送信先URL (部分一致)"
48+
},
49+
{
50+
"name": "limit",
51+
"in": "query",
52+
"type": "integer",
53+
"description": "取得件数 (default 50, 推奨 1000 以下)"
54+
},
55+
{
56+
"name": "offset",
57+
"in": "query",
58+
"type": "integer",
59+
"description": "取得開始位置 (default 0)"
60+
}
61+
],
62+
"response": {
63+
"type": "object",
64+
"properties": {
65+
"data": {
66+
"type": "array",
67+
"description": "ログデータ (存在しない場合は空配列)",
68+
"items": {
69+
"type": "object",
70+
"properties": {
71+
"domain_code": { "type": "string", "description": "ドメインコード" },
72+
"docid": { "type": "string", "description": "書類ID" },
73+
"form_code": { "type": "string", "description": "フォームコード" },
74+
"route_code": { "type": "string", "description": "承認ルートコード" },
75+
"title1": { "type": "string", "description": "件名" },
76+
"url": { "type": "string", "description": "送信先URL" },
77+
"status_code": { "type": "string", "description": "ステータスコード" },
78+
"send_date": { "type": "string", "description": "送信日時" },
79+
"uuid": { "type": "string", "description": "UUID (詳細取得に使用)" }
80+
}
81+
}
82+
}
83+
}
84+
}
85+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"method": "GET",
3+
"path": "/api/v1/system/webhooklog/{uuid}",
4+
"summary": "Webhook詳細ログ取得",
5+
"description": "Webhook 送信ログの詳細情報 (リクエスト情報、レスポンス情報) を取得する。\n管理者権限が必要。\n",
6+
"parameters": [
7+
{
8+
"name": "uuid",
9+
"in": "path",
10+
"required": true,
11+
"type": "string",
12+
"description": "Webhookログの UUID"
13+
}
14+
],
15+
"response": {
16+
"type": "object",
17+
"properties": {
18+
"domain_code": { "type": "string", "description": "ドメインコード" },
19+
"docid": { "type": "string", "description": "書類ID" },
20+
"url": { "type": "string", "description": "送信先URL" },
21+
"status_code": { "type": "string", "description": "ステータスコード" },
22+
"uuid": { "type": "string", "description": "UUID" },
23+
"request": {
24+
"type": "object",
25+
"description": "リクエスト情報 (header/body 等)"
26+
},
27+
"response": {
28+
"type": "object",
29+
"description": "レスポンス情報 (header/body 等。body は送信先応答により文字列等になる場合がある)"
30+
}
31+
}
32+
}
33+
}

internal/xpoint/client.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,88 @@ func (c *Client) UploadUserMasterCSV(ctx context.Context, tableName, fileName st
374374
return &out, nil
375375
}
376376

377+
type WebhooklogEntry struct {
378+
DomainCode string `json:"domain_code"`
379+
DocID string `json:"docid"`
380+
FormCode string `json:"form_code"`
381+
RouteCode string `json:"route_code"`
382+
Title1 string `json:"title1"`
383+
URL string `json:"url"`
384+
StatusCode string `json:"status_code"`
385+
SendDate string `json:"send_date"`
386+
UUID string `json:"uuid"`
387+
}
388+
389+
type WebhooklogListResponse struct {
390+
Data []WebhooklogEntry `json:"data"`
391+
}
392+
393+
// WebhooklogListParams holds query parameters for GET /api/v1/system/webhooklog.
394+
type WebhooklogListParams struct {
395+
From string // from
396+
To string // to
397+
DocID *int // docid
398+
FormCode string // form_code
399+
RouteCode string // route_code
400+
Status string // status: all | success | failed
401+
URL string // url
402+
Limit *int // limit
403+
Offset *int // offset
404+
}
405+
406+
func (p WebhooklogListParams) query() url.Values {
407+
v := url.Values{}
408+
if p.From != "" {
409+
v.Set("from", p.From)
410+
}
411+
if p.To != "" {
412+
v.Set("to", p.To)
413+
}
414+
if p.DocID != nil {
415+
v.Set("docid", strconv.Itoa(*p.DocID))
416+
}
417+
if p.FormCode != "" {
418+
v.Set("form_code", p.FormCode)
419+
}
420+
if p.RouteCode != "" {
421+
v.Set("route_code", p.RouteCode)
422+
}
423+
if p.Status != "" {
424+
v.Set("status", p.Status)
425+
}
426+
if p.URL != "" {
427+
v.Set("url", p.URL)
428+
}
429+
if p.Limit != nil {
430+
v.Set("limit", strconv.Itoa(*p.Limit))
431+
}
432+
if p.Offset != nil {
433+
v.Set("offset", strconv.Itoa(*p.Offset))
434+
}
435+
return v
436+
}
437+
438+
// ListWebhooklog calls GET /api/v1/system/webhooklog (admin).
439+
func (c *Client) ListWebhooklog(ctx context.Context, p WebhooklogListParams) (*WebhooklogListResponse, error) {
440+
var out WebhooklogListResponse
441+
if err := c.do(ctx, http.MethodGet, "/api/v1/system/webhooklog", p.query(), nil, &out); err != nil {
442+
return nil, err
443+
}
444+
return &out, nil
445+
}
446+
447+
// GetWebhooklog calls GET /api/v1/system/webhooklog/{uuid} (admin). The
448+
// response contains request/response detail whose body may be an arbitrary
449+
// type, so it is returned as raw JSON.
450+
func (c *Client) GetWebhooklog(ctx context.Context, uuid string) (json.RawMessage, error) {
451+
path := fmt.Sprintf("/api/v1/system/webhooklog/%s", url.PathEscape(uuid))
452+
var out json.RawMessage
453+
if err := c.do(ctx, http.MethodGet, path, nil, nil, &out); err != nil {
454+
return nil, err
455+
}
456+
return out, nil
457+
}
458+
377459
type Approval struct {
378460
DocID int `json:"docid"`
379461
Hidden *bool `json:"hidden,omitempty"`

0 commit comments

Comments
 (0)