aboutsummaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/carddav/carddav.go16
-rw-r--r--plugins/carddav/plugin.go4
-rw-r--r--plugins/carddav/public/address-book.html2
-rw-r--r--plugins/carddav/public/address-object.html2
-rw-r--r--plugins/carddav/public/update-address-object.html4
-rw-r--r--plugins/carddav/routes.go80
6 files changed, 62 insertions, 46 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)
}