summaryrefslogtreecommitdiff
path: root/internal/api/trello.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-13 13:58:53 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-13 13:58:53 -1000
commit1c79f105c960ddab2265cbfd8dfd728630b1ebfb (patch)
tree5c0c5f6bad50fb3214d75ac29de292c74fe2267d /internal/api/trello.go
parent429476f5ac97f56c7f6a755d6dd565767d31dfb6 (diff)
Implement Trello write operations (Phase 3 Step 1)
Add CreateCard and UpdateCard methods to Trello API client with full testability support: - Refactor TrelloClient with configurable baseURL for testing - Replace hardcoded trelloBaseURL constant with c.baseURL - Implement CreateCard with support for description and due date - Implement UpdateCard with flexible field updates - Add comprehensive test suite using httptest.NewServer - Tests cover success cases, error handling, and edge cases All tests pass. Write operations ready for Phase 3 UI integration. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/api/trello.go')
-rw-r--r--internal/api/trello.go111
1 files changed, 100 insertions, 11 deletions
diff --git a/internal/api/trello.go b/internal/api/trello.go
index c78ebc8..5b87e30 100644
--- a/internal/api/trello.go
+++ b/internal/api/trello.go
@@ -6,7 +6,9 @@ import (
"fmt"
"io"
"net/http"
+ "net/url"
"sort"
+ "strings"
"sync"
"time"
@@ -21,14 +23,16 @@ const (
type TrelloClient struct {
apiKey string
token string
+ baseURL string
httpClient *http.Client
}
// NewTrelloClient creates a new Trello API client
func NewTrelloClient(apiKey, token string) *TrelloClient {
return &TrelloClient{
- apiKey: apiKey,
- token: token,
+ apiKey: apiKey,
+ token: token,
+ baseURL: trelloBaseURL,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
@@ -60,7 +64,7 @@ type trelloListResponse struct {
// GetBoards fetches all boards for the authenticated user
func (c *TrelloClient) GetBoards(ctx context.Context) ([]models.Board, error) {
- url := fmt.Sprintf("%s/members/me/boards?key=%s&token=%s", trelloBaseURL, c.apiKey, c.token)
+ url := fmt.Sprintf("%s/members/me/boards?key=%s&token=%s", c.baseURL, c.apiKey, c.token)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
@@ -99,7 +103,7 @@ func (c *TrelloClient) GetBoards(ctx context.Context) ([]models.Board, error) {
// GetCards fetches all cards for a specific board
func (c *TrelloClient) GetCards(ctx context.Context, boardID string) ([]models.Card, error) {
- url := fmt.Sprintf("%s/boards/%s/cards?key=%s&token=%s", trelloBaseURL, boardID, c.apiKey, c.token)
+ url := fmt.Sprintf("%s/boards/%s/cards?key=%s&token=%s", c.baseURL, boardID, c.apiKey, c.token)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
@@ -156,7 +160,7 @@ func (c *TrelloClient) GetCards(ctx context.Context, boardID string) ([]models.C
// getLists fetches lists for a board and returns a map of list ID to name
func (c *TrelloClient) getLists(ctx context.Context, boardID string) (map[string]string, error) {
- url := fmt.Sprintf("%s/boards/%s/lists?key=%s&token=%s", trelloBaseURL, boardID, c.apiKey, c.token)
+ url := fmt.Sprintf("%s/boards/%s/lists?key=%s&token=%s", c.baseURL, boardID, c.apiKey, c.token)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
@@ -256,14 +260,99 @@ func (c *TrelloClient) GetBoardsWithCards(ctx context.Context) ([]models.Board,
return boards, nil
}
-// CreateCard creates a new card (for Phase 2)
+// CreateCard creates a new card in the specified list
func (c *TrelloClient) CreateCard(ctx context.Context, listID, name, description string, dueDate *time.Time) (*models.Card, error) {
- // This will be implemented in Phase 2
- return nil, fmt.Errorf("not implemented yet")
+ // Prepare request payload
+ data := url.Values{}
+ data.Set("key", c.apiKey)
+ data.Set("token", c.token)
+ data.Set("idList", listID)
+ data.Set("name", name)
+
+ if description != "" {
+ data.Set("desc", description)
+ }
+
+ if dueDate != nil {
+ data.Set("due", dueDate.Format(time.RFC3339))
+ }
+
+ // Create POST request
+ reqURL := c.baseURL + "/cards"
+ req, err := http.NewRequestWithContext(ctx, "POST", reqURL, strings.NewReader(data.Encode()))
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ // Execute request
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create card: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(resp.Body)
+ return nil, fmt.Errorf("trello API error (status %d): %s", resp.StatusCode, string(body))
+ }
+
+ // Decode response
+ var apiCard trelloCardResponse
+ if err := json.NewDecoder(resp.Body).Decode(&apiCard); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ // Convert to our model
+ card := &models.Card{
+ ID: apiCard.ID,
+ Name: apiCard.Name,
+ ListID: apiCard.IDList,
+ URL: apiCard.URL,
+ }
+
+ // Parse due date if present
+ if apiCard.Due != nil && *apiCard.Due != "" {
+ parsedDate, err := time.Parse(time.RFC3339, *apiCard.Due)
+ if err == nil {
+ card.DueDate = &parsedDate
+ }
+ }
+
+ return card, nil
}
-// UpdateCard updates a card (for Phase 2)
+// UpdateCard updates a card with the specified changes
func (c *TrelloClient) UpdateCard(ctx context.Context, cardID string, updates map[string]interface{}) error {
- // This will be implemented in Phase 2
- return fmt.Errorf("not implemented yet")
+ // Prepare request payload
+ data := url.Values{}
+ data.Set("key", c.apiKey)
+ data.Set("token", c.token)
+
+ // Add updates to payload
+ for key, value := range updates {
+ data.Set(key, fmt.Sprintf("%v", value))
+ }
+
+ // Create PUT request
+ reqURL := fmt.Sprintf("%s/cards/%s", c.baseURL, cardID)
+ req, err := http.NewRequestWithContext(ctx, "PUT", reqURL, strings.NewReader(data.Encode()))
+ if err != nil {
+ return fmt.Errorf("failed to create request: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ // Execute request
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return fmt.Errorf("failed to update card: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(resp.Body)
+ return fmt.Errorf("trello API error (status %d): %s", resp.StatusCode, string(body))
+ }
+
+ return nil
}