mirror of
https://github.com/Luzifer/worktime.git
synced 2024-12-23 14:31:16 +00:00
406 lines
8.3 KiB
Go
406 lines
8.3 KiB
Go
|
package couch
|
||
|
|
||
|
import (
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
"io"
|
||
|
"encoding/json"
|
||
|
"io/ioutil"
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
type Response struct {
|
||
|
Ok bool
|
||
|
ID string
|
||
|
Rev string
|
||
|
Error string
|
||
|
Reason string
|
||
|
}
|
||
|
|
||
|
type Client struct {
|
||
|
URL *url.URL
|
||
|
}
|
||
|
|
||
|
func NewClient(u *url.URL) *Client {
|
||
|
return &Client{u}
|
||
|
}
|
||
|
|
||
|
func NewClientURL(urlString string) (*Client, error) {
|
||
|
u, err := url.Parse(urlString)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &Client{u}, nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) AllDBs() ([]string, error) {
|
||
|
res := []string{}
|
||
|
_, err := c.execJSON("GET", "/_all_dbs", &res, nil, nil, nil)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return res, nil
|
||
|
}
|
||
|
|
||
|
// TODO
|
||
|
//func (c *Client) AllDesignDocs() {
|
||
|
//}
|
||
|
|
||
|
func (c *Client) CreateDB() (resp *Response, code int, err error) {
|
||
|
req, err := c.NewRequest("PUT", c.UrlString(c.DBPath(), nil), nil, nil)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
httpResp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
code, err = c.HandleResponse(httpResp, &resp)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Client) DeleteDB() (resp *Response, code int, err error) {
|
||
|
req, err := c.NewRequest("DELETE", c.UrlString(c.DBPath(), nil), nil, nil)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
httpResp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
code, err = c.HandleResponse(httpResp, &resp)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Client) Save(doc interface{}) (res *Response, err error) {
|
||
|
id, _, err := ParseIdRev(doc)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// If no id is provided, we assume POST
|
||
|
if id == "" {
|
||
|
_, err = c.execJSON("POST", c.URL.Path, &res, doc, nil, nil)
|
||
|
} else {
|
||
|
_, err = c.execJSON("PUT", c.DocPath(id), &res, doc, nil, nil)
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if res.Error != "" {
|
||
|
return res, fmt.Errorf(fmt.Sprintf("%s: %s", res.Error, res.Reason))
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Client) Get(id string, doc interface{}) error {
|
||
|
_, err := c.execJSON("GET", c.DocPath(id), &doc, nil, nil, nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) Delete(id string, rev string) error {
|
||
|
headers := http.Header{}
|
||
|
headers.Add("If-Match", rev)
|
||
|
res := Response{}
|
||
|
_, err := c.execJSON("DELETE", c.DocPath(id), &res, nil, nil, &headers)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type BulkSaveRequest struct {
|
||
|
Docs []interface{} `json:"docs"`
|
||
|
}
|
||
|
|
||
|
func (c *Client) BulkSave(docs ...interface{}) (resp *[]Response, code int, err error) {
|
||
|
bulkSaveRequest := &BulkSaveRequest{Docs: docs}
|
||
|
reader, err := docReader(bulkSaveRequest)
|
||
|
|
||
|
req, err := c.NewRequest("POST", c.UrlString(c.DBPath() + "/_bulk_docs", nil), reader, nil)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
httpResp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
code, err = c.HandleResponse(httpResp, &resp)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
type MultiDocResponse struct {
|
||
|
TotalRows uint64 `json:"total_rows"`
|
||
|
Offset uint64
|
||
|
Rows []Row
|
||
|
}
|
||
|
|
||
|
type Row struct {
|
||
|
ID *string
|
||
|
Key interface{}
|
||
|
Value interface{}
|
||
|
Doc interface{}
|
||
|
}
|
||
|
|
||
|
type KeysRequest struct {
|
||
|
Keys []string `json:"keys"`
|
||
|
}
|
||
|
|
||
|
func (c *Client) View(design string, name string, options *url.Values, keys *[]string) (multiDocResponse *MultiDocResponse, err error) {
|
||
|
url := c.UrlString(c.DBPath() + "/_design/" + design + "/_view/" + name, options)
|
||
|
|
||
|
method := ""
|
||
|
body := new(bytes.Buffer)
|
||
|
if keys != nil {
|
||
|
reqJson, _ := json.Marshal(KeysRequest{Keys: *keys})
|
||
|
body = bytes.NewBuffer(reqJson)
|
||
|
method = "POST"
|
||
|
} else {
|
||
|
method = "GET"
|
||
|
}
|
||
|
|
||
|
req, err := c.NewRequest(method, url, body, nil)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
httpResp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
respBody, err := ioutil.ReadAll(httpResp.Body)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err = json.Unmarshal(respBody, &multiDocResponse); err != nil {
|
||
|
return
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Client) Copy(src string, dest string, destRev *string) (resp *Response, code int, err error) {
|
||
|
if destRev != nil {
|
||
|
dest += "?rev=" + *destRev
|
||
|
}
|
||
|
|
||
|
req, err := c.NewRequest("COPY", c.UrlString(c.DocPath(src), nil), nil, nil)
|
||
|
req.Header.Add("Destination", dest)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
httpResp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
code, err = c.HandleResponse(httpResp, &resp)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
type ReplicateRequest struct {
|
||
|
Source string `json:"source"`
|
||
|
Target string `json:"target"`
|
||
|
Cancel bool `json:"cancel,omitempty"`
|
||
|
Continuous bool `json:"continuous,omitempty"`
|
||
|
CreateTarget bool `json:"create_target,omitempty"`
|
||
|
DocIDs []string `json:"doc_ids,omitempty"`
|
||
|
Filter string `json:"filter,omitempty"`
|
||
|
Proxy string `json:"proxy,omitempty"`
|
||
|
QueryParams map[string]string `json:"query_params,omitempty"`
|
||
|
}
|
||
|
|
||
|
type ReplicateResponse struct {
|
||
|
Ok bool `json:"ok"`
|
||
|
LocalID bool `json:"_local_id"`
|
||
|
}
|
||
|
|
||
|
func (c *Client) Replicate(repReq *ReplicateRequest) (resp *ReplicateResponse, code int, err error) {
|
||
|
reqReader, err := docReader(repReq)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
req, err := c.NewRequest("POST", c.UrlString("/_replicate", nil), reqReader, nil)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
httpResp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
code, err = c.HandleResponse(httpResp, &resp)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Client) DBPath() string {
|
||
|
return c.URL.Path
|
||
|
}
|
||
|
|
||
|
func (c *Client) DocPath(id string) string {
|
||
|
return c.DBPath() + "/" + id
|
||
|
}
|
||
|
|
||
|
func (c *Client) NewRequest(method, url string, body io.Reader, headers *http.Header) (req *http.Request, err error) {
|
||
|
req, err = http.NewRequest(method, url, body)
|
||
|
if headers != nil {
|
||
|
req.Header = *headers
|
||
|
}
|
||
|
req.Header.Add("Content-Type", "application/json")
|
||
|
req.Header.Add("Accept", "application/json")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Client) UrlString(path string, values *url.Values) string {
|
||
|
u := *c.URL
|
||
|
u.Path = path
|
||
|
if values != nil {
|
||
|
u.RawQuery = values.Encode()
|
||
|
}
|
||
|
return u.String()
|
||
|
}
|
||
|
|
||
|
func (c *Client) HandleResponse(resp *http.Response, result interface{}) (code int, err error) {
|
||
|
body, err := ioutil.ReadAll(resp.Body)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
code = resp.StatusCode
|
||
|
if err = c.HandleResponseError(code, body); err != nil {
|
||
|
return code, err
|
||
|
}
|
||
|
if err = json.Unmarshal(body, result); err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (c *Client) HandleResponseError(code int, resBytes []byte) error {
|
||
|
if code < 200 || code >= 300 {
|
||
|
res := Response{}
|
||
|
if err := json.Unmarshal(resBytes, &res); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return fmt.Errorf(fmt.Sprintf("Code: %d, Error: %s, Reason: %s", code, res.Error, res.Reason))
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) execJSON(method string, path string, result interface{}, doc interface{}, values *url.Values, headers *http.Header) (int, error) {
|
||
|
resBytes, code, err := c.execRead(method, path, doc, values, headers)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
if err = c.HandleResponseError(code, resBytes); err != nil {
|
||
|
return code, err
|
||
|
}
|
||
|
if err = json.Unmarshal(resBytes, result); err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
return code, nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) execRead(method string, path string, doc interface{}, values *url.Values, headers *http.Header) ([]byte, int, error) {
|
||
|
r, code, err := c.exec(method, path, doc, values, headers)
|
||
|
if err != nil {
|
||
|
return nil, 0, err
|
||
|
}
|
||
|
|
||
|
resBytes, err := ioutil.ReadAll(r)
|
||
|
if err != nil {
|
||
|
return nil, 0, err
|
||
|
}
|
||
|
return resBytes, code, nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) exec(method string, path string, doc interface{}, values *url.Values, headers *http.Header) (io.Reader, int, error) {
|
||
|
reqReader, err := docReader(doc)
|
||
|
if err != nil {
|
||
|
return nil, 0, err
|
||
|
}
|
||
|
|
||
|
req, err := c.NewRequest(method, c.UrlString(path, values), reqReader, headers)
|
||
|
if err != nil {
|
||
|
return nil, 0, err
|
||
|
}
|
||
|
resp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return nil, 0, err
|
||
|
}
|
||
|
|
||
|
return resp.Body, resp.StatusCode, nil
|
||
|
}
|
||
|
|
||
|
func docReader(doc interface{}) (io.Reader, error) {
|
||
|
if doc == nil {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
docJson, err := json.Marshal(doc)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
r := bytes.NewBuffer(docJson)
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
|
type IdRev struct {
|
||
|
ID string `json:"_id"`
|
||
|
Rev string `json:"_rev"`
|
||
|
}
|
||
|
|
||
|
func Remarshal(doc interface{}, newDoc interface{}) (err error) {
|
||
|
docJson, err := json.Marshal(doc)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
err = json.Unmarshal(docJson, newDoc)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func ParseIdRev(doc interface{}) (string, string, error) {
|
||
|
docJson, err := json.Marshal(doc)
|
||
|
if err != nil {
|
||
|
return "", "", err
|
||
|
}
|
||
|
|
||
|
idRev := &IdRev{}
|
||
|
if err = json.Unmarshal(docJson, idRev); err != nil {
|
||
|
return "", "", err
|
||
|
}
|
||
|
|
||
|
return idRev.ID, idRev.Rev, nil
|
||
|
}
|
||
|
|