From 0fda0e9e4b0c6a73be513987264329e4515170f1 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Tue, 13 Jan 2026 14:04:12 -1000 Subject: Add Trello Lists support for UI dropdowns Expose Trello Lists in Board model to enable card creation UI: - Add List model struct (ID, Name) to types.go - Add Lists []List field to Board model - Add GetLists method to TrelloAPI interface - Refactor private getLists to return []models.List - Update GetCards to build list map from slice - Add public GetLists method wrapping private implementation - Update GetBoardsWithCards to populate Lists field concurrently - Update mock Trello client in tests to implement GetLists All tests pass. Boards now include their lists for UI rendering. Co-Authored-By: Claude Sonnet 4.5 --- internal/api/interfaces.go | 1 + internal/api/trello.go | 38 ++++++++++++++++++++++++++++---------- internal/handlers/handlers_test.go | 4 ++++ internal/models/types.go | 7 +++++++ 4 files changed, 40 insertions(+), 10 deletions(-) (limited to 'internal') diff --git a/internal/api/interfaces.go b/internal/api/interfaces.go index 95cc0e7..31da0a8 100644 --- a/internal/api/interfaces.go +++ b/internal/api/interfaces.go @@ -19,6 +19,7 @@ type TodoistAPI interface { type TrelloAPI interface { GetBoards(ctx context.Context) ([]models.Board, error) GetCards(ctx context.Context, boardID string) ([]models.Card, error) + GetLists(ctx context.Context, boardID string) ([]models.List, error) GetBoardsWithCards(ctx context.Context) ([]models.Board, error) CreateCard(ctx context.Context, listID, name, description string, dueDate *time.Time) (*models.Card, error) UpdateCard(ctx context.Context, cardID string, updates map[string]interface{}) error diff --git a/internal/api/trello.go b/internal/api/trello.go index 5b87e30..9c18ade 100644 --- a/internal/api/trello.go +++ b/internal/api/trello.go @@ -128,9 +128,12 @@ func (c *TrelloClient) GetCards(ctx context.Context, boardID string) ([]models.C // Fetch lists to get list names lists, err := c.getLists(ctx, boardID) - if err != nil { - // If we can't get lists, continue with empty list names - lists = make(map[string]string) + listMap := make(map[string]string) + if err == nil { + // Build map of list ID to name + for _, list := range lists { + listMap[list.ID] = list.Name + } } // Convert to our model @@ -140,7 +143,7 @@ func (c *TrelloClient) GetCards(ctx context.Context, boardID string) ([]models.C ID: apiCard.ID, Name: apiCard.Name, ListID: apiCard.IDList, - ListName: lists[apiCard.IDList], + ListName: listMap[apiCard.IDList], URL: apiCard.URL, } @@ -158,8 +161,8 @@ func (c *TrelloClient) GetCards(ctx context.Context, boardID string) ([]models.C return cards, nil } -// 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) { +// getLists fetches lists for a board +func (c *TrelloClient) getLists(ctx context.Context, boardID string) ([]models.List, error) { 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) @@ -183,15 +186,23 @@ func (c *TrelloClient) getLists(ctx context.Context, boardID string) (map[string return nil, fmt.Errorf("failed to decode response: %w", err) } - // Convert to map - lists := make(map[string]string, len(apiLists)) - for _, list := range apiLists { - lists[list.ID] = list.Name + // Convert to model + lists := make([]models.List, 0, len(apiLists)) + for _, apiList := range apiLists { + lists = append(lists, models.List{ + ID: apiList.ID, + Name: apiList.Name, + }) } return lists, nil } +// GetLists fetches lists for a specific board +func (c *TrelloClient) GetLists(ctx context.Context, boardID string) ([]models.List, error) { + return c.getLists(ctx, boardID) +} + // GetBoardsWithCards fetches all boards and their cards in one call func (c *TrelloClient) GetBoardsWithCards(ctx context.Context) ([]models.Board, error) { boards, err := c.GetBoards(ctx) @@ -211,11 +222,18 @@ func (c *TrelloClient) GetBoardsWithCards(ctx context.Context) ([]models.Board, sem <- struct{}{} defer func() { <-sem }() + // Fetch cards cards, err := c.GetCards(ctx, boards[i].ID) if err == nil { // It is safe to write to specific indices of the slice concurrently boards[i].Cards = cards } + + // Fetch lists + lists, err := c.getLists(ctx, boards[i].ID) + if err == nil { + boards[i].Lists = lists + } }(i) } diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 902bebb..13973df 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -106,6 +106,10 @@ func (m *mockTrelloClient) GetCards(ctx context.Context, boardID string) ([]mode return []models.Card{}, nil } +func (m *mockTrelloClient) GetLists(ctx context.Context, boardID string) ([]models.List, error) { + return []models.List{}, nil +} + func (m *mockTrelloClient) CreateCard(ctx context.Context, listID, name, description string, dueDate *time.Time) (*models.Card, error) { return nil, nil } diff --git a/internal/models/types.go b/internal/models/types.go index d39a1d6..31308fc 100644 --- a/internal/models/types.go +++ b/internal/models/types.go @@ -36,11 +36,18 @@ type Meal struct { RecipeURL string `json:"recipe_url"` } +// List represents a Trello list +type List struct { + ID string `json:"id"` + Name string `json:"name"` +} + // Board represents a Trello board type Board struct { ID string `json:"id"` Name string `json:"name"` Cards []Card `json:"cards"` + Lists []List `json:"lists"` } // Card represents a Trello card -- cgit v1.2.3