hivedav

A curlable free/busy scheduler with CalDAV integration
git clone https://git.in0rdr.ch/hivedav.git
Log | Files | Refs | Pull requests | README | LICENSE

commit df1e43f379bceeb7b137656e950a0884a171a96d
parent fd570ae3106a48feb62de760c61609028757f694
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date:   Wed,  6 Sep 2023 16:07:52 +0200

feat(server): smaller functions

Diffstat:
MREADME.md | 2+-
Mconfig/config.go | 7+++++--
Mconfig/config_test.go | 14+-------------
Mmain.go | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mserver.go | 91++++++++++++++++++++++++++++++-------------------------------------------------
Mserver_test.go | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
6 files changed, 172 insertions(+), 89 deletions(-)

diff --git a/README.md b/README.md @@ -36,7 +36,7 @@ The application is configurable through environment variables or the # Optional #HIVEDAV_LISTEN_ADDRESS=127.0.0.1 #HIVEDAV_LISTEN_PORT=3737 -#HIVEDAV_CALDAV_HOMESET=0 +#HIVEDAV_CALENDAR=0 # Required HIVEDAV_CALDAV_URI= diff --git a/config/config.go b/config/config.go @@ -2,13 +2,14 @@ package config import ( "github.com/spf13/viper" + "log" ) type Config struct { ListenAddress string `mapstructure:"HIVEDAV_LISTEN_ADDRESS"` ListenPort int `mapstructure:"HIVEDAV_LISTEN_PORT"` CaldavUri string `mapstructure:"HIVEDAV_CALDAV_URI"` - Homeset int `mapstructure:"HIVEDAV_CALDAV_HOMESET"` + Calendar int `mapstructure:"HIVEDAV_CALENDAR"` CaldavUser string `mapstructure:"HIVEDAV_CALDAV_USER"` CaldavPassword string `mapstructure:"HIVEDAV_CALDAV_PASSWORD"` } @@ -27,15 +28,17 @@ func (c *Config) LoadConfig(path string) (*Config, error) { // define some defaults viper.SetDefault("HIVEDAV_LISTEN_PORT", 3737) viper.SetDefault("HIVEDAV_LISTEN_ADDRESS", "[::]") - viper.SetDefault("HIVEDAV_CALDAV_HOMESET", 0) + viper.SetDefault("HIVEDAV_CALENDAR", 0) err := viper.ReadInConfig() if err != nil { + log.Println("app.env config file not found") return nil, err } err = viper.Unmarshal(c) if err != nil { + log.Println("Error unmarshalling config") return nil, err } return c, nil diff --git a/config/config_test.go b/config/config_test.go @@ -11,18 +11,6 @@ func TestConfig(t *testing.T) { _, err := conf.LoadConfig("..") if err != nil { - log.Fatal("error loading config: ", err) + log.Fatal("Error loading config: ", err) } - - //if conf.ListenAddress != "[::]" { - // log.Fatal("wrong default listening address", conf.ListenAddress) - //} - - //if conf.ListenPort != 3737 { - // log.Fatal("wrong default listening port", conf.ListenPort) - //} - - //if conf.Homeset != 0 { - // log.Fatal("wrong default calendar in homeset", conf.Homeset) - //} } diff --git a/main.go b/main.go @@ -4,18 +4,83 @@ import ( "fmt" "log" "net/http" + "hivedav/config" + "github.com/emersion/go-ical" ) func main() { - var server Server // If exists, load config file from pwd - server.config.LoadConfig(".") + conf := &config.Config{} + conf, err := conf.LoadConfig(".") + if err != nil { + log.Fatal("Error loading config: ", err) + } - log.Printf("%+v\n", server.config) - log.Println("Running hivedav v0.1 🍯") - log.Println("Reading availability from:", server.config.CaldavUri) - log.Println("Reading as user:", server.config.CaldavUser) - log.Printf("Listening on %s:%d\n", server.config.ListenAddress, server.config.ListenPort) + var server *Server + server, err = server.NewServer(conf) + if err != nil { + log.Fatal("Error creating server with config: ", err) + } - http.ListenAndServe(fmt.Sprintf("%s:%d", server.config.ListenAddress, server.config.ListenPort), &server) + log.Println("Starting hivedav v0.1 🍯") + + log.Println("Reading availability from:", conf.CaldavUri) + log.Println("Reading as user:", conf.CaldavUser) + log.Println("Using nth calendar in homeset:", conf.Calendar) + + userPrincipal, _ := server.caldavClient.FindCurrentUserPrincipal() + log.Println("User principal: ", userPrincipal) + homeSet, _ := server.caldavClient.FindCalendarHomeSet(userPrincipal) + log.Println("Calendar homeset: ", homeSet) + + // Find all calendars in homeset + calendars, err := server.findCalendars(homeSet) + if err != nil { + log.Fatal(err) + } + server.calendars = calendars + + objects, err := server.getAvailability(server.calendars[conf.Calendar].Path) + if err != nil { + log.Println("Unable to fetch availability") + } + server.objects = objects + + log.Printf("Using calendar objects: %+v", objects) + + for i, obj := range server.objects { + //log.Printf("%d %s\n", i, obj.Path) + log.Printf("%d - ", i) + + // Get ics DTSTART and DTEND + // https://github.com/emersion/go-ical/blob/master/example_test.go + for _, event := range obj.Data.Events() { + // https://github.com/emersion/go-ical/blob/master/enums.go + summary, err := event.Props.Text(ical.PropSummary) + log.Printf("Found event: %v\n", summary) + //log.Printf("Found event: %+v\n", event.Props) + + //localTimezone, err := time.LoadLocation("Europe/Zurich") + + // Uses UTC when location is nil + // https://github.com/emersion/go-ical/blob/master/ical.go + // https://github.com/emersion/go-ical/issues/10 + //dtstart, err := event.DateTimeStart(localTimezone) + dtstart, err := event.DateTimeStart(nil) + if err != nil { + log.Println("TZID not in tzdb form") + // https://github.com/emersion/go-ical/blob/master/example_test.go + event.Props.SetText(ical.PropTimezoneID, "") + dtstart, err = event.DateTimeStart(nil) + } + + dtend, err := event.DateTimeEnd(nil) + + log.Printf("Event DTSTART: %+v\n", dtstart) + log.Printf("Event DTEND: %+v\n", dtend) + } + } + + http.ListenAndServe(fmt.Sprintf("%s:%d", conf.ListenAddress, conf.ListenPort), server) + log.Printf("Ready, listening on %s:%d\n", conf.ListenAddress, conf.ListenPort) } diff --git a/server.go b/server.go @@ -1,13 +1,12 @@ package main import ( + "hivedav/config" "fmt" - "github.com/emersion/go-ical" "github.com/in0rdr/go-webdav" "github.com/in0rdr/go-webdav/caldav" "github.com/pquerna/termchalk/ansistyle" "github.com/pquerna/termchalk/prettytable" - "hivedav/config" "io" "log" "net/http" @@ -16,12 +15,28 @@ import ( ) type Server struct { - config config.Config + config *config.Config + caldavClient *caldav.Client + calendars []caldav.Calendar + objects []caldav.CalendarObject } -func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { - s.getAvailability() +func (s *Server) NewServer(c *config.Config) (*Server, error) { + // with nil, http.DefaultClient is used + // https://godocs.io/github.com/emersion/go-webdav#HTTPClientWithBasicAuth + webdavClient := webdav.HTTPClientWithBasicAuth(nil, c.CaldavUser, c.CaldavPassword) + caldavClient, err := caldav.NewClient(webdavClient, c.CaldavUri) + if err != nil { + log.Fatal("Error creating caldav client:", err) + } + s = new(Server) + s.caldavClient = caldavClient + s.config = c + return s, nil +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { userAgent := req.Header.Get("user-agent") if strings.Contains(userAgent, "curl") { w.Header().Set("Content-Type", "text/plain") @@ -38,25 +53,18 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } -func (s *Server) getAvailability() { - // with nil, http.DefaultClient is used - // https://godocs.io/github.com/emersion/go-webdav#HTTPClientWithBasicAuth - webdavClient := webdav.HTTPClientWithBasicAuth(nil, - s.config.CaldavUser, s.config.CaldavPassword) - caldavClient, _ := caldav.NewClient(webdavClient, s.config.CaldavUri) - userPrincipal, _ := caldavClient.FindCurrentUserPrincipal() - log.Println("USER PRINCIPAL: ", userPrincipal) - homeSet, _ := caldavClient.FindCalendarHomeSet(userPrincipal) - log.Println("CALENDAR HOMESET: ", homeSet) - - calendars, err := caldavClient.FindCalendars(homeSet) +func (s *Server) findCalendars(homeSet string) ([]caldav.Calendar, error) { + calendars, err := s.caldavClient.FindCalendars(homeSet) if err != nil { - log.Fatalf("FindCalendars: %s", err) - } - for i, calendar := range calendars { - log.Printf("cal %d: %s %s\n", i, calendar.Name, calendar.Path) + log.Printf("No calendars in homeset: %v", err) + return nil, err } + log.Printf("Calendars in homeset: %+v", calendars) + return calendars, nil +} +// fetch availabiliy for a calendar +func (s *Server) getAvailability(calendarPath string) ([]caldav.CalendarObject, error) { compReq := caldav.CalendarCompRequest{ Name: "VCALENDAR", Props: []string{"VERSION"}, @@ -84,44 +92,13 @@ func (s *Server) getAvailability() { CompFilter: compFilter, } - // Query the calendar of the homeset - objects, err := caldavClient.QueryCalendar(calendars[s.config.Homeset].Path, query) + // Get the calendar objects + objects, err := s.caldavClient.QueryCalendar(calendarPath, query) if err != nil { - log.Fatalf("QueryCalendar: %s", err) - } - - for i, obj := range objects { - //log.Printf("%d %s\n", i, obj.Path) - log.Printf("%d - ", i) - - // Get ics DTSTART and DTEND - // https://github.com/emersion/go-ical/blob/master/example_test.go - for _, event := range obj.Data.Events() { - // https://github.com/emersion/go-ical/blob/master/enums.go - summary, err := event.Props.Text(ical.PropSummary) - log.Printf("Found event: %v\n", summary) - //log.Printf("Found event: %+v\n", event.Props) - - //localTimezone, err := time.LoadLocation("Europe/Zurich") - - // Uses UTC when location is nil - // https://github.com/emersion/go-ical/blob/master/ical.go - // https://github.com/emersion/go-ical/issues/10 - //dtstart, err := event.DateTimeStart(localTimezone) - dtstart, err := event.DateTimeStart(nil) - if err != nil { - log.Println("TZID not in tzdb form") - // https://github.com/emersion/go-ical/blob/master/example_test.go - event.Props.SetText(ical.PropTimezoneID, "") - dtstart, err = event.DateTimeStart(nil) - } - - dtend, err := event.DateTimeEnd(nil) - - log.Printf("Event DTSTART: %+v\n", dtstart) - log.Printf("Event DTEND: %+v\n", dtend) - } + log.Printf("No calendar objects found: %v", err) + return nil, err } + return objects, nil } func (s *Server) showCubicles() { diff --git a/server_test.go b/server_test.go @@ -1,23 +1,31 @@ package main import ( - "log" "net/http" "net/http/httptest" + "github.com/in0rdr/go-webdav" + "github.com/in0rdr/go-webdav/caldav" "strings" "testing" + "hivedav/config" ) -var server Server +var server *Server func TestServer(t *testing.T) { - _, err := server.config.LoadConfig(".") - + config := config.Config { + ListenAddress: "[::]", + ListenPort: 3737, + CaldavUri: "/", + Calendar: 0, + CaldavUser: "gandalf", + CaldavPassword: "ring", + } + server, err := server.NewServer(&config) if err != nil { - log.Fatal("error loading config: ", err) + t.Fatal("Could not create hivedav server") } - // ResponseRecorder is an implementation of http.ResponseWriter that // records its mutations for later inspection in tests. // https://pkg.go.dev/net/http/httptest @@ -62,7 +70,49 @@ func TestServer(t *testing.T) { } } +func TestFindCalendars(t *testing.T) { + // TODO: test the http calls + //config := config.Config { + // ListenAddress: "[::]", + // ListenPort: 3737, + // CaldavUri: "/", + // Calendar: 0, + // CaldavUser: "gandalf", + // CaldavPassword: "ring", + //} + //server, err := server.NewServer(&config) + //if err != nil { + // t.Fatal("Could not create hivedav server") + //} + + //homeSet := "/caldav/test@example.com/" + //_, err = server.findCalendars(homeSet) + //if err != nil { + // t.Fatal("Error fetching calendars for homeset", err) + //} +} + func TestGetAvailability(t *testing.T) { - server.config.LoadConfig(".") - server.getAvailability() + config := config.Config { + ListenAddress: "[::]", + ListenPort: 3737, + CaldavUri: "/", + Calendar: 0, + CaldavUser: "gandalf", + CaldavPassword: "ring", + } + server, err := server.NewServer(&config) + if err != nil { + t.Fatal("Could not create hivedav server") + } + // TODO: Mock the server calender events with + // https://github.com/emersion/go-webdav/blob/master/caldav/server_test.go + webdavClient := webdav.HTTPClientWithBasicAuth(nil, + config.CaldavUser, config.CaldavPassword) + server.caldavClient, _ = caldav.NewClient(webdavClient, config.CaldavUri) + server.calendars = make([]caldav.Calendar, 1) + server.calendars[0].Path = "/" + server.objects = make([]caldav.CalendarObject, 1) + + server.getAvailability(server.calendars[0].Path) }