Major rewrite, using a fork of webdav library

This commit is contained in:
Melon 2024-05-13 16:21:09 +01:00
parent 7edac1334d
commit cb65794fb6
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
8 changed files with 188 additions and 74 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
.idea/
.data/

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# Daisy
A CardDav-based contacts service.
Built using:
- [Tokidoki](https://git.sr.ht/~sircmpwn/tokidoki)
- [go-webdav](https://github.com/emersion/go-webdav)

View File

@ -1,50 +0,0 @@
package daisy
import (
"context"
"github.com/emersion/go-vcard"
"github.com/emersion/go-webdav/carddav"
)
type Backend struct {
}
func (b *Backend) AddressbookHomeSetPath(ctx context.Context) (string, error) {
//TODO implement me
panic("implement me")
}
func (b *Backend) AddressBook(ctx context.Context) (*carddav.AddressBook, error) {
//TODO implement me
panic("implement me")
}
func (b *Backend) GetAddressObject(ctx context.Context, path string, req *carddav.AddressDataRequest) (*carddav.AddressObject, error) {
//TODO implement me
panic("implement me")
}
func (b *Backend) ListAddressObjects(ctx context.Context, req *carddav.AddressDataRequest) ([]carddav.AddressObject, error) {
//TODO implement me
panic("implement me")
}
func (b *Backend) QueryAddressObjects(ctx context.Context, query *carddav.AddressBookQuery) ([]carddav.AddressObject, error) {
//TODO implement me
panic("implement me")
}
func (b *Backend) PutAddressObject(ctx context.Context, path string, card vcard.Card, opts *carddav.PutAddressObjectOptions) (loc string, err error) {
//TODO implement me
panic("implement me")
}
func (b *Backend) DeleteAddressObject(ctx context.Context, path string) error {
//TODO implement me
panic("implement me")
}
func (b *Backend) CurrentUserPrincipal(ctx context.Context) (string, error) {
//TODO implement me
panic("implement me")
}

View File

@ -6,14 +6,17 @@ import (
"flag"
"github.com/1f349/daisy"
"github.com/1f349/violet/utils"
"github.com/charmbracelet/log"
"github.com/google/subcommands"
"github.com/mrmelon54/exit-reload"
"log"
"os"
"path/filepath"
)
type serveCmd struct{ configPath string }
type serveCmd struct {
configPath string
debugLog bool
}
func (s *serveCmd) Name() string { return "serve" }
@ -21,6 +24,7 @@ func (s *serveCmd) Synopsis() string { return "Serve contacts service" }
func (s *serveCmd) SetFlags(f *flag.FlagSet) {
f.StringVar(&s.configPath, "conf", "", "/path/to/config.json : path to the config file")
f.BoolVar(&s.debugLog, "debug", false, "enable debug logging")
}
func (s *serveCmd) Usage() string {
@ -30,19 +34,22 @@ func (s *serveCmd) Usage() string {
}
func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...any) subcommands.ExitStatus {
log.Println("[Daisy] Starting...")
if s.debugLog {
daisy.Logger.SetLevel(log.DebugLevel)
}
daisy.Logger.Info("Starting...")
if s.configPath == "" {
log.Println("[Daisy] Error: config flag is missing")
daisy.Logger.Error("Config flag is missing")
return subcommands.ExitUsageError
}
openConf, err := os.Open(s.configPath)
if err != nil {
if os.IsNotExist(err) {
log.Println("[Daisy] Error: missing config file")
daisy.Logger.Error("Missing config file")
} else {
log.Println("[Daisy] Error: open config file: ", err)
daisy.Logger.Error("Open config file", "err", err)
}
return subcommands.ExitFailure
}
@ -50,13 +57,14 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...any) subcomm
var config daisy.Conf
err = json.NewDecoder(openConf).Decode(&config)
if err != nil {
log.Println("[Daisy] Error: invalid config file: ", err)
daisy.Logger.Error("Invalid config file", "err", err)
return subcommands.ExitFailure
}
configPathAbs, err := filepath.Abs(s.configPath)
if err != nil {
log.Fatal("[Daisy] Failed to get absolute config path")
daisy.Logger.Error("Failed to get absolute config path")
return subcommands.ExitFailure
}
wd := filepath.Dir(configPathAbs)
normalLoad(config, wd)
@ -65,7 +73,7 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...any) subcomm
func normalLoad(startUp daisy.Conf, wd string) {
srv := daisy.NewHttpServer(startUp, wd)
log.Printf("[Daisy] Starting HTTP server on '%s'\n", srv.Addr)
daisy.Logger.Infof("Starting HTTP server on '%s'", srv.Addr)
go utils.RunBackgroundHttp("HTTP", srv)
exit_reload.ExitReload("Daisy", func() {}, func() {

26
go.mod
View File

@ -3,14 +3,36 @@ module github.com/1f349/daisy
go 1.22
require (
git.sr.ht/~sircmpwn/tokidoki v0.0.0-20240419154046-4ca7d8c4e7ca
github.com/1f349/cardcaldav v0.0.3
github.com/1f349/violet v0.0.13
github.com/emersion/go-webdav v0.5.0
github.com/charmbracelet/log v0.4.0
github.com/emersion/go-webdav v0.5.1-0.20240419143909-21f251fa1de2
github.com/google/subcommands v1.2.0
github.com/julienschmidt/httprouter v1.3.0
github.com/mrmelon54/exit-reload v0.0.2
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.10.0 // indirect
github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6 // indirect
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/zerolog v1.32.0 // indirect
github.com/teambition/rrule-go v1.8.2 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
)
replace github.com/emersion/go-webdav => github.com/1f349/go-webdav v0.0.1

67
go.sum
View File

@ -1,27 +1,74 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.sr.ht/~sircmpwn/tokidoki v0.0.0-20240419154046-4ca7d8c4e7ca h1:HBRu4nwicaaZ4oqwTis2qy4Gb/CKLA2OSlibdKpMhb8=
git.sr.ht/~sircmpwn/tokidoki v0.0.0-20240419154046-4ca7d8c4e7ca/go.mod h1:/1jsi+vx9b/T8UHrDRJXwoY8td9JUXd9UajtVCpVoeg=
github.com/1f349/cardcaldav v0.0.3 h1:c2/wmOf+hwgKdaWLlKm5v8l0g9brD+Seqcp+xIDUXJI=
github.com/1f349/cardcaldav v0.0.3/go.mod h1:MCoGRimM7p/rLWtaJxYO91tsvgXZ47M69X3lldb5dfk=
github.com/1f349/go-webdav v0.0.1 h1:qF0k+zdcwE9DapLu+fGYQO866W269eHVLQ1mrhZGlb4=
github.com/1f349/go-webdav v0.0.1/go.mod h1:mI8iBx3RAODwX7PJJ7qzsKAKs/vY429YfS2/9wKnDbQ=
github.com/1f349/violet v0.0.13 h1:lJpTz15Ea83Uc1VAISXTjtKuzr8Pe8NM4cMGp3Aiyhk=
github.com/1f349/violet v0.0.13/go.mod h1:Ga5/hWqI+EkR6J1mAMNzs7aJhuGcA89XFqgQaDXC7Jo=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f/go.mod h1:2MKFUgfNMULRxqZkadG1Vh44we3y5gJAtTBlVsx1BKQ=
github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6 h1:kHoSgklT8weIDl6R6xFpBJ5IioRdBU1v2X2aCZRVCcM=
github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6/go.mod h1:BEksegNspIkjCQfmzWgsgbu6KdeJ/4LwUZs7DMBzjzw=
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9 h1:ATgqloALX6cHCranzkLb8/zjivwQ9DWWDCQRnxTPfaA=
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
github.com/emersion/go-webdav v0.5.0 h1:Ak/BQLgAihJt/UxJbCsEXDPxS5Uw4nZzgIMOq3rkKjc=
github.com/emersion/go-webdav v0.5.0/go.mod h1:ycyIzTelG5pHln4t+Y32/zBvmrM7+mV7x+V+Gx4ZQno=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mrmelon54/exit-reload v0.0.2 h1:vqgfrMD/bF21HkDsWgg5+NLjFDrD3KGVEN/iTrMn9Ms=
github.com/mrmelon54/exit-reload v0.0.2/go.mod h1:aE3NhsqGMLUqmv6cJZRouC/8gXkZTvVSabRGOpI+Vjc=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/teambition/rrule-go v1.7.2/go.mod h1:mBJ1Ht5uboJ6jexKdNUJg2NcwP8uUMNvStWXlJD3MvU=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8=
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

12
logger.go Normal file
View File

@ -0,0 +1,12 @@
package daisy
import (
"github.com/charmbracelet/log"
"os"
)
var Logger = log.NewWithOptions(os.Stderr, log.Options{
ReportCaller: true,
ReportTimestamp: true,
Prefix: "Daisy",
})

View File

@ -1,23 +1,90 @@
package daisy
import (
"git.sr.ht/~sircmpwn/tokidoki/storage"
"github.com/1f349/cardcaldav"
"github.com/emersion/go-webdav"
"github.com/emersion/go-webdav/carddav"
"net/http"
"path/filepath"
"time"
)
type Conf struct {
Listen string `json:"listen"`
DB string `json:"db"`
}
type daisyHandler struct {
auth *cardcaldav.Auth
backend carddav.Backend
}
func (d *daisyHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
principlePath, err := d.auth.CurrentUserPrincipal(req.Context())
if err != nil {
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
var homeSets []webdav.BackendSuppliedHomeSet
path, err := d.backend.AddressBookHomeSetPath(req.Context())
if err != nil {
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
homeSets = append(homeSets, carddav.NewAddressBookHomeSet(path))
if req.URL.Path == principlePath {
opts := webdav.ServePrincipalOptions{
CurrentUserPrincipalPath: principlePath,
HomeSets: homeSets,
Capabilities: []webdav.Capability{
carddav.CapabilityAddressBook,
},
}
webdav.ServePrincipal(rw, req, &opts)
return
}
if req.URL.Path == "/" {
http.Error(rw, "Daisy API Endpoint", http.StatusOK)
return
}
http.NotFound(rw, req)
}
func NewHttpServer(conf Conf, wd string) *http.Server {
h := &carddav.Handler{
Backend: &Backend{},
cardcaldav.SetupLogger(Logger)
principle := cardcaldav.NewAuth(conf.DB, Logger)
_, cardStorage, err := storage.NewFilesystem(filepath.Join(wd, "storage"), "/calendar/", "/contacts/", principle)
if err != nil {
Logger.Fatal("Failed to load storage backend", "err", err)
}
cardHandler := &carddav.Handler{Backend: cardStorage}
handler := &daisyHandler{
auth: principle,
backend: cardStorage,
}
r := http.NewServeMux()
r.Handle("/", handler)
r.Handle("/.well-known/carddav", cardHandler)
r.Handle("/{user}/contacts/", cardHandler)
r2 := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
t := time.Now()
r.ServeHTTP(rw, req)
td := time.Since(t)
Logger.Debug("Request", "method", req.Method, "url", req.URL.String(), "remote", req.RemoteAddr, "dur", td.String())
})
return &http.Server{
Addr: conf.Listen,
Handler: h,
Handler: principle.Middleware(r2),
ReadTimeout: time.Minute,
ReadHeaderTimeout: time.Minute,
WriteTimeout: time.Minute,