Skip to content

Commit fdb17c9

Browse files
buty4649claude
andauthored
system: 登録フォーム一覧・フォーム定義取得サブコマンドを追加 (#29)
管理者向けの 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 <form_code|form_id>` — GET /api/v1/system/ forms/{fid} でフォーム定義情報を取得する。form_code を与えた場合 は system form list で id を解決する refs #25 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent b7f4f4d commit fdb17c9

File tree

5 files changed

+384
-0
lines changed

5 files changed

+384
-0
lines changed

cmd/system.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"strconv"
8+
"text/tabwriter"
9+
10+
"github.com/pepabo/xpoint-cli/internal/xpoint"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var (
15+
systemFormListOutput string
16+
systemFormListJQ string
17+
systemFormShowOutput string
18+
systemFormShowJQ string
19+
)
20+
21+
var systemCmd = &cobra.Command{
22+
Use: "system",
23+
Short: "X-point system (admin) APIs",
24+
}
25+
26+
var systemFormCmd = &cobra.Command{
27+
Use: "form",
28+
Short: "Manage X-point forms via admin APIs",
29+
}
30+
31+
var systemFormListCmd = &cobra.Command{
32+
Use: "list",
33+
Short: "List registration forms (admin)",
34+
Long: "List all registered forms via GET /api/v1/system/forms. Requires an administrator account.",
35+
RunE: runSystemFormList,
36+
}
37+
38+
var systemFormShowCmd = &cobra.Command{
39+
Use: "show <form_code|form_id>",
40+
Short: "Show a form definition (admin)",
41+
Long: `Fetch field definitions via GET /api/v1/system/forms/{fid}.
42+
43+
The argument may be a form_code (e.g. "TORIHIKISAKI_a") or a numeric
44+
form_id. When a form_code is given, the CLI first calls
45+
/api/v1/system/forms to resolve the id. Requires an administrator
46+
account.`,
47+
Args: cobra.ExactArgs(1),
48+
RunE: runSystemFormShow,
49+
}
50+
51+
func init() {
52+
rootCmd.AddCommand(systemCmd)
53+
systemCmd.AddCommand(systemFormCmd)
54+
systemFormCmd.AddCommand(systemFormListCmd)
55+
systemFormCmd.AddCommand(systemFormShowCmd)
56+
57+
lf := systemFormListCmd.Flags()
58+
lf.StringVarP(&systemFormListOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
59+
lf.StringVar(&systemFormListJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
60+
61+
sf := systemFormShowCmd.Flags()
62+
sf.StringVarP(&systemFormShowOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
63+
sf.StringVar(&systemFormShowJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
64+
}
65+
66+
func runSystemFormList(cmd *cobra.Command, args []string) error {
67+
client, err := newClientFromFlags(cmd.Context())
68+
if err != nil {
69+
return err
70+
}
71+
res, err := client.ListSystemForms(cmd.Context())
72+
if err != nil {
73+
return err
74+
}
75+
76+
return render(res, resolveOutputFormat(systemFormListOutput), systemFormListJQ, func() error {
77+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
78+
defer w.Flush()
79+
fmt.Fprintln(w, "GROUP_ID\tGROUP_NAME\tFORMS\tFORM_ID\tFORM_CODE\tFORM_NAME\tPAGES\tTABLE")
80+
for _, g := range res.FormGroup {
81+
if len(g.Form) == 0 {
82+
fmt.Fprintf(w, "%s\t%s\t%d\t-\t-\t-\t-\t-\n", g.ID, g.Name, g.FormCount)
83+
continue
84+
}
85+
for _, f := range g.Form {
86+
fmt.Fprintf(w, "%s\t%s\t%d\t%d\t%s\t%s\t%d\t%s\n",
87+
g.ID, g.Name, g.FormCount, f.ID, f.Code, f.Name, f.PageCount, f.TableName,
88+
)
89+
}
90+
}
91+
return nil
92+
})
93+
}
94+
95+
func runSystemFormShow(cmd *cobra.Command, args []string) error {
96+
client, err := newClientFromFlags(cmd.Context())
97+
if err != nil {
98+
return err
99+
}
100+
101+
formID, err := resolveSystemFormID(cmd.Context(), client, args[0])
102+
if err != nil {
103+
return err
104+
}
105+
106+
res, err := client.GetSystemFormDetail(cmd.Context(), formID)
107+
if err != nil {
108+
return err
109+
}
110+
111+
return render(res, resolveOutputFormat(systemFormShowOutput), systemFormShowJQ, func() error {
112+
form := res.Form
113+
fmt.Fprintf(os.Stdout, "FORM: %s %s MAX_STEP: %d\n", form.Code, form.Name, form.MaxStep)
114+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
115+
defer w.Flush()
116+
fmt.Fprintln(w, "PAGE\tFIELD_ID\tTYPE\tREQUIRED\tUNIQUE\tARRAYSIZE\tLABEL")
117+
for _, p := range form.Pages {
118+
for _, f := range p.Fields {
119+
fmt.Fprintf(w, "%d\t%s\t%d\t%t\t%t\t%d\t%s\n",
120+
p.PageNo, f.FieldID, f.FieldType, f.Required, f.Unique, f.ArraySize, f.Label,
121+
)
122+
}
123+
}
124+
return nil
125+
})
126+
}
127+
128+
type systemFormLister interface {
129+
ListSystemForms(ctx context.Context) (*xpoint.SystemFormsListResponse, error)
130+
}
131+
132+
// resolveSystemFormID mirrors resolveFormID but consults the admin forms list.
133+
func resolveSystemFormID(ctx context.Context, lister systemFormLister, arg string) (int, error) {
134+
if id, err := strconv.Atoi(arg); err == nil {
135+
return id, nil
136+
}
137+
forms, err := lister.ListSystemForms(ctx)
138+
if err != nil {
139+
return 0, fmt.Errorf("resolve form code: %w", err)
140+
}
141+
for _, g := range forms.FormGroup {
142+
for _, f := range g.Form {
143+
if f.Code == arg {
144+
return f.ID, nil
145+
}
146+
}
147+
}
148+
return 0, fmt.Errorf("form code %q not found", arg)
149+
}

internal/schema/schema_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ func TestAliases_Sorted(t *testing.T) {
3636
"query.exec",
3737
"query.graph",
3838
"query.list",
39+
"system.form.list",
40+
"system.form.show",
3941
}
4042
if len(got) != len(want) {
4143
t.Fatalf("aliases = %v", got)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"method": "GET",
3+
"path": "/api/v1/system/forms",
4+
"summary": "登録フォーム一覧取得 (管理者)",
5+
"description": "管理者サイトの「フォーム管理」で表示される一覧相当のリストを取得する。\n管理者権限を持つユーザで認証した場合のみ利用可能。\n",
6+
"parameters": [],
7+
"response": {
8+
"type": "object",
9+
"properties": {
10+
"form_group": {
11+
"type": "array",
12+
"description": "フォームグループ情報 (存在しない場合は空配列)",
13+
"items": {
14+
"type": "object",
15+
"properties": {
16+
"id": {
17+
"type": "string",
18+
"description": "フォームグループID"
19+
},
20+
"name": {
21+
"type": "string",
22+
"description": "フォームグループ名称"
23+
},
24+
"form_count": {
25+
"type": "integer",
26+
"description": "所属フォーム数"
27+
},
28+
"form": {
29+
"type": "array",
30+
"description": "所属フォーム情報 (存在しない場合は空配列)",
31+
"items": {
32+
"type": "object",
33+
"properties": {
34+
"id": {
35+
"type": "integer",
36+
"description": "フォームID"
37+
},
38+
"name": {
39+
"type": "string",
40+
"description": "フォーム名称"
41+
},
42+
"code": {
43+
"type": "string",
44+
"description": "フォームコード"
45+
},
46+
"page_count": {
47+
"type": "integer",
48+
"description": "フォーム構成ページ数"
49+
},
50+
"table_name": {
51+
"type": "string",
52+
"description": "DBテーブル名"
53+
},
54+
"tsffile_name": {
55+
"type": "string",
56+
"description": "TSFファイル名"
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
}
66+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
{
2+
"method": "GET",
3+
"path": "/api/v1/system/forms/{fid}",
4+
"summary": "フォーム定義情報取得 (管理者)",
5+
"description": "フォームを構成するフィールドの詳細情報を取得する。\n管理者ユーザで認証した場合のみフォーム定義が取得できる。\n一般ユーザで取得する場合は /api/v1/forms/{fid} (form show) を利用する。\n",
6+
"parameters": [
7+
{
8+
"name": "fid",
9+
"in": "path",
10+
"required": true,
11+
"type": "integer",
12+
"description": "フォームID"
13+
}
14+
],
15+
"response": {
16+
"type": "object",
17+
"properties": {
18+
"form": {
19+
"type": "object",
20+
"properties": {
21+
"code": {
22+
"type": "string",
23+
"description": "フォームコード"
24+
},
25+
"name": {
26+
"type": "string",
27+
"description": "フォーム名称"
28+
},
29+
"max_step": {
30+
"type": "integer",
31+
"description": "フォーム最大ステップ数"
32+
},
33+
"route": {
34+
"type": "array",
35+
"description": "フォームルート情報 (通常フォームでは空配列)",
36+
"items": {
37+
"type": "object",
38+
"properties": {
39+
"code": {
40+
"type": "string",
41+
"description": "ルートコード"
42+
},
43+
"name": {
44+
"type": "string",
45+
"description": "ルート名称"
46+
},
47+
"condroute": {
48+
"type": "boolean",
49+
"description": "true: 条件付きルート / false: ユーザ選択ルート"
50+
}
51+
}
52+
}
53+
},
54+
"pages": {
55+
"type": "array",
56+
"description": "フォームページ情報",
57+
"items": {
58+
"type": "object",
59+
"properties": {
60+
"page_no": {
61+
"type": "integer",
62+
"description": "ページ番号"
63+
},
64+
"form_code": {
65+
"type": "string",
66+
"description": "複数枚フォームの場合のみ出力"
67+
},
68+
"form_name": {
69+
"type": "string",
70+
"description": "複数枚フォームの場合のみ出力"
71+
},
72+
"fields": {
73+
"type": "array",
74+
"description": "フィールド情報",
75+
"items": {
76+
"type": "object",
77+
"properties": {
78+
"seq": {
79+
"type": "integer",
80+
"description": "シーケンス番号"
81+
},
82+
"fieldid": {
83+
"type": "string",
84+
"description": "フィールドID"
85+
},
86+
"fieldtype": {
87+
"type": "integer",
88+
"description": "フィールドタイプ"
89+
},
90+
"maxlength": {
91+
"type": "integer",
92+
"description": "フィールド最大値"
93+
},
94+
"label": {
95+
"type": "string",
96+
"description": "フィールドラベル"
97+
},
98+
"groupname": {
99+
"type": "string",
100+
"description": "グループ名称"
101+
},
102+
"arraysize": {
103+
"type": "integer",
104+
"description": "表明細数 (0なら非表明細)"
105+
},
106+
"required": {
107+
"type": "boolean",
108+
"description": "必須フィールドフラグ"
109+
},
110+
"unique": {
111+
"type": "boolean",
112+
"description": "ユニークフィールドフラグ"
113+
}
114+
}
115+
}
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)