diff options
-rw-r--r-- | plugins/carddav/carddav.go | 16 | ||||
-rw-r--r-- | plugins/carddav/plugin.go | 4 | ||||
-rw-r--r-- | plugins/carddav/public/address-book.html | 2 | ||||
-rw-r--r-- | plugins/carddav/public/address-object.html | 2 | ||||
-rw-r--r-- | plugins/carddav/public/update-address-object.html | 4 | ||||
-rw-r--r-- | plugins/carddav/routes.go | 80 | ||||
-rw-r--r-- | themes/sourcehut/address-book.html | 5 | ||||
-rw-r--r-- | themes/sourcehut/address-object.html | 2 |
8 files changed, 64 insertions, 51 deletions
diff --git a/plugins/carddav/carddav.go b/plugins/carddav/carddav.go index 0c1a06d..2509872 100644 --- a/plugins/carddav/carddav.go +++ b/plugins/carddav/carddav.go @@ -28,3 +28,19 @@ func newClient(u *url.URL, session *koushin.Session) (*carddav.Client, error) { } return carddav.NewClient(&http.Client{Transport: &rt}, u.String()) } + +type AddressObject struct { + *carddav.AddressObject +} + +func newAddressObjectList(aos []carddav.AddressObject) []AddressObject { + l := make([]AddressObject, len(aos)) + for i := range aos { + l[i] = AddressObject{&aos[i]} + } + return l +} + +func (ao AddressObject) URL() string { + return "/contacts/" + url.PathEscape(ao.Path) +} diff --git a/plugins/carddav/plugin.go b/plugins/carddav/plugin.go index 5afdde2..3002da1 100644 --- a/plugins/carddav/plugin.go +++ b/plugins/carddav/plugin.go @@ -37,6 +37,10 @@ type plugin struct { homeSetCache map[string]string } +func (p *plugin) client(session *koushin.Session) (*carddav.Client, error) { + return newClient(p.url, session) +} + func (p *plugin) clientWithAddressBook(session *koushin.Session) (*carddav.Client, *carddav.AddressBook, error) { c, err := newClient(p.url, session) if err != nil { diff --git a/plugins/carddav/public/address-book.html b/plugins/carddav/public/address-book.html index 3c31017..d4e13b9 100644 --- a/plugins/carddav/public/address-book.html +++ b/plugins/carddav/public/address-book.html @@ -17,7 +17,7 @@ <ul> {{range .AddressObjects}} <li> - <a href="/contacts/{{.Card.Value "UID" | pathescape}}"> + <a href="{{.URL}}"> {{.Card.Value "FN"}} </a> {{$email := .Card.PreferredValue "EMAIL"}} diff --git a/plugins/carddav/public/address-object.html b/plugins/carddav/public/address-object.html index 1d564ee..e4ec135 100644 --- a/plugins/carddav/public/address-object.html +++ b/plugins/carddav/public/address-object.html @@ -11,7 +11,7 @@ <h2>Contact: {{$fn}}</h2> <p> - <a href="/contacts/{{.AddressObject.Card.Value "UID" | pathescape}}/edit"> + <a href="{{.AddressObject.URL}}/edit"> Edit </a> </p> diff --git a/plugins/carddav/public/update-address-object.html b/plugins/carddav/public/update-address-object.html index b0ab20f..82adf8e 100644 --- a/plugins/carddav/public/update-address-object.html +++ b/plugins/carddav/public/update-address-object.html @@ -6,7 +6,9 @@ <a href="/contacts">Back</a> </p> -<h2>Edit contact</h2> +<h2> + {{if .Card}}Edit{{else}}Create{{end}} contact +</h2> <form method="post"> <label for="fn">Name:</label> diff --git a/plugins/carddav/routes.go b/plugins/carddav/routes.go index 88b471a..b276944 100644 --- a/plugins/carddav/routes.go +++ b/plugins/carddav/routes.go @@ -3,6 +3,7 @@ package koushincarddav import ( "fmt" "net/http" + "net/url" "path" "strings" @@ -10,18 +11,19 @@ import ( "github.com/emersion/go-vcard" "github.com/emersion/go-webdav/carddav" "github.com/google/uuid" + "github.com/labstack/echo/v4" ) type AddressBookRenderData struct { koushin.BaseRenderData AddressBook *carddav.AddressBook - AddressObjects []carddav.AddressObject + AddressObjects []AddressObject Query string } type AddressObjectRenderData struct { koushin.BaseRenderData - AddressObject *carddav.AddressObject + AddressObject AddressObject } type UpdateAddressObjectRenderData struct { @@ -30,6 +32,15 @@ type UpdateAddressObjectRenderData struct { Card vcard.Card } +func parseObjectPath(s string) (string, error) { + p, err := url.PathUnescape(s) + if err != nil { + err = fmt.Errorf("failed to parse path: %v", err) + return "", echo.NewHTTPError(http.StatusBadRequest, err) + } + return string(p), nil +} + func registerRoutes(p *plugin) { p.GET("/contacts", func(ctx *koushin.Context) error { queryText := ctx.QueryParam("query") @@ -65,7 +76,7 @@ func registerRoutes(p *plugin) { } } - addrs, err := c.QueryAddressBook(addressBook.Path, &query) + aos, err := c.QueryAddressBook(addressBook.Path, &query) if err != nil { return fmt.Errorf("failed to query CardDAV addresses: %v", err) } @@ -73,20 +84,23 @@ func registerRoutes(p *plugin) { return ctx.Render(http.StatusOK, "address-book.html", &AddressBookRenderData{ BaseRenderData: *koushin.NewBaseRenderData(ctx), AddressBook: addressBook, - AddressObjects: addrs, + AddressObjects: newAddressObjectList(aos), Query: queryText, }) }) - p.GET("/contacts/:uid", func(ctx *koushin.Context) error { - uid := ctx.Param("uid") + p.GET("/contacts/:path", func(ctx *koushin.Context) error { + path, err := parseObjectPath(ctx.Param("path")) + if err != nil { + return err + } - c, addressBook, err := p.clientWithAddressBook(ctx.Session) + c, err := p.client(ctx.Session) if err != nil { return err } - query := carddav.AddressBookQuery{ + multiGet := carddav.AddressBookMultiGet{ DataRequest: carddav.AddressDataRequest{ Props: []string{ vcard.FieldFormattedName, @@ -94,31 +108,27 @@ func registerRoutes(p *plugin) { vcard.FieldUID, }, }, - PropFilters: []carddav.PropFilter{{ - Name: vcard.FieldUID, - TextMatches: []carddav.TextMatch{{ - Text: uid, - MatchType: carddav.MatchEquals, - }}, - }}, } - addrs, err := c.QueryAddressBook(addressBook.Path, &query) + aos, err := c.MultiGetAddressBook(path, &multiGet) if err != nil { return fmt.Errorf("failed to query CardDAV address: %v", err) } - if len(addrs) != 1 { - return fmt.Errorf("expected exactly one address object with UID %q, got %v", uid, len(addrs)) + if len(aos) != 1 { + return fmt.Errorf("expected exactly one address object with path %q, got %v", path, len(aos)) } - addr := &addrs[0] + ao := &aos[0] return ctx.Render(http.StatusOK, "address-object.html", &AddressObjectRenderData{ BaseRenderData: *koushin.NewBaseRenderData(ctx), - AddressObject: addr, + AddressObject: AddressObject{ao}, }) }) updateContact := func(ctx *koushin.Context) error { - uid := ctx.Param("uid") + addressObjectPath, err := parseObjectPath(ctx.Param("path")) + if err != nil { + return err + } c, addressBook, err := p.clientWithAddressBook(ctx.Session) if err != nil { @@ -127,25 +137,11 @@ func registerRoutes(p *plugin) { var ao *carddav.AddressObject var card vcard.Card - if uid != "" { - query := carddav.AddressBookQuery{ - DataRequest: carddav.AddressDataRequest{AllProp: true}, - PropFilters: []carddav.PropFilter{{ - Name: vcard.FieldUID, - TextMatches: []carddav.TextMatch{{ - Text: uid, - MatchType: carddav.MatchEquals, - }}, - }}, - } - aos, err := c.QueryAddressBook(addressBook.Path, &query) + if addressObjectPath != "" { + ao, err := c.GetAddressObject(addressObjectPath) if err != nil { return fmt.Errorf("failed to query CardDAV address: %v", err) } - if len(aos) != 1 { - return fmt.Errorf("expected exactly one address object with UID %q, got %v", uid, len(aos)) - } - ao = &aos[0] card = ao.Card } else { card = make(vcard.Card) @@ -189,14 +185,12 @@ func registerRoutes(p *plugin) { } else { p = path.Join(addressBook.Path, id.String()+".vcf") } - _, err = c.PutAddressObject(p, card) + ao, err = c.PutAddressObject(p, card) if err != nil { return fmt.Errorf("failed to put address object: %v", err) } - // TODO: check if the returned AddressObject's path matches, if not - // fetch the new UID (the server may mutate it) - return ctx.Redirect(http.StatusFound, "/contacts/"+card.Value(vcard.FieldUID)) + return ctx.Redirect(http.StatusFound, AddressObject{ao}.URL()) } return ctx.Render(http.StatusOK, "update-address-object.html", &UpdateAddressObjectRenderData{ @@ -209,6 +203,6 @@ func registerRoutes(p *plugin) { p.GET("/contacts/create", updateContact) p.POST("/contacts/create", updateContact) - p.GET("/contacts/:uid/edit", updateContact) - p.POST("/contacts/:uid/edit", updateContact) + p.GET("/contacts/:path/edit", updateContact) + p.POST("/contacts/:path/edit", updateContact) } diff --git a/themes/sourcehut/address-book.html b/themes/sourcehut/address-book.html index 666e08a..c5600d6 100644 --- a/themes/sourcehut/address-book.html +++ b/themes/sourcehut/address-book.html @@ -37,10 +37,7 @@ <ul class="nav flex-column"> {{range .AddressObjects}} <li class="nav-item"> - <a - class="nav-link" - href="/contacts/{{.Card.Value "UID" | pathescape}}" - >{{.Card.Value "FN"}}</a> + <a class="nav-link" href="{{.URL}}">{{.Card.Value "FN"}}</a> </li> {{end}} </ul> diff --git a/themes/sourcehut/address-object.html b/themes/sourcehut/address-object.html index b664f62..1ab445f 100644 --- a/themes/sourcehut/address-object.html +++ b/themes/sourcehut/address-object.html @@ -13,7 +13,7 @@ </li> <li class="nav-item"> <a class="nav-link" - href="/contacts/{{.AddressObject.Card.Value "UID" | pathescape}}/edit" + href="{{.AddressObject.URL}}/edit" >Edit</a> </li> <li class="mr-auto d-none d-sm-flex"></li> |