소스 검색

plugins/carddav: use paths instead of UIDs in URLs

master
Simon Ser 4 년 전
부모
커밋
89149b38c8
No known key found for this signature in database GPG 키 ID: FDE7BE0E88F5E48
8개의 변경된 파일64개의 추가작업 그리고 51개의 파일을 삭제
  1. +16
    -0
      plugins/carddav/carddav.go
  2. +4
    -0
      plugins/carddav/plugin.go
  3. +1
    -1
      plugins/carddav/public/address-book.html
  4. +1
    -1
      plugins/carddav/public/address-object.html
  5. +3
    -1
      plugins/carddav/public/update-address-object.html
  6. +37
    -43
      plugins/carddav/routes.go
  7. +1
    -4
      themes/sourcehut/address-book.html
  8. +1
    -1
      themes/sourcehut/address-object.html

+ 16
- 0
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)
}

+ 4
- 0
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 {


+ 1
- 1
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"}}


+ 1
- 1
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>


+ 3
- 1
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>


+ 37
- 43
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)
}

+ 1
- 4
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>


+ 1
- 1
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>


불러오는 중...
취소
저장