Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions cmd/system_webhooklog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package cmd

import (
"fmt"
"os"
"strings"
"text/tabwriter"

"github.com/pepabo/xpoint-cli/internal/xpoint"
"github.com/spf13/cobra"
)

var (
systemWebhooklogListFrom string
systemWebhooklogListTo string
systemWebhooklogListDocID int
systemWebhooklogListFormCode string
systemWebhooklogListRouteCode string
systemWebhooklogListStatus string
systemWebhooklogListURL string
systemWebhooklogListLimit int
systemWebhooklogListOffset int
systemWebhooklogListOutput string
systemWebhooklogListJQ string

systemWebhooklogShowJQ string
)

var systemWebhooklogCmd = &cobra.Command{
Use: "webhooklog",
Short: "Inspect X-point webhook delivery logs (admin)",
}

var systemWebhooklogListCmd = &cobra.Command{
Use: "list",
Short: "List webhook delivery logs",
Long: `List webhook delivery logs via GET /api/v1/system/webhooklog.

Filters are all optional. --status accepts all | success | failed.
Requires an administrator account.`,
RunE: runSystemWebhooklogList,
}

var systemWebhooklogShowCmd = &cobra.Command{
Use: "show <uuid>",
Short: "Show webhook delivery log detail (request/response)",
Long: `Fetch detailed request/response data for a single webhook log entry via
GET /api/v1/system/webhooklog/{uuid}. Requires an administrator account.

The response is emitted as JSON because the request/response body shape
depends on the remote endpoint. Use --jq to filter the output.`,
Args: cobra.ExactArgs(1),
RunE: runSystemWebhooklogShow,
}

func init() {
systemCmd.AddCommand(systemWebhooklogCmd)
systemWebhooklogCmd.AddCommand(systemWebhooklogListCmd)
systemWebhooklogCmd.AddCommand(systemWebhooklogShowCmd)

lf := systemWebhooklogListCmd.Flags()
lf.StringVar(&systemWebhooklogListFrom, "from", "", "send date from (e.g. 2022/10/01)")
lf.StringVar(&systemWebhooklogListTo, "to", "", "send date to (e.g. 2022/12/01)")
lf.IntVar(&systemWebhooklogListDocID, "docid", 0, "document id (0 = omit)")
lf.StringVar(&systemWebhooklogListFormCode, "form-code", "", "form code")
lf.StringVar(&systemWebhooklogListRouteCode, "route-code", "", "approval route code")
lf.StringVar(&systemWebhooklogListStatus, "status", "", "status: all | success | failed")
lf.StringVar(&systemWebhooklogListURL, "url", "", "destination URL (partial match)")
lf.IntVar(&systemWebhooklogListLimit, "limit", 0, "fetch count (0 = omit; server default 50)")
lf.IntVar(&systemWebhooklogListOffset, "offset", 0, "offset (0 = omit; server default 0)")
lf.StringVarP(&systemWebhooklogListOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
lf.StringVar(&systemWebhooklogListJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")

sf := systemWebhooklogShowCmd.Flags()
sf.StringVar(&systemWebhooklogShowJQ, "jq", "", "apply a gojq filter to the JSON response")
}

func runSystemWebhooklogList(cmd *cobra.Command, _ []string) error {
p := xpoint.WebhooklogListParams{
From: systemWebhooklogListFrom,
To: systemWebhooklogListTo,
FormCode: systemWebhooklogListFormCode,
RouteCode: systemWebhooklogListRouteCode,
Status: systemWebhooklogListStatus,
URL: systemWebhooklogListURL,
}
if cmd.Flags().Changed("docid") {
v := systemWebhooklogListDocID
p.DocID = &v
}
if cmd.Flags().Changed("limit") {
v := systemWebhooklogListLimit
p.Limit = &v
}
if cmd.Flags().Changed("offset") {
v := systemWebhooklogListOffset
p.Offset = &v
}
if p.Status != "" {
switch strings.ToLower(p.Status) {
case "all", "success", "failed":
default:
return fmt.Errorf("--status must be all|success|failed, got %q", p.Status)
}
}

client, err := newClientFromFlags(cmd.Context())
if err != nil {
return err
}
res, err := client.ListWebhooklog(cmd.Context(), p)
if err != nil {
return err
}

return render(res, resolveOutputFormat(systemWebhooklogListOutput), systemWebhooklogListJQ, func() error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
defer w.Flush()
fmt.Fprintln(w, "SEND_DATE\tSTATUS\tDOCID\tFORM_CODE\tROUTE_CODE\tTITLE\tURL\tUUID")
for _, e := range res.Data {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
e.SendDate, e.StatusCode, e.DocID, e.FormCode, e.RouteCode, e.Title1, e.URL, e.UUID,
)
}
return nil
})
}

