fizzy-cli 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/cmd/board_create.go +15 -16
- package/cmd/board_create_test.go +0 -17
- package/cmd/card_create.go +24 -25
- package/cmd/card_create_test.go +20 -40
- package/cmd/card_golden.go +46 -0
- package/cmd/card_golden_test.go +92 -0
- package/cmd/card_not_now.go +46 -0
- package/cmd/card_not_now_test.go +92 -0
- package/cmd/card_tag.go +51 -0
- package/cmd/card_tag_test.go +112 -0
- package/cmd/card_ungolden.go +46 -0
- package/cmd/card_ungolden_test.go +92 -0
- package/cmd/card_untriage.go +46 -0
- package/cmd/card_untriage_test.go +92 -0
- package/cmd/card_unwatch.go +46 -0
- package/cmd/card_unwatch_test.go +92 -0
- package/cmd/card_update.go +18 -19
- package/cmd/card_update_test.go +28 -24
- package/cmd/card_watch.go +46 -0
- package/cmd/card_watch_test.go +92 -0
- package/cmd/column_create.go +11 -12
- package/cmd/column_create_test.go +3 -21
- package/cmd/comment.go +14 -0
- package/cmd/comment_create.go +51 -0
- package/cmd/comment_create_test.go +129 -0
- package/cmd/comment_list.go +51 -0
- package/cmd/comment_list_test.go +132 -0
- package/cmd/comment_show.go +46 -0
- package/cmd/comment_show_test.go +104 -0
- package/cmd/tag.go +15 -0
- package/cmd/tag_list.go +47 -0
- package/cmd/tag_list_test.go +109 -0
- package/docs/API.md +36 -0
- package/internal/api/client.go +211 -1
- package/internal/ui/comment_list.go +25 -0
- package/package.json +1 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"net/http"
|
|
7
|
+
"net/http/httptest"
|
|
8
|
+
"testing"
|
|
9
|
+
|
|
10
|
+
"github.com/rogeriopvl/fizzy/internal/api"
|
|
11
|
+
"github.com/rogeriopvl/fizzy/internal/app"
|
|
12
|
+
"github.com/rogeriopvl/fizzy/internal/testutil"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func TestCommentShowCommandSuccess(t *testing.T) {
|
|
16
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
17
|
+
if r.URL.Path != "/cards/123/comments/comment-456" {
|
|
18
|
+
t.Errorf("expected /cards/123/comments/comment-456, got %s", r.URL.Path)
|
|
19
|
+
}
|
|
20
|
+
if r.Method != http.MethodGet {
|
|
21
|
+
t.Errorf("expected GET, got %s", r.Method)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
auth := r.Header.Get("Authorization")
|
|
25
|
+
if auth != "Bearer test-token" {
|
|
26
|
+
t.Errorf("expected Bearer test-token, got %s", auth)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
w.Header().Set("Content-Type", "application/json")
|
|
30
|
+
response := api.Comment{
|
|
31
|
+
ID: "comment-456",
|
|
32
|
+
CreatedAt: "2025-01-01T00:00:00Z",
|
|
33
|
+
UpdatedAt: "2025-01-01T00:00:00Z",
|
|
34
|
+
Creator: api.User{ID: "user-1", Name: "John Doe"},
|
|
35
|
+
Card: api.CardReference{ID: "card-123", Title: "Test Card"},
|
|
36
|
+
}
|
|
37
|
+
response.Body.PlainText = "This is a test comment"
|
|
38
|
+
response.Body.HTML = "<p>This is a test comment</p>"
|
|
39
|
+
json.NewEncoder(w).Encode(response)
|
|
40
|
+
}))
|
|
41
|
+
defer server.Close()
|
|
42
|
+
|
|
43
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
44
|
+
testApp := &app.App{Client: client}
|
|
45
|
+
|
|
46
|
+
cmd := commentShowCmd
|
|
47
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
48
|
+
|
|
49
|
+
if err := handleShowComment(cmd, "123", "comment-456"); err != nil {
|
|
50
|
+
t.Fatalf("handleShowComment failed: %v", err)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func TestCommentShowCommandInvalidCardNumber(t *testing.T) {
|
|
55
|
+
testApp := &app.App{}
|
|
56
|
+
|
|
57
|
+
cmd := commentShowCmd
|
|
58
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
59
|
+
|
|
60
|
+
err := handleShowComment(cmd, "not-a-number", "comment-456")
|
|
61
|
+
if err == nil {
|
|
62
|
+
t.Errorf("expected error for invalid card number")
|
|
63
|
+
}
|
|
64
|
+
if err.Error() != "invalid card number: strconv.Atoi: parsing \"not-a-number\": invalid syntax" {
|
|
65
|
+
t.Errorf("expected invalid card number error, got %v", err)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func TestCommentShowCommandNoClient(t *testing.T) {
|
|
70
|
+
testApp := &app.App{}
|
|
71
|
+
|
|
72
|
+
cmd := commentShowCmd
|
|
73
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
74
|
+
|
|
75
|
+
err := handleShowComment(cmd, "123", "comment-456")
|
|
76
|
+
if err == nil {
|
|
77
|
+
t.Errorf("expected error when client not available")
|
|
78
|
+
}
|
|
79
|
+
if err.Error() != "API client not available" {
|
|
80
|
+
t.Errorf("expected 'client not available' error, got %v", err)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func TestCommentShowCommandAPIError(t *testing.T) {
|
|
85
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
86
|
+
w.WriteHeader(http.StatusNotFound)
|
|
87
|
+
w.Write([]byte("Comment not found"))
|
|
88
|
+
}))
|
|
89
|
+
defer server.Close()
|
|
90
|
+
|
|
91
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
92
|
+
testApp := &app.App{Client: client}
|
|
93
|
+
|
|
94
|
+
cmd := commentShowCmd
|
|
95
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
96
|
+
|
|
97
|
+
err := handleShowComment(cmd, "123", "comment-456")
|
|
98
|
+
if err == nil {
|
|
99
|
+
t.Errorf("expected error for API failure")
|
|
100
|
+
}
|
|
101
|
+
if err.Error() != "fetching comment: unexpected status code 404: Comment not found" {
|
|
102
|
+
t.Errorf("expected API error, got %v", err)
|
|
103
|
+
}
|
|
104
|
+
}
|
package/cmd/tag.go
ADDED
package/cmd/tag_list.go
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
|
|
7
|
+
"github.com/rogeriopvl/fizzy/internal/app"
|
|
8
|
+
"github.com/spf13/cobra"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
var tagListCmd = &cobra.Command{
|
|
12
|
+
Use: "list",
|
|
13
|
+
Short: "List all tags",
|
|
14
|
+
Long: `Retrieve and display all tags in the account`,
|
|
15
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
16
|
+
if err := handleListTags(cmd); err != nil {
|
|
17
|
+
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func handleListTags(cmd *cobra.Command) error {
|
|
23
|
+
a := app.FromContext(cmd.Context())
|
|
24
|
+
if a == nil || a.Client == nil {
|
|
25
|
+
return fmt.Errorf("API client not available")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
tags, err := a.Client.GetTags(context.Background())
|
|
29
|
+
if err != nil {
|
|
30
|
+
return fmt.Errorf("fetching tags: %w", err)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if len(tags) == 0 {
|
|
34
|
+
fmt.Println("No tags found")
|
|
35
|
+
return nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for _, tag := range tags {
|
|
39
|
+
fmt.Printf("%s\n", tag.Title)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return nil
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func init() {
|
|
46
|
+
tagCmd.AddCommand(tagListCmd)
|
|
47
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"net/http"
|
|
7
|
+
"net/http/httptest"
|
|
8
|
+
"testing"
|
|
9
|
+
|
|
10
|
+
"github.com/rogeriopvl/fizzy/internal/api"
|
|
11
|
+
"github.com/rogeriopvl/fizzy/internal/app"
|
|
12
|
+
"github.com/rogeriopvl/fizzy/internal/testutil"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func TestTagListCommand(t *testing.T) {
|
|
16
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
17
|
+
if r.URL.Path != "/tags" {
|
|
18
|
+
t.Errorf("expected /tags, got %s", r.URL.Path)
|
|
19
|
+
}
|
|
20
|
+
if r.Method != http.MethodGet {
|
|
21
|
+
t.Errorf("expected GET, got %s", r.Method)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
auth := r.Header.Get("Authorization")
|
|
25
|
+
if auth != "Bearer test-token" {
|
|
26
|
+
t.Errorf("expected Bearer test-token, got %s", auth)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
w.Header().Set("Content-Type", "application/json")
|
|
30
|
+
response := []api.Tag{
|
|
31
|
+
{
|
|
32
|
+
ID: "tag-123",
|
|
33
|
+
Title: "bug",
|
|
34
|
+
CreatedAt: "2025-01-01T00:00:00Z",
|
|
35
|
+
URL: "http://fizzy.localhost:3006/897362094/cards?tag_ids[]=tag-123",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
ID: "tag-456",
|
|
39
|
+
Title: "feature",
|
|
40
|
+
CreatedAt: "2025-01-02T00:00:00Z",
|
|
41
|
+
URL: "http://fizzy.localhost:3006/897362094/cards?tag_ids[]=tag-456",
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
json.NewEncoder(w).Encode(response)
|
|
45
|
+
}))
|
|
46
|
+
defer server.Close()
|
|
47
|
+
|
|
48
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
49
|
+
testApp := &app.App{Client: client}
|
|
50
|
+
|
|
51
|
+
cmd := tagListCmd
|
|
52
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
53
|
+
|
|
54
|
+
if err := handleListTags(cmd); err != nil {
|
|
55
|
+
t.Fatalf("handleListTags failed: %v", err)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func TestTagListCommandNoTags(t *testing.T) {
|
|
60
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
61
|
+
w.Header().Set("Content-Type", "application/json")
|
|
62
|
+
w.Write([]byte("[]"))
|
|
63
|
+
}))
|
|
64
|
+
defer server.Close()
|
|
65
|
+
|
|
66
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
67
|
+
testApp := &app.App{Client: client}
|
|
68
|
+
|
|
69
|
+
cmd := tagListCmd
|
|
70
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
71
|
+
|
|
72
|
+
if err := handleListTags(cmd); err != nil {
|
|
73
|
+
t.Fatalf("handleListTags with no tags failed: %v", err)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func TestTagListCommandAPIError(t *testing.T) {
|
|
78
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
79
|
+
w.WriteHeader(http.StatusInternalServerError)
|
|
80
|
+
w.Write([]byte("Internal Server Error"))
|
|
81
|
+
}))
|
|
82
|
+
defer server.Close()
|
|
83
|
+
|
|
84
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
85
|
+
testApp := &app.App{Client: client}
|
|
86
|
+
|
|
87
|
+
cmd := tagListCmd
|
|
88
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
89
|
+
|
|
90
|
+
err := handleListTags(cmd)
|
|
91
|
+
if err == nil {
|
|
92
|
+
t.Errorf("expected error for API failure")
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func TestTagListCommandNoClient(t *testing.T) {
|
|
97
|
+
testApp := &app.App{}
|
|
98
|
+
|
|
99
|
+
cmd := tagListCmd
|
|
100
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
101
|
+
|
|
102
|
+
err := handleListTags(cmd)
|
|
103
|
+
if err == nil {
|
|
104
|
+
t.Errorf("expected error when client not available")
|
|
105
|
+
}
|
|
106
|
+
if err.Error() != "API client not available" {
|
|
107
|
+
t.Errorf("expected 'client not available' error, got %v", err)
|
|
108
|
+
}
|
|
109
|
+
}
|
package/docs/API.md
CHANGED
|
@@ -574,6 +574,7 @@ __Response:__
|
|
|
574
574
|
"description_html": "<div class=\"action-text-content\"><p>Hello, World!</p></div>",
|
|
575
575
|
"image_url": null,
|
|
576
576
|
"tags": ["programming"],
|
|
577
|
+
"closed": false,
|
|
577
578
|
"golden": false,
|
|
578
579
|
"last_active_at": "2025-12-05T19:38:48.553Z",
|
|
579
580
|
"created_at": "2025-12-05T19:38:48.540Z",
|
|
@@ -594,6 +595,15 @@ __Response:__
|
|
|
594
595
|
"url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7"
|
|
595
596
|
}
|
|
596
597
|
},
|
|
598
|
+
"column": {
|
|
599
|
+
"id": "03f5v9zkft4hj9qq0lsn9ohcn",
|
|
600
|
+
"name": "In Progress",
|
|
601
|
+
"color": {
|
|
602
|
+
"name": "Lime",
|
|
603
|
+
"value": "var(--color-card-4)"
|
|
604
|
+
},
|
|
605
|
+
"created_at": "2025-12-05T19:36:35.534Z"
|
|
606
|
+
},
|
|
597
607
|
"creator": {
|
|
598
608
|
"id": "03f5v9zjw7pz8717a4no1h8a7",
|
|
599
609
|
"name": "David Heinemeier Hansson",
|
|
@@ -619,6 +629,8 @@ __Response:__
|
|
|
619
629
|
}
|
|
620
630
|
```
|
|
621
631
|
|
|
632
|
+
> **Note:** The `closed` field indicates whether the card is in the "Done" state. The `column` field is only present when the card has been triaged into a column; cards in "Maybe?", "Not Now" or "Done" will not have this field.
|
|
633
|
+
|
|
622
634
|
### `POST /:account_slug/boards/:board_id/cards`
|
|
623
635
|
|
|
624
636
|
Creates a new card in a board.
|
|
@@ -683,6 +695,14 @@ __Response:__
|
|
|
683
695
|
|
|
684
696
|
Returns `204 No Content` on success.
|
|
685
697
|
|
|
698
|
+
### `DELETE /:account_slug/cards/:card_number/image`
|
|
699
|
+
|
|
700
|
+
Removes the header image from a card.
|
|
701
|
+
|
|
702
|
+
__Response:__
|
|
703
|
+
|
|
704
|
+
Returns `204 No Content` on success.
|
|
705
|
+
|
|
686
706
|
### `POST /:account_slug/cards/:card_number/closure`
|
|
687
707
|
|
|
688
708
|
Closes a card.
|
|
@@ -767,6 +787,22 @@ __Response:__
|
|
|
767
787
|
|
|
768
788
|
Returns `204 No Content` on success.
|
|
769
789
|
|
|
790
|
+
### `POST /:account_slug/cards/:card_number/goldness`
|
|
791
|
+
|
|
792
|
+
Marks a card as golden.
|
|
793
|
+
|
|
794
|
+
__Response:__
|
|
795
|
+
|
|
796
|
+
Returns `204 No Content` on success.
|
|
797
|
+
|
|
798
|
+
### `DELETE /:account_slug/cards/:card_number/goldness`
|
|
799
|
+
|
|
800
|
+
Removes golden status from a card.
|
|
801
|
+
|
|
802
|
+
__Response:__
|
|
803
|
+
|
|
804
|
+
Returns `204 No Content` on success.
|
|
805
|
+
|
|
770
806
|
## Comments
|
|
771
807
|
|
|
772
808
|
Comments are attached to cards and support rich text.
|
package/internal/api/client.go
CHANGED
|
@@ -84,7 +84,10 @@ func (c *Client) decodeResponse(req *http.Request, v any, expectedStatus ...int)
|
|
|
84
84
|
defer res.Body.Close()
|
|
85
85
|
|
|
86
86
|
if res.StatusCode != expectedCode {
|
|
87
|
-
body,
|
|
87
|
+
body, err := io.ReadAll(res.Body)
|
|
88
|
+
if err != nil {
|
|
89
|
+
return 0, fmt.Errorf("unexpected status code %d (failed to read error response: %w)", res.StatusCode, err)
|
|
90
|
+
}
|
|
88
91
|
return 0, fmt.Errorf("unexpected status code %d: %s", res.StatusCode, string(body))
|
|
89
92
|
}
|
|
90
93
|
|
|
@@ -307,6 +310,22 @@ func (c *Client) PostCardsClosure(ctx context.Context, cardNumber int) (bool, er
|
|
|
307
310
|
return true, nil
|
|
308
311
|
}
|
|
309
312
|
|
|
313
|
+
func (c *Client) PostCardNotNow(ctx context.Context, cardNumber int) (bool, error) {
|
|
314
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/not_now", c.AccountBaseURL, cardNumber)
|
|
315
|
+
|
|
316
|
+
req, err := c.newRequest(ctx, http.MethodPost, endpointURL, nil)
|
|
317
|
+
if err != nil {
|
|
318
|
+
return false, fmt.Errorf("failed to create post not now request: %w", err)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
_, err = c.decodeResponse(req, nil, http.StatusNoContent)
|
|
322
|
+
if err != nil {
|
|
323
|
+
return false, err
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return true, nil
|
|
327
|
+
}
|
|
328
|
+
|
|
310
329
|
func (c *Client) PostCardTriage(ctx context.Context, cardNumber int, columnID string) (bool, error) {
|
|
311
330
|
endpointURL := fmt.Sprintf("%s/cards/%d/triage", c.AccountBaseURL, cardNumber)
|
|
312
331
|
|
|
@@ -325,6 +344,86 @@ func (c *Client) PostCardTriage(ctx context.Context, cardNumber int, columnID st
|
|
|
325
344
|
return true, nil
|
|
326
345
|
}
|
|
327
346
|
|
|
347
|
+
func (c *Client) DeleteCardTriage(ctx context.Context, cardNumber int) (bool, error) {
|
|
348
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/triage", c.AccountBaseURL, cardNumber)
|
|
349
|
+
|
|
350
|
+
req, err := c.newRequest(ctx, http.MethodDelete, endpointURL, nil)
|
|
351
|
+
if err != nil {
|
|
352
|
+
return false, fmt.Errorf("failed to create delete triage request: %w", err)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
_, err = c.decodeResponse(req, nil, http.StatusNoContent)
|
|
356
|
+
if err != nil {
|
|
357
|
+
return false, err
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return true, nil
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
func (c *Client) PostCardWatch(ctx context.Context, cardNumber int) (bool, error) {
|
|
364
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/watch", c.AccountBaseURL, cardNumber)
|
|
365
|
+
|
|
366
|
+
req, err := c.newRequest(ctx, http.MethodPost, endpointURL, nil)
|
|
367
|
+
if err != nil {
|
|
368
|
+
return false, fmt.Errorf("failed to create post watch request: %w", err)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
_, err = c.decodeResponse(req, nil, http.StatusNoContent)
|
|
372
|
+
if err != nil {
|
|
373
|
+
return false, err
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return true, nil
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
func (c *Client) DeleteCardWatch(ctx context.Context, cardNumber int) (bool, error) {
|
|
380
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/watch", c.AccountBaseURL, cardNumber)
|
|
381
|
+
|
|
382
|
+
req, err := c.newRequest(ctx, http.MethodDelete, endpointURL, nil)
|
|
383
|
+
if err != nil {
|
|
384
|
+
return false, fmt.Errorf("failed to create delete watch request: %w", err)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
_, err = c.decodeResponse(req, nil, http.StatusNoContent)
|
|
388
|
+
if err != nil {
|
|
389
|
+
return false, err
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return true, nil
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
func (c *Client) PostCardGoldenness(ctx context.Context, cardNumber int) (bool, error) {
|
|
396
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/goldness", c.AccountBaseURL, cardNumber)
|
|
397
|
+
|
|
398
|
+
req, err := c.newRequest(ctx, http.MethodPost, endpointURL, nil)
|
|
399
|
+
if err != nil {
|
|
400
|
+
return false, fmt.Errorf("failed to create post goldness request: %w", err)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
_, err = c.decodeResponse(req, nil, http.StatusNoContent)
|
|
404
|
+
if err != nil {
|
|
405
|
+
return false, err
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return true, nil
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
func (c *Client) DeleteCardGoldenness(ctx context.Context, cardNumber int) (bool, error) {
|
|
412
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/goldness", c.AccountBaseURL, cardNumber)
|
|
413
|
+
|
|
414
|
+
req, err := c.newRequest(ctx, http.MethodDelete, endpointURL, nil)
|
|
415
|
+
if err != nil {
|
|
416
|
+
return false, fmt.Errorf("failed to create delete goldness request: %w", err)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
_, err = c.decodeResponse(req, nil, http.StatusNoContent)
|
|
420
|
+
if err != nil {
|
|
421
|
+
return false, err
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return true, nil
|
|
425
|
+
}
|
|
426
|
+
|
|
328
427
|
func (c *Client) DeleteCardsClosure(ctx context.Context, cardNumber int) (bool, error) {
|
|
329
428
|
endpointURL := fmt.Sprintf("%s/cards/%d/closure", c.AccountBaseURL, cardNumber)
|
|
330
429
|
|
|
@@ -359,6 +458,24 @@ func (c *Client) PostCardAssignments(ctx context.Context, cardNumber int, userID
|
|
|
359
458
|
return true, nil
|
|
360
459
|
}
|
|
361
460
|
|
|
461
|
+
func (c *Client) PostCardTagging(ctx context.Context, cardNumber int, tagTitle string) (bool, error) {
|
|
462
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/taggings", c.AccountBaseURL, cardNumber)
|
|
463
|
+
|
|
464
|
+
body := map[string]string{"tag_title": tagTitle}
|
|
465
|
+
|
|
466
|
+
req, err := c.newRequest(ctx, http.MethodPost, endpointURL, body)
|
|
467
|
+
if err != nil {
|
|
468
|
+
return false, fmt.Errorf("failed to create tagging request: %w", err)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
_, err = c.decodeResponse(req, nil, http.StatusNoContent)
|
|
472
|
+
if err != nil {
|
|
473
|
+
return false, err
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return true, nil
|
|
477
|
+
}
|
|
478
|
+
|
|
362
479
|
func (c *Client) GetMyIdentity(ctx context.Context) (*GetMyIdentityResponse, error) {
|
|
363
480
|
endpointURL := c.BaseURL + "/my/identity"
|
|
364
481
|
|
|
@@ -458,6 +575,78 @@ func (c *Client) PostBulkNotificationsReading(ctx context.Context) (bool, error)
|
|
|
458
575
|
return true, nil
|
|
459
576
|
}
|
|
460
577
|
|
|
578
|
+
func (c *Client) GetTags(ctx context.Context) ([]Tag, error) {
|
|
579
|
+
endpointURL := c.AccountBaseURL + "/tags"
|
|
580
|
+
|
|
581
|
+
req, err := c.newRequest(ctx, http.MethodGet, endpointURL, nil)
|
|
582
|
+
if err != nil {
|
|
583
|
+
return nil, fmt.Errorf("failed to create get tags request: %w", err)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
var response []Tag
|
|
587
|
+
_, err = c.decodeResponse(req, &response)
|
|
588
|
+
if err != nil {
|
|
589
|
+
return nil, err
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return response, nil
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
func (c *Client) GetCardComments(ctx context.Context, cardNumber int) ([]Comment, error) {
|
|
596
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/comments", c.AccountBaseURL, cardNumber)
|
|
597
|
+
|
|
598
|
+
req, err := c.newRequest(ctx, http.MethodGet, endpointURL, nil)
|
|
599
|
+
if err != nil {
|
|
600
|
+
return nil, fmt.Errorf("failed to create get card comments request: %w", err)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
var response []Comment
|
|
604
|
+
_, err = c.decodeResponse(req, &response)
|
|
605
|
+
if err != nil {
|
|
606
|
+
return nil, err
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return response, nil
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
func (c *Client) GetCardComment(ctx context.Context, cardNumber int, commentID string) (*Comment, error) {
|
|
613
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/comments/%s", c.AccountBaseURL, cardNumber, commentID)
|
|
614
|
+
|
|
615
|
+
req, err := c.newRequest(ctx, http.MethodGet, endpointURL, nil)
|
|
616
|
+
if err != nil {
|
|
617
|
+
return nil, fmt.Errorf("failed to create get card comment request: %w", err)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
var response Comment
|
|
621
|
+
_, err = c.decodeResponse(req, &response)
|
|
622
|
+
if err != nil {
|
|
623
|
+
return nil, err
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return &response, nil
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
func (c *Client) PostCardComment(ctx context.Context, cardNumber int, body string) (*Comment, error) {
|
|
630
|
+
endpointURL := fmt.Sprintf("%s/cards/%d/comments", c.AccountBaseURL, cardNumber)
|
|
631
|
+
|
|
632
|
+
payload := map[string]map[string]string{
|
|
633
|
+
"comment": {"body": body},
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
req, err := c.newRequest(ctx, http.MethodPost, endpointURL, payload)
|
|
637
|
+
if err != nil {
|
|
638
|
+
return nil, fmt.Errorf("failed to create post card comment request: %w", err)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
var response Comment
|
|
642
|
+
_, err = c.decodeResponse(req, &response, http.StatusCreated)
|
|
643
|
+
if err != nil {
|
|
644
|
+
return nil, err
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return &response, nil
|
|
648
|
+
}
|
|
649
|
+
|
|
461
650
|
type Board struct {
|
|
462
651
|
ID string `json:"id"`
|
|
463
652
|
Name string `json:"name"`
|
|
@@ -584,6 +773,27 @@ type CardReference struct {
|
|
|
584
773
|
URL string `json:"url"`
|
|
585
774
|
}
|
|
586
775
|
|
|
776
|
+
type Tag struct {
|
|
777
|
+
ID string `json:"id"`
|
|
778
|
+
Title string `json:"title"`
|
|
779
|
+
CreatedAt string `json:"created_at"`
|
|
780
|
+
URL string `json:"url"`
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
type Comment struct {
|
|
784
|
+
ID string `json:"id"`
|
|
785
|
+
CreatedAt string `json:"created_at"`
|
|
786
|
+
UpdatedAt string `json:"updated_at"`
|
|
787
|
+
Body struct {
|
|
788
|
+
PlainText string `json:"plain_text"`
|
|
789
|
+
HTML string `json:"html"`
|
|
790
|
+
} `json:"body"`
|
|
791
|
+
Creator User `json:"creator"`
|
|
792
|
+
Card CardReference `json:"card"`
|
|
793
|
+
ReactionsURL string `json:"reactions_url"`
|
|
794
|
+
URL string `json:"url"`
|
|
795
|
+
}
|
|
796
|
+
|
|
587
797
|
type Color string
|
|
588
798
|
|
|
589
799
|
// Color constants using centralized definitions
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package ui
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
|
|
6
|
+
"github.com/rogeriopvl/fizzy/internal/api"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func DisplayComments(comments []api.Comment) error {
|
|
10
|
+
for _, comment := range comments {
|
|
11
|
+
fmt.Printf("%s - %s (%s)\n", comment.Creator.Name, comment.Body.PlainText, DisplayID(comment.ID))
|
|
12
|
+
}
|
|
13
|
+
return nil
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func DisplayComment(comment *api.Comment) error {
|
|
17
|
+
fmt.Printf("Author: %s\n", comment.Creator.Name)
|
|
18
|
+
fmt.Printf("Created: %s\n", comment.CreatedAt)
|
|
19
|
+
if comment.UpdatedAt != comment.CreatedAt {
|
|
20
|
+
fmt.Printf("Updated: %s\n", comment.UpdatedAt)
|
|
21
|
+
}
|
|
22
|
+
fmt.Printf("Card: %s\n", comment.Card.Title)
|
|
23
|
+
fmt.Printf("\n%s\n", comment.Body.PlainText)
|
|
24
|
+
return nil
|
|
25
|
+
}
|