summaryrefslogtreecommitdiff
path: root/internal/api/plantoeat.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-23 21:37:18 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-23 21:37:18 -1000
commit465093343ddd398ce5f6377fc9c472d8251c618b (patch)
treed333a2f1c8879f7b114817e929c95e9fcf5f4c3b /internal/api/plantoeat.go
parente23c85577cbb0eac8b847dd989072698ff4e7a30 (diff)
Refactor: reduce code duplication with shared abstractions
- Add BaseClient HTTP abstraction (internal/api/http.go) to eliminate duplicated HTTP boilerplate across Todoist, Trello, and PlanToEat clients - Add response helpers (internal/handlers/response.go) for JSON/HTML responses - Add generic cache wrapper (internal/handlers/cache.go) using Go generics - Consolidate HandleCompleteAtom/HandleUncompleteAtom into handleAtomToggle - Merge TabsHandler into Handler, delete tabs.go - Extract sortTasksByUrgency and filterAndSortTrelloTasks helpers - Update tests to work with new BaseClient structure Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/api/plantoeat.go')
-rw-r--r--internal/api/plantoeat.go67
1 files changed, 19 insertions, 48 deletions
diff --git a/internal/api/plantoeat.go b/internal/api/plantoeat.go
index 1dae246..eb29c63 100644
--- a/internal/api/plantoeat.go
+++ b/internal/api/plantoeat.go
@@ -2,35 +2,32 @@ package api
import (
"context"
- "encoding/json"
"fmt"
- "io"
- "net/http"
"time"
"task-dashboard/internal/models"
)
-const (
- planToEatBaseURL = "https://www.plantoeat.com/api/v2"
-)
+const planToEatBaseURL = "https://www.plantoeat.com/api/v2"
// PlanToEatClient handles interactions with the PlanToEat API
type PlanToEatClient struct {
- apiKey string
- httpClient *http.Client
+ BaseClient
+ apiKey string
}
// NewPlanToEatClient creates a new PlanToEat API client
func NewPlanToEatClient(apiKey string) *PlanToEatClient {
return &PlanToEatClient{
- apiKey: apiKey,
- httpClient: &http.Client{
- Timeout: 15 * time.Second,
- },
+ BaseClient: NewBaseClient(planToEatBaseURL),
+ apiKey: apiKey,
}
}
+func (c *PlanToEatClient) authHeaders() map[string]string {
+ return map[string]string{"Authorization": "Bearer " + c.apiKey}
+}
+
// planToEatPlannerItem represents a planner item from the API
type planToEatPlannerItem struct {
ID int `json:"id"`
@@ -51,59 +48,35 @@ type planToEatResponse struct {
// GetUpcomingMeals fetches meals for the next N days
func (c *PlanToEatClient) GetUpcomingMeals(ctx context.Context, days int) ([]models.Meal, error) {
if days <= 0 {
- days = 7 // Default to 7 days
+ days = 7
}
startDate := time.Now()
endDate := startDate.AddDate(0, 0, days)
- req, err := http.NewRequestWithContext(ctx, "GET", planToEatBaseURL+"/planner_items", nil)
- if err != nil {
- return nil, fmt.Errorf("failed to create request: %w", err)
- }
-
- // Add query parameters
- q := req.URL.Query()
- q.Add("start_date", startDate.Format("2006-01-02"))
- q.Add("end_date", endDate.Format("2006-01-02"))
- req.URL.RawQuery = q.Encode()
-
- // Add API key (check docs for correct header name)
- req.Header.Set("Authorization", "Bearer "+c.apiKey)
-
- resp, err := c.httpClient.Do(req)
- if err != nil {
- return nil, fmt.Errorf("failed to fetch meals: %w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- body, _ := io.ReadAll(resp.Body)
- return nil, fmt.Errorf("plantoeat API error (status %d): %s", resp.StatusCode, string(body))
- }
+ path := fmt.Sprintf("/planner_items?start_date=%s&end_date=%s",
+ startDate.Format("2006-01-02"),
+ endDate.Format("2006-01-02"))
var apiResponse planToEatResponse
- if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
- return nil, fmt.Errorf("failed to decode response: %w", err)
+ if err := c.Get(ctx, path, c.authHeaders(), &apiResponse); err != nil {
+ return nil, fmt.Errorf("failed to fetch meals: %w", err)
}
- // Convert to our model
meals := make([]models.Meal, 0, len(apiResponse.Items))
for _, item := range apiResponse.Items {
mealDate, err := time.Parse("2006-01-02", item.Date)
if err != nil {
- continue // Skip invalid dates
+ continue
}
- meal := models.Meal{
+ meals = append(meals, models.Meal{
ID: fmt.Sprintf("%d", item.ID),
RecipeName: item.Recipe.Title,
Date: mealDate,
MealType: normalizeMealType(item.MealType),
RecipeURL: item.Recipe.URL,
- }
-
- meals = append(meals, meal)
+ })
}
return meals, nil
@@ -121,18 +94,16 @@ func normalizeMealType(mealType string) string {
case "snack", "Snack":
return "snack"
default:
- return "dinner" // Default to dinner
+ return "dinner"
}
}
// GetRecipes fetches recipes (for Phase 2)
func (c *PlanToEatClient) GetRecipes(ctx context.Context) error {
- // This will be implemented in Phase 2
return fmt.Errorf("not implemented yet")
}
// AddMealToPlanner adds a meal to the planner (for Phase 2)
func (c *PlanToEatClient) AddMealToPlanner(ctx context.Context, recipeID string, date time.Time, mealType string) error {
- // This will be implemented in Phase 2
return fmt.Errorf("not implemented yet")
}