func runSystemWebhooklogShow(cmd *cobra.Command, args []string) error {
uuid := strings.TrimSpace(args[0])
if uuid == "" {
return fmt.Errorf("uuid is required")
}
client, err := newClientFromFlags(cmd.Context())
if err != nil {
return err
}
out, err := client.GetWebhooklog(cmd.Context(), uuid)
if err != nil {
return err
}
if systemWebhooklogShowJQ != "" {
return runJQ(out, systemWebhooklogShowJQ)
}
return writeJSON(os.Stdout, out)
}
2 changes: 2 additions & 0 deletions internal/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ func TestAliases_Sorted(t *testing.T) {
"system.master.list",
"system.master.show",
"system.master.upload",
"system.webhooklog.list",
"system.webhooklog.show",
}
if len(got) != len(want) {
t.Fatalf("aliases = %v", got)
Expand Down
85 changes: 85 additions & 0 deletions internal/schema/system.webhooklog.list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"method": "GET",
"path": "/api/v1/system/webhooklog",
"summary": "Webhookログ取得",
"description": "Webhook の送信日時等を指定して Webhook 送信ログを検索・取得する。\n管理者権限が必要。\n",
"parameters": [
{
"name": "from",
"in": "query",
"type": "string",
"description": "送信日時 From (例: 2022/10/01)"
},
{
"name": "to",
"in": "query",
"type": "string",
"description": "送信日時 To (例: 2022/12/01)"
},
{
"name": "docid",
"in": "query",
"type": "integer",
"description": "書類ID"
},
{
"name": "form_code",
"in": "query",
"type": "string",
"description": "フォームコード"
},
{
"name": "route_code",
"in": "query",
"type": "string",
"description": "承認ルートコード"
},
{
"name": "status",
"in": "query",
"type": "string",
"description": "ステータス: all | success | failed"
},
{
"name": "url",
"in": "query",
"type": "string",
"description": "送信先URL (部分一致)"
},
{
"name": "limit",
"in": "query",
"type": "integer",
"description": "取得件数 (default 50, 推奨 1000 以下)"
},
{
"name": "offset",
"in": "query",
"type": "integer",
"description": "取得開始位置 (default 0)"
}
],
"response": {
"type": "object",
"properties": {
"data": {
"type": "array",
"description": "ログデータ (存在しない場合は空配列)",
"items": {
"type": "object",
"properties": {
"domain_code": { "type": "string", "description": "ドメインコード" },
"docid": { "type": "string", "description": "書類ID" },
"form_code": { "type": "string", "description": "フォームコード" },
"route_code": { "type": "string", "description": "承認ルートコード" },
"title1": { "type": "string", "description": "件名" },
"url": { "type": "string", "description": "送信先URL" },
"status_code": { "type": "string", "description": "ステータスコード" },
"send_date": { "type": "string", "description": "送信日時" },
"uuid": { "type": "string", "description": "UUID (詳細取得に使用)" }
}
}
}
}
}
}
33 changes: 33 additions & 0 deletions internal/schema/system.webhooklog.show.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"method": "GET",
"path": "/api/v1/system/webhooklog/{uuid}",
"summary": "Webhook詳細ログ取得",
"description": "Webhook 送信ログの詳細情報 (リクエスト情報、レスポンス情報) を取得する。\n管理者権限が必要。\n",
"parameters": [
{
"name": "uuid",
"in": "path",
"required": true,
"type": "string",
"description": "Webhookログの UUID"
}
],
"response": {
"type": "object",
"properties": {
"domain_code": { "type": "string", "description": "ドメインコード" },
"docid": { "type": "string", "description": "書類ID" },
"url": { "type": "string", "description": "送信先URL" },
"status_code": { "type": "string", "description": "ステータスコード" },
"uuid": { "type": "string", "description": "UUID" },
"request": {
"type": "object",
"description": "リクエスト情報 (header/body 等)"
},
"response": {
"type": "object",
"description": "レスポンス情報 (header/body 等。body は送信先応答により文字列等になる場合がある)"
}
}
}
}
82 changes: 82 additions & 0 deletions internal/xpoint/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,88 @@ func (c *Client) UploadUserMasterCSV(ctx context.Context, tableName, fileName st
return &out, nil
}

