summaryrefslogtreecommitdiff
path: root/internal/api
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api')
-rw-r--r--internal/api/interfaces.go2
-rw-r--r--internal/api/plantoeat.go6
-rw-r--r--internal/api/plantoeat_test.go126
-rw-r--r--internal/api/todoist.go8
-rw-r--r--internal/api/trello.go6
5 files changed, 142 insertions, 6 deletions
diff --git a/internal/api/interfaces.go b/internal/api/interfaces.go
index 842814c..70bba1f 100644
--- a/internal/api/interfaces.go
+++ b/internal/api/interfaces.go
@@ -31,6 +31,7 @@ type TrelloAPI interface {
// PlanToEatAPI defines the interface for PlanToEat operations
type PlanToEatAPI interface {
GetUpcomingMeals(ctx context.Context, days int) ([]models.Meal, error)
+ GetShoppingList(ctx context.Context) ([]models.ShoppingItem, error)
GetRecipes(ctx context.Context) error
AddMealToPlanner(ctx context.Context, recipeID string, date time.Time, mealType string) error
}
@@ -38,6 +39,7 @@ type PlanToEatAPI interface {
// GoogleCalendarAPI defines the interface for Google Calendar operations
type GoogleCalendarAPI interface {
GetUpcomingEvents(ctx context.Context, maxResults int) ([]models.CalendarEvent, error)
+ GetEventsByDateRange(ctx context.Context, start, end time.Time) ([]models.CalendarEvent, error)
}
// Ensure concrete types implement interfaces
diff --git a/internal/api/plantoeat.go b/internal/api/plantoeat.go
index 5c24cc1..ab5d2cd 100644
--- a/internal/api/plantoeat.go
+++ b/internal/api/plantoeat.go
@@ -11,6 +11,7 @@ import (
"github.com/PuerkitoBio/goquery"
+ "task-dashboard/internal/config"
"task-dashboard/internal/models"
)
@@ -90,8 +91,7 @@ func parsePlannerHTML(body io.Reader, days int) ([]models.Meal, error) {
}
var meals []models.Meal
- now := time.Now()
- today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+ today := config.Today()
endDate := today.AddDate(0, 0, days)
// PlanToEat structure:
@@ -108,7 +108,7 @@ func parsePlannerHTML(body io.Reader, days int) ([]models.Meal, error) {
return
}
- mealDate, err := time.Parse("2006-01-02", dateStr)
+ mealDate, err := config.ParseDateInDisplayTZ(dateStr)
if err != nil {
return
}
diff --git a/internal/api/plantoeat_test.go b/internal/api/plantoeat_test.go
new file mode 100644
index 0000000..50ad4ed
--- /dev/null
+++ b/internal/api/plantoeat_test.go
@@ -0,0 +1,126 @@
+package api
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestParseShoppingListHTML(t *testing.T) {
+ // Sample HTML structure matching PlanToEat's shopping list
+ html := `
+ <div id="shopping_items" data-total-items="3">
+ <div id="store-0" class="store" data-store-id="0">
+ <div class="store_heading">
+ <h4 class="store_name">Costco (3)</h4>
+ </div>
+ <table>
+ <tr>
+ <td class="column">
+ <div class="category-box" id="cat01_350">
+ <p class="category-title"><span>Baking</span></p>
+ <ul class="ingredients">
+ <li class="sli noselect i493745871" data-store-id="0">
+ <label class="checkbox-label">
+ <input type="checkbox" name="items[493745871]" value="493745871">
+ <strong>Olive oil</strong> <span class="quan">2 tablespoons</span>
+ </label>
+ </li>
+ <li class="sli noselect i493745872 checked" data-store-id="0">
+ <label class="checkbox-label">
+ <input type="checkbox" name="items[493745872]" value="493745872" checked>
+ <strong>Flour</strong> <span class="quan">1 cup</span>
+ </label>
+ </li>
+ </ul>
+ </div>
+ <div class="category-box" id="cat01_347">
+ <p class="category-title"><span>Meat</span></p>
+ <ul class="ingredients">
+ <li class="sli noselect i493745889" data-store-id="0">
+ <label class="checkbox-label">
+ <input type="checkbox" name="items[493745889]" value="493745889">
+ <strong>Ground beef</strong> <span class="quan">1 pound</span>
+ </label>
+ </li>
+ </ul>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>`
+
+ items, err := parseShoppingListHTML(strings.NewReader(html))
+ if err != nil {
+ t.Fatalf("parseShoppingListHTML failed: %v", err)
+ }
+
+ if len(items) != 3 {
+ t.Errorf("Expected 3 items, got %d", len(items))
+ }
+
+ // Check first item
+ if items[0].Name != "Olive oil" {
+ t.Errorf("Expected first item name 'Olive oil', got '%s'", items[0].Name)
+ }
+ if items[0].Quantity != "2 tablespoons" {
+ t.Errorf("Expected quantity '2 tablespoons', got '%s'", items[0].Quantity)
+ }
+ if items[0].Category != "Baking" {
+ t.Errorf("Expected category 'Baking', got '%s'", items[0].Category)
+ }
+ if items[0].Store != "Costco" {
+ t.Errorf("Expected store 'Costco', got '%s'", items[0].Store)
+ }
+ if items[0].ID != "493745871" {
+ t.Errorf("Expected ID '493745871', got '%s'", items[0].ID)
+ }
+ if items[0].Checked {
+ t.Error("Expected first item to not be checked")
+ }
+
+ // Check checked item
+ if !items[1].Checked {
+ t.Error("Expected second item to be checked")
+ }
+
+ // Check item from different category
+ if items[2].Name != "Ground beef" {
+ t.Errorf("Expected 'Ground beef', got '%s'", items[2].Name)
+ }
+ if items[2].Category != "Meat" {
+ t.Errorf("Expected category 'Meat', got '%s'", items[2].Category)
+ }
+}
+
+func TestParseShoppingListHTML_EmptyList(t *testing.T) {
+ html := `<div id="shopping_items" data-total-items="0"></div>`
+
+ items, err := parseShoppingListHTML(strings.NewReader(html))
+ if err != nil {
+ t.Fatalf("parseShoppingListHTML failed: %v", err)
+ }
+
+ if len(items) != 0 {
+ t.Errorf("Expected 0 items for empty list, got %d", len(items))
+ }
+}
+
+func TestCleanQuantity(t *testing.T) {
+ tests := []struct {
+ input string
+ expected string
+ }{
+ {"1 cup", "1 cup"},
+ {"2 tablespoons", "2 tablespoons"},
+ {"1\u00a0pound", "1 pound"},
+ {" 3 oz ", "3 oz"},
+ }
+
+ for _, tt := range tests {
+ result := cleanQuantity(tt.input)
+ if result != tt.expected {
+ t.Errorf("cleanQuantity(%q) = %q, want %q", tt.input, result, tt.expected)
+ }
+ }
+}
diff --git a/internal/api/todoist.go b/internal/api/todoist.go
index 2c94e08..6068d2e 100644
--- a/internal/api/todoist.go
+++ b/internal/api/todoist.go
@@ -5,6 +5,7 @@ import (
"fmt"
"time"
+ "task-dashboard/internal/config"
"task-dashboard/internal/models"
)
@@ -287,9 +288,14 @@ func parseDueDate(due *dueInfo) *time.Time {
var dueDate time.Time
var err error
if due.Datetime != "" {
+ // RFC3339 includes timezone, then convert to display timezone
dueDate, err = time.Parse(time.RFC3339, due.Datetime)
+ if err == nil {
+ dueDate = config.ToDisplayTZ(dueDate)
+ }
} else if due.Date != "" {
- dueDate, err = time.Parse("2006-01-02", due.Date)
+ // Date-only, parse in display timezone
+ dueDate, err = config.ParseDateInDisplayTZ(due.Date)
}
if err != nil {
return nil
diff --git a/internal/api/trello.go b/internal/api/trello.go
index a276726..1a5642c 100644
--- a/internal/api/trello.go
+++ b/internal/api/trello.go
@@ -125,7 +125,8 @@ func (c *TrelloClient) GetCards(ctx context.Context, boardID string) ([]models.C
if apiCard.Due != nil && *apiCard.Due != "" {
if dueDate, err := time.Parse(time.RFC3339, *apiCard.Due); err == nil {
- card.DueDate = &dueDate
+ dueInTZ := config.ToDisplayTZ(dueDate)
+ card.DueDate = &dueInTZ
}
}
@@ -267,7 +268,8 @@ func (c *TrelloClient) CreateCard(ctx context.Context, listID, name, description
if apiCard.Due != nil && *apiCard.Due != "" {
if parsedDate, err := time.Parse(time.RFC3339, *apiCard.Due); err == nil {
- card.DueDate = &parsedDate
+ dueInTZ := config.ToDisplayTZ(parsedDate)
+ card.DueDate = &dueInTZ
}
}