From d81dba2bdb829fde7f6dd7db95144a12f18f5480 Mon Sep 17 00:00:00 2001 From: buty4649 Date: Fri, 17 Apr 2026 17:07:07 +0900 Subject: [PATCH] =?UTF-8?q?system:=20=E7=99=BB=E9=8C=B2=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=BC=E3=83=A0=E4=B8=80=E8=A6=A7=E3=83=BB=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=BC=E3=83=A0=E5=AE=9A=E7=BE=A9=E5=8F=96=E5=BE=97=E3=82=B5?= =?UTF-8?q?=E3=83=96=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 管理者向けの system API を呼び出す `xpoint system form` サブコマンド群 を追加。 - `xpoint system form list` — GET /api/v1/system/forms で登録フォーム 一覧 (form_count / page_count / table_name / tsffile_name 付き) を 取得する - `xpoint system form show ` — GET /api/v1/system/ forms/{fid} でフォーム定義情報を取得する。form_code を与えた場合 は system form list で id を解決する refs #25 Co-Authored-By: Claude Opus 4.7 --- cmd/system.go | 149 ++++++++++++++++++++++++++ internal/schema/schema_test.go | 2 + internal/schema/system.form.list.json | 66 ++++++++++++ internal/schema/system.form.show.json | 124 +++++++++++++++++++++ internal/xpoint/client.go | 43 ++++++++ 5 files changed, 384 insertions(+) create mode 100644 cmd/system.go create mode 100644 internal/schema/system.form.list.json create mode 100644 internal/schema/system.form.show.json diff --git a/cmd/system.go b/cmd/system.go new file mode 100644 index 0000000..5402a04 --- /dev/null +++ b/cmd/system.go @@ -0,0 +1,149 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "strconv" + "text/tabwriter" + + "github.com/pepabo/xpoint-cli/internal/xpoint" + "github.com/spf13/cobra" +) + +var ( + systemFormListOutput string + systemFormListJQ string + systemFormShowOutput string + systemFormShowJQ string +) + +var systemCmd = &cobra.Command{ + Use: "system", + Short: "X-point system (admin) APIs", +} + +var systemFormCmd = &cobra.Command{ + Use: "form", + Short: "Manage X-point forms via admin APIs", +} + +var systemFormListCmd = &cobra.Command{ + Use: "list", + Short: "List registration forms (admin)", + Long: "List all registered forms via GET /api/v1/system/forms. Requires an administrator account.", + RunE: runSystemFormList, +} + +var systemFormShowCmd = &cobra.Command{ + Use: "show ", + Short: "Show a form definition (admin)", + Long: `Fetch field definitions via GET /api/v1/system/forms/{fid}. + +The argument may be a form_code (e.g. "TORIHIKISAKI_a") or a numeric +form_id. When a form_code is given, the CLI first calls +/api/v1/system/forms to resolve the id. Requires an administrator +account.`, + Args: cobra.ExactArgs(1), + RunE: runSystemFormShow, +} + +func init() { + rootCmd.AddCommand(systemCmd) + systemCmd.AddCommand(systemFormCmd) + systemFormCmd.AddCommand(systemFormListCmd) + systemFormCmd.AddCommand(systemFormShowCmd) + + lf := systemFormListCmd.Flags() + lf.StringVarP(&systemFormListOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)") + lf.StringVar(&systemFormListJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)") + + sf := systemFormShowCmd.Flags() + sf.StringVarP(&systemFormShowOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)") + sf.StringVar(&systemFormShowJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)") +} + +func runSystemFormList(cmd *cobra.Command, args []string) error { + client, err := newClientFromFlags(cmd.Context()) + if err != nil { + return err + } + res, err := client.ListSystemForms(cmd.Context()) + if err != nil { + return err + } + + return render(res, resolveOutputFormat(systemFormListOutput), systemFormListJQ, func() error { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + defer w.Flush() + fmt.Fprintln(w, "GROUP_ID\tGROUP_NAME\tFORMS\tFORM_ID\tFORM_CODE\tFORM_NAME\tPAGES\tTABLE") + for _, g := range res.FormGroup { + if len(g.Form) == 0 { + fmt.Fprintf(w, "%s\t%s\t%d\t-\t-\t-\t-\t-\n", g.ID, g.Name, g.FormCount) + continue + } + for _, f := range g.Form { + fmt.Fprintf(w, "%s\t%s\t%d\t%d\t%s\t%s\t%d\t%s\n", + g.ID, g.Name, g.FormCount, f.ID, f.Code, f.Name, f.PageCount, f.TableName, + ) + } + } + return nil + }) +} + +func runSystemFormShow(cmd *cobra.Command, args []string) error { + client, err := newClientFromFlags(cmd.Context()) + if err != nil { + return err + } + + formID, err := resolveSystemFormID(cmd.Context(), client, args[0]) + if err != nil { + return err + } + + res, err := client.GetSystemFormDetail(cmd.Context(), formID) + if err != nil { + return err + } + + return render(res, resolveOutputFormat(systemFormShowOutput), systemFormShowJQ, func() error { + form := res.Form + fmt.Fprintf(os.Stdout, "FORM: %s %s MAX_STEP: %d\n", form.Code, form.Name, form.MaxStep) + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + defer w.Flush() + fmt.Fprintln(w, "PAGE\tFIELD_ID\tTYPE\tREQUIRED\tUNIQUE\tARRAYSIZE\tLABEL") + for _, p := range form.Pages { + for _, f := range p.Fields { + fmt.Fprintf(w, "%d\t%s\t%d\t%t\t%t\t%d\t%s\n", + p.PageNo, f.FieldID, f.FieldType, f.Required, f.Unique, f.ArraySize, f.Label, + ) + } + } + return nil + }) +} + +type systemFormLister interface { + ListSystemForms(ctx context.Context) (*xpoint.SystemFormsListResponse, error) +} + +// resolveSystemFormID mirrors resolveFormID but consults the admin forms list. +func resolveSystemFormID(ctx context.Context, lister systemFormLister, arg string) (int, error) { + if id, err := strconv.Atoi(arg); err == nil { + return id, nil + } + forms, err := lister.ListSystemForms(ctx) + if err != nil { + return 0, fmt.Errorf("resolve form code: %w", err) + } + for _, g := range forms.FormGroup { + for _, f := range g.Form { + if f.Code == arg { + return f.ID, nil + } + } + } + return 0, fmt.Errorf("form code %q not found", arg) +} diff --git a/internal/schema/schema_test.go b/internal/schema/schema_test.go index e2d69c7..3823da8 100644 --- a/internal/schema/schema_test.go +++ b/internal/schema/schema_test.go @@ -24,6 +24,8 @@ func TestAliases_Sorted(t *testing.T) { "form.show", "query.exec", "query.list", + "system.form.list", + "system.form.show", } if len(got) != len(want) { t.Fatalf("aliases = %v", got) diff --git a/internal/schema/system.form.list.json b/internal/schema/system.form.list.json new file mode 100644 index 0000000..71791ce --- /dev/null +++ b/internal/schema/system.form.list.json @@ -0,0 +1,66 @@ +{ + "method": "GET", + "path": "/api/v1/system/forms", + "summary": "登録フォーム一覧取得 (管理者)", + "description": "管理者サイトの「フォーム管理」で表示される一覧相当のリストを取得する。\n管理者権限を持つユーザで認証した場合のみ利用可能。\n", + "parameters": [], + "response": { + "type": "object", + "properties": { + "form_group": { + "type": "array", + "description": "フォームグループ情報 (存在しない場合は空配列)", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "フォームグループID" + }, + "name": { + "type": "string", + "description": "フォームグループ名称" + }, + "form_count": { + "type": "integer", + "description": "所属フォーム数" + }, + "form": { + "type": "array", + "description": "所属フォーム情報 (存在しない場合は空配列)", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "フォームID" + }, + "name": { + "type": "string", + "description": "フォーム名称" + }, + "code": { + "type": "string", + "description": "フォームコード" + }, + "page_count": { + "type": "integer", + "description": "フォーム構成ページ数" + }, + "table_name": { + "type": "string", + "description": "DBテーブル名" + }, + "tsffile_name": { + "type": "string", + "description": "TSFファイル名" + } + } + } + } + } + } + } + } + } +} diff --git a/internal/schema/system.form.show.json b/internal/schema/system.form.show.json new file mode 100644 index 0000000..ac313bd --- /dev/null +++ b/internal/schema/system.form.show.json @@ -0,0 +1,124 @@ +{ + "method": "GET", + "path": "/api/v1/system/forms/{fid}", + "summary": "フォーム定義情報取得 (管理者)", + "description": "フォームを構成するフィールドの詳細情報を取得する。\n管理者ユーザで認証した場合のみフォーム定義が取得できる。\n一般ユーザで取得する場合は /api/v1/forms/{fid} (form show) を利用する。\n", + "parameters": [ + { + "name": "fid", + "in": "path", + "required": true, + "type": "integer", + "description": "フォームID" + } + ], + "response": { + "type": "object", + "properties": { + "form": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "フォームコード" + }, + "name": { + "type": "string", + "description": "フォーム名称" + }, + "max_step": { + "type": "integer", + "description": "フォーム最大ステップ数" + }, + "route": { + "type": "array", + "description": "フォームルート情報 (通常フォームでは空配列)", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "ルートコード" + }, + "name": { + "type": "string", + "description": "ルート名称" + }, + "condroute": { + "type": "boolean", + "description": "true: 条件付きルート / false: ユーザ選択ルート" + } + } + } + }, + "pages": { + "type": "array", + "description": "フォームページ情報", + "items": { + "type": "object", + "properties": { + "page_no": { + "type": "integer", + "description": "ページ番号" + }, + "form_code": { + "type": "string", + "description": "複数枚フォームの場合のみ出力" + }, + "form_name": { + "type": "string", + "description": "複数枚フォームの場合のみ出力" + }, + "fields": { + "type": "array", + "description": "フィールド情報", + "items": { + "type": "object", + "properties": { + "seq": { + "type": "integer", + "description": "シーケンス番号" + }, + "fieldid": { + "type": "string", + "description": "フィールドID" + }, + "fieldtype": { + "type": "integer", + "description": "フィールドタイプ" + }, + "maxlength": { + "type": "integer", + "description": "フィールド最大値" + }, + "label": { + "type": "string", + "description": "フィールドラベル" + }, + "groupname": { + "type": "string", + "description": "グループ名称" + }, + "arraysize": { + "type": "integer", + "description": "表明細数 (0なら非表明細)" + }, + "required": { + "type": "boolean", + "description": "必須フィールドフラグ" + }, + "unique": { + "type": "boolean", + "description": "ユニークフィールドフラグ" + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/internal/xpoint/client.go b/internal/xpoint/client.go index ff4c7ff..2e93138 100644 --- a/internal/xpoint/client.go +++ b/internal/xpoint/client.go @@ -147,6 +147,49 @@ func (c *Client) GetFormDetail(ctx context.Context, formID int) (*FormDetailResp return &out, nil } +// SystemForm is an entry in the admin-side form list (GET /api/v1/system/forms). +// It carries extra fields (page_count, table_name, tsffile_name) compared to +// the user-facing form list. +type SystemForm struct { + ID int `json:"id"` + Name string `json:"name"` + Code string `json:"code"` + PageCount int `json:"page_count"` + TableName string `json:"table_name"` + TsfFileName string `json:"tsffile_name"` +} + +type SystemFormGroup struct { + ID string `json:"id"` + Name string `json:"name"` + FormCount int `json:"form_count"` + Form []SystemForm `json:"form"` +} + +type SystemFormsListResponse struct { + FormGroup []SystemFormGroup `json:"form_group"` +} + +// ListSystemForms calls GET /api/v1/system/forms (admin: 登録フォーム一覧取得). +func (c *Client) ListSystemForms(ctx context.Context) (*SystemFormsListResponse, error) { + var out SystemFormsListResponse + if err := c.do(ctx, http.MethodGet, "/api/v1/system/forms", nil, nil, &out); err != nil { + return nil, err + } + return &out, nil +} + +// GetSystemFormDetail calls GET /api/v1/system/forms/{fid} (admin) and returns +// the same shape as GetFormDetail. +func (c *Client) GetSystemFormDetail(ctx context.Context, formID int) (*FormDetailResponse, error) { + path := fmt.Sprintf("/api/v1/system/forms/%d", formID) + var out FormDetailResponse + 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"`