summaryrefslogtreecommitdiff
path: root/internal/api
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-22 15:41:39 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-22 15:41:39 -1000
commit7c1e88ef3cea6b2f08bc6e63031ad4de041e8bf3 (patch)
treec2646cb27b12f828f756b88185b286104a8902e8 /internal/api
parent317d66d9f39d9ab4fbfd592b65ca2548cb072806 (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.go86
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
}