Skip to content

Commit 9d29113

Browse files
buty4649claude
andauthored
document: コメントの追加・取得・更新・削除サブコマンドを追加 (#23)
- xp document comment add <docid>: POST /api/v1/documents/{docid}/comments - xp document comment get <docid>: GET /api/v1/documents/{docid}/comments - xp document comment edit <docid> <seq>: PATCH /api/v1/documents/{docid}/comments/{seq} - xp document comment delete <docid> <seq>: DELETE /api/v1/documents/{docid}/comments/{seq} - schema document.comment.add/get/edit/delete を追加 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ee568f8 commit 9d29113

10 files changed

Lines changed: 718 additions & 0 deletions

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ xp document open 266248 # 既定のブラウザで書類を開く
124124
xp document open 266248 --no-browser # URLだけ出力(ブラウザは起動しない)
125125
```
126126

127+
### ドキュメントのコメント操作
128+
129+
```sh
130+
xp document comment get 266248 # コメント一覧
131+
xp document comment add 266248 --content "承認お願いします" # コメント追加
132+
xp document comment add 266248 --content "重要" --attention # 重要コメント
133+
xp document comment edit 266248 2 --content "修正後" # 内容を更新
134+
xp document comment edit 266248 2 --attention 1 # 重要フラグだけ更新
135+
xp document comment delete 266248 2 # コメント削除(確認あり)
136+
xp document comment delete 266248 2 -y # 確認なしで削除
137+
```
138+
127139
### ドキュメントのPDFダウンロード
128140

129141
```sh

cmd/document.go

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ var (
4343
docStatusJQ string
4444

4545
docOpenNoBrowser bool
46+
47+
docCommentAddContent string
48+
docCommentAddAttention bool
49+
docCommentAddOutput string
50+
docCommentAddJQ string
51+
docCommentGetOutput string
52+
docCommentGetJQ string
53+
docCommentEditContent string
54+
docCommentEditAttention string
55+
docCommentEditOutput string
56+
docCommentEditJQ string
57+
docCommentDeleteYes bool
58+
docCommentDeleteOutput string
59+
docCommentDeleteJQ string
4660
)
4761

4862
var documentCmd = &cobra.Command{
@@ -140,6 +154,51 @@ Pass --no-browser (or -n) to print the URL without launching the browser.`,
140154
RunE: runDocumentOpen,
141155
}
142156

157+
var documentCommentCmd = &cobra.Command{
158+
Use: "comment",
159+
Short: "Manage document comments",
160+
}
161+
162+
var documentCommentAddCmd = &cobra.Command{
163+
Use: "add <docid>",
164+
Short: "Add a comment to a document",
165+
Long: `Add a comment to a document via POST /api/v1/documents/{docid}/comments.
166+
167+
The comment body is provided with --content (required). Pass --attention
168+
to mark the comment as important.`,
169+
Args: cobra.ExactArgs(1),
170+
RunE: runDocumentCommentAdd,
171+
}
172+
173+
var documentCommentGetCmd = &cobra.Command{
174+
Use: "get <docid>",
175+
Short: "Get comments on a document",
176+
Long: `List comments on a document via GET /api/v1/documents/{docid}/comments.`,
177+
Args: cobra.ExactArgs(1),
178+
RunE: runDocumentCommentGet,
179+
}
180+
181+
var documentCommentEditCmd = &cobra.Command{
182+
Use: "edit <docid> <seq>",
183+
Short: "Update a comment",
184+
Long: `Update a comment via PATCH /api/v1/documents/{docid}/comments/{seq}.
185+
186+
At least one of --content / --attention must be provided. --attention accepts
187+
"0" (normal) or "1" (important); omit to leave unchanged.`,
188+
Args: cobra.ExactArgs(2),
189+
RunE: runDocumentCommentEdit,
190+
}
191+
192+
var documentCommentDeleteCmd = &cobra.Command{
193+
Use: "delete <docid> <seq>",
194+
Short: "Delete a comment",
195+
Long: `Delete a comment via DELETE /api/v1/documents/{docid}/comments/{seq}.
196+
197+
By default the command prompts for confirmation. Pass --yes to skip it.`,
198+
Args: cobra.ExactArgs(2),
199+
RunE: runDocumentCommentDelete,
200+
}
201+
143202
var documentDownloadCmd = &cobra.Command{
144203
Use: "download <docid>",
145204
Short: "Download a document as PDF",
@@ -164,6 +223,11 @@ func init() {
164223
documentCmd.AddCommand(documentStatusCmd)
165224
documentCmd.AddCommand(documentDownloadCmd)
166225
documentCmd.AddCommand(documentOpenCmd)
226+
documentCmd.AddCommand(documentCommentCmd)
227+
documentCommentCmd.AddCommand(documentCommentAddCmd)
228+
documentCommentCmd.AddCommand(documentCommentGetCmd)
229+
documentCommentCmd.AddCommand(documentCommentEditCmd)
230+
documentCommentCmd.AddCommand(documentCommentDeleteCmd)
167231

168232
f := documentSearchCmd.Flags()
169233
f.StringVar(&docSearchBody, "body", "", "search condition JSON: inline, file path, or - for stdin")
@@ -201,6 +265,27 @@ func init() {
201265

202266
of := documentOpenCmd.Flags()
203267
of.BoolVarP(&docOpenNoBrowser, "no-browser", "n", false, "print the URL without launching the browser")
268+
269+
caf := documentCommentAddCmd.Flags()
270+
caf.StringVar(&docCommentAddContent, "content", "", "comment content (required)")
271+
caf.BoolVar(&docCommentAddAttention, "attention", false, "mark as important comment (attentionflg=1)")
272+
caf.StringVarP(&docCommentAddOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
273+
caf.StringVar(&docCommentAddJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
274+
275+
cgf := documentCommentGetCmd.Flags()
276+
cgf.StringVarP(&docCommentGetOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
277+
cgf.StringVar(&docCommentGetJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
278+
279+
cef := documentCommentEditCmd.Flags()
280+
cef.StringVar(&docCommentEditContent, "content", "", "new comment content (omit to leave unchanged)")
281+
cef.StringVar(&docCommentEditAttention, "attention", "", "new attention flag: 0 (normal) | 1 (important); omit to leave unchanged")
282+
cef.StringVarP(&docCommentEditOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
283+
cef.StringVar(&docCommentEditJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
284+
285+
cdf := documentCommentDeleteCmd.Flags()
286+
cdf.BoolVarP(&docCommentDeleteYes, "yes", "y", false, "skip the interactive confirmation prompt")
287+
cdf.StringVarP(&docCommentDeleteOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
288+
cdf.StringVar(&docCommentDeleteJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
204289
}
205290

206291
func runDocumentSearch(cmd *cobra.Command, args []string) error {
@@ -440,6 +525,157 @@ func runDocumentOpen(_ *cobra.Command, args []string) error {
440525
return nil
441526
}
442527

528+
func runDocumentCommentAdd(cmd *cobra.Command, args []string) error {
529+
docID, err := parseDocID(args[0])
530+
if err != nil {
531+
return err
532+
}
533+
if docCommentAddContent == "" {
534+
return fmt.Errorf("--content is required")
535+
}
536+
client, err := newClientFromFlags(cmd.Context())
537+
if err != nil {
538+
return err
539+
}
540+
req := xpoint.AddCommentRequest{Content: docCommentAddContent}
541+
if docCommentAddAttention {
542+
req.AttentionFlg = 1
543+
}
544+
res, err := client.AddComment(cmd.Context(), docID, req)
545+
if err != nil {
546+
return err
547+
}
548+
return render(res, resolveOutputFormat(docCommentAddOutput), docCommentAddJQ, func() error {
549+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
550+
defer w.Flush()
551+
fmt.Fprintln(w, "DOCID\tSEQ\tMESSAGE_TYPE\tMESSAGE")
552+
fmt.Fprintf(w, "%d\t%d\t%d\t%s\n", res.DocID, res.Seq, res.MessageType, res.Message)
553+
return nil
554+
})
555+
}
556+
557+
func runDocumentCommentGet(cmd *cobra.Command, args []string) error {
558+
docID, err := parseDocID(args[0])
559+
if err != nil {
560+
return err
561+
}
562+
client, err := newClientFromFlags(cmd.Context())
563+
if err != nil {
564+
return err
565+
}
566+
res, err := client.GetComments(cmd.Context(), docID)
567+
if err != nil {
568+
return err
569+
}
570+
return render(res, resolveOutputFormat(docCommentGetOutput), docCommentGetJQ, func() error {
571+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
572+
defer w.Flush()
573+
fmt.Fprintln(w, "SEQ\tATTENTION\tWRITER\tWRITE_DATE\tCONTENT")
574+
for _, cm := range res.CommentList {
575+
attention := "-"
576+
if cm.AttentionFlg {
577+
attention = "*"
578+
}
579+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", cm.SeqNo, attention, cm.WriterName, cm.WriteDate, cm.Content)
580+
}
581+
return nil
582+
})
583+
}
584+
585+
func runDocumentCommentEdit(cmd *cobra.Command, args []string) error {
586+
docID, err := parseDocID(args[0])
587+
if err != nil {
588+
return err
589+
}
590+
seq, err := parseSeq(args[1])
591+
if err != nil {
592+
return err
593+
}
594+
if !cmd.Flags().Changed("content") && !cmd.Flags().Changed("attention") {
595+
return fmt.Errorf("at least one of --content / --attention is required")
596+
}
597+
req := xpoint.UpdateCommentRequest{}
598+
if cmd.Flags().Changed("content") {
599+
v := docCommentEditContent
600+
req.Content = &v
601+
}
602+
if cmd.Flags().Changed("attention") {
603+
switch docCommentEditAttention {
604+
case "0":
605+
v := 0
606+
req.AttentionFlg = &v
607+
case "1":
608+
v := 1
609+
req.AttentionFlg = &v
610+
default:
611+
return fmt.Errorf("--attention must be 0 or 1, got %q", docCommentEditAttention)
612+
}
613+
}
614+
client, err := newClientFromFlags(cmd.Context())
615+
if err != nil {
616+
return err
617+
}
618+
res, err := client.UpdateComment(cmd.Context(), docID, seq, req)
619+
if err != nil {
620+
return err
621+
}
622+
return render(res, resolveOutputFormat(docCommentEditOutput), docCommentEditJQ, func() error {
623+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
624+
defer w.Flush()
625+
fmt.Fprintln(w, "DOCID\tSEQ\tMESSAGE_TYPE\tMESSAGE")
626+
fmt.Fprintf(w, "%d\t%d\t%d\t%s\n", res.DocID, res.Seq, res.MessageType, res.Message)
627+
return nil
628+
})
629+
}
630+
631+
func runDocumentCommentDelete(cmd *cobra.Command, args []string) error {
632+
docID, err := parseDocID(args[0])
633+
if err != nil {
634+
return err
635+
}
636+
seq, err := parseSeq(args[1])
637+
if err != nil {
638+
return err
639+
}
640+
if !docCommentDeleteYes && !confirmDeleteComment(docID, seq) {
641+
return fmt.Errorf("aborted")
642+
}
643+
client, err := newClientFromFlags(cmd.Context())
644+
if err != nil {
645+
return err
646+
}
647+
res, err := client.DeleteComment(cmd.Context(), docID, seq)
648+
if err != nil {
649+
return err
650+
}
651+
return render(res, resolveOutputFormat(docCommentDeleteOutput), docCommentDeleteJQ, func() error {
652+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
653+
defer w.Flush()
654+
fmt.Fprintln(w, "DOCID\tSEQ\tMESSAGE_TYPE\tMESSAGE")
655+
fmt.Fprintf(w, "%d\t%d\t%d\t%s\n", res.DocID, res.Seq, res.MessageType, res.Message)
656+
return nil
657+
})
658+
}
659+
660+
func parseSeq(s string) (int, error) {
661+
n, err := strconv.Atoi(strings.TrimSpace(s))
662+
if err != nil || n <= 0 {
663+
return 0, fmt.Errorf("invalid seq %q: must be a positive integer", s)
664+
}
665+
return n, nil
666+
}
667+
668+
func confirmDeleteComment(docID, seq int) bool {
669+
fmt.Fprintf(os.Stderr, "Really delete comment seq=%d on document %d? [y/N]: ", seq, docID)
670+
var ans string
671+
_, _ = fmt.Fscanln(os.Stdin, &ans)
672+
switch strings.ToLower(strings.TrimSpace(ans)) {
673+
case "y", "yes":
674+
return true
675+
}
676+
return false
677+
}
678+
443679
// resolveDownloadPath decides the on-disk path for a downloaded PDF.
444680
//
445681
// When out is empty, the server-provided filename is used in the current

cmd/schema.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Supported aliases map to the CLI's commands:
2626
document.delete DELETE /api/v1/documents/{docid}
2727
document.download GET /api/v1/documents/{docid}/pdf
2828
document.status GET /api/v1/documents/{docid}/status
29+
document.comment.add POST /api/v1/documents/{docid}/comments
30+
document.comment.get GET /api/v1/documents/{docid}/comments
31+
document.comment.edit PATCH /api/v1/documents/{docid}/comments/{seq}
32+
document.comment.delete DELETE /api/v1/documents/{docid}/comments/{seq}
2933
query.list GET /api/v1/query/
3034
query.exec GET /api/v1/query/{query_code}
3135
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"method": "POST",
3+
"path": "/api/v1/documents/{docid}/comments",
4+
"summary": "コメント追加",
5+
"description": "指定された書類 ID にコメントを追加する。更新権限が必要。\n",
6+
"parameters": [
7+
{
8+
"name": "docid",
9+
"in": "path",
10+
"type": "integer",
11+
"required": true,
12+
"description": "書類ID"
13+
}
14+
],
15+
"requestBody": {
16+
"contentType": "application/json",
17+
"description": "コメント追加リクエスト。required: content, attentionflg。\n",
18+
"properties": {
19+
"content": {
20+
"type": "string",
21+
"required": true,
22+
"description": "コメント内容"
23+
},
24+
"attentionflg": {
25+
"type": "integer",
26+
"required": true,
27+
"description": "重要コメントフラグ (0: 通常 / 1: 重要)"
28+
}
29+
}
30+
},
31+
"response": {
32+
"type": "object",
33+
"properties": {
34+
"docid": {
35+
"type": "integer",
36+
"description": "書類ID"
37+
},
38+
"seq": {
39+
"type": "integer",
40+
"description": "コメント番号"
41+
},
42+
"message_type": {
43+
"type": "integer",
44+
"description": "メッセージタイプ (3: INFO 固定)"
45+
},
46+
"message": {
47+
"type": "string",
48+
"description": "メッセージ内容"
49+
}
50+
}
51+
}
52+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"method": "DELETE",
3+
"path": "/api/v1/documents/{docid}/comments/{seq}",
4+
"summary": "コメント削除",
5+
"description": "指定された書類 ID に登録されているコメントを削除する。削除権限が必要。\n",
6+
"parameters": [
7+
{
8+
"name": "docid",
9+
"in": "path",
10+
"type": "integer",
11+
"required": true,
12+
"description": "書類ID"
13+
},
14+
{
15+
"name": "seq",
16+
"in": "path",
17+
"type": "integer",
18+
"required": true,
19+
"description": "コメント番号"
20+
}
21+
],
22+
"response": {
23+
"type": "object",
24+
"properties": {
25+
"docid": {
26+
"type": "integer",
27+
"description": "書類ID"
28+
},
29+
"seq": {
30+
"type": "integer",
31+
"description": "コメント番号"
32+
},
33+
"message_type": {
34+
"type": "integer",
35+
"description": "メッセージタイプ (3: INFO 固定)"
36+
},
37+
"message": {
38+
"type": "string",
39+
"description": "メッセージ内容"
40+
}
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)