diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-01-22 15:41:39 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-01-22 15:41:39 -1000 |
| commit | 7c1e88ef3cea6b2f08bc6e63031ad4de041e8bf3 (patch) | |
| tree | c2646cb27b12f828f756b88185b286104a8902e8 /internal/api | |
| parent | 317d66d9f39d9ab4fbfd592b65ca2548cb072806 (diff) | |
Support multiple Google Calendars
GOOGLE_CALENDAR_ID now accepts comma-separated calendar IDs:
GOOGLE_CALENDAR_ID=cal1@group.calendar.google.com,cal2@gmail.com
Events from all calendars are merged and sorted by start time.
If one calendar fails to fetch, others still load.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/api')
| -rw-r--r-- | internal/api/google_calendar.go | 86 |
1 files changed, 57 insertions, 29 deletions
diff --git a/internal/api/google_calendar.go b/internal/api/google_calendar.go index 836d98c..8dd48f0 100644 --- a/internal/api/google_calendar.go +++ b/internal/api/google_calendar.go @@ -3,6 +3,9 @@ package api import ( "context" "fmt" + "log" + "sort" + "strings" "time" "task-dashboard/internal/models" @@ -12,51 +15,76 @@ import ( ) type GoogleCalendarClient struct { - srv *calendar.Service - calendarID string + srv *calendar.Service + calendarIDs []string } -func NewGoogleCalendarClient(ctx context.Context, credentialsFile, calendarID string) (*GoogleCalendarClient, error) { +// 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) { srv, err := calendar.NewService(ctx, option.WithCredentialsFile(credentialsFile)) if err != nil { return nil, fmt.Errorf("unable to retrieve Calendar client: %v", err) } + // Parse comma-separated calendar IDs + ids := strings.Split(calendarIDs, ",") + var trimmedIDs []string + for _, id := range ids { + if trimmed := strings.TrimSpace(id); trimmed != "" { + trimmedIDs = append(trimmedIDs, trimmed) + } + } + return &GoogleCalendarClient{ - srv: srv, - calendarID: calendarID, + srv: srv, + calendarIDs: trimmedIDs, }, nil } func (c *GoogleCalendarClient) GetUpcomingEvents(ctx context.Context, maxResults int) ([]models.CalendarEvent, error) { t := time.Now().Format(time.RFC3339) - events, err := c.srv.Events.List(c.calendarID).ShowDeleted(false). - SingleEvents(true).TimeMin(t).MaxResults(int64(maxResults)).OrderBy("startTime").Do() - if err != nil { - return nil, fmt.Errorf("unable to retrieve events: %v", err) - } + var allEvents []models.CalendarEvent - var calendarEvents []models.CalendarEvent - for _, item := range events.Items { - var start, end time.Time - if item.Start.DateTime == "" { - // All-day event - start, _ = time.Parse("2006-01-02", item.Start.Date) - end, _ = time.Parse("2006-01-02", item.End.Date) - } else { - start, _ = time.Parse(time.RFC3339, item.Start.DateTime) - end, _ = time.Parse(time.RFC3339, item.End.DateTime) + for _, calendarID := range c.calendarIDs { + events, err := c.srv.Events.List(calendarID).ShowDeleted(false). + 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 } - calendarEvents = append(calendarEvents, models.CalendarEvent{ - ID: item.Id, - Summary: item.Summary, - Description: item.Description, - Start: start, - End: end, - HTMLLink: item.HtmlLink, - }) + for _, item := range events.Items { + var start, end time.Time + if item.Start.DateTime == "" { + // All-day event + start, _ = time.Parse("2006-01-02", item.Start.Date) + end, _ = time.Parse("2006-01-02", item.End.Date) + } else { + start, _ = time.Parse(time.RFC3339, item.Start.DateTime) + end, _ = time.Parse(time.RFC3339, item.End.DateTime) + } + + allEvents = append(allEvents, models.CalendarEvent{ + ID: item.Id, + Summary: item.Summary, + Description: item.Description, + Start: start, + End: end, + HTMLLink: item.HtmlLink, + }) + } + } + + // Sort all events by start time + sort.Slice(allEvents, func(i, j int) bool { + return allEvents[i].Start.Before(allEvents[j].Start) + }) + + // Limit to maxResults + if len(allEvents) > maxResults { + allEvents = allEvents[:maxResults] } - return calendarEvents, nil + return allEvents, nil } |
