From fa95c71494458070b78270e3d9170076028fc974 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sun, 25 Jan 2026 20:04:03 -1000 Subject: Refactor: extract helpers and clean up hardcoded HTML - Extract parseEventTime() and deduplicateEvents() in google_calendar.go - Add scanTask() and scanTasks() SQL helpers in sqlite.go - Move completed-atom HTML to partial template - Add loadTestTemplates() test helper for template-dependent tests Co-Authored-By: Claude Opus 4.5 --- internal/api/google_calendar.go | 101 +++++++++++++++------------------------- 1 file changed, 38 insertions(+), 63 deletions(-) (limited to 'internal/api/google_calendar.go') diff --git a/internal/api/google_calendar.go b/internal/api/google_calendar.go index 8217b49..dc61f3d 100644 --- a/internal/api/google_calendar.go +++ b/internal/api/google_calendar.go @@ -19,6 +19,39 @@ type GoogleCalendarClient struct { calendarIDs []string } +// parseEventTime extracts start/end times from a Google Calendar event, converting to local timezone +func parseEventTime(item *calendar.Event) (start, end time.Time) { + if item.Start.DateTime == "" { + // All-day event - parse in local timezone + start, _ = time.ParseInLocation("2006-01-02", item.Start.Date, time.Local) + end, _ = time.ParseInLocation("2006-01-02", item.End.Date, time.Local) + } else { + // Timed event - parse RFC3339 then convert to local + start, _ = time.Parse(time.RFC3339, item.Start.DateTime) + end, _ = time.Parse(time.RFC3339, item.End.DateTime) + start = start.Local() + end = end.Local() + } + return +} + +// deduplicateEvents removes duplicate events (same summary + start time) +func deduplicateEvents(events []models.CalendarEvent) []models.CalendarEvent { + seen := make(map[string]bool) + var unique []models.CalendarEvent + for _, event := range events { + key := fmt.Sprintf("%s|%d", event.Summary, event.Start.Unix()) + if !seen[key] { + seen[key] = true + unique = append(unique, event) + } + } + sort.Slice(unique, func(i, j int) bool { + return unique[i].Start.Before(unique[j].Start) + }) + return unique +} + // NewGoogleCalendarClient creates a client that fetches from multiple calendars. // calendarIDs can be comma-separated (e.g., "cal1@group.calendar.google.com,cal2@group.calendar.google.com") func NewGoogleCalendarClient(ctx context.Context, credentialsFile, calendarIDs string) (*GoogleCalendarClient, error) { @@ -51,23 +84,11 @@ func (c *GoogleCalendarClient) GetUpcomingEvents(ctx context.Context, maxResults SingleEvents(true).TimeMin(t).MaxResults(int64(maxResults)).OrderBy("startTime").Do() if err != nil { log.Printf("Warning: failed to fetch events from calendar %s: %v", calendarID, err) - continue // Don't fail entirely, just skip this calendar + continue } for _, item := range events.Items { - var start, end time.Time - if item.Start.DateTime == "" { - // All-day event - parse in local timezone - start, _ = time.ParseInLocation("2006-01-02", item.Start.Date, time.Local) - end, _ = time.ParseInLocation("2006-01-02", item.End.Date, time.Local) - } else { - // Timed event - parse RFC3339 then convert to local - start, _ = time.Parse(time.RFC3339, item.Start.DateTime) - end, _ = time.Parse(time.RFC3339, item.End.DateTime) - start = start.Local() - end = end.Local() - } - + start, end := parseEventTime(item) allEvents = append(allEvents, models.CalendarEvent{ ID: item.Id, Summary: item.Summary, @@ -79,29 +100,10 @@ func (c *GoogleCalendarClient) GetUpcomingEvents(ctx context.Context, maxResults } } - // Deduplicate events (same event may appear in multiple calendars) - // Use Unix timestamp to handle timezone differences - seen := make(map[string]bool) - var uniqueEvents []models.CalendarEvent - for _, event := range allEvents { - // Use summary + unix timestamp as dedup key (handles timezone differences) - key := fmt.Sprintf("%s|%d", event.Summary, event.Start.Unix()) - if !seen[key] { - seen[key] = true - uniqueEvents = append(uniqueEvents, event) - } - } - - // Sort all events by start time - sort.Slice(uniqueEvents, func(i, j int) bool { - return uniqueEvents[i].Start.Before(uniqueEvents[j].Start) - }) - - // Limit to maxResults + uniqueEvents := deduplicateEvents(allEvents) if len(uniqueEvents) > maxResults { uniqueEvents = uniqueEvents[:maxResults] } - return uniqueEvents, nil } @@ -119,19 +121,7 @@ func (c *GoogleCalendarClient) GetEventsByDateRange(ctx context.Context, start, } for _, item := range events.Items { - var evtStart, evtEnd time.Time - if item.Start.DateTime == "" { - // All-day event - parse in local timezone - evtStart, _ = time.ParseInLocation("2006-01-02", item.Start.Date, time.Local) - evtEnd, _ = time.ParseInLocation("2006-01-02", item.End.Date, time.Local) - } else { - // Timed event - parse RFC3339 then convert to local - evtStart, _ = time.Parse(time.RFC3339, item.Start.DateTime) - evtEnd, _ = time.Parse(time.RFC3339, item.End.DateTime) - evtStart = evtStart.Local() - evtEnd = evtEnd.Local() - } - + evtStart, evtEnd := parseEventTime(item) allEvents = append(allEvents, models.CalendarEvent{ ID: item.Id, Summary: item.Summary, @@ -143,20 +133,5 @@ func (c *GoogleCalendarClient) GetEventsByDateRange(ctx context.Context, start, } } - // Deduplicate - seen := make(map[string]bool) - var uniqueEvents []models.CalendarEvent - for _, event := range allEvents { - key := fmt.Sprintf("%s|%d", event.Summary, event.Start.Unix()) - if !seen[key] { - seen[key] = true - uniqueEvents = append(uniqueEvents, event) - } - } - - sort.Slice(uniqueEvents, func(i, j int) bool { - return uniqueEvents[i].Start.Before(uniqueEvents[j].Start) - }) - - return uniqueEvents, nil + return deduplicateEvents(allEvents), nil } -- cgit v1.2.3