type WebhooklogEntry struct {
DomainCode string `json:"domain_code"`
DocID string `json:"docid"`
FormCode string `json:"form_code"`
RouteCode string `json:"route_code"`
Title1 string `json:"title1"`
URL string `json:"url"`
StatusCode string `json:"status_code"`
SendDate string `json:"send_date"`
UUID string `json:"uuid"`
}

type WebhooklogListResponse struct {
Data []WebhooklogEntry `json:"data"`
}

// WebhooklogListParams holds query parameters for GET /api/v1/system/webhooklog.
type WebhooklogListParams struct {
From string // from
To string // to
DocID *int // docid
FormCode string // form_code
RouteCode string // route_code
Status string // status: all | success | failed
URL string // url
Limit *int // limit
Offset *int // offset
}

func (p WebhooklogListParams) query() url.Values {
v := url.Values{}
if p.From != "" {
v.Set("from", p.From)
}
if p.To != "" {
v.Set("to", p.To)
}
if p.DocID != nil {
v.Set("docid", strconv.Itoa(*p.DocID))
}
if p.FormCode != "" {
v.Set("form_code", p.FormCode)
}
if p.RouteCode != "" {
v.Set("route_code", p.RouteCode)
}
if p.Status != "" {
v.Set("status", p.Status)
}
if p.URL != "" {
v.Set("url", p.URL)
}
if p.Limit != nil {
v.Set("limit", strconv.Itoa(*p.Limit))
}
if p.Offset != nil {
v.Set("offset", strconv.Itoa(*p.Offset))
}
return v
}

// ListWebhooklog calls GET /api/v1/system/webhooklog (admin).
func (c *Client) ListWebhooklog(ctx context.Context, p WebhooklogListParams) (*WebhooklogListResponse, error) {
var out WebhooklogListResponse
if err := c.do(ctx, http.MethodGet, "/api/v1/system/webhooklog", p.query(), nil, &out); err != nil {
return nil, err
}
return &out, nil
}

// GetWebhooklog calls GET /api/v1/system/webhooklog/{uuid} (admin). The
// response contains request/response detail whose body may be an arbitrary
// type, so it is returned as raw JSON.
func (c *Client) GetWebhooklog(ctx context.Context, uuid string) (json.RawMessage, error) {
path := fmt.Sprintf("/api/v1/system/webhooklog/%s", url.PathEscape(uuid))
var out json.RawMessage
if err := c.do(ctx, http.MethodGet, path, nil, nil, &out); err != nil {
return nil, err
}
return out, nil
}

type Approval struct {
DocID int `json:"docid"`
Hidden *bool `json:"hidden,omitempty"`
Expand Down