187 lines
4.3 KiB
Go
187 lines
4.3 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"luxick/chronological/internal/models"
|
|
|
|
"github.com/emersion/go-ical"
|
|
"github.com/emersion/go-webdav/caldav"
|
|
)
|
|
|
|
// CalDAVStore handles fetching events from a CalDAV server.
|
|
type CalDAVStore struct {
|
|
url string
|
|
username string
|
|
password string
|
|
client *caldav.Client
|
|
}
|
|
|
|
// NewCalDAVStore creates a new CalDAV store with the given credentials.
|
|
func NewCalDAVStore(url, username, password string) *CalDAVStore {
|
|
return &CalDAVStore{
|
|
url: url,
|
|
username: username,
|
|
password: password,
|
|
}
|
|
}
|
|
|
|
// Connect establishes a connection to the CalDAV server.
|
|
func (s *CalDAVStore) Connect(ctx context.Context) error {
|
|
httpClient := &http.Client{
|
|
Transport: &basicAuthTransport{
|
|
username: s.username,
|
|
password: s.password,
|
|
},
|
|
}
|
|
|
|
client, err := caldav.NewClient(httpClient, s.url)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create caldav client: %w", err)
|
|
}
|
|
|
|
s.client = client
|
|
return nil
|
|
}
|
|
|
|
// LoadEvents fetches events from the CalDAV server for a date range.
|
|
func (s *CalDAVStore) LoadEvents(ctx context.Context, start, end time.Time) ([]*models.Event, error) {
|
|
if s.client == nil {
|
|
return nil, fmt.Errorf("caldav client not connected")
|
|
}
|
|
|
|
principal, err := s.client.FindCurrentUserPrincipal(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find user principal: %w", err)
|
|
}
|
|
|
|
homeSet, err := s.client.FindCalendarHomeSet(ctx, principal)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find calendar home set: %w", err)
|
|
}
|
|
|
|
calendars, err := s.client.FindCalendars(ctx, homeSet)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find calendars: %w", err)
|
|
}
|
|
|
|
var allEvents []*models.Event
|
|
|
|
for _, cal := range calendars {
|
|
query := &caldav.CalendarQuery{
|
|
CompRequest: caldav.CalendarCompRequest{
|
|
Name: "VCALENDAR",
|
|
Comps: []caldav.CalendarCompRequest{{
|
|
Name: "VEVENT",
|
|
Props: []string{
|
|
"SUMMARY",
|
|
"DESCRIPTION",
|
|
"DTSTART",
|
|
"DTEND",
|
|
"LOCATION",
|
|
"UID",
|
|
},
|
|
}},
|
|
},
|
|
CompFilter: caldav.CompFilter{
|
|
Name: "VCALENDAR",
|
|
Comps: []caldav.CompFilter{{
|
|
Name: "VEVENT",
|
|
Start: start,
|
|
End: end,
|
|
}},
|
|
},
|
|
}
|
|
|
|
objects, err := s.client.QueryCalendar(ctx, cal.Path, query)
|
|
if err != nil {
|
|
continue // Skip calendars that fail
|
|
}
|
|
|
|
events, err := parseCalDAVObjects(objects)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
allEvents = append(allEvents, events...)
|
|
}
|
|
|
|
return allEvents, nil
|
|
}
|
|
|
|
// LoadEventsForMonth fetches events for a specific month.
|
|
func (s *CalDAVStore) LoadEventsForMonth(ctx context.Context, year int, month time.Month) ([]*models.Event, error) {
|
|
start := time.Date(year, month, 1, 0, 0, 0, 0, time.Local)
|
|
end := start.AddDate(0, 1, 0)
|
|
return s.LoadEvents(ctx, start, end)
|
|
}
|
|
|
|
func parseCalDAVObjects(objects []caldav.CalendarObject) ([]*models.Event, error) {
|
|
var events []*models.Event
|
|
|
|
for _, obj := range objects {
|
|
cal := obj.Data
|
|
if cal == nil {
|
|
continue
|
|
}
|
|
|
|
for _, child := range cal.Children {
|
|
if child.Name != ical.CompEvent {
|
|
continue
|
|
}
|
|
|
|
event := ical.Event{Component: child}
|
|
|
|
uid, _ := event.Props.Text(ical.PropUID)
|
|
summary, _ := event.Props.Text(ical.PropSummary)
|
|
description, _ := event.Props.Text(ical.PropDescription)
|
|
location, _ := event.Props.Text(ical.PropLocation)
|
|
|
|
dtstart, err := event.Props.DateTime(ical.PropDateTimeStart, time.Local)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
dtend, err := event.Props.DateTime(ical.PropDateTimeEnd, time.Local)
|
|
if err != nil {
|
|
dtend = dtstart.Add(time.Hour)
|
|
}
|
|
|
|
// Check if all-day event
|
|
prop := event.Props.Get(ical.PropDateTimeStart)
|
|
allDay := false
|
|
if prop != nil {
|
|
valueParam := prop.Params.Get(ical.ParamValue)
|
|
allDay = valueParam == "DATE"
|
|
}
|
|
|
|
events = append(events, &models.Event{
|
|
ID: uid,
|
|
Title: summary,
|
|
Description: description,
|
|
Start: dtstart,
|
|
End: dtend,
|
|
AllDay: allDay,
|
|
Location: location,
|
|
Source: models.SourceCalDAV,
|
|
})
|
|
}
|
|
}
|
|
|
|
return events, nil
|
|
}
|
|
|
|
// basicAuthTransport adds basic auth to HTTP requests.
|
|
type basicAuthTransport struct {
|
|
username string
|
|
password string
|
|
}
|
|
|
|
func (t *basicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
req.SetBasicAuth(t.username, t.password)
|
|
return http.DefaultTransport.RoundTrip(req)
|
|
}
|